/* 
 * GaugeView.m, analog gauge view
 * Author: Bruce Blumberg, NeXT Developer Support Group.
 * Originally written for 0.6 mid 1988, modified for 1.0 by Ali Ozer.
 * Redesigned for 2.0 by Julie Zelenski, NeXT Developer Support
 *
 * Subclass of view to implement a simple round analog gauge. You can set the 
 * minimum, maximum value, start angle, angle range, title, font, and more.  
 * It is a pretty generic round gauge view, if you ever have need for one.
 *
 * You may freely copy, distribute and reuse the code in this example.  
 * NeXT disclaims any warranty of any kind, expressed or implied, as to 
 * its fitness for any particular use.
 */

#import "GaugeView.h"
#import "Gauge.h"	// PSwrap routines
#import <appkit/Font.h>
#import <appkit/Window.h>
#import <appkit/Slider.h>
#import <soundkit/Sound.h>
#import <dpsclient/wraps.h>	// PScomposite(), ...
#import <math.h>		// sin(), cos(), ...
#import <string.h>		// strcpy(), ...


@implementation GaugeView : View


#define HANDRATIO	0.65	/* hand length compared face size */

- initFrame:(NXRect *)frameRect; 
/* Basic initFrame method which just calls the more complicated 
 * initFrame:min:max... method with some generally good default values.
 */
{
    [self initFrame:frameRect min:0.0 max:100.0 
    		startAngle:215.0 range:250.0 tickInterval:10]; 
    return self;
}

