Bug 1700051: part 28) Refactor `WordSplitState<T>::GetDOMWordSeparatorOffset` to...
[gecko.git] / widget / cocoa / nsMacCursor.mm
blob0f56cf9a279355d87b506a5e7d7f15b19275ac53
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "nsMacCursor.h"
6 #include "nsObjCExceptions.h"
7 #include "nsDebug.h"
8 #include "nsDirectoryServiceDefs.h"
9 #include "nsCOMPtr.h"
10 #include "nsIFile.h"
11 #include "nsString.h"
13 /*! @category   nsMacCursor (PrivateMethods)
14     @abstract   Private methods internal to the nsMacCursor class.
15     @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define
16    complete behaviour in and of itself, the subclasses defined in this file provide the useful
17    implementations.
19 @interface nsMacCursor (PrivateMethods)
21 /*! @method     getNextCursorFrame
22     @abstract   get the index of the next cursor frame to display.
23     @discussion Increments and returns the frame counter of an animated cursor.
24     @result     The index of the next frame to display in the cursor animation
26 - (int)getNextCursorFrame;
28 /*! @method     numFrames
29     @abstract   Query the number of frames in this cursor's animation.
30     @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
32 - (int)numFrames;
34 /*! @method     createTimer
35     @abstract   Create a Timer to use to animate the cursor.
36     @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor
37    animation. This method should only be called for cursors that are animated.
39 - (void)createTimer;
41 /*! @method     destroyTimer
42     @abstract   Destroy any timer instance associated with this cursor.
43     @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this
44    cursor.
45  */
46 - (void)destroyTimer;
47 /*! @method     destroyTimer
48     @abstract   Destroy any timer instance associated with this cursor.
49     @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this
50    cursor.
53 /*! @method     advanceAnimatedCursor:
54     @abstract   Method called by animation timer to perform animation.
55     @discussion Called by an animated cursor's associated timer to advance the animation to the next
56    frame. Determines which frame should occur next and sets the cursor to that frame.
57     @param      aTimer the timer causing the animation
59 - (void)advanceAnimatedCursor:(NSTimer*)aTimer;
61 /*! @method     setFrame:
62     @abstract   Sets the current cursor, using an index to determine which frame in the animation to
63    display.
64     @discussion Sets the current cursor. The frame index determines which frame is shown if the
65    cursor is animated. Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] -
66    1</code>. A static cursor has a single frame, numbered 0.
67     @param      aFrameIndex the index indicating which frame from the animation to display
69 - (void)setFrame:(int)aFrameIndex;
71 @end
73 /*! @class      nsCocoaCursor
74     @abstract   Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code>
75    instances.
76     @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances.
77    These can be either built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s
78    created from images. When more than one <code>NSCursor</code> is provided, the cursor will use
79    these as animation frames.
81 @interface nsCocoaCursor : nsMacCursor {
82  @private
83   NSArray* mFrames;
84   NSCursor* mLastSetCocoaCursor;
87 /*! @method     initWithFrames:
88     @abstract   Create an animated cursor by specifying the frames to use for the animation.
89     @discussion Creates a cursor that will animate by cycling through the given frames. Each element
90    of the array must be an instance of <code>NSCursor</code>
91     @param      aCursorFrames an array of <code>NSCursor</code>, representing the frames of an
92    animated cursor, in the order they should be played.
93     @param      aType the corresponding <code>nsCursor</code> constant
94     @result     an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
95  */
96 - (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType;
98 /*! @method     initWithCursor:
99     @abstract   Create a cursor by specifying a Cocoa <code>NSCursor</code>.
100     @discussion Creates a cursor representing the given Cocoa built-in cursor.
101     @param      aCursor the <code>NSCursor</code> to use
102     @param      aType the corresponding <code>nsCursor</code> constant
103     @result     an instance of <code>nsCocoaCursor</code> representing the given
104    <code>NSCursor</code>
106 - (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType;
108 /*! @method     initWithImageNamed:hotSpot:
109     @abstract   Create a cursor by specifying the name of an image resource to use for the cursor
110    and a hotspot.
111     @discussion Creates a cursor by loading the named image using the <code>+[NSImage
112    imageNamed:]</code> method. <p>The image must be compatible with any restrictions laid down by
113    <code>NSCursor</code>. These vary by operating system version.</p> <p>The hotspot precisely
114    determines the point where the user clicks when using the cursor.</p>
115     @param      aCursor the name of the image to use for the cursor
116     @param      aPoint the point within the cursor to use as the hotspot
117     @param      aType the corresponding <code>nsCursor</code> constant
118     @result     an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
120 - (id)initWithImageNamed:(NSString*)aCursorImage hotSpot:(NSPoint)aPoint type:(nsCursor)aType;
122 @end
124 @implementation nsMacCursor
126 + (nsMacCursor*)cursorWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
127   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
129   return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
131   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
134 + (nsMacCursor*)cursorWithImageNamed:(NSString*)aCursorImage
135                              hotSpot:(NSPoint)aPoint
136                                 type:(nsCursor)aType {
137   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
139   return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint
140                                                type:aType] autorelease];
142   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
145 + (nsMacCursor*)cursorWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
146   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
148   return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
150   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
153 + (NSCursor*)cocoaCursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint {
154   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
156   nsCOMPtr<nsIFile> resDir;
157   nsAutoCString resPath;
158   NSString *pathToImage, *pathToHiDpiImage;
159   NSImage *cursorImage, *hiDpiCursorImage;
161   nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
162   if (NS_FAILED(rv)) goto INIT_FAILURE;
163   resDir->AppendNative("res"_ns);
164   resDir->AppendNative("cursors"_ns);
166   rv = resDir->GetNativePath(resPath);
167   if (NS_FAILED(rv)) goto INIT_FAILURE;
169   pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
170   if (!pathToImage) goto INIT_FAILURE;
171   pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
172   pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
173   // Add same extension to both image paths.
174   pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
175   pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
177   cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
178   if (!cursorImage) goto INIT_FAILURE;
180   // Note 1: There are a few different ways to get a hidpi image via
181   // initWithContentsOfFile. We let the OS handle this here: when the
182   // file basename ends in "@2x", it will be displayed at native resolution
183   // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways.
184   //
185   // Note 2: The OS is picky, and will ignore the hidpi representation
186   // unless it is exactly twice the size of the lowdpi image.
187   hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
188   if (hiDpiCursorImage) {
189     NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
190     [cursorImage addRepresentation:imageRep];
191   }
192   return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];
194 INIT_FAILURE:
195   NS_WARNING("Problem getting path to cursor image file!");
196   [self release];
197   return nil;
199   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
202 - (BOOL)isSet {
203   // implemented by subclasses
204   return NO;
207 - (void)set {
208   if ([self isAnimated]) {
209     [self createTimer];
210   }
211   // if the cursor isn't animated or the timer creation fails for any reason...
212   if (!mTimer) {
213     [self setFrame:0];
214   }
217 - (void)unset {
218   [self destroyTimer];
221 - (BOOL)isAnimated {
222   return [self numFrames] > 1;
225 - (int)numFrames {
226   // subclasses need to override this to support animation
227   return 1;
230 - (int)getNextCursorFrame {
231   mFrameCounter = (mFrameCounter + 1) % [self numFrames];
232   return mFrameCounter;
235 - (void)createTimer {
236   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
238   if (!mTimer) {
239     mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
240                                                target:self
241                                              selector:@selector(advanceAnimatedCursor:)
242                                              userInfo:nil
243                                               repeats:YES] retain];
244   }
246   NS_OBJC_END_TRY_IGNORE_BLOCK;
249 - (void)destroyTimer {
250   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
252   if (mTimer) {
253     [mTimer invalidate];
254     [mTimer release];
255     mTimer = nil;
256   }
258   NS_OBJC_END_TRY_IGNORE_BLOCK;
261 - (void)advanceAnimatedCursor:(NSTimer*)aTimer {
262   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
264   if ([aTimer isValid]) {
265     [self setFrame:[self getNextCursorFrame]];
266   }
268   NS_OBJC_END_TRY_IGNORE_BLOCK;
271 - (void)setFrame:(int)aFrameIndex {
272   // subclasses need to do something useful here
275 - (nsCursor)type {
276   return mType;
279 - (void)dealloc {
280   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
282   [self destroyTimer];
283   [super dealloc];
285   NS_OBJC_END_TRY_IGNORE_BLOCK;
288 @end
290 @implementation nsCocoaCursor
292 - (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
293   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
295   self = [super init];
296   NSEnumerator* it = [aCursorFrames objectEnumerator];
297   NSObject* frame = nil;
298   while ((frame = [it nextObject])) {
299     NS_ASSERTION([frame isKindOfClass:[NSCursor class]],
300                  "Invalid argument: All frames must be of type NSCursor");
301   }
302   mFrames = [aCursorFrames retain];
303   mFrameCounter = 0;
304   mType = aType;
305   return self;
307   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
310 - (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
311   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
313   NSArray* frame = [NSArray arrayWithObjects:aCursor, nil];
314   return [self initWithFrames:frame type:aType];
316   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
319 - (id)initWithImageNamed:(NSString*)aCursorImage hotSpot:(NSPoint)aPoint type:(nsCursor)aType {
320   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
322   return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint]
323                          type:aType];
325   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
328 - (BOOL)isSet {
329   return [NSCursor currentCursor] == mLastSetCocoaCursor;
332 - (void)setFrame:(int)aFrameIndex {
333   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
335   NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
336   [newCursor set];
337   mLastSetCocoaCursor = newCursor;
339   NS_OBJC_END_TRY_IGNORE_BLOCK;
342 - (int)numFrames {
343   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
345   return [mFrames count];
347   NS_OBJC_END_TRY_BLOCK_RETURN(0);
350 - (NSString*)description {
351   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
353   return [mFrames description];
355   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
358 - (void)dealloc {
359   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
361   [mFrames release];
362   [super dealloc];
364   NS_OBJC_END_TRY_IGNORE_BLOCK;
367 @end