
/*
 * (a)  (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * (b)  If this Sample Code is distributed as part of the Display PostScript
 *	System Software Development Kit from Adobe Systems Incorporated,
 *	then this copy is designated as Development Software and its use is
 *	subject to the terms of the License Agreement attached to such Kit.
 *
 * (c)  If this Sample Code is distributed independently, then the following
 *	terms apply:
 *
 * (d)  This file may be freely copied and redistributed as long as:
 *	1) Parts (a), (d), (e) and (f) continue to be included in the file,
 *	2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * (e)  PostScript, Display PostScript, and Adobe are registered trademarks of
 *	Adobe Systems Incorporated.
 * 
 * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
 *	CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
 *	AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
 *	ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
 *	OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
 *	WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
 *	WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
 *	DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY, 
 *	FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
 *	OF THIRD PARTY RIGHTS.
 */

/*
 *	ControlView.m
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "ControlPoint.h"
#import "ControlView.h"
#import "ControlViewWraps.h"

#import <appkit/Button.h>
#import <appkit/Control.h>
#import <appkit/Matrix.h>
#import <appkit/NXImage.h>
#import <appkit/TextField.h>
#import <dpsclient/wraps.h>

@implementation ControlView

- initFrame:(const NXRect *) frameRect
{
	float		ViewRect[4];

	NXSize	size;

	[super initFrame:frameRect]; 
	[self allocateGState]; 

	NX_MALLOC(XYPoints, float, MAX_ARRAY);
	NX_MALLOC(OpsBuffer, char, MAX_UPATHOPS); 
	NX_MALLOC(XYBuffer, float, (MAX_UPATHPTS));

	ViewRect[0] = ViewRect[1] = 0.0;
	ViewRect[2] = bounds.size.width;
	ViewRect[3] = bounds.size.height;
	PSWDefsView (BGCOLOR, BGCOLORSTR, BGWIDTHSTR, ViewRect);
	
	srand(1);
	indexOfPoints = 0;

	size.width = size.height = FIGURESIZE;
	imageId = [[[NXImage alloc]  initSize:&size]  setFlipped:NO];

	[self	makePoints:self];
	
	return self;
}

/*
* Free the buffers
*/ 
- free
{
	if (OpsBuffer)
		NX_FREE(OpsBuffer);
	if (XYPoints)
		NX_FREE(XYPoints);
	if (XYBuffer)
		NX_FREE(XYBuffer);
    
	return [super free];
}

/* The following methods are used to obtain the ids of the appropriate text fields.  They
* were created by interface builder.
*/
-setDrawMethodsMatrix:anObject
{
	drawMethodsMatrix = anObject;
	return self;
}

-setDisplayTimesMatrix:anObject
{
	displayTimesMatrix = anObject;
	return self;
}

-setNumberPointsMatrix:anObject
{
	numberPointsMatrix = anObject;
	numberOfPoints = [numberPointsMatrix selectedTag];
	return self;
}

-setControlPoint:anObject
{
	controlPoint = anObject;
	return self;
}

-setButtonTitle:(OP) title
{
	if (title == FILL)
	{
		[[drawMethodsMatrix cellAt:2 :0] setTitle:"ufill"];
		[[drawMethodsMatrix cellAt:3 :0]setTitle:"rectfill"];
	}
	else
	{
		[[drawMethodsMatrix cellAt:2 :0] setTitle:"ustroke"];
		[[drawMethodsMatrix cellAt:3 :0]setTitle:"rectstroke"];
	}

	return self;
}

-setButtonEnable:(BOOL) enable
{
	[[drawMethodsMatrix cellAt:3 :0] setEnabled:enable];

	return self;
}

-psTrace:sender
{
	if (PSTrace == NO)
		[[sender selectedCell] setTitle:"Trace On"];
	else
		[[sender selectedCell] setTitle:"Trace Off"];

	PSTrace = !PSTrace;
	
	return self;
}

