michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsIBoxObject.h" michael@0: #include "nsTreeUtils.h" michael@0: #include "nsTreeContentView.h" michael@0: #include "ChildIterator.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "nsError.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsIXULSortService.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsTreeBodyFrame.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: #define NS_ENSURE_NATIVE_COLUMN(_col) \ michael@0: nsRefPtr col = nsTreeBodyFrame::GetColumnImpl(_col); \ michael@0: if (!col) { \ michael@0: return NS_ERROR_INVALID_ARG; \ michael@0: } michael@0: michael@0: // A content model view implementation for the tree. michael@0: michael@0: #define ROW_FLAG_CONTAINER 0x01 michael@0: #define ROW_FLAG_OPEN 0x02 michael@0: #define ROW_FLAG_EMPTY 0x04 michael@0: #define ROW_FLAG_SEPARATOR 0x08 michael@0: michael@0: class Row michael@0: { michael@0: public: michael@0: Row(nsIContent* aContent, int32_t aParentIndex) michael@0: : mContent(aContent), mParentIndex(aParentIndex), michael@0: mSubtreeSize(0), mFlags(0) { michael@0: } michael@0: michael@0: ~Row() { michael@0: } michael@0: michael@0: void SetContainer(bool aContainer) { michael@0: aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER; michael@0: } michael@0: bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; } michael@0: michael@0: void SetOpen(bool aOpen) { michael@0: aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN; michael@0: } michael@0: bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); } michael@0: michael@0: void SetEmpty(bool aEmpty) { michael@0: aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY; michael@0: } michael@0: bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); } michael@0: michael@0: void SetSeparator(bool aSeparator) { michael@0: aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR; michael@0: } michael@0: bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); } michael@0: michael@0: // Weak reference to a content item. michael@0: nsIContent* mContent; michael@0: michael@0: // The parent index of the item, set to -1 for the top level items. michael@0: int32_t mParentIndex; michael@0: michael@0: // Subtree size for this item. michael@0: int32_t mSubtreeSize; michael@0: michael@0: private: michael@0: // State flags michael@0: int8_t mFlags; michael@0: }; michael@0: michael@0: michael@0: // We don't reference count the reference to the document michael@0: // If the document goes away first, we'll be informed and we michael@0: // can drop our reference. michael@0: // If we go away first, we'll get rid of ourselves from the michael@0: // document's observer list. michael@0: michael@0: nsTreeContentView::nsTreeContentView(void) : michael@0: mBoxObject(nullptr), michael@0: mSelection(nullptr), michael@0: mRoot(nullptr), michael@0: mDocument(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsTreeContentView::~nsTreeContentView(void) michael@0: { michael@0: // Remove ourselves from mDocument's observers. michael@0: if (mDocument) michael@0: mDocument->RemoveObserver(this); michael@0: } michael@0: michael@0: nsresult michael@0: NS_NewTreeContentView(nsITreeView** aResult) michael@0: { michael@0: *aResult = new nsTreeContentView; michael@0: if (! *aResult) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: NS_ADDREF(*aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsTreeContentView, michael@0: mBoxObject, michael@0: mSelection, michael@0: mRoot, michael@0: mBody) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView) michael@0: michael@0: DOMCI_DATA(TreeContentView, nsTreeContentView) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView) michael@0: NS_INTERFACE_MAP_ENTRY(nsITreeView) michael@0: NS_INTERFACE_MAP_ENTRY(nsITreeContentView) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeContentView) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeContentView) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetRowCount(int32_t* aRowCount) michael@0: { michael@0: *aRowCount = mRows.Length(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetSelection(nsITreeSelection** aSelection) michael@0: { michael@0: NS_IF_ADDREF(*aSelection = mSelection); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue) michael@0: { michael@0: // Untrusted content is only allowed to specify known-good views michael@0: if (nsContentUtils::IsCallerChrome()) michael@0: return true; michael@0: nsCOMPtr nativeTreeSel = do_QueryInterface(aValue); michael@0: return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::SetSelection(nsITreeSelection* aSelection) michael@0: { michael@0: NS_ENSURE_TRUE(!aSelection || CanTrustTreeSelection(aSelection), michael@0: NS_ERROR_DOM_SECURITY_ERR); michael@0: michael@0: mSelection = aSelection; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aIndex]; michael@0: nsIContent* realRow; michael@0: if (row->IsSeparator()) michael@0: realRow = row->mContent; michael@0: else michael@0: realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: michael@0: if (realRow) { michael@0: realRow->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol, michael@0: nsAString& aProps) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) { michael@0: cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: nsCOMPtr element; michael@0: aCol->GetElement(getter_AddRefs(element)); michael@0: michael@0: element->GetAttribute(NS_LITERAL_STRING("properties"), aProps); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = mRows[aIndex]->IsContainer(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = mRows[aIndex]->IsOpen(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = mRows[aIndex]->IsEmpty(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsSeparator(int32_t aIndex, bool *_retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = mRows[aIndex]->IsSeparator(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsSorted(bool *_retval) michael@0: { michael@0: *_retval = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation, michael@0: nsIDOMDataTransfer* aDataTransfer, bool *_retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = false; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation, nsIDOMDataTransfer* aDataTransfer) michael@0: { michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) michael@0: { michael@0: NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), michael@0: "bad row index"); michael@0: if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = mRows[aRowIndex]->mParentIndex; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* _retval) michael@0: { michael@0: NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), michael@0: "bad row index"); michael@0: if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // We have a next sibling if the row is not the last in the subtree. michael@0: int32_t parentIndex = mRows[aRowIndex]->mParentIndex; michael@0: if (parentIndex >= 0) { michael@0: // Compute the last index in this subtree. michael@0: int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize; michael@0: Row* row = mRows[lastIndex]; michael@0: while (row->mParentIndex != parentIndex) { michael@0: lastIndex = row->mParentIndex; michael@0: row = mRows[lastIndex]; michael@0: } michael@0: michael@0: *_retval = aRowIndex < lastIndex; michael@0: } michael@0: else { michael@0: *_retval = uint32_t(aRowIndex) < mRows.Length() - 1; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: int32_t level = 0; michael@0: Row* row = mRows[aIndex]; michael@0: while (row->mParentIndex >= 0) { michael@0: level++; michael@0: row = mRows[row->mParentIndex]; michael@0: } michael@0: *_retval = level; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) michael@0: { michael@0: _retval.Truncate(); michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) michael@0: cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, _retval); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = nsITreeView::PROGRESS_NONE; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::normal, &nsGkAtoms::undetermined, nullptr}; michael@0: switch (cell->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::mode, michael@0: strings, eCaseMatters)) { michael@0: case 0: *_retval = nsITreeView::PROGRESS_NORMAL; break; michael@0: case 1: *_retval = nsITreeView::PROGRESS_UNDETERMINED; break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) michael@0: { michael@0: _retval.Truncate(); michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) michael@0: cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, _retval); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) michael@0: { michael@0: _retval.Truncate(); michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: NS_PRECONDITION(aCol, "bad column"); michael@0: michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length()) || !aCol) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: // Check for a "label" attribute - this is valid on an michael@0: // with a single implied column. michael@0: if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval) michael@0: && !_retval.IsEmpty()) michael@0: return NS_OK; michael@0: michael@0: nsIAtom *rowTag = row->mContent->Tag(); michael@0: if (rowTag == nsGkAtoms::treeitem && row->mContent->IsXUL()) { michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) michael@0: cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::SetTree(nsITreeBoxObject* aTree) michael@0: { michael@0: ClearRows(); michael@0: michael@0: mBoxObject = aTree; michael@0: michael@0: MOZ_ASSERT(!mRoot, "mRoot should have been cleared out by ClearRows"); michael@0: michael@0: if (aTree) { michael@0: // Get our root element michael@0: nsCOMPtr boxObject = do_QueryInterface(mBoxObject); michael@0: if (!boxObject) { michael@0: mBoxObject = nullptr; michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: nsCOMPtr element; michael@0: boxObject->GetElement(getter_AddRefs(element)); michael@0: michael@0: mRoot = do_QueryInterface(element); michael@0: NS_ENSURE_STATE(mRoot); michael@0: michael@0: // Add ourselves to document's observers. michael@0: nsIDocument* document = mRoot->GetDocument(); michael@0: if (document) { michael@0: document->AddObserver(this); michael@0: mDocument = document; michael@0: } michael@0: michael@0: nsCOMPtr bodyElement; michael@0: mBoxObject->GetTreeBody(getter_AddRefs(bodyElement)); michael@0: if (bodyElement) { michael@0: mBody = do_QueryInterface(bodyElement); michael@0: int32_t index = 0; michael@0: Serialize(mBody, -1, &index, mRows); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::ToggleOpenState(int32_t aIndex) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: // We don't serialize content right here, since content might be generated michael@0: // lazily. michael@0: Row* row = mRows[aIndex]; michael@0: michael@0: if (row->IsOpen()) michael@0: row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("false"), true); michael@0: else michael@0: row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::CycleHeader(nsITreeColumn* aCol) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: michael@0: if (!mRoot) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr element; michael@0: aCol->GetElement(getter_AddRefs(element)); michael@0: if (element) { michael@0: nsCOMPtr column = do_QueryInterface(element); michael@0: nsAutoString sort; michael@0: column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); michael@0: if (!sort.IsEmpty()) { michael@0: nsCOMPtr xs = do_GetService("@mozilla.org/xul/xul-sort-service;1"); michael@0: if (xs) { michael@0: nsAutoString sortdirection; michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr}; michael@0: switch (column->FindAttrValueIn(kNameSpaceID_None, michael@0: nsGkAtoms::sortDirection, michael@0: strings, eCaseMatters)) { michael@0: case 0: sortdirection.AssignLiteral("descending"); break; michael@0: case 1: sortdirection.AssignLiteral("natural"); break; michael@0: default: sortdirection.AssignLiteral("ascending"); break; michael@0: } michael@0: michael@0: nsAutoString hints; michael@0: column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints); michael@0: sortdirection.AppendLiteral(" "); michael@0: sortdirection += hints; michael@0: michael@0: nsCOMPtr rootnode = do_QueryInterface(mRoot); michael@0: xs->Sort(rootnode, sort, sortdirection); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::SelectionChanged() michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::CycleCell(int32_t aRow, nsITreeColumn* aCol) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) michael@0: { michael@0: *_retval = false; michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = true; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, michael@0: nsGkAtoms::_false, eCaseMatters)) { michael@0: *_retval = false; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: *_retval = true; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable, michael@0: nsGkAtoms::_false, eCaseMatters)) { michael@0: *_retval = false; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) michael@0: cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) michael@0: { michael@0: NS_ENSURE_NATIVE_COLUMN(aCol); michael@0: NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); michael@0: if (aRow < 0 || aRow >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aRow]; michael@0: michael@0: nsIContent* realRow = michael@0: nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); michael@0: if (realRow) { michael@0: nsIContent* cell = GetCell(realRow, aCol); michael@0: if (cell) michael@0: cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::PerformAction(const char16_t* aAction) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::PerformActionOnRow(const char16_t* aAction, int32_t aRow) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::PerformActionOnCell(const char16_t* aAction, int32_t aRow, nsITreeColumn* aCol) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetItemAtIndex(int32_t aIndex, nsIDOMElement** _retval) michael@0: { michael@0: NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); michael@0: if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: Row* row = mRows[aIndex]; michael@0: row->mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)_retval); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeContentView::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) michael@0: { michael@0: nsCOMPtr content = do_QueryInterface(aItem); michael@0: *_retval = FindContent(content); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::AttributeChanged(nsIDocument* aDocument, michael@0: dom::Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: // Lots of codepaths under here that do all sorts of stuff, so be safe. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: // Make sure this notification concerns us. michael@0: // First check the tag to see if it's one that we care about. michael@0: nsIAtom* tag = aElement->Tag(); michael@0: michael@0: if (mBoxObject && (aElement == mRoot || aElement == mBody)) { michael@0: mBoxObject->ClearStyleAndImageCaches(); michael@0: mBoxObject->Invalidate(); michael@0: } michael@0: michael@0: // We don't consider non-XUL nodes. michael@0: nsIContent* parent = nullptr; michael@0: if (!aElement->IsXUL() || michael@0: ((parent = aElement->GetParent()) && !parent->IsXUL())) { michael@0: return; michael@0: } michael@0: if (tag != nsGkAtoms::treecol && michael@0: tag != nsGkAtoms::treeitem && michael@0: tag != nsGkAtoms::treeseparator && michael@0: tag != nsGkAtoms::treerow && michael@0: tag != nsGkAtoms::treecell) { michael@0: return; michael@0: } michael@0: michael@0: // If we have a legal tag, go up to the tree/select and make sure michael@0: // that it's ours. michael@0: michael@0: for (nsIContent* element = aElement; element != mBody; element = element->GetParent()) { michael@0: if (!element) michael@0: return; // this is not for us michael@0: nsIAtom *parentTag = element->Tag(); michael@0: if (element->IsXUL() && parentTag == nsGkAtoms::tree) michael@0: return; // this is not for us michael@0: } michael@0: michael@0: // Handle changes of the hidden attribute. michael@0: if (aAttribute == nsGkAtoms::hidden && michael@0: (tag == nsGkAtoms::treeitem || tag == nsGkAtoms::treeseparator)) { michael@0: bool hidden = aElement->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::hidden, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: michael@0: int32_t index = FindContent(aElement); michael@0: if (hidden && index >= 0) { michael@0: // Hide this row along with its children. michael@0: int32_t count = RemoveRow(index); michael@0: if (mBoxObject) michael@0: mBoxObject->RowCountChanged(index, -count); michael@0: } michael@0: else if (!hidden && index < 0) { michael@0: // Show this row along with its children. michael@0: nsCOMPtr parent = aElement->GetParent(); michael@0: if (parent) { michael@0: InsertRowFor(parent, aElement); michael@0: } michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: if (tag == nsGkAtoms::treecol) { michael@0: if (aAttribute == nsGkAtoms::properties) { michael@0: if (mBoxObject) { michael@0: nsCOMPtr cols; michael@0: mBoxObject->GetColumns(getter_AddRefs(cols)); michael@0: if (cols) { michael@0: nsCOMPtr element = do_QueryInterface(aElement); michael@0: nsCOMPtr col; michael@0: cols->GetColumnFor(element, getter_AddRefs(col)); michael@0: mBoxObject->InvalidateColumn(col); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treeitem) { michael@0: int32_t index = FindContent(aElement); michael@0: if (index >= 0) { michael@0: Row* row = mRows[index]; michael@0: if (aAttribute == nsGkAtoms::container) { michael@0: bool isContainer = michael@0: aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: row->SetContainer(isContainer); michael@0: if (mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: else if (aAttribute == nsGkAtoms::open) { michael@0: bool isOpen = michael@0: aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: bool wasOpen = row->IsOpen(); michael@0: if (! isOpen && wasOpen) michael@0: CloseContainer(index); michael@0: else if (isOpen && ! wasOpen) michael@0: OpenContainer(index); michael@0: } michael@0: else if (aAttribute == nsGkAtoms::empty) { michael@0: bool isEmpty = michael@0: aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, michael@0: nsGkAtoms::_true, eCaseMatters); michael@0: row->SetEmpty(isEmpty); michael@0: if (mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treeseparator) { michael@0: int32_t index = FindContent(aElement); michael@0: if (index >= 0) { michael@0: if (aAttribute == nsGkAtoms::properties && mBoxObject) { michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treerow) { michael@0: if (aAttribute == nsGkAtoms::properties) { michael@0: nsCOMPtr parent = aElement->GetParent(); michael@0: if (parent) { michael@0: int32_t index = FindContent(parent); michael@0: if (index >= 0 && mBoxObject) { michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treecell) { michael@0: if (aAttribute == nsGkAtoms::ref || michael@0: aAttribute == nsGkAtoms::properties || michael@0: aAttribute == nsGkAtoms::mode || michael@0: aAttribute == nsGkAtoms::src || michael@0: aAttribute == nsGkAtoms::value || michael@0: aAttribute == nsGkAtoms::label) { michael@0: nsIContent* parent = aElement->GetParent(); michael@0: if (parent) { michael@0: nsCOMPtr grandParent = parent->GetParent(); michael@0: if (grandParent && grandParent->IsXUL()) { michael@0: int32_t index = FindContent(grandParent); michael@0: if (index >= 0 && mBoxObject) { michael@0: // XXX Should we make an effort to invalidate only cell ? michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::ContentAppended(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t /* unused */) michael@0: { michael@0: for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { michael@0: // Our contentinserted doesn't use the index michael@0: ContentInserted(aDocument, aContainer, cur, 0); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::ContentInserted(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t /* unused */) michael@0: { michael@0: NS_ASSERTION(aChild, "null ptr"); michael@0: michael@0: // Make sure this notification concerns us. michael@0: // First check the tag to see if it's one that we care about. michael@0: nsIAtom *childTag = aChild->Tag(); michael@0: michael@0: // Don't allow non-XUL nodes. michael@0: if (!aChild->IsXUL() || !aContainer->IsXUL()) michael@0: return; michael@0: if (childTag != nsGkAtoms::treeitem && michael@0: childTag != nsGkAtoms::treeseparator && michael@0: childTag != nsGkAtoms::treechildren && michael@0: childTag != nsGkAtoms::treerow && michael@0: childTag != nsGkAtoms::treecell) { michael@0: return; michael@0: } michael@0: michael@0: // If we have a legal tag, go up to the tree/select and make sure michael@0: // that it's ours. michael@0: michael@0: for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { michael@0: if (!element) michael@0: return; // this is not for us michael@0: nsIAtom *parentTag = element->Tag(); michael@0: if (element->IsXUL() && parentTag == nsGkAtoms::tree) michael@0: return; // this is not for us michael@0: } michael@0: michael@0: // Lots of codepaths under here that do all sorts of stuff, so be safe. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (childTag == nsGkAtoms::treechildren) { michael@0: int32_t index = FindContent(aContainer); michael@0: if (index >= 0) { michael@0: Row* row = mRows[index]; michael@0: row->SetEmpty(false); michael@0: if (mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: if (row->IsContainer() && row->IsOpen()) { michael@0: int32_t count = EnsureSubtree(index); michael@0: if (mBoxObject) michael@0: mBoxObject->RowCountChanged(index + 1, count); michael@0: } michael@0: } michael@0: } michael@0: else if (childTag == nsGkAtoms::treeitem || michael@0: childTag == nsGkAtoms::treeseparator) { michael@0: InsertRowFor(aContainer, aChild); michael@0: } michael@0: else if (childTag == nsGkAtoms::treerow) { michael@0: int32_t index = FindContent(aContainer); michael@0: if (index >= 0 && mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: else if (childTag == nsGkAtoms::treecell) { michael@0: nsCOMPtr parent = aContainer->GetParent(); michael@0: if (parent) { michael@0: int32_t index = FindContent(parent); michael@0: if (index >= 0 && mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::ContentRemoved(nsIDocument *aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: NS_ASSERTION(aChild, "null ptr"); michael@0: michael@0: // Make sure this notification concerns us. michael@0: // First check the tag to see if it's one that we care about. michael@0: nsIAtom *tag = aChild->Tag(); michael@0: michael@0: // We don't consider non-XUL nodes. michael@0: if (!aChild->IsXUL() || !aContainer->IsXUL()) michael@0: return; michael@0: if (tag != nsGkAtoms::treeitem && michael@0: tag != nsGkAtoms::treeseparator && michael@0: tag != nsGkAtoms::treechildren && michael@0: tag != nsGkAtoms::treerow && michael@0: tag != nsGkAtoms::treecell) { michael@0: return; michael@0: } michael@0: michael@0: // If we have a legal tag, go up to the tree/select and make sure michael@0: // that it's ours. michael@0: michael@0: for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { michael@0: if (!element) michael@0: return; // this is not for us michael@0: nsIAtom *parentTag = element->Tag(); michael@0: if (element->IsXUL() && parentTag == nsGkAtoms::tree) michael@0: return; // this is not for us michael@0: } michael@0: michael@0: // Lots of codepaths under here that do all sorts of stuff, so be safe. michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: michael@0: if (tag == nsGkAtoms::treechildren) { michael@0: int32_t index = FindContent(aContainer); michael@0: if (index >= 0) { michael@0: Row* row = mRows[index]; michael@0: row->SetEmpty(true); michael@0: int32_t count = RemoveSubtree(index); michael@0: // Invalidate also the row to update twisty. michael@0: if (mBoxObject) { michael@0: mBoxObject->InvalidateRow(index); michael@0: mBoxObject->RowCountChanged(index + 1, -count); michael@0: } michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treeitem || michael@0: tag == nsGkAtoms::treeseparator michael@0: ) { michael@0: int32_t index = FindContent(aChild); michael@0: if (index >= 0) { michael@0: int32_t count = RemoveRow(index); michael@0: if (mBoxObject) michael@0: mBoxObject->RowCountChanged(index, -count); michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treerow) { michael@0: int32_t index = FindContent(aContainer); michael@0: if (index >= 0 && mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: else if (tag == nsGkAtoms::treecell) { michael@0: nsCOMPtr parent = aContainer->GetParent(); michael@0: if (parent) { michael@0: int32_t index = FindContent(parent); michael@0: if (index >= 0 && mBoxObject) michael@0: mBoxObject->InvalidateRow(index); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode) michael@0: { michael@0: // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows? michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: ClearRows(); michael@0: } michael@0: michael@0: michael@0: // Recursively serialize content, starting with aContent. michael@0: void michael@0: nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex, michael@0: int32_t* aIndex, nsTArray >& aRows) michael@0: { michael@0: // Don't allow non-XUL nodes. michael@0: if (!aContent->IsXUL()) michael@0: return; michael@0: michael@0: dom::FlattenedChildIterator iter(aContent); michael@0: for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) { michael@0: nsIAtom *tag = content->Tag(); michael@0: int32_t count = aRows.Length(); michael@0: michael@0: if (content->IsXUL()) { michael@0: if (tag == nsGkAtoms::treeitem) michael@0: SerializeItem(content, aParentIndex, aIndex, aRows); michael@0: else if (tag == nsGkAtoms::treeseparator) michael@0: SerializeSeparator(content, aParentIndex, aIndex, aRows); michael@0: } michael@0: *aIndex += aRows.Length() - count; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::SerializeItem(nsIContent* aContent, int32_t aParentIndex, michael@0: int32_t* aIndex, nsTArray >& aRows) michael@0: { michael@0: if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return; michael@0: michael@0: Row* row = new Row(aContent, aParentIndex); michael@0: aRows.AppendElement(row); michael@0: michael@0: if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: row->SetContainer(true); michael@0: if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: row->SetOpen(true); michael@0: nsIContent* child = michael@0: nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren); michael@0: if (child && child->IsXUL()) { michael@0: // Now, recursively serialize our child. michael@0: int32_t count = aRows.Length(); michael@0: int32_t index = 0; michael@0: Serialize(child, aParentIndex + *aIndex + 1, &index, aRows); michael@0: row->mSubtreeSize += aRows.Length() - count; michael@0: } michael@0: else michael@0: row->SetEmpty(true); michael@0: } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: row->SetEmpty(true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::SerializeSeparator(nsIContent* aContent, michael@0: int32_t aParentIndex, int32_t* aIndex, michael@0: nsTArray >& aRows) michael@0: { michael@0: if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: return; michael@0: michael@0: Row* row = new Row(aContent, aParentIndex); michael@0: row->SetSeparator(true); michael@0: aRows.AppendElement(row); michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer, michael@0: nsIContent* aContent, int32_t* aIndex) michael@0: { michael@0: uint32_t childCount = aContainer->GetChildCount(); michael@0: michael@0: if (!aContainer->IsXUL()) michael@0: return; michael@0: michael@0: for (uint32_t i = 0; i < childCount; i++) { michael@0: nsIContent *content = aContainer->GetChildAt(i); michael@0: michael@0: if (content == aContent) michael@0: break; michael@0: michael@0: nsIAtom *tag = content->Tag(); michael@0: michael@0: if (content->IsXUL()) { michael@0: if (tag == nsGkAtoms::treeitem) { michael@0: if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: (*aIndex)++; michael@0: if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, michael@0: nsGkAtoms::_true, eCaseMatters) && michael@0: content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: nsIContent* child = michael@0: nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren); michael@0: if (child && child->IsXUL()) michael@0: GetIndexInSubtree(child, aContent, aIndex); michael@0: } michael@0: } michael@0: } michael@0: else if (tag == nsGkAtoms::treeseparator) { michael@0: if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: (*aIndex)++; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsTreeContentView::EnsureSubtree(int32_t aIndex) michael@0: { michael@0: Row* row = mRows[aIndex]; michael@0: michael@0: nsIContent* child; michael@0: child = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren); michael@0: if (!child || !child->IsXUL()) { michael@0: return 0; michael@0: } michael@0: michael@0: nsAutoTArray, 8> rows; michael@0: int32_t index = 0; michael@0: Serialize(child, aIndex, &index, rows); michael@0: // We can't use InsertElementsAt since the destination can't steal michael@0: // ownership from its const source argument. michael@0: for (nsTArray::index_type i = 0; i < rows.Length(); i++) { michael@0: nsAutoPtr* newRow = mRows.InsertElementAt(aIndex + i + 1); michael@0: *newRow = rows[i]; michael@0: } michael@0: int32_t count = rows.Length(); michael@0: michael@0: row->mSubtreeSize += count; michael@0: UpdateSubtreeSizes(row->mParentIndex, count); michael@0: michael@0: // Update parent indexes, but skip newly added rows. michael@0: // They already have correct values. michael@0: UpdateParentIndexes(aIndex, count + 1, count); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: int32_t michael@0: nsTreeContentView::RemoveSubtree(int32_t aIndex) michael@0: { michael@0: Row* row = mRows[aIndex]; michael@0: int32_t count = row->mSubtreeSize; michael@0: michael@0: mRows.RemoveElementsAt(aIndex + 1, count); michael@0: michael@0: row->mSubtreeSize -= count; michael@0: UpdateSubtreeSizes(row->mParentIndex, -count); michael@0: michael@0: UpdateParentIndexes(aIndex, 0, -count); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild) michael@0: { michael@0: int32_t grandParentIndex = -1; michael@0: bool insertRow = false; michael@0: michael@0: nsCOMPtr grandParent = aParent->GetParent(); michael@0: nsIAtom* grandParentTag = grandParent->Tag(); michael@0: michael@0: if (grandParent->IsXUL() && grandParentTag == nsGkAtoms::tree) { michael@0: // Allow insertion to the outermost container. michael@0: insertRow = true; michael@0: } michael@0: else { michael@0: // Test insertion to an inner container. michael@0: michael@0: // First try to find this parent in our array of rows, if we find one michael@0: // we can be sure that all other parents are open too. michael@0: grandParentIndex = FindContent(grandParent); michael@0: if (grandParentIndex >= 0) { michael@0: // Got it, now test if it is open. michael@0: if (mRows[grandParentIndex]->IsOpen()) michael@0: insertRow = true; michael@0: } michael@0: } michael@0: michael@0: if (insertRow) { michael@0: int32_t index = 0; michael@0: GetIndexInSubtree(aParent, aChild, &index); michael@0: michael@0: int32_t count = InsertRow(grandParentIndex, index, aChild); michael@0: if (mBoxObject) michael@0: mBoxObject->RowCountChanged(grandParentIndex + index + 1, count); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent) michael@0: { michael@0: nsAutoTArray, 8> rows; michael@0: nsIAtom *tag = aContent->Tag(); michael@0: if (aContent->IsXUL()) { michael@0: if (tag == nsGkAtoms::treeitem) michael@0: SerializeItem(aContent, aParentIndex, &aIndex, rows); michael@0: else if (tag == nsGkAtoms::treeseparator) michael@0: SerializeSeparator(aContent, aParentIndex, &aIndex, rows); michael@0: } michael@0: michael@0: // We can't use InsertElementsAt since the destination can't steal michael@0: // ownership from its const source argument. michael@0: for (nsTArray::index_type i = 0; i < rows.Length(); i++) { michael@0: nsAutoPtr* newRow = mRows.InsertElementAt(aParentIndex + aIndex + i + 1); michael@0: *newRow = rows[i]; michael@0: } michael@0: int32_t count = rows.Length(); michael@0: michael@0: UpdateSubtreeSizes(aParentIndex, count); michael@0: michael@0: // Update parent indexes, but skip added rows. michael@0: // They already have correct values. michael@0: UpdateParentIndexes(aParentIndex + aIndex, count + 1, count); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: int32_t michael@0: nsTreeContentView::RemoveRow(int32_t aIndex) michael@0: { michael@0: Row* row = mRows[aIndex]; michael@0: int32_t count = row->mSubtreeSize + 1; michael@0: int32_t parentIndex = row->mParentIndex; michael@0: michael@0: mRows.RemoveElementsAt(aIndex, count); michael@0: michael@0: UpdateSubtreeSizes(parentIndex, -count); michael@0: michael@0: UpdateParentIndexes(aIndex, 0, -count); michael@0: michael@0: return count; michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::ClearRows() michael@0: { michael@0: mRows.Clear(); michael@0: mRoot = nullptr; michael@0: mBody = nullptr; michael@0: // Remove ourselves from mDocument's observers. michael@0: if (mDocument) { michael@0: mDocument->RemoveObserver(this); michael@0: mDocument = nullptr; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::OpenContainer(int32_t aIndex) michael@0: { michael@0: Row* row = mRows[aIndex]; michael@0: row->SetOpen(true); michael@0: michael@0: int32_t count = EnsureSubtree(aIndex); michael@0: if (mBoxObject) { michael@0: mBoxObject->InvalidateRow(aIndex); michael@0: mBoxObject->RowCountChanged(aIndex + 1, count); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::CloseContainer(int32_t aIndex) michael@0: { michael@0: Row* row = mRows[aIndex]; michael@0: row->SetOpen(false); michael@0: michael@0: int32_t count = RemoveSubtree(aIndex); michael@0: if (mBoxObject) { michael@0: mBoxObject->InvalidateRow(aIndex); michael@0: mBoxObject->RowCountChanged(aIndex + 1, -count); michael@0: } michael@0: } michael@0: michael@0: int32_t michael@0: nsTreeContentView::FindContent(nsIContent* aContent) michael@0: { michael@0: for (uint32_t i = 0; i < mRows.Length(); i++) { michael@0: if (mRows[i]->mContent == aContent) { michael@0: return i; michael@0: } michael@0: } michael@0: michael@0: return -1; michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex, int32_t count) michael@0: { michael@0: while (aParentIndex >= 0) { michael@0: Row* row = mRows[aParentIndex]; michael@0: row->mSubtreeSize += count; michael@0: aParentIndex = row->mParentIndex; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount) michael@0: { michael@0: int32_t count = mRows.Length(); michael@0: for (int32_t i = aIndex + aSkip; i < count; i++) { michael@0: Row* row = mRows[i]; michael@0: if (row->mParentIndex > aIndex) { michael@0: row->mParentIndex += aCount; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsIContent* michael@0: nsTreeContentView::GetCell(nsIContent* aContainer, nsITreeColumn* aCol) michael@0: { michael@0: nsCOMPtr colAtom; michael@0: int32_t colIndex; michael@0: aCol->GetAtom(getter_AddRefs(colAtom)); michael@0: aCol->GetIndex(&colIndex); michael@0: michael@0: // Traverse through cells, try to find the cell by "ref" attribute or by cell michael@0: // index in a row. "ref" attribute has higher priority. michael@0: nsIContent* result = nullptr; michael@0: int32_t j = 0; michael@0: dom::FlattenedChildIterator iter(aContainer); michael@0: for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) { michael@0: if (cell->Tag() == nsGkAtoms::treecell) { michael@0: if (colAtom && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref, michael@0: colAtom, eCaseMatters)) { michael@0: result = cell; michael@0: break; michael@0: } michael@0: else if (j == colIndex) { michael@0: result = cell; michael@0: } michael@0: j++; michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: }