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