ftp.nice.ch/peanuts/GeneralData/Documents/adobe/DPS.Purple.HitDetect.tar.gz#/NX_HitDetect/DrawingView.m

This is DrawingView.m in view mode; [Download] [Up]

/*
 * (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.
 */

/*
 *	DrawingView.m
 *
 *	This view represents the page that the bezier is drawn onto. It is
 *	a subview of the doc view and can be zoomed and reduced.
 *
 *	The buffers are used to allow for fast redrawing and moving. 
 *	BuffeAlpha is a view inside a ClipView in a plain window and
 *	bufferBeta is a view in a plain window without a ClipView. The
 *	drawing is performed in the alpha buffer and then this is
 *	composited into the buffered window. When the bezier is
 *	selected and changed, the drawing of the new bezier 
 *	occurs in the buffered window. With each mouse drag the
 *	old image in alpha is first composited into the window and
 *	then the new bezier drawn atop the old image.
 *
 *	When the image is moved, it is first drawn into the beta
 *	buffer and then simply composited to a new location. If
 *	the image is larger than the beta buffer, then the image
 *	is redrawn instead of using the buffer.
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
 */

#import "Bezier.h"
#import "HitPointView.h"
#import "DetectApp.h"
#import "DocView.h"
#import "DrawingView.h"
#import "DrawingViewWraps.h"
#import <appkit/Application.h>
#import <appkit/Cell.h>
#import <appkit/Cursor.h>
#import <appkit/Matrix.h>
#import <appkit/View.h>
#import <appkit/ClipView.h>
#import <appkit/Window.h>
#import <appkit/nextstd.h>
#import <dpsclient/wraps.h>
#import <appkit/timer.h>

@implementation DrawingView

static char	fontname[ ] = "ControlPointsFont";

/*
*	Timers used to automatically scroll when the mouse is
*	outside the drawing view and not moving.
*/
static void startTimer(NXTrackingTimer ** timer, int *timermask, id window)
{
	if (!*timer)
	{
		*timer = NXBeginTimer(NULL, 0.15, 0.2);
		*timermask = NX_TIMERMASK;
		[window addToEventMask:NX_TIMERMASK];
	}
}

static void stopTimer(NXTrackingTimer **timer, int *timermask, id window)
{
	if (*timer)
	{
		NXEndTimer(*timer);
		*timer = NULL;
		*timermask = 0;
		[window removeFromEventMask:NX_TIMERMASK];			
	}
}

static void compositeBuffer(int gstate, const NXRect * srce, const NXPoint * dest, int op)
{
  	PScomposite(NX_X(srce), NX_Y(srce), NX_WIDTH(srce), NX_HEIGHT(srce),
                gstate, dest->x, dest->y, op);
}

/*
*	Instance variables - hitPoint holds the usre path for
*	hit detection while drawUpath is a scratch buffer used for
*	several misc. purposes.
*/
- initFrame:(NXRect *) frm
{	
	[super initFrame:frm]; 
	[[self allocateGState] setClipping:NO]; 

	[self  createGrid];
	PSWDefineFont(fontname);

	/*
	*  Normally these buffers might be allocated in the Application subclass and kept
	*  as an application wide resource for use with multiple documents. It is allocated
	*  here to simplify the code.
	*/
	NX_MALLOC(drawUpath.pts, float, PTS_UPATH_BUFFER);
	NX_MALLOC(drawUpath.ops, char, OPS_UPATH_BUFFER);

	NX_MALLOC(hitPoint.pts, float, NUM_POINTS_HIT);
	NX_MALLOC(hitPoint.ops, char, NUM_OPS_HIT);
	[self initializeHitPoint];

	bufferAlpha = [NXApp getBufferAlpha];
	bufferBeta = [NXApp getBufferBeta];

	selected = NO;
	timer = NULL;
	
	return self;
}

/*
 *  The user path uses relative movements to reduce the number
 *  of points that have to be inserted each mouse down.
 */
