JunoImageImpl.mesa

Pieces from JunoTop.mesa created May 1981 by Greg Nelson, Donna Auguste
Last Edited by: Gnelson, January 17, 1984 11:32 am
Last Edited by: Maureen Stone January 19, 1984 12:08 pm
Last Hacked by: Jorge Stolfi June 7, 1984 4:40:10 pm PDT

Procedures to manipulate and paint the current Juno image.

DIRECTORY

JunoStorage,
JunoImage,
JunoOldSolver USING [Solve, Outcome],
RealFns USING [SqRt];

JunoImageImpl: PROGRAM

IMPORTS

JunoStorage,
JunoOldSolver,
RealFns

EXPORTS

JunoImage

=

BEGIN

OPEN

JunoStorage,
JunoImage,
Solv: JunoOldSolver;

- - - - THE CURRENT IMAGE

points: PointList ← [NIL, NIL];
constrs: ConstrList ← [NIL, NIL];
actions: ActionList ← [NIL, NIL]; -- The current image

PurgeImage: PUBLIC PROC =

BEGIN
GcPoints [start: points.first, lim: NIL]; points ← [NIL, NIL];
GcConstrs[start: constrs.first, lim: NIL]; constrs ← [NIL, NIL];
GcActions[start: actions.first, lim: NIL]; actions ← [NIL, NIL]
END;

- - - - IMAGE POINTS

AddPoint: PUBLIC PROC [p: Point] =

BEGIN
points ← InsertPoint[p, points.last, points]
END;

RemovePoint: PUBLIC PROC [p: Point] =

BEGIN
points ← DeletePoint[p, NIL, points];
GcPoints[p, p.link]
END;

SortPoints: PUBLIC PROC =

BEGIN
[points.first, points.last] ← DoSortPoints[points.first]
END;

DoSortPoints: PROC [in: Point] RETURNS [first, last: Point] =
Sorts in, in.link, in.link.link, ... (up to last point) by increasing x-coordinate.
Should be O(n log n) worst case, O(n) if points are already sorted, and mostly fast if points are mostly sorted.
Hope it works.

BEGIN

p, q, pl, ql, t: Point ← NIL;
runs: INTEGER ← 0;

DO

Do one more pass over the entire list:

runs ← 0; last ← NIL;

DO

Are there any more runs?

IF in = NIL THEN EXIT;
runs ← runs+1;

Get the next ascending run

p ← in;
pl ← in; in ← in.link;
WHILE in # NIL AND in.coords.x >= pl.coords.x DO
pl ← in; in ← in.link
ENDLOOP;

Was it the only one?

IF in = NIL THEN
{last ← pl; EXIT};

Get following ascending run

q ← in;
ql ← in; in ← in.link;
WHILE in # NIL AND in.coords.x >= ql.coords.x DO
ql ← in; in ← in.link
ENDLOOP;
ql.link ← NIL;

Merge them into a single run.

WHILE p # q AND q # in DO
IF p.coords.x < q.coords.x THEN
{t ← p; p ← t.link}
ELSE
{t ← q; q ← t.link; t.link ← p; pl.link ← q};
IF last = NIL THEN {first ← t} ELSE {last.link ← t};
last ← t
ENDLOOP;

ENDLOOP;

IF runs < 2 THEN RETURN;
in ← first

ENDLOOP

END;

- - - - CONSTRAINTS

AddConstr: PUBLIC PROC [c: Constr] =

BEGIN
constrs ← InsertConstr [c, constrs.last, constrs]
END;

- - - - ACTIONS

AddAction: PUBLIC PROC [a: Action] =

BEGIN
actions ← InsertAction [a, actions.last, actions]
END;

- - - - ELEMENT ENUMERATION

EnumPoints: PUBLIC PROC [Proc: PointVisitProc] =

BEGIN
FOR p: Point ← points.first, p.link WHILE p # NIL DO Proc[p] ENDLOOP
END;

ReplacePoints: PUBLIC PROC [Proc: PointReplaceProc] =

