// -*- objc -*-

#import <appkit/Application.h>

#import "MiscImageDissolver.h"

void
DoDissolveStep (DPSTimedEntry tag, double now, void *userData)
{
    MiscImageDissolver *dissolver = (MiscImageDissolver*)userData;
    
    [dissolver dissolveStep];
}

@interface MiscImageDissolver ( Internals )

- postInit;

@end


@implementation MiscImageDissolver

/*"
MiscImageDissolver is a View that is able of not only displaying an image
but also of smoothly dissolving from one image into another. The parameters
of the dissolving process are the number of steps and the duration of one
step.

Note that the methods #dissolveToImage: and #dissolveOverImageList: only
start a dissolving process and return immediately. The dissolving
is taken out in the background (by virtue of a timed entry).
"*/

- initFrame: (const NXRect*) rect
/*"
Initializes the MiscImageDissolver object and sets sourceImage to the
image named image1Name (if set), otherwise to the image named "ImageDissolver"
(if available). The number of steps is
set to 20 and the duration of a step is set to 0.05 seconds.

This is the designated initializer for MiscImageDissolver.
"*/
{
    [super initFrame: rect];
    
    numSteps = 20;
    secs = 0.05;

    [self postInit];
    
    return self;
}

- free
/*"
Frees the MiscImageDissolver instance.
"*/
{
    if (currentStep >= 0)
    {
	DPSRemoveTimedEntry(timedEntry);
	if (currentIndex >= 0)
	    [imageList free];
    }

    return [super free];
}

- (int) numSteps
/*"
Returns the number of steps of the dissolving process.
"*/
{
    return numSteps;
}

- setNumSteps: (int) theNumSteps
/*"
Sets the number of steps of the dissolving process to theNumSteps.
"*/
{
    if (![self hasFinished])
	return nil;

    numSteps = theNumSteps;
    
    return self;
}

- (double) stepInterval
/*"
Returns the duration of one dissolving step in seconds.
"*/
{
    return secs;
}

- setStepInterval: (double) theSeconds
/*"
Sets the duration of a dissolving step to theSeconds.
"*/
{
    if (![self hasFinished])
	return nil;

    secs = theSeconds;
    
    return self;
}

- (const char*) image1Name
/*"
Returns image1Name.
"*/
{
    return image1Name;
}

- setImage1Name: (const char*) theName
/*"
Sets image1Name to the string pointed to by theName.
"*/
{
    if (image1Name != 0)
	free(image1Name);

    if (theName == 0)
	theName = "";

    image1Name = NXCopyStringBuffer(theName);

    return self;
}

- (const char*) image2Name
/*"
Returns image2Name.
"*/
{
    return image2Name;
}

- setImage2Name: (const char*) theName
/*"
Sets image2Name to the string pointed to by theName.
"*/
{
    if (image2Name != 0)
	free(image2Name);

    if (theName == 0)
	theName = "";

    image2Name = NXCopyStringBuffer(theName);

    return self;
}

- (NXImage*) image
/*"
Returns sourceImage.
"*/
{
    return sourceImage;
}

- setImage: (NXImage*) theImage
/*"
Sets the image displayed to theImage, if dissolving is not in progress, and returns self.
Otherwise, returns nil.
"*/
{
    if (![self hasFinished])
	return nil;

    sourceImage = theImage;

    [self update];
    
    return self;
}

- dissolveToImage: (NXImage*) theImage
/*"
Starts a dissolving process to theImage, if dissolving is not already in progress,
and returns self. Otherwise, returns nil.
"*/
{
    if (![self hasFinished])
	return nil;

    if (sourceImage == theImage)
	return nil;

    destImage = theImage;
    
    currentStep = 0;
    currentIndex = -1;
    
    timedEntry = DPSAddTimedEntry(secs, DoDissolveStep, (void*)self,  NX_RUNMODALTHRESHOLD);
    
    return self;
}

- dissolveOverImageList: (List*) theList
/*"
Starts a dissolving process over the list theList. theList must contain
pointers to NXImage's. The current image is dissolved to the first element
in the list, which in turn is then dissolved to the second etc.

If everything is in order and the dissolving has been started, this method
returns self. Otherwise, returns nil.

If the methods returns self, you can assume that the list will be freed.
Otherwise, you have to free it yourself.
"*/
{
    int i;

    if (![self hasFinished])
	return nil;

    if (![theList isKindOf: [List class]])
	return nil;

    if ([theList count] < 1)
	return nil;

    for (i = 0; i < [theList count]; ++i)
	if (![[theList objectAt: i] isKindOf: [NXImage class]])
	    return nil;

    imageList = theList;

    destImage = [theList objectAt: 0];

    currentStep = 0;
    currentIndex = 0;

    timedEntry = DPSAddTimedEntry(secs, DoDissolveStep, (void*)self,  NX_RUNMODALTHRESHOLD);

    return self;
}

