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 <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 */
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"
62 #include "nsPresContext.h"
63 #include "nsILookAndFeel.h"
64 #include "nsBlockFrame.h"
65 #include "nsISelectionController.h"
66 #include "nsDisplayList.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;
80 #include "nsIBidiKeyboard.h"
81 #include "nsContentUtils.h"
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.
91 CheckForTrailingTextFrameRecursive(nsIFrame
* aFrame
, nsIFrame
* aStopAtFrame
)
93 if (aFrame
== aStopAtFrame
||
94 ((aFrame
->GetType() == nsGkAtoms::textFrame
&&
95 (static_cast<nsTextFrame
*>(aFrame
))->IsAtEndOfLine())))
97 if (!aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
))
100 for (nsIFrame
* f
= aFrame
->GetFirstChild(nsnull
); f
; f
= f
->GetNextSibling())
102 nsIFrame
* r
= CheckForTrailingTextFrameRecursive(f
, aStopAtFrame
);
110 FindContainingLine(nsIFrame
* aFrame
)
112 while (aFrame
&& aFrame
->IsFrameOfType(nsIFrame::eLineParticipant
))
114 nsIFrame
* parent
= aFrame
->GetParent();
115 nsBlockFrame
* blockParent
= nsLayoutUtils::GetAsBlock(parent
);
119 nsBlockInFlowLineIterator
iter(blockParent
, aFrame
, &isValid
);
120 return isValid
? iter
.GetLine().get() : nsnull
;
128 AdjustCaretFrameForLineEnd(nsIFrame
** aFrame
, PRInt32
* aOffset
)
130 nsLineBox
* line
= FindContainingLine(*aFrame
);
133 PRInt32 count
= line
->GetChildCount();
134 for (nsIFrame
* f
= line
->mFirstChild
; count
> 0; --count
, f
= f
->GetNextSibling())
136 nsIFrame
* r
= CheckForTrailingTextFrameRecursive(f
, *aFrame
);
142 NS_ASSERTION(r
->GetType() == nsGkAtoms::textFrame
, "Expected text frame");
143 *aOffset
= (static_cast<nsTextFrame
*>(r
))->GetContentEnd();
150 FramesOnSameLineHaveZeroHeight(nsIFrame
* aFrame
)
152 nsLineBox
* line
= FindContainingLine(aFrame
);
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)
164 //-----------------------------------------------------------------------------
171 , mPendingDraw(PR_FALSE
)
172 , mReadOnly(PR_FALSE
)
173 , mShowDuringSelection(PR_FALSE
)
174 , mIgnoreUserModify(PR_TRUE
)
176 , mKeyboardRTL(PR_FALSE
)
179 , mLastContentOffset(0)
180 , mLastHint(nsFrameSelection::HINTLEFT
)
184 //-----------------------------------------------------------------------------
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())) {
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
222 nsCOMPtr
<nsISelectionController
> selCon
= do_QueryReferent(mPresShell
);
224 return NS_ERROR_FAILURE
;
226 nsCOMPtr
<nsISelection
> domSelection
;
227 nsresult rv
= selCon
->GetSelection(nsISelectionController::SELECTION_NORMAL
,
228 getter_AddRefs(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
245 mBidiUI
= nsContentUtils::GetBoolPref("bidi.browser.ui");
252 DrawCJKCaret(nsIFrame
* aFrame
, PRInt32 aOffset
)
254 nsIContent
* content
= aFrame
->GetContent();
255 const nsTextFragment
* frag
= content
->GetText();
258 if (aOffset
< 0 || PRUint32(aOffset
) >= frag
->GetLength())
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();
280 result
.mCaretWidth
= NS_ROUND_BORDER_TO_PIXELS(caretWidth
, tpp
);
281 result
.mBidiIndicatorSize
= NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize
, tpp
);
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.
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
;
302 mLastContent
= nsnull
;
305 //-----------------------------------------------------------------------------
306 NS_IMPL_ISUPPORTS1(nsCaret
, nsISelectionListener
)
308 //-----------------------------------------------------------------------------
309 nsISelection
* nsCaret::GetCaretDOMSelection()
311 nsCOMPtr
<nsISelection
> sel(do_QueryReferent(mDomSelectionWeak
));
315 //-----------------------------------------------------------------------------
316 nsresult
nsCaret::SetCaretDOMSelection(nsISelection
*aDOMSel
)
318 NS_ENSURE_ARG_POINTER(aDOMSel
);
319 mDomSelectionWeak
= do_GetWeakReference(aDOMSel
); // weak reference to pres shell
322 // Stop the caret from blinking in its previous location.
324 // Start the caret blinking in the new location.
331 //-----------------------------------------------------------------------------
332 void nsCaret::SetCaretVisible(PRBool inMakeVisible
)
334 mVisible
= inMakeVisible
;
337 SetIgnoreUserModify(PR_TRUE
);
340 SetIgnoreUserModify(PR_FALSE
);
345 //-----------------------------------------------------------------------------
346 nsresult
nsCaret::GetCaretVisible(PRBool
*outMakeVisible
)
348 NS_ENSURE_ARG_POINTER(outMakeVisible
);
349 *outMakeVisible
= (mVisible
&& MustDrawCaret(PR_TRUE
));
354 //-----------------------------------------------------------------------------
355 void nsCaret::SetCaretReadOnly(PRBool inMakeReadonly
)
357 mReadOnly
= inMakeReadonly
;
361 nsCaret::GetGeometryForFrame(nsIFrame
* aFrame
,
362 PRInt32 aFrameOffset
,
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");
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
);
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
;
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
)
413 rv
= aSelection
->GetFocusOffset(&focusOffset
);
417 nsCOMPtr
<nsIContent
> contentNode
= do_QueryInterface(focusNode
);
421 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
424 PRUint8 bidiLevel
= frameSelection
->GetCaretBidiLevel();
427 rv
= GetCaretFrameForNodeOffset(contentNode
, focusOffset
,
428 frameSelection
->GetHint(), bidiLevel
,
429 &frame
, &frameOffset
);
430 if (NS_FAILED(rv
) || !frame
)
433 GetGeometryForFrame(frame
, frameOffset
, aRect
, aBidiIndicatorSize
);
437 void nsCaret::DrawCaretAfterBriefDelay()
439 // Make sure readonly caret gets drawn again if it needs to be
442 mBlinkTimer
= do_CreateInstance("@mozilla.org/timer;1", &err
);
447 mBlinkTimer
->InitWithFuncCallback(CaretBlinkCallback
, this, 0,
448 nsITimer::TYPE_ONE_SHOT
);
451 void nsCaret::EraseCaret()
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
);
474 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
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.
484 // XXX we need to do more work here to get the correct hint.
485 nsresult rv
= DrawAtPositionWithHint(aNode
, aOffset
,
486 nsFrameSelection::HINTLEFT
,
488 ? NS_OK
: NS_ERROR_FAILURE
;
493 nsIFrame
* nsCaret::GetCaretFrame(PRInt32
*aOffset
)
495 // Return null if we're not drawn to prevent anybody from trying to draw us.
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.
502 nsIFrame
*frame
= nsnull
;
503 nsresult rv
= GetCaretFrameForNodeOffset(mLastContent
, mLastContentOffset
,
504 mLastHint
, mLastBidiLevel
, &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.
530 // A trick! Make the DrawCaret code recalculate the caret's current
536 void nsCaret::PaintCaret(nsDisplayListBuilder
*aBuilder
,
537 nsIRenderingContext
*aCtx
,
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();
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
);
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
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
592 if (domSel
!= aDomSel
)
597 // Stop the caret from blinking in its previous location.
600 // Start the caret blinking in the new location.
608 //-----------------------------------------------------------------------------
609 void nsCaret::KillTimer()
613 mBlinkTimer
->Cancel();
618 //-----------------------------------------------------------------------------
619 nsresult
nsCaret::PrimeTimer()
621 // set up the blink timer
622 if (!mReadOnly
&& mBlinkRate
> 0)
626 mBlinkTimer
= do_CreateInstance("@mozilla.org/timer;1", &err
);
631 mBlinkTimer
->InitWithFuncCallback(CaretBlinkCallback
, this, mBlinkRate
,
632 nsITimer::TYPE_REPEATING_SLACK
);
639 //-----------------------------------------------------------------------------
640 void nsCaret::StartBlinking()
643 // Make sure the one draw command we use for a readonly caret isn't
644 // done until the selection is set
645 DrawCaretAfterBriefDelay();
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.
658 DrawCaret(PR_TRUE
); // draw it right away
662 //-----------------------------------------------------------------------------
663 void nsCaret::StopBlinking()
665 if (mDrawn
) // erase the caret if necessary
668 NS_ASSERTION(!mDrawn
, "Caret still drawn after StopBlinking().");
673 nsCaret::DrawAtPositionWithHint(nsIDOMNode
* aNode
,
675 nsFrameSelection::HINT aFrameHint
,
679 nsCOMPtr
<nsIContent
> contentNode
= do_QueryInterface(aNode
);
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
)
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
))
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();
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
))
723 InvalidateRects(mCaretRect
, mHookRect
, theFrame
);
729 nsCaret::GetCaretFrameForNodeOffset(nsIContent
* aContentNode
,
731 nsFrameSelection::HINT aFrameHint
,
733 nsIFrame
** aReturnFrame
,
734 PRInt32
* aReturnOffset
)
737 //get frame selection and find out what frame to use...
738 nsCOMPtr
<nsIPresShell
> presShell
= do_QueryReferent(mPresShell
);
740 return NS_ERROR_FAILURE
;
742 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
744 return NS_ERROR_FAILURE
;
746 nsIFrame
* theFrame
= nsnull
;
747 PRInt32 theFrameOffset
= 0;
749 theFrame
= frameSelection
->GetFrameForNodeOffset(aContentNode
, aOffset
,
750 aFrameHint
, &theFrameOffset
);
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
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
);
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
;
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
)
838 // if there is a frameAfter, move into it
839 theFrame
= frameAfter
;
840 theFrame
->GetOffsets(start
, end
);
841 theFrameOffset
= start
;
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
;
899 nsresult
nsCaret::CheckCaretDrawingState()
902 // The caret is drawn; if it shouldn't be, erase it.
903 if (!mVisible
|| !MustDrawCaret(PR_TRUE
))
908 // The caret is not drawn; if it should be, draw it.
909 if (mPendingDraw
&& (mVisible
&& MustDrawCaret(PR_TRUE
)))
915 /*-----------------------------------------------------------------------------
919 Find out if we need to do any caret drawing. This returns true if
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
)
932 nsCOMPtr
<nsISelection
> domSelection
= do_QueryReferent(mDomSelectionWeak
);
937 if (NS_FAILED(domSelection
->GetIsCollapsed(&isCollapsed
)))
940 if (mShowDuringSelection
)
941 return PR_TRUE
; // show the caret even in selections
943 if (IsMenuPopupHidingCaret())
949 PRBool
nsCaret::IsMenuPopupHidingCaret()
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
);
964 return PR_TRUE
; // No selection/caret to draw.
965 domSelection
->GetFocusNode(getter_AddRefs(node
));
967 return PR_TRUE
; // No selection/caret to draw.
968 nsCOMPtr
<nsIContent
> caretContent
= do_QueryInterface(node
);
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.
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.
993 // There are no open menu popups, no need to hide the caret.
997 void nsCaret::DrawCaret(PRBool aInvalidate
)
999 // Do we need to draw the caret at all?
1000 if (!MustDrawCaret(PR_FALSE
))
1003 // Can we draw the caret now?
1004 nsCOMPtr
<nsIPresShell
> presShell
= do_QueryReferent(mPresShell
);
1005 NS_ENSURE_TRUE(presShell
, /**/);
1007 if (presShell
->IsPaintingSuppressed())
1010 mPendingDraw
= PR_TRUE
;
1012 // PresShell::UnsuppressAndInvalidate() will call CheckCaretDrawingState()
1018 nsCOMPtr
<nsIDOMNode
> node
;
1020 nsFrameSelection::HINT hint
;
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
)
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
));
1043 if (NS_FAILED(domSelection
->GetFocusOffset(&offset
)))
1046 nsCOMPtr
<nsFrameSelection
> frameSelection
= GetFrameSelection();
1047 if (!frameSelection
)
1050 bidiLevel
= frameSelection
->GetCaretBidiLevel();
1051 mPendingDraw
= PR_FALSE
;
1060 if (!mLastContent
->IsInDoc())
1062 mLastContent
= nsnull
;
1066 node
= do_QueryInterface(mLastContent
);
1067 offset
= mLastContentOffset
;
1069 bidiLevel
= mLastBidiLevel
;
1072 DrawAtPositionWithHint(node
, offset
, hint
, bidiLevel
, aInvalidate
);
1073 ToggleDrawnStatus();
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
;
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
)) &&
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
)))
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
1118 mHookRect
.SetRect(mCaretRect
.x
+ ((isCaretRTL
) ?
1119 bidiIndicatorSize
* -1 :
1121 mCaretRect
.y
+ bidiIndicatorSize
,
1130 void nsCaret::InvalidateRects(const nsRect
&aRect
, const nsRect
&aHook
,
1133 NS_ASSERTION(aFrame
, "Must have a frame to invalidate");
1135 rect
.UnionRect(aRect
, aHook
);
1136 aFrame
->Invalidate(rect
);
1139 //-----------------------------------------------------------------------------
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
)
1157 nsFrameSelection
* frameSelection
= nsnull
;
1158 privateSelection
->GetFrameSelection(&frameSelection
);
1159 return frameSelection
;
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();
1172 const nsStyleUserInterface
* userinterface
= frame
->GetStyleUserInterface();
1173 if (userinterface
->mUserModify
== NS_STYLE_USER_MODIFY_READ_ONLY
) {
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
;
1190 *aInstancePtrResult
= caret
;