//
//	MiscCircularSliderCell.m -- a SliderCell for circular sliders
//		Written by Vince DeMarco and Don Yacktman, 
//		Copyright (c) 1994 by Vince DeMarco and Don Yacktman.
//				Version 1.0  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.
//	

// This was created by modifying Vince's CircularSliderCell class and
// merging it with Don's RotationSliderCell class.  Here's the info
// from the original files.
/*
 *    Filename:	CircularSliderCell.m 
 *    Created :	Sun Oct 18 16:56:38 1992 
 *    Author  :	Vince DeMarco
 *		<vince@whatnxt.cuc.ab.ca>
 *    LastEditDate was "Wed Feb  3 17:30:28 1993"
 */
//	RotationSliderCell.m -- written by Don Yacktman
//	Copyright 1993 by Don Yacktman.  All rights reserved.

#import "MiscCircularSliderCell.h"
#import "MiscCSWraps.h"
#import <math.h>
#import <appkit/appkit.h>

@interface MiscCircularSliderCell(private)
- _cacheImages;
@end

#define _KNOB_WIDTH_FUDGE (5.0 / 8.0)
#define _KNOB_Y_FUDGE (- 1.0)
#define _KNOB_X_FUDGE (  0.0)
#define _BEZELS_FUDGE (  4.0)
@implementation MiscCircularSliderCell

inline static float angle(float x, float y)
{ // this is a little cleaner than Vince's original routine.  :-)
	double result = atan2(y, x) * (180 / M_PI);
	if (result < 0) result += 360.0; // keep in 0-360 degree range
	return result;
}

inline static void xycoord(float angle, float radius,float *x, float *y)
{ // the inverse of the above; given angle and radius, determines x & y
    *x = radius * (float)cos((M_PI / 180) * angle);
    *y = radius * (float)sin((M_PI / 180) * angle);
}

// Convert between a 0-360 degree angle and the slider's position.  The
// angle is used as the parameter to pass to the pswrap that does the drawing.
// These two #defines are inverses of each other.
#define currentangle(pos, min, max) (360.0 * ((pos) - (min)) / ((max) - (min)))
#define position(angle, min, max) ((angle) * ((max) - (min)) / 360.0 + (min))

- init
{
    self = [super init];
    minValue = 0.0;
	value = 45.0;
    maxValue = 360.0;
	radius = 50.0; sliderPathWidth = 10.0; 
	center.x = radius;
	center.y = radius;
	style = MISC_SLIDER_STYLE;
	jumpToMousePoint = YES;
    hidden = NO;
	[self _cacheImages];
    return self;
}

- awake
{
	id ret = [super awake];
	[self _cacheImages];
	return ret;
}

#define _SETUPIMAGE PSsetgray(0.0); PSsetalpha(0.0); \
		NXRectFill(&frame); PSsetalpha(1.0)

- _cacheImages
{	// builds the cached slider, etc., if necessary.  (ie, size changed
	// or no cache created yet... *****
	NXSize bezelSize = { radius * 2, radius * 2 }; NXPoint t;
	NXRect frame = { { 0.0, 0.0 }, { center.x * 2, center.y * 2 } };
	NXRect knobRect = { { center.x - sliderPathWidth / 4 + 1,
			center.y - sliderPathWidth / 4 - 2 },
			{ sliderPathWidth * 1.5 + 1, sliderPathWidth * 1.5 + 2 } };

	xycoord(45.0, radius - (sliderPathWidth + 2), &(t.x), &(t.y));
	NX_X(&knobRect) += t.x; NX_Y(&knobRect) += t.y;

	if (disabledBezelImage) [disabledBezelImage free];
	disabledBezelImage = [[NXImage alloc] initSize:&bezelSize];
	[disabledBezelImage lockFocus];
	PSgsave(); _SETUPIMAGE;
	MiscCSDrawBezel(0, 0, radius, sliderPathWidth, 0.666);
	PSgrestore();
	[disabledBezelImage unlockFocus];

	if (enabledBezelImage) [enabledBezelImage free];
	enabledBezelImage = [[NXImage alloc] initSize:&bezelSize];
	[enabledBezelImage lockFocus];
	PSgsave(); _SETUPIMAGE;
	MiscCSDrawBezel(0, 0, radius, sliderPathWidth, 0.5);
	PSgrestore();
	[enabledBezelImage unlockFocus];

	if (tempImage) [tempImage free];
	tempImage = [[NXImage alloc] initSize:&bezelSize];
	[tempImage lockFocus];
	PSgsave(); _SETUPIMAGE;
	MiscCSDrawKnob(0, 0, radius, sliderPathWidth, 45);
	PSgrestore();
	[tempImage unlockFocus];
	if (knobImage) [knobImage free];
	knobImage = [[NXImage alloc] initFromImage:tempImage rect:&knobRect];
	return self;
}

