layout/base/nsCaret.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial