Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
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/. */
6 #include "mozilla/AsyncEventDispatcher.h"
7 #include "mozilla/ContentEvents.h"
8 #include "mozilla/DebugOnly.h"
9 #include "mozilla/EventDispatcher.h"
10 #include "mozilla/MathAlgorithms.h"
11 #include "mozilla/MouseEvents.h"
12 #include "mozilla/Likely.h"
14 #include "nsCOMPtr.h"
15 #include "nsPresContext.h"
16 #include "nsNameSpaceManager.h"
18 #include "nsTreeBodyFrame.h"
19 #include "nsTreeSelection.h"
20 #include "nsTreeImageListener.h"
22 #include "nsGkAtoms.h"
23 #include "nsCSSAnonBoxes.h"
25 #include "nsIContent.h"
26 #include "nsStyleContext.h"
27 #include "nsIBoxObject.h"
28 #include "nsIDOMCustomEvent.h"
29 #include "nsIDOMMouseEvent.h"
30 #include "nsIDOMElement.h"
31 #include "nsIDOMNodeList.h"
32 #include "nsIDOMDocument.h"
33 #include "nsIDOMXULElement.h"
34 #include "nsIDocument.h"
35 #include "mozilla/css/StyleRule.h"
36 #include "nsCSSRendering.h"
37 #include "nsIXULTemplateBuilder.h"
38 #include "nsXPIDLString.h"
39 #include "nsContainerFrame.h"
40 #include "nsView.h"
41 #include "nsViewManager.h"
42 #include "nsWidgetsCID.h"
43 #include "nsBoxFrame.h"
44 #include "nsBoxObject.h"
45 #include "nsIURL.h"
46 #include "nsNetUtil.h"
47 #include "nsBoxLayoutState.h"
48 #include "nsTreeContentView.h"
49 #include "nsTreeUtils.h"
50 #include "nsITheme.h"
51 #include "imgIRequest.h"
52 #include "imgIContainer.h"
53 #include "imgILoader.h"
54 #include "nsINodeInfo.h"
55 #include "nsContentUtils.h"
56 #include "nsLayoutUtils.h"
57 #include "nsIScrollableFrame.h"
58 #include "nsDisplayList.h"
59 #include "nsTreeBoxObject.h"
60 #include "nsRenderingContext.h"
61 #include "nsIScriptableRegion.h"
62 #include <algorithm>
63 #include "ScrollbarActivity.h"
65 #ifdef ACCESSIBILITY
66 #include "nsAccessibilityService.h"
67 #include "nsIWritablePropertyBag2.h"
68 #endif
69 #include "nsBidiUtils.h"
71 using namespace mozilla;
72 using namespace mozilla::layout;
74 // Enumeration function that cancels all the image requests in our cache
75 static PLDHashOperator
76 CancelImageRequest(const nsAString& aKey,
77 nsTreeImageCacheEntry aEntry, void* aData)
78 {
80 // If our imgIRequest object was registered with the refresh driver,
81 // then we need to deregister it.
82 nsTreeBodyFrame* frame = static_cast<nsTreeBodyFrame*>(aData);
84 nsLayoutUtils::DeregisterImageRequest(frame->PresContext(), aEntry.request,
85 nullptr);
87 aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
88 return PL_DHASH_NEXT;
89 }
91 //
92 // NS_NewTreeFrame
93 //
94 // Creates a new tree frame
95 //
96 nsIFrame*
97 NS_NewTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
98 {
99 return new (aPresShell) nsTreeBodyFrame(aPresShell, aContext);
100 }
102 NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame)
104 NS_QUERYFRAME_HEAD(nsTreeBodyFrame)
105 NS_QUERYFRAME_ENTRY(nsIScrollbarMediator)
106 NS_QUERYFRAME_ENTRY(nsIScrollbarOwner)
107 NS_QUERYFRAME_ENTRY(nsTreeBodyFrame)
108 NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
110 // Constructor
111 nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
112 :nsLeafBoxFrame(aPresShell, aContext),
113 mSlots(nullptr),
114 mImageCache(16),
115 mTopRowIndex(0),
116 mPageLength(0),
117 mHorzPosition(0),
118 mOriginalHorzWidth(-1),
119 mHorzWidth(0),
120 mAdjustWidth(0),
121 mRowHeight(0),
122 mIndentation(0),
123 mStringWidth(-1),
124 mUpdateBatchNest(0),
125 mRowCount(0),
126 mMouseOverRow(-1),
127 mFocused(false),
128 mHasFixedRowCount(false),
129 mVerticalOverflow(false),
130 mHorizontalOverflow(false),
131 mReflowCallbackPosted(false),
132 mCheckingOverflow(false)
133 {
134 mColumns = new nsTreeColumns(this);
135 }
137 // Destructor
138 nsTreeBodyFrame::~nsTreeBodyFrame()
139 {
140 mImageCache.EnumerateRead(CancelImageRequest, this);
141 DetachImageListeners();
142 delete mSlots;
143 }
145 static void
146 GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin)
147 {
148 aMargin.SizeTo(0, 0, 0, 0);
149 if (!aContext->StylePadding()->GetPadding(aMargin)) {
150 NS_NOTYETIMPLEMENTED("percentage padding");
151 }
152 aMargin += aContext->StyleBorder()->GetComputedBorder();
153 }
155 static void
156 AdjustForBorderPadding(nsStyleContext* aContext, nsRect& aRect)
157 {
158 nsMargin borderPadding(0, 0, 0, 0);
159 GetBorderPadding(aContext, borderPadding);
160 aRect.Deflate(borderPadding);
161 }
163 void
164 nsTreeBodyFrame::Init(nsIContent* aContent,
165 nsIFrame* aParent,
166 nsIFrame* aPrevInFlow)
167 {
168 nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
170 mIndentation = GetIndentation();
171 mRowHeight = GetRowHeight();
173 EnsureBoxObject();
175 if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
176 mScrollbarActivity = new ScrollbarActivity(
177 static_cast<nsIScrollbarOwner*>(this));
178 }
179 }
181 nsSize
182 nsTreeBodyFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState)
183 {
184 EnsureView();
186 nsIContent* baseElement = GetBaseElement();
188 nsSize min(0,0);
189 int32_t desiredRows;
190 if (MOZ_UNLIKELY(!baseElement)) {
191 desiredRows = 0;
192 }
193 else if (baseElement->Tag() == nsGkAtoms::select &&
194 baseElement->IsHTML()) {
195 min.width = CalcMaxRowWidth();
196 nsAutoString size;
197 baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::size, size);
198 if (!size.IsEmpty()) {
199 nsresult err;
200 desiredRows = size.ToInteger(&err);
201 mHasFixedRowCount = true;
202 mPageLength = desiredRows;
203 }
204 else {
205 desiredRows = 1;
206 }
207 }
208 else {
209 // tree
210 nsAutoString rows;
211 baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows);
212 if (!rows.IsEmpty()) {
213 nsresult err;
214 desiredRows = rows.ToInteger(&err);
215 mPageLength = desiredRows;
216 }
217 else {
218 desiredRows = 0;
219 }
220 }
222 min.height = mRowHeight * desiredRows;
224 AddBorderAndPadding(min);
225 bool widthSet, heightSet;
226 nsIFrame::AddCSSMinSize(aBoxLayoutState, this, min, widthSet, heightSet);
228 return min;
229 }
231 nscoord
232 nsTreeBodyFrame::CalcMaxRowWidth()
233 {
234 if (mStringWidth != -1)
235 return mStringWidth;
237 if (!mView)
238 return 0;
240 nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
241 nsMargin rowMargin(0,0,0,0);
242 GetBorderPadding(rowContext, rowMargin);
244 nscoord rowWidth;
245 nsTreeColumn* col;
247 nsRefPtr<nsRenderingContext> rc =
248 PresContext()->PresShell()->CreateReferenceRenderingContext();
250 for (int32_t row = 0; row < mRowCount; ++row) {
251 rowWidth = 0;
253 for (col = mColumns->GetFirstColumn(); col; col = col->GetNext()) {
254 nscoord desiredWidth, currentWidth;
255 nsresult rv = GetCellWidth(row, col, rc, desiredWidth, currentWidth);
256 if (NS_FAILED(rv)) {
257 NS_NOTREACHED("invalid column");
258 continue;
259 }
260 rowWidth += desiredWidth;
261 }
263 if (rowWidth > mStringWidth)
264 mStringWidth = rowWidth;
265 }
267 mStringWidth += rowMargin.left + rowMargin.right;
268 return mStringWidth;
269 }
271 void
272 nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot)
273 {
274 if (mScrollbarActivity) {
275 mScrollbarActivity->Destroy();
276 mScrollbarActivity = nullptr;
277 }
279 mScrollEvent.Revoke();
280 // Make sure we cancel any posted callbacks.
281 if (mReflowCallbackPosted) {
282 PresContext()->PresShell()->CancelReflowCallback(this);
283 mReflowCallbackPosted = false;
284 }
286 if (mColumns)
287 mColumns->SetTree(nullptr);
289 // Save off our info into the box object.
290 nsCOMPtr<nsPIBoxObject> box(do_QueryInterface(mTreeBoxObject));
291 if (box) {
292 if (mTopRowIndex > 0) {
293 nsAutoString topRowStr; topRowStr.AssignLiteral("topRow");
294 nsAutoString topRow;
295 topRow.AppendInt(mTopRowIndex);
296 box->SetProperty(topRowStr.get(), topRow.get());
297 }
299 // Always null out the cached tree body frame.
300 box->ClearCachedValues();
302 mTreeBoxObject = nullptr; // Drop our ref here.
303 }
305 if (mView) {
306 nsCOMPtr<nsITreeSelection> sel;
307 mView->GetSelection(getter_AddRefs(sel));
308 if (sel)
309 sel->SetTree(nullptr);
310 mView->SetTree(nullptr);
311 mView = nullptr;
312 }
314 nsLeafBoxFrame::DestroyFrom(aDestructRoot);
315 }
317 void
318 nsTreeBodyFrame::EnsureBoxObject()
319 {
320 if (!mTreeBoxObject) {
321 nsIContent* parent = GetBaseElement();
322 if (parent) {
323 nsIDocument* nsDoc = parent->GetDocument();
324 if (!nsDoc) // there may be no document, if we're called from Destroy()
325 return;
326 ErrorResult ignored;
327 nsCOMPtr<nsIBoxObject> box =
328 nsDoc->GetBoxObjectFor(parent->AsElement(), ignored);
329 // Ensure that we got a native box object.
330 nsCOMPtr<nsPIBoxObject> pBox = do_QueryInterface(box);
331 if (pBox) {
332 nsCOMPtr<nsITreeBoxObject> realTreeBoxObject = do_QueryInterface(pBox);
333 if (realTreeBoxObject) {
334 nsTreeBodyFrame* innerTreeBoxObject =
335 static_cast<nsTreeBoxObject*>(realTreeBoxObject.get())
336 ->GetCachedTreeBody();
337 ENSURE_TRUE(!innerTreeBoxObject || innerTreeBoxObject == this);
338 mTreeBoxObject = realTreeBoxObject;
339 }
340 }
341 }
342 }
343 }
345 void
346 nsTreeBodyFrame::EnsureView()
347 {
348 if (!mView) {
349 if (PresContext()->PresShell()->IsReflowLocked()) {
350 if (!mReflowCallbackPosted) {
351 mReflowCallbackPosted = true;
352 PresContext()->PresShell()->PostReflowCallback(this);
353 }
354 return;
355 }
356 nsCOMPtr<nsIBoxObject> box = do_QueryInterface(mTreeBoxObject);
357 if (box) {
358 nsWeakFrame weakFrame(this);
359 nsCOMPtr<nsITreeView> treeView;
360 mTreeBoxObject->GetView(getter_AddRefs(treeView));
361 if (treeView && weakFrame.IsAlive()) {
362 nsXPIDLString rowStr;
363 box->GetProperty(MOZ_UTF16("topRow"),
364 getter_Copies(rowStr));
365 nsAutoString rowStr2(rowStr);
366 nsresult error;
367 int32_t rowIndex = rowStr2.ToInteger(&error);
369 // Set our view.
370 SetView(treeView);
371 ENSURE_TRUE(weakFrame.IsAlive());
373 // Scroll to the given row.
374 // XXX is this optimal if we haven't laid out yet?
375 ScrollToRow(rowIndex);
376 ENSURE_TRUE(weakFrame.IsAlive());
378 // Clear out the property info for the top row, but we always keep the
379 // view current.
380 box->RemoveProperty(MOZ_UTF16("topRow"));
381 }
382 }
383 }
384 }
386 void
387 nsTreeBodyFrame::ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth)
388 {
389 if (!mReflowCallbackPosted &&
390 (!aRect.IsEqualEdges(mRect) || mHorzWidth != aHorzWidth)) {
391 PresContext()->PresShell()->PostReflowCallback(this);
392 mReflowCallbackPosted = true;
393 mOriginalHorzWidth = mHorzWidth;
394 }
395 else if (mReflowCallbackPosted &&
396 mHorzWidth != aHorzWidth && mOriginalHorzWidth == aHorzWidth) {
397 PresContext()->PresShell()->CancelReflowCallback(this);
398 mReflowCallbackPosted = false;
399 mOriginalHorzWidth = -1;
400 }
401 }
403 void
404 nsTreeBodyFrame::SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect,
405 bool aRemoveOverflowArea)
406 {
407 nscoord horzWidth = CalcHorzWidth(GetScrollParts());
408 ManageReflowCallback(aRect, horzWidth);
409 mHorzWidth = horzWidth;
411 nsLeafBoxFrame::SetBounds(aBoxLayoutState, aRect, aRemoveOverflowArea);
412 }
415 bool
416 nsTreeBodyFrame::ReflowFinished()
417 {
418 if (!mView) {
419 nsWeakFrame weakFrame(this);
420 EnsureView();
421 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
422 }
423 if (mView) {
424 CalcInnerBox();
425 ScrollParts parts = GetScrollParts();
426 mHorzWidth = CalcHorzWidth(parts);
427 if (!mHasFixedRowCount) {
428 mPageLength = mInnerBox.height / mRowHeight;
429 }
431 int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength);
432 if (mTopRowIndex > lastPageTopRow)
433 ScrollToRowInternal(parts, lastPageTopRow);
435 nsIContent *treeContent = GetBaseElement();
436 if (treeContent &&
437 treeContent->AttrValueIs(kNameSpaceID_None,
438 nsGkAtoms::keepcurrentinview,
439 nsGkAtoms::_true, eCaseMatters)) {
440 // make sure that the current selected item is still
441 // visible after the tree changes size.
442 nsCOMPtr<nsITreeSelection> sel;
443 mView->GetSelection(getter_AddRefs(sel));
444 if (sel) {
445 int32_t currentIndex;
446 sel->GetCurrentIndex(¤tIndex);
447 if (currentIndex != -1)
448 EnsureRowIsVisibleInternal(parts, currentIndex);
449 }
450 }
452 if (!FullScrollbarsUpdate(false)) {
453 return false;
454 }
455 }
457 mReflowCallbackPosted = false;
458 return false;
459 }
461 void
462 nsTreeBodyFrame::ReflowCallbackCanceled()
463 {
464 mReflowCallbackPosted = false;
465 }
467 nsresult
468 nsTreeBodyFrame::GetView(nsITreeView * *aView)
469 {
470 *aView = nullptr;
471 nsWeakFrame weakFrame(this);
472 EnsureView();
473 NS_ENSURE_STATE(weakFrame.IsAlive());
474 NS_IF_ADDREF(*aView = mView);
475 return NS_OK;
476 }
478 nsresult
479 nsTreeBodyFrame::SetView(nsITreeView * aView)
480 {
481 // First clear out the old view.
482 if (mView) {
483 nsCOMPtr<nsITreeSelection> sel;
484 mView->GetSelection(getter_AddRefs(sel));
485 if (sel)
486 sel->SetTree(nullptr);
487 mView->SetTree(nullptr);
489 // Only reset the top row index and delete the columns if we had an old non-null view.
490 mTopRowIndex = 0;
491 }
493 // Tree, meet the view.
494 mView = aView;
496 // Changing the view causes us to refetch our data. This will
497 // necessarily entail a full invalidation of the tree.
498 Invalidate();
500 nsIContent *treeContent = GetBaseElement();
501 if (treeContent) {
502 #ifdef ACCESSIBILITY
503 nsAccessibilityService* accService = nsIPresShell::AccService();
504 if (accService)
505 accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent, mView);
506 #endif
507 FireDOMEvent(NS_LITERAL_STRING("TreeViewChanged"), treeContent);
508 }
510 if (mView) {
511 // Give the view a new empty selection object to play with, but only if it
512 // doesn't have one already.
513 nsCOMPtr<nsITreeSelection> sel;
514 mView->GetSelection(getter_AddRefs(sel));
515 if (sel) {
516 sel->SetTree(mTreeBoxObject);
517 }
518 else {
519 NS_NewTreeSelection(mTreeBoxObject, getter_AddRefs(sel));
520 mView->SetSelection(sel);
521 }
523 // View, meet the tree.
524 nsWeakFrame weakFrame(this);
525 mView->SetTree(mTreeBoxObject);
526 NS_ENSURE_STATE(weakFrame.IsAlive());
527 mView->GetRowCount(&mRowCount);
529 if (!PresContext()->PresShell()->IsReflowLocked()) {
530 // The scrollbar will need to be updated.
531 FullScrollbarsUpdate(false);
532 } else if (!mReflowCallbackPosted) {
533 mReflowCallbackPosted = true;
534 PresContext()->PresShell()->PostReflowCallback(this);
535 }
536 }
538 return NS_OK;
539 }
541 nsresult
542 nsTreeBodyFrame::GetFocused(bool* aFocused)
543 {
544 *aFocused = mFocused;
545 return NS_OK;
546 }
548 nsresult
549 nsTreeBodyFrame::SetFocused(bool aFocused)
550 {
551 if (mFocused != aFocused) {
552 mFocused = aFocused;
553 if (mView) {
554 nsCOMPtr<nsITreeSelection> sel;
555 mView->GetSelection(getter_AddRefs(sel));
556 if (sel)
557 sel->InvalidateSelection();
558 }
559 }
560 return NS_OK;
561 }
563 nsresult
564 nsTreeBodyFrame::GetTreeBody(nsIDOMElement** aElement)
565 {
566 //NS_ASSERTION(mContent, "no content, see bug #104878");
567 if (!mContent)
568 return NS_ERROR_NULL_POINTER;
570 return mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)aElement);
571 }
573 nsresult
574 nsTreeBodyFrame::GetRowHeight(int32_t* _retval)
575 {
576 *_retval = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
577 return NS_OK;
578 }
580 nsresult
581 nsTreeBodyFrame::GetRowWidth(int32_t *aRowWidth)
582 {
583 *aRowWidth = nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts()));
584 return NS_OK;
585 }
587 nsresult
588 nsTreeBodyFrame::GetHorizontalPosition(int32_t *aHorizontalPosition)
589 {
590 *aHorizontalPosition = nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition);
591 return NS_OK;
592 }
594 nsresult
595 nsTreeBodyFrame::GetSelectionRegion(nsIScriptableRegion **aRegion)
596 {
597 *aRegion = nullptr;
599 nsCOMPtr<nsITreeSelection> selection;
600 mView->GetSelection(getter_AddRefs(selection));
601 NS_ENSURE_TRUE(selection, NS_OK);
603 nsCOMPtr<nsIScriptableRegion> region = do_CreateInstance("@mozilla.org/gfx/region;1");
604 NS_ENSURE_TRUE(region, NS_ERROR_FAILURE);
605 region->Init();
607 nsRefPtr<nsPresContext> presContext = PresContext();
608 nsIntRect rect = mRect.ToOutsidePixels(presContext->AppUnitsPerCSSPixel());
610 nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame();
611 nsPoint origin = GetOffsetTo(rootFrame);
613 // iterate through the visible rows and add the selected ones to the
614 // drag region
615 int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
616 int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
617 int32_t top = y;
618 int32_t end = LastVisibleRow();
619 int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
620 for (int32_t i = mTopRowIndex; i <= end; i++) {
621 bool isSelected;
622 selection->IsSelected(i, &isSelected);
623 if (isSelected)
624 region->UnionRect(x, y, rect.width, rowHeight);
625 y += rowHeight;
626 }
628 // clip to the tree boundary in case one row extends past it
629 region->IntersectRect(x, top, rect.width, rect.height);
631 NS_ADDREF(*aRegion = region);
632 return NS_OK;
633 }
635 nsresult
636 nsTreeBodyFrame::Invalidate()
637 {
638 if (mUpdateBatchNest)
639 return NS_OK;
641 InvalidateFrame();
643 return NS_OK;
644 }
646 nsresult
647 nsTreeBodyFrame::InvalidateColumn(nsITreeColumn* aCol)
648 {
649 if (mUpdateBatchNest)
650 return NS_OK;
652 nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
653 if (!col)
654 return NS_ERROR_INVALID_ARG;
656 #ifdef ACCESSIBILITY
657 if (nsIPresShell::IsAccessibilityActive())
658 FireInvalidateEvent(-1, -1, aCol, aCol);
659 #endif
661 nsRect columnRect;
662 nsresult rv = col->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect);
663 NS_ENSURE_SUCCESS(rv, rv);
665 // When false then column is out of view
666 if (OffsetForHorzScroll(columnRect, true))
667 InvalidateFrameWithRect(columnRect);
669 return NS_OK;
670 }
672 nsresult
673 nsTreeBodyFrame::InvalidateRow(int32_t aIndex)
674 {
675 if (mUpdateBatchNest)
676 return NS_OK;
678 #ifdef ACCESSIBILITY
679 if (nsIPresShell::IsAccessibilityActive())
680 FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr);
681 #endif
683 aIndex -= mTopRowIndex;
684 if (aIndex < 0 || aIndex > mPageLength)
685 return NS_OK;
687 nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*aIndex, mInnerBox.width, mRowHeight);
688 InvalidateFrameWithRect(rowRect);
690 return NS_OK;
691 }
693 nsresult
694 nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsITreeColumn* aCol)
695 {
696 if (mUpdateBatchNest)
697 return NS_OK;
699 #ifdef ACCESSIBILITY
700 if (nsIPresShell::IsAccessibilityActive())
701 FireInvalidateEvent(aIndex, aIndex, aCol, aCol);
702 #endif
704 aIndex -= mTopRowIndex;
705 if (aIndex < 0 || aIndex > mPageLength)
706 return NS_OK;
708 nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
709 if (!col)
710 return NS_ERROR_INVALID_ARG;
712 nsRect cellRect;
713 nsresult rv = col->GetRect(this, mInnerBox.y+mRowHeight*aIndex, mRowHeight,
714 &cellRect);
715 NS_ENSURE_SUCCESS(rv, rv);
717 if (OffsetForHorzScroll(cellRect, true))
718 InvalidateFrameWithRect(cellRect);
720 return NS_OK;
721 }
723 nsresult
724 nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd)
725 {
726 if (mUpdateBatchNest)
727 return NS_OK;
729 if (aStart == aEnd)
730 return InvalidateRow(aStart);
732 int32_t last = LastVisibleRow();
733 if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last)
734 return NS_OK;
736 if (aStart < mTopRowIndex)
737 aStart = mTopRowIndex;
739 if (aEnd > last)
740 aEnd = last;
742 #ifdef ACCESSIBILITY
743 if (nsIPresShell::IsAccessibilityActive()) {
744 int32_t end =
745 mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
746 FireInvalidateEvent(aStart, end, nullptr, nullptr);
747 }
748 #endif
750 nsRect rangeRect(mInnerBox.x, mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), mInnerBox.width, mRowHeight*(aEnd-aStart+1));
751 InvalidateFrameWithRect(rangeRect);
753 return NS_OK;
754 }
756 nsresult
757 nsTreeBodyFrame::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol)
758 {
759 if (mUpdateBatchNest)
760 return NS_OK;
762 nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
763 if (!col)
764 return NS_ERROR_INVALID_ARG;
766 if (aStart == aEnd)
767 return InvalidateCell(aStart, col);
769 int32_t last = LastVisibleRow();
770 if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last)
771 return NS_OK;
773 if (aStart < mTopRowIndex)
774 aStart = mTopRowIndex;
776 if (aEnd > last)
777 aEnd = last;
779 #ifdef ACCESSIBILITY
780 if (nsIPresShell::IsAccessibilityActive()) {
781 int32_t end =
782 mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0;
783 FireInvalidateEvent(aStart, end, aCol, aCol);
784 }
785 #endif
787 nsRect rangeRect;
788 nsresult rv = col->GetRect(this,
789 mInnerBox.y+mRowHeight*(aStart-mTopRowIndex),
790 mRowHeight*(aEnd-aStart+1),
791 &rangeRect);
792 NS_ENSURE_SUCCESS(rv, rv);
794 InvalidateFrameWithRect(rangeRect);
796 return NS_OK;
797 }
799 static void
800 FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult)
801 {
802 if (!aResult->mColumnsScrollFrame) {
803 nsIScrollableFrame* f = do_QueryFrame(aCurrFrame);
804 if (f) {
805 aResult->mColumnsFrame = aCurrFrame;
806 aResult->mColumnsScrollFrame = f;
807 }
808 }
810 nsScrollbarFrame *sf = do_QueryFrame(aCurrFrame);
811 if (sf) {
812 if (!aCurrFrame->IsHorizontal()) {
813 if (!aResult->mVScrollbar) {
814 aResult->mVScrollbar = sf;
815 }
816 } else {
817 if (!aResult->mHScrollbar) {
818 aResult->mHScrollbar = sf;
819 }
820 }
821 // don't bother searching inside a scrollbar
822 return;
823 }
825 nsIFrame* child = aCurrFrame->GetFirstPrincipalChild();
826 while (child &&
827 !child->GetContent()->IsRootOfNativeAnonymousSubtree() &&
828 (!aResult->mVScrollbar || !aResult->mHScrollbar ||
829 !aResult->mColumnsScrollFrame)) {
830 FindScrollParts(child, aResult);
831 child = child->GetNextSibling();
832 }
833 }
835 nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts()
836 {
837 ScrollParts result = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr };
838 nsIContent* baseElement = GetBaseElement();
839 nsIFrame* treeFrame =
840 baseElement ? baseElement->GetPrimaryFrame() : nullptr;
841 if (treeFrame) {
842 // The way we do this, searching through the entire frame subtree, is pretty
843 // dumb! We should know where these frames are.
844 FindScrollParts(treeFrame, &result);
845 if (result.mHScrollbar) {
846 result.mHScrollbar->SetScrollbarMediatorContent(GetContent());
847 nsIFrame* f = do_QueryFrame(result.mHScrollbar);
848 result.mHScrollbarContent = f->GetContent();
849 }
850 if (result.mVScrollbar) {
851 result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
852 nsIFrame* f = do_QueryFrame(result.mVScrollbar);
853 result.mVScrollbarContent = f->GetContent();
854 }
855 }
856 return result;
857 }
859 void
860 nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts)
861 {
862 nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
864 nsWeakFrame weakFrame(this);
866 if (aParts.mVScrollbar) {
867 nsAutoString curPos;
868 curPos.AppendInt(mTopRowIndex*rowHeightAsPixels);
869 aParts.mVScrollbarContent->
870 SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
871 // 'this' might be deleted here
872 }
874 if (weakFrame.IsAlive() && aParts.mHScrollbar) {
875 nsAutoString curPos;
876 curPos.AppendInt(mHorzPosition);
877 aParts.mHScrollbarContent->
878 SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true);
879 // 'this' might be deleted here
880 }
882 if (weakFrame.IsAlive() && mScrollbarActivity) {
883 mScrollbarActivity->ActivityOccurred();
884 }
885 }
887 void
888 nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts)
889 {
890 bool verticalOverflowChanged = false;
891 bool horizontalOverflowChanged = false;
893 if (!mVerticalOverflow && mRowCount > mPageLength) {
894 mVerticalOverflow = true;
895 verticalOverflowChanged = true;
896 }
897 else if (mVerticalOverflow && mRowCount <= mPageLength) {
898 mVerticalOverflow = false;
899 verticalOverflowChanged = true;
900 }
902 if (aParts.mColumnsFrame) {
903 nsRect bounds = aParts.mColumnsFrame->GetRect();
904 if (bounds.width != 0) {
905 /* Ignore overflows that are less than half a pixel. Yes these happen
906 all over the place when flex boxes are compressed real small.
907 Probably a result of a rounding errors somewhere in the layout code. */
908 bounds.width += nsPresContext::CSSPixelsToAppUnits(0.5f);
909 if (!mHorizontalOverflow && bounds.width < mHorzWidth) {
910 mHorizontalOverflow = true;
911 horizontalOverflowChanged = true;
912 } else if (mHorizontalOverflow && bounds.width >= mHorzWidth) {
913 mHorizontalOverflow = false;
914 horizontalOverflowChanged = true;
915 }
916 }
917 }
919 nsWeakFrame weakFrame(this);
921 nsRefPtr<nsPresContext> presContext = PresContext();
922 nsCOMPtr<nsIPresShell> presShell = presContext->GetPresShell();
923 nsCOMPtr<nsIContent> content = mContent;
925 if (verticalOverflowChanged) {
926 InternalScrollPortEvent event(true,
927 mVerticalOverflow ? NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW,
928 nullptr);
929 event.orient = InternalScrollPortEvent::vertical;
930 EventDispatcher::Dispatch(content, presContext, &event);
931 }
933 if (horizontalOverflowChanged) {
934 InternalScrollPortEvent event(true,
935 mHorizontalOverflow ? NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW,
936 nullptr);
937 event.orient = InternalScrollPortEvent::horizontal;
938 EventDispatcher::Dispatch(content, presContext, &event);
939 }
941 // The synchronous event dispatch above can trigger reflow notifications.
942 // Flush those explicitly now, so that we can guard against potential infinite
943 // recursion. See bug 905909.
944 if (!weakFrame.IsAlive()) {
945 return;
946 }
947 NS_ASSERTION(!mCheckingOverflow, "mCheckingOverflow should not already be set");
948 // Don't use AutoRestore since we want to not touch mCheckingOverflow if we fail
949 // the weakFrame.IsAlive() check below
950 mCheckingOverflow = true;
951 presShell->FlushPendingNotifications(Flush_Layout);
952 if (!weakFrame.IsAlive()) {
953 return;
954 }
955 mCheckingOverflow = false;
956 }
958 void
959 nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame)
960 {
961 if (mUpdateBatchNest || !mView)
962 return;
963 nsWeakFrame weakFrame(this);
965 if (aParts.mVScrollbar) {
966 // Do Vertical Scrollbar
967 nsAutoString maxposStr;
969 nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
971 int32_t size = rowHeightAsPixels * (mRowCount > mPageLength ? mRowCount - mPageLength : 0);
972 maxposStr.AppendInt(size);
973 aParts.mVScrollbarContent->
974 SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true);
975 ENSURE_TRUE(weakFrame.IsAlive());
977 // Also set our page increment and decrement.
978 nscoord pageincrement = mPageLength*rowHeightAsPixels;
979 nsAutoString pageStr;
980 pageStr.AppendInt(pageincrement);
981 aParts.mVScrollbarContent->
982 SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
983 ENSURE_TRUE(weakFrame.IsAlive());
984 }
986 if (aParts.mHScrollbar && aParts.mColumnsFrame && aWeakColumnsFrame.IsAlive()) {
987 // And now Horizontal scrollbar
988 nsRect bounds = aParts.mColumnsFrame->GetRect();
989 nsAutoString maxposStr;
991 maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width : 0);
992 aParts.mHScrollbarContent->
993 SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true);
994 ENSURE_TRUE(weakFrame.IsAlive());
996 nsAutoString pageStr;
997 pageStr.AppendInt(bounds.width);
998 aParts.mHScrollbarContent->
999 SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true);
1000 ENSURE_TRUE(weakFrame.IsAlive());
1002 pageStr.Truncate();
1003 pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16));
1004 aParts.mHScrollbarContent->
1005 SetAttr(kNameSpaceID_None, nsGkAtoms::increment, pageStr, true);
1006 }
1008 if (weakFrame.IsAlive() && mScrollbarActivity) {
1009 mScrollbarActivity->ActivityOccurred();
1010 }
1011 }
1013 // Takes client x/y in pixels, converts them to appunits, and converts into
1014 // values relative to this nsTreeBodyFrame frame.
1015 nsPoint
1016 nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY)
1017 {
1018 nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
1019 nsPresContext::CSSPixelsToAppUnits(aY));
1021 nsPresContext* presContext = PresContext();
1022 point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame());
1024 // Adjust by the inner box coords, so that we're in the inner box's
1025 // coordinate space.
1026 point -= mInnerBox.TopLeft();
1027 return point;
1028 } // AdjustClientCoordsToBoxCoordSpace
1030 nsresult
1031 nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY, int32_t* _retval)
1032 {
1033 if (!mView)
1034 return NS_OK;
1036 nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
1038 // Check if the coordinates are above our visible space.
1039 if (point.y < 0) {
1040 *_retval = -1;
1041 return NS_OK;
1042 }
1044 *_retval = GetRowAt(point.x, point.y);
1046 return NS_OK;
1047 }
1049 nsresult
1050 nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow, nsITreeColumn** aCol,
1051 nsACString& aChildElt)
1052 {
1053 if (!mView)
1054 return NS_OK;
1056 nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY);
1058 // Check if the coordinates are above our visible space.
1059 if (point.y < 0) {
1060 *aRow = -1;
1061 return NS_OK;
1062 }
1064 nsTreeColumn* col;
1065 nsIAtom* child;
1066 GetCellAt(point.x, point.y, aRow, &col, &child);
1068 if (col) {
1069 NS_ADDREF(*aCol = col);
1070 if (child == nsCSSAnonBoxes::moztreecell)
1071 aChildElt.AssignLiteral("cell");
1072 else if (child == nsCSSAnonBoxes::moztreetwisty)
1073 aChildElt.AssignLiteral("twisty");
1074 else if (child == nsCSSAnonBoxes::moztreeimage)
1075 aChildElt.AssignLiteral("image");
1076 else if (child == nsCSSAnonBoxes::moztreecelltext)
1077 aChildElt.AssignLiteral("text");
1078 }
1080 return NS_OK;
1081 }
1084 //
1085 // GetCoordsForCellItem
1086 //
1087 // Find the x/y location and width/height (all in PIXELS) of the given object
1088 // in the given column.
1089 //
1090 // XXX IMPORTANT XXX:
1091 // Hyatt says in the bug for this, that the following needs to be done:
1092 // (1) You need to deal with overflow when computing cell rects. See other column
1093 // iteration examples... if you don't deal with this, you'll mistakenly extend the
1094 // cell into the scrollbar's rect.
1095 //
1096 // (2) You are adjusting the cell rect by the *row" border padding. That's
1097 // wrong. You need to first adjust a row rect by its border/padding, and then the
1098 // cell rect fits inside the adjusted row rect. It also can have border/padding
1099 // as well as margins. The vertical direction isn't that important, but you need
1100 // to get the horizontal direction right.
1101 //
1102 // (3) GetImageSize() does not include margins (but it does include border/padding).
1103 // You need to make sure to add in the image's margins as well.
1104 //
1105 nsresult
1106 nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsACString& aElement,
1107 int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight)
1108 {
1109 *aX = 0;
1110 *aY = 0;
1111 *aWidth = 0;
1112 *aHeight = 0;
1114 bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
1115 nscoord currX = mInnerBox.x - mHorzPosition;
1117 // The Rect for the requested item.
1118 nsRect theRect;
1120 nsPresContext* presContext = PresContext();
1122 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; currCol = currCol->GetNext()) {
1124 // The Rect for the current cell.
1125 nscoord colWidth;
1126 #ifdef DEBUG
1127 nsresult rv =
1128 #endif
1129 currCol->GetWidthInTwips(this, &colWidth);
1130 NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column");
1132 nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex),
1133 colWidth, mRowHeight);
1135 // Check the ID of the current column to see if it matches. If it doesn't
1136 // increment the current X value and continue to the next column.
1137 if (currCol != aCol) {
1138 currX += cellRect.width;
1139 continue;
1140 }
1141 // Now obtain the properties for our cell.
1142 PrefillPropertyArray(aRow, currCol);
1144 nsAutoString properties;
1145 mView->GetCellProperties(aRow, currCol, properties);
1146 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
1148 nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
1150 // We don't want to consider any of the decorations that may be present
1151 // on the current row, so we have to deflate the rect by the border and
1152 // padding and offset its left and top coordinates appropriately.
1153 AdjustForBorderPadding(rowContext, cellRect);
1155 nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
1157 NS_NAMED_LITERAL_CSTRING(cell, "cell");
1158 if (currCol->IsCycler() || cell.Equals(aElement)) {
1159 // If the current Column is a Cycler, then the Rect is just the cell - the margins.
1160 // Similarly, if we're just being asked for the cell rect, provide it.
1162 theRect = cellRect;
1163 nsMargin cellMargin;
1164 cellContext->StyleMargin()->GetMargin(cellMargin);
1165 theRect.Deflate(cellMargin);
1166 break;
1167 }
1169 // Since we're not looking for the cell, and since the cell isn't a cycler,
1170 // we're looking for some subcomponent, and now we need to subtract the
1171 // borders and padding of the cell from cellRect so this does not
1172 // interfere with our computations.
1173 AdjustForBorderPadding(cellContext, cellRect);
1175 nsRefPtr<nsRenderingContext> rc =
1176 presContext->PresShell()->CreateReferenceRenderingContext();
1178 // Now we'll start making our way across the cell, starting at the edge of
1179 // the cell and proceeding until we hit the right edge. |cellX| is the
1180 // working X value that we will increment as we crawl from left to right.
1181 nscoord cellX = cellRect.x;
1182 nscoord remainWidth = cellRect.width;
1184 if (currCol->IsPrimary()) {
1185 // If the current Column is a Primary, then we need to take into account the indentation
1186 // and possibly a twisty.
1188 // The amount of indentation is the indentation width (|mIndentation|) by the level.
1189 int32_t level;
1190 mView->GetLevel(aRow, &level);
1191 if (!isRTL)
1192 cellX += mIndentation * level;
1193 remainWidth -= mIndentation * level;
1195 // Find the twisty rect by computing its size.
1196 nsRect imageRect;
1197 nsRect twistyRect(cellRect);
1198 nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
1199 GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
1200 *rc, twistyContext);
1202 if (NS_LITERAL_CSTRING("twisty").Equals(aElement)) {
1203 // If we're looking for the twisty Rect, just return the size
1204 theRect = twistyRect;
1205 break;
1206 }
1208 // Now we need to add in the margins of the twisty element, so that we
1209 // can find the offset of the next element in the cell.
1210 nsMargin twistyMargin;
1211 twistyContext->StyleMargin()->GetMargin(twistyMargin);
1212 twistyRect.Inflate(twistyMargin);
1214 // Adjust our working X value with the twisty width (image size, margins,
1215 // borders, padding.
1216 if (!isRTL)
1217 cellX += twistyRect.width;
1218 }
1220 // Cell Image
1221 nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
1223 nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext);
1224 if (NS_LITERAL_CSTRING("image").Equals(aElement)) {
1225 theRect = imageSize;
1226 theRect.x = cellX;
1227 theRect.y = cellRect.y;
1228 break;
1229 }
1231 // Add in the margins of the cell image.
1232 nsMargin imageMargin;
1233 imageContext->StyleMargin()->GetMargin(imageMargin);
1234 imageSize.Inflate(imageMargin);
1236 // Increment cellX by the image width
1237 if (!isRTL)
1238 cellX += imageSize.width;
1240 // Cell Text
1241 nsAutoString cellText;
1242 mView->GetCellText(aRow, currCol, cellText);
1243 // We're going to measure this text so we need to ensure bidi is enabled if
1244 // necessary
1245 CheckTextForBidi(cellText);
1247 // Create a scratch rect to represent the text rectangle, with the current
1248 // X and Y coords, and a guess at the width and height. The width is the
1249 // remaining width we have left to traverse in the cell, which will be the
1250 // widest possible value for the text rect, and the row height.
1251 nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height);
1253 // Measure the width of the text. If the width of the text is greater than
1254 // the remaining width available, then we just assume that the text has
1255 // been cropped and use the remaining rect as the text Rect. Otherwise,
1256 // we add in borders and padding to the text dimension and give that back.
1257 nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
1259 nsRefPtr<nsFontMetrics> fm;
1260 nsLayoutUtils::GetFontMetricsForStyleContext(textContext,
1261 getter_AddRefs(fm));
1262 nscoord height = fm->MaxHeight();
1264 nsMargin textMargin;
1265 textContext->StyleMargin()->GetMargin(textMargin);
1266 textRect.Deflate(textMargin);
1268 // Center the text. XXX Obey vertical-align style prop?
1269 if (height < textRect.height) {
1270 textRect.y += (textRect.height - height) / 2;
1271 textRect.height = height;
1272 }
1274 nsMargin bp(0,0,0,0);
1275 GetBorderPadding(textContext, bp);
1276 textRect.height += bp.top + bp.bottom;
1278 rc->SetFont(fm);
1279 AdjustForCellText(cellText, aRow, currCol, *rc, textRect);
1281 theRect = textRect;
1282 }
1284 if (isRTL)
1285 theRect.x = mInnerBox.width - theRect.x - theRect.width;
1287 *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x);
1288 *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y);
1289 *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width);
1290 *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height);
1292 return NS_OK;
1293 }
1295 int32_t
1296 nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY)
1297 {
1298 // Now just mod by our total inner box height and add to our top row index.
1299 int32_t row = (aY/mRowHeight)+mTopRowIndex;
1301 // Check if the coordinates are below our visible space (or within our visible
1302 // space but below any row).
1303 if (row > mTopRowIndex + mPageLength || row >= mRowCount)
1304 return -1;
1306 return row;
1307 }
1309 void
1310 nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText)
1311 {
1312 // We could check to see whether the prescontext already has bidi enabled,
1313 // but usually it won't, so it's probably faster to avoid the call to
1314 // GetPresContext() when it's not needed.
1315 if (HasRTLChars(aText)) {
1316 PresContext()->SetBidiEnabled();
1317 }
1318 }
1320 void
1321 nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText,
1322 int32_t aRowIndex, nsTreeColumn* aColumn,
1323 nsRenderingContext& aRenderingContext,
1324 nsRect& aTextRect)
1325 {
1326 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
1328 nscoord width =
1329 nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aText.get(), aText.Length());
1330 nscoord maxWidth = aTextRect.width;
1332 if (aColumn->Overflow()) {
1333 DebugOnly<nsresult> rv;
1334 nsTreeColumn* nextColumn = aColumn->GetNext();
1335 while (nextColumn && width > maxWidth) {
1336 while (nextColumn) {
1337 nscoord width;
1338 rv = nextColumn->GetWidthInTwips(this, &width);
1339 NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
1341 if (width != 0)
1342 break;
1344 nextColumn = nextColumn->GetNext();
1345 }
1347 if (nextColumn) {
1348 nsAutoString nextText;
1349 mView->GetCellText(aRowIndex, nextColumn, nextText);
1350 // We don't measure or draw this text so no need to check it for
1351 // bidi-ness
1353 if (nextText.Length() == 0) {
1354 nscoord width;
1355 rv = nextColumn->GetWidthInTwips(this, &width);
1356 NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
1358 maxWidth += width;
1360 nextColumn = nextColumn->GetNext();
1361 }
1362 else {
1363 nextColumn = nullptr;
1364 }
1365 }
1366 }
1367 }
1369 if (width > maxWidth) {
1370 // See if the width is even smaller than the ellipsis
1371 // If so, clear the text completely.
1372 const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
1373 aRenderingContext.SetTextRunRTL(false);
1374 nscoord ellipsisWidth = aRenderingContext.GetWidth(kEllipsis);
1376 width = maxWidth;
1377 if (ellipsisWidth > width)
1378 aText.SetLength(0);
1379 else if (ellipsisWidth == width)
1380 aText.Assign(kEllipsis);
1381 else {
1382 // We will be drawing an ellipsis, thank you very much.
1383 // Subtract out the required width of the ellipsis.
1384 // This is the total remaining width we have to play with.
1385 width -= ellipsisWidth;
1387 // Now we crop.
1388 switch (aColumn->GetCropStyle()) {
1389 default:
1390 case 0: {
1391 // Crop right.
1392 nscoord cwidth;
1393 nscoord twidth = 0;
1394 uint32_t length = aText.Length();
1395 uint32_t i;
1396 for (i = 0; i < length; ++i) {
1397 char16_t ch = aText[i];
1398 // XXX this is horrible and doesn't handle clusters
1399 cwidth = aRenderingContext.GetWidth(ch);
1400 if (twidth + cwidth > width)
1401 break;
1402 twidth += cwidth;
1403 }
1404 aText.Truncate(i);
1405 aText.Append(kEllipsis);
1406 }
1407 break;
1409 case 2: {
1410 // Crop left.
1411 nscoord cwidth;
1412 nscoord twidth = 0;
1413 int32_t length = aText.Length();
1414 int32_t i;
1415 for (i=length-1; i >= 0; --i) {
1416 char16_t ch = aText[i];
1417 cwidth = aRenderingContext.GetWidth(ch);
1418 if (twidth + cwidth > width)
1419 break;
1420 twidth += cwidth;
1421 }
1423 nsAutoString copy;
1424 aText.Right(copy, length-1-i);
1425 aText.Assign(kEllipsis);
1426 aText += copy;
1427 }
1428 break;
1430 case 1:
1431 {
1432 // Crop center.
1433 nsAutoString leftStr, rightStr;
1434 nscoord cwidth, twidth = 0;
1435 int32_t length = aText.Length();
1436 int32_t rightPos = length - 1;
1437 for (int32_t leftPos = 0; leftPos < rightPos; ++leftPos) {
1438 char16_t ch = aText[leftPos];
1439 cwidth = aRenderingContext.GetWidth(ch);
1440 twidth += cwidth;
1441 if (twidth > width)
1442 break;
1443 leftStr.Append(ch);
1445 ch = aText[rightPos];
1446 cwidth = aRenderingContext.GetWidth(ch);
1447 twidth += cwidth;
1448 if (twidth > width)
1449 break;
1450 rightStr.Insert(ch, 0);
1451 --rightPos;
1452 }
1453 aText = leftStr;
1454 aText.Append(kEllipsis);
1455 aText += rightStr;
1456 }
1457 break;
1458 }
1459 }
1461 width = nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aText.get(), aText.Length());
1462 }
1464 switch (aColumn->GetTextAlignment()) {
1465 case NS_STYLE_TEXT_ALIGN_RIGHT: {
1466 aTextRect.x += aTextRect.width - width;
1467 }
1468 break;
1469 case NS_STYLE_TEXT_ALIGN_CENTER: {
1470 aTextRect.x += (aTextRect.width - width) / 2;
1471 }
1472 break;
1473 }
1475 aTextRect.width = width;
1476 }
1478 nsIAtom*
1479 nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect,
1480 int32_t aRowIndex,
1481 nsTreeColumn* aColumn)
1482 {
1483 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
1485 // Obtain the properties for our cell.
1486 PrefillPropertyArray(aRowIndex, aColumn);
1487 nsAutoString properties;
1488 mView->GetCellProperties(aRowIndex, aColumn, properties);
1489 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
1491 // Resolve style for the cell.
1492 nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
1494 // Obtain the margins for the cell and then deflate our rect by that
1495 // amount. The cell is assumed to be contained within the deflated rect.
1496 nsRect cellRect(aCellRect);
1497 nsMargin cellMargin;
1498 cellContext->StyleMargin()->GetMargin(cellMargin);
1499 cellRect.Deflate(cellMargin);
1501 // Adjust the rect for its border and padding.
1502 AdjustForBorderPadding(cellContext, cellRect);
1504 if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) {
1505 // The user clicked within the cell's margins/borders/padding. This constitutes a click on the cell.
1506 return nsCSSAnonBoxes::moztreecell;
1507 }
1509 nscoord currX = cellRect.x;
1510 nscoord remainingWidth = cellRect.width;
1512 // Handle right alignment hit testing.
1513 bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
1515 nsPresContext* presContext = PresContext();
1516 nsRefPtr<nsRenderingContext> rc =
1517 presContext->PresShell()->CreateReferenceRenderingContext();
1519 if (aColumn->IsPrimary()) {
1520 // If we're the primary column, we have indentation and a twisty.
1521 int32_t level;
1522 mView->GetLevel(aRowIndex, &level);
1524 if (!isRTL)
1525 currX += mIndentation*level;
1526 remainingWidth -= mIndentation*level;
1528 if ((isRTL && aX > currX + remainingWidth) ||
1529 (!isRTL && aX < currX)) {
1530 // The user clicked within the indentation.
1531 return nsCSSAnonBoxes::moztreecell;
1532 }
1534 // Always leave space for the twisty.
1535 nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
1536 bool hasTwisty = false;
1537 bool isContainer = false;
1538 mView->IsContainer(aRowIndex, &isContainer);
1539 if (isContainer) {
1540 bool isContainerEmpty = false;
1541 mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
1542 if (!isContainerEmpty)
1543 hasTwisty = true;
1544 }
1546 // Resolve style for the twisty.
1547 nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
1549 nsRect imageSize;
1550 GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext,
1551 *rc, twistyContext);
1553 // We will treat a click as hitting the twisty if it happens on the margins, borders, padding,
1554 // or content of the twisty object. By allowing a "slop" into the margin, we make it a little
1555 // bit easier for a user to hit the twisty. (We don't want to be too picky here.)
1556 nsMargin twistyMargin;
1557 twistyContext->StyleMargin()->GetMargin(twistyMargin);
1558 twistyRect.Inflate(twistyMargin);
1559 if (isRTL)
1560 twistyRect.x = currX + remainingWidth - twistyRect.width;
1562 // Now we test to see if aX is actually within the twistyRect. If it is, and if the item should
1563 // have a twisty, then we return "twisty". If it is within the rect but we shouldn't have a twisty,
1564 // then we return "cell".
1565 if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) {
1566 if (hasTwisty)
1567 return nsCSSAnonBoxes::moztreetwisty;
1568 else
1569 return nsCSSAnonBoxes::moztreecell;
1570 }
1572 if (!isRTL)
1573 currX += twistyRect.width;
1574 remainingWidth -= twistyRect.width;
1575 }
1577 // Now test to see if the user hit the icon for the cell.
1578 nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
1580 // Resolve style for the image.
1581 nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
1583 nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext);
1584 nsMargin imageMargin;
1585 imageContext->StyleMargin()->GetMargin(imageMargin);
1586 iconSize.Inflate(imageMargin);
1587 iconRect.width = iconSize.width;
1588 if (isRTL)
1589 iconRect.x = currX + remainingWidth - iconRect.width;
1591 if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) {
1592 // The user clicked on the image.
1593 return nsCSSAnonBoxes::moztreeimage;
1594 }
1596 if (!isRTL)
1597 currX += iconRect.width;
1598 remainingWidth -= iconRect.width;
1600 nsAutoString cellText;
1601 mView->GetCellText(aRowIndex, aColumn, cellText);
1602 // We're going to measure this text so we need to ensure bidi is enabled if
1603 // necessary
1604 CheckTextForBidi(cellText);
1606 nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height);
1608 nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
1610 nsMargin textMargin;
1611 textContext->StyleMargin()->GetMargin(textMargin);
1612 textRect.Deflate(textMargin);
1614 AdjustForBorderPadding(textContext, textRect);
1616 nsRefPtr<nsFontMetrics> fm;
1617 nsLayoutUtils::GetFontMetricsForStyleContext(textContext,
1618 getter_AddRefs(fm));
1619 rc->SetFont(fm);
1621 AdjustForCellText(cellText, aRowIndex, aColumn, *rc, textRect);
1623 if (aX >= textRect.x && aX < textRect.x + textRect.width)
1624 return nsCSSAnonBoxes::moztreecelltext;
1625 else
1626 return nsCSSAnonBoxes::moztreecell;
1627 }
1629 void
1630 nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow,
1631 nsTreeColumn** aCol, nsIAtom** aChildElt)
1632 {
1633 *aCol = nullptr;
1634 *aChildElt = nullptr;
1636 *aRow = GetRowAt(aX, aY);
1637 if (*aRow < 0)
1638 return;
1640 // Determine the column hit.
1641 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
1642 currCol = currCol->GetNext()) {
1643 nsRect cellRect;
1644 nsresult rv = currCol->GetRect(this,
1645 mInnerBox.y +
1646 mRowHeight * (*aRow - mTopRowIndex),
1647 mRowHeight,
1648 &cellRect);
1649 if (NS_FAILED(rv)) {
1650 NS_NOTREACHED("column has no frame");
1651 continue;
1652 }
1654 if (!OffsetForHorzScroll(cellRect, false))
1655 continue;
1657 if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) {
1658 // We know the column hit now.
1659 *aCol = currCol;
1661 if (currCol->IsCycler())
1662 // Cyclers contain only images. Fill this in immediately and return.
1663 *aChildElt = nsCSSAnonBoxes::moztreeimage;
1664 else
1665 *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol);
1666 break;
1667 }
1668 }
1669 }
1671 nsresult
1672 nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
1673 nsRenderingContext* aRenderingContext,
1674 nscoord& aDesiredSize, nscoord& aCurrentSize)
1675 {
1676 NS_PRECONDITION(aCol, "aCol must not be null");
1677 NS_PRECONDITION(aRenderingContext, "aRenderingContext must not be null");
1679 // The rect for the current cell.
1680 nscoord colWidth;
1681 nsresult rv = aCol->GetWidthInTwips(this, &colWidth);
1682 NS_ENSURE_SUCCESS(rv, rv);
1684 nsRect cellRect(0, 0, colWidth, mRowHeight);
1686 int32_t overflow = cellRect.x+cellRect.width-(mInnerBox.x+mInnerBox.width);
1687 if (overflow > 0)
1688 cellRect.width -= overflow;
1690 // Adjust borders and padding for the cell.
1691 nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
1692 nsMargin bp(0,0,0,0);
1693 GetBorderPadding(cellContext, bp);
1695 aCurrentSize = cellRect.width;
1696 aDesiredSize = bp.left + bp.right;
1698 if (aCol->IsPrimary()) {
1699 // If the current Column is a Primary, then we need to take into account
1700 // the indentation and possibly a twisty.
1702 // The amount of indentation is the indentation width (|mIndentation|) by the level.
1703 int32_t level;
1704 mView->GetLevel(aRow, &level);
1705 aDesiredSize += mIndentation * level;
1707 // Find the twisty rect by computing its size.
1708 nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
1710 nsRect imageSize;
1711 nsRect twistyRect(cellRect);
1712 GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(),
1713 *aRenderingContext, twistyContext);
1715 // Add in the margins of the twisty element.
1716 nsMargin twistyMargin;
1717 twistyContext->StyleMargin()->GetMargin(twistyMargin);
1718 twistyRect.Inflate(twistyMargin);
1720 aDesiredSize += twistyRect.width;
1721 }
1723 nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
1725 // Account for the width of the cell image.
1726 nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext);
1727 // Add in the margins of the cell image.
1728 nsMargin imageMargin;
1729 imageContext->StyleMargin()->GetMargin(imageMargin);
1730 imageSize.Inflate(imageMargin);
1732 aDesiredSize += imageSize.width;
1734 // Get the cell text.
1735 nsAutoString cellText;
1736 mView->GetCellText(aRow, aCol, cellText);
1737 // We're going to measure this text so we need to ensure bidi is enabled if
1738 // necessary
1739 CheckTextForBidi(cellText);
1741 nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
1743 // Get the borders and padding for the text.
1744 GetBorderPadding(textContext, bp);
1746 nsRefPtr<nsFontMetrics> fm;
1747 nsLayoutUtils::GetFontMetricsForStyleContext(textContext,
1748 getter_AddRefs(fm));
1749 aRenderingContext->SetFont(fm);
1751 // Get the width of the text itself
1752 nscoord width =
1753 nsLayoutUtils::GetStringWidth(this, aRenderingContext, cellText.get(), cellText.Length());
1754 nscoord totalTextWidth = width + bp.left + bp.right;
1755 aDesiredSize += totalTextWidth;
1756 return NS_OK;
1757 }
1759 nsresult
1760 nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *_retval)
1761 {
1762 nscoord currentSize, desiredSize;
1763 nsresult rv;
1765 nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
1766 if (!col)
1767 return NS_ERROR_INVALID_ARG;
1769 nsRefPtr<nsRenderingContext> rc =
1770 PresContext()->PresShell()->CreateReferenceRenderingContext();
1772 rv = GetCellWidth(aRow, col, rc, desiredSize, currentSize);
1773 NS_ENSURE_SUCCESS(rv, rv);
1775 *_retval = desiredSize > currentSize;
1777 return NS_OK;
1778 }
1780 void
1781 nsTreeBodyFrame::MarkDirtyIfSelect()
1782 {
1783 nsIContent* baseElement = GetBaseElement();
1785 if (baseElement && baseElement->Tag() == nsGkAtoms::select &&
1786 baseElement->IsHTML()) {
1787 // If we are an intrinsically sized select widget, we may need to
1788 // resize, if the widest item was removed or a new item was added.
1789 // XXX optimize this more
1791 mStringWidth = -1;
1792 PresContext()->PresShell()->FrameNeedsReflow(this,
1793 nsIPresShell::eTreeChange,
1794 NS_FRAME_IS_DIRTY);
1795 }
1796 }
1798 nsresult
1799 nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
1800 nsTimerCallbackFunc aFunc, int32_t aType,
1801 nsITimer** aTimer)
1802 {
1803 // Get the delay from the look and feel service.
1804 int32_t delay = LookAndFeel::GetInt(aID, 0);
1806 nsCOMPtr<nsITimer> timer;
1808 // Create a new timer only if the delay is greater than zero.
1809 // Zero value means that this feature is completely disabled.
1810 if (delay > 0) {
1811 timer = do_CreateInstance("@mozilla.org/timer;1");
1812 if (timer)
1813 timer->InitWithFuncCallback(aFunc, this, delay, aType);
1814 }
1816 NS_IF_ADDREF(*aTimer = timer);
1818 return NS_OK;
1819 }
1821 nsresult
1822 nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount)
1823 {
1824 if (aCount == 0 || !mView)
1825 return NS_OK; // Nothing to do.
1827 #ifdef ACCESSIBILITY
1828 if (nsIPresShell::IsAccessibilityActive())
1829 FireRowCountChangedEvent(aIndex, aCount);
1830 #endif
1832 // Adjust our selection.
1833 nsCOMPtr<nsITreeSelection> sel;
1834 mView->GetSelection(getter_AddRefs(sel));
1835 if (sel)
1836 sel->AdjustSelection(aIndex, aCount);
1838 if (mUpdateBatchNest)
1839 return NS_OK;
1841 mRowCount += aCount;
1842 #ifdef DEBUG
1843 int32_t rowCount = mRowCount;
1844 mView->GetRowCount(&rowCount);
1845 NS_ASSERTION(rowCount == mRowCount, "row count did not change by the amount suggested, check caller");
1846 #endif
1848 int32_t count = Abs(aCount);
1849 int32_t last = LastVisibleRow();
1850 if (aIndex >= mTopRowIndex && aIndex <= last)
1851 InvalidateRange(aIndex, last);
1853 ScrollParts parts = GetScrollParts();
1855 if (mTopRowIndex == 0) {
1856 // Just update the scrollbar and return.
1857 if (FullScrollbarsUpdate(false)) {
1858 MarkDirtyIfSelect();
1859 }
1860 return NS_OK;
1861 }
1863 bool needsInvalidation = false;
1864 // Adjust our top row index.
1865 if (aCount > 0) {
1866 if (mTopRowIndex > aIndex) {
1867 // Rows came in above us. Augment the top row index.
1868 mTopRowIndex += aCount;
1869 }
1870 }
1871 else if (aCount < 0) {
1872 if (mTopRowIndex > aIndex+count-1) {
1873 // No need to invalidate. The remove happened
1874 // completely above us (offscreen).
1875 mTopRowIndex -= count;
1876 }
1877 else if (mTopRowIndex >= aIndex) {
1878 // This is a full-blown invalidate.
1879 if (mTopRowIndex + mPageLength > mRowCount - 1) {
1880 mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
1881 }
1882 needsInvalidation = true;
1883 }
1884 }
1886 if (FullScrollbarsUpdate(needsInvalidation)) {
1887 MarkDirtyIfSelect();
1888 }
1889 return NS_OK;
1890 }
1892 nsresult
1893 nsTreeBodyFrame::BeginUpdateBatch()
1894 {
1895 ++mUpdateBatchNest;
1897 return NS_OK;
1898 }
1900 nsresult
1901 nsTreeBodyFrame::EndUpdateBatch()
1902 {
1903 NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch");
1905 if (--mUpdateBatchNest == 0) {
1906 if (mView) {
1907 Invalidate();
1908 int32_t countBeforeUpdate = mRowCount;
1909 mView->GetRowCount(&mRowCount);
1910 if (countBeforeUpdate != mRowCount) {
1911 if (mTopRowIndex + mPageLength > mRowCount - 1) {
1912 mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength);
1913 }
1914 FullScrollbarsUpdate(false);
1915 }
1916 }
1917 }
1919 return NS_OK;
1920 }
1922 void
1923 nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol)
1924 {
1925 NS_PRECONDITION(!aCol || aCol->GetFrame(), "invalid column passed");
1926 mScratchArray.Clear();
1928 // focus
1929 if (mFocused)
1930 mScratchArray.AppendElement(nsGkAtoms::focus);
1932 // sort
1933 bool sorted = false;
1934 mView->IsSorted(&sorted);
1935 if (sorted)
1936 mScratchArray.AppendElement(nsGkAtoms::sorted);
1938 // drag session
1939 if (mSlots && mSlots->mIsDragging)
1940 mScratchArray.AppendElement(nsGkAtoms::dragSession);
1942 if (aRowIndex != -1) {
1943 if (aRowIndex == mMouseOverRow)
1944 mScratchArray.AppendElement(nsGkAtoms::hover);
1946 nsCOMPtr<nsITreeSelection> selection;
1947 mView->GetSelection(getter_AddRefs(selection));
1949 if (selection) {
1950 // selected
1951 bool isSelected;
1952 selection->IsSelected(aRowIndex, &isSelected);
1953 if (isSelected)
1954 mScratchArray.AppendElement(nsGkAtoms::selected);
1956 // current
1957 int32_t currentIndex;
1958 selection->GetCurrentIndex(¤tIndex);
1959 if (aRowIndex == currentIndex)
1960 mScratchArray.AppendElement(nsGkAtoms::current);
1962 // active
1963 if (aCol) {
1964 nsCOMPtr<nsITreeColumn> currentColumn;
1965 selection->GetCurrentColumn(getter_AddRefs(currentColumn));
1966 if (aCol == currentColumn)
1967 mScratchArray.AppendElement(nsGkAtoms::active);
1968 }
1969 }
1971 // container or leaf
1972 bool isContainer = false;
1973 mView->IsContainer(aRowIndex, &isContainer);
1974 if (isContainer) {
1975 mScratchArray.AppendElement(nsGkAtoms::container);
1977 // open or closed
1978 bool isOpen = false;
1979 mView->IsContainerOpen(aRowIndex, &isOpen);
1980 if (isOpen)
1981 mScratchArray.AppendElement(nsGkAtoms::open);
1982 else
1983 mScratchArray.AppendElement(nsGkAtoms::closed);
1984 }
1985 else {
1986 mScratchArray.AppendElement(nsGkAtoms::leaf);
1987 }
1989 // drop orientation
1990 if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) {
1991 if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE)
1992 mScratchArray.AppendElement(nsGkAtoms::dropBefore);
1993 else if (mSlots->mDropOrient == nsITreeView::DROP_ON)
1994 mScratchArray.AppendElement(nsGkAtoms::dropOn);
1995 else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
1996 mScratchArray.AppendElement(nsGkAtoms::dropAfter);
1997 }
1999 // odd or even
2000 if (aRowIndex % 2)
2001 mScratchArray.AppendElement(nsGkAtoms::odd);
2002 else
2003 mScratchArray.AppendElement(nsGkAtoms::even);
2005 nsIContent* baseContent = GetBaseElement();
2006 if (baseContent && baseContent->HasAttr(kNameSpaceID_None, nsGkAtoms::editing))
2007 mScratchArray.AppendElement(nsGkAtoms::editing);
2009 // multiple columns
2010 if (mColumns->GetColumnAt(1))
2011 mScratchArray.AppendElement(nsGkAtoms::multicol);
2012 }
2014 if (aCol) {
2015 mScratchArray.AppendElement(aCol->GetAtom());
2017 if (aCol->IsPrimary())
2018 mScratchArray.AppendElement(nsGkAtoms::primary);
2020 if (aCol->GetType() == nsITreeColumn::TYPE_CHECKBOX) {
2021 mScratchArray.AppendElement(nsGkAtoms::checkbox);
2023 if (aRowIndex != -1) {
2024 nsAutoString value;
2025 mView->GetCellValue(aRowIndex, aCol, value);
2026 if (value.EqualsLiteral("true"))
2027 mScratchArray.AppendElement(nsGkAtoms::checked);
2028 }
2029 }
2030 else if (aCol->GetType() == nsITreeColumn::TYPE_PROGRESSMETER) {
2031 mScratchArray.AppendElement(nsGkAtoms::progressmeter);
2033 if (aRowIndex != -1) {
2034 int32_t state;
2035 mView->GetProgressMode(aRowIndex, aCol, &state);
2036 if (state == nsITreeView::PROGRESS_NORMAL)
2037 mScratchArray.AppendElement(nsGkAtoms::progressNormal);
2038 else if (state == nsITreeView::PROGRESS_UNDETERMINED)
2039 mScratchArray.AppendElement(nsGkAtoms::progressUndetermined);
2040 }
2041 }
2043 // Read special properties from attributes on the column content node
2044 if (aCol->mContent->AttrValueIs(kNameSpaceID_None,
2045 nsGkAtoms::insertbefore,
2046 nsGkAtoms::_true, eCaseMatters))
2047 mScratchArray.AppendElement(nsGkAtoms::insertbefore);
2048 if (aCol->mContent->AttrValueIs(kNameSpaceID_None,
2049 nsGkAtoms::insertafter,
2050 nsGkAtoms::_true, eCaseMatters))
2051 mScratchArray.AppendElement(nsGkAtoms::insertafter);
2052 }
2053 }
2055 nsITheme*
2056 nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex,
2057 nsTreeColumn* aColumn,
2058 nsRect& aImageRect,
2059 nsRect& aTwistyRect,
2060 nsPresContext* aPresContext,
2061 nsRenderingContext& aRenderingContext,
2062 nsStyleContext* aTwistyContext)
2063 {
2064 // The twisty rect extends all the way to the end of the cell. This is incorrect. We need to
2065 // determine the twisty rect's true width. This is done by examining the style context for
2066 // a width first. If it has one, we use that. If it doesn't, we use the image's natural width.
2067 // If the image hasn't loaded and if no width is specified, then we just bail. If there is
2068 // a -moz-appearance involved, adjust the rect by the minimum widget size provided by
2069 // the theme implementation.
2070 aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext);
2071 if (aImageRect.height > aTwistyRect.height)
2072 aImageRect.height = aTwistyRect.height;
2073 if (aImageRect.width > aTwistyRect.width)
2074 aImageRect.width = aTwistyRect.width;
2075 else
2076 aTwistyRect.width = aImageRect.width;
2078 bool useTheme = false;
2079 nsITheme *theme = nullptr;
2080 const nsStyleDisplay* twistyDisplayData = aTwistyContext->StyleDisplay();
2081 if (twistyDisplayData->mAppearance) {
2082 theme = aPresContext->GetTheme();
2083 if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, twistyDisplayData->mAppearance))
2084 useTheme = true;
2085 }
2087 if (useTheme) {
2088 nsIntSize minTwistySizePx(0,0);
2089 bool canOverride = true;
2090 theme->GetMinimumWidgetSize(&aRenderingContext, this, twistyDisplayData->mAppearance,
2091 &minTwistySizePx, &canOverride);
2093 // GMWS() returns size in pixels, we need to convert it back to app units
2094 nsSize minTwistySize;
2095 minTwistySize.width = aPresContext->DevPixelsToAppUnits(minTwistySizePx.width);
2096 minTwistySize.height = aPresContext->DevPixelsToAppUnits(minTwistySizePx.height);
2098 if (aTwistyRect.width < minTwistySize.width || !canOverride)
2099 aTwistyRect.width = minTwistySize.width;
2100 }
2102 return useTheme ? theme : nullptr;
2103 }
2105 nsresult
2106 nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
2107 nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult)
2108 {
2109 *aResult = nullptr;
2111 nsAutoString imageSrc;
2112 mView->GetImageSrc(aRowIndex, aCol, imageSrc);
2113 nsRefPtr<imgRequestProxy> styleRequest;
2114 if (!aUseContext && !imageSrc.IsEmpty()) {
2115 aAllowImageRegions = false;
2116 }
2117 else {
2118 // Obtain the URL from the style context.
2119 aAllowImageRegions = true;
2120 styleRequest = aStyleContext->StyleList()->GetListStyleImage();
2121 if (!styleRequest)
2122 return NS_OK;
2123 nsCOMPtr<nsIURI> uri;
2124 styleRequest->GetURI(getter_AddRefs(uri));
2125 nsAutoCString spec;
2126 uri->GetSpec(spec);
2127 CopyUTF8toUTF16(spec, imageSrc);
2128 }
2130 // Look the image up in our cache.
2131 nsTreeImageCacheEntry entry;
2132 if (mImageCache.Get(imageSrc, &entry)) {
2133 // Find out if the image has loaded.
2134 uint32_t status;
2135 imgIRequest *imgReq = entry.request;
2136 imgReq->GetImageStatus(&status);
2137 imgReq->GetImage(aResult); // We hand back the image here. The GetImage call addrefs *aResult.
2138 bool animated = true; // Assuming animated is the safe option
2140 // We can only call GetAnimated if we're decoded
2141 if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE))
2142 (*aResult)->GetAnimated(&animated);
2144 if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) {
2145 // We either aren't done loading, or we're animating. Add our row as a listener for invalidations.
2146 nsCOMPtr<imgINotificationObserver> obs;
2147 imgReq->GetNotificationObserver(getter_AddRefs(obs));
2149 if (obs) {
2150 static_cast<nsTreeImageListener*> (obs.get())->AddCell(aRowIndex, aCol);
2151 }
2153 return NS_OK;
2154 }
2155 }
2157 if (!*aResult) {
2158 // Create a new nsTreeImageListener object and pass it our row and column
2159 // information.
2160 nsTreeImageListener* listener = new nsTreeImageListener(this);
2161 if (!listener)
2162 return NS_ERROR_OUT_OF_MEMORY;
2164 if (!mCreatedListeners.PutEntry(listener)) {
2165 return NS_ERROR_FAILURE;
2166 }
2168 listener->AddCell(aRowIndex, aCol);
2169 nsCOMPtr<imgINotificationObserver> imgNotificationObserver = listener;
2171 nsRefPtr<imgRequestProxy> imageRequest;
2172 if (styleRequest) {
2173 styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest));
2174 } else {
2175 nsIDocument* doc = mContent->GetDocument();
2176 if (!doc)
2177 // The page is currently being torn down. Why bother.
2178 return NS_ERROR_FAILURE;
2180 nsCOMPtr<nsIURI> baseURI = mContent->GetBaseURI();
2182 nsCOMPtr<nsIURI> srcURI;
2183 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcURI),
2184 imageSrc,
2185 doc,
2186 baseURI);
2187 if (!srcURI)
2188 return NS_ERROR_FAILURE;
2190 // XXXbz what's the origin principal for this stuff that comes from our
2191 // view? I guess we should assume that it's the node's principal...
2192 if (nsContentUtils::CanLoadImage(srcURI, mContent, doc,
2193 mContent->NodePrincipal())) {
2194 nsresult rv = nsContentUtils::LoadImage(srcURI,
2195 doc,
2196 mContent->NodePrincipal(),
2197 doc->GetDocumentURI(),
2198 imgNotificationObserver,
2199 nsIRequest::LOAD_NORMAL,
2200 EmptyString(),
2201 getter_AddRefs(imageRequest));
2202 NS_ENSURE_SUCCESS(rv, rv);
2204 }
2205 }
2206 listener->UnsuppressInvalidation();
2208 if (!imageRequest)
2209 return NS_ERROR_FAILURE;
2211 // We don't want discarding/decode-on-draw for xul images
2212 imageRequest->StartDecoding();
2213 imageRequest->LockImage();
2215 // In a case it was already cached.
2216 imageRequest->GetImage(aResult);
2217 nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver);
2218 mImageCache.Put(imageSrc, cacheEntry);
2219 }
2220 return NS_OK;
2221 }
2223 nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
2224 nsStyleContext* aStyleContext)
2225 {
2226 // XXX We should respond to visibility rules for collapsed vs. hidden.
2228 // This method returns the width of the twisty INCLUDING borders and padding.
2229 // It first checks the style context for a width. If none is found, it tries to
2230 // use the default image width for the twisty. If no image is found, it defaults
2231 // to border+padding.
2232 nsRect r(0,0,0,0);
2233 nsMargin bp(0,0,0,0);
2234 GetBorderPadding(aStyleContext, bp);
2235 r.Inflate(bp);
2237 // Now r contains our border+padding info. We now need to get our width and
2238 // height.
2239 bool needWidth = false;
2240 bool needHeight = false;
2242 // We have to load image even though we already have a size.
2243 // Don't change this, otherwise things start to go crazy.
2244 bool useImageRegion = true;
2245 nsCOMPtr<imgIContainer> image;
2246 GetImage(aRowIndex, aCol, aUseContext, aStyleContext, useImageRegion, getter_AddRefs(image));
2248 const nsStylePosition* myPosition = aStyleContext->StylePosition();
2249 const nsStyleList* myList = aStyleContext->StyleList();
2251 if (useImageRegion) {
2252 r.x += myList->mImageRegion.x;
2253 r.y += myList->mImageRegion.y;
2254 }
2256 if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
2257 int32_t val = myPosition->mWidth.GetCoordValue();
2258 r.width += val;
2259 }
2260 else if (useImageRegion && myList->mImageRegion.width > 0)
2261 r.width += myList->mImageRegion.width;
2262 else
2263 needWidth = true;
2265 if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) {
2266 int32_t val = myPosition->mHeight.GetCoordValue();
2267 r.height += val;
2268 }
2269 else if (useImageRegion && myList->mImageRegion.height > 0)
2270 r.height += myList->mImageRegion.height;
2271 else
2272 needHeight = true;
2274 if (image) {
2275 if (needWidth || needHeight) {
2276 // Get the natural image size.
2278 if (needWidth) {
2279 // Get the size from the image.
2280 nscoord width;
2281 image->GetWidth(&width);
2282 r.width += nsPresContext::CSSPixelsToAppUnits(width);
2283 }
2285 if (needHeight) {
2286 nscoord height;
2287 image->GetHeight(&height);
2288 r.height += nsPresContext::CSSPixelsToAppUnits(height);
2289 }
2290 }
2291 }
2293 return r;
2294 }
2296 // GetImageDestSize returns the destination size of the image.
2297 // The width and height do not include borders and padding.
2298 // The width and height have not been adjusted to fit in the row height
2299 // or cell width.
2300 // The width and height reflect the destination size specified in CSS,
2301 // or the image region specified in CSS, or the natural size of the
2302 // image.
2303 // If only the destination width has been specified in CSS, the height is
2304 // calculated to maintain the aspect ratio of the image.
2305 // If only the destination height has been specified in CSS, the width is
2306 // calculated to maintain the aspect ratio of the image.
2307 nsSize
2308 nsTreeBodyFrame::GetImageDestSize(nsStyleContext* aStyleContext,
2309 bool useImageRegion,
2310 imgIContainer* image)
2311 {
2312 nsSize size(0,0);
2314 // We need to get the width and height.
2315 bool needWidth = false;
2316 bool needHeight = false;
2318 // Get the style position to see if the CSS has specified the
2319 // destination width/height.
2320 const nsStylePosition* myPosition = aStyleContext->StylePosition();
2322 if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
2323 // CSS has specified the destination width.
2324 size.width = myPosition->mWidth.GetCoordValue();
2325 }
2326 else {
2327 // We'll need to get the width of the image/region.
2328 needWidth = true;
2329 }
2331 if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) {
2332 // CSS has specified the destination height.
2333 size.height = myPosition->mHeight.GetCoordValue();
2334 }
2335 else {
2336 // We'll need to get the height of the image/region.
2337 needHeight = true;
2338 }
2340 if (needWidth || needHeight) {
2341 // We need to get the size of the image/region.
2342 nsSize imageSize(0,0);
2344 const nsStyleList* myList = aStyleContext->StyleList();
2346 if (useImageRegion && myList->mImageRegion.width > 0) {
2347 // CSS has specified an image region.
2348 // Use the width of the image region.
2349 imageSize.width = myList->mImageRegion.width;
2350 }
2351 else if (image) {
2352 nscoord width;
2353 image->GetWidth(&width);
2354 imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
2355 }
2357 if (useImageRegion && myList->mImageRegion.height > 0) {
2358 // CSS has specified an image region.
2359 // Use the height of the image region.
2360 imageSize.height = myList->mImageRegion.height;
2361 }
2362 else if (image) {
2363 nscoord height;
2364 image->GetHeight(&height);
2365 imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
2366 }
2368 if (needWidth) {
2369 if (!needHeight && imageSize.height != 0) {
2370 // The CSS specified the destination height, but not the destination
2371 // width. We need to calculate the width so that we maintain the
2372 // image's aspect ratio.
2373 size.width = imageSize.width * size.height / imageSize.height;
2374 }
2375 else {
2376 size.width = imageSize.width;
2377 }
2378 }
2380 if (needHeight) {
2381 if (!needWidth && imageSize.width != 0) {
2382 // The CSS specified the destination width, but not the destination
2383 // height. We need to calculate the height so that we maintain the
2384 // image's aspect ratio.
2385 size.height = imageSize.height * size.width / imageSize.width;
2386 }
2387 else {
2388 size.height = imageSize.height;
2389 }
2390 }
2391 }
2393 return size;
2394 }
2396 // GetImageSourceRect returns the source rectangle of the image to be
2397 // displayed.
2398 // The width and height reflect the image region specified in CSS, or
2399 // the natural size of the image.
2400 // The width and height do not include borders and padding.
2401 // The width and height do not reflect the destination size specified
2402 // in CSS.
2403 nsRect
2404 nsTreeBodyFrame::GetImageSourceRect(nsStyleContext* aStyleContext,
2405 bool useImageRegion,
2406 imgIContainer* image)
2407 {
2408 nsRect r(0,0,0,0);
2410 const nsStyleList* myList = aStyleContext->StyleList();
2412 if (useImageRegion &&
2413 (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) {
2414 // CSS has specified an image region.
2415 r = myList->mImageRegion;
2416 }
2417 else if (image) {
2418 // Use the actual image size.
2419 nscoord coord;
2420 image->GetWidth(&coord);
2421 r.width = nsPresContext::CSSPixelsToAppUnits(coord);
2422 image->GetHeight(&coord);
2423 r.height = nsPresContext::CSSPixelsToAppUnits(coord);
2424 }
2426 return r;
2427 }
2429 int32_t nsTreeBodyFrame::GetRowHeight()
2430 {
2431 // Look up the correct height. It is equal to the specified height
2432 // + the specified margins.
2433 mScratchArray.Clear();
2434 nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
2435 if (rowContext) {
2436 const nsStylePosition* myPosition = rowContext->StylePosition();
2438 nscoord minHeight = 0;
2439 if (myPosition->mMinHeight.GetUnit() == eStyleUnit_Coord)
2440 minHeight = myPosition->mMinHeight.GetCoordValue();
2442 nscoord height = 0;
2443 if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord)
2444 height = myPosition->mHeight.GetCoordValue();
2446 if (height < minHeight)
2447 height = minHeight;
2449 if (height > 0) {
2450 height = nsPresContext::AppUnitsToIntCSSPixels(height);
2451 height += height % 2;
2452 height = nsPresContext::CSSPixelsToAppUnits(height);
2454 // XXX Check box-sizing to determine if border/padding should augment the height
2455 // Inflate the height by our margins.
2456 nsRect rowRect(0,0,0,height);
2457 nsMargin rowMargin;
2458 rowContext->StyleMargin()->GetMargin(rowMargin);
2459 rowRect.Inflate(rowMargin);
2460 height = rowRect.height;
2461 return height;
2462 }
2463 }
2465 return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any.
2466 }
2468 int32_t nsTreeBodyFrame::GetIndentation()
2469 {
2470 // Look up the correct indentation. It is equal to the specified indentation width.
2471 mScratchArray.Clear();
2472 nsStyleContext* indentContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeindentation);
2473 if (indentContext) {
2474 const nsStylePosition* myPosition = indentContext->StylePosition();
2475 if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) {
2476 nscoord val = myPosition->mWidth.GetCoordValue();
2477 return val;
2478 }
2479 }
2481 return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any.
2482 }
2484 void nsTreeBodyFrame::CalcInnerBox()
2485 {
2486 mInnerBox.SetRect(0, 0, mRect.width, mRect.height);
2487 AdjustForBorderPadding(mStyleContext, mInnerBox);
2488 }
2490 nscoord
2491 nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts)
2492 {
2493 // Compute the adjustment to the last column. This varies depending on the
2494 // visibility of the columnpicker and the scrollbar.
2495 if (aParts.mColumnsFrame)
2496 mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width;
2497 else
2498 mAdjustWidth = 0;
2500 nscoord width = 0;
2502 // We calculate this from the scrollable frame, so that it
2503 // properly covers all contingencies of what could be
2504 // scrollable (columns, body, etc...)
2506 if (aParts.mColumnsScrollFrame) {
2507 width = aParts.mColumnsScrollFrame->GetScrollRange().width +
2508 aParts.mColumnsScrollFrame->GetScrollPortRect().width;
2509 }
2511 // If no horz scrolling periphery is present, then just return our width
2512 if (width == 0)
2513 width = mRect.width;
2515 return width;
2516 }
2518 nsresult
2519 nsTreeBodyFrame::GetCursor(const nsPoint& aPoint,
2520 nsIFrame::Cursor& aCursor)
2521 {
2522 // Check the GetScriptHandlingObject so we don't end up running code when
2523 // the document is a zombie.
2524 bool dummy;
2525 if (mView && GetContent()->GetCurrentDoc()->GetScriptHandlingObject(dummy)) {
2526 int32_t row;
2527 nsTreeColumn* col;
2528 nsIAtom* child;
2529 GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
2531 if (child) {
2532 // Our scratch array is already prefilled.
2533 nsStyleContext* childContext = GetPseudoStyleContext(child);
2535 FillCursorInformationFromStyle(childContext->StyleUserInterface(),
2536 aCursor);
2537 if (aCursor.mCursor == NS_STYLE_CURSOR_AUTO)
2538 aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
2540 return NS_OK;
2541 }
2542 }
2544 return nsLeafBoxFrame::GetCursor(aPoint, aCursor);
2545 }
2547 static uint32_t GetDropEffect(WidgetGUIEvent* aEvent)
2548 {
2549 NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "wrong event type");
2550 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
2551 nsContentUtils::SetDataTransferInEvent(dragEvent);
2553 uint32_t action = 0;
2554 if (dragEvent->dataTransfer)
2555 dragEvent->dataTransfer->GetDropEffectInt(&action);
2556 return action;
2557 }
2559 nsresult
2560 nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
2561 WidgetGUIEvent* aEvent,
2562 nsEventStatus* aEventStatus)
2563 {
2564 if (aEvent->message == NS_MOUSE_ENTER_SYNTH || aEvent->message == NS_MOUSE_MOVE) {
2565 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
2566 int32_t xTwips = pt.x - mInnerBox.x;
2567 int32_t yTwips = pt.y - mInnerBox.y;
2568 int32_t newrow = GetRowAt(xTwips, yTwips);
2569 if (mMouseOverRow != newrow) {
2570 // redraw the old and the new row
2571 if (mMouseOverRow != -1)
2572 InvalidateRow(mMouseOverRow);
2573 mMouseOverRow = newrow;
2574 if (mMouseOverRow != -1)
2575 InvalidateRow(mMouseOverRow);
2576 }
2577 }
2578 else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) {
2579 if (mMouseOverRow != -1) {
2580 InvalidateRow(mMouseOverRow);
2581 mMouseOverRow = -1;
2582 }
2583 }
2584 else if (aEvent->message == NS_DRAGDROP_ENTER) {
2585 if (!mSlots)
2586 mSlots = new Slots();
2588 // Cache several things we'll need throughout the course of our work. These
2589 // will all get released on a drag exit.
2591 if (mSlots->mTimer) {
2592 mSlots->mTimer->Cancel();
2593 mSlots->mTimer = nullptr;
2594 }
2596 // Cache the drag session.
2597 mSlots->mIsDragging = true;
2598 mSlots->mDropRow = -1;
2599 mSlots->mDropOrient = -1;
2600 mSlots->mDragAction = GetDropEffect(aEvent);
2601 }
2602 else if (aEvent->message == NS_DRAGDROP_OVER) {
2603 // The mouse is hovering over this tree. If we determine things are
2604 // different from the last time, invalidate the drop feedback at the old
2605 // position, query the view to see if the current location is droppable,
2606 // and then invalidate the drop feedback at the new location if it is.
2607 // The mouse may or may not have changed position from the last time
2608 // we were called, so optimize out a lot of the extra notifications by
2609 // checking if anything changed first. For drop feedback we use drop,
2610 // dropBefore and dropAfter property.
2612 if (!mView || !mSlots)
2613 return NS_OK;
2615 // Save last values, we will need them.
2616 int32_t lastDropRow = mSlots->mDropRow;
2617 int16_t lastDropOrient = mSlots->mDropOrient;
2618 #ifndef XP_MACOSX
2619 int16_t lastScrollLines = mSlots->mScrollLines;
2620 #endif
2622 // Find out the current drag action
2623 uint32_t lastDragAction = mSlots->mDragAction;
2624 mSlots->mDragAction = GetDropEffect(aEvent);
2626 // Compute the row mouse is over and the above/below/on state.
2627 // Below we'll use this to see if anything changed.
2628 // Also check if we want to auto-scroll.
2629 ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient, &mSlots->mScrollLines);
2631 // While we're here, handle tracking of scrolling during a drag.
2632 if (mSlots->mScrollLines) {
2633 if (mSlots->mDropAllowed) {
2634 // Invalidate primary cell at old location.
2635 mSlots->mDropAllowed = false;
2636 InvalidateDropFeedback(lastDropRow, lastDropOrient);
2637 }
2638 #ifdef XP_MACOSX
2639 ScrollByLines(mSlots->mScrollLines);
2640 #else
2641 if (!lastScrollLines) {
2642 // Cancel any previously initialized timer.
2643 if (mSlots->mTimer) {
2644 mSlots->mTimer->Cancel();
2645 mSlots->mTimer = nullptr;
2646 }
2648 // Set a timer to trigger the tree scrolling.
2649 CreateTimer(LookAndFeel::eIntID_TreeLazyScrollDelay,
2650 LazyScrollCallback, nsITimer::TYPE_ONE_SHOT,
2651 getter_AddRefs(mSlots->mTimer));
2652 }
2653 #endif
2654 // Bail out to prevent spring loaded timer and feedback line settings.
2655 return NS_OK;
2656 }
2658 // If changed from last time, invalidate primary cell at the old location and if allowed,
2659 // invalidate primary cell at the new location. If nothing changed, just bail.
2660 if (mSlots->mDropRow != lastDropRow ||
2661 mSlots->mDropOrient != lastDropOrient ||
2662 mSlots->mDragAction != lastDragAction) {
2664 // Invalidate row at the old location.
2665 if (mSlots->mDropAllowed) {
2666 mSlots->mDropAllowed = false;
2667 InvalidateDropFeedback(lastDropRow, lastDropOrient);
2668 }
2670 if (mSlots->mTimer) {
2671 // Timer is active but for a different row than the current one, kill it.
2672 mSlots->mTimer->Cancel();
2673 mSlots->mTimer = nullptr;
2674 }
2676 if (mSlots->mDropRow >= 0) {
2677 if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) {
2678 // Either there wasn't a timer running or it was just killed above.
2679 // If over a folder, start up a timer to open the folder.
2680 bool isContainer = false;
2681 mView->IsContainer(mSlots->mDropRow, &isContainer);
2682 if (isContainer) {
2683 bool isOpen = false;
2684 mView->IsContainerOpen(mSlots->mDropRow, &isOpen);
2685 if (!isOpen) {
2686 // This node isn't expanded, set a timer to expand it.
2687 CreateTimer(LookAndFeel::eIntID_TreeOpenDelay,
2688 OpenCallback, nsITimer::TYPE_ONE_SHOT,
2689 getter_AddRefs(mSlots->mTimer));
2690 }
2691 }
2692 }
2694 // The dataTransfer was initialized by the call to GetDropEffect above.
2695 bool canDropAtNewLocation = false;
2696 mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient,
2697 aEvent->AsDragEvent()->dataTransfer,
2698 &canDropAtNewLocation);
2700 if (canDropAtNewLocation) {
2701 // Invalidate row at the new location.
2702 mSlots->mDropAllowed = canDropAtNewLocation;
2703 InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
2704 }
2705 }
2706 }
2708 // Indicate that the drop is allowed by preventing the default behaviour.
2709 if (mSlots->mDropAllowed)
2710 *aEventStatus = nsEventStatus_eConsumeNoDefault;
2711 }
2712 else if (aEvent->message == NS_DRAGDROP_DROP) {
2713 // this event was meant for another frame, so ignore it
2714 if (!mSlots)
2715 return NS_OK;
2717 // Tell the view where the drop happened.
2719 // Remove the drop folder and all its parents from the array.
2720 int32_t parentIndex;
2721 nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex);
2722 while (NS_SUCCEEDED(rv) && parentIndex >= 0) {
2723 mSlots->mArray.RemoveElement(parentIndex);
2724 rv = mView->GetParentIndex(parentIndex, &parentIndex);
2725 }
2727 NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "wrong event type");
2728 WidgetDragEvent* dragEvent = aEvent->AsDragEvent();
2729 nsContentUtils::SetDataTransferInEvent(dragEvent);
2731 mView->Drop(mSlots->mDropRow, mSlots->mDropOrient, dragEvent->dataTransfer);
2732 mSlots->mDropRow = -1;
2733 mSlots->mDropOrient = -1;
2734 mSlots->mIsDragging = false;
2735 *aEventStatus = nsEventStatus_eConsumeNoDefault; // already handled the drop
2736 }
2737 else if (aEvent->message == NS_DRAGDROP_EXIT) {
2738 // this event was meant for another frame, so ignore it
2739 if (!mSlots)
2740 return NS_OK;
2742 // Clear out all our tracking vars.
2744 if (mSlots->mDropAllowed) {
2745 mSlots->mDropAllowed = false;
2746 InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
2747 }
2748 else
2749 mSlots->mDropAllowed = false;
2750 mSlots->mIsDragging = false;
2751 mSlots->mScrollLines = 0;
2752 // If a drop is occuring, the exit event will fire just before the drop
2753 // event, so don't reset mDropRow or mDropOrient as these fields are used
2754 // by the drop event.
2755 if (mSlots->mTimer) {
2756 mSlots->mTimer->Cancel();
2757 mSlots->mTimer = nullptr;
2758 }
2760 if (!mSlots->mArray.IsEmpty()) {
2761 // Close all spring loaded folders except the drop folder.
2762 CreateTimer(LookAndFeel::eIntID_TreeCloseDelay,
2763 CloseCallback, nsITimer::TYPE_ONE_SHOT,
2764 getter_AddRefs(mSlots->mTimer));
2765 }
2766 }
2768 return NS_OK;
2769 }
2771 static nsLineStyle
2772 ConvertBorderStyleToLineStyle(uint8_t aBorderStyle)
2773 {
2774 switch (aBorderStyle) {
2775 case NS_STYLE_BORDER_STYLE_DOTTED:
2776 return nsLineStyle_kDotted;
2777 case NS_STYLE_BORDER_STYLE_DASHED:
2778 return nsLineStyle_kDashed;
2779 default:
2780 return nsLineStyle_kSolid;
2781 }
2782 }
2784 static void
2785 PaintTreeBody(nsIFrame* aFrame, nsRenderingContext* aCtx,
2786 const nsRect& aDirtyRect, nsPoint aPt)
2787 {
2788 static_cast<nsTreeBodyFrame*>(aFrame)->PaintTreeBody(*aCtx, aDirtyRect, aPt);
2789 }
2791 // Painting routines
2792 void
2793 nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
2794 const nsRect& aDirtyRect,
2795 const nsDisplayListSet& aLists)
2796 {
2797 // REVIEW: why did we paint if we were collapsed? that makes no sense!
2798 if (!IsVisibleForPainting(aBuilder))
2799 return; // We're invisible. Don't paint.
2801 // Handles painting our background, border, and outline.
2802 nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
2804 // Bail out now if there's no view or we can't run script because the
2805 // document is a zombie
2806 if (!mView || !GetContent()->GetCurrentDoc()->GetWindow())
2807 return;
2809 aLists.Content()->AppendNewToTop(new (aBuilder)
2810 nsDisplayGeneric(aBuilder, this, ::PaintTreeBody, "XULTreeBody",
2811 nsDisplayItem::TYPE_XUL_TREE_BODY));
2812 }
2814 void
2815 nsTreeBodyFrame::PaintTreeBody(nsRenderingContext& aRenderingContext,
2816 const nsRect& aDirtyRect, nsPoint aPt)
2817 {
2818 // Update our available height and our page count.
2819 CalcInnerBox();
2820 aRenderingContext.PushState();
2821 aRenderingContext.IntersectClip(mInnerBox + aPt);
2822 int32_t oldPageCount = mPageLength;
2823 if (!mHasFixedRowCount)
2824 mPageLength = mInnerBox.height/mRowHeight;
2826 if (oldPageCount != mPageLength || mHorzWidth != CalcHorzWidth(GetScrollParts())) {
2827 // Schedule a ResizeReflow that will update our info properly.
2828 PresContext()->PresShell()->
2829 FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
2830 }
2831 #ifdef DEBUG
2832 int32_t rowCount = mRowCount;
2833 mView->GetRowCount(&rowCount);
2834 NS_WARN_IF_FALSE(mRowCount == rowCount, "row count changed unexpectedly");
2835 #endif
2837 // Loop through our columns and paint them (e.g., for sorting). This is only
2838 // relevant when painting backgrounds, since columns contain no content. Content
2839 // is contained in the rows.
2840 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
2841 currCol = currCol->GetNext()) {
2842 nsRect colRect;
2843 nsresult rv = currCol->GetRect(this, mInnerBox.y, mInnerBox.height,
2844 &colRect);
2845 // Don't paint hidden columns.
2846 if (NS_FAILED(rv) || colRect.width == 0) continue;
2848 if (OffsetForHorzScroll(colRect, false)) {
2849 nsRect dirtyRect;
2850 colRect += aPt;
2851 if (dirtyRect.IntersectRect(aDirtyRect, colRect)) {
2852 PaintColumn(currCol, colRect, PresContext(), aRenderingContext, aDirtyRect);
2853 }
2854 }
2855 }
2856 // Loop through our on-screen rows.
2857 for (int32_t i = mTopRowIndex; i < mRowCount && i <= mTopRowIndex+mPageLength; i++) {
2858 nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*(i-mTopRowIndex), mInnerBox.width, mRowHeight);
2859 nsRect dirtyRect;
2860 if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) &&
2861 rowRect.y < (mInnerBox.y+mInnerBox.height)) {
2862 PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext, aDirtyRect, aPt);
2863 }
2864 }
2866 if (mSlots && mSlots->mDropAllowed && (mSlots->mDropOrient == nsITreeView::DROP_BEFORE ||
2867 mSlots->mDropOrient == nsITreeView::DROP_AFTER)) {
2868 nscoord yPos = mInnerBox.y + mRowHeight * (mSlots->mDropRow - mTopRowIndex) - mRowHeight / 2;
2869 nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight);
2870 if (mSlots->mDropOrient == nsITreeView::DROP_AFTER)
2871 feedbackRect.y += mRowHeight;
2873 nsRect dirtyRect;
2874 feedbackRect += aPt;
2875 if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) {
2876 PaintDropFeedback(feedbackRect, PresContext(), aRenderingContext, aDirtyRect, aPt);
2877 }
2878 }
2879 aRenderingContext.PopState();
2880 }
2884 void
2885 nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn,
2886 const nsRect& aColumnRect,
2887 nsPresContext* aPresContext,
2888 nsRenderingContext& aRenderingContext,
2889 const nsRect& aDirtyRect)
2890 {
2891 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
2893 // Now obtain the properties for our cell.
2894 PrefillPropertyArray(-1, aColumn);
2895 nsAutoString properties;
2896 mView->GetColumnProperties(aColumn, properties);
2897 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2899 // Resolve style for the column. It contains all the info we need to lay ourselves
2900 // out and to paint.
2901 nsStyleContext* colContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecolumn);
2903 // Obtain the margins for the cell and then deflate our rect by that
2904 // amount. The cell is assumed to be contained within the deflated rect.
2905 nsRect colRect(aColumnRect);
2906 nsMargin colMargin;
2907 colContext->StyleMargin()->GetMargin(colMargin);
2908 colRect.Deflate(colMargin);
2910 PaintBackgroundLayer(colContext, aPresContext, aRenderingContext, colRect, aDirtyRect);
2911 }
2913 void
2914 nsTreeBodyFrame::PaintRow(int32_t aRowIndex,
2915 const nsRect& aRowRect,
2916 nsPresContext* aPresContext,
2917 nsRenderingContext& aRenderingContext,
2918 const nsRect& aDirtyRect,
2919 nsPoint aPt)
2920 {
2921 // We have been given a rect for our row. We treat this row like a full-blown
2922 // frame, meaning that it can have borders, margins, padding, and a background.
2924 // Without a view, we have no data. Check for this up front.
2925 if (!mView)
2926 return;
2928 nsresult rv;
2930 // Now obtain the properties for our row.
2931 // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused
2932 PrefillPropertyArray(aRowIndex, nullptr);
2934 nsAutoString properties;
2935 mView->GetRowProperties(aRowIndex, properties);
2936 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
2938 // Resolve style for the row. It contains all the info we need to lay ourselves
2939 // out and to paint.
2940 nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow);
2942 // Obtain the margins for the row and then deflate our rect by that
2943 // amount. The row is assumed to be contained within the deflated rect.
2944 nsRect rowRect(aRowRect);
2945 nsMargin rowMargin;
2946 rowContext->StyleMargin()->GetMargin(rowMargin);
2947 rowRect.Deflate(rowMargin);
2949 // Paint our borders and background for our row rect.
2950 // If a -moz-appearance is provided, use theme drawing only if the current row
2951 // is not selected (since we draw the selection as part of drawing the background).
2952 bool useTheme = false;
2953 nsITheme *theme = nullptr;
2954 const nsStyleDisplay* displayData = rowContext->StyleDisplay();
2955 if (displayData->mAppearance) {
2956 theme = aPresContext->GetTheme();
2957 if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance))
2958 useTheme = true;
2959 }
2960 bool isSelected = false;
2961 nsCOMPtr<nsITreeSelection> selection;
2962 mView->GetSelection(getter_AddRefs(selection));
2963 if (selection)
2964 selection->IsSelected(aRowIndex, &isSelected);
2965 if (useTheme && !isSelected) {
2966 nsRect dirty;
2967 dirty.IntersectRect(rowRect, aDirtyRect);
2968 theme->DrawWidgetBackground(&aRenderingContext, this,
2969 displayData->mAppearance, rowRect, dirty);
2970 } else {
2971 PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext, rowRect, aDirtyRect);
2972 }
2974 // Adjust the rect for its border and padding.
2975 nsRect originalRowRect = rowRect;
2976 AdjustForBorderPadding(rowContext, rowRect);
2978 bool isSeparator = false;
2979 mView->IsSeparator(aRowIndex, &isSeparator);
2980 if (isSeparator) {
2981 // The row is a separator.
2983 nscoord primaryX = rowRect.x;
2984 nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
2985 if (primaryCol) {
2986 // Paint the primary cell.
2987 nsRect cellRect;
2988 rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
2989 if (NS_FAILED(rv)) {
2990 NS_NOTREACHED("primary column is invalid");
2991 return;
2992 }
2994 if (OffsetForHorzScroll(cellRect, false)) {
2995 cellRect.x += aPt.x;
2996 nsRect dirtyRect;
2997 nsRect checkRect(cellRect.x, originalRowRect.y,
2998 cellRect.width, originalRowRect.height);
2999 if (dirtyRect.IntersectRect(aDirtyRect, checkRect))
3000 PaintCell(aRowIndex, primaryCol, cellRect, aPresContext,
3001 aRenderingContext, aDirtyRect, primaryX, aPt);
3002 }
3004 // Paint the left side of the separator.
3005 nscoord currX;
3006 nsTreeColumn* previousCol = primaryCol->GetPrevious();
3007 if (previousCol) {
3008 nsRect prevColRect;
3009 rv = previousCol->GetRect(this, 0, 0, &prevColRect);
3010 if (NS_SUCCEEDED(rv)) {
3011 currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x;
3012 } else {
3013 NS_NOTREACHED("The column before the primary column is invalid");
3014 currX = rowRect.x;
3015 }
3016 } else {
3017 currX = rowRect.x;
3018 }
3020 int32_t level;
3021 mView->GetLevel(aRowIndex, &level);
3022 if (level == 0)
3023 currX += mIndentation;
3025 if (currX > rowRect.x) {
3026 nsRect separatorRect(rowRect);
3027 separatorRect.width -= rowRect.x + rowRect.width - currX;
3028 PaintSeparator(aRowIndex, separatorRect, aPresContext, aRenderingContext, aDirtyRect);
3029 }
3030 }
3032 // Paint the right side (whole) separator.
3033 nsRect separatorRect(rowRect);
3034 if (primaryX > rowRect.x) {
3035 separatorRect.width -= primaryX - rowRect.x;
3036 separatorRect.x += primaryX - rowRect.x;
3037 }
3038 PaintSeparator(aRowIndex, separatorRect, aPresContext, aRenderingContext, aDirtyRect);
3039 }
3040 else {
3041 // Now loop over our cells. Only paint a cell if it intersects with our dirty rect.
3042 for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
3043 currCol = currCol->GetNext()) {
3044 nsRect cellRect;
3045 rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect);
3046 // Don't paint cells in hidden columns.
3047 if (NS_FAILED(rv) || cellRect.width == 0)
3048 continue;
3050 if (OffsetForHorzScroll(cellRect, false)) {
3051 cellRect.x += aPt.x;
3053 // for primary columns, use the row's vertical size so that the
3054 // lines get drawn properly
3055 nsRect checkRect = cellRect;
3056 if (currCol->IsPrimary())
3057 checkRect = nsRect(cellRect.x, originalRowRect.y,
3058 cellRect.width, originalRowRect.height);
3060 nsRect dirtyRect;
3061 nscoord dummy;
3062 if (dirtyRect.IntersectRect(aDirtyRect, checkRect))
3063 PaintCell(aRowIndex, currCol, cellRect, aPresContext,
3064 aRenderingContext, aDirtyRect, dummy, aPt);
3065 }
3066 }
3067 }
3068 }
3070 void
3071 nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex,
3072 const nsRect& aSeparatorRect,
3073 nsPresContext* aPresContext,
3074 nsRenderingContext& aRenderingContext,
3075 const nsRect& aDirtyRect)
3076 {
3077 // Resolve style for the separator.
3078 nsStyleContext* separatorContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeseparator);
3079 bool useTheme = false;
3080 nsITheme *theme = nullptr;
3081 const nsStyleDisplay* displayData = separatorContext->StyleDisplay();
3082 if ( displayData->mAppearance ) {
3083 theme = aPresContext->GetTheme();
3084 if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance))
3085 useTheme = true;
3086 }
3088 // use -moz-appearance if provided.
3089 if (useTheme) {
3090 nsRect dirty;
3091 dirty.IntersectRect(aSeparatorRect, aDirtyRect);
3092 theme->DrawWidgetBackground(&aRenderingContext, this,
3093 displayData->mAppearance, aSeparatorRect, dirty);
3094 }
3095 else {
3096 const nsStylePosition* stylePosition = separatorContext->StylePosition();
3098 // Obtain the height for the separator or use the default value.
3099 nscoord height;
3100 if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord)
3101 height = stylePosition->mHeight.GetCoordValue();
3102 else {
3103 // Use default height 2px.
3104 height = nsPresContext::CSSPixelsToAppUnits(2);
3105 }
3107 // Obtain the margins for the separator and then deflate our rect by that
3108 // amount. The separator is assumed to be contained within the deflated rect.
3109 nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y, aSeparatorRect.width, height);
3110 nsMargin separatorMargin;
3111 separatorContext->StyleMargin()->GetMargin(separatorMargin);
3112 separatorRect.Deflate(separatorMargin);
3114 // Center the separator.
3115 separatorRect.y += (aSeparatorRect.height - height) / 2;
3117 PaintBackgroundLayer(separatorContext, aPresContext, aRenderingContext, separatorRect, aDirtyRect);
3118 }
3119 }
3121 void
3122 nsTreeBodyFrame::PaintCell(int32_t aRowIndex,
3123 nsTreeColumn* aColumn,
3124 const nsRect& aCellRect,
3125 nsPresContext* aPresContext,
3126 nsRenderingContext& aRenderingContext,
3127 const nsRect& aDirtyRect,
3128 nscoord& aCurrX,
3129 nsPoint aPt)
3130 {
3131 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
3133 // Now obtain the properties for our cell.
3134 // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused, and the col ID.
3135 PrefillPropertyArray(aRowIndex, aColumn);
3136 nsAutoString properties;
3137 mView->GetCellProperties(aRowIndex, aColumn, properties);
3138 nsTreeUtils::TokenizeProperties(properties, mScratchArray);
3140 // Resolve style for the cell. It contains all the info we need to lay ourselves
3141 // out and to paint.
3142 nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
3144 bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
3146 // Obtain the margins for the cell and then deflate our rect by that
3147 // amount. The cell is assumed to be contained within the deflated rect.
3148 nsRect cellRect(aCellRect);
3149 nsMargin cellMargin;
3150 cellContext->StyleMargin()->GetMargin(cellMargin);
3151 cellRect.Deflate(cellMargin);
3153 // Paint our borders and background for our row rect.
3154 PaintBackgroundLayer(cellContext, aPresContext, aRenderingContext, cellRect, aDirtyRect);
3156 // Adjust the rect for its border and padding.
3157 AdjustForBorderPadding(cellContext, cellRect);
3159 nscoord currX = cellRect.x;
3160 nscoord remainingWidth = cellRect.width;
3162 // Now we paint the contents of the cells.
3163 // Directionality of the tree determines the order in which we paint.
3164 // NS_STYLE_DIRECTION_LTR means paint from left to right.
3165 // NS_STYLE_DIRECTION_RTL means paint from right to left.
3167 if (aColumn->IsPrimary()) {
3168 // If we're the primary column, we need to indent and paint the twisty and any connecting lines
3169 // between siblings.
3171 int32_t level;
3172 mView->GetLevel(aRowIndex, &level);
3174 if (!isRTL)
3175 currX += mIndentation * level;
3176 remainingWidth -= mIndentation * level;
3178 // Resolve the style to use for the connecting lines.
3179 nsStyleContext* lineContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeline);
3181 if (mIndentation && level &&
3182 lineContext->StyleVisibility()->IsVisibleOrCollapsed()) {
3183 // Paint the thread lines.
3185 // Get the size of the twisty. We don't want to paint the twisty
3186 // before painting of connecting lines since it would paint lines over
3187 // the twisty. But we need to leave a place for it.
3188 nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
3190 nsRect imageSize;
3191 nsRect twistyRect(aCellRect);
3192 GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext,
3193 aRenderingContext, twistyContext);
3195 nsMargin twistyMargin;
3196 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3197 twistyRect.Inflate(twistyMargin);
3199 aRenderingContext.PushState();
3201 const nsStyleBorder* borderStyle = lineContext->StyleBorder();
3202 nscolor color;
3203 bool foreground;
3204 borderStyle->GetBorderColor(NS_SIDE_LEFT, color, foreground);
3205 if (foreground) {
3206 // GetBorderColor didn't touch color, thus grab it from the treeline context
3207 color = lineContext->StyleColor()->mColor;
3208 }
3209 aRenderingContext.SetColor(color);
3210 uint8_t style;
3211 style = borderStyle->GetBorderStyle(NS_SIDE_LEFT);
3212 aRenderingContext.SetLineStyle(ConvertBorderStyleToLineStyle(style));
3214 nscoord srcX = currX + twistyRect.width - mIndentation / 2;
3215 nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y;
3217 // Don't paint off our cell.
3218 if (srcX <= cellRect.x + cellRect.width) {
3219 nscoord destX = currX + twistyRect.width;
3220 if (destX > cellRect.x + cellRect.width)
3221 destX = cellRect.x + cellRect.width;
3222 if (isRTL) {
3223 srcX = currX + remainingWidth - (srcX - cellRect.x);
3224 destX = currX + remainingWidth - (destX - cellRect.x);
3225 }
3226 aRenderingContext.DrawLine(srcX, lineY + mRowHeight / 2, destX, lineY + mRowHeight / 2);
3227 }
3229 int32_t currentParent = aRowIndex;
3230 for (int32_t i = level; i > 0; i--) {
3231 if (srcX <= cellRect.x + cellRect.width) {
3232 // Paint full vertical line only if we have next sibling.
3233 bool hasNextSibling;
3234 mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling);
3235 if (hasNextSibling)
3236 aRenderingContext.DrawLine(srcX, lineY, srcX, lineY + mRowHeight);
3237 else if (i == level)
3238 aRenderingContext.DrawLine(srcX, lineY, srcX, lineY + mRowHeight / 2);
3239 }
3241 int32_t parent;
3242 if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0)
3243 break;
3244 currentParent = parent;
3245 srcX -= mIndentation;
3246 }
3248 aRenderingContext.PopState();
3249 }
3251 // Always leave space for the twisty.
3252 nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
3253 PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext, aRenderingContext, aDirtyRect,
3254 remainingWidth, currX);
3255 }
3257 // Now paint the icon for our cell.
3258 nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
3259 nsRect dirtyRect;
3260 if (dirtyRect.IntersectRect(aDirtyRect, iconRect))
3261 PaintImage(aRowIndex, aColumn, iconRect, aPresContext, aRenderingContext, aDirtyRect,
3262 remainingWidth, currX);
3264 // Now paint our element, but only if we aren't a cycler column.
3265 // XXX until we have the ability to load images, allow the view to
3266 // insert text into cycler columns...
3267 if (!aColumn->IsCycler()) {
3268 nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height);
3269 nsRect dirtyRect;
3270 if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) {
3271 switch (aColumn->GetType()) {
3272 case nsITreeColumn::TYPE_TEXT:
3273 PaintText(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect, currX);
3274 break;
3275 case nsITreeColumn::TYPE_CHECKBOX:
3276 PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect);
3277 break;
3278 case nsITreeColumn::TYPE_PROGRESSMETER:
3279 int32_t state;
3280 mView->GetProgressMode(aRowIndex, aColumn, &state);
3281 switch (state) {
3282 case nsITreeView::PROGRESS_NORMAL:
3283 case nsITreeView::PROGRESS_UNDETERMINED:
3284 PaintProgressMeter(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect);
3285 break;
3286 case nsITreeView::PROGRESS_NONE:
3287 default:
3288 PaintText(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect, currX);
3289 break;
3290 }
3291 break;
3292 }
3293 }
3294 }
3296 aCurrX = currX;
3297 }
3299 void
3300 nsTreeBodyFrame::PaintTwisty(int32_t aRowIndex,
3301 nsTreeColumn* aColumn,
3302 const nsRect& aTwistyRect,
3303 nsPresContext* aPresContext,
3304 nsRenderingContext& aRenderingContext,
3305 const nsRect& aDirtyRect,
3306 nscoord& aRemainingWidth,
3307 nscoord& aCurrX)
3308 {
3309 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
3311 bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
3312 nscoord rightEdge = aCurrX + aRemainingWidth;
3313 // Paint the twisty, but only if we are a non-empty container.
3314 bool shouldPaint = false;
3315 bool isContainer = false;
3316 mView->IsContainer(aRowIndex, &isContainer);
3317 if (isContainer) {
3318 bool isContainerEmpty = false;
3319 mView->IsContainerEmpty(aRowIndex, &isContainerEmpty);
3320 if (!isContainerEmpty)
3321 shouldPaint = true;
3322 }
3324 // Resolve style for the twisty.
3325 nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
3327 // Obtain the margins for the twisty and then deflate our rect by that
3328 // amount. The twisty is assumed to be contained within the deflated rect.
3329 nsRect twistyRect(aTwistyRect);
3330 nsMargin twistyMargin;
3331 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3332 twistyRect.Deflate(twistyMargin);
3334 nsRect imageSize;
3335 nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect,
3336 aPresContext, aRenderingContext, twistyContext);
3338 // Subtract out the remaining width. This is done even when we don't actually paint a twisty in
3339 // this cell, so that cells in different rows still line up.
3340 nsRect copyRect(twistyRect);
3341 copyRect.Inflate(twistyMargin);
3342 aRemainingWidth -= copyRect.width;
3343 if (!isRTL)
3344 aCurrX += copyRect.width;
3346 if (shouldPaint) {
3347 // Paint our borders and background for our image rect.
3348 PaintBackgroundLayer(twistyContext, aPresContext, aRenderingContext, twistyRect, aDirtyRect);
3350 if (theme) {
3351 if (isRTL)
3352 twistyRect.x = rightEdge - twistyRect.width;
3353 // yeah, I know it says we're drawing a background, but a twisty is really a fg
3354 // object since it doesn't have anything that gecko would want to draw over it. Besides,
3355 // we have to prevent imagelib from drawing it.
3356 nsRect dirty;
3357 dirty.IntersectRect(twistyRect, aDirtyRect);
3358 theme->DrawWidgetBackground(&aRenderingContext, this,
3359 twistyContext->StyleDisplay()->mAppearance, twistyRect, dirty);
3360 }
3361 else {
3362 // Time to paint the twisty.
3363 // Adjust the rect for its border and padding.
3364 nsMargin bp(0,0,0,0);
3365 GetBorderPadding(twistyContext, bp);
3366 twistyRect.Deflate(bp);
3367 if (isRTL)
3368 twistyRect.x = rightEdge - twistyRect.width;
3369 imageSize.Deflate(bp);
3371 // Get the image for drawing.
3372 nsCOMPtr<imgIContainer> image;
3373 bool useImageRegion = true;
3374 GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion, getter_AddRefs(image));
3375 if (image) {
3376 nsPoint pt = twistyRect.TopLeft();
3378 // Center the image. XXX Obey vertical-align style prop?
3379 if (imageSize.height < twistyRect.height) {
3380 pt.y += (twistyRect.height - imageSize.height)/2;
3381 }
3383 // Paint the image.
3384 nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image,
3385 GraphicsFilter::FILTER_NEAREST, pt, &aDirtyRect,
3386 imgIContainer::FLAG_NONE, &imageSize);
3387 }
3388 }
3389 }
3390 }
3392 void
3393 nsTreeBodyFrame::PaintImage(int32_t aRowIndex,
3394 nsTreeColumn* aColumn,
3395 const nsRect& aImageRect,
3396 nsPresContext* aPresContext,
3397 nsRenderingContext& aRenderingContext,
3398 const nsRect& aDirtyRect,
3399 nscoord& aRemainingWidth,
3400 nscoord& aCurrX)
3401 {
3402 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
3404 bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
3405 nscoord rightEdge = aCurrX + aRemainingWidth;
3406 // Resolve style for the image.
3407 nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage);
3409 // Obtain opacity value for the image.
3410 float opacity = imageContext->StyleDisplay()->mOpacity;
3412 // Obtain the margins for the image and then deflate our rect by that
3413 // amount. The image is assumed to be contained within the deflated rect.
3414 nsRect imageRect(aImageRect);
3415 nsMargin imageMargin;
3416 imageContext->StyleMargin()->GetMargin(imageMargin);
3417 imageRect.Deflate(imageMargin);
3419 // Get the image.
3420 bool useImageRegion = true;
3421 nsCOMPtr<imgIContainer> image;
3422 GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion, getter_AddRefs(image));
3424 // Get the image destination size.
3425 nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image);
3426 if (!imageDestSize.width || !imageDestSize.height)
3427 return;
3429 // Get the borders and padding.
3430 nsMargin bp(0,0,0,0);
3431 GetBorderPadding(imageContext, bp);
3433 // destRect will be passed as the aDestRect argument in the DrawImage method.
3434 // Start with the imageDestSize width and height.
3435 nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height);
3436 // Inflate destRect for borders and padding so that we can compare/adjust
3437 // with respect to imageRect.
3438 destRect.Inflate(bp);
3440 // The destRect width and height have not been adjusted to fit within the
3441 // cell width and height.
3442 // We must adjust the width even if image is null, because the width is used
3443 // to update the aRemainingWidth and aCurrX values.
3444 // Since the height isn't used unless the image is not null, we will adjust
3445 // the height inside the if (image) block below.
3447 if (destRect.width > imageRect.width) {
3448 // The destRect is too wide to fit within the cell width.
3449 // Adjust destRect width to fit within the cell width.
3450 destRect.width = imageRect.width;
3451 }
3452 else {
3453 // The cell is wider than the destRect.
3454 // In a cycler column, the image is centered horizontally.
3455 if (!aColumn->IsCycler()) {
3456 // If this column is not a cycler, we won't center the image horizontally.
3457 // We adjust the imageRect width so that the image is placed at the start
3458 // of the cell.
3459 imageRect.width = destRect.width;
3460 }
3461 }
3463 if (image) {
3464 if (isRTL)
3465 imageRect.x = rightEdge - imageRect.width;
3466 // Paint our borders and background for our image rect
3467 PaintBackgroundLayer(imageContext, aPresContext, aRenderingContext, imageRect, aDirtyRect);
3469 // The destRect x and y have not been set yet. Let's do that now.
3470 // Initially, we use the imageRect x and y.
3471 destRect.x = imageRect.x;
3472 destRect.y = imageRect.y;
3474 if (destRect.width < imageRect.width) {
3475 // The destRect width is smaller than the cell width.
3476 // Center the image horizontally in the cell.
3477 // Adjust the destRect x accordingly.
3478 destRect.x += (imageRect.width - destRect.width)/2;
3479 }
3481 // Now it's time to adjust the destRect height to fit within the cell height.
3482 if (destRect.height > imageRect.height) {
3483 // The destRect height is larger than the cell height.
3484 // Adjust destRect height to fit within the cell height.
3485 destRect.height = imageRect.height;
3486 }
3487 else if (destRect.height < imageRect.height) {
3488 // The destRect height is smaller than the cell height.
3489 // Center the image vertically in the cell.
3490 // Adjust the destRect y accordingly.
3491 destRect.y += (imageRect.height - destRect.height)/2;
3492 }
3494 // It's almost time to paint the image.
3495 // Deflate destRect for the border and padding.
3496 destRect.Deflate(bp);
3498 // Get the image source rectangle - the rectangle containing the part of
3499 // the image that we are going to display.
3500 // sourceRect will be passed as the aSrcRect argument in the DrawImage method.
3501 nsRect sourceRect = GetImageSourceRect(imageContext, useImageRegion, image);
3503 // Let's say that the image is 100 pixels tall and
3504 // that the CSS has specified that the destination height should be 50
3505 // pixels tall. Let's say that the cell height is only 20 pixels. So, in
3506 // those 20 visible pixels, we want to see the top 20/50ths of the image.
3507 // So, the sourceRect.height should be 100 * 20 / 50, which is 40 pixels.
3508 // Essentially, we are scaling the image as dictated by the CSS destination
3509 // height and width, and we are then clipping the scaled image by the cell
3510 // width and height.
3511 nsIntSize rawImageSize;
3512 image->GetWidth(&rawImageSize.width);
3513 image->GetHeight(&rawImageSize.height);
3514 nsRect wholeImageDest =
3515 nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect,
3516 nsRect(destRect.TopLeft(), imageDestSize));
3518 gfxContext* ctx = aRenderingContext.ThebesContext();
3519 if (opacity != 1.0f) {
3520 ctx->PushGroup(gfxContentType::COLOR_ALPHA);
3521 }
3523 nsLayoutUtils::DrawImage(&aRenderingContext, image,
3524 nsLayoutUtils::GetGraphicsFilterForFrame(this),
3525 wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect,
3526 imgIContainer::FLAG_NONE);
3528 if (opacity != 1.0f) {
3529 ctx->PopGroupToSource();
3530 ctx->Paint(opacity);
3531 }
3532 }
3534 // Update the aRemainingWidth and aCurrX values.
3535 imageRect.Inflate(imageMargin);
3536 aRemainingWidth -= imageRect.width;
3537 if (!isRTL)
3538 aCurrX += imageRect.width;
3539 }
3541 void
3542 nsTreeBodyFrame::PaintText(int32_t aRowIndex,
3543 nsTreeColumn* aColumn,
3544 const nsRect& aTextRect,
3545 nsPresContext* aPresContext,
3546 nsRenderingContext& aRenderingContext,
3547 const nsRect& aDirtyRect,
3548 nscoord& aCurrX)
3549 {
3550 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
3552 bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL;
3554 // Now obtain the text for our cell.
3555 nsAutoString text;
3556 mView->GetCellText(aRowIndex, aColumn, text);
3557 // We're going to paint this text so we need to ensure bidi is enabled if
3558 // necessary
3559 CheckTextForBidi(text);
3561 if (text.Length() == 0)
3562 return; // Don't paint an empty string. XXX What about background/borders? Still paint?
3564 // Resolve style for the text. It contains all the info we need to lay ourselves
3565 // out and to paint.
3566 nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext);
3568 // Obtain opacity value for the image.
3569 float opacity = textContext->StyleDisplay()->mOpacity;
3571 // Obtain the margins for the text and then deflate our rect by that
3572 // amount. The text is assumed to be contained within the deflated rect.
3573 nsRect textRect(aTextRect);
3574 nsMargin textMargin;
3575 textContext->StyleMargin()->GetMargin(textMargin);
3576 textRect.Deflate(textMargin);
3578 // Adjust the rect for its border and padding.
3579 nsMargin bp(0,0,0,0);
3580 GetBorderPadding(textContext, bp);
3581 textRect.Deflate(bp);
3583 // Compute our text size.
3584 nsRefPtr<nsFontMetrics> fontMet;
3585 nsLayoutUtils::GetFontMetricsForStyleContext(textContext,
3586 getter_AddRefs(fontMet));
3588 nscoord height = fontMet->MaxHeight();
3589 nscoord baseline = fontMet->MaxAscent();
3591 // Center the text. XXX Obey vertical-align style prop?
3592 if (height < textRect.height) {
3593 textRect.y += (textRect.height - height)/2;
3594 textRect.height = height;
3595 }
3597 // Set our font.
3598 aRenderingContext.SetFont(fontMet);
3600 AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, textRect);
3601 textRect.Inflate(bp);
3603 // Subtract out the remaining width.
3604 if (!isRTL)
3605 aCurrX += textRect.width + textMargin.LeftRight();
3607 PaintBackgroundLayer(textContext, aPresContext, aRenderingContext, textRect, aDirtyRect);
3609 // Time to paint our text.
3610 textRect.Deflate(bp);
3612 // Set our color.
3613 aRenderingContext.SetColor(textContext->StyleColor()->mColor);
3615 // Draw decorations.
3616 uint8_t decorations = textContext->StyleTextReset()->mTextDecorationLine;
3618 nscoord offset;
3619 nscoord size;
3620 if (decorations & (NS_FONT_DECORATION_OVERLINE | NS_FONT_DECORATION_UNDERLINE)) {
3621 fontMet->GetUnderline(offset, size);
3622 if (decorations & NS_FONT_DECORATION_OVERLINE)
3623 aRenderingContext.FillRect(textRect.x, textRect.y, textRect.width, size);
3624 if (decorations & NS_FONT_DECORATION_UNDERLINE)
3625 aRenderingContext.FillRect(textRect.x, textRect.y + baseline - offset, textRect.width, size);
3626 }
3627 if (decorations & NS_FONT_DECORATION_LINE_THROUGH) {
3628 fontMet->GetStrikeout(offset, size);
3629 aRenderingContext.FillRect(textRect.x, textRect.y + baseline - offset, textRect.width, size);
3630 }
3631 nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell);
3633 gfxContext* ctx = aRenderingContext.ThebesContext();
3634 if (opacity != 1.0f) {
3635 ctx->PushGroup(gfxContentType::COLOR_ALPHA);
3636 }
3638 nsLayoutUtils::DrawString(this, &aRenderingContext, text.get(), text.Length(),
3639 textRect.TopLeft() + nsPoint(0, baseline), cellContext);
3641 if (opacity != 1.0f) {
3642 ctx->PopGroupToSource();
3643 ctx->Paint(opacity);
3644 }
3646 }
3648 void
3649 nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex,
3650 nsTreeColumn* aColumn,
3651 const nsRect& aCheckboxRect,
3652 nsPresContext* aPresContext,
3653 nsRenderingContext& aRenderingContext,
3654 const nsRect& aDirtyRect)
3655 {
3656 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
3658 // Resolve style for the checkbox.
3659 nsStyleContext* checkboxContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecheckbox);
3661 nscoord rightEdge = aCheckboxRect.XMost();
3663 // Obtain the margins for the checkbox and then deflate our rect by that
3664 // amount. The checkbox is assumed to be contained within the deflated rect.
3665 nsRect checkboxRect(aCheckboxRect);
3666 nsMargin checkboxMargin;
3667 checkboxContext->StyleMargin()->GetMargin(checkboxMargin);
3668 checkboxRect.Deflate(checkboxMargin);
3670 nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext);
3672 if (imageSize.height > checkboxRect.height)
3673 imageSize.height = checkboxRect.height;
3674 if (imageSize.width > checkboxRect.width)
3675 imageSize.width = checkboxRect.width;
3677 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL)
3678 checkboxRect.x = rightEdge - checkboxRect.width;
3680 // Paint our borders and background for our image rect.
3681 PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext, checkboxRect, aDirtyRect);
3683 // Time to paint the checkbox.
3684 // Adjust the rect for its border and padding.
3685 nsMargin bp(0,0,0,0);
3686 GetBorderPadding(checkboxContext, bp);
3687 checkboxRect.Deflate(bp);
3689 // Get the image for drawing.
3690 nsCOMPtr<imgIContainer> image;
3691 bool useImageRegion = true;
3692 GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion, getter_AddRefs(image));
3693 if (image) {
3694 nsPoint pt = checkboxRect.TopLeft();
3696 if (imageSize.height < checkboxRect.height) {
3697 pt.y += (checkboxRect.height - imageSize.height)/2;
3698 }
3700 if (imageSize.width < checkboxRect.width) {
3701 pt.x += (checkboxRect.width - imageSize.width)/2;
3702 }
3704 // Paint the image.
3705 nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image,
3706 GraphicsFilter::FILTER_NEAREST, pt, &aDirtyRect,
3707 imgIContainer::FLAG_NONE, &imageSize);
3708 }
3709 }
3711 void
3712 nsTreeBodyFrame::PaintProgressMeter(int32_t aRowIndex,
3713 nsTreeColumn* aColumn,
3714 const nsRect& aProgressMeterRect,
3715 nsPresContext* aPresContext,
3716 nsRenderingContext& aRenderingContext,
3717 const nsRect& aDirtyRect)
3718 {
3719 NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed");
3721 // Resolve style for the progress meter. It contains all the info we need
3722 // to lay ourselves out and to paint.
3723 nsStyleContext* meterContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeprogressmeter);
3725 // Obtain the margins for the progress meter and then deflate our rect by that
3726 // amount. The progress meter is assumed to be contained within the deflated
3727 // rect.
3728 nsRect meterRect(aProgressMeterRect);
3729 nsMargin meterMargin;
3730 meterContext->StyleMargin()->GetMargin(meterMargin);
3731 meterRect.Deflate(meterMargin);
3733 // Paint our borders and background for our progress meter rect.
3734 PaintBackgroundLayer(meterContext, aPresContext, aRenderingContext, meterRect, aDirtyRect);
3736 // Time to paint our progress.
3737 int32_t state;
3738 mView->GetProgressMode(aRowIndex, aColumn, &state);
3739 if (state == nsITreeView::PROGRESS_NORMAL) {
3740 // Adjust the rect for its border and padding.
3741 AdjustForBorderPadding(meterContext, meterRect);
3743 // Set our color.
3744 aRenderingContext.SetColor(meterContext->StyleColor()->mColor);
3746 // Now obtain the value for our cell.
3747 nsAutoString value;
3748 mView->GetCellValue(aRowIndex, aColumn, value);
3750 nsresult rv;
3751 int32_t intValue = value.ToInteger(&rv);
3752 if (intValue < 0)
3753 intValue = 0;
3754 else if (intValue > 100)
3755 intValue = 100;
3757 nscoord meterWidth = NSToCoordRound((float)intValue / 100 * meterRect.width);
3758 if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL)
3759 meterRect.x += meterRect.width - meterWidth; // right align
3760 meterRect.width = meterWidth;
3761 bool useImageRegion = true;
3762 nsCOMPtr<imgIContainer> image;
3763 GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image));
3764 if (image) {
3765 int32_t width, height;
3766 image->GetWidth(&width);
3767 image->GetHeight(&height);
3768 nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(),
3769 height*nsDeviceContext::AppUnitsPerCSSPixel());
3770 nsLayoutUtils::DrawImage(&aRenderingContext, image,
3771 nsLayoutUtils::GetGraphicsFilterForFrame(this),
3772 nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
3773 aDirtyRect, imgIContainer::FLAG_NONE);
3774 } else {
3775 aRenderingContext.FillRect(meterRect);
3776 }
3777 }
3778 else if (state == nsITreeView::PROGRESS_UNDETERMINED) {
3779 // Adjust the rect for its border and padding.
3780 AdjustForBorderPadding(meterContext, meterRect);
3782 bool useImageRegion = true;
3783 nsCOMPtr<imgIContainer> image;
3784 GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image));
3785 if (image) {
3786 int32_t width, height;
3787 image->GetWidth(&width);
3788 image->GetHeight(&height);
3789 nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(),
3790 height*nsDeviceContext::AppUnitsPerCSSPixel());
3791 nsLayoutUtils::DrawImage(&aRenderingContext, image,
3792 nsLayoutUtils::GetGraphicsFilterForFrame(this),
3793 nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(),
3794 aDirtyRect, imgIContainer::FLAG_NONE);
3795 }
3796 }
3797 }
3800 void
3801 nsTreeBodyFrame::PaintDropFeedback(const nsRect& aDropFeedbackRect,
3802 nsPresContext* aPresContext,
3803 nsRenderingContext& aRenderingContext,
3804 const nsRect& aDirtyRect,
3805 nsPoint aPt)
3806 {
3807 // Paint the drop feedback in between rows.
3809 nscoord currX;
3811 // Adjust for the primary cell.
3812 nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn();
3814 if (primaryCol) {
3815 #ifdef DEBUG
3816 nsresult rv =
3817 #endif
3818 primaryCol->GetXInTwips(this, &currX);
3819 NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?");
3821 currX += aPt.x - mHorzPosition;
3822 } else {
3823 currX = aDropFeedbackRect.x;
3824 }
3826 PrefillPropertyArray(mSlots->mDropRow, primaryCol);
3828 // Resolve the style to use for the drop feedback.
3829 nsStyleContext* feedbackContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreedropfeedback);
3831 // Paint only if it is visible.
3832 if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
3833 int32_t level;
3834 mView->GetLevel(mSlots->mDropRow, &level);
3836 // If our previous or next row has greater level use that for
3837 // correct visual indentation.
3838 if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) {
3839 if (mSlots->mDropRow > 0) {
3840 int32_t previousLevel;
3841 mView->GetLevel(mSlots->mDropRow - 1, &previousLevel);
3842 if (previousLevel > level)
3843 level = previousLevel;
3844 }
3845 }
3846 else {
3847 if (mSlots->mDropRow < mRowCount - 1) {
3848 int32_t nextLevel;
3849 mView->GetLevel(mSlots->mDropRow + 1, &nextLevel);
3850 if (nextLevel > level)
3851 level = nextLevel;
3852 }
3853 }
3855 currX += mIndentation * level;
3857 if (primaryCol){
3858 nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty);
3859 nsRect imageSize;
3860 nsRect twistyRect;
3861 GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect, aPresContext,
3862 aRenderingContext, twistyContext);
3863 nsMargin twistyMargin;
3864 twistyContext->StyleMargin()->GetMargin(twistyMargin);
3865 twistyRect.Inflate(twistyMargin);
3866 currX += twistyRect.width;
3867 }
3869 const nsStylePosition* stylePosition = feedbackContext->StylePosition();
3871 // Obtain the width for the drop feedback or use default value.
3872 nscoord width;
3873 if (stylePosition->mWidth.GetUnit() == eStyleUnit_Coord)
3874 width = stylePosition->mWidth.GetCoordValue();
3875 else {
3876 // Use default width 50px.
3877 width = nsPresContext::CSSPixelsToAppUnits(50);
3878 }
3880 // Obtain the height for the drop feedback or use default value.
3881 nscoord height;
3882 if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord)
3883 height = stylePosition->mHeight.GetCoordValue();
3884 else {
3885 // Use default height 2px.
3886 height = nsPresContext::CSSPixelsToAppUnits(2);
3887 }
3889 // Obtain the margins for the drop feedback and then deflate our rect
3890 // by that amount.
3891 nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height);
3892 nsMargin margin;
3893 feedbackContext->StyleMargin()->GetMargin(margin);
3894 feedbackRect.Deflate(margin);
3896 feedbackRect.y += (aDropFeedbackRect.height - height) / 2;
3898 // Finally paint the drop feedback.
3899 PaintBackgroundLayer(feedbackContext, aPresContext, aRenderingContext, feedbackRect, aDirtyRect);
3900 }
3901 }
3903 void
3904 nsTreeBodyFrame::PaintBackgroundLayer(nsStyleContext* aStyleContext,
3905 nsPresContext* aPresContext,
3906 nsRenderingContext& aRenderingContext,
3907 const nsRect& aRect,
3908 const nsRect& aDirtyRect)
3909 {
3910 const nsStyleBorder* myBorder = aStyleContext->StyleBorder();
3912 nsCSSRendering::PaintBackgroundWithSC(aPresContext, aRenderingContext,
3913 this, aDirtyRect, aRect,
3914 aStyleContext, *myBorder,
3915 nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES);
3917 nsCSSRendering::PaintBorderWithStyleBorder(aPresContext, aRenderingContext,
3918 this, aDirtyRect, aRect,
3919 *myBorder, mStyleContext);
3921 nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this,
3922 aDirtyRect, aRect, aStyleContext);
3923 }
3925 // Scrolling
3926 nsresult
3927 nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow)
3928 {
3929 ScrollParts parts = GetScrollParts();
3930 nsresult rv = EnsureRowIsVisibleInternal(parts, aRow);
3931 NS_ENSURE_SUCCESS(rv, rv);
3932 UpdateScrollbars(parts);
3933 return rv;
3934 }
3936 nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow)
3937 {
3938 if (!mView || !mPageLength)
3939 return NS_OK;
3941 if (mTopRowIndex <= aRow && mTopRowIndex+mPageLength > aRow)
3942 return NS_OK;
3944 if (aRow < mTopRowIndex)
3945 ScrollToRowInternal(aParts, aRow);
3946 else {
3947 // Bring it just on-screen.
3948 int32_t distance = aRow - (mTopRowIndex+mPageLength)+1;
3949 ScrollToRowInternal(aParts, mTopRowIndex+distance);
3950 }
3952 return NS_OK;
3953 }
3955 nsresult
3956 nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol)
3957 {
3958 nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
3959 if (!col)
3960 return NS_ERROR_INVALID_ARG;
3962 ScrollParts parts = GetScrollParts();
3964 nscoord result = -1;
3965 nsresult rv;
3967 nscoord columnPos;
3968 rv = col->GetXInTwips(this, &columnPos);
3969 if(NS_FAILED(rv)) return rv;
3971 nscoord columnWidth;
3972 rv = col->GetWidthInTwips(this, &columnWidth);
3973 if(NS_FAILED(rv)) return rv;
3975 // If the start of the column is before the
3976 // start of the horizontal view, then scroll
3977 if (columnPos < mHorzPosition)
3978 result = columnPos;
3979 // If the end of the column is past the end of
3980 // the horizontal view, then scroll
3981 else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width))
3982 result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) + mHorzPosition;
3984 if (result != -1) {
3985 rv = ScrollHorzInternal(parts, result);
3986 if(NS_FAILED(rv)) return rv;
3987 }
3989 rv = EnsureRowIsVisibleInternal(parts, aRow);
3990 NS_ENSURE_SUCCESS(rv, rv);
3991 UpdateScrollbars(parts);
3992 return rv;
3993 }
3995 nsresult
3996 nsTreeBodyFrame::ScrollToCell(int32_t aRow, nsITreeColumn* aCol)
3997 {
3998 ScrollParts parts = GetScrollParts();
3999 nsresult rv = ScrollToRowInternal(parts, aRow);
4000 NS_ENSURE_SUCCESS(rv, rv);
4002 rv = ScrollToColumnInternal(parts, aCol);
4003 NS_ENSURE_SUCCESS(rv, rv);
4005 UpdateScrollbars(parts);
4006 return rv;
4007 }
4009 nsresult
4010 nsTreeBodyFrame::ScrollToColumn(nsITreeColumn* aCol)
4011 {
4012 ScrollParts parts = GetScrollParts();
4013 nsresult rv = ScrollToColumnInternal(parts, aCol);
4014 NS_ENSURE_SUCCESS(rv, rv);
4015 UpdateScrollbars(parts);
4016 return rv;
4017 }
4019 nsresult nsTreeBodyFrame::ScrollToColumnInternal(const ScrollParts& aParts,
4020 nsITreeColumn* aCol)
4021 {
4022 nsRefPtr<nsTreeColumn> col = GetColumnImpl(aCol);
4023 if (!col)
4024 return NS_ERROR_INVALID_ARG;
4026 nscoord x;
4027 nsresult rv = col->GetXInTwips(this, &x);
4028 if (NS_FAILED(rv))
4029 return rv;
4031 return ScrollHorzInternal(aParts, x);
4032 }
4034 nsresult
4035 nsTreeBodyFrame::ScrollToHorizontalPosition(int32_t aHorizontalPosition)
4036 {
4037 ScrollParts parts = GetScrollParts();
4038 int32_t position = nsPresContext::CSSPixelsToAppUnits(aHorizontalPosition);
4039 nsresult rv = ScrollHorzInternal(parts, position);
4040 NS_ENSURE_SUCCESS(rv, rv);
4041 UpdateScrollbars(parts);
4042 return rv;
4043 }
4045 nsresult
4046 nsTreeBodyFrame::ScrollToRow(int32_t aRow)
4047 {
4048 ScrollParts parts = GetScrollParts();
4049 nsresult rv = ScrollToRowInternal(parts, aRow);
4050 NS_ENSURE_SUCCESS(rv, rv);
4051 UpdateScrollbars(parts);
4052 return rv;
4053 }
4055 nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow)
4056 {
4057 ScrollInternal(aParts, aRow);
4059 return NS_OK;
4060 }
4062 nsresult
4063 nsTreeBodyFrame::ScrollByLines(int32_t aNumLines)
4064 {
4065 if (!mView)
4066 return NS_OK;
4068 int32_t newIndex = mTopRowIndex + aNumLines;
4069 if (newIndex < 0)
4070 newIndex = 0;
4071 else {
4072 int32_t lastPageTopRow = mRowCount - mPageLength;
4073 if (newIndex > lastPageTopRow)
4074 newIndex = lastPageTopRow;
4075 }
4076 ScrollToRow(newIndex);
4078 return NS_OK;
4079 }
4081 nsresult
4082 nsTreeBodyFrame::ScrollByPages(int32_t aNumPages)
4083 {
4084 if (!mView)
4085 return NS_OK;
4087 int32_t newIndex = mTopRowIndex + aNumPages * mPageLength;
4088 if (newIndex < 0)
4089 newIndex = 0;
4090 else {
4091 int32_t lastPageTopRow = mRowCount - mPageLength;
4092 if (newIndex > lastPageTopRow)
4093 newIndex = lastPageTopRow;
4094 }
4095 ScrollToRow(newIndex);
4097 return NS_OK;
4098 }
4100 nsresult
4101 nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, int32_t aRow)
4102 {
4103 if (!mView)
4104 return NS_OK;
4106 int32_t delta = aRow - mTopRowIndex;
4108 if (delta > 0) {
4109 if (mTopRowIndex == (mRowCount - mPageLength + 1))
4110 return NS_OK;
4111 }
4112 else {
4113 if (mTopRowIndex == 0)
4114 return NS_OK;
4115 }
4117 mTopRowIndex += delta;
4119 Invalidate();
4121 PostScrollEvent();
4122 return NS_OK;
4123 }
4125 nsresult
4126 nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition)
4127 {
4128 if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar)
4129 return NS_OK;
4131 if (aPosition == mHorzPosition)
4132 return NS_OK;
4134 if (aPosition < 0 || aPosition > mHorzWidth)
4135 return NS_OK;
4137 nsRect bounds = aParts.mColumnsFrame->GetRect();
4138 if (aPosition > (mHorzWidth - bounds.width))
4139 aPosition = mHorzWidth - bounds.width;
4141 mHorzPosition = aPosition;
4143 Invalidate();
4145 // Update the column scroll view
4146 nsWeakFrame weakFrame(this);
4147 aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0),
4148 nsIScrollableFrame::INSTANT);
4149 if (!weakFrame.IsAlive()) {
4150 return NS_ERROR_FAILURE;
4151 }
4152 // And fire off an event about it all
4153 PostScrollEvent();
4154 return NS_OK;
4155 }
4157 NS_IMETHODIMP
4158 nsTreeBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex)
4159 {
4160 ScrollParts parts = GetScrollParts();
4162 if (aScrollbar == parts.mVScrollbar) {
4163 if (aNewIndex > aOldIndex)
4164 ScrollToRowInternal(parts, mTopRowIndex+1);
4165 else if (aNewIndex < aOldIndex)
4166 ScrollToRowInternal(parts, mTopRowIndex-1);
4167 } else {
4168 nsresult rv = ScrollHorzInternal(parts, aNewIndex);
4169 if (NS_FAILED(rv)) return rv;
4170 }
4172 UpdateScrollbars(parts);
4174 return NS_OK;
4175 }
4177 NS_IMETHODIMP
4178 nsTreeBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex)
4179 {
4180 ScrollParts parts = GetScrollParts();
4182 if (aOldIndex == aNewIndex)
4183 return NS_OK;
4185 // Vertical Scrollbar
4186 if (parts.mVScrollbar == aScrollbar) {
4187 nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight);
4189 nscoord newrow = aNewIndex/rh;
4190 ScrollInternal(parts, newrow);
4191 // Horizontal Scrollbar
4192 } else if (parts.mHScrollbar == aScrollbar) {
4193 nsresult rv = ScrollHorzInternal(parts, aNewIndex);
4194 if (NS_FAILED(rv)) return rv;
4195 }
4197 UpdateScrollbars(parts);
4198 return NS_OK;
4199 }
4201 // The style cache.
4202 nsStyleContext*
4203 nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement)
4204 {
4205 return mStyleCache.GetStyleContext(this, PresContext(), mContent,
4206 mStyleContext, aPseudoElement,
4207 mScratchArray);
4208 }
4210 // Our comparator for resolving our complex pseudos
4211 bool
4212 nsTreeBodyFrame::PseudoMatches(nsCSSSelector* aSelector)
4213 {
4214 // Iterate the class list. For each item in the list, see if
4215 // it is contained in our scratch array. If we have a miss, then
4216 // we aren't a match. If all items in the class list are
4217 // present in the scratch array, then we have a match.
4218 nsAtomList* curr = aSelector->mClassList;
4219 while (curr) {
4220 if (!mScratchArray.Contains(curr->mAtom))
4221 return false;
4222 curr = curr->mNext;
4223 }
4224 return true;
4225 }
4227 nsIContent*
4228 nsTreeBodyFrame::GetBaseElement()
4229 {
4230 nsIFrame* parent = GetParent();
4231 while (parent) {
4232 nsIContent* content = parent->GetContent();
4233 if (content) {
4234 nsINodeInfo* ni = content->NodeInfo();
4236 if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL) ||
4237 (ni->Equals(nsGkAtoms::select) &&
4238 content->IsHTML()))
4239 return content;
4240 }
4242 parent = parent->GetParent();
4243 }
4245 return nullptr;
4246 }
4248 nsresult
4249 nsTreeBodyFrame::ClearStyleAndImageCaches()
4250 {
4251 mStyleCache.Clear();
4252 mImageCache.EnumerateRead(CancelImageRequest, this);
4253 mImageCache.Clear();
4254 return NS_OK;
4255 }
4257 /* virtual */ void
4258 nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
4259 {
4260 nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext);
4262 // Clear the style cache; the pointers are no longer even valid
4263 mStyleCache.Clear();
4264 // XXX The following is hacky, but it's not incorrect,
4265 // and appears to fix a few bugs with style changes, like text zoom and
4266 // dpi changes
4267 mIndentation = GetIndentation();
4268 mRowHeight = GetRowHeight();
4269 mStringWidth = -1;
4270 }
4272 bool
4273 nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip)
4274 {
4275 rect.x -= mHorzPosition;
4277 // Scrolled out before
4278 if (rect.XMost() <= mInnerBox.x)
4279 return false;
4281 // Scrolled out after
4282 if (rect.x > mInnerBox.XMost())
4283 return false;
4285 if (clip) {
4286 nscoord leftEdge = std::max(rect.x, mInnerBox.x);
4287 nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost());
4288 rect.x = leftEdge;
4289 rect.width = rightEdge - leftEdge;
4291 // Should have returned false above
4292 NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync");
4293 }
4295 return true;
4296 }
4298 bool
4299 nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex)
4300 {
4301 // Check first for partially visible last row.
4302 if (aRowIndex == mRowCount - 1) {
4303 nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight;
4304 if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height)
4305 return true;
4306 }
4308 if (aRowIndex > 0 && aRowIndex < mRowCount - 1)
4309 return true;
4311 return false;
4312 }
4314 // Given a dom event, figure out which row in the tree the mouse is over,
4315 // if we should drop before/after/on that row or we should auto-scroll.
4316 // Doesn't query the content about if the drag is allowable, that's done elsewhere.
4317 //
4318 // For containers, we break up the vertical space of the row as follows: if in
4319 // the topmost 25%, the drop is _before_ the row the mouse is over; if in the
4320 // last 25%, _after_; in the middle 50%, we consider it a drop _on_ the container.
4321 //
4322 // For non-containers, if the mouse is in the top 50% of the row, the drop is
4323 // _before_ and the bottom 50% _after_
4324 void
4325 nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent,
4326 int32_t* aRow,
4327 int16_t* aOrient,
4328 int16_t* aScrollLines)
4329 {
4330 *aOrient = -1;
4331 *aScrollLines = 0;
4333 // Convert the event's point to our coordinates. We want it in
4334 // the coordinates of our inner box's coordinates.
4335 nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this);
4336 int32_t xTwips = pt.x - mInnerBox.x;
4337 int32_t yTwips = pt.y - mInnerBox.y;
4339 *aRow = GetRowAt(xTwips, yTwips);
4340 if (*aRow >=0) {
4341 // Compute the top/bottom of the row in question.
4342 int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
4344 bool isContainer = false;
4345 mView->IsContainer (*aRow, &isContainer);
4346 if (isContainer) {
4347 // for a container, use a 25%/50%/25% breakdown
4348 if (yOffset < mRowHeight / 4)
4349 *aOrient = nsITreeView::DROP_BEFORE;
4350 else if (yOffset > mRowHeight - (mRowHeight / 4))
4351 *aOrient = nsITreeView::DROP_AFTER;
4352 else
4353 *aOrient = nsITreeView::DROP_ON;
4354 }
4355 else {
4356 // for a non-container use a 50%/50% breakdown
4357 if (yOffset < mRowHeight / 2)
4358 *aOrient = nsITreeView::DROP_BEFORE;
4359 else
4360 *aOrient = nsITreeView::DROP_AFTER;
4361 }
4362 }
4364 if (CanAutoScroll(*aRow)) {
4365 // Get the max value from the look and feel service.
4366 int32_t scrollLinesMax =
4367 LookAndFeel::GetInt(LookAndFeel::eIntID_TreeScrollLinesMax, 0);
4368 scrollLinesMax--;
4369 if (scrollLinesMax < 0)
4370 scrollLinesMax = 0;
4372 // Determine if we're w/in a margin of the top/bottom of the tree during a drag.
4373 // This will ultimately cause us to scroll, but that's done elsewhere.
4374 nscoord height = (3 * mRowHeight) / 4;
4375 if (yTwips < height) {
4376 // scroll up
4377 *aScrollLines = NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1);
4378 }
4379 else if (yTwips > mRect.height - height) {
4380 // scroll down
4381 *aScrollLines = NSToIntRound(scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1);
4382 }
4383 }
4384 } // ComputeDropPosition
4386 void
4387 nsTreeBodyFrame::OpenCallback(nsITimer *aTimer, void *aClosure)
4388 {
4389 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4390 if (self) {
4391 aTimer->Cancel();
4392 self->mSlots->mTimer = nullptr;
4394 if (self->mSlots->mDropRow >= 0) {
4395 self->mSlots->mArray.AppendElement(self->mSlots->mDropRow);
4396 self->mView->ToggleOpenState(self->mSlots->mDropRow);
4397 }
4398 }
4399 }
4401 void
4402 nsTreeBodyFrame::CloseCallback(nsITimer *aTimer, void *aClosure)
4403 {
4404 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4405 if (self) {
4406 aTimer->Cancel();
4407 self->mSlots->mTimer = nullptr;
4409 for (uint32_t i = self->mSlots->mArray.Length(); i--; ) {
4410 if (self->mView)
4411 self->mView->ToggleOpenState(self->mSlots->mArray[i]);
4412 }
4413 self->mSlots->mArray.Clear();
4414 }
4415 }
4417 void
4418 nsTreeBodyFrame::LazyScrollCallback(nsITimer *aTimer, void *aClosure)
4419 {
4420 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4421 if (self) {
4422 aTimer->Cancel();
4423 self->mSlots->mTimer = nullptr;
4425 if (self->mView) {
4426 // Set a new timer to scroll the tree repeatedly.
4427 self->CreateTimer(LookAndFeel::eIntID_TreeScrollDelay,
4428 ScrollCallback, nsITimer::TYPE_REPEATING_SLACK,
4429 getter_AddRefs(self->mSlots->mTimer));
4430 self->ScrollByLines(self->mSlots->mScrollLines);
4431 // ScrollByLines may have deleted |self|.
4432 }
4433 }
4434 }
4436 void
4437 nsTreeBodyFrame::ScrollCallback(nsITimer *aTimer, void *aClosure)
4438 {
4439 nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure);
4440 if (self) {
4441 // Don't scroll if we are already at the top or bottom of the view.
4442 if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
4443 self->ScrollByLines(self->mSlots->mScrollLines);
4444 }
4445 else {
4446 aTimer->Cancel();
4447 self->mSlots->mTimer = nullptr;
4448 }
4449 }
4450 }
4452 NS_IMETHODIMP
4453 nsTreeBodyFrame::ScrollEvent::Run()
4454 {
4455 if (mInner) {
4456 mInner->FireScrollEvent();
4457 }
4458 return NS_OK;
4459 }
4462 void
4463 nsTreeBodyFrame::FireScrollEvent()
4464 {
4465 mScrollEvent.Forget();
4466 WidgetGUIEvent event(true, NS_SCROLL_EVENT, nullptr);
4467 // scroll events fired at elements don't bubble
4468 event.mFlags.mBubbles = false;
4469 EventDispatcher::Dispatch(GetContent(), PresContext(), &event);
4470 }
4472 void
4473 nsTreeBodyFrame::PostScrollEvent()
4474 {
4475 if (mScrollEvent.IsPending())
4476 return;
4478 nsRefPtr<ScrollEvent> ev = new ScrollEvent(this);
4479 if (NS_FAILED(NS_DispatchToCurrentThread(ev))) {
4480 NS_WARNING("failed to dispatch ScrollEvent");
4481 } else {
4482 mScrollEvent = ev;
4483 }
4484 }
4486 void
4487 nsTreeBodyFrame::ScrollbarActivityStarted() const
4488 {
4489 if (mScrollbarActivity) {
4490 mScrollbarActivity->ActivityStarted();
4491 }
4492 }
4494 void
4495 nsTreeBodyFrame::ScrollbarActivityStopped() const
4496 {
4497 if (mScrollbarActivity) {
4498 mScrollbarActivity->ActivityStopped();
4499 }
4500 }
4502 void
4503 nsTreeBodyFrame::DetachImageListeners()
4504 {
4505 mCreatedListeners.Clear();
4506 }
4508 void
4509 nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener)
4510 {
4511 if (aListener) {
4512 mCreatedListeners.RemoveEntry(aListener);
4513 }
4514 }
4516 #ifdef ACCESSIBILITY
4517 void
4518 nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount)
4519 {
4520 nsCOMPtr<nsIContent> content(GetBaseElement());
4521 if (!content)
4522 return;
4524 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc());
4525 if (!domDoc)
4526 return;
4528 nsCOMPtr<nsIDOMEvent> event;
4529 domDoc->CreateEvent(NS_LITERAL_STRING("customevent"),
4530 getter_AddRefs(event));
4532 nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event));
4533 if (!treeEvent)
4534 return;
4536 nsCOMPtr<nsIWritablePropertyBag2> propBag(
4537 do_CreateInstance("@mozilla.org/hash-property-bag;1"));
4538 if (!propBag)
4539 return;
4541 // Set 'index' data - the row index rows are changed from.
4542 propBag->SetPropertyAsInt32(NS_LITERAL_STRING("index"), aIndex);
4544 // Set 'count' data - the number of changed rows.
4545 propBag->SetPropertyAsInt32(NS_LITERAL_STRING("count"), aCount);
4547 nsCOMPtr<nsIWritableVariant> detailVariant(
4548 do_CreateInstance("@mozilla.org/variant;1"));
4549 if (!detailVariant)
4550 return;
4552 detailVariant->SetAsISupports(propBag);
4553 treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"),
4554 true, false, detailVariant);
4556 event->SetTrusted(true);
4558 nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
4559 new AsyncEventDispatcher(content, event);
4560 asyncDispatcher->PostDOMEvent();
4561 }
4563 void
4564 nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx,
4565 nsITreeColumn *aStartCol,
4566 nsITreeColumn *aEndCol)
4567 {
4568 nsCOMPtr<nsIContent> content(GetBaseElement());
4569 if (!content)
4570 return;
4572 nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(content->OwnerDoc());
4573 if (!domDoc)
4574 return;
4576 nsCOMPtr<nsIDOMEvent> event;
4577 domDoc->CreateEvent(NS_LITERAL_STRING("customevent"),
4578 getter_AddRefs(event));
4580 nsCOMPtr<nsIDOMCustomEvent> treeEvent(do_QueryInterface(event));
4581 if (!treeEvent)
4582 return;
4584 nsCOMPtr<nsIWritablePropertyBag2> propBag(
4585 do_CreateInstance("@mozilla.org/hash-property-bag;1"));
4586 if (!propBag)
4587 return;
4589 if (aStartRowIdx != -1 && aEndRowIdx != -1) {
4590 // Set 'startrow' data - the start index of invalidated rows.
4591 propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startrow"),
4592 aStartRowIdx);
4594 // Set 'endrow' data - the end index of invalidated rows.
4595 propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endrow"),
4596 aEndRowIdx);
4597 }
4599 if (aStartCol && aEndCol) {
4600 // Set 'startcolumn' data - the start index of invalidated rows.
4601 int32_t startColIdx = 0;
4602 nsresult rv = aStartCol->GetIndex(&startColIdx);
4603 if (NS_FAILED(rv))
4604 return;
4606 propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"),
4607 startColIdx);
4609 // Set 'endcolumn' data - the start index of invalidated rows.
4610 int32_t endColIdx = 0;
4611 rv = aEndCol->GetIndex(&endColIdx);
4612 if (NS_FAILED(rv))
4613 return;
4615 propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"),
4616 endColIdx);
4617 }
4619 nsCOMPtr<nsIWritableVariant> detailVariant(
4620 do_CreateInstance("@mozilla.org/variant;1"));
4621 if (!detailVariant)
4622 return;
4624 detailVariant->SetAsISupports(propBag);
4625 treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"),
4626 true, false, detailVariant);
4628 event->SetTrusted(true);
4630 nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
4631 new AsyncEventDispatcher(content, event);
4632 asyncDispatcher->PostDOMEvent();
4633 }
4634 #endif
4636 class nsOverflowChecker : public nsRunnable
4637 {
4638 public:
4639 nsOverflowChecker(nsTreeBodyFrame* aFrame) : mFrame(aFrame) {}
4640 NS_IMETHOD Run() MOZ_OVERRIDE
4641 {
4642 if (mFrame.IsAlive()) {
4643 nsTreeBodyFrame* tree = static_cast<nsTreeBodyFrame*>(mFrame.GetFrame());
4644 nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts();
4645 tree->CheckOverflow(parts);
4646 }
4647 return NS_OK;
4648 }
4649 private:
4650 nsWeakFrame mFrame;
4651 };
4653 bool
4654 nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation)
4655 {
4656 ScrollParts parts = GetScrollParts();
4657 nsWeakFrame weakFrame(this);
4658 nsWeakFrame weakColumnsFrame(parts.mColumnsFrame);
4659 UpdateScrollbars(parts);
4660 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4661 if (aNeedsFullInvalidation) {
4662 Invalidate();
4663 }
4664 InvalidateScrollbars(parts, weakColumnsFrame);
4665 NS_ENSURE_TRUE(weakFrame.IsAlive(), false);
4667 // Overflow checking dispatches synchronous events, which can cause infinite
4668 // recursion during reflow. Do the first overflow check synchronously, but
4669 // force any nested checks to round-trip through the event loop. See bug
4670 // 905909.
4671 nsRefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this);
4672 if (!mCheckingOverflow) {
4673 nsContentUtils::AddScriptRunner(checker);
4674 } else {
4675 NS_DispatchToCurrentThread(checker);
4676 }
4677 return weakFrame.IsAlive();
4678 }
4680 nsresult
4681 nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest)
4682 {
4683 nsLayoutUtils::RegisterImageRequest(PresContext(),
4684 aRequest, nullptr);
4686 return NS_OK;
4687 }