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
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 PROCESSNIL,
lockCount: CARDINAL ← 0,
lockFree: CONDITION,
outline: LIST OF Trajectory,
The first trajectory in the outline is the one currently being edited.
grabbed: LIST OF MarkedPoint,
Points to the item before the point or points actually grabbed.
grabbedCount: NAT,
selected: LIST OF MarkedPoint ← NIL,
Points to the item before the point actually selected.
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: NATNAT.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.STREAMFS.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 ANYNIL;
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: BOOLFALSE] = {
Locked: PROC [tool: Tool] ~ {
stream: IO.STREAMFS.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] = {
Breaks the trajectory before the current selection; turns a closed loop into an open trajectory, or an open trajectory into two trajectories.
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
PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL]
= {
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.STREAMIO.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 REFNIL;
ultimate: LIST OF REF ← tool.actionQueue;
IF ultimate = NIL THEN TRUSTED {
ultimate ← tool.actionQueue ← LIST[NIL];
List head is present iff DispatchProcess is alive.
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 REFNIL;
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];
Records touchup box
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: ROPENIL;
fullFName: ROPENIL;
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];
Records touchup box
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];
Records touchup box
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];
Will record boxes that need touchup
QueueTouchUp[];
};
$ShowPt => {
s: IO.STREAMIO.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;
Remove list head so Notify knows that DispatchProcess need restarting
ref ← NIL;
Returning NIL will cause this DispatchProcess to go away.
}
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: INTLAST[INT];
selectedToGo: INTLAST[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 => {
Figure out the coefficients of the bilinear analytic function that maps the endpoints of the current selection to themselves, and tool.moveSource to markedPoint.
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: BOOLTRUE;
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: BOOLFALSE;
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: ROPENIL] ~ {
rope ← stream.GetTokenRope[breakProc ! IO.EndOfStream => CONTINUE].token
};
SampledCurveEditCommand: Commander.CommandProc ~ {
stream: IO.STREAMIO.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]]
};
viewerClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: PaintProc,
notify: Notify,
init: InitViewer,
save: SaveProc,
menu: CreateMenu[],
tipTable: TIPUser.InstantiateNewTIPTable["SampledCurveEdit.TIP"]
]];
Extrema: BiScrollers.ExtremaProc ~ {
PROC [clientData: REF ANY, direction: VEC] RETURNS [min, max: VEC];
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]]]]];
ViewerOps.RegisterViewerClass[$SampledCurveEdit, viewerClass];
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] ~ {
Computes (b0z+b1)/(b2z+b3)
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] ~ {
Finds parameters for BilinearEval that map zi to wi
Returns parameters for identity mapping if exceptions occur
A: ARRAY [0..4) OF ARRAY [0..4) OF COMPLEXALL[ALL[[0,0]]];
p: ARRAY [0..4) OF COMPLEXALL[[0,0]];
b: ARRAY [0..4) OF COMPLEX ~ [ [0,0], [0,0], [0,0], [1,0] ];
ok: BOOLTRUE;
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] ~ {
solve Ax=b by Gaussian Elimination
SubtractMultiple: PROC [u, v, r: COMPLEX] RETURNS [COMPLEX] ~ {
Calculates u - v*r
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 COMPLEXA[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]];
Singular A causes divide by zero
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;
Now A is upper-triangular, so back substitute
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.