BEGIN
pAnt, pNew, pNext: Point ← NIL;
FOR p: Point ← points.first, pNext WHILE p # NIL DO
pNext ← p.link;
pNew ← Proc[p];
IF pNew # p THEN
{points ← DeletePoint[p, pAnt, points]; GcPoints [p, p.link];
IF pNew # NIL THEN
{points ← InsertPoint[pNew, pAnt, points];
pAnt ← pNew}}
ELSE
{pAnt ← p}
ENDLOOP
END;

EnumConstrs: PUBLIC PROC [Proc: ConstrVisitProc] =

BEGIN
FOR c: Constr ← constrs.first, c.link WHILE c # NIL DO Proc[c] ENDLOOP
END;

EnumConstrPoints: PUBLIC PROC [c: Constr, Proc: PointVisitProc] =

BEGIN
WITH c SELECT FROM
cc: HorConstr =>
{Proc[cc.i]; Proc[cc.j]};
cc: VerConstr =>
{Proc[cc.i]; Proc[cc.j]};
cc: CongConstr =>
{Proc[cc.i]; Proc[cc.j]; Proc[cc.k]; Proc[cc.l]};
cc: ParaConstr =>
{Proc[cc.i]; Proc[cc.j]; Proc[cc.k]; Proc[cc.l]};
cc: PerpConstr =>
{Proc[cc.i]; Proc[cc.j]; Proc[cc.k]; Proc[cc.l]};
cc: AtConstr =>
{Proc[cc.p]};
cc: CcwConstr =>
{Proc[cc.i]; Proc[cc.j]; Proc[cc.k]};
ENDCASE => ERROR;
IF c.frame.org # NIL THEN Proc[c.frame.org];
IF c.frame.hor # NIL THEN Proc[c.frame.hor];
IF c.frame.ver # NIL THEN Proc[c.frame.ver]
END;

ReplaceConstrPoints: PUBLIC PROC [c: Constr, Proc: PointReplaceProc] =

BEGIN
WITH c SELECT FROM
cc: HorConstr =>
{cc.i ← Proc[cc.i]; cc.j ← Proc[cc.j]};
cc: VerConstr =>
{cc.i ← Proc[cc.i]; cc.j ← Proc[cc.j]};
cc: CongConstr =>
{cc.i ← Proc[cc.i]; cc.j ← Proc[cc.j]; cc.k ← Proc[cc.k]; cc.l ← Proc[cc.l]};
cc: ParaConstr =>
{cc.i ← Proc[cc.i]; cc.j ← Proc[cc.j]; cc.k ← Proc[cc.k]; cc.l ← Proc[cc.l]};
cc: PerpConstr =>
{cc.i ← Proc[cc.i]; cc.j ← Proc[cc.j]; cc.k ← Proc[cc.k]; cc.l ← Proc[cc.l]};
cc: AtConstr =>
{cc.p ← Proc[cc.p]};
cc: CcwConstr =>
{cc.i ← Proc[cc.i]; cc.j ← Proc[cc.j]; cc.k ← Proc[cc.k]};
ENDCASE => ERROR;
IF c.frame.org # NIL THEN c.frame.org ← Proc[c.frame.org];
IF c.frame.hor # NIL THEN c.frame.hor ← Proc[c.frame.hor];
IF c.frame.ver # NIL THEN c.frame.ver ← Proc[c.frame.ver]
END;

EnumActions: PUBLIC PROC [Proc: ActionVisitProc] =

BEGIN
FOR a: Action ← actions.first, a.link WHILE a # NIL DO Proc[a] ENDLOOP
END;

EnumActionPoints: PUBLIC PROC [a: Action, Proc: PointVisitProc] =

BEGIN
FOR args: ActionArgs ← a.args, args.rest WHILE args # NIL DO
WITH args.first SELECT FROM
p: Point => Proc[p];
ENDCASE;
ENDLOOP
END;

ReplaceActionPoints: PUBLIC PROC [a: Action, Proc: PointReplaceProc] =

BEGIN
FOR args: ActionArgs ← a.args, args.rest WHILE args # NIL DO
WITH args.first SELECT FROM
p: Point => args.first ← Proc[p];
ENDCASE;
ENDLOOP
END;

