/*
 *    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"
 */


#import "CircularSliderCell.h"
#import <appkit/Control.h>
#import <appkit/Application.h>
#import <math.h>
#import <dpsclient/psops.h>
#import "wraps.h"

/* Calculate the Absolute value of y safely */
#define ABS(y)                       \
      ({typeof (y) __x = (y);        \
       (__x < 0) ? (-(__x)) : (__x); })

@implementation CircularSliderCell

/* Given a x,y point on a cart coordinate system this function returns the angle (in degrees) from
 * the positive side of the Y- axis
 *        ^
 *        |
 *        |\
 *   <____|_|__>
 *        | |
 *        |<-
 *        |
 */

inline static float angle(float x, float y)
{
    float result;
    
    if (y >= 0){                                 /* Quadrants 1,4 */
        if ( x >= 0){                   /* Quadrant 1 */
            result  = atan(ABS(x/y));
            result *= (180/M_PI);
            return(ABS(result));
        }else{                          /* Quadrant 4 */
            result  = atan(ABS(y/x));
            result *= (180/M_PI);
            return(ABS(result+270.0));
        }
    }else{                                      /* Quadrants 2,3 */
        if ( x >= 0){                   /* Quadrant 2 */
            result  = atan(ABS(y/x));
            result *= (180/M_PI);
            return(ABS(result+90.0));
        }else{                          /* Quadrant 3 */
            result  = atan(ABS(x/y));
            result *= (180/M_PI);
            return(ABS(result+180.0));
        }
    }
}

/* 
 * xycoord does the opposite of angle,
 * given an angle and a radius, xycoord returns the x,y position of 
 * a point on the radius
 *        ^
 *        |
 *        |\
 *        | \  angle in degrees
 *   <____|__\___>
 *        |\  |
 *        | \<-
 *        |  \ (x,y)
 */
inline static void xycoord(float angle, float radius,float *x, float *y)
{
    *x = (radius - 3.0)*(float)sin((M_PI/180)*angle);
    *y = (radius - 3.0)*(float)cos((M_PI/180)*angle);
}

+ (BOOL)prefersTrackingUntilMouseUp
{
    return YES;
}

- init
{
    self = [super init];
    minValue = value = currentang = 0.0;
    maxValue = 360.0;
    scale_value = 360.0/(ABS(maxValue-minValue));
    pieChart = hidden = jumpToMousePoint = NO;
    return self;
}

- drawSelf:(const NXRect *)cellFrame inView:controlView
{
    center.x = cellFrame->size.width/2;
    center.y = cellFrame->size.height/2;
    radius = ( center.x < center.y ? center.x : center.y) - 4;
    scalex = cellFrame->size.height/ cellFrame->size.width;
    scaley = cellFrame->size.width / cellFrame->size.height;

    if ([controlView isFlipped]){
        imFlipped = YES;
    }
    if (hidden == YES){
	[controlView lockFocus];
	/* PSsetgray(0.66666); */
	NXSetColor(NX_COLORLTGRAY);
	PSrectfill(cellFrame->origin.x,cellFrame->origin.y,cellFrame->size.width,cellFrame->size.height);
	[controlView unlockFocus]; 
	return self;
    }
    [controlView lockFocus];
    PSgsave();
    if (imFlipped) {
        PSscale(1.0, -1.0);
    }
    if (cFlags1.bezeled == 1){
	NXDrawButton(cellFrame,cellFrame);
    }else{
	if (cFlags1.bordered == 1){
	    /* PSsetgray(0.0); */
	    NXSetColor(NX_COLORBLACK);
	    NXFrameRect(cellFrame);
	}
    }

    if (pieChart == NO){
	PSDrawBackground(cellFrame->size.height,cellFrame->size.width, center.x,center.y,radius);
    }else{
	if (cFlags1.disabled == 0)
	    PSDrawBackground(cellFrame->size.height,cellFrame->size.width, center.x,center.y,radius);
    }
    PSgrestore();
    [controlView unlockFocus];

    [self drawInside:cellFrame inView:controlView];
    return self;
}

