Bug 379745, add some additional template sort hint options to sort case-insensitive, sort by integers, and skip natural sort order, r=neil,sr=sicking
authorNeil Deakin <neil@mozilla.com>
Wed, 16 Jun 2010 13:09:51 -0400
changeset 43693 3670872bb38ae69f44b7fcb74e7102d0f8b50f5f
parent 43692 469097d30eaca95c88b965c8eeb54fc058f189c2
child 43694 46261630d04692da76250d612d4fe07b8637a3c8
push id13858
push userneil@mozilla.com
push dateWed, 16 Jun 2010 17:10:38 +0000
treeherdermozilla-central@3670872bb38a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, sicking
bugs379745
milestone1.9.3a6pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 379745, add some additional template sort hint options to sort case-insensitive, sort by integers, and skip natural sort order, r=neil,sr=sicking
content/base/src/nsGkAtomList.h
content/xul/templates/public/nsIXULSortService.idl
content/xul/templates/public/nsIXULTemplateQueryProcessor.idl
content/xul/templates/src/nsXULContentBuilder.cpp
content/xul/templates/src/nsXULSortService.cpp
content/xul/templates/src/nsXULSortService.h
content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp
content/xul/templates/src/nsXULTemplateQueryProcessorStorage.cpp
content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
content/xul/templates/src/nsXULTreeBuilder.cpp
content/xul/templates/tests/Makefile.in
content/xul/templates/tests/chrome/Makefile.in
content/xul/templates/tests/chrome/test_tmpl_sortascendinginteger.xul
content/xul/templates/tests/test_sortservice.xul
layout/xul/base/src/tree/src/nsTreeContentView.cpp
toolkit/content/tests/widgets/tree_shared.js
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -840,16 +840,17 @@ GK_ATOM(sizetopopup, "sizetopopup")
 GK_ATOM(slider, "slider")
 GK_ATOM(small, "small")
 GK_ATOM(smooth, "smooth")
 GK_ATOM(snap, "snap")
 GK_ATOM(sort, "sort")
 GK_ATOM(sortActive, "sortActive")
 GK_ATOM(sortDirection, "sortDirection")
 GK_ATOM(sorted, "sorted")
+GK_ATOM(sorthints, "sorthints")
 GK_ATOM(sortLocked, "sortLocked")
 GK_ATOM(sortResource, "sortResource")
 GK_ATOM(sortResource2, "sortResource2")
 GK_ATOM(sortSeparators, "sortSeparators")
 GK_ATOM(sortStaticsLast, "sortStaticsLast")
 #ifdef MOZ_MEDIA
 GK_ATOM(source, "source")
 #endif
--- a/content/xul/templates/public/nsIXULSortService.idl
+++ b/content/xul/templates/public/nsIXULSortService.idl
@@ -40,29 +40,35 @@
 interface nsIDOMNode;
 
 /**
  * A service used to sort the contents of a XUL widget.
  */
 [scriptable, uuid(F29270C8-3BE5-4046-9B57-945A84DFF132)]
 interface nsIXULSortService : nsISupports
 {
+    const unsigned long SORT_COMPARECASE = 0x0001;
+    const unsigned long SORT_INTEGER = 0x0100;
+
     /**
      * Sort the contents of the widget containing <code>aNode</code>
      * using <code>aSortKey</code> as the comparison key, and
      * <code>aSortDirection</code> as the direction.
      *
      * @param aNode A node in the XUL widget whose children are to be sorted.
      * @param aSortKey The value to be used as the comparison key.
-     * @param aSortDirection May be either <b>natural</b> to return
-     * the contents to their natural (unsorted) order,
-     * <b>ascending</b> to sort the contents in ascending order, or
-     * <b>descending</b> to sort the contents in descending order.
+     * @param aSortHints One or more hints as to how to sort:
+     *
+     *   ascending: to sort the contents in ascending order
+     *   descending: to sort the contents in descending order
+     *   comparecase: perform case sensitive comparisons
+     *   integer: treat values as integers, non-integers are compared as strings
+     *   twostate: don't allow the natural (unordered state)
      */
     void sort(in nsIDOMNode aNode,
               in AString aSortKey,
-              in AString aSortDirection);
+              in AString aSortHints);
 };
 
 %{C++
 nsresult
 NS_NewXULSortService(nsIXULSortService **result);
 %}
--- a/content/xul/templates/public/nsIXULTemplateQueryProcessor.idl
+++ b/content/xul/templates/public/nsIXULTemplateQueryProcessor.idl
@@ -102,17 +102,17 @@ interface nsIXULTemplateBuilder;
  * initializeForBuilding, compileQuery and addBinding methods may not be
  * called after generateResults has been called until the builder indicates
  * that the generated output is being removed by calling the done method.
  *
  * Currently, the datasource supplied to the methods will always be an
  * nsIRDFDataSource or a DOM node, and will always be the same one in between
  * calls to initializeForBuilding and done.
  */
-[scriptable, uuid(970f1c36-5d2e-4cbc-a1cf-e3327b50df71)]
+[scriptable, uuid(C257573F-444F-468A-BA27-DE979DC55FE4)]
 interface nsIXULTemplateQueryProcessor : nsISupports
 {
   /**
    * Retrieve the datasource to use for the query processor. The list of
    * datasources in a template is specified using the datasources attribute as
    * a space separated list of URIs. This list is processed by the builder and
    * supplied to the query processor in the aDataSources array as a list of
    * nsIURI objects or nsIDOMNode objects. This method may return an object
@@ -286,21 +286,24 @@ interface nsIXULTemplateQueryProcessor :
    * 0 if both are equivalent, and 1 if the left is greater than the right.
    * The comparison should only consider the values for the specified
    * variable.
    *
    * If the comparison variable is null, the results may be
    * sorted in a natural order, for instance, based on the order the data in
    * stored in the datasource.
    *
+   * The sort hints are the flags in nsIXULSortService.
+   *
    * This method must only be called with results that were created by this
    * query processor.
    *
    * @param aLeft the left result to compare
    * @param aRight the right result to compare
    * @param aVar variable to compare
    *
    * @param returns -1 if less, 0 if equal, or 1 if greater
    */
    PRInt32 compareResults(in nsIXULTemplateResult aLeft,
                           in nsIXULTemplateResult aRight,
-                          in nsIAtom aVar);
+                          in nsIAtom aVar,
+                          in unsigned long aSortHints);
 };
