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, "