/*
 * (C) 1990 by Adobe Systems Incorporated. All rights reserved.
 *
 * This file may be freely copied and redistributed as long as:
 *   1) This entire notice continues to be included in the file, 
 *   2) If the file has been modified in any way, a notice of such
 *      modification is conspicuously indicated.
 *
 * PostScript, Display PostScript, and Adobe are registered trademarks of
 * Adobe Systems Incorporated.
 * 
 * ************************************************************************
 * 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 NONINFINGEMENT OF THIRD PARTY RIGHTS.
 * ************************************************************************
 */


/*
 * DrawView.m
 * Created by Ken Anderson, Ken Fromm
 *
 * The purpose of the application is to show different methods for drawing 
 * paths and the times obtained for each method.  Lines are used in the 
 * construction of the paths but curves and arcs could be used as well.
 *
 * This file contains the methods for the DrawView class, a subclass of view.
 * The important methods to note are the methods to draw the lines.
 */

#import "DrawView.h"
#import "DrawViewWraps.h"
#import <appkit/Button.h>
#import <appkit/Control.h>
#import <appkit/Matrix.h>
#import <appkit/TextField.h>
#import <dpsclient/wraps.h>

@implementation DrawView

/* A gstate is allocated for this view and the clippling is set to NO.  
 * The gstate is used because the view is considered important enough to 
 * have one.  The clipping is set to NO because it can be expensive.  
 * In addition, the lines are constructed to fall entirely within the view.
 * The initialization is done here because the "initailize" method is too 
 * late for instance variables that are involved in the display of the view.
 */
+newFrame:(NXRect *) frm
{
	self = [super newFrame:frm]; 
	[self allocateGState]; 
	[self setClipping:NO];
	
	srand(1);
	TotalLines = 0;
		
	PSTrace = Random = NO;

	PSWDefs ();
	
	return self;
}

/* Since we allocate a gstate, why not free it.  In all likelihood, the 
 * gstate is freed anyways.  A freeGState method is available, however, 
 * so conservative programming dictates that it be used. */ 
- free
{
    [self  freeGState];
    
    return [super free];
}

/* This method is used to obtain the ids of the text fields to display 
 * the times. */
-setMatrixDisplayTimes:anObject;
{
	matrixDisplayTimes = anObject;
	return self;
}

/* These two methods are used to obtain the ids of the line color and 
 * line width sliders. */
-setSliderColor:anObject
{
	sliderColor = anObject;
	LineColor = [sliderColor floatValue];

	return self;
}

-setSliderWidth:anObject
{
	sliderWidth = anObject;
	LineWidth = [sliderWidth floatValue];

	return self;
}

/* Id of total lines text field. */
-setFieldTotalLines:anObject
{
	fieldTotalLines = anObject;

	return self;
}

/* Messaged from the button to select the line color and width.  The 
 * sender is a matrix of buttons (a one cell matrix).  The state of the 
 * button is used to key the enabling or disabling of the slider controls. 
 * At the end, the value of Random is flipped to make it sematically 
 * correct. Otherwise, YES would be no and NO would be yes. */
-selectColorWidth:sender
{
	Random = [[sender selectedCell] state];
	[sliderColor setEnabled:Random];
	[sliderWidth setEnabled:Random];
	Random = !Random;
	
	return self;
}

/* The sliders message these methods.  */
-changeLineColor:sender
{
	LineColor = [sender floatValue];
	
	return self;
}

-changeLineWidth:sender
{
	LineWidth = [sender floatValue];
	
	return self;
}

/* This method changes the title of the menu cell according to the
 * value of the PSTrace variable. */
-psTrace:sender
{
	if (PSTrace == NO)
	{
		[[sender selectedCell] setTitle:"PS Trace On"];
		DPSTraceContext(DPSGetCurrentContext(), YES);
	}
	else
	{
		[[sender selectedCell] setTitle:"PS Trace Off"];
		DPSTraceContext(DPSGetCurrentContext(), NO);
	}
	PSTrace = !PSTrace;
	
	return self;
}

/* Erases the times. Messaged when new lines are made or the lines are 
 * cleared. */
-eraseTimes:sender
{
	int		i;
	
	for (i = 0; i < [matrixDisplayTimes cellCount]; i++)
		[[matrixDisplayTimes cellAt:i :0] setStringValue:""];

	return self;
}

/* This method is messaged by the row of buttons to make lines.  The sender 
 * is a matrix so the tag of the selected cell is used to obtain the number 
 * of lines to make.  If the total number of lines exceeds the size of the 
 * line arrays, then system beep; otherwise,  randomly create the lines 
 * and populate the arrays.  If Random is set, the line color and width are 
 * randomly selected with each line.  This is the worst case displaying 
 * lines, especially with the OptimizedStroke.  If Random is not set then 
 * the current values of LineColor and LineWidth are used. */
