<< JunoTop.mesa >> << Coded May 1981 by Greg Nelson >> << Converted to use the Viewers window package by Donna Auguste>> << Last Edited by: Gnelson, January 17, 1984 11:32 am>> << Last Edited by: Maureen Stone January 19, 1984 12:08 pm>> << Merged with JunoGerm.mesa by Jorge Stolfi May 19, 1984 4:12:07 am PDT>> << Last Edited by: Jorge Stolfi June 15, 1984 8:17:25 am PDT>> << This is the top level of an experimental interactive graphics program that allows a user to specify the positions of points by declaring geometrical contraints that the positions are to satisfy. The program solves constraints (which can be viewed as simultaneous polynomial equations) by an n-dimensional version of Newton's method for finding the root of a differentiable function.>> << JunoTop is the exclusive owner of the internal data structure representing the current image (that is, the points, constraints and actions moused in by the user). It handles those through the JunoImage interface.>> << JunoTop is also the only module that removes stuff from the user event queue. Things are placed there by (a) the cursor menu module, (b) the image viewer NotifyProc (see JunoUserEventsImpl), and (c) by the JunoButtons module.>> << TO FIX:>> << Error recovery: Check it out >> << JunoAlgebra.Eval: graphics state-pushing ops (paint, width, etc) should restore graphics state on UNWIND >> DIRECTORY JunoImage, JunoMatrix USING [Matrix, MapCoords, ComputeTransform], JunoBody USING [Se, MakeBody], JunoOldSolver USING [Outcome], JunoStorage USING [Point, Coords, Item, IntCoords, NewPoint, ActionKind, ItemArgs, ConstrKind, Cons, Frame, NewItem], JunoExpressions USING [comma, lambda], JunoAlgebra USING [Value, Call, EvalColor], JunoGraphics USING [Highight, DrawPoint, PointSymbol, DrawRope, SetPaperContext, SetScreenContext, DrawEdge, SetFontName, SetFontFace, SetFontSize, DrawArc, SetJustification, SetColor, black, invert, currentJustification, currentFontSize, SetCaret, caretOn, GetRopeDispl, Whiten, SetWidth, Color], JunoUserEvents USING [bugout, Event, Next, EventCoords, Blink, StartUp, Terminate], IO USING [PutF, real], -- DEBUG JunoGlobalAlist USING [AddDef, StartUp, Terminate, ParseAll], Rope USING [ROPE, FromChar, Cat, IsEmpty, Substr, Length], Convert USING [RopeFromInt, RopeFromReal], Process USING[Detach], Atom USING[GetPName, MakeAtom]; JunoTop: PROGRAM IMPORTS JunoImage, JunoStorage, JunoBody, JunoMatrix, JunoGraphics, JunoExpressions, JunoAlgebra, JunoUserEvents, JunoGlobalAlist, IO, -- DEBUG Rope, Convert, Process, Atom = BEGIN OPEN JunoStorage, Im: JunoImage, Solv: JunoOldSolver, Body: JunoBody, Evs: JunoUserEvents, Gr: JunoGraphics, Expr: JunoExpressions, Alg: JunoAlgebra, Mat: JunoMatrix, Glob: JunoGlobalAlist, Rope; debug1, debug2: BOOL _ FALSE; -- can be set by BugBane to avoid debug output << - - - - ARGUMENT QUEUES >> argMax: INTEGER = 50; -- max num. of arguments in each queue Queue: TYPE = REF QueueRec; QueueRec: TYPE = RECORD [top: [0 .. argMax] _ 0, a: ARRAY [1 .. argMax] OF RECORD [p: Point, sy: Gr.PointSymbol]]; << The symbol used for a point depends on how many points with same rouded coordinates are already there.>> que: Queue = NEW[QueueRec]; << There used to be a secondary queue, but its duties have been taken over by the primary one.>> L1Dist: PROC [p, q: Coords] RETURNS [REAL] = INLINE {RETURN[ABS[p.x-q.x]+ABS[p.y-q.y]]}; HilyteElem: PROC [ix: INTEGER] = <> <> {OPEN que; a[ix].sy _ plus; FOR k: INTEGER DECREASING IN [1..ix) DO IF L1Dist[a[k].p.coords, a[ix].p.coords] < 1 THEN {a[ix].sy _ IF a[k].sy >= box THEN box ELSE SUCC[a[k].sy]; EXIT} ENDLOOP; Gr.Highight[a[ix].p.coords, a[ix].sy]}; Push: PROC [p: Point] = <> <> {OPEN que; IF p=NIL THEN ERROR; top _ top + 1; a[top].p _ p; HilyteElem[top]}; Pop: PROC [] = <> {OPEN que; IF top = 0 THEN ERROR; Gr.Highight[a[1].p.coords, a[1].sy]; FOR i: INTEGER IN [1 .. top) DO Gr.Highight[a[i+1].p.coords, a[i+1].sy]; a[i] _ a[i + 1]; HilyteElem[i] ENDLOOP; top _ top - 1}; RefreshQueue: PROC [] = <> <> {OPEN que; FOR i: INTEGER IN [1..top] DO Gr.Highight[a[i].p.coords, a[i].sy] ENDLOOP}; FlushArgumentQueue: PROC = <> {OPEN que; FOR i: INTEGER IN [1 .. top] DO Gr.Highight[a[i].p.coords, a[i].sy] ENDLOOP; top _ 0}; << - - - - MAIN LOOP>> << The procedures in this section parse the stream of user events (as conditioned by the JunoUserEvents module) and perform the appropriate actions on the current image. >> ev: Evs.Event; -- the user event last returned by Evs.Next Unexpected: SIGNAL = CODE; -- parse/lexer found an event that could't possibly be there EvCoords: PROCEDURE RETURNS [Coords] = INLINE {RETURN[[ev.coords.x, ev.coords.y]]}; << Returns the coordinates of the curent event ev, assuming it is a mouse one (Roll, MouseUp, MouseDown). >> MainLoop: PROC = << Parses and executes each user command. >> BEGIN prevCmd: ATOM _ $Missing; ParseNextCommand: PROC = << Should be called with ev.type = Cursor or Button. Will return with ev.type = End.>> << ABORTED signals are caught here, if they have not been caught by the individual commands. >> BEGIN ENABLE {ABORTED => {GOTO CommandAborted}}; cmd: ATOM = NARROW [ev.value]; << Reset image to a reasonably clean state:>> ClearPoints: Im.PointVisitProc = {p.wn _ 0; p.fixed _ p.frozen; p.mark _ FALSE}; Im.EnumPoints[ClearPoints]; IF prevCmd # cmd THEN {FlushArgumentQueue[]; prevCmd _ cmd}; SELECT cmd FROM $Pencil => {ParseCursorCmdBody [maxPrim: 4, nLeave: 1, Proc: DrawThem]}; $Compass => {ParseCursorCmdBody [maxPrim: 4, nLeave: 2, Proc: CongThem, solve: TRUE]}; $HorTee => {ParseCursorCmdBody [maxPrim: 2, nLeave: 1, Proc: HorThem, solve: TRUE]}; $VerTee => {ParseCursorCmdBody [maxPrim: 2, nLeave: 1, Proc: VerThem, solve: TRUE]}; $Parallels => {ParseCursorCmdBody [maxPrim: 4, nLeave: 2, Proc: ParaThem, solve: TRUE]}; $RightAngle => {ParseCursorCmdBody [maxPrim: 4, nLeave: 2, Proc: PerpThem, solve: TRUE]}; $CallProc => {ParseCursorCmdBody [maxPrim: argMax, Proc: CallProcOnThem, solve: FALSE, refresh: FALSE]}; $MoveArrow => {ParseCursorCmdBody [maxPrim: argMax, baloonSel: TRUE, Proc: MoveThem, solve: TRUE]}; $CopyArrow => {ParseCursorCmdBody [maxPrim: argMax, baloonSel: TRUE, Proc: CopyThem, solve: TRUE]}; $MakeProc => {ParseCursorCmdBody [maxPrim: argMax, baloonSel: TRUE, Proc: MakeProcOfThem, solve: FALSE, refresh: FALSE]}; $Eraser => {ParseCursorCmdBody [maxPrim: argMax, create: FALSE, baloonSel: TRUE, Proc: DeleteThem, solve: TRUE]}; $Snowman => {ParseFreezeCmd []}; $Typewriter => {ParseTypeCmd []}; $Hardcopy => {ParseButtonCmdBody [Proc: DoHardcopy, solve: FALSE, refresh: FALSE]}; $Redraw => {ParseButtonCmdBody [Proc: DoRedraw, solve: FALSE, refresh: TRUE]}; $Solve => {ParseButtonCmdBody [Proc: DoSolve, solve: TRUE]}; $StartOver => {ParseButtonCmdBody [Proc: DoStartOver, solve: FALSE, refresh: TRUE]}; $LoadX => {ParseButtonCmdBody [Proc: DoLoadX, solve: FALSE, refresh: FALSE]}; $Parse => {ParseButtonCmdBody [Proc: DoParse, solve: FALSE, refresh: TRUE]}; $SetFont, $SetFontSize, $SetFontFace, $SetJustification, $SetWidth, $SetColor => {ParseButtonCmdBody [Proc: DoSetOption, solve: FALSE, refresh: FALSE]}; ENDCASE => {ERROR Unexpected}; IF ev.type # End THEN {ERROR Unexpected} -- command didn't get to End EXITS CommandAborted => {Evs.Blink["Command aborted!"]; WHILE ev.type # End DO ev _ Evs.Next[] ENDLOOP; IF Gr.caretOn THEN Gr.SetCaret[on: FALSE]; Refresh[screen: TRUE]} END; << Body of main loop: >> Evs.StartUp[debug: TRUE]; Glob.StartUp[]; DO ev _ Evs.Next[]; IF ev.type = Quit THEN EXIT; ParseNextCommand[! ABORTED => CONTINUE] -- in case ABORTED escapes after all ENDLOOP; Evs.Terminate[]; Glob.Terminate[] END; << - - - - TYPICAL CURSOR COMMANDS>> CursorCommandProc: TYPE = PROC [terminator: ATOM]; << A CursorCommandProc is called by ParseCursorCmdBody to do the actual processing.>> << The arguments to the CursorCommandProc (i.e., the moused points in that segment) will be in the primary argument queue and/or will have wn#0, depending on how they were selected. >> << The CursorCommandProc is called only with terminator = $Escape, $Return, or $Tab. >> ParseCursorCmdBody: PROC [maxPrim, nLeave: INT _ 0, create: BOOL _ TRUE, baloonSel: BOOL _ FALSE, Proc: CursorCommandProc, solve, refresh: BOOL _ FALSE] = << Parses the body of simple commands acording to the syntax End where the are as accepted by ParseMousedPoints.>> << On entry, ev should be Cursor or Button. On exit, it will be End.>> << The nLeave parameter is the number of points to be left on the primary argument queue (eg., nLeave=1 for draw, =2 for cong).>> << If solve = TRUE and the End-ing event is $Escape or $Return, Proc is called, the constraints are solved again, and the image is repainted. Note that the p.fixed and p.frozen bits may be set by Proc to prevent the solver from moving a point p. The p.fixed bit will be reset before the next user command is executed. >> << If solve=FALSE or the End-ing event is $Tab, Proc is called, but the constraints are NOT solved. The image will be repainted (with the current positions of the points) if refresh=TRUE.>> << Finally, if the End-ing event is $Delete or $None, Proc is not called; the argument queues are cleared, and no solving or repainting takes place. >> BEGIN terminator: ATOM; ParseMousedPoints [maxPrim, create, baloonSel]; IF ev.type # End THEN {ERROR Unexpected}; terminator _ NARROW [ev.value]; SELECT terminator FROM $None, $Delete => {FlushArgumentQueue[]}; $Escape, $Tab, $Return => {Proc[terminator]; WHILE que.top > 0 AND que.top # nLeave DO Pop[] ENDLOOP; IF solve AND (terminator = $Escape OR terminator = $Return) THEN {Solve[]; Refresh[screen: TRUE]} ELSE IF refresh THEN {Refresh[screen: TRUE]}}; ENDCASE => {ERROR Unexpected}; END; ParseMousedPoints: PROC [maxPrim: INT _ 0, create: BOOL _ TRUE, baloonSel: BOOL _ FALSE] = << Parses a sequence of mouse clicks that specifies the arguments to a typical command.>> << The parsed sequence consists of zero to maxPrim "primary" points (either new ones created with red clicks, or old ones selected with yellow clicks), plus, if baloonSel=TRUE, any number of "baloon-selected" or "wound" points (old ones selected by circling them with the mouse while the blue button is held down). Multiple blue baloons are cumulative. The two kinds of selections may be freely intermixed and may include the same point(s). The order and multiplicity of the primary arguemnts is important in general.>> << Red clicks (point creation) are allowed only if create=TRUE. All mouse clicks will track the mouse until the button is released.>> << The least recent entries in the primary queue are automatically removed if the length of the latter exceeds maxPrim. Setting maxPrim to zero disables primary selections. >> << Should be called with ev= event before point list (i.e., Cursor or Button). On return, ev is End. Events of type Char, Backspace, Atom, Real, and Rope are flagged and ignored.>> BEGIN DO ev _ Evs.Next[]; SELECT ev.type FROM MouseDown => {SELECT ev.value FROM $Red => {IF create THEN IF maxPrim <= 0 THEN {Evs.Blink ["Primary selections not allowed"]} ELSE {coords: Coords = ParseMousedCoords[]; Push[AddNewPoint [coords, TRUE]]; Evs.bugout.PutF["ParseMousedPoints: new point at [%g, %g]", IO.real[coords.x], IO.real[coords.y]]; -- DEBUG WHILE que.top > maxPrim DO Pop [] ENDLOOP} ELSE {Evs.Blink ["Point creation not allowed"]}}; $Yellow => {IF maxPrim <= 0 THEN {Evs.Blink ["Primary selections not allowed"]} ELSE {p: Point = ParseMouseSelectedPoint[wound: FALSE]; IF p # NIL THEN {Push[p]; Evs.bugout.PutF["ParseMousedPoints: old point at [%g, %g]", IO.real[p.coords.x], IO.real[p.coords.y]]; -- DEBUG WHILE que.top > maxPrim DO Pop[] ENDLOOP}}}; $Blue => {IF baloonSel THEN {TrackBlueBaloon[]; IF NOT Im.AnyWoundPoints[] THEN {Evs.Blink ["Warning - no points selected"]}} ELSE {Evs.Blink ["Baloon selections not allowed"]}}; ENDCASE => {SIGNAL Unexpected}; EatMouseTail}; Char, Atom, Rope, Real, BackSpace => {Evs.Blink ["Invalid input"]}; End => {RETURN}; ENDCASE => {SIGNAL Unexpected}; ENDLOOP; END; << - - - - TYPICAL BUTTON COMMANDS>> ButtonCommandProc: TYPE = PROC [button: ATOM]; << A ButtonCommandProc is called by ParseButtonCmdBody to process the body of a typical Button command.>> << The ButtonCommandProc is called with ev = first button argument (or End if none). It should return with ev=End.>> ParseButtonCmdBody: PROC [Proc: ButtonCommandProc, solve, refresh: BOOL _ FALSE] = << Parses the body of button commands, that obeys the syntax