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"
8 #include "nsDirectoryServiceDefs.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;
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
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
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.
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;
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
71 @param aFrameIndex the index indicating which frame from the animation
74 - (void)setFrame:(int)aFrameIndex;
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 {
90 NSCursor* mLastSetCocoaCursor;
93 /*! @method initWithFrames:
94 @abstract Create an animated cursor by specifying the frames to use for
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
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
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
131 - (id)initWithImageNamed:(NSString*)aCursorImage
132 hotSpot:(NSPoint)aPoint
133 type:(nsCursor)aType;
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
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"];
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
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.
207 [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
208 if (hiDpiCursorImage) {
209 NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
210 [cursorImage addRepresentation:imageRep];
212 return [[[NSCursor alloc] initWithImage:cursorImage
213 hotSpot:aPoint] autorelease];
216 NS_WARNING("Problem getting path to cursor image file!");
220 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
224 // implemented by subclasses
229 if ([self isAnimated]) {
232 // if the cursor isn't animated or the timer creation fails for any reason...
243 return [self numFrames] > 1;
247 // subclasses need to override this to support animation
251 - (int)getNextCursorFrame {
252 mFrameCounter = (mFrameCounter + 1) % [self numFrames];
253 return mFrameCounter;
256 - (void)createTimer {
257 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
261 scheduledTimerWithTimeInterval:0.25
263 selector:@selector(advanceAnimatedCursor:)
265 repeats:YES] retain];
268 NS_OBJC_END_TRY_IGNORE_BLOCK;
271 - (void)destroyTimer {
272 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
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]];
290 NS_OBJC_END_TRY_IGNORE_BLOCK;
293 - (void)setFrame:(int)aFrameIndex {
294 // subclasses need to do something useful here
302 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
307 NS_OBJC_END_TRY_IGNORE_BLOCK;
312 @implementation nsCocoaCursor
314 - (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
315 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
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");
324 mFrames = [aCursorFrames retain];
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;
347 [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage
351 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
355 return [NSCursor currentCursor] == mLastSetCocoaCursor;
358 - (void)setFrame:(int)aFrameIndex {
359 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
361 NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
363 mLastSetCocoaCursor = newCursor;
365 NS_OBJC_END_TRY_IGNORE_BLOCK;
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);
385 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
390 NS_OBJC_END_TRY_IGNORE_BLOCK;