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