//
//	MiscThreeStateButtonCell.m -- cell class for the three state button
//		Written by Don Yacktman (c) 1993 by Don Yacktman.
//				Version 1.0.  All rights reserved.
//
//		This notice may not be removed from this source code.
//
//		This is a free object!  Contact the author for the latest version.
//		Don Yacktman, 4279 N. Ivy Lane, Provo, UT, 84604
//		e-mail:  Don_Yacktman@byu.edu
//
//	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 "MiscThreeStateButtonCell.h"

// This would be a hell of a lot easier with NeXT's source code.  Since
// I don't have it, but I want to make sure that the same drawing mechanisms
// are used to render the third state of the button, I do a nasty hack.
// Whenever I want to go to the third state, I fake it and make the button
// think it's in the alternate state (of a normal button) but whenever it
// attempts to get the title or image, I swap in the third state button/image
// before the render and then switch back the true alt image/title upon
// finishing the render.  I use the same trick when the button is being queried
// about it's sizes for drawing areas, etc.  I swap icons and titles in and get
// the size they'd use, then I get the size of the correct icons, and then
// return the MAX of the two.  Without knowing exactly how NeXT does the
// calculations, I have to do a hack like this.  Since I'm essentially re-using
// their code when I take my measurements and do my rendering, that means that
// if it changes in the future, this class will follow the changes and keep
// doing the right thing, whatever that is.  I still think it's ugly, but it is
// a cute trick.

// As usual, ***** denotes an unfinished method.  This class is not at all
// done.  It won't do what it is supposed to do yet, at all!

@implementation MiscThreeStateButtonCell

- init
{
	id ret = [super init];
	thirdTitle = NULL;
	_thirdImage = nil;
	isCyclic = YES;
	thirdState = NO;
	return ret;
}

- copyFromZone:(NXZone *)zone
{ // ***** we may need something here...
	return [super copyFromZone:zone];
}

- free
{
	if (thirdTitle) free(thirdTitle);
//	[_thirdImage free];  // should I do this?
	return [super free];
}

- (const char *)altTitle { return trueAltTitle; }

- setAltTitle:(const char *)aString
{
	if (trueAltTitle) free(trueAltTitle);
	trueAltTitle = NXCopyStringBufferFromZone(aString, [self zone]);
	return self;
}

- (const char *)altIcon { return [_trueAltImage name]; }

- setAltIcon:(const char *)iconName
{
	return [self setAltImage:[NXImage findImageNamed:iconName]];
}

- altImage { return _trueAltImage; }

- setAltImage:image
{
//	[_trueAltImage free];  // should I do this?
	_trueAltImage = image;
	return self;
}

- (const char *)thirdTitle { return thirdTitle; }

- setThirdTitle:(const char *)aString
{
	if (thirdTitle) free(thirdTitle);
	thirdTitle = NXCopyStringBufferFromZone(aString, [self zone]);
	return self;
}

- (const char *)thirdIcon { return [_thirdImage name]; }

- setThirdIcon:(const char *)iconName
{
	return [self setThirdImage:[NXImage findImageNamed:iconName]];
}

- thirdImage { return _thirdImage; }

- setThirdImage:image
{
//	[_thirdImage free];  // should I do this?
	_thirdImage = image;
	return self;
}

- setType:(int)aType
{
	switch (aType) {
		case MISC_CYCLIC_THREE_STATE: isCyclic = YES; break;
		case MISC_PLAIN_THREE_STATE:  isCyclic = NO;  break;
		default: return [super setType:aType];
	}
	return self;
}

- (BOOL)isCyclic { return isCyclic; }

- (const char *)stringValue
{
	if (thirdState) return "alt";
	return [super stringValue];
}

- setStringValue:(const char *)aString
{
	thirdState = NO;
	if (aString) if (strlen(aString)) thirdState = YES;
	return [super setStringValue:aString];
}

- setStringValueNoCopy:(const char *)aString
{
	thirdState = NO;
	if (aString) if (strlen(aString)) thirdState = YES;
	return [super setStringValueNoCopy:aString];
}

- (int)intValue
{
	if (thirdState) return 2;
	return [super intValue];
}

- setIntValue:(int)anInt
{
	if (anInt == 2) thirdState = YES;
	else thirdState = NO;
	return [super setIntValue:anInt];
}

- (float)floatValue
{
	if (thirdState) return 2.0;
	return [super floatValue];
}