--- a/content/xul/templates/src/nsXULContentBuilder.cpp
+++ b/content/xul/templates/src/nsXULContentBuilder.cpp
@@ -1834,26 +1834,28 @@ nsXULContentBuilder::CompareResultToNode
     }
 
     if (!mQueryProcessor)
         return NS_OK;
 
     if (mSortState.direction == nsSortState_natural) {
         // sort in natural order
         nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
-                                                      nsnull, aSortOrder);
+                                                      nsnull, mSortState.sortHints,
+                                                      aSortOrder);
         NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
         // iterate over each sort key and compare. If the nodes are equal,
         // continue to compare using the next sort key. If not equal, stop.
         PRInt32 length = mSortState.sortKeys.Count();
         for (PRInt32 t = 0; t < length; t++) {
             nsresult rv = mQueryProcessor->CompareResults(aResult, match->mResult,
-                                                          mSortState.sortKeys[t], aSortOrder);
+                                                          mSortState.sortKeys[t],
+                                                          mSortState.sortHints, aSortOrder);
             NS_ENSURE_SUCCESS(rv, rv);
 
             if (*aSortOrder)
                 break;
         }
     }
 
     // flip the sort order if performing a descending sorting
@@ -1867,19 +1869,22 @@ nsresult
 nsXULContentBuilder::InsertSortedNode(nsIContent* aContainer,
                                       nsIContent* aNode,
                                       nsIXULTemplateResult* aResult,
                                       PRBool aNotify)
 {
     nsresult rv;
 
     if (!mSortState.initialized) {
-        nsAutoString sort, sortDirection;
+        nsAutoString sort, sortDirection, sortHints;
         mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
         mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, sortDirection);
+        mRoot->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, sortHints);
+        sortDirection.AppendLiteral(" ");
+        sortDirection += sortHints;
         rv = XULSortServiceImpl::InitializeSortState(mRoot, aContainer,
                                                      sort, sortDirection, &mSortState);
         NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // when doing a natural sort, items will typically be sorted according to
     // the order they appear in the datasource. For RDF, cache whether the
     // reference parent is an RDF Seq. That way, the items can be sorted in the
--- a/content/xul/templates/src/nsXULSortService.cpp
+++ b/content/xul/templates/src/nsXULSortService.cpp
@@ -67,16 +67,18 @@
 #include "nsXULContentUtils.h"
 #include "nsString.h"
 #include "nsQuickSort.h"
 #include "nsWhitespaceTokenizer.h"
 #include "nsXULSortService.h"
 #include "nsIDOMXULElement.h"
 #include "nsIXULTemplateBuilder.h"
 #include "nsTemplateMatch.h"
+#include "nsICollation.h"
+#include "nsUnicharUtils.h"
 
 NS_IMPL_ISUPPORTS1(XULSortServiceImpl, nsIXULSortService)
 
 void
 XULSortServiceImpl::SetSortHints(nsIContent *aNode, nsSortState* aSortState)
 {
   // set sort and sortDirection attributes when is sort is done
   aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sort,
@@ -151,17 +153,17 @@ XULSortServiceImpl::GetItemsToSort(nsICo
   nsCOMPtr<nsIDOMXULElement> element = do_QueryInterface(aContainer);
   if (element) {
     nsCOMPtr<nsIXULTemplateBuilder> builder;
     element->GetBuilder(getter_AddRefs(builder));
 
     if (builder) {
       nsresult rv = builder->GetQueryProcessor(getter_AddRefs(aSortState->processor));
       if (NS_FAILED(rv) || !aSortState->processor)
-  return rv;
+        return rv;
 
       return GetTemplateItemsToSort(aContainer, builder, aSortState, aSortItems);
     }
   }
   
   // if there is no template builder, just get the children. For trees,
   // get the treechildren element as use that as the parent
   nsCOMPtr<nsIContent> treechildren;
@@ -169,30 +171,30 @@ XULSortServiceImpl::GetItemsToSort(nsICo
     nsXULContentUtils::FindChildByTag(aContainer,
                                       kNameSpaceID_XUL,
                                       nsGkAtoms::treechildren,
                                       getter_AddRefs(treechildren));
     if (!treechildren)
       return NS_OK;
   
     aContainer = treechildren;
-    }
+  }
 
   PRUint32 count = aContainer->GetChildCount();
   for (PRUint32 c = 0; c < count; c++) {
     nsIContent *child = aContainer->GetChildAt(c);
 
     contentSortInfo* cinfo = aSortItems.AppendElement();
     if (!cinfo)
       return NS_ERROR_OUT_OF_MEMORY;
 
     cinfo->content = child;
-      }
+  }
 
-        return NS_OK;
+  return NS_OK;
 }
 
 
 nsresult
 XULSortServiceImpl::GetTemplateItemsToSort(nsIContent* aContainer,
                                            nsIXULTemplateBuilder* aBuilder,
                                            nsSortState* aSortState,
                                            nsTArray<contentSortInfo>& aSortItems)
