-- Clock.mesa -- Russ Atkinson, March 23, 1982 11:07 am -- McGregor, August 25, 1982 9:12 am DIRECTORY Graphics: TYPE USING [black, Box, Color, Context, DrawArea, DrawBox, DrawPath, GetBounds, LineTo, Mark, MoveTo, Rectangle, Restore, Rotate, Save, Scale, SetColor, SetPaintMode, Translate, white], Process: TYPE USING [Detach, MsecToTicks, Pause], ViewerClasses: TYPE, ViewerOps: TYPE, Time: TYPE USING [Current, defaultTime, Packed, Unpack, Unpacked]; Clock: MONITOR IMPORTS Graphics, Process, ViewerOps, Time = BEGIN OPEN Graphics, Process, Time; defaultUntime: Unpacked = Unpack[defaultTime]; MyData: TYPE = REF MyDataRec; MyDataRec: TYPE = RECORD [area: BOOLEAN _ FALSE, live: BOOLEAN _ TRUE, painting: BOOLEAN _ FALSE, drawSeconds: BOOLEAN _ TRUE, time: Unpacked _ defaultUntime, packed: Packed _ defaultTime, foreground: Color _ black, hourOffset: INTEGER _ 0]; EnterPaint: ENTRY PROC [data: MyData] RETURNS [died: BOOLEAN] = { DO IF NOT data.live THEN RETURN [TRUE]; IF NOT data.painting THEN EXIT; WAIT paintingChange ENDLOOP; data.painting _ TRUE; RETURN [FALSE] }; ExitPaint: ENTRY PROC [data: MyData] = { data.painting _ FALSE; BROADCAST paintingChange }; clockListChange: CONDITION; paintingChange: CONDITION; AddMeToList: ENTRY PROC [me: MyData] = { clockList _ CONS[me, clockList]; NOTIFY clockListChange }; WaitForListChange: ENTRY PROC [old: LIST OF MyData] = { WHILE clockList = old DO WAIT clockListChange ENDLOOP }; PaintMe: ViewerClasses.PaintProc = { -- self: Viewer, context: Graphics.Context, whatChanged: REF ANY ctx: Context _ context; data: MyData _ NARROW[self.data]; IF EnterPaint[data] THEN RETURN; {box: Box _ GetBounds[ctx]; maxX: REAL _ box.xmax; maxY: REAL _ box.ymax; minX: REAL _ box.xmin; minY: REAL _ box.ymin; halfX: REAL _ (maxX - minX) / 2.0; halfY: REAL _ (maxY - minY) / 2.0; radius: REAL _ IF halfX < halfY THEN halfX ELSE halfY; foreground: Color _ data.foreground; background: Color _ IF foreground = white THEN black ELSE white; spokes: NAT _ 32; oldTime: Unpacked _ data.time; curPacked: Packed _ Current[]; curTime: Unpacked _ Unpack[curPacked]; hoff: INTEGER _ data.hourOffset; TickMark: PROC [seconds: REAL, d: REAL _ 1.0 / 32] = { mark: Mark _ Save[ctx]; IF self.iconic THEN d _ d + d; SetColor[ctx, foreground]; Translate[ctx, halfX, halfY]; Scale[ctx, radius, radius]; Rotate[ctx, -6.0 * seconds]; Translate[ctx, d - 1.0, 0.0]; -- draw the box DrawBox[ctx, [-d, -d, d, d]]; Restore[ctx, mark] }; Face: PROC [r: REAL _ 1.0] = { mark: Mark _ Save[ctx]; SetColor[ctx, background]; Translate[ctx, halfX, halfY]; Scale[ctx, radius * r, radius * r]; MoveTo[ctx, 1.0, 0.0]; FOR i: NAT IN [0..60) DO -- add to the path Rotate[ctx, -6.0]; LineTo[ctx, 1.0, 0.0] ENDLOOP; DrawArea[ctx]; Restore[ctx, mark] }; DrawTime: PROC [oldT, newTime: Unpacked] = { Handy: PROC [seconds, length, width: REAL, invert: BOOLEAN _ FALSE] = { degrees: REAL _ -6.0 * seconds; localMark: Mark _ Save[ctx]; IF invert THEN -- force inversion of the hands {[] _ SetPaintMode[ctx, invert]; SetColor[ctx, black]}; -- rotate to proper angle Rotate[ctx, degrees]; -- enter the path MoveTo[ctx, 0.0, 0.0]; LineTo[ctx, 0.0, length]; IF width > 0 THEN -- not a simple line, but a triangle {LineTo[ctx, -width, -width]; LineTo[ctx, width, -width]; LineTo[ctx, 0.0, length]; LineTo[ctx, 0.0, 0.0]}; IF data.area AND width > 0.0 AND NOT self.iconic THEN DrawArea[ctx] ELSE DrawPath[ctx, 1.0, TRUE]; Restore[ctx, localMark] }; mark: Mark _ Save[ctx]; oldSecMod: CARDINAL _ oldTime.second - oldTime.second MOD 10; newSecMod: CARDINAL _ newTime.second - newTime.second MOD 10; needSecond: BOOLEAN _ data.drawSeconds AND NOT self.iconic; needMinute: BOOLEAN _ clear; needHour: BOOLEAN _ clear; Translate[ctx, halfX, halfY]; Scale[ctx, radius, radius]; -- erase the hands IF NOT clear THEN {SetColor[ctx, background]; IF needSecond THEN -- erase the second hand Handy[oldTime.second, 0.85, 0.0, TRUE]; IF oldTime.minute # newTime.minute OR oldSecMod # newSecMod THEN -- erase the minute hand {Handy[oldTime.minute + oldSecMod / 60.0, 0.75, 0.015]; needMinute _ TRUE}; IF oldTime.hour # newTime.hour OR oldTime.minute # newTime.minute THEN -- erase the hour hand {Handy [(oldTime.hour + hoff) * 5 + oldTime.minute / 12.0, 0.55, 0.02]; needHour _ TRUE}}; -- draw the hands SetColor[ctx, foreground]; IF needHour OR needMinute THEN Handy[(newTime.hour + hoff) * 5 + newTime.minute / 12.0, 0.55, 0.02]; IF needMinute THEN Handy[newTime.minute + newSecMod / 60.0, 0.75, 0.015]; IF needSecond THEN -- draw the second hand Handy[newTime.second, 0.85, 0.0, TRUE]; Restore[ctx, mark] }; data.time _ curTime; IF whatChanged = NIL THEN clear _ TRUE; IF curTime = oldTime THEN clear _ TRUE; IF clear THEN -- init the face {IF self.iconic -- draw just the face THEN Face[] -- init the screen to background ELSE {SetColor[ctx, background]; Rectangle[ctx, minX, minY, maxX, maxY]; DrawArea[ctx]; SetColor[ctx, foreground]}; -- draw the tick marks FOR i: NAT IN [0..12) DO TickMark[i * 5] ENDLOOP}; -- draw the hands DrawTime[oldTime, curTime]; ExitPaint[data]} }; clockList: LIST OF MyData _ NIL; viewerClass: ViewerClasses.ViewerClass _ NEW [ViewerClasses.ViewerClassRec _ [paint: PaintMe, -- called to repaint notify: NIL, -- TIP input events modify: NIL, -- called on InputFocus changes destroy: NIL, -- called on destroy op copy: NIL, -- copy data to new Viewer set: NIL, -- set the viewer contents get: NIL, -- get the viewer contents init: NIL, -- called on creation or reset save: NIL, -- requests client to save contents scroll: NIL, -- document scrolling icon: private, -- picture to display when small tipTable: NIL, -- class' tip table cursor: textPointer]]; -- cursor when mouse is in viewer Mother: PROC = { viewer: ViewerClasses.Viewer _ NIL; data: MyData _ NEW[MyDataRec _ []]; packed: Packed _ Current[]; viewer _ ViewerOps.CreateViewer [flavor: $Clock, info: [name: "Clock", column: right, iconic: iconicFlag, data: data]]; AddMeToList[data]; WHILE data.live DO newPacked: Packed _ Current[]; IF newPacked # packed THEN {IF viewer.iconic OR NOT data.drawSeconds THEN IF newPacked - packed < 30 THEN {Rest[]; LOOP}; IF NOT data.painting THEN ViewerOps.PaintViewer[viewer, client, FALSE, $time]; packed _ newPacked}; Rest[] ENDLOOP }; pause: CARDINAL _ 200; iconicFlag: BOOLEAN _ TRUE; Rest: PROC = { Process.Pause[Process.MsecToTicks[pause]] }; Start1: PROC = { old: LIST OF MyData _ clockList; Process.Detach[FORK Mother]; WaitForListChange[old] }; Start: PROC [n: INT _ 1, iconic: BOOLEAN _ TRUE] = { oldIconic: BOOLEAN _ iconicFlag; iconicFlag _ iconic; IF iconic # TRUE AND iconic # FALSE THEN ERROR; FOR i: INT IN [1..n] DO Start1[] ENDLOOP; iconicFlag _ oldIconic }; ViewerOps.RegisterViewerClass[$Clock, viewerClass]; Start1[] END.