- dissolveToImage1: sender
/*"
Dissolves to an image named image1Name using the method #dissolveToImage:.

Returns self on success, and nil otherwise.
"*/
{
    NXImage *image;

    if (image1Name == 0)
	return nil;

    image = [NXImage findImageNamed: image1Name];

    if (image == nil)
	return nil;

    return [self dissolveToImage: image];
}

- dissolveToImage2: sender
/*"
Dissolves to an image named image2Name using the method #dissolveToImage:.

Returns self on success, and nil otherwise.
"*/
{
    NXImage *image;

    if (image2Name == 0)
	return nil;

    image = [NXImage findImageNamed: image2Name];

    if (image == nil)
	return nil;

    return [self dissolveToImage: image];
}

- dissolveToOtherImage: sender
/*"
Dissolves to the image named image2Name, if sourceImage is the image
named image1Name. Otherwise dissolves to the image named image1Name.
In other words: If the currently displayed image is either of
image1Name or image2Name, this method dissolves to the other one,
and dissolves to image1Name if the currently displayed image is neither
of the two.

This method uses #dissolveToImage:.

Returns self on success, and nil otherwise.
"*/
{
    NXImage *image;

    if (image1Name == 0 || image2Name == 0)
	return nil;

    image = [NXImage findImageNamed: image2Name];

    if (image == nil || image == sourceImage)
	image = [NXImage findImageNamed: image1Name];

    if (image == nil)
	return nil;

    return [self dissolveToImage: image];
}

- dissolveBackAndForth: sender
/*"
This method dissolves over the two images named image1Name and image2Name.
If the currently displayed image is image1Name, it dissolves first to
image2Name and then back to image1Name, otherwise it dissolves the other
way round.

This method works by calling #dissolveOverImageList:.

Returns self on success, and nil otherwise.
"*/
{
    NXImage *image1,
	*image2;
    List *list;

    if (image1Name == 0 || image2Name == 0)
	return nil;

    image1 = [NXImage findImageNamed: image1Name];
    image2 = [NXImage findImageNamed: image2Name];

    if (image1 == nil || image2 == nil)
	return nil;

    list = [[List alloc] init];

    if (image2 == sourceImage)
    {
	[list addObject: image1];
	[list addObject: image2];
    }
    else
    {
	[list addObject: image2];
	[list addObject: image1];
    }

    if ([self dissolveOverImageList: list] == nil)
    {
	[list free];
	return nil;
    }
    
    return self;
}

- (BOOL) hasFinished
/*"
Returns YES if there is no dissolving in progress. Otherwise, returns NO.
"*/
{
    return currentStep < 0;
}

- drawSelf: (const NXRect*) rects : (int) rectCount
/*"
Draws the MiscImageDissolver object and returns self.
"*/
{
    NXPoint point;
    
    [self lockFocus];

    point.x = 0;
    point.y = 0;
    [sourceImage composite: NX_COPY toPoint: &point];
    
    if (destImage != nil)
	[destImage dissolve: (float)currentStep / (float)numSteps toPoint: &point];

    [self unlockFocus];
    
    return self; 
}

- dissolveStep
/*"
Does one step in the dissolving process. You do not call this method
directly. It is called from the timed entry function instead. Returns self.
"*/
{
    if (++currentStep > numSteps)
    {
	if (currentIndex >= 0)
	{
	    if (++currentIndex >= [imageList count])
	    {
		currentStep = -1;
		currentIndex = -1;
		sourceImage = [imageList lastObject];
		destImage = nil;
		DPSRemoveTimedEntry(timedEntry);
		[imageList free];
		imageList = nil;
	    }
	    else
	    {
		sourceImage = [imageList objectAt: currentIndex - 1];
		destImage = [imageList objectAt: currentIndex];
		currentStep = 1;   // displays image for two ticks
	    }
	}
	else
	{
	    currentStep = -1;
	    sourceImage = destImage;
	    destImage = nil;
	    DPSRemoveTimedEntry(timedEntry);
	}
    }

    [self update];

    return self;
}

- read: (NXTypedStream*) theStream
/*"
Reads the MiscImageDissolver object in from the typed stream theStream.
Returns self.
"*/
{
    [super read: theStream];

    NXReadTypes(theStream, "id**", &numSteps, &secs, &image1Name, &image2Name);

    [self postInit];

    return self;
}

- write: (NXTypedStream*) theStream
/*"
Writes the MiscImageDissolver object to the typed stream theStream.
Returns self.
"*/
{
    [super write: theStream];

    NXWriteTypes(theStream, "id**", &numSteps, &secs, &image1Name,
		 &image2Name);

    return self;
}

@end

@implementation MiscImageDissolver ( InterfaceBuilder )

- (const char*) getInspectorClassName
{
    return "MiscImageDissolverInspector";
}

@end

@implementation MiscImageDissolver ( Internals )

- postInit
{
    char *imageName = "ImageDissolver";

    if (image1Name != 0)
	imageName = image1Name;

    sourceImage = [NXImage findImageNamed: imageName];
    destImage = nil;

    currentStep = -1;

    return self;
}

@end