-configureDependence:sender
{
	[controlPoint  deviceDependence:[sender state]];
	[self  eraseTimes:self];

	return self;
}

-configurePoints:sender
{
	
	numberOfPoints = [numberPointsMatrix selectedTag];
	
	/* Choose a new starting index to instill a little change in the display. */
	indexOfPoints = (rand () % (MAX_POINTS - numberOfPoints + 1)) * 2;

	[self eraseTimes:self];
	return self;
}

/*
*   This method fills up the array of points randomly. If the number of points to draw
*   is different from the previous time, then a new place in the array will be selected for the
*   points (except for the case of the largest number of points, in this instance the 0 index will
*   be used). This random selection of indices introduces a little bit of change in the display.
*/
-makePoints:sender
{
	int  		i;
	
	for (i = 0; i < MAX_ARRAY; i = i +2)
	{
		XYPoints[i] = (rand () % (long)((bounds.size.width -8) * 100) + 400) * 0.01;
		XYPoints[i+1] = (rand () % (long)((bounds.size.height -8) * 100) + 400) * 0.01;
	}

	return self;
}

-eraseTimes:sender
{
	int		i;
	
	for (i = 0; i < [displayTimesMatrix cellCount]; i++)
		[[displayTimesMatrix cellAt:i :0] setStringValue:""];

	return self;
}

/* Draw using the method selected. */
-drawViewOne:sender
{
	int	i, row;
	
	row = [sender selectedRow];
		drawFlags.field = 0x80 >> row;
	
	[self display];

	return self;
}

/* Draw using all the methods. */
-drawViewAll:sender
{
	drawFlags.field = DRAWALL;

	[self display];

	return self;
}


/*
 *  Below are six methods that use different approaches to drawing control points.
 */

/* The drawing will center around the point passed in.*/
-drawBasic:(int) cell
{
	int		ElapsedTime, i;
	
	char		*basicProc, *basicOp;
	
	basicProc = [controlPoint  getBasicProc];
	basicOp = [controlPoint  getBasicOp];

	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
		PSWEraseView();
		PSsetlinewidth(0.15);
		PSWMarkTime(); NXPing();	
			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			for (i = indexOfPoints; i < indexOfPoints + (numberOfPoints*2); i = i+2)
				PSWBasic(XYPoints[i], XYPoints[i+1], basicProc, basicOp);

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
		PSWReturnTime (&ElapsedTime);
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}

/*
 * Cache a single user path. Translate to each location and then
 * image the user path.
 */
-drawUserCache:(int) cell
{
	int 		ElapsedTime, i, i_pt, i_op, j;
	
	char		*userOp, *userOpsArray;
	
	float		*userPtsArray; 

	userPtsArray = [controlPoint  getUserPtsArray];
	userOpsArray = [controlPoint  getUserOpsArray];
	userOp = [controlPoint  getUserOp];
	
	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
		PSWEraseView();
		PSsetlinewidth(0.15);
		PSWMarkTime ();  NXPing();
		
			/*
			*  Places a user path description for a generic control
			*  point into the OpsBuffer and XYBuffer.
			*/
			i_op = i_pt = 0;
			OpsBuffer[i_op++] = dps_ucache;
			
			/* The bounding box is the size of the control point. */
			XYBuffer[i_pt++] = -FIGURESIZE/2;
			XYBuffer[i_pt++] = -FIGURESIZE/2;
			XYBuffer[i_pt++] = FIGURESIZE/2;
			XYBuffer[i_pt++] = FIGURESIZE/2;
			OpsBuffer[i_op++] = dps_setbbox;
		
			XYBuffer[i_pt++] = 0;
			XYBuffer[i_pt++] = 0;
			OpsBuffer[i_op++] = dps_moveto;

			for (j = 1; j <= userPtsArray[0]; j++)
				XYBuffer[i_pt++] = userPtsArray[j];

			for (j = 1; j <= (int) userOpsArray[0]; j++)
				OpsBuffer[i_op++] = userOpsArray[j];

			/*
			* Performs an initial translate to the first location
			* and then performs a relative translation thereafter.
			*/
			PSgsave();
			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			PStranslate(XYPoints[indexOfPoints], XYPoints[indexOfPoints+1]);
			for (i = indexOfPoints; i < indexOfPoints + (numberOfPoints*2); i = i+2)
			{
				PSWUserPath(XYBuffer, i_pt, OpsBuffer, i_op, userOp);
				PStranslate(XYPoints[i+2] - XYPoints[i], XYPoints[i+3] - XYPoints[i+1]);
			}

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
			PSgrestore();
		PSWReturnTime (&ElapsedTime);
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}

