/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #include "nsNameSpaceManager.h" #include "nsGkAtoms.h" #include "nsTreeColumns.h" #include "nsTreeUtils.h" #include "mozilla/ComputedStyle.h" #include "nsContentUtils.h" #include "nsTreeBodyFrame.h" #include "mozilla/dom/Element.h" #include "mozilla/CSSOrderAwareFrameIterator.h" #include "mozilla/dom/TreeColumnBinding.h" #include "mozilla/dom/TreeColumnsBinding.h" #include "mozilla/dom/XULTreeElement.h" using namespace mozilla; using namespace mozilla::dom; // Column class that caches all the info about our column. nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, dom::Element* aElement) : mContent(aElement), mColumns(aColumns), mIndex(0), mPrevious(nullptr) { NS_ASSERTION(aElement && aElement->NodeInfo()->Equals(nsGkAtoms::treecol, kNameSpaceID_XUL), "nsTreeColumn's content must be a "); Invalidate(IgnoreErrors()); } nsTreeColumn::~nsTreeColumn() { if (mNext) { mNext->SetPrevious(nullptr); } } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(nsTreeColumn) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent) if (tmp->mNext) { tmp->mNext->SetPrevious(nullptr); NS_IMPL_CYCLE_COLLECTION_UNLINK(mNext) } NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn) // QueryInterface implementation for nsTreeColumn NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_ENTRY_CONCRETE(nsTreeColumn) NS_INTERFACE_MAP_END nsIFrame* nsTreeColumn::GetFrame() { return mContent->GetPrimaryFrame(); } bool nsTreeColumn::IsLastVisible(nsTreeBodyFrame* aBodyFrame) { NS_ASSERTION(GetFrame(), "should have checked for this already"); // cyclers are fixed width, don't adjust them if (IsCycler()) return false; // we're certainly not the last visible if we're not visible if (GetFrame()->GetRect().width == 0) return false; // try to find a visible successor for (nsTreeColumn* next = GetNext(); next; next = next->GetNext()) { nsIFrame* frame = next->GetFrame(); if (frame && frame->GetRect().width > 0) return false; } return true; } nsresult nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY, nscoord aHeight, nsRect* aResult) { nsIFrame* frame = GetFrame(); if (!frame) { *aResult = nsRect(); return NS_ERROR_FAILURE; } const bool isRTL = aBodyFrame->StyleVisibility()->mDirection == StyleDirection::Rtl; *aResult = frame->GetRect(); if (frame->StyleVisibility()->IsCollapse()) { aResult->SizeTo(nsSize()); } aResult->y = aY; aResult->height = aHeight; if (isRTL) aResult->x += aBodyFrame->mAdjustWidth; else if (IsLastVisible(aBodyFrame)) aResult->width += aBodyFrame->mAdjustWidth; return NS_OK; } nsresult nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult) { nsIFrame* frame = GetFrame(); if (!frame) { *aResult = 0; return NS_ERROR_FAILURE; } *aResult = frame->GetRect().x; return NS_OK; } nsresult nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame, nscoord* aResult) { nsIFrame* frame = GetFrame(); if (!frame) { *aResult = 0; return NS_ERROR_FAILURE; } *aResult = frame->GetRect().width; if (IsLastVisible(aBodyFrame)) *aResult += aBodyFrame->mAdjustWidth; return NS_OK; } void nsTreeColumn::GetId(nsAString& aId) const { aId = GetId(); } void nsTreeColumn::Invalidate(ErrorResult& aRv) { nsIFrame* frame = GetFrame(); if (NS_WARN_IF(!frame)) { aRv.Throw(NS_ERROR_FAILURE); return; } // Fetch the Id. mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mId); // If we have an Id, cache the Id as an atom. if (!mId.IsEmpty()) { mAtom = NS_Atomize(mId); } // Cache our index. nsTreeUtils::GetColumnIndex(mContent, &mIndex); const nsStyleVisibility* vis = frame->StyleVisibility(); // Cache our text alignment policy. const nsStyleText* textStyle = frame->StyleText(); mTextAlignment = textStyle->mTextAlign; // START or END alignment sometimes means RIGHT if ((mTextAlignment == StyleTextAlign::Start && vis->mDirection == StyleDirection::Rtl) || (mTextAlignment == StyleTextAlign::End && vis->mDirection == StyleDirection::Ltr)) { mTextAlignment = StyleTextAlign::Right; } else if (mTextAlignment == StyleTextAlign::Start || mTextAlignment == StyleTextAlign::End) { mTextAlignment = StyleTextAlign::Left; } // Figure out if we're the primary column (that has to have indentation // and twisties drawn. mIsPrimary = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary, nsGkAtoms::_true, eCaseMatters); // Figure out if we're a cycling column (one that doesn't cause a selection // to happen). mIsCycler = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::cycler, nsGkAtoms::_true, eCaseMatters); mIsEditable = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, nsGkAtoms::_true, eCaseMatters); mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow, nsGkAtoms::_true, eCaseMatters); // Figure out our column type. Default type is text. mType = TreeColumn_Binding::TYPE_TEXT; static Element::AttrValuesArray typestrings[] = {nsGkAtoms::checkbox, nullptr}; switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, typestrings, eCaseMatters)) { case 0: mType = TreeColumn_Binding::TYPE_CHECKBOX; break; } // Fetch the crop style. mCropStyle = 0; static Element::AttrValuesArray cropstrings[] = { nsGkAtoms::center, nsGkAtoms::left, nsGkAtoms::start, nullptr}; switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop, cropstrings, eCaseMatters)) { case 0: mCropStyle = 1; break; case 1: case 2: mCropStyle = 2; break; } } nsIContent* nsTreeColumn::GetParentObject() const { return mContent; } /* virtual */ JSObject* nsTreeColumn::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::TreeColumn_Binding::Wrap(aCx, this, aGivenProto); } Element* nsTreeColumn::Element() { return mContent; } int32_t nsTreeColumn::GetX(mozilla::ErrorResult& aRv) { nsIFrame* frame = GetFrame(); if (NS_WARN_IF(!frame)) { aRv.Throw(NS_ERROR_FAILURE); return 0; } return nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x); } int32_t nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv) { nsIFrame* frame = GetFrame(); if (NS_WARN_IF(!frame)) { aRv.Throw(NS_ERROR_FAILURE); return 0; } return nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width); } already_AddRefed nsTreeColumn::GetPreviousColumn() { return do_AddRef(mPrevious); } nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree) : mTree(aTree) {} nsTreeColumns::~nsTreeColumns() { nsTreeColumns::InvalidateColumns(); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsTreeColumns) // QueryInterface implementation for nsTreeColumns NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumns) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumns) nsIContent* nsTreeColumns::GetParentObject() const { return mTree ? mTree->GetBaseElement() : nullptr; } /* virtual */ JSObject* nsTreeColumns::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return dom::TreeColumns_Binding::Wrap(aCx, this, aGivenProto); } XULTreeElement* nsTreeColumns::GetTree() const { if (!mTree) { return nullptr; } return XULTreeElement::FromNodeOrNull(mTree->GetBaseElement()); } uint32_t nsTreeColumns::Count() { EnsureColumns(); uint32_t count = 0; for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { ++count; } return count; } nsTreeColumn* nsTreeColumns::GetLastColumn() { EnsureColumns(); nsTreeColumn* currCol = mFirstColumn; while (currCol) { nsTreeColumn* next = currCol->GetNext(); if (!next) { return currCol; } currCol = next; } return nullptr; } nsTreeColumn* nsTreeColumns::GetSortedColumn() { EnsureColumns(); for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, nsGkAtoms::sortDirection)) { return currCol; } } return nullptr; } nsTreeColumn* nsTreeColumns::GetKeyColumn() { EnsureColumns(); nsTreeColumn* first = nullptr; nsTreeColumn* primary = nullptr; nsTreeColumn* sorted = nullptr; for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { // Skip hidden columns. if (currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, nsGkAtoms::_true, eCaseMatters)) continue; // Skip non-text column if (currCol->GetType() != TreeColumn_Binding::TYPE_TEXT) continue; if (!first) first = currCol; if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, nsGkAtoms::sortDirection)) { // Use sorted column as the key. sorted = currCol; break; } if (currCol->IsPrimary()) if (!primary) primary = currCol; } if (sorted) return sorted; if (primary) return primary; return first; } nsTreeColumn* nsTreeColumns::GetColumnFor(dom::Element* aElement) { EnsureColumns(); for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { if (currCol->mContent == aElement) { return currCol; } } return nullptr; } nsTreeColumn* nsTreeColumns::NamedGetter(const nsAString& aId, bool& aFound) { EnsureColumns(); for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { if (currCol->GetId().Equals(aId)) { aFound = true; return currCol; } } aFound = false; return nullptr; } nsTreeColumn* nsTreeColumns::GetNamedColumn(const nsAString& aId) { bool dummy; return NamedGetter(aId, dummy); } void nsTreeColumns::GetSupportedNames(nsTArray& aNames) { for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { aNames.AppendElement(currCol->GetId()); } } nsTreeColumn* nsTreeColumns::IndexedGetter(uint32_t aIndex, bool& aFound) { EnsureColumns(); for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { if (currCol->GetIndex() == static_cast(aIndex)) { aFound = true; return currCol; } } aFound = false; return nullptr; } nsTreeColumn* nsTreeColumns::GetColumnAt(uint32_t aIndex) { bool dummy; return IndexedGetter(aIndex, dummy); } void nsTreeColumns::InvalidateColumns() { for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { currCol->SetColumns(nullptr); } mFirstColumn = nullptr; } nsTreeColumn* nsTreeColumns::GetPrimaryColumn() { EnsureColumns(); for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { if (currCol->IsPrimary()) { return currCol; } } return nullptr; } void nsTreeColumns::EnsureColumns() { if (mTree && !mFirstColumn) { nsIContent* treeContent = mTree->GetBaseElement(); if (!treeContent) return; nsIContent* colsContent = nsTreeUtils::GetDescendantChild(treeContent, nsGkAtoms::treecols); if (!colsContent) return; nsIContent* colContent = nsTreeUtils::GetDescendantChild(colsContent, nsGkAtoms::treecol); if (!colContent) return; nsIFrame* colFrame = colContent->GetPrimaryFrame(); if (!colFrame) return; colFrame = colFrame->GetParent(); if (!colFrame || !colFrame->GetContent()) return; nsTreeColumn* currCol = nullptr; // Enumerate the columns in visible order CSSOrderAwareFrameIterator iter( colFrame, FrameChildListID::Principal, CSSOrderAwareFrameIterator::ChildFilter::IncludeAll); for (; !iter.AtEnd(); iter.Next()) { nsIFrame* colFrame = iter.get(); nsIContent* colContent = colFrame->GetContent(); if (!colContent->IsXULElement(nsGkAtoms::treecol)) { continue; } // Create a new column structure. nsTreeColumn* col = new nsTreeColumn(this, colContent->AsElement()); if (currCol) { currCol->SetNext(col); col->SetPrevious(currCol); } else { mFirstColumn = col; } currCol = col; } } }