<> <> <> <> <> < NULL), processes (handling ABORTED), and suggests an indentation style for Mesa.>> DIRECTORY Convert USING [ValueToRope], 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], Time USING [Current, defaultTime, Packed, Unpack, Unpacked], ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, SetMenu]; MyClock: CEDAR MONITOR IMPORTS Convert, Graphics, Menus, Process, RealFns, Rope, ViewerOps, Time = BEGIN OPEN Rope; defaultUntime: Time.Unpacked; 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: Time.Unpacked _ defaultUntime, time: Time.Unpacked _ defaultUntime, packed: Time.Packed _ Time.defaultTime, foreground: Graphics.Color _ Graphics.black, -- hourOffset: INTEGER _ 0, 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: Time.Unpacked _ data.time; curPacked: Time.Packed _ [Time.Current[] + data.offSet]; curTime: Time.Unpacked _ Time.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: Time.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]; <> IF data.secWidth > 0 THEN {width _ data.secWidth/radius; Graphics.DrawBox[ctx, [-width, 0, width, length]]; Graphics.Restore[ctx, localMark]; RETURN}; <> 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 -- + hoff -- ) * 5 + oldTime.minute / 12.0, 0.60, 0.03]; needHour _ TRUE}}; <> Graphics.SetColor[ctx, foreground]; IF needHour OR needMinute THEN Handy[(newTime.hour -- + hoff -- ) * 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]; direction: INT = IF shift THEN 1 ELSE -1; [] _ EnterPaint[data]; IF control THEN data.offSet _ 0 ELSE data.offSet _ data.offSet + (direction * (SELECT mouseButton FROM red => 3600, yellow => 60, blue => 1, ENDCASE => ERROR)); 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.ValueToRope[[signed[data.dateTime.day]]]; month: ROPE _ NIL; year: ROPE _ Convert.ValueToRope[[signed[data.dateTime.year - 1900]]]; entry: Menus.MenuEntry _ NIL; SELECT data.dateTime.month FROM 0 => month _ " Jan "; 1 => month _ " Feb "; 2 => month _ " Mar "; 3 => month _ " Apr "; 4 => month _ " May "; 5 => month _ " Jun "; 6 => month _ " Jul "; 7 => month _ " Aug "; 8 => month _ " Sep "; 9 => month _ " Oct "; 10 => month _ " Nov "; 11 => 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: Time.Packed _ Time.Current[]; 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]; AddMeToList[data]; data.viewer _ viewer; RefreshDate[viewer, NIL]; WHILE data.live AND NOT viewer.destroyed DO newPacked: Time.Packed _ Time.Current[]; <> IF newPacked # packed THEN { IF viewer.iconic OR NOT data.drawSeconds THEN IF newPacked - packed < 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; -- note that if curPacked is set to some specific time, then the clock will not move until it is reset. }; 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 defaultUntime _ Time.Unpack[Time.defaultTime]; ViewerOps.RegisterViewerClass[$Clock, viewerClass]; }; Process.Detach[FORK Mother[iconicFlag]]; WaitForListChange[old] }; Start: PROC [n: INT _ 1, iconic: BOOL _ TRUE] = { <> IF iconic # TRUE AND iconic # FALSE THEN ERROR; FOR i: INT IN [1..n] DO Start1[iconic] ENDLOOP }; Start1[] -- the default is to start 1 iconic clock END. <> <> <> <<>> <<>> <<>>