- initializeHitPoint
{
	int		i;

	for (i = 0; i < NUM_POINTS_HIT; i++)
		hitPoint.pts[i] = 0;			
	hitPoint.num_pts = i;
 	
	hitPoint.ops[0] = dps_setbbox;
	hitPoint.ops[1] = dps_moveto;
	hitPoint.ops[2] = dps_rlineto;
	hitPoint.ops[3] = dps_rlineto;
	hitPoint.ops[4] = dps_rlineto;
	hitPoint.ops[5] = dps_closepath;
	hitPoint.num_ops = 6;

	return self;
}

/*
*	Create a cached user path and store it in the server. The grid does not change
*	so it can be stored in the server instead of being resent each time. Drawing the
*	entire grid each time instead of just the portion that is necessary is faster in
*	this case because the cache is every time. When selectively drawing only 
*	the portion necessary, a different path is cached thereby reducing the number
*	of cache hits.
*	
*/
- createGrid
{
	int		i, j, num_pts, num_ops;

	float		pt;

	char		*ops;

	float		*pts;

	num_ops = ceil((bounds.size.width/SIZEGRID + bounds.size.height/SIZEGRID) * 2) + 2;
	num_pts = num_ops * 2 + 4;
	NX_MALLOC(pts, float, num_pts); 
	NX_MALLOC(ops, char, num_ops);

	i = j = 0;
	ops[j++] = dps_ucache;

	pts[i++] = bounds.origin.x;
	pts[i++] = bounds.origin.y;
	pts[i++] = bounds.origin.x + bounds.size.width;
	pts[i++] = bounds.origin.y + bounds.size.height;
	ops[j++] = dps_setbbox;

	for (pt = bounds.origin.x; pt < bounds.origin.x + bounds.size.width; pt += SIZEGRID)
	{
		pts[i++] = pt;
		pts[i++] = bounds.origin.y;
		ops[j++] = dps_moveto;
		
		pts[i++] = pt;
		pts[i++] = bounds.origin.y + bounds.size.height;
		ops[j++] = dps_lineto;
	}

	for (pt = bounds.origin.y; pt < bounds.origin.y + bounds.size.height; pt += SIZEGRID)
	{
		pts[i++] = bounds.origin.x;
		pts[i++] = pt;
		ops[j++] = dps_moveto;
		
		pts[i++] = bounds.origin.x + bounds.size.width;
		pts[i++] = pt;
		ops[j++] = dps_lineto;
	}

	/* Store it as a user object first by placing it on the stack and then defining it */
	PSWSetUpath(pts, i, ops, j);
	gridUpath = DPSDefineUserObject(gridUpath);

	if (pts)
		NX_FREE(pts);
	if (ops)
		NX_FREE(ops);

	return self;
}

/* When the bezier is created, make sure it can be seen. */
- createObject
{
	NXRect	visRect;

	[self  getVisibleRect:&visRect];
	objectId = [[Bezier alloc]  initFrame:&visRect];
	
	return self;
}

- free
{
	if (drawUpath.pts)
		NX_FREE(drawUpath.pts);
	if (drawUpath.ops)
		NX_FREE(drawUpath.ops);

	if (hitPoint.pts)
		NX_FREE(hitPoint.pts);
	if (hitPoint.ops)
		NX_FREE(hitPoint.ops);

	return [super free];
}


/*
*	Change the menu title and toggle the trace boolean
*/
- traceDetect:sender
{
	if (!tracedetect)
		[[sender selectedCell] setTitle:"Hit Detection On"];
	else
		[[sender selectedCell] setTitle:"Hit Detection Off"];

	tracedetect = !tracedetect;
	
	return self;
}

- traceDraw:sender
{
	if (!tracedraw)
		[[sender selectedCell] setTitle:"Drawing On"];
	else
		[[sender selectedCell] setTitle:"Drawing Off"];

	tracedraw = !tracedraw;
	
	return self;
}

- drawGrid:sender
{
	drawgrid = [sender state];
	
	[self  display];

	return self;
}

