michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "mozilla/ContentEvents.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/EventDispatcher.h" michael@0: #include "mozilla/MathAlgorithms.h" michael@0: #include "mozilla/MouseEvents.h" michael@0: #include "mozilla/Likely.h" michael@0: michael@0: #include "nsCOMPtr.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsNameSpaceManager.h" michael@0: michael@0: #include "nsTreeBodyFrame.h" michael@0: #include "nsTreeSelection.h" michael@0: #include "nsTreeImageListener.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsCSSAnonBoxes.h" michael@0: michael@0: #include "nsIContent.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsIBoxObject.h" michael@0: #include "nsIDOMCustomEvent.h" michael@0: #include "nsIDOMMouseEvent.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMXULElement.h" michael@0: #include "nsIDocument.h" michael@0: #include "mozilla/css/StyleRule.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsIXULTemplateBuilder.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsContainerFrame.h" michael@0: #include "nsView.h" michael@0: #include "nsViewManager.h" michael@0: #include "nsWidgetsCID.h" michael@0: #include "nsBoxFrame.h" michael@0: #include "nsBoxObject.h" michael@0: #include "nsIURL.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsTreeContentView.h" michael@0: #include "nsTreeUtils.h" michael@0: #include "nsITheme.h" michael@0: #include "imgIRequest.h" michael@0: #include "imgIContainer.h" michael@0: #include "imgILoader.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsTreeBoxObject.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsIScriptableRegion.h" michael@0: #include michael@0: #include "ScrollbarActivity.h" michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #include "nsIWritablePropertyBag2.h" michael@0: #endif michael@0: #include "nsBidiUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::layout; michael@0: michael@0: // Enumeration function that cancels all the image requests in our cache michael@0: static PLDHashOperator michael@0: CancelImageRequest(const nsAString& aKey, michael@0: nsTreeImageCacheEntry aEntry, void* aData) michael@0: { michael@0: michael@0: // If our imgIRequest object was registered with the refresh driver, michael@0: // then we need to deregister it. michael@0: nsTreeBodyFrame* frame = static_cast(aData); michael@0: michael@0: nsLayoutUtils::DeregisterImageRequest(frame->PresContext(), aEntry.request, michael@0: nullptr); michael@0: michael@0: aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED); michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: // michael@0: // NS_NewTreeFrame michael@0: // michael@0: // Creates a new tree frame michael@0: // michael@0: nsIFrame* michael@0: NS_NewTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsTreeBodyFrame(aPresShell, aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsTreeBodyFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsTreeBodyFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIScrollbarMediator) michael@0: NS_QUERYFRAME_ENTRY(nsIScrollbarOwner) michael@0: NS_QUERYFRAME_ENTRY(nsTreeBodyFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame) michael@0: michael@0: // Constructor michael@0: nsTreeBodyFrame::nsTreeBodyFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: :nsLeafBoxFrame(aPresShell, aContext), michael@0: mSlots(nullptr), michael@0: mImageCache(16), michael@0: mTopRowIndex(0), michael@0: mPageLength(0), michael@0: mHorzPosition(0), michael@0: mOriginalHorzWidth(-1), michael@0: mHorzWidth(0), michael@0: mAdjustWidth(0), michael@0: mRowHeight(0), michael@0: mIndentation(0), michael@0: mStringWidth(-1), michael@0: mUpdateBatchNest(0), michael@0: mRowCount(0), michael@0: mMouseOverRow(-1), michael@0: mFocused(false), michael@0: mHasFixedRowCount(false), michael@0: mVerticalOverflow(false), michael@0: mHorizontalOverflow(false), michael@0: mReflowCallbackPosted(false), michael@0: mCheckingOverflow(false) michael@0: { michael@0: mColumns = new nsTreeColumns(this); michael@0: } michael@0: michael@0: // Destructor michael@0: nsTreeBodyFrame::~nsTreeBodyFrame() michael@0: { michael@0: mImageCache.EnumerateRead(CancelImageRequest, this); michael@0: DetachImageListeners(); michael@0: delete mSlots; michael@0: } michael@0: michael@0: static void michael@0: GetBorderPadding(nsStyleContext* aContext, nsMargin& aMargin) michael@0: { michael@0: aMargin.SizeTo(0, 0, 0, 0); michael@0: if (!aContext->StylePadding()->GetPadding(aMargin)) { michael@0: NS_NOTYETIMPLEMENTED("percentage padding"); michael@0: } michael@0: aMargin += aContext->StyleBorder()->GetComputedBorder(); michael@0: } michael@0: michael@0: static void michael@0: AdjustForBorderPadding(nsStyleContext* aContext, nsRect& aRect) michael@0: { michael@0: nsMargin borderPadding(0, 0, 0, 0); michael@0: GetBorderPadding(aContext, borderPadding); michael@0: aRect.Deflate(borderPadding); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: mIndentation = GetIndentation(); michael@0: mRowHeight = GetRowHeight(); michael@0: michael@0: EnsureBoxObject(); michael@0: michael@0: if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) { michael@0: mScrollbarActivity = new ScrollbarActivity( michael@0: static_cast(this)); michael@0: } michael@0: } michael@0: michael@0: nsSize michael@0: nsTreeBodyFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: EnsureView(); michael@0: michael@0: nsIContent* baseElement = GetBaseElement(); michael@0: michael@0: nsSize min(0,0); michael@0: int32_t desiredRows; michael@0: if (MOZ_UNLIKELY(!baseElement)) { michael@0: desiredRows = 0; michael@0: } michael@0: else if (baseElement->Tag() == nsGkAtoms::select && michael@0: baseElement->IsHTML()) { michael@0: min.width = CalcMaxRowWidth(); michael@0: nsAutoString size; michael@0: baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::size, size); michael@0: if (!size.IsEmpty()) { michael@0: nsresult err; michael@0: desiredRows = size.ToInteger(&err); michael@0: mHasFixedRowCount = true; michael@0: mPageLength = desiredRows; michael@0: } michael@0: else { michael@0: desiredRows = 1; michael@0: } michael@0: } michael@0: else { michael@0: // tree michael@0: nsAutoString rows; michael@0: baseElement->GetAttr(kNameSpaceID_None, nsGkAtoms::rows, rows); michael@0: if (!rows.IsEmpty()) { michael@0: nsresult err; michael@0: desiredRows = rows.ToInteger(&err); michael@0: mPageLength = desiredRows; michael@0: } michael@0: else { michael@0: desiredRows = 0; michael@0: } michael@0: } michael@0: michael@0: min.height = mRowHeight * desiredRows; michael@0: michael@0: AddBorderAndPadding(min); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSMinSize(aBoxLayoutState, this, min, widthSet, heightSet); michael@0: michael@0: return min; michael@0: } michael@0: michael@0: nscoord michael@0: nsTreeBodyFrame::CalcMaxRowWidth() michael@0: { michael@0: if (mStringWidth != -1) michael@0: return mStringWidth; michael@0: michael@0: if (!mView) michael@0: return 0; michael@0: michael@0: nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); michael@0: nsMargin rowMargin(0,0,0,0); michael@0: GetBorderPadding(rowContext, rowMargin); michael@0: michael@0: nscoord rowWidth; michael@0: nsTreeColumn* col; michael@0: michael@0: nsRefPtr rc = michael@0: PresContext()->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: for (int32_t row = 0; row < mRowCount; ++row) { michael@0: rowWidth = 0; michael@0: michael@0: for (col = mColumns->GetFirstColumn(); col; col = col->GetNext()) { michael@0: nscoord desiredWidth, currentWidth; michael@0: nsresult rv = GetCellWidth(row, col, rc, desiredWidth, currentWidth); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("invalid column"); michael@0: continue; michael@0: } michael@0: rowWidth += desiredWidth; michael@0: } michael@0: michael@0: if (rowWidth > mStringWidth) michael@0: mStringWidth = rowWidth; michael@0: } michael@0: michael@0: mStringWidth += rowMargin.left + rowMargin.right; michael@0: return mStringWidth; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: if (mScrollbarActivity) { michael@0: mScrollbarActivity->Destroy(); michael@0: mScrollbarActivity = nullptr; michael@0: } michael@0: michael@0: mScrollEvent.Revoke(); michael@0: // Make sure we cancel any posted callbacks. michael@0: if (mReflowCallbackPosted) { michael@0: PresContext()->PresShell()->CancelReflowCallback(this); michael@0: mReflowCallbackPosted = false; michael@0: } michael@0: michael@0: if (mColumns) michael@0: mColumns->SetTree(nullptr); michael@0: michael@0: // Save off our info into the box object. michael@0: nsCOMPtr box(do_QueryInterface(mTreeBoxObject)); michael@0: if (box) { michael@0: if (mTopRowIndex > 0) { michael@0: nsAutoString topRowStr; topRowStr.AssignLiteral("topRow"); michael@0: nsAutoString topRow; michael@0: topRow.AppendInt(mTopRowIndex); michael@0: box->SetProperty(topRowStr.get(), topRow.get()); michael@0: } michael@0: michael@0: // Always null out the cached tree body frame. michael@0: box->ClearCachedValues(); michael@0: michael@0: mTreeBoxObject = nullptr; // Drop our ref here. michael@0: } michael@0: michael@0: if (mView) { michael@0: nsCOMPtr sel; michael@0: mView->GetSelection(getter_AddRefs(sel)); michael@0: if (sel) michael@0: sel->SetTree(nullptr); michael@0: mView->SetTree(nullptr); michael@0: mView = nullptr; michael@0: } michael@0: michael@0: nsLeafBoxFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::EnsureBoxObject() michael@0: { michael@0: if (!mTreeBoxObject) { michael@0: nsIContent* parent = GetBaseElement(); michael@0: if (parent) { michael@0: nsIDocument* nsDoc = parent->GetDocument(); michael@0: if (!nsDoc) // there may be no document, if we're called from Destroy() michael@0: return; michael@0: ErrorResult ignored; michael@0: nsCOMPtr box = michael@0: nsDoc->GetBoxObjectFor(parent->AsElement(), ignored); michael@0: // Ensure that we got a native box object. michael@0: nsCOMPtr pBox = do_QueryInterface(box); michael@0: if (pBox) { michael@0: nsCOMPtr realTreeBoxObject = do_QueryInterface(pBox); michael@0: if (realTreeBoxObject) { michael@0: nsTreeBodyFrame* innerTreeBoxObject = michael@0: static_cast(realTreeBoxObject.get()) michael@0: ->GetCachedTreeBody(); michael@0: ENSURE_TRUE(!innerTreeBoxObject || innerTreeBoxObject == this); michael@0: mTreeBoxObject = realTreeBoxObject; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::EnsureView() michael@0: { michael@0: if (!mView) { michael@0: if (PresContext()->PresShell()->IsReflowLocked()) { michael@0: if (!mReflowCallbackPosted) { michael@0: mReflowCallbackPosted = true; michael@0: PresContext()->PresShell()->PostReflowCallback(this); michael@0: } michael@0: return; michael@0: } michael@0: nsCOMPtr box = do_QueryInterface(mTreeBoxObject); michael@0: if (box) { michael@0: nsWeakFrame weakFrame(this); michael@0: nsCOMPtr treeView; michael@0: mTreeBoxObject->GetView(getter_AddRefs(treeView)); michael@0: if (treeView && weakFrame.IsAlive()) { michael@0: nsXPIDLString rowStr; michael@0: box->GetProperty(MOZ_UTF16("topRow"), michael@0: getter_Copies(rowStr)); michael@0: nsAutoString rowStr2(rowStr); michael@0: nsresult error; michael@0: int32_t rowIndex = rowStr2.ToInteger(&error); michael@0: michael@0: // Set our view. michael@0: SetView(treeView); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // Scroll to the given row. michael@0: // XXX is this optimal if we haven't laid out yet? michael@0: ScrollToRow(rowIndex); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // Clear out the property info for the top row, but we always keep the michael@0: // view current. michael@0: box->RemoveProperty(MOZ_UTF16("topRow")); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::ManageReflowCallback(const nsRect& aRect, nscoord aHorzWidth) michael@0: { michael@0: if (!mReflowCallbackPosted && michael@0: (!aRect.IsEqualEdges(mRect) || mHorzWidth != aHorzWidth)) { michael@0: PresContext()->PresShell()->PostReflowCallback(this); michael@0: mReflowCallbackPosted = true; michael@0: mOriginalHorzWidth = mHorzWidth; michael@0: } michael@0: else if (mReflowCallbackPosted && michael@0: mHorzWidth != aHorzWidth && mOriginalHorzWidth == aHorzWidth) { michael@0: PresContext()->PresShell()->CancelReflowCallback(this); michael@0: mReflowCallbackPosted = false; michael@0: mOriginalHorzWidth = -1; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::SetBounds(nsBoxLayoutState& aBoxLayoutState, const nsRect& aRect, michael@0: bool aRemoveOverflowArea) michael@0: { michael@0: nscoord horzWidth = CalcHorzWidth(GetScrollParts()); michael@0: ManageReflowCallback(aRect, horzWidth); michael@0: mHorzWidth = horzWidth; michael@0: michael@0: nsLeafBoxFrame::SetBounds(aBoxLayoutState, aRect, aRemoveOverflowArea); michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsTreeBodyFrame::ReflowFinished() michael@0: { michael@0: if (!mView) { michael@0: nsWeakFrame weakFrame(this); michael@0: EnsureView(); michael@0: NS_ENSURE_TRUE(weakFrame.IsAlive(), false); michael@0: } michael@0: if (mView) { michael@0: CalcInnerBox(); michael@0: ScrollParts parts = GetScrollParts(); michael@0: mHorzWidth = CalcHorzWidth(parts); michael@0: if (!mHasFixedRowCount) { michael@0: mPageLength = mInnerBox.height / mRowHeight; michael@0: } michael@0: michael@0: int32_t lastPageTopRow = std::max(0, mRowCount - mPageLength); michael@0: if (mTopRowIndex > lastPageTopRow) michael@0: ScrollToRowInternal(parts, lastPageTopRow); michael@0: michael@0: nsIContent *treeContent = GetBaseElement(); michael@0: if (treeContent && michael@0: treeContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::keepcurrentinview, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: // make sure that the current selected item is still michael@0: // visible after the tree changes size. michael@0: nsCOMPtr sel; michael@0: mView->GetSelection(getter_AddRefs(sel)); michael@0: if (sel) { michael@0: int32_t currentIndex; michael@0: sel->GetCurrentIndex(¤tIndex); michael@0: if (currentIndex != -1) michael@0: EnsureRowIsVisibleInternal(parts, currentIndex); michael@0: } michael@0: } michael@0: michael@0: if (!FullScrollbarsUpdate(false)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: mReflowCallbackPosted = false; michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::ReflowCallbackCanceled() michael@0: { michael@0: mReflowCallbackPosted = false; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetView(nsITreeView * *aView) michael@0: { michael@0: *aView = nullptr; michael@0: nsWeakFrame weakFrame(this); michael@0: EnsureView(); michael@0: NS_ENSURE_STATE(weakFrame.IsAlive()); michael@0: NS_IF_ADDREF(*aView = mView); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::SetView(nsITreeView * aView) michael@0: { michael@0: // First clear out the old view. michael@0: if (mView) { michael@0: nsCOMPtr sel; michael@0: mView->GetSelection(getter_AddRefs(sel)); michael@0: if (sel) michael@0: sel->SetTree(nullptr); michael@0: mView->SetTree(nullptr); michael@0: michael@0: // Only reset the top row index and delete the columns if we had an old non-null view. michael@0: mTopRowIndex = 0; michael@0: } michael@0: michael@0: // Tree, meet the view. michael@0: mView = aView; michael@0: michael@0: // Changing the view causes us to refetch our data. This will michael@0: // necessarily entail a full invalidation of the tree. michael@0: Invalidate(); michael@0: michael@0: nsIContent *treeContent = GetBaseElement(); michael@0: if (treeContent) { michael@0: #ifdef ACCESSIBILITY michael@0: nsAccessibilityService* accService = nsIPresShell::AccService(); michael@0: if (accService) michael@0: accService->TreeViewChanged(PresContext()->GetPresShell(), treeContent, mView); michael@0: #endif michael@0: FireDOMEvent(NS_LITERAL_STRING("TreeViewChanged"), treeContent); michael@0: } michael@0: michael@0: if (mView) { michael@0: // Give the view a new empty selection object to play with, but only if it michael@0: // doesn't have one already. michael@0: nsCOMPtr sel; michael@0: mView->GetSelection(getter_AddRefs(sel)); michael@0: if (sel) { michael@0: sel->SetTree(mTreeBoxObject); michael@0: } michael@0: else { michael@0: NS_NewTreeSelection(mTreeBoxObject, getter_AddRefs(sel)); michael@0: mView->SetSelection(sel); michael@0: } michael@0: michael@0: // View, meet the tree. michael@0: nsWeakFrame weakFrame(this); michael@0: mView->SetTree(mTreeBoxObject); michael@0: NS_ENSURE_STATE(weakFrame.IsAlive()); michael@0: mView->GetRowCount(&mRowCount); michael@0: michael@0: if (!PresContext()->PresShell()->IsReflowLocked()) { michael@0: // The scrollbar will need to be updated. michael@0: FullScrollbarsUpdate(false); michael@0: } else if (!mReflowCallbackPosted) { michael@0: mReflowCallbackPosted = true; michael@0: PresContext()->PresShell()->PostReflowCallback(this); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetFocused(bool* aFocused) michael@0: { michael@0: *aFocused = mFocused; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::SetFocused(bool aFocused) michael@0: { michael@0: if (mFocused != aFocused) { michael@0: mFocused = aFocused; michael@0: if (mView) { michael@0: nsCOMPtr sel; michael@0: mView->GetSelection(getter_AddRefs(sel)); michael@0: if (sel) michael@0: sel->InvalidateSelection(); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetTreeBody(nsIDOMElement** aElement) michael@0: { michael@0: //NS_ASSERTION(mContent, "no content, see bug #104878"); michael@0: if (!mContent) michael@0: return NS_ERROR_NULL_POINTER; michael@0: michael@0: return mContent->QueryInterface(NS_GET_IID(nsIDOMElement), (void**)aElement); michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetRowHeight(int32_t* _retval) michael@0: { michael@0: *_retval = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetRowWidth(int32_t *aRowWidth) michael@0: { michael@0: *aRowWidth = nsPresContext::AppUnitsToIntCSSPixels(CalcHorzWidth(GetScrollParts())); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetHorizontalPosition(int32_t *aHorizontalPosition) michael@0: { michael@0: *aHorizontalPosition = nsPresContext::AppUnitsToIntCSSPixels(mHorzPosition); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetSelectionRegion(nsIScriptableRegion **aRegion) michael@0: { michael@0: *aRegion = nullptr; michael@0: michael@0: nsCOMPtr selection; michael@0: mView->GetSelection(getter_AddRefs(selection)); michael@0: NS_ENSURE_TRUE(selection, NS_OK); michael@0: michael@0: nsCOMPtr region = do_CreateInstance("@mozilla.org/gfx/region;1"); michael@0: NS_ENSURE_TRUE(region, NS_ERROR_FAILURE); michael@0: region->Init(); michael@0: michael@0: nsRefPtr presContext = PresContext(); michael@0: nsIntRect rect = mRect.ToOutsidePixels(presContext->AppUnitsPerCSSPixel()); michael@0: michael@0: nsIFrame* rootFrame = presContext->PresShell()->GetRootFrame(); michael@0: nsPoint origin = GetOffsetTo(rootFrame); michael@0: michael@0: // iterate through the visible rows and add the selected ones to the michael@0: // drag region michael@0: int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x); michael@0: int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y); michael@0: int32_t top = y; michael@0: int32_t end = LastVisibleRow(); michael@0: int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); michael@0: for (int32_t i = mTopRowIndex; i <= end; i++) { michael@0: bool isSelected; michael@0: selection->IsSelected(i, &isSelected); michael@0: if (isSelected) michael@0: region->UnionRect(x, y, rect.width, rowHeight); michael@0: y += rowHeight; michael@0: } michael@0: michael@0: // clip to the tree boundary in case one row extends past it michael@0: region->IntersectRect(x, top, rect.width, rect.height); michael@0: michael@0: NS_ADDREF(*aRegion = region); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::Invalidate() michael@0: { michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: InvalidateFrame(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::InvalidateColumn(nsITreeColumn* aCol) michael@0: { michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: nsRefPtr col = GetColumnImpl(aCol); michael@0: if (!col) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (nsIPresShell::IsAccessibilityActive()) michael@0: FireInvalidateEvent(-1, -1, aCol, aCol); michael@0: #endif michael@0: michael@0: nsRect columnRect; michael@0: nsresult rv = col->GetRect(this, mInnerBox.y, mInnerBox.height, &columnRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // When false then column is out of view michael@0: if (OffsetForHorzScroll(columnRect, true)) michael@0: InvalidateFrameWithRect(columnRect); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::InvalidateRow(int32_t aIndex) michael@0: { michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (nsIPresShell::IsAccessibilityActive()) michael@0: FireInvalidateEvent(aIndex, aIndex, nullptr, nullptr); michael@0: #endif michael@0: michael@0: aIndex -= mTopRowIndex; michael@0: if (aIndex < 0 || aIndex > mPageLength) michael@0: return NS_OK; michael@0: michael@0: nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*aIndex, mInnerBox.width, mRowHeight); michael@0: InvalidateFrameWithRect(rowRect); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::InvalidateCell(int32_t aIndex, nsITreeColumn* aCol) michael@0: { michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (nsIPresShell::IsAccessibilityActive()) michael@0: FireInvalidateEvent(aIndex, aIndex, aCol, aCol); michael@0: #endif michael@0: michael@0: aIndex -= mTopRowIndex; michael@0: if (aIndex < 0 || aIndex > mPageLength) michael@0: return NS_OK; michael@0: michael@0: nsRefPtr col = GetColumnImpl(aCol); michael@0: if (!col) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsRect cellRect; michael@0: nsresult rv = col->GetRect(this, mInnerBox.y+mRowHeight*aIndex, mRowHeight, michael@0: &cellRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (OffsetForHorzScroll(cellRect, true)) michael@0: InvalidateFrameWithRect(cellRect); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::InvalidateRange(int32_t aStart, int32_t aEnd) michael@0: { michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: if (aStart == aEnd) michael@0: return InvalidateRow(aStart); michael@0: michael@0: int32_t last = LastVisibleRow(); michael@0: if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last) michael@0: return NS_OK; michael@0: michael@0: if (aStart < mTopRowIndex) michael@0: aStart = mTopRowIndex; michael@0: michael@0: if (aEnd > last) michael@0: aEnd = last; michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (nsIPresShell::IsAccessibilityActive()) { michael@0: int32_t end = michael@0: mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0; michael@0: FireInvalidateEvent(aStart, end, nullptr, nullptr); michael@0: } michael@0: #endif michael@0: michael@0: nsRect rangeRect(mInnerBox.x, mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), mInnerBox.width, mRowHeight*(aEnd-aStart+1)); michael@0: InvalidateFrameWithRect(rangeRect); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::InvalidateColumnRange(int32_t aStart, int32_t aEnd, nsITreeColumn* aCol) michael@0: { michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: nsRefPtr col = GetColumnImpl(aCol); michael@0: if (!col) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: if (aStart == aEnd) michael@0: return InvalidateCell(aStart, col); michael@0: michael@0: int32_t last = LastVisibleRow(); michael@0: if (aStart > aEnd || aEnd < mTopRowIndex || aStart > last) michael@0: return NS_OK; michael@0: michael@0: if (aStart < mTopRowIndex) michael@0: aStart = mTopRowIndex; michael@0: michael@0: if (aEnd > last) michael@0: aEnd = last; michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (nsIPresShell::IsAccessibilityActive()) { michael@0: int32_t end = michael@0: mRowCount > 0 ? ((mRowCount <= aEnd) ? mRowCount - 1 : aEnd) : 0; michael@0: FireInvalidateEvent(aStart, end, aCol, aCol); michael@0: } michael@0: #endif michael@0: michael@0: nsRect rangeRect; michael@0: nsresult rv = col->GetRect(this, michael@0: mInnerBox.y+mRowHeight*(aStart-mTopRowIndex), michael@0: mRowHeight*(aEnd-aStart+1), michael@0: &rangeRect); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: InvalidateFrameWithRect(rangeRect); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static void michael@0: FindScrollParts(nsIFrame* aCurrFrame, nsTreeBodyFrame::ScrollParts* aResult) michael@0: { michael@0: if (!aResult->mColumnsScrollFrame) { michael@0: nsIScrollableFrame* f = do_QueryFrame(aCurrFrame); michael@0: if (f) { michael@0: aResult->mColumnsFrame = aCurrFrame; michael@0: aResult->mColumnsScrollFrame = f; michael@0: } michael@0: } michael@0: michael@0: nsScrollbarFrame *sf = do_QueryFrame(aCurrFrame); michael@0: if (sf) { michael@0: if (!aCurrFrame->IsHorizontal()) { michael@0: if (!aResult->mVScrollbar) { michael@0: aResult->mVScrollbar = sf; michael@0: } michael@0: } else { michael@0: if (!aResult->mHScrollbar) { michael@0: aResult->mHScrollbar = sf; michael@0: } michael@0: } michael@0: // don't bother searching inside a scrollbar michael@0: return; michael@0: } michael@0: michael@0: nsIFrame* child = aCurrFrame->GetFirstPrincipalChild(); michael@0: while (child && michael@0: !child->GetContent()->IsRootOfNativeAnonymousSubtree() && michael@0: (!aResult->mVScrollbar || !aResult->mHScrollbar || michael@0: !aResult->mColumnsScrollFrame)) { michael@0: FindScrollParts(child, aResult); michael@0: child = child->GetNextSibling(); michael@0: } michael@0: } michael@0: michael@0: nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts() michael@0: { michael@0: ScrollParts result = { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }; michael@0: nsIContent* baseElement = GetBaseElement(); michael@0: nsIFrame* treeFrame = michael@0: baseElement ? baseElement->GetPrimaryFrame() : nullptr; michael@0: if (treeFrame) { michael@0: // The way we do this, searching through the entire frame subtree, is pretty michael@0: // dumb! We should know where these frames are. michael@0: FindScrollParts(treeFrame, &result); michael@0: if (result.mHScrollbar) { michael@0: result.mHScrollbar->SetScrollbarMediatorContent(GetContent()); michael@0: nsIFrame* f = do_QueryFrame(result.mHScrollbar); michael@0: result.mHScrollbarContent = f->GetContent(); michael@0: } michael@0: if (result.mVScrollbar) { michael@0: result.mVScrollbar->SetScrollbarMediatorContent(GetContent()); michael@0: nsIFrame* f = do_QueryFrame(result.mVScrollbar); michael@0: result.mVScrollbarContent = f->GetContent(); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::UpdateScrollbars(const ScrollParts& aParts) michael@0: { michael@0: nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); michael@0: michael@0: nsWeakFrame weakFrame(this); michael@0: michael@0: if (aParts.mVScrollbar) { michael@0: nsAutoString curPos; michael@0: curPos.AppendInt(mTopRowIndex*rowHeightAsPixels); michael@0: aParts.mVScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true); michael@0: // 'this' might be deleted here michael@0: } michael@0: michael@0: if (weakFrame.IsAlive() && aParts.mHScrollbar) { michael@0: nsAutoString curPos; michael@0: curPos.AppendInt(mHorzPosition); michael@0: aParts.mHScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::curpos, curPos, true); michael@0: // 'this' might be deleted here michael@0: } michael@0: michael@0: if (weakFrame.IsAlive() && mScrollbarActivity) { michael@0: mScrollbarActivity->ActivityOccurred(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::CheckOverflow(const ScrollParts& aParts) michael@0: { michael@0: bool verticalOverflowChanged = false; michael@0: bool horizontalOverflowChanged = false; michael@0: michael@0: if (!mVerticalOverflow && mRowCount > mPageLength) { michael@0: mVerticalOverflow = true; michael@0: verticalOverflowChanged = true; michael@0: } michael@0: else if (mVerticalOverflow && mRowCount <= mPageLength) { michael@0: mVerticalOverflow = false; michael@0: verticalOverflowChanged = true; michael@0: } michael@0: michael@0: if (aParts.mColumnsFrame) { michael@0: nsRect bounds = aParts.mColumnsFrame->GetRect(); michael@0: if (bounds.width != 0) { michael@0: /* Ignore overflows that are less than half a pixel. Yes these happen michael@0: all over the place when flex boxes are compressed real small. michael@0: Probably a result of a rounding errors somewhere in the layout code. */ michael@0: bounds.width += nsPresContext::CSSPixelsToAppUnits(0.5f); michael@0: if (!mHorizontalOverflow && bounds.width < mHorzWidth) { michael@0: mHorizontalOverflow = true; michael@0: horizontalOverflowChanged = true; michael@0: } else if (mHorizontalOverflow && bounds.width >= mHorzWidth) { michael@0: mHorizontalOverflow = false; michael@0: horizontalOverflowChanged = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsWeakFrame weakFrame(this); michael@0: michael@0: nsRefPtr presContext = PresContext(); michael@0: nsCOMPtr presShell = presContext->GetPresShell(); michael@0: nsCOMPtr content = mContent; michael@0: michael@0: if (verticalOverflowChanged) { michael@0: InternalScrollPortEvent event(true, michael@0: mVerticalOverflow ? NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW, michael@0: nullptr); michael@0: event.orient = InternalScrollPortEvent::vertical; michael@0: EventDispatcher::Dispatch(content, presContext, &event); michael@0: } michael@0: michael@0: if (horizontalOverflowChanged) { michael@0: InternalScrollPortEvent event(true, michael@0: mHorizontalOverflow ? NS_SCROLLPORT_OVERFLOW : NS_SCROLLPORT_UNDERFLOW, michael@0: nullptr); michael@0: event.orient = InternalScrollPortEvent::horizontal; michael@0: EventDispatcher::Dispatch(content, presContext, &event); michael@0: } michael@0: michael@0: // The synchronous event dispatch above can trigger reflow notifications. michael@0: // Flush those explicitly now, so that we can guard against potential infinite michael@0: // recursion. See bug 905909. michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: NS_ASSERTION(!mCheckingOverflow, "mCheckingOverflow should not already be set"); michael@0: // Don't use AutoRestore since we want to not touch mCheckingOverflow if we fail michael@0: // the weakFrame.IsAlive() check below michael@0: mCheckingOverflow = true; michael@0: presShell->FlushPendingNotifications(Flush_Layout); michael@0: if (!weakFrame.IsAlive()) { michael@0: return; michael@0: } michael@0: mCheckingOverflow = false; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::InvalidateScrollbars(const ScrollParts& aParts, nsWeakFrame& aWeakColumnsFrame) michael@0: { michael@0: if (mUpdateBatchNest || !mView) michael@0: return; michael@0: nsWeakFrame weakFrame(this); michael@0: michael@0: if (aParts.mVScrollbar) { michael@0: // Do Vertical Scrollbar michael@0: nsAutoString maxposStr; michael@0: michael@0: nscoord rowHeightAsPixels = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); michael@0: michael@0: int32_t size = rowHeightAsPixels * (mRowCount > mPageLength ? mRowCount - mPageLength : 0); michael@0: maxposStr.AppendInt(size); michael@0: aParts.mVScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: // Also set our page increment and decrement. michael@0: nscoord pageincrement = mPageLength*rowHeightAsPixels; michael@0: nsAutoString pageStr; michael@0: pageStr.AppendInt(pageincrement); michael@0: aParts.mVScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: } michael@0: michael@0: if (aParts.mHScrollbar && aParts.mColumnsFrame && aWeakColumnsFrame.IsAlive()) { michael@0: // And now Horizontal scrollbar michael@0: nsRect bounds = aParts.mColumnsFrame->GetRect(); michael@0: nsAutoString maxposStr; michael@0: michael@0: maxposStr.AppendInt(mHorzWidth > bounds.width ? mHorzWidth - bounds.width : 0); michael@0: aParts.mHScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::maxpos, maxposStr, true); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: nsAutoString pageStr; michael@0: pageStr.AppendInt(bounds.width); michael@0: aParts.mHScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::pageincrement, pageStr, true); michael@0: ENSURE_TRUE(weakFrame.IsAlive()); michael@0: michael@0: pageStr.Truncate(); michael@0: pageStr.AppendInt(nsPresContext::CSSPixelsToAppUnits(16)); michael@0: aParts.mHScrollbarContent-> michael@0: SetAttr(kNameSpaceID_None, nsGkAtoms::increment, pageStr, true); michael@0: } michael@0: michael@0: if (weakFrame.IsAlive() && mScrollbarActivity) { michael@0: mScrollbarActivity->ActivityOccurred(); michael@0: } michael@0: } michael@0: michael@0: // Takes client x/y in pixels, converts them to appunits, and converts into michael@0: // values relative to this nsTreeBodyFrame frame. michael@0: nsPoint michael@0: nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX, int32_t aY) michael@0: { michael@0: nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX), michael@0: nsPresContext::CSSPixelsToAppUnits(aY)); michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame()); michael@0: michael@0: // Adjust by the inner box coords, so that we're in the inner box's michael@0: // coordinate space. michael@0: point -= mInnerBox.TopLeft(); michael@0: return point; michael@0: } // AdjustClientCoordsToBoxCoordSpace michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY, int32_t* _retval) michael@0: { michael@0: if (!mView) michael@0: return NS_OK; michael@0: michael@0: nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY); michael@0: michael@0: // Check if the coordinates are above our visible space. michael@0: if (point.y < 0) { michael@0: *_retval = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: *_retval = GetRowAt(point.x, point.y); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetCellAt(int32_t aX, int32_t aY, int32_t* aRow, nsITreeColumn** aCol, michael@0: nsACString& aChildElt) michael@0: { michael@0: if (!mView) michael@0: return NS_OK; michael@0: michael@0: nsPoint point = AdjustClientCoordsToBoxCoordSpace(aX, aY); michael@0: michael@0: // Check if the coordinates are above our visible space. michael@0: if (point.y < 0) { michael@0: *aRow = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsTreeColumn* col; michael@0: nsIAtom* child; michael@0: GetCellAt(point.x, point.y, aRow, &col, &child); michael@0: michael@0: if (col) { michael@0: NS_ADDREF(*aCol = col); michael@0: if (child == nsCSSAnonBoxes::moztreecell) michael@0: aChildElt.AssignLiteral("cell"); michael@0: else if (child == nsCSSAnonBoxes::moztreetwisty) michael@0: aChildElt.AssignLiteral("twisty"); michael@0: else if (child == nsCSSAnonBoxes::moztreeimage) michael@0: aChildElt.AssignLiteral("image"); michael@0: else if (child == nsCSSAnonBoxes::moztreecelltext) michael@0: aChildElt.AssignLiteral("text"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: // michael@0: // GetCoordsForCellItem michael@0: // michael@0: // Find the x/y location and width/height (all in PIXELS) of the given object michael@0: // in the given column. michael@0: // michael@0: // XXX IMPORTANT XXX: michael@0: // Hyatt says in the bug for this, that the following needs to be done: michael@0: // (1) You need to deal with overflow when computing cell rects. See other column michael@0: // iteration examples... if you don't deal with this, you'll mistakenly extend the michael@0: // cell into the scrollbar's rect. michael@0: // michael@0: // (2) You are adjusting the cell rect by the *row" border padding. That's michael@0: // wrong. You need to first adjust a row rect by its border/padding, and then the michael@0: // cell rect fits inside the adjusted row rect. It also can have border/padding michael@0: // as well as margins. The vertical direction isn't that important, but you need michael@0: // to get the horizontal direction right. michael@0: // michael@0: // (3) GetImageSize() does not include margins (but it does include border/padding). michael@0: // You need to make sure to add in the image's margins as well. michael@0: // michael@0: nsresult michael@0: nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsITreeColumn* aCol, const nsACString& aElement, michael@0: int32_t *aX, int32_t *aY, int32_t *aWidth, int32_t *aHeight) michael@0: { michael@0: *aX = 0; michael@0: *aY = 0; michael@0: *aWidth = 0; michael@0: *aHeight = 0; michael@0: michael@0: bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: nscoord currX = mInnerBox.x - mHorzPosition; michael@0: michael@0: // The Rect for the requested item. michael@0: nsRect theRect; michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; currCol = currCol->GetNext()) { michael@0: michael@0: // The Rect for the current cell. michael@0: nscoord colWidth; michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: currCol->GetWidthInTwips(this, &colWidth); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column"); michael@0: michael@0: nsRect cellRect(currX, mInnerBox.y + mRowHeight * (aRow - mTopRowIndex), michael@0: colWidth, mRowHeight); michael@0: michael@0: // Check the ID of the current column to see if it matches. If it doesn't michael@0: // increment the current X value and continue to the next column. michael@0: if (currCol != aCol) { michael@0: currX += cellRect.width; michael@0: continue; michael@0: } michael@0: // Now obtain the properties for our cell. michael@0: PrefillPropertyArray(aRow, currCol); michael@0: michael@0: nsAutoString properties; michael@0: mView->GetCellProperties(aRow, currCol, properties); michael@0: nsTreeUtils::TokenizeProperties(properties, mScratchArray); michael@0: michael@0: nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); michael@0: michael@0: // We don't want to consider any of the decorations that may be present michael@0: // on the current row, so we have to deflate the rect by the border and michael@0: // padding and offset its left and top coordinates appropriately. michael@0: AdjustForBorderPadding(rowContext, cellRect); michael@0: michael@0: nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); michael@0: michael@0: NS_NAMED_LITERAL_CSTRING(cell, "cell"); michael@0: if (currCol->IsCycler() || cell.Equals(aElement)) { michael@0: // If the current Column is a Cycler, then the Rect is just the cell - the margins. michael@0: // Similarly, if we're just being asked for the cell rect, provide it. michael@0: michael@0: theRect = cellRect; michael@0: nsMargin cellMargin; michael@0: cellContext->StyleMargin()->GetMargin(cellMargin); michael@0: theRect.Deflate(cellMargin); michael@0: break; michael@0: } michael@0: michael@0: // Since we're not looking for the cell, and since the cell isn't a cycler, michael@0: // we're looking for some subcomponent, and now we need to subtract the michael@0: // borders and padding of the cell from cellRect so this does not michael@0: // interfere with our computations. michael@0: AdjustForBorderPadding(cellContext, cellRect); michael@0: michael@0: nsRefPtr rc = michael@0: presContext->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: // Now we'll start making our way across the cell, starting at the edge of michael@0: // the cell and proceeding until we hit the right edge. |cellX| is the michael@0: // working X value that we will increment as we crawl from left to right. michael@0: nscoord cellX = cellRect.x; michael@0: nscoord remainWidth = cellRect.width; michael@0: michael@0: if (currCol->IsPrimary()) { michael@0: // If the current Column is a Primary, then we need to take into account the indentation michael@0: // and possibly a twisty. michael@0: michael@0: // The amount of indentation is the indentation width (|mIndentation|) by the level. michael@0: int32_t level; michael@0: mView->GetLevel(aRow, &level); michael@0: if (!isRTL) michael@0: cellX += mIndentation * level; michael@0: remainWidth -= mIndentation * level; michael@0: michael@0: // Find the twisty rect by computing its size. michael@0: nsRect imageRect; michael@0: nsRect twistyRect(cellRect); michael@0: nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); michael@0: GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext, michael@0: *rc, twistyContext); michael@0: michael@0: if (NS_LITERAL_CSTRING("twisty").Equals(aElement)) { michael@0: // If we're looking for the twisty Rect, just return the size michael@0: theRect = twistyRect; michael@0: break; michael@0: } michael@0: michael@0: // Now we need to add in the margins of the twisty element, so that we michael@0: // can find the offset of the next element in the cell. michael@0: nsMargin twistyMargin; michael@0: twistyContext->StyleMargin()->GetMargin(twistyMargin); michael@0: twistyRect.Inflate(twistyMargin); michael@0: michael@0: // Adjust our working X value with the twisty width (image size, margins, michael@0: // borders, padding. michael@0: if (!isRTL) michael@0: cellX += twistyRect.width; michael@0: } michael@0: michael@0: // Cell Image michael@0: nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); michael@0: michael@0: nsRect imageSize = GetImageSize(aRow, currCol, false, imageContext); michael@0: if (NS_LITERAL_CSTRING("image").Equals(aElement)) { michael@0: theRect = imageSize; michael@0: theRect.x = cellX; michael@0: theRect.y = cellRect.y; michael@0: break; michael@0: } michael@0: michael@0: // Add in the margins of the cell image. michael@0: nsMargin imageMargin; michael@0: imageContext->StyleMargin()->GetMargin(imageMargin); michael@0: imageSize.Inflate(imageMargin); michael@0: michael@0: // Increment cellX by the image width michael@0: if (!isRTL) michael@0: cellX += imageSize.width; michael@0: michael@0: // Cell Text michael@0: nsAutoString cellText; michael@0: mView->GetCellText(aRow, currCol, cellText); michael@0: // We're going to measure this text so we need to ensure bidi is enabled if michael@0: // necessary michael@0: CheckTextForBidi(cellText); michael@0: michael@0: // Create a scratch rect to represent the text rectangle, with the current michael@0: // X and Y coords, and a guess at the width and height. The width is the michael@0: // remaining width we have left to traverse in the cell, which will be the michael@0: // widest possible value for the text rect, and the row height. michael@0: nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height); michael@0: michael@0: // Measure the width of the text. If the width of the text is greater than michael@0: // the remaining width available, then we just assume that the text has michael@0: // been cropped and use the remaining rect as the text Rect. Otherwise, michael@0: // we add in borders and padding to the text dimension and give that back. michael@0: nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForStyleContext(textContext, michael@0: getter_AddRefs(fm)); michael@0: nscoord height = fm->MaxHeight(); michael@0: michael@0: nsMargin textMargin; michael@0: textContext->StyleMargin()->GetMargin(textMargin); michael@0: textRect.Deflate(textMargin); michael@0: michael@0: // Center the text. XXX Obey vertical-align style prop? michael@0: if (height < textRect.height) { michael@0: textRect.y += (textRect.height - height) / 2; michael@0: textRect.height = height; michael@0: } michael@0: michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(textContext, bp); michael@0: textRect.height += bp.top + bp.bottom; michael@0: michael@0: rc->SetFont(fm); michael@0: AdjustForCellText(cellText, aRow, currCol, *rc, textRect); michael@0: michael@0: theRect = textRect; michael@0: } michael@0: michael@0: if (isRTL) michael@0: theRect.x = mInnerBox.width - theRect.x - theRect.width; michael@0: michael@0: *aX = nsPresContext::AppUnitsToIntCSSPixels(theRect.x); michael@0: *aY = nsPresContext::AppUnitsToIntCSSPixels(theRect.y); michael@0: *aWidth = nsPresContext::AppUnitsToIntCSSPixels(theRect.width); michael@0: *aHeight = nsPresContext::AppUnitsToIntCSSPixels(theRect.height); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t michael@0: nsTreeBodyFrame::GetRowAt(int32_t aX, int32_t aY) michael@0: { michael@0: // Now just mod by our total inner box height and add to our top row index. michael@0: int32_t row = (aY/mRowHeight)+mTopRowIndex; michael@0: michael@0: // Check if the coordinates are below our visible space (or within our visible michael@0: // space but below any row). michael@0: if (row > mTopRowIndex + mPageLength || row >= mRowCount) michael@0: return -1; michael@0: michael@0: return row; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText) michael@0: { michael@0: // We could check to see whether the prescontext already has bidi enabled, michael@0: // but usually it won't, so it's probably faster to avoid the call to michael@0: // GetPresContext() when it's not needed. michael@0: if (HasRTLChars(aText)) { michael@0: PresContext()->SetBidiEnabled(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::AdjustForCellText(nsAutoString& aText, michael@0: int32_t aRowIndex, nsTreeColumn* aColumn, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsRect& aTextRect) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: nscoord width = michael@0: nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aText.get(), aText.Length()); michael@0: nscoord maxWidth = aTextRect.width; michael@0: michael@0: if (aColumn->Overflow()) { michael@0: DebugOnly rv; michael@0: nsTreeColumn* nextColumn = aColumn->GetNext(); michael@0: while (nextColumn && width > maxWidth) { michael@0: while (nextColumn) { michael@0: nscoord width; michael@0: rv = nextColumn->GetWidthInTwips(this, &width); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid"); michael@0: michael@0: if (width != 0) michael@0: break; michael@0: michael@0: nextColumn = nextColumn->GetNext(); michael@0: } michael@0: michael@0: if (nextColumn) { michael@0: nsAutoString nextText; michael@0: mView->GetCellText(aRowIndex, nextColumn, nextText); michael@0: // We don't measure or draw this text so no need to check it for michael@0: // bidi-ness michael@0: michael@0: if (nextText.Length() == 0) { michael@0: nscoord width; michael@0: rv = nextColumn->GetWidthInTwips(this, &width); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid"); michael@0: michael@0: maxWidth += width; michael@0: michael@0: nextColumn = nextColumn->GetNext(); michael@0: } michael@0: else { michael@0: nextColumn = nullptr; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (width > maxWidth) { michael@0: // See if the width is even smaller than the ellipsis michael@0: // If so, clear the text completely. michael@0: const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); michael@0: aRenderingContext.SetTextRunRTL(false); michael@0: nscoord ellipsisWidth = aRenderingContext.GetWidth(kEllipsis); michael@0: michael@0: width = maxWidth; michael@0: if (ellipsisWidth > width) michael@0: aText.SetLength(0); michael@0: else if (ellipsisWidth == width) michael@0: aText.Assign(kEllipsis); michael@0: else { michael@0: // We will be drawing an ellipsis, thank you very much. michael@0: // Subtract out the required width of the ellipsis. michael@0: // This is the total remaining width we have to play with. michael@0: width -= ellipsisWidth; michael@0: michael@0: // Now we crop. michael@0: switch (aColumn->GetCropStyle()) { michael@0: default: michael@0: case 0: { michael@0: // Crop right. michael@0: nscoord cwidth; michael@0: nscoord twidth = 0; michael@0: uint32_t length = aText.Length(); michael@0: uint32_t i; michael@0: for (i = 0; i < length; ++i) { michael@0: char16_t ch = aText[i]; michael@0: // XXX this is horrible and doesn't handle clusters michael@0: cwidth = aRenderingContext.GetWidth(ch); michael@0: if (twidth + cwidth > width) michael@0: break; michael@0: twidth += cwidth; michael@0: } michael@0: aText.Truncate(i); michael@0: aText.Append(kEllipsis); michael@0: } michael@0: break; michael@0: michael@0: case 2: { michael@0: // Crop left. michael@0: nscoord cwidth; michael@0: nscoord twidth = 0; michael@0: int32_t length = aText.Length(); michael@0: int32_t i; michael@0: for (i=length-1; i >= 0; --i) { michael@0: char16_t ch = aText[i]; michael@0: cwidth = aRenderingContext.GetWidth(ch); michael@0: if (twidth + cwidth > width) michael@0: break; michael@0: twidth += cwidth; michael@0: } michael@0: michael@0: nsAutoString copy; michael@0: aText.Right(copy, length-1-i); michael@0: aText.Assign(kEllipsis); michael@0: aText += copy; michael@0: } michael@0: break; michael@0: michael@0: case 1: michael@0: { michael@0: // Crop center. michael@0: nsAutoString leftStr, rightStr; michael@0: nscoord cwidth, twidth = 0; michael@0: int32_t length = aText.Length(); michael@0: int32_t rightPos = length - 1; michael@0: for (int32_t leftPos = 0; leftPos < rightPos; ++leftPos) { michael@0: char16_t ch = aText[leftPos]; michael@0: cwidth = aRenderingContext.GetWidth(ch); michael@0: twidth += cwidth; michael@0: if (twidth > width) michael@0: break; michael@0: leftStr.Append(ch); michael@0: michael@0: ch = aText[rightPos]; michael@0: cwidth = aRenderingContext.GetWidth(ch); michael@0: twidth += cwidth; michael@0: if (twidth > width) michael@0: break; michael@0: rightStr.Insert(ch, 0); michael@0: --rightPos; michael@0: } michael@0: aText = leftStr; michael@0: aText.Append(kEllipsis); michael@0: aText += rightStr; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: michael@0: width = nsLayoutUtils::GetStringWidth(this, &aRenderingContext, aText.get(), aText.Length()); michael@0: } michael@0: michael@0: switch (aColumn->GetTextAlignment()) { michael@0: case NS_STYLE_TEXT_ALIGN_RIGHT: { michael@0: aTextRect.x += aTextRect.width - width; michael@0: } michael@0: break; michael@0: case NS_STYLE_TEXT_ALIGN_CENTER: { michael@0: aTextRect.x += (aTextRect.width - width) / 2; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: aTextRect.width = width; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsTreeBodyFrame::GetItemWithinCellAt(nscoord aX, const nsRect& aCellRect, michael@0: int32_t aRowIndex, michael@0: nsTreeColumn* aColumn) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: // Obtain the properties for our cell. michael@0: PrefillPropertyArray(aRowIndex, aColumn); michael@0: nsAutoString properties; michael@0: mView->GetCellProperties(aRowIndex, aColumn, properties); michael@0: nsTreeUtils::TokenizeProperties(properties, mScratchArray); michael@0: michael@0: // Resolve style for the cell. michael@0: nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); michael@0: michael@0: // Obtain the margins for the cell and then deflate our rect by that michael@0: // amount. The cell is assumed to be contained within the deflated rect. michael@0: nsRect cellRect(aCellRect); michael@0: nsMargin cellMargin; michael@0: cellContext->StyleMargin()->GetMargin(cellMargin); michael@0: cellRect.Deflate(cellMargin); michael@0: michael@0: // Adjust the rect for its border and padding. michael@0: AdjustForBorderPadding(cellContext, cellRect); michael@0: michael@0: if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) { michael@0: // The user clicked within the cell's margins/borders/padding. This constitutes a click on the cell. michael@0: return nsCSSAnonBoxes::moztreecell; michael@0: } michael@0: michael@0: nscoord currX = cellRect.x; michael@0: nscoord remainingWidth = cellRect.width; michael@0: michael@0: // Handle right alignment hit testing. michael@0: bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: michael@0: nsPresContext* presContext = PresContext(); michael@0: nsRefPtr rc = michael@0: presContext->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: if (aColumn->IsPrimary()) { michael@0: // If we're the primary column, we have indentation and a twisty. michael@0: int32_t level; michael@0: mView->GetLevel(aRowIndex, &level); michael@0: michael@0: if (!isRTL) michael@0: currX += mIndentation*level; michael@0: remainingWidth -= mIndentation*level; michael@0: michael@0: if ((isRTL && aX > currX + remainingWidth) || michael@0: (!isRTL && aX < currX)) { michael@0: // The user clicked within the indentation. michael@0: return nsCSSAnonBoxes::moztreecell; michael@0: } michael@0: michael@0: // Always leave space for the twisty. michael@0: nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height); michael@0: bool hasTwisty = false; michael@0: bool isContainer = false; michael@0: mView->IsContainer(aRowIndex, &isContainer); michael@0: if (isContainer) { michael@0: bool isContainerEmpty = false; michael@0: mView->IsContainerEmpty(aRowIndex, &isContainerEmpty); michael@0: if (!isContainerEmpty) michael@0: hasTwisty = true; michael@0: } michael@0: michael@0: // Resolve style for the twisty. michael@0: nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); michael@0: michael@0: nsRect imageSize; michael@0: GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, presContext, michael@0: *rc, twistyContext); michael@0: michael@0: // We will treat a click as hitting the twisty if it happens on the margins, borders, padding, michael@0: // or content of the twisty object. By allowing a "slop" into the margin, we make it a little michael@0: // bit easier for a user to hit the twisty. (We don't want to be too picky here.) michael@0: nsMargin twistyMargin; michael@0: twistyContext->StyleMargin()->GetMargin(twistyMargin); michael@0: twistyRect.Inflate(twistyMargin); michael@0: if (isRTL) michael@0: twistyRect.x = currX + remainingWidth - twistyRect.width; michael@0: michael@0: // Now we test to see if aX is actually within the twistyRect. If it is, and if the item should michael@0: // have a twisty, then we return "twisty". If it is within the rect but we shouldn't have a twisty, michael@0: // then we return "cell". michael@0: if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) { michael@0: if (hasTwisty) michael@0: return nsCSSAnonBoxes::moztreetwisty; michael@0: else michael@0: return nsCSSAnonBoxes::moztreecell; michael@0: } michael@0: michael@0: if (!isRTL) michael@0: currX += twistyRect.width; michael@0: remainingWidth -= twistyRect.width; michael@0: } michael@0: michael@0: // Now test to see if the user hit the icon for the cell. michael@0: nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height); michael@0: michael@0: // Resolve style for the image. michael@0: nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); michael@0: michael@0: nsRect iconSize = GetImageSize(aRowIndex, aColumn, false, imageContext); michael@0: nsMargin imageMargin; michael@0: imageContext->StyleMargin()->GetMargin(imageMargin); michael@0: iconSize.Inflate(imageMargin); michael@0: iconRect.width = iconSize.width; michael@0: if (isRTL) michael@0: iconRect.x = currX + remainingWidth - iconRect.width; michael@0: michael@0: if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) { michael@0: // The user clicked on the image. michael@0: return nsCSSAnonBoxes::moztreeimage; michael@0: } michael@0: michael@0: if (!isRTL) michael@0: currX += iconRect.width; michael@0: remainingWidth -= iconRect.width; michael@0: michael@0: nsAutoString cellText; michael@0: mView->GetCellText(aRowIndex, aColumn, cellText); michael@0: // We're going to measure this text so we need to ensure bidi is enabled if michael@0: // necessary michael@0: CheckTextForBidi(cellText); michael@0: michael@0: nsRect textRect(currX, cellRect.y, remainingWidth, cellRect.height); michael@0: michael@0: nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); michael@0: michael@0: nsMargin textMargin; michael@0: textContext->StyleMargin()->GetMargin(textMargin); michael@0: textRect.Deflate(textMargin); michael@0: michael@0: AdjustForBorderPadding(textContext, textRect); michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForStyleContext(textContext, michael@0: getter_AddRefs(fm)); michael@0: rc->SetFont(fm); michael@0: michael@0: AdjustForCellText(cellText, aRowIndex, aColumn, *rc, textRect); michael@0: michael@0: if (aX >= textRect.x && aX < textRect.x + textRect.width) michael@0: return nsCSSAnonBoxes::moztreecelltext; michael@0: else michael@0: return nsCSSAnonBoxes::moztreecell; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::GetCellAt(nscoord aX, nscoord aY, int32_t* aRow, michael@0: nsTreeColumn** aCol, nsIAtom** aChildElt) michael@0: { michael@0: *aCol = nullptr; michael@0: *aChildElt = nullptr; michael@0: michael@0: *aRow = GetRowAt(aX, aY); michael@0: if (*aRow < 0) michael@0: return; michael@0: michael@0: // Determine the column hit. michael@0: for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; michael@0: currCol = currCol->GetNext()) { michael@0: nsRect cellRect; michael@0: nsresult rv = currCol->GetRect(this, michael@0: mInnerBox.y + michael@0: mRowHeight * (*aRow - mTopRowIndex), michael@0: mRowHeight, michael@0: &cellRect); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("column has no frame"); michael@0: continue; michael@0: } michael@0: michael@0: if (!OffsetForHorzScroll(cellRect, false)) michael@0: continue; michael@0: michael@0: if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) { michael@0: // We know the column hit now. michael@0: *aCol = currCol; michael@0: michael@0: if (currCol->IsCycler()) michael@0: // Cyclers contain only images. Fill this in immediately and return. michael@0: *aChildElt = nsCSSAnonBoxes::moztreeimage; michael@0: else michael@0: *aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol, michael@0: nsRenderingContext* aRenderingContext, michael@0: nscoord& aDesiredSize, nscoord& aCurrentSize) michael@0: { michael@0: NS_PRECONDITION(aCol, "aCol must not be null"); michael@0: NS_PRECONDITION(aRenderingContext, "aRenderingContext must not be null"); michael@0: michael@0: // The rect for the current cell. michael@0: nscoord colWidth; michael@0: nsresult rv = aCol->GetWidthInTwips(this, &colWidth); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRect cellRect(0, 0, colWidth, mRowHeight); michael@0: michael@0: int32_t overflow = cellRect.x+cellRect.width-(mInnerBox.x+mInnerBox.width); michael@0: if (overflow > 0) michael@0: cellRect.width -= overflow; michael@0: michael@0: // Adjust borders and padding for the cell. michael@0: nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(cellContext, bp); michael@0: michael@0: aCurrentSize = cellRect.width; michael@0: aDesiredSize = bp.left + bp.right; michael@0: michael@0: if (aCol->IsPrimary()) { michael@0: // If the current Column is a Primary, then we need to take into account michael@0: // the indentation and possibly a twisty. michael@0: michael@0: // The amount of indentation is the indentation width (|mIndentation|) by the level. michael@0: int32_t level; michael@0: mView->GetLevel(aRow, &level); michael@0: aDesiredSize += mIndentation * level; michael@0: michael@0: // Find the twisty rect by computing its size. michael@0: nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); michael@0: michael@0: nsRect imageSize; michael@0: nsRect twistyRect(cellRect); michael@0: GetTwistyRect(aRow, aCol, imageSize, twistyRect, PresContext(), michael@0: *aRenderingContext, twistyContext); michael@0: michael@0: // Add in the margins of the twisty element. michael@0: nsMargin twistyMargin; michael@0: twistyContext->StyleMargin()->GetMargin(twistyMargin); michael@0: twistyRect.Inflate(twistyMargin); michael@0: michael@0: aDesiredSize += twistyRect.width; michael@0: } michael@0: michael@0: nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); michael@0: michael@0: // Account for the width of the cell image. michael@0: nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext); michael@0: // Add in the margins of the cell image. michael@0: nsMargin imageMargin; michael@0: imageContext->StyleMargin()->GetMargin(imageMargin); michael@0: imageSize.Inflate(imageMargin); michael@0: michael@0: aDesiredSize += imageSize.width; michael@0: michael@0: // Get the cell text. michael@0: nsAutoString cellText; michael@0: mView->GetCellText(aRow, aCol, cellText); michael@0: // We're going to measure this text so we need to ensure bidi is enabled if michael@0: // necessary michael@0: CheckTextForBidi(cellText); michael@0: michael@0: nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); michael@0: michael@0: // Get the borders and padding for the text. michael@0: GetBorderPadding(textContext, bp); michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForStyleContext(textContext, michael@0: getter_AddRefs(fm)); michael@0: aRenderingContext->SetFont(fm); michael@0: michael@0: // Get the width of the text itself michael@0: nscoord width = michael@0: nsLayoutUtils::GetStringWidth(this, aRenderingContext, cellText.get(), cellText.Length()); michael@0: nscoord totalTextWidth = width + bp.left + bp.right; michael@0: aDesiredSize += totalTextWidth; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::IsCellCropped(int32_t aRow, nsITreeColumn* aCol, bool *_retval) michael@0: { michael@0: nscoord currentSize, desiredSize; michael@0: nsresult rv; michael@0: michael@0: nsRefPtr col = GetColumnImpl(aCol); michael@0: if (!col) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nsRefPtr rc = michael@0: PresContext()->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: rv = GetCellWidth(aRow, col, rc, desiredSize, currentSize); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *_retval = desiredSize > currentSize; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::MarkDirtyIfSelect() michael@0: { michael@0: nsIContent* baseElement = GetBaseElement(); michael@0: michael@0: if (baseElement && baseElement->Tag() == nsGkAtoms::select && michael@0: baseElement->IsHTML()) { michael@0: // If we are an intrinsically sized select widget, we may need to michael@0: // resize, if the widest item was removed or a new item was added. michael@0: // XXX optimize this more michael@0: michael@0: mStringWidth = -1; michael@0: PresContext()->PresShell()->FrameNeedsReflow(this, michael@0: nsIPresShell::eTreeChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID, michael@0: nsTimerCallbackFunc aFunc, int32_t aType, michael@0: nsITimer** aTimer) michael@0: { michael@0: // Get the delay from the look and feel service. michael@0: int32_t delay = LookAndFeel::GetInt(aID, 0); michael@0: michael@0: nsCOMPtr timer; michael@0: michael@0: // Create a new timer only if the delay is greater than zero. michael@0: // Zero value means that this feature is completely disabled. michael@0: if (delay > 0) { michael@0: timer = do_CreateInstance("@mozilla.org/timer;1"); michael@0: if (timer) michael@0: timer->InitWithFuncCallback(aFunc, this, delay, aType); michael@0: } michael@0: michael@0: NS_IF_ADDREF(*aTimer = timer); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount) michael@0: { michael@0: if (aCount == 0 || !mView) michael@0: return NS_OK; // Nothing to do. michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: if (nsIPresShell::IsAccessibilityActive()) michael@0: FireRowCountChangedEvent(aIndex, aCount); michael@0: #endif michael@0: michael@0: // Adjust our selection. michael@0: nsCOMPtr sel; michael@0: mView->GetSelection(getter_AddRefs(sel)); michael@0: if (sel) michael@0: sel->AdjustSelection(aIndex, aCount); michael@0: michael@0: if (mUpdateBatchNest) michael@0: return NS_OK; michael@0: michael@0: mRowCount += aCount; michael@0: #ifdef DEBUG michael@0: int32_t rowCount = mRowCount; michael@0: mView->GetRowCount(&rowCount); michael@0: NS_ASSERTION(rowCount == mRowCount, "row count did not change by the amount suggested, check caller"); michael@0: #endif michael@0: michael@0: int32_t count = Abs(aCount); michael@0: int32_t last = LastVisibleRow(); michael@0: if (aIndex >= mTopRowIndex && aIndex <= last) michael@0: InvalidateRange(aIndex, last); michael@0: michael@0: ScrollParts parts = GetScrollParts(); michael@0: michael@0: if (mTopRowIndex == 0) { michael@0: // Just update the scrollbar and return. michael@0: if (FullScrollbarsUpdate(false)) { michael@0: MarkDirtyIfSelect(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool needsInvalidation = false; michael@0: // Adjust our top row index. michael@0: if (aCount > 0) { michael@0: if (mTopRowIndex > aIndex) { michael@0: // Rows came in above us. Augment the top row index. michael@0: mTopRowIndex += aCount; michael@0: } michael@0: } michael@0: else if (aCount < 0) { michael@0: if (mTopRowIndex > aIndex+count-1) { michael@0: // No need to invalidate. The remove happened michael@0: // completely above us (offscreen). michael@0: mTopRowIndex -= count; michael@0: } michael@0: else if (mTopRowIndex >= aIndex) { michael@0: // This is a full-blown invalidate. michael@0: if (mTopRowIndex + mPageLength > mRowCount - 1) { michael@0: mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength); michael@0: } michael@0: needsInvalidation = true; michael@0: } michael@0: } michael@0: michael@0: if (FullScrollbarsUpdate(needsInvalidation)) { michael@0: MarkDirtyIfSelect(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::BeginUpdateBatch() michael@0: { michael@0: ++mUpdateBatchNest; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::EndUpdateBatch() michael@0: { michael@0: NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch"); michael@0: michael@0: if (--mUpdateBatchNest == 0) { michael@0: if (mView) { michael@0: Invalidate(); michael@0: int32_t countBeforeUpdate = mRowCount; michael@0: mView->GetRowCount(&mRowCount); michael@0: if (countBeforeUpdate != mRowCount) { michael@0: if (mTopRowIndex + mPageLength > mRowCount - 1) { michael@0: mTopRowIndex = std::max(0, mRowCount - 1 - mPageLength); michael@0: } michael@0: FullScrollbarsUpdate(false); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PrefillPropertyArray(int32_t aRowIndex, nsTreeColumn* aCol) michael@0: { michael@0: NS_PRECONDITION(!aCol || aCol->GetFrame(), "invalid column passed"); michael@0: mScratchArray.Clear(); michael@0: michael@0: // focus michael@0: if (mFocused) michael@0: mScratchArray.AppendElement(nsGkAtoms::focus); michael@0: michael@0: // sort michael@0: bool sorted = false; michael@0: mView->IsSorted(&sorted); michael@0: if (sorted) michael@0: mScratchArray.AppendElement(nsGkAtoms::sorted); michael@0: michael@0: // drag session michael@0: if (mSlots && mSlots->mIsDragging) michael@0: mScratchArray.AppendElement(nsGkAtoms::dragSession); michael@0: michael@0: if (aRowIndex != -1) { michael@0: if (aRowIndex == mMouseOverRow) michael@0: mScratchArray.AppendElement(nsGkAtoms::hover); michael@0: michael@0: nsCOMPtr selection; michael@0: mView->GetSelection(getter_AddRefs(selection)); michael@0: michael@0: if (selection) { michael@0: // selected michael@0: bool isSelected; michael@0: selection->IsSelected(aRowIndex, &isSelected); michael@0: if (isSelected) michael@0: mScratchArray.AppendElement(nsGkAtoms::selected); michael@0: michael@0: // current michael@0: int32_t currentIndex; michael@0: selection->GetCurrentIndex(¤tIndex); michael@0: if (aRowIndex == currentIndex) michael@0: mScratchArray.AppendElement(nsGkAtoms::current); michael@0: michael@0: // active michael@0: if (aCol) { michael@0: nsCOMPtr currentColumn; michael@0: selection->GetCurrentColumn(getter_AddRefs(currentColumn)); michael@0: if (aCol == currentColumn) michael@0: mScratchArray.AppendElement(nsGkAtoms::active); michael@0: } michael@0: } michael@0: michael@0: // container or leaf michael@0: bool isContainer = false; michael@0: mView->IsContainer(aRowIndex, &isContainer); michael@0: if (isContainer) { michael@0: mScratchArray.AppendElement(nsGkAtoms::container); michael@0: michael@0: // open or closed michael@0: bool isOpen = false; michael@0: mView->IsContainerOpen(aRowIndex, &isOpen); michael@0: if (isOpen) michael@0: mScratchArray.AppendElement(nsGkAtoms::open); michael@0: else michael@0: mScratchArray.AppendElement(nsGkAtoms::closed); michael@0: } michael@0: else { michael@0: mScratchArray.AppendElement(nsGkAtoms::leaf); michael@0: } michael@0: michael@0: // drop orientation michael@0: if (mSlots && mSlots->mDropAllowed && mSlots->mDropRow == aRowIndex) { michael@0: if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) michael@0: mScratchArray.AppendElement(nsGkAtoms::dropBefore); michael@0: else if (mSlots->mDropOrient == nsITreeView::DROP_ON) michael@0: mScratchArray.AppendElement(nsGkAtoms::dropOn); michael@0: else if (mSlots->mDropOrient == nsITreeView::DROP_AFTER) michael@0: mScratchArray.AppendElement(nsGkAtoms::dropAfter); michael@0: } michael@0: michael@0: // odd or even michael@0: if (aRowIndex % 2) michael@0: mScratchArray.AppendElement(nsGkAtoms::odd); michael@0: else michael@0: mScratchArray.AppendElement(nsGkAtoms::even); michael@0: michael@0: nsIContent* baseContent = GetBaseElement(); michael@0: if (baseContent && baseContent->HasAttr(kNameSpaceID_None, nsGkAtoms::editing)) michael@0: mScratchArray.AppendElement(nsGkAtoms::editing); michael@0: michael@0: // multiple columns michael@0: if (mColumns->GetColumnAt(1)) michael@0: mScratchArray.AppendElement(nsGkAtoms::multicol); michael@0: } michael@0: michael@0: if (aCol) { michael@0: mScratchArray.AppendElement(aCol->GetAtom()); michael@0: michael@0: if (aCol->IsPrimary()) michael@0: mScratchArray.AppendElement(nsGkAtoms::primary); michael@0: michael@0: if (aCol->GetType() == nsITreeColumn::TYPE_CHECKBOX) { michael@0: mScratchArray.AppendElement(nsGkAtoms::checkbox); michael@0: michael@0: if (aRowIndex != -1) { michael@0: nsAutoString value; michael@0: mView->GetCellValue(aRowIndex, aCol, value); michael@0: if (value.EqualsLiteral("true")) michael@0: mScratchArray.AppendElement(nsGkAtoms::checked); michael@0: } michael@0: } michael@0: else if (aCol->GetType() == nsITreeColumn::TYPE_PROGRESSMETER) { michael@0: mScratchArray.AppendElement(nsGkAtoms::progressmeter); michael@0: michael@0: if (aRowIndex != -1) { michael@0: int32_t state; michael@0: mView->GetProgressMode(aRowIndex, aCol, &state); michael@0: if (state == nsITreeView::PROGRESS_NORMAL) michael@0: mScratchArray.AppendElement(nsGkAtoms::progressNormal); michael@0: else if (state == nsITreeView::PROGRESS_UNDETERMINED) michael@0: mScratchArray.AppendElement(nsGkAtoms::progressUndetermined); michael@0: } michael@0: } michael@0: michael@0: // Read special properties from attributes on the column content node michael@0: if (aCol->mContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::insertbefore, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: mScratchArray.AppendElement(nsGkAtoms::insertbefore); michael@0: if (aCol->mContent->AttrValueIs(kNameSpaceID_None, michael@0: nsGkAtoms::insertafter, michael@0: nsGkAtoms::_true, eCaseMatters)) michael@0: mScratchArray.AppendElement(nsGkAtoms::insertafter); michael@0: } michael@0: } michael@0: michael@0: nsITheme* michael@0: nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: nsRect& aImageRect, michael@0: nsRect& aTwistyRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nsStyleContext* aTwistyContext) michael@0: { michael@0: // The twisty rect extends all the way to the end of the cell. This is incorrect. We need to michael@0: // determine the twisty rect's true width. This is done by examining the style context for michael@0: // a width first. If it has one, we use that. If it doesn't, we use the image's natural width. michael@0: // If the image hasn't loaded and if no width is specified, then we just bail. If there is michael@0: // a -moz-appearance involved, adjust the rect by the minimum widget size provided by michael@0: // the theme implementation. michael@0: aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext); michael@0: if (aImageRect.height > aTwistyRect.height) michael@0: aImageRect.height = aTwistyRect.height; michael@0: if (aImageRect.width > aTwistyRect.width) michael@0: aImageRect.width = aTwistyRect.width; michael@0: else michael@0: aTwistyRect.width = aImageRect.width; michael@0: michael@0: bool useTheme = false; michael@0: nsITheme *theme = nullptr; michael@0: const nsStyleDisplay* twistyDisplayData = aTwistyContext->StyleDisplay(); michael@0: if (twistyDisplayData->mAppearance) { michael@0: theme = aPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, twistyDisplayData->mAppearance)) michael@0: useTheme = true; michael@0: } michael@0: michael@0: if (useTheme) { michael@0: nsIntSize minTwistySizePx(0,0); michael@0: bool canOverride = true; michael@0: theme->GetMinimumWidgetSize(&aRenderingContext, this, twistyDisplayData->mAppearance, michael@0: &minTwistySizePx, &canOverride); michael@0: michael@0: // GMWS() returns size in pixels, we need to convert it back to app units michael@0: nsSize minTwistySize; michael@0: minTwistySize.width = aPresContext->DevPixelsToAppUnits(minTwistySizePx.width); michael@0: minTwistySize.height = aPresContext->DevPixelsToAppUnits(minTwistySizePx.height); michael@0: michael@0: if (aTwistyRect.width < minTwistySize.width || !canOverride) michael@0: aTwistyRect.width = minTwistySize.width; michael@0: } michael@0: michael@0: return useTheme ? theme : nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetImage(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, michael@0: nsStyleContext* aStyleContext, bool& aAllowImageRegions, imgIContainer** aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: michael@0: nsAutoString imageSrc; michael@0: mView->GetImageSrc(aRowIndex, aCol, imageSrc); michael@0: nsRefPtr styleRequest; michael@0: if (!aUseContext && !imageSrc.IsEmpty()) { michael@0: aAllowImageRegions = false; michael@0: } michael@0: else { michael@0: // Obtain the URL from the style context. michael@0: aAllowImageRegions = true; michael@0: styleRequest = aStyleContext->StyleList()->GetListStyleImage(); michael@0: if (!styleRequest) michael@0: return NS_OK; michael@0: nsCOMPtr uri; michael@0: styleRequest->GetURI(getter_AddRefs(uri)); michael@0: nsAutoCString spec; michael@0: uri->GetSpec(spec); michael@0: CopyUTF8toUTF16(spec, imageSrc); michael@0: } michael@0: michael@0: // Look the image up in our cache. michael@0: nsTreeImageCacheEntry entry; michael@0: if (mImageCache.Get(imageSrc, &entry)) { michael@0: // Find out if the image has loaded. michael@0: uint32_t status; michael@0: imgIRequest *imgReq = entry.request; michael@0: imgReq->GetImageStatus(&status); michael@0: imgReq->GetImage(aResult); // We hand back the image here. The GetImage call addrefs *aResult. michael@0: bool animated = true; // Assuming animated is the safe option michael@0: michael@0: // We can only call GetAnimated if we're decoded michael@0: if (*aResult && (status & imgIRequest::STATUS_DECODE_COMPLETE)) michael@0: (*aResult)->GetAnimated(&animated); michael@0: michael@0: if ((!(status & imgIRequest::STATUS_LOAD_COMPLETE)) || animated) { michael@0: // We either aren't done loading, or we're animating. Add our row as a listener for invalidations. michael@0: nsCOMPtr obs; michael@0: imgReq->GetNotificationObserver(getter_AddRefs(obs)); michael@0: michael@0: if (obs) { michael@0: static_cast (obs.get())->AddCell(aRowIndex, aCol); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (!*aResult) { michael@0: // Create a new nsTreeImageListener object and pass it our row and column michael@0: // information. michael@0: nsTreeImageListener* listener = new nsTreeImageListener(this); michael@0: if (!listener) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: if (!mCreatedListeners.PutEntry(listener)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: listener->AddCell(aRowIndex, aCol); michael@0: nsCOMPtr imgNotificationObserver = listener; michael@0: michael@0: nsRefPtr imageRequest; michael@0: if (styleRequest) { michael@0: styleRequest->Clone(imgNotificationObserver, getter_AddRefs(imageRequest)); michael@0: } else { michael@0: nsIDocument* doc = mContent->GetDocument(); michael@0: if (!doc) michael@0: // The page is currently being torn down. Why bother. michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr baseURI = mContent->GetBaseURI(); michael@0: michael@0: nsCOMPtr srcURI; michael@0: nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcURI), michael@0: imageSrc, michael@0: doc, michael@0: baseURI); michael@0: if (!srcURI) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // XXXbz what's the origin principal for this stuff that comes from our michael@0: // view? I guess we should assume that it's the node's principal... michael@0: if (nsContentUtils::CanLoadImage(srcURI, mContent, doc, michael@0: mContent->NodePrincipal())) { michael@0: nsresult rv = nsContentUtils::LoadImage(srcURI, michael@0: doc, michael@0: mContent->NodePrincipal(), michael@0: doc->GetDocumentURI(), michael@0: imgNotificationObserver, michael@0: nsIRequest::LOAD_NORMAL, michael@0: EmptyString(), michael@0: getter_AddRefs(imageRequest)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: } michael@0: } michael@0: listener->UnsuppressInvalidation(); michael@0: michael@0: if (!imageRequest) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // We don't want discarding/decode-on-draw for xul images michael@0: imageRequest->StartDecoding(); michael@0: imageRequest->LockImage(); michael@0: michael@0: // In a case it was already cached. michael@0: imageRequest->GetImage(aResult); michael@0: nsTreeImageCacheEntry cacheEntry(imageRequest, imgNotificationObserver); michael@0: mImageCache.Put(imageSrc, cacheEntry); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext, michael@0: nsStyleContext* aStyleContext) michael@0: { michael@0: // XXX We should respond to visibility rules for collapsed vs. hidden. michael@0: michael@0: // This method returns the width of the twisty INCLUDING borders and padding. michael@0: // It first checks the style context for a width. If none is found, it tries to michael@0: // use the default image width for the twisty. If no image is found, it defaults michael@0: // to border+padding. michael@0: nsRect r(0,0,0,0); michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(aStyleContext, bp); michael@0: r.Inflate(bp); michael@0: michael@0: // Now r contains our border+padding info. We now need to get our width and michael@0: // height. michael@0: bool needWidth = false; michael@0: bool needHeight = false; michael@0: michael@0: // We have to load image even though we already have a size. michael@0: // Don't change this, otherwise things start to go crazy. michael@0: bool useImageRegion = true; michael@0: nsCOMPtr image; michael@0: GetImage(aRowIndex, aCol, aUseContext, aStyleContext, useImageRegion, getter_AddRefs(image)); michael@0: michael@0: const nsStylePosition* myPosition = aStyleContext->StylePosition(); michael@0: const nsStyleList* myList = aStyleContext->StyleList(); michael@0: michael@0: if (useImageRegion) { michael@0: r.x += myList->mImageRegion.x; michael@0: r.y += myList->mImageRegion.y; michael@0: } michael@0: michael@0: if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) { michael@0: int32_t val = myPosition->mWidth.GetCoordValue(); michael@0: r.width += val; michael@0: } michael@0: else if (useImageRegion && myList->mImageRegion.width > 0) michael@0: r.width += myList->mImageRegion.width; michael@0: else michael@0: needWidth = true; michael@0: michael@0: if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) { michael@0: int32_t val = myPosition->mHeight.GetCoordValue(); michael@0: r.height += val; michael@0: } michael@0: else if (useImageRegion && myList->mImageRegion.height > 0) michael@0: r.height += myList->mImageRegion.height; michael@0: else michael@0: needHeight = true; michael@0: michael@0: if (image) { michael@0: if (needWidth || needHeight) { michael@0: // Get the natural image size. michael@0: michael@0: if (needWidth) { michael@0: // Get the size from the image. michael@0: nscoord width; michael@0: image->GetWidth(&width); michael@0: r.width += nsPresContext::CSSPixelsToAppUnits(width); michael@0: } michael@0: michael@0: if (needHeight) { michael@0: nscoord height; michael@0: image->GetHeight(&height); michael@0: r.height += nsPresContext::CSSPixelsToAppUnits(height); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: // GetImageDestSize returns the destination size of the image. michael@0: // The width and height do not include borders and padding. michael@0: // The width and height have not been adjusted to fit in the row height michael@0: // or cell width. michael@0: // The width and height reflect the destination size specified in CSS, michael@0: // or the image region specified in CSS, or the natural size of the michael@0: // image. michael@0: // If only the destination width has been specified in CSS, the height is michael@0: // calculated to maintain the aspect ratio of the image. michael@0: // If only the destination height has been specified in CSS, the width is michael@0: // calculated to maintain the aspect ratio of the image. michael@0: nsSize michael@0: nsTreeBodyFrame::GetImageDestSize(nsStyleContext* aStyleContext, michael@0: bool useImageRegion, michael@0: imgIContainer* image) michael@0: { michael@0: nsSize size(0,0); michael@0: michael@0: // We need to get the width and height. michael@0: bool needWidth = false; michael@0: bool needHeight = false; michael@0: michael@0: // Get the style position to see if the CSS has specified the michael@0: // destination width/height. michael@0: const nsStylePosition* myPosition = aStyleContext->StylePosition(); michael@0: michael@0: if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) { michael@0: // CSS has specified the destination width. michael@0: size.width = myPosition->mWidth.GetCoordValue(); michael@0: } michael@0: else { michael@0: // We'll need to get the width of the image/region. michael@0: needWidth = true; michael@0: } michael@0: michael@0: if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) { michael@0: // CSS has specified the destination height. michael@0: size.height = myPosition->mHeight.GetCoordValue(); michael@0: } michael@0: else { michael@0: // We'll need to get the height of the image/region. michael@0: needHeight = true; michael@0: } michael@0: michael@0: if (needWidth || needHeight) { michael@0: // We need to get the size of the image/region. michael@0: nsSize imageSize(0,0); michael@0: michael@0: const nsStyleList* myList = aStyleContext->StyleList(); michael@0: michael@0: if (useImageRegion && myList->mImageRegion.width > 0) { michael@0: // CSS has specified an image region. michael@0: // Use the width of the image region. michael@0: imageSize.width = myList->mImageRegion.width; michael@0: } michael@0: else if (image) { michael@0: nscoord width; michael@0: image->GetWidth(&width); michael@0: imageSize.width = nsPresContext::CSSPixelsToAppUnits(width); michael@0: } michael@0: michael@0: if (useImageRegion && myList->mImageRegion.height > 0) { michael@0: // CSS has specified an image region. michael@0: // Use the height of the image region. michael@0: imageSize.height = myList->mImageRegion.height; michael@0: } michael@0: else if (image) { michael@0: nscoord height; michael@0: image->GetHeight(&height); michael@0: imageSize.height = nsPresContext::CSSPixelsToAppUnits(height); michael@0: } michael@0: michael@0: if (needWidth) { michael@0: if (!needHeight && imageSize.height != 0) { michael@0: // The CSS specified the destination height, but not the destination michael@0: // width. We need to calculate the width so that we maintain the michael@0: // image's aspect ratio. michael@0: size.width = imageSize.width * size.height / imageSize.height; michael@0: } michael@0: else { michael@0: size.width = imageSize.width; michael@0: } michael@0: } michael@0: michael@0: if (needHeight) { michael@0: if (!needWidth && imageSize.width != 0) { michael@0: // The CSS specified the destination width, but not the destination michael@0: // height. We need to calculate the height so that we maintain the michael@0: // image's aspect ratio. michael@0: size.height = imageSize.height * size.width / imageSize.width; michael@0: } michael@0: else { michael@0: size.height = imageSize.height; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return size; michael@0: } michael@0: michael@0: // GetImageSourceRect returns the source rectangle of the image to be michael@0: // displayed. michael@0: // The width and height reflect the image region specified in CSS, or michael@0: // the natural size of the image. michael@0: // The width and height do not include borders and padding. michael@0: // The width and height do not reflect the destination size specified michael@0: // in CSS. michael@0: nsRect michael@0: nsTreeBodyFrame::GetImageSourceRect(nsStyleContext* aStyleContext, michael@0: bool useImageRegion, michael@0: imgIContainer* image) michael@0: { michael@0: nsRect r(0,0,0,0); michael@0: michael@0: const nsStyleList* myList = aStyleContext->StyleList(); michael@0: michael@0: if (useImageRegion && michael@0: (myList->mImageRegion.width > 0 || myList->mImageRegion.height > 0)) { michael@0: // CSS has specified an image region. michael@0: r = myList->mImageRegion; michael@0: } michael@0: else if (image) { michael@0: // Use the actual image size. michael@0: nscoord coord; michael@0: image->GetWidth(&coord); michael@0: r.width = nsPresContext::CSSPixelsToAppUnits(coord); michael@0: image->GetHeight(&coord); michael@0: r.height = nsPresContext::CSSPixelsToAppUnits(coord); michael@0: } michael@0: michael@0: return r; michael@0: } michael@0: michael@0: int32_t nsTreeBodyFrame::GetRowHeight() michael@0: { michael@0: // Look up the correct height. It is equal to the specified height michael@0: // + the specified margins. michael@0: mScratchArray.Clear(); michael@0: nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); michael@0: if (rowContext) { michael@0: const nsStylePosition* myPosition = rowContext->StylePosition(); michael@0: michael@0: nscoord minHeight = 0; michael@0: if (myPosition->mMinHeight.GetUnit() == eStyleUnit_Coord) michael@0: minHeight = myPosition->mMinHeight.GetCoordValue(); michael@0: michael@0: nscoord height = 0; michael@0: if (myPosition->mHeight.GetUnit() == eStyleUnit_Coord) michael@0: height = myPosition->mHeight.GetCoordValue(); michael@0: michael@0: if (height < minHeight) michael@0: height = minHeight; michael@0: michael@0: if (height > 0) { michael@0: height = nsPresContext::AppUnitsToIntCSSPixels(height); michael@0: height += height % 2; michael@0: height = nsPresContext::CSSPixelsToAppUnits(height); michael@0: michael@0: // XXX Check box-sizing to determine if border/padding should augment the height michael@0: // Inflate the height by our margins. michael@0: nsRect rowRect(0,0,0,height); michael@0: nsMargin rowMargin; michael@0: rowContext->StyleMargin()->GetMargin(rowMargin); michael@0: rowRect.Inflate(rowMargin); michael@0: height = rowRect.height; michael@0: return height; michael@0: } michael@0: } michael@0: michael@0: return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any. michael@0: } michael@0: michael@0: int32_t nsTreeBodyFrame::GetIndentation() michael@0: { michael@0: // Look up the correct indentation. It is equal to the specified indentation width. michael@0: mScratchArray.Clear(); michael@0: nsStyleContext* indentContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeindentation); michael@0: if (indentContext) { michael@0: const nsStylePosition* myPosition = indentContext->StylePosition(); michael@0: if (myPosition->mWidth.GetUnit() == eStyleUnit_Coord) { michael@0: nscoord val = myPosition->mWidth.GetCoordValue(); michael@0: return val; michael@0: } michael@0: } michael@0: michael@0: return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any. michael@0: } michael@0: michael@0: void nsTreeBodyFrame::CalcInnerBox() michael@0: { michael@0: mInnerBox.SetRect(0, 0, mRect.width, mRect.height); michael@0: AdjustForBorderPadding(mStyleContext, mInnerBox); michael@0: } michael@0: michael@0: nscoord michael@0: nsTreeBodyFrame::CalcHorzWidth(const ScrollParts& aParts) michael@0: { michael@0: // Compute the adjustment to the last column. This varies depending on the michael@0: // visibility of the columnpicker and the scrollbar. michael@0: if (aParts.mColumnsFrame) michael@0: mAdjustWidth = mRect.width - aParts.mColumnsFrame->GetRect().width; michael@0: else michael@0: mAdjustWidth = 0; michael@0: michael@0: nscoord width = 0; michael@0: michael@0: // We calculate this from the scrollable frame, so that it michael@0: // properly covers all contingencies of what could be michael@0: // scrollable (columns, body, etc...) michael@0: michael@0: if (aParts.mColumnsScrollFrame) { michael@0: width = aParts.mColumnsScrollFrame->GetScrollRange().width + michael@0: aParts.mColumnsScrollFrame->GetScrollPortRect().width; michael@0: } michael@0: michael@0: // If no horz scrolling periphery is present, then just return our width michael@0: if (width == 0) michael@0: width = mRect.width; michael@0: michael@0: return width; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::GetCursor(const nsPoint& aPoint, michael@0: nsIFrame::Cursor& aCursor) michael@0: { michael@0: // Check the GetScriptHandlingObject so we don't end up running code when michael@0: // the document is a zombie. michael@0: bool dummy; michael@0: if (mView && GetContent()->GetCurrentDoc()->GetScriptHandlingObject(dummy)) { michael@0: int32_t row; michael@0: nsTreeColumn* col; michael@0: nsIAtom* child; michael@0: GetCellAt(aPoint.x, aPoint.y, &row, &col, &child); michael@0: michael@0: if (child) { michael@0: // Our scratch array is already prefilled. michael@0: nsStyleContext* childContext = GetPseudoStyleContext(child); michael@0: michael@0: FillCursorInformationFromStyle(childContext->StyleUserInterface(), michael@0: aCursor); michael@0: if (aCursor.mCursor == NS_STYLE_CURSOR_AUTO) michael@0: aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT; michael@0: michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: return nsLeafBoxFrame::GetCursor(aPoint, aCursor); michael@0: } michael@0: michael@0: static uint32_t GetDropEffect(WidgetGUIEvent* aEvent) michael@0: { michael@0: NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "wrong event type"); michael@0: WidgetDragEvent* dragEvent = aEvent->AsDragEvent(); michael@0: nsContentUtils::SetDataTransferInEvent(dragEvent); michael@0: michael@0: uint32_t action = 0; michael@0: if (dragEvent->dataTransfer) michael@0: dragEvent->dataTransfer->GetDropEffectInt(&action); michael@0: return action; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext, michael@0: WidgetGUIEvent* aEvent, michael@0: nsEventStatus* aEventStatus) michael@0: { michael@0: if (aEvent->message == NS_MOUSE_ENTER_SYNTH || aEvent->message == NS_MOUSE_MOVE) { michael@0: nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); michael@0: int32_t xTwips = pt.x - mInnerBox.x; michael@0: int32_t yTwips = pt.y - mInnerBox.y; michael@0: int32_t newrow = GetRowAt(xTwips, yTwips); michael@0: if (mMouseOverRow != newrow) { michael@0: // redraw the old and the new row michael@0: if (mMouseOverRow != -1) michael@0: InvalidateRow(mMouseOverRow); michael@0: mMouseOverRow = newrow; michael@0: if (mMouseOverRow != -1) michael@0: InvalidateRow(mMouseOverRow); michael@0: } michael@0: } michael@0: else if (aEvent->message == NS_MOUSE_EXIT_SYNTH) { michael@0: if (mMouseOverRow != -1) { michael@0: InvalidateRow(mMouseOverRow); michael@0: mMouseOverRow = -1; michael@0: } michael@0: } michael@0: else if (aEvent->message == NS_DRAGDROP_ENTER) { michael@0: if (!mSlots) michael@0: mSlots = new Slots(); michael@0: michael@0: // Cache several things we'll need throughout the course of our work. These michael@0: // will all get released on a drag exit. michael@0: michael@0: if (mSlots->mTimer) { michael@0: mSlots->mTimer->Cancel(); michael@0: mSlots->mTimer = nullptr; michael@0: } michael@0: michael@0: // Cache the drag session. michael@0: mSlots->mIsDragging = true; michael@0: mSlots->mDropRow = -1; michael@0: mSlots->mDropOrient = -1; michael@0: mSlots->mDragAction = GetDropEffect(aEvent); michael@0: } michael@0: else if (aEvent->message == NS_DRAGDROP_OVER) { michael@0: // The mouse is hovering over this tree. If we determine things are michael@0: // different from the last time, invalidate the drop feedback at the old michael@0: // position, query the view to see if the current location is droppable, michael@0: // and then invalidate the drop feedback at the new location if it is. michael@0: // The mouse may or may not have changed position from the last time michael@0: // we were called, so optimize out a lot of the extra notifications by michael@0: // checking if anything changed first. For drop feedback we use drop, michael@0: // dropBefore and dropAfter property. michael@0: michael@0: if (!mView || !mSlots) michael@0: return NS_OK; michael@0: michael@0: // Save last values, we will need them. michael@0: int32_t lastDropRow = mSlots->mDropRow; michael@0: int16_t lastDropOrient = mSlots->mDropOrient; michael@0: #ifndef XP_MACOSX michael@0: int16_t lastScrollLines = mSlots->mScrollLines; michael@0: #endif michael@0: michael@0: // Find out the current drag action michael@0: uint32_t lastDragAction = mSlots->mDragAction; michael@0: mSlots->mDragAction = GetDropEffect(aEvent); michael@0: michael@0: // Compute the row mouse is over and the above/below/on state. michael@0: // Below we'll use this to see if anything changed. michael@0: // Also check if we want to auto-scroll. michael@0: ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient, &mSlots->mScrollLines); michael@0: michael@0: // While we're here, handle tracking of scrolling during a drag. michael@0: if (mSlots->mScrollLines) { michael@0: if (mSlots->mDropAllowed) { michael@0: // Invalidate primary cell at old location. michael@0: mSlots->mDropAllowed = false; michael@0: InvalidateDropFeedback(lastDropRow, lastDropOrient); michael@0: } michael@0: #ifdef XP_MACOSX michael@0: ScrollByLines(mSlots->mScrollLines); michael@0: #else michael@0: if (!lastScrollLines) { michael@0: // Cancel any previously initialized timer. michael@0: if (mSlots->mTimer) { michael@0: mSlots->mTimer->Cancel(); michael@0: mSlots->mTimer = nullptr; michael@0: } michael@0: michael@0: // Set a timer to trigger the tree scrolling. michael@0: CreateTimer(LookAndFeel::eIntID_TreeLazyScrollDelay, michael@0: LazyScrollCallback, nsITimer::TYPE_ONE_SHOT, michael@0: getter_AddRefs(mSlots->mTimer)); michael@0: } michael@0: #endif michael@0: // Bail out to prevent spring loaded timer and feedback line settings. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If changed from last time, invalidate primary cell at the old location and if allowed, michael@0: // invalidate primary cell at the new location. If nothing changed, just bail. michael@0: if (mSlots->mDropRow != lastDropRow || michael@0: mSlots->mDropOrient != lastDropOrient || michael@0: mSlots->mDragAction != lastDragAction) { michael@0: michael@0: // Invalidate row at the old location. michael@0: if (mSlots->mDropAllowed) { michael@0: mSlots->mDropAllowed = false; michael@0: InvalidateDropFeedback(lastDropRow, lastDropOrient); michael@0: } michael@0: michael@0: if (mSlots->mTimer) { michael@0: // Timer is active but for a different row than the current one, kill it. michael@0: mSlots->mTimer->Cancel(); michael@0: mSlots->mTimer = nullptr; michael@0: } michael@0: michael@0: if (mSlots->mDropRow >= 0) { michael@0: if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) { michael@0: // Either there wasn't a timer running or it was just killed above. michael@0: // If over a folder, start up a timer to open the folder. michael@0: bool isContainer = false; michael@0: mView->IsContainer(mSlots->mDropRow, &isContainer); michael@0: if (isContainer) { michael@0: bool isOpen = false; michael@0: mView->IsContainerOpen(mSlots->mDropRow, &isOpen); michael@0: if (!isOpen) { michael@0: // This node isn't expanded, set a timer to expand it. michael@0: CreateTimer(LookAndFeel::eIntID_TreeOpenDelay, michael@0: OpenCallback, nsITimer::TYPE_ONE_SHOT, michael@0: getter_AddRefs(mSlots->mTimer)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The dataTransfer was initialized by the call to GetDropEffect above. michael@0: bool canDropAtNewLocation = false; michael@0: mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient, michael@0: aEvent->AsDragEvent()->dataTransfer, michael@0: &canDropAtNewLocation); michael@0: michael@0: if (canDropAtNewLocation) { michael@0: // Invalidate row at the new location. michael@0: mSlots->mDropAllowed = canDropAtNewLocation; michael@0: InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Indicate that the drop is allowed by preventing the default behaviour. michael@0: if (mSlots->mDropAllowed) michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; michael@0: } michael@0: else if (aEvent->message == NS_DRAGDROP_DROP) { michael@0: // this event was meant for another frame, so ignore it michael@0: if (!mSlots) michael@0: return NS_OK; michael@0: michael@0: // Tell the view where the drop happened. michael@0: michael@0: // Remove the drop folder and all its parents from the array. michael@0: int32_t parentIndex; michael@0: nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex); michael@0: while (NS_SUCCEEDED(rv) && parentIndex >= 0) { michael@0: mSlots->mArray.RemoveElement(parentIndex); michael@0: rv = mView->GetParentIndex(parentIndex, &parentIndex); michael@0: } michael@0: michael@0: NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "wrong event type"); michael@0: WidgetDragEvent* dragEvent = aEvent->AsDragEvent(); michael@0: nsContentUtils::SetDataTransferInEvent(dragEvent); michael@0: michael@0: mView->Drop(mSlots->mDropRow, mSlots->mDropOrient, dragEvent->dataTransfer); michael@0: mSlots->mDropRow = -1; michael@0: mSlots->mDropOrient = -1; michael@0: mSlots->mIsDragging = false; michael@0: *aEventStatus = nsEventStatus_eConsumeNoDefault; // already handled the drop michael@0: } michael@0: else if (aEvent->message == NS_DRAGDROP_EXIT) { michael@0: // this event was meant for another frame, so ignore it michael@0: if (!mSlots) michael@0: return NS_OK; michael@0: michael@0: // Clear out all our tracking vars. michael@0: michael@0: if (mSlots->mDropAllowed) { michael@0: mSlots->mDropAllowed = false; michael@0: InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient); michael@0: } michael@0: else michael@0: mSlots->mDropAllowed = false; michael@0: mSlots->mIsDragging = false; michael@0: mSlots->mScrollLines = 0; michael@0: // If a drop is occuring, the exit event will fire just before the drop michael@0: // event, so don't reset mDropRow or mDropOrient as these fields are used michael@0: // by the drop event. michael@0: if (mSlots->mTimer) { michael@0: mSlots->mTimer->Cancel(); michael@0: mSlots->mTimer = nullptr; michael@0: } michael@0: michael@0: if (!mSlots->mArray.IsEmpty()) { michael@0: // Close all spring loaded folders except the drop folder. michael@0: CreateTimer(LookAndFeel::eIntID_TreeCloseDelay, michael@0: CloseCallback, nsITimer::TYPE_ONE_SHOT, michael@0: getter_AddRefs(mSlots->mTimer)); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static nsLineStyle michael@0: ConvertBorderStyleToLineStyle(uint8_t aBorderStyle) michael@0: { michael@0: switch (aBorderStyle) { michael@0: case NS_STYLE_BORDER_STYLE_DOTTED: michael@0: return nsLineStyle_kDotted; michael@0: case NS_STYLE_BORDER_STYLE_DASHED: michael@0: return nsLineStyle_kDashed; michael@0: default: michael@0: return nsLineStyle_kSolid; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: PaintTreeBody(nsIFrame* aFrame, nsRenderingContext* aCtx, michael@0: const nsRect& aDirtyRect, nsPoint aPt) michael@0: { michael@0: static_cast(aFrame)->PaintTreeBody(*aCtx, aDirtyRect, aPt); michael@0: } michael@0: michael@0: // Painting routines michael@0: void michael@0: nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: // REVIEW: why did we paint if we were collapsed? that makes no sense! michael@0: if (!IsVisibleForPainting(aBuilder)) michael@0: return; // We're invisible. Don't paint. michael@0: michael@0: // Handles painting our background, border, and outline. michael@0: nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); michael@0: michael@0: // Bail out now if there's no view or we can't run script because the michael@0: // document is a zombie michael@0: if (!mView || !GetContent()->GetCurrentDoc()->GetWindow()) michael@0: return; michael@0: michael@0: aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayGeneric(aBuilder, this, ::PaintTreeBody, "XULTreeBody", michael@0: nsDisplayItem::TYPE_XUL_TREE_BODY)); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintTreeBody(nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, nsPoint aPt) michael@0: { michael@0: // Update our available height and our page count. michael@0: CalcInnerBox(); michael@0: aRenderingContext.PushState(); michael@0: aRenderingContext.IntersectClip(mInnerBox + aPt); michael@0: int32_t oldPageCount = mPageLength; michael@0: if (!mHasFixedRowCount) michael@0: mPageLength = mInnerBox.height/mRowHeight; michael@0: michael@0: if (oldPageCount != mPageLength || mHorzWidth != CalcHorzWidth(GetScrollParts())) { michael@0: // Schedule a ResizeReflow that will update our info properly. michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); michael@0: } michael@0: #ifdef DEBUG michael@0: int32_t rowCount = mRowCount; michael@0: mView->GetRowCount(&rowCount); michael@0: NS_WARN_IF_FALSE(mRowCount == rowCount, "row count changed unexpectedly"); michael@0: #endif michael@0: michael@0: // Loop through our columns and paint them (e.g., for sorting). This is only michael@0: // relevant when painting backgrounds, since columns contain no content. Content michael@0: // is contained in the rows. michael@0: for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; michael@0: currCol = currCol->GetNext()) { michael@0: nsRect colRect; michael@0: nsresult rv = currCol->GetRect(this, mInnerBox.y, mInnerBox.height, michael@0: &colRect); michael@0: // Don't paint hidden columns. michael@0: if (NS_FAILED(rv) || colRect.width == 0) continue; michael@0: michael@0: if (OffsetForHorzScroll(colRect, false)) { michael@0: nsRect dirtyRect; michael@0: colRect += aPt; michael@0: if (dirtyRect.IntersectRect(aDirtyRect, colRect)) { michael@0: PaintColumn(currCol, colRect, PresContext(), aRenderingContext, aDirtyRect); michael@0: } michael@0: } michael@0: } michael@0: // Loop through our on-screen rows. michael@0: for (int32_t i = mTopRowIndex; i < mRowCount && i <= mTopRowIndex+mPageLength; i++) { michael@0: nsRect rowRect(mInnerBox.x, mInnerBox.y+mRowHeight*(i-mTopRowIndex), mInnerBox.width, mRowHeight); michael@0: nsRect dirtyRect; michael@0: if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) && michael@0: rowRect.y < (mInnerBox.y+mInnerBox.height)) { michael@0: PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext, aDirtyRect, aPt); michael@0: } michael@0: } michael@0: michael@0: if (mSlots && mSlots->mDropAllowed && (mSlots->mDropOrient == nsITreeView::DROP_BEFORE || michael@0: mSlots->mDropOrient == nsITreeView::DROP_AFTER)) { michael@0: nscoord yPos = mInnerBox.y + mRowHeight * (mSlots->mDropRow - mTopRowIndex) - mRowHeight / 2; michael@0: nsRect feedbackRect(mInnerBox.x, yPos, mInnerBox.width, mRowHeight); michael@0: if (mSlots->mDropOrient == nsITreeView::DROP_AFTER) michael@0: feedbackRect.y += mRowHeight; michael@0: michael@0: nsRect dirtyRect; michael@0: feedbackRect += aPt; michael@0: if (dirtyRect.IntersectRect(aDirtyRect, feedbackRect)) { michael@0: PaintDropFeedback(feedbackRect, PresContext(), aRenderingContext, aDirtyRect, aPt); michael@0: } michael@0: } michael@0: aRenderingContext.PopState(); michael@0: } michael@0: michael@0: michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintColumn(nsTreeColumn* aColumn, michael@0: const nsRect& aColumnRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: // Now obtain the properties for our cell. michael@0: PrefillPropertyArray(-1, aColumn); michael@0: nsAutoString properties; michael@0: mView->GetColumnProperties(aColumn, properties); michael@0: nsTreeUtils::TokenizeProperties(properties, mScratchArray); michael@0: michael@0: // Resolve style for the column. It contains all the info we need to lay ourselves michael@0: // out and to paint. michael@0: nsStyleContext* colContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecolumn); michael@0: michael@0: // Obtain the margins for the cell and then deflate our rect by that michael@0: // amount. The cell is assumed to be contained within the deflated rect. michael@0: nsRect colRect(aColumnRect); michael@0: nsMargin colMargin; michael@0: colContext->StyleMargin()->GetMargin(colMargin); michael@0: colRect.Deflate(colMargin); michael@0: michael@0: PaintBackgroundLayer(colContext, aPresContext, aRenderingContext, colRect, aDirtyRect); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintRow(int32_t aRowIndex, michael@0: const nsRect& aRowRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nsPoint aPt) michael@0: { michael@0: // We have been given a rect for our row. We treat this row like a full-blown michael@0: // frame, meaning that it can have borders, margins, padding, and a background. michael@0: michael@0: // Without a view, we have no data. Check for this up front. michael@0: if (!mView) michael@0: return; michael@0: michael@0: nsresult rv; michael@0: michael@0: // Now obtain the properties for our row. michael@0: // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused michael@0: PrefillPropertyArray(aRowIndex, nullptr); michael@0: michael@0: nsAutoString properties; michael@0: mView->GetRowProperties(aRowIndex, properties); michael@0: nsTreeUtils::TokenizeProperties(properties, mScratchArray); michael@0: michael@0: // Resolve style for the row. It contains all the info we need to lay ourselves michael@0: // out and to paint. michael@0: nsStyleContext* rowContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreerow); michael@0: michael@0: // Obtain the margins for the row and then deflate our rect by that michael@0: // amount. The row is assumed to be contained within the deflated rect. michael@0: nsRect rowRect(aRowRect); michael@0: nsMargin rowMargin; michael@0: rowContext->StyleMargin()->GetMargin(rowMargin); michael@0: rowRect.Deflate(rowMargin); michael@0: michael@0: // Paint our borders and background for our row rect. michael@0: // If a -moz-appearance is provided, use theme drawing only if the current row michael@0: // is not selected (since we draw the selection as part of drawing the background). michael@0: bool useTheme = false; michael@0: nsITheme *theme = nullptr; michael@0: const nsStyleDisplay* displayData = rowContext->StyleDisplay(); michael@0: if (displayData->mAppearance) { michael@0: theme = aPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance)) michael@0: useTheme = true; michael@0: } michael@0: bool isSelected = false; michael@0: nsCOMPtr selection; michael@0: mView->GetSelection(getter_AddRefs(selection)); michael@0: if (selection) michael@0: selection->IsSelected(aRowIndex, &isSelected); michael@0: if (useTheme && !isSelected) { michael@0: nsRect dirty; michael@0: dirty.IntersectRect(rowRect, aDirtyRect); michael@0: theme->DrawWidgetBackground(&aRenderingContext, this, michael@0: displayData->mAppearance, rowRect, dirty); michael@0: } else { michael@0: PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext, rowRect, aDirtyRect); michael@0: } michael@0: michael@0: // Adjust the rect for its border and padding. michael@0: nsRect originalRowRect = rowRect; michael@0: AdjustForBorderPadding(rowContext, rowRect); michael@0: michael@0: bool isSeparator = false; michael@0: mView->IsSeparator(aRowIndex, &isSeparator); michael@0: if (isSeparator) { michael@0: // The row is a separator. michael@0: michael@0: nscoord primaryX = rowRect.x; michael@0: nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn(); michael@0: if (primaryCol) { michael@0: // Paint the primary cell. michael@0: nsRect cellRect; michael@0: rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect); michael@0: if (NS_FAILED(rv)) { michael@0: NS_NOTREACHED("primary column is invalid"); michael@0: return; michael@0: } michael@0: michael@0: if (OffsetForHorzScroll(cellRect, false)) { michael@0: cellRect.x += aPt.x; michael@0: nsRect dirtyRect; michael@0: nsRect checkRect(cellRect.x, originalRowRect.y, michael@0: cellRect.width, originalRowRect.height); michael@0: if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) michael@0: PaintCell(aRowIndex, primaryCol, cellRect, aPresContext, michael@0: aRenderingContext, aDirtyRect, primaryX, aPt); michael@0: } michael@0: michael@0: // Paint the left side of the separator. michael@0: nscoord currX; michael@0: nsTreeColumn* previousCol = primaryCol->GetPrevious(); michael@0: if (previousCol) { michael@0: nsRect prevColRect; michael@0: rv = previousCol->GetRect(this, 0, 0, &prevColRect); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: currX = (prevColRect.x - mHorzPosition) + prevColRect.width + aPt.x; michael@0: } else { michael@0: NS_NOTREACHED("The column before the primary column is invalid"); michael@0: currX = rowRect.x; michael@0: } michael@0: } else { michael@0: currX = rowRect.x; michael@0: } michael@0: michael@0: int32_t level; michael@0: mView->GetLevel(aRowIndex, &level); michael@0: if (level == 0) michael@0: currX += mIndentation; michael@0: michael@0: if (currX > rowRect.x) { michael@0: nsRect separatorRect(rowRect); michael@0: separatorRect.width -= rowRect.x + rowRect.width - currX; michael@0: PaintSeparator(aRowIndex, separatorRect, aPresContext, aRenderingContext, aDirtyRect); michael@0: } michael@0: } michael@0: michael@0: // Paint the right side (whole) separator. michael@0: nsRect separatorRect(rowRect); michael@0: if (primaryX > rowRect.x) { michael@0: separatorRect.width -= primaryX - rowRect.x; michael@0: separatorRect.x += primaryX - rowRect.x; michael@0: } michael@0: PaintSeparator(aRowIndex, separatorRect, aPresContext, aRenderingContext, aDirtyRect); michael@0: } michael@0: else { michael@0: // Now loop over our cells. Only paint a cell if it intersects with our dirty rect. michael@0: for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol; michael@0: currCol = currCol->GetNext()) { michael@0: nsRect cellRect; michael@0: rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect); michael@0: // Don't paint cells in hidden columns. michael@0: if (NS_FAILED(rv) || cellRect.width == 0) michael@0: continue; michael@0: michael@0: if (OffsetForHorzScroll(cellRect, false)) { michael@0: cellRect.x += aPt.x; michael@0: michael@0: // for primary columns, use the row's vertical size so that the michael@0: // lines get drawn properly michael@0: nsRect checkRect = cellRect; michael@0: if (currCol->IsPrimary()) michael@0: checkRect = nsRect(cellRect.x, originalRowRect.y, michael@0: cellRect.width, originalRowRect.height); michael@0: michael@0: nsRect dirtyRect; michael@0: nscoord dummy; michael@0: if (dirtyRect.IntersectRect(aDirtyRect, checkRect)) michael@0: PaintCell(aRowIndex, currCol, cellRect, aPresContext, michael@0: aRenderingContext, aDirtyRect, dummy, aPt); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintSeparator(int32_t aRowIndex, michael@0: const nsRect& aSeparatorRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: // Resolve style for the separator. michael@0: nsStyleContext* separatorContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeseparator); michael@0: bool useTheme = false; michael@0: nsITheme *theme = nullptr; michael@0: const nsStyleDisplay* displayData = separatorContext->StyleDisplay(); michael@0: if ( displayData->mAppearance ) { michael@0: theme = aPresContext->GetTheme(); michael@0: if (theme && theme->ThemeSupportsWidget(aPresContext, nullptr, displayData->mAppearance)) michael@0: useTheme = true; michael@0: } michael@0: michael@0: // use -moz-appearance if provided. michael@0: if (useTheme) { michael@0: nsRect dirty; michael@0: dirty.IntersectRect(aSeparatorRect, aDirtyRect); michael@0: theme->DrawWidgetBackground(&aRenderingContext, this, michael@0: displayData->mAppearance, aSeparatorRect, dirty); michael@0: } michael@0: else { michael@0: const nsStylePosition* stylePosition = separatorContext->StylePosition(); michael@0: michael@0: // Obtain the height for the separator or use the default value. michael@0: nscoord height; michael@0: if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord) michael@0: height = stylePosition->mHeight.GetCoordValue(); michael@0: else { michael@0: // Use default height 2px. michael@0: height = nsPresContext::CSSPixelsToAppUnits(2); michael@0: } michael@0: michael@0: // Obtain the margins for the separator and then deflate our rect by that michael@0: // amount. The separator is assumed to be contained within the deflated rect. michael@0: nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y, aSeparatorRect.width, height); michael@0: nsMargin separatorMargin; michael@0: separatorContext->StyleMargin()->GetMargin(separatorMargin); michael@0: separatorRect.Deflate(separatorMargin); michael@0: michael@0: // Center the separator. michael@0: separatorRect.y += (aSeparatorRect.height - height) / 2; michael@0: michael@0: PaintBackgroundLayer(separatorContext, aPresContext, aRenderingContext, separatorRect, aDirtyRect); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintCell(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: const nsRect& aCellRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nscoord& aCurrX, michael@0: nsPoint aPt) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: // Now obtain the properties for our cell. michael@0: // XXX Automatically fill in the following props: open, closed, container, leaf, selected, focused, and the col ID. michael@0: PrefillPropertyArray(aRowIndex, aColumn); michael@0: nsAutoString properties; michael@0: mView->GetCellProperties(aRowIndex, aColumn, properties); michael@0: nsTreeUtils::TokenizeProperties(properties, mScratchArray); michael@0: michael@0: // Resolve style for the cell. It contains all the info we need to lay ourselves michael@0: // out and to paint. michael@0: nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); michael@0: michael@0: bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: michael@0: // Obtain the margins for the cell and then deflate our rect by that michael@0: // amount. The cell is assumed to be contained within the deflated rect. michael@0: nsRect cellRect(aCellRect); michael@0: nsMargin cellMargin; michael@0: cellContext->StyleMargin()->GetMargin(cellMargin); michael@0: cellRect.Deflate(cellMargin); michael@0: michael@0: // Paint our borders and background for our row rect. michael@0: PaintBackgroundLayer(cellContext, aPresContext, aRenderingContext, cellRect, aDirtyRect); michael@0: michael@0: // Adjust the rect for its border and padding. michael@0: AdjustForBorderPadding(cellContext, cellRect); michael@0: michael@0: nscoord currX = cellRect.x; michael@0: nscoord remainingWidth = cellRect.width; michael@0: michael@0: // Now we paint the contents of the cells. michael@0: // Directionality of the tree determines the order in which we paint. michael@0: // NS_STYLE_DIRECTION_LTR means paint from left to right. michael@0: // NS_STYLE_DIRECTION_RTL means paint from right to left. michael@0: michael@0: if (aColumn->IsPrimary()) { michael@0: // If we're the primary column, we need to indent and paint the twisty and any connecting lines michael@0: // between siblings. michael@0: michael@0: int32_t level; michael@0: mView->GetLevel(aRowIndex, &level); michael@0: michael@0: if (!isRTL) michael@0: currX += mIndentation * level; michael@0: remainingWidth -= mIndentation * level; michael@0: michael@0: // Resolve the style to use for the connecting lines. michael@0: nsStyleContext* lineContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeline); michael@0: michael@0: if (mIndentation && level && michael@0: lineContext->StyleVisibility()->IsVisibleOrCollapsed()) { michael@0: // Paint the thread lines. michael@0: michael@0: // Get the size of the twisty. We don't want to paint the twisty michael@0: // before painting of connecting lines since it would paint lines over michael@0: // the twisty. But we need to leave a place for it. michael@0: nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); michael@0: michael@0: nsRect imageSize; michael@0: nsRect twistyRect(aCellRect); michael@0: GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, aPresContext, michael@0: aRenderingContext, twistyContext); michael@0: michael@0: nsMargin twistyMargin; michael@0: twistyContext->StyleMargin()->GetMargin(twistyMargin); michael@0: twistyRect.Inflate(twistyMargin); michael@0: michael@0: aRenderingContext.PushState(); michael@0: michael@0: const nsStyleBorder* borderStyle = lineContext->StyleBorder(); michael@0: nscolor color; michael@0: bool foreground; michael@0: borderStyle->GetBorderColor(NS_SIDE_LEFT, color, foreground); michael@0: if (foreground) { michael@0: // GetBorderColor didn't touch color, thus grab it from the treeline context michael@0: color = lineContext->StyleColor()->mColor; michael@0: } michael@0: aRenderingContext.SetColor(color); michael@0: uint8_t style; michael@0: style = borderStyle->GetBorderStyle(NS_SIDE_LEFT); michael@0: aRenderingContext.SetLineStyle(ConvertBorderStyleToLineStyle(style)); michael@0: michael@0: nscoord srcX = currX + twistyRect.width - mIndentation / 2; michael@0: nscoord lineY = (aRowIndex - mTopRowIndex) * mRowHeight + aPt.y; michael@0: michael@0: // Don't paint off our cell. michael@0: if (srcX <= cellRect.x + cellRect.width) { michael@0: nscoord destX = currX + twistyRect.width; michael@0: if (destX > cellRect.x + cellRect.width) michael@0: destX = cellRect.x + cellRect.width; michael@0: if (isRTL) { michael@0: srcX = currX + remainingWidth - (srcX - cellRect.x); michael@0: destX = currX + remainingWidth - (destX - cellRect.x); michael@0: } michael@0: aRenderingContext.DrawLine(srcX, lineY + mRowHeight / 2, destX, lineY + mRowHeight / 2); michael@0: } michael@0: michael@0: int32_t currentParent = aRowIndex; michael@0: for (int32_t i = level; i > 0; i--) { michael@0: if (srcX <= cellRect.x + cellRect.width) { michael@0: // Paint full vertical line only if we have next sibling. michael@0: bool hasNextSibling; michael@0: mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling); michael@0: if (hasNextSibling) michael@0: aRenderingContext.DrawLine(srcX, lineY, srcX, lineY + mRowHeight); michael@0: else if (i == level) michael@0: aRenderingContext.DrawLine(srcX, lineY, srcX, lineY + mRowHeight / 2); michael@0: } michael@0: michael@0: int32_t parent; michael@0: if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0) michael@0: break; michael@0: currentParent = parent; michael@0: srcX -= mIndentation; michael@0: } michael@0: michael@0: aRenderingContext.PopState(); michael@0: } michael@0: michael@0: // Always leave space for the twisty. michael@0: nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height); michael@0: PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext, aRenderingContext, aDirtyRect, michael@0: remainingWidth, currX); michael@0: } michael@0: michael@0: // Now paint the icon for our cell. michael@0: nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height); michael@0: nsRect dirtyRect; michael@0: if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) michael@0: PaintImage(aRowIndex, aColumn, iconRect, aPresContext, aRenderingContext, aDirtyRect, michael@0: remainingWidth, currX); michael@0: michael@0: // Now paint our element, but only if we aren't a cycler column. michael@0: // XXX until we have the ability to load images, allow the view to michael@0: // insert text into cycler columns... michael@0: if (!aColumn->IsCycler()) { michael@0: nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height); michael@0: nsRect dirtyRect; michael@0: if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) { michael@0: switch (aColumn->GetType()) { michael@0: case nsITreeColumn::TYPE_TEXT: michael@0: PaintText(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect, currX); michael@0: break; michael@0: case nsITreeColumn::TYPE_CHECKBOX: michael@0: PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect); michael@0: break; michael@0: case nsITreeColumn::TYPE_PROGRESSMETER: michael@0: int32_t state; michael@0: mView->GetProgressMode(aRowIndex, aColumn, &state); michael@0: switch (state) { michael@0: case nsITreeView::PROGRESS_NORMAL: michael@0: case nsITreeView::PROGRESS_UNDETERMINED: michael@0: PaintProgressMeter(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect); michael@0: break; michael@0: case nsITreeView::PROGRESS_NONE: michael@0: default: michael@0: PaintText(aRowIndex, aColumn, elementRect, aPresContext, aRenderingContext, aDirtyRect, currX); michael@0: break; michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: aCurrX = currX; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintTwisty(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: const nsRect& aTwistyRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nscoord& aRemainingWidth, michael@0: nscoord& aCurrX) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: nscoord rightEdge = aCurrX + aRemainingWidth; michael@0: // Paint the twisty, but only if we are a non-empty container. michael@0: bool shouldPaint = false; michael@0: bool isContainer = false; michael@0: mView->IsContainer(aRowIndex, &isContainer); michael@0: if (isContainer) { michael@0: bool isContainerEmpty = false; michael@0: mView->IsContainerEmpty(aRowIndex, &isContainerEmpty); michael@0: if (!isContainerEmpty) michael@0: shouldPaint = true; michael@0: } michael@0: michael@0: // Resolve style for the twisty. michael@0: nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); michael@0: michael@0: // Obtain the margins for the twisty and then deflate our rect by that michael@0: // amount. The twisty is assumed to be contained within the deflated rect. michael@0: nsRect twistyRect(aTwistyRect); michael@0: nsMargin twistyMargin; michael@0: twistyContext->StyleMargin()->GetMargin(twistyMargin); michael@0: twistyRect.Deflate(twistyMargin); michael@0: michael@0: nsRect imageSize; michael@0: nsITheme* theme = GetTwistyRect(aRowIndex, aColumn, imageSize, twistyRect, michael@0: aPresContext, aRenderingContext, twistyContext); michael@0: michael@0: // Subtract out the remaining width. This is done even when we don't actually paint a twisty in michael@0: // this cell, so that cells in different rows still line up. michael@0: nsRect copyRect(twistyRect); michael@0: copyRect.Inflate(twistyMargin); michael@0: aRemainingWidth -= copyRect.width; michael@0: if (!isRTL) michael@0: aCurrX += copyRect.width; michael@0: michael@0: if (shouldPaint) { michael@0: // Paint our borders and background for our image rect. michael@0: PaintBackgroundLayer(twistyContext, aPresContext, aRenderingContext, twistyRect, aDirtyRect); michael@0: michael@0: if (theme) { michael@0: if (isRTL) michael@0: twistyRect.x = rightEdge - twistyRect.width; michael@0: // yeah, I know it says we're drawing a background, but a twisty is really a fg michael@0: // object since it doesn't have anything that gecko would want to draw over it. Besides, michael@0: // we have to prevent imagelib from drawing it. michael@0: nsRect dirty; michael@0: dirty.IntersectRect(twistyRect, aDirtyRect); michael@0: theme->DrawWidgetBackground(&aRenderingContext, this, michael@0: twistyContext->StyleDisplay()->mAppearance, twistyRect, dirty); michael@0: } michael@0: else { michael@0: // Time to paint the twisty. michael@0: // Adjust the rect for its border and padding. michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(twistyContext, bp); michael@0: twistyRect.Deflate(bp); michael@0: if (isRTL) michael@0: twistyRect.x = rightEdge - twistyRect.width; michael@0: imageSize.Deflate(bp); michael@0: michael@0: // Get the image for drawing. michael@0: nsCOMPtr image; michael@0: bool useImageRegion = true; michael@0: GetImage(aRowIndex, aColumn, true, twistyContext, useImageRegion, getter_AddRefs(image)); michael@0: if (image) { michael@0: nsPoint pt = twistyRect.TopLeft(); michael@0: michael@0: // Center the image. XXX Obey vertical-align style prop? michael@0: if (imageSize.height < twistyRect.height) { michael@0: pt.y += (twistyRect.height - imageSize.height)/2; michael@0: } michael@0: michael@0: // Paint the image. michael@0: nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image, michael@0: GraphicsFilter::FILTER_NEAREST, pt, &aDirtyRect, michael@0: imgIContainer::FLAG_NONE, &imageSize); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintImage(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: const nsRect& aImageRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nscoord& aRemainingWidth, michael@0: nscoord& aCurrX) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: nscoord rightEdge = aCurrX + aRemainingWidth; michael@0: // Resolve style for the image. michael@0: nsStyleContext* imageContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeimage); michael@0: michael@0: // Obtain opacity value for the image. michael@0: float opacity = imageContext->StyleDisplay()->mOpacity; michael@0: michael@0: // Obtain the margins for the image and then deflate our rect by that michael@0: // amount. The image is assumed to be contained within the deflated rect. michael@0: nsRect imageRect(aImageRect); michael@0: nsMargin imageMargin; michael@0: imageContext->StyleMargin()->GetMargin(imageMargin); michael@0: imageRect.Deflate(imageMargin); michael@0: michael@0: // Get the image. michael@0: bool useImageRegion = true; michael@0: nsCOMPtr image; michael@0: GetImage(aRowIndex, aColumn, false, imageContext, useImageRegion, getter_AddRefs(image)); michael@0: michael@0: // Get the image destination size. michael@0: nsSize imageDestSize = GetImageDestSize(imageContext, useImageRegion, image); michael@0: if (!imageDestSize.width || !imageDestSize.height) michael@0: return; michael@0: michael@0: // Get the borders and padding. michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(imageContext, bp); michael@0: michael@0: // destRect will be passed as the aDestRect argument in the DrawImage method. michael@0: // Start with the imageDestSize width and height. michael@0: nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height); michael@0: // Inflate destRect for borders and padding so that we can compare/adjust michael@0: // with respect to imageRect. michael@0: destRect.Inflate(bp); michael@0: michael@0: // The destRect width and height have not been adjusted to fit within the michael@0: // cell width and height. michael@0: // We must adjust the width even if image is null, because the width is used michael@0: // to update the aRemainingWidth and aCurrX values. michael@0: // Since the height isn't used unless the image is not null, we will adjust michael@0: // the height inside the if (image) block below. michael@0: michael@0: if (destRect.width > imageRect.width) { michael@0: // The destRect is too wide to fit within the cell width. michael@0: // Adjust destRect width to fit within the cell width. michael@0: destRect.width = imageRect.width; michael@0: } michael@0: else { michael@0: // The cell is wider than the destRect. michael@0: // In a cycler column, the image is centered horizontally. michael@0: if (!aColumn->IsCycler()) { michael@0: // If this column is not a cycler, we won't center the image horizontally. michael@0: // We adjust the imageRect width so that the image is placed at the start michael@0: // of the cell. michael@0: imageRect.width = destRect.width; michael@0: } michael@0: } michael@0: michael@0: if (image) { michael@0: if (isRTL) michael@0: imageRect.x = rightEdge - imageRect.width; michael@0: // Paint our borders and background for our image rect michael@0: PaintBackgroundLayer(imageContext, aPresContext, aRenderingContext, imageRect, aDirtyRect); michael@0: michael@0: // The destRect x and y have not been set yet. Let's do that now. michael@0: // Initially, we use the imageRect x and y. michael@0: destRect.x = imageRect.x; michael@0: destRect.y = imageRect.y; michael@0: michael@0: if (destRect.width < imageRect.width) { michael@0: // The destRect width is smaller than the cell width. michael@0: // Center the image horizontally in the cell. michael@0: // Adjust the destRect x accordingly. michael@0: destRect.x += (imageRect.width - destRect.width)/2; michael@0: } michael@0: michael@0: // Now it's time to adjust the destRect height to fit within the cell height. michael@0: if (destRect.height > imageRect.height) { michael@0: // The destRect height is larger than the cell height. michael@0: // Adjust destRect height to fit within the cell height. michael@0: destRect.height = imageRect.height; michael@0: } michael@0: else if (destRect.height < imageRect.height) { michael@0: // The destRect height is smaller than the cell height. michael@0: // Center the image vertically in the cell. michael@0: // Adjust the destRect y accordingly. michael@0: destRect.y += (imageRect.height - destRect.height)/2; michael@0: } michael@0: michael@0: // It's almost time to paint the image. michael@0: // Deflate destRect for the border and padding. michael@0: destRect.Deflate(bp); michael@0: michael@0: // Get the image source rectangle - the rectangle containing the part of michael@0: // the image that we are going to display. michael@0: // sourceRect will be passed as the aSrcRect argument in the DrawImage method. michael@0: nsRect sourceRect = GetImageSourceRect(imageContext, useImageRegion, image); michael@0: michael@0: // Let's say that the image is 100 pixels tall and michael@0: // that the CSS has specified that the destination height should be 50 michael@0: // pixels tall. Let's say that the cell height is only 20 pixels. So, in michael@0: // those 20 visible pixels, we want to see the top 20/50ths of the image. michael@0: // So, the sourceRect.height should be 100 * 20 / 50, which is 40 pixels. michael@0: // Essentially, we are scaling the image as dictated by the CSS destination michael@0: // height and width, and we are then clipping the scaled image by the cell michael@0: // width and height. michael@0: nsIntSize rawImageSize; michael@0: image->GetWidth(&rawImageSize.width); michael@0: image->GetHeight(&rawImageSize.height); michael@0: nsRect wholeImageDest = michael@0: nsLayoutUtils::GetWholeImageDestination(rawImageSize, sourceRect, michael@0: nsRect(destRect.TopLeft(), imageDestSize)); michael@0: michael@0: gfxContext* ctx = aRenderingContext.ThebesContext(); michael@0: if (opacity != 1.0f) { michael@0: ctx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: } michael@0: michael@0: nsLayoutUtils::DrawImage(&aRenderingContext, image, michael@0: nsLayoutUtils::GetGraphicsFilterForFrame(this), michael@0: wholeImageDest, destRect, destRect.TopLeft(), aDirtyRect, michael@0: imgIContainer::FLAG_NONE); michael@0: michael@0: if (opacity != 1.0f) { michael@0: ctx->PopGroupToSource(); michael@0: ctx->Paint(opacity); michael@0: } michael@0: } michael@0: michael@0: // Update the aRemainingWidth and aCurrX values. michael@0: imageRect.Inflate(imageMargin); michael@0: aRemainingWidth -= imageRect.width; michael@0: if (!isRTL) michael@0: aCurrX += imageRect.width; michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintText(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: const nsRect& aTextRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nscoord& aCurrX) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: bool isRTL = StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; michael@0: michael@0: // Now obtain the text for our cell. michael@0: nsAutoString text; michael@0: mView->GetCellText(aRowIndex, aColumn, text); michael@0: // We're going to paint this text so we need to ensure bidi is enabled if michael@0: // necessary michael@0: CheckTextForBidi(text); michael@0: michael@0: if (text.Length() == 0) michael@0: return; // Don't paint an empty string. XXX What about background/borders? Still paint? michael@0: michael@0: // Resolve style for the text. It contains all the info we need to lay ourselves michael@0: // out and to paint. michael@0: nsStyleContext* textContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecelltext); michael@0: michael@0: // Obtain opacity value for the image. michael@0: float opacity = textContext->StyleDisplay()->mOpacity; michael@0: michael@0: // Obtain the margins for the text and then deflate our rect by that michael@0: // amount. The text is assumed to be contained within the deflated rect. michael@0: nsRect textRect(aTextRect); michael@0: nsMargin textMargin; michael@0: textContext->StyleMargin()->GetMargin(textMargin); michael@0: textRect.Deflate(textMargin); michael@0: michael@0: // Adjust the rect for its border and padding. michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(textContext, bp); michael@0: textRect.Deflate(bp); michael@0: michael@0: // Compute our text size. michael@0: nsRefPtr fontMet; michael@0: nsLayoutUtils::GetFontMetricsForStyleContext(textContext, michael@0: getter_AddRefs(fontMet)); michael@0: michael@0: nscoord height = fontMet->MaxHeight(); michael@0: nscoord baseline = fontMet->MaxAscent(); michael@0: michael@0: // Center the text. XXX Obey vertical-align style prop? michael@0: if (height < textRect.height) { michael@0: textRect.y += (textRect.height - height)/2; michael@0: textRect.height = height; michael@0: } michael@0: michael@0: // Set our font. michael@0: aRenderingContext.SetFont(fontMet); michael@0: michael@0: AdjustForCellText(text, aRowIndex, aColumn, aRenderingContext, textRect); michael@0: textRect.Inflate(bp); michael@0: michael@0: // Subtract out the remaining width. michael@0: if (!isRTL) michael@0: aCurrX += textRect.width + textMargin.LeftRight(); michael@0: michael@0: PaintBackgroundLayer(textContext, aPresContext, aRenderingContext, textRect, aDirtyRect); michael@0: michael@0: // Time to paint our text. michael@0: textRect.Deflate(bp); michael@0: michael@0: // Set our color. michael@0: aRenderingContext.SetColor(textContext->StyleColor()->mColor); michael@0: michael@0: // Draw decorations. michael@0: uint8_t decorations = textContext->StyleTextReset()->mTextDecorationLine; michael@0: michael@0: nscoord offset; michael@0: nscoord size; michael@0: if (decorations & (NS_FONT_DECORATION_OVERLINE | NS_FONT_DECORATION_UNDERLINE)) { michael@0: fontMet->GetUnderline(offset, size); michael@0: if (decorations & NS_FONT_DECORATION_OVERLINE) michael@0: aRenderingContext.FillRect(textRect.x, textRect.y, textRect.width, size); michael@0: if (decorations & NS_FONT_DECORATION_UNDERLINE) michael@0: aRenderingContext.FillRect(textRect.x, textRect.y + baseline - offset, textRect.width, size); michael@0: } michael@0: if (decorations & NS_FONT_DECORATION_LINE_THROUGH) { michael@0: fontMet->GetStrikeout(offset, size); michael@0: aRenderingContext.FillRect(textRect.x, textRect.y + baseline - offset, textRect.width, size); michael@0: } michael@0: nsStyleContext* cellContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecell); michael@0: michael@0: gfxContext* ctx = aRenderingContext.ThebesContext(); michael@0: if (opacity != 1.0f) { michael@0: ctx->PushGroup(gfxContentType::COLOR_ALPHA); michael@0: } michael@0: michael@0: nsLayoutUtils::DrawString(this, &aRenderingContext, text.get(), text.Length(), michael@0: textRect.TopLeft() + nsPoint(0, baseline), cellContext); michael@0: michael@0: if (opacity != 1.0f) { michael@0: ctx->PopGroupToSource(); michael@0: ctx->Paint(opacity); michael@0: } michael@0: michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintCheckbox(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: const nsRect& aCheckboxRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: // Resolve style for the checkbox. michael@0: nsStyleContext* checkboxContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreecheckbox); michael@0: michael@0: nscoord rightEdge = aCheckboxRect.XMost(); michael@0: michael@0: // Obtain the margins for the checkbox and then deflate our rect by that michael@0: // amount. The checkbox is assumed to be contained within the deflated rect. michael@0: nsRect checkboxRect(aCheckboxRect); michael@0: nsMargin checkboxMargin; michael@0: checkboxContext->StyleMargin()->GetMargin(checkboxMargin); michael@0: checkboxRect.Deflate(checkboxMargin); michael@0: michael@0: nsRect imageSize = GetImageSize(aRowIndex, aColumn, true, checkboxContext); michael@0: michael@0: if (imageSize.height > checkboxRect.height) michael@0: imageSize.height = checkboxRect.height; michael@0: if (imageSize.width > checkboxRect.width) michael@0: imageSize.width = checkboxRect.width; michael@0: michael@0: if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) michael@0: checkboxRect.x = rightEdge - checkboxRect.width; michael@0: michael@0: // Paint our borders and background for our image rect. michael@0: PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext, checkboxRect, aDirtyRect); michael@0: michael@0: // Time to paint the checkbox. michael@0: // Adjust the rect for its border and padding. michael@0: nsMargin bp(0,0,0,0); michael@0: GetBorderPadding(checkboxContext, bp); michael@0: checkboxRect.Deflate(bp); michael@0: michael@0: // Get the image for drawing. michael@0: nsCOMPtr image; michael@0: bool useImageRegion = true; michael@0: GetImage(aRowIndex, aColumn, true, checkboxContext, useImageRegion, getter_AddRefs(image)); michael@0: if (image) { michael@0: nsPoint pt = checkboxRect.TopLeft(); michael@0: michael@0: if (imageSize.height < checkboxRect.height) { michael@0: pt.y += (checkboxRect.height - imageSize.height)/2; michael@0: } michael@0: michael@0: if (imageSize.width < checkboxRect.width) { michael@0: pt.x += (checkboxRect.width - imageSize.width)/2; michael@0: } michael@0: michael@0: // Paint the image. michael@0: nsLayoutUtils::DrawSingleUnscaledImage(&aRenderingContext, image, michael@0: GraphicsFilter::FILTER_NEAREST, pt, &aDirtyRect, michael@0: imgIContainer::FLAG_NONE, &imageSize); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintProgressMeter(int32_t aRowIndex, michael@0: nsTreeColumn* aColumn, michael@0: const nsRect& aProgressMeterRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: NS_PRECONDITION(aColumn && aColumn->GetFrame(), "invalid column passed"); michael@0: michael@0: // Resolve style for the progress meter. It contains all the info we need michael@0: // to lay ourselves out and to paint. michael@0: nsStyleContext* meterContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreeprogressmeter); michael@0: michael@0: // Obtain the margins for the progress meter and then deflate our rect by that michael@0: // amount. The progress meter is assumed to be contained within the deflated michael@0: // rect. michael@0: nsRect meterRect(aProgressMeterRect); michael@0: nsMargin meterMargin; michael@0: meterContext->StyleMargin()->GetMargin(meterMargin); michael@0: meterRect.Deflate(meterMargin); michael@0: michael@0: // Paint our borders and background for our progress meter rect. michael@0: PaintBackgroundLayer(meterContext, aPresContext, aRenderingContext, meterRect, aDirtyRect); michael@0: michael@0: // Time to paint our progress. michael@0: int32_t state; michael@0: mView->GetProgressMode(aRowIndex, aColumn, &state); michael@0: if (state == nsITreeView::PROGRESS_NORMAL) { michael@0: // Adjust the rect for its border and padding. michael@0: AdjustForBorderPadding(meterContext, meterRect); michael@0: michael@0: // Set our color. michael@0: aRenderingContext.SetColor(meterContext->StyleColor()->mColor); michael@0: michael@0: // Now obtain the value for our cell. michael@0: nsAutoString value; michael@0: mView->GetCellValue(aRowIndex, aColumn, value); michael@0: michael@0: nsresult rv; michael@0: int32_t intValue = value.ToInteger(&rv); michael@0: if (intValue < 0) michael@0: intValue = 0; michael@0: else if (intValue > 100) michael@0: intValue = 100; michael@0: michael@0: nscoord meterWidth = NSToCoordRound((float)intValue / 100 * meterRect.width); michael@0: if (StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) michael@0: meterRect.x += meterRect.width - meterWidth; // right align michael@0: meterRect.width = meterWidth; michael@0: bool useImageRegion = true; michael@0: nsCOMPtr image; michael@0: GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image)); michael@0: if (image) { michael@0: int32_t width, height; michael@0: image->GetWidth(&width); michael@0: image->GetHeight(&height); michael@0: nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(), michael@0: height*nsDeviceContext::AppUnitsPerCSSPixel()); michael@0: nsLayoutUtils::DrawImage(&aRenderingContext, image, michael@0: nsLayoutUtils::GetGraphicsFilterForFrame(this), michael@0: nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), michael@0: aDirtyRect, imgIContainer::FLAG_NONE); michael@0: } else { michael@0: aRenderingContext.FillRect(meterRect); michael@0: } michael@0: } michael@0: else if (state == nsITreeView::PROGRESS_UNDETERMINED) { michael@0: // Adjust the rect for its border and padding. michael@0: AdjustForBorderPadding(meterContext, meterRect); michael@0: michael@0: bool useImageRegion = true; michael@0: nsCOMPtr image; michael@0: GetImage(aRowIndex, aColumn, true, meterContext, useImageRegion, getter_AddRefs(image)); michael@0: if (image) { michael@0: int32_t width, height; michael@0: image->GetWidth(&width); michael@0: image->GetHeight(&height); michael@0: nsSize size(width*nsDeviceContext::AppUnitsPerCSSPixel(), michael@0: height*nsDeviceContext::AppUnitsPerCSSPixel()); michael@0: nsLayoutUtils::DrawImage(&aRenderingContext, image, michael@0: nsLayoutUtils::GetGraphicsFilterForFrame(this), michael@0: nsRect(meterRect.TopLeft(), size), meterRect, meterRect.TopLeft(), michael@0: aDirtyRect, imgIContainer::FLAG_NONE); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintDropFeedback(const nsRect& aDropFeedbackRect, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nsPoint aPt) michael@0: { michael@0: // Paint the drop feedback in between rows. michael@0: michael@0: nscoord currX; michael@0: michael@0: // Adjust for the primary cell. michael@0: nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn(); michael@0: michael@0: if (primaryCol) { michael@0: #ifdef DEBUG michael@0: nsresult rv = michael@0: #endif michael@0: primaryCol->GetXInTwips(this, &currX); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "primary column is invalid?"); michael@0: michael@0: currX += aPt.x - mHorzPosition; michael@0: } else { michael@0: currX = aDropFeedbackRect.x; michael@0: } michael@0: michael@0: PrefillPropertyArray(mSlots->mDropRow, primaryCol); michael@0: michael@0: // Resolve the style to use for the drop feedback. michael@0: nsStyleContext* feedbackContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreedropfeedback); michael@0: michael@0: // Paint only if it is visible. michael@0: if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) { michael@0: int32_t level; michael@0: mView->GetLevel(mSlots->mDropRow, &level); michael@0: michael@0: // If our previous or next row has greater level use that for michael@0: // correct visual indentation. michael@0: if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) { michael@0: if (mSlots->mDropRow > 0) { michael@0: int32_t previousLevel; michael@0: mView->GetLevel(mSlots->mDropRow - 1, &previousLevel); michael@0: if (previousLevel > level) michael@0: level = previousLevel; michael@0: } michael@0: } michael@0: else { michael@0: if (mSlots->mDropRow < mRowCount - 1) { michael@0: int32_t nextLevel; michael@0: mView->GetLevel(mSlots->mDropRow + 1, &nextLevel); michael@0: if (nextLevel > level) michael@0: level = nextLevel; michael@0: } michael@0: } michael@0: michael@0: currX += mIndentation * level; michael@0: michael@0: if (primaryCol){ michael@0: nsStyleContext* twistyContext = GetPseudoStyleContext(nsCSSAnonBoxes::moztreetwisty); michael@0: nsRect imageSize; michael@0: nsRect twistyRect; michael@0: GetTwistyRect(mSlots->mDropRow, primaryCol, imageSize, twistyRect, aPresContext, michael@0: aRenderingContext, twistyContext); michael@0: nsMargin twistyMargin; michael@0: twistyContext->StyleMargin()->GetMargin(twistyMargin); michael@0: twistyRect.Inflate(twistyMargin); michael@0: currX += twistyRect.width; michael@0: } michael@0: michael@0: const nsStylePosition* stylePosition = feedbackContext->StylePosition(); michael@0: michael@0: // Obtain the width for the drop feedback or use default value. michael@0: nscoord width; michael@0: if (stylePosition->mWidth.GetUnit() == eStyleUnit_Coord) michael@0: width = stylePosition->mWidth.GetCoordValue(); michael@0: else { michael@0: // Use default width 50px. michael@0: width = nsPresContext::CSSPixelsToAppUnits(50); michael@0: } michael@0: michael@0: // Obtain the height for the drop feedback or use default value. michael@0: nscoord height; michael@0: if (stylePosition->mHeight.GetUnit() == eStyleUnit_Coord) michael@0: height = stylePosition->mHeight.GetCoordValue(); michael@0: else { michael@0: // Use default height 2px. michael@0: height = nsPresContext::CSSPixelsToAppUnits(2); michael@0: } michael@0: michael@0: // Obtain the margins for the drop feedback and then deflate our rect michael@0: // by that amount. michael@0: nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height); michael@0: nsMargin margin; michael@0: feedbackContext->StyleMargin()->GetMargin(margin); michael@0: feedbackRect.Deflate(margin); michael@0: michael@0: feedbackRect.y += (aDropFeedbackRect.height - height) / 2; michael@0: michael@0: // Finally paint the drop feedback. michael@0: PaintBackgroundLayer(feedbackContext, aPresContext, aRenderingContext, feedbackRect, aDirtyRect); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PaintBackgroundLayer(nsStyleContext* aStyleContext, michael@0: nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsRect& aRect, michael@0: const nsRect& aDirtyRect) michael@0: { michael@0: const nsStyleBorder* myBorder = aStyleContext->StyleBorder(); michael@0: michael@0: nsCSSRendering::PaintBackgroundWithSC(aPresContext, aRenderingContext, michael@0: this, aDirtyRect, aRect, michael@0: aStyleContext, *myBorder, michael@0: nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES); michael@0: michael@0: nsCSSRendering::PaintBorderWithStyleBorder(aPresContext, aRenderingContext, michael@0: this, aDirtyRect, aRect, michael@0: *myBorder, mStyleContext); michael@0: michael@0: nsCSSRendering::PaintOutline(aPresContext, aRenderingContext, this, michael@0: aDirtyRect, aRect, aStyleContext); michael@0: } michael@0: michael@0: // Scrolling michael@0: nsresult michael@0: nsTreeBodyFrame::EnsureRowIsVisible(int32_t aRow) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: nsresult rv = EnsureRowIsVisibleInternal(parts, aRow); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: UpdateScrollbars(parts); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsTreeBodyFrame::EnsureRowIsVisibleInternal(const ScrollParts& aParts, int32_t aRow) michael@0: { michael@0: if (!mView || !mPageLength) michael@0: return NS_OK; michael@0: michael@0: if (mTopRowIndex <= aRow && mTopRowIndex+mPageLength > aRow) michael@0: return NS_OK; michael@0: michael@0: if (aRow < mTopRowIndex) michael@0: ScrollToRowInternal(aParts, aRow); michael@0: else { michael@0: // Bring it just on-screen. michael@0: int32_t distance = aRow - (mTopRowIndex+mPageLength)+1; michael@0: ScrollToRowInternal(aParts, mTopRowIndex+distance); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::EnsureCellIsVisible(int32_t aRow, nsITreeColumn* aCol) michael@0: { michael@0: nsRefPtr col = GetColumnImpl(aCol); michael@0: if (!col) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: ScrollParts parts = GetScrollParts(); michael@0: michael@0: nscoord result = -1; michael@0: nsresult rv; michael@0: michael@0: nscoord columnPos; michael@0: rv = col->GetXInTwips(this, &columnPos); michael@0: if(NS_FAILED(rv)) return rv; michael@0: michael@0: nscoord columnWidth; michael@0: rv = col->GetWidthInTwips(this, &columnWidth); michael@0: if(NS_FAILED(rv)) return rv; michael@0: michael@0: // If the start of the column is before the michael@0: // start of the horizontal view, then scroll michael@0: if (columnPos < mHorzPosition) michael@0: result = columnPos; michael@0: // If the end of the column is past the end of michael@0: // the horizontal view, then scroll michael@0: else if ((columnPos + columnWidth) > (mHorzPosition + mInnerBox.width)) michael@0: result = ((columnPos + columnWidth) - (mHorzPosition + mInnerBox.width)) + mHorzPosition; michael@0: michael@0: if (result != -1) { michael@0: rv = ScrollHorzInternal(parts, result); michael@0: if(NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: rv = EnsureRowIsVisibleInternal(parts, aRow); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: UpdateScrollbars(parts); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollToCell(int32_t aRow, nsITreeColumn* aCol) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: nsresult rv = ScrollToRowInternal(parts, aRow); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ScrollToColumnInternal(parts, aCol); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: UpdateScrollbars(parts); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollToColumn(nsITreeColumn* aCol) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: nsresult rv = ScrollToColumnInternal(parts, aCol); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: UpdateScrollbars(parts); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsTreeBodyFrame::ScrollToColumnInternal(const ScrollParts& aParts, michael@0: nsITreeColumn* aCol) michael@0: { michael@0: nsRefPtr col = GetColumnImpl(aCol); michael@0: if (!col) michael@0: return NS_ERROR_INVALID_ARG; michael@0: michael@0: nscoord x; michael@0: nsresult rv = col->GetXInTwips(this, &x); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: return ScrollHorzInternal(aParts, x); michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollToHorizontalPosition(int32_t aHorizontalPosition) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: int32_t position = nsPresContext::CSSPixelsToAppUnits(aHorizontalPosition); michael@0: nsresult rv = ScrollHorzInternal(parts, position); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: UpdateScrollbars(parts); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollToRow(int32_t aRow) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: nsresult rv = ScrollToRowInternal(parts, aRow); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: UpdateScrollbars(parts); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsTreeBodyFrame::ScrollToRowInternal(const ScrollParts& aParts, int32_t aRow) michael@0: { michael@0: ScrollInternal(aParts, aRow); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollByLines(int32_t aNumLines) michael@0: { michael@0: if (!mView) michael@0: return NS_OK; michael@0: michael@0: int32_t newIndex = mTopRowIndex + aNumLines; michael@0: if (newIndex < 0) michael@0: newIndex = 0; michael@0: else { michael@0: int32_t lastPageTopRow = mRowCount - mPageLength; michael@0: if (newIndex > lastPageTopRow) michael@0: newIndex = lastPageTopRow; michael@0: } michael@0: ScrollToRow(newIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollByPages(int32_t aNumPages) michael@0: { michael@0: if (!mView) michael@0: return NS_OK; michael@0: michael@0: int32_t newIndex = mTopRowIndex + aNumPages * mPageLength; michael@0: if (newIndex < 0) michael@0: newIndex = 0; michael@0: else { michael@0: int32_t lastPageTopRow = mRowCount - mPageLength; michael@0: if (newIndex > lastPageTopRow) michael@0: newIndex = lastPageTopRow; michael@0: } michael@0: ScrollToRow(newIndex); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollInternal(const ScrollParts& aParts, int32_t aRow) michael@0: { michael@0: if (!mView) michael@0: return NS_OK; michael@0: michael@0: int32_t delta = aRow - mTopRowIndex; michael@0: michael@0: if (delta > 0) { michael@0: if (mTopRowIndex == (mRowCount - mPageLength + 1)) michael@0: return NS_OK; michael@0: } michael@0: else { michael@0: if (mTopRowIndex == 0) michael@0: return NS_OK; michael@0: } michael@0: michael@0: mTopRowIndex += delta; michael@0: michael@0: Invalidate(); michael@0: michael@0: PostScrollEvent(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ScrollHorzInternal(const ScrollParts& aParts, int32_t aPosition) michael@0: { michael@0: if (!mView || !aParts.mColumnsScrollFrame || !aParts.mHScrollbar) michael@0: return NS_OK; michael@0: michael@0: if (aPosition == mHorzPosition) michael@0: return NS_OK; michael@0: michael@0: if (aPosition < 0 || aPosition > mHorzWidth) michael@0: return NS_OK; michael@0: michael@0: nsRect bounds = aParts.mColumnsFrame->GetRect(); michael@0: if (aPosition > (mHorzWidth - bounds.width)) michael@0: aPosition = mHorzWidth - bounds.width; michael@0: michael@0: mHorzPosition = aPosition; michael@0: michael@0: Invalidate(); michael@0: michael@0: // Update the column scroll view michael@0: nsWeakFrame weakFrame(this); michael@0: aParts.mColumnsScrollFrame->ScrollTo(nsPoint(mHorzPosition, 0), michael@0: nsIScrollableFrame::INSTANT); michael@0: if (!weakFrame.IsAlive()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: // And fire off an event about it all michael@0: PostScrollEvent(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeBodyFrame::ScrollbarButtonPressed(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t aNewIndex) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: michael@0: if (aScrollbar == parts.mVScrollbar) { michael@0: if (aNewIndex > aOldIndex) michael@0: ScrollToRowInternal(parts, mTopRowIndex+1); michael@0: else if (aNewIndex < aOldIndex) michael@0: ScrollToRowInternal(parts, mTopRowIndex-1); michael@0: } else { michael@0: nsresult rv = ScrollHorzInternal(parts, aNewIndex); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: UpdateScrollbars(parts); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeBodyFrame::PositionChanged(nsScrollbarFrame* aScrollbar, int32_t aOldIndex, int32_t& aNewIndex) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: michael@0: if (aOldIndex == aNewIndex) michael@0: return NS_OK; michael@0: michael@0: // Vertical Scrollbar michael@0: if (parts.mVScrollbar == aScrollbar) { michael@0: nscoord rh = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); michael@0: michael@0: nscoord newrow = aNewIndex/rh; michael@0: ScrollInternal(parts, newrow); michael@0: // Horizontal Scrollbar michael@0: } else if (parts.mHScrollbar == aScrollbar) { michael@0: nsresult rv = ScrollHorzInternal(parts, aNewIndex); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: UpdateScrollbars(parts); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // The style cache. michael@0: nsStyleContext* michael@0: nsTreeBodyFrame::GetPseudoStyleContext(nsIAtom* aPseudoElement) michael@0: { michael@0: return mStyleCache.GetStyleContext(this, PresContext(), mContent, michael@0: mStyleContext, aPseudoElement, michael@0: mScratchArray); michael@0: } michael@0: michael@0: // Our comparator for resolving our complex pseudos michael@0: bool michael@0: nsTreeBodyFrame::PseudoMatches(nsCSSSelector* aSelector) michael@0: { michael@0: // Iterate the class list. For each item in the list, see if michael@0: // it is contained in our scratch array. If we have a miss, then michael@0: // we aren't a match. If all items in the class list are michael@0: // present in the scratch array, then we have a match. michael@0: nsAtomList* curr = aSelector->mClassList; michael@0: while (curr) { michael@0: if (!mScratchArray.Contains(curr->mAtom)) michael@0: return false; michael@0: curr = curr->mNext; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: nsIContent* michael@0: nsTreeBodyFrame::GetBaseElement() michael@0: { michael@0: nsIFrame* parent = GetParent(); michael@0: while (parent) { michael@0: nsIContent* content = parent->GetContent(); michael@0: if (content) { michael@0: nsINodeInfo* ni = content->NodeInfo(); michael@0: michael@0: if (ni->Equals(nsGkAtoms::tree, kNameSpaceID_XUL) || michael@0: (ni->Equals(nsGkAtoms::select) && michael@0: content->IsHTML())) michael@0: return content; michael@0: } michael@0: michael@0: parent = parent->GetParent(); michael@0: } michael@0: michael@0: return nullptr; michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::ClearStyleAndImageCaches() michael@0: { michael@0: mStyleCache.Clear(); michael@0: mImageCache.EnumerateRead(CancelImageRequest, this); michael@0: mImageCache.Clear(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsTreeBodyFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) michael@0: { michael@0: nsLeafBoxFrame::DidSetStyleContext(aOldStyleContext); michael@0: michael@0: // Clear the style cache; the pointers are no longer even valid michael@0: mStyleCache.Clear(); michael@0: // XXX The following is hacky, but it's not incorrect, michael@0: // and appears to fix a few bugs with style changes, like text zoom and michael@0: // dpi changes michael@0: mIndentation = GetIndentation(); michael@0: mRowHeight = GetRowHeight(); michael@0: mStringWidth = -1; michael@0: } michael@0: michael@0: bool michael@0: nsTreeBodyFrame::OffsetForHorzScroll(nsRect& rect, bool clip) michael@0: { michael@0: rect.x -= mHorzPosition; michael@0: michael@0: // Scrolled out before michael@0: if (rect.XMost() <= mInnerBox.x) michael@0: return false; michael@0: michael@0: // Scrolled out after michael@0: if (rect.x > mInnerBox.XMost()) michael@0: return false; michael@0: michael@0: if (clip) { michael@0: nscoord leftEdge = std::max(rect.x, mInnerBox.x); michael@0: nscoord rightEdge = std::min(rect.XMost(), mInnerBox.XMost()); michael@0: rect.x = leftEdge; michael@0: rect.width = rightEdge - leftEdge; michael@0: michael@0: // Should have returned false above michael@0: NS_ASSERTION(rect.width >= 0, "horz scroll code out of sync"); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsTreeBodyFrame::CanAutoScroll(int32_t aRowIndex) michael@0: { michael@0: // Check first for partially visible last row. michael@0: if (aRowIndex == mRowCount - 1) { michael@0: nscoord y = mInnerBox.y + (aRowIndex - mTopRowIndex) * mRowHeight; michael@0: if (y < mInnerBox.height && y + mRowHeight > mInnerBox.height) michael@0: return true; michael@0: } michael@0: michael@0: if (aRowIndex > 0 && aRowIndex < mRowCount - 1) michael@0: return true; michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Given a dom event, figure out which row in the tree the mouse is over, michael@0: // if we should drop before/after/on that row or we should auto-scroll. michael@0: // Doesn't query the content about if the drag is allowable, that's done elsewhere. michael@0: // michael@0: // For containers, we break up the vertical space of the row as follows: if in michael@0: // the topmost 25%, the drop is _before_ the row the mouse is over; if in the michael@0: // last 25%, _after_; in the middle 50%, we consider it a drop _on_ the container. michael@0: // michael@0: // For non-containers, if the mouse is in the top 50% of the row, the drop is michael@0: // _before_ and the bottom 50% _after_ michael@0: void michael@0: nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent, michael@0: int32_t* aRow, michael@0: int16_t* aOrient, michael@0: int16_t* aScrollLines) michael@0: { michael@0: *aOrient = -1; michael@0: *aScrollLines = 0; michael@0: michael@0: // Convert the event's point to our coordinates. We want it in michael@0: // the coordinates of our inner box's coordinates. michael@0: nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, this); michael@0: int32_t xTwips = pt.x - mInnerBox.x; michael@0: int32_t yTwips = pt.y - mInnerBox.y; michael@0: michael@0: *aRow = GetRowAt(xTwips, yTwips); michael@0: if (*aRow >=0) { michael@0: // Compute the top/bottom of the row in question. michael@0: int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex); michael@0: michael@0: bool isContainer = false; michael@0: mView->IsContainer (*aRow, &isContainer); michael@0: if (isContainer) { michael@0: // for a container, use a 25%/50%/25% breakdown michael@0: if (yOffset < mRowHeight / 4) michael@0: *aOrient = nsITreeView::DROP_BEFORE; michael@0: else if (yOffset > mRowHeight - (mRowHeight / 4)) michael@0: *aOrient = nsITreeView::DROP_AFTER; michael@0: else michael@0: *aOrient = nsITreeView::DROP_ON; michael@0: } michael@0: else { michael@0: // for a non-container use a 50%/50% breakdown michael@0: if (yOffset < mRowHeight / 2) michael@0: *aOrient = nsITreeView::DROP_BEFORE; michael@0: else michael@0: *aOrient = nsITreeView::DROP_AFTER; michael@0: } michael@0: } michael@0: michael@0: if (CanAutoScroll(*aRow)) { michael@0: // Get the max value from the look and feel service. michael@0: int32_t scrollLinesMax = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_TreeScrollLinesMax, 0); michael@0: scrollLinesMax--; michael@0: if (scrollLinesMax < 0) michael@0: scrollLinesMax = 0; michael@0: michael@0: // Determine if we're w/in a margin of the top/bottom of the tree during a drag. michael@0: // This will ultimately cause us to scroll, but that's done elsewhere. michael@0: nscoord height = (3 * mRowHeight) / 4; michael@0: if (yTwips < height) { michael@0: // scroll up michael@0: *aScrollLines = NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1); michael@0: } michael@0: else if (yTwips > mRect.height - height) { michael@0: // scroll down michael@0: *aScrollLines = NSToIntRound(scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1); michael@0: } michael@0: } michael@0: } // ComputeDropPosition michael@0: michael@0: void michael@0: nsTreeBodyFrame::OpenCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsTreeBodyFrame* self = static_cast(aClosure); michael@0: if (self) { michael@0: aTimer->Cancel(); michael@0: self->mSlots->mTimer = nullptr; michael@0: michael@0: if (self->mSlots->mDropRow >= 0) { michael@0: self->mSlots->mArray.AppendElement(self->mSlots->mDropRow); michael@0: self->mView->ToggleOpenState(self->mSlots->mDropRow); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::CloseCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsTreeBodyFrame* self = static_cast(aClosure); michael@0: if (self) { michael@0: aTimer->Cancel(); michael@0: self->mSlots->mTimer = nullptr; michael@0: michael@0: for (uint32_t i = self->mSlots->mArray.Length(); i--; ) { michael@0: if (self->mView) michael@0: self->mView->ToggleOpenState(self->mSlots->mArray[i]); michael@0: } michael@0: self->mSlots->mArray.Clear(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::LazyScrollCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsTreeBodyFrame* self = static_cast(aClosure); michael@0: if (self) { michael@0: aTimer->Cancel(); michael@0: self->mSlots->mTimer = nullptr; michael@0: michael@0: if (self->mView) { michael@0: // Set a new timer to scroll the tree repeatedly. michael@0: self->CreateTimer(LookAndFeel::eIntID_TreeScrollDelay, michael@0: ScrollCallback, nsITimer::TYPE_REPEATING_SLACK, michael@0: getter_AddRefs(self->mSlots->mTimer)); michael@0: self->ScrollByLines(self->mSlots->mScrollLines); michael@0: // ScrollByLines may have deleted |self|. michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::ScrollCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsTreeBodyFrame* self = static_cast(aClosure); michael@0: if (self) { michael@0: // Don't scroll if we are already at the top or bottom of the view. michael@0: if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) { michael@0: self->ScrollByLines(self->mSlots->mScrollLines); michael@0: } michael@0: else { michael@0: aTimer->Cancel(); michael@0: self->mSlots->mTimer = nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTreeBodyFrame::ScrollEvent::Run() michael@0: { michael@0: if (mInner) { michael@0: mInner->FireScrollEvent(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsTreeBodyFrame::FireScrollEvent() michael@0: { michael@0: mScrollEvent.Forget(); michael@0: WidgetGUIEvent event(true, NS_SCROLL_EVENT, nullptr); michael@0: // scroll events fired at elements don't bubble michael@0: event.mFlags.mBubbles = false; michael@0: EventDispatcher::Dispatch(GetContent(), PresContext(), &event); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::PostScrollEvent() michael@0: { michael@0: if (mScrollEvent.IsPending()) michael@0: return; michael@0: michael@0: nsRefPtr ev = new ScrollEvent(this); michael@0: if (NS_FAILED(NS_DispatchToCurrentThread(ev))) { michael@0: NS_WARNING("failed to dispatch ScrollEvent"); michael@0: } else { michael@0: mScrollEvent = ev; michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::ScrollbarActivityStarted() const michael@0: { michael@0: if (mScrollbarActivity) { michael@0: mScrollbarActivity->ActivityStarted(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::ScrollbarActivityStopped() const michael@0: { michael@0: if (mScrollbarActivity) { michael@0: mScrollbarActivity->ActivityStopped(); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::DetachImageListeners() michael@0: { michael@0: mCreatedListeners.Clear(); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::RemoveTreeImageListener(nsTreeImageListener* aListener) michael@0: { michael@0: if (aListener) { michael@0: mCreatedListeners.RemoveEntry(aListener); michael@0: } michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: void michael@0: nsTreeBodyFrame::FireRowCountChangedEvent(int32_t aIndex, int32_t aCount) michael@0: { michael@0: nsCOMPtr content(GetBaseElement()); michael@0: if (!content) michael@0: return; michael@0: michael@0: nsCOMPtr domDoc = do_QueryInterface(content->OwnerDoc()); michael@0: if (!domDoc) michael@0: return; michael@0: michael@0: nsCOMPtr event; michael@0: domDoc->CreateEvent(NS_LITERAL_STRING("customevent"), michael@0: getter_AddRefs(event)); michael@0: michael@0: nsCOMPtr treeEvent(do_QueryInterface(event)); michael@0: if (!treeEvent) michael@0: return; michael@0: michael@0: nsCOMPtr propBag( michael@0: do_CreateInstance("@mozilla.org/hash-property-bag;1")); michael@0: if (!propBag) michael@0: return; michael@0: michael@0: // Set 'index' data - the row index rows are changed from. michael@0: propBag->SetPropertyAsInt32(NS_LITERAL_STRING("index"), aIndex); michael@0: michael@0: // Set 'count' data - the number of changed rows. michael@0: propBag->SetPropertyAsInt32(NS_LITERAL_STRING("count"), aCount); michael@0: michael@0: nsCOMPtr detailVariant( michael@0: do_CreateInstance("@mozilla.org/variant;1")); michael@0: if (!detailVariant) michael@0: return; michael@0: michael@0: detailVariant->SetAsISupports(propBag); michael@0: treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeRowCountChanged"), michael@0: true, false, detailVariant); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: nsRefPtr asyncDispatcher = michael@0: new AsyncEventDispatcher(content, event); michael@0: asyncDispatcher->PostDOMEvent(); michael@0: } michael@0: michael@0: void michael@0: nsTreeBodyFrame::FireInvalidateEvent(int32_t aStartRowIdx, int32_t aEndRowIdx, michael@0: nsITreeColumn *aStartCol, michael@0: nsITreeColumn *aEndCol) michael@0: { michael@0: nsCOMPtr content(GetBaseElement()); michael@0: if (!content) michael@0: return; michael@0: michael@0: nsCOMPtr domDoc = do_QueryInterface(content->OwnerDoc()); michael@0: if (!domDoc) michael@0: return; michael@0: michael@0: nsCOMPtr event; michael@0: domDoc->CreateEvent(NS_LITERAL_STRING("customevent"), michael@0: getter_AddRefs(event)); michael@0: michael@0: nsCOMPtr treeEvent(do_QueryInterface(event)); michael@0: if (!treeEvent) michael@0: return; michael@0: michael@0: nsCOMPtr propBag( michael@0: do_CreateInstance("@mozilla.org/hash-property-bag;1")); michael@0: if (!propBag) michael@0: return; michael@0: michael@0: if (aStartRowIdx != -1 && aEndRowIdx != -1) { michael@0: // Set 'startrow' data - the start index of invalidated rows. michael@0: propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startrow"), michael@0: aStartRowIdx); michael@0: michael@0: // Set 'endrow' data - the end index of invalidated rows. michael@0: propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endrow"), michael@0: aEndRowIdx); michael@0: } michael@0: michael@0: if (aStartCol && aEndCol) { michael@0: // Set 'startcolumn' data - the start index of invalidated rows. michael@0: int32_t startColIdx = 0; michael@0: nsresult rv = aStartCol->GetIndex(&startColIdx); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: propBag->SetPropertyAsInt32(NS_LITERAL_STRING("startcolumn"), michael@0: startColIdx); michael@0: michael@0: // Set 'endcolumn' data - the start index of invalidated rows. michael@0: int32_t endColIdx = 0; michael@0: rv = aEndCol->GetIndex(&endColIdx); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: propBag->SetPropertyAsInt32(NS_LITERAL_STRING("endcolumn"), michael@0: endColIdx); michael@0: } michael@0: michael@0: nsCOMPtr detailVariant( michael@0: do_CreateInstance("@mozilla.org/variant;1")); michael@0: if (!detailVariant) michael@0: return; michael@0: michael@0: detailVariant->SetAsISupports(propBag); michael@0: treeEvent->InitCustomEvent(NS_LITERAL_STRING("TreeInvalidated"), michael@0: true, false, detailVariant); michael@0: michael@0: event->SetTrusted(true); michael@0: michael@0: nsRefPtr asyncDispatcher = michael@0: new AsyncEventDispatcher(content, event); michael@0: asyncDispatcher->PostDOMEvent(); michael@0: } michael@0: #endif michael@0: michael@0: class nsOverflowChecker : public nsRunnable michael@0: { michael@0: public: michael@0: nsOverflowChecker(nsTreeBodyFrame* aFrame) : mFrame(aFrame) {} michael@0: NS_IMETHOD Run() MOZ_OVERRIDE michael@0: { michael@0: if (mFrame.IsAlive()) { michael@0: nsTreeBodyFrame* tree = static_cast(mFrame.GetFrame()); michael@0: nsTreeBodyFrame::ScrollParts parts = tree->GetScrollParts(); michael@0: tree->CheckOverflow(parts); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsWeakFrame mFrame; michael@0: }; michael@0: michael@0: bool michael@0: nsTreeBodyFrame::FullScrollbarsUpdate(bool aNeedsFullInvalidation) michael@0: { michael@0: ScrollParts parts = GetScrollParts(); michael@0: nsWeakFrame weakFrame(this); michael@0: nsWeakFrame weakColumnsFrame(parts.mColumnsFrame); michael@0: UpdateScrollbars(parts); michael@0: NS_ENSURE_TRUE(weakFrame.IsAlive(), false); michael@0: if (aNeedsFullInvalidation) { michael@0: Invalidate(); michael@0: } michael@0: InvalidateScrollbars(parts, weakColumnsFrame); michael@0: NS_ENSURE_TRUE(weakFrame.IsAlive(), false); michael@0: michael@0: // Overflow checking dispatches synchronous events, which can cause infinite michael@0: // recursion during reflow. Do the first overflow check synchronously, but michael@0: // force any nested checks to round-trip through the event loop. See bug michael@0: // 905909. michael@0: nsRefPtr checker = new nsOverflowChecker(this); michael@0: if (!mCheckingOverflow) { michael@0: nsContentUtils::AddScriptRunner(checker); michael@0: } else { michael@0: NS_DispatchToCurrentThread(checker); michael@0: } michael@0: return weakFrame.IsAlive(); michael@0: } michael@0: michael@0: nsresult michael@0: nsTreeBodyFrame::OnImageIsAnimated(imgIRequest* aRequest) michael@0: { michael@0: nsLayoutUtils::RegisterImageRequest(PresContext(), michael@0: aRequest, nullptr); michael@0: michael@0: return NS_OK; michael@0: }