- setFloatValue:(float)aFloat
{
	if ((aFloat > 1.0) && (aFloat <= 2.0)) thirdState = YES;
	else thirdState = NO;
	return [super setFloatValue:aFloat];
}

- (double)doubleValue
{
	if (thirdState) return 2.0;
	return [super doubleValue];
}

- setDoubleValue:(double)aDouble
{
	if ((aDouble > 1.0) && (aDouble <= 2.0)) thirdState = YES;
	else thirdState = NO;
	return [super setDoubleValue:aDouble];
}

// Here's where the fun begins with us intercepting and fooling the button.
- getDrawRect:(NXRect *)theRect
{	// perform the super method for both alt and third state, then merge rects
	// Note, this may not really be necessary, since the NeXT implementation
	// may use the two methods below to give the answer to this method.  If
	// that's the case, then this method doesn't need the override, since the
	// two methods below take the third state into account.  If they don't we
	// need this method.  The docs don't say one way or the other, and I don't
	// feel like disassembling the Appkit, so at worst, we'll be inefficient
	// here.  This is a good reason for why NeXT should let us look at source,
	// or make MAJOR improvements in the docs!
	NXRect tempRect  = { { 0.0, 0.0 }, { 0.0, 0.0 } };
	icon.bmap.alternate = _thirdImage;
	[self replaceAltTitle:thirdTitle];
	[super getDrawRect:&tempRect];
	[self replaceAltTitle:trueAltTitle];
	icon.bmap.alternate = _trueAltImage;
	[super getDrawRect:theRect];
	if (thirdState) {
		icon.bmap.alternate = _thirdImage;
		[self replaceAltTitle:thirdTitle];
	}
	NXUnionRect(&tempRect, theRect);
	return self;
}

- getTitleRect:(NXRect *)theRect
{ // perform the super method for both alt and third state, then merge rects
	NXRect tempRect  = { { 0.0, 0.0 }, { 0.0, 0.0 } };
	icon.bmap.alternate = _thirdImage;
	[self replaceAltTitle:thirdTitle];
	[super getTitleRect:&tempRect];
	[self replaceAltTitle:trueAltTitle];
	icon.bmap.alternate = _trueAltImage;
	[super getTitleRect:theRect];
	if (thirdState) {
		icon.bmap.alternate = _thirdImage;
		[self replaceAltTitle:thirdTitle];
	}
	NXUnionRect(&tempRect, theRect);
	return self;
}

- getIconRect:(NXRect *)theRect
{ // perform the super method for both alt and third state, then merge rects
	NXRect tempRect  = { { 0.0, 0.0 }, { 0.0, 0.0 } };
	icon.bmap.alternate = _thirdImage;
	[self replaceAltTitle:thirdTitle];
	[super getIconRect:&tempRect];
	[self replaceAltTitle:trueAltTitle];
	icon.bmap.alternate = _trueAltImage;
	[super getIconRect:theRect];
	if (thirdState) {
		icon.bmap.alternate = _thirdImage;
		[self replaceAltTitle:thirdTitle];
	}
	NXUnionRect(&tempRect, theRect);
	return self;
}


- calcCellSize:(NXSize *)theSize inRect:(const NXRect *)aRect
{
	NXSize tempSize  = { 0.0, 0.0 };
	icon.bmap.alternate = _thirdImage;
	[self replaceAltTitle:thirdTitle];
	[super calcCellSize:&tempSize inRect:aRect];
	[self replaceAltTitle:trueAltTitle];
	icon.bmap.alternate = _trueAltImage;
	[super calcCellSize:theSize inRect:aRect];
	if (thirdState) {
		icon.bmap.alternate = _thirdImage;
		[self replaceAltTitle:thirdTitle];
	}
	theSize->width  = MAX(theSize->width,  tempSize.width);
	theSize->height = MAX(theSize->height, tempSize.height);
	return self;
}

- drawInside:(const NXRect *)aRect inView:controlView
{
	static BOOL calledAlready = NO;
//	BOOL inThird = thirdState;
	id ret;
	if (calledAlready) return [super drawInside:aRect inView:controlView];
	[[controlView window] disableFlushWindow];
	calledAlready = YES;
	if (thirdState) {
		[super setIntValue:1]; // make sure the alt state is on
		icon.bmap.alternate = _thirdImage;
		[self replaceAltTitle:thirdTitle];
	} else {
		[self replaceAltTitle:trueAltTitle];
		icon.bmap.alternate = _trueAltImage;
	}
	[[controlView window] reenableFlushWindow];
	ret = [super drawInside:aRect inView:controlView];
	calledAlready = NO;
	return ret;
}

