ControlsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Bloomenthal, May 19, 1986 11:19:08 pm PDT
DIRECTORY Buttons, Commander, Controls, Convert, FileNames, Imager, ImagerFont, ImagerOps, ImagerPath, IO, List, Process, ProcessProps, Real, RealFns, Rope, TIPUser, TypeScript, VFonts, ViewerClasses, ViewerIO, ViewerOps, ViewerTools;
ControlsImpl: CEDAR MONITOR
IMPORTS Buttons, Convert, FileNames, Imager, ImagerOps, ImagerPath, IO, Process, ProcessProps, Real, RealFns, Rope, TIPUser, TypeScript, VFonts, ViewerIO, ViewerOps, ViewerTools
EXPORTS Controls
~ BEGIN
OPEN Controls;
ROPE:   TYPE ~ Rope.ROPE;
maxNRows: INTEGER ~ 10;
tsHeight:   INTEGER ~ 18;
capHeight: INTEGER ~ 14;
PaintOuter: ViewerClasses.PaintProc ~ {
IF whatChanged = NIL THEN {
o: OuterData ← NARROW[self.data];
Imager.MaskRectangleI[context, 0, o.outerH-capHeight, self.ww, 1];
IF o.entries # NIL THEN Imager.MaskRectangleI[context, 0, o.entryY, self.ww, 1];
IF o.typeScript # NIL THEN Imager.MaskRectangleI[context, 0, o.tsY, self.ww, 1];
IF o.graphics # NIL THEN Imager.MaskRectangleI[context, 0, o.graphicsY-1, self.ww, 1];
};
};
AdjustOuter: ViewerClasses.AdjustProc ~ {
o: OuterData ← NARROW[self.data];
o.outerH ← self.wh;
IF o.graphics # NIL THEN {
o.graphicsH ← o.outerH-capHeight-o.controlH-o.entryH-o.tsH;
SetYs[o];
ViewerOps.EstablishViewerPosition[o.graphics, 0, o.graphicsY, self.ww, o.graphicsH];
};
IF o.typeScript # NIL
THEN ViewerOps.EstablishViewerPosition[o.typeScript, 0, o.tsY+1, self.ww, o.tsH-2];
IF o.entries # NIL THEN
FOR e: LIST OF Entry ← o.entries, e.rest WHILE e # NIL DO
ee: Entry ← e.first;
ViewerOps.EstablishViewerPosition[ee.viewer, ee.x, o.entryY+ee.y, ee.w, ee.h];
ENDLOOP;
};
PaintGraphics: ViewerClasses.PaintProc ~ {
d: GraphicsData ← NARROW[self.data];
d.show[context, d.viewer.ww, d.viewer.wh, d.data, whatChanged];
};
PaintControl: ViewerClasses.PaintProc ~ {
c: ControlData ← NARROW[self.data];
Action: PROC ~ {
IF whatChanged = NIL AND c.type = circ THEN Circle[context, c.cx, c.cy, c.rad, FALSE];
FOR detents: LIST OF Detents ← c.detents, detents.rest WHILE detents # NIL DO
Tick[context, c, detents.first.val, FALSE, 1];
ENDLOOP;
UnTick[context, c];
Tick[context, c, c.val, c.detented, 2];
c.predetented ← c.detented;
IF c.status # NIL THEN {
ViewerTools.SetContents[c.status, Convert.FtoRope[c.val, IF c.truncate THEN 0 ELSE 3]];
ViewerOps.PaintViewer[c.status, client];
};
};
ImagerOps.DoWithBuffer[context, Action, 0, 0, self.ww, self.wh];
};
gControlData: REF ANY;
gControlInput: LIST OF REF ANY;
newControlInputBoolean: BOOL;
newControlInputCondition: CONDITION;
NotifyControl: ViewerClasses.NotifyProc ~ {NewControlInput[self.data, input]};
NewControlInput: ENTRY PROC [data: REF ANY, input: LIST OF REF ANY] ~ {
gControlData ← data;
gControlInput ← input;
NOTIFY newControlInputCondition;
newControlInputBoolean ← TRUE;
};
GetControlInput: ENTRY PROC RETURNS [control: ControlData] ~ {
IF NOT newControlInputBoolean THEN WAIT newControlInputCondition;
newControlInputBoolean ← FALSE;
control ← NARROW[gControlData];
IF gControlInput = NIL THEN RETURN;
control.mouse ← SetMouse[NARROW[gControlInput.rest.first], NARROW[gControlInput.first]];
};
WatchControl: PROC ~ {
DO
c: ControlData ← GetControlInput[];
ComputeControlVal[c, c.mouse.x, c.mouse.y];
IF c.outerData # NIL THEN c.outerData.val ← c.val;
ViewerOps.PaintViewer[c.viewer, client, FALSE, c];
IF c.proc # NIL THEN c.proc[c];
ENDLOOP;
};
gGraphicsData: REF ANY;
gGraphicsInput: LIST OF REF ANY;
newGraphicsInputBoolean: BOOL;
newGraphicsInputCondition: CONDITION;
NotifyGraphics: ViewerClasses.NotifyProc ~ {NewGraphicsInput[self.data, input]};
NewGraphicsInput: ENTRY PROC [data: REF ANY, input: LIST OF REF ANY] ~ {
gGraphicsData ← data;
gGraphicsInput ← input;
newGraphicsInputBoolean ← TRUE;
NOTIFY newGraphicsInputCondition;
};
GetGraphicsInput: ENTRY PROC RETURNS [graphics: GraphicsData] ~ {
IF NOT newGraphicsInputBoolean THEN WAIT newGraphicsInputCondition;
newGraphicsInputBoolean ← FALSE;
graphics ← NARROW[gGraphicsData];
IF gGraphicsInput = NIL THEN RETURN;
graphics.mouse ←SetMouse[NARROW[gGraphicsInput.rest.first],NARROW[gGraphicsInput.first]];
};
WatchGraphics: PROC ~ {
DO
g: GraphicsData ← GetGraphicsInput[];
IF g.proc # NIL THEN g.proc[g];
ViewerOps.PaintViewer[g.viewer, client, FALSE, g];
ENDLOOP;
};
OuterViewer: PUBLIC PROC [
name: ROPENIL,
column: ViewerClasses.Column ← right,
entries: LIST OF Entry ← NIL,
controls: ControlList ← NIL,
controlSizes: ControlSizes ← [200, 25, 60],
graphicsHeight: INT ← 0,
graphicsProc: GraphicsProc ← NIL,
graphicsShow: GraphicsShow ← NIL,
destroyProc: DestroyProc ← NIL,
typeScript: BOOLFALSE,
data: REF ANYNIL,
noOpen: BOOLFALSE]
RETURNS [viewer: Viewer] ~ {
outerData: OuterData ← NEW[OuterDataRec ← [controls: controls, entries: entries, data: data]];
outerData.directory ← FileNames.CurrentWorkingDirectory[];
outerData.cmdOut ← WITH ProcessProps.GetProp[$CommanderHandle] SELECT FROM
cmd: Commander.Handle => cmd.err,
ENDCASE => NIL;
outerData.controlH ← ControlPositions[controls, controlSizes];
outerData.graphicsH ← graphicsHeight;
outerData.tsH ← IF typeScript THEN tsHeight ELSE 0;
outerData.entryH ← EntryPositions[entries];
outerData.outerH ←
outerData.controlH+outerData.graphicsH+outerData.tsH+outerData.entryH+capHeight;
SetYs[outerData];
viewer ← ViewerOps.CreateViewer[
flavor: $Outer,
paint: FALSE,
info: [
name: name,
data: outerData,
openHeight: outerData.outerH,
scrollable: FALSE,
column: column,
iconic: TRUE]
];
IF typeScript THEN {
outerData.typeScript ← TypeScript.Create[[parent: viewer, border: FALSE]];
[outerData.tSin,outerData.tSout] ← ViewerIO.CreateViewerStreams[NIL, outerData.typeScript];
};
IF graphicsHeight # 0 THEN {
outerData.graphics ← GraphicsViewer[viewer, outerData.controlH, graphicsHeight-100, graphicsProc, graphicsShow, data];
outerData.graphicsData ← NARROW[outerData.graphics.data];
};
EntryViewerList[viewer, entries, outerData];
ControlViewerList[viewer, outerData.graphics, controls, outerData];
outerData.destroyProc ← destroyProc;
outerData.parent ← viewer;
IF NOT noOpen THEN ViewerOps.OpenIcon[viewer];
};
SetYs: PROC [outer: OuterData] ~ {
outer.controlY ← 0;
outer.graphicsY ← outer.controlY+outer.controlH;
outer.tsY ← outer.graphicsY+outer.graphicsH;
outer.entryY ← outer.tsY+outer.tsH;
};
GraphicsViewer: PUBLIC PROC [parent: Viewer, y, h: INT ← 0, proc: GraphicsProc, show: GraphicsShow, data: REF ANY] RETURNS [viewer: Viewer] ~ {
graphicsData: GraphicsData ← NEW[GraphicsDataRec ← [
proc: proc, show: show, data: data, parent: parent]];
viewer ← ViewerOps.CreateViewer[
flavor: $Graphics,
paint: FALSE,
info: [
data: graphicsData,
scrollable: FALSE,
ww: parent.ww,
wy: y,
wh: h,
border: FALSE,
parent: parent]];
graphicsData.viewer ← viewer;
};
NewControl: PUBLIC PROC [
name: ROPENIL,
type: ControlType ← horiz,
taper: ControlTaper ← lin,
min, max, init: REAL ← 0.0,
report: BOOL ← TRUE,
truncate: BOOL ← FALSE,
detents: LIST OF Detents ← NIL,
proc: ControlProc ← NIL,
data: REF ANYNIL,
row: INTEGER ← 0,
x, y, w, h: INTEGER ← 0,
dummy: BOOLFALSE]
RETURNS [ControlData]
~ {
c: ControlData ← NEW[ControlDataRec];
IF type = circ THEN w ← h ← MIN[w, h];
c^ ← [
name: name,
type: type,
taper: taper,
min: min, max: max, init: init, val: init,
truncate: truncate,
report: report,
detents: detents,
proc: proc,
data: data,
x: x, y: y, w: w, h: h,
dummy: dummy];
IF row # 0 THEN ControlRow[c, row];
RETURN[c];
};
EntryReLabel: PUBLIC PROC [outerData: OuterData, oldName, newName: ROPE] ~ {
FOR e: LIST OF Entry ← outerData.entries, e.rest WHILE e # NIL DO
IF Rope.Equal[e.first.viewer.name, oldName]
THEN Buttons.ReLabel[e.first.viewer, newName];
ENDLOOP;
};
EntryToggle: PUBLIC PROC [outerData: OuterData, state: BOOL, trueName, falseName: ROPE] ~ {
IF state
THEN EntryReLabel[outerData, falseName, trueName]
ELSE EntryReLabel[outerData, trueName, falseName];
};
EntryStyle: PUBLIC PROC [outerData: OuterData, name: ROPE, style: ATOM] ~ {
FOR e: LIST OF Entry ← outerData.entries, e.rest WHILE e # NIL DO
IF Rope.Equal[e.first.viewer.name, name]
THEN Buttons.SetDisplayStyle[e.first.viewer, style];
ENDLOOP;
};
EntryPositions: PUBLIC PROC [entries: LIST OF Entry] RETURNS [entryHeight: INTEGER] ~ {
margin: INTEGER ~ 4;
entryRows: ARRAY [0..maxNRows) OF RECORD [x: INTEGER ← margin, y, h: INTEGER ← 0];
FOR e: LIST OF Entry ← entries, e.rest WHILE e # NIL DO
font: Imager.Font ← VFonts.DefaultFont[e.first.font];
IF e.first.w = 0 THEN e.first.w ← VFonts.StringWidth[e.first.name, font]+8;
IF e.first.h = 0 THEN e.first.h ← VFonts.FontHeight[font]+3;
ENDLOOP;
FOR e: LIST OF Entry ← entries, e.rest WHILE e # NIL DO
e.first.row ← MAX[0, MIN[maxNRows-1, e.first.row]];
DO
IF MAX[e.first.x, entryRows[e.first.row].x]+e.first.w < 600 THEN EXIT;
IF e.first.row >= maxNRows-2 THEN EXIT;
e.first.row ← e.first.row+1;
ENDLOOP;
IF e.first.x = 0
THEN e.first.x ← entryRows[e.first.row].x
ELSE entryRows[e.first.row].x ← e.first.x;
entryRows[e.first.row].x ← e.first.x+e.first.w+margin-1;
entryRows[e.first.row].h ← MAX[entryRows[e.first.row].h, e.first.h];
ENDLOOP;
entryRows[0].y ← margin+1;
entryHeight ← entryRows[0].y+entryRows[0].h+margin;
FOR n: NAT IN [1..maxNRows) DO
IF entryRows[n].h # 0 THEN entryHeight ← entryHeight+entryRows[n].h+margin;
entryRows[n].y ← entryRows[n-1].y+entryRows[n].h+margin;
ENDLOOP;
FOR e: LIST OF Entry ← entries, e.rest WHILE e # NIL DO
e.first.y ← entryRows[e.first.row].y;
ENDLOOP;
};
EntryViewerList: PUBLIC PROC [parent: Viewer, entries: LIST OF Entry, data: OuterData] ~ {
FOR e: LIST OF Entry ← entries, e.rest WHILE e # NIL DO
e.first.viewer ← EntryViewer[parent, e.first, data];
ENDLOOP;
};
EntryViewer: PUBLIC PROC [parent: Viewer, e: Entry, data: OuterData] RETURNS [Viewer] ~ {
RETURN[Buttons.Create[
info: [
parent: parent,
name: e.name,
wx: e.x,
wy: e.y],
proc: e.proc,
clientData: data,
fork: e.fork,
font: e.font,
documentation: e.documentation,
guarded: e.guarded,
paint: e.paint
]];
};
TypeScriptClear: PUBLIC PROC [outerData: OuterData] ~ {
IF outerData.tSout = NIL OR outerData.tSclear THEN RETURN;
IO.PutRope[outerData.tSout, "\n"];
outerData.tSclear ← TRUE;
};
TypeScriptWrite: PUBLIC PROC [outerData: OuterData, rope: ROPE] ~ {
IF outerData.tSout = NIL THEN RETURN;
IO.PutRope[outerData.tSout, rope];
outerData.tSclear ← FALSE;
};
TypeScriptRead: PUBLIC PROC [outerData: OuterData] RETURNS [ROPE] ~ {
IF outerData.tSin = NIL THEN RETURN[NIL];
ViewerTools.SetSelection[outerData.typeScript];
RETURN[IO.GetLineRope[outerData.tSin]];
};
TypeScriptReadFileName: PUBLIC PROC [outer: Controls.OuterData] RETURNS [ROPE] ~ {
reply: ROPE;
TypeScriptWrite[outer, "\nFilename: "];
reply ← TypeScriptRead[outer];
IF reply = NIL THEN {
TypeScriptWrite[outer, ". . . aborted.\n"];
RETURN[NIL];
};
RETURN[IF Rope.Find[reply, "/"] # -1 THEN reply ELSE Rope.Concat[outer.directory, reply]];
};
ControlRow: PUBLIC PROC [c: ControlData, row: INTEGER] ~ {
c.x ← 15;
c.y ← 15+(row-1)*(60+30);
c.row ← row;
};
ControlPositions: PUBLIC PROC [controls: ControlList, sizes: ControlSizes]
RETURNS [height: INTEGER]
~ {
x, y: INTEGER ← 15;
row, maxH: INTEGER ← 0;
hw: INTEGER ← sizes.horizWid;
vw: INTEGER ← sizes.vertWid;
dia: INTEGER ← sizes.circDia;
FOR c: ControlList ← controls, c.rest WHILE c # NIL AND c.first # NIL DO
cc: ControlData ← c.first;
IF cc.w = 0 THEN cc.w ← SELECT cc.type FROM horiz => hw, vert => vw, ENDCASE => dia;
IF cc.h = 0 THEN cc.h ← SELECT cc.type FROM horiz => vw, vert => dia, ENDCASE => dia;
IF cc.h > maxH THEN maxH ← cc.h;
IF cc.x # 0 THEN x ← cc.x;
IF cc.y # 0 THEN y ← cc.y;
IF x+cc.w+15 > 600 THEN {x ← 15; y ← y+maxH+30; row ← row+1; maxH ← 0};
cc.row ← row; cc.x ← x; cc.y ← y; x ← x+cc.w+15;
ENDLOOP;
height ← 0;
FOR c: ControlList ← controls, c.rest WHILE c # NIL AND c.first # NIL DO
temp: INTEGER ← c.first.h+c.first.y+12;
IF c.first.type = vert OR c.first.type = circ THEN {
IF c.first.name # NIL THEN temp ← temp+15;
IF c.first.report THEN temp ← temp+15;
};
height ← MAX[height, temp];
ENDLOOP;
};
ControlViewerList: PUBLIC PROC [parent, graphics: Viewer ← NIL, controls: Controls.ControlList, data: REF ANY] ~ {
FOR c: ControlList ← controls, c.rest WHILE c # NIL AND c.first # NIL DO
ControlViewer[parent, graphics, c.first, data];
ENDLOOP;
};
ControlViewer: PUBLIC PROC [parent, graphics: Viewer ← NIL, control: ControlData, data: REF ANYNIL, outerData: OuterData ← NIL] ~ {
viewer: Viewer;
IF control.dummy THEN RETURN;
FOR detents: LIST OF Detents ← control.detents, detents.rest WHILE detents # NIL DO
detents.first.t ← (detents.first.val-control.min)/(control.max-control.min);
ENDLOOP;
control.parent ← parent;
control.graphics ← graphics;
control.outerData ← outerData;
control.t ← (control.init-control.min)/(control.max-control.min);
control.val ← control.init;
IF outerData # NIL THEN outerData.val ← control.init;
control.rad ← MIN[control.w, control.h]/2-2;
control.cx ← control.w/2;
control.cy ← control.cx;
viewer ← ViewerOps.CreateViewer[
flavor: $Control,
paint: FALSE,
info: [
data: control,
wx: control.x,
wy: control.y,
ww: control.w,
wh: control.h,
border: control.type # circ,
scrollable: FALSE,
parent: parent]];
control.viewer ← viewer;
IF control.name # NIL THEN {
control.title ← NameViewer[control];
ViewerTools.SetContents[control.title, control.name];
};
IF control.report THEN control.status ← StatusViewer[control];
};
SetControlVal: PUBLIC PROC [c: ControlData, val: REAL, repaint: BOOLTRUE] ~ {
c.preval ← c.val;
c.val ← val;
IF repaint THEN ViewerOps.PaintViewer[c.viewer, client, FALSE, c];
};
GetControlVal: PUBLIC PROC [c: ControlData] RETURNS [REAL] ~ {
RETURN[c.val];
};
GetControlValInt: PUBLIC PROC [c: ControlData] RETURNS [INTEGER] ~ {
RETURN[Real.RoundI[c.val]];
};
ControlReset: PUBLIC PROC [c1, c2, c3, c4, c5, c6: Controls.ControlData ← NIL] ~ {
IF c1 # NIL THEN SetControlVal[c1, 0.0];
IF c2 # NIL THEN SetControlVal[c2, 0.0];
IF c3 # NIL THEN SetControlVal[c3, 0.0];
IF c4 # NIL THEN SetControlVal[c4, 0.0];
IF c5 # NIL THEN SetControlVal[c5, 0.0];
IF c6 # NIL THEN SetControlVal[c6, 0.0];
};
NameViewer: PROC [c: ControlData] RETURNS [v: Viewer] ~ {
font: Imager.Font ← VFonts.DefaultFont[];
v ← ViewerTools.MakeNewTextViewer[[
parent: c.parent,
ww: VFonts.StringWidth[c.name, font]+10,
wx: IF c.type = horiz THEN c.x+c.w+15 ELSE c.x,
wy: 13 + (IF c.type = horiz THEN c.y ELSE c.y+c.h+3),
wh: 13,
scrollable: FALSE,
border: FALSE]];
};
StatusViewer: PROC [c: ControlData] RETURNS [v: Viewer] ~ {
v ← ViewerTools.MakeNewTextViewer[[
parent: c.parent,
ww: IF c.type = horiz THEN 40 ELSE IF c.type = vert THEN 40 ELSE c.w,
wx: IF c.type = horiz THEN c.x+c.w+15 ELSE c.x,
wy: (IF c.type = horiz THEN c.y ELSE c.y+c.h+3) + (IF c.name = NIL THEN 13 ELSE 0),
wh: 13,
scrollable: FALSE,
border: FALSE]];
};
SetMouse: PUBLIC PROC [a: ATOM, t: TIPUser.TIPScreenCoords] RETURNS [Mouse] ~ {
RETURN[[
t.mouseX,
t.mouseY,
SELECT a FROM
$downLeft, $downMid, $downRite => down,
$heldLeft, $heldMid, $heldRite => held,
$upLeft, $upMid, $upRite => up,
ENDCASE => none,
SELECT a FROM
$downLeft, $heldLeft, $upLeft => left,
$downMid, $heldMid, $upMid => middle,
$downRite, $heldRite, $upRite => right,
ENDCASE => none
]];
};
Quit: PUBLIC ClickProc ~ {EndViewer[NARROW[parent, Controls.Viewer].parent]};
EndViewer: PUBLIC PROC [viewer: REF ANY] ~ {
ViewerOps.DestroyViewer[NARROW[viewer]];
};
DestroyOuter: ViewerClasses.DestroyProc ~ {
outer: OuterData ← NARROW[self.data];
outer.destroyed ← TRUE;
IF outer.destroyProc # NIL THEN outer.destroyProc[outer];
};
Restore: PUBLIC ClickProc ~ {
data: OuterData ← NARROW[clientData];
FOR c: ControlList ← data.controls, c.rest WHILE c # NIL DO
c.first.val ← c.first.init;
ViewerOps.PaintViewer[c.first.viewer, client, FALSE, c.first];
ENDLOOP;
};
UnTick: PUBLIC PROC[context: Imager.Context, c: ControlData] ~ {
Imager.SetColor[context, Imager.white];
Tick[context, c, c.preval, c.predetented AND NOT c.detented, 2];
Imager.SetColor[context, Imager.black];
};
Tick: PUBLIC PROC [context: Imager.Context, c: ControlData, val: REAL, detent: BOOLFALSE, width: INT ← 1] ~ {
SELECT c.type FROM
vert => {
y: REAL ← c.h*(val-c.min)/(c.max-c.min);
context.MaskRectangle[[0, y-width/2, c.w, width]];
IF detent THEN Square[context, c.w/2, y, 3.0];
};
horiz => {
x: REAL ← c.w*(val-c.min)/(c.max-c.min);
context.MaskRectangle[[x-width/2, 0, width, c.h]];
IF detent THEN Square[context, x+1, c.h/2-1, 3.0];
};
circ => {
rad: REAL ← c.rad-3;
deg: REAL ← 360.0*(val-c.min)/(c.max-c.min);
Imager.SetStrokeWidth[context, width];
context.MaskVector[[c.cx+rad*RealFns.CosDeg[deg], c.cy+rad*RealFns.SinDeg[deg]], [c.cx, c.cy]];
IF detent THEN Circle[context, c.cx, c.cy, 3.0, TRUE];
};
ENDCASE => NULL;
};
Circle: PUBLIC PROC [context: Imager.Context, x, y, rad: REAL, fill: BOOLFALSE] ~ {
t: Imager.Trajectory ← ImagerPath.MoveTo[[x+rad, y]];
t ← ImagerPath.ArcTo[t, [x, y+rad], [x-rad, y]];
t ← ImagerPath.ArcTo[t, [x, y-rad], [x+rad, y]];
Imager.SetStrokeWidth[context, 1.15];
IF fill THEN Imager.MaskFillTrajectory[context, t]
ELSE Imager.MaskStrokeTrajectory[context, t, TRUE];
};
Square: PUBLIC PROC [context: Imager.Context, x, y, size: REAL] ~ {
context.MaskRectangle[[x-size-1, y-size, 2*size, 2*size]];
};
ComputeControlVal: PROC [c: ControlData, x, y: INT] ~ {
t, val0, val1: REAL;
SELECT c.type FROM
vert => c.t ← REAL[y]/REAL[c.h-3];
horiz => c.t ← REAL[x]/REAL[c.w-2];
ENDCASE => {
c.t ← RealFns.ArcTan[y-c.cy, x-c.cx]/(2.0*3.1415926535);
IF y-c.cy < 0.0 THEN c.t ← c.t+1.0;
};
c.t ← MIN[1.0, MAX[0.0, c.t]];
c.detented ← FALSE;
IF c.detents = NIL THEN {t ← c.t; val0 ← c.min; val1 ← c.max}
ELSE {
grain: REAL ~ 0.05;
detents: LIST OF Detents ← c.detents;
t0, t1: REAL ← 0.0;
val1 ← c.min;
WHILE detents # NIL DO
t0 ← t1; t1 ← detents.first.t;
val0 ← val1; val1 ← detents.first.val;
IF c.t < t1 THEN EXIT;
detents ← detents.rest;
ENDLOOP;
IF detents = NIL THEN {t1 ← 1.0; val1 ← c.max};
IF t0 > 0.0 AND c.t-t0 < grain THEN {t ← 0.0; c.detented ← TRUE}
ELSE IF t1 < 1.0 AND t1-c.t < grain THEN {t ← 1.0; c.detented ← TRUE}
ELSE {
IF t0 > 0.0 THEN t0 ← t0+grain; IF t1 < 1.0 THEN t1 ← t1-grain;
t ← (c.t-t0)/(t1-t0);
};
};
IF c.taper = log THEN t ← Real.SqRt[t]
ELSE IF c.taper = exp THEN t ← t*t;
c.preval ← c.val;
c.val ← (1.0-t)*val0+t*val1;
IF c.truncate THEN c.val ← Real.Round[c.val];
};
Append: PUBLIC PROC [control: ControlData, list: ControlList ← NIL] RETURNS [ControlList] ~ {
tail: ControlList ← list;
List: TYPE ~ RECORD[first: ControlData, rest: REF List ← NIL];
IF tail = NIL THEN RETURN[LIST[control]];
WHILE tail.rest # NIL DO tail ← tail.rest; ENDLOOP;
tail.rest ← LIST[control];
RETURN[list];
};
TRUSTED {Process.Detach[FORK WatchControl]};
TRUSTED {Process.Detach[FORK WatchGraphics]};
     
ViewerOps.RegisterViewerClass[$Outer, NEW[ViewerClasses.ViewerClassRec ← [
paint: PaintOuter,
adjust: AdjustOuter,
destroy: DestroyOuter]]];
ViewerOps.RegisterViewerClass[$Graphics, NEW[ViewerClasses.ViewerClassRec ← [
paint: PaintGraphics,
notify: NotifyGraphics,
tipTable: TIPUser.InstantiateNewTIPTable["Controls.TIP"]]]];
ViewerOps.RegisterViewerClass[$Control, NEW[ViewerClasses.ViewerClassRec ← [
paint: PaintControl,
notify: NotifyControl,
tipTable: TIPUser.InstantiateNewTIPTable["Controls.TIP"]]]];
END.