@@ -209,17 +211,17 @@ XULSortServiceImpl::GetTemplateItemsToSo
 
     if (result) {
       contentSortInfo* cinfo = aSortItems.AppendElement();
       if (!cinfo)
         return NS_ERROR_OUT_OF_MEMORY;
 
       cinfo->content = child;
       cinfo->result = result;
-      }
+    }
     else if (aContainer->Tag() != nsGkAtoms::_template) {
       rv = GetTemplateItemsToSort(child, aBuilder, aSortState, aSortItems);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return NS_OK;
 }
@@ -232,39 +234,37 @@ testSortCallback(const void *data1, cons
   contentSortInfo *right = (contentSortInfo *)data2;
   nsSortState* sortState = (nsSortState *)privateData;
       
   PRInt32 sortOrder = 0;
 
   if (sortState->direction == nsSortState_natural && sortState->processor) {
     // sort in natural order
     sortState->processor->CompareResults(left->result, right->result,
-                                         nsnull, &sortOrder);
+                                         nsnull, sortState->sortHints, &sortOrder);
   }
   else {
     PRInt32 length = sortState->sortKeys.Count();
     for (PRInt32 t = 0; t < length; t++) {
       // for templates, use the query processor to do sorting
       if (sortState->processor) {
         sortState->processor->CompareResults(left->result, right->result,
-                                             sortState->sortKeys[t], &sortOrder);
+                                             sortState->sortKeys[t],
+                                             sortState->sortHints, &sortOrder);
         if (sortOrder)
           break;
-  }
+      }
       else {
         // no template, so just compare attributes. Ignore namespaces for now.
         nsAutoString leftstr, rightstr;
         left->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], leftstr);
         right->content->GetAttr(kNameSpaceID_None, sortState->sortKeys[t], rightstr);
 
-        if (!leftstr.Equals(rightstr)) {
-          sortOrder = (leftstr > rightstr) ? 1 : -1;
-          break;
-    }
-  }
+        sortOrder = XULSortServiceImpl::CompareValues(leftstr, rightstr, sortState->sortHints);
+      }
     }
   }
 
   if (sortState->direction == nsSortState_descending)
     sortOrder = -sortOrder;
 
   return sortOrder;
 }
@@ -292,28 +292,28 @@ XULSortServiceImpl::SortContainer(nsICon
         if (type.EqualsLiteral("separator")) {
           if (aSortState->invertSort)
             InvertSortInfo(items, startIndex, i - startIndex);
           else
             NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
                          sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
 
           startIndex = i + 1;
+        }
       }
     }
-  }
-  
+
     if (i > startIndex + 1) {
       if (aSortState->invertSort)
         InvertSortInfo(items, startIndex, i - startIndex);
-          else
+      else
         NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
                      sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
-      }
-    } else {
+    }
+  } else {
     // if the items are just being inverted, that is, just switching between
     // ascending and descending, just reverse the list.
     if (aSortState->invertSort)
       InvertSortInfo(items, 0, numResults);
     else
       NS_QuickSort((void *)items.Elements(), numResults,
                    sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
   }
@@ -325,18 +325,18 @@ XULSortServiceImpl::SortContainer(nsICon
 
     if (parent) {
       // remember the parent so that it can be reinserted back
       // into the same parent. This is necessary as multiple rules
       // may generate results which get placed in different locations.
       items[i].parent = parent;
       PRInt32 index = parent->IndexOf(child);
       parent->RemoveChildAt(index, PR_TRUE);
-      }
     }
+  }
 
   // now add the items back in sorted order
   for (i = 0; i < numResults; i++)
   {
     nsIContent* child = items[i].content;
     nsIContent* parent = items[i].parent;
     if (parent) {
       parent->AppendChildTo(child, PR_TRUE);
@@ -379,17 +379,17 @@ XULSortServiceImpl::InvertSortInfo(nsTAr
   }
   return NS_OK;
 }
 
 nsresult
 XULSortServiceImpl::InitializeSortState(nsIContent* aRootElement,
                                         nsIContent* aContainer,
                                         const nsAString& aSortKey,
-                                        const nsAString& aSortDirection,
+                                        const nsAString& aSortHints,
                                         nsSortState* aSortState)
 {
   // used as an optimization for the content builder
   if (aContainer != aSortState->lastContainer.get()) {
     aSortState->lastContainer = aContainer;
     aSortState->lastWasFirst = PR_FALSE;
     aSortState->lastWasLast = PR_FALSE;
   }
@@ -423,72 +423,120 @@ XULSortServiceImpl::InitializeSortState(
     while (tokenizer.hasMoreTokens()) {
       nsCOMPtr<nsIAtom> keyatom = do_GetAtom(tokenizer.nextToken());
       NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY);
       aSortState->sortKeys.AppendObject(keyatom);
     }
   }
 
   aSortState->sort.Assign(sort);
+  aSortState->direction = nsSortState_natural;
+
+  PRBool noNaturalState = PR_FALSE;
+  nsWhitespaceTokenizer tokenizer(aSortHints);
+  while (tokenizer.hasMoreTokens()) {
+    const nsDependentSubstring& token(tokenizer.nextToken());
+    if (token.EqualsLiteral("comparecase"))
+      aSortState->sortHints |= nsIXULSortService::SORT_COMPARECASE;
+    else if (token.EqualsLiteral("integer"))
+      aSortState->sortHints |= nsIXULSortService::SORT_INTEGER;
+    else if (token.EqualsLiteral("descending"))
+      aSortState->direction = nsSortState_descending;
+    else if (token.EqualsLiteral("ascending"))
+      aSortState->direction = nsSortState_ascending;
+    else if (token.EqualsLiteral("twostate"))
+      noNaturalState = PR_TRUE;
+  }
+
+  // if the twostate flag was set, the natural order is skipped and only
+  // ascending and descending are allowed
+  if (aSortState->direction == nsSortState_natural && noNaturalState) {
+    aSortState->direction = nsSortState_ascending;
+  }
 
   // set up sort order info
-  if (aSortDirection.EqualsLiteral("descending"))
-    aSortState->direction = nsSortState_descending;
-  else if (aSortDirection.EqualsLiteral("ascending"))
-    aSortState->direction = nsSortState_ascending;
-        else
-    aSortState->direction = nsSortState_natural;
+  aSortState->invertSort = PR_FALSE;
 
-  aSortState->invertSort = PR_FALSE;
-          
   nsAutoString existingsort;
   aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, existingsort);
   nsAutoString existingsortDirection;
   aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, existingsortDirection);
           
   // if just switching direction, set the invertSort flag
   if (sort.Equals(existingsort)) {
     if (aSortState->direction == nsSortState_descending) {
       if (existingsortDirection.EqualsLiteral("ascending"))
         aSortState->invertSort = PR_TRUE;
-      }
+    }
     else if (aSortState->direction == nsSortState_ascending &&
-              existingsortDirection.EqualsLiteral("descending")) {
+             existingsortDirection.EqualsLiteral("descending")) {
       aSortState->invertSort = PR_TRUE;
     }
   }
 