- highlight:(const NXRect *)cellFrame inView:controlView lit:(BOOL)flag
{
	if (thirdState) {
		[super setIntValue:1]; // make sure the alt state is on
		icon.bmap.alternate = _thirdImage;
		[self replaceAltTitle:thirdTitle];
	} else {
		[self replaceAltTitle:trueAltTitle];
		icon.bmap.alternate = _trueAltImage;
	}
	return [super highlight:cellFrame inView:controlView lit:flag];
}

#define ALTKEY (theEvent->flags & NX_ALTERNATEMASK)

- (BOOL)trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)cellFrame
		ofView:controlView
{
	BOOL ret; int next = 0;
	char *realTitle = NXCopyStringBufferFromZone([self title], [self zone]);
	id realImage = [self image];
	int curState = [self intValue];
	[[controlView window] disableFlushWindow];
	
	switch (curState) { // set up to do proper button display while tracking
		case 0 : if (ALTKEY) {
					icon.bmap.alternate = _thirdImage;
					[self replaceAltTitle:thirdTitle];
				} else {
					[self replaceAltTitle:trueAltTitle];
					icon.bmap.alternate = _trueAltImage;
				} [super setIntValue:0]; break;
		case 1 : if (isCyclic || ALTKEY) {
					[self replaceTitle:trueAltTitle];
					[self replaceAltTitle:thirdTitle];
					icon.bmap.normal = _trueAltImage;
					icon.bmap.alternate = _thirdImage;
					[super setIntValue:0];
				} else {
					[self replaceAltTitle:trueAltTitle];
					icon.bmap.alternate = _trueAltImage;
					[super setIntValue:1];
				} break;
		case 2 : // make sure the alt state is on
				icon.bmap.alternate = _thirdImage;
				[self replaceAltTitle:thirdTitle];
				[super setIntValue:1]; break;
		default: break;
	}
	[[[controlView window] reenableFlushWindow] flushWindow];
	ret = [super trackMouse:theEvent inRect:cellFrame ofView:controlView];
	[[controlView window] disableFlushWindow];
	if (ret) { // change the button to the proper next state
		switch (curState) {
			case 0 : next = 1; break;
			case 1 : if (isCyclic) next = 2;
					else next = 0;
					break;
			case 2 : next = 0; break;
			default : break;
		}
		if (ALTKEY && (curState < 2)) next = 2;
		[self setIntValue:next];
	} else {
		[self setIntValue:curState];
	}
	// restore things...
	[self replaceTitle:realTitle]; icon.bmap.normal = realImage;
	[[[controlView window] reenableFlushWindow] flushWindow];
	[self drawInside:cellFrame inView:controlView]; // is this needed?
	return ret;
}

- replaceTitle:(const char *)aString
{
	if (contents) free(contents);
	contents = NXCopyStringBufferFromZone(aString, [self zone]);
	return self;
}

- replaceAltTitle:(const char *)aString
{
	if (altContents) free(altContents);
	altContents = NXCopyStringBufferFromZone(aString, [self zone]);
	return self;
}

- performClick:sender
{
	if (isCyclic && [self intValue] && !thirdState) {
		thirdState = YES;
		[super setIntValue:0];
	} else thirdState = NO;
	return [super performClick:sender];
}

- performAltClick:sender
{
	thirdState = (thirdState ? NO : YES);
	return [super performClick:sender];
}

- write:(NXTypedStream *)stream
{
	id ret = [super write:stream];
	NXWriteTypes(stream, "**cc", &thirdTitle, &trueAltTitle,
			&isCyclic, &thirdState);
	NXWriteObject(stream, _thirdImage);
	NXWriteObject(stream, _trueAltImage);
	return ret;
}

- read:(NXTypedStream *)stream
{
	id ret = [super read:stream];
	NXReadTypes(stream, "**cc", &thirdTitle, &trueAltTitle,
			&isCyclic, &thirdState);
	_thirdImage = NXReadObject(stream);
	_trueAltImage =  NXReadObject(stream);
	return ret;
}

@end