- compositeBufferAlpha:(NXRect *) r
{
	NXRect		viewFrame;

	if (!r)
	{
		[self  getVisibleRect:&viewFrame];
		r = &viewFrame;
	}

	[bufferAlpha  lockFocus];
		compositeBuffer([self  gState], r, &r->origin, NX_COPY);
	[bufferAlpha unlockFocus];
	
	return self;
}

/*
*	When the drawing view moves, then move bufferAlpha so that
*	the composites from bufferAlpha are taken from the correct spot.
*	The beta buffer does not need to be moved because the drawing
*	always starts in the lower left corner of the beta buffer.
*/
- moveTo:(NXCoord)x :(NXCoord)y
{
	[super moveTo:x :y];
	[bufferAlpha moveTo:x :y];

	return self;
}

/*
*	A scale that happens to this view should be reflected in the alpha and
*	beta buffers.
*/
- scale:(NXCoord)x :(NXCoord)y
{
	[super scale:x :y];
	[bufferAlpha scale:x :y];
	[bufferBeta scale:x :y];

	return self;
}

/*
*	A sizeTo should also be reflected in the alpha and beta buffers.
*/
- sizeTo:(NXCoord)x :(NXCoord)y
{
	[super sizeTo:x :y];
	[bufferAlpha sizeTo:x :y];
	[bufferBeta sizeTo:x :y];

	return self;
}

/*
*	Constrain the point within the view. An offset is needed because when
*	an object is moved, it is often grabbed in the center of the object. If the
*	lower left offset and the upper right offset were not included then part of
*	the object could be moved off of the view. (In some applications, that might
*	be allowed but in this one the object is constrained to always lie in the
*	page.)
*/
- constrainPoint:(NXPoint *)aPt  withOffset:(const NXSize*)llOffset  :(const NXSize*)urOffset
{
	float			margin;

	NXPoint		viewMin, viewMax;

	margin = ceil(FONTSIZE/2);

	viewMin.x = bounds.origin.x + llOffset->width + margin;
	viewMin.y = bounds.origin.y + llOffset->height + margin;

	viewMax.x = bounds.origin.x + bounds.size.width - urOffset->width - margin;
	viewMax.y = bounds.origin.y + bounds.size.height  - urOffset->height - margin;

	aPt->x = MAX(viewMin.x, aPt->x);
	aPt->y = MAX(viewMin.y, aPt->y);

	aPt->x = MIN(viewMax.x, aPt->x);	
	aPt->y = MIN(viewMax.y, aPt->y);

	return self;
}

/*
*	Constrain a rectangle within the view.
*/
- constrainRect:(NXRect *)aRect
{
	float			margin;
	
	NXPoint		viewMin, viewMax;

	margin = ceil(FONTSIZE/2);

	viewMin.x = bounds.origin.x + margin;
	viewMin.y = bounds.origin.y + margin;

	viewMax.x = bounds.origin.x + bounds.size.width  - aRect->size.width - margin;
	viewMax.y = bounds.origin.y + bounds.size.height - aRect->size.height - margin;

	aRect->origin.x = MAX(viewMin.x, aRect->origin.x);
	aRect->origin.y = MAX(viewMin.y, aRect->origin.y);

	aRect->origin.x = MIN(viewMax.x, aRect->origin.x );	
	aRect->origin.y = MIN(viewMax.y, aRect->origin.y);

	return self;
}

- (BOOL) isScrolling
{
	return scrolling;
}

/*
 * Scrolls to rectangle passed in if it is not in visible portion of the view.
 * If the rectangle is larger in width or height than the view, the scrollRectToVisible
 * method is not altogether consistent. As a result, the rectangle contains only
 * the image that was previously visible.
 */
 - scrollToRect:(const NXRect *)toRect
{
	NXRect		visRect, tooRect;

	[self getVisibleRect:&visRect];
	if (!NXContainsRect(&visRect, toRect))
	{
		scrolling = YES;
		[window disableFlushWindow];
		[self scrollRectToVisible:toRect];
		[window reenableFlushWindow];
		scrolling = NO;

		startTimer(&timer, &timermask, window);
	}
	else
		stopTimer(&timer, &timermask, window);

	return self;
}

