/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// See if the width is even smaller than the ellipsis // If so, clear the text completely. const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis();
aFontMetrics.SetTextRunRTL(false);
nscoord ellipsisWidth =
nsLayoutUtils::AppUnitWidthOfString(kEllipsis, aFontMetrics, drawTarget);
if (ellipsisWidth > aWidth) {
aText.Truncate(0); return;
} if (ellipsisWidth == aWidth) {
aText.Assign(kEllipsis); return;
}
// We will be drawing an ellipsis, thank you very much. // Subtract out the required width of the ellipsis. // This is the total remaining width we have to play with.
aWidth -= ellipsisWidth;
using mozilla::intl::GraphemeClusterBreakIteratorUtf16; using mozilla::intl::GraphemeClusterBreakReverseIteratorUtf16;
// Now we crop. This is quite basic: it will not be really accurate in the // presence of complex scripts with contextual shaping, etc., as it measures // each grapheme cluster in isolation, not in its proper context. switch (aCropType) { case CropAuto: case CropNone: case CropRight: { const Span text(aText);
GraphemeClusterBreakIteratorUtf16 iter(text);
uint32_t pos = 0;
nscoord totalWidth = 0;
// nextPos is decreasing since we use a reverse iterator. while (Maybe<uint32_t> nextPos = iter.Next()) { const nscoord charWidth = nsLayoutUtils::AppUnitWidthOfString(
text.FromTo(*nextPos, pos), aFontMetrics, drawTarget); if (totalWidth + charWidth > aWidth) { break;
}
staticvoid DoCancelImageCacheEntry(const nsTreeImageCacheEntry& aEntry,
nsPresContext* aPc) { // If our imgIRequest object was registered with the refresh driver // then we need to deregister it.
aEntry.listener->ClearFrame();
nsLayoutUtils::DeregisterImageRequest(aPc, aEntry.request, nullptr);
aEntry.request->UnlockImage();
aEntry.request->CancelAndForgetObserver(NS_BINDING_ABORTED);
}
// Function that cancels all the image requests in our cache. void nsTreeBodyFrame::CancelImageRequests() { auto* pc = PresContext(); for (const nsTreeImageCacheEntry& entry : mImageCache.Values()) {
DoCancelImageCacheEntry(entry, pc);
}
}
// // NS_NewTreeFrame // // Creates a new tree frame //
nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle) { returnnew (aPresShell) nsTreeBodyFrame(aStyle, aPresShell->GetPresContext());
}
mScrollEvent.Revoke(); // Make sure we cancel any posted callbacks. if (mReflowCallbackPosted) {
PresShell()->CancelReflowCallback(this);
mReflowCallbackPosted = false;
}
if (mColumns) {
mColumns->SetTree(nullptr);
}
RefPtr tree = mTree;
if (nsCOMPtr<nsITreeView> view = std::move(mView)) {
nsCOMPtr<nsITreeSelection> sel;
view->GetSelection(getter_AddRefs(sel)); if (sel) {
sel->SetTree(nullptr);
}
view->SetTree(nullptr);
}
// Make this call now because view->SetTree can run js which can undo this // call. if (tree) {
tree->BodyDestroyed(mTopRowIndex);
} if (mTree && mTree != tree) {
mTree->BodyDestroyed(mTopRowIndex);
}
SimpleXULLeafFrame::Destroy(aContext);
}
void nsTreeBodyFrame::EnsureView() { if (mView) { return;
}
if (PresShell()->IsReflowLocked()) { if (!mReflowCallbackPosted) {
mReflowCallbackPosted = true;
PresShell()->PostReflowCallback(this);
} return;
}
AutoWeakFrame weakFrame(this);
RefPtr<XULTreeElement> tree = GetBaseElement(); if (!tree) { return;
}
nsCOMPtr<nsITreeView> treeView = tree->GetView(); if (!treeView || !weakFrame.IsAlive()) { return;
}
int32_t rowIndex = tree->GetCachedTopVisibleRow();
// Set our view.
SetView(treeView);
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
// Scroll to the given row. // XXX is this optimal if we haven't laid out yet?
ScrollToRow(rowIndex);
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
}
void nsTreeBodyFrame::ManageReflowCallback() { const nscoord horzWidth = mRect.width; if (!mReflowCallbackPosted) { if (!mLastReflowRect || !mLastReflowRect->IsEqualEdges(mRect) ||
mHorzWidth != horzWidth) {
PresShell()->PostReflowCallback(this);
mReflowCallbackPosted = true;
mOriginalHorzWidth = mHorzWidth;
}
} elseif (mHorzWidth != horzWidth && mOriginalHorzWidth == horzWidth) { // FIXME(emilio): This doesn't seem sound to me, if the rect changes in the // block axis.
PresShell()->CancelReflowCallback(this);
mReflowCallbackPosted = false;
mOriginalHorzWidth = -1;
}
mLastReflowRect = Some(mRect);
mHorzWidth = horzWidth;
}
XULTreeElement* treeContent = GetBaseElement(); if (treeContent && treeContent->AttrValueIs(
kNameSpaceID_None, nsGkAtoms::keepcurrentinview,
nsGkAtoms::_true, eCaseMatters)) { // make sure that the current selected item is still // visible after the tree changes size. if (nsCOMPtr<nsITreeSelection> sel = GetSelection()) {
int32_t currentIndex;
sel->GetCurrentIndex(¤tIndex); if (currentIndex != -1) {
EnsureRowIsVisibleInternal(parts, currentIndex);
}
}
}
if (!FullScrollbarsUpdate(false)) { returnfalse;
}
}
if (aView) { // Give the view a new empty selection object to play with, but only if it // doesn't have one already.
nsCOMPtr<nsITreeSelection> sel;
aView->GetSelection(getter_AddRefs(sel)); if (sel) {
sel->SetTree(treeContent);
} else {
NS_NewTreeSelection(treeContent, getter_AddRefs(sel));
aView->SetSelection(sel);
}
// View, meet the tree.
AutoWeakFrame weakFrame(this);
aView->SetTree(treeContent);
NS_ENSURE_STATE(weakFrame.IsAlive());
aView->GetRowCount(&mRowCount);
if (!PresShell()->IsReflowLocked()) { // The scrollbar will need to be updated.
FullScrollbarsUpdate(false);
} elseif (!mReflowCallbackPosted) {
mReflowCallbackPosted = true;
PresShell()->PostReflowCallback(this);
}
}
// iterate through the visible rows and add the selected ones to the // drag region
int32_t x = nsPresContext::AppUnitsToIntCSSPixels(origin.x);
int32_t y = nsPresContext::AppUnitsToIntCSSPixels(origin.y);
int32_t top = y;
int32_t end = LastVisibleRow();
int32_t rowHeight = nsPresContext::AppUnitsToIntCSSPixels(mRowHeight); for (int32_t i = mTopRowIndex; i <= end; i++) { bool isSelected;
selection->IsSelected(i, &isSelected); if (isSelected) {
region.OrWith(CSSIntRect(x, y, rect.width, rowHeight));
}
y += rowHeight;
}
// clip to the tree boundary in case one row extends past it
region.AndWith(CSSIntRect(x, top, rect.width, rect.height));
return Some(region);
}
nsresult nsTreeBodyFrame::Invalidate() { if (mUpdateBatchNest) { return NS_OK;
}
InvalidateFrame();
return NS_OK;
}
nsresult nsTreeBodyFrame::InvalidateColumn(nsTreeColumn* aCol) { if (mUpdateBatchNest) { return NS_OK;
}
nsTreeBodyFrame::ScrollParts nsTreeBodyFrame::GetScrollParts() {
ScrollParts result;
XULTreeElement* tree = GetBaseElement(); if (nsIFrame* treeFrame = tree ? tree->GetPrimaryFrame() : nullptr) { // The way we do this, searching through the entire frame subtree, is pretty // dumb! We should know where these frames are.
FindScrollParts(treeFrame, &result); if (result.mVScrollbar) {
result.mVScrollbar->SetScrollbarMediatorContent(GetContent());
result.mVScrollbarContent = result.mVScrollbar->GetContent()->AsElement();
}
} return result;
}
// The synchronous event dispatch above can trigger reflow notifications. // Flush those explicitly now, so that we can guard against potential infinite // recursion. See bug 905909. if (!weakFrame.IsAlive()) { return;
}
NS_ASSERTION(!mCheckingOverflow, "mCheckingOverflow should not already be set"); // Don't use AutoRestore since we want to not touch mCheckingOverflow if we // fail the weakFrame.IsAlive() check below
mCheckingOverflow = true;
presShell->FlushPendingNotifications(FlushType::Layout); if (!weakFrame.IsAlive()) { return;
}
mCheckingOverflow = false;
}
// Also set our page increment and decrement.
nscoord pageincrement = mPageLength * rowHeightAsPixels;
nsAutoString pageStr;
pageStr.AppendInt(pageincrement);
aParts.mVScrollbarContent->SetAttr(kNameSpaceID_None,
nsGkAtoms::pageincrement, pageStr, true);
NS_ENSURE_TRUE_VOID(weakFrame.IsAlive());
}
if (weakFrame.IsAlive() && mScrollbarActivity) {
mScrollbarActivity->ActivityOccurred();
}
}
// Takes client x/y in pixels, converts them to appunits, and converts into // values relative to this nsTreeBodyFrame frame.
nsPoint nsTreeBodyFrame::AdjustClientCoordsToBoxCoordSpace(int32_t aX,
int32_t aY) {
nsPoint point(nsPresContext::CSSPixelsToAppUnits(aX),
nsPresContext::CSSPixelsToAppUnits(aY));
nsPresContext* presContext = PresContext();
point -= GetOffsetTo(presContext->GetPresShell()->GetRootFrame());
// Adjust by the inner box coords, so that we're in the inner box's // coordinate space.
point -= mInnerBox.TopLeft(); return point;
} // AdjustClientCoordsToBoxCoordSpace
// // GetCoordsForCellItem // // Find the x/y location and width/height (all in PIXELS) of the given object // in the given column. // // XXX IMPORTANT XXX: // Hyatt says in the bug for this, that the following needs to be done: // (1) You need to deal with overflow when computing cell rects. See other // column iteration examples... if you don't deal with this, you'll mistakenly // extend the cell into the scrollbar's rect. // // (2) You are adjusting the cell rect by the *row" border padding. That's // wrong. You need to first adjust a row rect by its border/padding, and then // the cell rect fits inside the adjusted row rect. It also can have // border/padding as well as margins. The vertical direction isn't that // important, but you need to get the horizontal direction right. // // (3) GetImageSize() does not include margins (but it does include // border/padding). You need to make sure to add in the image's margins as well. //
nsresult nsTreeBodyFrame::GetCoordsForCellItem(int32_t aRow, nsTreeColumn* aCol, const nsACString& aElement,
int32_t* aX, int32_t* aY,
int32_t* aWidth,
int32_t* aHeight) {
*aX = 0;
*aY = 0;
*aWidth = 0;
*aHeight = 0;
// The Rect for the requested item.
nsRect theRect;
nsPresContext* presContext = PresContext();
nsCOMPtr<nsITreeView> view = GetExistingView();
for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
currCol = currCol->GetNext()) { // The Rect for the current cell.
nscoord colWidth; #ifdef DEBUG
nsresult rv = #endif
currCol->GetWidthInTwips(this, &colWidth);
NS_ASSERTION(NS_SUCCEEDED(rv), "invalid column");
// Check the ID of the current column to see if it matches. If it doesn't // increment the current X value and continue to the next column. if (currCol != aCol) {
currX += cellRect.width; continue;
} // Now obtain the properties for our cell.
PrefillPropertyArray(aRow, currCol);
// We don't want to consider any of the decorations that may be present // on the current row, so we have to deflate the rect by the border and // padding and offset its left and top coordinates appropriately.
AdjustForBorderPadding(rowContext, cellRect);
constexpr auto cell = "cell"_ns; if (currCol->IsCycler() || cell.Equals(aElement)) { // If the current Column is a Cycler, then the Rect is just the cell - the // margins. Similarly, if we're just being asked for the cell rect, // provide it.
// Since we're not looking for the cell, and since the cell isn't a cycler, // we're looking for some subcomponent, and now we need to subtract the // borders and padding of the cell from cellRect so this does not // interfere with our computations.
AdjustForBorderPadding(cellContext, cellRect);
// Now we'll start making our way across the cell, starting at the edge of // the cell and proceeding until we hit the right edge. |cellX| is the // working X value that we will increment as we crawl from left to right.
nscoord cellX = cellRect.x;
nscoord remainWidth = cellRect.width;
if (currCol->IsPrimary()) { // If the current Column is a Primary, then we need to take into account // the indentation and possibly a twisty.
// The amount of indentation is the indentation width (|mIndentation|) by // the level.
int32_t level;
view->GetLevel(aRow, &level); if (!isRTL) {
cellX += mIndentation * level;
}
remainWidth -= mIndentation * level;
// Find the twisty rect by computing its size.
nsRect imageRect;
nsRect twistyRect(cellRect);
ComputedStyle* twistyContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
GetTwistyRect(aRow, currCol, imageRect, twistyRect, presContext,
twistyContext);
if ("twisty"_ns.Equals(aElement)) { // If we're looking for the twisty Rect, just return the size
theRect = twistyRect; break;
}
// Now we need to add in the margins of the twisty element, so that we // can find the offset of the next element in the cell.
nsMargin twistyMargin;
twistyContext->StyleMargin()->GetMargin(twistyMargin);
twistyRect.Inflate(twistyMargin);
// Adjust our working X value with the twisty width (image size, margins, // borders, padding. if (!isRTL) {
cellX += twistyRect.width;
}
}
// Add in the margins of the cell image.
nsMargin imageMargin;
imageContext->StyleMargin()->GetMargin(imageMargin);
imageSize.Inflate(imageMargin);
// Increment cellX by the image width if (!isRTL) {
cellX += imageSize.width;
}
// Cell Text
nsAutoString cellText;
view->GetCellText(aRow, currCol, cellText); // We're going to measure this text so we need to ensure bidi is enabled if // necessary
CheckTextForBidi(cellText);
// Create a scratch rect to represent the text rectangle, with the current // X and Y coords, and a guess at the width and height. The width is the // remaining width we have left to traverse in the cell, which will be the // widest possible value for the text rect, and the row height.
nsRect textRect(cellX, cellRect.y, remainWidth, cellRect.height);
// Measure the width of the text. If the width of the text is greater than // the remaining width available, then we just assume that the text has // been cropped and use the remaining rect as the text Rect. Otherwise, // we add in borders and padding to the text dimension and give that back.
ComputedStyle* textContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
RefPtr<nsFontMetrics> fm =
nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, presContext);
nscoord height = fm->MaxHeight();
// Now just mod by our total inner box height and add to our top row index.
int32_t row = (aY / mRowHeight) + mTopRowIndex;
// Check if the coordinates are below our visible space (or within our visible // space but below any row). if (row > mTopRowIndex + mPageLength || row >= mRowCount) { return -1;
}
return row;
}
void nsTreeBodyFrame::CheckTextForBidi(nsAutoString& aText) { // We could check to see whether the prescontext already has bidi enabled, // but usually it won't, so it's probably faster to avoid the call to // GetPresContext() when it's not needed. if (HasRTLChars(aText)) {
PresContext()->SetBidiEnabled();
}
}
nsCOMPtr<nsITreeView> view = GetExistingView(); if (aColumn->Overflow()) {
DebugOnly<nsresult> rv;
nsTreeColumn* nextColumn = aColumn->GetNext(); while (nextColumn && widthIsGreater) { while (nextColumn) {
nscoord width;
rv = nextColumn->GetWidthInTwips(this, &width);
NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
if (width != 0) { break;
}
nextColumn = nextColumn->GetNext();
}
if (nextColumn) {
nsAutoString nextText;
view->GetCellText(aRowIndex, nextColumn, nextText); // We don't measure or draw this text so no need to check it for // bidi-ness
if (nextText.Length() == 0) {
nscoord width;
rv = nextColumn->GetWidthInTwips(this, &width);
NS_ASSERTION(NS_SUCCEEDED(rv), "nextColumn is invalid");
// Obtain the properties for our cell.
PrefillPropertyArray(aRowIndex, aColumn);
nsAutoString properties;
nsCOMPtr<nsITreeView> view = GetExistingView();
view->GetCellProperties(aRowIndex, aColumn, properties);
nsTreeUtils::TokenizeProperties(properties, mScratchArray);
// Resolve style for the cell.
ComputedStyle* cellContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
// Obtain the margins for the cell and then deflate our rect by that // amount. The cell is assumed to be contained within the deflated rect.
nsRect cellRect(aCellRect);
nsMargin cellMargin;
cellContext->StyleMargin()->GetMargin(cellMargin);
cellRect.Deflate(cellMargin);
// Adjust the rect for its border and padding.
AdjustForBorderPadding(cellContext, cellRect);
if (aX < cellRect.x || aX >= cellRect.x + cellRect.width) { // The user clicked within the cell's margins/borders/padding. This // constitutes a click on the cell. return nsCSSAnonBoxes::mozTreeCell();
}
if ((isRTL && aX > currX + remainingWidth) || (!isRTL && aX < currX)) { // The user clicked within the indentation. return nsCSSAnonBoxes::mozTreeCell();
}
// Always leave space for the twisty.
nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height); bool hasTwisty = false; bool isContainer = false;
view->IsContainer(aRowIndex, &isContainer); if (isContainer) { bool isContainerEmpty = false;
view->IsContainerEmpty(aRowIndex, &isContainerEmpty); if (!isContainerEmpty) {
hasTwisty = true;
}
}
// Resolve style for the twisty.
ComputedStyle* twistyContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
// We will treat a click as hitting the twisty if it happens on the margins, // borders, padding, or content of the twisty object. By allowing a "slop" // into the margin, we make it a little bit easier for a user to hit the // twisty. (We don't want to be too picky here.)
nsMargin twistyMargin;
twistyContext->StyleMargin()->GetMargin(twistyMargin);
twistyRect.Inflate(twistyMargin); if (isRTL) {
twistyRect.x = currX + remainingWidth - twistyRect.width;
}
// Now we test to see if aX is actually within the twistyRect. If it is, // and if the item should have a twisty, then we return "twisty". If it is // within the rect but we shouldn't have a twisty, then we return "cell". if (aX >= twistyRect.x && aX < twistyRect.x + twistyRect.width) { if (hasTwisty) { return nsCSSAnonBoxes::mozTreeTwisty();
} return nsCSSAnonBoxes::mozTreeCell();
}
if (aX >= iconRect.x && aX < iconRect.x + iconRect.width) { // The user clicked on the image. return nsCSSAnonBoxes::mozTreeImage();
}
if (!isRTL) {
currX += iconRect.width;
}
remainingWidth -= iconRect.width;
nsAutoString cellText;
view->GetCellText(aRowIndex, aColumn, cellText); // We're going to measure this text so we need to ensure bidi is enabled if // necessary
CheckTextForBidi(cellText);
// Determine the column hit. for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
currCol = currCol->GetNext()) {
nsRect cellRect;
nsresult rv = currCol->GetRect( this, mInnerBox.y + mRowHeight * (*aRow - mTopRowIndex), mRowHeight,
&cellRect); if (NS_FAILED(rv)) {
MOZ_ASSERT_UNREACHABLE("column has no frame"); continue;
}
if (!OffsetForHorzScroll(cellRect, false)) { continue;
}
if (aX >= cellRect.x && aX < cellRect.x + cellRect.width) { // We know the column hit now.
*aCol = currCol;
if (currCol->IsCycler()) { // Cyclers contain only images. Fill this in immediately and return.
*aChildElt = nsCSSAnonBoxes::mozTreeImage();
} else {
*aChildElt = GetItemWithinCellAt(aX, cellRect, *aRow, currCol);
} break;
}
}
}
nsresult nsTreeBodyFrame::GetCellWidth(int32_t aRow, nsTreeColumn* aCol,
gfxContext* aRenderingContext,
nscoord& aDesiredSize,
nscoord& aCurrentSize) {
MOZ_ASSERT(aCol, "aCol must not be null");
MOZ_ASSERT(aRenderingContext, "aRenderingContext must not be null");
// The rect for the current cell.
nscoord colWidth;
nsresult rv = aCol->GetWidthInTwips(this, &colWidth);
NS_ENSURE_SUCCESS(rv, rv);
if (aCol->IsPrimary()) { // If the current Column is a Primary, then we need to take into account // the indentation and possibly a twisty.
// The amount of indentation is the indentation width (|mIndentation|) by // the level.
int32_t level;
view->GetLevel(aRow, &level);
aDesiredSize += mIndentation * level;
// Find the twisty rect by computing its size.
ComputedStyle* twistyContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
// Add in the margins of the twisty element.
nsMargin twistyMargin;
twistyContext->StyleMargin()->GetMargin(twistyMargin);
twistyRect.Inflate(twistyMargin);
// Account for the width of the cell image.
nsRect imageSize = GetImageSize(aRow, aCol, false, imageContext); // Add in the margins of the cell image.
nsMargin imageMargin;
imageContext->StyleMargin()->GetMargin(imageMargin);
imageSize.Inflate(imageMargin);
aDesiredSize += imageSize.width;
// Get the cell text.
nsAutoString cellText;
view->GetCellText(aRow, aCol, cellText); // We're going to measure this text so we need to ensure bidi is enabled if // necessary
CheckTextForBidi(cellText);
nsresult nsTreeBodyFrame::CreateTimer(const LookAndFeel::IntID aID,
nsTimerCallbackFunc aFunc, int32_t aType,
nsITimer** aTimer, constchar* aName) { // Get the delay from the look and feel service.
int32_t delay = LookAndFeel::GetInt(aID, 0);
nsCOMPtr<nsITimer> timer;
// Create a new timer only if the delay is greater than zero. // Zero value means that this feature is completely disabled. if (delay > 0) {
MOZ_TRY_VAR(timer,
NS_NewTimerWithFuncCallback(aFunc, this, delay, aType, aName,
GetMainThreadSerialEventTarget()));
}
timer.forget(aTimer); return NS_OK;
}
nsresult nsTreeBodyFrame::RowCountChanged(int32_t aIndex, int32_t aCount) { if (aCount == 0 || !mView) { return NS_OK; // Nothing to do.
}
// odd or even if (aRowIndex % 2) {
mScratchArray.AppendElement(nsGkAtoms::odd);
} else {
mScratchArray.AppendElement(nsGkAtoms::even);
}
XULTreeElement* tree = GetBaseElement(); if (tree && tree->HasAttr(nsGkAtoms::editing)) {
mScratchArray.AppendElement(nsGkAtoms::editing);
}
// multiple columns if (mColumns->GetColumnAt(1)) {
mScratchArray.AppendElement(nsGkAtoms::multicol);
}
}
if (aCol) {
mScratchArray.AppendElement(aCol->GetAtom());
if (aCol->IsPrimary()) {
mScratchArray.AppendElement(nsGkAtoms::primary);
}
if (aCol->GetType() == TreeColumn_Binding::TYPE_CHECKBOX) {
mScratchArray.AppendElement(nsGkAtoms::checkbox);
if (aRowIndex != -1) {
nsAutoString value;
mView->GetCellValue(aRowIndex, aCol, value); if (value.EqualsLiteral("true")) {
mScratchArray.AppendElement(nsGkAtoms::checked);
}
}
}
if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::ordinal,
u"1"_ns, eIgnoreCase)) {
mScratchArray.AppendElement(nsGkAtoms::firstColumn);
}
// Read special properties from attributes on the column content node if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertbefore,
nsGkAtoms::_true, eCaseMatters)) {
mScratchArray.AppendElement(nsGkAtoms::insertbefore);
} if (aCol->mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::insertafter,
nsGkAtoms::_true, eCaseMatters)) {
mScratchArray.AppendElement(nsGkAtoms::insertafter);
}
}
}
void nsTreeBodyFrame::GetTwistyRect(int32_t aRowIndex, nsTreeColumn* aColumn,
nsRect& aImageRect, nsRect& aTwistyRect,
nsPresContext* aPresContext,
ComputedStyle* aTwistyContext) { // The twisty rect extends all the way to the end of the cell. This is // incorrect. We need to determine the twisty rect's true width. This is // done by examining the ComputedStyle for a width first. If it has one, we // use that. If it doesn't, we use the image's natural width. If the image // hasn't loaded and if no width is specified, then we just bail. If there is // a -moz-appearance involved, adjust the rect by the minimum widget size // provided by the theme implementation.
aImageRect = GetImageSize(aRowIndex, aColumn, true, aTwistyContext); if (aImageRect.height > aTwistyRect.height) {
aImageRect.height = aTwistyRect.height;
} if (aImageRect.width > aTwistyRect.width) {
aImageRect.width = aTwistyRect.width;
} else {
aTwistyRect.width = aImageRect.width;
}
}
already_AddRefed<imgIContainer> nsTreeBodyFrame::GetImage(
int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
ComputedStyle* aComputedStyle) {
Document* doc = PresContext()->Document();
nsAutoString imageSrc;
mView->GetImageSrc(aRowIndex, aCol, imageSrc);
RefPtr<imgRequestProxy> styleRequest;
nsCOMPtr<nsIURI> uri; if (aUseContext || imageSrc.IsEmpty()) { // Obtain the URL from the ComputedStyle.
styleRequest =
aComputedStyle->StyleList()->mListStyleImage.GetImageRequest(); if (!styleRequest) { return nullptr;
}
styleRequest->GetURI(getter_AddRefs(uri));
} else {
nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), imageSrc,
doc, mContent->GetBaseURI());
} if (!uri) { return nullptr;
} // Look the image up in our cache.
nsTreeImageCacheEntry entry; if (mImageCache.Get(uri, &entry)) {
nsCOMPtr<imgIContainer> result;
entry.request->GetImage(getter_AddRefs(result));
entry.listener->AddCell(aRowIndex, aCol); return result.forget();
}
// Create a new nsTreeImageListener object and pass it our row and column // information. auto listener = MakeRefPtr<nsTreeImageListener>(this);
listener->AddCell(aRowIndex, aCol);
RefPtr<imgRequestProxy> imageRequest; if (styleRequest) {
styleRequest->SyncClone(listener, doc, getter_AddRefs(imageRequest));
} else { auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc); // XXXbz what's the origin principal for this stuff that comes from our // view? I guess we should assume that it's the node's principal...
nsContentUtils::LoadImage(uri, mContent, doc, mContent->NodePrincipal(), 0,
referrerInfo, listener, nsIRequest::LOAD_NORMAL,
u""_ns, getter_AddRefs(imageRequest));
}
listener->UnsuppressInvalidation(); if (!imageRequest) { return nullptr;
}
// NOTE(heycam): If it's an SVG image, and we need to want the image to // able to respond to media query changes, it needs to be added to the // document's ImageTracker. For now, assume we don't need this. // We don't want discarding/decode-on-draw for xul images
imageRequest->StartDecoding(imgIContainer::FLAG_ASYNC_NOTIFY);
imageRequest->LockImage();
// In a case it was already cached.
mImageCache.InsertOrUpdate(uri,
nsTreeImageCacheEntry(imageRequest, listener));
nsCOMPtr<imgIContainer> result;
imageRequest->GetImage(getter_AddRefs(result)); return result.forget();
}
nsRect nsTreeBodyFrame::GetImageSize(int32_t aRowIndex, nsTreeColumn* aCol, bool aUseContext,
ComputedStyle* aComputedStyle) { // XXX We should respond to visibility rules for collapsed vs. hidden.
// This method returns the width of the twisty INCLUDING borders and padding. // It first checks the ComputedStyle for a width. If none is found, it tries // to use the default image width for the twisty. If no image is found, it // defaults to border+padding.
nsRect r(0, 0, 0, 0);
nsMargin bp(0, 0, 0, 0);
GetBorderPadding(aComputedStyle, bp);
r.Inflate(bp);
// Now r contains our border+padding info. We now need to get our width and // height. bool needWidth = false; bool needHeight = false;
// We have to load image even though we already have a size. // Don't change this, otherwise things start to go awry.
nsCOMPtr<imgIContainer> image =
GetImage(aRowIndex, aCol, aUseContext, aComputedStyle);
// GetImageDestSize returns the destination size of the image. // The width and height do not include borders and padding. // The width and height have not been adjusted to fit in the row height // or cell width. // The width and height reflect the destination size specified in CSS, // or the image region specified in CSS, or the natural size of the // image. // If only the destination width has been specified in CSS, the height is // calculated to maintain the aspect ratio of the image. // If only the destination height has been specified in CSS, the width is // calculated to maintain the aspect ratio of the image.
nsSize nsTreeBodyFrame::GetImageDestSize(ComputedStyle* aComputedStyle,
imgIContainer* image) {
nsSize size(0, 0);
// We need to get the width and height. bool needWidth = false; bool needHeight = false;
// Get the style position to see if the CSS has specified the // destination width/height. const nsStylePosition* myPosition = aComputedStyle->StylePosition();
if (myPosition->GetWidth().ConvertsToLength()) { // CSS has specified the destination width.
size.width = myPosition->GetWidth().ToLength();
} else { // We'll need to get the width of the image/region.
needWidth = true;
}
if (myPosition->GetHeight().ConvertsToLength()) { // CSS has specified the destination height.
size.height = myPosition->GetHeight().ToLength();
} else { // We'll need to get the height of the image/region.
needHeight = true;
}
if (needWidth || needHeight) { // We need to get the size of the image/region.
nsSize imageSize(0, 0); if (image) {
nscoord width;
image->GetWidth(&width);
imageSize.width = nsPresContext::CSSPixelsToAppUnits(width);
nscoord height;
image->GetHeight(&height);
imageSize.height = nsPresContext::CSSPixelsToAppUnits(height);
}
if (needWidth) { if (!needHeight && imageSize.height != 0) { // The CSS specified the destination height, but not the destination // width. We need to calculate the width so that we maintain the // image's aspect ratio.
size.width = imageSize.width * size.height / imageSize.height;
} else {
size.width = imageSize.width;
}
}
if (needHeight) { if (!needWidth && imageSize.width != 0) { // The CSS specified the destination width, but not the destination // height. We need to calculate the height so that we maintain the // image's aspect ratio.
size.height = imageSize.height * size.width / imageSize.width;
} else {
size.height = imageSize.height;
}
}
}
return size;
}
// GetImageSourceRect returns the source rectangle of the image to be // displayed. // The width and height reflect the image region specified in CSS, or // the natural size of the image. // The width and height do not include borders and padding. // The width and height do not reflect the destination size specified // in CSS.
nsRect nsTreeBodyFrame::GetImageSourceRect(ComputedStyle* aComputedStyle,
imgIContainer* image) { if (!image) { return nsRect();
}
nsRect r; // Use the actual image size.
nscoord coord; if (NS_SUCCEEDED(image->GetWidth(&coord))) {
r.width = nsPresContext::CSSPixelsToAppUnits(coord);
} if (NS_SUCCEEDED(image->GetHeight(&coord))) {
r.height = nsPresContext::CSSPixelsToAppUnits(coord);
} return r;
}
int32_t nsTreeBodyFrame::GetRowHeight() { // Look up the correct height. It is equal to the specified height // + the specified margins.
mScratchArray.Clear();
ComputedStyle* rowContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow()); if (rowContext) { const nsStylePosition* myPosition = rowContext->StylePosition();
// XXX Check box-sizing to determine if border/padding should augment the // height Inflate the height by our margins.
nsRect rowRect(0, 0, 0, height);
nsMargin rowMargin;
rowContext->StyleMargin()->GetMargin(rowMargin);
rowRect.Inflate(rowMargin);
height = rowRect.height; return height;
}
}
return nsPresContext::CSSPixelsToAppUnits(18); // As good a default as any.
}
int32_t nsTreeBodyFrame::GetIndentation() { // Look up the correct indentation. It is equal to the specified indentation // width.
mScratchArray.Clear();
ComputedStyle* indentContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeIndentation()); if (indentContext) { const nsStylePosition* myPosition = indentContext->StylePosition(); if (myPosition->GetWidth().ConvertsToLength()) { return myPosition->GetWidth().ToLength();
}
}
return nsPresContext::CSSPixelsToAppUnits(16); // As good a default as any.
}
nsIFrame::Cursor nsTreeBodyFrame::GetCursor(const nsPoint& aPoint) { // Check the GetScriptHandlingObject so we don't end up running code when // the document is a zombie. bool dummy; if (mView && GetContent()->GetComposedDoc()->GetScriptHandlingObject(dummy)) {
int32_t row;
nsTreeColumn* col;
nsCSSAnonBoxPseudoStaticAtom* child;
GetCellAt(aPoint.x, aPoint.y, &row, &col, &child);
nsresult nsTreeBodyFrame::HandleEvent(nsPresContext* aPresContext,
WidgetGUIEvent* aEvent,
nsEventStatus* aEventStatus) { if (aEvent->mMessage == eMouseOver || aEvent->mMessage == eMouseMove) {
nsPoint pt =
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
int32_t xTwips = pt.x - mInnerBox.x;
int32_t yTwips = pt.y - mInnerBox.y;
int32_t newrow = GetRowAtInternal(xTwips, yTwips); if (mMouseOverRow != newrow) { // redraw the old and the new row if (mMouseOverRow != -1) {
InvalidateRow(mMouseOverRow);
}
mMouseOverRow = newrow; if (mMouseOverRow != -1) {
InvalidateRow(mMouseOverRow);
}
}
} elseif (aEvent->mMessage == eMouseOut) { if (mMouseOverRow != -1) {
InvalidateRow(mMouseOverRow);
mMouseOverRow = -1;
}
} elseif (aEvent->mMessage == eDragEnter) { if (!mSlots) {
mSlots = MakeUnique<Slots>();
}
// Cache several things we'll need throughout the course of our work. These // will all get released on a drag exit.
if (mSlots->mTimer) {
mSlots->mTimer->Cancel();
mSlots->mTimer = nullptr;
}
// Cache the drag session.
mSlots->mIsDragging = true;
mSlots->mDropRow = -1;
mSlots->mDropOrient = -1;
mSlots->mDragAction = GetDropEffect(aEvent);
} elseif (aEvent->mMessage == eDragOver) { // The mouse is hovering over this tree. If we determine things are // different from the last time, invalidate the drop feedback at the old // position, query the view to see if the current location is droppable, // and then invalidate the drop feedback at the new location if it is. // The mouse may or may not have changed position from the last time // we were called, so optimize out a lot of the extra notifications by // checking if anything changed first. For drop feedback we use drop, // dropBefore and dropAfter property. if (!mView || !mSlots) { return NS_OK;
}
// Save last values, we will need them.
int32_t lastDropRow = mSlots->mDropRow;
int16_t lastDropOrient = mSlots->mDropOrient; #ifndef XP_MACOSX
int16_t lastScrollLines = mSlots->mScrollLines; #endif
// Find out the current drag action
uint32_t lastDragAction = mSlots->mDragAction;
mSlots->mDragAction = GetDropEffect(aEvent);
// Compute the row mouse is over and the above/below/on state. // Below we'll use this to see if anything changed. // Also check if we want to auto-scroll.
ComputeDropPosition(aEvent, &mSlots->mDropRow, &mSlots->mDropOrient,
&mSlots->mScrollLines);
// While we're here, handle tracking of scrolling during a drag. if (mSlots->mScrollLines) { if (mSlots->mDropAllowed) { // Invalidate primary cell at old location.
mSlots->mDropAllowed = false;
InvalidateDropFeedback(lastDropRow, lastDropOrient);
} #ifdef XP_MACOSX
ScrollByLines(mSlots->mScrollLines); #else if (!lastScrollLines) { // Cancel any previously initialized timer. if (mSlots->mTimer) {
mSlots->mTimer->Cancel();
mSlots->mTimer = nullptr;
}
// Set a timer to trigger the tree scrolling.
CreateTimer(LookAndFeel::IntID::TreeLazyScrollDelay, LazyScrollCallback,
nsITimer::TYPE_ONE_SHOT, getter_AddRefs(mSlots->mTimer), "nsTreeBodyFrame::LazyScrollCallback");
} #endif // Bail out to prevent spring loaded timer and feedback line settings. return NS_OK;
}
// If changed from last time, invalidate primary cell at the old location // and if allowed, invalidate primary cell at the new location. If nothing // changed, just bail. if (mSlots->mDropRow != lastDropRow ||
mSlots->mDropOrient != lastDropOrient ||
mSlots->mDragAction != lastDragAction) { // Invalidate row at the old location. if (mSlots->mDropAllowed) {
mSlots->mDropAllowed = false;
InvalidateDropFeedback(lastDropRow, lastDropOrient);
}
if (mSlots->mTimer) { // Timer is active but for a different row than the current one, kill // it.
mSlots->mTimer->Cancel();
mSlots->mTimer = nullptr;
}
if (mSlots->mDropRow >= 0) { if (!mSlots->mTimer && mSlots->mDropOrient == nsITreeView::DROP_ON) { // Either there wasn't a timer running or it was just killed above. // If over a folder, start up a timer to open the folder. bool isContainer = false;
mView->IsContainer(mSlots->mDropRow, &isContainer); if (isContainer) { bool isOpen = false;
mView->IsContainerOpen(mSlots->mDropRow, &isOpen); if (!isOpen) { // This node isn't expanded, set a timer to expand it.
CreateTimer(LookAndFeel::IntID::TreeOpenDelay, OpenCallback,
nsITimer::TYPE_ONE_SHOT,
getter_AddRefs(mSlots->mTimer), "nsTreeBodyFrame::OpenCallback");
}
}
}
// The dataTransfer was initialized by the call to GetDropEffect above. bool canDropAtNewLocation = false;
mView->CanDrop(mSlots->mDropRow, mSlots->mDropOrient,
aEvent->AsDragEvent()->mDataTransfer,
&canDropAtNewLocation);
if (canDropAtNewLocation) { // Invalidate row at the new location.
mSlots->mDropAllowed = canDropAtNewLocation;
InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
}
}
}
// Indicate that the drop is allowed by preventing the default behaviour. if (mSlots->mDropAllowed) {
*aEventStatus = nsEventStatus_eConsumeNoDefault;
}
} elseif (aEvent->mMessage == eDrop) { // this event was meant for another frame, so ignore it if (!mSlots) { return NS_OK;
}
// Tell the view where the drop happened.
// Remove the drop folder and all its parents from the array.
int32_t parentIndex;
nsresult rv = mView->GetParentIndex(mSlots->mDropRow, &parentIndex); while (NS_SUCCEEDED(rv) && parentIndex >= 0) {
mSlots->mArray.RemoveElement(parentIndex);
rv = mView->GetParentIndex(parentIndex, &parentIndex);
}
mView->Drop(mSlots->mDropRow, mSlots->mDropOrient,
dragEvent->mDataTransfer);
mSlots->mDropRow = -1;
mSlots->mDropOrient = -1;
mSlots->mIsDragging = false;
*aEventStatus =
nsEventStatus_eConsumeNoDefault; // already handled the drop
} elseif (aEvent->mMessage == eDragExit) { // this event was meant for another frame, so ignore it if (!mSlots) { return NS_OK;
}
// Clear out all our tracking vars.
if (mSlots->mDropAllowed) {
mSlots->mDropAllowed = false;
InvalidateDropFeedback(mSlots->mDropRow, mSlots->mDropOrient);
} else {
mSlots->mDropAllowed = false;
}
mSlots->mIsDragging = false;
mSlots->mScrollLines = 0; // If a drop is occuring, the exit event will fire just before the drop // event, so don't reset mDropRow or mDropOrient as these fields are used // by the drop event. if (mSlots->mTimer) {
mSlots->mTimer->Cancel();
mSlots->mTimer = nullptr;
}
if (!mSlots->mArray.IsEmpty()) { // Close all spring loaded folders except the drop folder.
CreateTimer(LookAndFeel::IntID::TreeCloseDelay, CloseCallback,
nsITimer::TYPE_ONE_SHOT, getter_AddRefs(mSlots->mTimer), "nsTreeBodyFrame::CloseCallback");
}
}
return NS_OK;
}
namespace mozilla {
class nsDisplayTreeBody final : public nsPaintedDisplayItem { public:
nsDisplayTreeBody(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
: nsPaintedDisplayItem(aBuilder, aFrame) {
MOZ_COUNT_CTOR(nsDisplayTreeBody);
}
// Painting routines void nsTreeBodyFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const nsDisplayListSet& aLists) { // REVIEW: why did we paint if we were collapsed? that makes no sense! if (!IsVisibleForPainting()) { return; // We're invisible. Don't paint.
}
// Handles painting our background, border, and outline.
SimpleXULLeafFrame::BuildDisplayList(aBuilder, aLists);
// Bail out now if there's no view or we can't run script because the // document is a zombie if (!mView || !GetContent()->GetComposedDoc()->GetWindow()) { return;
}
if (oldPageCount != mPageLength || mHorzWidth != mRect.width) { // Schedule a ResizeReflow that will update our info properly.
PresShell()->FrameNeedsReflow(this, IntrinsicDirty::FrameAndAncestors,
NS_FRAME_IS_DIRTY);
} #ifdef DEBUG
int32_t rowCount = mRowCount;
mView->GetRowCount(&rowCount);
NS_WARNING_ASSERTION(mRowCount == rowCount, "row count changed unexpectedly"); #endif
ImgDrawResult result = ImgDrawResult::SUCCESS;
// Loop through our columns and paint them (e.g., for sorting). This is only // relevant when painting backgrounds, since columns contain no content. // Content is contained in the rows. for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
currCol = currCol->GetNext()) {
nsRect colRect;
nsresult rv =
currCol->GetRect(this, mInnerBox.y, mInnerBox.height, &colRect); // Don't paint hidden columns. if (NS_FAILED(rv) || colRect.width == 0) { continue;
}
if (OffsetForHorzScroll(colRect, false)) {
nsRect dirtyRect;
colRect += aPt; if (dirtyRect.IntersectRect(aDirtyRect, colRect)) {
result &= PaintColumn(currCol, colRect, PresContext(),
aRenderingContext, aDirtyRect);
}
}
} // Loop through our on-screen rows. for (int32_t i = mTopRowIndex;
i < mRowCount && i <= mTopRowIndex + mPageLength; i++) {
nsRect rowRect(mInnerBox.x, mInnerBox.y + mRowHeight * (i - mTopRowIndex),
mInnerBox.width, mRowHeight);
nsRect dirtyRect; if (dirtyRect.IntersectRect(aDirtyRect, rowRect + aPt) &&
rowRect.y < (mInnerBox.y + mInnerBox.height)) {
result &= PaintRow(i, rowRect + aPt, PresContext(), aRenderingContext,
aDirtyRect, aPt, aBuilder);
}
}
// Resolve style for the column. It contains all the info we need to lay // ourselves out and to paint.
ComputedStyle* colContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeColumn());
// Obtain the margins for the cell and then deflate our rect by that // amount. The cell is assumed to be contained within the deflated rect.
nsRect colRect(aColumnRect);
nsMargin colMargin;
colContext->StyleMargin()->GetMargin(colMargin);
colRect.Deflate(colMargin);
ImgDrawResult nsTreeBodyFrame::PaintRow(int32_t aRowIndex, const nsRect& aRowRect,
nsPresContext* aPresContext,
gfxContext& aRenderingContext, const nsRect& aDirtyRect, nsPoint aPt,
nsDisplayListBuilder* aBuilder) { // We have been given a rect for our row. We treat this row like a full-blown // frame, meaning that it can have borders, margins, padding, and a // background.
// Without a view, we have no data. Check for this up front.
nsCOMPtr<nsITreeView> view = GetExistingView(); if (!view) { return ImgDrawResult::SUCCESS;
}
nsresult rv;
// Now obtain the properties for our row. // XXX Automatically fill in the following props: open, closed, container, // leaf, selected, focused
PrefillPropertyArray(aRowIndex, nullptr);
// Resolve style for the row. It contains all the info we need to lay // ourselves out and to paint.
ComputedStyle* rowContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeRow());
// Obtain the margins for the row and then deflate our rect by that // amount. The row is assumed to be contained within the deflated rect.
nsRect rowRect(aRowRect);
nsMargin rowMargin;
rowContext->StyleMargin()->GetMargin(rowMargin);
rowRect.Deflate(rowMargin);
ImgDrawResult result = ImgDrawResult::SUCCESS;
// Paint our borders and background for our row rect.
result &= PaintBackgroundLayer(rowContext, aPresContext, aRenderingContext,
rowRect, aDirtyRect);
// Adjust the rect for its border and padding.
nsRect originalRowRect = rowRect;
AdjustForBorderPadding(rowContext, rowRect);
bool isSeparator = false;
view->IsSeparator(aRowIndex, &isSeparator); if (isSeparator) { // The row is a separator.
nscoord primaryX = rowRect.x;
nsTreeColumn* primaryCol = mColumns->GetPrimaryColumn(); if (primaryCol) { // Paint the primary cell.
nsRect cellRect;
rv = primaryCol->GetRect(this, rowRect.y, rowRect.height, &cellRect); if (NS_FAILED(rv)) {
MOZ_ASSERT_UNREACHABLE("primary column is invalid"); return result;
}
// Paint the right side (whole) separator.
nsRect separatorRect(rowRect); if (primaryX > rowRect.x) {
separatorRect.width -= primaryX - rowRect.x;
separatorRect.x += primaryX - rowRect.x;
}
result &= PaintSeparator(aRowIndex, separatorRect, aPresContext,
aRenderingContext, aDirtyRect);
} else { // Now loop over our cells. Only paint a cell if it intersects with our // dirty rect. for (nsTreeColumn* currCol = mColumns->GetFirstColumn(); currCol;
currCol = currCol->GetNext()) {
nsRect cellRect;
rv = currCol->GetRect(this, rowRect.y, rowRect.height, &cellRect); // Don't paint cells in hidden columns. if (NS_FAILED(rv) || cellRect.width == 0) { continue;
}
if (OffsetForHorzScroll(cellRect, false)) {
cellRect.x += aPt.x;
// for primary columns, use the row's vertical size so that the // lines get drawn properly
nsRect checkRect = cellRect; if (currCol->IsPrimary()) {
checkRect = nsRect(cellRect.x, originalRowRect.y, cellRect.width,
originalRowRect.height);
}
// Obtain the height for the separator or use the default value.
nscoord height; if (stylePosition->GetHeight().ConvertsToLength()) {
height = stylePosition->GetHeight().ToLength();
} else { // Use default height 2px.
height = nsPresContext::CSSPixelsToAppUnits(2);
}
// Obtain the margins for the separator and then deflate our rect by that // amount. The separator is assumed to be contained within the deflated // rect.
nsRect separatorRect(aSeparatorRect.x, aSeparatorRect.y, aSeparatorRect.width,
height);
nsMargin separatorMargin;
separatorContext->StyleMargin()->GetMargin(separatorMargin);
separatorRect.Deflate(separatorMargin);
// Center the separator.
separatorRect.y += (aSeparatorRect.height - height) / 2;
// Now obtain the properties for our cell. // XXX Automatically fill in the following props: open, closed, container, // leaf, selected, focused, and the col ID.
PrefillPropertyArray(aRowIndex, aColumn);
nsAutoString properties;
nsCOMPtr<nsITreeView> view = GetExistingView();
view->GetCellProperties(aRowIndex, aColumn, properties);
nsTreeUtils::TokenizeProperties(properties, mScratchArray);
// Resolve style for the cell. It contains all the info we need to lay // ourselves out and to paint.
ComputedStyle* cellContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCell());
// Obtain the margins for the cell and then deflate our rect by that // amount. The cell is assumed to be contained within the deflated rect.
nsRect cellRect(aCellRect);
nsMargin cellMargin;
cellContext->StyleMargin()->GetMargin(cellMargin);
cellRect.Deflate(cellMargin);
// Paint our borders and background for our row rect.
ImgDrawResult result = PaintBackgroundLayer(
cellContext, aPresContext, aRenderingContext, cellRect, aDirtyRect);
// Adjust the rect for its border and padding.
AdjustForBorderPadding(cellContext, cellRect);
// Now we paint the contents of the cells. // Directionality of the tree determines the order in which we paint. // StyleDirection::Ltr means paint from left to right. // StyleDirection::Rtl means paint from right to left.
if (aColumn->IsPrimary()) { // If we're the primary column, we need to indent and paint the twisty and // any connecting lines between siblings.
// Resolve the style to use for the connecting lines.
ComputedStyle* lineContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeLine());
if (mIndentation && level &&
lineContext->StyleVisibility()->IsVisibleOrCollapsed()) { // Paint the thread lines.
// Get the size of the twisty. We don't want to paint the twisty // before painting of connecting lines since it would paint lines over // the twisty. But we need to leave a place for it.
ComputedStyle* twistyContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
int32_t currentParent = aRowIndex; for (int32_t i = level; i > 0; i--) { if (srcX <= cellRect.x + cellRect.width) { // Paint full vertical line only if we have next sibling. bool hasNextSibling;
view->HasNextSibling(currentParent, aRowIndex, &hasNextSibling); if (hasNextSibling || i == level) {
Point p1(pc->AppUnitsToGfxUnits(srcX),
pc->AppUnitsToGfxUnits(lineY));
Point p2;
p2.x = pc->AppUnitsToGfxUnits(srcX);
// Always leave space for the twisty.
nsRect twistyRect(currX, cellRect.y, remainingWidth, cellRect.height);
result &= PaintTwisty(aRowIndex, aColumn, twistyRect, aPresContext,
aRenderingContext, aDirtyRect, remainingWidth, currX);
}
// Now paint the icon for our cell.
nsRect iconRect(currX, cellRect.y, remainingWidth, cellRect.height);
nsRect dirtyRect; if (dirtyRect.IntersectRect(aDirtyRect, iconRect)) {
result &= PaintImage(aRowIndex, aColumn, iconRect, aPresContext,
aRenderingContext, aDirtyRect, remainingWidth, currX,
aBuilder);
}
// Now paint our element, but only if we aren't a cycler column. // XXX until we have the ability to load images, allow the view to // insert text into cycler columns... if (!aColumn->IsCycler()) {
nsRect elementRect(currX, cellRect.y, remainingWidth, cellRect.height);
nsRect dirtyRect; if (dirtyRect.IntersectRect(aDirtyRect, elementRect)) { switch (aColumn->GetType()) { case TreeColumn_Binding::TYPE_TEXT:
result &= PaintText(aRowIndex, aColumn, elementRect, aPresContext,
aRenderingContext, aDirtyRect, currX); break; case TreeColumn_Binding::TYPE_CHECKBOX:
result &= PaintCheckbox(aRowIndex, aColumn, elementRect, aPresContext,
aRenderingContext, aDirtyRect); break;
}
}
}
bool isRTL = StyleVisibility()->mDirection == StyleDirection::Rtl;
nscoord rightEdge = aCurrX + aRemainingWidth; // Paint the twisty, but only if we are a non-empty container. bool shouldPaint = false; bool isContainer = false;
nsCOMPtr<nsITreeView> view = GetExistingView();
view->IsContainer(aRowIndex, &isContainer); if (isContainer) { bool isContainerEmpty = false;
view->IsContainerEmpty(aRowIndex, &isContainerEmpty); if (!isContainerEmpty) {
shouldPaint = true;
}
}
// Resolve style for the twisty.
ComputedStyle* twistyContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeTwisty());
// Obtain the margins for the twisty and then deflate our rect by that // amount. The twisty is assumed to be contained within the deflated rect.
nsRect twistyRect(aTwistyRect);
nsMargin twistyMargin;
twistyContext->StyleMargin()->GetMargin(twistyMargin);
twistyRect.Deflate(twistyMargin);
// Subtract out the remaining width. This is done even when we don't actually // paint a twisty in this cell, so that cells in different rows still line up.
nsRect copyRect(twistyRect);
copyRect.Inflate(twistyMargin);
aRemainingWidth -= copyRect.width; if (!isRTL) {
aCurrX += copyRect.width;
}
auto result = ImgDrawResult::SUCCESS; if (!shouldPaint) { return result;
} // Paint our borders and background for our image rect.
result &= PaintBackgroundLayer(twistyContext, aPresContext, aRenderingContext,
twistyRect, aDirtyRect);
// Time to paint the twisty. // Adjust the rect for its border and padding.
nsMargin bp;
GetBorderPadding(twistyContext, bp);
twistyRect.Deflate(bp); if (isRTL) {
twistyRect.x = rightEdge - twistyRect.width;
}
imageSize.Deflate(bp);
// Get the image for drawing.
nsCOMPtr<imgIContainer> image =
GetImage(aRowIndex, aColumn, true, twistyContext); if (!image) { return result;
}
nsPoint anchorPoint = twistyRect.TopLeft();
// Center the image. XXX Obey vertical-align style prop? if (imageSize.height < twistyRect.height) {
anchorPoint.y += (twistyRect.height - imageSize.height) / 2;
}
// Obtain the margins for the image and then deflate our rect by that // amount. The image is assumed to be contained within the deflated rect.
nsRect imageRect(aImageRect);
nsMargin imageMargin;
imageContext->StyleMargin()->GetMargin(imageMargin);
imageRect.Deflate(imageMargin);
// Get the image.
nsCOMPtr<imgIContainer> image =
GetImage(aRowIndex, aColumn, false, imageContext);
// Get the image destination size.
nsSize imageDestSize = GetImageDestSize(imageContext, image); if (!imageDestSize.width || !imageDestSize.height) { return ImgDrawResult::SUCCESS;
}
// Get the borders and padding.
nsMargin bp(0, 0, 0, 0);
GetBorderPadding(imageContext, bp);
// destRect will be passed as the aDestRect argument in the DrawImage method. // Start with the imageDestSize width and height.
nsRect destRect(0, 0, imageDestSize.width, imageDestSize.height); // Inflate destRect for borders and padding so that we can compare/adjust // with respect to imageRect.
destRect.Inflate(bp);
// The destRect width and height have not been adjusted to fit within the // cell width and height. // We must adjust the width even if image is null, because the width is used // to update the aRemainingWidth and aCurrX values. // Since the height isn't used unless the image is not null, we will adjust // the height inside the if (image) block below.
if (destRect.width > imageRect.width) { // The destRect is too wide to fit within the cell width. // Adjust destRect width to fit within the cell width.
destRect.width = imageRect.width;
} else { // The cell is wider than the destRect. // In a cycler column, the image is centered horizontally. if (!aColumn->IsCycler()) { // If this column is not a cycler, we won't center the image horizontally. // We adjust the imageRect width so that the image is placed at the start // of the cell.
imageRect.width = destRect.width;
}
}
ImgDrawResult result = ImgDrawResult::SUCCESS;
if (image) { if (isRTL) {
imageRect.x = rightEdge - imageRect.width;
} // Paint our borders and background for our image rect
result &= PaintBackgroundLayer(imageContext, aPresContext,
aRenderingContext, imageRect, aDirtyRect);
// The destRect x and y have not been set yet. Let's do that now. // Initially, we use the imageRect x and y.
destRect.x = imageRect.x;
destRect.y = imageRect.y;
if (destRect.width < imageRect.width) { // The destRect width is smaller than the cell width. // Center the image horizontally in the cell. // Adjust the destRect x accordingly.
destRect.x += (imageRect.width - destRect.width) / 2;
}
// Now it's time to adjust the destRect height to fit within the cell // height. if (destRect.height > imageRect.height) { // The destRect height is larger than the cell height. // Adjust destRect height to fit within the cell height.
destRect.height = imageRect.height;
} elseif (destRect.height < imageRect.height) { // The destRect height is smaller than the cell height. // Center the image vertically in the cell. // Adjust the destRect y accordingly.
destRect.y += (imageRect.height - destRect.height) / 2;
}
// It's almost time to paint the image. // Deflate destRect for the border and padding.
destRect.Deflate(bp);
// Compute the area where our whole image would be mapped, to get the // desired subregion onto our actual destRect:
nsRect wholeImageDest;
CSSIntSize rawImageCSSIntSize; if (NS_SUCCEEDED(image->GetWidth(&rawImageCSSIntSize.width)) &&
NS_SUCCEEDED(image->GetHeight(&rawImageCSSIntSize.height))) { // Get the image source rectangle - the rectangle containing the part of // the image that we are going to display. sourceRect will be passed as // the aSrcRect argument in the DrawImage method.
nsRect sourceRect = GetImageSourceRect(imageContext, image);
// Let's say that the image is 100 pixels tall and that the CSS has // specified that the destination height should be 50 pixels tall. Let's // say that the cell height is only 20 pixels. So, in those 20 visible // pixels, we want to see the top 20/50ths of the image. So, the // sourceRect.height should be 100 * 20 / 50, which is 40 pixels. // Essentially, we are scaling the image as dictated by the CSS // destination height and width, and we are then clipping the scaled // image by the cell width and height.
nsSize rawImageSize(CSSPixel::ToAppUnits(rawImageCSSIntSize));
wholeImageDest = nsLayoutUtils::GetWholeImageDestination(
rawImageSize, sourceRect, nsRect(destRect.TopLeft(), imageDestSize));
} else { // GetWidth/GetHeight failed, so we can't easily map a subregion of the // source image onto the destination area. // * If this happens with a RasterImage, it probably means the image is // in an error state, and we shouldn't draw anything. Hence, we leave // wholeImageDest as an empty rect (its initial state). // * If this happens with a VectorImage, it probably means the image has // no explicit width or height attribute -- but we can still proceed and // just treat the destination area as our whole SVG image area. Hence, we // set wholeImageDest to the full destRect. if (image->GetType() == imgIContainer::TYPE_VECTOR) {
wholeImageDest = destRect;
}
}
// Resolve style for the text. It contains all the info we need to lay // ourselves out and to paint.
ComputedStyle* textContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCellText());
// Obtain the margins for the text and then deflate our rect by that // amount. The text is assumed to be contained within the deflated rect.
nsRect textRect(aTextRect);
nsMargin textMargin;
textContext->StyleMargin()->GetMargin(textMargin);
textRect.Deflate(textMargin);
// Adjust the rect for its border and padding.
nsMargin bp(0, 0, 0, 0);
GetBorderPadding(textContext, bp);
textRect.Deflate(bp);
// Compute our text size.
RefPtr<nsFontMetrics> fontMet =
nsLayoutUtils::GetFontMetricsForComputedStyle(textContext, PresContext());
// Resolve style for the checkbox.
ComputedStyle* checkboxContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeCheckbox());
nscoord rightEdge = aCheckboxRect.XMost();
// Obtain the margins for the checkbox and then deflate our rect by that // amount. The checkbox is assumed to be contained within the deflated rect.
nsRect checkboxRect(aCheckboxRect);
nsMargin checkboxMargin;
checkboxContext->StyleMargin()->GetMargin(checkboxMargin);
checkboxRect.Deflate(checkboxMargin);
// Paint our borders and background for our image rect.
ImgDrawResult result =
PaintBackgroundLayer(checkboxContext, aPresContext, aRenderingContext,
checkboxRect, aDirtyRect);
// Time to paint the checkbox. // Adjust the rect for its border and padding.
nsMargin bp(0, 0, 0, 0);
GetBorderPadding(checkboxContext, bp);
checkboxRect.Deflate(bp);
// Get the image for drawing.
nsCOMPtr<imgIContainer> image =
GetImage(aRowIndex, aColumn, true, checkboxContext); if (image) {
nsPoint pt = checkboxRect.TopLeft();
// Resolve the style to use for the drop feedback.
ComputedStyle* feedbackContext =
GetPseudoComputedStyle(nsCSSAnonBoxes::mozTreeDropFeedback());
ImgDrawResult result = ImgDrawResult::SUCCESS;
// Paint only if it is visible.
nsCOMPtr<nsITreeView> view = GetExistingView(); if (feedbackContext->StyleVisibility()->IsVisibleOrCollapsed()) {
int32_t level;
view->GetLevel(mSlots->mDropRow, &level);
// If our previous or next row has greater level use that for // correct visual indentation. if (mSlots->mDropOrient == nsITreeView::DROP_BEFORE) { if (mSlots->mDropRow > 0) {
int32_t previousLevel;
view->GetLevel(mSlots->mDropRow - 1, &previousLevel); if (previousLevel > level) {
level = previousLevel;
}
}
} else { if (mSlots->mDropRow < mRowCount - 1) {
int32_t nextLevel;
view->GetLevel(mSlots->mDropRow + 1, &nextLevel); if (nextLevel > level) {
level = nextLevel;
}
}
}
// Obtain the width for the drop feedback or use default value.
nscoord width; if (stylePosition->GetWidth().ConvertsToLength()) {
width = stylePosition->GetWidth().ToLength();
} else { // Use default width 50px.
width = nsPresContext::CSSPixelsToAppUnits(50);
}
// Obtain the height for the drop feedback or use default value.
nscoord height; if (stylePosition->GetHeight().ConvertsToLength()) {
height = stylePosition->GetHeight().ToLength();
} else { // Use default height 2px.
height = nsPresContext::CSSPixelsToAppUnits(2);
}
// Obtain the margins for the drop feedback and then deflate our rect // by that amount.
nsRect feedbackRect(currX, aDropFeedbackRect.y, width, height);
nsMargin margin;
feedbackContext->StyleMargin()->GetMargin(margin);
feedbackRect.Deflate(margin);
// Note that we may be "over scrolled" at this point; that is the // current mTopRowIndex may be larger than mRowCount - mPageLength. // This can happen when items are removed for example. (bug 1085050)
// Clear the style cache; the pointers are no longer even valid
mStyleCache.Clear(); // XXX The following is hacky, but it's not incorrect, // and appears to fix a few bugs with style changes, like text zoom and // dpi changes
mIndentation = GetIndentation();
mRowHeight = GetRowHeight();
}
// Given a dom event, figure out which row in the tree the mouse is over, // if we should drop before/after/on that row or we should auto-scroll. // Doesn't query the content about if the drag is allowable, that's done // elsewhere. // // For containers, we break up the vertical space of the row as follows: if in // the topmost 25%, the drop is _before_ the row the mouse is over; if in the // last 25%, _after_; in the middle 50%, we consider it a drop _on_ the // container. // // For non-containers, if the mouse is in the top 50% of the row, the drop is // _before_ and the bottom 50% _after_ void nsTreeBodyFrame::ComputeDropPosition(WidgetGUIEvent* aEvent, int32_t* aRow,
int16_t* aOrient,
int16_t* aScrollLines) {
*aOrient = -1;
*aScrollLines = 0;
// Convert the event's point to our coordinates. We want it in // the coordinates of our inner box's coordinates.
nsPoint pt =
nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, RelativeTo{this});
int32_t xTwips = pt.x - mInnerBox.x;
int32_t yTwips = pt.y - mInnerBox.y;
nsCOMPtr<nsITreeView> view = GetExistingView();
*aRow = GetRowAtInternal(xTwips, yTwips); if (*aRow >= 0) { // Compute the top/bottom of the row in question.
int32_t yOffset = yTwips - mRowHeight * (*aRow - mTopRowIndex);
bool isContainer = false;
view->IsContainer(*aRow, &isContainer); if (isContainer) { // for a container, use a 25%/50%/25% breakdown if (yOffset < mRowHeight / 4) {
*aOrient = nsITreeView::DROP_BEFORE;
} elseif (yOffset > mRowHeight - (mRowHeight / 4)) {
*aOrient = nsITreeView::DROP_AFTER;
} else {
*aOrient = nsITreeView::DROP_ON;
}
} else { // for a non-container use a 50%/50% breakdown if (yOffset < mRowHeight / 2) {
*aOrient = nsITreeView::DROP_BEFORE;
} else {
*aOrient = nsITreeView::DROP_AFTER;
}
}
}
if (CanAutoScroll(*aRow)) { // Get the max value from the look and feel service.
int32_t scrollLinesMax =
LookAndFeel::GetInt(LookAndFeel::IntID::TreeScrollLinesMax, 0);
scrollLinesMax--; if (scrollLinesMax < 0) {
scrollLinesMax = 0;
}
// Determine if we're w/in a margin of the top/bottom of the tree during a // drag. This will ultimately cause us to scroll, but that's done elsewhere.
nscoord height = (3 * mRowHeight) / 4; if (yTwips < height) { // scroll up
*aScrollLines =
NSToIntRound(-scrollLinesMax * (1 - (float)yTwips / height) - 1);
} elseif (yTwips > mRect.height - height) { // scroll down
*aScrollLines = NSToIntRound(
scrollLinesMax * (1 - (float)(mRect.height - yTwips) / height) + 1);
}
}
} // ComputeDropPosition
if (self->mView) { // Set a new timer to scroll the tree repeatedly.
self->CreateTimer(LookAndFeel::IntID::TreeScrollDelay, ScrollCallback,
nsITimer::TYPE_REPEATING_SLACK,
getter_AddRefs(self->mSlots->mTimer), "nsTreeBodyFrame::ScrollCallback");
self->ScrollByLines(self->mSlots->mScrollLines); // ScrollByLines may have deleted |self|.
}
}
}
void nsTreeBodyFrame::ScrollCallback(nsITimer* aTimer, void* aClosure) {
nsTreeBodyFrame* self = static_cast<nsTreeBodyFrame*>(aClosure); if (self) { // Don't scroll if we are already at the top or bottom of the view. if (self->mView && self->CanAutoScroll(self->mSlots->mDropRow)) {
self->ScrollByLines(self->mSlots->mScrollLines);
} else {
aTimer->Cancel();
self->mSlots->mTimer = nullptr;
}
}
}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsTreeBodyFrame::ScrollEvent::Run() { if (mInner) {
mInner->FireScrollEvent();
} return NS_OK;
}
CustomEvent* treeEvent = event->AsCustomEvent(); if (!treeEvent) { return;
}
nsCOMPtr<nsIWritablePropertyBag2> propBag(
do_CreateInstance("@mozilla.org/hash-property-bag;1")); if (!propBag) { return;
}
if (aStartRowIdx != -1 && aEndRowIdx != -1) { // Set 'startrow' data - the start index of invalidated rows.
propBag->SetPropertyAsInt32(u"startrow"_ns, aStartRowIdx);
// Set 'endrow' data - the end index of invalidated rows.
propBag->SetPropertyAsInt32(u"endrow"_ns, aEndRowIdx);
}
if (aStartCol && aEndCol) { // Set 'startcolumn' data - the start index of invalidated rows.
int32_t startColIdx = aStartCol->GetIndex();
// Set 'endcolumn' data - the start index of invalidated rows.
int32_t endColIdx = aEndCol->GetIndex();
propBag->SetPropertyAsInt32(u"endcolumn"_ns, endColIdx);
}
// Overflow checking dispatches synchronous events, which can cause infinite // recursion during reflow. Do the first overflow check synchronously, but // force any nested checks to round-trip through the event loop. See bug // 905909.
RefPtr<nsOverflowChecker> checker = new nsOverflowChecker(this); if (!mCheckingOverflow) {
nsContentUtils::AddScriptRunner(checker);
} else {
mContent->OwnerDoc()->Dispatch(checker.forget());
} return weakFrame.IsAlive();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.