michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsMacCursor.h" michael@0: #include "nsObjCExceptions.h" michael@0: #include "nsDebug.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIFile.h" michael@0: #include "nsString.h" michael@0: michael@0: /*! @category nsMacCursor (PrivateMethods) michael@0: @abstract Private methods internal to the nsMacCursor class. michael@0: @discussion nsMacCursor is effectively an abstract class. It does not define complete michael@0: behaviour in and of itself, the subclasses defined in this file provide the useful implementations. michael@0: */ michael@0: @interface nsMacCursor (PrivateMethods) michael@0: michael@0: /*! @method getNextCursorFrame michael@0: @abstract get the index of the next cursor frame to display. michael@0: @discussion Increments and returns the frame counter of an animated cursor. michael@0: @result The index of the next frame to display in the cursor animation michael@0: */ michael@0: - (int) getNextCursorFrame; michael@0: michael@0: /*! @method numFrames michael@0: @abstract Query the number of frames in this cursor's animation. michael@0: @discussion Returns the number of frames in this cursor's animation. Static cursors return 1. michael@0: */ michael@0: - (int) numFrames; michael@0: michael@0: /*! @method createTimer michael@0: @abstract Create a Timer to use to animate the cursor. michael@0: @discussion Creates an instance of NSTimer which is used to drive the cursor animation. michael@0: This method should only be called for cursors that are animated. michael@0: */ michael@0: - (void) createTimer; michael@0: michael@0: /*! @method destroyTimer michael@0: @abstract Destroy any timer instance associated with this cursor. michael@0: @discussion Invalidates and releases any NSTimer instance associated with this cursor. michael@0: */ michael@0: - (void) destroyTimer; michael@0: /*! @method destroyTimer michael@0: @abstract Destroy any timer instance associated with this cursor. michael@0: @discussion Invalidates and releases any NSTimer instance associated with this cursor. michael@0: */ michael@0: michael@0: /*! @method advanceAnimatedCursor: michael@0: @abstract Method called by animation timer to perform animation. michael@0: @discussion Called by an animated cursor's associated timer to advance the animation to the next frame. michael@0: Determines which frame should occur next and sets the cursor to that frame. michael@0: @param aTimer the timer causing the animation michael@0: */ michael@0: - (void) advanceAnimatedCursor: (NSTimer *) aTimer; michael@0: michael@0: /*! @method setFrame: michael@0: @abstract Sets the current cursor, using an index to determine which frame in the animation to display. michael@0: @discussion Sets the current cursor. The frame index determines which frame is shown if the cursor is animated. michael@0: Frames and numbered from 0 to -[nsMacCursor numFrames] - 1. A static cursor michael@0: has a single frame, numbered 0. michael@0: @param aFrameIndex the index indicating which frame from the animation to display michael@0: */ michael@0: - (void) setFrame: (int) aFrameIndex; michael@0: michael@0: @end michael@0: michael@0: /*! @class nsCocoaCursor michael@0: @abstract Implementation of nsMacCursor that uses Cocoa NSCursor instances. michael@0: @discussion Displays a static or animated cursor, using Cocoa NSCursor instances. These can be either michael@0: built-in NSCursor instances, or custom NSCursors created from images. michael@0: When more than one NSCursor is provided, the cursor will use these as animation frames. michael@0: */ michael@0: @interface nsCocoaCursor : nsMacCursor michael@0: { michael@0: @private michael@0: NSArray *mFrames; michael@0: NSCursor *mLastSetCocoaCursor; michael@0: } michael@0: michael@0: /*! @method initWithFrames: michael@0: @abstract Create an animated cursor by specifying the frames to use for the animation. michael@0: @discussion Creates a cursor that will animate by cycling through the given frames. Each element of the array michael@0: must be an instance of NSCursor michael@0: @param aCursorFrames an array of NSCursor, representing the frames of an animated cursor, in the michael@0: order they should be played. michael@0: @param aType the corresponding nsCursor constant michael@0: @result an instance of nsCocoaCursor that will animate the given cursor frames michael@0: */ michael@0: - (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType; michael@0: michael@0: /*! @method initWithCursor: michael@0: @abstract Create a cursor by specifying a Cocoa NSCursor. michael@0: @discussion Creates a cursor representing the given Cocoa built-in cursor. michael@0: @param aCursor the NSCursor to use michael@0: @param aType the corresponding nsCursor constant michael@0: @result an instance of nsCocoaCursor representing the given NSCursor michael@0: */ michael@0: - (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType; michael@0: michael@0: /*! @method initWithImageNamed:hotSpot: michael@0: @abstract Create a cursor by specifying the name of an image resource to use for the cursor and a hotspot. michael@0: @discussion Creates a cursor by loading the named image using the +[NSImage imageNamed:] method. michael@0:

