1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 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 #ifndef nsCaret_h__ 10 #define nsCaret_h__ 11 12 #include "mozilla/MemoryReporting.h" 13 #include "mozilla/dom/Selection.h" 14 #include "nsCoord.h" 15 #include "nsISelectionListener.h" 16 #include "nsIWeakReferenceUtils.h" 17 #include "CaretAssociationHint.h" 18 #include "nsPoint.h" 19 #include "nsRect.h" 20 21 class nsDisplayListBuilder; 22 class nsFrameSelection; 23 class nsIContent; 24 class nsIDOMNode; 25 class nsIFrame; 26 class nsINode; 27 class nsIPresShell; 28 class nsITimer; 29 30 namespace mozilla { 31 namespace gfx { 32 class DrawTarget; 33 } // namespace gfx 34 } // namespace mozilla 35 36 //----------------------------------------------------------------------------- 37 class nsCaret final : public nsISelectionListener { 38 typedef mozilla::gfx::DrawTarget DrawTarget; 39 40 public: 41 nsCaret(); 42 43 protected: 44 virtual ~nsCaret(); 45 46 public: 47 NS_DECL_ISUPPORTS 48 49 typedef mozilla::CaretAssociationHint CaretAssociationHint; 50 51 nsresult Init(nsIPresShell* inPresShell); 52 void Terminate(); 53 54 void SetSelection(nsISelection* aDOMSel); 55 nsISelection* GetSelection(); 56 57 /** 58 * Sets whether the caret should only be visible in nodes that are not 59 * user-modify: read-only, or whether it should be visible in all nodes. 60 * 61 * @param aIgnoreUserModify true to have the cursor visible in all nodes, 62 * false to have it visible in all nodes except 63 * those with user-modify: read-only 64 */ 65 void SetIgnoreUserModify(bool aIgnoreUserModify); 66 /** SetVisible will set the visibility of the caret 67 * @param inMakeVisible true to show the caret, false to hide it 68 */ 69 void SetVisible(bool intMakeVisible); 70 /** IsVisible will get the visibility of the caret. 71 * This returns false if the caret is hidden because it was set 72 * to not be visible, or because the selection is not collapsed, or 73 * because an open popup is hiding the caret. 74 * It does not take account of blinking or the caret being hidden 75 * because we're in non-editable/disabled content. 76 */ 77 bool IsVisible(nsISelection* aSelection = nullptr) { 78 if (!mVisible || mHideCount) { 79 return false; 80 } 81 82 if (!mShowDuringSelection) { 83 mozilla::dom::Selection* selection; 84 if (aSelection) { 85 selection = static_cast<mozilla::dom::Selection*>(aSelection); 86 } else { 87 selection = GetSelectionInternal(); 88 } 89 if (!selection || !selection->IsCollapsed()) { 90 return false; 91 } 92 } 93 94 if (IsMenuPopupHidingCaret()) { 95 return false; 96 } 97 98 return true; 99 } 100 /** 101 * AddForceHide() increases mHideCount and hide the caret even if 102 * SetVisible(true) has been or will be called. This is useful when the 103 * caller wants to hide caret temporarily and it needs to cancel later. 104 * Especially, in the latter case, it's too difficult to decide if the 105 * caret should be actually visible or not because caret visible state 106 * is set from a lot of event handlers. So, it's very stateful. 107 */ 108 void AddForceHide(); 109 /** 110 * RemoveForceHide() decreases mHideCount if it's over 0. 111 * If the value becomes 0, this may show the caret if SetVisible(true) 112 * has been called. 113 */ 114 void RemoveForceHide(); 115 /** SetCaretReadOnly set the appearance of the caret 116 * @param inMakeReadonly true to show the caret in a 'read only' state, 117 * false to show the caret in normal, editing state 118 */ 119 void SetCaretReadOnly(bool inMakeReadonly); 120 /** 121 * @param aVisibility true if the caret should be visible even when the 122 * selection is not collapsed. 123 */ 124 void SetVisibilityDuringSelection(bool aVisibility); 125 126 /** 127 * Set the caret's position explicitly to the specified node and offset 128 * instead of tracking its selection. 129 * Passing null for aNode would set the caret to track its selection again. 130 **/ 131 void SetCaretPosition(nsIDOMNode* aNode, int32_t aOffset); 132 133 /** 134 * Schedule a repaint for the frame where the caret would appear. 135 * Does not check visibility etc. 136 */ 137 void SchedulePaint(nsISelection* aSelection = nullptr); 138 139 /** 140 * Returns a frame to paint in, and the bounds of the painted caret 141 * relative to that frame. 142 * The rectangle includes bidi decorations. 143 * Returns null if the caret should not be drawn (including if it's blinked 144 * off). 145 */ 146 nsIFrame* GetPaintGeometry(nsRect* aRect); 147 /** 148 * A simple wrapper around GetGeometry. Does not take any caret state into 149 * account other than the current selection. 150 */ GetGeometry(nsRect * aRect)151 nsIFrame* GetGeometry(nsRect* aRect) { 152 return GetGeometry(GetSelection(), aRect); 153 } 154 155 /** PaintCaret 156 * Actually paint the caret onto the given rendering context. 157 */ 158 void PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame, 159 const nsPoint& aOffset); 160 161 // nsISelectionListener interface 162 NS_DECL_NSISELECTIONLISTENER 163 164 /** 165 * Gets the position and size of the caret that would be drawn for 166 * the focus node/offset of aSelection (assuming it would be drawn, 167 * i.e., disregarding blink status). The geometry is stored in aRect, 168 * and we return the frame aRect is relative to. 169 * Only looks at the focus node of aSelection, so you can call it even if 170 * aSelection is not collapsed. 171 * This rect does not include any extra decorations for bidi. 172 * @param aRect must be non-null 173 */ 174 static nsIFrame* GetGeometry(nsISelection* aSelection, nsRect* aRect); 175 static nsresult GetCaretFrameForNodeOffset( 176 nsFrameSelection* aFrameSelection, nsIContent* aContentNode, 177 int32_t aOffset, CaretAssociationHint aFrameHint, uint8_t aBidiLevel, 178 nsIFrame** aReturnFrame, int32_t* aReturnOffset); 179 static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset, 180 nscoord* aBidiIndicatorSize); 181 182 // Get the frame and frame offset based on the focus node and focus offset 183 // of aSelection. If aOverrideNode and aOverride are provided, use them 184 // instead. 185 // @param aFrameOffset return the frame offset if non-null. 186 // @return the frame of the focus node. 187 static nsIFrame* GetFrameAndOffset(mozilla::dom::Selection* aSelection, 188 nsINode* aOverrideNode, 189 int32_t aOverrideOffset, 190 int32_t* aFrameOffset); 191 192 size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; 193 194 nsIFrame* GetFrame(int32_t* aContentOffset); 195 void ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset, 196 nsRect* aCaretRect, nsRect* aHookRect); 197 198 protected: 199 static void CaretBlinkCallback(nsITimer* aTimer, void* aClosure); 200 201 void CheckSelectionLanguageChange(); 202 203 void ResetBlinking(); 204 void StopBlinking(); 205 206 mozilla::dom::Selection* GetSelectionInternal(); 207 208 struct Metrics { 209 nscoord mBidiIndicatorSize; // width and height of bidi indicator 210 nscoord mCaretWidth; // full caret width including bidi indicator 211 }; 212 static Metrics ComputeMetrics(nsIFrame* aFrame, int32_t aOffset, 213 nscoord aCaretHeight); 214 215 // Returns true if we should not draw the caret because of XUL menu popups. 216 // The caret should be hidden if: 217 // 1. An open popup contains the caret, but a menu popup exists before the 218 // caret-owning popup in the popup list (i.e. a menu is in front of the 219 // popup with the caret). If the menu itself contains the caret we don't 220 // hide it. 221 // 2. A menu popup is open, but there is no caret present in any popup. 222 // 3. The caret selection is empty. 223 bool IsMenuPopupHidingCaret(); 224 225 nsWeakPtr mPresShell; 226 nsWeakPtr mDomSelectionWeak; 227 228 nsCOMPtr<nsITimer> mBlinkTimer; 229 230 /** 231 * The content to draw the caret at. If null, we use mDomSelectionWeak's 232 * focus node instead. 233 */ 234 nsCOMPtr<nsINode> mOverrideContent; 235 /** 236 * The character offset to draw the caret at. 237 * Ignored if mOverrideContent is null. 238 */ 239 int32_t mOverrideOffset; 240 /** 241 * mBlinkCount is used to control the number of times to blink the caret 242 * before stopping the blink. This is reset each time we reset the 243 * blinking. 244 */ 245 int32_t mBlinkCount; 246 /** 247 * mBlinkRate is the rate of the caret blinking the last time we read it. 248 * It is used as a way to optimize whether we need to reset the blinking 249 * timer. 250 */ 251 uint32_t mBlinkRate; 252 /** 253 * mHideCount is not 0, it means that somebody doesn't want the caret 254 * to be visible. See AddForceHide() and RemoveForceHide(). 255 */ 256 uint32_t mHideCount; 257 258 /** 259 * mIsBlinkOn is true when we're in a blink cycle where the caret is on. 260 */ 261 bool mIsBlinkOn; 262 /** 263 * mIsVisible is true when SetVisible was last called with 'true'. 264 */ 265 bool mVisible; 266 /** 267 * mReadOnly is true when the caret is set to "read only" mode (i.e., 268 * it doesn't blink). 269 */ 270 bool mReadOnly; 271 /** 272 * mShowDuringSelection is true when the caret should be shown even when 273 * the selection is not collapsed. 274 */ 275 bool mShowDuringSelection; 276 /** 277 * mIgnoreUserModify is true when the caret should be shown even when 278 * it's in non-user-modifiable content. 279 */ 280 bool mIgnoreUserModify; 281 }; 282 283 #endif // nsCaret_h__ 284