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.