summaryrefslogtreecommitdiffstats
path: root/widget/cocoa/nsMacCursor.mm
blob: 0f56cf9a279355d87b506a5e7d7f15b19275ac53 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsMacCursor.h"
#include "nsObjCExceptions.h"
#include "nsDebug.h"
#include "nsDirectoryServiceDefs.h"
#include "nsCOMPtr.h"
#include "nsIFile.h"
#include "nsString.h"

/*! @category   nsMacCursor (PrivateMethods)
    @abstract   Private methods internal to the nsMacCursor class.
    @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define
   complete behaviour in and of itself, the subclasses defined in this file provide the useful
   implementations.
*/
@interface nsMacCursor (PrivateMethods)

/*! @method     getNextCursorFrame
    @abstract   get the index of the next cursor frame to display.
    @discussion Increments and returns the frame counter of an animated cursor.
    @result     The index of the next frame to display in the cursor animation
*/
- (int)getNextCursorFrame;

/*! @method     numFrames
    @abstract   Query the number of frames in this cursor's animation.
    @discussion Returns the number of frames in this cursor's animation. Static cursors return 1.
*/
- (int)numFrames;

/*! @method     createTimer
    @abstract   Create a Timer to use to animate the cursor.
    @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor
   animation. This method should only be called for cursors that are animated.
*/
- (void)createTimer;

/*! @method     destroyTimer
    @abstract   Destroy any timer instance associated with this cursor.
    @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this
   cursor.
 */
- (void)destroyTimer;
/*! @method     destroyTimer
    @abstract   Destroy any timer instance associated with this cursor.
    @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this
   cursor.
*/

/*! @method     advanceAnimatedCursor:
    @abstract   Method called by animation timer to perform animation.
    @discussion Called by an animated cursor's associated timer to advance the animation to the next
   frame. Determines which frame should occur next and sets the cursor to that frame.
    @param      aTimer the timer causing the animation
*/
- (void)advanceAnimatedCursor:(NSTimer*)aTimer;

/*! @method     setFrame:
    @abstract   Sets the current cursor, using an index to determine which frame in the animation to
   display.
    @discussion Sets the current cursor. The frame index determines which frame is shown if the
   cursor is animated. Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] -
   1</code>. A static cursor has a single frame, numbered 0.
    @param      aFrameIndex the index indicating which frame from the animation to display
*/
- (void)setFrame:(int)aFrameIndex;

@end

/*! @class      nsCocoaCursor
    @abstract   Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code>
   instances.
    @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances.
   These can be either built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s
   created from images. When more than one <code>NSCursor</code> is provided, the cursor will use
   these as animation frames.
*/
@interface nsCocoaCursor : nsMacCursor {
 @private
  NSArray* mFrames;
  NSCursor* mLastSetCocoaCursor;
}

/*! @method     initWithFrames:
    @abstract   Create an animated cursor by specifying the frames to use for the animation.
    @discussion Creates a cursor that will animate by cycling through the given frames. Each element
   of the array must be an instance of <code>NSCursor</code>
    @param      aCursorFrames an array of <code>NSCursor</code>, representing the frames of an
   animated cursor, in the order they should be played.
    @param      aType the corresponding <code>nsCursor</code> constant
    @result     an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames
 */
