ftp.nice.ch/peanuts/GeneralData/Documents/adobe/DPS.Purple.Clock.tar.gz#/NX_Clock/ClockView.m

This is ClockView.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.
 */

/*
*	ClockView.m
*
*	This class handles the drawing of the clock and the moving of the alarm.
*	The clock face is drawn into a bitmap and then composited into the buffered
*	window before drawing the hands. The hands are stored in the server
*	as user paths. Each hand also has a graphic state associated with it. Before
*	hand is drawn, its graphic state is installed and then rotated to its current
*	angle and then the user path is rendered.
*
*	An animator object has been borrowed from the stopwatch implementation in
*	the NeXTDeveloper directory. This object makes adjustments to the timed
*	entry in order to keep the timing up to date.
 *
 *	Version:	2.0
 *	Author:	Ken Fromm
 *	History:
 *			03-07-91		Added this comment.
*/

#import "Animator.h"
#import "ClockView.h"
#import "ClockViewWraps.h"

#import <appkit/Cell.h>
#import <appkit/Control.h>
#import <appkit/NXImage.h>
#import <appkit/View.h>
#import <appkit/nextstd.h>

#import <dpsclient/dpsclient.h>
#import <dpsclient/wraps.h>

@implementation ClockView

static void drawClockHand(id self, int hand);

/*
*  These are the user path operands and operators for the clock hands.
*  They are sent and stored in the server.
*/
static float ptsHour[] = { -10, -10, 10, 170, -4.5, 0, 0, 120, 0,120, 4.5, 180, 0, 0, -120,
		0, 0, 0, 0, 10, 360, 0};
static char opsHour[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
		dps_closepath, dps_moveto, dps_arcn, dps_closepath};
static float ptsMin[] = { -10, -10, 10, 175, -4.5, 0, 0, 162, 0,162, 4.5, 180, 0, 0, -162,
		0, 0, 0, 0, 10, 360, 0};
static char opsMin[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto, 
		dps_closepath, dps_moveto, dps_arcn, dps_closepath};
static float ptsSec[] = { -10, -30, 10, 170, -1.5, 0, 0, 145, 3, 0, 0, -145,
		4, 0, 0, -20, 0, -20, 5.5, 360, 180, 0, 20, 4, 0, 0, 0, 0, 0, 10, 360, 0};
static char opsSec[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_rlineto, dps_rlineto,
		dps_rlineto, dps_rlineto, dps_arcn, dps_rlineto, dps_rlineto, dps_closepath,
		dps_moveto, dps_arcn, dps_closepath};

static float ptsAlarmTop[] = { -5, 70, 5, 120, -1.0, 100, 0, 5, 0, 105, 1.0, 180, 0, 0, -5};
static char opsAlarmTop[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
		dps_closepath};
static float ptsAlarmBot[] = { -5, -2, 5, 120, -1.0, 0, 0, 100, 2.0, 0, 0, -100};
static char opsAlarmBot[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_rlineto,
		dps_rlineto, dps_closepath};

/* 
* 	Initialize the instance variables and create an Animator object.
* 	The animator is used to keep the timed_entry up to date. Without
*	the animator adjustments, the clock loses time. An array to hold
*	the hit detection user path is allocated. This mouse location
*	will be inserted into this user path before the hit detection is
*	tested for.
*/
- initFrame:(const NXRect *) frameRect
{	
	[super initFrame:frameRect]; 
	[self setClipping:NO];

	gstatesOn = upathsServer = YES;
	totalTime = numIterations = 0;
	angleAlarm = 0;
	gstateHour = gstateMin = gstateSec = gstateShad = 0;
	upathHour = upathMin = upathSec = upathAlarmTop = upathAlarmBot = 0;

	NX_MALLOC(hitPoint.pts, float, MAX_PTS_HIT);
	NX_MALLOC(hitPoint.ops, char, MAX_OPS_HIT);
	[self initializeHitPoint];

	animatorId = [Animator  newChronon: 1.0
			  adaptation: 3.0				
			  target: self
			  action: @selector(tick:)
			  autoStart: NO
			  eventMask: NX_ALLEVENTS];

	return self;
}

/* Most of these will remain the same between hit detection tests. */
- initializeHitPoint
{
	int		i;

	for (i = 0; i < MAX_PTS_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;
}

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

	[animatorId free]; 
	[imageId  free];

	return [super  free];
}

- setDisplayTime:anObject
{
	displayTime = anObject;

	return self;
}

- toggleGstate:sender
{
	totalTime = numIterations = 0;
	gstatesOn = [sender state];

	return self;
}

