<* PRAGMA LL *> <* PRAGMA SPEC *> MODULEIf; IMPORT CurrCmd, View, JunoError, JunoBuild, Drag, PSImpl, JunoPt; IMPORT Editor, ToolBox, JunoConfig; IMPORT JunoAST, JunoASTUtils, JunoCompileErr; IMPORT JunoValue, JunoRT; IMPORT TextEditVBT, KeyTrans; IMPORT VBT, VBTClass, PaintOp, Point, Region, Rect, Path; IMPORT Axis, Filter, Pixmap, DblBufferVBT; IMPORT KeyboardKey, Latin1Key; IMPORT Atom, Text, Real, Fmt; IMPORT RTCollector; CONST DummyXY = JunoPt.T{0.0, 0.0}; (* dummy point location *) REVEAL View.Drawing = Drag.T BRANDED "View.Drawing" OBJECT (* General state *) tool: ArgTool := NIL; lastTool: Tool := NIL; usingTempTool := FALSE; freezeTool, dragTool: Tool := NIL; (* CONST after initialization *) labelStyle: [0..2] := 2; pointCount := 0; continuousUnparse := TRUE; hasMouseFocus: BOOLEAN; prevStackSize: CARDINAL := 0; (* "Click" args *) goodDownClick: BOOLEAN; (* "Text" arg *) txt: TEXT; (* "txt # NIL" <=> user typing text argument *) (* Grid state *) toolBox: ToolBox.T; gridMode := FALSE; grid0, grid1: JunoPt.T; (* grid origin and unix X vector *) grid0nm, grid1nm: JunoAST.QId; gridOn, gridOff: Tool; OVERRIDES init := InitDrawing; update := Update; END; Drawing
d is a Drawing.T, then d.tool is the currently selected tool; it
is NIL if no tool is currently selected. The UI supports freezing and
dragging without having selected those tools directly (by using different
mouse buttons). In the case that one of these tools is being applied in
that way, usingTempTool is true and lastTool stores the tool that was
in effect before the freeze or drag tool was used; they are FALSE and NIL,
respectively, otherwise. freezeTool and dragTool are simply cached
tools so new ones don't have to be allocated on each use.
The values for d.labelStyle are: 0 for no labels, 1 for crosses, and 2
for point-name labels.
The value d.pointCount is the number of the next potential point. The
drawing view maps cardinals into point names. When a new point is created,
it searches for an available point name starting with the one designated by
d.pointCount. Since point names can only be deleted by editing the source
view, d.pointCount need only be reset whenever the drawing view is
updated.
The value d.hasMouseFocus is true if d was delivered a transition of
type FirstDown, but not the matching transition of type LastUp.
Drawings ignore transitions of type LastUp if they do not have the mouse
focus.
The field d.goodDownClick is relevant when the current argument of
d.tool has type ArgType.Click. It is true if the most recent
FirstDown mouse transition or down transition of the space key
successfully selected an argument point in the drawing.
The field d.txt is relevant when the current argument of d.tool has
type argType.Text. In this case, d.tool must be a ProcTool. The field
d.txt contains the text typed by the user so far (modulo backspaces), but
not containing a trailing vertical bar character.
REVEAL
ChildWriteOnly = ChildPublic BRANDED "Drawing.ChildWO" OBJECT
originVal: JunoConfig.Origin
OVERRIDES
init := InitWriteOnly;
getOrigin := ChildGetOrigin;
setOrigin := ChildSetOrigin;
<* LL.sup = VBT.mu.SELF *>
rescreen := RescreenWriteOnly;
reshape := ReshapeWriteOnly;
END;
Child = ChildWriteOnly BRANDED "Drawing.Child" OBJECT
hasFocus := FALSE
OVERRIDES
<* LL.sup = VBT.mu *>
mouse := Mouse;
position := Position;
key := Key;
misc := Misc;
<* LL.sup = VBT.mu.SELF *>
reshape := Reshape;
repaint := Repaint;
END;
A Child is the leaf VBT in which painting for the drawing view is
performed. A Child is installed as a child (in the tree of VBT's) of
a Drawing.T (which is also both a View.T and a Filter.T).
REVEAL
ArgTool = Drag.ArgTool BRANDED "Drawing.ArgTool" OBJECT OVERRIDES
setup := SetupNoop;
END;
PredTool = ArgTool BRANDED "Drawing.PredTool" OBJECT OVERRIDES
apply := ApplyPred
END;
FuncTool = ArgTool BRANDED "Drawing.FuncTool" OBJECT OVERRIDES
apply := ApplyFunc
END;
ProcTool = ProcToolPublic BRANDED "Drawing.ProcTool" OBJECT
txt: JunoAST.Text := NIL;
call: JunoAST.Save
OVERRIDES
setup := SetupProc;
apply := ApplyProc;
text := TextProc
END;
(* "txt # NIL => call # NIL"; "txt" will be the last argument of "call" *)
SetTool = Tool BRANDED "Drawing.SetTool" OBJECT
cmd: JunoAST.ProcCall
OVERRIDES
setup := SetupNoop;
apply := ApplySetTool
END;
PROCEDURE SetupNoop (<*UNUSED*> tl: Tool; <*UNUSED*> d: T;
<*UNUSED*> time: VBT.TimeStamp) =
BEGIN END SetupNoop;
VAR (*CONST*)
Red := PaintOp.FromRGB(1.0, 0.0, 0.0);
PathColor := Red;
SelectColor := Red;
FrozenColor := PaintOp.FromRGB(0.0, 0.35, 1.0);
LabelColor := PaintOp.BgFg;
PROCEDURE InitDrawing (d: T; ch: ChildPublic; root: View.Root): PSImpl.T =
BEGIN
EVAL PSImpl.T.init(d, ch, root);
d.freezeTool := NewFreezeTool();
d.dragTool := Drag.NewTool();
RETURN d
END InitDrawing;
PROCEDURE InitWriteOnly (ch: ChildWriteOnly; origin: JunoConfig.Origin): Child =
BEGIN
ch.originVal := origin;
RETURN ch
END InitWriteOnly;
PROCEDURE SetLabelStyle (d: T; style: [0..2]) =
BEGIN
IF style # d.labelStyle THEN
d.labelStyle := style;
d.root.dTrue := FALSE;
d.update()
END
END SetLabelStyle;
PROCEDURE AcquireKBFocus (ch: Child; time: VBT.TimeStamp) =
Acquire the keyboard focus if Juno doesn't have it already.
BEGIN
IF NOT ch.hasFocus THEN
TRY
VBT.Acquire(ch, VBT.KBFocus, time);
ch.hasFocus := TRUE
EXCEPT VBT.Error => (* SKIP *)
END
END
END AcquireKBFocus;
PROCEDURE Misc (ch: Child; READONLY cd: VBT.MiscRec) =
<* LL.sup = VBT.mu *>
BEGIN
IF cd.type = VBT.Lost AND cd.selection = VBT.KBFocus THEN
ch.hasFocus := FALSE;
EVAL FinishTextTool(VBT.Parent(ch))
END
END Misc;
PROCEDURE DragModeFromModifiers (mods: VBT.Modifiers): Drag.DragMode =
Return the drag mode appropriate for the modifier keys mods.
BEGIN
IF VBT.Modifier.Shift IN mods THEN
RETURN Drag.DragMode.Hor
ELSIF VBT.Modifier.Option IN mods OR VBT.Modifier.Control IN mods THEN
RETURN Drag.DragMode.Ver
ELSE
RETURN Drag.DragMode.Unconstrained
END
END DragModeFromModifiers;
PROCEDURE Mouse (ch: Child; READONLY cd: VBT.MouseRec) =
<* LL.sup = VBT.mu *>
VAR d: T := VBT.Parent(ch); BEGIN
(* Ignore "LastUp" events if "d" does not have the mouse focus. *)
IF NOT d.hasMouseFocus AND cd.clickType = VBT.ClickType.LastUp THEN
RETURN
END;
IF cd.clickType = VBT.ClickType.FirstDown THEN
AcquireKBFocus(ch, cd.time)
END;
(* Don't allow manipulations to drawing unless the AST is up to date
and we are not in the process of entering text. *)
IF NOT d.root.astTrue THEN
JunoError.Display(ch, "Oops! You forgot to click Run.");
RETURN
ELSIF d.txt # NIL THEN
RETURN
END;
(* Check for use of "freeze" or "drag" tool shortcut *)
IF cd.clickType = VBT.ClickType.FirstDown AND d.stackSize = 0 THEN
CASE cd.whatChanged OF
VBT.Modifier.MouseM =>
d.usingTempTool := TRUE; d.lastTool := d.tool; d.tool := d.dragTool
| VBT.Modifier.MouseR =>
d.usingTempTool := TRUE; d.lastTool := d.tool; d.tool := d.freezeTool
ELSE (* SKIP *)
END
END;
(* Don't proceed unless a tool has been selected; ignore errant
middle- and right-mouse clicks. *)
IF d.tool = NIL THEN
JunoError.Display(ch, "Oops! You need to choose a tool.");
RETURN
ELSIF cd.whatChanged # VBT.Modifier.MouseL AND NOT d.usingTempTool THEN
RETURN
END;
(* Process the mouse click based on the type of the current argument for
the current drawing tool. *)
CASE d.tool.argType[d.stackSize] OF <* NOWARN *>
ArgType.Click =>
CASE cd.clickType OF
VBT.ClickType.FirstDown =>
d.hasMouseFocus := TRUE;
VAR arg: Arg; BEGIN
IF VBT.Modifier.Shift IN cd.modifiers THEN
arg := ClickNewPt(d, cd.cp.pt, ch.xform,
frozen := VBT.Modifier.Option IN cd.modifiers);
SourceUntrue(d, View.ModKind.ImplicitConsistent);
ELSIF NOT FindArg(d, JunoPt.FromHV(cd.cp.pt, ch.xform), arg) THEN
d.goodDownClick := FALSE;
RETURN
END;
Push(d, arg);
PaintStackTop(d, select := TRUE)
END;
d.goodDownClick := TRUE;
| VBT.ClickType.LastUp =>
d.hasMouseFocus := FALSE;
IF d.goodDownClick THEN
PaintStackTop(d, select := FALSE);
ApplyIfReady(d, cd.time)
END
ELSE
RETURN
END
| ArgType.CreateClick =>
CASE cd.clickType OF
VBT.ClickType.FirstDown =>
d.hasMouseFocus := TRUE;
VAR arg: Arg; BEGIN
arg := ClickNewPt(d, cd.cp.pt, ch.xform,
frozen := VBT.Modifier.Option IN cd.modifiers);
SourceUntrue(d, View.ModKind.ImplicitConsistent);
Push(d, arg)
END;
PaintStackTop(d, select := TRUE)
| VBT.ClickType.LastUp =>
d.hasMouseFocus := FALSE;
PaintStackTop(d, select := FALSE);
ApplyIfReady(d, cd.time)
ELSE
RETURN
END
| ArgType.Drag =>
CASE cd.clickType OF
VBT.ClickType.FirstDown =>
d.hasMouseFocus := TRUE;
WITH arg = d.stack[d.stackSize] DO
IF FindArg(d, JunoPt.FromHV(cd.cp.pt, ch.xform), arg) THEN
VBT.SetCage(ch, VBT.CageFromPosition(cd.cp));
d.dragging := TRUE;
d.dragger := cd.cp.pt;
d.draggee := JunoPt.ToHV(arg.loc, ch.xform);
d.dragName := Atom.ToText(arg.name.id1);
d.dragMode := DragModeFromModifiers(cd.modifiers);
(* d.root.marquee.putArg(d.dragName); *)
TRY d.tool.pre(d, cd, d.stackSize) EXCEPT Drag.Aborted =>
(* perform same actions as mouse up-click *)
UpDrag(d, ch, VBT.MouseRec{whatChanged:=VBT.Modifier.MouseL,
time := cd.time, cp := cd.cp, modifiers := cd.modifiers,
clickType := VBT.ClickType.LastUp, clickCount := 1});
(* set cage so "Position" won't be called again this drag *)
VBT.SetCage(ch, VBT.EverywhereCage)
END
END
END
| VBT.ClickType.LastUp =>
(* drag completed successfully *)
d.hasMouseFocus := FALSE;
IF d.dragging THEN UpDrag(d, ch, cd) END
ELSE
RETURN
END
END;
(* undo the "freeze" or "drag" tool shortcut *)
IF d.usingTempTool AND cd.clickType = VBT.ClickType.LastUp THEN
d.tool := d.lastTool; d.lastTool := NIL; d.usingTempTool := FALSE
END;
Sync(ch)
END Mouse;
PROCEDURE Position (ch: Child; READONLY cd: VBT.PositionRec) =
<* LL.sup = VBT.mu *>
VAR d: T := VBT.Parent(ch); BEGIN
IF NOT d.dragging THEN
VBT.SetCage(ch, VBT.EverywhereCage)
ELSIF cd.cp.gone THEN
VBT.SetCage(ch, VBT.GoneCage)
ELSE
TRY
VAR delta := Point.Sub(cd.cp.pt, d.dragger); BEGIN
d.dragger := cd.cp.pt;
d.draggee := d.tool.during(d, delta, d.stackSize);
END;
VBT.SetCage(ch, VBT.CageFromPosition(cd.cp))
EXCEPT
Drag.Aborted =>
(* perform same actions as mouse up-click *)
UpDrag(d, ch, VBT.MouseRec{whatChanged := VBT.Modifier.MouseL,
time := cd.time, cp := cd.cp, modifiers := cd.modifiers,
clickType := VBT.ClickType.LastUp, clickCount := 1});
(* set cage so "Position" won't be called again this drag *)
VBT.SetCage(ch, VBT.EverywhereCage)
END
END
END Position;
PROCEDURE UpDrag (d: T; ch: Child; READONLY cd: VBT.MouseRec) =
Perform the action associated with up-clicking at the end of a drag arg. This can happen when the user explicitly up-clicks while dragging, if a compilation error occurred at the start of the drag, or if a Juno run-time error occurs while dragging.
<* LL.sup = VBT.mu *>
BEGIN
d.dragging := FALSE;
d.stack[d.stackSize].locUp := JunoPt.FromHV(d.draggee, ch.xform);
d.tool.post(d, cd, d.stackSize);
ApplyIfReady(d, cd.time);
END UpDrag;
TYPE ArgSet = SET OF ArgType;
VAR collecting := TRUE;
PROCEDURE Key (ch: Child; READONLY cd: VBT.KeyRec) =
<* LL.sup = VBT.mu *>
CONST
ClickArgs = ArgSet{ArgType.Click, ArgType.CreateClick};
NoModifiers = VBT.Modifiers{};
VAR d: T := VBT.Parent(ch); BEGIN
IF d.dragging THEN RETURN END;
IF d.txt # NIL THEN
(* user is typing a text argument *)
WITH tool = NARROW(d.tool, ProcTool) DO
IF cd.wentDown THEN
IF cd.whatChanged = KeyboardKey.BackSpace OR
cd.whatChanged = KeyboardKey.Delete THEN
IF Text.Length(d.txt) > 0 THEN
d.txt := Text.Sub(d.txt, 0, Text.Length(d.txt) - 1);
tool.text(d, d.txt)
END
ELSIF cd.whatChanged = KeyboardKey.Return AND
cd.modifiers = NoModifiers THEN
d.stack[d.stackSize].text := d.txt;
d.txt := NIL;
ApplyIfReady(d, cd.time)
ELSE
VAR ch := KeyTrans.TTY(cd); BEGIN
IF ch # KeyTrans.NullKey THEN
d.txt := d.txt & Text.FromChar(ch);
tool.text(d, d.txt)
END
END
END;
END
END
ELSE
(* user is not typing a text argument *)
IF cd.whatChanged = Latin1Key.space THEN
IF cd.wentDown THEN
IF d.tool = NIL OR (d.stackSize >= d.prevStackSize) THEN
JunoError.Display(d, "No previous argument to copy");
d.goodDownClick := FALSE;
RETURN
ELSIF d.tool.argType[d.stackSize] IN ClickArgs THEN
d.root.marquee.putArg(Atom.ToText(d.stack[d.stackSize].name.id1));
PaintStackTop(d, select := TRUE);
d.goodDownClick := TRUE
END
ELSIF d.goodDownClick AND d.tool # NIL
AND d.stackSize < d.prevStackSize
AND d.tool.argType[d.stackSize] IN ClickArgs THEN
PaintStackTop(d, select := FALSE);
ApplyIfReady(d, cd.time)
END
ELSIF cd.wentDown THEN
IF cd.whatChanged = Latin1Key.g OR cd.whatChanged = Latin1Key.G THEN
(* do a garbage collection now *)
RTCollector.Collect()
ELSIF cd.whatChanged = Latin1Key.o OR cd.whatChanged = Latin1Key.O THEN
(* toggle whether collection is on or off *)
IF collecting
THEN RTCollector.Disable()
ELSE RTCollector.Enable()
END;
collecting := NOT collecting
ELSIF cd.whatChanged = Latin1Key.u OR cd.whatChanged = Latin1Key.U THEN
IF d.stackSize > 0 THEN
d.root.marquee.remArg();
DEC(d.stackSize)
END
ELSIF cd.whatChanged = Latin1Key.c OR cd.whatChanged = Latin1Key.C THEN
d.continuousUnparse := NOT d.continuousUnparse;
IF d.continuousUnparse AND NOT d.root.sTrue THEN
d.root.source.update()
END
END
END
END;
Sync(ch)
END Key;
PROCEDURE ClickNewPt (
d: T;
READONLY hvPt: Point.T;
READONLY xform: JunoPt.Transform;
frozen: BOOLEAN)
: Arg =
Create a new point indat Trestle coordinatehvPtand updated's source to contain it. Thefrozenparameter determines whether the new point is frozen or not.
<* LL.sup = VBT.mu *>
VAR xyPt := JunoPt.FromHV(hvPt, xform); BEGIN
RETURN CreatePoint(d, xyPt, frozen)
END ClickNewPt;
PROCEDURE CreatePoint (d: T; READONLY xyPt: JunoPt.T; frozen: BOOLEAN): Arg =
<* LL.sup = VBT.mu *>
VAR res: Arg; atom: Atom.T; BEGIN
REPEAT
VAR
letter := VAL(ORD('a') + d.pointCount MOD 26, CHAR);
digit := (d.pointCount DIV 26) - 1;
name := Text.FromChar(letter);
BEGIN
IF digit >= 0 THEN name := name & Fmt.Int(digit) END;
atom := Atom.FromText(name);
INC(d.pointCount)
END
UNTIL CurrCmd.GetVariable(CurrCmd.GetAST(d.root.ccmd), atom) = NIL;
VAR hint: JunoAST.Expr; BEGIN
res.name := IdToQId(atom);
IF GridActive(d) THEN
VAR x, y: JunoValue.Real; BEGIN
EVAL JunoPt.RelVal(xyPt.x, xyPt.y, d.grid0.x, d.grid0.y,
d.grid1.x, d.grid1.y, x, y);
x := FLOAT(ROUND(x), JunoValue.Real);
y := FLOAT(ROUND(y), JunoValue.Real);
hint := NEW(JunoAST.Rel,
e1 := JunoASTUtils.NewPoint(x, y),
e2 := NEW(JunoAST.Pair, e1 := d.grid0nm, e2 := d.grid1nm));
res.loc := EvalRel(x, y, d.grid0, d.grid1);
CurrCmd.AddVariable(d.root.ccmd, atom, res.loc, hint, frozen := TRUE)
END
ELSE
res.loc := xyPt;
hint := JunoPt.ToASTPair(xyPt);
CurrCmd.AddVariable(d.root.ccmd, atom, xyPt, hint, frozen := frozen);
END;
res.locUp := DummyXY;
RETURN res
END
END CreatePoint;
PROCEDURE FindArg (d: T; READONLY pt: JunoPt.T; VAR (*OUT*) arg: Arg): BOOLEAN =
VAR nm := FindPoint(d, pt); BEGIN
IF nm = NIL THEN RETURN FALSE END;
VAR px, py: JunoValue.Real; BEGIN
EVAL CurrCmd.PointLocation(d.root.ccmd, nm, px, py);
arg := Arg{IdToQId(nm), JunoPt.T{px, py}, DummyXY};
RETURN TRUE
END
END FindArg;
PROCEDURE FindPoint (d: T; READONLY pt0: JunoPt.T): JunoAST.Id =
<* LL.sup <= VBT.mu *>
VAR
best := Real.MaxFinite;
bestatom: Atom.T := NIL;
PROCEDURE P(n: Atom.T; READONLY pt1: JunoPt.T) =
VAR dx := pt1.x - pt0.x; dy := pt1.y - pt0.y; d := dx*dx + dy*dy; BEGIN
IF d < best THEN best := d; bestatom := n END
END P;
BEGIN
CurrCmd.ForAllPoints(d.root.ccmd, P);
RETURN bestatom
END FindPoint;
PROCEDURE Push (m: T; READONLY arg: Arg) =
BEGIN
m.stack[m.stackSize] := arg
(* ; m.root.marquee.putArg(Atom.ToText(arg.name.id1)) *)
END Push;
<* SPEC ApplyIfReady REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyIfReady (d: T; time: VBT.TimeStamp) =
Increment the stack pointer, and applyd.toolif there are enough arguments. If the most recent argument was not the last one and the current argument has typeArgType.Text, initialize state indfor the next argument.This procedure changes the drawing without doing a
Sync.
BEGIN
INC(d.stackSize);
IF d.stackSize = NUMBER(d.tool.argType^) THEN
d.tool.apply(d, SUBARRAY(d.stack, 0, d.stackSize));
(* d.root.marquee.newline(); *)
ClearStack(d, time)
ELSIF d.tool.argType[d.stackSize] = ArgType.Text THEN
d.txt := "";
NARROW(d.tool, ProcTool).text(d, d.txt)
END;
END ApplyIfReady;
PROCEDURE ClearStack (d: T; time: VBT.TimeStamp; tl: Tool := NIL) =
Cleard's stack, and call thesetupmethod of the tooltl. Iftlis defaulted, then use the currentArgToolofdif one is selected.
BEGIN
IF d.stackSize # 0 THEN d.prevStackSize := d.stackSize END;
d.stackSize := 0;
(* d.root.marquee.erase(); *)
IF tl = NIL THEN tl := d.tool END;
IF tl # NIL THEN
tl.setup(d, time);
(* ; d.root.marquee.putName(tl.label) *)
(* invoke "text" method of 1-arg text tool *)
TYPECASE tl OF
ProcTool (pt) =>
IF NUMBER(pt.argType^) = 1 AND pt.argType[0] = ArgType.Text THEN
d.txt := ""; (* indicate we are typing a text arg *)
pt.text(d, d.txt);
AcquireKBFocus(Filter.Child(d), time)
END
ELSE (*SKIP*)
END
END
END ClearStack;
PROCEDURE RescreenWriteOnly (ch: ChildWriteOnly; READONLY cd: VBT.RescreenRec) =
<* LL.sup = VBT.mu.ch *>
CONST MMPerPoint = 25.4(*mm/in*) / 72.0(*pt/in*); BEGIN
IF cd.st # NIL THEN
ch.xform.xScale := VBT.MMToPixels(ch, MMPerPoint, Axis.T.Hor);
ch.xform.yScale := VBT.MMToPixels(ch, MMPerPoint, Axis.T.Ver);
ch.xform.widthScale := (ch.xform.xScale + ch.xform.yScale) / 2.0
END
END RescreenWriteOnly;
PROCEDURE ReshapeWriteOnly (ch: ChildWriteOnly; READONLY cd: VBT.ReshapeRec) =
<* LL.sup = VBT.mu.ch *>
BEGIN
IF cd.new # Rect.Empty THEN
CASE ch.originVal OF
JunoConfig.Origin.SW => ch.xform.origin := Rect.SouthWest(cd.new)
| JunoConfig.Origin.Center => ch.xform.origin := Rect.Middle(cd.new)
END;
ch.repaint(Region.Full)
END
END ReshapeWriteOnly;
PROCEDURE Reshape (ch: Child; READONLY cd: VBT.ReshapeRec) =
<* LL.sup = VBT.mu.ch *>
BEGIN
IF cd.new # Rect.Empty THEN
VAR d: T := VBT.Parent(ch); BEGIN
d.root.dTrue := FALSE
END
END;
ChildWriteOnly.reshape(ch, cd)
END Reshape;
PROCEDURE Repaint (ch: Child; <*UNUSED*> READONLY rgn: Region.T) =
<* LL.sup = VBT.mu.ch *>
VAR d: T := VBT.Parent(ch); BEGIN
DisplayError(d, Exec(d));
Sync(ch)
END Repaint;
PROCEDURE Exec (d: T): TEXT =
VAR res: TEXT := NIL; BEGIN
IF NOT d.root.dTrue THEN
res := ExecCurrCmd(d, d.root.skipify); Annotations(d);
IF res = NIL THEN d.root.dTrue := TRUE END
END;
RETURN res
END Exec;
PROCEDURE ExecCurrCmd (d: T; skipify: BOOLEAN): TEXT =
VAR ch: Child := Filter.Child(d); BEGIN
(* prepare drawing *)
PSImpl.Reset(d, inExec := FALSE);
VBT.PaintTint(ch, Rect.Full, PaintOp.Bg);
DblBufferVBT.ClearSaved(ch);
(* run current command *)
TRY
IF CurrCmd.Run(d.root.ccmd, skipify) THEN
SourceUntrue(d, View.ModKind.ImplicitConsistent)
END
EXCEPT
CurrCmd.CompileError (msg) => RETURN msg
| CurrCmd.RuntimeError (rec) => RETURN rec.errorMsg
END;
RETURN NIL
END ExecCurrCmd;
PROCEDURE Annotations (d: T) =
Draw annotations after running the current command
BEGIN
PaintPath(d);
PaintGrid(d);
PaintBBox(d);
PaintPoints(d);
END Annotations;
PROCEDURE Sync (v: VBT.T) =
BEGIN VBT.Sync(v) END Sync;
PROCEDURE PaintPath (d: T) =
VAR ch: Child := Filter.Child(d); BEGIN
WITH path = d.ps.path DO
VBT.Stroke(ch, Rect.Full, path, width := 3, end := VBT.EndStyle.Square,
join := VBT.JoinStyle.Bevel, op := PaintOp.Bg);
VBT.Stroke(ch, Rect.Full, path, op := PathColor)
END
END PaintPath;
PROCEDURE GridActive (d: T): BOOLEAN =
BEGIN
RETURN d.gridMode AND
1.0 <= ABS(d.grid1.x - d.grid0.x) + ABS(d.grid1.y - d.grid0.y)
END GridActive;
PROCEDURE AdjustGridPoints (d: T) =
Update the values ofd.grid0andd.grid1from the current command. If either of the pointsd.grid0nmord.grid1nmis not defined in the current command, then resetd.gridMode.
PROCEDURE UpdatePoint(nm: JunoAST.Id; VAR (*OUT*) pt: JunoPt.T) =
VAR x, y: JunoValue.Real; BEGIN
IF CurrCmd.PointLocation(d.root.ccmd, nm, x, y)
THEN pt := JunoPt.T{x, y}
ELSE d.gridMode := FALSE
END
END UpdatePoint;
BEGIN
IF d.gridMode THEN
UpdatePoint(d.grid0nm.id1, d.grid0);
UpdatePoint(d.grid1nm.id1, d.grid1);
(* turn off grid tool if grid is no longer active *)
IF NOT d.gridMode THEN
ToolBox.SwapButton(d.toolBox, d.gridOff, d.gridOn, "Grid On")
END
END
END AdjustGridPoints;
PROCEDURE ToGridCoords (d: T; ch: Child; hvPt: Point.T): JunoPt.T =
VAR res: JunoPt.T; xyPt := JunoPt.FromHV(hvPt, ch.xform); BEGIN
EVAL JunoPt.RelVal(xyPt.x, xyPt.y, d.grid0.x, d.grid0.y,
d.grid1.x, d.grid1.y, res.x, res.y);
RETURN res
END ToGridCoords;
PROCEDURE PaintGrid (d: T) =
BEGIN
AdjustGridPoints(d);
IF GridActive(d) THEN
VAR
ch: Child := Filter.Child(d);
dom := VBT.Domain(d);
nw := ToGridCoords(d, ch, Rect.NorthWest(dom));
ne := ToGridCoords(d, ch, Rect.NorthEast(dom));
se := ToGridCoords(d, ch, Rect.SouthEast(dom));
sw := ToGridCoords(d, ch, Rect.SouthWest(dom));
mini := MIN(MIN(FLOOR(nw.x), FLOOR(ne.x)),
MIN(FLOOR(se.x), FLOOR(sw.x)));
maxi := MAX(MAX(CEILING(nw.x), CEILING(ne.x)),
MAX(CEILING(se.x), CEILING(sw.x)));
minj := MIN(MIN(FLOOR(nw.y), FLOOR(ne.y)),
MIN(FLOOR(se.y), FLOOR(sw.y)));
maxj := MAX(MAX(CEILING(nw.y), CEILING(ne.y)),
MAX(CEILING(se.y), CEILING(sw.y)));
BEGIN
FOR i := mini TO maxi DO
FOR j := minj TO maxj DO
VBT.PaintTint(ch, Rect.FromPoint(
JunoPt.ToHV(
EvalRel(
FLOAT(i, JunoValue.Real), FLOAT(j, JunoValue.Real),
d.grid0, d.grid1),
ch.xform)),
PaintOp.Fg)
END
END
END
END
END PaintGrid;
PROCEDURE PaintBBox (d: T) =
VAR ch: Child := Filter.Child(d);
PROCEDURE HVPoint(x, y: JunoValue.Real): Point.T =
BEGIN RETURN JunoPt.ToHV(JunoPt.T{x, y}, ch.xform) END HVPoint;
VAR path := NEW(Path.T); BEGIN
WITH bbox = d.ps.bbox DO
Path.MoveTo(path, HVPoint(bbox.west, bbox.south));
Path.LineTo(path, HVPoint(bbox.east, bbox.south));
Path.LineTo(path, HVPoint(bbox.east, bbox.north));
Path.LineTo(path, HVPoint(bbox.west, bbox.north));
Path.Close(path)
END;
VBT.Stroke(ch, Rect.Full, path, width := 1, end := VBT.EndStyle.Butt,
join := VBT.JoinStyle.Miter, src := Pixmap.Gray)
END PaintBBox;
PROCEDURE PaintPoints (d: T) =
VAR
ccmd := d.root.ccmd;
ch: Child := Filter.Child(d);
PROCEDURE P(atom: Atom.T; READONLY pt: JunoPt.T) =
VAR op: PaintOp.T; BEGIN
IF CurrCmd.IsFrozen(ccmd, atom)
THEN op := FrozenColor
ELSE op := LabelColor
END;
PaintPoint(ch, Atom.ToText(atom), op,
pt := JunoPt.ToHV(pt, ch.xform))
END P;
BEGIN
IF d.labelStyle > 0 THEN
CurrCmd.ForAllPoints(ccmd, P)
END
END PaintPoints;
PROCEDURE PaintStackTop (d: T; select: BOOLEAN) =
Paint the label on the top ofd's stack. Ifselectis true, then paint the label inSelectColor; otherwise, paint it inLabelColororFrozenColorbased on whether the point is frozen or not.
VAR
arg := d.stack[d.stackSize];
ch: Child := Filter.Child(d);
op: PaintOp.T;
BEGIN
IF select THEN
op := SelectColor
ELSE
IF CurrCmd.IsFrozen(d.root.ccmd, arg.name.id1)
THEN op := FrozenColor
ELSE op := LabelColor
END;
END;
PaintPoint(ch, Atom.ToText(arg.name.id1), op := op,
pt := JunoPt.ToHV(arg.loc, ch.xform))
END PaintStackTop;
PROCEDURE PaintPoint (ch: Child; name: TEXT; op: PaintOp.T; pt: Point.T) =
VAR m: T := VBT.Parent(ch); BEGIN
CASE m.labelStyle OF
0 => (* SKIP *)
| 1 =>
VAR delta := Point.Sub(pt, Rect.Middle(JunoConfig.cross.r)); BEGIN
JunoConfig.crossBdry := Region.Add(JunoConfig.crossBdry, delta);
JunoConfig.cross := Region.Add(JunoConfig.cross, delta);
VBT.PaintRegion(ch, JunoConfig.crossBdry, PaintOp.Bg);
VBT.PaintRegion(ch, JunoConfig.cross, op)
END
| 2 =>
VAR
delta := Point.Sub(pt, Rect.Middle(JunoConfig.dot.r));
rect := VBT.BoundingBox(ch, name, JunoConfig.labelFont);
BEGIN
JunoConfig.dot := Region.Add(JunoConfig.dot, delta);
VBT.PaintText(ch, t := name, op := op, fnt := JunoConfig.labelFont,
pt := Point.Sub(pt, Rect.NorthEast(rect)), clip := Rect.Full);
VBT.PaintRegion(ch, JunoConfig.dot, op)
END
END
END PaintPoint;
PROCEDURE Update (d: T) =
<* LL.sup <= VBT.mu *>
BEGIN
d.pointCount := 0;
d.txt := NIL;
ClearStack(d, 0);
DisplayError(d, Exec(d));
Sync(Filter.Child(d))
END Update;
PROCEDURE ChildGetOrigin (ch: ChildWriteOnly): JunoConfig.Origin =
BEGIN RETURN ch.originVal END ChildGetOrigin;
PROCEDURE ChildSetOrigin (ch: ChildWriteOnly; origin: JunoConfig.Origin) =
BEGIN ch.originVal := origin END ChildSetOrigin;
PROCEDURE FinishTextTool (d: T): BOOLEAN =
VAR res := d.txt # NIL; BEGIN
IF res THEN ToolBox.Unselect(d.root) END;
RETURN res
END FinishTextTool;
PROCEDURE Make (d: T; skipify: BOOLEAN) =
BEGIN
d.pointCount := 0;
d.txt := NIL;
ClearStack(d, 0);
VAR err := ExecCurrCmd(d, skipify); BEGIN
Annotations(d);
IF err = NIL
THEN d.root.dTrue := TRUE
ELSE DisplayError(d, err)
END
END;
Sync(Filter.Child(d))
END Make;
PROCEDURE ArrayToList (READONLY arg: ARRAY OF Arg; byVal := FALSE):
JunoAST.ExprList =
Return a list of expressions inarg, where theith expression in the list corresponds toarg[i]. Ifarg[i].text # NIL, then theith expression is the text stringarg[i].text. Otherwise, ifbyVal, then theith expression is the valuearg[i].loc; if not, then it is the name of the pointarg[i].name.
VAR res: JunoAST.ExprList; expr: JunoAST.Expr; BEGIN
IF byVal
THEN res := NEW(JunoAST.ExprList, bp := JunoAST.End)
ELSE res := NEW(JunoAST.QIdList, bp := JunoAST.End)
END;
FOR i := LAST(arg) TO FIRST(arg) BY -1 DO
IF arg[i].text # NIL THEN
expr := ToASTText(arg[i].text)
ELSIF byVal THEN
expr := JunoPt.ToASTPair(arg[i].loc)
ELSE
<* ASSERT arg[i].name # NIL *>
expr := arg[i].name
END;
res.head := NEW(JunoAST.ExprLink, expr := expr, next := res.head);
INC(res.size)
END;
RETURN res
END ArrayToList;
PROCEDURE FindBreak (t: TEXT): CARDINAL =
Return the index of the character after the last run of newlines not counting a run of newlines that terminatest, or 0 iftcontains no non-terminal newlines.
VAR i := Text.Length(t) - 1; BEGIN
WHILE i >= 0 AND Text.GetChar(t, i) = '\n' DO DEC(i) END;
RETURN Text.FindCharR(t, '\n', start := i) + 1
END FindBreak;
PROCEDURE ToASTText (t: TEXT): JunoAST.Expr =
Return an expression equivalent to the textt. Iftcontains newline characters, the result will be a concatenation of text literals.
VAR i := FindBreak(t); res: JunoAST.Expr; BEGIN
res := NEW(JunoAST.Text, val := Text.Sub(t, i), bp := JunoAST.End);
IF i # 0 THEN
res := NEW(JunoAST.Concat, bp := JunoAST.End,
e1 := ToASTText(Text.Sub(t, 0, i)), e2 := res)
END;
RETURN res
END ToASTText;
PROCEDURE MakeConstraint (tl: PredTool; READONLY arg: ARRAY OF Arg):
JunoAST.Formula =
BEGIN
IF tl.name.id0 = JunoAST.NilId THEN
IF tl.name.id1 = CongToolSym THEN
RETURN NEW(JunoAST.Cong, bp := JunoAST.End,
e1 := MakePair(arg[0].name, arg[1].name),
e2 := MakePair(arg[2].name, arg[3].name))
ELSIF tl.name.id1 = ParaToolSym THEN
RETURN NEW(JunoAST.Para, bp := JunoAST.End,
e1 := MakePair(arg[0].name, arg[1].name),
e2 := MakePair(arg[2].name, arg[3].name))
ELSIF tl.name.id1 = HorToolSym THEN
RETURN NEW(JunoAST.Hor, bp := JunoAST.End,
e1 := arg[0].name, e2 := arg[1].name)
ELSIF tl.name.id1 = VerToolSym THEN
RETURN NEW(JunoAST.Ver, bp := JunoAST.End,
e1 := arg[0].name, e2 := arg[1].name)
END
END;
RETURN NEW(JunoAST.Call, inouts := JunoAST.EmptyExprList,
name := tl.name, ins := ArrayToList(arg), bp := JunoAST.End)
END MakeConstraint;
PROCEDURE MakeEqConstraint (tl: FuncTool; READONLY arg: ARRAY OF Arg):
JunoAST.Formula =
BEGIN
RETURN NEW(JunoAST.Equals, bp := JunoAST.End, e1 := arg[0].name,
e2 := NEW(JunoAST.Call, bp := JunoAST.End,
inouts := JunoAST.EmptyExprList, name := tl.name,
ins := ArrayToList(SUBARRAY(arg, 1, tl.in_cnt))))
END MakeEqConstraint;
PROCEDURE MakeCommand (
tl: ProcTool;
READONLY arg: ARRAY OF Arg;
byVal := FALSE): JunoAST.ProcCall =
Return a procedure call to the proceduretl.namewith the arguments stored inarg.
BEGIN
<* ASSERT tl.out_cnt + tl.inout_cnt + tl.in_cnt = NUMBER(arg) *>
WITH
outArgs = SUBARRAY(arg, 0, tl.out_cnt),
inoutArgs = SUBARRAY(arg, tl.out_cnt, tl.inout_cnt),
inArgs = SUBARRAY(arg, tl.out_cnt + tl.inout_cnt, tl.in_cnt)
DO
RETURN NEW(JunoAST.ProcCall, bp := JunoAST.End, name := tl.name,
outs := ArrayToList(outArgs, byVal := FALSE),
inouts := ArrayToList(inoutArgs, byVal := FALSE),
inout_parens := NUMBER(inoutArgs) > 1,
ins := ArrayToList(inArgs, byVal))
END
END MakeCommand;
PROCEDURE MakePair (id1, id2: JunoAST.QId): JunoAST.Pair =
BEGIN
RETURN NEW(JunoAST.Pair, e1 := id1, e2 := id2, bp := JunoAST.End)
END MakePair;
PROCEDURE EvalRel (x, y: JunoValue.Real; p0, p1: JunoPt.T): JunoPt.T =
VAR res: JunoPt.T; dx := p1.x - p0.x; dy := p1.y - p0.y; BEGIN
res.x := p0.x + dx * x - dy * y;
res.y := p0.y + dx * y + dy * x;
RETURN res
END EvalRel;
<* INLINE *>
PROCEDURE IdToQId (a: JunoAST.Id): JunoAST.QId =
BEGIN
RETURN NEW(JunoAST.QId, bp := JunoAST.End, id0 := JunoAST.NilId, id1 := a)
END IdToQId;
PROCEDURE SelectTool (d: T; t: Tool; time: VBT.TimeStamp) =
BEGIN
(* IF d.tool = NIL THEN PopupMarquee(d) END; *)
IF d.txt # NIL THEN
IF Text.Length(d.txt) > 0 THEN
(* In the middle of entering text; simulate <RETURN> *)
d.stack[d.stackSize].text := d.txt;
d.txt := NIL;
ApplyIfReady(d, time);
(* NOTE: If the current tool is a text tool taking a single argument,
its "setup" method will have set "d.txt" to the empty string. *)
END;
IF d.txt # NIL THEN
<* ASSERT Text.Length(d.txt) = 0 *>
(* erase the vertical bar for the empty text arg *)
DblBufferVBT.Restore(Filter.Child(d));
d.txt := NIL
END;
Sync(Filter.Child(d))
END;
IF t # NIL AND NUMBER(t.argType^) = 0 THEN
(* attempt to apply a tool with 0 user-supplied arguments;
this includes set tools *)
IF d.root.astTrue THEN (* AST must be up-to-date *)
ClearStack(d, time, tl := t);
t.apply(d, ARRAY OF Arg{});
ClearStack(d, time);
Sync(Filter.Child(d));
d.root.marquee.erase(); d.root.marquee.putName(t.label); d.root.marquee.newline()
ELSE
JunoError.Display(Filter.Child(d), "Oops! You forgot to click Run.");
END
ELSE
(* deselect tool or select tool with > 0 arguments *)
d.tool := t;
ClearStack(d, time)
END
END SelectTool;
PROCEDURE NewArgArray (n: CARDINAL; type: ArgType): REF ARRAY OF ArgType =
VAR res := NEW(REF ARRAY OF ArgType, n); BEGIN
FOR i := 0 TO LAST(res^) DO res[i] := type END;
RETURN res
END NewArgArray;
PROCEDURE NewCreateTool (): ArgTool =
BEGIN
RETURN NEW(ArgTool, argType := NewArgArray(1, ArgType.CreateClick),
apply := ApplyPoint)
END NewCreateTool;
<* SPEC ApplyPoint REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyPoint (
<*UNUSED*> tl: Tool;
<*UNUSED*> d: T;
<*UNUSED*> READONLY arg: ARRAY OF Arg) =
BEGIN
(*SKIP*)
END ApplyPoint;
PROCEDURE NewPredTool (name: JunoAST.QId; in_cnt: CARDINAL): ArgTool =
BEGIN
RETURN NEW(PredTool, name := name, in_cnt := in_cnt,
argType := NewArgArray(in_cnt, ArgType.Click))
END NewPredTool;
<* SPEC ApplyPred REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyPred (tl: PredTool; d: T; READONLY arg: ARRAY OF Arg) =
This is the default implementation of the apply method for predicate
tools.
BEGIN
<* ASSERT d.root.astTrue *>
CurrCmd.AddConstraint(d.root.ccmd, MakeConstraint(tl, arg));
d.root.dTrue := FALSE; (* indicate implicit modification *)
SourceUntrue(d, View.ModKind.ImplicitOutOfDate)
END ApplyPred;
PROCEDURE NewFuncTool (name: JunoAST.QId; in_cnt: CARDINAL): ArgTool =
BEGIN
RETURN NEW(FuncTool, name := name, in_cnt := in_cnt,
argType := NewArgArray(1 + in_cnt, ArgType.Click))
END NewFuncTool;
<* SPEC ApplyFunc REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyFunc (tl: FuncTool; d: T; READONLY arg: ARRAY OF Arg) =
This is the default implementation of the apply method for function
tools.
BEGIN
<* ASSERT d.root.astTrue *>
CurrCmd.AddConstraint(d.root.ccmd, MakeEqConstraint(tl, arg));
d.root.dTrue := FALSE; (* indicate implicit modification *)
SourceUntrue(d, View.ModKind.ImplicitOutOfDate)
END ApplyFunc;
PROCEDURE NewProcTool (name: JunoAST.QId; in_cnt: CARDINAL;
out_cnt, inout_cnt: CARDINAL := 0; isText: BOOLEAN): ArgTool =
VAR
num := in_cnt + out_cnt + inout_cnt;
args := NewArgArray(num, ArgType.Click);
BEGIN
IF isText THEN args[num-1] := ArgType.Text END;
RETURN NEW(ProcTool, name := name, argType := args,
out_cnt := out_cnt, inout_cnt := inout_cnt, in_cnt := in_cnt)
END NewProcTool;
PROCEDURE SetupProc (tl: ProcTool; <*UNUSED*> d: T;
<*UNUSED*> time: VBT.TimeStamp) =
BEGIN
(* initialize "ProcTool" fields *)
tl.txt := NIL; tl.call := NIL;
END SetupProc;
<* SPEC ApplyProc REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyProc (tl: ProcTool; d: T; READONLY arg: ARRAY OF Arg) =
This is the default implementation of the apply method for procedure
tools.
BEGIN
<* ASSERT d.root.astTrue *>
IF tl.txt # NIL THEN
(* This is a procedure whose last argument is a text *)
tl.txt.val := d.stack[d.stackSize-1].text;
DblBufferVBT.Restore(Filter.Child(d));
tl.txt := NIL; tl.call := NIL
END;
IncrRunCmd(d, MakeCommand(tl, arg, byVal := TRUE));
CurrCmd.AddCommand(d.root.ccmd, MakeCommand(tl, arg, byVal := FALSE));
SourceUntrue(d, View.ModKind.ImplicitConsistent)
END ApplyProc;
PROCEDURE IncrRunCmd (d: T; cmd: JunoAST.Cmd) =
Incrementally compile and runcmd, then annotate the current path and the point labels. Requires thatcmdcompiles without any errors.
<* FATAL JunoCompileErr.Error *>
VAR
slot := JunoBuild.Cmd(cmd, CurrCmd.GetScope(d.root.ccmd));
execRes: JunoRT.ExecRes;
BEGIN
execRes := JunoRT.ExecFromSlot(slot);
IF execRes.trapCode # JunoRT.TrapCode.NormalHalt THEN
JunoError.Display(d, JunoRT.TrapMessage(execRes))
END;
PaintPath(d);
PaintPoints(d)
END IncrRunCmd;
PROCEDURE TextProc (t: ProcTool; d: T; txt: TEXT) =
<* LL.sup = VBT.mu *>
VAR txtArg := txt & "|"; ch := Filter.Child(d); BEGIN
IF t.txt = NIL THEN
(* save partial arg on stack for subsequent call to "MakeCommand" *)
d.stack[d.stackSize].text := txtArg;
WITH args = SUBARRAY(d.stack, 0, d.stackSize+1) DO
t.call := NEW(JunoAST.Save, bp := JunoAST.End,
nm := JunoASTUtils.QIdFromText("PS"),
body := MakeCommand(t, args, byVal := TRUE))
END;
t.txt := GetLastArg(t.call.body);
DblBufferVBT.Save(ch);
ELSE
DblBufferVBT.Restore(ch);
t.txt.val := txtArg (* destructively change "t.call" *)
END;
IncrRunCmd(d, t.call);
Sync(ch)
END TextProc;
PROCEDURE GetLastArg (c: JunoAST.ProcCall): JunoAST.Expr =
Return the lastINargument ofc. Requires thatchas at least oneINparameter.
VAR curr := c.ins.head; BEGIN
WHILE curr.next # NIL DO curr := curr.next END;
RETURN curr.expr
END GetLastArg;
PROCEDURE NewRelTool (): ArgTool =
BEGIN
RETURN NEW(ArgTool, argType := NewArgArray(3, ArgType.Click),
apply := ApplyRel)
END NewRelTool;
<* SPEC ApplyRel REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyRel (<*UNUSED*> tl: Tool; m: T; READONLY arg: ARRAY OF Arg) =
BEGIN
CurrCmd.DoRel(m.root.ccmd, arg[0].name.id1,
arg[1].name.id1, arg[2].name.id1);
SourceUntrue(m, View.ModKind.ImplicitConsistent)
END ApplyRel;
PROCEDURE NewRel1Tool (): ArgTool =
BEGIN
RETURN NEW(ArgTool, argType := NewArgArray(2, ArgType.Click),
apply := ApplyRel1)
END NewRel1Tool;
<* SPEC ApplyRel1 REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyRel1 (<*UNUSED*> tl: Tool; m: T; READONLY arg: ARRAY OF Arg) =
BEGIN
CurrCmd.DoRel1(m.root.ccmd, arg[0].name.id1,
arg[1].name.id1);
SourceUntrue(m, View.ModKind.ImplicitConsistent)
END ApplyRel1;
PROCEDURE NewFreezeTool (): ArgTool =
BEGIN
RETURN NEW(ArgTool, argType := NewArgArray(1, ArgType.Click),
apply := ApplyFreeze)
END NewFreezeTool;
<* SPEC ApplyFreeze REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyFreeze (<*UNUSED*> tl: Tool; m: T; READONLY arg: ARRAY OF Arg) =
BEGIN
CurrCmd.FreezePoint(m.root.ccmd, arg[0].name.id1);
SourceUntrue(m, View.ModKind.ImplicitConsistent);
PaintPoints(m)
END ApplyFreeze;
PROCEDURE NewAdjustTool (): ArgTool =
BEGIN
RETURN NEW(ArgTool, argType := NewArgArray(1, ArgType.Drag),
apply := ApplyAdjust);
END NewAdjustTool;
PROCEDURE RoundToGrid (d: T; p: JunoPt.T): JunoPt.T =
Ifd's grid is not active, returnp. Otherwise, return the grid point nearest top.
VAR x, y: JunoValue.Real; BEGIN
IF NOT GridActive(d) THEN RETURN p END;
EVAL JunoPt.RelVal(p.x, p.y, d.grid0.x, d.grid0.y,
d.grid1.x, d.grid1.y, x, y);
x := FLOAT(ROUND(x), JunoValue.Real);
y := FLOAT(ROUND(y), JunoValue.Real);
RETURN EvalRel(x, y, d.grid0, d.grid1)
END RoundToGrid;
<* SPEC ApplyAdjust REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyAdjust (<*UNUSED*> tl: Tool; d: T; READONLY arg: ARRAY OF Arg) =
VAR newLoc := RoundToGrid(d, arg[0].locUp); BEGIN
Adjust(d, arg[0].name.id1, newLoc)
END ApplyAdjust;
PROCEDURE Adjust (d: T; ptName: JunoAST.Id; READONLY ptLoc: JunoPt.T) =
Adjusts the point namedptNamein the drawingdto the new locationptLoc. If the point is currently unfrozen, Juno temporarily freezes the point and runs the current command. If that does not succeed, the point is unfrozen again, and the current command is re-tried.This procedure calls
SourceUntrue(d), and displays any run-time error message. It also automatically adjusts the drawing's grid if the grid is active andptNameis one of the grid control points.
VAR isFrozen := CurrCmd.IsFrozen(d.root.ccmd, ptName); res: TEXT; BEGIN
IF NOT isFrozen THEN
(* temporarily freeze the point *)
CurrCmd.FreezePoint(d.root.ccmd, ptName)
END;
CurrCmd.MovePoint(d.root.ccmd, ptName, ptLoc.x, ptLoc.y);
res := ExecCurrCmd(d, d.root.skipify);
IF NOT isFrozen THEN
(* point was originally unfrozen; unfreeze the point *)
CurrCmd.FreezePoint(d.root.ccmd, ptName);
(* run again if the command failed with the point frozen *)
IF res # NIL THEN
res := ExecCurrCmd(d, d.root.skipify)
END
END;
Annotations(d);
SourceUntrue(d, View.ModKind.ImplicitConsistent);
DisplayError(d, res)
END Adjust;
PROCEDURE NewGridTools (tb: VBT.Split; d: T): ArgTool =
BEGIN
d.toolBox := tb;
d.gridOn := NEW(ArgTool, argType := NewArgArray(2, ArgType.Click),
apply := ApplyGridOn);
d.gridOff := NEW(ArgTool, argType := NewArgArray(0, ArgType.Click),
apply := ApplyGridOff);
RETURN d.gridOn
END NewGridTools;
<* SPEC ApplyGridOn REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyGridOn (<*UNUSED*> tl: Tool; d: T; READONLY arg: ARRAY OF Arg) =
BEGIN
d.gridMode := TRUE;
d.grid0 := arg[0].loc; d.grid0nm := arg[0].name;
d.grid1 := arg[1].loc; d.grid1nm := arg[1].name;
ToolBox.Unselect(d.root);
ToolBox.SwapButton(d.toolBox, d.gridOn, d.gridOff, "Grid Off");
PaintGrid(d);
PaintBBox(d);
PaintPoints(d);
Sync(Filter.Child(d))
END ApplyGridOn;
<* SPEC ApplyGridOff REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplyGridOff (
<*UNUSED*> tl: Tool;
d: T;
<*UNUSED*> READONLY arg: ARRAY OF Arg) =
BEGIN
ToolBox.SwapButton(d.toolBox, d.gridOff, d.gridOn, "Grid On");
d.gridMode := FALSE;
d.root.dTrue := FALSE; (* force exec to repaint drawing *)
DisplayError(d, Exec(d));
Sync(Filter.Child(d))
END ApplyGridOff;
PROCEDURE NewSetTool (procNm: JunoAST.QId; arg: JunoAST.Expr): SetTool =
VAR res := NEW(SetTool, argType := NEW(REF ARRAY OF ArgType, 0)); BEGIN
res.cmd := NEW(JunoAST.ProcCall, bp := JunoAST.End,
outs := JunoAST.EmptyQIdList, inouts := JunoAST.EmptyQIdList,
name := procNm, ins := JunoASTUtils.NewExprList(arg));
RETURN res
END NewSetTool;
<* SPEC ApplySetTool REQUIRES sup(LL) = VBT.mu *>
PROCEDURE ApplySetTool (tl: SetTool; d: T;
<*UNUSED*> READONLY arg: ARRAY OF Arg) =
VAR cmd := Unqualify(tl.cmd, Editor.ModuleName(d.root.editor)); BEGIN
CurrCmd.AddCommand(d.root.ccmd, cmd);
IncrRunCmd(d, cmd);
SourceUntrue(d, View.ModKind.ImplicitConsistent)
END ApplySetTool;
PROCEDURE Unqualify (call: JunoAST.ProcCall; mod: Atom.T): JunoAST.Cmd =
Requirescallto have one argument. This unqualifies any top-level qualified identifier incallwhose qualifier ismod.
BEGIN
RETURN NEW(JunoAST.ProcCall, bp := JunoAST.End,
outs := call.outs, inouts := call.inouts,
name := UnqualifyQId(call.name, mod),
ins := JunoASTUtils.NewExprList(UnqualifyQId(call.ins.head.expr, mod)))
END Unqualify;
PROCEDURE UnqualifyQId (ex: JunoAST.Expr; mod: Atom.T): JunoAST.Expr =
BEGIN
TYPECASE ex OF
JunoAST.QId (qid) =>
IF qid.id0 # JunoAST.NilId AND qid.id0 = mod THEN
RETURN NEW(JunoAST.QId, bp := JunoAST.End,
id0 := JunoAST.NilId, id1 := qid.id1)
END
ELSE (* SKIP *)
END;
RETURN ex
END UnqualifyQId;
PROCEDURE PopupMarquee(m: T) = BEGIN IF VBT.Parent(m.root.marquee) = NIL THEN VAR b := BorderedVBT.New( BorderedVBT.New(m.root.marquee, size := 3.0, op := PaintOp.Bg)); BEGIN VBTClass.Rescreen(b, VBT.ScreenTypeOf(m.root.source)); VAR sh := VBTClass.GetShapes(b); se := Rect.SouthEast(VBT.Domain(m.root.source)); BEGIN ZSplit.InsertAt(VBT.Parent(m.root.source), b, Point.MoveHV(se, -sh[Axis.T.Hor].pref - 10, -sh[Axis.T.Ver].pref - 10)); ZSplit.SetReshapeControl(b, ZSplit.ESChains) END END END END PopupMarquee;
PROCEDURESourceUntrue (d: T; how: View.ModKind) = BEGIN d.root.source.modified(how); IF d.continuousUnparse THEN d.root.source.update() END END SourceUntrue; PROCEDUREDisplayError (d: T; errmsg: TEXT) = <* LL.sup = VBT.mu *> BEGIN IF errmsg # NIL THEN VAR ch := Filter.Child(d.root.source); BEGIN JunoError.P(NARROW(ch, TextEditVBT.T).tp, errmsg) END END END DisplayError; BEGIN HorToolSym := Atom.FromText("_HOR"); VerToolSym := Atom.FromText("_VER"); CongToolSym := Atom.FromText("_CONG"); ParaToolSym := Atom.FromText("_PARA"); END Drawing.