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. SampledCurveEditImpl.mesa Copyright (C) 1983, Xerox Corporation. All rights reserved. Michael Plass, January 31, 1986 2:58:24 pm PST Maureen Stone, September 25, 1987 4:17:29 pm PDT The first trajectory in the outline is the one currently being edited. Points to the item before the point or points actually grabbed. Points to the item before the point actually selected. Breaks the trajectory before the current selection; turns a closed loop into an open trajectory, or an open trajectory into two trajectories. PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] List head is present iff DispatchProcess is alive. Records touchup box Records touchup box Records touchup box Will record boxes that need touchup Remove list head so Notify knows that DispatchProcess need restarting Returning NIL will cause this DispatchProcess to go away. Figure out the coefficients of the bilinear analytic function that maps the endpoints of the current selection to themselves, and tool.moveSource to markedPoint. viewerClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [ paint: PaintProc, notify: Notify, init: InitViewer, save: SaveProc, menu: CreateMenu[], tipTable: TIPUser.InstantiateNewTIPTable["SampledCurveEdit.TIP"] ]]; PROC [clientData: REF ANY, direction: VEC] RETURNS [min, max: VEC]; ViewerOps.RegisterViewerClass[$SampledCurveEdit, viewerClass]; Computes (b0z+b1)/(b2z+b3) Finds parameters for BilinearEval that map zi to wi Returns parameters for identity mapping if exceptions occur solve Ax=b by Gaussian Elimination Calculates u - v*r Singular A causes divide by zero Now A is upper-triangular, so back substitute Κ<{˜Jšœ™J™<™.Icode™0—šΟk ˜ J˜Jšœ ˜ J˜ J˜J˜J˜J˜J˜ Jšœ˜Jšœ˜J˜ J˜Jšœ˜Jšœ˜J˜J˜J˜J˜J˜ Jšœ˜J˜Jšœ˜Jšœ ˜ J˜ Jšœ˜J˜—šΠlnœœ˜#JšœEœœZ˜ΔJšœ˜—šœ œ˜J˜—Jšœœ˜)Jšœœœ˜Jšœœ œ˜šœœ œ˜J˜—Jšœœœ ˜šœ œœ˜Jšœ œœœ˜Jšœ œœ˜"Jšœ œ˜Jšœ  œ˜šœ œœ ˜J™F—šœ œœ ˜J™?—Jšœœ˜šœ œœœ˜$Jšœ6™6—Jšœœ˜Jšœ œœœ˜$Jšœ!˜!Jšœ!˜!Jšœœœ˜'Jšœ˜J˜—šΟnœœ˜'Kšœ# œ˜0Kšœ#œ˜+Kšœœœœ˜šœœœ˜Kšœ œœœ˜š œœœ*œœ˜Hšœœ˜Kšœœœ˜Kšœœ˜Kšœ*˜*Kšœ˜—šœœ˜Kšœœœ˜Kšœœ˜Kšœ(˜(Kšœ˜—Kšœœ˜0Kšœœ˜.Kšœ˜Kšœ˜Kšœ˜—šœ&œ˜.Kšœ*œœ˜7Kšœ*œœ˜7Kšœ˜—Kšœ˜—Kš œœœœœœ˜;Kš œœœœœœ˜9Kšœœœ˜#Kšœœœ˜"Kšœ˜J˜—šœœœ˜Jšœ˜Jšœ˜J˜—šŸœœœœ˜Jšœ œ ˜Jšœœœ ˜"Jšœœ˜Jšœ˜Kšœ˜Kšœ˜K˜—šŸ œœ˜#šœ œœ˜Jšœ˜Jšœ˜—J˜J˜—šŸœœ˜!šŸœœœœ ˜(Jšœœ˜ Jšœœ0œ˜EJšœ˜Jšœ˜—Jš œœœœœ3œœ˜”Jšœœ˜Jšœœ œœ˜Jšœ œ˜J˜šŸœœœ˜Jšœœ?œ˜]Jšœ˜Jšœ˜Jšœ˜—šŸœœœœ˜&Jšœœ@œ˜aJšœ˜Jšœ˜Jšœ˜—Jšœœ˜š˜Jšœœœœ˜Jšœœœœ˜6Jšœœœœ˜šœœ˜Jšœœœ˜%Jšœ œœœ˜1Jšœ œœ˜(šœœ˜šœ˜šœ ˜ Jšœœ ˜Jšœœ ˜Jš œ œœœ&œ˜uJšœ ˜ J˜Jšœ˜—šœ ˜ Jšœœ ˜Jšœœ ˜Jšœœœ;œ˜YJšœœœ˜BJšœ˜J˜Jšœ˜—šœ ˜ J˜Jšœ˜—šœ ˜ J˜ Jšœ˜—šœ ˜ Jšœœ˜Jšœœ˜Jšœ˜Jšœ˜Jšœ"˜"Jšœ œ˜Jšœ˜—JšœHœ˜U—Jšœ˜—JšœHœ˜U—Jšœ˜—Jšœœ1˜EJšœ"˜"Jšœ˜Jšœ˜Jšœ œ˜J˜J˜—šŸœœ œœ œ œœœ˜cšœ œ˜Kšœœœ˜ Kšœ˜Kšœ˜Kšœ ˜ Kšœ˜—Kšœ˜K˜—šŸ œœœ˜?Kšœ@˜@Kšœ˜K˜—šŸœœœ œ˜@šœœ˜šœ˜šŸœœœ˜Jš œœœ)œœœ˜fKšœ(˜(Kšœ$˜$Kšœ˜—šŸœœœ˜šœ+œ˜3Kšœœ˜Kšœ˜Kšœ˜—Kšœ˜—Kšœ˜Kšœ œ˜!Kšœ ˜ Kšœ˜—Kšœœ˜—Kšœ˜K˜—Jšœœ˜šŸœœœœ˜6šŸœœ˜Jš œœœœFœ3œœ˜»šŸœœ˜$Kšœœ œ ˜4šœ ˜Kšœ$˜$Kšœ ˜ Kšœœ˜—Kšœ˜—Jšœœ˜šœ&œ˜5Jšœ'˜'Jšœœ˜—šœ œ˜Jšœ!˜!šœ œœ˜Jšœ˜Jšœ6˜6šœ'œœ˜8JšœD˜DJšœ˜—šœœ˜ Jšœ˜Jšœ˜—Jšœ˜—Jšœ˜Jšœ˜—Jšœ<˜Jšœœ ˜+Jšœ˜J˜—š œœ˜*Jš œœœœGœœ˜žJšœ œ'˜:J˜šŸœœ˜Jš œœœœœ˜Jšœ"˜"Jšœ œ˜Jšœœœ˜3Jšœ˜Jšœ˜—Jšœ œ'˜7šŸœœ œF˜^šŸ œœœœ˜"šœœœ˜'Jšœ9˜9Jšœ˜Jšœ˜—Jšœ˜—Jš œ œœœœ˜Jšœ œœœ˜)šœ œœœ˜ šœœœ˜(Jšœ2™2—Jšœœ˜-Jšœ˜—š œœœœœœ˜>Jšœ˜Jšœ ˜ Jšœ˜—šœ˜Jšœ œ˜Jš œœœœœ˜Ošœœœ˜.šœœ˜JšœN˜NJšœœ˜—Jš˜Jšœ˜—Jšœœ˜—šœœ˜Jšœœ˜#Jšœœœ˜Jšœ˜—šœ ˜ šœ ˜ J™—Jšœ˜J˜—šœ ˜ Jšœ˜Jšœ˜Jšœ˜—šœ˜ šœ%œ˜4šœ!˜!Jšœ˜Jšœ˜Jšœ˜—Jšœ˜—Jšœ˜——Jšœ˜—šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜—šœ œœ˜Jšœ8œ ˜GJšœ+œ"˜Sšœ˜šœ ˜ Jšœ ˜ Jšœ&˜&Jšœ˜Jšœ ˜ Jšœ˜—šœ˜Jšœ ˜ J˜šœ˜J™—Jšœ ˜ Jšœ˜Jšœ˜—šœ˜Jšœ ˜ Jšœ,˜,Jšœ ˜ Jšœ˜—šœ ˜ Jšœ ˜ Jšœ'˜'J˜J˜Jšœ˜Jšœ)˜)Jšœ$˜$Jšœ ˜ Jšœ˜—šœ ˜ Jšœ ˜ Jšœ$˜$Jšœ)˜)Jšœ$˜$Jšœ ˜ Jšœ˜—šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜—šœ˜Kšœ*˜*Kšœ˜—šœ ˜ Kšœ'˜'Jšœ˜Kšœ˜—šœ ˜ Kšœ(˜(Jšœ˜Kšœ˜—šœ˜Jšœ ˜ Jšœ+˜+Jšœ ˜ Jšœ˜—šœ˜Jšœ˜Jšœ˜šœ3˜3J™#—Jšœ˜Jšœ˜—šœ ˜ Jš œœœœœ˜Jšœœœ˜GJšœ$œ˜*Jšœ˜—šœ ˜ Jšœ ˜ Jšœ˜Jšœ˜Jšœ(˜(Jšœ˜Jšœ˜Jšœ˜—Jšœ˜—Jšœ˜—Jšœ˜—Jšœ œ˜J˜J˜—š Ÿœœœœœ˜7Jšœœœ˜šœœœ˜%šœœ˜JšœE™E—šœœ˜ Jšœ9™9—Jšœ˜—šœ˜Jšœ"˜"Jšœ3˜3Jšœ˜—Jšœ˜J˜—šŸœœœœ#˜PJšœ3˜3Jšœ˜J˜—šŸΠbn œ œ9˜WJšœœœ œ0˜PJšœœ˜Jšœ2˜2J˜J™J˜—šŸ œœœ ˜6Kš œ œœœœœ˜MKšœ ˜Kšœ˜K˜—šŸœœ˜*šœ œ˜Kšœ"˜"Jšœœ˜ JšœQ˜QJšŸ œœF˜UJšœœ˜šŸœœ˜,Jšœ œ˜.Jšœ œ˜.Jšœ œ˜.Jšœ œ˜.Jšœ˜—šŸ œœ˜Jšœœœ˜!š œ œœ7œ œ˜eJšœ&˜&Jšœ˜Jšœ˜Jšœ˜šœœ˜Jšœ ˜ Jšœ˜Jšœ˜—Jšœ˜Jšœ˜Jšœ˜šœœ˜Jšœ˜Jšœ ˜ Jšœ˜Jšœ˜—Jšœ˜—Jšœœ˜7Jšœ˜—š Ÿ œœœœœ˜=š œœœ œœ˜,Jšœœ˜3Jšœ˜Jšœ˜—Jšœ˜—KšŸ œœ6˜Iš Ÿ œœœœœ˜>Jšœ˜J˜ Jšœ˜—šœœ˜$Jšœœ˜ Jšœ˜šœœ˜Jšœœ˜šœ%˜%Jšœ˜Jšœ*˜*Jšœ˜—šœ!˜!šœœœ˜Jšœ œ˜Jšœ œ˜Jšœ1˜1Jšœf˜fšœ˜ šœ˜Jšœ2˜2Jšœœ˜4Jšœ˜Jšœ˜—Jšœ ˜—Jšœ˜—Jšœ˜—šœ˜Jšœ˜Jšœ˜—šœ˜Kšœ$œ ˜6Kšœ˜—Jšœœ˜—šœ˜Jšœœ˜šœ ˜ J˜Jšœ˜—šœ ˜ Jšœœ˜Jšœ˜Jšœœ˜Jšœ˜Jšœœœ%˜=J˜Jšœ˜—šœ˜Jšœ œœ˜+Jšœ'˜)Jšœ"œœ˜0Jšœ ˜Jšœ˜—šœ ˜ šœœœœ˜HJšœ,˜,Jšœ˜—Jšœœ'˜EJ˜Jšœ˜—šœ ˜ Jšœ9œ˜=Jšœœ˜Jšœ+˜+J˜J˜—šœ ˜ š œœœœœœ˜OKšœœR˜jKšœ˜—J˜J˜—šœ˜Jšœœ˜Jšœ˜Jšœ˜—šœ˜Jšœœ˜Jšœ˜Jšœœ˜Jšœ˜Jšœœ˜J˜—šœ˜š œœœœœ˜>Jšœ˜Jšœ!˜!Jšœœœ'˜KJ˜ Jšœ&˜&Jšœ+˜+J˜—Jšœœ˜#Jšœ+˜+J˜Jšœ˜—šœ˜J˜šœœœ˜Jšœ2˜2šœœœ˜AJšœ-˜-Jšœ˜—Jšœ˜—Jšœœ˜Jšœ˜Jšœœ˜Jšœ˜Jšœœ˜J˜Jšœ˜—šœ ˜ š œœœœœ˜:Jšœ˜Kšœ"˜"Jšœ˜J˜ Kšœ˜—Jšœœ˜Jšœ˜Jšœœ˜J˜Jšœ˜—šœœœœ˜+Jšœœœœ˜Jšœœ˜Jšœ œœœ˜Jšœœœœ˜Jšœ˜Jšœœœ,˜=š œœœœ˜;šœœ˜Jšœœœ˜%Jšœ ˜ Jšœ˜—šœœ˜Jšœœœ˜&Jšœ"˜"Jšœ˜—Jšœœœ˜$Jšœ˜Jšœ ˜ Jšœ˜—Jšœ˜Jšœ˜Jšœœ˜Jšœ˜Jšœœ˜Jšœ˜—Jšœ(˜(š œ œœœœ˜>Jšœœœ"˜/Jšœ0˜0Jšœ&˜&šœœœœ˜AKšœ˜Kšœ˜Kšœ˜—J˜J˜—JšœE˜EJšœ9œœ˜DJšœG˜Gšœ˜Kšœ˜Kšœ˜—šœ ˜ Kšœœ%˜1Kšœœ%˜1Kšœ˜šœœœ˜Kšœœœ"˜,J˜š œœœœœ˜6Kšœ˜Kšœ˜K˜ Kšœ˜—J˜J˜ J˜Kšœ˜—Kšœ˜—šœ ˜ Kšœ‘™‘šœœœœ˜6Kšœœœ"˜,Jšœœ˜"Jšœœ˜*Jšœœ0˜;Jšœœ˜&Jšœœœœ1˜Kšœœ˜J˜š œœœœœ˜6Kšœœ(˜2Kšœ˜Kšœ˜K˜ Kšœ˜—J˜J˜ J˜Kšœ˜—Kšœ˜Kšœ˜—Kšœ˜—šœ˜šœœœ˜J˜*Jšœœ˜šœœ˜Jšœ˜Jšœ˜Jšœ$˜$Jšœ˜Jšœ˜—Jšœ˜—J˜J˜—šœ˜Jšœ˜šœœ˜&Jšœ˜Jšœ'˜'Jšœ˜—Jšœœ˜Jšœ˜Jšœ˜—šœ ˜ J˜š œœœœœœ˜PJšœœœ"˜.Jšœ œœ˜.Jšœ œœœ˜#Jšœœ˜Jš œœœœœ˜Cš œœœ%œœ˜AKšœ˜Kšœ œœ ˜!Kšœ˜—Kšœ˜Kšœ#˜#Kšœ˜Kšœ˜Kšœ œ˜Kšœ˜—J˜J˜ J˜J˜—šœ˜Jšœ$œ ˜3Jšœœ˜Jšœ˜—šœ;˜;Jšœ$œ ˜3Jšœ˜—Jšœœ˜—Jšœ œ˜(Jšœ˜—Kšœ˜—J˜J˜J˜—šœ œœ˜J˜—šŸ œœ˜"Jšœœœœ˜Jšœœœœ˜Jšœ œœ˜-Kšœœ˜šœœœ˜Jšœœœ"˜/š œœœœœ˜9Kšœœœœ˜ Kšœ œœœ ˜-šœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œ˜Kšœ˜—Kšœœœ$œ˜KKšœ˜Kšœ˜—Kšœ˜—Jšœ˜šœ œ˜Kšœœœ˜!Kšœ˜Kšœ œ˜ Kšœ˜—Jšœ˜J˜—šŸœ˜#šœ œœ œ˜'Jšœœœ˜+Jšœ$˜$Jšœœœœ˜7š œœœ œœ˜,Jšœ"˜"Jš œœ œœ œ˜IJšœ.˜.Jšœ.˜.Jšœ˜Jšœœ˜3Jšœ˜Jšœ˜—Jšœ˜—Kšœ œ˜Kšœ˜K˜—šœœœ˜;K˜—šŸœ˜#Jšœœœ ˜4šœ œœ œ˜'Jšœœœ˜+š œœœ œœ˜,Jšœœ˜>Jšœ˜Jšœ˜—Jšœ˜—Kšœ œ˜Kšœ˜K˜—šŸœ˜#šœ œœ œ˜'Jšœœœ˜+J˜š œœœ œœ˜,Kšœ˜šœœœ˜.Kšœ˜Kšœ œ˜ Kšœ˜K˜Kšœ˜—Jšœ˜Jšœ˜—šœœ˜Kšœœ˜5Kšœ˜—Jšœ˜—Kšœ œ˜Kšœ˜K˜—šŸœ˜%šœ œœ œ˜'Jšœœœ˜&Jšœ œœ˜J˜š œœœ œ œ˜1Kšœœœ˜&šœœœ˜/Kšœ˜K˜Kšœ˜—Jšœ œ ˜Jšœ ˜ Jšœ˜—Jšœ˜—Kšœ œ˜Kšœ˜K˜—šŸœ˜$šœ œœ œ˜'Jšœœœ˜+Jšœ,˜,Jšœ˜Jšœœ˜Jšœœ˜š œœœ œœ˜,Jšœ˜Jšœ œ œœ˜6Jšœ˜Jšœ˜Jšœ˜Jšœ˜—Jšœ˜Kšœ˜Kšœ˜šœœœœ˜;Kšœœ˜š œœœ œœ˜,Jšœ˜Kšœ5˜5Kšœ ˜ Kšœ˜Jšœ˜Kšœ˜—Kšœ˜—šœœœ˜#Kšœœ˜š œœœ œœ˜,Jšœ˜Kšœ ˜ Kšœ5˜5Kšœ˜Jšœ˜Kšœ˜—Kšœ˜—Kšœœ˜Jšœ˜—Kšœœ˜Kšœ˜K˜—šŸ œœ œ˜IJ˜—šŸ œœœœ˜9Jšœœ˜!šœ˜Jšœ˜˜Jšœ ˜ Jšœ˜Jšœ˜Jšœ˜J˜—J˜—Jšœ˜J˜—šŸ œœœ˜)Jšœ6˜6Jšœ#˜#šœ˜Jšœ˜˜J˜Jšœ˜Jšœ˜J˜!J˜—J˜—šœ˜Jšœ˜˜J˜Jšœ˜Jšœ˜J˜#J˜—J˜—šœ˜Jšœ˜˜J˜ Jšœ˜Jšœ˜J˜!J˜—J˜—šœ˜Jšœ˜˜J˜Jšœ˜Jšœ˜J˜6J˜—J˜—šœ˜Jšœ˜˜J˜Jšœ˜Jšœ˜J˜0J˜—J˜—šœ˜Jšœ˜˜Jšœ˜Jšœ˜Jšœ˜JšœJ˜JJ˜—J˜—šœ˜Jšœ˜˜Jšœ˜Jšœ˜Jšœ˜Jšœ5˜5J˜—J˜—Jšœ˜Jšœ˜J˜—– "cedar" styleš Ÿœœœœœ˜3Jšœ œ œœ ˜.Jš œ œ œ œ œœ˜HJšœ ˜Jšœ˜J˜—šŸœœ œœ œ œœœ˜ZJšœ'œœ˜HJšœ˜J˜—šŸœ˜2Jš œœœœœ˜,Jšœœ˜%Jš œœœ6œœ ˜nšœœ˜JšœVœ˜l—š˜Jšœ…˜…—Jšœ˜J˜—šŸ œœœ!œ ˜NJšœ#˜#Jšœœœ˜0Jšœ œœ=˜TJšœ˜Jšœ5˜5Kšœ$˜*Jšœ˜J˜—šœ)œ!™MJšœ™Jšœ™J™J™Jšœ™Jšœ@™@J™J™—šŸœ˜$Kš œœœ œœ œ™CšŸœœ˜JšœQ˜QšŸœœœ˜3Jšœ œ˜.Jšœ œ˜.Jšœ œ˜.Jšœ œ˜.Jšœ˜—Jšœœ˜šŸ œœœœ˜1šœœ˜Jšœœ˜3Jšœ˜Jšœ˜—Jšœ˜—š œœœ#œœ˜?Jšœ˜Kšœ˜—Kšœœœ˜6Kšœj˜nKšœ˜—Kšœ%˜%Kšœ˜K˜—Kšœ%˜%šœ%˜%K˜—Kšœœœ˜/Kšœœ˜+šŸœœ˜Kšœ"Οc$˜Fšœ&˜&Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœ ˜ J˜J˜Jšœ˜KšœA˜AKšœ œ’&˜9Kšœ œœ’@˜[K˜—Jšœœ%œ˜MJšœœ%œ˜MJšœ œ'œ˜QJšœœ&œ˜OJšœœ%œ.˜wJšœ!œ%œ.˜zJšœ>™>Jšœk˜kKšœ˜K˜—š Ÿ œœœœœ˜>Kšœ ˜Kšœ˜K˜—šŸ œœœœœœœœ˜QKš œ Οdœ£œ£œ£œ™Kšœœ£œ£œ˜7Kšœœ£œ£œ˜9Kšœœ˜%Kšœ˜ Kšœ˜K˜—Kšœœœœ&˜Ošœœ ˜K˜—šŸœœœœœœœœœœœœ˜vJšœ,£œ£™3Jšœ;™;Jšœœœœœœœœ ˜=Jš œœœœœ˜(Kšœœœœ"˜