gecko-dev/dom/xul/templates/nsXULSortService.cpp

508 строки
17 KiB
C++

/* -*- 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;
}