michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ 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: /* the caret is the text cursor used, e.g., when editing */ michael@0: michael@0: #include "nsCOMPtr.h" michael@0: michael@0: #include "nsITimer.h" michael@0: michael@0: #include "nsFrameSelection.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsISelection.h" michael@0: #include "nsISelectionPrivate.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsRenderingContext.h" michael@0: #include "nsPresContext.h" michael@0: #include "nsBlockFrame.h" michael@0: #include "nsISelectionController.h" michael@0: #include "nsCaret.h" michael@0: #include "nsTextFrame.h" michael@0: #include "nsXULPopupManager.h" michael@0: #include "nsMenuPopupFrame.h" michael@0: #include "nsTextFragment.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/LookAndFeel.h" michael@0: #include "mozilla/dom/Selection.h" michael@0: #include michael@0: michael@0: // The bidi indicator hangs off the caret to one side, to show which michael@0: // direction the typing is in. It needs to be at least 2x2 to avoid looking like michael@0: // an insignificant dot michael@0: static const int32_t kMinBidiIndicatorPixels = 2; michael@0: michael@0: #include "nsIBidiKeyboard.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /** michael@0: * Find the first frame in an in-order traversal of the frame subtree rooted michael@0: * at aFrame which is either a text frame logically at the end of a line, michael@0: * or which is aStopAtFrame. Return null if no such frame is found. We don't michael@0: * descend into the children of non-eLineParticipant frames. michael@0: */ michael@0: static nsIFrame* michael@0: CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame) michael@0: { michael@0: if (aFrame == aStopAtFrame || michael@0: ((aFrame->GetType() == nsGkAtoms::textFrame && michael@0: (static_cast(aFrame))->IsAtEndOfLine()))) michael@0: return aFrame; michael@0: if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) michael@0: return nullptr; michael@0: michael@0: for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); f; f = f->GetNextSibling()) michael@0: { michael@0: nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame); michael@0: if (r) michael@0: return r; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static nsLineBox* michael@0: FindContainingLine(nsIFrame* aFrame) michael@0: { michael@0: while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant)) michael@0: { michael@0: nsIFrame* parent = aFrame->GetParent(); michael@0: nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent); michael@0: if (blockParent) michael@0: { michael@0: bool isValid; michael@0: nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid); michael@0: return isValid ? iter.GetLine().get() : nullptr; michael@0: } michael@0: aFrame = parent; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static void michael@0: AdjustCaretFrameForLineEnd(nsIFrame** aFrame, int32_t* aOffset) michael@0: { michael@0: nsLineBox* line = FindContainingLine(*aFrame); michael@0: if (!line) michael@0: return; michael@0: int32_t count = line->GetChildCount(); michael@0: for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling()) michael@0: { michael@0: nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame); michael@0: if (r == *aFrame) michael@0: return; michael@0: if (r) michael@0: { michael@0: *aFrame = r; michael@0: NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame"); michael@0: *aOffset = (static_cast(r))->GetContentEnd(); michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsCaret::nsCaret() michael@0: : mPresShell(nullptr) michael@0: , mBlinkRate(500) michael@0: , mVisible(false) michael@0: , mDrawn(false) michael@0: , mPendingDraw(false) michael@0: , mReadOnly(false) michael@0: , mShowDuringSelection(false) michael@0: , mIgnoreUserModify(true) michael@0: , mKeyboardRTL(false) michael@0: , mLastBidiLevel(0) michael@0: , mLastContentOffset(0) michael@0: , mLastHint(nsFrameSelection::HINTLEFT) michael@0: { michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsCaret::~nsCaret() michael@0: { michael@0: KillTimer(); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsresult nsCaret::Init(nsIPresShell *inPresShell) michael@0: { michael@0: NS_ENSURE_ARG(inPresShell); michael@0: michael@0: mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref michael@0: NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs"); michael@0: michael@0: // XXX we should just do this LookAndFeel consultation every time michael@0: // we need these values. michael@0: mCaretWidthCSSPx = LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1); michael@0: mCaretAspectRatio = michael@0: LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f); michael@0: michael@0: mBlinkRate = static_cast( michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, mBlinkRate)); michael@0: mShowDuringSelection = michael@0: LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection, michael@0: mShowDuringSelection ? 1 : 0) != 0; michael@0: michael@0: // get the selection from the pres shell, and set ourselves up as a selection michael@0: // listener michael@0: michael@0: nsCOMPtr selCon = do_QueryReferent(mPresShell); michael@0: if (!selCon) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr domSelection; michael@0: nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, michael@0: getter_AddRefs(domSelection)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (!domSelection) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr privateSelection = do_QueryInterface(domSelection); michael@0: if (privateSelection) michael@0: privateSelection->AddSelectionListener(this); michael@0: mDomSelectionWeak = do_GetWeakReference(domSelection); michael@0: michael@0: // set up the blink timer michael@0: if (mVisible) michael@0: { michael@0: StartBlinking(); michael@0: } michael@0: mBidiUI = Preferences::GetBool("bidi.browser.ui"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static bool michael@0: DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) michael@0: { michael@0: nsIContent* content = aFrame->GetContent(); michael@0: const nsTextFragment* frag = content->GetText(); michael@0: if (!frag) michael@0: return false; michael@0: if (aOffset < 0 || uint32_t(aOffset) >= frag->GetLength()) michael@0: return false; michael@0: char16_t ch = frag->CharAt(aOffset); michael@0: return 0x2e80 <= ch && ch <= 0xd7ff; michael@0: } michael@0: michael@0: nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, nscoord aCaretHeight) michael@0: { michael@0: // Compute nominal sizes in appunits michael@0: nscoord caretWidth = (aCaretHeight * mCaretAspectRatio) + michael@0: nsPresContext::CSSPixelsToAppUnits(mCaretWidthCSSPx); michael@0: michael@0: if (DrawCJKCaret(aFrame, aOffset)) { michael@0: caretWidth += nsPresContext::CSSPixelsToAppUnits(1); michael@0: } michael@0: nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels); michael@0: bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize); michael@0: michael@0: // Round them to device pixels. Always round down, except that anything michael@0: // between 0 and 1 goes up to 1 so we don't let the caret disappear. michael@0: int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel(); michael@0: Metrics result; michael@0: result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp); michael@0: result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp); michael@0: return result; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void nsCaret::Terminate() michael@0: { michael@0: // this doesn't erase the caret if it's drawn. Should it? We might not have michael@0: // a good drawing environment during teardown. michael@0: michael@0: KillTimer(); michael@0: mBlinkTimer = nullptr; michael@0: michael@0: // unregiser ourselves as a selection listener michael@0: nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); michael@0: nsCOMPtr privateSelection(do_QueryInterface(domSelection)); michael@0: if (privateSelection) michael@0: privateSelection->RemoveSelectionListener(this); michael@0: mDomSelectionWeak = nullptr; michael@0: mPresShell = nullptr; michael@0: michael@0: mLastContent = nullptr; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsISelection* nsCaret::GetCaretDOMSelection() michael@0: { michael@0: nsCOMPtr sel(do_QueryReferent(mDomSelectionWeak)); michael@0: return sel; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsresult nsCaret::SetCaretDOMSelection(nsISelection *aDOMSel) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDOMSel); michael@0: mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell michael@0: if (mVisible) michael@0: { michael@0: // Stop the caret from blinking in its previous location. michael@0: StopBlinking(); michael@0: // Start the caret blinking in the new location. michael@0: StartBlinking(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void nsCaret::SetCaretVisible(bool inMakeVisible) michael@0: { michael@0: mVisible = inMakeVisible; michael@0: if (mVisible) { michael@0: SetIgnoreUserModify(true); michael@0: StartBlinking(); michael@0: } else { michael@0: StopBlinking(); michael@0: SetIgnoreUserModify(false); michael@0: } michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsresult nsCaret::GetCaretVisible(bool *outMakeVisible) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(outMakeVisible); michael@0: *outMakeVisible = (mVisible && MustDrawCaret(true)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void nsCaret::SetCaretReadOnly(bool inMakeReadonly) michael@0: { michael@0: mReadOnly = inMakeReadonly; michael@0: } michael@0: michael@0: nsresult michael@0: nsCaret::GetGeometryForFrame(nsIFrame* aFrame, michael@0: int32_t aFrameOffset, michael@0: nsRect* aRect, michael@0: nscoord* aBidiIndicatorSize) michael@0: { michael@0: nsPoint framePos(0, 0); michael@0: nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsIFrame *frame = aFrame->GetContentInsertionFrame(); michael@0: NS_ASSERTION(frame, "We should not be in the middle of reflow"); michael@0: nscoord baseline = frame->GetCaretBaseline(); michael@0: nscoord ascent = 0, descent = 0; michael@0: nsRefPtr fm; michael@0: nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm), michael@0: nsLayoutUtils::FontSizeInflationFor(aFrame)); michael@0: NS_ASSERTION(fm, "We should be able to get the font metrics"); michael@0: if (fm) { michael@0: ascent = fm->MaxAscent(); michael@0: descent = fm->MaxDescent(); michael@0: } michael@0: nscoord height = ascent + descent; michael@0: framePos.y = baseline - ascent; michael@0: Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height); michael@0: *aRect = nsRect(framePos, nsSize(caretMetrics.mCaretWidth, height)); michael@0: michael@0: // Clamp the x-position to be within our scroll frame. If we don't, then it michael@0: // clips us, and we don't appear at all. See bug 335560. michael@0: nsIFrame *scrollFrame = michael@0: nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame); michael@0: if (scrollFrame) { michael@0: // First, use the scrollFrame to get at the scrollable view that we're in. michael@0: nsIScrollableFrame *sf = do_QueryFrame(scrollFrame); michael@0: nsIFrame *scrolled = sf->GetScrolledFrame(); michael@0: nsRect caretInScroll = *aRect + aFrame->GetOffsetTo(scrolled); michael@0: michael@0: // Now see if thet caret extends beyond the view's bounds. If it does, michael@0: // then snap it back, put it as close to the edge as it can. michael@0: nscoord overflow = caretInScroll.XMost() - michael@0: scrolled->GetVisualOverflowRectRelativeToSelf().width; michael@0: if (overflow > 0) michael@0: aRect->x -= overflow; michael@0: } michael@0: michael@0: if (aBidiIndicatorSize) michael@0: *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect, michael@0: nscoord* aBidiIndicatorSize) michael@0: { michael@0: nsCOMPtr focusNode; michael@0: nsresult rv = aSelection->GetFocusNode(getter_AddRefs(focusNode)); michael@0: if (NS_FAILED(rv) || !focusNode) michael@0: return nullptr; michael@0: michael@0: int32_t focusOffset; michael@0: rv = aSelection->GetFocusOffset(&focusOffset); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: michael@0: nsCOMPtr contentNode = do_QueryInterface(focusNode); michael@0: if (!contentNode) michael@0: return nullptr; michael@0: michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: if (!frameSelection) michael@0: return nullptr; michael@0: uint8_t bidiLevel = frameSelection->GetCaretBidiLevel(); michael@0: nsIFrame* frame; michael@0: int32_t frameOffset; michael@0: rv = GetCaretFrameForNodeOffset(contentNode, focusOffset, michael@0: frameSelection->GetHint(), bidiLevel, michael@0: &frame, &frameOffset); michael@0: if (NS_FAILED(rv) || !frame) michael@0: return nullptr; michael@0: michael@0: GetGeometryForFrame(frame, frameOffset, aRect, aBidiIndicatorSize); michael@0: return frame; michael@0: } michael@0: michael@0: void nsCaret::DrawCaretAfterBriefDelay() michael@0: { michael@0: // Make sure readonly caret gets drawn again if it needs to be michael@0: if (!mBlinkTimer) { michael@0: nsresult err; michael@0: mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err); michael@0: if (NS_FAILED(err)) michael@0: return; michael@0: } michael@0: michael@0: mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, 0, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: void nsCaret::EraseCaret() michael@0: { michael@0: if (mDrawn) { michael@0: DrawCaret(true); michael@0: if (mReadOnly && mBlinkRate) { michael@0: // If readonly we don't have a blink timer set, so caret won't michael@0: // be redrawn automatically. We need to force the caret to get michael@0: // redrawn right after the paint michael@0: DrawCaretAfterBriefDelay(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void nsCaret::SetVisibilityDuringSelection(bool aVisibility) michael@0: { michael@0: mShowDuringSelection = aVisibility; michael@0: } michael@0: michael@0: static michael@0: nsFrameSelection::HINT GetHintForPosition(nsIDOMNode* aNode, int32_t aOffset) michael@0: { michael@0: nsFrameSelection::HINT hint = nsFrameSelection::HINTLEFT; michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: if (!node || aOffset < 1) { michael@0: return hint; michael@0: } michael@0: const nsTextFragment* text = node->GetText(); michael@0: if (text && text->CharAt(aOffset - 1) == '\n') { michael@0: // Attach the caret to the next line if needed michael@0: hint = nsFrameSelection::HINTRIGHT; michael@0: } michael@0: return hint; michael@0: } michael@0: michael@0: nsresult nsCaret::DrawAtPosition(nsIDOMNode* aNode, int32_t aOffset) michael@0: { michael@0: NS_ENSURE_ARG(aNode); michael@0: michael@0: uint8_t bidiLevel; michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: if (!frameSelection) michael@0: return NS_ERROR_FAILURE; michael@0: bidiLevel = frameSelection->GetCaretBidiLevel(); michael@0: michael@0: // DrawAtPosition is used by consumers who want us to stay drawn where they michael@0: // tell us. Setting mBlinkRate to 0 tells us to not set a timer to erase michael@0: // ourselves, our consumer will take care of that. michael@0: mBlinkRate = 0; michael@0: michael@0: nsresult rv = DrawAtPositionWithHint(aNode, aOffset, michael@0: GetHintForPosition(aNode, aOffset), michael@0: bidiLevel, true) michael@0: ? NS_OK : NS_ERROR_FAILURE; michael@0: ToggleDrawnStatus(); michael@0: return rv; michael@0: } michael@0: michael@0: nsIFrame * nsCaret::GetCaretFrame(int32_t *aOffset) michael@0: { michael@0: // Return null if we're not drawn to prevent anybody from trying to draw us. michael@0: if (!mDrawn) michael@0: return nullptr; michael@0: michael@0: // Recompute the frame that we're supposed to draw in to guarantee that michael@0: // we're not going to try to draw into a stale (dead) frame. michael@0: int32_t offset; michael@0: nsIFrame *frame = nullptr; michael@0: nsresult rv = GetCaretFrameForNodeOffset(mLastContent, mLastContentOffset, michael@0: mLastHint, mLastBidiLevel, &frame, michael@0: &offset); michael@0: if (NS_FAILED(rv)) michael@0: return nullptr; michael@0: michael@0: if (aOffset) { michael@0: *aOffset = offset; michael@0: } michael@0: return frame; michael@0: } michael@0: michael@0: void nsCaret::InvalidateOutsideCaret() michael@0: { michael@0: nsIFrame *frame = GetCaretFrame(); michael@0: michael@0: // Only invalidate if we are not fully contained by our frame's rect. michael@0: if (frame && !frame->GetVisualOverflowRect().Contains(GetCaretRect())) { michael@0: frame->SchedulePaint(); michael@0: } michael@0: } michael@0: michael@0: void nsCaret::UpdateCaretPosition() michael@0: { michael@0: // We'll recalculate anyway if we're not drawn right now. michael@0: if (!mDrawn) michael@0: return; michael@0: michael@0: // A trick! Make the DrawCaret code recalculate the caret's current michael@0: // position. michael@0: mDrawn = false; michael@0: DrawCaret(false); michael@0: } michael@0: michael@0: void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder, michael@0: nsRenderingContext *aCtx, michael@0: nsIFrame* aForFrame, michael@0: const nsPoint &aOffset) michael@0: { michael@0: NS_ASSERTION(mDrawn, "The caret shouldn't be drawing"); michael@0: michael@0: const nsRect drawCaretRect = mCaretRect + aOffset; michael@0: int32_t contentOffset; michael@0: michael@0: #ifdef DEBUG michael@0: nsIFrame* frame = michael@0: #endif michael@0: GetCaretFrame(&contentOffset); michael@0: NS_ASSERTION(frame == aForFrame, "We're referring different frame"); michael@0: // If the offset falls outside of the frame, then don't paint the caret. michael@0: int32_t startOffset, endOffset; michael@0: if (aForFrame->GetType() == nsGkAtoms::textFrame && michael@0: (NS_FAILED(aForFrame->GetOffsets(startOffset, endOffset)) || michael@0: startOffset > contentOffset || michael@0: endOffset < contentOffset)) { michael@0: return; michael@0: } michael@0: nscolor foregroundColor = aForFrame->GetCaretColorAt(contentOffset); michael@0: michael@0: aCtx->SetColor(foregroundColor); michael@0: aCtx->FillRect(drawCaretRect); michael@0: if (!GetHookRect().IsEmpty()) michael@0: aCtx->FillRect(GetHookRect() + aOffset); michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: NS_IMETHODIMP nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel, int16_t aReason) michael@0: { michael@0: if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr domSel(do_QueryReferent(mDomSelectionWeak)); michael@0: michael@0: // The same caret is shared amongst the document and any text widgets it michael@0: // may contain. This means that the caret could get notifications from michael@0: // multiple selections. michael@0: // michael@0: // If this notification is for a selection that is not the one the michael@0: // the caret is currently interested in (mDomSelectionWeak), then there michael@0: // is nothing to do! michael@0: michael@0: if (domSel != aDomSel) michael@0: return NS_OK; michael@0: michael@0: if (mVisible) michael@0: { michael@0: // Stop the caret from blinking in its previous location. michael@0: StopBlinking(); michael@0: michael@0: // Start the caret blinking in the new location. michael@0: StartBlinking(); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void nsCaret::KillTimer() michael@0: { michael@0: if (mBlinkTimer) michael@0: { michael@0: mBlinkTimer->Cancel(); michael@0: } michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsresult nsCaret::PrimeTimer() michael@0: { michael@0: // set up the blink timer michael@0: if (!mReadOnly && mBlinkRate > 0) michael@0: { michael@0: if (!mBlinkTimer) { michael@0: nsresult err; michael@0: mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err); michael@0: if (NS_FAILED(err)) michael@0: return err; michael@0: } michael@0: michael@0: mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, mBlinkRate, michael@0: nsITimer::TYPE_REPEATING_SLACK); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void nsCaret::StartBlinking() michael@0: { michael@0: if (mReadOnly) { michael@0: // Make sure the one draw command we use for a readonly caret isn't michael@0: // done until the selection is set michael@0: DrawCaretAfterBriefDelay(); michael@0: return; michael@0: } michael@0: PrimeTimer(); michael@0: michael@0: // If we are currently drawn, then the second call to DrawCaret below will michael@0: // actually erase the caret. That would cause the caret to spend an "off" michael@0: // cycle before it appears, which is not really what we want. This first michael@0: // call to DrawCaret makes sure that the first cycle after a call to michael@0: // StartBlinking is an "on" cycle. michael@0: if (mDrawn) michael@0: DrawCaret(true); michael@0: michael@0: DrawCaret(true); // draw it right away michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: void nsCaret::StopBlinking() michael@0: { michael@0: if (mDrawn) // erase the caret if necessary michael@0: DrawCaret(true); michael@0: michael@0: NS_ASSERTION(!mDrawn, "Caret still drawn after StopBlinking()."); michael@0: KillTimer(); michael@0: } michael@0: michael@0: bool michael@0: nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode, michael@0: int32_t aOffset, michael@0: nsFrameSelection::HINT aFrameHint, michael@0: uint8_t aBidiLevel, michael@0: bool aInvalidate) michael@0: { michael@0: nsCOMPtr contentNode = do_QueryInterface(aNode); michael@0: if (!contentNode) michael@0: return false; michael@0: michael@0: nsIFrame* theFrame = nullptr; michael@0: int32_t theFrameOffset = 0; michael@0: michael@0: nsresult rv = GetCaretFrameForNodeOffset(contentNode, aOffset, aFrameHint, aBidiLevel, michael@0: &theFrame, &theFrameOffset); michael@0: if (NS_FAILED(rv) || !theFrame) michael@0: return false; michael@0: michael@0: // now we have a frame, check whether it's appropriate to show the caret here michael@0: const nsStyleUserInterface* userinterface = theFrame->StyleUserInterface(); michael@0: if ((!mIgnoreUserModify && michael@0: userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) || michael@0: (userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE) || michael@0: (userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: if (!mDrawn) michael@0: { michael@0: // save stuff so we can figure out what frame we're in later. michael@0: mLastContent = contentNode; michael@0: mLastContentOffset = aOffset; michael@0: mLastHint = aFrameHint; michael@0: mLastBidiLevel = aBidiLevel; michael@0: michael@0: // If there has been a reflow, set the caret Bidi level to the level of the current frame michael@0: if (aBidiLevel & BIDI_LEVEL_UNDEFINED) { michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: if (!frameSelection) michael@0: return false; michael@0: frameSelection->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame)); michael@0: } michael@0: michael@0: // Only update the caret's rect when we're not currently drawn. michael@0: if (!UpdateCaretRects(theFrame, theFrameOffset)) michael@0: return false; michael@0: } michael@0: michael@0: if (aInvalidate) michael@0: theFrame->SchedulePaint(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsCaret::GetCaretFrameForNodeOffset(nsIContent* aContentNode, michael@0: int32_t aOffset, michael@0: nsFrameSelection::HINT aFrameHint, michael@0: uint8_t aBidiLevel, michael@0: nsIFrame** aReturnFrame, michael@0: int32_t* aReturnOffset) michael@0: { michael@0: michael@0: //get frame selection and find out what frame to use... michael@0: nsCOMPtr presShell = do_QueryReferent(mPresShell); michael@0: if (!presShell) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (!aContentNode || !aContentNode->IsInDoc() || michael@0: presShell->GetDocument() != aContentNode->GetCurrentDoc()) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: if (!frameSelection) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIFrame* theFrame = nullptr; michael@0: int32_t theFrameOffset = 0; michael@0: michael@0: theFrame = frameSelection->GetFrameForNodeOffset(aContentNode, aOffset, michael@0: aFrameHint, &theFrameOffset); michael@0: if (!theFrame) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // if theFrame is after a text frame that's logically at the end of the line michael@0: // (e.g. if theFrame is a
frame), then put the caret at the end of michael@0: // that text frame instead. This way, the caret will be positioned as if michael@0: // trailing whitespace was not trimmed. michael@0: AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset); michael@0: michael@0: // Mamdouh : modification of the caret to work at rtl and ltr with Bidi michael@0: // michael@0: // Direction Style from visibility->mDirection michael@0: // ------------------ michael@0: // NS_STYLE_DIRECTION_LTR : LTR or Default michael@0: // NS_STYLE_DIRECTION_RTL michael@0: // NS_STYLE_DIRECTION_INHERIT michael@0: if (mBidiUI) michael@0: { michael@0: // If there has been a reflow, take the caret Bidi level to be the level of the current frame michael@0: if (aBidiLevel & BIDI_LEVEL_UNDEFINED) michael@0: aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame); michael@0: michael@0: int32_t start; michael@0: int32_t end; michael@0: nsIFrame* frameBefore; michael@0: nsIFrame* frameAfter; michael@0: uint8_t levelBefore; // Bidi level of the character before the caret michael@0: uint8_t levelAfter; // Bidi level of the character after the caret michael@0: michael@0: theFrame->GetOffsets(start, end); michael@0: if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset) michael@0: { michael@0: nsPrevNextBidiLevels levels = frameSelection-> michael@0: GetPrevNextBidiLevels(aContentNode, aOffset, false); michael@0: michael@0: /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */ michael@0: if (levels.mFrameBefore || levels.mFrameAfter) michael@0: { michael@0: frameBefore = levels.mFrameBefore; michael@0: frameAfter = levels.mFrameAfter; michael@0: levelBefore = levels.mLevelBefore; michael@0: levelAfter = levels.mLevelAfter; michael@0: michael@0: if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore)) michael@0: { michael@0: aBidiLevel = std::max(aBidiLevel, std::min(levelBefore, levelAfter)); // rule c3 michael@0: aBidiLevel = std::min(aBidiLevel, std::max(levelBefore, levelAfter)); // rule c4 michael@0: if (aBidiLevel == levelBefore // rule c1 michael@0: || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelBefore) & 1)) // rule c5 michael@0: || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelBefore) & 1))) // rule c9 michael@0: { michael@0: if (theFrame != frameBefore) michael@0: { michael@0: if (frameBefore) // if there is a frameBefore, move into it michael@0: { michael@0: theFrame = frameBefore; michael@0: theFrame->GetOffsets(start, end); michael@0: theFrameOffset = end; michael@0: } michael@0: else michael@0: { michael@0: // if there is no frameBefore, we must be at the beginning of the line michael@0: // so we stay with the current frame. michael@0: // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no michael@0: // real frame for the caret to be in. We have to find the visually first frame on the line. michael@0: uint8_t baseLevel = NS_GET_BASE_LEVEL(frameAfter); michael@0: if (baseLevel != levelAfter) michael@0: { michael@0: nsPeekOffsetStruct pos(eSelectBeginLine, eDirPrevious, 0, 0, false, true, false, true); michael@0: if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) { michael@0: theFrame = pos.mResultFrame; michael@0: theFrameOffset = pos.mContentOffset; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else if (aBidiLevel == levelAfter // rule c2 michael@0: || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelAfter) & 1)) // rule c6 michael@0: || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelAfter) & 1))) // rule c10 michael@0: { michael@0: if (theFrame != frameAfter) michael@0: { michael@0: if (frameAfter) michael@0: { michael@0: // if there is a frameAfter, move into it michael@0: theFrame = frameAfter; michael@0: theFrame->GetOffsets(start, end); michael@0: theFrameOffset = start; michael@0: } michael@0: else michael@0: { michael@0: // if there is no frameAfter, we must be at the end of the line michael@0: // so we stay with the current frame. michael@0: // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no michael@0: // real frame for the caret to be in. We have to find the visually last frame on the line. michael@0: uint8_t baseLevel = NS_GET_BASE_LEVEL(frameBefore); michael@0: if (baseLevel != levelBefore) michael@0: { michael@0: nsPeekOffsetStruct pos(eSelectEndLine, eDirNext, 0, 0, false, true, false, true); michael@0: if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) { michael@0: theFrame = pos.mResultFrame; michael@0: theFrameOffset = pos.mContentOffset; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8 michael@0: && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity michael@0: && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity michael@0: { michael@0: if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame))) michael@0: { michael@0: theFrame->GetOffsets(start, end); michael@0: levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame); michael@0: if (aBidiLevel & 1) // c8: caret to the right of the rightmost character michael@0: theFrameOffset = (levelAfter & 1) ? start : end; michael@0: else // c7: caret to the left of the leftmost character michael@0: theFrameOffset = (levelAfter & 1) ? end : start; michael@0: } michael@0: } michael@0: else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12 michael@0: && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity michael@0: && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity michael@0: { michael@0: if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame))) michael@0: { michael@0: theFrame->GetOffsets(start, end); michael@0: levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame); michael@0: if (aBidiLevel & 1) // c12: caret to the left of the leftmost character michael@0: theFrameOffset = (levelBefore & 1) ? end : start; michael@0: else // c11: caret to the right of the rightmost character michael@0: theFrameOffset = (levelBefore & 1) ? start : end; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_ASSERTION(!theFrame || theFrame->PresContext()->PresShell() == presShell, michael@0: "caret frame is in wrong document"); michael@0: *aReturnFrame = theFrame; michael@0: *aReturnOffset = theFrameOffset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsCaret::CheckCaretDrawingState() michael@0: { michael@0: if (mDrawn) { michael@0: // The caret is drawn; if it shouldn't be, erase it. michael@0: if (!mVisible || !MustDrawCaret(true)) michael@0: EraseCaret(); michael@0: } michael@0: else michael@0: { michael@0: // The caret is not drawn; if it should be, draw it. michael@0: if (mPendingDraw && (mVisible && MustDrawCaret(true))) michael@0: DrawCaret(true); michael@0: } michael@0: } michael@0: michael@0: /*----------------------------------------------------------------------------- michael@0: michael@0: MustDrawCaret michael@0: michael@0: Find out if we need to do any caret drawing. This returns true if michael@0: either: michael@0: a) The caret has been drawn, and we need to erase it. michael@0: b) The caret is not drawn, and the selection is collapsed. michael@0: c) The caret is not hidden due to open XUL popups michael@0: (see IsMenuPopupHidingCaret()). michael@0: michael@0: ----------------------------------------------------------------------------- */ michael@0: bool nsCaret::MustDrawCaret(bool aIgnoreDrawnState) michael@0: { michael@0: if (!aIgnoreDrawnState && mDrawn) michael@0: return true; michael@0: michael@0: nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); michael@0: if (!domSelection) michael@0: return false; michael@0: michael@0: bool isCollapsed; michael@0: if (NS_FAILED(domSelection->GetIsCollapsed(&isCollapsed))) michael@0: return false; michael@0: michael@0: if (mShowDuringSelection) michael@0: return true; // show the caret even in selections michael@0: michael@0: if (IsMenuPopupHidingCaret()) michael@0: return false; michael@0: michael@0: return isCollapsed; michael@0: } michael@0: michael@0: bool nsCaret::IsMenuPopupHidingCaret() michael@0: { michael@0: #ifdef MOZ_XUL michael@0: // Check if there are open popups. michael@0: nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance(); michael@0: nsTArray popups; michael@0: popMgr->GetVisiblePopups(popups); michael@0: michael@0: if (popups.Length() == 0) michael@0: return false; // No popups, so caret can't be hidden by them. michael@0: michael@0: // Get the selection focus content, that's where the caret would michael@0: // go if it was drawn. michael@0: nsCOMPtr node; michael@0: nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); michael@0: if (!domSelection) michael@0: return true; // No selection/caret to draw. michael@0: domSelection->GetFocusNode(getter_AddRefs(node)); michael@0: if (!node) michael@0: return true; // No selection/caret to draw. michael@0: nsCOMPtr caretContent = do_QueryInterface(node); michael@0: if (!caretContent) michael@0: return true; // No selection/caret to draw. michael@0: michael@0: // If there's a menu popup open before the popup with michael@0: // the caret, don't show the caret. michael@0: for (uint32_t i=0; i(popups[i]); michael@0: nsIContent* popupContent = popupFrame->GetContent(); michael@0: michael@0: if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) { michael@0: // The caret is in this popup. There were no menu popups before this michael@0: // popup, so don't hide the caret. michael@0: return false; michael@0: } michael@0: michael@0: if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) { michael@0: // This is an open menu popup. It does not contain the caret (else we'd michael@0: // have returned above). Even if the caret is in a subsequent popup, michael@0: // or another document/frame, it should be hidden. michael@0: return true; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: // There are no open menu popups, no need to hide the caret. michael@0: return false; michael@0: } michael@0: michael@0: void nsCaret::DrawCaret(bool aInvalidate) michael@0: { michael@0: // Do we need to draw the caret at all? michael@0: if (!MustDrawCaret(false)) michael@0: return; michael@0: michael@0: // Can we draw the caret now? michael@0: nsCOMPtr presShell = do_QueryReferent(mPresShell); michael@0: NS_ENSURE_TRUE_VOID(presShell); michael@0: { michael@0: if (presShell->IsPaintingSuppressed()) michael@0: { michael@0: if (!mDrawn) michael@0: mPendingDraw = true; michael@0: michael@0: // PresShell::UnsuppressAndInvalidate() will call CheckCaretDrawingState() michael@0: // to get us drawn. michael@0: return; michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr node; michael@0: int32_t offset; michael@0: nsFrameSelection::HINT hint; michael@0: uint8_t bidiLevel; michael@0: michael@0: if (!mDrawn) michael@0: { michael@0: nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); michael@0: nsCOMPtr privateSelection(do_QueryInterface(domSelection)); michael@0: if (!privateSelection) return; michael@0: michael@0: bool isCollapsed = false; michael@0: domSelection->GetIsCollapsed(&isCollapsed); michael@0: if (!mShowDuringSelection && !isCollapsed) michael@0: return; michael@0: michael@0: bool hintRight; michael@0: privateSelection->GetInterlinePosition(&hintRight);//translate hint. michael@0: hint = hintRight ? nsFrameSelection::HINTRIGHT : nsFrameSelection::HINTLEFT; michael@0: michael@0: // get the node and offset, which is where we want the caret to draw michael@0: domSelection->GetFocusNode(getter_AddRefs(node)); michael@0: if (!node) michael@0: return; michael@0: michael@0: if (NS_FAILED(domSelection->GetFocusOffset(&offset))) michael@0: return; michael@0: michael@0: nsRefPtr frameSelection = GetFrameSelection(); michael@0: if (!frameSelection) michael@0: return; michael@0: michael@0: bidiLevel = frameSelection->GetCaretBidiLevel(); michael@0: mPendingDraw = false; michael@0: } michael@0: else michael@0: { michael@0: if (!mLastContent) michael@0: { michael@0: mDrawn = false; michael@0: return; michael@0: } michael@0: if (!mLastContent->IsInDoc() || michael@0: presShell->GetDocument() != mLastContent->GetCurrentDoc()) michael@0: { michael@0: mLastContent = nullptr; michael@0: mDrawn = false; michael@0: return; michael@0: } michael@0: node = do_QueryInterface(mLastContent); michael@0: offset = mLastContentOffset; michael@0: hint = mLastHint; michael@0: bidiLevel = mLastBidiLevel; michael@0: } michael@0: michael@0: DrawAtPositionWithHint(node, offset, hint, bidiLevel, aInvalidate); michael@0: ToggleDrawnStatus(); michael@0: } michael@0: michael@0: bool michael@0: nsCaret::UpdateCaretRects(nsIFrame* aFrame, int32_t aFrameOffset) michael@0: { michael@0: NS_ASSERTION(aFrame, "Should have a frame here"); michael@0: michael@0: nscoord bidiIndicatorSize; michael@0: nsresult rv = michael@0: GetGeometryForFrame(aFrame, aFrameOffset, &mCaretRect, &bidiIndicatorSize); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: // on RTL frames the right edge of mCaretRect must be equal to framePos michael@0: const nsStyleVisibility* vis = aFrame->StyleVisibility(); michael@0: if (NS_STYLE_DIRECTION_RTL == vis->mDirection) michael@0: mCaretRect.x -= mCaretRect.width; michael@0: michael@0: mHookRect.SetEmpty(); michael@0: michael@0: // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction michael@0: bool isCaretRTL = false; michael@0: nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard(); michael@0: // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the michael@0: // keyboard direction, or the user has no right-to-left keyboard michael@0: // installed, so we never draw the hook. michael@0: if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL)) && michael@0: mBidiUI) { michael@0: if (isCaretRTL != mKeyboardRTL) { michael@0: /* if the caret bidi level and the keyboard language direction are not in michael@0: * synch, the keyboard language must have been changed by the michael@0: * user, and if the caret is in a boundary condition (between left-to-right and michael@0: * right-to-left characters) it may have to change position to michael@0: * reflect the location in which the next character typed will michael@0: * appear. We will call |SelectionLanguageChange| and exit michael@0: * without drawing the caret in the old position. michael@0: */ michael@0: mKeyboardRTL = isCaretRTL; michael@0: nsCOMPtr domSelection = do_QueryReferent(mDomSelectionWeak); michael@0: if (!domSelection || michael@0: NS_SUCCEEDED(domSelection->SelectionLanguageChange(mKeyboardRTL))) michael@0: return false; michael@0: } michael@0: // If keyboard language is RTL, draw the hook on the left; if LTR, to the right michael@0: // The height of the hook rectangle is the same as the width of the caret michael@0: // rectangle. michael@0: mHookRect.SetRect(mCaretRect.x + ((isCaretRTL) ? michael@0: bidiIndicatorSize * -1 : michael@0: mCaretRect.width), michael@0: mCaretRect.y + bidiIndicatorSize, michael@0: bidiIndicatorSize, michael@0: mCaretRect.width); michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: /* static */ michael@0: void nsCaret::CaretBlinkCallback(nsITimer *aTimer, void *aClosure) michael@0: { michael@0: nsCaret *theCaret = reinterpret_cast(aClosure); michael@0: if (!theCaret) return; michael@0: michael@0: theCaret->DrawCaret(true); michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: nsFrameSelection* michael@0: nsCaret::GetFrameSelection() michael@0: { michael@0: nsCOMPtr sel = do_QueryReferent(mDomSelectionWeak); michael@0: if (!sel) michael@0: return nullptr; michael@0: michael@0: return static_cast(sel.get())->GetFrameSelection(); michael@0: } michael@0: michael@0: void michael@0: nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify) michael@0: { michael@0: if (!aIgnoreUserModify && mIgnoreUserModify && mDrawn) { michael@0: // We're turning off mIgnoreUserModify. If the caret's drawn michael@0: // in a read-only node we must erase it, else the next call michael@0: // to DrawCaret() won't erase the old caret, due to the new michael@0: // mIgnoreUserModify value. michael@0: nsIFrame *frame = GetCaretFrame(); michael@0: if (frame) { michael@0: const nsStyleUserInterface* userinterface = frame->StyleUserInterface(); michael@0: if (userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) { michael@0: StopBlinking(); michael@0: } michael@0: } michael@0: } michael@0: mIgnoreUserModify = aIgnoreUserModify; michael@0: }