-makeLines:sender
{
	int  Numsetlines, j;

	[self  eraseTimes:self];
	
	Numsetlines = [sender selectedTag];

	if (TotalLines >= MAXARRAY)
		NXBeep();
	else
	{
		if (TotalLines + Numsetlines > MAXARRAY)
			Numsetlines = MAXARRAY - TotalLines;

		for (j = TotalLines; j < TotalLines + Numsetlines; j++)
		{
			X[j] = rand () % ((int)bounds.size.width -4) +2;
			Y[j] = rand () % ((int)bounds.size.height -4) +2;
			X1[j] = rand () % ((int)bounds.size.width -4) +2;
			Y1[j] = rand () % ((int)bounds.size.height -4) +2;
			if (Random)
			{
				C[j] = (rand() % 1000) * 0.001;
				W[j] = (rand() % (MAXWIDTH * 10)) * 0.1;
			}
			else
			{
				C[j] = LineColor;
				W[j] = LineWidth;
			}
		}
		
		TotalLines += Numsetlines;
		[fieldTotalLines setIntValue:TotalLines];
	}

	return self;
}

-clearLines:sender
{
	TotalLines = 0;
	[fieldTotalLines setIntValue:TotalLines];

	[self  eraseTimes:self];
	
	return self;
}

/* Messaged by the method drawing matrix of buttons. Messages display 
 * which in turn will message drawSelf::. */
-drawViewOne:sender
{
	int	i, row;
	
	row = [sender selectedRow];
		drawFlags.field = 0x80 >> row;
	
	[self display];

	return self;
}

/* Messaged by "Draw All" button. Messages display which in turn will 
 * message drawSelf::.  */
-drawViewAll:sender
{
	drawFlags.field = DRAWALL;

	[self display];

	return self;
}

/* Below are five methods.  Each uses a different approach to drawing lines.
 * In some cases, the approaches are similar. Each is annotated. */

/* This approach uses the single operator function calls which are quite 
 * easy to use because no wraps are necessary.  The drawback is that each 
 * call is a separate message to the interpreter whereas a wrap is just 
 * one message.  This approach is fine for simple drawing but wraps should 
 * be used for anything over a few lines and repetitions. */
-drawSingleOps:(int) cell
{
	int		ElapsedTime,counter;
	
	[[matrixDisplayTimes cellAt:cell :0] setStringValue:""];

	PSWMarkTime(); NXPing();	
		PSsetgray (BGCOLOR);
		PSrectfill (0.0, 0.0, bounds.size.width, bounds.size.height);
		PSsetgray (BGSTRCOLOR);
		PSsetlinewidth (BGSTRWIDTH);
		PSrectstroke (0.0, 0.0, bounds.size.width, bounds.size.height);

		for (counter = 0; counter < TotalLines; counter++)
		{
			PSsetlinewidth(W[counter]);
			PSsetgray (C[counter]);
			PSmoveto (X[counter], Y[counter]);	
			PSlineto (X1[counter], Y1[counter]);
			PSstroke ();
		}	
	PSWReturnTime (&ElapsedTime);

	[[matrixDisplayTimes cellAt:cell:0] setIntValue:ElapsedTime];

	return self;
}

/* This approach creates wraps to erase the view and draw the lines.  The 
 * appropriate arguments are passed to each wrap.  Compare this method 
 * with the one that follows.  The time difference between the two is 
 * slight.  As in the singler operator approach, this method is fine for 
 * drawings that are displayed infrequently.  For drawings that are 
 * repeated, binding is suggested.  See the comments in DrawViewWraps.psw
 * for more information on binding. */
-drawWraps:(int) cell
{
	int		ElapsedTime, counter;
	float		ViewRect[4];
	
	[[matrixDisplayTimes cellAt:cell :0] setStringValue:""];

	PSWMarkTime(); NXPing();
		ViewRect[0] = ViewRect[1] = 0.0;
		ViewRect[2] = bounds.size.width;
		ViewRect[3] = bounds.size.height;
		PSWEraseView (BGCOLOR, BGSTRCOLOR, BGSTRWIDTH, ViewRect);
		
		for (counter = 0; counter < TotalLines; counter++)
		{
			PSWDrawLine(W[counter], C[counter], X[counter], Y[counter],
				X1[counter], Y1[counter]);
		}
	PSWReturnTime(&ElapsedTime);

	[[matrixDisplayTimes cellAt:cell:0] setIntValue:ElapsedTime];

	return self;
}

