SampledCurveEditImpl.mesa
Copyright (C) 1983, Xerox Corporation. All rights reserved.
Michael Plass, September 24, 1984 3:11:36 pm PDT
Last Edited by: Stone, October 9, 1984 8:29:12 pm PDT
DIRECTORY
Atom,
Commander,
Convert,
SampledCurveEdit,
InputFocus,
FS,
Graphics,
IO,
Menus,
MessageWindow,
Process,
Rope,
RuntimeError,
TIPUser,
ViewerClasses,
ViewerOps
;
SampledCurveEditImpl: CEDAR MONITOR
IMPORTS Atom, Commander, Convert, InputFocus, FS, Graphics, IO, Menus, MessageWindow, Process, Rope, RuntimeError, TIPUser, ViewerOps
EXPORTS SampledCurveEdit
~ BEGIN OPEN SampledCurveEdit;
header: MarkedPoint ~ [2, 2, TRUE, open];
ROPE: TYPE ~ Rope.ROPE;
Tool: TYPE = REF ToolRec;
ToolRec: TYPE = RECORD [
actionQueue: LIST OF REF,
actionQueueEmpty: 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,
dragSource: MarkedPoint ← header,
paintRectangles: LIST OF PaintRectangle
];
PaintRectangle: TYPE ~ RECORD [
xMin, yMin, xMax, yMax: REAL
];
InitViewer: PROC [self: Viewer] = {
tool: Tool ← NEW[ToolRec];
tool.outline ← LIST[LIST[header]];
tool.grabbed ← NIL;
tool.grabbedCount ← 0;
self.column ← left;
self.data ← tool;
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;
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 ← stream.GetRefAny[ ! 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;
};
$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: sample]];
curTrajEnd ← curTrajEnd.rest;
};
$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"];
SetOutline[self, outline];
stream.Close;
EXITS Quit => NULL;
};
SaveProc: PROC [self: Viewer, force: BOOLFALSE] = {
IF self.file # NIL THEN {
stream: IO.STREAMFS.StreamOpen[self.file, $create ! FS.Error => {MessageWindow.Append[error.explanation, TRUE]; MessageWindow.Blink[]; GOTO Quit}];
outline: Outline ← NIL;
WITH self.data SELECT FROM
tool: Tool => {outline ← tool.outline};
ENDCASE => NULL;
WHILE outline # NIL DO
traj: Trajectory ← outline.first;
IF traj.rest # NIL THEN {
stream.PutF["%g %g MOVETO\n", IO.real[traj.rest.first.x], IO.real[traj.rest.first.y]];
FOR p: PointList ← traj.rest.rest, p.rest UNTIL p=NIL DO
stream.PutF[" %g %g LINETO\n", IO.real[p.first.x], IO.real[p.first.y]];
ENDLOOP;
IF traj.rest.first.kind # open THEN {
stream.PutRope[" CLOSE"];
};
};
outline ← outline.rest;
ENDLOOP;
stream.Close;
};
EXITS Quit => NULL;
};
PaintPoint: PROCEDURE [context: Graphics.Context, x, y: REAL] = {
Graphics.SetColor[context, Graphics.black];
Graphics.DrawBox[context, [x-2, y-2, x+2, y+2]];
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [x-1, y-1, x+1, y+1]];
};
DrawOutline: PROCEDURE [tool: Tool, context: Graphics.Context] = {
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;
Graphics.SetCP[context, start.x, start.y];
FOR p: LIST OF MarkedPoint ← t.first.rest.rest, p.rest UNTIL p=NIL DO
Graphics.DrawTo[context, p.first.x, p.first.y];
ENDLOOP;
};
ENDLOOP;
IF tool.outline # NIL THEN {
FOR p: LIST OF MarkedPoint ← tool.outline.first.rest, p.rest UNTIL p=NIL DO
PaintPoint[context, p.first.x, p.first.y];
ENDLOOP;
};
};
InvertSelection: PROCEDURE [viewer: Viewer, context: Graphics.Context] = {
tool: Tool ← NARROW[viewer.data];
[] ← Graphics.SetPaintMode[context, invert];
Graphics.SetColor[context, Graphics.black];
IF tool.selected # NIL THEN {
list: LIST OF MarkedPoint ← tool.selected.rest;
FOR i: NAT IN [0..tool.selectedCount) DO
p: MarkedPoint ← list.first;
Graphics.DrawBox[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: Graphics.Context, whatChanged: REF ANY, clear: BOOL]
= {
tool: Tool ← NARROW[self.data];
SELECT whatChanged FROM
NIL => Notify[self, LIST[$Refresh]];
$PaintAll => {
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [0, 0, self.cw, self.ch]];
Graphics.SetColor[context, Graphics.black];
DrawOutline[tool, context];
InvertSelection[self, context];
};
$TouchUp => {
Graphics.SetColor[context, Graphics.white];
FOR paintList: LIST OF PaintRectangle ← tool.paintRectangles, paintList.rest UNTIL paintList = NIL DO
box: PaintRectangle ~ paintList.first;
Graphics.DrawBox[context, [box.xMin, box.yMin, box.xMax, box.yMax]];
ENDLOOP;
Graphics.SetColor[context, Graphics.black];
FOR paintList: LIST OF PaintRectangle ← tool.paintRectangles, paintList.rest UNTIL paintList = NIL DO
box: PaintRectangle ~ paintList.first;
mark: Graphics.Mark ~ Graphics.Save[context];
Graphics.ClipBox[context, [box.xMin, box.yMin, box.xMax, box.yMax]];
DrawOutline[tool, context];
InvertSelection[self, context];
Graphics.Restore[context, mark];
ENDLOOP;
};
$NewPt => {
p: MarkedPoint ← tool.outline.first.rest.first;
Graphics.SetColor[context, Graphics.black];
IF tool.outline.first.rest.rest # NIL THEN {
q: MarkedPoint ← tool.outline.first.rest.rest.first;
Graphics.SetCP[context, q.x, q.y];
Graphics.DrawTo[context, p.x, p.y];
PaintPoint[context, q.x, q.y];
};
PaintPoint[context, p.x, p.y];
};
$InvertGrabbed => IF tool.grabbed # NIL THEN {
prevKind: PointKind ← open;
list: LIST OF MarkedPoint;
[] ← Graphics.SetPaintMode[context, invert];
list ← tool.grabbed.rest;
FOR i: NAT IN [0..tool.grabbedCount) WHILE list # NIL DO
p: MarkedPoint ← list.first;
Graphics.DrawBox[context, [p.x-2, p.y-2, p.x+2, p.y+2]];
list ← list.rest;
ENDLOOP;
Graphics.SetCP[context, 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) WHILE list # NIL DO
p: MarkedPoint ← list.first;
IF prevKind = open OR p.kind = open
THEN Graphics.SetCP[context, p.x, p.y]
ELSE Graphics.DrawTo[context, p.x, p.y];
prevKind ← p.kind;
list ← list.rest;
ENDLOOP;
};
$EraseGrabbedPoint => IF tool.grabbed # NIL THEN {
list: LIST OF MarkedPoint ← tool.grabbed.rest;
Graphics.SetColor[context, Graphics.white];
FOR i: NAT IN [0..tool.grabbedCount) WHILE list # NIL DO
p: MarkedPoint ← list.first;
Graphics.DrawBox[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];
};
};
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 ENTRY PROC [viewer: Viewer] RETURNS [Outline] ~ {
ENABLE UNWIND => NULL;
WITH viewer.data SELECT FROM
tool: Tool => {
UNTIL tool.actionQueue = NIL DO WAIT tool.actionQueueEmpty ENDLOOP;
RETURN [CopyOutline[tool.outline]]
};
ENDCASE => RETURN [NIL];
};
ObtainOutline: PUBLIC ENTRY PROC [viewer: Viewer] RETURNS [outline: Outline] ~ {
ENABLE UNWIND => NULL;
WITH viewer.data SELECT FROM
tool: Tool => {
UNTIL tool.actionQueue = NIL DO WAIT tool.actionQueueEmpty ENDLOOP;
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]]};
};
ENDCASE => RETURN [NIL];
};
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[self.data];
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;
};
$InvertSel, $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[];
};
$Refresh => QueuePaintAll[];
$Reverse => {
Queue[$InvertSel, $AutoInverse];
Queue[$ClearSel, $Idempotent];
Queue[$ReverseTrajectory, $AutoInverse];
};
$Save => {
Queue[$Save, $Idempotent];
};
$Smooth => {
Queue[$Smooth];
Records touchup box
QueueTouchUp[];
};
ENDCASE => {
WITH Atom.GetProp[atom, viewerClass] SELECT FROM
pointModifier: PointModifier => {
Queue[pointModifier];
QueueTouchUp[];
};
ENDCASE => Unknown[];
};
};
outline: Outline => {
Queue[$ClearSel, $Idempotent];
Queue[outline];
QueuePaintAll[];
};
mousePlace: TIPUser.TIPScreenCoords => {
markedPoint: MarkedPoint ← [mousePlace.mouseX, mousePlace.mouseY, 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[$GrabPt];
Queue[$DeleteGrabbed];
Records touchup box
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[];
};
$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.
NOTIFY tool.actionQueueEmpty;
In case an external client was waiting for access to the outline.
}
ELSE {
ref ← tool.actionQueue.rest.first;
tool.actionQueue.rest ← tool.actionQueue.rest.rest;
};
};
RegisterPointModifer: PUBLIC PROC [atom: ATOM, pointModifier: PointModifier] ~ {
Atom.PutProp[atom, viewerClass, pointModifier];
};
DispatchProcess: PROC [viewer: Viewer] ~ {
tool: Tool ← NARROW[viewer.data];
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;
};
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 {
BoundPoints[tool.selected, tool.selectedCount+2];
IF pointModifier.pointModifyProc[pointModifier, tool.selected, tool.selectedCount]
THEN RecordPoints[tool.selected, tool.selectedCount+2]
ELSE ResetBBox[];
}
ELSE [] ← pointModifier.pointModifyProc[pointModifier, NIL, 0];
};
outline: Outline => {
tool.outline ← outline;
};
ENDCASE => ERROR;
SELECT action FROM
$NothingMoreToDo => NULL;
$Save => {
ViewerOps.SaveViewer[viewer];
};
$StartPt => {
tool.selected ← NIL;
tool.selectedCount ← 0;
tool.grabbed ← NIL;
tool.grabbedCount ← 0;
tool.outline ← CONS[LIST[header, markedPoint], tool.outline];
ViewerOps.SetNewVersion[viewer];
};
$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[viewer];
};
$Clear => {
tool.outline.first.rest ← tool.grabbed ← tool.selected ← NIL;
tool.outline.rest ← NIL;
tool.grabbedCount ← tool.selectedCount ← 0;
ViewerOps.SetNewVersion[viewer];
};
$ClearGrabbed => {
tool.grabbed ← NIL;
tool.grabbedCount ← 0;
};
$ClearSel => {
tool.selected ← NIL;
tool.selectedCount ← 0;
tool.grabbed ← NIL;
tool.grabbedCount ← 0;
};
$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[viewer];
};
$DeleteSelected => {
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;
ViewerOps.SetNewVersion[viewer];
};
$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;
ViewerOps.SetNewVersion[viewer];
};
$GrabPt => GrabPoint[tool, markedPoint];
$MoveTo => {
markedPoint.kind ← tool.grabbed.rest.first.kind;
tool.grabbed.rest.first ← markedPoint;
ViewerOps.SetNewVersion[viewer];
};
$RecordGrabbedBox => RecordPoints[tool.grabbed, tool.grabbedCount+2];
$RecordTrajectoryBox => RecordPoints[tool.outline.first, LAST[NAT]];
$RecordSelectionBox => RecordPoints[tool.selected, tool.selectedCount];
$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[viewer];
};
$SelectPt => {
GrabPoint[tool, markedPoint];
IF tool.selected # tool.grabbed THEN {
tool.selected ← tool.grabbed;
tool.selectedCount ← tool.grabbedCount;
};
tool.grabbed ← NIL;
tool.grabbedCount ← 0;
};
$Smooth => {
IF tool.selected # NIL THEN {
list: LIST OF MarkedPoint ← tool.selected.rest;
prev: MarkedPoint ← tool.selected.first;
BBPoint[prev];
FOR i: NAT IN [0..tool.selectedCount) DO
current: MarkedPoint ← list.first;
next: MarkedPoint ← IF list.rest = NIL THEN tool.outline.first.rest.first ELSE list.rest.first;
BBPoint[current];
current.x ← (2*current.x + prev.x + next.x)/4;
current.y ← (2*current.y + prev.y + next.y)/4;
BBPoint[current];
prev ← list.first;
list.first ← current;
list ← list.rest;
IF list = NIL THEN list ← tool.outline.first.rest;
ENDLOOP;
BBPoint[list.first];
};
RecordBBox[];
ViewerOps.SetNewVersion[viewer];
};
$PaintAll, $TouchUp => {
ViewerOps.PaintViewer[viewer, client, FALSE, action];
tool.paintRectangles ← NIL;
};
$NewPt, $InvertGrabbed, $InvertSel, $EraseGrabbedPoint => {
ViewerOps.PaintViewer[viewer, client, FALSE, action];
};
ENDCASE => ERROR;
ENDLOOP;
};
MenuAction: Menus.ClickProc = {Notify[NARROW[parent], LIST[clientData]]};
AddMenuItem: PUBLIC PROC [atom: ATOM] = {
name: ROPE ~ Atom.GetPName[atom];
menu: Menus.Menu ← NARROW[viewerClass.menu];
ResetViewerMenu: PROC [v: Viewer] RETURNS [BOOLTRUE] ~ {
IF v.class = viewerClass THEN ViewerOps.SetMenu[v, menu];
};
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: name,
proc: MenuAction,
clientData: atom,
documentation: NIL
]
];
ViewerOps.EnumerateViewers[ResetViewerMenu];
};
CreateMenu: PROC RETURNS [Menus.Menu] = {
menu: Menus.Menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Clear",
proc: MenuAction,
clientData: $Clear,
documentation: "Clear the viewer"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Refresh",
proc: MenuAction,
clientData: $Refresh,
documentation: "Refresh the viewer"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Save",
proc: MenuAction,
clientData: $Save,
documentation: "Save the samples"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Smooth",
proc: MenuAction,
clientData: $Smooth,
documentation: "Smooth out the selected samples"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Reverse",
proc: MenuAction,
clientData: $Reverse,
documentation: "Reverse the order of the points in the current trajectory"
]
];
Menus.AppendMenuEntry[
menu: menu,
entry: Menus.CreateEntry[
name: "Delete",
proc: MenuAction,
clientData: $Delete,
documentation: "Delete the currently selected points"
]
];
RETURN [menu];
};
NewTIP: PUBLIC PROC = {
ResetViewerTIP: PROC [v: Viewer] RETURNS [BOOLTRUE] ~ {
IF v.class = viewerClass THEN v.tipTable ← viewerClass.tipTable;
};
viewerClass.tipTable ← TIPUser.InstantiateNewTIPTable["SampledCurveEdit.TIP"];
ViewerOps.EnumerateViewers[ResetViewerTIP];
};
viewerClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ← [
paint: PaintProc,
notify: Notify,
init: InitViewer,
save: SaveProc,
menu: CreateMenu[],
tipTable: TIPUser.InstantiateNewTIPTable["SampledCurveEdit.TIP"]
]];
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
[] ← ViewerOps.CreateViewer[$SampledCurveEdit, [name: "Sampled Curve Editor [No File]", file: NIL]]
ELSE
[] ← ViewerOps.CreateViewer[$SampledCurveEdit, [name: Rope.Concat["Sampled Curve Editor ", name], file: name]];
};
ViewerOps.RegisterViewerClass[$SampledCurveEdit, viewerClass];
Commander.Register["SampledCurveEdit", SampledCurveEditCommand, "Create a viewer to edit a sampled curve"];
END.