<* PRAGMA LL *> MODULEFor debugging:; IMPORT View, Drawing, JunoUIImpl, PSImpl, Source, JunoPt, CurrCmd; IMPORT ExternalProc, JunoError; IMPORT JunoAST, JunoASTUtils, JunoScope, JunoCompile, JunoCompileErr; IMPORT JunoRT, RTVal, JunoArgs, JunoRTError; IMPORT VBT, Cursor, Point, PaintOp, Filter, DblBufferVBT; IMPORT Atom, Thread, Fmt; Drag
IMPORT Wr, Stdio, Time;
REVEAL
T = Public BRANDED "Drag.T" OBJECT
(* The following fields are used by the "DuringDrag", "PostDrag",
"RunDeltaCmd", and "CallbackProc" procedures. The first two of these
procedures are invoked by a Trestle thread in response to a mouse
motion or up-click, and the latter two are invoked by the thread that
is forked to run the compiled drag command. *)
firstTime: BOOLEAN; (* unprotected; only accessed by drag loop thread *)
mu: MUTEX := NIL; (* protects the following fields *)
c: Thread.Condition := NIL;
lastReady: BOOLEAN;
last: Point.T;
deltaReady: BOOLEAN;
delta: Point.T; (* read/written by "DuringDrag" *)
done: BOOLEAN; (* set by "PostDrag" *)
END;
REVEAL
ArgTool = ArgToolPublic BRANDED "Drag.ArgTool" OBJECT OVERRIDES
pre := PreNoop;
post := PostNoop;
during := DuringNoop;
END;
VAR (*CONST*)
DragColor := PaintOp.FromRGB(1.0, 0.0, 0.0);
Default Pre, Post, During methods ---------------------------------------
PROCEDUREPreNoop ( <*UNUSED*> tl: ArgTool; d: Drawing.T; <*UNUSED*> READONLY cd: VBT.MouseRec; <*UNUSED*> i: INTEGER) = VAR ch: Drawing.Child := Filter.Child(d); BEGIN DblBufferVBT.Save(ch); Drawing.PaintPoint(ch, d.dragName, DragColor, d.draggee) END PreNoop; PROCEDUREDuringNoop ( <*UNUSED*> tl: ArgTool; d: Drawing.T; READONLY delta: Point.T; <*UNUSED*> i: INTEGER): Point.T RAISES {} = VAR ch: Drawing.Child := Filter.Child(d); new := Point.Add(d.draggee, ProjectMode(delta, d.dragMode)); BEGIN DblBufferVBT.Restore(ch); Drawing.PaintPoint(ch, d.dragName, DragColor, new); Drawing.Sync(ch); RETURN new END DuringNoop; PROCEDUREProjectMode (READONLY delta: Point.T; mode: DragMode): Point.T =
Return the pointdeltaprojected to the horizontal or vertical axis according to the dragging modemode.
BEGIN
CASE mode OF
DragMode.Unconstrained => RETURN delta
| DragMode.Hor => RETURN Point.T{delta.h, 0}
| DragMode.Ver => RETURN Point.T{0, delta.v}
END
END ProjectMode;
PROCEDURE PostNoop (
<*UNUSED*> tl: ArgTool;
<*UNUSED*> d: Drawing.T;
<*UNUSED*> READONLY cd: VBT.MouseRec;
<*UNUSED*> i: INTEGER) =
BEGIN END PostNoop;
New Dragging Tools ------------------------------------------------------
TYPE
DraggingTool = Drawing.ArgTool BRANDED "Drag.DraggingTool" OBJECT
forkee: Thread.T;
execRes: JunoRT.ExecRes
OVERRIDES
pre := PreDrag;
during := DuringDrag;
post := PostDrag;
apply := ApplyDrag
END;
A DraggingTool is the tool used to implement continuous solve dragging.
It's pre, during, post, and apply methods work together. If tl is
a DraggingTool, then tl.pre forks off a thread and sets tl.forkee to
this thread. The thread is set running a compiled command (a loop) that
gets new values for the dragged point by synchronizing with the during
method, and determines when the user has stopped dragging by synchronizing
with the post method. This command is contructed by the CompileDrag
procedure below. When the thread finishes, tl.execRes is set to the
execution result.
PROCEDUREDrag Tool Pre, Post, During, Apply methods ------------------------------NewTool (): Drawing.ArgTool = VAR args := Drawing.NewArgArray(1, Drawing.ArgType.Drag); BEGIN RETURN NEW(DraggingTool, argType := args) END NewTool;
TYPE
Closure = Thread.Closure BRANDED "Drag.Closure" OBJECT
d: Drawing.T;
slot: CARDINAL;
OVERRIDES
apply := RunDeltaCmd
END;
(* A "Drag.Closure" is the closure used to fork the compiled drag command.
If "cl: Closure", "cl.apply" executes the bytestream in "cl.slot", using
the fields of "cl.d" (for example, "cl.d.mu", "cl.d.c", "cl.d.firstTime",
and "cl.d.done") to synchronize with the drag command. *)
PROCEDURE RunDeltaCmd (cl: Closure): REFANY =
Theapplymethod of aDrag.Closure; i.e., this procedure is called by the thread that gets forked to run the compiled drag command. It runs the byte-code program in code table slotcl.slot. Returns a pointer to the resulting trapcode.
VAR res := NEW(REF JunoRT.ExecRes); BEGIN
cl.d.firstTime := TRUE;
res^ := JunoRT.ExecFromSlot(cl.slot, reset := TRUE);
<* ASSERT res^.trapCode = JunoRT.TrapCode.Error *>
(* If the current command failed, abort the dragging prematurely by
setting "cl.d.done". *)
IF res.errorCode # JunoRTError.Code.Halt THEN
LOCK cl.d.mu DO cl.d.done := TRUE END
END;
(* signal thread block in "DuringDrag" *)
Thread.Signal(cl.d.c);
RETURN res
END RunDeltaCmd;
VAR
debug := 0;
start: Time.T; (* start of drag *)
count: CARDINAL; (* number of drag frames *)
VAR (*CONST*)
WatchCursor: Cursor.T;
PROCEDURE PreDrag (
tl: DraggingTool;
d: Drawing.T;
READONLY cd: VBT.MouseRec;
i: INTEGER)
RAISES {Aborted} =
<* LL.sup = VBT.mu *>
VAR ch := Filter.Child(d); BEGIN
DblBufferVBT.ClearSaved(ch);
start := Time.Now();
count := 0;
IF d.mu = NIL THEN
d.mu := NEW(MUTEX);
d.c := NEW(Thread.Condition)
END;
LOCK d.mu DO
d.last := d.draggee;
d.lastReady := FALSE;
d.deltaReady := FALSE;
d.done := FALSE;
END;
(* hilight the selected point and display watch cursor *)
Drawing.PaintPoint(ch, d.dragName, DragColor, d.draggee);
VBT.SetCursor(ch, WatchCursor);
Drawing.Sync(ch);
(* initialize "forkee" in case "CompileDrag" raises an exception *)
tl.forkee := NIL;
TRY
(* Fork off a new thread to run the compiled Juno command. *)
tl.forkee := Thread.Fork(NEW(Closure, d := d,
slot := CompileDrag(d, d.stack[i].name, cd.time)));
FINALLY
(* reset the cursor *)
VBT.SetCursor(ch, Cursor.DontCare)
END
END PreDrag;
VAR (* CONST *)
DragLoopAtom := Atom.FromText("Drag Loop");
NewptAtom := Atom.FromText("_newpt");
OldptAtom := Atom.FromText("_oldpt");
VAR (* CONST *)
LoopMu := NEW(MUTEX);
LoopHeader := NEW(JunoAST.ProcHeader, name := DragLoopAtom,
ins := JunoAST.EmptyIdList, outs := JunoAST.EmptyIdList,
inouts := JunoAST.EmptyIdList, bp := JunoAST.End);
LoopDecl := NEW(JunoAST.ProcDecl, header := LoopHeader, bp := JunoAST.End);
Newpt := NEW(JunoAST.QId, bp := JunoAST.End,
id0 := JunoAST.NilId, id1 := NewptAtom);
Oldpt := NEW(JunoAST.QId, bp := JunoAST.End,
id0 := JunoAST.NilId, id1 := OldptAtom);
NewGrd := NEW(JunoAST.Differs, bp := JunoAST.End,
e1 := Newpt, e2 := JunoAST.NilVal);
Locals := NEW(JunoAST.NearVarList, bp := JunoAST.End, size := 2, head :=
NEW(JunoAST.NearVarLink, id := NewptAtom, hint := JunoAST.NilExpr, next :=
NEW(JunoAST.NearVarLink, id := OldptAtom, hint := JunoAST.NilExpr)));
IfCmd := NEW(JunoAST.If, bp := JunoAST.End);
PROCEDURE CompileDrag (d: Drawing.T; p: JunoAST.QId; ts: VBT.TimeStamp):
CARDINAL RAISES {Aborted} =
Letpbe the name of the dragged point, and letrdenote the rest of the points in the current command. Let the current command C have the form:
VAR nv IN P(nv) -> A ENDand let P' be P conjoined with any equality constraints innv(except for equality constraints on the dragged pointp). Then create, compile, install, and return the slot for the followingdragcommand:
IF VAR nv IN VAR _newpt, _oldpt IN _newpt := JunoUI._DRAG(p) ; DO _newpt # NIL -> _oldpt := p ; p := _newpt *** ; { P'?(r) | P'?(p,r) | p := _oldpt } *** ; IF A FI ; _newpt := JunoUI._DRAG(p) OD ; HALT END END FIIn the event thatd.dragModeisDragMode.HororDragMode.Ver, we have to compile a slightly different command. In that case, the two lines with (**
beside them become:
| DragMode.Hor: DragMode.Ver:
| ; p := (CAR(p), CDR(_newpt)) ; p := (CAR(_newpt), CDR(p))
| ; { PH'?(p,r) | p := _oldpt } ; { PV'?(p,r) | p := _oldpt }
where "PH'" is "P' AND p HOR _newpt" and "PV'" is "P' AND p VER _newpt".
Note how the drag command is cobbled together from syntactic parts of the
current command, rather than from semantic parts. In particular, the drag
command only solves for the variables listed at the outermost level of the
current command, and it solves these variables only for the outermost
predicate P. This technique fails to reflect the true semantics of the
current command, for example, if A has the form:
| VAR nv' IN Q(nv, nv') -> A' END
Now, "Grd(C) = P(nv) AND Q(nv, nv') AND Grd(A')", and the solver solves the
current command for the set of variables "nv \union nv'". But, as previously
mentioned, the drag command solves only for the variables "nv" such that
"P(nv)". To rectify this problem, we would have to inductively define the
"solve variables" of a command and the "non-guard" part of a command. We
could then compile a drag command that solved the "SolveVars(C)" for
"Grd(C)", and then executed "NonGuard(C)" in the drag loop. *)
<* LL.sup = VBT.mu *>
VAR
ast := CurrCmd.GetAST(d.root.ccmd);
locals := CurrCmd.GetVariables(ast);
con := Conjoin(CurrCmd.GetConstraint(ast), EqConstraints(locals, p.id1));
cmd := SkipifyBody(ast, d.root.skipify);
allVars := MoveToFront(locals, p.id1);
dragCall := NEW(JunoAST.ProcCall, bp := JunoAST.End,
outs := JunoASTUtils.NewQIdList(Newpt),
inouts := JunoAST.EmptyExprList,
name := JunoASTUtils.QIdFromIds(JunoUIImpl.ModSym, JunoUIImpl.DragSym),
ins := JunoASTUtils.NewExprList(p));
innerSemi := NEW(JunoAST.Seq, bp := JunoAST.End,
c1 := JunoASTUtils.NewAssign(Oldpt, p),
c2 := NEW(JunoAST.Seq, bp := JunoAST.End,
c1 := HintP(p, d.dragMode),
c2 := NEW(JunoAST.Seq, bp := JunoAST.End,
c1 := SolveCmd(p, con, allVars, d.dragMode),
c2 := NEW(JunoAST.Seq, bp := JunoAST.End,
c1 := cmd, c2 := dragCall))));
do := NEW(JunoAST.Do, bp := JunoAST.End,
body := NEW(JunoAST.Guard, bp := JunoAST.End,
grd := NewGrd, body := innerSemi));
outerSemi := NEW(JunoAST.Seq, bp := JunoAST.End, c1 := dragCall,
c2 := NEW(JunoAST.Seq, bp := JunoAST.End, c1 := do,
c2 := JunoAST.HaltVal));
innerProj := NEW(JunoAST.Proj, bp := JunoAST.End,
vars := Locals, body := outerSemi);
outerProj := NEW(JunoAST.Proj, bp := JunoAST.End,
vars := locals, body := innerProj);
outerIf := NEW(JunoAST.If, bp := JunoAST.End, body := outerProj);
proc: JunoScope.Proc;
BEGIN
LOCK LoopMu DO
LoopDecl.body := outerIf;
TRY
proc := JunoScope.NewProc(LoopDecl, mod := NIL);
EVAL JunoCompile.ProcDecl(DragLoopAtom, proc,
CurrCmd.GetScope(d.root.ccmd))
EXCEPT
JunoCompileErr.Error (err) =>
Source.ShowError(d.root.source, ast, err, ts);
RAISE Aborted
END
END;
RETURN proc.index
END CompileDrag;
PROCEDURE MoveToFront (l: JunoAST.NearVarList; id: JunoAST.Id):
JunoAST.NearVarList =
Return a list likelsuch that all occurrences of variables namedidappear first in the result. The order of the other variables in the result is undefined. Each variable in the result has itsevarbit reset, is unhinted, and has itsfrozenbit set iff the corresponding variable inlwas frozen or hinted.
VAR
res := NEW(JunoAST.NearVarList, bp := l, size := l.size);
curr := l.head; named: JunoAST.NearVarLink := NIL;
BEGIN
WHILE curr # NIL DO
<* ASSERT NOT curr.frozen OR curr.hint # JunoAST.NilExpr *>
IF curr.id = id THEN
named := NEW(JunoAST.NearVarLink, id := curr.id, evar := FALSE,
hint := JunoAST.NilExpr, frozen := (curr.hint # JunoAST.NilExpr),
index := curr.index, next := named)
ELSE
res.head := NEW(JunoAST.NearVarLink, id := curr.id, evar := FALSE,
hint := JunoAST.NilExpr, frozen := (curr.hint # JunoAST.NilExpr),
index := curr.index, next := res.head)
END;
curr := curr.next
END;
IF named # NIL THEN
(* ``res.head := Append(named, res.head)'' *)
curr := named;
WHILE curr.next # NIL DO curr := curr.next END;
curr.next := res.head;
res.head := named
END;
RETURN res
END MoveToFront;
PROCEDURE EqConstraints (l: JunoAST.NearVarList; id: JunoAST.Id):
JunoAST.Formula =
Return a conjunction containing a conjunct for each frozen variable inlother thanid. Return NIL if there are no variables inlother thanid.
VAR res: JunoAST.Formula := NIL; curr := l.head; eq: JunoAST.Equals; BEGIN
WHILE curr # NIL DO
<* ASSERT NOT curr.frozen OR curr.hint # JunoAST.NilExpr *>
IF curr.id # id AND curr.frozen THEN
eq := NEW(JunoAST.Equals, bp := JunoAST.End, near := FALSE,
e1 := JunoASTUtils.QIdFromNearVar(curr), e2 := curr.hint);
res := Conjoin(eq, res);
END;
curr := curr.next
END;
RETURN res
END EqConstraints;
PROCEDURE Conjoin (f1, f2: JunoAST.Formula): JunoAST.Formula =
Iff2 # NIL, return the ASTf1 AND f2; otherwise, returnf1
BEGIN
IF f2 = NIL THEN RETURN f1 ELSE
RETURN NEW(JunoAST.And, bp := JunoAST.End, f1 := f1, f2 := f2)
END
END Conjoin;
PROCEDURE SkipNamed (l: JunoAST.NearVarList; id: JunoAST.Id):
JunoAST.NearVarList =
Return a list whose head points to the element just after the longest prefix oflcontaining links namedid.
VAR
res := NEW(JunoAST.NearVarList, bp := l, size := l.size);
curr := l.head;
BEGIN
WHILE curr # NIL AND curr.id = id DO
DEC(res.size); curr := curr.next
END;
res.head := curr;
RETURN res
END SkipNamed;
PROCEDURE SkipifyBody (ast: JunoAST.Cmd; skipify: BOOLEAN): JunoAST.Cmd =
Ifskipify, returnSKIP. Otherwise, returnIF ast FI.
BEGIN
IF skipify
THEN RETURN JunoAST.SkipVal
ELSE IfCmd.body := CurrCmd.GetCmd(ast); RETURN IfCmd
END
END SkipifyBody;
PROCEDURE HintP (p: JunoAST.QId; mode: DragMode): JunoAST.Cmd =
Return the assignment statement appropriate for the drag modemode:DragMode.Unconstrained: "p := _newpt" DragMode.Hor: "p := (CAR(p), CDR(_newpt)) DragMode.Ver: "p := (CAR(_newpt), CDR(p))
PROCEDURE CarCdrPair(e1, e2: JunoAST.Expr): JunoAST.Pair =
(* Return the expression "(CAR(e1), CDR(e2))". *)
BEGIN
RETURN NEW(JunoAST.Pair, bp := JunoAST.End,
e1 := NEW(JunoAST.Car, bp := JunoAST.End, e := e1),
e2 := NEW(JunoAST.Cdr, bp := JunoAST.End, e := e2))
END CarCdrPair;
BEGIN
CASE mode OF
DragMode.Unconstrained => RETURN JunoASTUtils.NewAssign(p, Newpt)
| DragMode.Hor => RETURN JunoASTUtils.NewAssign(p, CarCdrPair(p, Newpt))
| DragMode.Ver => RETURN JunoASTUtils.NewAssign(p, CarCdrPair(Newpt, p))
END
END HintP;
PROCEDURE SolveCmd (p: JunoAST.QId; con: JunoAST.Formula;
vars: JunoAST.NearVarList; mode: DragMode): JunoAST.Cmd =
Return the solve command for the pointpgiven dragging modemode.conis the constraint to solve, andvarsis the list of unknowns (withpmoved to the front for easy processing). The different cases formodeare:
DragMode.Unconstrained: { P'?(r) | P'?(p,r) | p := _oldpt } DragMode.Hor: { ((p HOR _newpt) AND P')?(p,r) | p := _oldpt } DragMode.Ver: { ((p VER _newpt) AND P')?(p,r) | p := _oldpt }where P' is the set of constraintsconandris the set of variablesvarsminus the variablep.
VAR res: JunoAST.Cmd; BEGIN
CASE mode OF
DragMode.Hor =>
con := Conjoin(NEW(JunoAST.Hor, e1 := p, e2 := Newpt), con)
| DragMode.Ver =>
con := Conjoin(NEW(JunoAST.Ver, e1 := p, e2 := Newpt), con)
| DragMode.Unconstrained => (* SKIP *)
END;
res := NEW(JunoAST.Else, bp := JunoAST.End,
c1 := NEW(JunoAST.Query, bp := JunoAST.End, f := con, vars := vars),
c2 := JunoASTUtils.NewAssign(p, Oldpt));
IF mode = DragMode.Unconstrained THEN
res := NEW(JunoAST.Else, bp := JunoAST.End, c2 := res,
c1 := NEW(JunoAST.Query, bp := JunoAST.End,
f := con, vars := SkipNamed(vars, p.id1)))
END;
RETURN res
END SolveCmd;
PROCEDURE CallbackProc (cl: ExternalProc.Closure): BOOLEAN =
This procedure is the implementation of the Juno procedure named by the global variableJunoUIImpl.DragSym, which is called inside the drag loop compiled byCompileDrag. Hence, it is called by the thread running the compiled Juno program, namely, the one forked inPreDrag.It is called with the current value of the point being dragged (as a Juno value representing a point in Juno coordinates). It sets
d.lastto the new point value (converted to Trestle coordinates), and signals theDuringDragprocedure that this value is ready.It then synchronizes with the
DuringDragprocedure to get the delta for the difference between the mouse and the dragged point, adds this delta to the current value of the point, and returns (on the Juno stack) this value as the result. However, ifd.done, then this procedure instead returns JunoNIL.
<* LL.sup < VBT.mu *>
VAR
d := cl.rt.drawing;
ch: Drawing.Child := Filter.Child(d);
new: Point.T;
res: RTVal.T;
BEGIN
<* FATAL JunoPt.BadPt *>
VAR err := FALSE; pr: RTVal.Pair; BEGIN
(* convert argument from Juno coords -> Trestle coords *)
pr := JunoArgs.ReadPair(1, err);
<* ASSERT NOT err *>
new := JunoPt.ToHV(JunoPt.FromValuePair(pr), ch.xform)
END;
IF d.firstTime THEN
d.firstTime := FALSE;
ELSE
(* add final annotations to drawing *)
Drawing.PaintPath(d);
Drawing.PaintPoint(ch, d.dragName, DragColor, new);
Drawing.Sync(ch);
(* update shared data *)
LOCK d.mu DO
d.last := new;
d.lastReady := TRUE;
d.deltaReady := FALSE;
END;
(* signal "DuringDrag" thread *)
Thread.Signal(d.c)
END;
LOCK d.mu DO
(* wait for next call to "DuringDrag" (i.e., next mouse motion)
or "PostDrag" (i.e., next up-click terminating drag) *)
WHILE NOT d.deltaReady DO
Thread.Wait(d.mu, d.c)
END;
IF d.done THEN res := RTVal.nil ELSE
res := JunoPt.ToValuePair(JunoPt.FromHV(
Point.Add(d.last, d.delta), ch.xform))
END
END;
JunoArgs.WriteValue(2, res);
(* if not done, restart a new drawing *)
IF res # RTVal.nil THEN
PSImpl.Reset(d);
DblBufferVBT.Restore(ch);
END;
RETURN TRUE
END CallbackProc;
PROCEDURE DuringDrag (
tl: DraggingTool;
d: Drawing.T;
<*UNUSED*> READONLY delta: Point.T;
<*UNUSED*> i: INTEGER):
Point.T RAISES {Aborted} =
This is the procedure implementing theduringmethod of the built-inDragtool. This procedure is invoked by theDrawing.Positionprocedure each time the mouse moves.This procedure ignores its
deltaargument, and instead computesd.deltaas the difference between the current mouse locationd.draggerand the current value of the point being draggedd.draggee. In this way, we solve the system for the case where the dragged point is initially at the mouse cursor.If the thread running the compiled Juno command has set
d.done, then that command hit a run-time error, so this procedure raisesAbortedto abort the drag prematurely.
<* LL.sup = VBT.mu *>
BEGIN
INC(count);
IF tl.forkee = NIL THEN
(* The drag was aborted during "pre" method due to compilation error,
so just return the current location of the dragged point. *)
RETURN d.draggee
ELSE
LOCK d.mu DO
d.delta := Point.Sub(d.dragger, d.draggee);
d.deltaReady := TRUE;
d.lastReady := FALSE
END;
(* signal "CallbackProc" thread *)
Thread.Signal(d.c);
LOCK d.mu DO
(* wait for current frame to finish painting to get new value *)
WHILE NOT d.lastReady AND NOT d.done DO
Thread.Wait(d.mu, d.c)
END;
IF d.done THEN RAISE Aborted END;
RETURN d.last
END
END
END DuringDrag;
PROCEDURE PostDrag (
tl: DraggingTool;
d: Drawing.T;
<*UNUSED*> READONLY cd: VBT.MouseRec;
<*UNUSED*> i: INTEGER) =
<* LL.sup = VBT.mu *>
BEGIN
IF debug > 0 THEN
<* FATAL Thread.Alerted, Wr.Failure *>
VAR
delta := Time.Now() - start;
usecs := ROUND(1.0d6 * delta) DIV count;
BEGIN
Wr.PutText(Stdio.stderr, "Average time = "
& Fmt.Pad(Fmt.Int(usecs), 8) & " usecs\n");
END
END;
IF tl.forkee # NIL THEN
LOCK d.mu DO
d.delta := Point.Origin;
d.deltaReady := TRUE;
d.lastReady := FALSE
END;
(* signal "CallbackProc" thread *)
Thread.Signal(d.c);
LOCK d.mu DO
(* wait for current frame to finish painting *)
WHILE NOT d.lastReady AND NOT d.done DO
Thread.Wait(d.mu, d.c)
END;
END;
(* now finish dragging *)
LOCK d.mu DO
d.done := TRUE;
d.deltaReady := TRUE
END;
(* signal thread blocked in "CallbackProc" *)
Thread.Signal(d.c);
(* Wait for the thread running the compiled Juno command to terminate *)
tl.execRes := NARROW(Thread.Join(tl.forkee), REF JunoRT.ExecRes)^
END
END PostDrag;
PROCEDURE ApplyDrag (
tl: DraggingTool;
d: Drawing.T;
<*UNUSED*> READONLY arg: ARRAY OF Drawing.Arg) =
VAR hintsChanged: BOOLEAN; BEGIN
IF tl.forkee # NIL THEN
<* ASSERT tl.execRes.trapCode = JunoRT.TrapCode.Error *>
hintsChanged := CurrCmd.UpdateHints(d.root.ccmd);
IF tl.execRes.errorCode # JunoRTError.Code.Halt THEN
(* display an error message *)
JunoError.Display(d, JunoRT.TrapMessage(tl.execRes))
ELSE
(* finish running current command if it ran up to final "HALT" *)
VAR execRes2 := JunoRT.Exec(); BEGIN
<* ASSERT execRes2.trapCode = JunoRT.TrapCode.NormalHalt *>
END
END;
IF hintsChanged THEN
Drawing.SourceUntrue(d, View.ModKind.ImplicitConsistent)
END
END;
Drawing.Annotations(d)
END ApplyDrag;
BEGIN
WatchCursor := Cursor.FromName(ARRAY OF TEXT{"XC_watch"})
END Drag.