|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "nsNameSpaceManager.h" |
|
7 #include "nsGkAtoms.h" |
|
8 #include "nsIBoxObject.h" |
|
9 #include "nsTreeUtils.h" |
|
10 #include "nsTreeContentView.h" |
|
11 #include "ChildIterator.h" |
|
12 #include "nsDOMClassInfoID.h" |
|
13 #include "nsError.h" |
|
14 #include "nsINodeInfo.h" |
|
15 #include "nsIXULSortService.h" |
|
16 #include "nsContentUtils.h" |
|
17 #include "nsTreeBodyFrame.h" |
|
18 #include "mozilla/dom/Element.h" |
|
19 #include "nsServiceManagerUtils.h" |
|
20 |
|
21 using namespace mozilla; |
|
22 |
|
23 #define NS_ENSURE_NATIVE_COLUMN(_col) \ |
|
24 nsRefPtr<nsTreeColumn> col = nsTreeBodyFrame::GetColumnImpl(_col); \ |
|
25 if (!col) { \ |
|
26 return NS_ERROR_INVALID_ARG; \ |
|
27 } |
|
28 |
|
29 // A content model view implementation for the tree. |
|
30 |
|
31 #define ROW_FLAG_CONTAINER 0x01 |
|
32 #define ROW_FLAG_OPEN 0x02 |
|
33 #define ROW_FLAG_EMPTY 0x04 |
|
34 #define ROW_FLAG_SEPARATOR 0x08 |
|
35 |
|
36 class Row |
|
37 { |
|
38 public: |
|
39 Row(nsIContent* aContent, int32_t aParentIndex) |
|
40 : mContent(aContent), mParentIndex(aParentIndex), |
|
41 mSubtreeSize(0), mFlags(0) { |
|
42 } |
|
43 |
|
44 ~Row() { |
|
45 } |
|
46 |
|
47 void SetContainer(bool aContainer) { |
|
48 aContainer ? mFlags |= ROW_FLAG_CONTAINER : mFlags &= ~ROW_FLAG_CONTAINER; |
|
49 } |
|
50 bool IsContainer() { return mFlags & ROW_FLAG_CONTAINER; } |
|
51 |
|
52 void SetOpen(bool aOpen) { |
|
53 aOpen ? mFlags |= ROW_FLAG_OPEN : mFlags &= ~ROW_FLAG_OPEN; |
|
54 } |
|
55 bool IsOpen() { return !!(mFlags & ROW_FLAG_OPEN); } |
|
56 |
|
57 void SetEmpty(bool aEmpty) { |
|
58 aEmpty ? mFlags |= ROW_FLAG_EMPTY : mFlags &= ~ROW_FLAG_EMPTY; |
|
59 } |
|
60 bool IsEmpty() { return !!(mFlags & ROW_FLAG_EMPTY); } |
|
61 |
|
62 void SetSeparator(bool aSeparator) { |
|
63 aSeparator ? mFlags |= ROW_FLAG_SEPARATOR : mFlags &= ~ROW_FLAG_SEPARATOR; |
|
64 } |
|
65 bool IsSeparator() { return !!(mFlags & ROW_FLAG_SEPARATOR); } |
|
66 |
|
67 // Weak reference to a content item. |
|
68 nsIContent* mContent; |
|
69 |
|
70 // The parent index of the item, set to -1 for the top level items. |
|
71 int32_t mParentIndex; |
|
72 |
|
73 // Subtree size for this item. |
|
74 int32_t mSubtreeSize; |
|
75 |
|
76 private: |
|
77 // State flags |
|
78 int8_t mFlags; |
|
79 }; |
|
80 |
|
81 |
|
82 // We don't reference count the reference to the document |
|
83 // If the document goes away first, we'll be informed and we |
|
84 // can drop our reference. |
|
85 // If we go away first, we'll get rid of ourselves from the |
|
86 // document's observer list. |
|
87 |
|
88 nsTreeContentView::nsTreeContentView(void) : |
|
89 mBoxObject(nullptr), |
|
90 mSelection(nullptr), |
|
91 mRoot(nullptr), |
|
92 mDocument(nullptr) |
|
93 { |
|
94 } |
|
95 |
|
96 nsTreeContentView::~nsTreeContentView(void) |
|
97 { |
|
98 // Remove ourselves from mDocument's observers. |
|
99 if (mDocument) |
|
100 mDocument->RemoveObserver(this); |
|
101 } |
|
102 |
|
103 nsresult |
|
104 NS_NewTreeContentView(nsITreeView** aResult) |
|
105 { |
|
106 *aResult = new nsTreeContentView; |
|
107 if (! *aResult) |
|
108 return NS_ERROR_OUT_OF_MEMORY; |
|
109 NS_ADDREF(*aResult); |
|
110 return NS_OK; |
|
111 } |
|
112 |
|
113 NS_IMPL_CYCLE_COLLECTION(nsTreeContentView, |
|
114 mBoxObject, |
|
115 mSelection, |
|
116 mRoot, |
|
117 mBody) |
|
118 |
|
119 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTreeContentView) |
|
120 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTreeContentView) |
|
121 |
|
122 DOMCI_DATA(TreeContentView, nsTreeContentView) |
|
123 |
|
124 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTreeContentView) |
|
125 NS_INTERFACE_MAP_ENTRY(nsITreeView) |
|
126 NS_INTERFACE_MAP_ENTRY(nsITreeContentView) |
|
127 NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) |
|
128 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) |
|
129 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITreeContentView) |
|
130 NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TreeContentView) |
|
131 NS_INTERFACE_MAP_END |
|
132 |
|
133 NS_IMETHODIMP |
|
134 nsTreeContentView::GetRowCount(int32_t* aRowCount) |
|
135 { |
|
136 *aRowCount = mRows.Length(); |
|
137 |
|
138 return NS_OK; |
|
139 } |
|
140 |
|
141 NS_IMETHODIMP |
|
142 nsTreeContentView::GetSelection(nsITreeSelection** aSelection) |
|
143 { |
|
144 NS_IF_ADDREF(*aSelection = mSelection); |
|
145 |
|
146 return NS_OK; |
|
147 } |
|
148 |
|
149 bool |
|
150 nsTreeContentView::CanTrustTreeSelection(nsISupports* aValue) |
|
151 { |
|
152 // Untrusted content is only allowed to specify known-good views |
|
153 if (nsContentUtils::IsCallerChrome()) |
|
154 return true; |
|
155 nsCOMPtr<nsINativeTreeSelection> nativeTreeSel = do_QueryInterface(aValue); |
|
156 return nativeTreeSel && NS_SUCCEEDED(nativeTreeSel->EnsureNative()); |
|
157 } |
|
158 |
|
159 NS_IMETHODIMP |
|
160 nsTreeContentView::SetSelection(nsITreeSelection* aSelection) |
|
161 { |
|
162 NS_ENSURE_TRUE(!aSelection || CanTrustTreeSelection(aSelection), |
|
163 NS_ERROR_DOM_SECURITY_ERR); |
|
164 |
|
165 mSelection = aSelection; |
|
166 return NS_OK; |
|
167 } |
|
168 |
|
169 NS_IMETHODIMP |
|
170 nsTreeContentView::GetRowProperties(int32_t aIndex, nsAString& aProps) |
|
171 { |
|
172 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
173 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
174 return NS_ERROR_INVALID_ARG; |
|
175 |
|
176 Row* row = mRows[aIndex]; |
|
177 nsIContent* realRow; |
|
178 if (row->IsSeparator()) |
|
179 realRow = row->mContent; |
|
180 else |
|
181 realRow = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
182 |
|
183 if (realRow) { |
|
184 realRow->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); |
|
185 } |
|
186 |
|
187 return NS_OK; |
|
188 } |
|
189 |
|
190 NS_IMETHODIMP |
|
191 nsTreeContentView::GetCellProperties(int32_t aRow, nsITreeColumn* aCol, |
|
192 nsAString& aProps) |
|
193 { |
|
194 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
195 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
196 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
197 return NS_ERROR_INVALID_ARG; |
|
198 |
|
199 Row* row = mRows[aRow]; |
|
200 nsIContent* realRow = |
|
201 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
202 if (realRow) { |
|
203 nsIContent* cell = GetCell(realRow, aCol); |
|
204 if (cell) { |
|
205 cell->GetAttr(kNameSpaceID_None, nsGkAtoms::properties, aProps); |
|
206 } |
|
207 } |
|
208 |
|
209 return NS_OK; |
|
210 } |
|
211 |
|
212 NS_IMETHODIMP |
|
213 nsTreeContentView::GetColumnProperties(nsITreeColumn* aCol, nsAString& aProps) |
|
214 { |
|
215 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
216 nsCOMPtr<nsIDOMElement> element; |
|
217 aCol->GetElement(getter_AddRefs(element)); |
|
218 |
|
219 element->GetAttribute(NS_LITERAL_STRING("properties"), aProps); |
|
220 return NS_OK; |
|
221 } |
|
222 |
|
223 NS_IMETHODIMP |
|
224 nsTreeContentView::IsContainer(int32_t aIndex, bool* _retval) |
|
225 { |
|
226 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
227 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
228 return NS_ERROR_INVALID_ARG; |
|
229 |
|
230 *_retval = mRows[aIndex]->IsContainer(); |
|
231 |
|
232 return NS_OK; |
|
233 } |
|
234 |
|
235 NS_IMETHODIMP |
|
236 nsTreeContentView::IsContainerOpen(int32_t aIndex, bool* _retval) |
|
237 { |
|
238 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
239 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
240 return NS_ERROR_INVALID_ARG; |
|
241 |
|
242 *_retval = mRows[aIndex]->IsOpen(); |
|
243 |
|
244 return NS_OK; |
|
245 } |
|
246 |
|
247 NS_IMETHODIMP |
|
248 nsTreeContentView::IsContainerEmpty(int32_t aIndex, bool* _retval) |
|
249 { |
|
250 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
251 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
252 return NS_ERROR_INVALID_ARG; |
|
253 |
|
254 *_retval = mRows[aIndex]->IsEmpty(); |
|
255 |
|
256 return NS_OK; |
|
257 } |
|
258 |
|
259 NS_IMETHODIMP |
|
260 nsTreeContentView::IsSeparator(int32_t aIndex, bool *_retval) |
|
261 { |
|
262 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
263 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
264 return NS_ERROR_INVALID_ARG; |
|
265 |
|
266 *_retval = mRows[aIndex]->IsSeparator(); |
|
267 |
|
268 return NS_OK; |
|
269 } |
|
270 |
|
271 NS_IMETHODIMP |
|
272 nsTreeContentView::IsSorted(bool *_retval) |
|
273 { |
|
274 *_retval = false; |
|
275 |
|
276 return NS_OK; |
|
277 } |
|
278 |
|
279 NS_IMETHODIMP |
|
280 nsTreeContentView::CanDrop(int32_t aIndex, int32_t aOrientation, |
|
281 nsIDOMDataTransfer* aDataTransfer, bool *_retval) |
|
282 { |
|
283 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
284 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
285 return NS_ERROR_INVALID_ARG; |
|
286 |
|
287 *_retval = false; |
|
288 |
|
289 return NS_OK; |
|
290 } |
|
291 |
|
292 NS_IMETHODIMP |
|
293 nsTreeContentView::Drop(int32_t aRow, int32_t aOrientation, nsIDOMDataTransfer* aDataTransfer) |
|
294 { |
|
295 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
296 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
297 return NS_ERROR_INVALID_ARG; |
|
298 |
|
299 return NS_OK; |
|
300 } |
|
301 |
|
302 NS_IMETHODIMP |
|
303 nsTreeContentView::GetParentIndex(int32_t aRowIndex, int32_t* _retval) |
|
304 { |
|
305 NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), |
|
306 "bad row index"); |
|
307 if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) |
|
308 return NS_ERROR_INVALID_ARG; |
|
309 |
|
310 *_retval = mRows[aRowIndex]->mParentIndex; |
|
311 |
|
312 return NS_OK; |
|
313 } |
|
314 |
|
315 NS_IMETHODIMP |
|
316 nsTreeContentView::HasNextSibling(int32_t aRowIndex, int32_t aAfterIndex, bool* _retval) |
|
317 { |
|
318 NS_PRECONDITION(aRowIndex >= 0 && aRowIndex < int32_t(mRows.Length()), |
|
319 "bad row index"); |
|
320 if (aRowIndex < 0 || aRowIndex >= int32_t(mRows.Length())) |
|
321 return NS_ERROR_INVALID_ARG; |
|
322 |
|
323 // We have a next sibling if the row is not the last in the subtree. |
|
324 int32_t parentIndex = mRows[aRowIndex]->mParentIndex; |
|
325 if (parentIndex >= 0) { |
|
326 // Compute the last index in this subtree. |
|
327 int32_t lastIndex = parentIndex + (mRows[parentIndex])->mSubtreeSize; |
|
328 Row* row = mRows[lastIndex]; |
|
329 while (row->mParentIndex != parentIndex) { |
|
330 lastIndex = row->mParentIndex; |
|
331 row = mRows[lastIndex]; |
|
332 } |
|
333 |
|
334 *_retval = aRowIndex < lastIndex; |
|
335 } |
|
336 else { |
|
337 *_retval = uint32_t(aRowIndex) < mRows.Length() - 1; |
|
338 } |
|
339 |
|
340 return NS_OK; |
|
341 } |
|
342 |
|
343 NS_IMETHODIMP |
|
344 nsTreeContentView::GetLevel(int32_t aIndex, int32_t* _retval) |
|
345 { |
|
346 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
347 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
348 return NS_ERROR_INVALID_ARG; |
|
349 |
|
350 int32_t level = 0; |
|
351 Row* row = mRows[aIndex]; |
|
352 while (row->mParentIndex >= 0) { |
|
353 level++; |
|
354 row = mRows[row->mParentIndex]; |
|
355 } |
|
356 *_retval = level; |
|
357 |
|
358 return NS_OK; |
|
359 } |
|
360 |
|
361 NS_IMETHODIMP |
|
362 nsTreeContentView::GetImageSrc(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) |
|
363 { |
|
364 _retval.Truncate(); |
|
365 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
366 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
367 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
368 return NS_ERROR_INVALID_ARG; |
|
369 |
|
370 Row* row = mRows[aRow]; |
|
371 |
|
372 nsIContent* realRow = |
|
373 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
374 if (realRow) { |
|
375 nsIContent* cell = GetCell(realRow, aCol); |
|
376 if (cell) |
|
377 cell->GetAttr(kNameSpaceID_None, nsGkAtoms::src, _retval); |
|
378 } |
|
379 |
|
380 return NS_OK; |
|
381 } |
|
382 |
|
383 NS_IMETHODIMP |
|
384 nsTreeContentView::GetProgressMode(int32_t aRow, nsITreeColumn* aCol, int32_t* _retval) |
|
385 { |
|
386 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
387 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
388 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
389 return NS_ERROR_INVALID_ARG; |
|
390 |
|
391 *_retval = nsITreeView::PROGRESS_NONE; |
|
392 |
|
393 Row* row = mRows[aRow]; |
|
394 |
|
395 nsIContent* realRow = |
|
396 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
397 if (realRow) { |
|
398 nsIContent* cell = GetCell(realRow, aCol); |
|
399 if (cell) { |
|
400 static nsIContent::AttrValuesArray strings[] = |
|
401 {&nsGkAtoms::normal, &nsGkAtoms::undetermined, nullptr}; |
|
402 switch (cell->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::mode, |
|
403 strings, eCaseMatters)) { |
|
404 case 0: *_retval = nsITreeView::PROGRESS_NORMAL; break; |
|
405 case 1: *_retval = nsITreeView::PROGRESS_UNDETERMINED; break; |
|
406 } |
|
407 } |
|
408 } |
|
409 |
|
410 return NS_OK; |
|
411 } |
|
412 |
|
413 NS_IMETHODIMP |
|
414 nsTreeContentView::GetCellValue(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) |
|
415 { |
|
416 _retval.Truncate(); |
|
417 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
418 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
419 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
420 return NS_ERROR_INVALID_ARG; |
|
421 |
|
422 Row* row = mRows[aRow]; |
|
423 |
|
424 nsIContent* realRow = |
|
425 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
426 if (realRow) { |
|
427 nsIContent* cell = GetCell(realRow, aCol); |
|
428 if (cell) |
|
429 cell->GetAttr(kNameSpaceID_None, nsGkAtoms::value, _retval); |
|
430 } |
|
431 |
|
432 return NS_OK; |
|
433 } |
|
434 |
|
435 NS_IMETHODIMP |
|
436 nsTreeContentView::GetCellText(int32_t aRow, nsITreeColumn* aCol, nsAString& _retval) |
|
437 { |
|
438 _retval.Truncate(); |
|
439 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
440 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
441 NS_PRECONDITION(aCol, "bad column"); |
|
442 |
|
443 if (aRow < 0 || aRow >= int32_t(mRows.Length()) || !aCol) |
|
444 return NS_ERROR_INVALID_ARG; |
|
445 |
|
446 Row* row = mRows[aRow]; |
|
447 |
|
448 // Check for a "label" attribute - this is valid on an <treeitem> |
|
449 // with a single implied column. |
|
450 if (row->mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval) |
|
451 && !_retval.IsEmpty()) |
|
452 return NS_OK; |
|
453 |
|
454 nsIAtom *rowTag = row->mContent->Tag(); |
|
455 if (rowTag == nsGkAtoms::treeitem && row->mContent->IsXUL()) { |
|
456 nsIContent* realRow = |
|
457 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
458 if (realRow) { |
|
459 nsIContent* cell = GetCell(realRow, aCol); |
|
460 if (cell) |
|
461 cell->GetAttr(kNameSpaceID_None, nsGkAtoms::label, _retval); |
|
462 } |
|
463 } |
|
464 |
|
465 return NS_OK; |
|
466 } |
|
467 |
|
468 NS_IMETHODIMP |
|
469 nsTreeContentView::SetTree(nsITreeBoxObject* aTree) |
|
470 { |
|
471 ClearRows(); |
|
472 |
|
473 mBoxObject = aTree; |
|
474 |
|
475 MOZ_ASSERT(!mRoot, "mRoot should have been cleared out by ClearRows"); |
|
476 |
|
477 if (aTree) { |
|
478 // Get our root element |
|
479 nsCOMPtr<nsIBoxObject> boxObject = do_QueryInterface(mBoxObject); |
|
480 if (!boxObject) { |
|
481 mBoxObject = nullptr; |
|
482 return NS_ERROR_INVALID_ARG; |
|
483 } |
|
484 nsCOMPtr<nsIDOMElement> element; |
|
485 boxObject->GetElement(getter_AddRefs(element)); |
|
486 |
|
487 mRoot = do_QueryInterface(element); |
|
488 NS_ENSURE_STATE(mRoot); |
|
489 |
|
490 // Add ourselves to document's observers. |
|
491 nsIDocument* document = mRoot->GetDocument(); |
|
492 if (document) { |
|
493 document->AddObserver(this); |
|
494 mDocument = document; |
|
495 } |
|
496 |
|
497 nsCOMPtr<nsIDOMElement> bodyElement; |
|
498 mBoxObject->GetTreeBody(getter_AddRefs(bodyElement)); |
|
499 if (bodyElement) { |
|
500 mBody = do_QueryInterface(bodyElement); |
|
501 int32_t index = 0; |
|
502 Serialize(mBody, -1, &index, mRows); |
|
503 } |
|
504 } |
|
505 |
|
506 return NS_OK; |
|
507 } |
|
508 |
|
509 NS_IMETHODIMP |
|
510 nsTreeContentView::ToggleOpenState(int32_t aIndex) |
|
511 { |
|
512 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
513 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
514 return NS_ERROR_INVALID_ARG; |
|
515 |
|
516 // We don't serialize content right here, since content might be generated |
|
517 // lazily. |
|
518 Row* row = mRows[aIndex]; |
|
519 |
|
520 if (row->IsOpen()) |
|
521 row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("false"), true); |
|
522 else |
|
523 row->mContent->SetAttr(kNameSpaceID_None, nsGkAtoms::open, NS_LITERAL_STRING("true"), true); |
|
524 |
|
525 return NS_OK; |
|
526 } |
|
527 |
|
528 NS_IMETHODIMP |
|
529 nsTreeContentView::CycleHeader(nsITreeColumn* aCol) |
|
530 { |
|
531 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
532 |
|
533 if (!mRoot) |
|
534 return NS_OK; |
|
535 |
|
536 nsCOMPtr<nsIDOMElement> element; |
|
537 aCol->GetElement(getter_AddRefs(element)); |
|
538 if (element) { |
|
539 nsCOMPtr<nsIContent> column = do_QueryInterface(element); |
|
540 nsAutoString sort; |
|
541 column->GetAttr(kNameSpaceID_None, nsGkAtoms::sort, sort); |
|
542 if (!sort.IsEmpty()) { |
|
543 nsCOMPtr<nsIXULSortService> xs = do_GetService("@mozilla.org/xul/xul-sort-service;1"); |
|
544 if (xs) { |
|
545 nsAutoString sortdirection; |
|
546 static nsIContent::AttrValuesArray strings[] = |
|
547 {&nsGkAtoms::ascending, &nsGkAtoms::descending, nullptr}; |
|
548 switch (column->FindAttrValueIn(kNameSpaceID_None, |
|
549 nsGkAtoms::sortDirection, |
|
550 strings, eCaseMatters)) { |
|
551 case 0: sortdirection.AssignLiteral("descending"); break; |
|
552 case 1: sortdirection.AssignLiteral("natural"); break; |
|
553 default: sortdirection.AssignLiteral("ascending"); break; |
|
554 } |
|
555 |
|
556 nsAutoString hints; |
|
557 column->GetAttr(kNameSpaceID_None, nsGkAtoms::sorthints, hints); |
|
558 sortdirection.AppendLiteral(" "); |
|
559 sortdirection += hints; |
|
560 |
|
561 nsCOMPtr<nsIDOMNode> rootnode = do_QueryInterface(mRoot); |
|
562 xs->Sort(rootnode, sort, sortdirection); |
|
563 } |
|
564 } |
|
565 } |
|
566 |
|
567 return NS_OK; |
|
568 } |
|
569 |
|
570 NS_IMETHODIMP |
|
571 nsTreeContentView::SelectionChanged() |
|
572 { |
|
573 return NS_OK; |
|
574 } |
|
575 |
|
576 NS_IMETHODIMP |
|
577 nsTreeContentView::CycleCell(int32_t aRow, nsITreeColumn* aCol) |
|
578 { |
|
579 return NS_OK; |
|
580 } |
|
581 |
|
582 NS_IMETHODIMP |
|
583 nsTreeContentView::IsEditable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) |
|
584 { |
|
585 *_retval = false; |
|
586 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
587 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
588 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
589 return NS_ERROR_INVALID_ARG; |
|
590 |
|
591 *_retval = true; |
|
592 |
|
593 Row* row = mRows[aRow]; |
|
594 |
|
595 nsIContent* realRow = |
|
596 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
597 if (realRow) { |
|
598 nsIContent* cell = GetCell(realRow, aCol); |
|
599 if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::editable, |
|
600 nsGkAtoms::_false, eCaseMatters)) { |
|
601 *_retval = false; |
|
602 } |
|
603 } |
|
604 |
|
605 return NS_OK; |
|
606 } |
|
607 |
|
608 NS_IMETHODIMP |
|
609 nsTreeContentView::IsSelectable(int32_t aRow, nsITreeColumn* aCol, bool* _retval) |
|
610 { |
|
611 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
612 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
613 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
614 return NS_ERROR_INVALID_ARG; |
|
615 |
|
616 *_retval = true; |
|
617 |
|
618 Row* row = mRows[aRow]; |
|
619 |
|
620 nsIContent* realRow = |
|
621 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
622 if (realRow) { |
|
623 nsIContent* cell = GetCell(realRow, aCol); |
|
624 if (cell && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::selectable, |
|
625 nsGkAtoms::_false, eCaseMatters)) { |
|
626 *_retval = false; |
|
627 } |
|
628 } |
|
629 |
|
630 return NS_OK; |
|
631 } |
|
632 |
|
633 NS_IMETHODIMP |
|
634 nsTreeContentView::SetCellValue(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) |
|
635 { |
|
636 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
637 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
638 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
639 return NS_ERROR_INVALID_ARG; |
|
640 |
|
641 Row* row = mRows[aRow]; |
|
642 |
|
643 nsIContent* realRow = |
|
644 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
645 if (realRow) { |
|
646 nsIContent* cell = GetCell(realRow, aCol); |
|
647 if (cell) |
|
648 cell->SetAttr(kNameSpaceID_None, nsGkAtoms::value, aValue, true); |
|
649 } |
|
650 |
|
651 return NS_OK; |
|
652 } |
|
653 |
|
654 NS_IMETHODIMP |
|
655 nsTreeContentView::SetCellText(int32_t aRow, nsITreeColumn* aCol, const nsAString& aValue) |
|
656 { |
|
657 NS_ENSURE_NATIVE_COLUMN(aCol); |
|
658 NS_PRECONDITION(aRow >= 0 && aRow < int32_t(mRows.Length()), "bad row"); |
|
659 if (aRow < 0 || aRow >= int32_t(mRows.Length())) |
|
660 return NS_ERROR_INVALID_ARG; |
|
661 |
|
662 Row* row = mRows[aRow]; |
|
663 |
|
664 nsIContent* realRow = |
|
665 nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treerow); |
|
666 if (realRow) { |
|
667 nsIContent* cell = GetCell(realRow, aCol); |
|
668 if (cell) |
|
669 cell->SetAttr(kNameSpaceID_None, nsGkAtoms::label, aValue, true); |
|
670 } |
|
671 |
|
672 return NS_OK; |
|
673 } |
|
674 |
|
675 NS_IMETHODIMP |
|
676 nsTreeContentView::PerformAction(const char16_t* aAction) |
|
677 { |
|
678 return NS_OK; |
|
679 } |
|
680 |
|
681 NS_IMETHODIMP |
|
682 nsTreeContentView::PerformActionOnRow(const char16_t* aAction, int32_t aRow) |
|
683 { |
|
684 return NS_OK; |
|
685 } |
|
686 |
|
687 NS_IMETHODIMP |
|
688 nsTreeContentView::PerformActionOnCell(const char16_t* aAction, int32_t aRow, nsITreeColumn* aCol) |
|
689 { |
|
690 return NS_OK; |
|
691 } |
|
692 |
|
693 |
|
694 NS_IMETHODIMP |
|
695 nsTreeContentView::GetItemAtIndex(int32_t aIndex, nsIDOMElement** _retval) |
|
696 { |
|
697 NS_PRECONDITION(aIndex >= 0 && aIndex < int32_t(mRows.Length()), "bad index"); |
|
698 if (aIndex < 0 || aIndex >= int32_t(mRows.Length())) |
|
699 return NS_ERROR_INVALID_ARG; |
|
700 |
|
701 Row* row = mRows[aIndex]; |
|
702 row->mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)_retval); |
|
703 |
|
704 return NS_OK; |
|
705 } |
|
706 |
|
707 NS_IMETHODIMP |
|
708 nsTreeContentView::GetIndexOfItem(nsIDOMElement* aItem, int32_t* _retval) |
|
709 { |
|
710 nsCOMPtr<nsIContent> content = do_QueryInterface(aItem); |
|
711 *_retval = FindContent(content); |
|
712 |
|
713 return NS_OK; |
|
714 } |
|
715 |
|
716 void |
|
717 nsTreeContentView::AttributeChanged(nsIDocument* aDocument, |
|
718 dom::Element* aElement, |
|
719 int32_t aNameSpaceID, |
|
720 nsIAtom* aAttribute, |
|
721 int32_t aModType) |
|
722 { |
|
723 // Lots of codepaths under here that do all sorts of stuff, so be safe. |
|
724 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
|
725 |
|
726 // Make sure this notification concerns us. |
|
727 // First check the tag to see if it's one that we care about. |
|
728 nsIAtom* tag = aElement->Tag(); |
|
729 |
|
730 if (mBoxObject && (aElement == mRoot || aElement == mBody)) { |
|
731 mBoxObject->ClearStyleAndImageCaches(); |
|
732 mBoxObject->Invalidate(); |
|
733 } |
|
734 |
|
735 // We don't consider non-XUL nodes. |
|
736 nsIContent* parent = nullptr; |
|
737 if (!aElement->IsXUL() || |
|
738 ((parent = aElement->GetParent()) && !parent->IsXUL())) { |
|
739 return; |
|
740 } |
|
741 if (tag != nsGkAtoms::treecol && |
|
742 tag != nsGkAtoms::treeitem && |
|
743 tag != nsGkAtoms::treeseparator && |
|
744 tag != nsGkAtoms::treerow && |
|
745 tag != nsGkAtoms::treecell) { |
|
746 return; |
|
747 } |
|
748 |
|
749 // If we have a legal tag, go up to the tree/select and make sure |
|
750 // that it's ours. |
|
751 |
|
752 for (nsIContent* element = aElement; element != mBody; element = element->GetParent()) { |
|
753 if (!element) |
|
754 return; // this is not for us |
|
755 nsIAtom *parentTag = element->Tag(); |
|
756 if (element->IsXUL() && parentTag == nsGkAtoms::tree) |
|
757 return; // this is not for us |
|
758 } |
|
759 |
|
760 // Handle changes of the hidden attribute. |
|
761 if (aAttribute == nsGkAtoms::hidden && |
|
762 (tag == nsGkAtoms::treeitem || tag == nsGkAtoms::treeseparator)) { |
|
763 bool hidden = aElement->AttrValueIs(kNameSpaceID_None, |
|
764 nsGkAtoms::hidden, |
|
765 nsGkAtoms::_true, eCaseMatters); |
|
766 |
|
767 int32_t index = FindContent(aElement); |
|
768 if (hidden && index >= 0) { |
|
769 // Hide this row along with its children. |
|
770 int32_t count = RemoveRow(index); |
|
771 if (mBoxObject) |
|
772 mBoxObject->RowCountChanged(index, -count); |
|
773 } |
|
774 else if (!hidden && index < 0) { |
|
775 // Show this row along with its children. |
|
776 nsCOMPtr<nsIContent> parent = aElement->GetParent(); |
|
777 if (parent) { |
|
778 InsertRowFor(parent, aElement); |
|
779 } |
|
780 } |
|
781 |
|
782 return; |
|
783 } |
|
784 |
|
785 if (tag == nsGkAtoms::treecol) { |
|
786 if (aAttribute == nsGkAtoms::properties) { |
|
787 if (mBoxObject) { |
|
788 nsCOMPtr<nsITreeColumns> cols; |
|
789 mBoxObject->GetColumns(getter_AddRefs(cols)); |
|
790 if (cols) { |
|
791 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aElement); |
|
792 nsCOMPtr<nsITreeColumn> col; |
|
793 cols->GetColumnFor(element, getter_AddRefs(col)); |
|
794 mBoxObject->InvalidateColumn(col); |
|
795 } |
|
796 } |
|
797 } |
|
798 } |
|
799 else if (tag == nsGkAtoms::treeitem) { |
|
800 int32_t index = FindContent(aElement); |
|
801 if (index >= 0) { |
|
802 Row* row = mRows[index]; |
|
803 if (aAttribute == nsGkAtoms::container) { |
|
804 bool isContainer = |
|
805 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, |
|
806 nsGkAtoms::_true, eCaseMatters); |
|
807 row->SetContainer(isContainer); |
|
808 if (mBoxObject) |
|
809 mBoxObject->InvalidateRow(index); |
|
810 } |
|
811 else if (aAttribute == nsGkAtoms::open) { |
|
812 bool isOpen = |
|
813 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, |
|
814 nsGkAtoms::_true, eCaseMatters); |
|
815 bool wasOpen = row->IsOpen(); |
|
816 if (! isOpen && wasOpen) |
|
817 CloseContainer(index); |
|
818 else if (isOpen && ! wasOpen) |
|
819 OpenContainer(index); |
|
820 } |
|
821 else if (aAttribute == nsGkAtoms::empty) { |
|
822 bool isEmpty = |
|
823 aElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, |
|
824 nsGkAtoms::_true, eCaseMatters); |
|
825 row->SetEmpty(isEmpty); |
|
826 if (mBoxObject) |
|
827 mBoxObject->InvalidateRow(index); |
|
828 } |
|
829 } |
|
830 } |
|
831 else if (tag == nsGkAtoms::treeseparator) { |
|
832 int32_t index = FindContent(aElement); |
|
833 if (index >= 0) { |
|
834 if (aAttribute == nsGkAtoms::properties && mBoxObject) { |
|
835 mBoxObject->InvalidateRow(index); |
|
836 } |
|
837 } |
|
838 } |
|
839 else if (tag == nsGkAtoms::treerow) { |
|
840 if (aAttribute == nsGkAtoms::properties) { |
|
841 nsCOMPtr<nsIContent> parent = aElement->GetParent(); |
|
842 if (parent) { |
|
843 int32_t index = FindContent(parent); |
|
844 if (index >= 0 && mBoxObject) { |
|
845 mBoxObject->InvalidateRow(index); |
|
846 } |
|
847 } |
|
848 } |
|
849 } |
|
850 else if (tag == nsGkAtoms::treecell) { |
|
851 if (aAttribute == nsGkAtoms::ref || |
|
852 aAttribute == nsGkAtoms::properties || |
|
853 aAttribute == nsGkAtoms::mode || |
|
854 aAttribute == nsGkAtoms::src || |
|
855 aAttribute == nsGkAtoms::value || |
|
856 aAttribute == nsGkAtoms::label) { |
|
857 nsIContent* parent = aElement->GetParent(); |
|
858 if (parent) { |
|
859 nsCOMPtr<nsIContent> grandParent = parent->GetParent(); |
|
860 if (grandParent && grandParent->IsXUL()) { |
|
861 int32_t index = FindContent(grandParent); |
|
862 if (index >= 0 && mBoxObject) { |
|
863 // XXX Should we make an effort to invalidate only cell ? |
|
864 mBoxObject->InvalidateRow(index); |
|
865 } |
|
866 } |
|
867 } |
|
868 } |
|
869 } |
|
870 } |
|
871 |
|
872 void |
|
873 nsTreeContentView::ContentAppended(nsIDocument *aDocument, |
|
874 nsIContent* aContainer, |
|
875 nsIContent* aFirstNewContent, |
|
876 int32_t /* unused */) |
|
877 { |
|
878 for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { |
|
879 // Our contentinserted doesn't use the index |
|
880 ContentInserted(aDocument, aContainer, cur, 0); |
|
881 } |
|
882 } |
|
883 |
|
884 void |
|
885 nsTreeContentView::ContentInserted(nsIDocument *aDocument, |
|
886 nsIContent* aContainer, |
|
887 nsIContent* aChild, |
|
888 int32_t /* unused */) |
|
889 { |
|
890 NS_ASSERTION(aChild, "null ptr"); |
|
891 |
|
892 // Make sure this notification concerns us. |
|
893 // First check the tag to see if it's one that we care about. |
|
894 nsIAtom *childTag = aChild->Tag(); |
|
895 |
|
896 // Don't allow non-XUL nodes. |
|
897 if (!aChild->IsXUL() || !aContainer->IsXUL()) |
|
898 return; |
|
899 if (childTag != nsGkAtoms::treeitem && |
|
900 childTag != nsGkAtoms::treeseparator && |
|
901 childTag != nsGkAtoms::treechildren && |
|
902 childTag != nsGkAtoms::treerow && |
|
903 childTag != nsGkAtoms::treecell) { |
|
904 return; |
|
905 } |
|
906 |
|
907 // If we have a legal tag, go up to the tree/select and make sure |
|
908 // that it's ours. |
|
909 |
|
910 for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { |
|
911 if (!element) |
|
912 return; // this is not for us |
|
913 nsIAtom *parentTag = element->Tag(); |
|
914 if (element->IsXUL() && parentTag == nsGkAtoms::tree) |
|
915 return; // this is not for us |
|
916 } |
|
917 |
|
918 // Lots of codepaths under here that do all sorts of stuff, so be safe. |
|
919 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
|
920 |
|
921 if (childTag == nsGkAtoms::treechildren) { |
|
922 int32_t index = FindContent(aContainer); |
|
923 if (index >= 0) { |
|
924 Row* row = mRows[index]; |
|
925 row->SetEmpty(false); |
|
926 if (mBoxObject) |
|
927 mBoxObject->InvalidateRow(index); |
|
928 if (row->IsContainer() && row->IsOpen()) { |
|
929 int32_t count = EnsureSubtree(index); |
|
930 if (mBoxObject) |
|
931 mBoxObject->RowCountChanged(index + 1, count); |
|
932 } |
|
933 } |
|
934 } |
|
935 else if (childTag == nsGkAtoms::treeitem || |
|
936 childTag == nsGkAtoms::treeseparator) { |
|
937 InsertRowFor(aContainer, aChild); |
|
938 } |
|
939 else if (childTag == nsGkAtoms::treerow) { |
|
940 int32_t index = FindContent(aContainer); |
|
941 if (index >= 0 && mBoxObject) |
|
942 mBoxObject->InvalidateRow(index); |
|
943 } |
|
944 else if (childTag == nsGkAtoms::treecell) { |
|
945 nsCOMPtr<nsIContent> parent = aContainer->GetParent(); |
|
946 if (parent) { |
|
947 int32_t index = FindContent(parent); |
|
948 if (index >= 0 && mBoxObject) |
|
949 mBoxObject->InvalidateRow(index); |
|
950 } |
|
951 } |
|
952 } |
|
953 |
|
954 void |
|
955 nsTreeContentView::ContentRemoved(nsIDocument *aDocument, |
|
956 nsIContent* aContainer, |
|
957 nsIContent* aChild, |
|
958 int32_t aIndexInContainer, |
|
959 nsIContent* aPreviousSibling) |
|
960 { |
|
961 NS_ASSERTION(aChild, "null ptr"); |
|
962 |
|
963 // Make sure this notification concerns us. |
|
964 // First check the tag to see if it's one that we care about. |
|
965 nsIAtom *tag = aChild->Tag(); |
|
966 |
|
967 // We don't consider non-XUL nodes. |
|
968 if (!aChild->IsXUL() || !aContainer->IsXUL()) |
|
969 return; |
|
970 if (tag != nsGkAtoms::treeitem && |
|
971 tag != nsGkAtoms::treeseparator && |
|
972 tag != nsGkAtoms::treechildren && |
|
973 tag != nsGkAtoms::treerow && |
|
974 tag != nsGkAtoms::treecell) { |
|
975 return; |
|
976 } |
|
977 |
|
978 // If we have a legal tag, go up to the tree/select and make sure |
|
979 // that it's ours. |
|
980 |
|
981 for (nsIContent* element = aContainer; element != mBody; element = element->GetParent()) { |
|
982 if (!element) |
|
983 return; // this is not for us |
|
984 nsIAtom *parentTag = element->Tag(); |
|
985 if (element->IsXUL() && parentTag == nsGkAtoms::tree) |
|
986 return; // this is not for us |
|
987 } |
|
988 |
|
989 // Lots of codepaths under here that do all sorts of stuff, so be safe. |
|
990 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
|
991 |
|
992 if (tag == nsGkAtoms::treechildren) { |
|
993 int32_t index = FindContent(aContainer); |
|
994 if (index >= 0) { |
|
995 Row* row = mRows[index]; |
|
996 row->SetEmpty(true); |
|
997 int32_t count = RemoveSubtree(index); |
|
998 // Invalidate also the row to update twisty. |
|
999 if (mBoxObject) { |
|
1000 mBoxObject->InvalidateRow(index); |
|
1001 mBoxObject->RowCountChanged(index + 1, -count); |
|
1002 } |
|
1003 } |
|
1004 } |
|
1005 else if (tag == nsGkAtoms::treeitem || |
|
1006 tag == nsGkAtoms::treeseparator |
|
1007 ) { |
|
1008 int32_t index = FindContent(aChild); |
|
1009 if (index >= 0) { |
|
1010 int32_t count = RemoveRow(index); |
|
1011 if (mBoxObject) |
|
1012 mBoxObject->RowCountChanged(index, -count); |
|
1013 } |
|
1014 } |
|
1015 else if (tag == nsGkAtoms::treerow) { |
|
1016 int32_t index = FindContent(aContainer); |
|
1017 if (index >= 0 && mBoxObject) |
|
1018 mBoxObject->InvalidateRow(index); |
|
1019 } |
|
1020 else if (tag == nsGkAtoms::treecell) { |
|
1021 nsCOMPtr<nsIContent> parent = aContainer->GetParent(); |
|
1022 if (parent) { |
|
1023 int32_t index = FindContent(parent); |
|
1024 if (index >= 0 && mBoxObject) |
|
1025 mBoxObject->InvalidateRow(index); |
|
1026 } |
|
1027 } |
|
1028 } |
|
1029 |
|
1030 void |
|
1031 nsTreeContentView::NodeWillBeDestroyed(const nsINode* aNode) |
|
1032 { |
|
1033 // XXXbz do we need this strong ref? Do we drop refs to self in ClearRows? |
|
1034 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); |
|
1035 ClearRows(); |
|
1036 } |
|
1037 |
|
1038 |
|
1039 // Recursively serialize content, starting with aContent. |
|
1040 void |
|
1041 nsTreeContentView::Serialize(nsIContent* aContent, int32_t aParentIndex, |
|
1042 int32_t* aIndex, nsTArray<nsAutoPtr<Row> >& aRows) |
|
1043 { |
|
1044 // Don't allow non-XUL nodes. |
|
1045 if (!aContent->IsXUL()) |
|
1046 return; |
|
1047 |
|
1048 dom::FlattenedChildIterator iter(aContent); |
|
1049 for (nsIContent* content = iter.GetNextChild(); content; content = iter.GetNextChild()) { |
|
1050 nsIAtom *tag = content->Tag(); |
|
1051 int32_t count = aRows.Length(); |
|
1052 |
|
1053 if (content->IsXUL()) { |
|
1054 if (tag == nsGkAtoms::treeitem) |
|
1055 SerializeItem(content, aParentIndex, aIndex, aRows); |
|
1056 else if (tag == nsGkAtoms::treeseparator) |
|
1057 SerializeSeparator(content, aParentIndex, aIndex, aRows); |
|
1058 } |
|
1059 *aIndex += aRows.Length() - count; |
|
1060 } |
|
1061 } |
|
1062 |
|
1063 void |
|
1064 nsTreeContentView::SerializeItem(nsIContent* aContent, int32_t aParentIndex, |
|
1065 int32_t* aIndex, nsTArray<nsAutoPtr<Row> >& aRows) |
|
1066 { |
|
1067 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, |
|
1068 nsGkAtoms::_true, eCaseMatters)) |
|
1069 return; |
|
1070 |
|
1071 Row* row = new Row(aContent, aParentIndex); |
|
1072 aRows.AppendElement(row); |
|
1073 |
|
1074 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, |
|
1075 nsGkAtoms::_true, eCaseMatters)) { |
|
1076 row->SetContainer(true); |
|
1077 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, |
|
1078 nsGkAtoms::_true, eCaseMatters)) { |
|
1079 row->SetOpen(true); |
|
1080 nsIContent* child = |
|
1081 nsTreeUtils::GetImmediateChild(aContent, nsGkAtoms::treechildren); |
|
1082 if (child && child->IsXUL()) { |
|
1083 // Now, recursively serialize our child. |
|
1084 int32_t count = aRows.Length(); |
|
1085 int32_t index = 0; |
|
1086 Serialize(child, aParentIndex + *aIndex + 1, &index, aRows); |
|
1087 row->mSubtreeSize += aRows.Length() - count; |
|
1088 } |
|
1089 else |
|
1090 row->SetEmpty(true); |
|
1091 } else if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::empty, |
|
1092 nsGkAtoms::_true, eCaseMatters)) { |
|
1093 row->SetEmpty(true); |
|
1094 } |
|
1095 } |
|
1096 } |
|
1097 |
|
1098 void |
|
1099 nsTreeContentView::SerializeSeparator(nsIContent* aContent, |
|
1100 int32_t aParentIndex, int32_t* aIndex, |
|
1101 nsTArray<nsAutoPtr<Row> >& aRows) |
|
1102 { |
|
1103 if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, |
|
1104 nsGkAtoms::_true, eCaseMatters)) |
|
1105 return; |
|
1106 |
|
1107 Row* row = new Row(aContent, aParentIndex); |
|
1108 row->SetSeparator(true); |
|
1109 aRows.AppendElement(row); |
|
1110 } |
|
1111 |
|
1112 void |
|
1113 nsTreeContentView::GetIndexInSubtree(nsIContent* aContainer, |
|
1114 nsIContent* aContent, int32_t* aIndex) |
|
1115 { |
|
1116 uint32_t childCount = aContainer->GetChildCount(); |
|
1117 |
|
1118 if (!aContainer->IsXUL()) |
|
1119 return; |
|
1120 |
|
1121 for (uint32_t i = 0; i < childCount; i++) { |
|
1122 nsIContent *content = aContainer->GetChildAt(i); |
|
1123 |
|
1124 if (content == aContent) |
|
1125 break; |
|
1126 |
|
1127 nsIAtom *tag = content->Tag(); |
|
1128 |
|
1129 if (content->IsXUL()) { |
|
1130 if (tag == nsGkAtoms::treeitem) { |
|
1131 if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, |
|
1132 nsGkAtoms::_true, eCaseMatters)) { |
|
1133 (*aIndex)++; |
|
1134 if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::container, |
|
1135 nsGkAtoms::_true, eCaseMatters) && |
|
1136 content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::open, |
|
1137 nsGkAtoms::_true, eCaseMatters)) { |
|
1138 nsIContent* child = |
|
1139 nsTreeUtils::GetImmediateChild(content, nsGkAtoms::treechildren); |
|
1140 if (child && child->IsXUL()) |
|
1141 GetIndexInSubtree(child, aContent, aIndex); |
|
1142 } |
|
1143 } |
|
1144 } |
|
1145 else if (tag == nsGkAtoms::treeseparator) { |
|
1146 if (! content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden, |
|
1147 nsGkAtoms::_true, eCaseMatters)) |
|
1148 (*aIndex)++; |
|
1149 } |
|
1150 } |
|
1151 } |
|
1152 } |
|
1153 |
|
1154 int32_t |
|
1155 nsTreeContentView::EnsureSubtree(int32_t aIndex) |
|
1156 { |
|
1157 Row* row = mRows[aIndex]; |
|
1158 |
|
1159 nsIContent* child; |
|
1160 child = nsTreeUtils::GetImmediateChild(row->mContent, nsGkAtoms::treechildren); |
|
1161 if (!child || !child->IsXUL()) { |
|
1162 return 0; |
|
1163 } |
|
1164 |
|
1165 nsAutoTArray<nsAutoPtr<Row>, 8> rows; |
|
1166 int32_t index = 0; |
|
1167 Serialize(child, aIndex, &index, rows); |
|
1168 // We can't use InsertElementsAt since the destination can't steal |
|
1169 // ownership from its const source argument. |
|
1170 for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) { |
|
1171 nsAutoPtr<Row>* newRow = mRows.InsertElementAt(aIndex + i + 1); |
|
1172 *newRow = rows[i]; |
|
1173 } |
|
1174 int32_t count = rows.Length(); |
|
1175 |
|
1176 row->mSubtreeSize += count; |
|
1177 UpdateSubtreeSizes(row->mParentIndex, count); |
|
1178 |
|
1179 // Update parent indexes, but skip newly added rows. |
|
1180 // They already have correct values. |
|
1181 UpdateParentIndexes(aIndex, count + 1, count); |
|
1182 |
|
1183 return count; |
|
1184 } |
|
1185 |
|
1186 int32_t |
|
1187 nsTreeContentView::RemoveSubtree(int32_t aIndex) |
|
1188 { |
|
1189 Row* row = mRows[aIndex]; |
|
1190 int32_t count = row->mSubtreeSize; |
|
1191 |
|
1192 mRows.RemoveElementsAt(aIndex + 1, count); |
|
1193 |
|
1194 row->mSubtreeSize -= count; |
|
1195 UpdateSubtreeSizes(row->mParentIndex, -count); |
|
1196 |
|
1197 UpdateParentIndexes(aIndex, 0, -count); |
|
1198 |
|
1199 return count; |
|
1200 } |
|
1201 |
|
1202 void |
|
1203 nsTreeContentView::InsertRowFor(nsIContent* aParent, nsIContent* aChild) |
|
1204 { |
|
1205 int32_t grandParentIndex = -1; |
|
1206 bool insertRow = false; |
|
1207 |
|
1208 nsCOMPtr<nsIContent> grandParent = aParent->GetParent(); |
|
1209 nsIAtom* grandParentTag = grandParent->Tag(); |
|
1210 |
|
1211 if (grandParent->IsXUL() && grandParentTag == nsGkAtoms::tree) { |
|
1212 // Allow insertion to the outermost container. |
|
1213 insertRow = true; |
|
1214 } |
|
1215 else { |
|
1216 // Test insertion to an inner container. |
|
1217 |
|
1218 // First try to find this parent in our array of rows, if we find one |
|
1219 // we can be sure that all other parents are open too. |
|
1220 grandParentIndex = FindContent(grandParent); |
|
1221 if (grandParentIndex >= 0) { |
|
1222 // Got it, now test if it is open. |
|
1223 if (mRows[grandParentIndex]->IsOpen()) |
|
1224 insertRow = true; |
|
1225 } |
|
1226 } |
|
1227 |
|
1228 if (insertRow) { |
|
1229 int32_t index = 0; |
|
1230 GetIndexInSubtree(aParent, aChild, &index); |
|
1231 |
|
1232 int32_t count = InsertRow(grandParentIndex, index, aChild); |
|
1233 if (mBoxObject) |
|
1234 mBoxObject->RowCountChanged(grandParentIndex + index + 1, count); |
|
1235 } |
|
1236 } |
|
1237 |
|
1238 int32_t |
|
1239 nsTreeContentView::InsertRow(int32_t aParentIndex, int32_t aIndex, nsIContent* aContent) |
|
1240 { |
|
1241 nsAutoTArray<nsAutoPtr<Row>, 8> rows; |
|
1242 nsIAtom *tag = aContent->Tag(); |
|
1243 if (aContent->IsXUL()) { |
|
1244 if (tag == nsGkAtoms::treeitem) |
|
1245 SerializeItem(aContent, aParentIndex, &aIndex, rows); |
|
1246 else if (tag == nsGkAtoms::treeseparator) |
|
1247 SerializeSeparator(aContent, aParentIndex, &aIndex, rows); |
|
1248 } |
|
1249 |
|
1250 // We can't use InsertElementsAt since the destination can't steal |
|
1251 // ownership from its const source argument. |
|
1252 for (nsTArray<Row>::index_type i = 0; i < rows.Length(); i++) { |
|
1253 nsAutoPtr<Row>* newRow = mRows.InsertElementAt(aParentIndex + aIndex + i + 1); |
|
1254 *newRow = rows[i]; |
|
1255 } |
|
1256 int32_t count = rows.Length(); |
|
1257 |
|
1258 UpdateSubtreeSizes(aParentIndex, count); |
|
1259 |
|
1260 // Update parent indexes, but skip added rows. |
|
1261 // They already have correct values. |
|
1262 UpdateParentIndexes(aParentIndex + aIndex, count + 1, count); |
|
1263 |
|
1264 return count; |
|
1265 } |
|
1266 |
|
1267 int32_t |
|
1268 nsTreeContentView::RemoveRow(int32_t aIndex) |
|
1269 { |
|
1270 Row* row = mRows[aIndex]; |
|
1271 int32_t count = row->mSubtreeSize + 1; |
|
1272 int32_t parentIndex = row->mParentIndex; |
|
1273 |
|
1274 mRows.RemoveElementsAt(aIndex, count); |
|
1275 |
|
1276 UpdateSubtreeSizes(parentIndex, -count); |
|
1277 |
|
1278 UpdateParentIndexes(aIndex, 0, -count); |
|
1279 |
|
1280 return count; |
|
1281 } |
|
1282 |
|
1283 void |
|
1284 nsTreeContentView::ClearRows() |
|
1285 { |
|
1286 mRows.Clear(); |
|
1287 mRoot = nullptr; |
|
1288 mBody = nullptr; |
|
1289 // Remove ourselves from mDocument's observers. |
|
1290 if (mDocument) { |
|
1291 mDocument->RemoveObserver(this); |
|
1292 mDocument = nullptr; |
|
1293 } |
|
1294 } |
|
1295 |
|
1296 void |
|
1297 nsTreeContentView::OpenContainer(int32_t aIndex) |
|
1298 { |
|
1299 Row* row = mRows[aIndex]; |
|
1300 row->SetOpen(true); |
|
1301 |
|
1302 int32_t count = EnsureSubtree(aIndex); |
|
1303 if (mBoxObject) { |
|
1304 mBoxObject->InvalidateRow(aIndex); |
|
1305 mBoxObject->RowCountChanged(aIndex + 1, count); |
|
1306 } |
|
1307 } |
|
1308 |
|
1309 void |
|
1310 nsTreeContentView::CloseContainer(int32_t aIndex) |
|
1311 { |
|
1312 Row* row = mRows[aIndex]; |
|
1313 row->SetOpen(false); |
|
1314 |
|
1315 int32_t count = RemoveSubtree(aIndex); |
|
1316 if (mBoxObject) { |
|
1317 mBoxObject->InvalidateRow(aIndex); |
|
1318 mBoxObject->RowCountChanged(aIndex + 1, -count); |
|
1319 } |
|
1320 } |
|
1321 |
|
1322 int32_t |
|
1323 nsTreeContentView::FindContent(nsIContent* aContent) |
|
1324 { |
|
1325 for (uint32_t i = 0; i < mRows.Length(); i++) { |
|
1326 if (mRows[i]->mContent == aContent) { |
|
1327 return i; |
|
1328 } |
|
1329 } |
|
1330 |
|
1331 return -1; |
|
1332 } |
|
1333 |
|
1334 void |
|
1335 nsTreeContentView::UpdateSubtreeSizes(int32_t aParentIndex, int32_t count) |
|
1336 { |
|
1337 while (aParentIndex >= 0) { |
|
1338 Row* row = mRows[aParentIndex]; |
|
1339 row->mSubtreeSize += count; |
|
1340 aParentIndex = row->mParentIndex; |
|
1341 } |
|
1342 } |
|
1343 |
|
1344 void |
|
1345 nsTreeContentView::UpdateParentIndexes(int32_t aIndex, int32_t aSkip, int32_t aCount) |
|
1346 { |
|
1347 int32_t count = mRows.Length(); |
|
1348 for (int32_t i = aIndex + aSkip; i < count; i++) { |
|
1349 Row* row = mRows[i]; |
|
1350 if (row->mParentIndex > aIndex) { |
|
1351 row->mParentIndex += aCount; |
|
1352 } |
|
1353 } |
|
1354 } |
|
1355 |
|
1356 nsIContent* |
|
1357 nsTreeContentView::GetCell(nsIContent* aContainer, nsITreeColumn* aCol) |
|
1358 { |
|
1359 nsCOMPtr<nsIAtom> colAtom; |
|
1360 int32_t colIndex; |
|
1361 aCol->GetAtom(getter_AddRefs(colAtom)); |
|
1362 aCol->GetIndex(&colIndex); |
|
1363 |
|
1364 // Traverse through cells, try to find the cell by "ref" attribute or by cell |
|
1365 // index in a row. "ref" attribute has higher priority. |
|
1366 nsIContent* result = nullptr; |
|
1367 int32_t j = 0; |
|
1368 dom::FlattenedChildIterator iter(aContainer); |
|
1369 for (nsIContent* cell = iter.GetNextChild(); cell; cell = iter.GetNextChild()) { |
|
1370 if (cell->Tag() == nsGkAtoms::treecell) { |
|
1371 if (colAtom && cell->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ref, |
|
1372 colAtom, eCaseMatters)) { |
|
1373 result = cell; |
|
1374 break; |
|
1375 } |
|
1376 else if (j == colIndex) { |
|
1377 result = cell; |
|
1378 } |
|
1379 j++; |
|
1380 } |
|
1381 } |
|
1382 |
|
1383 return result; |
|
1384 } |