
/*
	MiscGaugeCell.m
  
  Since most of the code from this class came from the GaugeView class, here
  is the original comments for your reading pleasure:
   
  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.

  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 <AppKit/AppKit.h>
#import "Gauge.h"				// For the pswraps.
#import "MiscGaugeCell.h"

// Used for class versioning. If you happen to change the ivars, then 
// bump the version up and update the initWithCoder:/encodeWithCoder:
//methods.
#define MISCGAUGECELL_VERSION 2
#define MISCGAUGECELL_NAME @"MiscGaugeCell"


@implementation MiscGaugeCell 

/*"
	This class is a simple round analog Gauge that came about while 
	extending the GaugeView class that comes with the BusyBox example 
	(/NextDeveloper/Examples/AppKit/BusyBox). It should be fairly 
	easy to modify the look of the gauge by overriding #drawFace: and/or
	#drawHand:flipped:. Let me know if you try and run into any problems 
	that I hadn't considered. 

	Instead of me explaining all the features, and what most of the methods do 	
	(unless you plan to subclass), load up the palette in Interface Builder and 
	play with the options on the Inspector. If you have any comments and/or 
	suggestions, make improvements, or <gasp> find a bug, please let me know.
"*/


+ (void) initialize
/*"
   Sets the class verison for archiving purposes.
"*/
{
    if (self == [MiscGaugeCell class]) {
		// Set the class's version for archiving purposes.
		[self setVersion: MISCGAUGECELL_VERSION];
    }
}


- init
/*"
	MiscGaugeCell's destinated initializer. Don't use either of 
	NSCell's other initializers (initTextCell: or initImageCell:).
"*/
{
    BOOL error = ([super init] == nil);

    if (!error) {
        cache = [[NSImage alloc] init];
        radius = 0.0;
        center.x = center.y = 0.0;
        startAngle = angleRange = degreesPerUnit = 0.0;
        tickInterval = 0;
        gaugeColor = [NSColor darkGrayColor];
        textColor = [NSColor blackColor];
        minValue = 0.0;
        maxValue = 1.0;
        tickRatio = 0.65;
        handRatio = 0.6;
        degreesPerUnit = angleRange/(maxValue-minValue);
        [self setType:NSTextCellType];	// Need this so I can use [self font].
        [self setTitlePosition: NSAtTop];
        titleCell = [[NSCell alloc] initTextCell:@"Gauge"];
        [self setTitleFont:[self font]];
        [self setIntValue:0];
        
        if (error) {
            [self autorelease];
        }
    }
    
	return error ? nil :self;
}


- (void) dealloc
/*"
	Deallocates our cache, fonts and colors.
"*/
{
    [cache release];
    [titleCell release];
    [gaugeColor release];
    [textColor release];
    [super dealloc];
}


- (double) maxValue 
/*"
	Returns the maximum value of the gauge.
"*/
{ 
	return maxValue; 
}


- (void) setMaxValue:(double)max
/*"
	Sets a new maximum value for the gauge. If the new maximum 
	value happens to be lower than the current minimum, the minimum 
	value is adjusted to be one less than max. If the value of the 
	gauge (as returned by #floatValue) is now larger than the gauge's 
	new maximum it is also changed to equal max.
"*/
{
    if ([self maxValue] != max) {
        maxValue = max;

        if ([self minValue] >= [self maxValue]) {
            [self setMinValue:max-1];
        }
        if ([self doubleValue] > maxValue) {
            [self setDoubleValue:maxValue];
        }
        degreesPerUnit = angleRange / (maxValue-minValue);
    }
}


- (double) minValue 
/*"
	Returns the minimum value of the gauge.
"*/
{ 
	return minValue; 
}


- (void) setMinValue:(double)min
/*"
	Sets a new minimum value for the gauge. If the new minimum 
	value happens to be higher than the current maximum, the 
	maximum value is adjusted to be one greater than min. If the 
	value of the gauge (as returned by #floatValue) is now smaller 
	than the gauge's new minimum it is also changed to equal min.
"*/
{
    if ([self minValue] != min) {
        minValue = min;

        if ([self minValue] >= [self maxValue]) {
            [self setMaxValue:min+1];
        }
        if ([self doubleValue] < minValue) {
            [self setDoubleValue: minValue];
        }

        degreesPerUnit = angleRange/(maxValue-minValue);
    }
}


