//
//	MiscAppIconAnimator.m -- This is an Object to animate the icon
//				for an application.  This Object will allow
//				similiar activity to NeXTMail when new mail arrives.
//		Written by Craig Laurent Copyright 1995 by Craig Laurent (cdl@is.com).
//				Version 1.1.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//	This object is included in the MiscKit by permission from the author
//	and its use is governed by the MiscKit license, found in the file
//	"License.rtf" in the MiscKit distribution.  Please refer to that file
//	for a list of all applicable permissions and restrictions.
//	
#import "MiscAppIconAnimator.h"
//#import <misckit/MiscAppIconAnimator.h> 

@implementation MiscAppIconAnimator

- init
{
	return [self initWithImageArray:[NSMutableArray array] animationPattern:[NSMutableArray array] andPatternLoops:0];
}

//** designated initializer
- initWithImageArray:(NSArray*)anArray animationPattern:(NSArray*)patternArray andPatternLoops:(int)loops
{
	if (self = [super init]) {
		//** set default value for instance variables
		appTile = nil;
		imageArray = nil;
		animationPattern = nil;
		patternLoops = 0;
		timeInterval = 0.2;
		indexOfStopIcon = 0;
		indexOfEndIcon = 1;
		timedEntry = nil;
		patternIterator = 0;
		loopCount = 0;
		patternEnd = 0;
		offset = 0;

		//** set value for instance variables
		[self setImageArray:anArray];
		[self setAnimationPattern:patternArray];
		[self setPatternLoops:loops];
		
		return self;
	}
	return nil;
}


- (void)dealloc
{
    [imageArray autorelease];
    [animationPattern autorelease];
	[super dealloc];
}


//** instance methods
- (NSArray*)imageArray
{ return imageArray; }
- (void)setImageArray:(NSArray*)anArray
{
	[imageArray autorelease];
	imageArray = [anArray retain];
}

- (NSArray*)animationPattern
{ return animationPattern; }
- (void)setAnimationPattern:(NSArray*)anArray
{
	[animationPattern autorelease];
	animationPattern = [anArray retain];

	patternEnd = [animationPattern count];
}

- (int)patternLoops
{	return patternLoops; }
- (void)setPatternLoops:(int)numLoops
{	patternLoops = numLoops; }

- (float)timeInterval
{	return timeInterval; }
- (void)setTimeInterval:(float)interval
{	timeInterval = interval; }

- (int)indexOfEndIcon
{	return indexOfEndIcon; }
- (void)setIndexOfEndIcon:(int)endIcon
{	indexOfEndIcon = endIcon; }

- (int)indexOfStopIcon
{	return indexOfStopIcon; }
- (void)setIndexOfStopIcon:(int)stopIcon
{	indexOfEndIcon = stopIcon; }


//** methods to change Animation
/* startAnimation - starts running the animation sequence */
- (void)startAnimation:sender
{
//  if none exists, add a timed entry to begin the animation sequence...
	
	if (!timedEntry) {
	{
            timedEntry = [[NSTimer timerWithTimeInterval:[self timeInterval] target:self selector:@selector(animateIcon:) userInfo:(void*)nil repeats:YES] retain];
	[[NSRunLoop currentRunLoop] addTimer:timedEntry forMode:NSDefaultRunLoopMode];
	[[NSRunLoop currentRunLoop] addTimer:timedEntry forMode:NSModalPanelRunLoopMode];
	[[NSRunLoop currentRunLoop] addTimer:timedEntry forMode:NSEventTrackingRunLoopMode];
	}
	}
}

/* stopAnimation - stops the animation sequence and sets the application icon to a standard image */
- (void)stopAnimation
{
//  Indicate Animation stopped by setting image to image one.  Do necessary housekeeping.
	
	[self removeTimedEntry];
	if ([[self imageArray] count]) {
		[self displayImage: (NSImage*)[[self imageArray] objectAtIndex: [self indexOfStopIcon]]];
	}
	patternIterator = 0;
	loopCount = 0;
}


//** Internal methods
/* removeTimedEntry - removes the timedEntry if it is running */
- (void)removeTimedEntry
{
	if (timedEntry) {
		[timedEntry invalidate]; [timedEntry release];;
		timedEntry = nil;
	}
}