- (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType;

/*! @method     initWithCursor:
    @abstract   Create a cursor by specifying a Cocoa <code>NSCursor</code>.
    @discussion Creates a cursor representing the given Cocoa built-in cursor.
    @param      aCursor the <code>NSCursor</code> to use
    @param      aType the corresponding <code>nsCursor</code> constant
    @result     an instance of <code>nsCocoaCursor</code> representing the given
   <code>NSCursor</code>
*/
- (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType;

/*! @method     initWithImageNamed:hotSpot:
    @abstract   Create a cursor by specifying the name of an image resource to use for the cursor
   and a hotspot.
    @discussion Creates a cursor by loading the named image using the <code>+[NSImage
   imageNamed:]</code> method. <p>The image must be compatible with any restrictions laid down by
   <code>NSCursor</code>. These vary by operating system version.</p> <p>The hotspot precisely
   determines the point where the user clicks when using the cursor.</p>
    @param      aCursor the name of the image to use for the cursor
    @param      aPoint the point within the cursor to use as the hotspot
    @param      aType the corresponding <code>nsCursor</code> constant
    @result     an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot
*/
- (id)initWithImageNamed:(NSString*)aCursorImage hotSpot:(NSPoint)aPoint type:(nsCursor)aType;

@end

@implementation nsMacCursor

+ (nsMacCursor*)cursorWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

+ (nsMacCursor*)cursorWithImageNamed:(NSString*)aCursorImage
                             hotSpot:(NSPoint)aPoint
                                type:(nsCursor)aType {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint
                                               type:aType] autorelease];

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

+ (nsMacCursor*)cursorWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

+ (NSCursor*)cocoaCursorWithImageNamed:(NSString*)imageName hotSpot:(NSPoint)aPoint {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  nsCOMPtr<nsIFile> resDir;
  nsAutoCString resPath;
  NSString *pathToImage, *pathToHiDpiImage;
  NSImage *cursorImage, *hiDpiCursorImage;

  nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir));
  if (NS_FAILED(rv)) goto INIT_FAILURE;
  resDir->AppendNative("res"_ns);
  resDir->AppendNative("cursors"_ns);

  rv = resDir->GetNativePath(resPath);
  if (NS_FAILED(rv)) goto INIT_FAILURE;

  pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()];
  if (!pathToImage) goto INIT_FAILURE;
  pathToImage = [pathToImage stringByAppendingPathComponent:imageName];
  pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"];
  // Add same extension to both image paths.
  pathToImage = [pathToImage stringByAppendingPathExtension:@"png"];
  pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"];

  cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
  if (!cursorImage) goto INIT_FAILURE;

  // Note 1: There are a few different ways to get a hidpi image via
  // initWithContentsOfFile. We let the OS handle this here: when the
  // file basename ends in "@2x", it will be displayed at native resolution
  // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways.
  //
  // Note 2: The OS is picky, and will ignore the hidpi representation
  // unless it is exactly twice the size of the lowdpi image.
  hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease];
  if (hiDpiCursorImage) {
    NSImageRep* imageRep = [[hiDpiCursorImage representations] objectAtIndex:0];
    [cursorImage addRepresentation:imageRep];
  }
  return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease];

INIT_FAILURE:
  NS_WARNING("Problem getting path to cursor image file!");
  [self release];
  return nil;

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (BOOL)isSet {
  // implemented by subclasses
  return NO;
}

- (void)set {
  if ([self isAnimated]) {
    [self createTimer];
  }
  // if the cursor isn't animated or the timer creation fails for any reason...
  if (!mTimer) {
    [self setFrame:0];
  }
}

- (void)unset {
  [self destroyTimer];
}

- (BOOL)isAnimated {
  return [self numFrames] > 1;
}

- (int)numFrames {
  // subclasses need to override this to support animation
  return 1;
}

- (int)getNextCursorFrame {
  mFrameCounter = (mFrameCounter + 1) % [self numFrames];
  return mFrameCounter;
}

- (void)createTimer {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  if (!mTimer) {
    mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
                                               target:self
                                             selector:@selector(advanceAnimatedCursor:)
                                             userInfo:nil
                                              repeats:YES] retain];
  }

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

- (void)destroyTimer {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  if (mTimer) {
    [mTimer invalidate];
    [mTimer release];
    mTimer = nil;
  }

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

- (void)advanceAnimatedCursor:(NSTimer*)aTimer {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  if ([aTimer isValid]) {
    [self setFrame:[self getNextCursorFrame]];
  }

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

- (void)setFrame:(int)aFrameIndex {
  // subclasses need to do something useful here
}

- (nsCursor)type {
  return mType;
}

- (void)dealloc {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  [self destroyTimer];
  [super dealloc];

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

@end

@implementation nsCocoaCursor

- (id)initWithFrames:(NSArray*)aCursorFrames type:(nsCursor)aType {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  self = [super init];
  NSEnumerator* it = [aCursorFrames objectEnumerator];
  NSObject* frame = nil;
  while ((frame = [it nextObject])) {
    NS_ASSERTION([frame isKindOfClass:[NSCursor class]],
                 "Invalid argument: All frames must be of type NSCursor");
  }
  mFrames = [aCursorFrames retain];
  mFrameCounter = 0;
  mType = aType;
  return self;

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (id)initWithCursor:(NSCursor*)aCursor type:(nsCursor)aType {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  NSArray* frame = [NSArray arrayWithObjects:aCursor, nil];
  return [self initWithFrames:frame type:aType];

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (id)initWithImageNamed:(NSString*)aCursorImage hotSpot:(NSPoint)aPoint type:(nsCursor)aType {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint]
                         type:aType];

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (BOOL)isSet {
  return [NSCursor currentCursor] == mLastSetCocoaCursor;
}

- (void)setFrame:(int)aFrameIndex {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
  [newCursor set];
  mLastSetCocoaCursor = newCursor;

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

- (int)numFrames {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  return [mFrames count];

  NS_OBJC_END_TRY_BLOCK_RETURN(0);
}

- (NSString*)description {
  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;

  return [mFrames description];

  NS_OBJC_END_TRY_BLOCK_RETURN(nil);
}

- (void)dealloc {
  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;

  [mFrames release];
  [super dealloc];

  NS_OBJC_END_TRY_IGNORE_BLOCK;
}

@end