/*
 *	Redraws the graphic. The image from the alpha buffer is composited
 *	into the window and then the changed object is drawn atop the
 *	old image. A copy of the image is necessary because when the
 *	window is scrolled the alpha buffer is also scrolled. When the
 *	alpha buffer is scrolled, the old image might have to be redrawn.
 *	As a result, a copy is created and the changes performed on the
 *	copy.  Care is taken to limit the amount of area that must be
 *	composited and redrawn. A timer is started is the scrolling rect
 *	moves outside the visible portion of the view.
 */

- redrawObject:(int) pt_num :(NXRect *)redrawRect
{
	id			copyId;

	BOOL		tracking = YES;

	int			old_mask;

	NXPoint		pt, pt_last, pt_old, delta;
	
	NXRect		rect_now, rect_last, rect_scroll, rect_vis;

	NXEvent		*event;

	/*
	*  Composite the current image in the window into the first buffer.
	*/
	[self  compositeBufferAlpha:NULL];

	/*
	*  Create a copy of the selected object. If we scroll we will need
	*  to redraw the old curve. If we do not create a copy we will
	*  not have an old curve.
	*/
	copyId = [objectId  copy];
	[copyId  copyPts:objectId];

	timermask = 0;

	if (tracedraw)
		DPSTraceContext(DPSGetCurrentContext(), YES);
	
	/*
	*  The rect_scroll will cause scrolling whenever it goes outside the
	*  visible portion of the view.
	*/
	[self  getVisibleRect:&rect_vis];
	[copyId  getScrollRect:pt_num :&rect_scroll];
	NXIntersectionRect(&rect_vis, &rect_scroll);

	[copyId  getBounds:&rect_now  withKnobs:YES];
	*redrawRect = rect_last = rect_now;

	[copyId  getPoint:pt_num :&pt_last];
	pt_old = pt_last;

	old_mask = [window addToEventMask:
		NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	event = [NXApp getNextEvent:
		NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	if (event->type != NX_MOUSEUP)
	{
		while (tracking)
		{
			/*
			*  If its a timer event than use the last point. It will be converted to
			*  into the view's coordinate so it will appear as a new point.
			*/
			if (event->type == NX_TIMER)
				pt = pt_old;
			else
				pt = pt_old = event->location;

			[self convertPoint:&pt fromView:nil];
			[copyId constrainPoint:&pt andNumber:pt_num toView:self];

			delta.x = pt.x - pt_last.x;
			delta.y = pt.y - pt_last.y;

			if (delta.x || delta.y)
			{
				/* Change the point location and get the new bounds. */
				[copyId setPoint:pt_num :&delta];
				[copyId getBounds:&rect_now  withKnobs:YES];

				/* Change the scrolling rectangle. */
				NXOffsetRect(&rect_scroll, delta.x, delta.y);
				[self  scrollToRect:&rect_scroll];

				/* Composite the old image and then redraw the new curve. */
				compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY);
				[self  drawObject:NULL  for:copyId  withUcache:NO];
				[self  drawControl:NULL  for:copyId];

				/* Flush the drawing so that it's consistent. */
				[window flushWindow];
				NXPing();
				
				rect_last = rect_now;
				pt_last = pt;
			}
			else
				stopTimer(&timer, &timermask, window);

			event = [NXApp getNextEvent:NX_MOUSEUPMASK|
							NX_MOUSEDRAGGEDMASK|timermask];
			tracking = (event->type != NX_MOUSEUP);
		}
		stopTimer(&timer, &timermask, window);
	}
	[window setEventMask:old_mask];

	[objectId  free];
	objectId = copyId;

	/*
	*  Figure out the area that has to be redrawn
	*  (the union of the old and the new rectangles).
	*/
	NXUnionRect(&rect_now, redrawRect);

	return self;
}

/*
 *	Moves the graphic object. If the selected graphic can fit in the beta
 *	buffer than the image is drawn into this buffer and then composited
 *	to each new location. The image is redrawn at the new location
 *	when the user releases the mouse button. If the selected graphic
 *	cannot fit in the beta buffer than it is redrawn each time. This can
 *	happen when the drawing view is scaled upwards.
 *
 *	The offsets constrain the selected object to stay within the dimensions
 *	of the view. 
 */
- moveObject:(NXEvent *)event :(NXRect *)redrawRect
{
	BOOL		tracking = YES, beta;

	int			old_mask;

	float			scale;

	NXSize		llOffset, urOffset;

	NXPoint		pt, pt_last, pt_old, delta, delta_scroll;

	NXRect		rect_now, rect_start, rect_last, rect_scroll, rect_vis;

	/*
	*  Composite the current image in the window
	*  into the first buffer.
	*/
	[self  compositeBufferAlpha:NULL];

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

	/* Check whether the object can fit in the second buffer. */
	[[bufferBeta superview] getFrame:&rect_now];
	[objectId  getBounds:&rect_start  withKnobs:YES];
	scale = [superview  scale];  
	if (rect_now.size.width > rect_start.size.width * scale &&
	    rect_now.size.height > rect_start.size.height * scale)
	{
		[bufferBeta  setDrawOrigin:rect_start.origin.x  :rect_start.origin.y];
		[bufferBeta  lockFocus];
			PSsetgray(NX_WHITE);
			PSsetalpha(0.0);
			NXRectFill(&rect_start);
			PSsetalpha(1.0);
			[self  drawObject:NULL  for:objectId  withUcache:NO];
			[self  drawControl:NULL  for:objectId];
		[bufferBeta  unlockFocus];
		beta = YES;
	}
	else
		beta = NO;

	/*
	*  Get the scrolling rectangle. If it turns out to be the visible portion of the window
	*  then reduce it a bit so that the user is not playing pong when trying to
	*  move the image.
	*/
	[objectId  getScrollRect:-1  :&rect_scroll];
	[self  getVisibleRect:&rect_vis];
	if (NXContainsRect(&rect_scroll, &rect_vis))
	{
		rect_scroll = rect_vis;
		NXInsetRect(&rect_scroll, rect_scroll.size.width * .20, rect_scroll.size.height * .20);
	}
	else
		NXIntersectionRect(&rect_vis, &rect_scroll);

	*redrawRect = rect_now = rect_last = rect_start;
	delta_scroll.x = rect_scroll.origin.x - rect_now.origin.x;
	delta_scroll.y = rect_scroll.origin.y - rect_now.origin.y;

	timermask = 0;
	pt_last = pt_old = event->location;
	[self convertPoint:&pt_last  fromView:nil];
	
	/* Calculate where the mouse point falls relative to the object. */
	llOffset.width = pt_last.x - rect_start.origin.x;
	llOffset.height = pt_last.y - rect_start.origin.y;	
	urOffset.width = rect_start.origin.x + rect_start.size.width - pt_last.x;
	urOffset.height = rect_start.origin.y + rect_start.size.height - pt_last.y;

	old_mask = [window addToEventMask:
			NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	event = [NXApp getNextEvent:
			NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
	if (event->type != NX_MOUSEUP)
	{
		while (tracking)
		{			
			if (event->type == NX_TIMER)
				pt = pt_old;
			else
				pt = pt_old = event->location;
				
			[self convertPoint:&pt fromView:nil];
			[self constrainPoint:&pt  withOffset:&llOffset :&urOffset];
			[self constrainPoint:&pt_last  withOffset:&llOffset :&urOffset];
			delta.x = pt.x - pt_last.x;
			delta.y = pt.y - pt_last.y;

			if (delta.x || delta.y)
			{
				NXOffsetRect(&rect_now, delta.x, delta.y);
				[self  constrainRect:&rect_now];

				rect_scroll.origin.x = rect_now.origin.x + delta_scroll.x;
				rect_scroll.origin.y = rect_now.origin.y + delta_scroll.y;
				[self  scrollToRect:&rect_scroll];

				/*
				*  Copy the old image into the window. If using the second buffer, copy
				*  it atop the old buffer. Otherwise, translate and redraw.
				*/
				compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY);
				if (beta)
				{
					compositeBuffer([bufferBeta gState], &rect_start, &rect_now.origin, 
								NX_SOVER);
				}
				else
				{
					PSgsave();
					PStranslate(rect_now.origin.x - rect_start.origin.x,
						rect_now.origin.y - rect_start.origin.y);
					[self  drawObject:NULL  for:objectId  withUcache:YES];
					[self  drawControl:NULL  for:objectId];
					PSgrestore();
				}

				/* Flush the drawing so that it's consistent. */
				[window flushWindow];
				NXPing();
				
				rect_last = rect_now;
				pt_last = pt;
			}
			else
				stopTimer(&timer, &timermask, window);

			event = [NXApp getNextEvent:NX_MOUSEUPMASK|
						NX_MOUSEDRAGGEDMASK|timermask];

			tracking = (event->type != NX_MOUSEUP);
		}
		stopTimer(&timer, &timermask, window);

		delta.x = rect_now.origin.x - rect_start.origin.x;
		delta.y = rect_now.origin.y - rect_start.origin.y;		
		[objectId moveAll:&delta];
	}

	NXUnionRect(&rect_now, redrawRect);
	[window setEventMask:old_mask];

	return self;
}

/* Check to see whether a control point has been hit. */
 - (BOOL) checkControl:(const NXPoint *) p :(int *) pt_num
 {
 	BOOL	hit;
	
	float		controlsize, hitsetting;

	NXRect	hitRect;

	controlsize = [superview  controlPointSize];
	hitsetting = [superview  hitSetting];	
	NXSetRect(&hitRect, p->x - hitsetting/2, p->y - hitsetting/2, hitsetting, hitsetting);
	hit = [objectId  hitControl:&hitRect :pt_num :controlsize];
	
	return hit;
 
 }

/*
 *  A method in the DrawingView class. Invoked by the mouse down
 *  event. The mouse point and the current hit setting dimensions
 *  are placed in the hit point user path before invoking the
 *  hitObject method of the Bezier object.
 */
- (BOOL) checkObject:(const NXPoint *) p
 {
 	BOOL	hit;

	float		hitsetting;
	
	hitsetting = [superview  hitSetting];	

	/*  Bounding Box */
	hitPoint.pts[0] = floor(p->x - hitsetting/2);
	hitPoint.pts[1] = floor(p->y - hitsetting/2);
	hitPoint.pts[2] = ceil(p->x + hitsetting/2);
	hitPoint.pts[3] = ceil(p->y + hitsetting/2);
	
	/*  Moveto */
	hitPoint.pts[4] = p->x - hitsetting/2;
	hitPoint.pts[5] = p->y - hitsetting/2;

	/* Rlineto's */
	hitPoint.pts[7] = hitsetting;
	hitPoint.pts[8] = hitsetting;
	hitPoint.pts[11] = -hitsetting;

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

	hit = [objectId hitObject:&hitPoint];
	
	if (tracedetect)
		DPSTraceContext(DPSGetCurrentContext(), NO);
	
	return hit;
 
 }
 
 /*
 *	If the docview is zooming, then scale the drawing view. Else
 *	check for hit detection on the bezier or the control points.
 */
- mouseDown:(NXEvent *)event
{
	BOOL		redraw = YES;
	
	int			pt_num;

	NXPoint		p;
	
	NXRect		drawRect;
 
 	p = event->location; 
	if ([superview  isZooming])
		return [nextResponder  scaleDrawView:self  toPoint:&p];

	[self convertPoint:&p fromView:nil];
	[self  lockFocus];
		if (!selected)
		{
			if ([self checkObject:&p])
			{
				[objectId	getBounds:&drawRect  withKnobs:YES];
				selected = YES;
			}
			else
				redraw = NO;	
		}
		else
		{
			if ([self checkControl:&p :&pt_num])
				[self  redrawObject:pt_num :&drawRect];			
			else if ([self checkObject:&p])
				[self  moveObject:event :&drawRect];	
			else
			{
				[objectId	getBounds:&drawRect  withKnobs:YES];
				selected = NO;
			}
		}

		/*
		*  The view has already been focused and we know what
		*  has to be redrawn so call drawSelf:: instead of display
		*/
		if (redraw)
		{
			[self drawSelf:&drawRect :1];
			[window flushWindow];
			NXPing();
		}

	[self  unlockFocus];	

	return self;
}

/*
*	Draw the object. This is a relatively simple drawing operation. More
*	sophiticated drawing apps would probably want to pass the current
*	drawing parameters to avoid unnecessarily resetting them with
*	each drawing.
*/
- drawObject:(NXRect *)r  for:object  withUcache:(BOOL)flag
{
	[object drawObject:r  withUcache:flag];

	return self;
}

/*
*	Draw the control points using the user path buffer to hold
*	the data for the xyshow. Having the object fill in the data 
*	structure allows for combining the control points for multiple
*	objects. This increases drawing performance by reducing
*	the number of operations.
*/
- drawControl:(NXRect *)r  for:object
{
	int			i;

	NXPoint		lastpoint;

	PSselectfont(fontname, [superview controlPointSize]); 
	PSsetgray(NX_BLACK);
	PSsetlinewidth(0.15);

	lastpoint.x = 0;
	lastpoint.y = 0;

	drawUpath.num_ops = 0;
	drawUpath.num_pts = 0;
	[object  putControlUPath:&drawUpath forRect:r  :&lastpoint];
	if (drawUpath.num_ops > 0)
	{
		drawUpath.ops[drawUpath.num_ops] = 0;
		drawUpath.pts[drawUpath.num_pts] = 0;
		drawUpath.pts[drawUpath.num_pts + 1] = 0;

		PSWDrawControlPoints(drawUpath.pts[0], drawUpath.pts[1],
			&drawUpath.pts[2], drawUpath.num_pts, drawUpath.ops);
	}

	drawUpath.num_ops = 0;
	drawUpath.pts[0] = 99999;
	drawUpath.pts[1] = 99999;
	drawUpath.pts[2] = -99999;
	drawUpath.pts[3] = -99999;
	drawUpath.num_pts = 4;
	[object  putControlLinesUPath:&drawUpath  forRect:r];
	if (drawUpath.num_ops > 0)
	{
		DPSDoUserPath(&drawUpath.pts[4], drawUpath.num_pts - 4, dps_float,
			drawUpath.ops, drawUpath.num_ops, drawUpath.pts, dps_ustroke);
	}
	
	return self;
}

/*
*	Compare the bounds of the object with the rectangle to draw in order to
*	eliminate unnecessary drawing.
*/
- drawSelf:(NXRect *)r :(int) count
{
	NXRect		objRect;

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

	PSsetgray(NX_WHITE);
	NXRectFill(r);

	if (drawgrid)
	{
		PSgsave();
		PSsetgray(COLORGRID);
		PSsetlinewidth(WIDTHGRID);
		NXRectClip(r);
		PSWUpathStroke(gridUpath);
		PSgrestore();
	}

	[self drawObject:r  for:objectId  withUcache:YES];
	if (selected && NXDrawingStatus == NX_DRAWING)
		[self  drawControl:r  for:objectId];

	if (scrolling)
		[self  compositeBufferAlpha:r];

	if (tracedraw)
	{
		if (![superview isTraceZooming]  || ![superview isZooming])
			DPSTraceContext(DPSGetCurrentContext(), NO);
	}

	return self;			
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.