ftp.nice.ch/peanuts/GeneralData/Documents/adobe/_NX_Clock/ClockView.m

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

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

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

#import "Animator.h"
#import "ClockView.h"
#import "ClockViewWraps.h"
#import <appkit/Bitmap.h>
#import <appkit/Cell.h>
#import <appkit/Control.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.
*/
+ new
{	
	self = [super new]; 
	[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;
}

- 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]; 
	[bitmapId  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;
}

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);
}

- drawFace
{	
	char			*ops;

	float			*pts;

	NXSize		bmapsize;

	NXPoint		center;

	float			maxnums, maxdashes, maxcircle;

	if (bitmapId)
	{
		[bitmapId  getSize:&bmapsize];
		if (bmapsize.width < frame.size.width || bmapsize.height < frame.size.height)
			[bitmapId  resize:frame.size.width  :frame.size.height];
	}
	else
	{	
		bitmapId = [[Bitmap newSize:frame.size.width
				:frame.size.height    type:NX_NOALPHABITMAP]  setFlip: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;

	[bitmapId 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);
	[bitmapId unlockFocus];
	
	if (pts)
		NX_FREE(pts);
	if (ops)
		NX_FREE(ops);

	return self;
}

- defineUPaths
{
	/* Setup hour hand upath. */	
	PSWSetUpath(ptsHour, sizeof (ptsHour)/sizeof (float),
		opsHour, sizeof (opsHour)/sizeof (char));
	upathHour = DPSDefineUserObject(upathHour);

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

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

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

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

	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;
}

- defineGStates
{
	float				angle;

	NXPoint			center;

	struct timeval		timeofDay;
	struct tm			*localTime;

	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;
}

/* 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 pick up the scale view.
*/
- sizeTo:(NXCoord)width :(NXCoord)height
{	
	NXRect		xframe;

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

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

- 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;
}

- 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;
}

- (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;
}

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

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

	return self;
}

- 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();
	}
}

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;

	NXRect		cRect;

	[displayTime  setStringValue:""];

	cRect .origin = bounds.origin;
	cRect.size = frame.size;

	PSWMarkTime (); NXPing ();		
		[bitmapId composite:NX_COPY fromRect:&cRect  toPoint:&bounds.origin];
		[self  setStateAndDraw];
	PSWReturnTime (&ElapsedTime);

	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.