<<>> <> <> <> DIRECTORY Atom, BiScrollers, Commander, Complex, Convert, Geom2D, SampledCurveEdit, InputFocus, FS, Imager, ImagerPath, ImagerBackdoor, IO, Menus, MessageWindow, Process, Real, Rope, RuntimeError, TIPUser, Vector2, ViewerClasses, ViewerOps, ViewerTools ; SampledCurveEditImpl: CEDAR MONITOR IMPORTS Atom, BiScrollers, Commander, Complex, Convert, Geom2D, InputFocus, FS, Imager, ImagerBackdoor, IO, Menus, MessageWindow, Process, Real, Rope, RuntimeError, TIPUser, ViewerOps, ViewerTools EXPORTS SampledCurveEdit ~ BEGIN OPEN SampledCurveEdit; header: MarkedPoint ~ [2, 2, TRUE, open]; ROPE: TYPE ~ Rope.ROPE; VEC: TYPE ~ Vector2.VEC; COMPLEX: TYPE ~ Complex.VEC; Tool: TYPE = REF ToolRec; ToolRec: TYPE = RECORD [ actionQueue: LIST OF REF, lockProcess: UNSAFE PROCESS ¬ NIL, lockCount: CARDINAL ¬ 0, lockFree: CONDITION, outline: LIST OF Trajectory, <> grabbed: LIST OF MarkedPoint, <> grabbedCount: NAT, selected: LIST OF MarkedPoint ¬ NIL, <> selectedCount: NAT ¬ 0, undoList: LIST OF MarkedPoint ¬ NIL, dragSource: MarkedPoint ¬ header, moveSource: MarkedPoint ¬ header, paintRectangles: LIST OF PaintRectangle ]; CheckConsistency: PROC [tool: Tool] ~ { grabbedAppeared, selectedAppeared: BOOL ¬ FALSE; grabbedLeftover, selectedLeftover: INT ¬ 0; end: LIST OF MarkedPoint ¬ NIL; IF tool.outline # NIL THEN { stopper: NAT ¬ NAT.LAST; FOR p: LIST OF MarkedPoint ¬ tool.outline.first, p.rest UNTIL p = NIL DO IF p = tool.selected THEN { IF selectedAppeared THEN ERROR; selectedAppeared ¬ TRUE; selectedLeftover ¬ tool.selectedCount + 1; }; IF p = tool.grabbed THEN { IF grabbedAppeared THEN ERROR; grabbedAppeared ¬ TRUE; grabbedLeftover ¬ tool.grabbedCount + 1; }; selectedLeftover ¬ MAX[selectedLeftover - 1, 0]; grabbedLeftover ¬ MAX[grabbedLeftover - 1, 0]; stopper ¬ stopper - 1; end ¬ p; ENDLOOP; IF tool.outline.first.first.kind # open THEN { IF tool.outline.first.first.x # end.first.x THEN ERROR; IF tool.outline.first.first.y # end.first.y THEN ERROR; }; }; IF tool.selected # NIL AND NOT selectedAppeared THEN ERROR; IF tool.grabbed # NIL AND NOT grabbedAppeared THEN ERROR; IF selectedLeftover # 0 THEN ERROR; IF grabbedLeftover # 0 THEN ERROR; }; PaintRectangle: TYPE ~ RECORD [ xMin, yMin, xMax, yMax: REAL ]; NewData: PROC RETURNS [REF] ~ { tool: Tool ¬ NEW[ToolRec]; tool.outline ¬ LIST[LIST[header]]; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; RETURN [tool]; }; InitViewer: PROC [self: Viewer] = { IF self.file # NIL THEN { LoadFile[self]; }; }; LoadFile: PROC [self: Viewer] = { Err: PROC [msg: ROPE, pos: INT ¬ -1] ~ { MessageWindow.Append[msg, TRUE]; IF pos>=0 THEN MessageWindow.Append[Convert.RopeFromInt[pos], FALSE]; MessageWindow.Blink[]; }; stream: IO.STREAM ¬ FS.StreamOpen[self.file, $read ! FS.Error => {MessageWindow.Append[error.explanation, TRUE]; MessageWindow.Blink[]; GOTO Quit}]; outline: Outline ¬ NIL; stack: ARRAY [0..10) OF REAL; stackTop: INT ¬ 0; kind: PointKind ¬ sample; PushReal: PROC [real: REAL] ~ { IF stackTop >= 10 THEN {Err["SampledCurveEdit stack overflow at ", stream.GetIndex]; RETURN}; stack[stackTop] ¬ real; stackTop ¬ stackTop + 1; }; PopReal: PROC RETURNS [real: REAL] ~ { IF stackTop <= 0 THEN {Err["SampledCurveEdit stack underflow at ", stream.GetIndex]; RETURN [0]}; stackTop ¬ stackTop - 1; real ¬ stack[stackTop]; }; curTrajEnd: Trajectory ¬ NIL; DO r: REF ANY ¬ NIL; r ¬ IO.GetRefAny[stream ! IO.EndOfStream => CONTINUE]; IF r = NIL THEN EXIT; WITH r SELECT FROM refInt: REF INT => PushReal[refInt­]; refCard: REF LONG CARDINAL => PushReal[refCard­]; refReal: REF REAL => PushReal[refReal­]; atom: ATOM => { SELECT atom FROM $MOVETO => { y: REAL ¬ PopReal[]; x: REAL ¬ PopReal[]; outline ¬ CONS[LIST[[x: 0, y: 0, isHeader: TRUE, kind: open], [x: x, y: y, isHeader: FALSE, kind: sample]], outline]; curTrajEnd ¬ outline.first.rest; kind ¬ sample; }; $LINETO => { y: REAL ¬ PopReal[]; x: REAL ¬ PopReal[]; IF curTrajEnd = NIL THEN {Err["SampledCurveEdit Missing MOVETO", stream.GetIndex]; EXIT}; curTrajEnd.rest ¬ LIST[[x: x, y: y, isHeader: FALSE, kind: kind]]; curTrajEnd ¬ curTrajEnd.rest; kind ¬ sample; }; $CORNER => { kind ¬ corner; }; $KNOT => { kind ¬ knot; }; $CLOSE => { y: REAL ¬ curTrajEnd.first.y; x: REAL ¬ curTrajEnd.first.x; outline.first.first.x ¬ x; outline.first.first.y ¬ y; outline.first.first.kind ¬ sample; curTrajEnd ¬ NIL; }; ENDCASE => {Err["SampledCurveEdit parse error at position ", stream.GetIndex]; EXIT}; }; ENDCASE => {Err["SampledCurveEdit parse error at position ", stream.GetIndex]; EXIT}; ENDLOOP; IF stackTop # 0 THEN Err["SampledCurveEdit stack not empty at exit"]; outline ¬ ReverseOutline[outline]; SetOutline[self, outline]; IO.Close[stream]; EXITS Quit => NULL; }; ReverseOutline: PROC [outline: LIST OF Trajectory] RETURNS [reversed: LIST OF Trajectory ¬ NIL] ~ { WHILE outline # NIL DO t: LIST OF Trajectory ¬ outline; outline ¬ outline.rest; t.rest ¬ reversed; reversed ¬ t; ENDLOOP; }; DoWithLock: PROC [viewer: Viewer, inner: PROC [tool: Tool]] ~ { DoWithLockedData[BiScrollers.ClientDataOfViewer[viewer], inner]; }; DoWithLockedData: PROC [data: REF, inner: PROC [tool: Tool]] ~ { WITH data SELECT FROM tool: Tool => { Lock: ENTRY PROC ~ { UNTIL tool.lockProcess = NIL OR tool.lockProcess = Process.GetCurrent[] DO WAIT tool.lockFree ENDLOOP; tool.lockProcess ¬ Process.GetCurrent[]; tool.lockCount ¬ tool.lockCount + 1; }; UnLock: ENTRY PROC ~ { IF (tool.lockCount ¬ tool.lockCount - 1) = 0 THEN { tool.lockProcess ¬ NIL; NOTIFY tool.lockFree; }; }; Lock[]; inner[tool ! UNWIND => UnLock[]]; UnLock[]; }; ENDCASE => NULL; }; keep: NAT ¬ 2; SaveProc: PROC [self: Viewer, force: BOOL ¬ FALSE] = { Locked: PROC [tool: Tool] ~ { stream: IO.STREAM ¬ FS.StreamOpen[fileName: self.file, accessOptions: $create, keep: keep ! FS.Error => {MessageWindow.Append[error.explanation, TRUE]; MessageWindow.Blink[]; GOTO Quit}]; PutPoint: PROC [pt: MarkedPoint] ~ { stream.PutF["%g %g ", IO.real[pt.x], IO.real[pt.y]]; SELECT pt.kind FROM corner => stream.PutRope["CORNER "]; knot => stream.PutRope["KNOT "]; ENDCASE => NULL; }; outline: Outline ¬ NIL; WITH BiScrollers.ClientDataOfViewer[self] SELECT FROM tool: Tool => {outline ¬ tool.outline}; ENDCASE => NULL; WHILE outline # NIL DO traj: Trajectory ¬ outline.first; IF traj.rest # NIL THEN { stream.PutRope[" \n"]; PutPoint[traj.rest.first]; stream.PutRope["MOVETO\n"]; FOR p: PointList ¬ traj.rest.rest, p.rest UNTIL p=NIL DO stream.PutRope[" "]; PutPoint[p.first]; stream.PutRope["LINETO\n"]; ENDLOOP; IF traj.first.kind # open THEN { stream.PutRope[" CLOSE\n"]; }; }; outline ¬ outline.rest; ENDLOOP; self.name ¬ Rope.Concat["Sampled Curve Editor ", self.file]; self.label ¬ self.file; stream.Close; EXITS Quit => NULL; }; IF self.file # NIL THEN DoWithLock[self, Locked]; }; PaintPoint: PROCEDURE [context: Imager.Context, pt: MarkedPoint] = { x: REAL ~ pt.x; y: REAL ~ pt.y; SELECT pt.kind FROM corner => { Imager.SetColor[context, Imager.black]; Imager.MaskBox[context, [x-2, y-3, x+2, y+3]]; Imager.MaskBox[context, [x-3, y-2, x+3, y+2]]; Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [x-1, y-2, x+1, y+2]]; Imager.MaskBox[context, [x-2, y-1, x+2, y+1]]; }; sample, knot => { Imager.SetColor[context, Imager.black]; Imager.MaskBox[context, [x-2, y-2, x+2, y+2]]; Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [x-1, y-1, x+1, y+1]]; }; ENDCASE => NULL; }; FetchPt: PROC [l: LIST OF MarkedPoint, i: INT] RETURNS [MarkedPoint] ~ { WHILE i > 0 DO l ¬ l.rest; i ¬ i-1 ENDLOOP; RETURN [l.first] }; BreakCurrentTrajectory: PROCEDURE [tool: Tool] = { <> IF tool.selected # NIL AND NOT tool.selected.first.isHeader THEN { IF tool.outline.first.first.kind = open THEN { new: LIST OF MarkedPoint ¬ CONS[tool.outline.first.first, tool.selected.rest]; tool.selected.rest ¬ NIL; tool.selected ¬ new; tool.outline ¬ CONS[new, tool.outline]; } ELSE { new: LIST OF MarkedPoint ¬ tool.outline.first; newTail: LIST OF MarkedPoint ¬ new.rest; p: LIST OF MarkedPoint ¬ tool.selected.rest; new.rest ¬ p; tool.selected.rest ¬ NIL; UNTIL p.rest = NIL DO p ¬ p.rest ENDLOOP; p.rest ¬ newTail; new.first.x ¬ 0; new.first.y ¬ 0; new.first.kind ¬ open; tool.selected ¬ new; }; }; }; DrawOutline: PROCEDURE [tool: Tool, context: Imager.Context] = { Path: ImagerPath.PathProc ~ { FOR t: LIST OF Trajectory ¬ tool.outline, t.rest UNTIL t=NIL DO IF t.first.rest # NIL THEN { start: MarkedPoint ¬ t.first.first; IF start.kind = open THEN start ¬ t.first.rest.first; moveTo[[start.x, start.y]]; FOR p: LIST OF MarkedPoint ¬ t.first.rest.rest, p.rest UNTIL p=NIL DO lineTo[[p.first.x, p.first.y]]; ENDLOOP; }; ENDLOOP; }; Imager.SetColor[context, Imager.black]; Imager.SetStrokeWidth[context, 0]; Imager.SetStrokeEnd[context, $round]; Imager.SetStrokeJoint[context, $round]; Imager.MaskStroke[context: context, path: Path, closed: FALSE]; IF tool.outline # NIL THEN { FOR p: LIST OF MarkedPoint ¬ tool.outline.first.rest, p.rest UNTIL p=NIL DO PaintPoint[context, p.first]; ENDLOOP; }; }; InvertSelection: PROCEDURE [viewer: Viewer, context: Imager.Context] = { tool: Tool ¬ NARROW[BiScrollers.ClientDataOfViewer[viewer]]; [] ¬ Imager.SetColor[context, ImagerBackdoor.invert]; IF tool.selected # NIL THEN { list: LIST OF MarkedPoint ¬ tool.selected.rest; FOR i: NAT IN [0..tool.selectedCount) DO p: MarkedPoint ¬ list.first; Imager.MaskBox[context, [p.x-4, p.y-4, p.x+4, p.y+4]]; list ¬ list.rest; IF list = NIL THEN list ¬ tool.outline.first.rest; ENDLOOP; }; }; MagSqr: PROCEDURE [x, y: REAL] RETURNS [REAL] = {RETURN[x*x+y*y]}; SelectNewTrajectory: PROCEDURE [tool: Tool, v: MarkedPoint] RETURNS [changed: BOOLEAN] ~ { d: REAL ¬ 99999999; last: LIST OF Trajectory ¬ NIL; new: LIST OF Trajectory ¬ NIL; IF tool.outline = NIL THEN RETURN [FALSE]; FOR t: LIST OF Trajectory ¬ tool.outline, t.rest UNTIL t=NIL DO FOR p: LIST OF MarkedPoint ¬ t.first, p.rest UNTIL p.rest=NIL DO markedPoint: MarkedPoint ¬ p.rest.first; s: REAL ¬ MagSqr[markedPoint.x-v.x, markedPoint.y-v.y]; IF s < d THEN {new ¬ t; d ¬ s}; ENDLOOP; last ¬ t; ENDLOOP; IF new = tool.outline THEN RETURN [FALSE]; last.rest ¬ tool.outline; DO IF tool.outline.rest = new THEN {tool.outline.rest ¬ NIL; tool.outline ¬ new; RETURN [TRUE]}; tool.outline ¬ tool.outline.rest; ENDLOOP; }; GrabPoint: PROCEDURE [tool: Tool, v: MarkedPoint] = { d: REAL ¬ 99999999; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; IF tool.outline # NIL THEN { FOR p: LIST OF MarkedPoint ¬ tool.outline.first, p.rest UNTIL p.rest=NIL DO markedPoint: MarkedPoint ¬ p.rest.first; s: REAL ¬ MagSqr[markedPoint.x-v.x, markedPoint.y-v.y]; IF s < d THEN {tool.grabbed ¬ p; tool.grabbedCount ¬ 1; d ¬ s}; ENDLOOP; }; }; metersPerPixel: REAL ~ 3.527777e-4; pixelsPerMeter: REAL ~ 1.0/metersPerPixel; PaintProc: ViewerClasses.PaintProc <> = { tool: Tool ¬ NARROW[BiScrollers.ClientDataOfViewer[self]]; SELECT whatChanged FROM NIL => Notify[self, LIST[$Refresh]]; $PaintAll => { Imager.SetColor[context, Imager.white]; Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]]; DrawOutline[tool, context]; InvertSelection[self, context]; }; $TouchUp => { Imager.SetColor[context, Imager.white]; FOR paintList: LIST OF PaintRectangle ¬ tool.paintRectangles, paintList.rest UNTIL paintList = NIL DO box: PaintRectangle ~ paintList.first; Imager.MaskBox[context, [box.xMin, box.yMin, box.xMax, box.yMax]]; ENDLOOP; Imager.SetColor[context, Imager.black]; FOR paintList: LIST OF PaintRectangle ¬ tool.paintRectangles, paintList.rest UNTIL paintList = NIL DO box: PaintRectangle ~ paintList.first; Proc: PROC ~ { Imager.ClipRectangle[context, [box.xMin, box.yMin, box.xMax-box.xMin, box.yMax-box.yMin]]; DrawOutline[tool, context]; InvertSelection[self, context]; }; Imager.DoSaveAll[context, Proc]; ENDLOOP; }; $NewPt => { p: MarkedPoint ¬ tool.outline.first.rest.first; Imager.SetColor[context, Imager.black]; IF tool.outline.first.rest.rest # NIL THEN { q: MarkedPoint ¬ tool.outline.first.rest.rest.first; Imager.SetStrokeWidth[context, 0]; Imager.MaskVector[context, [q.x, q.y], [p.x, p.y]]; PaintPoint[context, q]; }; PaintPoint[context, p]; }; $InvertGrabbed => IF tool.grabbed # NIL THEN { prevKind: PointKind ¬ open; list: LIST OF MarkedPoint; Imager.SetColor[context, ImagerBackdoor.invert]; list ¬ tool.grabbed.rest; FOR i: NAT IN [0..tool.grabbedCount) WHILE list # NIL DO p: MarkedPoint ¬ list.first; Imager.MaskBox[context, [p.x-2, p.y-2, p.x+2, p.y+2]]; list ¬ list.rest; ENDLOOP; { Path: ImagerPath.PathProc ~ { moveTo[[tool.grabbed.first.x, tool.grabbed.first.y]]; prevKind ¬ tool.grabbed.first.kind; list ¬ tool.grabbed.rest; FOR i: NAT IN [0..tool.grabbedCount+1) DO p: MarkedPoint ¬ list.first; IF prevKind = open OR p.kind = open THEN moveTo[[p.x, p.y]] ELSE lineTo[[p.x, p.y]]; prevKind ¬ p.kind; list ¬ list.rest; IF list = NIL THEN list ¬ tool.outline.first.rest; ENDLOOP; }; Imager.SetStrokeWidth[context, 0]; Imager.MaskStroke[context, Path]; }; }; $EraseGrabbedPoint => IF tool.grabbed # NIL THEN { list: LIST OF MarkedPoint ¬ tool.grabbed.rest; Imager.SetColor[context, Imager.white]; FOR i: NAT IN [0..tool.grabbedCount) WHILE list # NIL DO p: MarkedPoint ¬ list.first; Imager.MaskBox[context, [p.x-2, p.y-2, p.x+2, p.y+2]]; list ¬ list.rest; ENDLOOP; }; $InvertSel => InvertSelection[self, context]; ENDCASE => ERROR; }; ActionWithPoint: TYPE = REF ActionWithPointRep; ActionWithPointRep: TYPE = RECORD [ atom: ATOM, markedPoint: MarkedPoint ]; MalformedTrajectory: PUBLIC ERROR ~ CODE; CopyTrajectory: PUBLIC PROC [trajectory: Trajectory] RETURNS [Trajectory] ~ { IF trajectory = NIL THEN ERROR MalformedTrajectory ELSE { new: Trajectory ~ LIST[trajectory.first]; end: Trajectory ¬ new; markedPoint: MarkedPoint ¬ trajectory.first; IF NOT new.first.isHeader THEN ERROR MalformedTrajectory; WHILE (trajectory ¬ trajectory.rest) # NIL DO markedPoint ¬ trajectory.first; IF markedPoint.isHeader THEN ERROR MalformedTrajectory; end.rest ¬ LIST[markedPoint]; end ¬ end.rest; ENDLOOP; IF new.first.kind # open THEN { headPoint: MarkedPoint ¬ new.first; headPoint.isHeader ¬ markedPoint.isHeader; IF headPoint # markedPoint THEN ERROR MalformedTrajectory; }; RETURN [new]; }; }; TrajectoryFromPoints: PROC [list: LIST OF MarkedPoint, count: INT] RETURNS [Trajectory] ~ { new: Trajectory ~ LIST[header]; end: Trajectory ¬ new; FOR i: INT IN [0..count) WHILE list # NIL DO markedPoint: MarkedPoint ¬ list.first; end.rest ¬ LIST[markedPoint]; end ¬ end.rest; list ¬ list.rest; ENDLOOP; RETURN [new]; }; CopyOutline: PUBLIC PROC [outline: Outline] RETURNS [Outline] ~ { new: Outline ~ LIST[NIL]; end: Outline ¬ new; WHILE outline # NIL DO traj: Trajectory ¬ CopyTrajectory[outline.first]; end.rest ¬ LIST[traj]; end ¬ end.rest; outline ¬ outline.rest; ENDLOOP; RETURN [new.rest]; }; GetOutline: PUBLIC PROC [viewer: Viewer] RETURNS [outline: Outline ¬ NIL] ~ { Locked: PROC [tool: Tool] ~ {outline ¬ CopyOutline[tool.outline]}; DoWithLock[viewer, Locked]; }; ObtainOutline: PUBLIC PROC [viewer: Viewer] RETURNS [outline: Outline] ~ { Locked: PROC [tool: Tool] ~ { outline ¬ tool.outline; outline ¬ tool.outline; tool.outline ¬ NIL; tool.grabbed ¬ tool.selected ¬ NIL; tool.grabbedCount ¬ tool.selectedCount ¬ 0; tool.actionQueue ¬ LIST[NIL, $PaintAll]; TRUSTED {Process.Detach[FORK DispatchProcess[viewer]]}; }; DoWithLock[viewer, Locked]; }; SetOutline: PUBLIC PROC [viewer: Viewer, outline: Outline] ~ { viewer.class.notify[viewer, LIST[outline]]; }; Notify: ENTRY ViewerClasses.NotifyProc = { ENABLE {UNWIND => NULL; RuntimeError.UNCAUGHT => {MessageWindow.Append["SampledCurveEdit UNCAUGHT ERROR in Notify", TRUE]; MessageWindow.Blink[]; GOTO Quit}}; tool: Tool ¬ NARROW[BiScrollers.ClientDataOfViewer[self]]; viewer: Viewer ~ self; Unknown: PROC ~ { stream: IO.STREAM ¬ IO.ROS[]; stream.PutRope["Unknown input: "]; stream.Put1[IO.refAny[input]]; MessageWindow.Append[IO.RopeFromROS[stream], TRUE]; stream.Close[]; }; ActionKind: TYPE ~ {Ordinary, AutoInverse, Idempotent}; Queue: PROC [action: REF, kind: ActionKind ¬ $Ordinary, markedPoint: MarkedPoint ¬ header] ~ { LastAction: PROC RETURNS [REF] ~ { RETURN [WITH ultimate.first SELECT FROM actionWithPoint: ActionWithPoint => actionWithPoint.atom, ENDCASE => ultimate.first ] }; penultimate: LIST OF REF ¬ NIL; ultimate: LIST OF REF ¬ tool.actionQueue; IF ultimate = NIL THEN TRUSTED { ultimate ¬ tool.actionQueue ¬ LIST[NIL]; <> Process.Detach[FORK DispatchProcess[viewer]]; }; FOR q: LIST OF REF ¬ tool.actionQueue, q.rest UNTIL q = NIL DO penultimate ¬ ultimate; ultimate ¬ q; ENDLOOP; SELECT kind FROM $Ordinary => NULL; $AutoInverse => IF LastAction[] = action THEN {penultimate.rest ¬ NIL; RETURN}; $Idempotent => IF LastAction[] = action THEN { WITH ultimate.first SELECT FROM actionWithPoint: ActionWithPoint => actionWithPoint.markedPoint ¬ markedPoint; ENDCASE => NULL; RETURN }; ENDCASE => ERROR; ultimate.rest ¬ LIST[ IF markedPoint = header THEN action ELSE NEW[ActionWithPointRep ¬ [NARROW[action], markedPoint]] ]; }; QueuePaintAll: PROC ~ { q: LIST OF REF ¬ tool.actionQueue; IF q # NIL THEN { WHILE q.rest#NIL DO SELECT q.rest.first FROM $InvertSel, $InvertGrabbed, $TouchUp, $EraseGrabbedPoint, $PaintAll => q.rest ¬ q.rest.rest; ENDCASE => q ¬ q.rest; ENDLOOP; }; Queue[$PaintAll]; }; QueueTouchUp: PROC ~ { q: LIST OF REF ¬ tool.actionQueue; deferred: LIST OF REF ¬ NIL; IF q # NIL THEN { WHILE q.rest#NIL DO SELECT q.rest.first FROM $TouchUp => { deferred ¬ q.rest; q.rest ¬ q.rest.rest; deferred.rest ¬ NIL; }; $InvertGrabbed, $EraseGrabbedPoint => { IF deferred # NIL THEN { deferred.rest ¬ q.rest; q.rest ¬ deferred; deferred ¬ NIL; q ¬ q.rest; }; q ¬ q.rest; }; ENDCASE => q ¬ q.rest; ENDLOOP; IF deferred # NIL THEN {q.rest ¬ deferred; RETURN}; }; Queue[$TouchUp]; }; WITH input.first SELECT FROM atom: ATOM => { SELECT atom FROM $Clear => {Queue[$Clear]; QueuePaintAll[]}; $Delete => { Queue[$DeleteSelected]; <> QueueTouchUp[]; }; $Copy => { Queue[$InvertSel, $AutoInverse]; Queue[$Copy]; Queue[$InvertSel, $AutoInverse]; }; $Refresh => QueuePaintAll[]; $Reverse => { Queue[$InvertSel, $AutoInverse]; Queue[$ClearSel, $Idempotent]; Queue[$ReverseTrajectory, $AutoInverse]; }; $Save => { Queue[$Save, $Idempotent]; }; $Store => { selection: ROPE ¬ ViewerTools.GetSelectionContents[]; wd: ROPE ¬ NIL; fullFName: ROPE ¬ NIL; cp: FS.ComponentPositions; [wd, cp] ¬ FS.ExpandName[self.file ! FS.Error => CONTINUE]; IF wd # NIL THEN wd ¬ Rope.Substr[wd, 0, cp.base.start]; [fullFName, cp] ¬ FS.ExpandName[selection, wd ! FS.Error => {MessageWindow.Append[error.explanation, TRUE]; MessageWindow.Blink[]; CONTINUE}]; IF fullFName # NIL THEN {self.file ¬ fullFName; Queue[$Save]}; }; $Undo => { Queue[$Undo]; <> QueueTouchUp[]; }; $Break => { Queue[$Break]; QueueTouchUp[]; }; ENDCASE => { WITH Atom.GetProp[atom, registrationKey] SELECT FROM pointModifier: PointModifier => { Queue[pointModifier]; QueueTouchUp[]; }; ENDCASE => Unknown[]; }; }; outline: Outline => { Queue[$ClearSel, $Idempotent]; Queue[outline]; QueuePaintAll[]; }; mousePlace: REF VEC => { markedPoint: MarkedPoint ¬ [mousePlace.x, mousePlace.y, FALSE, sample]; IF InputFocus.GetInputFocus[].owner # viewer THEN InputFocus.SetInputFocus[viewer]; SELECT input.rest.first FROM $AddPt => { Queue[$InvertSel, $AutoInverse]; Queue[$AddPt, $Ordinary, markedPoint]; Queue[$NewPt]; Queue[$InvertSel, $AutoInverse]; }; $DeletePt => { Queue[$InvertSel, $AutoInverse]; Queue[$GrabPt]; Queue[$DeleteGrabbed]; <> Queue[$InvertSel, $AutoInverse]; QueueTouchUp[]; }; $ExtendSelection => { Queue[$InvertSel, $AutoInverse]; Queue[$ExtendSel, $Idempotent, markedPoint]; Queue[$InvertSel, $AutoInverse]; }; $GrabPt => { Queue[$InvertSel, $AutoInverse]; Queue[$GrabPt, $Ordinary, markedPoint]; Queue[$InvertGrabbed]; Queue[$EraseGrabbedPoint]; Queue[$RecordGrabbedBox]; Queue[$MoveTo, $Idempotent, markedPoint]; Queue[$InvertGrabbed, $AutoInverse]; Queue[$InvertSel, $AutoInverse]; }; $MovePt => { Queue[$InvertSel, $AutoInverse]; Queue[$InvertGrabbed, $AutoInverse]; Queue[$MoveTo, $Idempotent, markedPoint]; Queue[$InvertGrabbed, $AutoInverse]; Queue[$InvertSel, $AutoInverse]; }; $ReleasePt => { Queue[$RecordGrabbedBox]; Queue[$ClearGrabbed]; QueueTouchUp[]; }; $StartMove => { Queue[$StartMove, $Ordinary, markedPoint]; }; $Move => { Queue[$Move, $Idempotent, markedPoint]; QueueTouchUp[]; }; $Bulge => { Queue[$Bulge, $Idempotent, markedPoint]; QueueTouchUp[]; }; $SelectPt => { Queue[$InvertSel, $AutoInverse]; Queue[$SelectPt, $Idempotent, markedPoint]; Queue[$InvertSel, $AutoInverse]; }; $SelectTrajectory => { Queue[$RecordSelectionBox]; Queue[$ClearSel, $Idempotent]; Queue[$SelectTrajectory, $Idempotent, markedPoint]; <> QueueTouchUp[]; }; $ShowPt => { s: IO.STREAM ¬ IO.ROS[]; s.PutF["%7.1g %7.1g ", IO.real[markedPoint.x], IO.real[markedPoint.y]]; MessageWindow.Append[s.RopeFromROS, TRUE]; }; $StartPt => { Queue[$InvertSel, $AutoInverse]; Queue[$ClearSel, $Idempotent]; Queue[$RecordTrajectoryBox]; Queue[$StartPt, $Ordinary, markedPoint]; QueueTouchUp[]; Queue[$NewPt]; }; ENDCASE => Unknown[]; }; ENDCASE => Unknown[]; EXITS Quit => NULL; }; Dequeue: ENTRY PROC [tool: Tool] RETURNS [ref: REF] ~ { ENABLE UNWIND => NULL; IF tool.actionQueue.rest = NIL THEN { tool.actionQueue ¬ NIL; <> ref ¬ NIL; <> } ELSE { ref ¬ tool.actionQueue.rest.first; tool.actionQueue.rest ¬ tool.actionQueue.rest.rest; }; }; RegisterPointModifer: PUBLIC PROC [atom: ATOM, pointModifier: PointModifier] ~ { Atom.PutProp[atom, registrationKey, pointModifier]; }; RootViewer: PROC [viewer: Viewer] RETURNS [Viewer] ~ { WHILE viewer # NIL AND viewer.parent # NIL DO viewer ¬ viewer.parent ENDLOOP; RETURN [viewer]; }; DispatchProcess: PROC [viewer: Viewer] ~ { dispatch: PROC [tool: Tool] ~ { root: Viewer ~ RootViewer[viewer]; ref: REF; box: PaintRectangle ¬ [xMin: 999999, yMin: 999999, xMax: -999999, yMax: -999999]; ResetBBox: PROC ~ {box ¬ [xMin: 999999, yMin: 999999, xMax: -999999, yMax: -999999]}; slop: REAL ~ 5; BBPoint: PROC [markedPoint: MarkedPoint] ~ { box.xMin ¬ MIN[box.xMin, markedPoint.x-slop]; box.xMax ¬ MAX[box.xMax, markedPoint.x+slop]; box.yMin ¬ MIN[box.yMin, markedPoint.y-slop]; box.yMax ¬ MAX[box.yMax, markedPoint.y+slop]; }; RecordBBox: PROC ~ { IF box.xMin>box.xMax THEN RETURN; FOR paintList: LIST OF PaintRectangle ¬ tool.paintRectangles, paintList.rest UNTIL paintList = NIL DO old: PaintRectangle ~ paintList.first; IF old.xMin<=box.xMin AND old.yMin<=box.yMin AND old.xMax>=box.xMax AND old.yMax>=box.yMax THEN { ResetBBox[]; RETURN; }; IF box.xMin<=old.xMin AND box.yMin<=old.yMin AND box.xMax>=old.xMax AND box.yMax>=old.yMax THEN { paintList.first ¬ box; ResetBBox[]; RETURN; }; ENDLOOP; tool.paintRectangles ¬ CONS[box, tool.paintRectangles]; }; BoundPoints: PROC [list: LIST OF MarkedPoint, count: NAT] ~ { FOR i: NAT IN [0..count) WHILE list # NIL DO IF list.first.kind # open THEN BBPoint[list.first]; list ¬ list.rest; ENDLOOP; }; BoundSelected: PROC ~ {BoundPoints[tool.selected, tool.selectedCount+2]}; RecordPoints: PROC [list: LIST OF MarkedPoint, count: NAT] ~ { BoundPoints[list, count]; RecordBBox[]; }; WHILE (ref ¬ Dequeue[tool]) # NIL DO action: ATOM ¬ $NothingMoreToDo; markedPoint: MarkedPoint; WITH ref SELECT FROM atom: ATOM => action ¬ atom; actionWithPoint: ActionWithPoint => { action ¬ actionWithPoint.atom; markedPoint ¬ actionWithPoint.markedPoint; }; pointModifier: PointModifier => { IF tool.selected # NIL THEN { changed: BOOL; newCount: INT; BoundPoints[tool.selected, tool.selectedCount+2]; [changed, newCount] ¬ pointModifier.pointModifyProc[pointModifier, tool.selected, tool.selectedCount]; IF changed THEN { RecordPoints[tool.selected, tool.selectedCount+2]; IF newCount >= 0 THEN tool.selectedCount ¬ newCount; ViewerOps.SetNewVersion[root] } ELSE ResetBBox[]; }; }; outline: Outline => { tool.outline ¬ outline; }; ENDCASE => ERROR; SELECT action FROM $NothingMoreToDo => NULL; $Save => { [] ¬ ViewerOps.SaveViewer[root]; }; $StartPt => { tool.selected ¬ NIL; tool.selectedCount ¬ 0; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; tool.outline ¬ CONS[LIST[header, markedPoint], tool.outline]; ViewerOps.SetNewVersion[root]; }; $SelectTrajectory => { BoundPoints[tool.outline.first, LAST[NAT]]; IF SelectNewTrajectory[tool, markedPoint] THEN RecordPoints[tool.outline.first, LAST[NAT]] ELSE ResetBBox[]; }; $AddPt => { IF tool.outline.first.rest # NIL AND tool.outline.rest.rest # NIL THEN { tool.outline.first.rest.first.kind ¬ sample; }; tool.outline.first.rest ¬ CONS[markedPoint, tool.outline.first.rest]; ViewerOps.SetNewVersion[root]; }; $Clear => { tool.outline.first.rest ¬ tool.grabbed ¬ tool.selected ¬ NIL; tool.outline.rest ¬ NIL; tool.grabbedCount ¬ tool.selectedCount ¬ 0; ViewerOps.SetNewVersion[root]; }; $Copy => { IF tool.outline # NIL AND tool.selected # NIL AND tool.selectedCount > 0 THEN { tool.outline.rest ¬ CONS[TrajectoryFromPoints[tool.selected.rest, tool.selectedCount], tool.outline.rest]; }; ViewerOps.SetNewVersion[root]; }; $ClearGrabbed => { tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; }; $ClearSel => { tool.selected ¬ NIL; tool.selectedCount ¬ 0; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; tool.undoList ¬ NIL; }; $DeleteGrabbed => { IF tool.outline.first.rest # NIL AND tool.grabbed # NIL THEN { BBPoint[tool.grabbed.first]; BBPoint[tool.grabbed.rest.first]; IF tool.grabbed.rest.rest # NIL THEN BBPoint[tool.grabbed.rest.rest.first]; RecordBBox[]; markedPoint ¬ tool.grabbed.rest.first; tool.grabbed.rest ¬ tool.grabbed.rest.rest; }; tool.grabbed ¬ tool.selected ¬ NIL; tool.grabbedCount ¬ tool.selectedCount ¬ 0; ViewerOps.SetNewVersion[root]; }; $DeleteSelected => { SaveForUndo[tool]; IF tool.selected # NIL THEN { RecordPoints[tool.selected, tool.selectedCount+2]; THROUGH [0..tool.selectedCount) WHILE tool.selected.rest # NIL DO tool.selected.rest ¬ tool.selected.rest.rest; ENDLOOP; }; tool.selected ¬ NIL; tool.selectedCount ¬ 0; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; tool.undoList ¬ NIL; ViewerOps.SetNewVersion[root]; }; $Break => { IF tool.selected # NIL AND tool.selected.rest # NIL THEN { BBPoint[tool.selected.first]; BBPoint[tool.selected.rest.first]; BreakCurrentTrajectory[tool]; RecordBBox[]; }; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; tool.undoList ¬ NIL; ViewerOps.SetNewVersion[root]; }; $ExtendSel => IF tool.selected # NIL THEN { new: LIST OF MarkedPoint ¬ NIL; count: INT ¬ 0; grabbedToGo: INT ¬ LAST[INT]; selectedToGo: INT ¬ LAST[INT]; GrabPoint[tool, markedPoint]; FOR list: LIST OF MarkedPoint ¬ tool.outline.first, list.rest UNTIL list = NIL OR (grabbedToGo<=0 AND selectedToGo<=0) DO IF list = tool.grabbed THEN { IF new = NIL THEN new ¬ tool.grabbed; grabbedToGo ¬ tool.grabbedCount; }; IF list = tool.selected THEN { IF new = NIL THEN new ¬ tool.selected; selectedToGo ¬ tool.selectedCount; }; IF new # NIL THEN count ¬ count + 1; grabbedToGo ¬ grabbedToGo - 1; selectedToGo ¬ selectedToGo - 1; ENDLOOP; tool.selected ¬ new; tool.selectedCount ¬ count; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; tool.undoList ¬ NIL; }; $GrabPt => GrabPoint[tool, markedPoint]; $MoveTo => IF tool.outline # NIL THEN { head: LIST OF MarkedPoint ¬ tool.outline.first; markedPoint.kind ¬ tool.grabbed.rest.first.kind; tool.grabbed.rest.first ¬ markedPoint; IF tool.grabbed.rest.rest = NIL AND head.first.kind # open THEN { head.first.x ¬ markedPoint.x; head.first.y ¬ markedPoint.y; }; ViewerOps.SetNewVersion[root]; }; $RecordGrabbedBox => RecordPoints[tool.grabbed, tool.grabbedCount+2]; $RecordTrajectoryBox => RecordPoints[tool.outline.first, LAST[NAT]]; $RecordSelectionBox => RecordPoints[tool.selected, tool.selectedCount]; $StartMove => { tool.moveSource ¬ markedPoint; }; $Move => { deltaX: REAL ~ markedPoint.x - tool.moveSource.x; deltaY: REAL ~ markedPoint.y - tool.moveSource.y; tool.moveSource ¬ markedPoint; IF tool.selected#NIL THEN { p: LIST OF MarkedPoint ¬ tool.selected.rest; BoundSelected[]; FOR i: INT IN [0..tool.selectedCount) WHILE p # NIL DO p.first.x ¬ p.first.x + deltaX; p.first.y ¬ p.first.y + deltaY; p ¬ p.rest; ENDLOOP; BoundSelected[]; RecordBBox[]; ViewerOps.SetNewVersion[root]; }; }; $Bulge => { <
> IF tool.selected#NIL AND tool.selectedCount > 2 THEN { p: LIST OF MarkedPoint ¬ tool.selected.rest; z0: COMPLEX ~ Complexify[p.first]; z1: COMPLEX ~ Complexify[tool.moveSource]; z2: COMPLEX ~ Complexify[FetchPt[p, tool.selectedCount-1]]; w1: COMPLEX ~ Complexify[markedPoint]; b: ARRAY [0..4) OF COMPLEX ~ FindBilinearParam[[z0, z1, z2], [z0, w1, z2]]; IF b # bilinearIdentity THEN { BoundSelected[]; FOR i: INT IN [0..tool.selectedCount) WHILE p # NIL DO v: COMPLEX ~ BilinearEval[b, Complexify[p.first]]; p.first.x ¬ v.x; p.first.y ¬ v.y; p ¬ p.rest; ENDLOOP; BoundSelected[]; RecordBBox[]; ViewerOps.SetNewVersion[root]; }; tool.moveSource ¬ markedPoint; }; }; $ReverseTrajectory => { IF tool.outline # NIL THEN { old: Trajectory ¬ tool.outline.first.rest; tool.outline.first.rest ¬ NIL; WHILE old # NIL DO temp: Trajectory ¬ old; old ¬ old.rest; temp.rest ¬ tool.outline.first.rest; tool.outline.first.rest ¬ temp ENDLOOP; }; ViewerOps.SetNewVersion[root]; }; $SelectPt => { GrabPoint[tool, markedPoint]; IF tool.selected # tool.grabbed THEN { tool.selected ¬ tool.grabbed; tool.selectedCount ¬ tool.grabbedCount; }; tool.grabbed ¬ NIL; tool.grabbedCount ¬ 0; }; $Undo => { BoundSelected[]; IF tool.selected # NIL AND tool.selectedCount > 0 AND tool.undoList # NIL THEN { end: LIST OF MarkedPoint ¬ tool.selected.rest; undoList: LIST OF MarkedPoint ¬ tool.undoList; undoEnd: LIST OF MarkedPoint ¬ NIL; count: NAT ¬ 0; FOR i: NAT IN [0..tool.selectedCount-1) DO end ¬ end.rest; ENDLOOP; FOR p: LIST OF MarkedPoint ¬ tool.undoList, p.rest UNTIL p=NIL DO count ¬ count + 1; IF p.rest = NIL THEN undoEnd ¬ p; ENDLOOP; undoEnd.rest ¬ end.rest; tool.undoList ¬ tool.selected.rest; tool.selected.rest ¬ undoList; tool.selectedCount ¬ count; end.rest ¬ NIL; }; BoundSelected[]; RecordBBox[]; ViewerOps.SetNewVersion[root]; }; $PaintAll, $TouchUp => { ViewerOps.PaintViewer[root, client, FALSE, action]; tool.paintRectangles ¬ NIL; }; $NewPt, $InvertGrabbed, $InvertSel, $EraseGrabbedPoint => { ViewerOps.PaintViewer[root, client, FALSE, action]; }; ENDCASE => ERROR; IF paranoid THEN CheckConsistency[tool]; ENDLOOP; }; DoWithLock[viewer, dispatch]; }; paranoid: BOOL ¬ TRUE; SaveForUndo: PROC [tool: Tool] = { new: LIST OF MarkedPoint ¬ NIL; end: LIST OF MarkedPoint ¬ NIL; scratch: LIST OF MarkedPoint ¬ tool.undoList; tool.undoList ¬ NIL; IF tool.selected # NIL THEN { list: LIST OF MarkedPoint ¬ tool.selected.rest; FOR i: NAT IN [0..tool.selectedCount) WHILE list # NIL DO node: LIST OF MarkedPoint ¬ NIL; IF scratch = NIL THEN node ¬ LIST[list.first] ELSE { node ¬ scratch; scratch ¬ scratch.rest; node.first ¬ list.first; node.rest ¬ NIL; }; IF end # NIL THEN {end.rest ¬ node; end ¬ node.rest} ELSE new ¬ end ¬ node; list ¬ list.rest; ENDLOOP; }; tool.undoList ¬ new; UNTIL scratch = NIL DO t: LIST OF MarkedPoint ¬ scratch; scratch ¬ scratch.rest; t.rest ¬ NIL; ENDLOOP; }; SmoothModifier: PointModifyProc ~ { IF pointList # NIL AND count > 0 THEN { list: LIST OF MarkedPoint ¬ pointList.rest; prev: MarkedPoint ¬ pointList.first; IF prev.isHeader AND list # NIL THEN prev ¬ list.first; FOR i: INT IN [0..count) WHILE list # NIL DO current: MarkedPoint ¬ list.first; next: MarkedPoint ¬ IF list.rest = NIL THEN current ELSE list.rest.first; current.x ¬ (2*current.x + prev.x + next.x)/4; current.y ¬ (2*current.y + prev.y + next.y)/4; prev ¬ list.first; IF current.kind # corner THEN list.first ¬ current; list ¬ list.rest; ENDLOOP; } ELSE changed ¬ FALSE; }; CornerModifierDataRep: TYPE ~ RECORD [old, new: PointKind]; CornerModifier: PointModifyProc ~ { data: REF CornerModifierDataRep ~ NARROW[self.data]; IF pointList # NIL AND count > 0 THEN { list: LIST OF MarkedPoint ¬ pointList.rest; FOR i: INT IN [0..count) WHILE list # NIL DO IF list.first.kind = data.old THEN list.first.kind ¬ data.new; list ¬ list.rest; ENDLOOP; } ELSE changed ¬ FALSE; }; DoubleModifier: PointModifyProc ~ { IF pointList # NIL AND count > 0 THEN { list: LIST OF MarkedPoint ¬ pointList.rest; newCount ¬ count; FOR i: INT IN [0..count) WHILE list # NIL DO pt: MarkedPoint ¬ list.first; IF pt.kind = sample OR pt.kind = corner THEN { pt.kind ¬ sample; list.rest ¬ CONS[pt, list.rest]; list ¬ list.rest; newCount ¬ newCount + 1; }; list ¬ list.rest; ENDLOOP; IF newCount > 2 THEN { [] ¬ SmoothModifier[NIL, pointList.rest, newCount-2]; }; } ELSE changed ¬ FALSE; }; UnDoubleModifier: PointModifyProc ~ { IF pointList # NIL AND count > 0 THEN { list: LIST OF MarkedPoint ¬ pointList; flipFlop: BOOL ¬ FALSE; newCount ¬ count; FOR i: INT IN [0..count) WHILE list.rest # NIL DO next: LIST OF MarkedPoint ¬ list.rest; IF next.first.kind = sample AND flipFlop THEN { list.rest ¬ next.rest; newCount ¬ newCount - 1; }; flipFlop ¬ NOT flipFlop; list ¬ next; ENDLOOP; } ELSE changed ¬ FALSE; }; TSquareModifier: PointModifyProc ~ { IF pointList # NIL AND count > 0 THEN { list: LIST OF MarkedPoint ¬ pointList.rest; firstPt: MarkedPoint ~ pointList.rest.first; lastPt: MarkedPoint ¬ firstPt; xBar: REAL ¬ 0; yBar: REAL ¬ 0; FOR i: INT IN [0..count) WHILE list # NIL DO cur: MarkedPoint ¬ list.first; IF i = count-1 OR list.rest = NIL THEN {lastPt ¬ cur}; xBar ¬ xBar + cur.x; yBar ¬ yBar + cur.y; list ¬ list.rest; ENDLOOP; list ¬ pointList.rest; xBar ¬ xBar/count; yBar ¬ yBar/count; IF ABS[lastPt.x-firstPt.x] > ABS[lastPt.y-firstPt.y] THEN { y: REAL ¬ yBar; FOR i: INT IN [0..count) WHILE list # NIL DO cur: MarkedPoint ¬ list.first; cur.x ¬ firstPt.x + (lastPt.x-firstPt.x)*i/(count-1); cur.y ¬ y; list.first ¬ cur; list ¬ list.rest; ENDLOOP; } ELSE IF lastPt.y # firstPt.y THEN { x: REAL ¬ xBar; FOR i: INT IN [0..count) WHILE list # NIL DO cur: MarkedPoint ¬ list.first; cur.x ¬ x; cur.y ¬ firstPt.y + (lastPt.y-firstPt.y)*i/(count-1); list.first ¬ cur; list ¬ list.rest; ENDLOOP; }; RETURN [TRUE]; }; RETURN [FALSE] }; MenuAction: Menus.ClickProc = {Notify[NARROW[parent], LIST[clientData]]}; <> <> <> <> <> <<};>> <> <> <> <> <> <> <> <<]>> <<];>> <> <<};>> <<>> CreateMenu: PROC RETURNS [Menus.Menu] = { menu: Menus.Menu ¬ Menus.CreateMenu[lines: 2]; bsMenu: Menus.Menu ¬ Menus.CopyMenu[BiScrollers.bsMenu]; Menus.AppendMenuEntry[ menu: menu, line: 0, entry: Menus.CopyEntry[Menus.FindEntry[bsMenu, "Rotate"]] ]; Menus.AppendMenuEntry[ menu: menu, line: 0, entry: Menus.CopyEntry[Menus.FindEntry[bsMenu, "Scale"]] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Clear", proc: MenuAction, clientData: $Clear, documentation: "Clear the viewer" ] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Refresh", proc: MenuAction, clientData: $Refresh, documentation: "Refresh the viewer" ] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Save", proc: MenuAction, clientData: $Save, documentation: "Save the samples" ] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Store", proc: MenuAction, clientData: $Store, documentation: "Save the samples with a new file name" ] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Smooth", proc: MenuAction, clientData: $Smooth, documentation: "Smooth out the selected samples" ] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Reverse", proc: MenuAction, clientData: $Reverse, documentation: "Reverse the order of the points in the current trajectory" ] ]; Menus.AppendMenuEntry[ menu: menu, line: 1, entry: Menus.CreateEntry[ name: "Delete", proc: MenuAction, clientData: $Delete, documentation: "Delete the currently selected points" ] ]; RETURN [menu]; }; Break: PROC [char: CHAR] RETURNS [IO.CharClass] = { IF char = '_ OR char = '; THEN RETURN [break]; IF char = ' OR char = ' OR char = ', OR char = '\n THEN RETURN [sepr]; RETURN [other]; }; GetToken: PROC [stream: IO.STREAM, breakProc: IO.BreakProc] RETURNS [rope: ROPE ¬ NIL] ~ { rope ¬ stream.GetTokenRope[breakProc ! IO.EndOfStream => CONTINUE].token }; SampledCurveEditCommand: Commander.CommandProc ~ { stream: IO.STREAM ¬ IO.RIS[cmd.commandLine]; name: ROPE ¬ GetToken[stream, Break]; IF name # NIL THEN { name ¬ FS.ExpandName[name ! FS.Error => {cmd.err.PutRope[error.explanation]; name ¬ NIL; CONTINUE}].fullFName; }; IF name = NIL THEN { [] ¬ bsStyle.CreateBiScroller[bsClass, [name: "Sampled Curve Editor [No File]", file: NIL, data: NewData[]]] } ELSE { [] ¬ bsStyle.CreateBiScroller[bsClass, [name: Rope.Concat["Sampled Curve Editor ", name], file: name, label: name, data: NewData[]]]; }; }; CreateViewer: PUBLIC PROC [info: ViewerClasses.ViewerRec] RETURNS [Viewer] ~ { biScroller: BiScrollers.BiScroller; IF info.label = NIL THEN info.label ¬ info.file; IF info.name = NIL THEN info.name ¬ Rope.Concat["Sampled Curve Editor ", info.file]; info.data ¬ NewData[]; biScroller ¬ bsStyle.CreateBiScroller[bsClass, info]; RETURN [BiScrollers.QuaViewer[biScroller]] }; <> <> <> <> <> <> <> <<]];>> <<>> Extrema: BiScrollers.ExtremaProc ~ { <> Locked: PROC [tool: Tool] ~ { box: PaintRectangle ¬ [xMin: 999999, yMin: 999999, xMax: -999999, yMax: -999999]; BBPoint: PROC [markedPoint: MarkedPoint] ~ INLINE { box.xMin ¬ MIN[box.xMin, markedPoint.x-slop]; box.xMax ¬ MAX[box.xMax, markedPoint.x+slop]; box.yMin ¬ MIN[box.yMin, markedPoint.y-slop]; box.yMax ¬ MAX[box.yMax, markedPoint.y+slop]; }; slop: REAL ~ 5; BoundPoints: PROC [list: LIST OF MarkedPoint] ~ { WHILE list # NIL DO IF list.first.kind # open THEN BBPoint[list.first]; list ¬ list.rest; ENDLOOP; }; FOR t: LIST OF Trajectory ¬ tool.outline, t.rest UNTIL t=NIL DO BoundPoints[t.first.rest]; ENDLOOP; IF box.xMin < box.yMin THEN {min ¬ [0,0]; max ¬ [1,1]} ELSE [min, max] ¬ Geom2D.ExtremaOfRect[[box.xMin, box.yMin, box.xMax-box.xMin, box.yMax-box.yMin], direction]; }; min ¬ max ¬ [0,0]; DoWithLockedData[clientData, Locked]; }; bsStyle: BiScrollers.BiScrollerStyle; bsClass: BiScrollers.BiScrollerClass; registrationKey: REF TEXT ~ "SampledCurveEdit"; Init: PROC ~ { bsStyle ¬ BiScrollers.GetStyle[]; -- default gets BiScrollersButtonned bsClass ¬ bsStyle.NewBiScrollerClass[[ flavor: $SampledCurveEdit, extrema: Extrema, notify: Notify, paint: PaintProc, destroy: NIL, get: NIL, init: InitViewer, save: SaveProc, menu: CreateMenu[], tipTable: TIPUser.InstantiateNewTIPTable["SampledCurveEdit.tip"], mayStretch: FALSE, -- NOT OK to scale X and Y differently preserve: [X: 0.5, Y: 0.5] --this specifies point that stays fixed when viewer size changes ]]; RegisterPointModifer[$Smooth, NEW[PointModifierRep ¬ [SmoothModifier, NIL]]]; RegisterPointModifer[$Double, NEW[PointModifierRep ¬ [DoubleModifier, NIL]]]; RegisterPointModifer[$UnDouble, NEW[PointModifierRep ¬ [UnDoubleModifier, NIL]]]; RegisterPointModifer[$TSquare, NEW[PointModifierRep ¬ [TSquareModifier, NIL]]]; RegisterPointModifer[$Corner, NEW[PointModifierRep ¬ [CornerModifier, NEW[CornerModifierDataRep ¬ [sample, corner]]]]]; RegisterPointModifer[$NonCorner, NEW[PointModifierRep ¬ [CornerModifier, NEW[CornerModifierDataRep ¬ [corner, sample]]]]]; <> Commander.Register["SampledCurveEdit", SampledCurveEditCommand, "Create a viewer to edit a sampled curve"]; }; Complexify: PROC [m: MarkedPoint] RETURNS [COMPLEX] ~ INLINE { RETURN [[m.x, m.y]] }; BilinearEval: PROC [b: ARRAY [0..4) OF COMPLEX, z: COMPLEX] RETURNS [COMPLEX] ~ { <> num: COMPLEX ~ Complex.Add[Complex.Mul[b[0], z], b[1]]; denom: COMPLEX ~ Complex.Add[Complex.Mul[b[2], z], b[3]]; w: COMPLEX ~ Complex.Div[num, denom]; RETURN [w] }; bilinearIdentity: ARRAY [0..4) OF COMPLEX ~ [ [1, 0], [0, 0], [0, 0], [1, 0] ]; maxAbs: REAL ¬ 10000000; FindBilinearParam: PROC [z: ARRAY [0..3) OF COMPLEX, w: ARRAY [0..3) OF COMPLEX] RETURNS [ARRAY [0..4) OF COMPLEX] ~ { <> <> A: ARRAY [0..4) OF ARRAY [0..4) OF COMPLEX ¬ ALL[ALL[[0,0]]]; p: ARRAY [0..4) OF COMPLEX ¬ ALL[[0,0]]; b: ARRAY [0..4) OF COMPLEX ~ [ [0,0], [0,0], [0,0], [1,0] ]; ok: BOOL ¬ TRUE; FOR i: NAT IN [0..3) DO A[i] ¬ [ z[i], [1, 0], Complex.Neg[Complex.Mul[z[i], w[i]]], Complex.Neg[w[i]]]; ENDLOOP; A[3] ¬ [ [0,0], [0,0], [1,0], [0,0] ]; p ¬ Solve4[A, b ! Real.RealException => {ok ¬ FALSE; CONTINUE}]; IF NOT ok THEN { A[3] ¬ [ [0,0], [0,0], [0,0], [1,0] ]; p ¬ Solve4[A, b ! Real.RealException => {ok ¬ FALSE; CONTINUE}]; }; FOR i: NAT IN [0..4) DO IF ABS[p[i].x] > maxAbs OR ABS[p[i].y] > maxAbs THEN ok ¬ FALSE; ENDLOOP; IF NOT ok THEN p ¬ bilinearIdentity; RETURN [p] }; Solve4: PROC [A: ARRAY [0..4) OF ARRAY [0..4) OF COMPLEX, b: ARRAY [0..4) OF COMPLEX] RETURNS [x: ARRAY [0..4) OF COMPLEX ¬ ALL[[0,0]]] ~ { <> SubtractMultiple: PROC [u, v, r: COMPLEX] RETURNS [COMPLEX] ~ { <> RETURN [Complex.Sub[u, Complex.Mul[v, r]]] }; n: NAT = 4; FOR i: [0..n) IN [0..n) DO bestk: [0..n) ¬ i; FOR k: [0..n) IN [i..n) DO IF Complex.SqrAbs[A[k][i]] > Complex.SqrAbs[A[bestk][i]] THEN bestk ¬ k; ENDLOOP; {t: ARRAY [0..n) OF COMPLEX ¬ A[i]; A[i] ¬ A[bestk]; A[bestk] ¬ t}; {t: COMPLEX ¬ b[i]; b[i] ¬ b[bestk]; b[bestk] ¬ t}; FOR k: [0..n) IN (i..n) DO r: COMPLEX = Complex.Div[A[k][i], A[i][i]]; <> FOR j: [0..n) IN [i..n) DO A[k][j] ¬ SubtractMultiple[A[k][j], A[i][j], r]; ENDLOOP; b[k] ¬ SubtractMultiple[b[k], b[i], r]; ENDLOOP; ENDLOOP; <> FOR i: [0..n) DECREASING IN [0..n) DO xi: COMPLEX ¬ b[i]; FOR j: [0..n) IN (i..n) DO xi ¬ SubtractMultiple[xi, A[i][j], x[j]]; ENDLOOP; x[i] ¬ Complex.Div[xi, A[i][i]]; ENDLOOP; }; Init[]; END.