/* PlotView.m -- Implementation file for the PlotView class */

#import "PlotView.h"
#import "drawingFuncs.h"
#import <objc/Storage.h>
#import <appkit/NXCursor.h>
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/Cell.h>
#import <appkit/Pasteboard.h>
#import <NXCType.h> 
#import <math.h> 
#import <dpsclient/psops.h>
#import <dpsclient/wraps.h>
#import <stdlib.h> 
#import <string.h> 

extern float getNumber(NXStream *stream);
extern int getSeparator(NXStream *stream);

#define MAXNUMLENGTH 50
#define DEFAULTRADIUS 1.0

@implementation PlotView

- initFrame:(const NXRect *)frameRect
/*
 * Initializes the new PlotView object.  First, an initFrame: message is sent to 
 * super to initialize PlotView as a View.  Next, the PlotView sets its own 
 * state -- that it is opaque and that the origin of its coordinate system lies in 
 * the center of its area.  It then creates and initializes its associated objects,
 * a Storage object, an NXCursor, and a Cell.  Finally, it loads into the Window 
 * Server some PostScript procedures that it will use in drawing itself. 
 */
{
	NXPoint	spot;
	
	[super initFrame:frameRect];
	[self setOpaque:YES];
	[self setRadius:DEFAULTRADIUS];
	[self translate:floor(frame.size.width/2) :floor(frame.size.height/2)];
	points = [[Storage alloc] initCount:0 elementSize:sizeof(NXPoint) description:"{ff}"];
	crossCursor = [NXCursor newFromImage:[NXImage newFromSection:"cross.tiff"]];
	spot.x = spot.y = 7.0;
	[crossCursor setHotSpot:&spot];
	/* 
	 * Normally, the next two message expressions would be combined, as:
	 * 		readOut = [[Cell alloc] initTextCell:""];
	 * but are here separated for clarity.
	 */
	readOut = [Cell alloc];
	[readOut initTextCell:""];
	[readOut setBezeled:YES];
	loadPSProcedures();
	return self;
}


- setDelegate:anObject
/*
 * Sets the PlotView's delegate instance variable to the supplied object.
 */
{
	delegate = anObject;
	return self;
}


- drawSelf:(const NXRect *)rects :(int)rectCount
/*
 * Draws the PlotView's background and axes.  If there are any points,
 * these are drawn too.
 */
{
	unsigned int	 i;
	NXPoint		*aPoint;
	
	if (rects == NULL) return self;

	/* If we're printing, we need to load drawing procedures to printing context */
	if (NXDrawingStatus != NX_DRAWING) 
		loadPSProcedures();

	/* paint visible area white then draw axes */
	PSsetgray(NX_WHITE);
	NXRectFill(&rects[0]);
	PSsetgray(NX_DKGRAY);
	drawAxes(bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height);

	/* now take each point and draw it */
	PSsetgray(NX_BLACK);
	i = [points count];
	while (i--) {
		aPoint = (NXPoint *)[points elementAt:i];
		drawCircle(aPoint->x, aPoint->y, radius);
	} 
	return self;
}