-  // sort items between separatore independently
+  // sort items between separators independently
   aSortState->inbetweenSeparatorSort =
     aRootElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::sortSeparators,
                               nsGkAtoms::_true, eCaseMatters);
 
   // sort static content (non template generated nodes) after generated content
   aSortState->sortStaticsLast = aRootElement->AttrValueIs(kNameSpaceID_None,
                                   nsGkAtoms::sortStaticsLast,
                                   nsGkAtoms::_true, eCaseMatters);
 
   aSortState->initialized = PR_TRUE;
 
   return NS_OK;
 }
 
+PRInt32
+XULSortServiceImpl::CompareValues(const nsAString& aLeft,
+                                  const nsAString& aRight,
+                                  PRUint32 aSortHints)
+{
+  if (aSortHints & SORT_INTEGER) {
+    PRInt32 err;
+    PRInt32 leftint = nsDependentString(aLeft).ToInteger(&err);
+    if (NS_SUCCEEDED(err)) {
+      PRInt32 rightint = nsDependentString(aRight).ToInteger(&err);
+      if (NS_SUCCEEDED(err)) {
+        return leftint - rightint;
+      }
+    }
+    // if they aren't integers, just fall through and compare strings
+  }
+
+  if (aSortHints & SORT_COMPARECASE) {
+    return ::Compare(aLeft, aRight);
+  }
+
+  nsICollation* collation = nsXULContentUtils::GetCollation();
+  if (collation) {
+    PRInt32 result;
+    collation->CompareString(nsICollation::kCollationCaseInSensitive,
+                             aLeft, aRight, &result);
+    return result;
+  }
+
+  return ::Compare(aLeft, aRight, nsCaseInsensitiveStringComparator());
+}
+
 NS_IMETHODIMP
 XULSortServiceImpl::Sort(nsIDOMNode* aNode,
                          const nsAString& aSortKey,
-                         const nsAString& aSortDirection)
+                         const nsAString& aSortHints)
 {
   // get root content node
   nsCOMPtr<nsIContent> sortNode = do_QueryInterface(aNode);
   if (!sortNode)
     return NS_ERROR_FAILURE;
 
   nsSortState sortState;
   nsresult rv = InitializeSortState(sortNode, sortNode,
-                                    aSortKey, aSortDirection, &sortState);
+                                    aSortKey, aSortHints, &sortState);
   NS_ENSURE_SUCCESS(rv, rv);
   
   // store sort info in attributes on content
   SetSortHints(sortNode, &sortState);
   rv = SortContainer(sortNode, &sortState);
   
   sortState.processor = nsnull; // don't hang on to this reference
   return rv;
--- a/content/xul/templates/src/nsXULSortService.h
+++ b/content/xul/templates/src/nsXULSortService.h
@@ -75,26 +75,29 @@ enum nsSortState_direction {
 struct nsSortState
 {
   PRBool initialized;
   PRBool invertSort;
   PRBool inbetweenSeparatorSort;
   PRBool sortStaticsLast;
   PRBool isContainerRDFSeq;
 
+  PRUint32 sortHints;
+
   nsSortState_direction direction;
   nsAutoString sort;
   nsCOMArray<nsIAtom> sortKeys;
 
   nsCOMPtr<nsIXULTemplateQueryProcessor> processor;
   nsCOMPtr<nsIContent> lastContainer;
   PRBool lastWasFirst, lastWasLast;
 
   nsSortState()
-    : initialized(PR_FALSE)
+    : initialized(PR_FALSE),
+      sortHints(0)
   {
   }
   void Traverse(nsCycleCollectionTraversalCallback &cb) const
   {
     cb.NoteXPCOMChild(processor);
     cb.NoteXPCOMChild(lastContainer);
   }
 };
@@ -198,9 +201,17 @@ public:
    * @param aSortState structure filled in with sort data
    */
   static nsresult
   InitializeSortState(nsIContent* aRootElement,
                       nsIContent* aContainer,
                       const nsAString& aSortKey,
                       const nsAString& aSortDirection,
                       nsSortState* aSortState);
+
+  /**
+   * Compares aLeft and aRight and returns < 0, 0, or > 0. The sort
+   * hints are checked for case matching and integer sorting.
+   */
+  static PRInt32 CompareValues(const nsAString& aLeft,
+                               const nsAString& aRight,
+                               PRUint32 aSortHints);
 };
--- a/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp
+++ b/content/xul/templates/src/nsXULTemplateQueryProcessorRDF.cpp
@@ -50,32 +50,32 @@
 #include "nsIRDFInferDataSource.h"
 #include "nsIRDFService.h"
 #include "nsRDFCID.h"
 #include "nsIServiceManager.h"
 #include "nsINameSpaceManager.h"
 #include "nsGkAtoms.h"
 #include "nsIDocument.h"
 #include "nsIXULDocument.h"