- toggleUpath:sender
{
	totalTime = numIterations = 0;
	upathsServer = [sender state];

	return self;
}

/* Use DPSDoUserPath to draw the lines of the clock. */
static void drawUpathLines (pts, ops, clr, wid, x, y, startlen, endlen, deg)
	float		pts[];
	char		ops[];
	float 	clr, wid, x, y, startlen, endlen, deg;
{
	int		i , j;
	
	float		angle;
	
	deg = ABS(deg * RADIAN);
	i = 4; j = 1;
	for (angle = 0; angle < 2 * M_PI; angle += deg)
	{
		pts[i++] = (floor) (x + (float) cos(angle) * startlen);
		pts[i++] = (floor) (y + (float) sin(angle) * startlen);
		ops[j++] = dps_moveto;
		
		pts[i++] = (floor) (x + (float) cos(angle) * endlen);
		pts[i++] = (floor) (y + (float) sin(angle) * endlen);
		ops[j++] = dps_lineto;
	}

	PSsetgray(clr);
	PSsetlinewidth(wid);
	DPSDoUserPath(&pts[4], i - 4, dps_float, &ops[1], j -1, pts, dps_ustroke);
}

/*
*	Draw the clock face into an offscreen bitmap. Resizes the bitmap if the
*	frame of this view is larger than the current size of the bitmap. Uses the
*	frame dimensions instead of the bounds because the bounds are 
*	affected by the scale:: method and do not produce the correct
*	dimensions in the default user space.
*/
- drawFace
{	
	char			*ops;

	float			*pts;

	NXSize		size;

	NXPoint		center;

	float			maxnums, maxdashes, maxcircle;

	if (imageId)
	{
		[imageId  getSize:&size];
		if (size.width < frame.size.width || size.height < frame.size.height)
			[imageId  setSize:&frame.size];
	}
	else
		imageId = [[NXImage newSize:&frame.size]  setFlipped:NO];

	center.x =  floor(bounds.size.width/2);
	center.y = floor(bounds.size.height/2);
	maxcircle = MIN(center.y - 10, center.x -10);
	maxnums = maxcircle * SIZENUMS;	
	maxdashes = maxcircle * SIZEDASHES;
					
	NX_MALLOC(pts, float, MAX_PTS); 
	NX_MALLOC(ops, char, MAX_OPS);
	
	pts[0] = bounds.origin.x;
	pts[1] = bounds.origin.y;
	pts[2] = bounds.origin.x + bounds.size.width;
	pts[3] = bounds.origin.y + bounds.size.height;
	ops[0] = dps_setbbox;

	[imageId lockFocus];
		PSscale(frame.size.width/bounds.size.width, frame.size.height/bounds.size.height);
		PSWEraseView (CLRVIEW, bounds.origin.x, bounds.origin.y,
			bounds.size.width, bounds.size.height);
		PSWMakeCircle(center.x, center.y, maxcircle);
		PSWFillPath(CLRCIRC);
		
		PSsetlinecap(1);
		drawUpathLines(pts, ops, CLRMIN, WIDMIN, center.x, center.y,
			maxdashes * LENMIN, maxdashes, DEGMIN);
		drawUpathLines(pts, ops, CLRHOUR, WIDHOUR, center.x, center.y,
			maxdashes * LENHOUR, maxdashes, DEGHOUR);
	[imageId unlockFocus];
	
	if (pts)
		NX_FREE(pts);
	if (ops)
		NX_FREE(ops);

	return self;
}

/* Define the userpaths of the hands as user objects. */
- defineUPaths
{
	/* Setup hour hand upath. */	
	PSWSetUpath(ptsHour, sizeof (ptsHour)/sizeof (float),
		opsHour, sizeof (opsHour)/sizeof (char));
	upathHour = DPSDefineUserObject(0);

	/* Setup minute hand upath. */
	PSWSetUpath(ptsMin, sizeof (ptsMin)/sizeof (float),
		opsMin, sizeof (opsMin)/sizeof (char));
	upathMin = DPSDefineUserObject(0);

	/* Setup seconds hand upath. */
	PSWSetUpath(ptsSec, sizeof (ptsSec)/sizeof (float),
		opsSec, sizeof (opsSec)/sizeof (char));
	upathSec = DPSDefineUserObject(0);

	/* Setup top of alarm hand upath. */
	PSWSetUpath(ptsAlarmTop, sizeof (ptsAlarmTop)/sizeof (float),
		opsAlarmTop, sizeof (opsAlarmTop)/sizeof (char));
	upathAlarmTop = DPSDefineUserObject(0);

	/* Setup bottom of alarm hand upath. */
	PSWSetUpath(ptsAlarmBot, sizeof (ptsAlarmBot)/sizeof (float),
		opsAlarmBot, sizeof (opsAlarmBot)/sizeof (char));
	upathAlarmBot = DPSDefineUserObject(0);

	return self;
}

