diff options
Diffstat (limited to 'layout/xul/tree/nsTreeColumns.cpp')
-rw-r--r-- | layout/xul/tree/nsTreeColumns.cpp | 765 |
1 files changed, 765 insertions, 0 deletions
diff --git a/layout/xul/tree/nsTreeColumns.cpp b/layout/xul/tree/nsTreeColumns.cpp new file mode 100644 index 000000000..c6ee19342 --- /dev/null +++ b/layout/xul/tree/nsTreeColumns.cpp @@ -0,0 +1,765 @@ +/* -*- 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/. */ + +#include "nsNameSpaceManager.h" +#include "nsGkAtoms.h" +#include "nsIDOMElement.h" +#include "nsIBoxObject.h" +#include "nsTreeColumns.h" +#include "nsTreeUtils.h" +#include "nsStyleContext.h" +#include "nsDOMClassInfoID.h" +#include "nsContentUtils.h" +#include "nsTreeBodyFrame.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TreeBoxObject.h" +#include "mozilla/dom/TreeColumnBinding.h" +#include "mozilla/dom/TreeColumnsBinding.h" + +using namespace mozilla; + +// Column class that caches all the info about our column. +nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, nsIContent* aContent) + : mContent(aContent), + mColumns(aColumns), + mPrevious(nullptr) +{ + NS_ASSERTION(aContent && + aContent->NodeInfo()->Equals(nsGkAtoms::treecol, + kNameSpaceID_XUL), + "nsTreeColumn's content must be a <xul:treecol>"); + + Invalidate(); +} + +nsTreeColumn::~nsTreeColumn() +{ + if (mNext) { + mNext->SetPrevious(nullptr); + } +} + +NS_IMPL_CYCLE_COLLECTION_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_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsTreeColumn) + +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(nsITreeColumn) + NS_INTERFACE_MAP_ENTRY(nsISupports) + if (aIID.Equals(NS_GET_IID(nsTreeColumn))) { + AddRef(); + *aInstancePtr = this; + return NS_OK; + } + else +NS_INTERFACE_MAP_END + +nsIFrame* +nsTreeColumn::GetFrame() +{ + NS_ENSURE_TRUE(mContent, nullptr); + + 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; + } + + bool isRTL = aBodyFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; + *aResult = frame->GetRect(); + 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; +} + + +NS_IMETHODIMP +nsTreeColumn::GetElement(nsIDOMElement** aElement) +{ + if (mContent) { + return CallQueryInterface(mContent, aElement); + } + *aElement = nullptr; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsTreeColumn::GetColumns(nsITreeColumns** aColumns) +{ + NS_IF_ADDREF(*aColumns = mColumns); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetX(int32_t* aX) +{ + nsIFrame* frame = GetFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + *aX = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetWidth(int32_t* aWidth) +{ + nsIFrame* frame = GetFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + *aWidth = nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetId(nsAString& aId) +{ + aId = GetId(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetIdConst(const char16_t** aIdConst) +{ + *aIdConst = mId.get(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetAtom(nsIAtom** aAtom) +{ + NS_IF_ADDREF(*aAtom = GetAtom()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetIndex(int32_t* aIndex) +{ + *aIndex = GetIndex(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetPrimary(bool* aPrimary) +{ + *aPrimary = IsPrimary(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetCycler(bool* aCycler) +{ + *aCycler = IsCycler(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetEditable(bool* aEditable) +{ + *aEditable = IsEditable(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetSelectable(bool* aSelectable) +{ + *aSelectable = IsSelectable(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetType(int16_t* aType) +{ + *aType = GetType(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetNext(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetNext()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::GetPrevious(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetPrevious()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumn::Invalidate() +{ + nsIFrame* frame = GetFrame(); + NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE); + + // 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 == NS_STYLE_TEXT_ALIGN_START && + vis->mDirection == NS_STYLE_DIRECTION_RTL) || + (mTextAlignment == NS_STYLE_TEXT_ALIGN_END && + vis->mDirection == NS_STYLE_DIRECTION_LTR)) { + mTextAlignment = NS_STYLE_TEXT_ALIGN_RIGHT; + } else if (mTextAlignment == NS_STYLE_TEXT_ALIGN_START || + mTextAlignment == NS_STYLE_TEXT_ALIGN_END) { + mTextAlignment = NS_STYLE_TEXT_ALIGN_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); + + mIsSelectable = !mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable, + nsGkAtoms::_false, eCaseMatters); + + mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow, + nsGkAtoms::_true, eCaseMatters); + + // Figure out our column type. Default type is text. + mType = nsITreeColumn::TYPE_TEXT; + static nsIContent::AttrValuesArray typestrings[] = + {&nsGkAtoms::checkbox, &nsGkAtoms::progressmeter, &nsGkAtoms::password, + nullptr}; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, + typestrings, eCaseMatters)) { + case 0: mType = nsITreeColumn::TYPE_CHECKBOX; break; + case 1: mType = nsITreeColumn::TYPE_PROGRESSMETER; break; + case 2: mType = nsITreeColumn::TYPE_PASSWORD; break; + } + + // Fetch the crop style. + mCropStyle = 0; + static nsIContent::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; + } + + return NS_OK; +} + +nsIContent* +nsTreeColumn::GetParentObject() const +{ + return mContent; +} + +/* virtual */ JSObject* +nsTreeColumn::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return dom::TreeColumnBinding::Wrap(aCx, this, aGivenProto); +} + +mozilla::dom::Element* +nsTreeColumn::GetElement(mozilla::ErrorResult& aRv) +{ + nsCOMPtr<nsIDOMElement> element; + aRv = GetElement(getter_AddRefs(element)); + if (aRv.Failed()) { + return nullptr; + } + nsCOMPtr<nsINode> node = do_QueryInterface(element); + return node->AsElement(); +} + +int32_t +nsTreeColumn::GetX(mozilla::ErrorResult& aRv) +{ + int32_t x; + aRv = GetX(&x); + return x; +} + +int32_t +nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv) +{ + int32_t width; + aRv = GetWidth(&width); + return width; +} + +void +nsTreeColumn::Invalidate(mozilla::ErrorResult& aRv) +{ + aRv = Invalidate(); +} + +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(nsITreeColumns) + 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<JSObject*> aGivenProto) +{ + return dom::TreeColumnsBinding::Wrap(aCx, this, aGivenProto); +} + +dom::TreeBoxObject* +nsTreeColumns::GetTree() const +{ + return mTree ? static_cast<mozilla::dom::TreeBoxObject*>(mTree->GetTreeBoxObject()) : nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetTree(nsITreeBoxObject** _retval) +{ + NS_IF_ADDREF(*_retval = GetTree()); + return NS_OK; +} + +uint32_t +nsTreeColumns::Count() +{ + EnsureColumns(); + uint32_t count = 0; + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + ++count; + } + return count; +} + +NS_IMETHODIMP +nsTreeColumns::GetCount(int32_t* _retval) +{ + *_retval = Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::GetLength(int32_t* _retval) +{ + *_retval = Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::GetFirstColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetFirstColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetLastColumn() +{ + EnsureColumns(); + nsTreeColumn* currCol = mFirstColumn; + while (currCol) { + nsTreeColumn* next = currCol->GetNext(); + if (!next) { + return currCol; + } + currCol = next; + } + return nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetLastColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetLastColumn()); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::GetPrimaryColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetPrimaryColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetSortedColumn() +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->mContent && + nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None, + nsGkAtoms::sortDirection)) { + return currCol; + } + } + return nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetSortedColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetSortedColumn()); + return NS_OK; +} + +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 || + currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, + nsGkAtoms::_true, eCaseMatters)) + continue; + + // Skip non-text column + if (currCol->GetType() != nsITreeColumn::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; +} + +NS_IMETHODIMP +nsTreeColumns::GetKeyColumn(nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetKeyColumn()); + return NS_OK; +} + +nsTreeColumn* +nsTreeColumns::GetColumnFor(dom::Element* aElement) +{ + EnsureColumns(); + for (nsTreeColumn* currCol = mFirstColumn; currCol; currCol = currCol->GetNext()) { + if (currCol->mContent == aElement) { + return currCol; + } + } + return nullptr; +} + +NS_IMETHODIMP +nsTreeColumns::GetColumnFor(nsIDOMElement* aElement, nsITreeColumn** _retval) +{ + nsCOMPtr<dom::Element> element = do_QueryInterface(aElement); + NS_ADDREF(*_retval = GetColumnFor(element)); + return NS_OK; +} + +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); +} + +NS_IMETHODIMP +nsTreeColumns::GetNamedColumn(const nsAString& aId, nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetNamedColumn(aId)); + return NS_OK; +} + +void +nsTreeColumns::GetSupportedNames(nsTArray<nsString>& 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<int32_t>(aIndex)) { + aFound = true; + return currCol; + } + } + aFound = false; + return nullptr; +} + +nsTreeColumn* +nsTreeColumns::GetColumnAt(uint32_t aIndex) +{ + bool dummy; + return IndexedGetter(aIndex, dummy); +} + +NS_IMETHODIMP +nsTreeColumns::GetColumnAt(int32_t aIndex, nsITreeColumn** _retval) +{ + NS_IF_ADDREF(*_retval = GetColumnAt(static_cast<uint32_t>(aIndex))); + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::InvalidateColumns() +{ + for (nsTreeColumn* currCol = mFirstColumn; currCol; + currCol = currCol->GetNext()) { + currCol->SetColumns(nullptr); + } + mFirstColumn = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsTreeColumns::RestoreNaturalOrder() +{ + if (!mTree) + return NS_OK; + + nsIContent* content = mTree->GetBaseElement(); + + // Strong ref, since we'll be setting attributes + nsCOMPtr<nsIContent> colsContent = + nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treecols); + if (!colsContent) + return NS_OK; + + for (uint32_t i = 0; i < colsContent->GetChildCount(); ++i) { + nsCOMPtr<nsIContent> child = colsContent->GetChildAt(i); + nsAutoString ordinal; + ordinal.AppendInt(i); + child->SetAttr(kNameSpaceID_None, nsGkAtoms::ordinal, ordinal, true); + } + + nsTreeColumns::InvalidateColumns(); + + if (mTree) { + mTree->Invalidate(); + } + return NS_OK; +} + +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(); + 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) + return; + + colFrame = colFrame->PrincipalChildList().FirstChild(); + if (!colFrame) + return; + + // Now that we have the first visible column, + // we can enumerate the columns in visible order + nsTreeColumn* currCol = nullptr; + while (colFrame) { + nsIContent* colContent = colFrame->GetContent(); + + if (colContent->NodeInfo()->Equals(nsGkAtoms::treecol, + kNameSpaceID_XUL)) { + // Create a new column structure. + nsTreeColumn* col = new nsTreeColumn(this, colContent); + if (!col) + return; + + if (currCol) { + currCol->SetNext(col); + col->SetPrevious(currCol); + } + else { + mFirstColumn = col; + } + currCol = col; + } + + colFrame = colFrame->GetNextSibling(); + } + } +} |