-#include "nsUnicharUtils.h"
 #include "nsAttrName.h"
 #include "rdf.h"
 #include "nsArrayUtils.h"
 
 #include "nsContentTestNode.h"
 #include "nsRDFConInstanceTestNode.h"
 #include "nsRDFConMemberTestNode.h"
 #include "nsRDFPropertyTestNode.h"
 #include "nsInstantiationNode.h"
 #include "nsRDFTestNode.h"
 #include "nsXULContentUtils.h"
 #include "nsXULTemplateBuilder.h"
 #include "nsXULTemplateResultRDF.h"
 #include "nsXULTemplateResultSetRDF.h"
 #include "nsXULTemplateQueryProcessorRDF.h"
+#include "nsXULSortService.h"
 
 //----------------------------------------------------------------------
 
 static NS_DEFINE_CID(kRDFContainerUtilsCID,      NS_RDFCONTAINERUTILS_CID);
 static NS_DEFINE_CID(kRDFServiceCID,             NS_RDFSERVICE_CID);
 
 #define PARSE_TYPE_INTEGER  "Integer"
 
@@ -649,16 +649,17 @@ nsXULTemplateQueryProcessorRDF::Translat
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULTemplateQueryProcessorRDF::CompareResults(nsIXULTemplateResult* aLeft,
                                                nsIXULTemplateResult* aRight,
                                                nsIAtom* aVar,
+                                               PRUint32 aSortHints,
                                                PRInt32* aResult)
 {
     NS_ENSURE_ARG_POINTER(aLeft);
     NS_ENSURE_ARG_POINTER(aRight);
 
     *aResult = 0;
 
     // for natural order sorting, use the index in the RDF container for the
@@ -699,41 +700,35 @@ nsXULTemplateQueryProcessorRDF::CompareR
         NS_ENSURE_SUCCESS(rv, rv);
 
         rv = GetSortValue(aRight, predicate, sortPredicate, getter_AddRefs(rightNode));
         NS_ENSURE_SUCCESS(rv, rv);
     }
     else {
         // get the values for the sort key from the results
         aLeft->GetBindingObjectFor(aVar, getter_AddRefs(leftNode));
-    aRight->GetBindingObjectFor(aVar, getter_AddRefs(rightNode));
+        aRight->GetBindingObjectFor(aVar, getter_AddRefs(rightNode));
     }
 
     {
         // Literals?
         nsCOMPtr<nsIRDFLiteral> l = do_QueryInterface(leftNode);
         if (l) {
             nsCOMPtr<nsIRDFLiteral> r = do_QueryInterface(rightNode);
             if (r) {
                 const PRUnichar *lstr, *rstr;
                 l->GetValueConst(&lstr);
                 r->GetValueConst(&rstr);
 
-                nsICollation* collation = nsXULContentUtils::GetCollation();
-                if (collation) {
-                    collation->CompareString(nsICollation::kCollationCaseInSensitive,
-                                             nsDependentString(lstr),
-                                             nsDependentString(rstr),
-                                             aResult);
-                }
-                else
-                    *aResult = ::Compare(nsDependentString(lstr),
-                                         nsDependentString(rstr),
-                                         nsCaseInsensitiveStringComparator());
+                *aResult = XULSortServiceImpl::CompareValues(
+                               nsDependentString(lstr),
+                               nsDependentString(rstr), aSortHints);
             }
+
+            return NS_OK;
         }
     }
 
     {
         // Dates?
         nsCOMPtr<nsIRDFDate> l = do_QueryInterface(leftNode);
         if (l) {
             nsCOMPtr<nsIRDFDate> r = do_QueryInterface(rightNode);
@@ -747,31 +742,35 @@ nsXULTemplateQueryProcessorRDF::CompareR
 
                 if (LL_IS_ZERO(delta))
                     *aResult = 0;
                 else if (LL_GE_ZERO(delta))
                     *aResult = 1;
                 else
                     *aResult = -1;
             }
+
+            return NS_OK;
         }
     }
 
     {
         // Integers?
         nsCOMPtr<nsIRDFInt> l = do_QueryInterface(leftNode);
         if (l) {
             nsCOMPtr<nsIRDFInt> r = do_QueryInterface(rightNode);
             if (r) {
                 PRInt32 lval, rval;
                 l->GetValue(&lval);
                 r->GetValue(&rval);
 
                 *aResult = lval - rval;
             }
+
+            return NS_OK;
         }
     }
 
     nsICollation* collation = nsXULContentUtils::GetCollation();
     if (collation) {
         // Blobs? (We can only compare these reasonably if we have a
         // collation object.)
         nsCOMPtr<nsIRDFBlob> l = do_QueryInterface(leftNode);
--- a/content/xul/templates/src/nsXULTemplateQueryProcessorStorage.cpp
+++ b/content/xul/templates/src/nsXULTemplateQueryProcessorStorage.cpp
@@ -49,16 +49,17 @@
 #include "nsIFileChannel.h"
 #include "nsIFile.h"
 #include "nsGkAtoms.h"
 #include "nsContentUtils.h"
 
 #include "nsXULTemplateBuilder.h"
 #include "nsXULTemplateResultStorage.h"
 #include "nsXULContentUtils.h"
+#include "nsXULSortService.h"
 
 #include "mozIStorageService.h"
 
 //----------------------------------------------------------------------
 //
 // nsXULTemplateResultSetStorage
 //
 
@@ -463,16 +464,17 @@ nsXULTemplateQueryProcessorStorage::Tran
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsXULTemplateQueryProcessorStorage::CompareResults(nsIXULTemplateResult* aLeft,
                                                    nsIXULTemplateResult* aRight,
                                                    nsIAtom* aVar,
+                                                   PRUint32 aSortHints,
                                                    PRInt32* aResult)
 {
     *aResult = 0;
     if (!aVar)
       return NS_OK;
 
     // We're going to see if values are integers or float, to perform
     // a suitable comparison
@@ -519,19 +521,17 @@ nsXULTemplateQueryProcessorStorage::Comp
                 }
             }
         }
     }
 
     // Values are not integers or floats, so we just compare them as simple strings
     nsAutoString leftVal;
     if (aLeft)