/* Represents the control points as a large user path comprised of a set of sub user paths. */
-drawUserPath:(int) cell
{
	int 		ElapsedTime, i,  i_pt, i_op, j;
	
	char		*userOp, *userOpsArray;
	
	float		*userPtsArray; 

	userPtsArray = [controlPoint  getUserPtsArray];
	userOpsArray = [controlPoint  getUserOpsArray];
	userOp = [controlPoint  getUserOp];
	
	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
		PSWEraseView();
		PSsetlinewidth(0);
		PSWMarkTime ();  NXPing();
			/* Place the bounding box in the user path description. */
			XYBuffer[0] = bounds.origin.x;
			XYBuffer[1] = bounds.origin.y;
			XYBuffer[2] = bounds.origin.x + bounds.size.width;
			XYBuffer[3] = bounds.origin.y + bounds.size.height;
			OpsBuffer[0] = dps_setbbox;
		
			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			i = 0; i_pt = 4; i_op = 1;
			while (i < numberOfPoints * 2)
			{
				/* 
				* This check sends the array to the server if the array
				* limit has been reached.
				*/
				if ((i_pt + userPtsArray[0] > MAX_UPATHPTS) ||
				    (i_op + (int) userOpsArray[0] > MAX_UPATHOPS))
				{
					PSWUserPath(XYBuffer, i_pt, OpsBuffer, i_op, userOp);
					i_pt = 4; i_op = 1;
				}
			
				XYBuffer[i_pt++] = XYPoints[indexOfPoints + i++];
				XYBuffer[i_pt++] = XYPoints[indexOfPoints + i++];
				OpsBuffer[i_op++] = dps_moveto;

				for (j = 1; j <= userPtsArray[0]; j++, i_pt++)
					XYBuffer[i_pt] = userPtsArray[j];
				
				for (j = 1; j <= (int) userOpsArray[0]; j++, i_op++)
					OpsBuffer[i_op] = userOpsArray[j];
			}
			PSWUserPath(XYBuffer, i_pt, OpsBuffer, i_op, userOp);

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
		PSWReturnTime (&ElapsedTime);
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}

/*
 *  Here we have to calculate the offset from the center because
 *  rectfill starts drawing at the location passed in.
 */
-drawRectOp:(int) cell
{
	int		ElapsedTime, i, j;
	
	char		*rectOp;
	
	rectOp = [controlPoint  getRectOp];
	
	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
		PSWEraseView();
		PSsetlinewidth(0);
		PSWMarkTime (); NXPing();		
			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			/* Draw the rectangles if the array limit has been reached. */
			for (i = indexOfPoints, j = 0; i < indexOfPoints + (numberOfPoints*2); i = i+2, j = j+4)
			{
				/* Flush the buffer if full. */
				if (j+3 >  MAX_RECTPTS)
				{
					PSWRectDraw (XYBuffer, j, rectOp);
					j = 0;
				}
				XYBuffer[j] = XYPoints[i] - RECTOFFSET;
				XYBuffer[j+1] = XYPoints[i+1] - RECTOFFSET;
				XYBuffer[j+2] = RECTSIZE;
				XYBuffer[j+3] = RECTSIZE;
			}
			PSWRectDraw (XYBuffer, j, rectOp);

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
		PSWReturnTime (&ElapsedTime);
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}	