- - - - POINT LOCATION

Distance: PROC [p, q: Coords] RETURNS [REAL] = INLINE

BEGIN
RETURN[RealFns.SqRt[(p.x-q.x)*(p.x-q.x)+(p.y-q.y)*(p.y-q.y)]]
END;

FindPoint: PUBLIC PROC [coords: Coords, wound: BOOLFALSE] RETURNS [champ: Point] =

BEGIN
champdistance, pdistance: REAL;
champ ← NIL;
champdistance ← 1.0E+30;
FOR p: Point ← points.first, p.link WHILE p # NIL DO
IF wound AND p.wn = 0 THEN LOOP;
pdistance ← Distance[p.coords, coords];
IF pdistance < champdistance THEN
{champ ← p; champdistance ← pdistance}
ENDLOOP
END;

- - - - BALOON SELECTION

BaloonSelect: PUBLIC PROC [start: IntCoords, NextPoint: NextPointProc] =

BEGIN

WARNING: while this procedure is working, the links of the image points list are not valid.

temp, pl, pr: Point;
old: IntCoords ← start;
new: IntCoords;
lastPoint: BOOLFALSE;

BaloonSelect works by repeatedly sampling the mouse coordinates andcalling the procedure Wind:

Wind: PROCEDURE =

BEGIN

The effect of Wind is to compute the winding number of the small segmentfrom old to new around every point.

The winding number of the segment around the point (px, py) is zero unless px is in the range [old.x, new.x) and the point p is abovethe line through old and new. If non-zero, it is 1 or -1 according as new.x > old.x or new.x < old.x.

To rapidly find the points (px,py) such that px is in [old.x, new.x), we arrange that (a) the points pl, link[pl], link[link[pl]] ... are exactly those points whose x coordinates are less than old.x, and the points are listed in decreasing order of their x coordinates, and (b) the points pr, link[pr], link[link[pr]], ... are exactly those points whose x coordinates are greater than or equal to old.x, and the points are listed in increasing order of their x coordinates.

IF old.x < new.x THEN -- move right:

{WHILE pr # NIL AND pr.coords.x < new.x DO

pc: Coords = pl.coords;
-- transfer one point from the list pr to the list pl:
temp ← pr.link;
pr.link ← pl;
pl ← pr;
pr ← temp;

Now update winding number of point if it is above line of mouse motion.

IF (pc.y - old.y)*(new.x - old.x) > (new.y - old.y)*(pc.x - old.x) THEN
{pl.wn ← pl.wn + 1}

ENDLOOP}

ELSE IF old.x > new.x THEN -- move left:

{WHILE pl # NIL AND pl.coords.x >= new.x DO

pc: Coords = pr.coords;
temp ← pl.link;
pl.link ← pr;
pr ← pl;
pl ← temp;
IF (pc.y - new.y)*(old.x - new.x) > (old.y - new.y)*(pc.x - new.x) THEN
{pr.wn ← pr.wn - 1}

ENDLOOP};

END;

SortPoints; -- just to make sure
pl ← points.first; -- initialize linked lists pl and pr:
pr ← NIL;

WHILE pr # NIL AND pr.coords.x < start.x DO
temp ← pr.link; pr.link ← pl; pl ← pr; pr ← temp
ENDLOOP;

UNTIL lastPoint DO
[new, lastPoint] ← NextPoint[];
Wind;
old ← new;
ENDLOOP;

new ← start;
Wind;

Now the winding numbers have been computed for all points. Must reset thelinked list of points.

WHILE pl # NIL DO
temp ← pl.link; pl.link ← pr; pr ← pl; pl ← temp
ENDLOOP;

END;

AnyWoundPoints: PUBLIC PROC RETURNS [BOOL] =

BEGIN
FOR p: Point ← points.first, p.link WHILE p # NIL DO
IF p.wn#0 THEN RETURN [TRUE]
ENDLOOP;
RETURN [FALSE]
END;

ConstrIsWound: PUBLIC PROC [c: Constr] RETURNS [wound: BOOL] =

BEGIN

