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