-- 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: BOOLEANFALSE,
live: BOOLEANTRUE,
painting: BOOLEANFALSE,
drawSeconds: BOOLEANTRUE,
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: REALIF 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: BOOLEANFALSE] = {
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: BOOLEANTRUE;

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: BOOLEANTRUE] = {
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.