- setSliderPathWidth:(float)val
{
	if (val == sliderPathWidth) return self;
	sliderPathWidth = val;
	[self _cacheImages];
	return self;
}

- (float)sliderPathWidth
{
    return sliderPathWidth;
}

- setSliderStyle:(MiscCircularSliderStyle)aBOOL
{
	if (aBOOL == style) return self;
	style = aBOOL;
	if ((style == MISC_SLIDER_STYLE) || (style == MISC_PIECHART_STYLE)) {
		jumpToMousePoint = YES;
		if (style == MISC_SLIDER_STYLE) [self _cacheImages];
	} else jumpToMousePoint = NO;
    return self;
}

- (MiscCircularSliderStyle)sliderStyle
{
    return style;
}

- setJumpToMousePoint:(BOOL)aBOOL
{
    jumpToMousePoint = aBOOL;
    return self;
}

- (BOOL)jumpToMousePoint
{
    return jumpToMousePoint;
}

- setHidden:(BOOL)flag
{
    hidden = flag;
    return self;
}

- (BOOL)hidden
{
    return hidden;
}

- read:(NXTypedStream *)stream
{
    [super read:stream];
    NXReadTypes(stream, "iccff", &style, &jumpToMousePoint, &hidden, &radius, &sliderPathWidth);
    NXReadPoint(stream, &center);
    return self;
}

- write:(NXTypedStream *)stream
{
    [super write:stream];
    NXWriteTypes(stream, "iccff", &style, &jumpToMousePoint, &hidden , &radius, &sliderPathWidth);
    NXWritePoint(stream, &center);
    return self;
}

// overridden methods here:
- calcCellSize:(NXSize *)theSize inRect:(const NXRect *)aRect
{
	theSize->width = NX_WIDTH(aRect);
	theSize->height = NX_HEIGHT(aRect);

	if (style != MISC_SLIDER_STYLE) return self;
	// the "slider looking" style should always be a perfect circle.
	if (theSize->height < theSize->width) theSize->width = theSize->height;
	else theSize->height = theSize->width;
	return self;
}

#define _FINDXYFROMPOINT(z) if (imFlipped) y = center.y - (z)->y; \
		else y = (z)->y - center.y; \
		x = (z)->x - center.x

