widget/cocoa/nsMacCursor.mm

changeset 2
7e26c7da4463
equal deleted inserted replaced
-1:000000000000 0:c371cce5d1c3
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/. */
4
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"
12
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 complete
16 behaviour in and of itself, the subclasses defined in this file provide the useful implementations.
17 */
18 @interface nsMacCursor (PrivateMethods)
19
20 /*! @method getNextCursorFrame
21 @abstract get the index of the next cursor frame to display.
22 @discussion Increments and returns the frame counter of an animated cursor.
23 @result The index of the next frame to display in the cursor animation
24 */
25 - (int) getNextCursorFrame;
26
27 /*! @method numFrames
28 @abstract Query the number of frames in this cursor's animation.
29 @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
30 */
31 - (int) numFrames;
32
33 /*! @method createTimer
34 @abstract Create a Timer to use to animate the cursor.
35 @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor animation.
36 This method should only be called for cursors that are animated.
37 */
38 - (void) createTimer;
39
40 /*! @method destroyTimer
41 @abstract Destroy any timer instance associated with this cursor.
42 @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
43 */
44 - (void) destroyTimer;
45 /*! @method destroyTimer
46 @abstract Destroy any timer instance associated with this cursor.
47 @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor.
48 */
49
50 /*! @method advanceAnimatedCursor:
51 @abstract Method called by animation timer to perform animation.
52 @discussion Called by an animated cursor's associated timer to advance the animation to the next frame.
53 Determines which frame should occur next and sets the cursor to that frame.
54 @param aTimer the timer causing the animation
55 */
56 - (void) advanceAnimatedCursor: (NSTimer *) aTimer;
57
58 /*! @method setFrame:
59 @abstract Sets the current cursor, using an index to determine which frame in the animation to display.
60 @discussion Sets the current cursor. The frame index determines which frame is shown if the cursor is animated.
61 Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] - 1</code>. A static cursor
62 has a single frame, numbered 0.
63 @param aFrameIndex the index indicating which frame from the animation to display
64 */
65 - (void) setFrame: (int) aFrameIndex;
66
67 @end
68
69 /*! @class nsCocoaCursor
70 @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code> instances.
71 @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances. These can be either
72 built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s created from images.
73 When more than one <code>NSCursor</code> is provided, the cursor will use these as animation frames.
74 */
75 @interface nsCocoaCursor : nsMacCursor
76 {
77 @private
78 NSArray *mFrames;
79 NSCursor *mLastSetCocoaCursor;
80 }
81
82 /*! @method initWithFrames:
83 @abstract Create an animated cursor by specifying the frames to use for the animation.
84 @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array
85 must be an instance of <code>NSCursor</code>
86 @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the
87 order they should be played.
88 @param aType the corresponding <code>nsCursor</code> constant
89 @result an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
90 */
91 - (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType;
92
93 /*! @method initWithCursor:
94 @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>.
95 @discussion Creates a cursor representing the given Cocoa built-in cursor.
96 @param aCursor the <code>NSCursor</code> to use
97 @param aType the corresponding <code>nsCursor</code> constant
98 @result an instance of <code>nsCocoaCursor</code> representing the given <code>NSCursor</code>
99 */
100 - (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType;
101
102 /*! @method initWithImageNamed:hotSpot:
103 @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot.
104 @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method.
105 <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary
106 by operating system version.</p>
107 <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p>
108 @param aCursor the name of the image to use for the cursor
109 @param aPoint the point within the cursor to use as the hotspot
110 @param aType the corresponding <code>nsCursor</code> constant
111 @result an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
112 */
113 - (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType;
114
115 @end
116
117 @implementation nsMacCursor
118
119 + (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
120 {
121 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
122
123 return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
124
125 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
126 }
127
128 + (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
129 {
130 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
131
132 return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease];
133
134 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
135 }
136
137 + (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
138 {
139 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
140
141 return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
142
143 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
144 }
145
146 + (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint
147 {
148 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
149
150 nsCOMPtr<nsIFile> resDir;
151 nsAutoCString resPath;
152 NSString* pathToImage, *pathToHiDpiImage;
153 NSImage* cursorImage, *hiDpiCursorImage;
154
155 nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
156 if (NS_FAILED(rv))
157 goto INIT_FAILURE;
158 resDir->AppendNative(NS_LITERAL_CSTRING("res"));
159 resDir->AppendNative(NS_LITERAL_CSTRING("cursors"));
160
161 rv = resDir->GetNativePath(resPath);
162 if (NS_FAILED(rv))
163 goto INIT_FAILURE;
164
165 pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
166 if (!pathToImage)
167 goto INIT_FAILURE;
168 pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
169 pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
170 // Add same extension to both image paths.
171 pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
172 pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];
173
174 cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
175 if (!cursorImage)
176 goto INIT_FAILURE;
177
178 // Note 1: There are a few different ways to get a hidpi image via
179 // initWithContentsOfFile. We let the OS handle this here: when the
180 // file basename ends in "@2x", it will be displayed at native resolution
181 // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways.
182 //
183 // Note 2: The OS is picky, and will ignore the hidpi representation
184 // unless it is exactly twice the size of the lowdpi image.
185 hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
186 if (hiDpiCursorImage) {
187 NSImageRep *imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
188 [cursorImage addRepresentation: imageRep];
189 }
190 return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];
191
192 INIT_FAILURE:
193 NS_WARNING("Problem getting path to cursor image file!");
194 [self release];
195 return nil;
196
197 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
198 }
199
200 - (BOOL) isSet
201 {
202 // implemented by subclasses
203 return NO;
204 }
205
206 - (void) set
207 {
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 }
215 }
216
217 - (void) unset
218 {
219 [self destroyTimer];
220 }
221
222 - (BOOL) isAnimated
223 {
224 return [self numFrames] > 1;
225 }
226
227 - (int) numFrames
228 {
229 // subclasses need to override this to support animation
230 return 1;
231 }
232
233 - (int) getNextCursorFrame
234 {
235 mFrameCounter = (mFrameCounter + 1) % [self numFrames];
236 return mFrameCounter;
237 }
238
239 - (void) createTimer
240 {
241 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
242
243 if (!mTimer) {
244 mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
245 target:self
246 selector:@selector(advanceAnimatedCursor:)
247 userInfo:nil
248 repeats:YES] retain];
249 }
250
251 NS_OBJC_END_TRY_ABORT_BLOCK;
252 }
253
254 - (void) destroyTimer
255 {
256 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
257
258 if (mTimer) {
259 [mTimer invalidate];
260 [mTimer release];
261 mTimer = nil;
262 }
263
264 NS_OBJC_END_TRY_ABORT_BLOCK;
265 }
266
267 - (void) advanceAnimatedCursor: (NSTimer *) aTimer
268 {
269 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
270
271 if ([aTimer isValid]) {
272 [self setFrame:[self getNextCursorFrame]];
273 }
274
275 NS_OBJC_END_TRY_ABORT_BLOCK;
276 }
277
278 - (void) setFrame: (int) aFrameIndex
279 {
280 // subclasses need to do something useful here
281 }
282
283 - (nsCursor) type {
284 return mType;
285 }
286
287 - (void) dealloc
288 {
289 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
290
291 [self destroyTimer];
292 [super dealloc];
293
294 NS_OBJC_END_TRY_ABORT_BLOCK;
295 }
296
297 @end
298
299 @implementation nsCocoaCursor
300
301 - (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
302 {
303 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
304
305 self = [super init];
306 NSEnumerator *it = [aCursorFrames objectEnumerator];
307 NSObject *frame = nil;
308 while ((frame = [it nextObject])) {
309 NS_ASSERTION([frame isKindOfClass:[NSCursor class]], "Invalid argument: All frames must be of type NSCursor");
310 }
311 mFrames = [aCursorFrames retain];
312 mFrameCounter = 0;
313 mType = aType;
314 return self;
315
316 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
317 }
318
319 - (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
320 {
321 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
322
323 NSArray *frame = [NSArray arrayWithObjects:aCursor, nil];
324 return [self initWithFrames:frame type:aType];
325
326 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
327 }
328
329 - (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
330 {
331 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
332
333 return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType];
334
335 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
336 }
337
338 - (BOOL) isSet
339 {
340 return [NSCursor currentCursor] == mLastSetCocoaCursor;
341 }
342
343 - (void) setFrame: (int) aFrameIndex
344 {
345 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
346
347 NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
348 [newCursor set];
349 mLastSetCocoaCursor = newCursor;
350
351 NS_OBJC_END_TRY_ABORT_BLOCK;
352 }
353
354 - (int) numFrames
355 {
356 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
357
358 return [mFrames count];
359
360 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
361 }
362
363 - (NSString *) description
364 {
365 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
366
367 return [mFrames description];
368
369 NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
370 }
371
372 - (void) dealloc
373 {
374 NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
375
376 [mFrames release];
377 [super dealloc];
378
379 NS_OBJC_END_TRY_ABORT_BLOCK;
380 }
381
382 @end

mercurial