summaryrefslogtreecommitdiffstats
path: root/dom/xul/templates/nsXULSortService.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xul/templates/nsXULSortService.cpp')
-rw-r--r--dom/xul/templates/nsXULSortService.cpp507
1 files changed, 507 insertions, 0 deletions
diff --git a/dom/xul/templates/nsXULSortService.cpp b/dom/xul/templates/nsXULSortService.cpp
new file mode 100644
index 000000000..ab3e13461
--- /dev/null
+++ b/dom/xul/templates/nsXULSortService.cpp
@@ -0,0 +1,507 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Original Code has been modified by IBM Corporation.
+ * Modifications made by IBM described herein are
+ * Copyright (c) International Business Machines
+ * Corporation, 2000
+ *
+ * Modifications to Mozilla code or documentation
+ * identified per MPL Section 3.3
+ *
+ * Date Modified by Description of modification
+ * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink
+ * use in OS2
+ */
+
+/*
+ This file provides the implementation for the sort service manager.
+ */
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMNode.h"
+#include "nsIServiceManager.h"
+#include "nsGkAtoms.h"
+#include "nsNameSpaceManager.h"
+#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_ISUPPORTS(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,
+ aSortState->sort, true);
+
+ nsAutoString direction;
+ if (aSortState->direction == nsSortState_descending)
+ direction.AssignLiteral("descending");
+ else if (aSortState->direction == nsSortState_ascending)
+ direction.AssignLiteral("ascending");
+ aNode->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ direction, true);
+
+ // for trees, also set the sort info on the currently sorted column
+ if (aNode->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ if (aSortState->sortKeys.Count() >= 1) {
+ nsAutoString sortkey;
+ aSortState->sortKeys[0]->ToString(sortkey);
+ SetSortColumnHints(aNode, sortkey, direction);
+ }
+ }
+}
+
+void
+XULSortServiceImpl::SetSortColumnHints(nsIContent *content,
+ const nsAString &sortResource,
+ const nsAString &sortDirection)
+{
+ // set sort info on current column. This ensures that the
+ // column header sort indicator is updated properly.
+ for (nsIContent* child = content->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::treecols)) {
+ SetSortColumnHints(child, sortResource, sortDirection);
+ } else if (child->IsXULElement(nsGkAtoms::treecol)) {
+ nsAutoString value;
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, value);
+ // also check the resource attribute for older code
+ if (value.IsEmpty())
+ child->GetAttr(kNameSpaceID_None, nsGkAtoms::resource, value);
+ if (value == sortResource) {
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
+ NS_LITERAL_STRING("true"), true);
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ sortDirection, true);
+ // Note: don't break out of loop; want to set/unset
+ // attribs on ALL sort columns
+ } else if (!value.IsEmpty()) {
+ child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortActive,
+ true);
+ child->UnsetAttr(kNameSpaceID_None, nsGkAtoms::sortDirection,
+ true);
+ }
+ }
+ }
+}
+
+nsresult
+XULSortServiceImpl::GetItemsToSort(nsIContent *aContainer,
+ nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems)
+{
+ // if there is a template attached to the sort node, use the builder to get
+ // the items to be sorted
+ 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 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;
+ if (aContainer->NodeInfo()->Equals(nsGkAtoms::tree, kNameSpaceID_XUL)) {
+ nsXULContentUtils::FindChildByTag(aContainer,
+ kNameSpaceID_XUL,
+ nsGkAtoms::treechildren,
+ getter_AddRefs(treechildren));
+ if (!treechildren)
+ return NS_OK;
+
+ aContainer = treechildren;
+ }
+
+ for (nsIContent* child = aContainer->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+ contentSortInfo* cinfo = aSortItems.AppendElement();
+ if (!cinfo)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ cinfo->content = child;
+ }
+
+ return NS_OK;
+}
+
+
+nsresult
+XULSortServiceImpl::GetTemplateItemsToSort(nsIContent* aContainer,
+ nsIXULTemplateBuilder* aBuilder,
+ nsSortState* aSortState,
+ nsTArray<contentSortInfo>& aSortItems)
+{
+ for (nsIContent* child = aContainer->GetFirstChild();
+ child;
+ child = child->GetNextSibling()) {
+
+ nsCOMPtr<nsIDOMElement> childnode = do_QueryInterface(child);
+
+ nsCOMPtr<nsIXULTemplateResult> result;
+ nsresult rv = aBuilder->GetResultForContent(childnode, getter_AddRefs(result));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (result) {
+ contentSortInfo* cinfo = aSortItems.AppendElement();
+ if (!cinfo)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ cinfo->content = child;
+ cinfo->result = result;
+ }
+ else if (!aContainer->IsXULElement(nsGkAtoms::_template)) {
+ rv = GetTemplateItemsToSort(child, aBuilder, aSortState, aSortItems);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ return NS_OK;
+}
+
+int
+testSortCallback(const void *data1, const void *data2, void *privateData)
+{
+ /// Note: testSortCallback is a small C callback stub for NS_QuickSort
+ contentSortInfo *left = (contentSortInfo *)data1;
+ contentSortInfo *right = (contentSortInfo *)data2;
+ nsSortState* sortState = (nsSortState *)privateData;
+
+ int32_t sortOrder = 0;
+
+ if (sortState->direction == nsSortState_natural && sortState->processor) {
+ // sort in natural order
+ sortState->processor->CompareResults(left->result, right->result,
+ nullptr, sortState->sortHints, &sortOrder);
+ }
+ else {
+ int32_t length = sortState->sortKeys.Count();
+ for (int32_t 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],
+ 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);
+
+ sortOrder = XULSortServiceImpl::CompareValues(leftstr, rightstr, sortState->sortHints);
+ }
+ }
+ }
+
+ if (sortState->direction == nsSortState_descending)
+ sortOrder = -sortOrder;
+
+ return sortOrder;
+}
+
+nsresult
+XULSortServiceImpl::SortContainer(nsIContent *aContainer, nsSortState* aSortState)
+{
+ nsTArray<contentSortInfo> items;
+ nsresult rv = GetItemsToSort(aContainer, aSortState, items);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t numResults = items.Length();
+ if (!numResults)
+ return NS_OK;
+
+ uint32_t i;
+
+ // inbetweenSeparatorSort sorts the items between separators independently
+ if (aSortState->inbetweenSeparatorSort) {
+ uint32_t startIndex = 0;
+ for (i = 0; i < numResults; i++) {
+ if (i > startIndex + 1) {
+ nsAutoString type;
+ items[i].result->GetType(type);
+ 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
+ NS_QuickSort((void *)(items.Elements() + startIndex), i - startIndex,
+ sizeof(contentSortInfo), testSortCallback, (void*)aSortState);
+ }
+ } 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);
+ }
+
+ // first remove the items from the old positions
+ for (i = 0; i < numResults; i++) {
+ nsIContent* child = items[i].content;
+ nsIContent* parent = child->GetParent();
+
+ 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;
+ int32_t index = parent->IndexOf(child);
+ parent->RemoveChildAt(index, 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, true);
+
+ // if it's a container in a tree or menu, find its children,
+ // and sort those also
+ if (!child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container,
+ nsGkAtoms::_true, eCaseMatters))
+ continue;
+
+ for (nsIContent* grandchild = child->GetFirstChild();
+ grandchild;
+ grandchild = grandchild->GetNextSibling()) {
+ mozilla::dom::NodeInfo *ni = grandchild->NodeInfo();
+ nsIAtom *localName = ni->NameAtom();
+ if (ni->NamespaceID() == kNameSpaceID_XUL &&
+ (localName == nsGkAtoms::treechildren ||
+ localName == nsGkAtoms::menupopup)) {
+ SortContainer(grandchild, aSortState);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult
+XULSortServiceImpl::InvertSortInfo(nsTArray<contentSortInfo>& aData,
+ int32_t aStart, int32_t aNumItems)
+{
+ if (aNumItems > 1) {
+ // reverse the items in the array starting from aStart
+ int32_t upPoint = (aNumItems + 1) / 2 + aStart;
+ int32_t downPoint = (aNumItems - 2) / 2 + aStart;
+ int32_t half = aNumItems / 2;
+ while (half-- > 0) {
+ aData[downPoint--].swap(aData[upPoint++]);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult
+XULSortServiceImpl::InitializeSortState(nsIContent* aRootElement,
+ nsIContent* aContainer,
+ const nsAString& aSortKey,
+ const nsAString& aSortHints,
+ nsSortState* aSortState)
+{
+ // used as an optimization for the content builder
+ if (aContainer != aSortState->lastContainer.get()) {
+ aSortState->lastContainer = aContainer;
+ aSortState->lastWasFirst = false;
+ aSortState->lastWasLast = false;
+ }
+
+ // The attributes allowed are either:
+ // sort="key1 key2 ..."
+ // or sortResource="key1" sortResource2="key2"
+ // The latter is for backwards compatibility, and is equivalent to concatenating
+ // both values in the sort attribute
+ nsAutoString sort(aSortKey);
+ aSortState->sortKeys.Clear();
+ if (sort.IsEmpty()) {
+ nsAutoString sortResource, sortResource2;
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource, sortResource);
+ if (!sortResource.IsEmpty()) {
+ nsCOMPtr<nsIAtom> sortkeyatom = NS_Atomize(sortResource);
+ aSortState->sortKeys.AppendObject(sortkeyatom);
+ sort.Append(sortResource);
+
+ aRootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::sortResource2, sortResource2);
+ if (!sortResource2.IsEmpty()) {
+ nsCOMPtr<nsIAtom> sortkeyatom2 = NS_Atomize(sortResource2);
+ aSortState->sortKeys.AppendObject(sortkeyatom2);
+ sort.Append(' ');
+ sort.Append(sortResource2);
+ }
+ }
+ }
+ else {
+ nsWhitespaceTokenizer tokenizer(sort);
+ while (tokenizer.hasMoreTokens()) {
+ nsCOMPtr<nsIAtom> keyatom = NS_Atomize(tokenizer.nextToken());
+ NS_ENSURE_TRUE(keyatom, NS_ERROR_OUT_OF_MEMORY);
+ aSortState->sortKeys.AppendObject(keyatom);
+ }
+ }
+
+ aSortState->sort.Assign(sort);
+ aSortState->direction = nsSortState_natural;
+
+ bool noNaturalState = 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 = 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
+ aSortState->invertSort = 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 = true;
+ }
+ else if (aSortState->direction == nsSortState_ascending &&
+ existingsortDirection.EqualsLiteral("descending")) {
+ aSortState->invertSort = true;
+ }
+ }
+
+ // 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 = true;
+
+ return NS_OK;
+}
+
+int32_t
+XULSortServiceImpl::CompareValues(const nsAString& aLeft,
+ const nsAString& aRight,
+ uint32_t aSortHints)
+{
+ if (aSortHints & SORT_INTEGER) {
+ nsresult err;
+ int32_t leftint = PromiseFlatString(aLeft).ToInteger(&err);
+ if (NS_SUCCEEDED(err)) {
+ int32_t rightint = PromiseFlatString(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) {
+ int32_t 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& 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, aSortHints, &sortState);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // store sort info in attributes on content
+ SetSortHints(sortNode, &sortState);
+ rv = SortContainer(sortNode, &sortState);
+
+ sortState.processor = nullptr; // don't hang on to this reference
+ return rv;
+}
+
+nsresult
+NS_NewXULSortService(nsIXULSortService** sortService)
+{
+ *sortService = new XULSortServiceImpl();
+ NS_ADDREF(*sortService);
+ return NS_OK;
+}