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