widget/cocoa/nsMacCursor.mm

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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"
     7 #include "nsDebug.h"
     8 #include "nsDirectoryServiceDefs.h"
     9 #include "nsCOMPtr.h"
    10 #include "nsIFile.h"
    11 #include "nsString.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 complete 
    16                 behaviour in and of itself, the subclasses defined in this file provide the useful implementations.
    17 */
    18 @interface nsMacCursor (PrivateMethods)
    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;
    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;
    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;
    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 */
    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;
    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;
    67 @end
    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 }
    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;
    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;
   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;
   115 @end
   117 @implementation nsMacCursor
   119 + (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
   120 {
   121   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   123   return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease];
   125   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   126 }
   128 + (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
   129 {
   130   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   132   return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease];
   134   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   135 }
   137 + (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
   138 {
   139   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   141   return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease];
   143   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   144 }
   146 + (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint
   147 {
   148   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   150   nsCOMPtr<nsIFile> resDir;
   151   nsAutoCString resPath;
   152   NSString* pathToImage, *pathToHiDpiImage;
   153   NSImage* cursorImage, *hiDpiCursorImage;
   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"));
   161   rv = resDir->GetNativePath(resPath);
   162   if (NS_FAILED(rv))
   163     goto INIT_FAILURE;
   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"];
   174   cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease];
   175   if (!cursorImage)
   176     goto INIT_FAILURE;
   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];
   192 INIT_FAILURE:
   193   NS_WARNING("Problem getting path to cursor image file!");
   194   [self release];
   195   return nil;
   197   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   198 }
   200 - (BOOL) isSet
   201 {
   202   // implemented by subclasses
   203   return NO;
   204 }
   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 }
   217 - (void) unset
   218 {
   219   [self destroyTimer];    
   220 }
   222 - (BOOL) isAnimated
   223 {
   224   return [self numFrames] > 1;
   225 }
   227 - (int) numFrames
   228 {
   229   // subclasses need to override this to support animation
   230   return 1;
   231 }
   233 - (int) getNextCursorFrame
   234 {
   235   mFrameCounter = (mFrameCounter + 1) % [self numFrames];
   236   return mFrameCounter;
   237 }
   239 - (void) createTimer
   240 {
   241   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   243   if (!mTimer) {
   244     mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25
   245                                                target:self
   246                                              selector:@selector(advanceAnimatedCursor:)
   247                                              userInfo:nil
   248                                               repeats:YES] retain];
   249   }
   251   NS_OBJC_END_TRY_ABORT_BLOCK;
   252 }
   254 - (void) destroyTimer
   255 {
   256   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   258   if (mTimer) {
   259       [mTimer invalidate];
   260       [mTimer release];
   261       mTimer = nil;
   262   }
   264   NS_OBJC_END_TRY_ABORT_BLOCK;
   265 }
   267 - (void) advanceAnimatedCursor: (NSTimer *) aTimer
   268 {
   269   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   271   if ([aTimer isValid]) {
   272     [self setFrame:[self getNextCursorFrame]];
   273   }
   275   NS_OBJC_END_TRY_ABORT_BLOCK;
   276 }
   278 - (void) setFrame: (int) aFrameIndex
   279 {
   280   // subclasses need to do something useful here
   281 }
   283 - (nsCursor) type {
   284   return mType;
   285 }
   287 - (void) dealloc
   288 {
   289   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   291   [self destroyTimer];
   292   [super dealloc];
   294   NS_OBJC_END_TRY_ABORT_BLOCK;
   295 }
   297 @end
   299 @implementation nsCocoaCursor
   301 - (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType
   302 {
   303   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   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;
   316   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   317 }
   319 - (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType
   320 {
   321   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   323   NSArray *frame = [NSArray arrayWithObjects:aCursor, nil];
   324   return [self initWithFrames:frame type:aType];
   326   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   327 }
   329 - (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType
   330 {
   331   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   333   return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType];
   335   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   336 }
   338 - (BOOL) isSet
   339 {
   340   return [NSCursor currentCursor] == mLastSetCocoaCursor;
   341 }
   343 - (void) setFrame: (int) aFrameIndex
   344 {
   345   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   347   NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex];
   348   [newCursor set];
   349   mLastSetCocoaCursor = newCursor;
   351   NS_OBJC_END_TRY_ABORT_BLOCK;
   352 }
   354 - (int) numFrames
   355 {
   356   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
   358   return [mFrames count];
   360   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0);
   361 }
   363 - (NSString *) description
   364 {
   365   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
   367   return [mFrames description];
   369   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
   370 }
   372 - (void) dealloc
   373 {
   374   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
   376   [mFrames release];
   377   [super dealloc];
   379   NS_OBJC_END_TRY_ABORT_BLOCK;
   380 }
   382 @end

mercurial