- clear:sender
/*
 * Clears the PlotView by emptying its Storage object of all points
 * and then redisplaying the PlotView.  This action is taken only if
 * the PlotView's state requires it.
 */
{
	if (needsClearing) {
		[points empty];
		[self display];
		needsClearing = NO;
	}
	return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
/*
 * Ensures that whenever the PlotView is resized, the origin of its 
 * coordinate system is repositioned to the center of its area.
 */
{
	[super sizeTo:width :height];
	[self setDrawOrigin:-floor(width/2) : -floor(height/2)]; 
	return self;
}

- registerPoint:(NXPoint *)aPoint
/*
 * Adds a point to the list the PlotView keeps in its Storage object. 
 */
{
	[points addElement:aPoint];
	return self;
}

- mouseDown:(NXEvent *) theEvent
/*
 * Responds to a message the system sends whenever the user presses the mouse
 * button when the cursor is over the PlotView.  The PlotView changes the 
 * cursor to a cross-hairs image and then starts asking for mouse-dragged or mouse-
 * up events.  As it receives mouse-dragged events, the PlotView updates the readOut
 * text Cell with the cursor's coordinates.  If the user releases the mouse
 * button while the cursor is over the PlotView, the PlotView registers the 
 * point and then sends a message to its delegate notifying it of the new 
 * point.
 */
{
	int	looping = YES, oldMask;
	NXPoint	aPoint;
	NXRect	plotRect, cellRect;
   	char	buffer[100];

	[crossCursor set];
   	[self getBounds:&plotRect];
 	NXSetRect(&cellRect, plotRect.origin.x, plotRect.origin.y, 100.0, 20.0);

	oldMask = [window addToEventMask:NX_MOUSEDRAGGEDMASK];
	[self lockFocus];
	do {
		aPoint = theEvent->location;
		[self convertPoint:&aPoint fromView:nil];
		sprintf(buffer, "(%d, %d)", (int)aPoint.x, (int)aPoint.y); 
  		[readOut setStringValue:buffer];
  		[readOut drawInside:&cellRect inView:self];
		[window flushWindow];
		if (theEvent->type == NX_MOUSEUP) {
	    	/* on mouse-up, register point, inform delegate, and clean up state */
			[readOut setStringValue:""];
  			[readOut drawInside:&cellRect inView:self];
			[window flushWindow];
			if (NXPointInRect(&aPoint, &plotRect)) {
				PSsetgray(NX_BLACK);
				drawCircle(aPoint.x, aPoint.y, radius);
				[window flushWindow];
				[self registerPoint:&aPoint];
				needsClearing = YES;
				if (delegate && [delegate respondsTo:@selector(plotView:pointDidChange:)])
					[delegate plotView:self pointDidChange:&aPoint];
			}
			looping = NO;
		}	
   	} while (looping && (theEvent=[NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK]));
	[self unlockFocus];
	[window setEventMask:oldMask];
	[NXArrow set];
	return self;
}

- setRadius:(float)aFloat
/*
 * Sets the value for the radius of the PlotView's points.
 */
{
	radius = aFloat;
	return self;
}


- (float)radius
/*
 * Returns the value for the radius of the PlotView's points.
 */
{
	return radius;
}


- read:(NXTypedStream*)stream
/*
 * Unarchives the PlotView.  Initializes four of the PlotView's instance variables
 * to the values stored in the stream. 
 */
{
	[super read:stream];
	delegate = NXReadObject(stream);
	NXReadTypes(stream, "@@@f", &points, &crossCursor, &readOut, &radius);
	return self;
}

- write:(NXTypedStream*)stream
/*
 * Archives the PlotView by writing its important instance variables to the stream. 
 */
{
	[super write:stream];
	NXWriteObjectReference(stream, delegate);
	NXWriteTypes(stream, "@@@f", &points, &crossCursor, &readOut, &radius);
	return self;
}

- awake
/*
 * Finishes initializing a PlotView that has just been unarchived (see the read: method).
 */
{
	loadPSProcedures();
	return [super awake];
}


- (const char*)inspectorName
{
    return "PlotViewInspector";
}

- plot:sender
/*
 * Responds to a plot: message by asking the PlotView's delegate for a stream
 * containing the points to plot.  It then extracts number pairs from the stream
 * and creates NXPoint structures with them.  For each structure it creates, the 
 * PlotView sends itself a registerPoint: message to add the point to its list of 
 * points.  This simple parser ignores input it doesn't understand.
 */
{
	int	 	 c, sign, retval;
	float	 	 aNum;
	NXPoint	 	 aPoint;
	NXStream	*stream;
	BOOL		 processedLine = NO, gotFirst = NO, gotSecond = NO;
	
	if (delegate && [delegate respondsTo:@selector(plotView:providePoints:)])
		[delegate plotView:self providePoints:&stream];
	NXSeek(stream, 0, NX_FROMSTART);
	while ((c = NXGetc(stream)) != EOF){
		sign = 1;
		retval = getSeparator(stream);
		if (retval == 1) {
			c = NXGetc(stream);
		} else if (retval == -1) {
			break;
		}
		if (!NXIsDigit(c)) {
			if ((c == '-') && NXIsDigit(c = NXGetc(stream))){
					sign = -1;
			} else {
				while( (c != '\n') && (c != EOF)) {
					c = NXGetc(stream);
					processedLine = YES;
				}
			}
		}
		if (c == EOF)
			break;
		else if (processedLine) {
			processedLine = NO;
			continue;
		}
		aNum = getNumber(stream);
		if (!gotFirst) {
			aPoint.x = sign * aNum;
			gotFirst = YES;
		} else if (!gotSecond) {
			aPoint.y = sign * aNum;
			[self registerPoint:&aPoint];
			gotFirst = gotSecond = NO;
		} 
	}
	[self display];
	needsClearing = YES;
	return self;
}

int getSeparator(NXStream *stream)
/* 
 * Removes white space, commas, and plus signs from between 
 * number pairs.
 */
{
	int c, firstChar;
	
	NXUngetc(stream);
	c = firstChar = NXGetc(stream);
	while (NXIsSpace(c) || (c == ',') || (c == '+'))
		c = NXGetc(stream);
	/* 1 = ate something; 0 = ate nothing; -1 means EOF */
	if (c == firstChar)
		return 0;
	else if (c == EOF)
		return -1;
	NXUngetc(stream);
	return 1;
}

float getNumber(NXStream *stream)
/* 
 * Composes a floating point number from a string of number  
 * characters.
 */
{
	int c, i = 0;
	char temp[MAXNUMLENGTH];
	
	NXUngetc(stream);
	c = NXGetc(stream);
	while ((NXIsDigit(c) || (c == '.')) && (c != '\n') && (c != EOF)) {
		temp[i++] = c;
		c = NXGetc(stream);
	}
	NXUngetc(stream);
	temp[i] = 0;
	return (atof(temp));
}

@end