michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsTextBoxFrame.h" michael@0: michael@0: #include "nsReadableUtils.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsStyleContext.h" michael@0: #include "nsIContent.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsBoxLayoutState.h" michael@0: #include "nsMenuBarListener.h" michael@0: #include "nsXPIDLString.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMXULLabelElement.h" michael@0: #include "mozilla/EventStateManager.h" michael@0: #include "nsITheme.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsDisplayList.h" michael@0: #include "nsCSSRendering.h" michael@0: #include "nsIReflowCallback.h" michael@0: #include "nsBoxFrame.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsLayoutUtils.h" michael@0: #include "mozilla/Attributes.h" michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: #include "nsAccessibilityService.h" michael@0: #endif michael@0: michael@0: #include "nsBidiUtils.h" michael@0: #include "nsBidiPresUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: class nsAccessKeyInfo michael@0: { michael@0: public: michael@0: int32_t mAccesskeyIndex; michael@0: nscoord mBeforeWidth, mAccessWidth, mAccessUnderlineSize, mAccessOffset; michael@0: }; michael@0: michael@0: michael@0: bool nsTextBoxFrame::gAlwaysAppendAccessKey = false; michael@0: bool nsTextBoxFrame::gAccessKeyPrefInitialized = false; michael@0: bool nsTextBoxFrame::gInsertSeparatorBeforeAccessKey = false; michael@0: bool nsTextBoxFrame::gInsertSeparatorPrefInitialized = false; michael@0: michael@0: nsIFrame* michael@0: NS_NewTextBoxFrame (nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsTextBoxFrame (aPresShell, aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsTextBoxFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsTextBoxFrame) michael@0: NS_QUERYFRAME_ENTRY(nsTextBoxFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsTextBoxFrameSuper) michael@0: michael@0: nsresult michael@0: nsTextBoxFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: bool aResize; michael@0: bool aRedraw; michael@0: michael@0: UpdateAttributes(aAttribute, aResize, aRedraw); michael@0: michael@0: if (aResize) { michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: } else if (aRedraw) { michael@0: nsBoxLayoutState state(PresContext()); michael@0: Redraw(state); michael@0: } michael@0: michael@0: // If the accesskey changed, register for the new value michael@0: // The old value has been unregistered in nsXULElement::SetAttr michael@0: if (aAttribute == nsGkAtoms::accesskey || aAttribute == nsGkAtoms::control) michael@0: RegUnregAccessKey(true); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsTextBoxFrame::nsTextBoxFrame(nsIPresShell* aShell, nsStyleContext* aContext): michael@0: nsLeafBoxFrame(aShell, aContext), mAccessKeyInfo(nullptr), mCropType(CropRight), michael@0: mNeedsReflowCallback(false) michael@0: { michael@0: MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: nsTextBoxFrame::~nsTextBoxFrame() michael@0: { michael@0: delete mAccessKeyInfo; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsTextBoxFrame::Init(nsIContent* aContent, michael@0: nsIFrame* aParent, michael@0: nsIFrame* aPrevInFlow) michael@0: { michael@0: nsTextBoxFrameSuper::Init(aContent, aParent, aPrevInFlow); michael@0: michael@0: bool aResize; michael@0: bool aRedraw; michael@0: UpdateAttributes(nullptr, aResize, aRedraw); /* update all */ michael@0: michael@0: // register access key michael@0: RegUnregAccessKey(true); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: // unregister access key michael@0: RegUnregAccessKey(false); michael@0: nsTextBoxFrameSuper::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: bool michael@0: nsTextBoxFrame::AlwaysAppendAccessKey() michael@0: { michael@0: if (!gAccessKeyPrefInitialized) michael@0: { michael@0: gAccessKeyPrefInitialized = true; michael@0: michael@0: const char* prefName = "intl.menuitems.alwaysappendaccesskeys"; michael@0: nsAdoptingString val = Preferences::GetLocalizedString(prefName); michael@0: gAlwaysAppendAccessKey = val.Equals(NS_LITERAL_STRING("true")); michael@0: } michael@0: return gAlwaysAppendAccessKey; michael@0: } michael@0: michael@0: bool michael@0: nsTextBoxFrame::InsertSeparatorBeforeAccessKey() michael@0: { michael@0: if (!gInsertSeparatorPrefInitialized) michael@0: { michael@0: gInsertSeparatorPrefInitialized = true; michael@0: michael@0: const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys"; michael@0: nsAdoptingString val = Preferences::GetLocalizedString(prefName); michael@0: gInsertSeparatorBeforeAccessKey = val.EqualsLiteral("true"); michael@0: } michael@0: return gInsertSeparatorBeforeAccessKey; michael@0: } michael@0: michael@0: class nsAsyncAccesskeyUpdate MOZ_FINAL : public nsIReflowCallback michael@0: { michael@0: public: michael@0: nsAsyncAccesskeyUpdate(nsIFrame* aFrame) : mWeakFrame(aFrame) michael@0: { michael@0: } michael@0: michael@0: virtual bool ReflowFinished() MOZ_OVERRIDE michael@0: { michael@0: bool shouldFlush = false; michael@0: nsTextBoxFrame* frame = michael@0: static_cast(mWeakFrame.GetFrame()); michael@0: if (frame) { michael@0: shouldFlush = frame->UpdateAccesskey(mWeakFrame); michael@0: } michael@0: delete this; michael@0: return shouldFlush; michael@0: } michael@0: michael@0: virtual void ReflowCallbackCanceled() MOZ_OVERRIDE michael@0: { michael@0: delete this; michael@0: } michael@0: michael@0: nsWeakFrame mWeakFrame; michael@0: }; michael@0: michael@0: bool michael@0: nsTextBoxFrame::UpdateAccesskey(nsWeakFrame& aWeakThis) michael@0: { michael@0: nsAutoString accesskey; michael@0: nsCOMPtr labelElement = do_QueryInterface(mContent); michael@0: NS_ENSURE_TRUE(aWeakThis.IsAlive(), false); michael@0: if (labelElement) { michael@0: // Accesskey may be stored on control. michael@0: // Because this method is called by the reflow callback, current context michael@0: // may not be the right one. Pushing the context of mContent so that michael@0: // if nsIDOMXULLabelElement is implemented in XBL, we don't get a michael@0: // security exception. michael@0: nsCxPusher cx; michael@0: if (cx.Push(mContent)) { michael@0: labelElement->GetAccessKey(accesskey); michael@0: NS_ENSURE_TRUE(aWeakThis.IsAlive(), false); michael@0: } michael@0: } michael@0: else { michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accesskey); michael@0: } michael@0: michael@0: if (!accesskey.Equals(mAccessKey)) { michael@0: // Need to get clean mTitle. michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle); michael@0: mAccessKey = accesskey; michael@0: UpdateAccessTitle(); michael@0: PresContext()->PresShell()-> michael@0: FrameNeedsReflow(this, nsIPresShell::eStyleChange, michael@0: NS_FRAME_IS_DIRTY); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::UpdateAttributes(nsIAtom* aAttribute, michael@0: bool& aResize, michael@0: bool& aRedraw) michael@0: { michael@0: bool doUpdateTitle = false; michael@0: aResize = false; michael@0: aRedraw = false; michael@0: michael@0: if (aAttribute == nullptr || aAttribute == nsGkAtoms::crop) { michael@0: static nsIContent::AttrValuesArray strings[] = michael@0: {&nsGkAtoms::left, &nsGkAtoms::start, &nsGkAtoms::center, michael@0: &nsGkAtoms::right, &nsGkAtoms::end, nullptr}; michael@0: CroppingStyle cropType; michael@0: switch (mContent->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::crop, michael@0: strings, eCaseMatters)) { michael@0: case 0: michael@0: case 1: michael@0: cropType = CropLeft; michael@0: break; michael@0: case 2: michael@0: cropType = CropCenter; michael@0: break; michael@0: case 3: michael@0: case 4: michael@0: cropType = CropRight; michael@0: break; michael@0: default: michael@0: cropType = CropNone; michael@0: break; michael@0: } michael@0: michael@0: if (cropType != mCropType) { michael@0: aResize = true; michael@0: mCropType = cropType; michael@0: } michael@0: } michael@0: michael@0: if (aAttribute == nullptr || aAttribute == nsGkAtoms::value) { michael@0: mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::value, mTitle); michael@0: doUpdateTitle = true; michael@0: } michael@0: michael@0: if (aAttribute == nullptr || aAttribute == nsGkAtoms::accesskey) { michael@0: mNeedsReflowCallback = true; michael@0: // Ensure that layout is refreshed and reflow callback called. michael@0: aResize = true; michael@0: } michael@0: michael@0: if (doUpdateTitle) { michael@0: UpdateAccessTitle(); michael@0: aResize = true; michael@0: } michael@0: michael@0: } michael@0: michael@0: class nsDisplayXULTextBox : public nsDisplayItem { michael@0: public: michael@0: nsDisplayXULTextBox(nsDisplayListBuilder* aBuilder, michael@0: nsTextBoxFrame* aFrame) : michael@0: nsDisplayItem(aBuilder, aFrame), michael@0: mDisableSubpixelAA(false) michael@0: { michael@0: MOZ_COUNT_CTOR(nsDisplayXULTextBox); michael@0: } michael@0: #ifdef NS_BUILD_REFCNT_LOGGING michael@0: virtual ~nsDisplayXULTextBox() { michael@0: MOZ_COUNT_DTOR(nsDisplayXULTextBox); michael@0: } michael@0: #endif michael@0: michael@0: virtual void Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) MOZ_OVERRIDE; michael@0: virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, michael@0: bool* aSnap) MOZ_OVERRIDE; michael@0: NS_DISPLAY_DECL_NAME("XULTextBox", TYPE_XUL_TEXT_BOX) michael@0: michael@0: virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE; michael@0: michael@0: virtual void DisableComponentAlpha() MOZ_OVERRIDE { michael@0: mDisableSubpixelAA = true; michael@0: } michael@0: michael@0: void PaintTextToContext(nsRenderingContext* aCtx, michael@0: nsPoint aOffset, michael@0: const nscolor* aColor); michael@0: michael@0: bool mDisableSubpixelAA; michael@0: }; michael@0: michael@0: static void michael@0: PaintTextShadowCallback(nsRenderingContext* aCtx, michael@0: nsPoint aShadowOffset, michael@0: const nscolor& aShadowColor, michael@0: void* aData) michael@0: { michael@0: reinterpret_cast(aData)-> michael@0: PaintTextToContext(aCtx, aShadowOffset, &aShadowColor); michael@0: } michael@0: michael@0: void michael@0: nsDisplayXULTextBox::Paint(nsDisplayListBuilder* aBuilder, michael@0: nsRenderingContext* aCtx) michael@0: { michael@0: gfxContextAutoDisableSubpixelAntialiasing disable(aCtx->ThebesContext(), michael@0: mDisableSubpixelAA); michael@0: michael@0: // Paint the text shadow before doing any foreground stuff michael@0: nsRect drawRect = static_cast(mFrame)->mTextDrawRect + michael@0: ToReferenceFrame(); michael@0: nsLayoutUtils::PaintTextShadow(mFrame, aCtx, michael@0: drawRect, mVisibleRect, michael@0: mFrame->StyleColor()->mColor, michael@0: PaintTextShadowCallback, michael@0: (void*)this); michael@0: michael@0: PaintTextToContext(aCtx, nsPoint(0, 0), nullptr); michael@0: } michael@0: michael@0: void michael@0: nsDisplayXULTextBox::PaintTextToContext(nsRenderingContext* aCtx, michael@0: nsPoint aOffset, michael@0: const nscolor* aColor) michael@0: { michael@0: static_cast(mFrame)-> michael@0: PaintTitle(*aCtx, mVisibleRect, ToReferenceFrame() + aOffset, aColor); michael@0: } michael@0: michael@0: nsRect michael@0: nsDisplayXULTextBox::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) { michael@0: *aSnap = false; michael@0: return mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame(); michael@0: } michael@0: michael@0: nsRect michael@0: nsDisplayXULTextBox::GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) michael@0: { michael@0: return static_cast(mFrame)->GetComponentAlphaBounds() + michael@0: ToReferenceFrame(); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, michael@0: const nsRect& aDirtyRect, michael@0: const nsDisplayListSet& aLists) michael@0: { michael@0: if (!IsVisibleForPainting(aBuilder)) michael@0: return; michael@0: michael@0: nsLeafBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists); michael@0: michael@0: aLists.Content()->AppendNewToTop(new (aBuilder) michael@0: nsDisplayXULTextBox(aBuilder, this)); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::PaintTitle(nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: nsPoint aPt, michael@0: const nscolor* aOverrideColor) michael@0: { michael@0: if (mTitle.IsEmpty()) michael@0: return; michael@0: michael@0: DrawText(aRenderingContext, aDirtyRect, mTextDrawRect + aPt, aOverrideColor); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::DrawText(nsRenderingContext& aRenderingContext, michael@0: const nsRect& aDirtyRect, michael@0: const nsRect& aTextRect, michael@0: const nscolor* aOverrideColor) michael@0: { michael@0: nsPresContext* presContext = PresContext(); michael@0: michael@0: // paint the title michael@0: nscolor overColor; michael@0: nscolor underColor; michael@0: nscolor strikeColor; michael@0: uint8_t overStyle; michael@0: uint8_t underStyle; michael@0: uint8_t strikeStyle; michael@0: michael@0: // Begin with no decorations michael@0: uint8_t decorations = NS_STYLE_TEXT_DECORATION_LINE_NONE; michael@0: // A mask of all possible decorations. michael@0: uint8_t decorMask = NS_STYLE_TEXT_DECORATION_LINE_LINES_MASK; michael@0: michael@0: nsIFrame* f = this; michael@0: do { // find decoration colors michael@0: nsStyleContext* context = f->StyleContext(); michael@0: if (!context->HasTextDecorationLines()) { michael@0: break; michael@0: } michael@0: const nsStyleTextReset* styleText = context->StyleTextReset(); michael@0: michael@0: if (decorMask & styleText->mTextDecorationLine) { // a decoration defined here michael@0: nscolor color; michael@0: if (aOverrideColor) { michael@0: color = *aOverrideColor; michael@0: } else { michael@0: bool isForeground; michael@0: styleText->GetDecorationColor(color, isForeground); michael@0: if (isForeground) { michael@0: color = nsLayoutUtils::GetColor(f, eCSSProperty_color); michael@0: } michael@0: } michael@0: uint8_t style = styleText->GetDecorationStyle(); michael@0: michael@0: if (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE & decorMask & michael@0: styleText->mTextDecorationLine) { michael@0: underColor = color; michael@0: underStyle = style; michael@0: decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; michael@0: decorations |= NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE; michael@0: } michael@0: if (NS_STYLE_TEXT_DECORATION_LINE_OVERLINE & decorMask & michael@0: styleText->mTextDecorationLine) { michael@0: overColor = color; michael@0: overStyle = style; michael@0: decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; michael@0: decorations |= NS_STYLE_TEXT_DECORATION_LINE_OVERLINE; michael@0: } michael@0: if (NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH & decorMask & michael@0: styleText->mTextDecorationLine) { michael@0: strikeColor = color; michael@0: strikeStyle = style; michael@0: decorMask &= ~NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; michael@0: decorations |= NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH; michael@0: } michael@0: } michael@0: } while (0 != decorMask && michael@0: (f = nsLayoutUtils::GetParentOrPlaceholderFor(f))); michael@0: michael@0: nsRefPtr fontMet; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)); michael@0: michael@0: nscoord offset; michael@0: nscoord size; michael@0: nscoord ascent = fontMet->MaxAscent(); michael@0: michael@0: nscoord baseline = michael@0: presContext->RoundAppUnitsToNearestDevPixels(aTextRect.y + ascent); michael@0: nsRefPtr ctx = aRenderingContext.ThebesContext(); michael@0: gfxPoint pt(presContext->AppUnitsToGfxUnits(aTextRect.x), michael@0: presContext->AppUnitsToGfxUnits(aTextRect.y)); michael@0: gfxFloat width = presContext->AppUnitsToGfxUnits(aTextRect.width); michael@0: gfxFloat ascentPixel = presContext->AppUnitsToGfxUnits(ascent); michael@0: gfxFloat xInFrame = PresContext()->AppUnitsToGfxUnits(mTextDrawRect.x); michael@0: gfxRect dirtyRect(presContext->AppUnitsToGfxUnits(aDirtyRect)); michael@0: michael@0: // Underlines are drawn before overlines, and both before the text michael@0: // itself, per http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1. michael@0: // (We don't apply this rule to the access-key underline because we only michael@0: // find out where that is as a side effect of drawing the text, in the michael@0: // general case -- see below.) michael@0: if (decorations & (NS_FONT_DECORATION_OVERLINE | michael@0: NS_FONT_DECORATION_UNDERLINE)) { michael@0: fontMet->GetUnderline(offset, size); michael@0: gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset); michael@0: gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size); michael@0: if ((decorations & NS_FONT_DECORATION_UNDERLINE) && michael@0: underStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, underColor, michael@0: pt, xInFrame, gfxSize(width, sizePixel), michael@0: ascentPixel, offsetPixel, michael@0: NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, underStyle); michael@0: } michael@0: if ((decorations & NS_FONT_DECORATION_OVERLINE) && michael@0: overStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, overColor, michael@0: pt, xInFrame, gfxSize(width, sizePixel), michael@0: ascentPixel, ascentPixel, michael@0: NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, overStyle); michael@0: } michael@0: } michael@0: michael@0: nsRefPtr refContext = michael@0: PresContext()->PresShell()->CreateReferenceRenderingContext(); michael@0: michael@0: aRenderingContext.SetFont(fontMet); michael@0: refContext->SetFont(fontMet); michael@0: michael@0: CalculateUnderline(*refContext); michael@0: michael@0: aRenderingContext.SetColor(aOverrideColor ? *aOverrideColor : StyleColor()->mColor); michael@0: michael@0: nsresult rv = NS_ERROR_FAILURE; michael@0: michael@0: if (mState & NS_FRAME_IS_BIDI) { michael@0: presContext->SetBidiEnabled(); michael@0: nsBidiLevel level = nsBidiPresUtils::BidiLevelFromStyle(StyleContext()); michael@0: if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { michael@0: // We let the RenderText function calculate the mnemonic's michael@0: // underline position for us. michael@0: nsBidiPositionResolve posResolve; michael@0: posResolve.logicalIndex = mAccessKeyInfo->mAccesskeyIndex; michael@0: rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level, michael@0: presContext, aRenderingContext, michael@0: *refContext, michael@0: aTextRect.x, baseline, michael@0: &posResolve, michael@0: 1); michael@0: mAccessKeyInfo->mBeforeWidth = posResolve.visualLeftTwips; michael@0: mAccessKeyInfo->mAccessWidth = posResolve.visualWidth; michael@0: } michael@0: else michael@0: { michael@0: rv = nsBidiPresUtils::RenderText(mCroppedTitle.get(), mCroppedTitle.Length(), level, michael@0: presContext, aRenderingContext, michael@0: *refContext, michael@0: aTextRect.x, baseline); michael@0: } michael@0: } michael@0: if (NS_FAILED(rv)) { michael@0: aRenderingContext.SetTextRunRTL(false); michael@0: michael@0: if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { michael@0: // In the simple (non-BiDi) case, we calculate the mnemonic's michael@0: // underline position by getting the text metric. michael@0: // XXX are attribute values always two byte? michael@0: if (mAccessKeyInfo->mAccesskeyIndex > 0) michael@0: mAccessKeyInfo->mBeforeWidth = michael@0: refContext->GetWidth(mCroppedTitle.get(), michael@0: mAccessKeyInfo->mAccesskeyIndex); michael@0: else michael@0: mAccessKeyInfo->mBeforeWidth = 0; michael@0: } michael@0: michael@0: fontMet->DrawString(mCroppedTitle.get(), mCroppedTitle.Length(), michael@0: aTextRect.x, baseline, &aRenderingContext, michael@0: refContext.get()); michael@0: } michael@0: michael@0: if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { michael@0: aRenderingContext.FillRect(aTextRect.x + mAccessKeyInfo->mBeforeWidth, michael@0: aTextRect.y + mAccessKeyInfo->mAccessOffset, michael@0: mAccessKeyInfo->mAccessWidth, michael@0: mAccessKeyInfo->mAccessUnderlineSize); michael@0: } michael@0: michael@0: // Strikeout is drawn on top of the text, per michael@0: // http://www.w3.org/TR/CSS21/zindex.html point 7.2.1.4.1.1. michael@0: if ((decorations & NS_FONT_DECORATION_LINE_THROUGH) && michael@0: strikeStyle != NS_STYLE_TEXT_DECORATION_STYLE_NONE) { michael@0: fontMet->GetStrikeout(offset, size); michael@0: gfxFloat offsetPixel = presContext->AppUnitsToGfxUnits(offset); michael@0: gfxFloat sizePixel = presContext->AppUnitsToGfxUnits(size); michael@0: nsCSSRendering::PaintDecorationLine(this, ctx, dirtyRect, strikeColor, michael@0: pt, xInFrame, gfxSize(width, sizePixel), ascentPixel, michael@0: offsetPixel, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, michael@0: strikeStyle); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::CalculateUnderline(nsRenderingContext& aRenderingContext) michael@0: { michael@0: if (mAccessKeyInfo && mAccessKeyInfo->mAccesskeyIndex != kNotFound) { michael@0: // Calculate all fields of mAccessKeyInfo which michael@0: // are the same for both BiDi and non-BiDi frames. michael@0: const char16_t *titleString = mCroppedTitle.get(); michael@0: aRenderingContext.SetTextRunRTL(false); michael@0: mAccessKeyInfo->mAccessWidth = michael@0: aRenderingContext.GetWidth(titleString[mAccessKeyInfo-> michael@0: mAccesskeyIndex]); michael@0: michael@0: nscoord offset, baseline; michael@0: nsFontMetrics* metrics = aRenderingContext.FontMetrics(); michael@0: metrics->GetUnderline(offset, mAccessKeyInfo->mAccessUnderlineSize); michael@0: baseline = metrics->MaxAscent(); michael@0: mAccessKeyInfo->mAccessOffset = baseline - offset; michael@0: } michael@0: } michael@0: michael@0: nscoord michael@0: nsTextBoxFrame::CalculateTitleForWidth(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: nscoord aWidth) michael@0: { michael@0: if (mTitle.IsEmpty()) { michael@0: mCroppedTitle.Truncate(); michael@0: return 0; michael@0: } michael@0: michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm)); michael@0: aRenderingContext.SetFont(fm); michael@0: michael@0: // see if the text will completely fit in the width given michael@0: nscoord titleWidth = nsLayoutUtils::GetStringWidth(this, &aRenderingContext, michael@0: mTitle.get(), mTitle.Length()); michael@0: michael@0: if (titleWidth <= aWidth) { michael@0: mCroppedTitle = mTitle; michael@0: if (HasRTLChars(mTitle)) { michael@0: mState |= NS_FRAME_IS_BIDI; michael@0: } michael@0: return titleWidth; // fits, done. michael@0: } michael@0: michael@0: const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); michael@0: // start with an ellipsis michael@0: mCroppedTitle.Assign(kEllipsis); michael@0: michael@0: // see if the width is even smaller than the ellipsis michael@0: // if so, clear the text (XXX set as many '.' as we can?). michael@0: aRenderingContext.SetTextRunRTL(false); michael@0: titleWidth = aRenderingContext.GetWidth(kEllipsis); michael@0: michael@0: if (titleWidth > aWidth) { michael@0: mCroppedTitle.SetLength(0); michael@0: return 0; michael@0: } michael@0: michael@0: // if the ellipsis fits perfectly, no use in trying to insert michael@0: if (titleWidth == aWidth) michael@0: return titleWidth; michael@0: michael@0: aWidth -= titleWidth; michael@0: michael@0: // XXX: This whole block should probably take surrogates into account michael@0: // XXX and clusters! michael@0: // ok crop things michael@0: switch (mCropType) michael@0: { michael@0: case CropNone: michael@0: case CropRight: michael@0: { michael@0: nscoord cwidth; michael@0: nscoord twidth = 0; michael@0: int length = mTitle.Length(); michael@0: int i; michael@0: for (i = 0; i < length; ++i) { michael@0: char16_t ch = mTitle.CharAt(i); michael@0: // still in LTR mode michael@0: cwidth = aRenderingContext.GetWidth(ch); michael@0: if (twidth + cwidth > aWidth) michael@0: break; michael@0: michael@0: twidth += cwidth; michael@0: if (UCS2_CHAR_IS_BIDI(ch) ) { michael@0: mState |= NS_FRAME_IS_BIDI; michael@0: } michael@0: } michael@0: michael@0: if (i == 0) michael@0: return titleWidth; michael@0: michael@0: // insert what character we can in. michael@0: nsAutoString title( mTitle ); michael@0: title.Truncate(i); michael@0: mCroppedTitle.Insert(title, 0); michael@0: } michael@0: break; michael@0: michael@0: case CropLeft: michael@0: { michael@0: nscoord cwidth; michael@0: nscoord twidth = 0; michael@0: int length = mTitle.Length(); michael@0: int i; michael@0: for (i=length-1; i >= 0; --i) { michael@0: char16_t ch = mTitle.CharAt(i); michael@0: cwidth = aRenderingContext.GetWidth(ch); michael@0: if (twidth + cwidth > aWidth) michael@0: break; michael@0: michael@0: twidth += cwidth; michael@0: if (UCS2_CHAR_IS_BIDI(ch) ) { michael@0: mState |= NS_FRAME_IS_BIDI; michael@0: } michael@0: } michael@0: michael@0: if (i == length-1) michael@0: return titleWidth; michael@0: michael@0: nsAutoString copy; michael@0: mTitle.Right(copy, length-1-i); michael@0: mCroppedTitle += copy; michael@0: } michael@0: break; michael@0: michael@0: case CropCenter: michael@0: { michael@0: nscoord stringWidth = michael@0: nsLayoutUtils::GetStringWidth(this, &aRenderingContext, michael@0: mTitle.get(), mTitle.Length()); michael@0: if (stringWidth <= aWidth) { michael@0: // the entire string will fit in the maximum width michael@0: mCroppedTitle.Insert(mTitle, 0); michael@0: break; michael@0: } michael@0: michael@0: // determine how much of the string will fit in the max width michael@0: nscoord charWidth = 0; michael@0: nscoord totalWidth = 0; michael@0: char16_t ch; michael@0: int leftPos, rightPos; michael@0: nsAutoString leftString, rightString; michael@0: michael@0: rightPos = mTitle.Length() - 1; michael@0: aRenderingContext.SetTextRunRTL(false); michael@0: for (leftPos = 0; leftPos <= rightPos;) { michael@0: // look at the next character on the left end michael@0: ch = mTitle.CharAt(leftPos); michael@0: charWidth = aRenderingContext.GetWidth(ch); michael@0: totalWidth += charWidth; michael@0: if (totalWidth > aWidth) michael@0: // greater than the allowable width michael@0: break; michael@0: leftString.Insert(ch, leftString.Length()); michael@0: michael@0: if (UCS2_CHAR_IS_BIDI(ch)) michael@0: mState |= NS_FRAME_IS_BIDI; michael@0: michael@0: // look at the next character on the right end michael@0: if (rightPos > leftPos) { michael@0: // haven't looked at this character yet michael@0: ch = mTitle.CharAt(rightPos); michael@0: charWidth = aRenderingContext.GetWidth(ch); michael@0: totalWidth += charWidth; michael@0: if (totalWidth > aWidth) michael@0: // greater than the allowable width michael@0: break; michael@0: rightString.Insert(ch, 0); michael@0: michael@0: if (UCS2_CHAR_IS_BIDI(ch)) michael@0: mState |= NS_FRAME_IS_BIDI; michael@0: } michael@0: michael@0: // look at the next two characters michael@0: leftPos++; michael@0: rightPos--; michael@0: } michael@0: michael@0: mCroppedTitle = leftString + kEllipsis + rightString; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: return nsLayoutUtils::GetStringWidth(this, &aRenderingContext, michael@0: mCroppedTitle.get(), mCroppedTitle.Length()); michael@0: } michael@0: michael@0: #define OLD_ELLIPSIS NS_LITERAL_STRING("...") michael@0: michael@0: // the following block is to append the accesskey to mTitle if there is an accesskey michael@0: // but the mTitle doesn't have the character michael@0: void michael@0: nsTextBoxFrame::UpdateAccessTitle() michael@0: { michael@0: /* michael@0: * Note that if you change appending access key label spec, michael@0: * you need to maintain same logic in following methods. See bug 324159. michael@0: * toolkit/content/commonDialog.js (setLabelForNode) michael@0: * toolkit/content/widgets/text.xml (formatAccessKey) michael@0: */ michael@0: int32_t menuAccessKey; michael@0: nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); michael@0: if (!menuAccessKey || mAccessKey.IsEmpty()) michael@0: return; michael@0: michael@0: if (!AlwaysAppendAccessKey() && michael@0: FindInReadable(mAccessKey, mTitle, nsCaseInsensitiveStringComparator())) michael@0: return; michael@0: michael@0: nsAutoString accessKeyLabel; michael@0: accessKeyLabel += '('; michael@0: accessKeyLabel += mAccessKey; michael@0: ToUpperCase(accessKeyLabel); michael@0: accessKeyLabel += ')'; michael@0: michael@0: if (mTitle.IsEmpty()) { michael@0: mTitle = accessKeyLabel; michael@0: return; michael@0: } michael@0: michael@0: const nsDependentString& kEllipsis = nsContentUtils::GetLocalizedEllipsis(); michael@0: uint32_t offset = mTitle.Length(); michael@0: if (StringEndsWith(mTitle, kEllipsis)) { michael@0: offset -= kEllipsis.Length(); michael@0: } else if (StringEndsWith(mTitle, OLD_ELLIPSIS)) { michael@0: // Try to check with our old ellipsis (for old addons) michael@0: offset -= OLD_ELLIPSIS.Length(); michael@0: } else { michael@0: // Try to check with michael@0: // our default ellipsis (for non-localized addons) or ':' michael@0: const char16_t kLastChar = mTitle.Last(); michael@0: if (kLastChar == char16_t(0x2026) || kLastChar == char16_t(':')) michael@0: offset--; michael@0: } michael@0: michael@0: if (InsertSeparatorBeforeAccessKey() && michael@0: offset > 0 && !NS_IS_SPACE(mTitle[offset - 1])) { michael@0: mTitle.Insert(' ', offset); michael@0: offset++; michael@0: } michael@0: michael@0: mTitle.Insert(accessKeyLabel, offset); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::UpdateAccessIndex() michael@0: { michael@0: int32_t menuAccessKey; michael@0: nsMenuBarListener::GetMenuAccessKey(&menuAccessKey); michael@0: if (menuAccessKey) { michael@0: if (mAccessKey.IsEmpty()) { michael@0: if (mAccessKeyInfo) { michael@0: delete mAccessKeyInfo; michael@0: mAccessKeyInfo = nullptr; michael@0: } michael@0: } else { michael@0: if (!mAccessKeyInfo) { michael@0: mAccessKeyInfo = new nsAccessKeyInfo(); michael@0: if (!mAccessKeyInfo) michael@0: return; michael@0: } michael@0: michael@0: nsAString::const_iterator start, end; michael@0: michael@0: mCroppedTitle.BeginReading(start); michael@0: mCroppedTitle.EndReading(end); michael@0: michael@0: // remember the beginning of the string michael@0: nsAString::const_iterator originalStart = start; michael@0: michael@0: bool found; michael@0: if (!AlwaysAppendAccessKey()) { michael@0: // not appending access key - do case-sensitive search michael@0: // first michael@0: found = FindInReadable(mAccessKey, start, end); michael@0: if (!found) { michael@0: // didn't find it - perform a case-insensitive search michael@0: start = originalStart; michael@0: found = FindInReadable(mAccessKey, start, end, michael@0: nsCaseInsensitiveStringComparator()); michael@0: } michael@0: } else { michael@0: found = RFindInReadable(mAccessKey, start, end, michael@0: nsCaseInsensitiveStringComparator()); michael@0: } michael@0: michael@0: if (found) michael@0: mAccessKeyInfo->mAccesskeyIndex = Distance(originalStart, start); michael@0: else michael@0: mAccessKeyInfo->mAccesskeyIndex = kNotFound; michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsTextBoxFrame::DoLayout(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: if (mNeedsReflowCallback) { michael@0: nsIReflowCallback* cb = new nsAsyncAccesskeyUpdate(this); michael@0: if (cb) { michael@0: PresContext()->PresShell()->PostReflowCallback(cb); michael@0: } michael@0: mNeedsReflowCallback = false; michael@0: } michael@0: michael@0: nsresult rv = nsLeafBoxFrame::DoLayout(aBoxLayoutState); michael@0: michael@0: CalcDrawRect(*aBoxLayoutState.GetRenderingContext()); michael@0: michael@0: const nsStyleText* textStyle = StyleText(); michael@0: michael@0: nsRect scrollBounds(nsPoint(0, 0), GetSize()); michael@0: nsRect textRect = mTextDrawRect; michael@0: michael@0: nsRefPtr fontMet; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)); michael@0: nsBoundingMetrics metrics = michael@0: fontMet->GetInkBoundsForVisualOverflow(mCroppedTitle.get(), michael@0: mCroppedTitle.Length(), michael@0: aBoxLayoutState.GetRenderingContext()); michael@0: michael@0: textRect.x -= metrics.leftBearing; michael@0: textRect.width = metrics.width; michael@0: // In DrawText() we always draw with the baseline at MaxAscent() (relative to mTextDrawRect), michael@0: textRect.y += fontMet->MaxAscent() - metrics.ascent; michael@0: textRect.height = metrics.ascent + metrics.descent; michael@0: michael@0: // Our scrollable overflow is our bounds; our visual overflow may michael@0: // extend beyond that. michael@0: nsRect visualBounds; michael@0: visualBounds.UnionRect(scrollBounds, textRect); michael@0: nsOverflowAreas overflow(visualBounds, scrollBounds); michael@0: michael@0: if (textStyle->mTextShadow) { michael@0: // text-shadow extends our visual but not scrollable bounds michael@0: nsRect &vis = overflow.VisualOverflow(); michael@0: vis.UnionRect(vis, nsLayoutUtils::GetTextShadowRectsUnion(mTextDrawRect, this)); michael@0: } michael@0: FinishAndStoreOverflow(overflow, GetSize()); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsRect michael@0: nsTextBoxFrame::GetComponentAlphaBounds() michael@0: { michael@0: if (StyleText()->mTextShadow) { michael@0: return GetVisualOverflowRectRelativeToSelf(); michael@0: } michael@0: return mTextDrawRect; michael@0: } michael@0: michael@0: bool michael@0: nsTextBoxFrame::ComputesOwnOverflowArea() michael@0: { michael@0: return true; michael@0: } michael@0: michael@0: /* virtual */ void michael@0: nsTextBoxFrame::MarkIntrinsicWidthsDirty() michael@0: { michael@0: mNeedsRecalc = true; michael@0: nsTextBoxFrameSuper::MarkIntrinsicWidthsDirty(); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::GetTextSize(nsPresContext* aPresContext, michael@0: nsRenderingContext& aRenderingContext, michael@0: const nsString& aString, michael@0: nsSize& aSize, nscoord& aAscent) michael@0: { michael@0: nsRefPtr fontMet; michael@0: nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet)); michael@0: aSize.height = fontMet->MaxHeight(); michael@0: aRenderingContext.SetFont(fontMet); michael@0: aSize.width = michael@0: nsLayoutUtils::GetStringWidth(this, &aRenderingContext, michael@0: aString.get(), aString.Length()); michael@0: aAscent = fontMet->MaxAscent(); michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::CalcTextSize(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: if (mNeedsRecalc) michael@0: { michael@0: nsSize size; michael@0: nsPresContext* presContext = aBoxLayoutState.PresContext(); michael@0: nsRenderingContext* rendContext = aBoxLayoutState.GetRenderingContext(); michael@0: if (rendContext) { michael@0: GetTextSize(presContext, *rendContext, michael@0: mTitle, size, mAscent); michael@0: mTextSize = size; michael@0: mNeedsRecalc = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsTextBoxFrame::CalcDrawRect(nsRenderingContext &aRenderingContext) michael@0: { michael@0: nsRect textRect(nsPoint(0, 0), GetSize()); michael@0: nsMargin borderPadding; michael@0: GetBorderAndPadding(borderPadding); michael@0: textRect.Deflate(borderPadding); michael@0: michael@0: // determine (cropped) title and underline position michael@0: nsPresContext* presContext = PresContext(); michael@0: // determine (cropped) title which fits in aRect.width and its width michael@0: nscoord titleWidth = michael@0: CalculateTitleForWidth(presContext, aRenderingContext, textRect.width); michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: // Make sure to update the accessible tree in case when cropped title is michael@0: // changed. michael@0: nsAccessibilityService* accService = GetAccService(); michael@0: if (accService) { michael@0: accService->UpdateLabelValue(PresContext()->PresShell(), mContent, michael@0: mCroppedTitle); michael@0: } michael@0: #endif michael@0: michael@0: // determine if and at which position to put the underline michael@0: UpdateAccessIndex(); michael@0: michael@0: // make the rect as small as our (cropped) text. michael@0: nscoord outerWidth = textRect.width; michael@0: textRect.width = titleWidth; michael@0: michael@0: // Align our text within the overall rect by checking our text-align property. michael@0: const nsStyleVisibility* vis = StyleVisibility(); michael@0: const nsStyleText* textStyle = StyleText(); michael@0: michael@0: if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_CENTER) michael@0: textRect.x += (outerWidth - textRect.width)/2; michael@0: else if (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_RIGHT || michael@0: (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_DEFAULT && michael@0: vis->mDirection == NS_STYLE_DIRECTION_RTL) || michael@0: (textStyle->mTextAlign == NS_STYLE_TEXT_ALIGN_END && michael@0: vis->mDirection == NS_STYLE_DIRECTION_LTR)) { michael@0: textRect.x += (outerWidth - textRect.width); michael@0: } michael@0: michael@0: mTextDrawRect = textRect; michael@0: } michael@0: michael@0: /** michael@0: * Ok return our dimensions michael@0: */ michael@0: nsSize michael@0: nsTextBoxFrame::GetPrefSize(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: CalcTextSize(aBoxLayoutState); michael@0: michael@0: nsSize size = mTextSize; michael@0: DISPLAY_PREF_SIZE(this, size); michael@0: michael@0: AddBorderAndPadding(size); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSPrefSize(this, size, widthSet, heightSet); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: /** michael@0: * Ok return our dimensions michael@0: */ michael@0: nsSize michael@0: nsTextBoxFrame::GetMinSize(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: CalcTextSize(aBoxLayoutState); michael@0: michael@0: nsSize size = mTextSize; michael@0: DISPLAY_MIN_SIZE(this, size); michael@0: michael@0: // if there is cropping our min width becomes our border and padding michael@0: if (mCropType != CropNone) michael@0: size.width = 0; michael@0: michael@0: AddBorderAndPadding(size); michael@0: bool widthSet, heightSet; michael@0: nsIFrame::AddCSSMinSize(aBoxLayoutState, this, size, widthSet, heightSet); michael@0: michael@0: return size; michael@0: } michael@0: michael@0: nscoord michael@0: nsTextBoxFrame::GetBoxAscent(nsBoxLayoutState& aBoxLayoutState) michael@0: { michael@0: CalcTextSize(aBoxLayoutState); michael@0: michael@0: nscoord ascent = mAscent; michael@0: michael@0: nsMargin m(0,0,0,0); michael@0: GetBorderAndPadding(m); michael@0: ascent += m.top; michael@0: michael@0: return ascent; michael@0: } michael@0: michael@0: #ifdef DEBUG_FRAME_DUMP michael@0: nsresult michael@0: nsTextBoxFrame::GetFrameName(nsAString& aResult) const michael@0: { michael@0: MakeFrameName(NS_LITERAL_STRING("TextBox"), aResult); michael@0: aResult += NS_LITERAL_STRING("[value=") + mTitle + NS_LITERAL_STRING("]"); michael@0: return NS_OK; michael@0: } michael@0: #endif michael@0: michael@0: // If you make changes to this function, check its counterparts michael@0: // in nsBoxFrame and nsXULLabelFrame michael@0: nsresult michael@0: nsTextBoxFrame::RegUnregAccessKey(bool aDoReg) michael@0: { michael@0: // if we have no content, we can't do anything michael@0: if (!mContent) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // check if we have a |control| attribute michael@0: // do this check first because few elements have control attributes, and we michael@0: // can weed out most of the elements quickly. michael@0: michael@0: // XXXjag a side-effect is that we filter out anonymous