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
5 : *
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/
10 : *
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.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
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.
22 : *
23 : * Contributor(s):
24 : * Pierre Phaneuf <pp@ludusdesign.com>
25 : * Mats Palmgren <matspal@gmail.com>
26 : *
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.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : /* the caret is the text cursor used, e.g., when editing */
42 :
43 : #include "nsCOMPtr.h"
44 :
45 : #include "nsITimer.h"
46 :
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 "nsISelection.h"
55 : #include "nsISelectionPrivate.h"
56 : #include "nsIDOMCharacterData.h"
57 : #include "nsIContent.h"
58 : #include "nsIPresShell.h"
59 : #include "nsRenderingContext.h"
60 : #include "nsPresContext.h"
61 : #include "nsBlockFrame.h"
62 : #include "nsISelectionController.h"
63 : #include "nsDisplayList.h"
64 : #include "nsCaret.h"
65 : #include "nsTextFrame.h"
66 : #include "nsXULPopupManager.h"
67 : #include "nsMenuPopupFrame.h"
68 : #include "nsTextFragment.h"
69 : #include "nsThemeConstants.h"
70 : #include "mozilla/Preferences.h"
71 : #include "mozilla/LookAndFeel.h"
72 :
73 : // The bidi indicator hangs off the caret to one side, to show which
74 : // direction the typing is in. It needs to be at least 2x2 to avoid looking like
75 : // an insignificant dot
76 : static const PRInt32 kMinBidiIndicatorPixels = 2;
77 :
78 : #ifdef IBMBIDI
79 : #include "nsIBidiKeyboard.h"
80 : #include "nsContentUtils.h"
81 : #endif //IBMBIDI
82 :
83 : using namespace mozilla;
84 :
85 : /**
86 : * Find the first frame in an in-order traversal of the frame subtree rooted
87 : * at aFrame which is either a text frame logically at the end of a line,
88 : * or which is aStopAtFrame. Return null if no such frame is found. We don't
89 : * descend into the children of non-eLineParticipant frames.
90 : */
91 : static nsIFrame*
92 0 : CheckForTrailingTextFrameRecursive(nsIFrame* aFrame, nsIFrame* aStopAtFrame)
93 : {
94 0 : if (aFrame == aStopAtFrame ||
95 0 : ((aFrame->GetType() == nsGkAtoms::textFrame &&
96 0 : (static_cast<nsTextFrame*>(aFrame))->IsAtEndOfLine())))
97 0 : return aFrame;
98 0 : if (!aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
99 0 : return nsnull;
100 :
101 0 : for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); f; f = f->GetNextSibling())
102 : {
103 0 : nsIFrame* r = CheckForTrailingTextFrameRecursive(f, aStopAtFrame);
104 0 : if (r)
105 0 : return r;
106 : }
107 0 : return nsnull;
108 : }
109 :
110 : static nsLineBox*
111 0 : FindContainingLine(nsIFrame* aFrame)
112 : {
113 0 : while (aFrame && aFrame->IsFrameOfType(nsIFrame::eLineParticipant))
114 : {
115 0 : nsIFrame* parent = aFrame->GetParent();
116 0 : nsBlockFrame* blockParent = nsLayoutUtils::GetAsBlock(parent);
117 0 : if (blockParent)
118 : {
119 : bool isValid;
120 0 : nsBlockInFlowLineIterator iter(blockParent, aFrame, &isValid);
121 0 : return isValid ? iter.GetLine().get() : nsnull;
122 : }
123 0 : aFrame = parent;
124 : }
125 0 : return nsnull;
126 : }
127 :
128 : static void
129 0 : AdjustCaretFrameForLineEnd(nsIFrame** aFrame, PRInt32* aOffset)
130 : {
131 0 : nsLineBox* line = FindContainingLine(*aFrame);
132 0 : if (!line)
133 0 : return;
134 0 : PRInt32 count = line->GetChildCount();
135 0 : for (nsIFrame* f = line->mFirstChild; count > 0; --count, f = f->GetNextSibling())
136 : {
137 0 : nsIFrame* r = CheckForTrailingTextFrameRecursive(f, *aFrame);
138 0 : if (r == *aFrame)
139 0 : return;
140 0 : if (r)
141 : {
142 0 : *aFrame = r;
143 0 : NS_ASSERTION(r->GetType() == nsGkAtoms::textFrame, "Expected text frame");
144 0 : *aOffset = (static_cast<nsTextFrame*>(r))->GetContentEnd();
145 0 : return;
146 : }
147 : }
148 : }
149 :
150 : //-----------------------------------------------------------------------------
151 :
152 0 : nsCaret::nsCaret()
153 : : mPresShell(nsnull)
154 : , mBlinkRate(500)
155 : , mVisible(false)
156 : , mDrawn(false)
157 : , mPendingDraw(false)
158 : , mReadOnly(false)
159 : , mShowDuringSelection(false)
160 : , mIgnoreUserModify(true)
161 : #ifdef IBMBIDI
162 : , mKeyboardRTL(false)
163 : , mLastBidiLevel(0)
164 : #endif
165 : , mLastContentOffset(0)
166 0 : , mLastHint(nsFrameSelection::HINTLEFT)
167 : {
168 0 : }
169 :
170 : //-----------------------------------------------------------------------------
171 0 : nsCaret::~nsCaret()
172 : {
173 0 : KillTimer();
174 0 : }
175 :
176 : //-----------------------------------------------------------------------------
177 0 : nsresult nsCaret::Init(nsIPresShell *inPresShell)
178 : {
179 0 : NS_ENSURE_ARG(inPresShell);
180 :
181 0 : mPresShell = do_GetWeakReference(inPresShell); // the presshell owns us, so no addref
182 0 : NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
183 :
184 : // XXX we should just do this LookAndFeel consultation every time
185 : // we need these values.
186 0 : mCaretWidthCSSPx = LookAndFeel::GetInt(LookAndFeel::eIntID_CaretWidth, 1);
187 : mCaretAspectRatio =
188 0 : LookAndFeel::GetFloat(LookAndFeel::eFloatID_CaretAspectRatio, 0.0f);
189 :
190 : mBlinkRate = static_cast<PRUint32>(
191 0 : LookAndFeel::GetInt(LookAndFeel::eIntID_CaretBlinkTime, mBlinkRate));
192 : mShowDuringSelection =
193 : LookAndFeel::GetInt(LookAndFeel::eIntID_ShowCaretDuringSelection,
194 0 : mShowDuringSelection ? 1 : 0) != 0;
195 :
196 : // get the selection from the pres shell, and set ourselves up as a selection
197 : // listener
198 :
199 0 : nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mPresShell);
200 0 : if (!selCon)
201 0 : return NS_ERROR_FAILURE;
202 :
203 0 : nsCOMPtr<nsISelection> domSelection;
204 0 : nsresult rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
205 0 : getter_AddRefs(domSelection));
206 0 : if (NS_FAILED(rv))
207 0 : return rv;
208 0 : if (!domSelection)
209 0 : return NS_ERROR_FAILURE;
210 :
211 0 : nsCOMPtr<nsISelectionPrivate> privateSelection = do_QueryInterface(domSelection);
212 0 : if (privateSelection)
213 0 : privateSelection->AddSelectionListener(this);
214 0 : mDomSelectionWeak = do_GetWeakReference(domSelection);
215 :
216 : // set up the blink timer
217 0 : if (mVisible)
218 : {
219 0 : StartBlinking();
220 : }
221 : #ifdef IBMBIDI
222 0 : mBidiUI = Preferences::GetBool("bidi.browser.ui");
223 : #endif
224 :
225 0 : return NS_OK;
226 : }
227 :
228 : static bool
229 0 : DrawCJKCaret(nsIFrame* aFrame, PRInt32 aOffset)
230 : {
231 0 : nsIContent* content = aFrame->GetContent();
232 0 : const nsTextFragment* frag = content->GetText();
233 0 : if (!frag)
234 0 : return false;
235 0 : if (aOffset < 0 || PRUint32(aOffset) >= frag->GetLength())
236 0 : return false;
237 0 : PRUnichar ch = frag->CharAt(aOffset);
238 0 : return 0x2e80 <= ch && ch <= 0xd7ff;
239 : }
240 :
241 0 : nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, PRInt32 aOffset, nscoord aCaretHeight)
242 : {
243 : // Compute nominal sizes in appunits
244 : nscoord caretWidth = (aCaretHeight * mCaretAspectRatio) +
245 0 : nsPresContext::CSSPixelsToAppUnits(mCaretWidthCSSPx);
246 :
247 0 : if (DrawCJKCaret(aFrame, aOffset)) {
248 0 : caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
249 : }
250 0 : nscoord bidiIndicatorSize = nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
251 0 : bidiIndicatorSize = NS_MAX(caretWidth, bidiIndicatorSize);
252 :
253 : // Round them to device pixels. Always round down, except that anything
254 : // between 0 and 1 goes up to 1 so we don't let the caret disappear.
255 0 : PRUint32 tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
256 : Metrics result;
257 0 : result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
258 0 : result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
259 : return result;
260 : }
261 :
262 : //-----------------------------------------------------------------------------
263 0 : void nsCaret::Terminate()
264 : {
265 : // this doesn't erase the caret if it's drawn. Should it? We might not have
266 : // a good drawing environment during teardown.
267 :
268 0 : KillTimer();
269 0 : mBlinkTimer = nsnull;
270 :
271 : // unregiser ourselves as a selection listener
272 0 : nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
273 0 : nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
274 0 : if (privateSelection)
275 0 : privateSelection->RemoveSelectionListener(this);
276 0 : mDomSelectionWeak = nsnull;
277 0 : mPresShell = nsnull;
278 :
279 0 : mLastContent = nsnull;
280 0 : }
281 :
282 : //-----------------------------------------------------------------------------
283 0 : NS_IMPL_ISUPPORTS1(nsCaret, nsISelectionListener)
284 :
285 : //-----------------------------------------------------------------------------
286 0 : nsISelection* nsCaret::GetCaretDOMSelection()
287 : {
288 0 : nsCOMPtr<nsISelection> sel(do_QueryReferent(mDomSelectionWeak));
289 0 : return sel;
290 : }
291 :
292 : //-----------------------------------------------------------------------------
293 0 : nsresult nsCaret::SetCaretDOMSelection(nsISelection *aDOMSel)
294 : {
295 0 : NS_ENSURE_ARG_POINTER(aDOMSel);
296 0 : mDomSelectionWeak = do_GetWeakReference(aDOMSel); // weak reference to pres shell
297 0 : if (mVisible)
298 : {
299 : // Stop the caret from blinking in its previous location.
300 0 : StopBlinking();
301 : // Start the caret blinking in the new location.
302 0 : StartBlinking();
303 : }
304 0 : return NS_OK;
305 : }
306 :
307 :
308 : //-----------------------------------------------------------------------------
309 0 : void nsCaret::SetCaretVisible(bool inMakeVisible)
310 : {
311 0 : mVisible = inMakeVisible;
312 0 : if (mVisible) {
313 0 : SetIgnoreUserModify(true);
314 0 : StartBlinking();
315 : } else {
316 0 : StopBlinking();
317 0 : SetIgnoreUserModify(false);
318 : }
319 0 : }
320 :
321 :
322 : //-----------------------------------------------------------------------------
323 0 : nsresult nsCaret::GetCaretVisible(bool *outMakeVisible)
324 : {
325 0 : NS_ENSURE_ARG_POINTER(outMakeVisible);
326 0 : *outMakeVisible = (mVisible && MustDrawCaret(true));
327 0 : return NS_OK;
328 : }
329 :
330 :
331 : //-----------------------------------------------------------------------------
332 0 : void nsCaret::SetCaretReadOnly(bool inMakeReadonly)
333 : {
334 0 : mReadOnly = inMakeReadonly;
335 0 : }
336 :
337 : nsresult
338 0 : nsCaret::GetGeometryForFrame(nsIFrame* aFrame,
339 : PRInt32 aFrameOffset,
340 : nsRect* aRect,
341 : nscoord* aBidiIndicatorSize)
342 : {
343 0 : nsPoint framePos(0, 0);
344 0 : nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
345 0 : if (NS_FAILED(rv))
346 0 : return rv;
347 :
348 0 : nsIFrame *frame = aFrame->GetContentInsertionFrame();
349 0 : NS_ASSERTION(frame, "We should not be in the middle of reflow");
350 0 : nscoord baseline = frame->GetCaretBaseline();
351 0 : nscoord ascent = 0, descent = 0;
352 0 : nsRefPtr<nsFontMetrics> fm;
353 : nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
354 0 : nsLayoutUtils::FontSizeInflationFor(aFrame, nsLayoutUtils::eNotInReflow));
355 0 : NS_ASSERTION(fm, "We should be able to get the font metrics");
356 0 : if (fm) {
357 0 : ascent = fm->MaxAscent();
358 0 : descent = fm->MaxDescent();
359 : }
360 0 : nscoord height = ascent + descent;
361 0 : framePos.y = baseline - ascent;
362 0 : Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
363 0 : *aRect = nsRect(framePos, nsSize(caretMetrics.mCaretWidth, height));
364 :
365 : // Clamp the x-position to be within our scroll frame. If we don't, then it
366 : // clips us, and we don't appear at all. See bug 335560.
367 : nsIFrame *scrollFrame =
368 0 : nsLayoutUtils::GetClosestFrameOfType(aFrame, nsGkAtoms::scrollFrame);
369 0 : if (scrollFrame) {
370 : // First, use the scrollFrame to get at the scrollable view that we're in.
371 0 : nsIScrollableFrame *sf = do_QueryFrame(scrollFrame);
372 0 : nsIFrame *scrolled = sf->GetScrolledFrame();
373 0 : nsRect caretInScroll = *aRect + aFrame->GetOffsetTo(scrolled);
374 :
375 : // Now see if thet caret extends beyond the view's bounds. If it does,
376 : // then snap it back, put it as close to the edge as it can.
377 0 : nscoord overflow = caretInScroll.XMost() -
378 0 : scrolled->GetVisualOverflowRectRelativeToSelf().width;
379 0 : if (overflow > 0)
380 0 : aRect->x -= overflow;
381 : }
382 :
383 0 : if (aBidiIndicatorSize)
384 0 : *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
385 :
386 0 : return NS_OK;
387 : }
388 :
389 0 : nsIFrame* nsCaret::GetGeometry(nsISelection* aSelection, nsRect* aRect,
390 : nscoord* aBidiIndicatorSize)
391 : {
392 0 : nsCOMPtr<nsIDOMNode> focusNode;
393 0 : nsresult rv = aSelection->GetFocusNode(getter_AddRefs(focusNode));
394 0 : if (NS_FAILED(rv) || !focusNode)
395 0 : return nsnull;
396 :
397 : PRInt32 focusOffset;
398 0 : rv = aSelection->GetFocusOffset(&focusOffset);
399 0 : if (NS_FAILED(rv))
400 0 : return nsnull;
401 :
402 0 : nsCOMPtr<nsIContent> contentNode = do_QueryInterface(focusNode);
403 0 : if (!contentNode)
404 0 : return nsnull;
405 :
406 0 : nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
407 0 : if (!frameSelection)
408 0 : return nsnull;
409 0 : PRUint8 bidiLevel = frameSelection->GetCaretBidiLevel();
410 : nsIFrame* frame;
411 : PRInt32 frameOffset;
412 : rv = GetCaretFrameForNodeOffset(contentNode, focusOffset,
413 : frameSelection->GetHint(), bidiLevel,
414 0 : &frame, &frameOffset);
415 0 : if (NS_FAILED(rv) || !frame)
416 0 : return nsnull;
417 :
418 0 : GetGeometryForFrame(frame, frameOffset, aRect, aBidiIndicatorSize);
419 0 : return frame;
420 : }
421 :
422 0 : void nsCaret::DrawCaretAfterBriefDelay()
423 : {
424 : // Make sure readonly caret gets drawn again if it needs to be
425 0 : if (!mBlinkTimer) {
426 : nsresult err;
427 0 : mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
428 0 : if (NS_FAILED(err))
429 0 : return;
430 : }
431 :
432 0 : mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, 0,
433 0 : nsITimer::TYPE_ONE_SHOT);
434 : }
435 :
436 0 : void nsCaret::EraseCaret()
437 : {
438 0 : if (mDrawn) {
439 0 : DrawCaret(true);
440 0 : if (mReadOnly && mBlinkRate) {
441 : // If readonly we don't have a blink timer set, so caret won't
442 : // be redrawn automatically. We need to force the caret to get
443 : // redrawn right after the paint
444 0 : DrawCaretAfterBriefDelay();
445 : }
446 : }
447 0 : }
448 :
449 0 : void nsCaret::SetVisibilityDuringSelection(bool aVisibility)
450 : {
451 0 : mShowDuringSelection = aVisibility;
452 0 : }
453 :
454 0 : nsresult nsCaret::DrawAtPosition(nsIDOMNode* aNode, PRInt32 aOffset)
455 : {
456 0 : NS_ENSURE_ARG(aNode);
457 :
458 : PRUint8 bidiLevel;
459 0 : nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
460 0 : if (!frameSelection)
461 0 : return NS_ERROR_FAILURE;
462 0 : bidiLevel = frameSelection->GetCaretBidiLevel();
463 :
464 : // DrawAtPosition is used by consumers who want us to stay drawn where they
465 : // tell us. Setting mBlinkRate to 0 tells us to not set a timer to erase
466 : // ourselves, our consumer will take care of that.
467 0 : mBlinkRate = 0;
468 :
469 : // XXX we need to do more work here to get the correct hint.
470 : nsresult rv = DrawAtPositionWithHint(aNode, aOffset,
471 : nsFrameSelection::HINTLEFT,
472 0 : bidiLevel, true)
473 0 : ? NS_OK : NS_ERROR_FAILURE;
474 0 : ToggleDrawnStatus();
475 0 : return rv;
476 : }
477 :
478 0 : nsIFrame * nsCaret::GetCaretFrame(PRInt32 *aOffset)
479 : {
480 : // Return null if we're not drawn to prevent anybody from trying to draw us.
481 0 : if (!mDrawn)
482 0 : return nsnull;
483 :
484 : // Recompute the frame that we're supposed to draw in to guarantee that
485 : // we're not going to try to draw into a stale (dead) frame.
486 : PRInt32 offset;
487 0 : nsIFrame *frame = nsnull;
488 : nsresult rv = GetCaretFrameForNodeOffset(mLastContent, mLastContentOffset,
489 : mLastHint, mLastBidiLevel, &frame,
490 0 : &offset);
491 0 : if (NS_FAILED(rv))
492 0 : return nsnull;
493 :
494 0 : if (aOffset) {
495 0 : *aOffset = offset;
496 : }
497 0 : return frame;
498 : }
499 :
500 0 : void nsCaret::InvalidateOutsideCaret()
501 : {
502 0 : nsIFrame *frame = GetCaretFrame();
503 :
504 : // Only invalidate if we are not fully contained by our frame's rect.
505 0 : if (frame && !frame->GetVisualOverflowRect().Contains(GetCaretRect()))
506 0 : InvalidateRects(mCaretRect, GetHookRect(), frame);
507 0 : }
508 :
509 0 : void nsCaret::UpdateCaretPosition()
510 : {
511 : // We'll recalculate anyway if we're not drawn right now.
512 0 : if (!mDrawn)
513 0 : return;
514 :
515 : // A trick! Make the DrawCaret code recalculate the caret's current
516 : // position.
517 0 : mDrawn = false;
518 0 : DrawCaret(false);
519 : }
520 :
521 0 : void nsCaret::PaintCaret(nsDisplayListBuilder *aBuilder,
522 : nsRenderingContext *aCtx,
523 : nsIFrame* aForFrame,
524 : const nsPoint &aOffset)
525 : {
526 0 : NS_ASSERTION(mDrawn, "The caret shouldn't be drawing");
527 :
528 0 : const nsRect drawCaretRect = mCaretRect + aOffset;
529 : PRInt32 contentOffset;
530 :
531 : #ifdef DEBUG
532 : nsIFrame* frame =
533 : #endif
534 0 : GetCaretFrame(&contentOffset);
535 0 : NS_ASSERTION(frame == aForFrame, "We're referring different frame");
536 : // If the offset falls outside of the frame, then don't paint the caret.
537 : PRInt32 startOffset, endOffset;
538 0 : if (aForFrame->GetType() == nsGkAtoms::textFrame &&
539 0 : (NS_FAILED(aForFrame->GetOffsets(startOffset, endOffset)) ||
540 : startOffset > contentOffset ||
541 : endOffset < contentOffset)) {
542 : return;
543 : }
544 0 : nscolor foregroundColor = aForFrame->GetCaretColorAt(contentOffset);
545 :
546 : // Only draw the native caret if the foreground color matches that of
547 : // -moz-fieldtext (the color of the text in a textbox). If it doesn't match
548 : // we are likely in contenteditable or a custom widget and we risk being hard to see
549 : // against the background. In that case, fall back to the CSS color.
550 0 : nsPresContext* presContext = aForFrame->PresContext();
551 :
552 0 : if (GetHookRect().IsEmpty() && presContext) {
553 0 : nsITheme *theme = presContext->GetTheme();
554 0 : if (theme && theme->ThemeSupportsWidget(presContext, aForFrame, NS_THEME_TEXTFIELD_CARET)) {
555 : nscolor fieldText;
556 : nsresult rv = LookAndFeel::GetColor(LookAndFeel::eColorID__moz_fieldtext,
557 0 : &fieldText);
558 0 : if (NS_SUCCEEDED(rv) && fieldText == foregroundColor) {
559 : theme->DrawWidgetBackground(aCtx, aForFrame, NS_THEME_TEXTFIELD_CARET,
560 0 : drawCaretRect, drawCaretRect);
561 : return;
562 : }
563 : }
564 : }
565 :
566 0 : aCtx->SetColor(foregroundColor);
567 0 : aCtx->FillRect(drawCaretRect);
568 0 : if (!GetHookRect().IsEmpty())
569 0 : aCtx->FillRect(GetHookRect() + aOffset);
570 : }
571 :
572 :
573 : //-----------------------------------------------------------------------------
574 0 : NS_IMETHODIMP nsCaret::NotifySelectionChanged(nsIDOMDocument *, nsISelection *aDomSel, PRInt16 aReason)
575 : {
576 0 : if (aReason & nsISelectionListener::MOUSEUP_REASON)//this wont do
577 0 : return NS_OK;
578 :
579 0 : nsCOMPtr<nsISelection> domSel(do_QueryReferent(mDomSelectionWeak));
580 :
581 : // The same caret is shared amongst the document and any text widgets it
582 : // may contain. This means that the caret could get notifications from
583 : // multiple selections.
584 : //
585 : // If this notification is for a selection that is not the one the
586 : // the caret is currently interested in (mDomSelectionWeak), then there
587 : // is nothing to do!
588 :
589 0 : if (domSel != aDomSel)
590 0 : return NS_OK;
591 :
592 0 : if (mVisible)
593 : {
594 : // Stop the caret from blinking in its previous location.
595 0 : StopBlinking();
596 :
597 : // Start the caret blinking in the new location.
598 0 : StartBlinking();
599 : }
600 :
601 0 : return NS_OK;
602 : }
603 :
604 :
605 : //-----------------------------------------------------------------------------
606 0 : void nsCaret::KillTimer()
607 : {
608 0 : if (mBlinkTimer)
609 : {
610 0 : mBlinkTimer->Cancel();
611 : }
612 0 : }
613 :
614 :
615 : //-----------------------------------------------------------------------------
616 0 : nsresult nsCaret::PrimeTimer()
617 : {
618 : // set up the blink timer
619 0 : if (!mReadOnly && mBlinkRate > 0)
620 : {
621 0 : if (!mBlinkTimer) {
622 : nsresult err;
623 0 : mBlinkTimer = do_CreateInstance("@mozilla.org/timer;1", &err);
624 0 : if (NS_FAILED(err))
625 0 : return err;
626 : }
627 :
628 0 : mBlinkTimer->InitWithFuncCallback(CaretBlinkCallback, this, mBlinkRate,
629 0 : nsITimer::TYPE_REPEATING_SLACK);
630 : }
631 :
632 0 : return NS_OK;
633 : }
634 :
635 0 : void nsCaret::InvalidateTextOverflowBlock()
636 : {
637 : // If the nearest block has a potential 'text-overflow' marker then
638 : // invalidate it.
639 0 : if (mLastContent) {
640 0 : nsIFrame* caretFrame = mLastContent->GetPrimaryFrame();
641 0 : if (caretFrame) {
642 0 : nsIFrame* block = nsLayoutUtils::GetAsBlock(caretFrame) ? caretFrame :
643 0 : nsLayoutUtils::FindNearestBlockAncestor(caretFrame);
644 0 : if (block) {
645 0 : const nsStyleTextReset* style = block->GetStyleTextReset();
646 0 : if (style->mTextOverflow.mLeft.mType != NS_STYLE_TEXT_OVERFLOW_CLIP ||
647 : style->mTextOverflow.mRight.mType != NS_STYLE_TEXT_OVERFLOW_CLIP) {
648 0 : block->InvalidateOverflowRect();
649 : }
650 : }
651 : }
652 : }
653 0 : }
654 :
655 : //-----------------------------------------------------------------------------
656 0 : void nsCaret::StartBlinking()
657 : {
658 0 : InvalidateTextOverflowBlock();
659 :
660 0 : if (mReadOnly) {
661 : // Make sure the one draw command we use for a readonly caret isn't
662 : // done until the selection is set
663 0 : DrawCaretAfterBriefDelay();
664 0 : return;
665 : }
666 0 : PrimeTimer();
667 :
668 : // If we are currently drawn, then the second call to DrawCaret below will
669 : // actually erase the caret. That would cause the caret to spend an "off"
670 : // cycle before it appears, which is not really what we want. This first
671 : // call to DrawCaret makes sure that the first cycle after a call to
672 : // StartBlinking is an "on" cycle.
673 0 : if (mDrawn)
674 0 : DrawCaret(true);
675 :
676 0 : DrawCaret(true); // draw it right away
677 : }
678 :
679 :
680 : //-----------------------------------------------------------------------------
681 0 : void nsCaret::StopBlinking()
682 : {
683 0 : InvalidateTextOverflowBlock();
684 :
685 0 : if (mDrawn) // erase the caret if necessary
686 0 : DrawCaret(true);
687 :
688 0 : NS_ASSERTION(!mDrawn, "Caret still drawn after StopBlinking().");
689 0 : KillTimer();
690 0 : }
691 :
692 : bool
693 0 : nsCaret::DrawAtPositionWithHint(nsIDOMNode* aNode,
694 : PRInt32 aOffset,
695 : nsFrameSelection::HINT aFrameHint,
696 : PRUint8 aBidiLevel,
697 : bool aInvalidate)
698 : {
699 0 : nsCOMPtr<nsIContent> contentNode = do_QueryInterface(aNode);
700 0 : if (!contentNode)
701 0 : return false;
702 :
703 0 : nsIFrame* theFrame = nsnull;
704 0 : PRInt32 theFrameOffset = 0;
705 :
706 : nsresult rv = GetCaretFrameForNodeOffset(contentNode, aOffset, aFrameHint, aBidiLevel,
707 0 : &theFrame, &theFrameOffset);
708 0 : if (NS_FAILED(rv) || !theFrame)
709 0 : return false;
710 :
711 : // now we have a frame, check whether it's appropriate to show the caret here
712 0 : const nsStyleUserInterface* userinterface = theFrame->GetStyleUserInterface();
713 0 : if ((!mIgnoreUserModify &&
714 : userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) ||
715 : (userinterface->mUserInput == NS_STYLE_USER_INPUT_NONE) ||
716 : (userinterface->mUserInput == NS_STYLE_USER_INPUT_DISABLED))
717 : {
718 0 : return false;
719 : }
720 :
721 0 : if (!mDrawn)
722 : {
723 : // save stuff so we can figure out what frame we're in later.
724 0 : mLastContent = contentNode;
725 0 : mLastContentOffset = aOffset;
726 0 : mLastHint = aFrameHint;
727 0 : mLastBidiLevel = aBidiLevel;
728 :
729 : // If there has been a reflow, set the caret Bidi level to the level of the current frame
730 0 : if (aBidiLevel & BIDI_LEVEL_UNDEFINED) {
731 0 : nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
732 0 : if (!frameSelection)
733 0 : return false;
734 0 : frameSelection->SetCaretBidiLevel(NS_GET_EMBEDDING_LEVEL(theFrame));
735 : }
736 :
737 : // Only update the caret's rect when we're not currently drawn.
738 0 : if (!UpdateCaretRects(theFrame, theFrameOffset))
739 0 : return false;
740 : }
741 :
742 0 : if (aInvalidate)
743 0 : InvalidateRects(mCaretRect, mHookRect, theFrame);
744 :
745 0 : return true;
746 : }
747 :
748 : nsresult
749 0 : nsCaret::GetCaretFrameForNodeOffset(nsIContent* aContentNode,
750 : PRInt32 aOffset,
751 : nsFrameSelection::HINT aFrameHint,
752 : PRUint8 aBidiLevel,
753 : nsIFrame** aReturnFrame,
754 : PRInt32* aReturnOffset)
755 : {
756 :
757 : //get frame selection and find out what frame to use...
758 0 : nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
759 0 : if (!presShell)
760 0 : return NS_ERROR_FAILURE;
761 :
762 0 : if (!aContentNode || !aContentNode->IsInDoc() ||
763 0 : presShell->GetDocument() != aContentNode->GetCurrentDoc())
764 0 : return NS_ERROR_FAILURE;
765 :
766 0 : nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
767 0 : if (!frameSelection)
768 0 : return NS_ERROR_FAILURE;
769 :
770 0 : nsIFrame* theFrame = nsnull;
771 0 : PRInt32 theFrameOffset = 0;
772 :
773 0 : theFrame = frameSelection->GetFrameForNodeOffset(aContentNode, aOffset,
774 0 : aFrameHint, &theFrameOffset);
775 0 : if (!theFrame)
776 0 : return NS_ERROR_FAILURE;
777 :
778 : // if theFrame is after a text frame that's logically at the end of the line
779 : // (e.g. if theFrame is a <br> frame), then put the caret at the end of
780 : // that text frame instead. This way, the caret will be positioned as if
781 : // trailing whitespace was not trimmed.
782 0 : AdjustCaretFrameForLineEnd(&theFrame, &theFrameOffset);
783 :
784 : // Mamdouh : modification of the caret to work at rtl and ltr with Bidi
785 : //
786 : // Direction Style from this->GetStyleData()
787 : // now in (visibility->mDirection)
788 : // ------------------
789 : // NS_STYLE_DIRECTION_LTR : LTR or Default
790 : // NS_STYLE_DIRECTION_RTL
791 : // NS_STYLE_DIRECTION_INHERIT
792 0 : if (mBidiUI)
793 : {
794 : // If there has been a reflow, take the caret Bidi level to be the level of the current frame
795 0 : if (aBidiLevel & BIDI_LEVEL_UNDEFINED)
796 0 : aBidiLevel = NS_GET_EMBEDDING_LEVEL(theFrame);
797 :
798 : PRInt32 start;
799 : PRInt32 end;
800 : nsIFrame* frameBefore;
801 : nsIFrame* frameAfter;
802 : PRUint8 levelBefore; // Bidi level of the character before the caret
803 : PRUint8 levelAfter; // Bidi level of the character after the caret
804 :
805 0 : theFrame->GetOffsets(start, end);
806 0 : if (start == 0 || end == 0 || start == theFrameOffset || end == theFrameOffset)
807 : {
808 0 : nsPrevNextBidiLevels levels = frameSelection->
809 0 : GetPrevNextBidiLevels(aContentNode, aOffset, false);
810 :
811 : /* Boundary condition, we need to know the Bidi levels of the characters before and after the caret */
812 0 : if (levels.mFrameBefore || levels.mFrameAfter)
813 : {
814 0 : frameBefore = levels.mFrameBefore;
815 0 : frameAfter = levels.mFrameAfter;
816 0 : levelBefore = levels.mLevelBefore;
817 0 : levelAfter = levels.mLevelAfter;
818 :
819 0 : if ((levelBefore != levelAfter) || (aBidiLevel != levelBefore))
820 : {
821 0 : aBidiLevel = NS_MAX(aBidiLevel, NS_MIN(levelBefore, levelAfter)); // rule c3
822 0 : aBidiLevel = NS_MIN(aBidiLevel, NS_MAX(levelBefore, levelAfter)); // rule c4
823 0 : if (aBidiLevel == levelBefore // rule c1
824 0 : || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelBefore) & 1)) // rule c5
825 0 : || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelBefore) & 1))) // rule c9
826 : {
827 0 : if (theFrame != frameBefore)
828 : {
829 0 : if (frameBefore) // if there is a frameBefore, move into it
830 : {
831 0 : theFrame = frameBefore;
832 0 : theFrame->GetOffsets(start, end);
833 0 : theFrameOffset = end;
834 : }
835 : else
836 : {
837 : // if there is no frameBefore, we must be at the beginning of the line
838 : // so we stay with the current frame.
839 : // Exception: when the first frame on the line has a different Bidi level from the paragraph level, there is no
840 : // real frame for the caret to be in. We have to find the visually first frame on the line.
841 0 : PRUint8 baseLevel = NS_GET_BASE_LEVEL(frameAfter);
842 0 : if (baseLevel != levelAfter)
843 : {
844 0 : nsPeekOffsetStruct pos;
845 0 : pos.SetData(eSelectBeginLine, eDirPrevious, 0, 0, false, true, false, true);
846 0 : if (NS_SUCCEEDED(frameAfter->PeekOffset(&pos))) {
847 0 : theFrame = pos.mResultFrame;
848 0 : theFrameOffset = pos.mContentOffset;
849 : }
850 : }
851 : }
852 0 : }
853 : }
854 0 : else if (aBidiLevel == levelAfter // rule c2
855 0 : || (aBidiLevel > levelBefore && aBidiLevel < levelAfter && !((aBidiLevel ^ levelAfter) & 1)) // rule c6
856 0 : || (aBidiLevel < levelBefore && aBidiLevel > levelAfter && !((aBidiLevel ^ levelAfter) & 1))) // rule c10
857 : {
858 0 : if (theFrame != frameAfter)
859 : {
860 0 : if (frameAfter)
861 : {
862 : // if there is a frameAfter, move into it
863 0 : theFrame = frameAfter;
864 0 : theFrame->GetOffsets(start, end);
865 0 : theFrameOffset = start;
866 : }
867 : else
868 : {
869 : // if there is no frameAfter, we must be at the end of the line
870 : // so we stay with the current frame.
871 : // Exception: when the last frame on the line has a different Bidi level from the paragraph level, there is no
872 : // real frame for the caret to be in. We have to find the visually last frame on the line.
873 0 : PRUint8 baseLevel = NS_GET_BASE_LEVEL(frameBefore);
874 0 : if (baseLevel != levelBefore)
875 : {
876 0 : nsPeekOffsetStruct pos;
877 0 : pos.SetData(eSelectEndLine, eDirNext, 0, 0, false, true, false, true);
878 0 : if (NS_SUCCEEDED(frameBefore->PeekOffset(&pos))) {
879 0 : theFrame = pos.mResultFrame;
880 0 : theFrameOffset = pos.mContentOffset;
881 : }
882 : }
883 : }
884 0 : }
885 : }
886 0 : else if (aBidiLevel > levelBefore && aBidiLevel < levelAfter // rule c7/8
887 0 : && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
888 : && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
889 : {
890 0 : if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameAfter, eDirNext, aBidiLevel, &theFrame)))
891 : {
892 0 : theFrame->GetOffsets(start, end);
893 0 : levelAfter = NS_GET_EMBEDDING_LEVEL(theFrame);
894 0 : if (aBidiLevel & 1) // c8: caret to the right of the rightmost character
895 0 : theFrameOffset = (levelAfter & 1) ? start : end;
896 : else // c7: caret to the left of the leftmost character
897 0 : theFrameOffset = (levelAfter & 1) ? end : start;
898 : }
899 : }
900 0 : else if (aBidiLevel < levelBefore && aBidiLevel > levelAfter // rule c11/12
901 0 : && !((levelBefore ^ levelAfter) & 1) // before and after have the same parity
902 : && ((aBidiLevel ^ levelAfter) & 1)) // caret has different parity
903 : {
904 0 : if (NS_SUCCEEDED(frameSelection->GetFrameFromLevel(frameBefore, eDirPrevious, aBidiLevel, &theFrame)))
905 : {
906 0 : theFrame->GetOffsets(start, end);
907 0 : levelBefore = NS_GET_EMBEDDING_LEVEL(theFrame);
908 0 : if (aBidiLevel & 1) // c12: caret to the left of the leftmost character
909 0 : theFrameOffset = (levelBefore & 1) ? end : start;
910 : else // c11: caret to the right of the rightmost character
911 0 : theFrameOffset = (levelBefore & 1) ? start : end;
912 : }
913 : }
914 : }
915 : }
916 : }
917 : }
918 :
919 0 : NS_ASSERTION(!theFrame || theFrame->PresContext()->PresShell() == presShell,
920 : "caret frame is in wrong document");
921 0 : *aReturnFrame = theFrame;
922 0 : *aReturnOffset = theFrameOffset;
923 0 : return NS_OK;
924 : }
925 :
926 0 : nsresult nsCaret::CheckCaretDrawingState()
927 : {
928 0 : if (mDrawn) {
929 : // The caret is drawn; if it shouldn't be, erase it.
930 0 : if (!mVisible || !MustDrawCaret(true))
931 0 : EraseCaret();
932 : }
933 : else
934 : {
935 : // The caret is not drawn; if it should be, draw it.
936 0 : if (mPendingDraw && (mVisible && MustDrawCaret(true)))
937 0 : DrawCaret(true);
938 : }
939 0 : return NS_OK;
940 : }
941 :
942 : /*-----------------------------------------------------------------------------
943 :
944 : MustDrawCaret
945 :
946 : Find out if we need to do any caret drawing. This returns true if
947 : either:
948 : a) The caret has been drawn, and we need to erase it.
949 : b) The caret is not drawn, and the selection is collapsed.
950 : c) The caret is not hidden due to open XUL popups
951 : (see IsMenuPopupHidingCaret()).
952 :
953 : ----------------------------------------------------------------------------- */
954 0 : bool nsCaret::MustDrawCaret(bool aIgnoreDrawnState)
955 : {
956 0 : if (!aIgnoreDrawnState && mDrawn)
957 0 : return true;
958 :
959 0 : nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
960 0 : if (!domSelection)
961 0 : return false;
962 :
963 : bool isCollapsed;
964 0 : if (NS_FAILED(domSelection->GetIsCollapsed(&isCollapsed)))
965 0 : return false;
966 :
967 0 : if (mShowDuringSelection)
968 0 : return true; // show the caret even in selections
969 :
970 0 : if (IsMenuPopupHidingCaret())
971 0 : return false;
972 :
973 0 : return isCollapsed;
974 : }
975 :
976 0 : bool nsCaret::IsMenuPopupHidingCaret()
977 : {
978 : #ifdef MOZ_XUL
979 : // Check if there are open popups.
980 0 : nsXULPopupManager *popMgr = nsXULPopupManager::GetInstance();
981 0 : nsTArray<nsIFrame*> popups = popMgr->GetVisiblePopups();
982 :
983 0 : if (popups.Length() == 0)
984 0 : return false; // No popups, so caret can't be hidden by them.
985 :
986 : // Get the selection focus content, that's where the caret would
987 : // go if it was drawn.
988 0 : nsCOMPtr<nsIDOMNode> node;
989 0 : nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
990 0 : if (!domSelection)
991 0 : return true; // No selection/caret to draw.
992 0 : domSelection->GetFocusNode(getter_AddRefs(node));
993 0 : if (!node)
994 0 : return true; // No selection/caret to draw.
995 0 : nsCOMPtr<nsIContent> caretContent = do_QueryInterface(node);
996 0 : if (!caretContent)
997 0 : return true; // No selection/caret to draw.
998 :
999 : // If there's a menu popup open before the popup with
1000 : // the caret, don't show the caret.
1001 0 : for (PRUint32 i=0; i<popups.Length(); i++) {
1002 0 : nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
1003 0 : nsIContent* popupContent = popupFrame->GetContent();
1004 :
1005 0 : if (nsContentUtils::ContentIsDescendantOf(caretContent, popupContent)) {
1006 : // The caret is in this popup. There were no menu popups before this
1007 : // popup, so don't hide the caret.
1008 0 : return false;
1009 : }
1010 :
1011 0 : if (popupFrame->PopupType() == ePopupTypeMenu && !popupFrame->IsContextMenu()) {
1012 : // This is an open menu popup. It does not contain the caret (else we'd
1013 : // have returned above). Even if the caret is in a subsequent popup,
1014 : // or another document/frame, it should be hidden.
1015 0 : return true;
1016 : }
1017 : }
1018 : #endif
1019 :
1020 : // There are no open menu popups, no need to hide the caret.
1021 0 : return false;
1022 : }
1023 :
1024 0 : void nsCaret::DrawCaret(bool aInvalidate)
1025 : {
1026 : // Do we need to draw the caret at all?
1027 0 : if (!MustDrawCaret(false))
1028 0 : return;
1029 :
1030 : // Can we draw the caret now?
1031 0 : nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
1032 0 : NS_ENSURE_TRUE(presShell, /**/);
1033 : {
1034 0 : if (presShell->IsPaintingSuppressed())
1035 : {
1036 0 : if (!mDrawn)
1037 0 : mPendingDraw = true;
1038 :
1039 : // PresShell::UnsuppressAndInvalidate() will call CheckCaretDrawingState()
1040 : // to get us drawn.
1041 : return;
1042 : }
1043 : }
1044 :
1045 0 : nsCOMPtr<nsIDOMNode> node;
1046 : PRInt32 offset;
1047 : nsFrameSelection::HINT hint;
1048 : PRUint8 bidiLevel;
1049 :
1050 0 : if (!mDrawn)
1051 : {
1052 0 : nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
1053 0 : nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSelection));
1054 0 : if (!privateSelection) return;
1055 :
1056 0 : bool isCollapsed = false;
1057 0 : domSelection->GetIsCollapsed(&isCollapsed);
1058 0 : if (!mShowDuringSelection && !isCollapsed)
1059 : return;
1060 :
1061 : bool hintRight;
1062 0 : privateSelection->GetInterlinePosition(&hintRight);//translate hint.
1063 0 : hint = hintRight ? nsFrameSelection::HINTRIGHT : nsFrameSelection::HINTLEFT;
1064 :
1065 : // get the node and offset, which is where we want the caret to draw
1066 0 : domSelection->GetFocusNode(getter_AddRefs(node));
1067 0 : if (!node)
1068 : return;
1069 :
1070 0 : if (NS_FAILED(domSelection->GetFocusOffset(&offset)))
1071 : return;
1072 :
1073 0 : nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
1074 0 : if (!frameSelection)
1075 : return;
1076 :
1077 0 : bidiLevel = frameSelection->GetCaretBidiLevel();
1078 0 : mPendingDraw = false;
1079 : }
1080 : else
1081 : {
1082 0 : if (!mLastContent)
1083 : {
1084 0 : mDrawn = false;
1085 : return;
1086 : }
1087 0 : if (!mLastContent->IsInDoc() ||
1088 0 : presShell->GetDocument() != mLastContent->GetCurrentDoc())
1089 : {
1090 0 : mLastContent = nsnull;
1091 0 : mDrawn = false;
1092 : return;
1093 : }
1094 0 : node = do_QueryInterface(mLastContent);
1095 0 : offset = mLastContentOffset;
1096 0 : hint = mLastHint;
1097 0 : bidiLevel = mLastBidiLevel;
1098 : }
1099 :
1100 0 : DrawAtPositionWithHint(node, offset, hint, bidiLevel, aInvalidate);
1101 0 : ToggleDrawnStatus();
1102 : }
1103 :
1104 : bool
1105 0 : nsCaret::UpdateCaretRects(nsIFrame* aFrame, PRInt32 aFrameOffset)
1106 : {
1107 0 : NS_ASSERTION(aFrame, "Should have a frame here");
1108 :
1109 : nscoord bidiIndicatorSize;
1110 : nsresult rv =
1111 0 : GetGeometryForFrame(aFrame, aFrameOffset, &mCaretRect, &bidiIndicatorSize);
1112 0 : if (NS_FAILED(rv)) {
1113 0 : return false;
1114 : }
1115 :
1116 : // on RTL frames the right edge of mCaretRect must be equal to framePos
1117 0 : const nsStyleVisibility* vis = aFrame->GetStyleVisibility();
1118 0 : if (NS_STYLE_DIRECTION_RTL == vis->mDirection)
1119 0 : mCaretRect.x -= mCaretRect.width;
1120 :
1121 : #ifdef IBMBIDI
1122 0 : mHookRect.SetEmpty();
1123 :
1124 : // Simon -- make a hook to draw to the left or right of the caret to show keyboard language direction
1125 0 : bool isCaretRTL = false;
1126 0 : nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
1127 : // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
1128 : // keyboard direction, or the user has no right-to-left keyboard
1129 : // installed, so we never draw the hook.
1130 0 : if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL)) &&
1131 : mBidiUI) {
1132 0 : if (isCaretRTL != mKeyboardRTL) {
1133 : /* if the caret bidi level and the keyboard language direction are not in
1134 : * synch, the keyboard language must have been changed by the
1135 : * user, and if the caret is in a boundary condition (between left-to-right and
1136 : * right-to-left characters) it may have to change position to
1137 : * reflect the location in which the next character typed will
1138 : * appear. We will call |SelectionLanguageChange| and exit
1139 : * without drawing the caret in the old position.
1140 : */
1141 0 : mKeyboardRTL = isCaretRTL;
1142 0 : nsCOMPtr<nsISelection> domSelection = do_QueryReferent(mDomSelectionWeak);
1143 0 : if (!domSelection ||
1144 0 : NS_SUCCEEDED(domSelection->SelectionLanguageChange(mKeyboardRTL)))
1145 0 : return false;
1146 : }
1147 : // If keyboard language is RTL, draw the hook on the left; if LTR, to the right
1148 : // The height of the hook rectangle is the same as the width of the caret
1149 : // rectangle.
1150 : mHookRect.SetRect(mCaretRect.x + ((isCaretRTL) ?
1151 : bidiIndicatorSize * -1 :
1152 : mCaretRect.width),
1153 : mCaretRect.y + bidiIndicatorSize,
1154 : bidiIndicatorSize,
1155 0 : mCaretRect.width);
1156 : }
1157 : #endif //IBMBIDI
1158 0 : return true;
1159 : }
1160 :
1161 : // static
1162 0 : void nsCaret::InvalidateRects(const nsRect &aRect, const nsRect &aHook,
1163 : nsIFrame *aFrame)
1164 : {
1165 0 : NS_ASSERTION(aFrame, "Must have a frame to invalidate");
1166 0 : nsRect rect;
1167 0 : rect.UnionRect(aRect, aHook);
1168 0 : aFrame->Invalidate(rect);
1169 0 : }
1170 :
1171 : //-----------------------------------------------------------------------------
1172 : /* static */
1173 0 : void nsCaret::CaretBlinkCallback(nsITimer *aTimer, void *aClosure)
1174 : {
1175 0 : nsCaret *theCaret = reinterpret_cast<nsCaret*>(aClosure);
1176 0 : if (!theCaret) return;
1177 :
1178 0 : theCaret->DrawCaret(true);
1179 : }
1180 :
1181 :
1182 : //-----------------------------------------------------------------------------
1183 : already_AddRefed<nsFrameSelection>
1184 0 : nsCaret::GetFrameSelection()
1185 : {
1186 0 : nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryReferent(mDomSelectionWeak));
1187 0 : if (!privateSelection)
1188 0 : return nsnull;
1189 0 : nsFrameSelection* frameSelection = nsnull;
1190 0 : privateSelection->GetFrameSelection(&frameSelection);
1191 0 : return frameSelection;
1192 : }
1193 :
1194 : void
1195 0 : nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify)
1196 : {
1197 0 : if (!aIgnoreUserModify && mIgnoreUserModify && mDrawn) {
1198 : // We're turning off mIgnoreUserModify. If the caret's drawn
1199 : // in a read-only node we must erase it, else the next call
1200 : // to DrawCaret() won't erase the old caret, due to the new
1201 : // mIgnoreUserModify value.
1202 0 : nsIFrame *frame = GetCaretFrame();
1203 0 : if (frame) {
1204 0 : const nsStyleUserInterface* userinterface = frame->GetStyleUserInterface();
1205 0 : if (userinterface->mUserModify == NS_STYLE_USER_MODIFY_READ_ONLY) {
1206 0 : StopBlinking();
1207 : }
1208 : }
1209 : }
1210 0 : mIgnoreUserModify = aIgnoreUserModify;
1211 0 : }
|