<> <> <> < NULL), processes (handling ABORTED), and suggests an indentation style for Mesa.>> DIRECTORY BasicTime USING [GMT, Now, Period, Unpack, Unpacked, Update], Commander USING [CommandProc, Register], Convert USING [RopeFromInt], Graphics USING [black, Box, Color, Context, DrawArea, DrawBox, DrawStroke, FlushPath, GetBounds, LineTo, Mark, MoveTo, NewPath, Path, Restore, Rotate, Save, SetFat, Scale, SetColor, SetPaintMode, Translate, white], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, FindEntry, Menu, MenuEntry, MenuProc, ReplaceMenuEntry], Process USING [Detach, MsecToTicks, SetTimeout], RealFns USING [CosDeg, SinDeg], Rope USING [Concat, ROPE], ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, SetMenu]; Clock: CEDAR MONITOR IMPORTS BasicTime, Commander, Convert, Graphics, Menus, Process, RealFns, Rope, ViewerOps = BEGIN OPEN Rope; defaultTime: BasicTime.GMT = BasicTime.Now[]; defaultUntime: BasicTime.Unpacked = BasicTime.Unpack[defaultTime]; MyData: TYPE = REF MyDataRec; <> MyDataRec: TYPE = RECORD [ viewer: ViewerClasses.Viewer _ NIL, area: BOOL _ FALSE, live: BOOL _ TRUE, painting: BOOL _ FALSE, drawSeconds: BOOL _ TRUE, forceClear: BOOL _ TRUE, dateEntry: ROPE _ NIL, dateTime: BasicTime.Unpacked _ defaultUntime, time: BasicTime.Unpacked _ defaultUntime, packed: BasicTime.GMT _ defaultTime, foreground: Graphics.Color _ Graphics.black, offset: INT _ 0, -- in seconds path: Graphics.Path _ NIL, minWidth: REAL _ 0.0, secWidth: REAL _ 0.0, paintingChange: CONDITION]; EnterPaint: ENTRY PROC [data: MyData] RETURNS [died: BOOL] = { <> ENABLE UNWIND => NULL; IF data = NIL THEN RETURN [TRUE]; DO IF NOT data.live THEN RETURN [TRUE]; IF NOT data.painting THEN EXIT; WAIT data.paintingChange; ENDLOOP; data.painting _ TRUE; RETURN [FALSE]; }; ExitPaint: ENTRY PROC [data: MyData] = { <> ENABLE UNWIND => NULL; IF data = NIL THEN RETURN; data.painting _ FALSE; BROADCAST data.paintingChange; }; clockListChange: CONDITION; -- notified when the clock list changes AddMeToList: ENTRY PROC [me: MyData] = { <> ENABLE UNWIND => NULL; clockList _ CONS[me, clockList]; BROADCAST clockListChange; }; WaitForListChange: ENTRY PROC [old: LIST OF MyData] = { <> ENABLE UNWIND => NULL; WHILE clockList = old DO WAIT clockListChange; ENDLOOP; }; PaintMe: ViewerClasses.PaintProc = TRUSTED { <<[self: Viewer, context: Graphics.Context, whatChanged: REF, clear: BOOL]>> <> ctx: Graphics.Context _ context; data: MyData _ NARROW[self.data]; IF NOT EnterPaint[data] THEN { box: Graphics.Box _ Graphics.GetBounds[ctx]; -- scale to viewer size 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: Graphics.Color _ data.foreground; background: Graphics.Color _ IF foreground = Graphics.white THEN Graphics.black ELSE Graphics.white; spokes: NAT _ 32; oldTime: BasicTime.Unpacked _ data.time; offset: INT _ data.offset; curPacked: BasicTime.GMT _ BasicTime.Update[BasicTime.Now[], offset]; curTime: BasicTime.Unpacked _ BasicTime.Unpack[curPacked]; TickMark: PROC [seconds: REAL, d: REAL _ 1.0 / 32] = TRUSTED { <> mark: Graphics.Mark _ Graphics.Save[ctx]; -- mark original state of graphics context IF self.iconic THEN d _ d + d; -- iconic => double tick size Graphics.SetColor[ctx, foreground]; -- use foreground color for ticks Graphics.Rotate[ctx, -6.0 * seconds]; -- rotate to tick mark angle Graphics.Translate[ctx, d - 1.0, 0.0]; -- move to near circle edge Graphics.DrawBox[ctx, [-d, -d, d, d]]; -- actually draw the box Graphics.Restore[ctx, mark]; -- restore context to original state }; Face: PROC = TRUSTED { <> mark: Graphics.Mark _ Graphics.Save[ctx]; Graphics.SetColor[ctx, background]; Graphics.MoveTo[path, 1.0, 0.0]; FOR i: NAT IN [1..60] DO <> deg: NAT _ 6*i; Graphics.LineTo[path, RealFns.CosDeg[deg], RealFns.SinDeg[deg]] ENDLOOP; Graphics.DrawArea[ctx, path]; Graphics.Restore[ctx, mark] }; DrawTime: PROC [oldTime, newTime: BasicTime.Unpacked] = TRUSTED { <> Handy: PROC [seconds, length, width: REAL, invert: BOOL _ FALSE] = TRUSTED { <> degrees: REAL _ -6.0 * seconds; localMark: Graphics.Mark _ Graphics.Save[ctx]; closed: BOOL _ FALSE; IF invert THEN { <> Graphics.SetColor[ctx, Graphics.black]; [] _ Graphics.SetPaintMode[ctx, invert]}; <> Graphics.Rotate[ctx, degrees]; <> Graphics.MoveTo[path, 0.0, 0.0]; Graphics.LineTo[path, 0.0, length]; IF width > 0 THEN { <> closed _ TRUE; Graphics.LineTo[path, -width, -width]; Graphics.LineTo[path, width, -width]; Graphics.LineTo[path, 0.0, length]}; IF data.area AND width > 0.0 AND NOT self.iconic THEN Graphics.DrawArea[ctx, path] ELSE Graphics.DrawStroke[ctx, path, 0.0, closed, round]; Graphics.Restore[ctx, localMark] }; mark: Graphics.Mark _ Graphics.Save[ctx]; oldSecMod: CARDINAL _ oldTime.second - oldTime.second MOD 10; newSecMod: CARDINAL _ newTime.second - newTime.second MOD 10; needSecond: BOOL _ data.drawSeconds AND NOT self.iconic; needMinute: BOOL _ clear; needHour: BOOL _ clear; IF NOT clear THEN { <> Graphics.SetColor[ctx, background]; IF needSecond THEN <> Handy[oldTime.second, 0.85, 0.0, TRUE]; IF oldTime.minute # newTime.minute OR oldSecMod # newSecMod THEN { <> Handy[oldTime.minute + oldSecMod / 60.0, 0.80, 0.02]; needMinute _ TRUE}; IF oldTime.hour # newTime.hour OR oldTime.minute # newTime.minute THEN { <> Handy[oldTime.hour * 5 + oldTime.minute / 12.0, 0.60, 0.03]; needHour _ TRUE}}; <> Graphics.SetColor[ctx, foreground]; IF needHour OR needMinute THEN Handy[(newTime.hour) * 5 + newTime.minute / 12.0, 0.60, 0.03]; IF needMinute THEN Handy[newTime.minute + newSecMod / 60.0, 0.80, 0.02]; IF needSecond THEN -- draw the second hand Handy[newTime.second, 0.85, 0.0, TRUE]; Graphics.Restore[ctx, mark] }; path: Graphics.Path _ data.path; IF path = NIL THEN data.path _ path _ Graphics.NewPath[16] ELSE Graphics.FlushPath[path]; Graphics.Translate[ctx, halfX, halfY]; Graphics.Scale[ctx, radius, radius]; Graphics.SetColor[ctx, foreground]; [] _ Graphics.SetFat[ctx, FALSE]; data.time _ curTime; IF whatChanged = NIL OR curTime = oldTime OR data.forceClear THEN { clear _ TRUE; data.forceClear _ FALSE}; IF clear THEN { <> IF self.iconic THEN Face[] ELSE {-- init the screen to background Graphics.SetColor[ctx, background]; Graphics.DrawBox [ctx, [-halfX/radius, -halfY/radius, halfX/radius, halfY/radius]]; Graphics.SetColor[ctx, foreground]}; <> FOR i: NAT IN [0..12) DO TickMark[i * 5] ENDLOOP}; <> DrawTime[oldTime, curTime]; ExitPaint[data]} }; SwapColor: Menus.MenuProc = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> <> viewer: ViewerClasses.Viewer _ NARROW[parent]; data: MyData _ NARROW[viewer.data]; [] _ EnterPaint[data]; IF data.foreground = Graphics.white THEN data.foreground _ Graphics.black ELSE data.foreground _ Graphics.white; data.forceClear _ TRUE; ExitPaint[data]; ViewerOps.PaintViewer[viewer, client, FALSE] }; ChangeAllOffSets: PROC [offset: INT] = { FOR l: LIST OF MyData _ clockList, l.rest UNTIL l = NIL DO data: MyData _ l.first; [] _ EnterPaint[data]; data.offset _ offset; data.forceClear _ TRUE; ExitPaint[data]; ViewerOps.PaintViewer[data.viewer, client, FALSE] ENDLOOP; }; ChangeOffset: Menus.MenuProc = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> <> viewer: ViewerClasses.Viewer _ NARROW[parent]; data: MyData _ NARROW[viewer.data]; [] _ EnterPaint[data]; IF control THEN data.offset _ 0 ELSE { delta: INT _ 0; SELECT mouseButton FROM red => delta _ 60*60; yellow => delta _ 60; blue => delta _ 1; ENDCASE; data.offset _ IF shift THEN data.offset + delta ELSE data.offset - delta; }; data.forceClear _ TRUE; ExitPaint[data]; ViewerOps.PaintViewer[viewer, client, FALSE] }; RefreshDate: Menus.MenuProc = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> viewer: ViewerClasses.Viewer _ NARROW[parent]; data: MyData _ NARROW[viewer.data]; IF EnterPaint[data] THEN RETURN; IF data.dateEntry = NIL THEN {ExitPaint[data]; RETURN}; data.dateTime _ data.time; {-- now split out the parts of the date day: ROPE _ Convert.RopeFromInt[data.dateTime.day]; month: ROPE _ NIL; year: ROPE _ Convert.RopeFromInt[data.dateTime.year - 1900]; entry: Menus.MenuEntry _ NIL; SELECT data.dateTime.month FROM January => month _ " Jan "; February => month _ " Feb "; March => month _ " Mar "; April => month _ " Apr "; May => month _ " May "; June => month _ " Jun "; July => month _ " Jul "; August => month _ " Aug "; September => month _ " Sep "; October => month _ " Oct "; November => month _ " Nov "; December => month _ " Dec "; ENDCASE => month _ " ??? "; IF data.dateTime.day < 10 THEN day _ Rope.Concat[" ", day]; day _ day.Concat[month.Concat[year]]; entry _ Menus.CreateEntry[day, RefreshDate]; Menus.ReplaceMenuEntry [viewer.menu, Menus.FindEntry[viewer.menu, data.dateEntry], entry ! UNWIND => ExitPaint[data]]; data.dateEntry _ day; }; data.forceClear _ TRUE; ExitPaint[data]; ViewerOps.PaintViewer[viewer, menu, FALSE] }; clockList: LIST OF MyData _ NIL; <> viewerClass: ViewerClasses.ViewerClass _ NIL; Mother: PROC [iconicFlag: BOOL _ TRUE] = TRUSTED { <> viewer: ViewerClasses.Viewer _ NIL; data: MyData _ NEW[MyDataRec _ []]; data.dateTime.year _ 0; Process.SetTimeout[@data.paintingChange, Process.MsecToTicks[pause]]; Child[viewer, data, iconicFlag ! ABORTED => CONTINUE]; data.live _ FALSE }; Child: PROC [viewer: ViewerClasses.Viewer, data: MyData, iconicFlag: BOOL] = TRUSTED { <> packed: BasicTime.GMT _ BasicTime.Now[]; menu: Menus.Menu _ Menus.CreateMenu[]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["SwapColor", SwapColor]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["ChangeOffset", ChangeOffset]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry[data.dateEntry _ "XX-XXX-XX", RefreshDate]]; viewer _ ViewerOps.CreateViewer [flavor: $Clock, info: [name: "Clock", column: right, iconic: iconicFlag, data: data]]; ViewerOps.SetMenu[viewer, menu]; data.viewer _ viewer; AddMeToList[data]; RefreshDate[viewer, NIL]; WHILE data.live AND NOT viewer.destroyed DO newPacked: BasicTime.GMT _ BasicTime.Now[]; IF newPacked # packed THEN { IF viewer.iconic OR NOT data.drawSeconds THEN IF BasicTime.Period[packed, newPacked] < 30 THEN {Rest[data]; LOOP}; IF NOT data.painting THEN ViewerOps.PaintViewer[viewer, client, FALSE, $time]; IF data.time.day # data.dateTime.day OR data.time.month # data.dateTime.month THEN RefreshDate[viewer, NIL]; packed _ newPacked; }; Rest[data] ENDLOOP }; pause: CARDINAL _ 200; -- in milliseconds Rest: ENTRY PROC [data: MyData] = { <> ENABLE UNWIND => NULL; WAIT data.paintingChange; }; Start1: PROC [iconicFlag: BOOL _ TRUE] = TRUSTED { <> old: LIST OF MyData _ clockList; IF viewerClass = NIL THEN { viewerClass _ NEW [ViewerClasses.ViewerClassRec _ [paint: PaintMe, -- called to repaint icon: private, -- picture to display when small cursor: textPointer]]; -- cursor when mouse is in viewer ViewerOps.RegisterViewerClass[$Clock, viewerClass]; }; Process.Detach[FORK Mother[iconicFlag]]; WaitForListChange[old] }; Start: Commander.CommandProc = { Start1[TRUE] }; Start1[TRUE]; -- we start one clock at initialization because Clock is intended to be run from PreRun instead of via commands. Of course, the commands work too. Commander.Register["Clock", Start, "starts a clock to display the time"]; END.