/* The wrap calls used in this approach make use of definitions contained 
 * in PSWDefs().  The definitions bind their operators which simply means 
 * that the name lookup occurs at definition time only once rather than 
 * with each interpretation of the definition (which can occur many times).
 * This approach is recommended for more than the simplest drawing.  
 * Drawings with lines that are of uniform line color and width might benefit
 * from the opitimized stroke method.  Random line colors and widths show 
 * this to be the fastest. */
-drawWrapsBind:(int) cell
{
	int		ElapsedTime, counter;
	float		ViewRect[4];
	
	[[matrixDisplayTimes cellAt:cell :0] setStringValue:""];

	PSWMarkTime(); NXPing();
		ViewRect[0] = ViewRect[1] = 0.0;
		ViewRect[2] = bounds.size.width;
		ViewRect[3] = bounds.size.height;
		PSWEraseViewBind (BGCOLOR, BGSTRCOLOR, BGSTRWIDTH, ViewRect);
		
		for (counter = 0; counter < TotalLines; counter++)
		{
			PSWDrawLineBind(W[counter], C[counter], X[counter], Y[counter],
					X1[counter], Y1[counter]);
		}
	PSWReturnTime(&ElapsedTime);

	[[matrixDisplayTimes cellAt:cell:0] setIntValue:ElapsedTime];

	return self;
}	


/* This approach is uses the interpreter to loop through the array and 
 * draw the lines.  As a result, a good deal of stack manipulation must 
 * be performed in the wrap.  The timing results show this to be a poor 
 * approach.  Not recommended at this time. */
-drawWrapsRepeat:(int) cell
{
	int		ElapsedTime;
	float		ViewRect[4];
	
	[[matrixDisplayTimes cellAt:cell :0] setStringValue:""];

	PSWMarkTime(); NXPing();
		ViewRect[0] = ViewRect[1] = 0.0;
		ViewRect[2] = bounds.size.width;
		ViewRect[3] = bounds.size.height;
		PSWEraseViewBind(BGCOLOR, BGSTRCOLOR, BGSTRWIDTH, ViewRect);
		PSWDrawLineRepeatBind(W, C, X, Y, X1, Y1, TotalLines);
	PSWReturnTime(&ElapsedTime);

	[[matrixDisplayTimes cellAt:cell:0] setIntValue:ElapsedTime];

	return self;
}

/* This approach builds the path with successive moveto's and lineto's 
 * until the end of the array or a change in line color or width occurs.  
 * At this point, the path is stroked.  The timing studies show this to 
 * be an efficient approach when the line color and width appear in 
 * non-random order.  As a result, this approach might be used to display 
 * a grid or other similar uniform drawing.  NOTE: as of this writing the 
 * path stack contains a 1500 point limit.  Therefore, a check should be 
 * included to stroke if the limit has been reached.  Otherwise
 * a limiterror may occur. */
-drawOptimizedStroke:(int) cell
{
	int		ElapsedTime,counter;
	float		ViewRect[4];
	
	[[matrixDisplayTimes cellAt:cell :0] setStringValue:""];

	PSWMarkTime(); NXPing();
		ViewRect[0] = ViewRect[1] = 0.0;
		ViewRect[2] = bounds.size.width;
		ViewRect[3] = bounds.size.height;
		PSWEraseViewBind(BGCOLOR, BGSTRCOLOR, BGSTRWIDTH, ViewRect);
		
		for (counter = 0; counter < TotalLines; counter++)
		{
			PSWMakeLineBind(X[counter], Y[counter], X1[counter], Y1[counter]);

			if (!((counter != TotalLines -1) 
			    && (C[counter] == C[counter + 1]) 
			    && (W[counter] == W[counter+1])))
			{
				PSWStrokeLineBind(W[counter], C[counter]);
			}
		}
	PSWReturnTime(&ElapsedTime);

	[[matrixDisplayTimes cellAt:cell:0] setIntValue:ElapsedTime];

	return self;
}

/* Messaged by the "display" method.  This method should not be called 
 * directly. */
-drawSelf:(NXRect *)r :(int) count
{	
	float	ViewRect[4];
	
	ViewRect[0] = ViewRect[1] = 0.0;
	ViewRect[2] = bounds.size.width;
	ViewRect[3] = bounds.size.height;
	PSWEraseViewBind (BGCOLOR, BGSTRCOLOR, BGSTRWIDTH, ViewRect);

	if (drawFlags.flags.singleops)
		[self drawSingleOps:0];
	if (drawFlags.flags.wraps)
		[self drawWraps:1];
	if (drawFlags.flags.bind)
		[self drawWrapsBind:2];
	if (drawFlags.flags.repeat)
		[self drawWrapsRepeat:3];
	if (drawFlags.flags.optimized)
		[self drawOptimizedStroke:4];

	return self;
}

@end
