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