-      aLeft->GetBindingFor(aVar, leftVal);
+        aLeft->GetBindingFor(aVar, leftVal);
 
     nsAutoString rightVal;
     if (aRight)
-      aRight->GetBindingFor(aVar, rightVal);
+        aRight->GetBindingFor(aVar, rightVal);
 
-    *aResult = Compare(nsDependentString(leftVal),
-                       nsDependentString(rightVal),
-                       nsCaseInsensitiveStringComparator());
+    *aResult = XULSortServiceImpl::CompareValues(leftVal, rightVal, aSortHints);
     return NS_OK;
 }
--- a/content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
+++ b/content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
@@ -56,16 +56,17 @@
 #include "nsContentUtils.h"
 #include "nsArrayUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsXULContentUtils.h"
 
 #include "nsXULTemplateBuilder.h"
 #include "nsXULTemplateQueryProcessorXML.h"
 #include "nsXULTemplateResultXML.h"
+#include "nsXULSortService.h"
 
 NS_IMPL_ISUPPORTS1(nsXMLQuery, nsXMLQuery)
 
 //----------------------------------------------------------------------
 //
 // nsXULTemplateResultSetXML
 //
 
@@ -423,37 +424,32 @@ nsXULTemplateQueryProcessorXML::Translat
     return NS_OK;
 }
 
 
 NS_IMETHODIMP
 nsXULTemplateQueryProcessorXML::CompareResults(nsIXULTemplateResult* aLeft,
                                                nsIXULTemplateResult* aRight,
                                                nsIAtom* aVar,
+                                               PRUint32 aSortHints,
                                                PRInt32* aResult)
 {
     *aResult = 0;
     if (!aVar)
-      return NS_OK;
-
-    // XXXndeakin - bug 379745
-    // it would be good for this to handle other types such as integers,
-    // so that sorting can be optimized for different types.
+        return NS_OK;
 
     nsAutoString leftVal;
     if (aLeft)
-      aLeft->GetBindingFor(aVar, leftVal);
+        aLeft->GetBindingFor(aVar, leftVal);
 
     nsAutoString rightVal;
     if (aRight)
-      aRight->GetBindingFor(aVar, rightVal);
+        aRight->GetBindingFor(aVar, rightVal);
 
-    // currently templates always sort case-insensitive
-    *aResult = ::Compare(leftVal, rightVal,
-                         nsCaseInsensitiveStringComparator());
+    *aResult = XULSortServiceImpl::CompareValues(leftVal, rightVal, aSortHints);
     return NS_OK;
 }
 
 nsXMLBindingSet*
 nsXULTemplateQueryProcessorXML::GetOptionalBindingsForRule(nsIDOMNode* aRuleNode)
 {
     return mRuleToBindingsMap.GetWeak(aRuleNode);
 }
--- a/content/xul/templates/src/nsXULTreeBuilder.cpp
+++ b/content/xul/templates/src/nsXULTreeBuilder.cpp
@@ -54,20 +54,22 @@
 #include "nsReadableUtils.h"
 #include "nsQuickSort.h"
 #include "nsTreeRows.h"
 #include "nsTemplateRule.h"
 #include "nsTemplateMatch.h"
 #include "nsGkAtoms.h"
 #include "nsXULContentUtils.h"
 #include "nsXULTemplateBuilder.h"
+#include "nsIXULSortService.h"
 #include "nsTArray.h"
 #include "nsUnicharUtils.h"
 #include "nsINameSpaceManager.h"
 #include "nsIDOMClassInfo.h"
+#include "nsWhitespaceTokenizer.h"
 
 // For security check
 #include "nsIDocument.h"
 
 /**
  * A XUL template builder that serves as an tree view, allowing
  * (pretty much) arbitrary RDF to be presented in an tree.
  */
@@ -257,16 +259,21 @@ protected:
         eDirection_Ascending  = +1
     };
 
     /**
      * The currently active sort order
      */
     Direction mSortDirection;
 
+    /*
+     * Sort hints (compare case, etc)
+     */
+    PRUint32 mSortHints;
+
     /** 
      * The builder observers.
      */
     nsCOMPtr<nsISupportsArray> mObservers;
 };
 
 //----------------------------------------------------------------------
 
@@ -303,17 +310,17 @@ DOMCI_DATA(XULTreeBuilder, nsXULTreeBuil
 NS_INTERFACE_MAP_BEGIN(nsXULTreeBuilder)
   NS_INTERFACE_MAP_ENTRY(nsIXULTreeBuilder)
   NS_INTERFACE_MAP_ENTRY(nsITreeView)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XULTreeBuilder)
 NS_INTERFACE_MAP_END_INHERITING(nsXULTemplateBuilder)
 
 
 nsXULTreeBuilder::nsXULTreeBuilder()
