Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / widget / cocoa / nsMacCursor.mm
blobc4dda5acabf19f3887b904078f92009c0ae23cf5
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
16    does not define complete behaviour in and of itself, the subclasses defined
17    in this file provide the useful 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
31    cursors return 1.
33 - (int)numFrames;
35 /*! @method     createTimer
36     @abstract   Create a Timer to use to animate the cursor.
37     @discussion Creates an instance of <code>NSTimer</code> which is used to
38    drive the cursor animation. This method should only be called for cursors
39    that are animated.
41 - (void)createTimer;
43 /*! @method     destroyTimer
44     @abstract   Destroy any timer instance associated with this cursor.
45     @discussion Invalidates and releases any <code>NSTimer</code> instance
46    associated with this cursor.
47  */
48 - (void)destroyTimer;
49 /*! @method     destroyTimer
50     @abstract   Destroy any timer instance associated with this cursor.
51     @discussion Invalidates and releases any <code>NSTimer</code> instance
52    associated with this cursor.
55 /*! @method     advanceAnimatedCursor:
56     @abstract   Method called by animation timer to perform animation.
57     @discussion Called by an animated cursor's associated timer to advance the
58    animation to the next frame. Determines which frame should occur next and
59    sets the cursor to that frame.
60     @param      aTimer the timer causing the animation
62 - (void)advanceAnimatedCursor:(NSTimer*)aTimer;
64 /*! @method     setFrame:
65     @abstract   Sets the current cursor, using an index to determine which frame
66    in the animation to display.
67     @discussion Sets the current cursor. The frame index determines which frame
68    is shown if the cursor is animated. Frames and numbered from <code>0</code>
69    to <code>-[nsMacCursor numFrames] - 1</code>. A static cursor has a single
70    frame, numbered 0.
71     @param      aFrameIndex the index indicating which frame from the animation
72    to display
74 - (void)setFrame:(int)aFrameIndex;
76 @end
78 /*! @class      nsCocoaCursor
79     @abstract   Implementation of <code>nsMacCursor</code> that uses Cocoa
80    <code>NSCursor</code> instances.
81     @discussion Displays a static or animated cursor, using Cocoa
82    <code>NSCursor</code> instances. These can be either built-in
83    <code>NSCursor</code> instances, or custom <code>NSCursor</code>s created
84    from images. When more than one <code>NSCursor</code> is provided, the cursor
85    will use these as animation frames.
87 @interface nsCocoaCursor : nsMacCursor {
88  @private
89   NSArray* mFrames;
90   NSCursor* mLastSetCocoaCursor;
93 /*! @method     initWithFrames:
94     @abstract   Create an animated cursor by specifying the frames to use for
95    the animation.
96     @discussion Creates a cursor that will animate by cycling through the given
97    frames. Each element of the array must be an instance of
98    <code>NSCursor</code>
99     @param      aCursorFrames an array of <code>NSCursor</code>, representing
100    the frames of an animated cursor, in the order they should be played.
101     @param      aType the corresponding <code>nsCursor</code> constant
102     @result     an instance of <code>nsCocoaCursor</code> that will animate the
103    given cursor frames
104  */
105 - (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType;
107 /*! @method     initWithCursor:
108     @abstract   Create a cursor by specifying a Cocoa <code>NSCursor</code>.
109     @discussion Creates a cursor representing the given Cocoa built-in cursor.
110     @param      aCursor the <code>NSCursor</code> to use
111     @param      aType the corresponding <code>nsCursor</code> constant
112     @result     an instance of <code>nsCocoaCursor</code> representing the given
113    <code>NSCursor</code>
115 - (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType;
117 /*! @method     initWithImageNamed:hotSpot:
118     @abstract   Create a cursor by specifying the name of an image resource to
119    use for the cursor and a hotspot.
120     @discussion Creates a cursor by loading the named image using the
121    <code>+[NSImage imageNamed:]</code> method. <p>The image must be compatible
122    with any restrictions laid down by <code>NSCursor</code>. These vary by
123    operating system version.</p> <p>The hotspot precisely determines the point
124    where the user clicks when using the cursor.</p>
125     @param      aCursor the name of the image to use for the cursor
126     @param      aPoint the point within the cursor to use as the hotspot
127     @param      aType the corresponding <code>nsCursor</code> constant
128     @result     an instance of <code>nsCocoaCursor</code> that uses the given
129    image and hotspot
131 - (id)initWithImageNamed:(NSString*)aCursorImage
132                  hotSpot:(NSPoint)aPoint
133                     type:(nsCursor)aType;
135 @end
137 @implementation nsMacCursor
139 + (nsMacCursor*)cursorWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
140   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
142   return [[[nsCocoaCursor alloc] initWithCursor:aCursor
143                                            type:aType] autorelease];
145   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
148 + (nsMacCursor*)cursorWithImageNamed:(NSString*)aCursorImage
149                              hotSpot:(NSPoint)aPoint
150                                 type:(nsCursor)aType {
151   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
153   return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage
154                                             hotSpot:aPoint
155                                                type:aType] autorelease];
157   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
160 + (nsMacCursor*)cursorWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
161   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
163   return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames
164                                            type:aType] autorelease];
166   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
169 + (NSCursor*)cocoaCursorWithImageNamed:(NSString*)imageName
170                                hotSpot:(NSPoint)aPoint {
171   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
173   nsCOMPtr<nsIFile> resDir;
174   nsAutoCString resPath;
175   NSString *pathToImage, *pathToHiDpiImage;
176   NSImage *cursorImage, *hiDpiCursorImage;
178   nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
179   if (NS_FAILED(rv)) goto INIT_FAILURE;
180   resDir->AppendNative("res"_ns);
181   resDir->AppendNative("cursors"_ns);
183   rv = resDir->GetNativePath(resPath);
184   if (NS_FAILED(rv)) goto INIT_FAILURE;
186   pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
187   if (!pathToImage) goto INIT_FAILURE;
188   pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
189   pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
190   // Add same extension to both image paths.
191   pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
192   pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
194   cursorImage =
195       [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
196   if (!cursorImage) goto INIT_FAILURE;
198   // Note 1: There are a few different ways to get a hidpi image via
199   // initWithContentsOfFile. We let the OS handle this here: when the
200   // file basename ends in "@2x", it will be displayed at native resolution
201   // instead of being pixel-doubled. See bug 784909 comment 7 for alternates
202   // ways.
203   //
204   // Note 2: The OS is picky, and will ignore the hidpi representation
205   // unless it is exactly twice the size of the lowdpi image.
206   hiDpiCursorImage =
207       [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
208   if (hiDpiCursorImage) {
209     NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
210     [cursorImage addRepresentation:imageRep];
211   }
212   return [[[NSCursor alloc] initWithImage:cursorImage
213                                   hotSpot:aPoint] autorelease];
215 INIT_FAILURE:
216   NS_WARNING("Problem getting path to cursor image file!");
217   [self release];
218   return nil;
220   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
223 - (BOOL)isSet {
224   // implemented by subclasses
225   return NO;
228 - (void)set {
229   if ([self isAnimated]) {
230     [self createTimer];
231   }
232   // if the cursor isn't animated or the timer creation fails for any reason...
233   if (!mTimer) {
234     [self setFrame:0];
235   }
238 - (void)unset {
239   [self destroyTimer];
242 - (BOOL)isAnimated {
243   return [self numFrames] > 1;
246 - (int)numFrames {
247   // subclasses need to override this to support animation
248   return 1;
251 - (int)getNextCursorFrame {
252   mFrameCounter = (mFrameCounter + 1) % [self numFrames];
253   return mFrameCounter;
256 - (void)createTimer {
257   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
259   if (!mTimer) {
260     mTimer = [[NSTimer
261         scheduledTimerWithTimeInterval:0.25
262                                 target:self
263                               selector:@selector(advanceAnimatedCursor:)
264                               userInfo:nil
265                                repeats:YES] retain];
266   }
268   NS_OBJC_END_TRY_IGNORE_BLOCK;
271 - (void)destroyTimer {
272   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
274   if (mTimer) {
275     [mTimer invalidate];
276     [mTimer release];
277     mTimer = nil;
278   }
280   NS_OBJC_END_TRY_IGNORE_BLOCK;
283 - (void)advanceAnimatedCursor:(NSTimer*)aTimer {
284   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
286   if ([aTimer isValid]) {
287     [self setFrame:[self getNextCursorFrame]];
288   }
290   NS_OBJC_END_TRY_IGNORE_BLOCK;
293 - (void)setFrame:(int)aFrameIndex {
294   // subclasses need to do something useful here
297 - (nsCursor)type {
298   return mType;
301 - (void)dealloc {
302   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
304   [self destroyTimer];
305   [super dealloc];
307   NS_OBJC_END_TRY_IGNORE_BLOCK;
310 @end
312 @implementation nsCocoaCursor
314 - (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
315   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
317   self = [super init];
318   NSEnumerator* it = [aCursorFrames objectEnumerator];
319   NSObject* frame = nil;
320   while ((frame = [it nextObject])) {
321     NS_ASSERTION([frame isKindOfClass:[NSCursor class]],
322                  "Invalid argument: All frames must be of type NSCursor");
323   }
324   mFrames = [aCursorFrames retain];
325   mFrameCounter = 0;
326   mType = aType;
327   return self;
329   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
332 - (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
333   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
335   NSArray* frame = [NSArray arrayWithObjects:aCursor, nil];
336   return [self initWithFrames:frame type:aType];
338   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
341 - (id)initWithImageNamed:(NSString*)aCursorImage
342                  hotSpot:(NSPoint)aPoint
343                     type:(nsCursor)aType {
344   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
346   return
347       [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage
348                                                           hotSpot:aPoint]
349                       type:aType];
351   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
354 - (BOOL)isSet {
355   return [NSCursor currentCursor] == mLastSetCocoaCursor;
358 - (void)setFrame:(int)aFrameIndex {
359   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
361   NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
362   [newCursor set];
363   mLastSetCocoaCursor = newCursor;
365   NS_OBJC_END_TRY_IGNORE_BLOCK;
368 - (int)numFrames {
369   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
371   return [mFrames count];
373   NS_OBJC_END_TRY_BLOCK_RETURN(0);
376 - (NSString*)description {
377   NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
379   return [mFrames description];
381   NS_OBJC_END_TRY_BLOCK_RETURN(nil);
384 - (void)dealloc {
385   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
387   [mFrames release];
388   [super dealloc];
390   NS_OBJC_END_TRY_IGNORE_BLOCK;
393 @end