<> <> <> <> 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.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]]; WITH whatChanged SELECT FROM userPaint: UserPaint => { userPaint.proc[context, userPaint.data]; RETURN }; ENDCASE => NULL; SELECT whatChanged FROM NIL => { Notify[self, LIST[$Refresh]]; }; $PaintAll => { list: LIST OF UserPaint ~ NARROW[ViewerOps.FetchProp[self.parent, registrationAtom]]; Imager.SetColor[context, Imager.white]; Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]]; DrawOutline[tool, context]; FOR l: LIST OF UserPaint _ list, l.rest UNTIL l=NIL DO l.first.proc[context, l.first.data]; ENDLOOP; 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 list: LIST OF UserPaint ~ NARROW[ViewerOps.FetchProp[self.parent, registrationAtom]]; box: PaintRectangle ~ paintList.first; Proc: PROC ~ { Imager.ClipRectangle[context, [box.xMin, box.yMin, box.xMax-box.xMin, box.yMax-box.yMin]]; DrawOutline[tool, context]; FOR l: LIST OF UserPaint _ list, l.rest UNTIL l=NIL DO l.first.proc[context, l.first.data]; ENDLOOP; 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.Put[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]; }; RegisterUserPaint: PUBLIC PROC [viewer: ViewerClasses.Viewer, userPaint: UserPaint] = { list: LIST OF UserPaint _ NARROW[ViewerOps.FetchProp[viewer, registrationAtom]]; list _ CONS[userPaint, list]; ViewerOps.AddProp[viewer, registrationAtom, list]; }; <<>> 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; }; userPaint: UserPaint => { ViewerOps.PaintViewer[root, client, FALSE, userPaint]; }; 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 AND tool.grabbed # 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]]}; AddMenuItem: PUBLIC PROC [viewer: Viewer, atom: ATOM] = { name: ROPE ~ Atom.GetPName[atom]; Menus.AppendMenuEntry[ menu: viewer.menu, line: 1, entry: Menus.CreateEntry[ name: name, proc: MenuAction, clientData: atom, documentation: NIL ] ]; }; CreateMenu: PROC RETURNS [Menus.Menu] = { menu: Menus.Menu _ Menus.CopyMenu[BiScrollers.bsMenu]; Menus.ChangeNumberOfLines[menu, 2]; 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]; name _ FS.ExpandName[name ! FS.Error => {cmd.out.PutRope[error.explanation]; name _ NIL; CONTINUE}].fullFName; IF Rope.Length[name] = 0 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]; }; DoWithLockedData[clientData, Locked]; }; bsStyle: BiScrollers.BiScrollerStyle; bsClass: BiScrollers.BiScrollerClass; registrationKey: REF TEXT ~ "SampledCurveEdit"; registrationAtom: ATOM ~ $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] ~ { <> 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.