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 does not define
16 complete behaviour in and of itself, the subclasses defined in this file provide the useful
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 cursors return 1.
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.
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
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
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;
62 @abstract Sets the current cursor, using an index to determine which frame in the animation to
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;
73 /*! @class nsCocoaCursor
74 @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code>
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 {
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
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
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;
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.
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];
192 return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];
195 NS_WARNING("Problem getting path to cursor image file!");
199 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
203 // implemented by subclasses
208 if ([self isAnimated]) {
211 // if the cursor isn't animated or the timer creation fails for any reason...
222 return [self numFrames] > 1;
226 // subclasses need to override this to support animation
230 - (int)getNextCursorFrame {
231 mFrameCounter = (mFrameCounter + 1) % [self numFrames];
232 return mFrameCounter;
235 - (void)createTimer {
236 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
239 mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
241 selector:@selector(advanceAnimatedCursor:)
243 repeats:YES] retain];
246 NS_OBJC_END_TRY_IGNORE_BLOCK;
249 - (void)destroyTimer {
250 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
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]];
268 NS_OBJC_END_TRY_IGNORE_BLOCK;
271 - (void)setFrame:(int)aFrameIndex {
272 // subclasses need to do something useful here
280 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
285 NS_OBJC_END_TRY_IGNORE_BLOCK;
290 @implementation nsCocoaCursor
292 - (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
293 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
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");
302 mFrames = [aCursorFrames retain];
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]
325 NS_OBJC_END_TRY_BLOCK_RETURN(nil);
329 return [NSCursor currentCursor] == mLastSetCocoaCursor;
332 - (void)setFrame:(int)aFrameIndex {
333 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
335 NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
337 mLastSetCocoaCursor = newCursor;
339 NS_OBJC_END_TRY_IGNORE_BLOCK;
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);
359 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
364 NS_OBJC_END_TRY_IGNORE_BLOCK;