1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/cocoa/nsMacCursor.mm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,382 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +#include "nsMacCursor.h" 1.9 +#include "nsObjCExceptions.h" 1.10 +#include "nsDebug.h" 1.11 +#include "nsDirectoryServiceDefs.h" 1.12 +#include "nsCOMPtr.h" 1.13 +#include "nsIFile.h" 1.14 +#include "nsString.h" 1.15 + 1.16 +/*! @category nsMacCursor (PrivateMethods) 1.17 + @abstract Private methods internal to the nsMacCursor class. 1.18 + @discussion <code>nsMacCursor</code> is effectively an abstract class. It does not define complete 1.19 + behaviour in and of itself, the subclasses defined in this file provide the useful implementations. 1.20 +*/ 1.21 +@interface nsMacCursor (PrivateMethods) 1.22 + 1.23 +/*! @method getNextCursorFrame 1.24 + @abstract get the index of the next cursor frame to display. 1.25 + @discussion Increments and returns the frame counter of an animated cursor. 1.26 + @result The index of the next frame to display in the cursor animation 1.27 +*/ 1.28 +- (int) getNextCursorFrame; 1.29 + 1.30 +/*! @method numFrames 1.31 + @abstract Query the number of frames in this cursor's animation. 1.32 + @discussion Returns the number of frames in this cursor's animation. Static cursors return 1. 1.33 +*/ 1.34 +- (int) numFrames; 1.35 + 1.36 +/*! @method createTimer 1.37 + @abstract Create a Timer to use to animate the cursor. 1.38 + @discussion Creates an instance of <code>NSTimer</code> which is used to drive the cursor animation. 1.39 + This method should only be called for cursors that are animated. 1.40 +*/ 1.41 +- (void) createTimer; 1.42 + 1.43 +/*! @method destroyTimer 1.44 + @abstract Destroy any timer instance associated with this cursor. 1.45 + @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor. 1.46 + */ 1.47 +- (void) destroyTimer; 1.48 +/*! @method destroyTimer 1.49 + @abstract Destroy any timer instance associated with this cursor. 1.50 + @discussion Invalidates and releases any <code>NSTimer</code> instance associated with this cursor. 1.51 +*/ 1.52 + 1.53 +/*! @method advanceAnimatedCursor: 1.54 + @abstract Method called by animation timer to perform animation. 1.55 + @discussion Called by an animated cursor's associated timer to advance the animation to the next frame. 1.56 + Determines which frame should occur next and sets the cursor to that frame. 1.57 + @param aTimer the timer causing the animation 1.58 +*/ 1.59 +- (void) advanceAnimatedCursor: (NSTimer *) aTimer; 1.60 + 1.61 +/*! @method setFrame: 1.62 + @abstract Sets the current cursor, using an index to determine which frame in the animation to display. 1.63 + @discussion Sets the current cursor. The frame index determines which frame is shown if the cursor is animated. 1.64 + Frames and numbered from <code>0</code> to <code>-[nsMacCursor numFrames] - 1</code>. A static cursor 1.65 + has a single frame, numbered 0. 1.66 + @param aFrameIndex the index indicating which frame from the animation to display 1.67 +*/ 1.68 +- (void) setFrame: (int) aFrameIndex; 1.69 + 1.70 +@end 1.71 + 1.72 +/*! @class nsCocoaCursor 1.73 + @abstract Implementation of <code>nsMacCursor</code> that uses Cocoa <code>NSCursor</code> instances. 1.74 + @discussion Displays a static or animated cursor, using Cocoa <code>NSCursor</code> instances. These can be either 1.75 + built-in <code>NSCursor</code> instances, or custom <code>NSCursor</code>s created from images. 1.76 + When more than one <code>NSCursor</code> is provided, the cursor will use these as animation frames. 1.77 +*/ 1.78 +@interface nsCocoaCursor : nsMacCursor 1.79 +{ 1.80 + @private 1.81 + NSArray *mFrames; 1.82 + NSCursor *mLastSetCocoaCursor; 1.83 +} 1.84 + 1.85 +/*! @method initWithFrames: 1.86 + @abstract Create an animated cursor by specifying the frames to use for the animation. 1.87 + @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array 1.88 + must be an instance of <code>NSCursor</code> 1.89 + @param aCursorFrames an array of <code>NSCursor</code>, representing the frames of an animated cursor, in the 1.90 + order they should be played. 1.91 + @param aType the corresponding <code>nsCursor</code> constant 1.92 + @result an instance of <code>nsCocoaCursor</code> that will animate the given cursor frames 1.93 + */ 1.94 +- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType; 1.95 + 1.96 +/*! @method initWithCursor: 1.97 + @abstract Create a cursor by specifying a Cocoa <code>NSCursor</code>. 1.98 + @discussion Creates a cursor representing the given Cocoa built-in cursor. 1.99 + @param aCursor the <code>NSCursor</code> to use 1.100 + @param aType the corresponding <code>nsCursor</code> constant 1.101 + @result an instance of <code>nsCocoaCursor</code> representing the given <code>NSCursor</code> 1.102 +*/ 1.103 +- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType; 1.104 + 1.105 +/*! @method initWithImageNamed:hotSpot: 1.106 + @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot. 1.107 + @discussion Creates a cursor by loading the named image using the <code>+[NSImage imageNamed:]</code> method. 1.108 + <p>The image must be compatible with any restrictions laid down by <code>NSCursor</code>. These vary 1.109 + by operating system version.</p> 1.110 + <p>The hotspot precisely determines the point where the user clicks when using the cursor.</p> 1.111 + @param aCursor the name of the image to use for the cursor 1.112 + @param aPoint the point within the cursor to use as the hotspot 1.113 + @param aType the corresponding <code>nsCursor</code> constant 1.114 + @result an instance of <code>nsCocoaCursor</code> that uses the given image and hotspot 1.115 +*/ 1.116 +- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType; 1.117 + 1.118 +@end 1.119 + 1.120 +@implementation nsMacCursor 1.121 + 1.122 ++ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType 1.123 +{ 1.124 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.125 + 1.126 + return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease]; 1.127 + 1.128 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.129 +} 1.130 + 1.131 ++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType 1.132 +{ 1.133 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.134 + 1.135 + return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease]; 1.136 + 1.137 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.138 +} 1.139 + 1.140 ++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType 1.141 +{ 1.142 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.143 + 1.144 + return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease]; 1.145 + 1.146 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.147 +} 1.148 + 1.149 ++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint 1.150 +{ 1.151 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.152 + 1.153 + nsCOMPtr<nsIFile> resDir; 1.154 + nsAutoCString resPath; 1.155 + NSString* pathToImage, *pathToHiDpiImage; 1.156 + NSImage* cursorImage, *hiDpiCursorImage; 1.157 + 1.158 + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir)); 1.159 + if (NS_FAILED(rv)) 1.160 + goto INIT_FAILURE; 1.161 + resDir->AppendNative(NS_LITERAL_CSTRING("res")); 1.162 + resDir->AppendNative(NS_LITERAL_CSTRING("cursors")); 1.163 + 1.164 + rv = resDir->GetNativePath(resPath); 1.165 + if (NS_FAILED(rv)) 1.166 + goto INIT_FAILURE; 1.167 + 1.168 + pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()]; 1.169 + if (!pathToImage) 1.170 + goto INIT_FAILURE; 1.171 + pathToImage = [pathToImage stringByAppendingPathComponent:imageName]; 1.172 + pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"]; 1.173 + // Add same extension to both image paths. 1.174 + pathToImage = [pathToImage stringByAppendingPathExtension:@"png"]; 1.175 + pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"]; 1.176 + 1.177 + cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease]; 1.178 + if (!cursorImage) 1.179 + goto INIT_FAILURE; 1.180 + 1.181 + // Note 1: There are a few different ways to get a hidpi image via 1.182 + // initWithContentsOfFile. We let the OS handle this here: when the 1.183 + // file basename ends in "@2x", it will be displayed at native resolution 1.184 + // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways. 1.185 + // 1.186 + // Note 2: The OS is picky, and will ignore the hidpi representation 1.187 + // unless it is exactly twice the size of the lowdpi image. 1.188 + hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease]; 1.189 + if (hiDpiCursorImage) { 1.190 + NSImageRep *imageRep = [[hiDpiCursorImage representations] objectAtIndex:0]; 1.191 + [cursorImage addRepresentation: imageRep]; 1.192 + } 1.193 + return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease]; 1.194 + 1.195 +INIT_FAILURE: 1.196 + NS_WARNING("Problem getting path to cursor image file!"); 1.197 + [self release]; 1.198 + return nil; 1.199 + 1.200 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.201 +} 1.202 + 1.203 +- (BOOL) isSet 1.204 +{ 1.205 + // implemented by subclasses 1.206 + return NO; 1.207 +} 1.208 + 1.209 +- (void) set 1.210 +{ 1.211 + if ([self isAnimated]) { 1.212 + [self createTimer]; 1.213 + } 1.214 + // if the cursor isn't animated or the timer creation fails for any reason... 1.215 + if (!mTimer) { 1.216 + [self setFrame:0]; 1.217 + } 1.218 +} 1.219 + 1.220 +- (void) unset 1.221 +{ 1.222 + [self destroyTimer]; 1.223 +} 1.224 + 1.225 +- (BOOL) isAnimated 1.226 +{ 1.227 + return [self numFrames] > 1; 1.228 +} 1.229 + 1.230 +- (int) numFrames 1.231 +{ 1.232 + // subclasses need to override this to support animation 1.233 + return 1; 1.234 +} 1.235 + 1.236 +- (int) getNextCursorFrame 1.237 +{ 1.238 + mFrameCounter = (mFrameCounter + 1) % [self numFrames]; 1.239 + return mFrameCounter; 1.240 +} 1.241 + 1.242 +- (void) createTimer 1.243 +{ 1.244 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.245 + 1.246 + if (!mTimer) { 1.247 + mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25 1.248 + target:self 1.249 + selector:@selector(advanceAnimatedCursor:) 1.250 + userInfo:nil 1.251 + repeats:YES] retain]; 1.252 + } 1.253 + 1.254 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.255 +} 1.256 + 1.257 +- (void) destroyTimer 1.258 +{ 1.259 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.260 + 1.261 + if (mTimer) { 1.262 + [mTimer invalidate]; 1.263 + [mTimer release]; 1.264 + mTimer = nil; 1.265 + } 1.266 + 1.267 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.268 +} 1.269 + 1.270 +- (void) advanceAnimatedCursor: (NSTimer *) aTimer 1.271 +{ 1.272 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.273 + 1.274 + if ([aTimer isValid]) { 1.275 + [self setFrame:[self getNextCursorFrame]]; 1.276 + } 1.277 + 1.278 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.279 +} 1.280 + 1.281 +- (void) setFrame: (int) aFrameIndex 1.282 +{ 1.283 + // subclasses need to do something useful here 1.284 +} 1.285 + 1.286 +- (nsCursor) type { 1.287 + return mType; 1.288 +} 1.289 + 1.290 +- (void) dealloc 1.291 +{ 1.292 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.293 + 1.294 + [self destroyTimer]; 1.295 + [super dealloc]; 1.296 + 1.297 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.298 +} 1.299 + 1.300 +@end 1.301 + 1.302 +@implementation nsCocoaCursor 1.303 + 1.304 +- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType 1.305 +{ 1.306 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.307 + 1.308 + self = [super init]; 1.309 + NSEnumerator *it = [aCursorFrames objectEnumerator]; 1.310 + NSObject *frame = nil; 1.311 + while ((frame = [it nextObject])) { 1.312 + NS_ASSERTION([frame isKindOfClass:[NSCursor class]], "Invalid argument: All frames must be of type NSCursor"); 1.313 + } 1.314 + mFrames = [aCursorFrames retain]; 1.315 + mFrameCounter = 0; 1.316 + mType = aType; 1.317 + return self; 1.318 + 1.319 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.320 +} 1.321 + 1.322 +- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType 1.323 +{ 1.324 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.325 + 1.326 + NSArray *frame = [NSArray arrayWithObjects:aCursor, nil]; 1.327 + return [self initWithFrames:frame type:aType]; 1.328 + 1.329 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.330 +} 1.331 + 1.332 +- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType 1.333 +{ 1.334 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.335 + 1.336 + return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType]; 1.337 + 1.338 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.339 +} 1.340 + 1.341 +- (BOOL) isSet 1.342 +{ 1.343 + return [NSCursor currentCursor] == mLastSetCocoaCursor; 1.344 +} 1.345 + 1.346 +- (void) setFrame: (int) aFrameIndex 1.347 +{ 1.348 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.349 + 1.350 + NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex]; 1.351 + [newCursor set]; 1.352 + mLastSetCocoaCursor = newCursor; 1.353 + 1.354 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.355 +} 1.356 + 1.357 +- (int) numFrames 1.358 +{ 1.359 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; 1.360 + 1.361 + return [mFrames count]; 1.362 + 1.363 + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); 1.364 +} 1.365 + 1.366 +- (NSString *) description 1.367 +{ 1.368 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; 1.369 + 1.370 + return [mFrames description]; 1.371 + 1.372 + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; 1.373 +} 1.374 + 1.375 +- (void) dealloc 1.376 +{ 1.377 + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; 1.378 + 1.379 + [mFrames release]; 1.380 + [super dealloc]; 1.381 + 1.382 + NS_OBJC_END_TRY_ABORT_BLOCK; 1.383 +} 1.384 + 1.385 +@end