Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 <xul:treecol>");
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(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<JSObject*> 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> 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<JSObject*> 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<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);
}
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;
}
}
}