- drawInside:(const NXRect *)cellFrame inView:aView  /* Draws only control portion not bezzel */
{
    float x,y;

  
    if (jumpToMousePoint == NO){
	currentang += deltaAngle;
	if (currentang > 360.0)
	    currentang -= 360.0;
	if (currentang < 0.0)
	    currentang += 360.0;
    }

    [aView lockFocus];
    PSgsave();
    if (imFlipped) {
        PSscale(1.0, -1.0);
    }

    if (pieChart){
	if (cFlags1.disabled == 0){ 
	    PSPieChart(cellFrame->size.height,cellFrame->size.width, 
		       center.x,center.y,radius,currentang);
	}else{
	    PSPieChartDisabled(cellFrame->size.height,cellFrame->size.width, 
			       center.x,center.y,radius,currentang);
	}
    }else{
	if (cFlags1.disabled == 0){ 
	    xycoord(currentang,radius,&x,&y);
	    PSControlKnob(cellFrame->size.height,cellFrame->size.width, center.x,center.y,radius,x,y);
	}else{
	    PSControlKnobDisabled(cellFrame->size.height,cellFrame->size.width, center.x,center.y,radius);
	}
    }
    PSgrestore();
    [aView unlockFocus];
    return self;
}

- (BOOL)startTrackingAt:(const NXPoint *)startPoint inView:aView
{
    return YES;
}

