<> <> <> <> <> <> < 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], SafeStorage USING [PinObject, UnpinObject], 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, SafeStorage, 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]; FOR l: LIST OF MyData _ clockList, l.rest UNTIL l = NIL DO l.first.showDateIconic _ showDateIconic; 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; SafeStorage.PinObject[data]; <> 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]; 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; SafeStorage.UnpinObject[data]; <> }; }; 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.