widget/cocoa/nsMacCursor.mm

changeset 0
6474c204b198
     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

mercurial