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