diff options
Diffstat (limited to 'layout/xul/nsTextBoxFrame.cpp')
-rw-r--r-- | layout/xul/nsTextBoxFrame.cpp | 1241 |
1 files changed, 1241 insertions, 0 deletions
diff --git a/layout/xul/nsTextBoxFrame.cpp b/layout/xul/nsTextBoxFrame.cpp new file mode 100644 index 0000000000..c82d3d6b9b --- /dev/null +++ b/layout/xul/nsTextBoxFrame.cpp @@ -0,0 +1,1241 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cindent: */ +/* 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/. */ + +#include "nsTextBoxFrame.h" + +#include "gfx2DGlue.h" +#include "gfxUtils.h" +#include "mozilla/gfx/2D.h" +#include "nsFontMetrics.h" +#include "nsReadableUtils.h" +#include "nsCOMPtr.h" +#include "nsGkAtoms.h" +#include "nsPresContext.h" +#include "nsRenderingContext.h" +#include "nsStyleContext.h" +#include "nsIContent.h" +#include "nsNameSpaceManager.h" +#include "nsBoxLayoutState.h" +#include "nsMenuBarListener.h" +#include "nsXPIDLString.h" +#include "nsIServiceManager.h" +#include "nsIDOMElement.h" +#include "nsIDOMXULLabelElement.h" +#include "mozilla/EventStateManager.h" +#include "nsITheme.h" +#include "nsUnicharUtils.h" +#include "nsContentUtils.h" +#include "nsDisplayList.h" +#include "nsCSSRendering.h" +#include "nsIReflowCallback.h" +#include "nsBoxFrame.h" +#include "mozilla/Preferences.h" +#include "nsLayoutUtils.h" +#include "mozilla/Attributes.h" +#include "nsUnicodeProperties.h" + +#ifdef ACCESSIBILITY +#include "nsAccessibilityService.h" +#endif + +#include "nsBidiUtils.h" +#include "nsBidiPresUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +class nsAccessKeyInfo +{ +public: + int32_t mAccesskeyIndex; + nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset; +}; + + +bool nsTextBoxFrame::gAlwaysAppendAccessKey = false; +bool nsTextBoxFrame::gAccessKeyPrefInitialized = false; +bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false; +bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false; + +nsIFrame* +NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) +{ + return new (aPresShell) nsTextBoxFrame(aContext); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame) + +NS_QUERYFRAME_HEAD(nsTextBoxFrame) + NS_QUERYFRAME_ENTRY(nsTextBoxFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame) + +nsresult +nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID, + nsIAtom* aAttribute, + int32_t aModType) +{ + bool aResize; + bool aRedraw; + + UpdateAttributes(aAttribute, aResize, aRedraw); + + if (aResize) { + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + } else if (aRedraw) { + nsBoxLayoutState state(PresContext()); + XULRedraw(state); + } + + // If the accesskey changed, register for the new value + // The old value has been unregistered in nsXULElement::SetAttr + if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control) + RegUnregAccessKey(true); + + return NS_OK; +} + +nsTextBoxFrame::nsTextBoxFrame(nsStyleContext* aContext): + nsLeafBoxFrame(aContext), mAccessKeyInfo(nullptr), mCropType(CropRight), + mNeedsReflowCallback(false) +{ + MarkIntrinsicISizesDirty(); +} + +nsTextBoxFrame::~nsTextBoxFrame() +{ + delete mAccessKeyInfo; +} + + +void +nsTextBoxFrame::Init(nsIContent* aContent, + nsContainerFrame* aParent, + nsIFrame* aPrevInFlow) +{ + nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow); + + bool aResize; + bool aRedraw; + UpdateAttributes(nullptr, aResize, aRedraw); /* update all */ + + // register access key + RegUnregAccessKey(true); +} + +void +nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) +{ + // unregister access key + RegUnregAccessKey(false); + nsLeafBoxFrame::DestroyFrom(aDestructRoot); +} + +bool +nsTextBoxFrame::AlwaysAppendAccessKey() +{ + if (!gAccessKeyPrefInitialized) + { + gAccessKeyPrefInitialized = true; + + const char* prefName = "intl.menuitems.alwaysappendaccesskeys"; + nsAdoptingString val = Preferences::GetLocalizedString(prefName); + gAlwaysAppendAccessKey = val.EqualsLiteral("true"); + } + return gAlwaysAppendAccessKey; +} + +bool +nsTextBoxFrame::InsertSeparatorBeforeAccessKey() +{ + if (!gInsertSeparatorPrefInitialized) + { + gInsertSeparatorPrefInitialized = true; + + const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys"; + nsAdoptingString val = Preferences::GetLocalizedString(prefName); + gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true"); + } + return gInsertSeparatorBeforeAccessKey; +} + +class nsAsyncAccesskeyUpdate final : public nsIReflowCallback +{ +public: + explicit nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) + { + } + + virtual bool ReflowFinished() override + { + bool shouldFlush = false; + nsTextBoxFrame* frame = + static_cast<nsTextBoxFrame*>(mWeakFrame.GetFrame()); + if (frame) { + shouldFlush = frame->UpdateAccesskey(mWeakFrame); + } + delete this; + return shouldFlush; + } + + virtual void ReflowCallbackCanceled() override + { + delete this; + } + + nsWeakFrame mWeakFrame; +}; + +bool +nsTextBoxFrame::UpdateAccesskey(nsWeakFrame& aWeakThis) +{ + nsAutoString accesskey; + nsCOMPtr<nsIDOMXULLabelElement> labelElement = do_QueryInterface(mContent); + NS_ENSURE_TRUE(aWeakThis.IsAlive(), false); + if (labelElement) { + // Accesskey may be stored on control. + labelElement->GetAccessKey(accesskey); + NS_ENSURE_TRUE(aWeakThis.IsAlive(), false); + } + else { + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey); + } + + if (!accesskey.Equals(mAccessKey)) { + // Need to get clean mTitle. + RecomputeTitle(); + mAccessKey = accesskey; + UpdateAccessTitle(); + PresContext()->PresShell()-> + FrameNeedsReflow(this, nsIPresShell::eStyleChange, + NS_FRAME_IS_DIRTY); + return true; + } + return false; +} + +void +nsTextBoxFrame::UpdateAttributes(nsIAtom* aAttribute, + bool& aResize, + bool& aRedraw) +{ + bool doUpdateTitle = false; + aResize = false; + aRedraw = false; + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) { + static nsIContent::AttrValuesArray strings[] = + {&nsGkAtoms::left, &nsGkAtoms::start, &nsGkAtoms::center, + &nsGkAtoms::right, &nsGkAtoms::end, &nsGkAtoms::none, nullptr}; + CroppingStyle cropType; + switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop, + strings, eCaseMatters)) { + case 0: + case 1: + cropType = CropLeft; + break; + case 2: + cropType = CropCenter; + break; + case 3: + case 4: + cropType = CropRight; + break; + case 5: + cropType = CropNone; + break; + default: + cropType = CropAuto; + break; + } + + if (cropType != mCropType) { + aResize = true; + mCropType = cropType; + } + } + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) { + RecomputeTitle(); + doUpdateTitle = true; + } + + if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) { + mNeedsReflowCallback = true; + // Ensure that layout is refreshed and reflow callback called. + aResize = true; + } + + if (doUpdateTitle) { + UpdateAccessTitle(); + aResize = true; + } + +} + +class nsDisplayXULTextBox : public nsDisplayItem { +public: + nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, + nsTextBoxFrame* aFrame) : + nsDisplayItem(aBuilder, aFrame), + mDisableSubpixelAA(false) + { + MOZ_COUNT_CTOR(nsDisplayXULTextBox); + } +#ifdef NS_BUILD_REFCNT_LOGGING + virtual ~nsDisplayXULTextBox() { + MOZ_COUNT_DTOR(nsDisplayXULTextBox); + } +#endif + + virtual void Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) override; + virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) override; + NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX) + + virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) override; + + virtual void DisableComponentAlpha() override { + mDisableSubpixelAA = true; + } + + void PaintTextToContext(nsRenderingContext* aCtx, + nsPoint aOffset, + const nscolor* aColor); + + bool mDisableSubpixelAA; +}; + +static void +PaintTextShadowCallback(nsRenderingContext* aCtx, + nsPoint aShadowOffset, + const nscolor& aShadowColor, + void* aData) +{ + reinterpret_cast<nsDisplayXULTextBox*>(aData)-> + PaintTextToContext(aCtx, aShadowOffset, &aShadowColor); +} + +void +nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx) +{ + DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(), + mDisableSubpixelAA); + + // Paint the text shadow before doing any foreground stuff + nsRect drawRect = static_cast<nsTextBoxFrame*>(mFrame)->mTextDrawRect + + ToReferenceFrame(); + nsLayoutUtils::PaintTextShadow(mFrame, aCtx, + drawRect, mVisibleRect, + mFrame->StyleColor()->mColor, + PaintTextShadowCallback, + (void*)this); + + PaintTextToContext(aCtx, nsPoint(0, 0), nullptr); +} + +void +nsDisplayXULTextBox::PaintTextToContext(nsRenderingContext* aCtx, + nsPoint aOffset, + const nscolor* aColor) +{ + static_cast<nsTextBoxFrame*>(mFrame)-> + PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor); +} + +nsRect +nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { + *aSnap = false; + return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +nsRect +nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) +{ + return static_cast<nsTextBoxFrame*>(mFrame)->GetComponentAlphaBounds() + + ToReferenceFrame(); +} + +void +nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, + const nsRect& aDirtyRect, + const nsDisplayListSet& aLists) +{ + if (!IsVisibleForPainting(aBuilder)) + return; + + nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); + + aLists.Content()->AppendNewToTop(new (aBuilder) + nsDisplayXULTextBox(aBuilder, this)); +} + +void +nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + nsPoint aPt, + const nscolor* aOverrideColor) +{ + if (mTitle.IsEmpty()) + return; + + DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor); +} + +void +nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext, + const nsRect& aDirtyRect, + const nsRect& aTextRect, + const nscolor* aOverrideColor) +{ + nsPresContext* presContext = PresContext(); + int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + // paint the title + nscolor overColor = 0; + nscolor underColor = 0; + nscolor strikeColor = 0; + uint8_t overStyle = 0; + uint8_t underStyle = 0; + uint8_t strikeStyle = 0; + + // Begin with no decorations + uint8_t decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE; + // A mask of all possible decorations. + uint8_t decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK; + + WritingMode wm = GetWritingMode(); + bool vertical = wm.IsVertical(); + + nsIFrame* f = this; + do { // find decoration colors + nsStyleContext* context = f->StyleContext(); + if (!context->HasTextDecorationLines()) { + break; + } + const nsStyleTextReset* styleText = context->StyleTextReset(); + + if (decorMask & styleText->mTextDecorationLine) { // a decoration defined here + nscolor color; + if (aOverrideColor) { + color = *aOverrideColor; + } else { + color = context->StyleColor()-> + CalcComplexColor(styleText->mTextDecorationColor); + } + uint8_t style = styleText->mTextDecorationStyle; + + if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decorMask & + styleText->mTextDecorationLine) { + underColor = color; + underStyle = style; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + decorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + } + if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decorMask & + styleText->mTextDecorationLine) { + overColor = color; + overStyle = style; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; + decorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; + } + if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decorMask & + styleText->mTextDecorationLine) { + strikeColor = color; + strikeStyle = style; + decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; + decorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; + } + } + } while (0 != decorMask && + (f = nsLayoutUtils::GetParentOrPlaceholderFor(f))); + + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + fontMet->SetVertical(wm.IsVertical()); + fontMet->SetTextOrientation(StyleVisibility()->mTextOrientation); + + nscoord offset; + nscoord size; + nscoord ascent = fontMet->MaxAscent(); + + nsPoint baselinePt; + if (wm.IsVertical()) { + baselinePt.x = + presContext->RoundAppUnitsToNearestDevPixels(aTextRect.x + + (wm.IsVerticalRL() ? aTextRect.width - ascent : ascent)); + baselinePt.y = aTextRect.y; + } else { + baselinePt.x = aTextRect.x; + baselinePt.y = + presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent); + } + + nsCSSRendering::PaintDecorationLineParams params; + params.dirtyRect = ToRect(presContext->AppUnitsToGfxUnits(aDirtyRect)); + params.pt = Point(presContext->AppUnitsToGfxUnits(aTextRect.x), + presContext->AppUnitsToGfxUnits(aTextRect.y)); + params.icoordInFrame = + Float(PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x)); + params.lineSize = Size(presContext->AppUnitsToGfxUnits(aTextRect.width), 0); + params.ascent = presContext->AppUnitsToGfxUnits(ascent); + params.vertical = vertical; + + // XXX todo: vertical-mode support for decorations not tested yet, + // probably won't be positioned correctly + + // Underlines are drawn before overlines, and both before the text + // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1. + // (We don't apply this rule to the access-key underline because we only + // find out where that is as a side effect of drawing the text, in the + // general case -- see below.) + if (decorations & (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE | + NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE)) { + fontMet->GetUnderline(offset, size); + params.lineSize.height = presContext->AppUnitsToGfxUnits(size); + if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE) && + underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + params.color = underColor; + params.offset = presContext->AppUnitsToGfxUnits(offset); + params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; + params.style = underStyle; + nsCSSRendering::PaintDecorationLine(this, *drawTarget, params); + } + if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_OVERLINE) && + overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + params.color = overColor; + params.offset = params.ascent; + params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; + params.style = overStyle; + nsCSSRendering::PaintDecorationLine(this, *drawTarget, params); + } + } + + nsRenderingContext refContext( + PresContext()->PresShell()->CreateReferenceRenderingContext()); + DrawTarget* refDrawTarget = refContext.GetDrawTarget(); + + CalculateUnderline(refDrawTarget, *fontMet); + + nscolor c = aOverrideColor ? *aOverrideColor : StyleColor()->mColor; + ColorPattern color(ToDeviceColor(c)); + aRenderingContext.ThebesContext()->SetColor(Color::FromABGR(c)); + + nsresult rv = NS_ERROR_FAILURE; + + if (mState & NS_FRAME_IS_BIDI) { + presContext->SetBidiEnabled(); + nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(StyleContext()); + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // We let the RenderText function calculate the mnemonic's + // underline position for us. + nsBidiPositionResolve posResolve; + posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex; + rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level, + presContext, aRenderingContext, + refDrawTarget, *fontMet, + baselinePt.x, baselinePt.y, + &posResolve, + 1); + mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips; + mAccessKeyInfo->mAccessWidth = posResolve.visualWidth; + } + else + { + rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level, + presContext, aRenderingContext, + refDrawTarget, *fontMet, + baselinePt.x, baselinePt.y); + } + } + if (NS_FAILED(rv)) { + fontMet->SetTextRunRTL(false); + + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // In the simple (non-BiDi) case, we calculate the mnemonic's + // underline position by getting the text metric. + // XXX are attribute values always two byte? + if (mAccessKeyInfo->mAccesskeyIndex > 0) + mAccessKeyInfo->mBeforeWidth = nsLayoutUtils:: + AppUnitWidthOfString(mCroppedTitle.get(), + mAccessKeyInfo->mAccesskeyIndex, + *fontMet, refDrawTarget); + else + mAccessKeyInfo->mBeforeWidth = 0; + } + + fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(), + baselinePt.x, baselinePt.y, &aRenderingContext, + refDrawTarget); + } + + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + nsRect r(aTextRect.x + mAccessKeyInfo->mBeforeWidth, + aTextRect.y + mAccessKeyInfo->mAccessOffset, + mAccessKeyInfo->mAccessWidth, + mAccessKeyInfo->mAccessUnderlineSize); + Rect devPxRect = + NSRectToSnappedRect(r, appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(devPxRect, color); + } + + // Strikeout is drawn on top of the text, per + // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1. + if ((decorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) && + strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { + fontMet->GetStrikeout(offset, size); + params.color = strikeColor; + params.lineSize.height = presContext->AppUnitsToGfxUnits(size); + params.offset = presContext->AppUnitsToGfxUnits(offset); + params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; + params.style = strikeStyle; + nsCSSRendering::PaintDecorationLine(this, *drawTarget, params); + } +} + +void +nsTextBoxFrame::CalculateUnderline(DrawTarget* aDrawTarget, + nsFontMetrics& aFontMetrics) +{ + if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { + // Calculate all fields of mAccessKeyInfo which + // are the same for both BiDi and non-BiDi frames. + const char16_t *titleString = mCroppedTitle.get(); + aFontMetrics.SetTextRunRTL(false); + mAccessKeyInfo->mAccessWidth = nsLayoutUtils:: + AppUnitWidthOfString(titleString[mAccessKeyInfo->mAccesskeyIndex], + aFontMetrics, aDrawTarget); + + nscoord offset, baseline; + aFontMetrics.GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize); + baseline = aFontMetrics.MaxAscent(); + mAccessKeyInfo->mAccessOffset = baseline - offset; + } +} + +nscoord +nsTextBoxFrame::CalculateTitleForWidth(nsRenderingContext& aRenderingContext, + nscoord aWidth) +{ + DrawTarget* drawTarget = aRenderingContext.GetDrawTarget(); + + if (mTitle.IsEmpty()) { + mCroppedTitle.Truncate(); + return 0; + } + + RefPtr<nsFontMetrics> fm = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + + // see if the text will completely fit in the width given + nscoord titleWidth = + nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm, + aRenderingContext); + if (titleWidth <= aWidth) { + mCroppedTitle = mTitle; + if (HasRTLChars(mTitle) || + StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + mState |= NS_FRAME_IS_BIDI; + } + return titleWidth; // fits, done. + } + + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + if (mCropType != CropNone) { + // start with an ellipsis + mCroppedTitle.Assign(kEllipsis); + + // see if the width is even smaller than the ellipsis + // if so, clear the text (XXX set as many '.' as we can?). + fm->SetTextRunRTL(false); + titleWidth = nsLayoutUtils::AppUnitWidthOfString(kEllipsis, *fm, + drawTarget); + + if (titleWidth > aWidth) { + mCroppedTitle.SetLength(0); + return 0; + } + + // if the ellipsis fits perfectly, no use in trying to insert + if (titleWidth == aWidth) + return titleWidth; + + aWidth -= titleWidth; + } else { + mCroppedTitle.Truncate(0); + titleWidth = 0; + } + + using mozilla::unicode::ClusterIterator; + using mozilla::unicode::ClusterReverseIterator; + + // ok crop things + switch (mCropType) + { + case CropAuto: + case CropNone: + case CropRight: + { + ClusterIterator iter(mTitle.Data(), mTitle.Length()); + const char16_t* dataBegin = iter; + const char16_t* pos = dataBegin; + nscoord charWidth; + nscoord totalWidth = 0; + + while (!iter.AtEnd()) { + iter.Next(); + const char16_t* nextPos = iter; + ptrdiff_t length = nextPos - pos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*pos)) { + mState |= NS_FRAME_IS_BIDI; + } + pos = nextPos; + totalWidth += charWidth; + } + + if (pos == dataBegin) { + return titleWidth; + } + + // insert what character we can in. + nsAutoString title(mTitle); + title.Truncate(pos - dataBegin); + mCroppedTitle.Insert(title, 0); + } + break; + + case CropLeft: + { + ClusterReverseIterator iter(mTitle.Data(), mTitle.Length()); + const char16_t* dataEnd = iter; + const char16_t* prevPos = dataEnd; + nscoord charWidth; + nscoord totalWidth = 0; + + while (!iter.AtEnd()) { + iter.Next(); + const char16_t* pos = iter; + ptrdiff_t length = prevPos - pos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*pos)) { + mState |= NS_FRAME_IS_BIDI; + } + prevPos = pos; + totalWidth += charWidth; + } + + if (prevPos == dataEnd) { + return titleWidth; + } + + nsAutoString copy; + mTitle.Right(copy, dataEnd - prevPos); + mCroppedTitle += copy; + } + break; + + case CropCenter: + { + nscoord stringWidth = + nsLayoutUtils::AppUnitWidthOfStringBidi(mTitle, this, *fm, + aRenderingContext); + if (stringWidth <= aWidth) { + // the entire string will fit in the maximum width + mCroppedTitle.Insert(mTitle, 0); + break; + } + + // determine how much of the string will fit in the max width + nscoord charWidth = 0; + nscoord totalWidth = 0; + ClusterIterator leftIter(mTitle.Data(), mTitle.Length()); + ClusterReverseIterator rightIter(mTitle.Data(), mTitle.Length()); + const char16_t* dataBegin = leftIter; + const char16_t* dataEnd = rightIter; + const char16_t* leftPos = dataBegin; + const char16_t* rightPos = dataEnd; + const char16_t* pos; + ptrdiff_t length; + nsAutoString leftString, rightString; + + while (leftPos < rightPos) { + leftIter.Next(); + pos = leftIter; + length = pos - leftPos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(leftPos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*leftPos)) { + mState |= NS_FRAME_IS_BIDI; + } + + leftString.Append(leftPos, length); + leftPos = pos; + totalWidth += charWidth; + + if (leftPos >= rightPos) { + break; + } + + rightIter.Next(); + pos = rightIter; + length = rightPos - pos; + charWidth = nsLayoutUtils::AppUnitWidthOfString(pos, length, + *fm, + drawTarget); + if (totalWidth + charWidth > aWidth) { + break; + } + + if (UCS2_CHAR_IS_BIDI(*pos)) { + mState |= NS_FRAME_IS_BIDI; + } + + rightString.Insert(pos, 0, length); + rightPos = pos; + totalWidth += charWidth; + } + + mCroppedTitle = leftString + kEllipsis + rightString; + } + break; + } + + return nsLayoutUtils::AppUnitWidthOfStringBidi(mCroppedTitle, this, *fm, + aRenderingContext); +} + +#define OLD_ELLIPSIS NS_LITERAL_STRING("...") + +// the following block is to append the accesskey to mTitle if there is an accesskey +// but the mTitle doesn't have the character +void +nsTextBoxFrame::UpdateAccessTitle() +{ + /* + * Note that if you change appending access key label spec, + * you need to maintain same logic in following methods. See bug 324159. + * toolkit/content/commonDialog.js (setLabelForNode) + * toolkit/content/widgets/text.xml (formatAccessKey) + */ + int32_t menuAccessKey; + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); + if (!menuAccessKey || mAccessKey.IsEmpty()) + return; + + if (!AlwaysAppendAccessKey() && + FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator())) + return; + + nsAutoString accessKeyLabel; + accessKeyLabel += '('; + accessKeyLabel += mAccessKey; + ToUpperCase(accessKeyLabel); + accessKeyLabel += ')'; + + if (mTitle.IsEmpty()) { + mTitle = accessKeyLabel; + return; + } + + const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); + uint32_t offset = mTitle.Length(); + if (StringEndsWith(mTitle, kEllipsis)) { + offset -= kEllipsis.Length(); + } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) { + // Try to check with our old ellipsis (for old addons) + offset -= OLD_ELLIPSIS.Length(); + } else { + // Try to check with + // our default ellipsis (for non-localized addons) or ':' + const char16_t kLastChar = mTitle.Last(); + if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':')) + offset--; + } + + if (InsertSeparatorBeforeAccessKey() && + offset > 0 && !NS_IS_SPACE(mTitle[offset - 1])) { + mTitle.Insert(' ', offset); + offset++; + } + + mTitle.Insert(accessKeyLabel, offset); +} + +void +nsTextBoxFrame::UpdateAccessIndex() +{ + int32_t menuAccessKey; + nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); + if (menuAccessKey) { + if (mAccessKey.IsEmpty()) { + if (mAccessKeyInfo) { + delete mAccessKeyInfo; + mAccessKeyInfo = nullptr; + } + } else { + if (!mAccessKeyInfo) { + mAccessKeyInfo = new nsAccessKeyInfo(); + if (!mAccessKeyInfo) + return; + } + + nsAString::const_iterator start, end; + + mCroppedTitle.BeginReading(start); + mCroppedTitle.EndReading(end); + + // remember the beginning of the string + nsAString::const_iterator originalStart = start; + + bool found; + if (!AlwaysAppendAccessKey()) { + // not appending access key - do case-sensitive search + // first + found = FindInReadable(mAccessKey, start, end); + if (!found) { + // didn't find it - perform a case-insensitive search + start = originalStart; + found = FindInReadable(mAccessKey, start, end, + nsCaseInsensitiveStringComparator()); + } + } else { + found = RFindInReadable(mAccessKey, start, end, + nsCaseInsensitiveStringComparator()); + } + + if (found) + mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start); + else + mAccessKeyInfo->mAccesskeyIndex = kNotFound; + } + } +} + +void +nsTextBoxFrame::RecomputeTitle() +{ + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle); + + // This doesn't handle language-specific uppercasing/lowercasing + // rules, unlike textruns. + uint8_t textTransform = StyleText()->mTextTransform; + if (textTransform == NS_STYLE_TEXT_TRANSFORM_UPPERCASE) { + ToUpperCase(mTitle); + } else if (textTransform == NS_STYLE_TEXT_TRANSFORM_LOWERCASE) { + ToLowerCase(mTitle); + } + // We can't handle NS_STYLE_TEXT_TRANSFORM_CAPITALIZE because we + // have no clue about word boundaries here. We also don't handle + // NS_STYLE_TEXT_TRANSFORM_FULL_WIDTH. +} + +void +nsTextBoxFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) +{ + if (!aOldStyleContext) { + // We're just being initialized + return; + } + + const nsStyleText* oldTextStyle = aOldStyleContext->PeekStyleText(); + // We should really have oldTextStyle here, since we asked for our + // nsStyleText during Init(), but if it's not there for some reason + // just assume the worst and recompute mTitle. + if (!oldTextStyle || + oldTextStyle->mTextTransform != StyleText()->mTextTransform) { + RecomputeTitle(); + UpdateAccessTitle(); + } +} + +NS_IMETHODIMP +nsTextBoxFrame::DoXULLayout(nsBoxLayoutState& aBoxLayoutState) +{ + if (mNeedsReflowCallback) { + nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this); + if (cb) { + PresContext()->PresShell()->PostReflowCallback(cb); + } + mNeedsReflowCallback = false; + } + + nsresult rv = nsLeafBoxFrame::DoXULLayout(aBoxLayoutState); + + CalcDrawRect(*aBoxLayoutState.GetRenderingContext()); + + const nsStyleText* textStyle = StyleText(); + + nsRect scrollBounds(nsPoint(0, 0), GetSize()); + nsRect textRect = mTextDrawRect; + + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + nsBoundingMetrics metrics = + fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(), + mCroppedTitle.Length(), + aBoxLayoutState.GetRenderingContext()->GetDrawTarget()); + + WritingMode wm = GetWritingMode(); + LogicalRect tr(wm, textRect, GetSize()); + + tr.IStart(wm) -= metrics.leftBearing; + tr.ISize(wm) = metrics.width; + // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect), + tr.BStart(wm) += fontMet->MaxAscent() - metrics.ascent; + tr.BSize(wm) = metrics.ascent + metrics.descent; + + textRect = tr.GetPhysicalRect(wm, GetSize()); + + // Our scrollable overflow is our bounds; our visual overflow may + // extend beyond that. + nsRect visualBounds; + visualBounds.UnionRect(scrollBounds, textRect); + nsOverflowAreas overflow(visualBounds, scrollBounds); + + if (textStyle->mTextShadow) { + // text-shadow extends our visual but not scrollable bounds + nsRect &vis = overflow.VisualOverflow(); + vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this)); + } + FinishAndStoreOverflow(overflow, GetSize()); + + return rv; +} + +nsRect +nsTextBoxFrame::GetComponentAlphaBounds() +{ + if (StyleText()->mTextShadow) { + return GetVisualOverflowRectRelativeToSelf(); + } + return mTextDrawRect; +} + +bool +nsTextBoxFrame::ComputesOwnOverflowArea() +{ + return true; +} + +/* virtual */ void +nsTextBoxFrame::MarkIntrinsicISizesDirty() +{ + mNeedsRecalc = true; + nsLeafBoxFrame::MarkIntrinsicISizesDirty(); +} + +void +nsTextBoxFrame::GetTextSize(nsRenderingContext& aRenderingContext, + const nsString& aString, + nsSize& aSize, nscoord& aAscent) +{ + RefPtr<nsFontMetrics> fontMet = + nsLayoutUtils::GetFontMetricsForFrame(this, 1.0f); + aSize.height = fontMet->MaxHeight(); + aSize.width = + nsLayoutUtils::AppUnitWidthOfStringBidi(aString, this, *fontMet, + aRenderingContext); + aAscent = fontMet->MaxAscent(); +} + +void +nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) +{ + if (mNeedsRecalc) { + nsSize size; + nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext(); + if (rendContext) { + GetTextSize(*rendContext, mTitle, size, mAscent); + if (GetWritingMode().IsVertical()) { + Swap(size.width, size.height); + } + mTextSize = size; + mNeedsRecalc = false; + } + } +} + +void +nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext) +{ + WritingMode wm = GetWritingMode(); + + LogicalRect textRect(wm, LogicalPoint(wm, 0, 0), GetLogicalSize(wm)); + nsMargin borderPadding; + GetXULBorderAndPadding(borderPadding); + textRect.Deflate(wm, LogicalMargin(wm, borderPadding)); + + // determine (cropped) title and underline position + // determine (cropped) title which fits in aRect, and its width + // (where "width" is the text measure along its baseline, i.e. actually + // a physical height in vertical writing modes) + nscoord titleWidth = + CalculateTitleForWidth(aRenderingContext, textRect.ISize(wm)); + +#ifdef ACCESSIBILITY + // Make sure to update the accessible tree in case when cropped title is + // changed. + nsAccessibilityService* accService = GetAccService(); + if (accService) { + accService->UpdateLabelValue(PresContext()->PresShell(), mContent, + mCroppedTitle); + } +#endif + + // determine if and at which position to put the underline + UpdateAccessIndex(); + + // make the rect as small as our (cropped) text. + nscoord outerISize = textRect.ISize(wm); + textRect.ISize(wm) = titleWidth; + + // Align our text within the overall rect by checking our text-align property. + const nsStyleText* textStyle = StyleText(); + if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER) { + textRect.IStart(wm) += (outerISize - textRect.ISize(wm)) / 2; + } else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END || + (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_LEFT && + !wm.IsBidiLTR()) || + (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT && + wm.IsBidiLTR())) { + textRect.IStart(wm) += (outerISize - textRect.ISize(wm)); + } + + mTextDrawRect = textRect.GetPhysicalRect(wm, GetSize()); +} + +/** + * Ok return our dimensions + */ +nsSize +nsTextBoxFrame::GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nsSize size = mTextSize; + DISPLAY_PREF_SIZE(this, size); + + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddXULPrefSize(this, size, widthSet, heightSet); + + return size; +} + +/** + * Ok return our dimensions + */ +nsSize +nsTextBoxFrame::GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nsSize size = mTextSize; + DISPLAY_MIN_SIZE(this, size); + + // if there is cropping our min width becomes our border and padding + if (mCropType != CropNone && mCropType != CropAuto) { + if (GetWritingMode().IsVertical()) { + size.height = 0; + } else { + size.width = 0; + } + } + + AddBorderAndPadding(size); + bool widthSet, heightSet; + nsIFrame::AddXULMinSize(aBoxLayoutState, this, size, widthSet, heightSet); + + return size; +} + +nscoord +nsTextBoxFrame::GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) +{ + CalcTextSize(aBoxLayoutState); + + nscoord ascent = mAscent; + + nsMargin m(0,0,0,0); + GetXULBorderAndPadding(m); + + WritingMode wm = GetWritingMode(); + ascent += LogicalMargin(wm, m).BStart(wm); + + return ascent; +} + +#ifdef DEBUG_FRAME_DUMP +nsresult +nsTextBoxFrame::GetFrameName(nsAString& aResult) const +{ + MakeFrameName(NS_LITERAL_STRING("TextBox"), aResult); + aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]"); + return NS_OK; +} +#endif + +// If you make changes to this function, check its counterparts +// in nsBoxFrame and nsXULLabelFrame +nsresult +nsTextBoxFrame::RegUnregAccessKey(bool aDoReg) +{ + // if we have no content, we can't do anything + if (!mContent) + return NS_ERROR_FAILURE; + + // check if we have a |control| attribute + // do this check first because few elements have control attributes, and we + // can weed out most of the elements quickly. + + // XXXjag a side-effect is that we filter out anonymous <label>s + // in e.g. <menu>, <menuitem>, <button>. These <label>s inherit + // |accesskey| and would otherwise register themselves, overwriting + // the content we really meant to be registered. + if (!mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::control)) + return NS_OK; + + // see if we even have an access key + nsAutoString accessKey; + mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey); + + if (accessKey.IsEmpty()) + return NS_OK; + + // With a valid PresContext we can get the ESM + // and (un)register the access key + EventStateManager* esm = PresContext()->EventStateManager(); + + uint32_t key = accessKey.First(); + if (aDoReg) + esm->RegisterAccessKey(mContent, key); + else + esm->UnregisterAccessKey(mContent, key); + + return NS_OK; +} |