/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Camino Cursor code.
*
* The Initial Developer of the Original Code is
* Andrew Thompson.
* Portions created by the Andrew Thompson are Copyright (C) 2004
* Andrew Thompson. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#import "nsMacCursor.h"
#import "nsMacResources.h"
#import "nsDebug.h"
/*! @category nsMacCursor (PrivateMethods)
@abstract Private methods internal to the nsMacCursor class.
@discussion nsMacCursor 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 NSTimer 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 NSTimer instance associated with this cursor.
*/
- (void) destroyTimer;
/*! @method destroyTimer
@abstract Destroy any timer instance associated with this cursor.
@discussion Invalidates and releases any NSTimer instance associated with this cursor.
*/
/*! @method spinCursor:
@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) spinCursor: (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 0 to -[nsMacCursor numFrames] - 1. 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 nsThemeCursor
@abstract Implementation of nsMacCursor that uses Carbon Appearance Manager cursors.
@discussion Displays a static or animated ThemeCursor using Carbon Appearance Manager functions.
Understands how many frames exist in each of the built-in ThemeCursors.
*/
@interface nsThemeCursor : nsMacCursor
{
@private
ThemeCursor mCursor;
}
/*! @method initWithThemeCursor:
@abstract Create a cursor by specifying a Carbon Apperance Manager ThemeCursor constant.
@discussion Creates a cursor representing the given Appearance Manager built in cursor.
@param aCursor the ThemeCursor to use
@result an instance of nsThemeCursor representing the given ThemeCursor
*/
- (id) initWithThemeCursor: (ThemeCursor) aCursor;
@end
/*! @class nsCocoaCursor
@abstract Implementation of nsMacCursor that uses Cocoa NSCursor instances.
@discussion Displays a static or animated cursor, using Cocoa NSCursor instances. These can be either
built-in NSCursor instances, or custom NSCursors created from images.
When more than one NSCursor is provided, the cursor will use these as animation frames.
*/
@interface nsCocoaCursor : nsMacCursor
{
@private
NSArray *mFrames;
}
/*! @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 NSCursor
@param aCursorFrames an array of NSCursor, representing the frames of an animated cursor, in the
order they should be played.
@result an instance of nsCocoaCursor that will animate the given cursor frames
*/
- (id) initWithFrames: (NSArray *) aCursorFrames;
/*! @method initWithCursor:
@abstract Create a cursor by specifying a Cocoa NSCursor.
@discussion Creates a cursor representing the given Cocoa built-in cursor.
@param aCursor the NSCursor to use
@result an instance of nsCocoaCursor representing the given NSCursor
*/
- (id) initWithCursor: (NSCursor *) aCursor;
/*! @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 +[NSImage imageNamed:] method.
The image must be compatible with any restrictions laid down by NSCursor. These vary
by operating system version. eg, Jaguar has a smaller maximum size than Panther.
The hotspot precisely determines the point where the user clicks when using the cursor.
@param aCursor the name of the image to use for the cursor @param aPoint the point within the cursor to use as the hotspot @result an instance ofnsCocoaCursor that uses the given image and hotspot
*/
- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint;
@end
/*! @class nsResourceCursor
@abstract Implementation of nsMacCursor that uses Carbon CURS resources.
@discussion Displays a static or animated cursor, using Carbon CURS resources.
Animated cursors are produced by cycling through a range of cursor resource ids.
The frames are loaded from the compiled version of the resource file nsMacWidget.r.
*/ @interface nsResourceCursor : nsMacCursor { @private int mFirstFrame; int mLastFrame; } /*! @method initWithResources:lastFrame: @abstract Create an animated cursor by specifying a range ofCURS resources to load and animate.
@discussion Creates a cursor that will animate by cycling through the given range of cursor resource ids. Each
resource in the range must be the next frame in the animation.
To create a static cursor, simply pass the same resource id for both parameters.
The frames are loaded from the compiled version of the resource file nsMacWidget.r.
@param aFirstFrame the resource id for the first frame of the animation. Must be 128 or greated @param aLastFrame the resource id for the last frame of the animation. Must be 128 or greater, and greater than or equal toaFirstFrame
@result an instance of nsResourceCursor that will animate the given cursor resources
*/
- (id) initWithFirstFrame: (int) aFirstFrame lastFrame: (int) aLastFrame;
@end
@implementation nsMacCursor
+ (nsMacCursor *) cursorWithThemeCursor: (ThemeCursor) aCursor
{
return [[[nsThemeCursor alloc] initWithThemeCursor: aCursor] autorelease];
}
+ (nsMacCursor *) cursorWithResources: (int) aFirstFrame lastFrame: (int) aLastFrame
{
return [[[nsResourceCursor alloc] initWithFirstFrame: aFirstFrame lastFrame: aLastFrame] autorelease];
}
+ (nsMacCursor *) cursorWithCursor: (NSCursor *) aCursor
{
return [[[nsCocoaCursor alloc] initWithCursor: aCursor] autorelease];
}
+ (nsMacCursor *) cursorWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint
{
return [[[nsCocoaCursor alloc] initWithImageNamed: aCursorImage hotSpot: aPoint] autorelease];
}
+ (nsMacCursor *) cursorWithFrames: (NSArray *) aCursorFrames
{
return [[[nsCocoaCursor alloc] initWithFrames: aCursorFrames] autorelease];
}
- (void) set
{
if ( [self isAnimated])
{
[self createTimer];
}
//if the cursor isn't animated or the timer creation fails for any reason...
if (mTimer == nil)
{
[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
{
if ( mTimer == nil)
{
mTimer = [[NSTimer scheduledTimerWithTimeInterval: 0.25
target: self
selector: @selector(spinCursor:)
userInfo: nil
repeats: YES] retain];
}
}
- (void) destroyTimer
{
if ( mTimer != nil)
{
[mTimer invalidate];
[mTimer release];
mTimer = nil;
}
}
- (void) spinCursor: (NSTimer *) aTimer
{
if ( [aTimer isValid] )
{
[self setFrame: [self getNextCursorFrame]];
}
}
- (void) setFrame: (int) aFrameIndex
{
//subclasses need to do something useful here
}
- (void) dealloc
{
[self destroyTimer];
[super dealloc];
}
@end
@implementation nsThemeCursor
- (id) initWithThemeCursor: (ThemeCursor) aCursor
{
self = [super init];
//Appearance Manager cursors all fall into the range 0..127. Custom application CURS resources begin at id 128.
NS_ASSERTION(mCursor >= 0 && mCursor < 128, "Theme cursors must be in the range 0 <= num < 128");
mCursor = aCursor;
return self;
}
- (void) setFrame: (int) aFrameIndex
{
if ( [self isAnimated] )
{
//if the cursor is animated try to draw the appropriate frame
OSStatus err = ::SetAnimatedThemeCursor(mCursor, aFrameIndex);
if ( err != noErr )
{
//in the event of any kind of problem, just try to show the first frame
::SetThemeCursor(mCursor);
}
}
else
{
::SetThemeCursor(mCursor);
}
}
- (int) numFrames
{
//These don't appear to be documented. Trial and Error...
switch (mCursor)
{
case kThemeWatchCursor:
case kThemeSpinningCursor:
return 8;
default:
return 1;
}
}
@end
@implementation nsCocoaCursor
- (id) initWithFrames: (NSArray *) aCursorFrames
{
self = [super init];
NSEnumerator *it = [aCursorFrames objectEnumerator];
NSObject *frame = nil;
while ((frame = [it nextObject]) != nil)
{
NS_ASSERTION([frame isKindOfClass: [NSCursor class]], "Invalid argument: All frames must be of type NSCursor");
}
mFrames = [aCursorFrames retain];
mFrameCounter = 0;
return self;
}
- (id) initWithCursor: (NSCursor *) aCursor
{
NSArray *frame = [NSArray arrayWithObjects: aCursor, nil];
return [self initWithFrames: frame];
}
- (id) initWithImageNamed: (NSString *) aCursorImage hotSpot: (NSPoint) aPoint
{
return [self initWithCursor: [[NSCursor alloc] initWithImage: [NSImage imageNamed: aCursorImage] hotSpot: aPoint]];
}
- (void) setFrame: (int) aFrameIndex
{
[[mFrames objectAtIndex: aFrameIndex] performSelectorOnMainThread: @selector(set) withObject: nil waitUntilDone: NO];
}
- (int) numFrames
{
return [mFrames count];
}
- (NSString *) description
{
return [mFrames description];
}
- (void) dealloc
{
[mFrames release];
[super dealloc];
}
@end
@implementation nsResourceCursor
-(id) initWithFirstFrame: (int) aFirstFrame lastFrame: (int) aLastFrame
{
self= [super init];
//Appearance Manager cursors all fall into the range 0..127. Custom application CURS resources begin at id 128.
NS_ASSERTION(aFirstFrame >= 128 && aLastFrame >= 128 && aLastFrame >= aFirstFrame, "Nonsensical frame indicies");
mFirstFrame = aFirstFrame;
mLastFrame = aLastFrame;
return self;
}
- (void) setFrame: (int) aFrameIndex
{
nsMacResources::OpenLocalResourceFile();
CursHandle cursHandle = ::GetCursor(mFirstFrame + aFrameIndex);
NS_ASSERTION(cursHandle, "Can't load cursor, is the resource file installed correctly?");
if (cursHandle)
{
::SetCursor(*cursHandle);
}
nsMacResources::CloseLocalResourceFile();
}
- (int) numFrames
{
return (mLastFrame - mFirstFrame) + 1;
}
@end