/*
 * If a user object has not been allocated, then a gstate has also not been 
 * allocated. As a result, create a gstate before defining the user object.
 * If a user object exists, then copy the new gstate into the old
 * structure. No need to redefine the user object because
 * it still refers to the same structure. The PSpop() pops the result of
 * PScurrentgstate() off of the stack.
 */  
static int  definegstate(gstate, offsetx, offsety, color, linewidth)
	int			gstate;
	float			offsetx, offsety, color, linewidth;
{
	PSgsave();
	PSWSetGstate(offsetx, offsety, color, linewidth);
	if (!gstate)
	{
		PSgstate();
		gstate = DPSDefineUserObject(gstate);
	}
	else
	{
		PScurrentgstate(gstate);
		PSpop();
	}
	PSgrestore();

	return gstate;
}

/*
*	Redefine the gsates because the CTM has changed as the result
*	of the scale:: method.
*/
- defineGStates
{
	float				angle;

	NXPoint			center;

	struct timeval		timeofDay;
	struct tm			*localTime;

	if (window)
	{
		center.x =  floor(bounds.size.width/2);
		center.y = floor(bounds.size.height/2);

		[[animatorId startEntry] resetRealTime]; 

		[self  lockFocus];
			gettimeofday(&timeofDay, NULL);
			localTime = localtime(&timeofDay.tv_sec);

			angleHour = ((localTime->tm_hour % 12) + localTime->tm_min/60.0) * DEGHOUR;
			gstateHour = definegstate (gstateHour, center.x, center.y,
			CLRHANDS - 0.2, LNWIDHANDS);

			angleMin = (localTime->tm_min + localTime->tm_sec/60.0) * DEGMIN;
			gstateMin = definegstate(gstateMin, center.x + OFFSETHANDSX,
				center.y + OFFSETHANDSY, CLRHANDS - 0.2, LNWIDHANDS);

			angleSec = localTime->tm_sec * DEGMIN;
				gstateSec = definegstate(gstateSec,
				center.x + (2 * OFFSETHANDSX),
				center.y + (2 * OFFSETHANDSY),
				CLRSECOND, LNWIDSECOND);

			gstateShad = definegstate(gstateShad,
				center.x + (2 * OFFSETHANDSX) + OFFSETSHADX,
				center.y + (2 * OFFSETHANDSY) + OFFSETSHADY,
				CLRSHADOW, LNWIDSECOND);
		[self  unlockFocus];
	}

	return self;
}

/*
*	This method changes the title of the menu cell according to the
* 	value of the trace variable.
*/
-trace:sender
{
	trace = YES;
	
	return self;
}

/* Messaged by the Animator object after a timed entry has been received. */
- tick:sender
{
	angleSec = angleSec + 	TICKSEC;
	angleMin = angleMin + TICKMIN;
	angleHour =  angleHour + TICKHOUR;

	[self display];

	return self;
}

/*
*  Scales the view and then redefines the graphic states.
*  The graphic state objects pick up the scaled view upon redefinition.
*/
- sizeTo:(NXCoord)width :(NXCoord)height
{	
	NXRect		xframe;

	[animatorId stopEntry]; 
	
	xframe = frame;
	[super sizeTo:width :height];

	if (xframe.size.width && xframe.size.height)
	{	
		[self scale:width/xframe.size.width :height/xframe.size.height];
		[self  drawFace];
		[self  defineGStates];
	}
	
	return self;
}

