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