- (void) setFloatValue:(float)val
/*"
	Sets the value of the gauge to be val. If val is less than the 
	minimum or greater than the maximum value, it will be set to 
	either the minimum or maximum value, respectively.
"*/
{
    [self setDoubleValue:(double)val];
}


- (void) setDoubleValue:(double)val
/*"
	Sets the value of the gauge to be val. If val is less than 
	the minimum or greater than the maximum value, it will be set 
	to either the minimum or maximum value, respectively.
"*/
{
    if (val < [self minValue]) {
        val = minValue;
    }
    if (val > [self maxValue]) {
        val = maxValue;
    }
    [super setDoubleValue:val];
}


- (void) setIntValue:(int)val
/*"
	Sets the value of the gauge. Internally val will be converted to 
	a double. If the value is not between the minimum and maximum, it 
	will be adjusted so that it is.
"*/
{
	[self setDoubleValue:(double)val];
}


- (void)setStringValue:(NSString*)val
/*"
	Trys to convert val into a floating point number. If no value can 
	be found, the value of the gauge will be set to 0. If the converted 
	value is not in between the minimum and maximum value, it will be 
	set so that it is.
"*/
{
    if (val == nil) {
		[self setDoubleValue:0.0];
    }
    else {
		[self setDoubleValue:[val doubleValue]];
    }
}


- (NSColor *) gaugeColor 
/*"
	Returns the current color of the gauge face.
"*/
{ 
	return gaugeColor; 
}


- (void) setGaugeColor:(NSColor*)color
/*"
	Sets the color of the gauge face. I've found that pale colors are 
	not a good choice.
"*/
{
    [gaugeColor autorelease];
    gaugeColor = [color retain];
}


- (NSColor*) textColor 
/*"
	Returns the color that the text and gauge hand are drawn in.
"*/
{ 
	return textColor; 
}


- (void) setTextColor:(NSColor*)color
/*"
	Sets the color of the text and gauge hand.
"*/
{
    [textColor autorelease];
    textColor = [color retain];
}


- (float) startAngle 
/*"
	Returns the gauge's start angle (where the minimum value is located). 
	See #setStartAngle: for more information.
"*/
{ 
	return startAngle; 
}


- (void) setStartAngle:(float)newValue
/*"
	Sets the angle, in degrees, where the minimum value of the gauge 
	will appear. Zero degrees is due east, with increasing numbers 
	moving the start counter-clockwise. The newValue should be in 
	between 0 and 360. If not, it will be adjusted so it is.
"*/
{
	// needRedraw is set to indicate that face image must be 
	// redrawn. 0 degrees is straight right, with increasing numbers going
	// counter clockwise.
    if (newValue < 0.0) {
		newValue = 0.0;
    }
    if (newValue > 360.0) {
		newValue = 360.0;
    }
    startAngle = newValue; 
}




- (float) angleRange	
/*"
	Returns the sweep of the hand from minimum to maximum value, 
	starting at startAngle. This will not be less than 0 or more than 
	360 degrees.
"*/
{ 
	return angleRange; 
}


- (void) setAngleRange:(float)newValue
/*"
	Sets the span, in degrees, from the gauge's minimum to the maximum. 
	The span starts at startAngle and continues clockwise. if newValue 
	is less than 0 or greater then 360, it will be adjusted (set to 
	either 0 or 360, respectively) so it is within a meaningful range.
"*/
{
	newValue = (newValue > 360.0) ? 360.0 : newValue;
	newValue = (newValue < 0.0) ? 0.0 : newValue;
	
    angleRange = newValue;
    degreesPerUnit = angleRange/(maxValue-minValue); 
}


- (int) tickInterval 
/*"
	Returns the current tick interval.
"*/
{ 
	return tickInterval; 
}


- (void) setTickInterval:(int)newValue
/*"
	Sets the interval that the gauge face's tick marks should 
	be drawn (including the numbers). For example, if you had a 
	minimum value of 10.0 and a maximum of 100.0, then a tick interval 
	of 10 would probably be around right. A current limitation is that 
	the tick interval cannot be less than 1. This will be fixed in a 
	coming release.
"*/
{
    tickInterval = (newValue < 1) ? 1 : newValue;
}


- (float) tickRatio 
/*"
	Returns the radius of the tick marks in relation to the gauge's 
	radius. This value will be between 0.0 (no tick marks) and 1.0.
"*/
{ 
	return tickRatio; 
}