/* cornerPointForImage: -  calculates and returns the point that should be used as the corner for this image.  This is used to make sure the image is centered on the application icon. */
- (NSPoint)cornerPointForImage:(NSImage*)anImage
{
//*  You must center the composited image within
//*   the contentView of the appIcon window.
	NSSize	appTileSize;
	NSSize	imageSize;
	NSPoint	cornerPoint = { 0.0, 0.0 };

	appTileSize = [appTile size];
	imageSize = [anImage size];
	
	if ( imageSize.width < appTileSize.width ) 
		cornerPoint.x = (appTileSize.width - imageSize.width ) / 2.0;
	
	if ( imageSize.height < appTileSize.height ) 
		cornerPoint.y = (appTileSize.height - imageSize.height ) / 2.0;
		
	return cornerPoint;
}

/* displayImage: - displays the given image into the application icon. */
- (void)displayImage:(NSImage*)anImage
{
//*  NXAppTile is composited first at 0,0 of the icon window's content view 
//*  (this is required in order to maintain the NeXT icon look).
//*  'anImage' is then composited at center (centering is also a requirement).

	NSPoint	cornerPoint;

	//** get application image every time
        appTile = [[NSApp applicationIconImage] copy]; // we really want a blank here...
	cornerPoint = [self cornerPointForImage:anImage];

        [appTile lockFocus];
	[anImage compositeToPoint:cornerPoint operation:NSCompositeSourceOver];
        [appTile unlockFocus];
        [NSApp setApplicationIconImage:appTile];
}


/* displayImageAtIndex: - displays the image from the array, at the specified index into the application icon. */
- (void)displayImageAtIndex:(int)imageNum;
{
	if (imageNum < [[self imageArray] count])
		[self displayImage:[[self imageArray] objectAtIndex:imageNum]];
}

/* animateIcon - called every time a timedEntry executes.  This contains logic for the icon animation (image sequencing).  The images contained in imageArray are used.  The animationPattern provides an index into that list, giving us the right image at the right time. */
- (void)animateIcon:(NSTimer *)theTimer
{
	[self displayImage: (NSImage*)[[self imageArray] objectAtIndex:[[[self animationPattern] objectAtIndex:patternIterator++] intValue]]];

	if (patternIterator == (patternEnd - offset)) {
		[self removeTimedEntry];
		patternIterator = 0;
		loopCount++;
		if (loopCount < [self patternLoops]) {
			offset = 1;
//** the following only works for Object, not NSObject
//			[self perform: @selector(startAnimation:) with:nil afterDelay:500 cancelPrevious:YES];
			[self performSelector: @selector(startAnimation:) withObject:nil];
		} else {
			loopCount = offset = 0;
			[self displayImage:(NSImage*)[imageArray objectAtIndex: [self indexOfEndIcon]]];
		}			
	}
}





//****************************************
//** Distributed Object/NSCoding encoding methods
/* encodeRemotelyFor:.. -  Distributed Object method to indicate what should be passed, The object or a copy.  NSObject won't be passed as a copy. */
- replacementObjectForPortCoder:(NSPortCoder *)aPortCoder
{
	if ([aPortCoder isBycopy])
		return self;	//** return copy of object, not a proxy
//	return nil;	//** NSObject doesn't encode for DO.
    return [super replacementObjectForPortCoder:aPortCoder];
}

/* encodeUsing: -  Distributed Object method to encode the data for transfer */
- (void)encodeWithCoder:(NSCoder *)aPortCoder
{
	[aPortCoder encodeValueOfObjCType:"@" at:&animationPattern];
	[aPortCoder encodeValueOfObjCType:"@" at:&imageArray];
	[aPortCoder encodeValueOfObjCType:"i" at:&patternLoops];
	[aPortCoder encodeValueOfObjCType:"f" at:&timeInterval];
	[aPortCoder encodeValueOfObjCType:"i" at:&indexOfEndIcon];
	[aPortCoder encodeValueOfObjCType:"i" at:&indexOfStopIcon]; 
}

/* decodeUsing: -  Distributed Object method to decode the data after transfer */
- (id)initWithCoder:(NSCoder *)aPortCoder
{
	self = [[self init] autorelease];

	//** decode data
	[aPortCoder decodeValueOfObjCType:"@" at:&animationPattern];
	patternEnd = [animationPattern count];	// done in set method
	[aPortCoder decodeValueOfObjCType:"@" at:&imageArray];
	[aPortCoder decodeValueOfObjCType:"i" at:&patternLoops];
	[aPortCoder decodeValueOfObjCType:"f" at:&timeInterval];
	[aPortCoder decodeValueOfObjCType:"i" at:&indexOfEndIcon];
	[aPortCoder decodeValueOfObjCType:"i" at:&indexOfStopIcon];

	//** retain data
	[animationPattern retain];
	[imageArray retain];

        //** init vars
        loopCount = 0;
        offset = 0;
        patternIterator = 0;
        timedEntry = nil;

	return self;
}


@end