-    : mSortDirection(eDirection_Natural)
+    : mSortDirection(eDirection_Natural), mSortHints(0)
 {
 }
 
 void
 nsXULTreeBuilder::Uninit(PRBool aIsFinal)
 {
     PRInt32 count = mRows.Count();
     mRows.Clear();
@@ -390,25 +397,40 @@ nsXULTreeBuilder::Sort(nsIDOMElement* aE
     header->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort);
 
     if (sort.IsEmpty())
         return NS_OK;
 
     // Grab the new sort variable
     mSortVariable = do_GetAtom(sort);
 
+    nsAutoString hints;
+    header->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints);
+
+    PRBool hasNaturalState = PR_TRUE;
+    nsWhitespaceTokenizer tokenizer(hints);
+    while (tokenizer.hasMoreTokens()) {
+      const nsDependentSubstring& token(tokenizer.nextToken());
+      if (token.EqualsLiteral("comparecase"))
+        mSortHints |= nsIXULSortService::SORT_COMPARECASE;
+      else if (token.EqualsLiteral("integer"))
+        mSortHints |= nsIXULSortService::SORT_INTEGER;
+      else if (token.EqualsLiteral("twostate"))
+        hasNaturalState = PR_FALSE;
+    }
+
     // Cycle the sort direction
     nsAutoString dir;
     header->GetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection, dir);
 
     if (dir.EqualsLiteral("ascending")) {
         dir.AssignLiteral("descending");
         mSortDirection = eDirection_Descending;
     }
-    else if (dir.EqualsLiteral("descending")) {
+    else if (hasNaturalState && dir.EqualsLiteral("descending")) {
         dir.AssignLiteral("natural");
         mSortDirection = eDirection_Natural;
     }
     else {
         dir.AssignLiteral("ascending");
         mSortDirection = eDirection_Ascending;
     }
 
@@ -1860,17 +1882,17 @@ nsXULTreeBuilder::CompareResults(nsIXULT
                 }
 
                 return lindex - rindex;
             }
         }
     }
 
     PRInt32 sortorder;
-    mQueryProcessor->CompareResults(aLeft, aRight, mSortVariable, &sortorder);
+    mQueryProcessor->CompareResults(aLeft, aRight, mSortVariable, mSortHints, &sortorder);
 
     if (sortorder)
         sortorder = sortorder * mSortDirection;
     return sortorder;
 }
 
 nsresult
 nsXULTreeBuilder::SortSubtree(nsTreeRows::Subtree* aSubtree)
--- a/content/xul/templates/tests/Makefile.in
+++ b/content/xul/templates/tests/Makefile.in
@@ -44,14 +44,15 @@ relativesrcdir = content/xul/templates/t
 include $(DEPTH)/config/autoconf.mk
 
 DIRS = chrome
 
 _TEST_FILES = \
 		test_bug441785.xul \
 		bug441785-1.rdf \
 		bug441785-2.rdf \
+		test_sortservice.xul \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
--- a/content/xul/templates/tests/chrome/Makefile.in
+++ b/content/xul/templates/tests/chrome/Makefile.in
@@ -135,16 +135,17 @@ include $(topsrcdir)/config/rules.mk
 		test_tmpl_simplevariablesubstitutiontextandvariable.xul \
 		test_tmpl_simplevariablesubstitutionvariableandtextconcatenated.xul \
 		test_tmpl_simplevariablesubstitutionvariablesconcatenated.xul \
 		test_tmpl_simplesyntaxfilterwithmultiplerules.xul \
 		test_tmpl_simplesyntaxfilterwithrule.xul \
 		test_tmpl_simplesyntaxfilter.xul \
 		test_tmpl_sortascendingquerysyntax.xul \
 		test_tmpl_sortdescendingquerysyntax.xul \
+		test_tmpl_sortascendinginteger.xul \
 		test_tmpl_sortunknownascendingquerysyntax.xul \
 		test_tmpl_sortresourceascendingquerysyntax.xul \
 		test_tmpl_sortresourcedescendingquerysyntax.xul \
 		test_tmpl_sortresourcesettopredicateascendingquerysyntax.xul \
 		test_tmpl_sortresourcesettopredicatedescendingquerysyntax.xul \
 		test_tmpl_sortresource2settopredicateascendingquerysyntax.xul \
 		test_tmpl_sortresource2settopredicatedescendingquerysyntax.xul \
 		test_tmpl_sorttworesourcessettopredicateascendingquerysyntax.xul \
