diff options
Diffstat (limited to 'widget/cocoa/nsMacCursor.mm')
-rw-r--r-- | widget/cocoa/nsMacCursor.mm | 382 |
1 files changed, 382 insertions, 0 deletions
diff --git a/widget/cocoa/nsMacCursor.mm b/widget/cocoa/nsMacCursor.mm new file mode 100644 index 000000000..4fcdfd3e5 --- /dev/null +++ b/widget/cocoa/nsMacCursor.mm @@ -0,0 +1,382 @@ +/* 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_ABORT_BLOCK_NIL; + + return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + ++ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + ++ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + ++ (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + 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(NS_LITERAL_CSTRING("res")); + resDir->AppendNative(NS_LITERAL_CSTRING("cursors")); + + 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_ABORT_BLOCK_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_ABORT_BLOCK; + + if (!mTimer) { + mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25 + target:self + selector:@selector(advanceAnimatedCursor:) + userInfo:nil + repeats:YES] retain]; + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void) destroyTimer +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + if (mTimer) { + [mTimer invalidate]; + [mTimer release]; + mTimer = nil; + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void) advanceAnimatedCursor: (NSTimer *) aTimer +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + if ([aTimer isValid]) { + [self setFrame:[self getNextCursorFrame]]; + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (void) setFrame: (int) aFrameIndex +{ + // subclasses need to do something useful here +} + +- (nsCursor) type { + return mType; +} + +- (void) dealloc +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [self destroyTimer]; + [super dealloc]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +@end + +@implementation nsCocoaCursor + +- (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + 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_ABORT_BLOCK_NIL; +} + +- (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + NSArray *frame = [NSArray arrayWithObjects:aCursor, nil]; + return [self initWithFrames:frame type:aType]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (BOOL) isSet +{ + return [NSCursor currentCursor] == mLastSetCocoaCursor; +} + +- (void) setFrame: (int) aFrameIndex +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex]; + [newCursor set]; + mLastSetCocoaCursor = newCursor; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +- (int) numFrames +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + return [mFrames count]; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); +} + +- (NSString *) description +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + return [mFrames description]; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (void) dealloc +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [mFrames release]; + [super dealloc]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +@end |