gecko-dev/widget/cocoa/nsMacCursor.mm

368 строки
12 KiB
Plaintext

/* 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("res"_ns);
resDir->AppendNative("cursors"_ns);
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