new file mode 100644
--- /dev/null
+++ b/content/xul/templates/tests/chrome/test_tmpl_sortascendinginteger.xul
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<!--
+  sort ascending - integers
+-->
+
+<window title="XUL Template Tests" width="500" height="600"
+        onload="test_template();"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/MochiKit/packed.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script src="templates_shared.js"/>
+
+<script>
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var testid ="sort ascending - integers";
+var queryType = "rdf";
+var isTreeBuilder = false;
+var needsOpen = false;
+var notWorkingYet = false;
+var notWorkingYetDynamic = false;
+var expectedOutput =
+<output>
+  <label id="http://www.some-fictitious-zoo.com/mammals/ninebandedarmadillo" value="Nine-banded Armadillo"/>
+  <label id="http://www.some-fictitious-zoo.com/mammals/aardvark" value="aardvark"/>
+  <label id="http://www.some-fictitious-zoo.com/mammals/hippopotamus" value="HIPPOPOTAMUS"/>
+  <label id="http://www.some-fictitious-zoo.com/mammals/lion" value="Lion"/>
+  <label step="-1" id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+  <label id="http://www.some-fictitious-zoo.com/mammals/gorilla" value="Gorilla"/>
+  <label step="1" id="http://www.some-fictitious-zoo.com/mammals/llama" value="LLAMA"/>
+  <label id="http://www.some-fictitious-zoo.com/mammals/africanelephant" value="African Elephant"/>
+  <label id="http://www.some-fictitious-zoo.com/mammals/polarbear" value="Polar Bear"/>
+</output>;
+
+var changes = [
+  // step 1
+  function(targetds, root) {
+    var subject = RDF.GetResource(ZOO_NS + 'mammals/llama');
+    var predicate = RDF.GetResource(ZOO_NS + 'rdf#specimensAsString');
+    var oldval = targetds.GetTarget(subject, predicate, true);
+    targetds.Change(subject, predicate, oldval, RDF.GetLiteral('12'), true);
+  }
+];
+]]>
+</script>
+
+<vbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="root" datasources="animals.rdf"
+      ref="http://www.some-fictitious-zoo.com/mammals" sort="?specimens ?name" sortDirection="ascending" sorthints="integer">
+<template id="template">
+<query id="query">
+<content uri="?uri"/>
+<member container="?uri" child="?animal"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#name" object="?name"/>
+<triple subject="?animal" predicate="http://www.some-fictitious-zoo.com/rdf#specimensAsString" object="?specimens"/>
+</query>
+<action id="action">
+<label uri="?animal" value="?name"/>
+</action>
+</template>
+</vbox>
+
+</window>
new file mode 100644
--- /dev/null
+++ b/content/xul/templates/tests/test_sortservice.xul
@@ -0,0 +1,73 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<vbox id="box"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+<script type="application/javascript" src="/MochiKit/packed.js"></script>
+<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>      
+
+<script type="application/x-javascript">
+<![CDATA[
+
+var tests = [
+  [["One", "Two", "Three", "Four"], "", ["Four One Three Two"]],
+  [["One", "Two", "Three", "Four"], "integer", ["Four One Three Two"]],
+  [["One", "Two", "Three", "Four"], "descending", ["Two Three One Four"]],
+  [["One", "Two", "Three", "Four"], "descending integer", ["Two Three One Four"]],
+  [["One", "Two", "Three", "Four"], "integer cat descending", ["Two Three One Four"]],
+  [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "", ["1 12 13 170 2 2 222 240 7 98"]],
+  [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "integer", ["1 2 2 7 12 13 98 170 222 240"]],
+  [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "ascending integer", ["1 2 2 7 12 13 98 170 222 240"]],
+  [["1", "13", "2", "7", "12", "240", "2", "170", "222", "98"], "integer descending", ["240 222 170 98 13 12 7 2 2 1"]],
+  [["Cat", "cat", "Candy", "candy"], "comparecase", ["Candy Cat candy cat"]],
+  [["1", "102", "22", "One", "40", "Two"], "integer", ["1 22 40 102 One Two"]],
+];
+
+SimpleTest.waitForExplicitFinish();
+
+function doTests()
+{
+  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+  var box = document.getElementById("box");
+
+  const sortService = Components.classes["@mozilla.org/xul/xul-sort-service;1"].
+                        getService(Components.interfaces.nsIXULSortService);
+
+  for (let t = 0; t < tests.length; t++) {
+    var test = tests[t];
+
+    for (let e = 0; e < test[0].length; e++) {
+      var label = document.createElement("label");
+      label.setAttribute("value", test[0][e]);
+      box.appendChild(label);
+    }
+
+    sortService.sort(box, "value", test[1]);
+
+    var actual = "";
+    for (let e = 0; e < box.childNodes.length; e++) {
+      if (actual)
+        actual += " ";
+      actual += box.childNodes[e].getAttribute("value");
+    }
+    is(actual, test[2], "sorted step " + (t + 1));
+
+    while(box.hasChildNodes())
+      box.removeChild(box.firstChild);
+    box.removeAttribute("sortDirection");
+  }
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", doTests, false);
+
+]]>
+</script>
+</window>
--- a/layout/xul/base/src/tree/src/nsTreeContentView.cpp
+++ b/layout/xul/base/src/tree/src/nsTreeContentView.cpp
@@ -630,16 +630,21 @@ nsTreeContentView::CycleHeader(nsITreeCo
         switch (column->FindAttrValueIn(kNameSpaceID_None,
                                         nsGkAtoms::sortDirection,
                                         strings, eCaseMatters)) {
           case 0: sortdirection.AssignLiteral("descending"); break;
           case 1: sortdirection.AssignLiteral("natural"); break;
           default: sortdirection.AssignLiteral("ascending"); break;
         }
 
+        nsAutoString hints;
+        column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints);
+        sortdirection.AppendLiteral(" ");
+        sortdirection += hints;
+
         nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot);
         xs->Sort(rootnode, sort, sortdirection);
       }
     }
   }
 
   return NS_OK;
 }
--- a/toolkit/content/tests/widgets/tree_shared.js
+++ b/toolkit/content/tests/widgets/tree_shared.js
@@ -1026,16 +1026,30 @@ function testtag_tree_TreeView_rows_sort
        "double click cycleHeader column sortDirection descending");
     // 1 single clicks should restore natural sorting.
     mouseClickOnColumnHeader(columns, columnIndex, 0, 1);
   }
 
   // Check we have gone back to natural sorting.
   is(columnElement.getAttribute("sortDirection"), "",
      "cycleHeader column sortDirection");
+
+  columnElement.setAttribute("sorthints", "twostate");
+  view.cycleHeader(column);
+  is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate");
+  view.cycleHeader(column);
+  is(tree.getAttribute("sortDirection"), "descending", "cycleHeader sortDirection ascending twostate");
+  view.cycleHeader(column);
+  is(tree.getAttribute("sortDirection"), "ascending", "cycleHeader sortDirection ascending twostate again");
+  columnElement.removeAttribute("sorthints");
+  view.cycleHeader(column);
+  view.cycleHeader(column);
+
+  is(columnElement.getAttribute("sortDirection"), "",
+     "cycleHeader column sortDirection reset");
 }
 
 // checks if the current and selected rows are correct
 // current is the index of the current row
 // selected is an array of the indicies of the selected rows
 // column is the selected column
 // viewidx is the row that should be visible at the top of the tree
 function testtag_tree_TreeSelection_State(tree, testid, current, selected, viewidx, column)