- (void) setTickRatio:(float)newRatio
/*"
	Sets the tick mark's radius in ratio to the gauge face's radius. 
	Usually this is adjusted if either the numbers or the gauge's 
	title overlap the tick marks.
"*/
{
	// Make sure new tick ratio is between 0.0 (no ticks) and 1.0.
	newRatio = (newRatio < 0.0) ? 0.0 : newRatio;
	newRatio = (newRatio > 1.0) ? 1.0 : newRatio;

	tickRatio = newRatio; 
}


- (float) handRatio 
/*"
	Returns the ratio of the hand length to the radius of the face. 
	For reference, a value of  0.5 would mean the length of the hand 
	was half of the radius.
"*/
{ 
	return handRatio; 
}


- (void) setHandRatio:(float)newRatio
/*"
	Sets the hand length in ratio to the gauge's radius. A value of 0.5 
	would draw a hand with a length half of the gauge's radius.  The value 
	must be between 0.2 and 0.9. If it is either larger or smaller, the 
	new ratio will be set to either 0.2 or 0.9 respectively.
"*/
{
	// Make sure new hand ratio is between 0.2 and 0.9.
	newRatio = (newRatio < 0.2) ? 0.2 : newRatio;
	newRatio = (newRatio > 0.9) ? 0.9 : newRatio;

	handRatio = newRatio; 
}


- (NSFont*) titleFont 
/*"
	Returns the current font used for displaying the title. By default 
	it is the same as Cell's font.
"*/
{ 
	return [titleCell font]; 
}


- (void) setTitleFont:(NSFont*)newFont
/*"

	Sets the font for the gauge's title. No matter if this view is flipped 
	or not, newFont should have an NSFontIdentityMatrix matrix.
"*/
{
    if ([self titleFont] != newFont) {
        [titleCell setFont:newFont];
    }
}


- (NSString*) title 
/*"
	Returns the current font used for displaying the title. By default 
	it is the same as Cell's font.
"*/
{ 
	return [titleCell stringValue]; 
}


- (void) setTitle:(NSString*)newTitle
/*"
	Sets our title.
"*/
{
    [titleCell setStringValue:newTitle];
}	
	

- (NSTitlePosition) titlePosition 
/*"
	Returns the current position of the title even if there is no 
	current title. Currently, either NSAtTop or NSAtBottom will be 
	returned.
"*/
{ 
	return titlePosition; 
}


- (void) setTitlePosition:(NSTitlePosition)newPos
/*"
	Sets the position of the title. Currently the only valid positions 
	are NSAtTop and NSAtBottom."*/
{
    if (newPos == NSAtTop || newPos == NSAtBottom) {
		titlePosition = newPos;
    }
}


- (NSSize) cellSizeForBounds:(NSRect)rect
/*"
	Overridden from Cell to calculate the minimum size that the cell 
	will occupy.
"*/
{
    NSSize size;
    size.width = NSWidth(rect);
	size.height = NSHeight(rect);

    if (size.height < size.width) {
		size.width = size.height;
    }
    else { 
		size.height = size.width;
    }

	return size;
}


- (void) drawWithFrame:(NSRect)rect inView:(NSView*)controlView
/*"
	Calls #drawFace: to redraw the cached gauge face, then calls 	
	#drawInside:inView: to composite the new face and draw the hand
"*/
{
    center.x = NSWidth(rect)/2.0;
    center.y = NSHeight(rect)/2.0;
    if (NSWidth(rect) > NSHeight(rect)) {
    	radius = (NSHeight(rect)/2.0) - 8.0;
    }
    else {
		radius = (NSWidth(rect)/2.0) - 8.0;
    }

	// Redraws the face in the cached image.		
	[self drawFace:rect];
	// Draws the gauge itself. 
	[self drawInteriorWithFrame:rect inView:controlView];
}


- (void) drawInteriorWithFrame:(NSRect)rect inView:(NSView*)controlView
/*"
	Does a minimal update, which composites the gauge face, then draws 
	the gauge hand. If you are customizing the look of the gauge, 
	you'll probably want to override #drawFace: and/or #drawHand:flipped: 	
	instead.
"*/
{
	// Composites the "face" of the gauge, then draws the hand.
	// This method will also work inside a flipped view (like a Matrix).
	NSPoint corner = {0.0, 0.0};

	// If view is flipped composite from lower left corner.
    if ([controlView isFlipped]) {
		corner.y = NSHeight(rect);
    }

	// Composite the gauge face into the view.
	[cache compositeToPoint:corner operation:NSCompositeSourceOver];
	// Draw the hand.
	[self drawHand:rect flipped:[controlView isFlipped] ];	
}