- initFrame:(NXRect *)frameRect min:(float)min max:(float)max 
	startAngle:(float)start range:(float)range ti(6terval:(int)interval
/* Init method for newly created analog gauge. It creates the face of the 
 * gauge in an offscreen window called cacheWindow when drawSelf:: is called 
 * the image is composited into the view and the hand is drawn on top of the 
 * face.
 */
{
    [super initFrame:frameRect];
	/* Create offscreen window for face. Note that the defer: argument
	 * has to be NO for windows which will remain offscreen. Deferred
	 * windows only get created on a orderFront: (or any other method
	 * that causes them to come on screen).
	 */
    cacheWindow = [[Window allocFromZone:[self zone]]
    			 initContent:frameRect 
                               style:NX_PLAINSTYLE
			     backing:NX_RETAINED 
			  buttonMask:0 
			       defer:NO];    
    startAngle = start;
    angleRange = range;
    tickInterval = interval;
    value = min;
    [self setMin:min];
    [self setMax:max];
    [self setFont:"Helvetica" size:10.0];
    [self setTitle:"Stress"];
    center.x = bounds.size.width/2.0;
    center.y = bounds.size.height/2.0;
    radius = (bounds.size.height/2.0) - 8.0;

    /* This pswrap creates a PS function which is used to draw the hand. */
    PSWmakeHand(radius*HANDRATIO);
    [self drawFace];
    return self;
}

- free;
/* Free cacheWindow, too!
 */
{
    [cacheWindow free];
    return [super free];
}


/* SET PARAMETERS */

- setFont:(char *)fName size:(float)fSize
/* Sets font used for drawing title and numbers. Change is displayed
 * next time drawSelf:: is executed. needRedraw is set to indicate that 
 * face image must be redrawn.
 */
{	
    font = [Font newFont:fName size:fSize style:0 matrix:NX_IDENTITYMATRIX];
    needRedraw = YES;
    return self;
}

- setTitle:(char *)str;
/* Sets title of gauge.  needRedraw is set to indicate that 
 * face image must be redrawn.
 */
{
    strcpy(title,str);
    needRedraw = YES;
    return self;
}

- setMin:(float)newValue;
/* Sets minimum for gauge, recalculates degreesPerUnit which is used to 
 * determine interval of ticks and labels. needRedraw is set to indicate that 
 * face image must be redrawn.
 */
{
    minValue = newValue;
    degreesPerUnit = angleRange/(maxValue-minValue);
    needRedraw = YES;
    return self;
}

- setMax:(float)newValue;
/* Sets maximum for gauge, recalculates degreesPerUnit which is used to 
 * determine interval of ti(7and labels. needRedraw is set to indicate that 
 * face image must be redrawn.
 */
{
    maxValue = newValue;
    degreesPerUnit = angleRange/(maxValue-minValue);
    needRedraw = YES;
    return self;
}

- setStartAngle:(float)newValue;
/* Sets start angle for gauge, which is the angle of the arm when at the 
 * minimum value.  needRedraw is set to indicate that face image must be 
 * redrawn.
 */
{
    startAngle = newValue;
    needRedraw = YES;
    return self;
}

- setAngleRange:(float)newValue;
/* Sets angle range for gauge, which is the sweep of the arm from minimum
 * value to maximum value.  The value cannot exceed 360 degrees (a full
 * revolution).  Recalculates degreesPerUnit which is used to 
 * determine interval of ticks and labels. needRedraw is set to indicate that 
 * face image must be redrawn.
 */
{
    if (newValue > 360) newValue = 360.0;
    angleRange = newValue;
    degreesPerUnit = angleRange/(maxValue-minValue);
    needRedraw = YES;
    return self;
}

- setTickInterval:(int)newValue;
/* Sets tick interval for gauge, which is number of units between tick
 * marks on gauge face.  needRedraw is set to indicate that 
 * face image must be redrawn.
 */
{
    tickInterval = newValue;
    needRedraw = YES;
    return self;
}


- setValueFormCell:anObject;
{
    valueFormCell = anObject;
    [valueFormCell setFloatingPointFormat:NO left:3 right:0];
    return self;
}

- (BOOL)textWillEnd:textObject;
/* Rejects entry into the form cell if it isn't in the range of the
 * min and max value for the gauge.
 */
{
    id cell;
    float newValue;
    
    cell = [textObject delegate];
    newValue = [cell floatValue];
    return (newValue > maxValue) || (newValue < minValue);
}

/* TARGET/ACTION METHODS  */

- changeValue:sender
/* Target/Action for a IB control.  Takes floatValue from control (could be
 * slider or field), makes sure slider and field are in sync,  displays arm 
 * at new value.
 */
{   float newValue;

    newValue = [sender floatValue];
    if (newValue != value) {	// if value changed
        value = newValue;
        if (sender == valueSlider)
	    [valueFormCell setFloatValue:newValue];
	else
	    [valueSlider setFloatValue:newValue];
	[self display]; 
    	if (value == maxValue)  // if at maximum value
            [[Sound findSoundFor:"HighStress"] play];
    }
    return(8f;
}
	
	
/* PRIVATE METHODS */

- drawFace
/* drawFace draws the gauge face image in the offscreen window.  It erases
 * the background, draws the circular border, displays the gauge title, 
 * draws the tick marks and labels them appropriately. The offscreen cache 
 * is composited on screen and then the hand is drawn on top of the face 
 * for the current gauge value.
 */
{	
    float angle, angleIncrement;
    int number;
    NXSize string;
    NXPoint pt;
    char numString[10];
    
    [[cacheWindow contentView] lockFocus];
    
    PSsetgray(NX_LTGRAY);
    NXRectFill(&bounds);
    PSWdrawBorder(center.x, center.y, radius);
    angleIncrement = angleRange/((maxValue-minValue)/tickInterval); 
    PSWdrawTicks(center.x, center.y, radius*HANDRATIO,
    		angleIncrement/2, startAngle, startAngle+angleRange); 
    [font set];    
    string.height = [font pointSize];
    string.width = [font getWidthOf:title];
    PSWdrawString((bounds.size.width-string.width)/2, center.y+8, title);
    
    number =  minValue;
    for(angle=startAngle;angle>=startAngle-angleRange;angle -= angleIncrement){
        sprintf(numString,"%d",number);
	string.width = [font getWidthOf:numString];
	pt.x = cos(angle/57.3)*(radius-1.0-string.width/2)+ center.x; 
	pt.y = sin(angle/57.3)*(radius-1.0-string.height/2) + center.y ;
	PSWdrawString(pt.x-(string.width/2), pt.y-(string.height/2.5),
			numString);
	number += tickInterval;
    }
    
    [[cacheWindow contentView] unlockFocus]; 
    needRedraw = NO;
    return self;
}	


- drawHand
/* Calculates the angle for the current value and draws the hand there.
 */
{
    float valueAngle;
    
    valueAngle = startAngle - degreesPerUnit*(value-minValue);
    PSWdrawHand(center.x,center.y,valueAngle);
    return self;
}

- drawSelf:(NXRect *)drawRects :(int)rectCount
/* Draws face and hand of gauge. If needRedraw is YES, a parameter has 
 * changed and the face must be redrawn in the offscreen window. 
 * Otherwise, the image in cacheWindow is copied into the bounds of the 
 * view, and a PSWrap is called which draws the hand in the 
 * correct position.
 */
{
	
    if (needRedraw)
        [self drawFace];
    PScomposite(0.0, 0.0, bounds.size.width, bounds.size.height,
    		[cacheWindow gState], 0.0, 0.0, NX_COPY);
    [self drawHand];
    return self;
}

@end