- (BOOL)trackMouse:(NXEvent *)theEvent inRect:(const NXRect *)cellFrame ofView:aView
{
    int             looping;                /* Flag for while in modal loop     */
    int             oldMask;                /* Old event mask                   */
    NXPoint         startPoint;             /* Location of mouseDown            */
    NXPoint         currentPoint;           /* Location of mouseDragged/mouseUp */
    float           newangle   = 0.0;
    float           startangle = 0.0;

    if ((cFlags1.disabled == 0) && (hidden == NO)){ 
	/* Allow mouseDragged events */
	oldMask = [[aView window] addToEventMask:NX_MOUSEDRAGGEDMASK];
	
	/* Get the location of the mouseDown in view coordinates */
	startPoint = theEvent->location;
	[aView convertPoint:&startPoint fromView:nil];
	
	if ([self startTrackingAt:&startPoint inView:aView]){
	    
	    /* Adjust the mouseDown event's location if the Slider has been
	     * scaled in either the x or y direction
	     */
	    
	    if (scalex > scaley ){
		startangle = angle(scalex*(startPoint.x-(center.x)),startPoint.y-(center.y));
	    }else
		if (scalex < scaley ){
		    startangle = angle(startPoint.x-(center.x),scaley*(startPoint.y-(center.y)));
		}else
		    startangle = angle(startPoint.x-(center.x),startPoint.y-(center.y));
	    
	    if (jumpToMousePoint == YES){
		currentang = startangle;
		[self drawInside:cellFrame inView:aView];
		[[aView window] flushWindow];
	    }
	    
	    /* Run modal loop until mouse up */
	    looping = YES;
	    while (looping) {
		/* Get the next mouseDragged/mouseUp event */
		theEvent=[NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
		
		/* Convert location to view coordinates */
		currentPoint = theEvent->location;
		[aView convertPoint:&currentPoint fromView:nil];
		
		/* Handle the event */
		if (theEvent->type == NX_MOUSEDRAGGED) {
		    
		    /* Adjust the mouseDown event's location if the Slider has been
		     * scaled in either the x or y direction
		     */
		    
		    if (scalex > scaley ){
			newangle = angle(scalex*(currentPoint.x-(center.x)),currentPoint.y-(center.y));
		    }else
			if (scalex < scaley ){
			    newangle = angle(currentPoint.x-(center.x),scaley*(currentPoint.y-(center.y)));
			}else
			    if (scalex = scaley ){
				newangle = angle(currentPoint.x-(center.x),currentPoint.y-(center.y));
			    }
		    if (jumpToMousePoint == NO){
			deltaAngle = newangle - startangle;
			startangle = newangle;
		    }else{
			currentang = newangle;
		    }
		    if (!finite(currentang)){
			currentang = 0.0;
		    }
		    if (!finite(deltaAngle)){
			deltaAngle = 0.0;
		    }
#if 0
		    if (currentang != currentang){ /* If this occurs, ie this test is true then 
						    * currentang == NAN */
			currentang = 0.0;
		    }
		    if (deltaAngle != deltaAngle){ /* If this occurs, ie this test is true then
						    * deltaAngle == NAN */
			deltaAngle = 0.0;
		    }
#endif
		    [self drawInside:cellFrame inView:aView];
		    [[aView window] flushWindow];
		    if ([self continueTracking:&startPoint at:&currentPoint inView:aView]){
			[aView sendAction: action to: target];
		    }
		}else{
		    looping = NO;
		    [[aView window] flushWindow];
		    [aView sendAction: action to: target];
		}
		startPoint.x = currentPoint.x;
		startPoint.y = currentPoint.y;	    
	    }
	    [aView sendAction: action to: target];
	}
	[[aView window] setEventMask:oldMask];
	return YES;
    }
    return NO;
}

- highlight:(const NXRect *)cellFrame inView:aView lit:(BOOL)flag
{
    return self;
}

- (float)floatValue
{
    value = (currentang/scale_value) + minValue;

    return(value);
}

- (double)doubleValue
{
    return((double)[self floatValue]);
}

- (int)intValue
{
    return((int)[self floatValue]);
}

- setDoubleValue:(double)aDouble
{
    [self setFloatValue:(float)aDouble];
    return self;
}

- setFloatValue:(float)aFloat
{
    if (aFloat < minValue){
	value = minValue;
	currentang = 0;
    }else
	if (aFloat > maxValue){
	    value = maxValue;
	    currentang = 360.0;
        }else{
	    value = aFloat;
	    currentang = (value - minValue)*scale_value;
	}

    return self;
}

- setIntValue:(int)aInt
{
    [self setFloatValue:(float)aInt];
    return self;
}

- (float)minValue
{
    return minValue;
}

- setMinValue:(float)aFloat
{
    float scale = ABS(aFloat-minValue);
    if (scale != 0.0){
	minValue = aFloat;
	scale_value = 360.0/(ABS(maxValue-minValue));
    }
    return self;
}

- (float)maxValue
{
    return maxValue;
}

- setMaxValue:(float)aFloat
{
    float scale = ABS(aFloat-minValue);
    if (scale != 0.0){
	maxValue = aFloat;
	scale_value = 360.0/scale;
    }
    return self;
}

- setDrawAsPieChart:(BOOL)aBOOL
{
    pieChart = aBOOL;
    return self;
}

- (BOOL)drawAsPieChart
{
    return pieChart;
}

- 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,"fff",&value,&maxValue,&minValue);
    NXReadTypes(stream,"ccc",&pieChart,&jumpToMousePoint,&hidden);
    NXReadTypes(stream,"ff",&currentang,&radius);
    NXReadPoint(stream,&center);
    NXReadTypes(stream,"ffff",&deltaAngle,&scale_value,&scalex,&scaley);
    return self;
}

- write:(NXTypedStream *)stream
{
    [super write:stream];
    NXWriteTypes(stream,"fff",&value,&maxValue,&minValue);
    NXWriteTypes(stream,"ccc",&pieChart,&jumpToMousePoint,&hidden);
    NXWriteTypes(stream,"ff",&currentang,&radius);
    NXWritePoint(stream,&center);
    NXWriteTypes(stream,"ffff",&deltaAngle,&scale_value,&scalex,&scaley);
    return self;
}

@end

