1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nsNameSpaceManager.h"
8 #include "nsGkAtoms.h"
9 #include "nsTreeColumns.h"
10 #include "nsTreeUtils.h"
11 #include "mozilla/ComputedStyle.h"
12 #include "nsContentUtils.h"
13 #include "nsTreeBodyFrame.h"
14 #include "mozilla/dom/Element.h"
15 #include "mozilla/CSSOrderAwareFrameIterator.h"
16 #include "mozilla/dom/TreeColumnBinding.h"
17 #include "mozilla/dom/TreeColumnsBinding.h"
18 #include "mozilla/dom/XULTreeElement.h"
19
20 using namespace mozilla;
21 using namespace mozilla::dom;
22
23 // Column class that caches all the info about our column.
nsTreeColumn(nsTreeColumns * aColumns,dom::Element * aElement)24 nsTreeColumn::nsTreeColumn(nsTreeColumns* aColumns, dom::Element* aElement)
25 : mContent(aElement), mColumns(aColumns), mIndex(0), mPrevious(nullptr) {
26 NS_ASSERTION(aElement && aElement->NodeInfo()->Equals(nsGkAtoms::treecol,
27 kNameSpaceID_XUL),
28 "nsTreeColumn's content must be a <xul:treecol>");
29
30 Invalidate(IgnoreErrors());
31 }
32
~nsTreeColumn()33 nsTreeColumn::~nsTreeColumn() {
34 if (mNext) {
35 mNext->SetPrevious(nullptr);
36 }
37 }
38
39 NS_IMPL_CYCLE_COLLECTION_CLASS(nsTreeColumn)
40
41 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsTreeColumn)
42 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
43 NS_IMPL_CYCLE_COLLECTION_UNLINK(mContent)
44 if (tmp->mNext) {
45 tmp->mNext->SetPrevious(nullptr);
46 NS_IMPL_CYCLE_COLLECTION_UNLINK(mNext)
47 }
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn)49 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTreeColumn)
50 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContent)
51 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNext)
52 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
53 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsTreeColumn)
54
55 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumn)
56 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumn)
57
58 // QueryInterface implementation for nsTreeColumn
59 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumn)
60 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
61 NS_INTERFACE_MAP_ENTRY(nsISupports)
62 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsTreeColumn)
63 NS_INTERFACE_MAP_END
64
65 nsIFrame* nsTreeColumn::GetFrame() { return mContent->GetPrimaryFrame(); }
66
IsLastVisible(nsTreeBodyFrame * aBodyFrame)67 bool nsTreeColumn::IsLastVisible(nsTreeBodyFrame* aBodyFrame) {
68 NS_ASSERTION(GetFrame(), "should have checked for this already");
69
70 // cyclers are fixed width, don't adjust them
71 if (IsCycler()) return false;
72
73 // we're certainly not the last visible if we're not visible
74 if (GetFrame()->GetRect().width == 0) return false;
75
76 // try to find a visible successor
77 for (nsTreeColumn* next = GetNext(); next; next = next->GetNext()) {
78 nsIFrame* frame = next->GetFrame();
79 if (frame && frame->GetRect().width > 0) return false;
80 }
81 return true;
82 }
83
GetRect(nsTreeBodyFrame * aBodyFrame,nscoord aY,nscoord aHeight,nsRect * aResult)84 nsresult nsTreeColumn::GetRect(nsTreeBodyFrame* aBodyFrame, nscoord aY,
85 nscoord aHeight, nsRect* aResult) {
86 nsIFrame* frame = GetFrame();
87 if (!frame) {
88 *aResult = nsRect();
89 return NS_ERROR_FAILURE;
90 }
91
92 bool isRTL = aBodyFrame->StyleVisibility()->mDirection == StyleDirection::Rtl;
93 *aResult = frame->GetRect();
94 aResult->y = aY;
95 aResult->height = aHeight;
96 if (isRTL)
97 aResult->x += aBodyFrame->mAdjustWidth;
98 else if (IsLastVisible(aBodyFrame))
99 aResult->width += aBodyFrame->mAdjustWidth;
100 return NS_OK;
101 }
102
GetXInTwips(nsTreeBodyFrame * aBodyFrame,nscoord * aResult)103 nsresult nsTreeColumn::GetXInTwips(nsTreeBodyFrame* aBodyFrame,
104 nscoord* aResult) {
105 nsIFrame* frame = GetFrame();
106 if (!frame) {
107 *aResult = 0;
108 return NS_ERROR_FAILURE;
109 }
110 *aResult = frame->GetRect().x;
111 return NS_OK;
112 }
113
GetWidthInTwips(nsTreeBodyFrame * aBodyFrame,nscoord * aResult)114 nsresult nsTreeColumn::GetWidthInTwips(nsTreeBodyFrame* aBodyFrame,
115 nscoord* aResult) {
116 nsIFrame* frame = GetFrame();
117 if (!frame) {
118 *aResult = 0;
119 return NS_ERROR_FAILURE;
120 }
121 *aResult = frame->GetRect().width;
122 if (IsLastVisible(aBodyFrame)) *aResult += aBodyFrame->mAdjustWidth;
123 return NS_OK;
124 }
125
GetId(nsAString & aId) const126 void nsTreeColumn::GetId(nsAString& aId) const { aId = GetId(); }
127
Invalidate(ErrorResult & aRv)128 void nsTreeColumn::Invalidate(ErrorResult& aRv) {
129 nsIFrame* frame = GetFrame();
130 if (NS_WARN_IF(!frame)) {
131 aRv.Throw(NS_ERROR_FAILURE);
132 return;
133 }
134
135 // Fetch the Id.
136 mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, mId);
137
138 // If we have an Id, cache the Id as an atom.
139 if (!mId.IsEmpty()) {
140 mAtom = NS_Atomize(mId);
141 }
142
143 // Cache our index.
144 nsTreeUtils::GetColumnIndex(mContent, &mIndex);
145
146 const nsStyleVisibility* vis = frame->StyleVisibility();
147
148 // Cache our text alignment policy.
149 const nsStyleText* textStyle = frame->StyleText();
150
151 mTextAlignment = textStyle->mTextAlign;
152 // START or END alignment sometimes means RIGHT
153 if ((mTextAlignment == StyleTextAlign::Start &&
154 vis->mDirection == StyleDirection::Rtl) ||
155 (mTextAlignment == StyleTextAlign::End &&
156 vis->mDirection == StyleDirection::Ltr)) {
157 mTextAlignment = StyleTextAlign::Right;
158 } else if (mTextAlignment == StyleTextAlign::Start ||
159 mTextAlignment == StyleTextAlign::End) {
160 mTextAlignment = StyleTextAlign::Left;
161 }
162
163 // Figure out if we're the primary column (that has to have indentation
164 // and twisties drawn.
165 mIsPrimary = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
166 nsGkAtoms::_true, eCaseMatters);
167
168 // Figure out if we're a cycling column (one that doesn't cause a selection
169 // to happen).
170 mIsCycler = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::cycler,
171 nsGkAtoms::_true, eCaseMatters);
172
173 mIsEditable = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable,
174 nsGkAtoms::_true, eCaseMatters);
175
176 mOverflow = mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::overflow,
177 nsGkAtoms::_true, eCaseMatters);
178
179 // Figure out our column type. Default type is text.
180 mType = TreeColumn_Binding::TYPE_TEXT;
181 static Element::AttrValuesArray typestrings[] = {nsGkAtoms::checkbox,
182 nullptr};
183 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type,
184 typestrings, eCaseMatters)) {
185 case 0:
186 mType = TreeColumn_Binding::TYPE_CHECKBOX;
187 break;
188 }
189
190 // Fetch the crop style.
191 mCropStyle = 0;
192 static Element::AttrValuesArray cropstrings[] = {
193 nsGkAtoms::center, nsGkAtoms::left, nsGkAtoms::start, nullptr};
194 switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop,
195 cropstrings, eCaseMatters)) {
196 case 0:
197 mCropStyle = 1;
198 break;
199 case 1:
200 case 2:
201 mCropStyle = 2;
202 break;
203 }
204 }
205
GetParentObject() const206 nsIContent* nsTreeColumn::GetParentObject() const { return mContent; }
207
208 /* virtual */
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)209 JSObject* nsTreeColumn::WrapObject(JSContext* aCx,
210 JS::Handle<JSObject*> aGivenProto) {
211 return dom::TreeColumn_Binding::Wrap(aCx, this, aGivenProto);
212 }
213
Element()214 Element* nsTreeColumn::Element() { return mContent; }
215
GetX(mozilla::ErrorResult & aRv)216 int32_t nsTreeColumn::GetX(mozilla::ErrorResult& aRv) {
217 nsIFrame* frame = GetFrame();
218 if (NS_WARN_IF(!frame)) {
219 aRv.Throw(NS_ERROR_FAILURE);
220 return 0;
221 }
222
223 return nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().x);
224 }
225
GetWidth(mozilla::ErrorResult & aRv)226 int32_t nsTreeColumn::GetWidth(mozilla::ErrorResult& aRv) {
227 nsIFrame* frame = GetFrame();
228 if (NS_WARN_IF(!frame)) {
229 aRv.Throw(NS_ERROR_FAILURE);
230 return 0;
231 }
232
233 return nsPresContext::AppUnitsToIntCSSPixels(frame->GetRect().width);
234 }
235
GetPreviousColumn()236 already_AddRefed<nsTreeColumn> nsTreeColumn::GetPreviousColumn() {
237 return do_AddRef(mPrevious);
238 }
239
nsTreeColumns(nsTreeBodyFrame * aTree)240 nsTreeColumns::nsTreeColumns(nsTreeBodyFrame* aTree) : mTree(aTree) {}
241
~nsTreeColumns()242 nsTreeColumns::~nsTreeColumns() { nsTreeColumns::InvalidateColumns(); }
243
244 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(nsTreeColumns)
245
246 // QueryInterface implementation for nsTreeColumns
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns)247 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeColumns)
248 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
249 NS_INTERFACE_MAP_ENTRY(nsISupports)
250 NS_INTERFACE_MAP_END
251
252 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeColumns)
253 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeColumns)
254
255 nsIContent* nsTreeColumns::GetParentObject() const {
256 return mTree ? mTree->GetBaseElement() : nullptr;
257 }
258
259 /* virtual */
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)260 JSObject* nsTreeColumns::WrapObject(JSContext* aCx,
261 JS::Handle<JSObject*> aGivenProto) {
262 return dom::TreeColumns_Binding::Wrap(aCx, this, aGivenProto);
263 }
264
GetTree() const265 XULTreeElement* nsTreeColumns::GetTree() const {
266 if (!mTree) {
267 return nullptr;
268 }
269
270 return XULTreeElement::FromNodeOrNull(mTree->GetBaseElement());
271 }
272
Count()273 uint32_t nsTreeColumns::Count() {
274 EnsureColumns();
275 uint32_t count = 0;
276 for (nsTreeColumn* currCol = mFirstColumn; currCol;
277 currCol = currCol->GetNext()) {
278 ++count;
279 }
280 return count;
281 }
282
GetLastColumn()283 nsTreeColumn* nsTreeColumns::GetLastColumn() {
284 EnsureColumns();
285 nsTreeColumn* currCol = mFirstColumn;
286 while (currCol) {
287 nsTreeColumn* next = currCol->GetNext();
288 if (!next) {
289 return currCol;
290 }
291 currCol = next;
292 }
293 return nullptr;
294 }
295
GetSortedColumn()296 nsTreeColumn* nsTreeColumns::GetSortedColumn() {
297 EnsureColumns();
298 for (nsTreeColumn* currCol = mFirstColumn; currCol;
299 currCol = currCol->GetNext()) {
300 if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None,
301 nsGkAtoms::sortDirection)) {
302 return currCol;
303 }
304 }
305 return nullptr;
306 }
307
GetKeyColumn()308 nsTreeColumn* nsTreeColumns::GetKeyColumn() {
309 EnsureColumns();
310
311 nsTreeColumn* first = nullptr;
312 nsTreeColumn* primary = nullptr;
313 nsTreeColumn* sorted = nullptr;
314
315 for (nsTreeColumn* currCol = mFirstColumn; currCol;
316 currCol = currCol->GetNext()) {
317 // Skip hidden columns.
318 if (currCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
319 nsGkAtoms::_true, eCaseMatters))
320 continue;
321
322 // Skip non-text column
323 if (currCol->GetType() != TreeColumn_Binding::TYPE_TEXT) continue;
324
325 if (!first) first = currCol;
326
327 if (nsContentUtils::HasNonEmptyAttr(currCol->mContent, kNameSpaceID_None,
328 nsGkAtoms::sortDirection)) {
329 // Use sorted column as the key.
330 sorted = currCol;
331 break;
332 }
333
334 if (currCol->IsPrimary())
335 if (!primary) primary = currCol;
336 }
337
338 if (sorted) return sorted;
339 if (primary) return primary;
340 return first;
341 }
342
GetColumnFor(dom::Element * aElement)343 nsTreeColumn* nsTreeColumns::GetColumnFor(dom::Element* aElement) {
344 EnsureColumns();
345 for (nsTreeColumn* currCol = mFirstColumn; currCol;
346 currCol = currCol->GetNext()) {
347 if (currCol->mContent == aElement) {
348 return currCol;
349 }
350 }
351 return nullptr;
352 }
353
NamedGetter(const nsAString & aId,bool & aFound)354 nsTreeColumn* nsTreeColumns::NamedGetter(const nsAString& aId, bool& aFound) {
355 EnsureColumns();
356 for (nsTreeColumn* currCol = mFirstColumn; currCol;
357 currCol = currCol->GetNext()) {
358 if (currCol->GetId().Equals(aId)) {
359 aFound = true;
360 return currCol;
361 }
362 }
363 aFound = false;
364 return nullptr;
365 }
366
GetNamedColumn(const nsAString & aId)367 nsTreeColumn* nsTreeColumns::GetNamedColumn(const nsAString& aId) {
368 bool dummy;
369 return NamedGetter(aId, dummy);
370 }
371
GetSupportedNames(nsTArray<nsString> & aNames)372 void nsTreeColumns::GetSupportedNames(nsTArray<nsString>& aNames) {
373 for (nsTreeColumn* currCol = mFirstColumn; currCol;
374 currCol = currCol->GetNext()) {
375 aNames.AppendElement(currCol->GetId());
376 }
377 }
378
IndexedGetter(uint32_t aIndex,bool & aFound)379 nsTreeColumn* nsTreeColumns::IndexedGetter(uint32_t aIndex, bool& aFound) {
380 EnsureColumns();
381 for (nsTreeColumn* currCol = mFirstColumn; currCol;
382 currCol = currCol->GetNext()) {
383 if (currCol->GetIndex() == static_cast<int32_t>(aIndex)) {
384 aFound = true;
385 return currCol;
386 }
387 }
388 aFound = false;
389 return nullptr;
390 }
391
GetColumnAt(uint32_t aIndex)392 nsTreeColumn* nsTreeColumns::GetColumnAt(uint32_t aIndex) {
393 bool dummy;
394 return IndexedGetter(aIndex, dummy);
395 }
396
InvalidateColumns()397 void nsTreeColumns::InvalidateColumns() {
398 for (nsTreeColumn* currCol = mFirstColumn; currCol;
399 currCol = currCol->GetNext()) {
400 currCol->SetColumns(nullptr);
401 }
402 mFirstColumn = nullptr;
403 }
404
GetPrimaryColumn()405 nsTreeColumn* nsTreeColumns::GetPrimaryColumn() {
406 EnsureColumns();
407 for (nsTreeColumn* currCol = mFirstColumn; currCol;
408 currCol = currCol->GetNext()) {
409 if (currCol->IsPrimary()) {
410 return currCol;
411 }
412 }
413 return nullptr;
414 }
415
EnsureColumns()416 void nsTreeColumns::EnsureColumns() {
417 if (mTree && !mFirstColumn) {
418 nsIContent* treeContent = mTree->GetBaseElement();
419 if (!treeContent) return;
420
421 nsIContent* colsContent =
422 nsTreeUtils::GetDescendantChild(treeContent, nsGkAtoms::treecols);
423 if (!colsContent) return;
424
425 nsIContent* colContent =
426 nsTreeUtils::GetDescendantChild(colsContent, nsGkAtoms::treecol);
427 if (!colContent) return;
428
429 nsIFrame* colFrame = colContent->GetPrimaryFrame();
430 if (!colFrame) return;
431
432 colFrame = colFrame->GetParent();
433 if (!colFrame) return;
434
435 nsTreeColumn* currCol = nullptr;
436
437 // Enumerate the columns in visible order
438 CSSOrderAwareFrameIterator iter(
439 colFrame, mozilla::layout::kPrincipalList,
440 CSSOrderAwareFrameIterator::ChildFilter::IncludeAll,
441 CSSOrderAwareFrameIterator::OrderState::Unknown,
442 CSSOrderAwareFrameIterator::OrderingProperty::BoxOrdinalGroup);
443 for (; !iter.AtEnd(); iter.Next()) {
444 nsIFrame* colFrame = iter.get();
445 nsIContent* colContent = colFrame->GetContent();
446 if (!colContent->IsXULElement(nsGkAtoms::treecol)) {
447 continue;
448 }
449 // Create a new column structure.
450 nsTreeColumn* col = new nsTreeColumn(this, colContent->AsElement());
451
452 if (currCol) {
453 currCol->SetNext(col);
454 col->SetPrevious(currCol);
455 } else {
456 mFirstColumn = col;
457 }
458 currCol = col;
459 }
460 }
461 }
462