- (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:controlView
{ // make sure we're within the slider's "hot" area, a circle (or ring)
	float x, y, rad, ir;

	_FINDXYFROMPOINT(startPoint);
	_offsetAng = angle(x, y) - currentangle(value, minValue, maxValue);
	rad = x * x + y * y;
	if (rad > radius * radius) return NO; // outside
	ir = radius - sliderPathWidth - 3.0;
	if ((rad < ir * ir) && (style == MISC_SLIDER_STYLE)) return NO;
	return [super startTrackingAt:startPoint inView:controlView];
}

- (BOOL)continueTracking:(const NXPoint *)lastPoint
		at:(const NXPoint *)currentPoint inView:controlView
{
	NXPoint pos; float x, y, oldx, oldy, curAng, oldAng;

	if (hidden) {
		PSsetgray(0.666); NXRectFill(&_lastFrame);
		return NO;
	}
	// get the current angle of the mouse pointer
	_FINDXYFROMPOINT(currentPoint);
	curAng = angle(x, y);
	if (!jumpToMousePoint) {
		curAng -= _offsetAng;
		if (curAng < 0) curAng += 360.0;
	}
	// get the last angle of the mouse pointer
	oldAng = currentangle(value, minValue, maxValue);

	xycoord(curAng, radius - sliderPathWidth / 2 - 2, &(pos.x), &(pos.y));
	pos.x += (center.x - sliderPathWidth * _KNOB_WIDTH_FUDGE + _KNOB_X_FUDGE);
	pos.y += (center.y - sliderPathWidth * _KNOB_WIDTH_FUDGE + _KNOB_Y_FUDGE);
	value = position(curAng, minValue, maxValue);

	xycoord(oldAng, radius - sliderPathWidth / 2 - 2, &oldx, &oldy);
	oldx += (center.x - sliderPathWidth * _KNOB_WIDTH_FUDGE + _KNOB_X_FUDGE);
	oldy += (center.y - sliderPathWidth * _KNOB_WIDTH_FUDGE + _KNOB_Y_FUDGE);

	[[controlView window] disableFlushWindow];
	PSgsave();
	if (imFlipped) {
		NXRect aFrame;
		[controlView getFrame:&aFrame];
		PSscale(1.0, -1.0);
		PStranslate(0.0, -NX_HEIGHT(&aFrame));
	}
	if (style == MISC_SLIDER_STYLE) {
		NXRect eraseRect;
		id bezel = (cFlags1.disabled ? disabledBezelImage : enabledBezelImage);
		NXSetRect(&eraseRect, oldx, oldy,
				sliderPathWidth * 1.5 + 2, sliderPathWidth * 1.5 + 2);
		NXIntegralRect(&eraseRect);
		NXInsetRect(&eraseRect, -4.0, -4.0);
		[bezel composite:NX_SOVER fromRect:&eraseRect
				toPoint:&(eraseRect.origin)];
		[knobImage composite:NX_SOVER toPoint:&pos];
	} else {
		[self renderAll];
	}
	PSgrestore();
	[[[controlView window] reenableFlushWindow] flushWindow];
	NXPing();
	return YES;
}

- drawBarInside:(const NXRect *)cellFrame flipped:(BOOL)flipped
{
	NXRect frame = { { 0.0, 0.0 },
			{ NX_WIDTH(cellFrame), NX_HEIGHT(cellFrame) } };
	id bezel = (cFlags1.disabled ? disabledBezelImage : enabledBezelImage);

	if (hidden) {
		PSsetgray(0.666); NXRectFill(cellFrame);
		return self;
	}
	PSgsave();
	if (flipped) {
		PSscale(1.0, -1.0);
		PStranslate(0.0, -NX_HEIGHT(cellFrame));
		imFlipped = YES;
	}
	if ((style != MISC_SLIDER_STYLE) &&
			(!(style == MISC_PIECHART_STYLE) && !cFlags1.disabled)) {
		MiscCSDrawBackground(center.x, center.y, radius,
				NX_HEIGHT(cellFrame), NX_WIDTH(cellFrame));
	}
	if (style == MISC_SLIDER_STYLE)
		[bezel composite:NX_SOVER fromRect:&frame toPoint:&(frame.origin)];
	PSgrestore();
	return self;
}

- renderAll
{
	float ang, r4 = radius - _BEZELS_FUDGE;

	if ((style != MISC_SLIDER_STYLE) &&
			!((style == MISC_PIECHART_STYLE) && cFlags1.disabled)) {
		MiscCSDrawBackground(center.x, center.y, r4, radius, radius);
	}
	ang = currentangle(value, minValue, maxValue);
	switch (style) {
		case MISC_PIECHART_STYLE : {
			if (cFlags1.disabled) {
				MiscCSPieChartDisabled(center.x, center.y,
						r4, radius, radius, ang);
			} else {
				MiscCSPieChart(center.x, center.y, r4,
						radius, radius, 90 - ang);
			}
			break;
		}
		case MISC_DIAL_STYLE : {
			MiscCSControlDial(center.x, center.y, r4, radius, radius,
					ang, (cFlags1.disabled ? 0.333 : 0.0));
			break;
		}
		case MISC_SHUTTLEWHEEL_STYLE : {
			if (cFlags1.disabled) {
				MiscCSControlKnobDisabled(center.x, center.y,
						r4, radius, radius);
			} else {
				MiscCSControlKnob(center.x, center.y, r4, radius, radius, ang);
			}
			break;
		}
		default : break;
	}
	return self;
}

- drawKnob:(const NXRect *)knobRect
{
	NXRect frame = { { 0.0, 0.0 }, { radius * 2, radius * 2 } };
	NXPoint position; float ang;
	id bezel = (cFlags1.disabled ? disabledBezelImage : enabledBezelImage);

	if (hidden) {
		PSsetgray(0.666); NXRectFill(&_lastFrame);
		return self;
	}
	PSgsave();
	if (imFlipped) {
		PSscale(1.0, -1.0);
		PStranslate(0.0, -NX_HEIGHT(&_lastFrame)); // ***** probably wrong
		
	}
	if ((style != MISC_SLIDER_STYLE) &&
			(!(style == MISC_PIECHART_STYLE) && !cFlags1.disabled)) {
		MiscCSDrawBackground(center.x, center.y, radius - 4,
				NX_HEIGHT(&_lastFrame), NX_WIDTH(&_lastFrame));
	}
	ang = currentangle(value, minValue, maxValue);
	if  (style != MISC_SLIDER_STYLE) {
		[self renderAll];
	} else {
		[bezel composite:NX_SOVER fromRect:&frame toPoint:&(frame.origin)];
		xycoord(ang, radius - sliderPathWidth / 2 - 2,
				&(position.x), &(position.y));
		position.x += (center.x - sliderPathWidth
				* _KNOB_WIDTH_FUDGE + _KNOB_X_FUDGE);
		position.y += (center.y - sliderPathWidth
				* _KNOB_WIDTH_FUDGE + _KNOB_Y_FUDGE);
		[knobImage composite:NX_SOVER toPoint:&position];
	}
	PSgrestore();
	return self;
}

#define _COPYRECT(x, y) NXSetRect((x), \
		NX_X(y), NX_Y(y), NX_WIDTH(y), NX_HEIGHT(y))

- drawSelf:(const NXRect *)cellFrame inView:controlView
{
	if ([controlView isFlipped]) imFlipped = YES;
	if (!NXEqualRect(&_lastFrame, cellFrame)) {
		_COPYRECT(&_lastFrame, cellFrame);
		radius = MIN(NX_WIDTH(cellFrame), NX_HEIGHT(cellFrame)) / 2;
		center.x = NX_X(cellFrame) + radius;
		center.y = NX_Y(cellFrame) + radius;
		if (style == MISC_SLIDER_STYLE) [self _cacheImages];
	}
	if (hidden) {
		PSsetgray(0.666); NXRectFill(cellFrame);
		return self;
	}
	if (NXDrawingStatus == NX_PRINTING) {
		// make sure everything gets rendered...
		[self drawBarInside:cellFrame flipped:imFlipped];
	}
	[self drawKnob:cellFrame];
	return self;
}

- getKnobRect:(NXRect *)knobRect flipped:(BOOL)flipped
{
	[super getKnobRect:knobRect flipped:flipped];
	return self;
}

@end