TestPoint
: PointVisitProc = {wound ← wound AND p.wn # 0};

wound ← TRUE;
EnumConstrPoints[c, TestPoint]
END;

ActionIsWound: PUBLIC PROC [a: Action] RETURNS [wound: BOOL] =

BEGIN

TestPoint: PointVisitProc = {wound ← wound AND p.wn # 0};

wound ← TRUE;
EnumActionPoints[a, TestPoint];
END;

- - - - OPERATIONS ON BALOON-SELECTED POINTS

DeleteWoundItems: PUBLIC PROCEDURE =

BEGIN

MarkIt
: PointVisitProc = {p.mark ← TRUE};

{UnMark: PointVisitProc = {p.mark ← FALSE}; EnumPoints[UnMark]};
Delete all constraints and actions whose arguments are all wound,
-- and mark the arguments of those that are not deleted.

BEGIN
cAnt, cNext: Constr ← NIL;
FOR c: Constr ← constrs.first, cNext WHILE c # NIL DO
cNext ← c.link;
IF ConstrIsWound[c] THEN
{constrs ← DeleteConstr[c, cAnt, constrs]; GcConstrs[c, c.link]}
ELSE
{EnumConstrPoints[c, MarkIt]; cAnt ← c};
ENDLOOP
END;

BEGIN
aAnt, aNext: Action ← NIL;
FOR a: Action ← actions.first, aNext WHILE a # NIL DO
aNext ← a.link;
IF ActionIsWound[a] THEN
{actions ← DeleteAction[a, aAnt, actions]; GcActions[a, a.link]}
ELSE
{EnumActionPoints[a, MarkIt]; aAnt ← a};
ENDLOOP
END;

-- Now delete all wound points that belong to no action of constraint,
-- and reset the winding numbers and marks of the others:
BEGIN
pAnt, pNext: Point ← NIL;
FOR p: Point ← points.first, pNext WHILE p # NIL DO
pNext ← p.link;
IF p.wn # 0 AND NOT p.mark THEN
{points ← DeletePoint[p, pAnt, points]; GcPoints[p, p.link]}
ELSE
{pAnt ← p};
p.wn ← 0; p.mark ← FALSE;
ENDLOOP
END

END;

DuplicateWoundItems: PUBLIC PROCEDURE =

BEGIN

RepByCopy: PointReplaceProc = {RETURN [IF p.copy # NIL THEN p.copy ELSE p]};

CopyConstr: PROC [c: Constr] RETURNS [cCopy: Constr] =
Creates a copy of constraint c using the same points as c.
Note: the copy links of the points are not examined

BEGIN

cCopy ←
WITH c SELECT FROM
cc: HorConstr =>
NewHor[cc.i, cc.j],
cc: VerConstr =>
NewHor[cc.i, cc.j],
cc: CongConstr =>
NewCong[cc.i, cc.j, cc.k, cc.l],
cc: ParaConstr =>
NewPara[cc.i, cc.j, cc.k, cc.l],
cc: PerpConstr =>
NewPerp[cc.i, cc.j, cc.k, cc.l],
cc: AtConstr =>
NewAt[cc.p, cc.coords],
cc: CcwConstr =>
NewCcw[cc.i, cc.j, cc.k],
ENDCASE => ERROR;

cCopy.frame ← c.frame

END;
CopyAction: PROC [a: Action] RETURNS [aCopy: Action] =
Creates a copy of action a (including the top level of the argument list).
Note: the points occuring in aCopy are those occuring in a, NOT their copies (if any).

BEGIN

CopyArgs: PROC [args: ActionArgs] RETURNS [copy: ActionArgs] =
{RETURN[IF args = NIL THEN NIL ELSE Cons [args.first, CopyArgs[args.rest]]]};

aCopy ← NewAction[kind: a.kind, args: CopyArgs[a.args]];

END;

Duplicate all wound points (including the wn field), and link every point to its copy (or NIL if not wound):

BEGIN

pCopy, pNext: Point ← NIL;
FOR p: Point ← points.first, pNext WHILE p # NIL DO
pNext ← p.link;
IF p.wn # 0 THEN
{pCopy ← NewPoint[p.coords, p.visible];
points ← InsertPoint[pCopy, p, points];
p.copy ← pCopy; pCopy.wn ← p.wn;
pCopy.mark ← FALSE}
ELSE
{p.copy ← NIL}
ENDLOOP

END;

Copy all constraints whose arguments are all wound:

BEGIN

cCopy, cNext: Constr ← NIL;
FOR c: Constr ← constrs.first, cNext WHILE c # NIL DO
cNext ← c.link;
IF ConstrIsWound[c] THEN
{cCopy ← CopyConstr[c]; ReplaceConstrPoints [cCopy, RepByCopy];
constrs ← InsertConstr[cCopy, c, constrs]}
ENDLOOP

END;

Similarly for actions:

BEGIN

aCopy, aNext: Action ← NIL;
FOR a: Action ← actions.first, aNext WHILE a # NIL DO
aNext ← a.link;
IF ActionIsWound[a] THEN
{aCopy ← CopyAction[a]; ReplaceActionPoints [aCopy, RepByCopy];
actions ← InsertAction[aCopy, a, actions]}
ENDLOOP

END;

Reset the winding numbers of the original points (but not the copies)

{Unw: PointVisitProc = {IF p.copy # NIL THEN p.wn ← 0}; EnumPoints[Unw]}

END;

- - - - POINT IDENTIFICATION

IdentifyPoints: PUBLIC PROC =

BEGIN

UnMark: PointVisitProc = {p.mark ← FALSE};

UpdateAndMark: PointReplaceProc =
{IF p.copy # NIL THEN {p.copy.mark ← TRUE; RETURN [p.copy]}
ELSE RETURN [p]};

IdConstrArgs: ConstrVisitProc = {ReplaceConstrPoints[c, UpdateAndMark]};

IdActionArgs: ActionVisitProc = {ReplaceActionPoints[a, UpdateAndMark]};

DeleteUnreachableOriginals: PointReplaceProc =
{pNew ← IF p.mark OR p.copy=NIL THEN p ELSE NIL;
p.mark ← FALSE; p.copy ← NIL};

-- Mark copies so that we know who becomes unreachable
EnumPoints[UnMark];
-- Replace p by p.copy in all actions and constraints, whenever p.copy # NIL
EnumConstrs[IdConstrArgs];
EnumActions[IdActionArgs];
-- Now delete original points that have become unreachable
ReplacePoints[DeleteUnreachableOriginals]

END;

- - - - CONSTRAINT SOLVING

SolveImage: PUBLIC PROC [eps: REAL] RETURNS [outcome: Solv.Outcome] =

Solves the image constraints for all image points that are not fixed.

{-- Should display an hourglass and perhaps disable mouse/keyboard input.
outcome ← Solv.Solve[constrs, eps];
SortPoints[];
-- Should turn off hourglass and re-enable mouse/keyboard input.
};

END.

- - - - JUNKYARD

Basis: TYPE = REF BasisRec;

BasisRec: TYPE = RECORD[head:TangentVector ← NIL, tail:Basis];

TangentVector: TYPE = REF TvRec;

TvRec: TYPE = RECORD[head:PointPtr ← NIL, x, y: REAL ← 0, tail:TangentVector];

PushState: PUBLIC PROCEDURE;

PopState: PUBLIC PROCEDURE;

InitJunoStorage: PROC;

ResetJunoStorage: PROC; -- reclaims all records.

pointLpad, pointRpad: PointPtr; -- The lists of points, edges, arcs
edgeLpad, edgeRpad: EdgePtr; -- line constraints, and cong. constraints
arcLpad, arcRpad: ArcPtr; -- are padded on both sides.
lineLpad, lineRpad: LinPtr;
congLpad, congRpad: CongPtr;
stringLpad, stringRpad: StringPtr;
horLpad, horRpad: HorPtr;
verLpad, verRpad: VerPtr;
ccLpad, ccRpad: CCPtr;

SortPoints: PROC =

BEGIN
p ← NIL; q ← image.points; image.points ← NIL;
UNTIL q = NIL DO
t ← q; q ← t.link; t.link ← NIL;
r ← p; rant ← NIL;
WHILE r # NIL AND
(t.x < r.x OR (t.x = r.x AND t.y < r.y)) DO
rant ← r; r ← r.link;
ENDLOOP;
p ← InsertPoint [t, rant, p];
ENDLOOP;
-- Now reverse the list by moving backwards through it:
UNTIL p = NIL DO
t ← p.link; p.link ← image.points; image.points ← p; p ← t
ENDLOOP}

END;

NewPoint: PROC[x,y: INTEGER, visible: BOOLTRUE] RETURNS [PointPtr] =

{p: PointPtr ← AddPoint[x,y];
p.visible ← visible;
IF visible THEN JG.DrawPoint[x,y];
SortPoints[];
RETURN [p]};

AddLambda: PROC [f: REF ANY, args: LIST OF PointPtr] =

{JG.SetPaintMode[opaque];
JA.Apply[f, args, NIL]; -- f is an atom
JG.SetPaintMode[invert];
JG.viewerChanged ← TRUE;
AddX[f, args]; 
SortPoints[] };

CopyToViewer: PROC =

{ DO -- IF JunoA.newVersion = TRUE AND gotNewJunoA = FALSE
-- THEN { RefreshCursors[];
-- numCursor ← 11; notice that this is one-macro dependent!!
-- JunoA.newVersion ← FALSE;
-- gotNewJunoA ← TRUE;
-- RefreshCursors[]};
IF viewerDead THEN EXIT;
{t: Terminal.Virtual = Terminal.Current[];
Terminal.WaitForBWVerticalRetrace [t]; --! Should there be one instead of two calls?
Terminal.WaitForBWVerticalRetrace [t]};
IF JG.viewerChanged
THEN {ViewerOps.PaintViewer [viewer: self, hint: client, clearClient: FALSE];
JG.viewerChanged ← FALSE;}
ENDLOOP};

MoveStep: PROC[copying: BOOLEAN, solving: BOOLEAN] =

-- this proc is used by both MoveCmd and CopyCmd. It (a) identifies t with s,
-- for each source-target pair (s,t) that satisfies s is selected and t is not
-- selected; (b) computes a transform that takes the sources to the targets,
-- and (c) applies the transform to all selected points (if copying = FALSE) or
-- to all copied points (if copying = TRUE). It also actually copies the selected
-- list if copying = TRUE. It is only within this procedure that copy fields are
-- ever non-NIL.
BEGIN
n: INTEGER = MIN[atop,satop];
i: INTEGER;
savedFixBit: ARRAY[0..10] OF BOOLEAN;
savedSourcePoint: ARRAY[0..10] OF PointPtr;
singular ← FALSE;
IF n > 2 THEN ComputeTransform[a[atop].p, a[atop-1].p, a[atop-2].p, sa[satop].p, sa[satop-1].p, sa[satop-2].p];
IF n = 2 OR singular THEN ComputeTransform[a[atop].p, a[atop-1].p, NIL, sa[satop].p, sa[satop-1].p, NIL];
IF n = 1 OR singular THEN ComputeTransform[a[atop].p, NIL, NIL, sa[satop].p, NIL, NIL];
IF n = 0 OR singular THEN
{JG.Blink["No such affine transformation"];
RETURN};
IF copying THEN {Copy[]; PerformTransform[copiedPoints]}
ELSE PerformTransform[pointLpad.slink];
FOR i IN [0.. n-1] DO
IF UnSelected[a[atop-i].p] AND NOT(UnSelected[sa[satop-i].p])
THEN {IF copying THEN a[atop-i].p.copy ← sa[satop-i].p.copy
ELSE a[atop-i].p.copy ← sa[satop-i].p}
ENDLOOP;
FOR i IN [0..n-1] DO
IF copying THEN savedSourcePoint[i] ← sa[satop-i].p.copy
ELSE savedSourcePoint[i] ← sa[satop-i].p;
savedFixBit[i] ← savedSourcePoint[i].fixed;
savedSourcePoint[i].fixed ← TRUE;
ENDLOOP;
IF copying
THEN {p: PointPtr ← pointLpad.slink;
UNTIL p = pointRpad DO p.copy ← NIL; p ← p.slink ENDLOOP};
Identify[]; -- identify p with p.copy for all p such that p.copy # NIL
IF solving THEN {JunoClass.cursor ← hourGlass;
WindowManager.RestoreCursor;
[] ← JunoSolver.Solve[.5];
JunoClass.cursor ← cursor[currentCursor].cursorName;
WindowManager.RestoreCursor};
SortPoints[];
FOR i IN [0..n-1] DO savedSourcePoint[i].fixed ← savedFixBit[i] ENDLOOP;
Refresh[];
END;

AlgebraStep: PROC =

{body: REF ← MakeDefBody[sa[satop-1].p, sa[satop].p]; -- MakeDefBody is from JunoBody
colon: ATOM ← Atom.MakeAtom[":"];
leftpren: ATOM ← Atom.MakeAtom["("];
comma: ATOM ← Atom.MakeAtom[","];
AddDef[name: $X, locals: LIST[comma, $a, $b], body: body];
PW.AddTree[JunoParserEtc.junoA,
LIST[colon, LIST[leftpren, $X, LIST[comma, $a, $b]], body]]};

OtherAlgebraStep: PROC =

{body: REF ← MakeDefBody[sa[satop].p, NIL]; -- MakeDefBody is from JunoBody
colon: ATOM ← Atom.MakeAtom[":"];
leftpren: ATOM ← Atom.MakeAtom["("];
comma: ATOM ← Atom.MakeAtom[","];
AddDef[name: $X, locals: $a, body: body];
PW.AddTree[JunoParserEtc.junoA,
LIST[colon, LIST[leftpren, $X, $a], body]]};

RefreshPoints: PROCEDURE =

BEGIN
p: PointPtr ← pointLpad.link;
WHILE p # pointRpad
DO IF p.visible THEN JG.DrawPoint[Real.FixI[p.x], Real.FixI[p.y]];
  p ← p.link ENDLOOP;
END;

RefreshEdges: PROCEDURE =

BEGIN
p: EdgePtr ← edgeLpad.link;
WHILE p # edgeRpad
DO JG.DrawEdge[p.b1.x, p.b1.y, p.b2.x, p.b2.y];
  p ← p.link ENDLOOP;
END;

RefreshArcs: PROCEDURE =

BEGIN
p: ArcPtr ← arcLpad.link;
WHILE p # arcRpad
DO JG.DrawArc[p.b1.x, p.b1.y, p.b2.x, p.b2.y, p.b3.x, p.b3.y, p.b4.x, p.b4.y];
  p ← p.link ENDLOOP;
END;

RefreshStrings: PROCEDURE =

BEGIN
p : StringPtr ← stringLpad.link;
WHILE p # stringRpad
DO
JG.DrawString[p.b3.x, p.b3.y, p.stringText, p.stringFont,
p.fontName, p.fontSize, p.bold, p.italic];
p ← p.link ENDLOOP;
END;

RefreshX: PROC = {RefreshXR[constructionList]};

RefreshXR: PROC[l: LIST OF ApplyRecord] =
{
IF l # NIL THEN {RefreshXR[l.rest]; JA.Apply[l.first.f, l.first.args, NIL]}};

SaveStateRec: TYPE = RECORD[

savePointLpad: PointPtr ← NIL,
saveEdgeLpad: EdgePtr ← NIL,
saveArcLpad: ArcPtr ← NIL,
saveLineLpad: LinPtr ← NIL,
saveCongLpad: CongPtr ← NIL,
saveHorLpad: HorPtr ← NIL,
saveVerLpad: VerPtr ← NIL,
saveStringLpad: StringPtr ← NIL,
saveCCLpad: CCPtr ← NIL];

UnSelected: PROC[q:PointPtr] RETURNS [BOOLEAN] =

{p:PointPtr ← pointLpad.slink;
UNTIL p = pointRpad DO IF p = q THEN RETURN [FALSE] ELSE p ← p.slink ENDLOOP;
RETURN [TRUE]};