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
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.
24 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Mats Palmgren <mats.palmgren@bredband.net>
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 */
47 #include "nsIComponentManager.h"
48 #include "nsIServiceManager.h"
49 #include "nsFrameSelection.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"
63 #include "nsIScrollableView.h"
64 #include "nsIViewManager.h"
65 #include "nsPresContext.h"
66 #include "nsILookAndFeel.h"
67 #include "nsBlockFrame.h"
68 #include "nsISelectionController.h"
69 #include "nsDisplayList.h"
71 #include "nsTextFrame.h"
72 #include "nsXULPopupManager.h"
73 #include "nsMenuPopupFrame.h"
74 #include "nsTextFragment.h"
75 #include "nsThemeConstants.h"
77 // The bidi indicator hangs off the caret to one side, to show which
78 // direction the typing is in. It needs to be at least 2x2 to avoid looking like
79 // an insignificant dot
80 static const PRInt32 kMinBidiIndicatorPixels
= 2;
83 #include "nsIBidiKeyboard.h"
84 #include "nsContentUtils.h"
87 //-----------------------------------------------------------------------------
95 , mShowDuringSelection(PR_FALSE
)
96 , mLastContentOffset(0)
97 , mLastHint(nsFrameSelection::HINTLEFT
)
98 , mIgnoreUserModify(PR_TRUE
)
101 , mKeyboardRTL(PR_FALSE
)
106 //-----------------------------------------------------------------------------
112 //-----------------------------------------------------------------------------
113 nsresult
nsCaret::Init(nsIPresShell
*inPresShell
)
115 NS_ENSURE_ARG(inPresShell
);
117 mPresShell
= do_GetWeakReference(inPresShell
); // the presshell owns us, so no addref
118 NS_ASSERTION(mPresShell
, "Hey, pres shell should support weak refs");
120 // get nsILookAndFeel from the pres context, which has one cached.
121 nsILookAndFeel
*lookAndFeel
= nsnull
;
122 nsPresContext
*presContext
= inPresShell
->GetPresContext();
124 // XXX we should just do this nsILookAndFeel consultation every time
125 // we need these values.
126 mCaretWidthCSSPx
= 1;
127 mCaretAspectRatio
= 0;
128 if (presContext
&& (lookAndFeel
= presContext
->LookAndFeel())) {
131 if (NS_SUCCEEDED(lookAndFeel
->GetMetric(nsILookAndFeel::eMetric_CaretWidth
, tempInt
)))
132 mCaretWidthCSSPx
= (nscoord
)tempInt
;
133 if (NS_SUCCEEDED(lookAndFeel
->GetMetric(nsILookAndFeel::eMetricFloat_CaretAspectRatio
, tempFloat
)))
134 mCaretAspectRatio
= tempFloat
;
135 if (NS_SUCCEEDED(lookAndFeel
->GetMetric(nsILookAndFeel::eMetric_CaretBlinkTime
, tempInt
)))
136 mBlinkRate
= (PRUint32
)tempInt
;
137 if (NS_SUCCEEDED(lookAndFeel
->GetMetric(nsILookAndFeel::eMetric_ShowCaretDuringSelection
, tempInt
)))
138 mShowDuringSelection
= tempInt
? PR_TRUE
: PR_FALSE
;
141 // get the selection from the pres shell, and set ourselves up as a selection
144 nsCOMPtr
<nsISelectionController
> selCon
= do_QueryReferent(mPresShell
);
146 return NS_ERROR_FAILURE
;
148 nsCOMPtr
<nsISelection
> domSelection
;
149 nsresult rv
= selCon
->GetSelection(nsISelectionController::SELECTION_NORMAL
,
150 getter_AddRefs(domSelection
));
154 return NS_ERROR_FAILURE
;
156 nsCOMPtr
<nsISelectionPrivate
> privateSelection
= do_QueryInterface(domSelection
);
157 if (privateSelection
)
158 privateSelection
->AddSelectionListener(this);
159 mDomSelectionWeak
= do_GetWeakReference(domSelection
);
161 // set up the blink timer
167 mBidiUI
= nsContentUtils::GetBoolPref("bidi.browser.ui");
174 DrawCJKCaret(nsIFrame
* aFrame
, PRInt32 aOffset
)
176 nsIContent
* content
= aFrame
->GetContent();
177 const nsTextFragment
* frag
= content
->GetText();
180 if (aOffset
< 0 || aOffset
>= frag
->GetLength())
182 PRUnichar ch
= frag
->CharAt(aOffset
);
183 return 0x2e80 <= ch
&& ch
<= 0xd7ff;
186 nsCaret::Metrics
nsCaret::ComputeMetrics(nsIFrame
* aFrame
, PRInt32 aOffset
, nscoord aCaretHeight
)
188 // Compute nominal sizes in appunits
189 nscoord caretWidth
= (aCaretHeight
* mCaretAspectRatio
) +
190 nsPresContext::CSSPixelsToAppUnits(mCaretWidthCSSPx
);
192 if (DrawCJKCaret(aFrame
, aOffset
)) {
193 caretWidth
+= nsPresContext::CSSPixelsToAppUnits(1);
195 nscoord bidiIndicatorSize
= nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels
);
196 bidiIndicatorSize
= PR_MAX(caretWidth
, bidiIndicatorSize
);
198 // Round them to device pixels. Always round down, except that anything
199 // between 0 and 1 goes up to 1 so we don't let the caret disappear.
200 PRUint32 tpp
= aFrame
->PresContext()->AppUnitsPerDevPixel();
202 result
.mCaretWidth
= NS_ROUND_BORDER_TO_PIXELS(caretWidth
, tpp
);
203 result
.mBidiIndicatorSize
= NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize
, tpp
);
207 //-----------------------------------------------------------------------------
208 void nsCaret::Terminate()
210 // this doesn't erase the caret if it's drawn. Should it? We might not have
211 // a good drawing environment during teardown.
214 mBlinkTimer
= nsnull
;
216 // unregiser ourselves as a selection listener
217 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
218 nsCOMPtr
<nsISelectionPrivate
> privateSelection(do_QueryInterface(domSelection
));
219 if (privateSelection
)
220 privateSelection
->RemoveSelectionListener(this);
221 mDomSelectionWeak
= nsnull
;
224 mLastContent
= nsnull
;
227 //-----------------------------------------------------------------------------
228 NS_IMPL_ISUPPORTS1(nsCaret
, nsISelectionListener
)
230 //-----------------------------------------------------------------------------
231 nsISelection
* nsCaret::GetCaretDOMSelection()
233 nsCOMPtr
<nsISelection
> sel(do_QueryReferent(mDomSelectionWeak
));
237 //-----------------------------------------------------------------------------
238 nsresult
nsCaret::SetCaretDOMSelection(nsISelection
*aDOMSel
)
240 NS_ENSURE_ARG_POINTER(aDOMSel
);
241 mDomSelectionWeak
= do_GetWeakReference(aDOMSel
); // weak reference to pres shell
244 // Stop the caret from blinking in its previous location.
246 // Start the caret blinking in the new location.
253 //-----------------------------------------------------------------------------
254 void nsCaret::SetCaretVisible(PRBool inMakeVisible
)
256 mVisible
= inMakeVisible
;
259 SetIgnoreUserModify(PR_TRUE
);
262 SetIgnoreUserModify(PR_FALSE
);
267 //-----------------------------------------------------------------------------
268 nsresult
nsCaret::GetCaretVisible(PRBool
*outMakeVisible
)
270 NS_ENSURE_ARG_POINTER(outMakeVisible
);
271 *outMakeVisible
= (mVisible
&& MustDrawCaret(PR_TRUE
));
276 //-----------------------------------------------------------------------------
277 void nsCaret::SetCaretReadOnly(PRBool inMakeReadonly
)
279 mReadOnly
= inMakeReadonly
;
283 //-----------------------------------------------------------------------------
284 nsresult
nsCaret::GetCaretCoordinates(EViewCoordinates aRelativeToType
,
285 nsISelection
*aDOMSel
,
286 nsRect
*outCoordinates
,
287 PRBool
*outIsCollapsed
,
291 return NS_ERROR_NOT_INITIALIZED
;
292 if (!outCoordinates
|| !outIsCollapsed
)
293 return NS_ERROR_NULL_POINTER
;
295 nsCOMPtr
<nsISelection
> domSelection
= aDOMSel
;
300 // fill in defaults for failure
301 outCoordinates
->x
= -1;
302 outCoordinates
->y
= -1;
303 outCoordinates
->width
= -1;
304 outCoordinates
->height
= -1;
305 *outIsCollapsed
= PR_FALSE
;
307 nsresult err
= domSelection
->GetIsCollapsed(outIsCollapsed
);
311 nsCOMPtr
<nsIDOMNode
> focusNode
;
313 err
= domSelection
->GetFocusNode(getter_AddRefs(focusNode
));
317 return NS_ERROR_FAILURE
;
320 err
= domSelection
->GetFocusOffset(&focusOffset
);
324 nsCOMPtr
<nsIContent
> contentNode
= do_QueryInterface(focusNode
);
326 return NS_ERROR_FAILURE
;
328 // find the frame that contains the content node that has focus
329 nsIFrame
* theFrame
= nsnull
;
330 PRInt32 theFrameOffset
= 0;
332 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
334 return NS_ERROR_FAILURE
;
335 PRUint8 bidiLevel
= frameSelection
->GetCaretBidiLevel();
337 err
= GetCaretFrameForNodeOffset(contentNode
, focusOffset
,
338 frameSelection
->GetHint(), bidiLevel
,
339 &theFrame
, &theFrameOffset
);
340 if (NS_FAILED(err
) || !theFrame
)
343 nsPoint
viewOffset(0, 0);
344 nsIView
*drawingView
; // views are not refcounted
346 GetViewForRendering(theFrame
, aRelativeToType
, viewOffset
, &drawingView
, outView
);
348 return NS_ERROR_UNEXPECTED
;
350 nsPoint
framePos(0, 0);
351 err
= theFrame
->GetPointFromOffset(theFrameOffset
, &framePos
);
355 // we don't need drawingView anymore so reuse that; reset viewOffset values for our purposes
356 if (aRelativeToType
== eClosestViewCoordinates
)
358 theFrame
->GetOffsetFromView(viewOffset
, &drawingView
);
360 *outView
= drawingView
;
362 // now add the frame offset to the view offset, and we're done
363 viewOffset
+= framePos
;
364 outCoordinates
->x
= viewOffset
.x
;
365 outCoordinates
->y
= viewOffset
.y
;
366 outCoordinates
->height
= theFrame
->GetSize().height
;
367 outCoordinates
->width
= ComputeMetrics(theFrame
, theFrameOffset
, outCoordinates
->height
).mCaretWidth
;
372 void nsCaret::DrawCaretAfterBriefDelay()
374 // Make sure readonly caret gets drawn again if it needs to be
377 mBlinkTimer
= do_CreateInstance("@mozilla.org/timer;1", &err
);
382 mBlinkTimer
->InitWithFuncCallback(CaretBlinkCallback
, this, 0,
383 nsITimer::TYPE_ONE_SHOT
);
386 void nsCaret::EraseCaret()
390 if (mReadOnly
&& mBlinkRate
) {
391 // If readonly we don't have a blink timer set, so caret won't
392 // be redrawn automatically. We need to force the caret to get
393 // redrawn right after the paint
394 DrawCaretAfterBriefDelay();
399 void nsCaret::SetVisibilityDuringSelection(PRBool aVisibility
)
401 mShowDuringSelection
= aVisibility
;
404 nsresult
nsCaret::DrawAtPosition(nsIDOMNode
* aNode
, PRInt32 aOffset
)
406 NS_ENSURE_ARG(aNode
);
409 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
411 return NS_ERROR_FAILURE
;
412 bidiLevel
= frameSelection
->GetCaretBidiLevel();
414 // DrawAtPosition is used by consumers who want us to stay drawn where they
415 // tell us. Setting mBlinkRate to 0 tells us to not set a timer to erase
416 // ourselves, our consumer will take care of that.
419 // XXX we need to do more work here to get the correct hint.
420 nsresult rv
= DrawAtPositionWithHint(aNode
, aOffset
,
421 nsFrameSelection::HINTLEFT
,
423 ? NS_OK
: NS_ERROR_FAILURE
;
428 nsIFrame
* nsCaret::GetCaretFrame()
430 // Return null if we're not drawn to prevent anybody from trying to draw us.
434 // Recompute the frame that we're supposed to draw in to guarantee that
435 // we're not going to try to draw into a stale (dead) frame.
437 nsIFrame
*frame
= nsnull
;
438 nsresult rv
= GetCaretFrameForNodeOffset(mLastContent
, mLastContentOffset
,
439 mLastHint
, mLastBidiLevel
, &frame
,
447 void nsCaret::InvalidateOutsideCaret()
449 nsIFrame
*frame
= GetCaretFrame();
451 // Only invalidate if we are not fully contained by our frame's rect.
452 if (frame
&& !frame
->GetOverflowRect().Contains(GetCaretRect()))
453 InvalidateRects(mCaretRect
, GetHookRect(), frame
);
456 void nsCaret::UpdateCaretPosition()
458 // We'll recalculate anyway if we're not drawn right now.
462 // A trick! Make the DrawCaret code recalculate the caret's current
468 void nsCaret::PaintCaret(nsDisplayListBuilder
*aBuilder
,
469 nsIRenderingContext
*aCtx
,
471 const nsPoint
&aOffset
)
473 NS_ASSERTION(mDrawn
, "The caret shouldn't be drawing");
475 const nsRect drawCaretRect
= mCaretRect
+ aOffset
;
476 nscolor cssColor
= aForFrame
->GetStyleColor()->mColor
;
478 // Only draw the native caret if the foreground color matches that of
479 // -moz-fieldtext (the color of the text in a textbox). If it doesn't match
480 // we are likely in contenteditable or a custom widget and we risk being hard to see
481 // against the background. In that case, fall back to the CSS color.
482 nsPresContext
* presContext
= aForFrame
->PresContext();
484 if (GetHookRect().IsEmpty() && presContext
) {
485 nsITheme
*theme
= presContext
->GetTheme();
486 if (theme
&& theme
->ThemeSupportsWidget(presContext
, aForFrame
, NS_THEME_TEXTFIELD_CARET
)) {
487 nsILookAndFeel
* lookAndFeel
= presContext
->LookAndFeel();
489 if (NS_SUCCEEDED(lookAndFeel
->GetColor(nsILookAndFeel::eColor__moz_fieldtext
, fieldText
)) &&
490 fieldText
== cssColor
) {
491 theme
->DrawWidgetBackground(aCtx
, aForFrame
, NS_THEME_TEXTFIELD_CARET
,
492 drawCaretRect
, drawCaretRect
);
498 aCtx
->SetColor(cssColor
);
499 aCtx
->FillRect(drawCaretRect
);
500 if (!GetHookRect().IsEmpty())
501 aCtx
->FillRect(GetHookRect() + aOffset
);
505 //-----------------------------------------------------------------------------
506 NS_IMETHODIMP
nsCaret::NotifySelectionChanged(nsIDOMDocument
*, nsISelection
*aDomSel
, PRInt16 aReason
)
508 if (aReason
& nsISelectionListener::MOUSEUP_REASON
)//this wont do
511 nsCOMPtr
<nsISelection
> domSel(do_QueryReferent(mDomSelectionWeak
));
513 // The same caret is shared amongst the document and any text widgets it
514 // may contain. This means that the caret could get notifications from
515 // multiple selections.
517 // If this notification is for a selection that is not the one the
518 // the caret is currently interested in (mDomSelectionWeak), then there
521 if (domSel
!= aDomSel
)
526 // Stop the caret from blinking in its previous location.
529 // Start the caret blinking in the new location.
537 //-----------------------------------------------------------------------------
538 void nsCaret::KillTimer()
542 mBlinkTimer
->Cancel();
547 //-----------------------------------------------------------------------------
548 nsresult
nsCaret::PrimeTimer()
550 // set up the blink timer
551 if (!mReadOnly
&& mBlinkRate
> 0)
555 mBlinkTimer
= do_CreateInstance("@mozilla.org/timer;1", &err
);
560 mBlinkTimer
->InitWithFuncCallback(CaretBlinkCallback
, this, mBlinkRate
,
561 nsITimer::TYPE_REPEATING_SLACK
);
568 //-----------------------------------------------------------------------------
569 void nsCaret::StartBlinking()
572 // Make sure the one draw command we use for a readonly caret isn't
573 // done until the selection is set
574 DrawCaretAfterBriefDelay();
579 // If we are currently drawn, then the second call to DrawCaret below will
580 // actually erase the caret. That would cause the caret to spend an "off"
581 // cycle before it appears, which is not really what we want. This first
582 // call to DrawCaret makes sure that the first cycle after a call to
583 // StartBlinking is an "on" cycle.
587 DrawCaret(PR_TRUE
); // draw it right away
591 //-----------------------------------------------------------------------------
592 void nsCaret::StopBlinking()
594 if (mDrawn
) // erase the caret if necessary
597 NS_ASSERTION(!mDrawn
, "Caret still drawn after StopBlinking().");
602 nsCaret::DrawAtPositionWithHint(nsIDOMNode
* aNode
,
604 nsFrameSelection::HINT aFrameHint
,
608 nsCOMPtr
<nsIContent
> contentNode
= do_QueryInterface(aNode
);
612 nsIFrame
* theFrame
= nsnull
;
613 PRInt32 theFrameOffset
= 0;
615 nsresult rv
= GetCaretFrameForNodeOffset(contentNode
, aOffset
, aFrameHint
, aBidiLevel
,
616 &theFrame
, &theFrameOffset
);
617 if (NS_FAILED(rv
) || !theFrame
)
620 // now we have a frame, check whether it's appropriate to show the caret here
621 const nsStyleUserInterface
* userinterface
= theFrame
->GetStyleUserInterface();
622 if ((!mIgnoreUserModify
&&
623 userinterface
->mUserModify
== NS_STYLE_USER_MODIFY_READ_ONLY
) ||
624 (userinterface
->mUserInput
== NS_STYLE_USER_INPUT_NONE
) ||
625 (userinterface
->mUserInput
== NS_STYLE_USER_INPUT_DISABLED
))
632 // save stuff so we can figure out what frame we're in later.
633 mLastContent
= contentNode
;
634 mLastContentOffset
= aOffset
;
635 mLastHint
= aFrameHint
;
636 mLastBidiLevel
= aBidiLevel
;
638 // If there has been a reflow, set the caret Bidi level to the level of the current frame
639 if (aBidiLevel
& BIDI_LEVEL_UNDEFINED
) {
640 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
643 frameSelection
->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame
));
646 // Only update the caret's rect when we're not currently drawn.
647 rv
= UpdateCaretRects(theFrame
, theFrameOffset
);
653 InvalidateRects(mCaretRect
, mHookRect
, theFrame
);
659 * Find the first frame in an in-order traversal of the frame subtree rooted
660 * at aFrame which is either a text frame logically at the end of a line,
661 * or which is aStopAtFrame. Return null if no such frame is found. We don't
662 * descend into the children of non-eLineParticipant frames.
665 CheckForTrailingTextFrameRecursive(nsIFrame
* aFrame
, nsIFrame
* aStopAtFrame
)
667 if (aFrame
== aStopAtFrame
||
668 ((aFrame
->GetType() == nsGkAtoms::textFrame
&&
669 (static_cast<nsTextFrame
*>(aFrame
))->IsAtEndOfLine())))
671 if (!aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
))
674 for (nsIFrame
* f
= aFrame
->GetFirstChild(nsnull
); f
; f
= f
->GetNextSibling())
676 nsIFrame
* r
= CheckForTrailingTextFrameRecursive(f
, aStopAtFrame
);
684 FindContainingLine(nsIFrame
* aFrame
)
686 while (aFrame
&& aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
))
688 nsIFrame
* parent
= aFrame
->GetParent();
689 nsBlockFrame
* blockParent
= nsLayoutUtils::GetAsBlock(parent
);
693 nsBlockInFlowLineIterator
iter(blockParent
, aFrame
, &isValid
);
694 return isValid
? iter
.GetLine().get() : nsnull
;
702 AdjustCaretFrameForLineEnd(nsIFrame
** aFrame
, PRInt32
* aOffset
)
704 nsLineBox
* line
= FindContainingLine(*aFrame
);
707 PRInt32 count
= line
->GetChildCount();
708 for (nsIFrame
* f
= line
->mFirstChild
; count
> 0; --count
, f
= f
->GetNextSibling())
710 nsIFrame
* r
= CheckForTrailingTextFrameRecursive(f
, *aFrame
);
716 NS_ASSERTION(r
->GetType() == nsGkAtoms::textFrame
, "Expected text frame");
717 *aOffset
= (static_cast<nsTextFrame
*>(r
))->GetContentEnd();
724 nsCaret::GetCaretFrameForNodeOffset(nsIContent
* aContentNode
,
726 nsFrameSelection::HINT aFrameHint
,
728 nsIFrame
** aReturnFrame
,
729 PRInt32
* aReturnOffset
)
732 //get frame selection and find out what frame to use...
733 nsCOMPtr
<nsIPresShell
> presShell
= do_QueryReferent(mPresShell
);
735 return NS_ERROR_FAILURE
;
737 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
739 return NS_ERROR_FAILURE
;
741 nsIFrame
* theFrame
= nsnull
;
742 PRInt32 theFrameOffset
= 0;
744 theFrame
= frameSelection
->GetFrameForNodeOffset(aContentNode
, aOffset
,
745 aFrameHint
, &theFrameOffset
);
747 return NS_ERROR_FAILURE
;
749 // if theFrame is after a text frame that's logically at the end of the line
750 // (e.g. if theFrame is a <br> frame), then put the caret at the end of
751 // that text frame instead. This way, the caret will be positioned as if
752 // trailing whitespace was not trimmed.
753 AdjustCaretFrameForLineEnd(&theFrame
, &theFrameOffset
);
755 // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
757 // Direction Style from this->GetStyleData()
758 // now in (visibility->mDirection)
759 // ------------------
760 // NS_STYLE_DIRECTION_LTR : LTR or Default
761 // NS_STYLE_DIRECTION_RTL
762 // NS_STYLE_DIRECTION_INHERIT
765 // If there has been a reflow, take the caret Bidi level to be the level of the current frame
766 if (aBidiLevel
& BIDI_LEVEL_UNDEFINED
)
767 aBidiLevel
= NS_GET_EMBEDDING_LEVEL(theFrame
);
771 nsIFrame
* frameBefore
;
772 nsIFrame
* frameAfter
;
773 PRUint8 levelBefore
; // Bidi level of the character before the caret
774 PRUint8 levelAfter
; // Bidi level of the character after the caret
776 theFrame
->GetOffsets(start
, end
);
777 if (start
== 0 || end
== 0 || start
== theFrameOffset
|| end
== theFrameOffset
)
779 nsPrevNextBidiLevels levels
= frameSelection
->
780 GetPrevNextBidiLevels(aContentNode
, aOffset
, PR_FALSE
);
782 /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
783 if (levels
.mFrameBefore
|| levels
.mFrameAfter
)
785 frameBefore
= levels
.mFrameBefore
;
786 frameAfter
= levels
.mFrameAfter
;
787 levelBefore
= levels
.mLevelBefore
;
788 levelAfter
= levels
.mLevelAfter
;
790 if ((levelBefore
!= levelAfter
) || (aBidiLevel
!= levelBefore
))
792 aBidiLevel
= PR_MAX(aBidiLevel
, PR_MIN(levelBefore
, levelAfter
)); // rule c3
793 aBidiLevel
= PR_MIN(aBidiLevel
, PR_MAX(levelBefore
, levelAfter
)); // rule c4
794 if (aBidiLevel
== levelBefore
// rule c1
795 || (aBidiLevel
> levelBefore
&& aBidiLevel
< levelAfter
&& !((aBidiLevel
^ levelBefore
) & 1)) // rule c5
796 || (aBidiLevel
< levelBefore
&& aBidiLevel
> levelAfter
&& !((aBidiLevel
^ levelBefore
) & 1))) // rule c9
798 if (theFrame
!= frameBefore
)
800 if (frameBefore
) // if there is a frameBefore, move into it
802 theFrame
= frameBefore
;
803 theFrame
->GetOffsets(start
, end
);
804 theFrameOffset
= end
;
808 // if there is no frameBefore, we must be at the beginning of the line
809 // so we stay with the current frame.
810 // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
811 // real frame for the caret to be in. We have to find the visually first frame on the line.
812 PRUint8 baseLevel
= NS_GET_BASE_LEVEL(frameAfter
);
813 if (baseLevel
!= levelAfter
)
815 nsPeekOffsetStruct pos
;
816 pos
.SetData(eSelectBeginLine
, eDirPrevious
, 0, 0, PR_FALSE
, PR_TRUE
, PR_FALSE
, PR_TRUE
);
817 if (NS_SUCCEEDED(frameAfter
->PeekOffset(&pos
))) {
818 theFrame
= pos
.mResultFrame
;
819 theFrameOffset
= pos
.mContentOffset
;
825 else if (aBidiLevel
== levelAfter
// rule c2
826 || (aBidiLevel
> levelBefore
&& aBidiLevel
< levelAfter
&& !((aBidiLevel
^ levelAfter
) & 1)) // rule c6
827 || (aBidiLevel
< levelBefore
&& aBidiLevel
> levelAfter
&& !((aBidiLevel
^ levelAfter
) & 1))) // rule c10
829 if (theFrame
!= frameAfter
)
833 // if there is a frameAfter, move into it
834 theFrame
= frameAfter
;
835 theFrame
->GetOffsets(start
, end
);
836 theFrameOffset
= start
;
840 // if there is no frameAfter, we must be at the end of the line
841 // so we stay with the current frame.
842 // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
843 // real frame for the caret to be in. We have to find the visually last frame on the line.
844 PRUint8 baseLevel
= NS_GET_BASE_LEVEL(frameBefore
);
845 if (baseLevel
!= levelBefore
)
847 nsPeekOffsetStruct pos
;
848 pos
.SetData(eSelectEndLine
, eDirNext
, 0, 0, PR_FALSE
, PR_TRUE
, PR_FALSE
, PR_TRUE
);
849 if (NS_SUCCEEDED(frameBefore
->PeekOffset(&pos
))) {
850 theFrame
= pos
.mResultFrame
;
851 theFrameOffset
= pos
.mContentOffset
;
857 else if (aBidiLevel
> levelBefore
&& aBidiLevel
< levelAfter
// rule c7/8
858 && !((levelBefore
^ levelAfter
) & 1) // before and after have the same parity
859 && ((aBidiLevel
^ levelAfter
) & 1)) // caret has different parity
861 if (NS_SUCCEEDED(frameSelection
->GetFrameFromLevel(frameAfter
, eDirNext
, aBidiLevel
, &theFrame
)))
863 theFrame
->GetOffsets(start
, end
);
864 levelAfter
= NS_GET_EMBEDDING_LEVEL(theFrame
);
865 if (aBidiLevel
& 1) // c8: caret to the right of the rightmost character
866 theFrameOffset
= (levelAfter
& 1) ? start
: end
;
867 else // c7: caret to the left of the leftmost character
868 theFrameOffset
= (levelAfter
& 1) ? end
: start
;
871 else if (aBidiLevel
< levelBefore
&& aBidiLevel
> levelAfter
// rule c11/12
872 && !((levelBefore
^ levelAfter
) & 1) // before and after have the same parity
873 && ((aBidiLevel
^ levelAfter
) & 1)) // caret has different parity
875 if (NS_SUCCEEDED(frameSelection
->GetFrameFromLevel(frameBefore
, eDirPrevious
, aBidiLevel
, &theFrame
)))
877 theFrame
->GetOffsets(start
, end
);
878 levelBefore
= NS_GET_EMBEDDING_LEVEL(theFrame
);
879 if (aBidiLevel
& 1) // c12: caret to the left of the leftmost character
880 theFrameOffset
= (levelBefore
& 1) ? end
: start
;
881 else // c11: caret to the right of the rightmost character
882 theFrameOffset
= (levelBefore
& 1) ? start
: end
;
889 *aReturnFrame
= theFrame
;
890 *aReturnOffset
= theFrameOffset
;
895 //-----------------------------------------------------------------------------
896 void nsCaret::GetViewForRendering(nsIFrame
*caretFrame
,
897 EViewCoordinates coordType
,
899 nsIView
**outRenderingView
,
900 nsIView
**outRelativeView
)
902 if (!caretFrame
|| !outRenderingView
)
905 *outRenderingView
= nsnull
;
907 *outRelativeView
= nsnull
;
909 NS_ASSERTION(caretFrame
, "Should have a frame here");
914 nsPoint
withinViewOffset(0, 0);
915 // get the offset of this frame from its parent view (walks up frame hierarchy)
916 nsIView
* theView
= nsnull
;
917 caretFrame
->GetOffsetFromView(withinViewOffset
, &theView
);
921 if (outRelativeView
&& coordType
== eClosestViewCoordinates
)
922 *outRelativeView
= theView
;
924 // Note: views are not refcounted.
925 nsIView
* returnView
= nsIView::GetViewFor(theView
->GetNearestWidget(nsnull
));
927 // This gets uses the first view with a widget
928 if (coordType
== eRenderingViewCoordinates
) {
930 // Now adjust the view offset for this view.
931 withinViewOffset
+= theView
->GetOffsetTo(returnView
);
933 // Account for the view's origin not lining up with the widget's
935 withinViewOffset
+= returnView
->GetPosition() -
936 returnView
->GetBounds().TopLeft();
937 viewOffset
= withinViewOffset
;
940 *outRelativeView
= returnView
;
944 // window-relative coordinates. Done for us by the view.
945 withinViewOffset
+= theView
->GetOffsetTo(nsnull
);
946 viewOffset
= withinViewOffset
;
948 // Get the relative view for top level window coordinates
949 if (outRelativeView
&& coordType
== eTopLevelWindowCoordinates
) {
950 nsCOMPtr
<nsIPresShell
> presShell
= do_QueryReferent(mPresShell
);
952 nsIViewManager
* vm
= presShell
->GetViewManager();
954 vm
->GetRootView(*outRelativeView
);
960 *outRenderingView
= returnView
;
963 nsresult
nsCaret::CheckCaretDrawingState()
965 // If the caret's drawn when it shouldn't be, erase it.
966 if (mDrawn
&& (!mVisible
|| !MustDrawCaret(PR_TRUE
)))
971 /*-----------------------------------------------------------------------------
975 Find out if we need to do any caret drawing. This returns true if
977 a) The caret has been drawn, and we need to erase it.
978 b) The caret is not drawn, and the selection is collapsed.
979 c) The caret is not hidden due to open XUL popups
980 (see IsMenuPopupHidingCaret()).
982 ----------------------------------------------------------------------------- */
983 PRBool
nsCaret::MustDrawCaret(PRBool aIgnoreDrawnState
)
985 nsCOMPtr
<nsIPresShell
> presShell
= do_QueryReferent(mPresShell
);
987 PRBool isPaintingSuppressed
;
988 presShell
->IsPaintingSuppressed(&isPaintingSuppressed
);
989 if (isPaintingSuppressed
)
993 if (!aIgnoreDrawnState
&& mDrawn
)
996 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
1001 if (NS_FAILED(domSelection
->GetIsCollapsed(&isCollapsed
)))
1004 if (mShowDuringSelection
)
1005 return PR_TRUE
; // show the caret even in selections
1007 if (IsMenuPopupHidingCaret())
1013 PRBool
nsCaret::IsMenuPopupHidingCaret()
1016 // Check if there are open popups.
1017 nsXULPopupManager
*popMgr
= nsXULPopupManager::GetInstance();
1018 nsTArray
<nsIFrame
*> popups
= popMgr
->GetOpenPopups();
1020 if (popups
.Length() == 0)
1021 return PR_FALSE
; // No popups, so caret can't be hidden by them.
1023 // Get the selection focus content, that's where the caret would
1024 // go if it was drawn.
1025 nsCOMPtr
<nsIDOMNode
> node
;
1026 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
1028 return PR_TRUE
; // No selection/caret to draw.
1029 domSelection
->GetFocusNode(getter_AddRefs(node
));
1031 return PR_TRUE
; // No selection/caret to draw.
1032 nsCOMPtr
<nsIContent
> caretContent
= do_QueryInterface(node
);
1034 return PR_TRUE
; // No selection/caret to draw.
1036 // If there's a menu popup open before the popup with
1037 // the caret, don't show the caret.
1038 for (PRUint32 i
=0; i
<popups
.Length(); i
++) {
1039 nsMenuPopupFrame
* popupFrame
= static_cast<nsMenuPopupFrame
*>(popups
[i
]);
1040 nsIContent
* popupContent
= popupFrame
->GetContent();
1042 if (nsContentUtils::ContentIsDescendantOf(caretContent
, popupContent
)) {
1043 // The caret is in this popup. There were no menu popups before this
1044 // popup, so don't hide the caret.
1048 if (popupFrame
->PopupType() == ePopupTypeMenu
&& !popupFrame
->IsContextMenu()) {
1049 // This is an open menu popup. It does not contain the caret (else we'd
1050 // have returned above). Even if the caret is in a subsequent popup,
1051 // or another document/frame, it should be hidden.
1057 // There are no open menu popups, no need to hide the caret.
1061 /*-----------------------------------------------------------------------------
1065 ----------------------------------------------------------------------------- */
1067 void nsCaret::DrawCaret(PRBool aInvalidate
)
1069 // do we need to draw the caret at all?
1070 if (!MustDrawCaret(PR_FALSE
))
1073 nsCOMPtr
<nsIDOMNode
> node
;
1075 nsFrameSelection::HINT hint
;
1080 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
1081 nsCOMPtr
<nsISelectionPrivate
> privateSelection(do_QueryInterface(domSelection
));
1082 if (!privateSelection
) return;
1084 PRBool isCollapsed
= PR_FALSE
;
1085 domSelection
->GetIsCollapsed(&isCollapsed
);
1086 if (!mShowDuringSelection
&& !isCollapsed
)
1090 privateSelection
->GetInterlinePosition(&hintRight
);//translate hint.
1091 hint
= hintRight
? nsFrameSelection::HINTRIGHT
: nsFrameSelection::HINTLEFT
;
1093 // get the node and offset, which is where we want the caret to draw
1094 domSelection
->GetFocusNode(getter_AddRefs(node
));
1098 if (NS_FAILED(domSelection
->GetFocusOffset(&offset
)))
1101 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
1102 if (!frameSelection
)
1104 bidiLevel
= frameSelection
->GetCaretBidiLevel();
1113 if (!mLastContent
->IsInDoc())
1115 mLastContent
= nsnull
;
1119 node
= do_QueryInterface(mLastContent
);
1120 offset
= mLastContentOffset
;
1122 bidiLevel
= mLastBidiLevel
;
1125 DrawAtPositionWithHint(node
, offset
, hint
, bidiLevel
, aInvalidate
);
1126 ToggleDrawnStatus();
1129 nsresult
nsCaret::UpdateCaretRects(nsIFrame
* aFrame
, PRInt32 aFrameOffset
)
1131 NS_ASSERTION(aFrame
, "Should have a frame here");
1133 nsRect frameRect
= aFrame
->GetRect();
1137 nsCOMPtr
<nsIPresShell
> presShell
= do_QueryReferent(mPresShell
);
1138 if (!presShell
) return NS_ERROR_FAILURE
;
1140 nsPresContext
*presContext
= presShell
->GetPresContext();
1142 // if we got a zero-height frame, it's probably a BR frame at the end of a non-empty line
1143 // (see BRFrame::Reflow). In that case, figure out a height. We have to do this
1144 // after we've got an RC.
1145 if (frameRect
.height
== 0)
1147 nsCOMPtr
<nsIFontMetrics
> fm
;
1148 nsLayoutUtils::GetFontMetricsForFrame(aFrame
, getter_AddRefs(fm
));
1152 nscoord ascent
, descent
;
1153 fm
->GetMaxAscent(ascent
);
1154 fm
->GetMaxDescent(descent
);
1155 frameRect
.height
= ascent
+ descent
;
1156 frameRect
.y
-= ascent
; // BR frames sit on the baseline of the text, so we need to subtract
1157 // the ascent to account for the frame height.
1161 mCaretRect
= frameRect
;
1162 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
1163 nsCOMPtr
<nsISelectionPrivate
> privateSelection
= do_QueryInterface(domSelection
);
1167 // if cache in selection is available, apply it, else refresh it
1168 nsresult rv
= privateSelection
->GetCachedFrameOffset(aFrame
, aFrameOffset
,
1176 mCaretRect
+= framePos
;
1177 Metrics metrics
= ComputeMetrics(aFrame
, aFrameOffset
, mCaretRect
.height
);
1178 mCaretRect
.width
= metrics
.mCaretWidth
;
1180 // Clamp our position to be within our scroll frame. If we don't, then it
1181 // clips us, and we don't appear at all. See bug 335560.
1182 nsIFrame
*scrollFrame
=
1183 nsLayoutUtils::GetClosestFrameOfType(aFrame
, nsGkAtoms::scrollFrame
);
1186 // First, use the scrollFrame to get at the scrollable view that we're in.
1187 nsIScrollableFrame
*scrollable
;
1188 CallQueryInterface(scrollFrame
, &scrollable
);
1189 nsIScrollableView
*scrollView
= scrollable
->GetScrollableView();
1191 scrollView
->GetScrolledView(view
);
1193 // Compute the caret's coordinates in the enclosing view's coordinate
1194 // space. To do so, we need to correct for both the original frame's
1195 // offset from the scrollframe, and the scrollable view's offset from the
1196 // scrolled frame's view.
1197 nsPoint toScroll
= aFrame
->GetOffsetTo(scrollFrame
) -
1198 view
->GetOffsetTo(scrollFrame
->GetView());
1199 nsRect caretInScroll
= mCaretRect
+ toScroll
;
1201 // Now see if thet caret extends beyond the view's bounds. If it does,
1202 // then snap it back, put it as close to the edge as it can.
1203 nscoord overflow
= caretInScroll
.XMost() - view
->GetBounds().width
;
1205 mCaretRect
.x
-= overflow
;
1208 // on RTL frames the right edge of mCaretRect must be equal to framePos
1209 const nsStyleVisibility
* vis
= aFrame
->GetStyleVisibility();
1210 if (NS_STYLE_DIRECTION_RTL
== vis
->mDirection
)
1211 mCaretRect
.x
-= mCaretRect
.width
;
1213 return UpdateHookRect(presContext
, metrics
);
1216 nsresult
nsCaret::UpdateHookRect(nsPresContext
* aPresContext
,
1217 const Metrics
& aMetrics
)
1222 // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
1223 PRBool isCaretRTL
=PR_FALSE
;
1224 nsIBidiKeyboard
* bidiKeyboard
= nsContentUtils::GetBidiKeyboard();
1225 if (!bidiKeyboard
|| NS_FAILED(bidiKeyboard
->IsLangRTL(&isCaretRTL
)))
1226 // if bidiKeyboard->IsLangRTL() failed, there is no way to tell the
1227 // keyboard direction, or the user has no right-to-left keyboard
1228 // installed, so we never draw the hook.
1232 if (isCaretRTL
!= mKeyboardRTL
)
1234 /* if the caret bidi level and the keyboard language direction are not in
1235 * synch, the keyboard language must have been changed by the
1236 * user, and if the caret is in a boundary condition (between left-to-right and
1237 * right-to-left characters) it may have to change position to
1238 * reflect the location in which the next character typed will
1239 * appear. We will call |SelectionLanguageChange| and exit
1240 * without drawing the caret in the old position.
1242 mKeyboardRTL
= isCaretRTL
;
1243 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
1246 if (NS_SUCCEEDED(domSelection
->SelectionLanguageChange(mKeyboardRTL
)))
1248 return NS_ERROR_FAILURE
;
1252 // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
1253 // The height of the hook rectangle is the same as the width of the caret
1255 nscoord bidiIndicatorSize
= aMetrics
.mBidiIndicatorSize
;
1256 mHookRect
.SetRect(mCaretRect
.x
+ ((isCaretRTL
) ?
1257 bidiIndicatorSize
* -1 :
1259 mCaretRect
.y
+ bidiIndicatorSize
,
1269 void nsCaret::InvalidateRects(const nsRect
&aRect
, const nsRect
&aHook
,
1272 NS_ASSERTION(aFrame
, "Must have a frame to invalidate");
1274 rect
.UnionRect(aRect
, aHook
);
1275 aFrame
->Invalidate(rect
);
1278 //-----------------------------------------------------------------------------
1280 void nsCaret::CaretBlinkCallback(nsITimer
*aTimer
, void *aClosure
)
1282 nsCaret
*theCaret
= reinterpret_cast<nsCaret
*>(aClosure
);
1283 if (!theCaret
) return;
1285 theCaret
->DrawCaret(PR_TRUE
);
1289 //-----------------------------------------------------------------------------
1290 already_AddRefed
<nsFrameSelection
>
1291 nsCaret::GetFrameSelection()
1293 nsCOMPtr
<nsISelectionPrivate
> privateSelection(do_QueryReferent(mDomSelectionWeak
));
1294 if (!privateSelection
)
1296 nsFrameSelection
* frameSelection
= nsnull
;
1297 privateSelection
->GetFrameSelection(&frameSelection
);
1298 return frameSelection
;
1302 nsCaret::SetIgnoreUserModify(PRBool aIgnoreUserModify
)
1304 if (!aIgnoreUserModify
&& mIgnoreUserModify
&& mDrawn
) {
1305 // We're turning off mIgnoreUserModify. If the caret's drawn
1306 // in a read-only node we must erase it, else the next call
1307 // to DrawCaret() won't erase the old caret, due to the new
1308 // mIgnoreUserModify value.
1309 nsIFrame
*frame
= GetCaretFrame();
1311 const nsStyleUserInterface
* userinterface
= frame
->GetStyleUserInterface();
1312 if (userinterface
->mUserModify
== NS_STYLE_USER_MODIFY_READ_ONLY
) {
1317 mIgnoreUserModify
= aIgnoreUserModify
;
1320 //-----------------------------------------------------------------------------
1321 nsresult
NS_NewCaret(nsCaret
** aInstancePtrResult
)
1323 NS_PRECONDITION(aInstancePtrResult
, "null ptr");
1325 nsCaret
* caret
= new nsCaret();
1326 if (nsnull
== caret
)
1327 return NS_ERROR_OUT_OF_MEMORY
;
1329 *aInstancePtrResult
= caret
;