DIRECTORY CedarProcess, Commander, CommanderOps, Controls, Draw2d, FileNames, G2dBasic, G2dImplicitTool, G2dVector, Icons, Imager, ImagerBackdoor, ImagerSample, IO, Process, Real, RealFns, Rope, VFonts, ViewerClasses, ViewerOps;
G2dImplicitToolImpl: CEDAR PROGRAM
IMPORTS CedarProcess, CommanderOps, Controls, Draw2d, FileNames, G2dBasic, G2dVector, Icons, Imager, IO, Process, Real, RealFns, Rope, VFonts, ViewerOps
EXPORTS G2dImplicitTool
~ BEGIN
ForkableProc: TYPE ~ CedarProcess.ForkableProc;
CommandProc: TYPE ~ Commander.CommandProc;
ButtonList: TYPE ~ Controls.ButtonList;
ClickProc: TYPE ~ Controls.ClickProc;
ControlList: TYPE ~ Controls.ControlList;
Request: TYPE ~ Controls.Request;
DrawProc: TYPE ~ Draw2d.DrawProc;
Zip: TYPE ~ Draw2d.Zip;
IntegerPair: TYPE ~ G2dBasic.IntegerPair;
Pair: TYPE ~ G2dBasic.Pair;
PairSequence: TYPE ~ G2dBasic.PairSequence;
StartProc: TYPE ~ G2dImplicitTool.StartProc;
Tool: TYPE ~ G2dImplicitTool.Tool;
ValueProc: TYPE ~ G2dImplicitTool.ValueProc;
Context: TYPE ~ Imager.Context;
ROPE: TYPE ~ Rope.ROPE;
Direction: TYPE ~ {l, t, r, b}; -- clockwise: left, top, right, bottom
Point: TYPE ~ RECORD [pos: Pair, value: REAL];
icon: Icons.IconFlavor ¬ Icons.NewIconFromFile["G2dUser.icons", 8];
MakeTool: PUBLIC PROC [
name: ROPE,
valueProc: ValueProc,
extraButtons: ButtonList ¬ NIL,
extraControls: ControlList ¬ NIL,
clientData: REF ANY ¬ NIL,
clientDraw: DrawProc ¬ NIL,
startProc: StartProc ¬ NIL,
scale: REAL ¬ 1.0,
move: Pair ¬ [0.0, 0.0],
toolSettings: G2dImplicitTool.ToolRep ¬ []]
RETURNS [t: Tool]
~ {
AddButton: PROC [name: ROPE, proc: ClickProc, guarded: BOOL ¬ FALSE] ~ {
extraButtons ¬ CONS[Controls.ClickButton[name, proc, t,,,,,,, guarded], extraButtons];
};
t ¬ NEW[G2dImplicitTool.ToolRep ¬ toolSettings];
AddButton["IO", IOButton];
AddButton["Display", DisplayButton];
AddButton["Curve", CurveButton];
AddButton["STOP", StopButton, TRUE];
AddButton["Animation", AnimationButton];
extraControls ¬ CONS[t.scale ¬ Controls.NewControl["Scale",, t, 0, 2, scale, Cam], extraControls];
extraControls ¬ CONS[t.moveX ¬Controls.NewControl["X",, t, -2, 2, move.x, Cam], extraControls];
extraControls ¬ CONS[t.moveY ¬Controls.NewControl["Y",, t, -2, 2, move.y, Cam], extraControls];
t.startProc ¬ startProc;
t.valueProc ¬ valueProc;
t.clientDraw ¬ clientDraw;
t.clientData ¬ clientData;
t.buttons ¬ extraButtons;
t.outerData ¬ Controls.OuterViewer[
name: name,
buttons: extraButtons,
controls: extraControls,
typescriptHeight: 18,
graphicsHeight: 300,
destroyProc: Destroy,
drawProc: Draw,
noOpen: TRUE,
icon: icon,
clientData: t];
t.graphics ¬ t.outerData.graphics;
t.outerData.parent.label ¬ name;
t.typescript ¬ t.outerData.typescript;
[] ¬ CedarProcess.Fork[OpenViewer, t]; -- don't ask client to draw till after tool returned
};
Cam: Controls.ControlProc ~ {Repaint[NARROW[clientData]]};
OpenViewer: ForkableProc ~ {
t: Tool ¬ NARROW[data];
Process.PauseMsec[500]; -- should be plenty
ViewerOps.OpenIcon[t.outerData.parent];
};
Destroy: Controls.DestroyProc ~ {CedarProcess.Abort[NARROW[clientData, Tool].process]};
ToolBusy: PUBLIC PROC [tool: Tool] RETURNS [BOOL] ~ {
RETURN[tool.process # NIL AND CedarProcess.GetStatus[tool.process] = busy];
};
MaybeFork: PUBLIC PROC [tool: Tool, proc: ForkableProc] RETURNS [forked: BOOL] ~ {
IF (forked ¬ NOT ToolBusy[tool])
THEN tool.process ¬ CedarProcess.Fork[proc, tool, [background, TRUE]]
ELSE TSWrite[tool, "Tool is busy!\n"];
};
Stop: PUBLIC PROC [tool: Tool, reason: ROPE ¬ NIL, waitTilAborted: BOOL ¬ FALSE] ~ {
IF tool.process = NIL OR tool.process.abortRequested THEN RETURN;
CedarProcess.Abort[tool.process];
IF waitTilAborted THEN [] ¬ CedarProcess.Join[tool.process];
IF reason # NIL THEN TSWrite[tool, reason];
};
StopButton: ClickProc ~ {Stop[NARROW[clientData], " . . . aborted\n"]};
AnimationButton: ClickProc ~ {
t: Tool ¬ NARROW[clientData];
SELECT Controls.PopUpRequest[["Animation Options"], LIST[
--1 -- IntRequest["First Frame", "First frame of animation", t.firstFrame],
--2 -- IntRequest["Last Frame", "Last frame of animation", t.lastFrame],
--3 -- IntRequest["Current Frame", "Current frame in the animation", t.currentFrame],
--4 -- RopeRequest["Ouptut IP name", "Base for Interpress files", t.animateIP],
--5 -- ["ANIMATE Curve", "Create frame from current through last"]]] FROM
1 => t.firstFrame ¬ Controls.GetNat[t.typescript, "first frame", t.firstFrame];
2 => t.lastFrame ¬ Controls.GetNat[t.typescript, "last frame", t.lastFrame];
3 => t.currentFrame ¬ Controls.GetNat[t.typescript, "current frame", t.currentFrame];
4 => t.animateIP ¬ Controls.TypescriptReadFileName[t.typescript];
5 => [] ¬ MaybeFork[t, ForkAnimateCurve];
ENDCASE;
};
ForkAnimateCurve: ForkableProc ~ {
t: Tool ¬ NARROW[data];
FOR i: NAT IN [t.currentFrame..t.lastFrame] DO
IF t.animateIP # NIL THEN t.saveIP ¬ IO.PutFR["%g.%g.ip", IO.rope[t.animateIP], IO.int[i]];
t.currentFrame ¬ i;
MakeCurve[t];
ENDLOOP;
};
Repaint: PUBLIC PROC [tool: Tool, whatChanged: REF ANY ¬ NIL] ~ {
ViewerOps.PaintViewer[tool.graphics, client, FALSE, whatChanged];
};
Draw: DrawProc ~ {
Action: PROC ~ {
DrawClient: PROC ~ {
IF t.clientDraw # NIL AND t.displayClient
THEN t.clientDraw[context, t.clientData, whatChanged, viewer];
};
DrawCurve: PROC ~ {
IF t.curve # NIL THEN {
p0: Pair ¬ t.curve[0];
FOR n: NAT IN [1..t.curve.length) DO
p1: Pair ¬ t.curve[n];
Draw2d.Line[context, p0, p1,, t.zip];
p0 ¬ p1;
ENDLOOP;
};
};
Imager.TranslateT[context, [t.moveX.value+0.5*viewer.cw, t.moveY.value+0.5*viewer.ch]];
Imager.ScaleT[context, t.scale.value*0.5*MIN[viewer.cw, viewer.ch]];
t.zip ¬ Draw2d.GetZip[context];
SELECT whatChanged FROM
$Clear => Draw2d.Clear[context];
$Client => DrawClient[];
$Point => Draw2d.Line[context, t.curve[t.curve.length-2], t.curve[t.curve.length-1],,t.zip];
ENDCASE => {Draw2d.Clear[context]; DrawCurve[]; DrawClient[]};
Draw2d.ReleaseZip[t.zip];
};
t: Tool ¬ NARROW[clientData];
IF whatChanged = $Client
THEN Draw2d.DoWithBuffer[context, Action, t.displayClient]
ELSE Action[];
};
DisplayButton: ClickProc ~ {
t: Tool ¬ NARROW[clientData];
SELECT Controls.PopUpRequest[["Display"], LIST[
--1-- Controls.BoolRequest[t.displayClient, "Display Client"]]] FROM
1 => t.displayClient ¬ NOT t.displayClient;
ENDCASE => RETURN;
IF Controls.GetPopUpButton[] = right THEN Repaint[t, $Client];
};
CurveButton: ClickProc ~ {
RealRequest: PROC [title, doc: ROPE, value: REAL] RETURNS [Request] ~ {
RETURN[[IO.PutFR["%g (now %6.3f)", IO.rope[title], IO.real[value]], doc]];
};
t: Tool ¬ NARROW[clientData];
SELECT Controls.PopUpRequest[["Quadtree Options"], LIST[
--1-- RealRequest["tracking size", "Square size for tracking curve", t.size],
--2-- RealRequest["threshold", "Contour level", t.threshold],
--3-- ["MAKE CURVE", "Create the contour curve"]]] FROM
1 => t.size ¬ Controls.GetReal[t.typescript, "track size", t.size];
2 => t.threshold ¬ Controls.GetReal[t.typescript, "threshold", t.threshold];
3 => [] ¬ MaybeFork[t, ForkMakeCurve];
ENDCASE;
};
ForkMakeCurve: ForkableProc ~ {MakeCurve[NARROW[data]]};
MakeCurve: PROC [t: Tool] ~ {
Cross: PROC [p0, p1: Point] RETURNS [b: BOOL] ~ {b ¬ (p0.value < 0.0) # (p1.value <= 0.0)};
DoPoint: PROC [p: Point, d: Direction] RETURNS [Point] ~ {
pos: Pair ¬ SELECT d FROM
l => [p.pos.x-t.size, p.pos.y],
t => [p.pos.x, p.pos.y-t.size],
r => [p.pos.x+t.size, p.pos.y],
ENDCASE => [p.pos.x, p.pos.y+t.size];
RETURN[[pos, t.valueProc[pos, t.clientData]-t.threshold]];
};
NewVertex: PROC [in, out: Point] ~ {
t.curve ¬ G2dBasic.AddToPairSequence[t.curve,
Converge[in, out, t.valueProc, t.threshold, t.clientData]];
IF t.curve.length > 1 THEN Repaint[t, $Point];
};
Process: PROC [p0, p1: Point, d: Direction] ~ {
id: IntegerPair ¬ [0, 0];
DO
CedarProcess.CheckAbort[];
NewVertex[p0, p1];
d ¬ SELECT d FROM l => b, t => l, r => t, ENDCASE => r;
DO
p1 ¬ DoPoint[p0, d];
IF Cross[p0, p1] THEN EXIT;
p0 ¬ p1;
d ¬ SELECT d FROM l => t, t => r, r => b, ENDCASE => l;
ENDLOOP;
id ¬ SELECT d FROM
l => [id.x-1, id.y], t => [id.x, id.y-1], r => [id.x+1, id.y], ENDCASE => [id.x, id.y+1];
IF id = [0, 0] THEN {NewVertex[p0, p1]; EXIT};
ENDLOOP;
};
start: Pair ¬ FindStart[t.valueProc, t.threshold, t.clientData ! FindError => GOTO Bad];
p: Pair ¬ [t.size*REAL[Real.Round[start.x/t.size]], t.size*REAL[Real.Round[start.y/t.size]]];
lt: Point ¬ [p, t.valueProc[p, t.clientData]-t.threshold];
rt: Point ¬ DoPoint[lt, r];
rb: Point ¬ DoPoint[rt, b];
lb: Point ¬ DoPoint[rb, l];
IF t.curve # NIL THEN t.curve.length ¬ 0;
Repaint[t, $Clear];
IF Cross[rt, lt] THEN Process[lt, rt, r] ELSE
IF Cross[rb, rt] THEN Process[rt, rb, b] ELSE
IF Cross[lb, rb] THEN Process[rb, lb, l] ELSE
IF Cross[lt, lb] THEN Process[lb, lt, t];
EXITS Bad => TSWrite[t, "Can't find starting point"];
};
FindError: ERROR = CODE;
FindStart: PROC [value: ValueProc, threshold: REAL, clientData: REF ANY] RETURNS [Pair] ~ {
Find: PROC [sign: {neg, pos}] RETURNS [p: Point] ~ {
d, v: REAL ¬ 0.1;
THROUGH [0..20) DO
FOR alpha: REAL ¬ 0.0, alpha+30.0 WHILE alpha <= 360.0 DO
p.pos ¬ [d*RealFns.CosDeg[alpha], d*RealFns.SinDeg[alpha]];
p.value ¬ value[p.pos, clientData]-threshold;
IF (sign = neg AND p.value <= 0.0) OR (sign = pos AND p.value >= 0.0) THEN RETURN;
ENDLOOP;
d ¬ d+d;
ENDLOOP;
ERROR FindError;
};
RETURN[Converge[Find[pos], Find[neg], value, threshold, clientData]];
};
Converge: PROC [in, out: Point, value: ValueProc, threshold: REAL, clientData: REF ANY]
RETURNS [p: Pair]
~ {
Inner: PROC [in, out: Point, nTries: NAT ¬ 0] RETURNS [p: Pair] ~ {
v, dAbs: REAL ¬ ABS[G2dVector.Distance[in.pos, out.pos]];
p ¬ G2dVector.Midpoint[in.pos, out.pos];
IF nTries = 0 THEN epsilon ¬ 0.001*dAbs;
IF dAbs < epsilon OR (nTries ¬ nTries+1) > 10 THEN RETURN;
RETURN[IF (v ¬ value[p, clientData]-threshold) < 0.0
THEN Inner[in, [p, v], nTries]
ELSE Inner[[p, v], out, nTries]];
};
epsilon: REAL;
IF in.value < 0.0 THEN {pt: Point ¬ in; in ¬ out; out ¬ pt};
p ¬ SELECT TRUE FROM in.value=0=> in.pos, out.value=0=> out.pos, ENDCASE => Inner[in, out];
};
IOButton: ClickProc ~ {
t: Tool ¬ NARROW[clientData];
Inner: PROC ~ {
SELECT Controls.PopUpRequest[["Output"], LIST[
--1-- ["OUTPUT INTERPRESS", "Output interpress file of the current display"],
--2-- ["IP stroke width", "width of strokes for IP output"]]] FROM
1 => Draw2d.IPOut[Controls.TypescriptReadFileName[t.typescript], Draw, t];
2 => t.ipStrokeWidth ¬ Controls.GetReal[t.typescript, "Stroke width", t.ipStrokeWidth];
ENDCASE;
};
CedarProcess.DoWithPriority[background, Inner];
};
RopeRequest: PROC [title, doc, value: ROPE] RETURNS [req: Request] ~ {
req ¬ IF value = NIL
THEN [IO.PutFR1["%g", IO.rope[title]], doc]
ELSE [IO.PutFR["%g (now %g)", IO.rope[title], IO.rope[value]], doc];
};
IntRequest: PROC [title, doc: ROPE, value: NAT] RETURNS [Request] ~ {
RETURN[[IO.PutFR["%g (now %g)", IO.rope[title], IO.int[value]], doc]];
};
Eq: PROC [r1, r2: ROPE] RETURNS [b: BOOL] ~ {b ¬ Rope.Equal[r1, r2, FALSE]};
TSWrite: PROC [t: Tool, rope: ROPE] ~ {Controls.TypescriptWrite[t.typescript, rope]};
Registration: TYPE ~ RECORD [
name: ROPE, -- name of implicit function
command: CommandProc, -- proc to be called
doc: ROPE -- documentation of function
];
registry: LIST OF Registration ¬ NIL;
Register: PUBLIC PROC [name: ROPE, command: CommandProc, doc: ROPE] ~ {
IF registry = NIL
THEN registry ¬ LIST[[name, command, doc]]
ELSE FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
IF Eq[l.first.name, name] THEN {l.first ¬ [name, command, doc]; EXIT};
IF l.rest = NIL THEN {l.rest ¬ LIST[[name, command, doc]]; EXIT};
ENDLOOP;
};
ToolOptions: PUBLIC PROC RETURNS [toolOptions: ROPE ¬ NIL] ~ {
tabWidth: INT ¬ 2*VFonts.StringWidth["\t"]; -- VFonts says 12; but should be 24
FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
colonTabs: ROPE ¬ ":";
nTabs: INT ¬ 2+MAX[81-VFonts.StringWidth[Rope.Concat[l.first.name, ":"]], 0]/tabWidth;
THROUGH [1..nTabs] DO colonTabs ¬ Rope.Concat[colonTabs, "\t"]; ENDLOOP;
toolOptions ¬ Rope.Cat[toolOptions, "\n\t", l.first.name, colonTabs, l.first.doc];
ENDLOOP;
};
ExecuteOption: PUBLIC CommandProc ~ {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
IF argv.argc > 1 THEN SELECT TRUE FROM
Eq[argv[1], "reset"] => registry ¬ NIL;
ENDCASE => {
FOR l: LIST OF Registration ¬ registry, l.rest WHILE l # NIL DO
IF Eq[l.first.name, argv[1]] THEN {
[result, msg] ¬ l.first.command[cmd];
RETURN;
};
ENDLOOP;
RETURN[$Failure, "No such option."];
};
};
DesignCmd: PUBLIC CommandProc ~ {
argv: CommanderOps.ArgumentVector ¬ CommanderOps.Parse[cmd];
name: ROPE ¬ FileNames.GetShortName[cmd.command]; -- proc may be registered elsewhere
IF argv.argc < 2 THEN RETURN[$Failure, Rope.Concat[name, "