The image must be compatible with any restrictions laid down by NSCursor. These vary michael@0: by operating system version.

michael@0:

The hotspot precisely determines the point where the user clicks when using the cursor.

michael@0: @param aCursor the name of the image to use for the cursor michael@0: @param aPoint the point within the cursor to use as the hotspot michael@0: @param aType the corresponding nsCursor constant michael@0: @result an instance of nsCocoaCursor that uses the given image and hotspot michael@0: */ michael@0: - (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType; michael@0: michael@0: @end michael@0: michael@0: @implementation nsMacCursor michael@0: michael@0: + (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor type: (nsCursor) aType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [[[nsCocoaCursor alloc] initWithCursor:aCursor type:aType] autorelease]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: + (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [[[nsCocoaCursor alloc] initWithImageNamed:aCursorImage hotSpot:aPoint type:aType] autorelease]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: + (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [[[nsCocoaCursor alloc] initWithFrames:aCursorFrames type:aType] autorelease]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: + (NSCursor *) cocoaCursorWithImageNamed: (NSString *) imageName hotSpot: (NSPoint) aPoint michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: nsCOMPtr resDir; michael@0: nsAutoCString resPath; michael@0: NSString* pathToImage, *pathToHiDpiImage; michael@0: NSImage* cursorImage, *hiDpiCursorImage; michael@0: michael@0: nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(resDir)); michael@0: if (NS_FAILED(rv)) michael@0: goto INIT_FAILURE; michael@0: resDir->AppendNative(NS_LITERAL_CSTRING("res")); michael@0: resDir->AppendNative(NS_LITERAL_CSTRING("cursors")); michael@0: michael@0: rv = resDir->GetNativePath(resPath); michael@0: if (NS_FAILED(rv)) michael@0: goto INIT_FAILURE; michael@0: michael@0: pathToImage = [NSString stringWithUTF8String:(const char*)resPath.get()]; michael@0: if (!pathToImage) michael@0: goto INIT_FAILURE; michael@0: pathToImage = [pathToImage stringByAppendingPathComponent:imageName]; michael@0: pathToHiDpiImage = [pathToImage stringByAppendingString:@"@2x"]; michael@0: // Add same extension to both image paths. michael@0: pathToImage = [pathToImage stringByAppendingPathExtension:@"png"]; michael@0: pathToHiDpiImage = [pathToHiDpiImage stringByAppendingPathExtension:@"png"]; michael@0: michael@0: cursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToImage] autorelease]; michael@0: if (!cursorImage) michael@0: goto INIT_FAILURE; michael@0: michael@0: // Note 1: There are a few different ways to get a hidpi image via michael@0: // initWithContentsOfFile. We let the OS handle this here: when the michael@0: // file basename ends in "@2x", it will be displayed at native resolution michael@0: // instead of being pixel-doubled. See bug 784909 comment 7 for alternates ways. michael@0: // michael@0: // Note 2: The OS is picky, and will ignore the hidpi representation michael@0: // unless it is exactly twice the size of the lowdpi image. michael@0: hiDpiCursorImage = [[[NSImage alloc] initWithContentsOfFile:pathToHiDpiImage] autorelease]; michael@0: if (hiDpiCursorImage) { michael@0: NSImageRep *imageRep = [[hiDpiCursorImage representations] objectAtIndex:0]; michael@0: [cursorImage addRepresentation: imageRep]; michael@0: } michael@0: return [[[NSCursor alloc] initWithImage:cursorImage hotSpot:aPoint] autorelease]; michael@0: michael@0: INIT_FAILURE: michael@0: NS_WARNING("Problem getting path to cursor image file!"); michael@0: [self release]; michael@0: return nil; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (BOOL) isSet michael@0: { michael@0: // implemented by subclasses michael@0: return NO; michael@0: } michael@0: michael@0: - (void) set michael@0: { michael@0: if ([self isAnimated]) { michael@0: [self createTimer]; michael@0: } michael@0: // if the cursor isn't animated or the timer creation fails for any reason... michael@0: if (!mTimer) { michael@0: [self setFrame:0]; michael@0: } michael@0: } michael@0: michael@0: - (void) unset michael@0: { michael@0: [self destroyTimer]; michael@0: } michael@0: michael@0: - (BOOL) isAnimated michael@0: { michael@0: return [self numFrames] > 1; michael@0: } michael@0: michael@0: - (int) numFrames michael@0: { michael@0: // subclasses need to override this to support animation michael@0: return 1; michael@0: } michael@0: michael@0: - (int) getNextCursorFrame michael@0: { michael@0: mFrameCounter = (mFrameCounter + 1) % [self numFrames]; michael@0: return mFrameCounter; michael@0: } michael@0: michael@0: - (void) createTimer michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (!mTimer) { michael@0: mTimer = [[NSTimer scheduledTimerWithTimeInterval:0.25 michael@0: target:self michael@0: selector:@selector(advanceAnimatedCursor:) michael@0: userInfo:nil michael@0: repeats:YES] retain]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void) destroyTimer michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if (mTimer) { michael@0: [mTimer invalidate]; michael@0: [mTimer release]; michael@0: mTimer = nil; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void) advanceAnimatedCursor: (NSTimer *) aTimer michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: if ([aTimer isValid]) { michael@0: [self setFrame:[self getNextCursorFrame]]; michael@0: } michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (void) setFrame: (int) aFrameIndex michael@0: { michael@0: // subclasses need to do something useful here michael@0: } michael@0: michael@0: - (nsCursor) type { michael@0: return mType; michael@0: } michael@0: michael@0: - (void) dealloc michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [self destroyTimer]; michael@0: [super dealloc]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: @end michael@0: michael@0: @implementation nsCocoaCursor michael@0: michael@0: - (id) initWithFrames: (NSArray *) aCursorFrames type: (nsCursor) aType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: self = [super init]; michael@0: NSEnumerator *it = [aCursorFrames objectEnumerator]; michael@0: NSObject *frame = nil; michael@0: while ((frame = [it nextObject])) { michael@0: NS_ASSERTION([frame isKindOfClass:[NSCursor class]], "Invalid argument: All frames must be of type NSCursor"); michael@0: } michael@0: mFrames = [aCursorFrames retain]; michael@0: mFrameCounter = 0; michael@0: mType = aType; michael@0: return self; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (id) initWithCursor: (NSCursor *) aCursor type: (nsCursor) aType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: NSArray *frame = [NSArray arrayWithObjects:aCursor, nil]; michael@0: return [self initWithFrames:frame type:aType]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint type: (nsCursor) aType michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [self initWithCursor:[nsMacCursor cocoaCursorWithImageNamed:aCursorImage hotSpot:aPoint] type:aType]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (BOOL) isSet michael@0: { michael@0: return [NSCursor currentCursor] == mLastSetCocoaCursor; michael@0: } michael@0: michael@0: - (void) setFrame: (int) aFrameIndex michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: NSCursor* newCursor = [mFrames objectAtIndex:aFrameIndex]; michael@0: [newCursor set]; michael@0: mLastSetCocoaCursor = newCursor; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: - (int) numFrames michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; michael@0: michael@0: return [mFrames count]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); michael@0: } michael@0: michael@0: - (NSString *) description michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; michael@0: michael@0: return [mFrames description]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK_NIL; michael@0: } michael@0: michael@0: - (void) dealloc michael@0: { michael@0: NS_OBJC_BEGIN_TRY_ABORT_BLOCK; michael@0: michael@0: [mFrames release]; michael@0: [super dealloc]; michael@0: michael@0: NS_OBJC_END_TRY_ABORT_BLOCK; michael@0: } michael@0: michael@0: @end