/* Enter a modal loop to redraw the alarm hand per mouse drag event. */
- setAlarm:(NXEvent *)event
{
	int			old_mask;

	NXPoint		p, center;
    
  	NXEvent		peek;

	center.x =  floor(bounds.size.width/2);
	center.y = floor(bounds.size.height/2);

	old_mask = [window addToEventMask:NX_MOUSEUPMASK|
				NX_MOUSEDRAGGEDMASK|NX_TIMERMASK];
	event = [NXApp getNextEvent:NX_MOUSEUPMASK|
				NX_MOUSEDRAGGEDMASK|NX_TIMERMASK];

	[self  lockFocus];
		while (event->type != NX_MOUSEUP)
		{
			if (event->type == NX_TIMER)
			{
				angleSec = angleSec + TICKSEC;
				angleMin = angleMin + TICKMIN;
				angleHour = angleHour + TICKHOUR;
				if (![NXApp  peekNextEvent:NX_MOUSEDRAGGEDMASK into:&peek])
					event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK];
			}

			if (event->type == NX_MOUSEDRAGGED)
			{
				p = event->location;		
				[self convertPoint:&p fromView:nil];
				angleAlarm = atan((p.y - center.y)/(p.x - center.x))/RADIAN - 90;
				if (p.x - center.x < 0)
					angleAlarm -= 180;
				if ([NXApp  peekNextEvent:NX_TIMERMASK into:&peek
						waitFor:0  threshold:NX_BASETHRESHOLD])
				{
					angleSec = angleSec + TICKSEC;
					angleMin = angleMin + TICKMIN;
					angleHour = angleHour + TICKHOUR;
					event = [NXApp getNextEvent:NX_TIMERMASK];
				}
			}

			PSgsave();
				[self  drawSelf:&bounds :1];
			PSgrestore();

			[window flushWindow];
			NXPing();

			event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK
						waitFor:1000  threshold:NX_BASETHRESHOLD];
		}

	[window setEventMask:old_mask];

	return self;
}

/* Set the hit detection userpath upon a mouse down. */
- setHitPoint:(const NXPoint *)p
{
	float			z;

	NXPoint		pt;
  
	pt.x = p->x - floor(bounds.size.width/2);
	pt.y = p->y - floor(bounds.size.height/2);

	z = sqrt (pt.x * pt.x  + pt.y * pt.y);
	pt.x = pt.x - (z * sin(ABS(angleAlarm) * RADIAN));
	pt.y = pt.y + z - z * cos(ABS(angleAlarm) * RADIAN);

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

	/* Rlineto's */
	hitPoint.pts[7] = HITSETTING;
	hitPoint.pts[8] = HITSETTING;
	hitPoint.pts[11] = -HITSETTING;
	
	return self;
}

/*
*	Check for hit detection. No boundary check is made because the
*	alarm hand can reside in pretty much the whole view.
*/
- (BOOL) isHit:(const NXPoint *) p
 {
 	int	hit;

	[self setHitPoint:p];
	PSgsave();
		PSWInstallGstate(gstateHour, angleAlarm);
		PSWHitPath(upathAlarmTop, upathAlarmBot, hitPoint.pts,
			hitPoint.num_pts, hitPoint.ops, hitPoint.num_ops, &hit);
	PSgrestore();

	return (BOOL) hit;
}

/*  This method handles a mouse down.  */
- mouseDown:(NXEvent *)event
{
	NXPoint		p;
  
  	p = event->location;
	[self convertPoint:&p fromView:nil];

	if ([self isHit:&p])
		[self setAlarm:event];

	return self;
}

/*
*	Either draws with gstates or doesn't. A slight performance advantage is
*	gained with gstates but they use up an appreciable amount of memory
*	so they should be used judiciously.
*/
- setStateAndDraw
{
	NXPoint		center;

	if (gstatesOn)
	{
		PSWInstallGstate(gstateHour, angleAlarm);
		drawClockHand(self, ALARM);

		PSWInstallGstate(gstateHour, angleHour);
		drawClockHand(self, HOUR);

		PSWInstallGstate(gstateMin, angleMin);
		drawClockHand(self, MINUTE);

		PSWInstallGstate(gstateShad, angleSec);
		drawClockHand(self, SHADOW);

		PSWInstallGstate(gstateSec, angleSec);
		drawClockHand(self, SECOND);
	}
	else
	{
		center.x =  floor(bounds.size.width/2);
		center.y = floor(bounds.size.height/2);

		PSgsave();
			PStranslate(center.x, center.y);
			PSrotate(angleAlarm);
			drawClockHand(self, ALARM);
		PSgrestore();

		PSgsave();
			PSsetgray(CLRHANDS - 0.2);
			PSsetlinewidth(LNWIDHANDS);
			PStranslate(center.x , center.y);
			PSrotate(angleHour);
			drawClockHand(self, HOUR);
		PSgrestore();
		
		PSgsave();
			PSsetgray(CLRHANDS - 0.2);
			PSsetlinewidth(LNWIDHANDS);
			PStranslate(center.x + OFFSETHANDSX,
				center.y + OFFSETHANDSY);
			PSrotate(angleMin);
			drawClockHand(self, MINUTE);
		PSgrestore();

		PSgsave();
			PSsetgray(CLRSHADOW);
			PStranslate(center.x + (2*OFFSETHANDSX) + OFFSETSHADX,
				center.y + (2*OFFSETHANDSY) + OFFSETSHADY);
			PSrotate(angleSec);
			drawClockHand(self, SHADOW);
		PSgrestore();

		PSgsave();
			PSsetgray(CLRSECOND);
			PSsetlinewidth(LNWIDSECOND);
			PStranslate(center.x + OFFSETHANDSX + OFFSETSHADX,
				center.y + OFFSETHANDSY + OFFSETSHADY);
			PSrotate(angleSec);
			drawClockHand(self, SECOND);
		PSgrestore();
	}
}

