<< 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] = <> 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: BOOL _ FALSE] 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: BOOL _ FALSE; << 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]}; <> 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] = <> <> 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] = <> <> 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: BOOL _ TRUE] 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]};