Tracer build fixes. (b=588021, r=dvander)
[mozilla-central.git] / layout / base / nsCaret.cpp
blob14a94dce12f2e92143a9bd59cc37f3a4c33b419f
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 et tw=78: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is mozilla.org code.
18 * The Initial Developer of the Original Code is
19 * Netscape Communications Corporation.
20 * Portions created by the Initial Developer are Copyright (C) 1998
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Mats Palmgren <matspal@gmail.com>
27 * Alternatively, the contents of this file may be used under the terms of
28 * either of the GNU General Public License Version 2 or later (the "GPL"),
29 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 /* the caret is the text cursor used, e.g., when editing */
43 #include "nsCOMPtr.h"
45 #include "nsITimer.h"
47 #include "nsIComponentManager.h"
48 #include "nsIServiceManager.h"
49 #include "nsFrameSelection.h"
50 #include "nsIFrame.h"
51 #include "nsIScrollableFrame.h"
52 #include "nsIDOMNode.h"
53 #include "nsIDOMRange.h"
54 #include "nsIFontMetrics.h"
55 #include "nsISelection.h"
56 #include "nsISelectionPrivate.h"
57 #include "nsIDOMCharacterData.h"
58 #include "nsIContent.h"
59 #include "nsIPresShell.h"
60 #include "nsIRenderingContext.h"
61 #include "nsIDeviceContext.h"
62 #include "nsPresContext.h"
63 #include "nsILookAndFeel.h"
64 #include "nsBlockFrame.h"
65 #include "nsISelectionController.h"
66 #include "nsDisplayList.h"
67 #include "nsCaret.h"
68 #include "nsTextFrame.h"
69 #include "nsXULPopupManager.h"
70 #include "nsMenuPopupFrame.h"
71 #include "nsTextFragment.h"
72 #include "nsThemeConstants.h"
74 // The bidi indicator hangs off the caret to one side, to show which
75 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
76 // an insignificant dot
77 static const PRInt32 kMinBidiIndicatorPixels = 2;
79 #ifdef IBMBIDI
80 #include "nsIBidiKeyboard.h"
81 #include "nsContentUtils.h"
82 #endif //IBMBIDI
84 /**
85 * Find the first frame in an in-order traversal of the frame subtree rooted
86 * at aFrame which is either a text frame logically at the end of a line,
87 * or which is aStopAtFrame. Return null if no such frame is found. We don't
88 * descend into the children of non-eLineParticipant frames.
90 static nsIFrame*
91 CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
93 if (aFrame == aStopAtFrame ||
94 ((aFrame->GetType() == nsGkAtoms::textFrame &&
95 (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
96 return aFrame;
97 if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
98 return nsnull;
100 for (nsIFrame* f = aFrame->GetFirstChild(nsnull); f; f = f->GetNextSibling())
102 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
103 if (r)
104 return r;
106 return nsnull;
109 static nsLineBox*
110 FindContainingLine(nsIFrame* aFrame)
112 while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
114 nsIFrame* parent = aFrame->GetParent();
115 nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
116 if (blockParent)
118 PRBool isValid;
119 nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
120 return isValid ? iter.GetLine().get() : nsnull;
122 aFrame = parent;
124 return nsnull;
127 static void
128 AdjustCaretFrameForLineEnd(nsIFrame** aFrame, PRInt32* aOffset)
130 nsLineBox* line = FindContainingLine(*aFrame);
131 if (!line)
132 return;
133 PRInt32 count = line->GetChildCount();
134 for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
136 nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
137 if (r == *aFrame)
138 return;
139 if (r)
141 *aFrame = r;
142 NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
143 *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
144 return;
149 static PRBool
150 FramesOnSameLineHaveZeroHeight(nsIFrame* aFrame)
152 nsLineBox* line = FindContainingLine(aFrame);
153 if (!line)
154 return aFrame->GetRect().height == 0;
155 PRInt32 count = line->GetChildCount();
156 for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
158 if (f->GetRect().height != 0)
159 return PR_FALSE;
161 return PR_TRUE;
164 //-----------------------------------------------------------------------------
166 nsCaret::nsCaret()
167 : mPresShell(nsnull)
168 , mBlinkRate(500)
169 , mVisible(PR_FALSE)
170 , mDrawn(PR_FALSE)
171 , mPendingDraw(PR_FALSE)
172 , mReadOnly(PR_FALSE)
173 , mShowDuringSelection(PR_FALSE)
174 , mIgnoreUserModify(PR_TRUE)
175 #ifdef IBMBIDI
176 , mKeyboardRTL(PR_FALSE)
177 , mLastBidiLevel(0)
178 #endif
179 , mLastContentOffset(0)
180 , mLastHint(nsFrameSelection::HINTLEFT)
184 //-----------------------------------------------------------------------------
185 nsCaret::~nsCaret()
187 KillTimer();
190 //-----------------------------------------------------------------------------
191 nsresult nsCaret::Init(nsIPresShell *inPresShell)
193 NS_ENSURE_ARG(inPresShell);
195 mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref
196 NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
198 // get nsILookAndFeel from the pres context, which has one cached.
199 nsILookAndFeel *lookAndFeel = nsnull;
200 nsPresContext *presContext = inPresShell->GetPresContext();
202 // XXX we should just do this nsILookAndFeel consultation every time
203 // we need these values.
204 mCaretWidthCSSPx = 1;
205 mCaretAspectRatio = 0;
206 if (presContext && (lookAndFeel = presContext->LookAndFeel())) {
207 PRInt32 tempInt;
208 float tempFloat;
209 if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetric_CaretWidth, tempInt)))
210 mCaretWidthCSSPx = (nscoord)tempInt;
211 if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetricFloat_CaretAspectRatio, tempFloat)))
212 mCaretAspectRatio = tempFloat;
213 if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetric_CaretBlinkTime, tempInt)))
214 mBlinkRate = (PRUint32)tempInt;
215 if (NS_SUCCEEDED(lookAndFeel->GetMetric(nsILookAndFeel::eMetric_ShowCaretDuringSelection, tempInt)))
216 mShowDuringSelection = tempInt ? PR_TRUE : PR_FALSE;
219 // get the selection from the pres shell, and set ourselves up as a selection
220 // listener
222 nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
223 if (!selCon)
224 return NS_ERROR_FAILURE;
226 nsCOMPtr<nsISelection> domSelection;
227 nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
228 getter_AddRefs(domSelection));
229 if (NS_FAILED(rv))
230 return rv;
231 if (!domSelection)
232 return NS_ERROR_FAILURE;
234 nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
235 if (privateSelection)
236 privateSelection->AddSelectionListener(this);
237 mDomSelectionWeak = do_GetWeakReference(domSelection);
239 // set up the blink timer
240 if (mVisible)
242 StartBlinking();
244 #ifdef IBMBIDI
245 mBidiUI = nsContentUtils::GetBoolPref("bidi.browser.ui");
246 #endif
248 return NS_OK;
251 static PRBool
252 DrawCJKCaret(nsIFrame* aFrame, PRInt32 aOffset)
254 nsIContent* content = aFrame->GetContent();
255 const nsTextFragment* frag = content->GetText();
256 if (!frag)
257 return PR_FALSE;
258 if (aOffset < 0 || PRUint32(aOffset) >= frag->GetLength())
259 return PR_FALSE;
260 PRUnichar ch = frag->CharAt(aOffset);
261 return 0x2e80 <= ch && ch <= 0xd7ff;
264 nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, PRInt32 aOffset, nscoord aCaretHeight)
266 // Compute nominal sizes in appunits
267 nscoord caretWidth = (aCaretHeight * mCaretAspectRatio) +
268 nsPresContext::CSSPixelsToAppUnits(mCaretWidthCSSPx);
270 if (DrawCJKCaret(aFrame, aOffset)) {
271 caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
273 nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
274 bidiIndicatorSize = NS_MAX(caretWidth, bidiIndicatorSize);
276 // Round them to device pixels. Always round down, except that anything
277 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
278 PRUint32 tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
279 Metrics result;
280 result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
281 result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
282 return result;
285 //-----------------------------------------------------------------------------
286 void nsCaret::Terminate()
288 // this doesn't erase the caret if it's drawn. Should it? We might not have
289 // a good drawing environment during teardown.
291 KillTimer();
292 mBlinkTimer = nsnull;
294 // unregiser ourselves as a selection listener
295 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
296 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
297 if (privateSelection)
298 privateSelection->RemoveSelectionListener(this);
299 mDomSelectionWeak = nsnull;
300 mPresShell = nsnull;
302 mLastContent = nsnull;
305 //-----------------------------------------------------------------------------
306 NS_IMPL_ISUPPORTS1(nsCaret, nsISelectionListener)
308 //-----------------------------------------------------------------------------
309 nsISelection* nsCaret::GetCaretDOMSelection()
311 nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
312 return sel;
315 //-----------------------------------------------------------------------------
316 nsresult nsCaret::SetCaretDOMSelection(nsISelection *aDOMSel)
318 NS_ENSURE_ARG_POINTER(aDOMSel);
319 mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
320 if (mVisible)
322 // Stop the caret from blinking in its previous location.
323 StopBlinking();
324 // Start the caret blinking in the new location.
325 StartBlinking();
327 return NS_OK;
331 //-----------------------------------------------------------------------------
332 void nsCaret::SetCaretVisible(PRBool inMakeVisible)
334 mVisible = inMakeVisible;
335 if (mVisible) {
336 StartBlinking();
337 SetIgnoreUserModify(PR_TRUE);
338 } else {
339 StopBlinking();
340 SetIgnoreUserModify(PR_FALSE);
345 //-----------------------------------------------------------------------------
346 nsresult nsCaret::GetCaretVisible(PRBool *outMakeVisible)
348 NS_ENSURE_ARG_POINTER(outMakeVisible);
349 *outMakeVisible = (mVisible && MustDrawCaret(PR_TRUE));
350 return NS_OK;
354 //-----------------------------------------------------------------------------
355 void nsCaret::SetCaretReadOnly(PRBool inMakeReadonly)
357 mReadOnly = inMakeReadonly;
360 void
361 nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
362 PRInt32 aFrameOffset,
363 nsRect* aRect,
364 nscoord* aBidiIndicatorSize)
366 nsPoint framePos(0, 0);
367 aFrame->GetPointFromOffset(aFrameOffset, &framePos);
368 nscoord baseline = aFrame->GetCaretBaseline();
369 nscoord ascent = 0, descent = 0;
370 nsCOMPtr<nsIFontMetrics> fm;
371 nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm));
372 NS_ASSERTION(fm, "We should be able to get the font metrics");
373 if (fm) {
374 fm->GetMaxAscent(ascent);
375 fm->GetMaxDescent(descent);
377 nscoord height = ascent + descent;
378 framePos.y = baseline - ascent;
379 Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
380 *aRect = nsRect(framePos, nsSize(caretMetrics.mCaretWidth, height));
382 // Clamp the x-position to be within our scroll frame. If we don't, then it
383 // clips us, and we don't appear at all. See bug 335560.
384 nsIFrame *scrollFrame =
385 nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
386 if (scrollFrame) {
387 // First, use the scrollFrame to get at the scrollable view that we're in.
388 nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
389 nsIFrame *scrolled = sf->GetScrolledFrame();
390 nsRect caretInScroll = *aRect + aFrame->GetOffsetTo(scrolled);
392 // Now see if thet caret extends beyond the view's bounds. If it does,
393 // then snap it back, put it as close to the edge as it can.
394 nscoord overflow = caretInScroll.XMost() -
395 scrolled->GetVisualOverflowRectRelativeToSelf().width;
396 if (overflow > 0)
397 aRect->x -= overflow;
400 if (aBidiIndicatorSize)
401 *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
404 nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect,
405 nscoord* aBidiIndicatorSize)
407 nsCOMPtr<nsIDOMNode> focusNode;
408 nsresult rv = aSelection->GetFocusNode(getter_AddRefs(focusNode));
409 if (NS_FAILED(rv) || !focusNode)
410 return nsnull;
412 PRInt32 focusOffset;
413 rv = aSelection->GetFocusOffset(&focusOffset);
414 if (NS_FAILED(rv))
415 return nsnull;
417 nsCOMPtr<nsIContent> contentNode = do_QueryInterface(focusNode);
418 if (!contentNode)
419 return nsnull;
421 nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
422 if (!frameSelection)
423 return nsnull;
424 PRUint8 bidiLevel = frameSelection->GetCaretBidiLevel();
425 nsIFrame* frame;
426 PRInt32 frameOffset;
427 rv = GetCaretFrameForNodeOffset(contentNode, focusOffset,
428 frameSelection->GetHint(), bidiLevel,
429 &frame, &frameOffset);
430 if (NS_FAILED(rv) || !frame)
431 return nsnull;
433 GetGeometryForFrame(frame, frameOffset, aRect, aBidiIndicatorSize);
434 return frame;
437 void nsCaret::DrawCaretAfterBriefDelay()
439 // Make sure readonly caret gets drawn again if it needs to be
440 if (!mBlinkTimer) {
441 nsresult err;
442 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
443 if (NS_FAILED(err))
444 return;
447 mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, 0,
448 nsITimer::TYPE_ONE_SHOT);
451 void nsCaret::EraseCaret()
453 if (mDrawn) {
454 DrawCaret(PR_TRUE);
455 if (mReadOnly && mBlinkRate) {
456 // If readonly we don't have a blink timer set, so caret won't
457 // be redrawn automatically. We need to force the caret to get
458 // redrawn right after the paint
459 DrawCaretAfterBriefDelay();
464 void nsCaret::SetVisibilityDuringSelection(PRBool aVisibility)
466 mShowDuringSelection = aVisibility;
469 nsresult nsCaret::DrawAtPosition(nsIDOMNode* aNode, PRInt32 aOffset)
471 NS_ENSURE_ARG(aNode);
473 PRUint8 bidiLevel;
474 nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
475 if (!frameSelection)
476 return NS_ERROR_FAILURE;
477 bidiLevel = frameSelection->GetCaretBidiLevel();
479 // DrawAtPosition is used by consumers who want us to stay drawn where they
480 // tell us. Setting mBlinkRate to 0 tells us to not set a timer to erase
481 // ourselves, our consumer will take care of that.
482 mBlinkRate = 0;
484 // XXX we need to do more work here to get the correct hint.
485 nsresult rv = DrawAtPositionWithHint(aNode, aOffset,
486 nsFrameSelection::HINTLEFT,
487 bidiLevel, PR_TRUE)
488 ? NS_OK : NS_ERROR_FAILURE;
489 ToggleDrawnStatus();
490 return rv;
493 nsIFrame * nsCaret::GetCaretFrame(PRInt32 *aOffset)
495 // Return null if we're not drawn to prevent anybody from trying to draw us.
496 if (!mDrawn)
497 return nsnull;
499 // Recompute the frame that we're supposed to draw in to guarantee that
500 // we're not going to try to draw into a stale (dead) frame.
501 PRInt32 offset;
502 nsIFrame *frame = nsnull;
503 nsresult rv = GetCaretFrameForNodeOffset(mLastContent, mLastContentOffset,
504 mLastHint, mLastBidiLevel, &frame,
505 &offset);
506 if (NS_FAILED(rv))
507 return nsnull;
509 if (aOffset) {
510 *aOffset = offset;
512 return frame;
515 void nsCaret::InvalidateOutsideCaret()
517 nsIFrame *frame = GetCaretFrame();
519 // Only invalidate if we are not fully contained by our frame's rect.
520 if (frame && !frame->GetVisualOverflowRect().Contains(GetCaretRect()))
521 InvalidateRects(mCaretRect, GetHookRect(), frame);
524 void nsCaret::UpdateCaretPosition()
526 // We'll recalculate anyway if we're not drawn right now.
527 if (!mDrawn)
528 return;
530 // A trick! Make the DrawCaret code recalculate the caret's current
531 // position.
532 mDrawn = PR_FALSE;
533 DrawCaret(PR_FALSE);
536 void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder,
537 nsIRenderingContext *aCtx,
538 nsIFrame* aForFrame,
539 const nsPoint &aOffset)
541 NS_ASSERTION(mDrawn, "The caret shouldn't be drawing");
543 const nsRect drawCaretRect = mCaretRect + aOffset;
544 PRInt32 contentOffset;
545 nsIFrame* frame = GetCaretFrame(&contentOffset);
546 NS_ASSERTION(frame == aForFrame, "We're referring different frame");
547 nscolor foregroundColor = aForFrame->GetCaretColorAt(contentOffset);
549 // Only draw the native caret if the foreground color matches that of
550 // -moz-fieldtext (the color of the text in a textbox). If it doesn't match
551 // we are likely in contenteditable or a custom widget and we risk being hard to see
552 // against the background. In that case, fall back to the CSS color.
553 nsPresContext* presContext = aForFrame->PresContext();
555 if (GetHookRect().IsEmpty() && presContext) {
556 nsITheme *theme = presContext->GetTheme();
557 if (theme && theme->ThemeSupportsWidget(presContext, aForFrame, NS_THEME_TEXTFIELD_CARET)) {
558 nsILookAndFeel* lookAndFeel = presContext->LookAndFeel();
559 nscolor fieldText;
560 if (NS_SUCCEEDED(lookAndFeel->GetColor(nsILookAndFeel::eColor__moz_fieldtext, fieldText)) &&
561 fieldText == foregroundColor) {
562 theme->DrawWidgetBackground(aCtx, aForFrame, NS_THEME_TEXTFIELD_CARET,
563 drawCaretRect, drawCaretRect);
564 return;
569 aCtx->SetColor(foregroundColor);
570 aCtx->FillRect(drawCaretRect);
571 if (!GetHookRect().IsEmpty())
572 aCtx->FillRect(GetHookRect() + aOffset);
576 //-----------------------------------------------------------------------------
577 NS_IMETHODIMP nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel, PRInt16 aReason)
579 if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do
580 return NS_OK;
582 nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
584 // The same caret is shared amongst the document and any text widgets it
585 // may contain. This means that the caret could get notifications from
586 // multiple selections.
588 // If this notification is for a selection that is not the one the
589 // the caret is currently interested in (mDomSelectionWeak), then there
590 // is nothing to do!
592 if (domSel != aDomSel)
593 return NS_OK;
595 if (mVisible)
597 // Stop the caret from blinking in its previous location.
598 StopBlinking();
600 // Start the caret blinking in the new location.
601 StartBlinking();
604 return NS_OK;
608 //-----------------------------------------------------------------------------
609 void nsCaret::KillTimer()
611 if (mBlinkTimer)
613 mBlinkTimer->Cancel();
618 //-----------------------------------------------------------------------------
619 nsresult nsCaret::PrimeTimer()
621 // set up the blink timer
622 if (!mReadOnly && mBlinkRate > 0)
624 if (!mBlinkTimer) {
625 nsresult err;
626 mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
627 if (NS_FAILED(err))
628 return err;
631 mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, mBlinkRate,
632 nsITimer::TYPE_REPEATING_SLACK);
635 return NS_OK;
639 //-----------------------------------------------------------------------------
640 void nsCaret::StartBlinking()
642 if (mReadOnly) {
643 // Make sure the one draw command we use for a readonly caret isn't
644 // done until the selection is set
645 DrawCaretAfterBriefDelay();
646 return;
648 PrimeTimer();
650 // If we are currently drawn, then the second call to DrawCaret below will
651 // actually erase the caret. That would cause the caret to spend an "off"
652 // cycle before it appears, which is not really what we want. This first
653 // call to DrawCaret makes sure that the first cycle after a call to
654 // StartBlinking is an "on" cycle.
655 if (mDrawn)
656 DrawCaret(PR_TRUE);
658 DrawCaret(PR_TRUE); // draw it right away
662 //-----------------------------------------------------------------------------
663 void nsCaret::StopBlinking()
665 if (mDrawn) // erase the caret if necessary
666 DrawCaret(PR_TRUE);
668 NS_ASSERTION(!mDrawn, "Caret still drawn after StopBlinking().");
669 KillTimer();
672 PRBool
673 nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode,
674 PRInt32 aOffset,
675 nsFrameSelection::HINT aFrameHint,
676 PRUint8 aBidiLevel,
677 PRBool aInvalidate)
679 nsCOMPtr<nsIContent> contentNode = do_QueryInterface(aNode);
680 if (!contentNode)
681 return PR_FALSE;
683 nsIFrame* theFrame = nsnull;
684 PRInt32 theFrameOffset = 0;
686 nsresult rv = GetCaretFrameForNodeOffset(contentNode, aOffset, aFrameHint, aBidiLevel,
687 &theFrame, &theFrameOffset);
688 if (NS_FAILED(rv) || !theFrame)
689 return PR_FALSE;
691 // now we have a frame, check whether it's appropriate to show the caret here
692 const nsStyleUserInterface* userinterface = theFrame->GetStyleUserInterface();
693 if ((!mIgnoreUserModify &&
694 userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
695 (userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE) ||
696 (userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED))
698 return PR_FALSE;
701 if (!mDrawn)
703 // save stuff so we can figure out what frame we're in later.
704 mLastContent = contentNode;
705 mLastContentOffset = aOffset;
706 mLastHint = aFrameHint;
707 mLastBidiLevel = aBidiLevel;
709 // If there has been a reflow, set the caret Bidi level to the level of the current frame
710 if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
711 nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
712 if (!frameSelection)
713 return PR_FALSE;
714 frameSelection->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
717 // Only update the caret's rect when we're not currently drawn.
718 if (!UpdateCaretRects(theFrame, theFrameOffset))
719 return PR_FALSE;
722 if (aInvalidate)
723 InvalidateRects(mCaretRect, mHookRect, theFrame);
725 return PR_TRUE;
728 nsresult
729 nsCaret::GetCaretFrameForNodeOffset(nsIContent* aContentNode,
730 PRInt32 aOffset,
731 nsFrameSelection::HINT aFrameHint,
732 PRUint8 aBidiLevel,
733 nsIFrame** aReturnFrame,
734 PRInt32* aReturnOffset)
737 //get frame selection and find out what frame to use...
738 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
739 if (!presShell)
740 return NS_ERROR_FAILURE;
742 nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
743 if (!frameSelection)
744 return NS_ERROR_FAILURE;
746 nsIFrame* theFrame = nsnull;
747 PRInt32 theFrameOffset = 0;
749 theFrame = frameSelection->GetFrameForNodeOffset(aContentNode, aOffset,
750 aFrameHint, &theFrameOffset);
751 if (!theFrame)
752 return NS_ERROR_FAILURE;
754 // if theFrame is after a text frame that's logically at the end of the line
755 // (e.g. if theFrame is a <br> frame), then put the caret at the end of
756 // that text frame instead. This way, the caret will be positioned as if
757 // trailing whitespace was not trimmed.
758 AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
760 // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
762 // Direction Style from this->GetStyleData()
763 // now in (visibility->mDirection)
764 // ------------------
765 // NS_STYLE_DIRECTION_LTR : LTR or Default
766 // NS_STYLE_DIRECTION_RTL
767 // NS_STYLE_DIRECTION_INHERIT
768 if (mBidiUI)
770 // If there has been a reflow, take the caret Bidi level to be the level of the current frame
771 if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
772 aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
774 PRInt32 start;
775 PRInt32 end;
776 nsIFrame* frameBefore;
777 nsIFrame* frameAfter;
778 PRUint8 levelBefore; // Bidi level of the character before the caret
779 PRUint8 levelAfter; // Bidi level of the character after the caret
781 theFrame->GetOffsets(start, end);
782 if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
784 nsPrevNextBidiLevels levels = frameSelection->
785 GetPrevNextBidiLevels(aContentNode, aOffset, PR_FALSE);
787 /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
788 if (levels.mFrameBefore || levels.mFrameAfter)
790 frameBefore = levels.mFrameBefore;
791 frameAfter = levels.mFrameAfter;
792 levelBefore = levels.mLevelBefore;
793 levelAfter = levels.mLevelAfter;
795 if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
797 aBidiLevel = NS_MAX(aBidiLevel, NS_MIN(levelBefore, levelAfter)); // rule c3
798 aBidiLevel = NS_MIN(aBidiLevel, NS_MAX(levelBefore, levelAfter)); // rule c4
799 if (aBidiLevel == levelBefore // rule c1
800 || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelBefore) & 1)) // rule c5
801 || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelBefore) & 1))) // rule c9
803 if (theFrame != frameBefore)
805 if (frameBefore) // if there is a frameBefore, move into it
807 theFrame = frameBefore;
808 theFrame->GetOffsets(start, end);
809 theFrameOffset = end;
811 else
813 // if there is no frameBefore, we must be at the beginning of the line
814 // so we stay with the current frame.
815 // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
816 // real frame for the caret to be in. We have to find the visually first frame on the line.
817 PRUint8 baseLevel = NS_GET_BASE_LEVEL(frameAfter);
818 if (baseLevel != levelAfter)
820 nsPeekOffsetStruct pos;
821 pos.SetData(eSelectBeginLine, eDirPrevious, 0, 0, PR_FALSE, PR_TRUE, PR_FALSE, PR_TRUE);
822 if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
823 theFrame = pos.mResultFrame;
824 theFrameOffset = pos.mContentOffset;
830 else if (aBidiLevel == levelAfter // rule c2
831 || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelAfter) & 1)) // rule c6
832 || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelAfter) & 1))) // rule c10
834 if (theFrame != frameAfter)
836 if (frameAfter)
838 // if there is a frameAfter, move into it
839 theFrame = frameAfter;
840 theFrame->GetOffsets(start, end);
841 theFrameOffset = start;
843 else
845 // if there is no frameAfter, we must be at the end of the line
846 // so we stay with the current frame.
847 // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
848 // real frame for the caret to be in. We have to find the visually last frame on the line.
849 PRUint8 baseLevel = NS_GET_BASE_LEVEL(frameBefore);
850 if (baseLevel != levelBefore)
852 nsPeekOffsetStruct pos;
853 pos.SetData(eSelectEndLine, eDirNext, 0, 0, PR_FALSE, PR_TRUE, PR_FALSE, PR_TRUE);
854 if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
855 theFrame = pos.mResultFrame;
856 theFrameOffset = pos.mContentOffset;
862 else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8
863 && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
864 && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
866 if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
868 theFrame->GetOffsets(start, end);
869 levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
870 if (aBidiLevel & 1) // c8: caret to the right of the rightmost character
871 theFrameOffset = (levelAfter & 1) ? start : end;
872 else // c7: caret to the left of the leftmost character
873 theFrameOffset = (levelAfter & 1) ? end : start;
876 else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12
877 && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
878 && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
880 if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
882 theFrame->GetOffsets(start, end);
883 levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
884 if (aBidiLevel & 1) // c12: caret to the left of the leftmost character
885 theFrameOffset = (levelBefore & 1) ? end : start;
886 else // c11: caret to the right of the rightmost character
887 theFrameOffset = (levelBefore & 1) ? start : end;
894 *aReturnFrame = theFrame;
895 *aReturnOffset = theFrameOffset;
896 return NS_OK;
899 nsresult nsCaret::CheckCaretDrawingState()
901 if (mDrawn) {
902 // The caret is drawn; if it shouldn't be, erase it.
903 if (!mVisible || !MustDrawCaret(PR_TRUE))
904 EraseCaret();
906 else
908 // The caret is not drawn; if it should be, draw it.
909 if (mPendingDraw && (mVisible && MustDrawCaret(PR_TRUE)))
910 DrawCaret(PR_TRUE);
912 return NS_OK;
915 /*-----------------------------------------------------------------------------
917 MustDrawCaret
919 Find out if we need to do any caret drawing. This returns true if
920 either:
921 a) The caret has been drawn, and we need to erase it.
922 b) The caret is not drawn, and the selection is collapsed.
923 c) The caret is not hidden due to open XUL popups
924 (see IsMenuPopupHidingCaret()).
926 ----------------------------------------------------------------------------- */
927 PRBool nsCaret::MustDrawCaret(PRBool aIgnoreDrawnState)
929 if (!aIgnoreDrawnState && mDrawn)
930 return PR_TRUE;
932 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
933 if (!domSelection)
934 return PR_FALSE;
936 PRBool isCollapsed;
937 if (NS_FAILED(domSelection->GetIsCollapsed(&isCollapsed)))
938 return PR_FALSE;
940 if (mShowDuringSelection)
941 return PR_TRUE; // show the caret even in selections
943 if (IsMenuPopupHidingCaret())
944 return PR_FALSE;
946 return isCollapsed;
949 PRBool nsCaret::IsMenuPopupHidingCaret()
951 #ifdef MOZ_XUL
952 // Check if there are open popups.
953 nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
954 nsTArray<nsIFrame*> popups = popMgr->GetVisiblePopups();
956 if (popups.Length() == 0)
957 return PR_FALSE; // No popups, so caret can't be hidden by them.
959 // Get the selection focus content, that's where the caret would
960 // go if it was drawn.
961 nsCOMPtr<nsIDOMNode> node;
962 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
963 if (!domSelection)
964 return PR_TRUE; // No selection/caret to draw.
965 domSelection->GetFocusNode(getter_AddRefs(node));
966 if (!node)
967 return PR_TRUE; // No selection/caret to draw.
968 nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
969 if (!caretContent)
970 return PR_TRUE; // No selection/caret to draw.
972 // If there's a menu popup open before the popup with
973 // the caret, don't show the caret.
974 for (PRUint32 i=0; i<popups.Length(); i++) {
975 nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
976 nsIContent* popupContent = popupFrame->GetContent();
978 if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
979 // The caret is in this popup. There were no menu popups before this
980 // popup, so don't hide the caret.
981 return PR_FALSE;
984 if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
985 // This is an open menu popup. It does not contain the caret (else we'd
986 // have returned above). Even if the caret is in a subsequent popup,
987 // or another document/frame, it should be hidden.
988 return PR_TRUE;
991 #endif
993 // There are no open menu popups, no need to hide the caret.
994 return PR_FALSE;
997 void nsCaret::DrawCaret(PRBool aInvalidate)
999 // Do we need to draw the caret at all?
1000 if (!MustDrawCaret(PR_FALSE))
1001 return;
1003 // Can we draw the caret now?
1004 nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
1005 NS_ENSURE_TRUE(presShell, /**/);
1007 if (presShell->IsPaintingSuppressed())
1009 if (!mDrawn)
1010 mPendingDraw = PR_TRUE;
1012 // PresShell::UnsuppressAndInvalidate() will call CheckCaretDrawingState()
1013 // to get us drawn.
1014 return;
1018 nsCOMPtr<nsIDOMNode> node;
1019 PRInt32 offset;
1020 nsFrameSelection::HINT hint;
1021 PRUint8 bidiLevel;
1023 if (!mDrawn)
1025 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
1026 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
1027 if (!privateSelection) return;
1029 PRBool isCollapsed = PR_FALSE;
1030 domSelection->GetIsCollapsed(&isCollapsed);
1031 if (!mShowDuringSelection && !isCollapsed)
1032 return;
1034 PRBool hintRight;
1035 privateSelection->GetInterlinePosition(&hintRight);//translate hint.
1036 hint = hintRight ? nsFrameSelection::HINTRIGHT : nsFrameSelection::HINTLEFT;
1038 // get the node and offset, which is where we want the caret to draw
1039 domSelection->GetFocusNode(getter_AddRefs(node));
1040 if (!node)
1041 return;
1043 if (NS_FAILED(domSelection->GetFocusOffset(&offset)))
1044 return;
1046 nsCOMPtr<nsFrameSelection> frameSelection = GetFrameSelection();
1047 if (!frameSelection)
1048 return;
1050 bidiLevel = frameSelection->GetCaretBidiLevel();
1051 mPendingDraw = PR_FALSE;
1053 else
1055 if (!mLastContent)
1057 mDrawn = PR_FALSE;
1058 return;
1060 if (!mLastContent->IsInDoc())
1062 mLastContent = nsnull;
1063 mDrawn = PR_FALSE;
1064 return;
1066 node = do_QueryInterface(mLastContent);
1067 offset = mLastContentOffset;
1068 hint = mLastHint;
1069 bidiLevel = mLastBidiLevel;
1072 DrawAtPositionWithHint(node, offset, hint, bidiLevel, aInvalidate);
1073 ToggleDrawnStatus();
1076 PRBool
1077 nsCaret::UpdateCaretRects(nsIFrame* aFrame, PRInt32 aFrameOffset)
1079 NS_ASSERTION(aFrame, "Should have a frame here");
1081 nscoord bidiIndicatorSize;
1082 GetGeometryForFrame(aFrame, aFrameOffset, &mCaretRect, &bidiIndicatorSize);
1084 // on RTL frames the right edge of mCaretRect must be equal to framePos
1085 const nsStyleVisibility* vis = aFrame->GetStyleVisibility();
1086 if (NS_STYLE_DIRECTION_RTL == vis->mDirection)
1087 mCaretRect.x -= mCaretRect.width;
1089 #ifdef IBMBIDI
1090 mHookRect.Empty();
1092 // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
1093 PRBool isCaretRTL = PR_FALSE;
1094 nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
1095 // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
1096 // keyboard direction, or the user has no right-to-left keyboard
1097 // installed, so we never draw the hook.
1098 if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL)) &&
1099 mBidiUI) {
1100 if (isCaretRTL != mKeyboardRTL) {
1101 /* if the caret bidi level and the keyboard language direction are not in
1102 * synch, the keyboard language must have been changed by the
1103 * user, and if the caret is in a boundary condition (between left-to-right and
1104 * right-to-left characters) it may have to change position to
1105 * reflect the location in which the next character typed will
1106 * appear. We will call |SelectionLanguageChange| and exit
1107 * without drawing the caret in the old position.
1109 mKeyboardRTL = isCaretRTL;
1110 nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
1111 if (!domSelection ||
1112 NS_SUCCEEDED(domSelection->SelectionLanguageChange(mKeyboardRTL)))
1113 return PR_FALSE;
1115 // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
1116 // The height of the hook rectangle is the same as the width of the caret
1117 // rectangle.
1118 mHookRect.SetRect(mCaretRect.x + ((isCaretRTL) ?
1119 bidiIndicatorSize * -1 :
1120 mCaretRect.width),
1121 mCaretRect.y + bidiIndicatorSize,
1122 bidiIndicatorSize,
1123 mCaretRect.width);
1125 #endif //IBMBIDI
1126 return PR_TRUE;
1129 // static
1130 void nsCaret::InvalidateRects(const nsRect &aRect, const nsRect &aHook,
1131 nsIFrame *aFrame)
1133 NS_ASSERTION(aFrame, "Must have a frame to invalidate");
1134 nsRect rect;
1135 rect.UnionRect(aRect, aHook);
1136 aFrame->Invalidate(rect);
1139 //-----------------------------------------------------------------------------
1140 /* static */
1141 void nsCaret::CaretBlinkCallback(nsITimer *aTimer, void *aClosure)
1143 nsCaret *theCaret = reinterpret_cast<nsCaret*>(aClosure);
1144 if (!theCaret) return;
1146 theCaret->DrawCaret(PR_TRUE);
1150 //-----------------------------------------------------------------------------
1151 already_AddRefed<nsFrameSelection>
1152 nsCaret::GetFrameSelection()
1154 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryReferent(mDomSelectionWeak));
1155 if (!privateSelection)
1156 return nsnull;
1157 nsFrameSelection* frameSelection = nsnull;
1158 privateSelection->GetFrameSelection(&frameSelection);
1159 return frameSelection;
1162 void
1163 nsCaret::SetIgnoreUserModify(PRBool aIgnoreUserModify)
1165 if (!aIgnoreUserModify && mIgnoreUserModify && mDrawn) {
1166 // We're turning off mIgnoreUserModify. If the caret's drawn
1167 // in a read-only node we must erase it, else the next call
1168 // to DrawCaret() won't erase the old caret, due to the new
1169 // mIgnoreUserModify value.
1170 nsIFrame *frame = GetCaretFrame();
1171 if (frame) {
1172 const nsStyleUserInterface* userinterface = frame->GetStyleUserInterface();
1173 if (userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) {
1174 StopBlinking();
1178 mIgnoreUserModify = aIgnoreUserModify;
1181 //-----------------------------------------------------------------------------
1182 nsresult NS_NewCaret(nsCaret** aInstancePtrResult)
1184 NS_PRECONDITION(aInstancePtrResult, "null ptr");
1186 nsCaret* caret = new nsCaret();
1187 if (nsnull == caret)
1188 return NS_ERROR_OUT_OF_MEMORY;
1189 NS_ADDREF(caret);
1190 *aInstancePtrResult = caret;
1191 return NS_OK;