/*
*	Draws the clock hands either as stored in the server
*	or by sending them each time.
*/
static void drawClockHand(id self, int hand)
{
	if (self->upathsServer)
	{
		switch(hand)
		{
			case ALARM:
				PSsetgray(CLRALARMTOP);
				PSWUpathFill(self->upathAlarmTop);
				PSsetgray(CLRALARMBOT);
				PSWUpathFill(self->upathAlarmBot);
				break;
			case HOUR:
				PSWUpathStrokeFill(self->upathHour);
				break;			
			case MINUTE:
				PSWUpathStrokeFill(self->upathMin);
				break;			
			case SHADOW:
				PSWUpathFill(self->upathSec);
				break;			
			case SECOND:
				PSWUpathFill(self->upathSec);
				PSWDrawCircle(CLRSECOND - 0.2);
				break;		
		}
	}
	else
	{
		switch(hand)
		{
			case ALARM:
				PSsetgray(CLRALARMTOP);
				DPSDoUserPath(&ptsAlarmTop[4], sizeof (ptsAlarmTop)/sizeof (float) - 4,
					dps_float, &opsAlarmTop[1], sizeof (opsAlarmTop)/sizeof (char) -1,
					ptsAlarmTop, dps_ufill);
				PSsetgray(CLRALARMBOT);
				DPSDoUserPath(&ptsAlarmBot[4], sizeof (ptsAlarmBot)/sizeof (float) - 4,
					dps_float, &opsAlarmBot[1], sizeof (opsAlarmBot)/sizeof (char) - 1,
					ptsAlarmBot, dps_ufill);
				break;
			case HOUR:
				DPSDoUserPath(&ptsHour[4], sizeof (ptsHour)/sizeof (float) - 4,
					dps_float, &opsHour[1], sizeof (opsHour)/sizeof (char) - 1,
					ptsHour, dps_ustroke);
				PSsetgray(CLRHANDS);
				DPSDoUserPath(&ptsHour[4], sizeof (ptsHour)/sizeof (float) - 4,
					dps_float, &opsHour[1], sizeof (opsHour)/sizeof (char) - 1,
					ptsHour, dps_ufill);
				break;
			case MINUTE:
				DPSDoUserPath(&ptsMin[4], sizeof (ptsMin)/sizeof (float) - 4,
					dps_float, &opsMin[1], sizeof (opsMin)/sizeof (char) - 1,
					ptsMin, dps_ustroke);
				PSsetgray(CLRHANDS);
				DPSDoUserPath(&ptsMin[4], sizeof (ptsMin)/sizeof (float) - 4,
					dps_float, &opsMin[1], sizeof (opsMin)/sizeof (char) - 1,
					ptsMin, dps_ufill);
				break;
			case SHADOW:
				DPSDoUserPath(&ptsSec[4], sizeof (ptsSec)/sizeof (float) - 4,
					dps_float, &opsSec[1], sizeof (opsSec)/sizeof (char) - 1,
					ptsSec, dps_ufill);
				break;		
			case SECOND:
				DPSDoUserPath(&ptsSec[4], sizeof (ptsSec)/sizeof (float) - 4,
					dps_float, &opsSec[1], sizeof (opsSec)/sizeof (char) - 1,
					ptsSec, dps_ufill);
				PSWDrawCircle(CLRSECOND - 0.2);
				break;		
		}
	}
}

- drawSelf:(NXRect *)r :(int) count
{
	int			ElapsedTime;

	[displayTime  setStringValue:""];

	PSWMarkTime (); NXPing ();		
		if (trace)
			DPSTraceContext(DPSGetCurrentContext(), YES);
		[imageId composite:NX_COPY toPoint:&bounds.origin];
		[self  setStateAndDraw];
		if (trace)
			DPSTraceContext(DPSGetCurrentContext(), NO);
	PSWReturnTime (&ElapsedTime);

	trace = NO;
	totalTime += ElapsedTime;
	++numIterations;
	[displayTime setIntValue:(totalTime/numIterations)];

	return self;
}

@end

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