<<>> <> <> <> <> <> <> <> < 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], Imager, ImagerBackdoor USING [GetBounds, invert, MakeStipple], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, FindEntry, Menu, MenuEntry, MenuProc, ReplaceMenuEntry], Process USING [Detach, DisableTimeout, MsecToTicks, SetTimeout], Real USING [Fix], RealFns USING [CosDeg, SinDeg], Rope USING [Concat, ROPE], <> UserProfile USING [Boolean, CallWhenProfileChanges, ProfileChangedProc], VFonts USING [EstablishFont], ViewerClasses USING [Column, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, SetMenu]; Clock: CEDAR MONITOR IMPORTS BasicTime, Commander, Convert, Imager, ImagerBackdoor, Menus, Process, Real, RealFns, Rope, UserProfile, VFonts, ViewerOps SHARES ViewerOps = BEGIN OPEN Rope; defaultTime: BasicTime.GMT = BasicTime.Now[]; defaultUntime: BasicTime.Unpacked = BasicTime.Unpack[defaultTime]; desktopGrey: Imager.Color ~ ImagerBackdoor.MakeStipple[104042B]; -- magic bit pattern for desk top grey labelFont: Imager.Font ¬ NIL; 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, forceRepaint: BOOL ¬ FALSE, showDateIconic: BOOL ¬ FALSE, dateEntry: ROPE ¬ NIL, textDay: ROPE ¬ NIL, dateCount: INTEGER ¬ -3, dateTime: BasicTime.Unpacked ¬ defaultUntime, time: BasicTime.Unpacked ¬ defaultUntime, packed: BasicTime.GMT ¬ defaultTime, foreground: Imager.Color ¬ Imager.black, offset: INT ¬ 0, -- in seconds 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; }; NewProfile: ENTRY UserProfile.ProfileChangedProc = { ENABLE UNWIND => NULL; showDateIconic: BOOL = UserProfile.Boolean[key: "Clock.showDateIconic", default: FALSE]; foreground: Imager.Color = IF UserProfile.Boolean[key: "Clock.showWhiteHands", default: FALSE] THEN Imager.white ELSE Imager.black; FOR l: LIST OF MyData ¬ clockList, l.rest UNTIL l = NIL DO l.first.showDateIconic ¬ showDateIconic; l.first.foreground ¬ foreground; l.first.forceRepaint ¬ TRUE; l.first.forceClear ¬ TRUE; ENDLOOP; }; WaitForListChange: ENTRY PROC [old: LIST OF MyData] = { <> ENABLE UNWIND => NULL; WHILE clockList = old DO WAIT clockListChange; ENDLOOP; }; PaintMe: ViewerClasses.PaintProc = { <<[self: Viewer, context: Imager.Context, whatChanged: REF, clear: BOOL]>> <> ctx: Imager.Context ¬ context; data: MyData ¬ NARROW[self.data]; IF NOT EnterPaint[data] THEN { rect: Imager.Rectangle ~ ImagerBackdoor.GetBounds[ctx]; minX: REAL ¬ rect.x; minXI: INT ¬ Real.Fix[minX]; minY: REAL ¬ rect.y; minYI: INT ¬ Real.Fix[minY]; maxX: REAL ¬ minX+rect.w; widthI: INT ¬ Real.Fix[rect.w]; maxY: REAL ¬ minY+(IF data.showDateIconic THEN rect.h-12 ELSE rect.h); heightI: INT ¬ Real.Fix[rect.h]; halfX: REAL ¬ (maxX - minX) / 2.0; halfY: REAL ¬ (maxY - minY) / 2.0; radius: REAL ¬ MIN[halfX, halfY]; foreground: Imager.Color ¬ data.foreground; background: Imager.Color ¬ IF foreground=Imager.white THEN Imager.black ELSE Imager.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]; greyIcon: PROC ~ { Imager.SetColor[ctx, desktopGrey]; Imager.MaskRectangleI[ctx, 0, 0, widthI, heightI]; }; TickMark: PROC [seconds: REAL, d: REAL ¬ 1.0 / 32] = { <> tick: PROC ~ { IF self.iconic THEN d ¬ d + d; -- iconic => double tick size Imager.SetColor[ctx, foreground]; -- use foreground color for ticks Imager.RotateT[ctx, -6.0 * seconds]; -- rotate to tick mark angle Imager.TranslateT[ctx, [d - 1.0, 0.0]]; -- move to near circle edge Imager.MaskBox[ctx, [-d, -d, d, d]]; -- actually draw the box }; Imager.DoSave[ctx, tick]; -- restore context to original state after calling tick }; Face: PROC = { <> circle: PROC ~ { path: Imager.PathProc ~ { moveTo[[1.0, 0.0]]; FOR i: NAT IN [1..60] DO <> deg: NAT ¬ 6*i; lineTo[[RealFns.CosDeg[deg], RealFns.SinDeg[deg]]]; ENDLOOP; }; Imager.SetColor[ctx, background]; Imager.MaskFill[ctx, path]; }; Imager.DoSave[ctx, circle]; }; DrawTime: PROC [oldTime, newTime: BasicTime.Unpacked] = { <> Handy: PROC [seconds, length, width: REAL, invert: BOOL ¬ FALSE] = { <> hand: PROC ~ { degrees: REAL ¬ -6.0 * seconds; Imager.RotateT[ctx, degrees]; IF invert THEN Imager.SetColor[ctx, ImagerBackdoor.invert]; IF data.area AND width > 0.0 AND NOT self.iconic THEN { triangle: Imager.PathProc ~ { moveTo[[0.0, length]]; lineTo[[-width, -width]]; lineTo[[width, -width]]; }; Imager.MaskFill[ctx, triangle]; } ELSE { path: Imager.PathProc ~ { moveTo[[0.0, 0.0]]; lineTo[[0.0, length]]; lineTo[[-width, -width]]; lineTo[[width, -width]]; lineTo[[0.0, length]]; }; Imager.SetStrokeWidth[ctx, 0]; Imager.SetStrokeJoint[ctx, round]; IF width=0 THEN Imager.MaskVector[ctx, [0.0, 0.0], [0.0, length]] ELSE Imager.MaskStroke[context: ctx, path: path, closed: TRUE]; }; }; Imager.DoSave[ctx, hand]; }; 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; drawHands: PROC ~ { IF NOT clear THEN { <> Imager.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; } }; <> Imager.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]; }; Imager.DoSave[ctx, drawHands]; }; Banner: PROC = { IF self.iconic AND data.showDateIconic AND data.dateCount <= 0 THEN { Imager.SetColor[ctx, background]; Imager.MaskRectangleI[ctx, 0, heightI-10, widthI, 13]; Imager.SetColor[ctx, foreground]; Imager.SetFont[ctx, labelFont]; Imager.SetXYI[ctx, 0, heightI-9]; Imager.ShowRope[ctx, data.textDay]; }; data.dateCount ¬ data.dateCount + 1; IF data.dateCount > 10 THEN data.dateCount ¬ 0 ; }; ClockFace: PROC = { Imager.TranslateT[ctx, [halfX, halfY]]; Imager.ScaleT[ctx, radius]; Imager.SetColor[ctx, foreground]; IF clear THEN { <> IF self.iconic THEN Face[] ELSE {-- init the screen to background Imager.SetColor[ctx, background]; Imager.MaskBox [ctx, [-halfX/radius, -halfY/radius, halfX/radius, halfY/radius]]; Imager.SetColor[ctx, foreground]; }; <> FOR i: NAT IN [0..12) DO TickMark[i * 5] ENDLOOP; }; <> data.dateCount ¬ -2; DrawTime[oldTime, curTime]; }; data.time ¬ curTime; IF whatChanged = NIL OR curTime = oldTime OR data.forceClear THEN { clear ¬ TRUE; data.forceClear ¬ FALSE; }; IF clear AND self.iconic THEN Imager.DoSave[ctx, greyIcon]; IF radius>0 THEN Imager.DoSave[ctx, ClockFace]; Imager.DoSave[ctx, Banner]; 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 = Imager.white THEN data.foreground ¬ Imager.black ELSE data.foreground ¬ Imager.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]; weekDay: ROPE ¬ ""; 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 ¬ " ??? "; SELECT data.dateTime.weekday FROM Monday => weekDay ¬ "mon "; Tuesday => weekDay ¬ "tue "; Wednesday => weekDay ¬ "wed "; Thursday => weekDay ¬ "thu "; Friday => weekDay ¬ "fri "; Saturday => weekDay ¬ "sat "; Sunday => weekDay ¬ "sun "; ENDCASE => weekDay ¬ "???"; IF data.dateTime.day < 10 THEN day ¬ Rope.Concat[" ", day]; data.textDay ¬ weekDay.Concat[day.Concat[month]]; 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]; TurnOff[data]; }; 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; data.showDateIconic ¬ UserProfile.Boolean[key: "Clock.showDateIconic", default: FALSE]; data.foreground ¬ IF UserProfile.Boolean[key: "Clock.showWhiteHands", default: FALSE] THEN Imager.white ELSE Imager.black; AddMeToList[data]; RefreshDate[viewer, NIL]; WHILE data.live AND NOT viewer.destroyed DO newPacked: BasicTime.GMT ¬ BasicTime.Now[]; IF newPacked # packed THEN { IF ~data.forceRepaint AND (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; data.forceRepaint ¬ FALSE; }; Rest[data] ENDLOOP }; pause: CARDINAL ¬ 200; -- in milliseconds TurnOff: ENTRY PROC [data: MyData] = TRUSTED { <> ENABLE UNWIND => NULL; IF data.live THEN { Process.DisableTimeout[@data.paintingChange]; data.live ¬ FALSE; BROADCAST data.paintingChange; <> <> }; }; 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] }; labelFont ¬ VFonts.EstablishFont["Helvetica", 8]; Commander.Register["Clock", Start, "starts a clock to display the time"]; UserProfile.CallWhenProfileChanges[NewProfile]; END.