- (void)drawHand:(NSRect)rect flipped:(BOOL)viewFlipped
/*"
	Called by #drawInside:inView: to draw the gauge hand. If you want 
	to change the look of the hand, this would be the method to override. 
	Since the view that we are drawing into is already lockfocused, just 
	draw to your heart's content. Make sure to compensate for flipped views 
	so that your gauge can be used in a matrix too.
"*/
{
	// If you want the gauge to have a different look you should only
	// have to override drawFace: and drawHand:. The passed rect
	// is the same rect that is passed to drawInside:.
    float valueAngle;	

    valueAngle = startAngle - degreesPerUnit*([self floatValue] - [self minValue]);
	
    if (viewFlipped == YES) {
		PSgsave();
		PSWflipme (NSHeight(rect));
	 }
	
	[[self textColor] set];
    PSWdrawHand(center.x,center.y,valueAngle, radius*handRatio);

    if (viewFlipped == YES) {
		PSgrestore();
    }
}

	
- (void) drawFace:(NSRect)rect 
/*"
	Called by #drawSelf:inView: to draw the gauge face in the image 
	cache. This should be called only when one of the attributes that make 
	up the gauge face changes. The passed rect is the same rect that was 
	passed to #drawSelf:inView:. If you wanted to alter the face of the gauge, 	
	this would be the method to override. Just composite whatever image into 
	the cache and it will be composited when it is needed.
"*/
{	
	// This method draws the gauge face image in an NXImage.  It erases
	// the background, draws the circular border, draws the tick marks and 
	// labels them appropriately. Since we are drawing into an NXImage
	// we don't care whether the view we are compositing into is flipped or 
	// not.
    float angle, angleIncrement;
    int number;
    NSSize string;
    NSPoint pt;
    char numString[10];
    NSSize cacheSize;
	NSFont* cellsFont = [self font];
	NSFont* flippedFont;
	float titleY;
	
	cacheSize.width = NSWidth(rect);
	cacheSize.height = NSHeight(rect);
	[cache setSize:cacheSize];
	
	[cache lockFocus];

	// Do the entire rect in our background color.
	PSsetalpha (0.0);
    NSRectFill(rect);
	PSsetalpha (1.0);

	// Draw the gauge face.
	[[self gaugeColor] set];
    PSWdrawBorder(center.x, center.y, radius);
    angleIncrement = angleRange/((maxValue-minValue)/(float)tickInterval); 
	if (tickRatio > 0.0)
	{
		[[self textColor] set];
    	PSWdrawTicks(center.x, center.y, radius*tickRatio, angleIncrement/2.0, 
						startAngle, startAngle+angleRange); 
	 }

	if ([[self title] cString] != NULL)
	{
        NSFont* titleFont = [self titleFont];
        
		// Makes sure that the title font if using the NX_IDENTITYMATRIX.	 
		flippedFont = [NSFont fontWithName:[titleFont fontName] 
			size:[titleFont pointSize]];
		[flippedFont set];     
	
		// Spit out the title.
		string.height = [flippedFont pointSize];
		string.width = [flippedFont widthOfString:[self title]];
		if ([self titlePosition] == NSAtTop)
		    titleY = center.y+(NSHeight(rect)/10.0);
		else
		    titleY = center.y-(NSHeight(rect)/12.0)-string.height;
		
		[[self textColor] set];	
		PSWdrawString((NSWidth(rect)-string.width)/2, titleY, 
						[[self title] cString]);
	 }
	 
	// Make sure the cell's font (for the numbers) is also using
	// the NX_IDENTITYMATRIX.	 
	flippedFont = [NSFont fontWithName:[cellsFont fontName] 
		size:[cellsFont pointSize]];
	[flippedFont set];     
    string.height = [flippedFont pointSize];
    
	// Spit out all the numbers.
    number =  minValue;
	[[self textColor] set];
    for(angle=startAngle;angle>=startAngle-angleRange;angle -= angleIncrement){
        sprintf(numString,"%d",number);
		string.width = [cellsFont widthOfString:[NSString stringWithCString: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;
    }
    
    [cache unlockFocus]; 
}	


- (void) highlight:(BOOL)flag withFrame:(NSRect)cellFrame inView:aView
/*"
	Overridden to do nothing since we never want highlighting to occur.
"*/
{
}


- (id) initWithCoder:(NSCoder*)aDecoder
/*"
	Returns a newly unarchived instance of MiscGaugeCell. It doesn't seem 
	to be able to read pre-OpenStep created gauge cells because I 
	haven't figured out how to unarchive an NXColor structure yet.
"*/
{
    float value;		// old ivar
    NSFont* titleFont;	// me too
    int version;
    
	[super initWithCoder:aDecoder];
	version = [aDecoder versionForClassName:MISCGAUGECELL_NAME];
    switch (version) {

      case 0:		// For reading 3.x MiscGaugeCells already in a nib.
        [aDecoder decodeValuesOfObjCTypes:"ffiff", &startAngle, &angleRange,
            &tickInterval, &tickRatio, &handRatio];
        [aDecoder decodeValuesOfObjCTypes:"fffs", &minValue, &maxValue, &value,
            &titlePosition];

        // Read the old NXColor structure for the gauge and text color.
        gaugeColor = [[aDecoder decodeNXColor] retain];
        textColor = [[aDecoder decodeNXColor] retain];
        titleFont = [[aDecoder decodeObject] retain];

        // Put all the displaced ivar values somwhere.
        [self setFloatValue:value];
        [self setTitleFont:titleFont];
        titleCell = [[NSCell alloc] initTextCell:@""];
        break;

      // Start of the OpenStep world archiving.
      case 1: 
         [aDecoder decodeValuesOfObjCTypes:"ffiff", &startAngle, &angleRange,
            &tickInterval, &tickRatio, &handRatio];
        [aDecoder decodeValuesOfObjCTypes:"fffs", &minValue, &maxValue, &value,
            &titlePosition];

        gaugeColor = [[aDecoder decodeObject] retain];
        textColor = [[aDecoder decodeObject] retain];
        titleFont = [[aDecoder decodeObject] retain];

        // Put all the displaced ivar values somwhere.
        [self setFloatValue:value];
        [self setTitleFont:titleFont];
        titleCell = [[NSCell alloc] initTextCell:@""];
        break;

      // Removed our value and titleFont ivars and added a titleCell ivar.
      case MISCGAUGECELL_VERSION:
        [aDecoder decodeValuesOfObjCTypes:"ffiff", &startAngle, &angleRange,
            &tickInterval, &tickRatio, &handRatio];
        [aDecoder decodeValuesOfObjCTypes:"ffs", &minValue, &maxValue,
            &titlePosition];

        gaugeColor = [[aDecoder decodeObject] retain];
        textColor = [[aDecoder decodeObject] retain];
        titleCell = [[aDecoder decodeObject] retain];
        break;

      default:
        NSLog(@"Could not read typed stream for class %@, version %d",
              MISCGAUGECELL_NAME, version);
        break;
    }

    cache = [[NSImage alloc] init];
 	degreesPerUnit = angleRange/(maxValue-minValue);

	return self;
}


- (void) encodeWithCoder:(NSCoder*)aCoder
/*"
	Encodes an instance of MiscGaugeCell.
"*/
{
	[super encodeWithCoder:aCoder];
	[aCoder encodeValuesOfObjCTypes:"ffiff", &startAngle, &angleRange, 
					&tickInterval, &tickRatio, &handRatio];
	[aCoder encodeValuesOfObjCTypes:"ffs", &minValue, &maxValue,
					&titlePosition]; 

	[aCoder encodeObject:gaugeColor];
	[aCoder encodeObject:textColor];
	[aCoder encodeObject:titleCell];
}

@end


/****************************************************************************
  CHANGES:
   Version 0.3, March 19, 1995
  1. Fixed so it would draw with a transparent background correctly.
   Version 0.4, March 21, 1995
  2. Removed the lastRect and needRedraw ivars since as long as 
     drawSelf:inView: is called only when needed the face needs 
	 redrawing (from GaugeView) then drawing will be fairly efficient.
  3. Added drawHand:flipped: so you could easily customize the look
     of the gauge face and hand.
   Version 1.0 July 11, 1996
  4. Converted the code to OpenStep and cleaned up a little.	
   Version 1.1 August 4, 1996
  5. Cleaned up some more and put autodoc documentation throughout.
   
 ****************************************************************************/
 