/*
* Draws the control point into the NXImage and then composites it at each
* control point location.
*/
-drawComposite:(int) cell
{
	int			ElapsedTime, i;

	NXPoint		point;

	[controlPoint drawImage:imageId];
	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
		PSWEraseView();
		PSWMarkTime (); NXPing();
			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			/* The adjustment centers the rectangle on the point location. */
			for (i = indexOfPoints; i < indexOfPoints + (numberOfPoints*2); i = i+2)
			{
				point.x = XYPoints[i] - FIGUREHALFSIZE;
				point.y = XYPoints[i+1] - FIGUREHALFSIZE;
				[imageId composite:NX_SOVER  toPoint:&point];
			}

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
		PSWReturnTime (&ElapsedTime);
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}

/*
* Performs a moveto show for each control point.
*/
-drawShow:(int) cell
{
	int		ElapsedTime, i;

	char		fontchar[2];

	fontchar[0] = [controlPoint getChar];
	fontchar[1] = 0;
	[controlPoint selectFont:FONTSIZE];

	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
	    PSgsave();
		PSWEraseView();
		PSWMarkTime (); NXPing();
			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			for (i = indexOfPoints; i < indexOfPoints + (numberOfPoints*2); i = i+2)
				PSWShow(XYPoints[i], XYPoints[i+1], fontchar);

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
		PSWReturnTime (&ElapsedTime);
	    PSgrestore();
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}

/*
* Places the characters and relative offsets into arrays and executes
* a single xyshow.
*/
-drawXYShow:(int) cell
{
	int		ElapsedTime, i, j;

	char		fontchar;

	fontchar = [controlPoint getChar];
	/*
	* Place the characters into the character string for xyshow.
	* Terminate the string with a NULL character.
	*/
	for (i = 0; i < numberOfPoints; i++)
		OpsBuffer[i] = fontchar;
	OpsBuffer[i] = 0;

	[controlPoint selectFont:FONTSIZE];
	
	[[displayTimesMatrix cellAt:cell :0] setStringValue:""];
	    PSgsave();
		PSWEraseView();
		PSWMarkTime (); NXPing();
			/* Calculate the displacement from the previous character. */
			for (i = indexOfPoints+2, j = 0; i < indexOfPoints + (numberOfPoints*2); i++, j++)
				XYBuffer[j] = XYPoints[i] - XYPoints[i-2];
			
			/*
			*  Provide a dummy set of displacements for the move after
			*  the last character has been shown.
			*/
			XYBuffer[j++] = 0;
			XYBuffer[j++] = 0;

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), YES);

			/* Establish a current point and then execute the xyshow. */
			PSWXYShow(XYPoints[indexOfPoints], XYPoints[indexOfPoints+1],
				OpsBuffer, XYBuffer, j);		

			if (PSTrace)
				DPSTraceContext(DPSGetCurrentContext(), NO);
		
		PSWReturnTime (&ElapsedTime);
	    PSgrestore();
	[[displayTimesMatrix cellAt:cell :0] setIntValue:ElapsedTime];

	return self;
}

/* Messaged by the "display" method.  This method should not be called directly. */
-drawSelf:(NXRect *)r :(int) count
{
		
	PSWEraseView();

	PSsetgray(NX_BLACK);
	if (drawFlags.flags.basic)
		[self drawBasic:0];
	if (drawFlags.flags.usercache)
		[self drawUserCache:1];
	if (drawFlags.flags.userpath)
		[self drawUserPath:2];
	if (drawFlags.flags.rectop  && [[drawMethodsMatrix cellAt:3 :0] isEnabled])
		[self drawRectOp:3];
	if (drawFlags.flags.composite)
		[self drawComposite:4];
	if (drawFlags.flags.show)
		[self drawShow:5];
	if (drawFlags.flags.xyshow)
		[self drawXYShow:6];

	return self;
}

@end
