GraphEdit.mesa, Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, November 21, 1985 2:20:33 pm PST
DIRECTORY
Convert USING [RopeFromInt],
Graph USING [AutoType, CaretIndex, Entity, EntityList, GraphHandle, GraphProc, NullVec, SegmentData, SegmentDataList, Text, Texts, ValueList, Viewer, XY],
GraphCarets USING [Move, TurnOn, TurnOff, StartBlinking, StopBlinking],
GraphConvert USING [RopeOfSlope],
GraphCleanUp USING [CleanUpVL],
GraphOps USING [Lock, Unlock],
GraphPrivate USING [FlipToggle, GraphNumberProc, GraphPlaceProc, PaintAll, PaintGrids, PaintTarget, PaintText, RemoveText, RemoveEntity, ResumeFromPanel, ResumeEntityFields, ResumeTextFields],
GraphUtil USING [Almost, BlinkMsg, ChartPosToTextPos, ControllerNotNil, CrossSegment, GraphViewerExits, HandleNotNil, NotANan, NotANumber, RealToScreenI, RectangleValid, TextRect, ScreenIToReal, ScreenToReal, ScreenIToRealVec, SetToggleColor, ViewerNotNil],
Imager USING [Context, MaskStroke, PathProc, Rectangle, SetColor, SetStrokeWidth, VEC],
ImagerBackdoor USING [invert],
Process USING [Abort, CheckForAbort, Detach],
Real USING [Float, LargestNumber],
RealFns USING [SqRt],
Rope USING [Cat],
ViewerPrivate USING [Screen, PaintScreen, ViewerScreen],
ViewerTools USING [SetContents];
GraphEdit: CEDAR PROGRAM
IMPORTS Convert, GraphCarets, GraphCleanUp, GraphConvert, GraphOps, GraphPrivate, GraphUtil, Imager, ImagerBackdoor, Process, Real, RealFns, Rope, ViewerPrivate, ViewerTools
EXPORTS GraphPrivate = {
OPEN Graph, GraphConvert, GraphOps, GraphPrivate, GraphUtil;
procs in this module may affect either the graph viewer or the controller, or both.
SwitchAuto: PUBLIC PROC[handle: GraphHandle ← NIL, type: AutoType ← bounds] = {
invoked by b or d on chart, or AutoDiv or AutoBounds on controller.
IF HandleNotNil[handle] THEN { OPEN handle;
graph.auto[type] ← ~graph.auto[type];
IF controller # NIL THEN {
IF graph.auto[type] # controller.auto[type] THEN
FlipToggle[handle, IF type = divisions THEN $AutoDiv ELSE $AutoBounds];
IF NOT graph.auto[type] THEN Update[handle,
IF type = divisions THEN $Divisions ELSE $Bounds];
};
IF chart.viewer # NIL THEN PaintAll[handle];
};
}; -- SwitchAuto
SwitchCaretVisibility: PUBLIC PROC[handle: GraphHandle ← NIL, index: CaretIndex ← primary] = { -- invoked by ctrl-1, ctrl-2, or ctrl-3 on chart, or menus on controller.
IF HandleNotNil[handle] THEN { OPEN handle;
IF chart.viewer = NIL THEN graph.caret[index].on ← ~graph.caret[index].on
ELSE
IF graph.caret[index].on THEN GraphCarets.TurnOff[handle, index]
ELSE GraphCarets.TurnOn[handle, index];
IF controller # NIL THEN
IF graph.caret[index].on # controller.caretOn[index] THEN
FlipToggle[handle, SELECT index FROM
primary => $Primary, secondary => $Secondary, ENDCASE => $TextCaret];
};
}; -- SwitchCaretVisibility
SwitchCaretBlinking: PUBLIC PROC[handle: GraphHandle ← NIL, index: CaretIndex ← primary] = { -- shift-1, shift-2, or shift-3 on chart, or menus on controller with shift down.
IF HandleNotNil[handle] THEN { OPEN handle;
IF chart.viewer = NIL THEN chart.caretState[index].toBlink ← ~chart.caretState[index].toBlink
ELSE
IF chart.caretState[index].toBlink THEN GraphCarets.StopBlinking[handle, index]
ELSE GraphCarets.StartBlinking[handle, index];
IF handle.controller # NIL THEN
IF graph.caret[index].on # controller.caretOn[index] THEN
FlipToggle[handle, SELECT index FROM
primary => $Primary, secondary => $Secondary, ENDCASE => $TextCaret];
};
}; -- SwitchCaretBlinking
SwitchSlope: PUBLIC GraphProc = { OPEN handle;
graph.showSlope ← ~graph.showSlope;
IF controller # NIL THEN {
IF graph.showSlope # controller.slopeOn THEN FlipToggle[handle, $Slope];
ViewerTools.SetContents[controller.slope,
IF controller.slopeOn THEN
GraphConvert.RopeOfSlope[graph.caret[primary].place, graph.caret[secondary].place]
ELSE NIL];
};
}; -- SwitchSlope
SwitchTarget: PUBLIC PROC[handle: GraphHandle ← NIL, xy: XY ← x] = {
invoked by T - X or T - Y on graph viewer; or menus on controller.
IF HandleNotNil[handle] THEN { OPEN handle;
graph.target[xy].on ← ~graph.target[xy].on;
IF controller # NIL THEN
IF graph.target[xy].on # controller.targetOn[xy] THEN
FlipToggle[handle, IF xy = x THEN $TargetXOn ELSE $TargetYOn];
IF chart.viewer # NIL THEN PaintTarget[handle,
IF graph.target[xy].on THEN paint ELSE erase, xy];
};
}; -- SwitchTarget
SwitchGrid: PUBLIC PROC[handle: GraphHandle ← NIL, xy: XY ← x] = {
Invoked by G - X or G - Y on graph viewer; or menus on controller.
IF HandleNotNil[handle] THEN { OPEN handle;
IF chart.viewer # NIL THEN PaintGrids[handle, erase, xy];
graph.grids[xy] ← ~graph.grids[xy];
IF controller # NIL THEN IF graph.grids[xy] # controller.gridOn[xy] THEN
FlipToggle[handle, IF xy = x THEN $GridX ELSE $GridY];
IF chart.viewer # NIL THEN PaintGrids[handle, paint, xy];
};
}; -- SwitchGrid
SelectPoint: PUBLIC GraphPlaceProc = {
Invoked by plain red click or dragging red on graph viewer. (place is in real coordinates instead of screen coordinates.)
IF GraphViewerExits[handle] THEN {
OPEN handle;
graph.caret[primary]^ ← [on: TRUE, place: ScreenIToRealVec[handle, sx, sy]];
IF controller # NIL THEN {
ResumeFromPanel[handle, $Primary];
ResumeFromPanel[handle, $Slope];
};
IF chart.viewer # NIL THEN GraphCarets.Move[handle, sx, sy, primary, TRUE];
};
}; -- Point
MoveGraphCaret: PROC[handle: GraphHandle, index: CaretIndex, place: Imager.VEC, on: BOOLTRUE] = { -- place is in real coordinates.
OPEN handle;
graph.caret[index]^ ← IF index # text THEN [on: on, place: place]
ELSE [on: on, place: [(place.x - realRect.x)/realRect.w, (place.y - realRect.y)/realRect.h]];
IF controller # NIL THEN {
ResumeFromPanel[handle, SELECT index FROM primary => $Primary, secondary => $Secondary ENDCASE => $TextCaret];
IF index # text THEN ResumeFromPanel[handle, $Slope];
};
IF chart.viewer # NIL THEN GraphCarets.Move[handle,
RealToScreenI[handle, place.x, x], RealToScreenI[handle, place.y, y], index, on];
}; -- MoveGraphCaret
ErasePrimary: PUBLIC GraphProc = {
Invoked by plain blue click on graph viewer.
IF HandleNotNil[handle] THEN IF ViewerNotNil[handle.chart.viewer] THEN { OPEN handle;
graph.caret[primary].on ← FALSE;
IF controller # NIL THEN
controller.caretOn[primary] ← SetToggleColor[controller.swCaret[primary], FALSE];
IF handle.chart.viewer # NIL THEN GraphCarets.TurnOff[handle, primary];
};
}; -- ErasePrimary
MoveXhair2To1: PUBLIC GraphProc = {
Invoked by 2-1 or 1-2 on graph viewer.
IF GraphViewerExits[handle] THEN
MoveGraphCaret[handle, secondary, handle.graph.caret[primary].place, TRUE];
}; -- MoveXhair2To1
entity
SelectEntity: PUBLIC GraphPlaceProc = {
Invoked by C-red on graph viewer.
IF GraphViewerExits[handle] THEN {
selectEntityProc: PROC [hd: GraphHandle, sx, sy: INTEGER, procId: INT] = {
point: Imager.VEC;
entity: Entity;
[point, entity] ← ClosestPointAndEntity[hd,
ScreenIToRealVec[hd, sx, sy], hd.graph.entityList, procId];
Lock[hd];
IF hd.selectProcId = procId AND entity # NIL THEN {
MoveGraphCaret[hd, primary, point, TRUE];
hd.chart.selectedEntity ← entity;
};
hd.selectProc ← NIL;
Unlock[hd];
}; -- selectProc
handle.selectProcId ← (handle.selectProcId + 1) MOD 500;
TRUSTED {
IF handle.selectProc # NIL THEN Process.Abort[handle.selectProc];
Process.Detach[handle.selectProc ←
FORK selectEntityProc[handle, sx, sy, handle.selectProcId]];
};
};
}; -- SelectEntity
ClosestPointOnEntity: PROC[from: Imager.VEC, entity: Entity] RETURNS [point: Imager.VEC ← NullVec, dist: REAL ← Real.LargestNumber] = {
xseg: SegmentDataList ← entity.group.x.segments;
IF entity # NIL THEN -- in case it is removed during waiting period in the process.
IF entity.segments # NIL AND xseg # NIL THEN {
FOR yseg: SegmentDataList ← entity.segments, yseg.rest UNTIL yseg.rest = NIL DO
IF yseg.first.ok THEN {OPEN yseg.first;
signedD: REAL ← from.x*nx + from.y*ny - d0;
absD: REALABS[signedD];
IF absD < dist THEN {
tx: REAL ← from.x - signedD*nx;
ty: REAL ← from.y - signedD*ny;
x1: REAL ← xseg.first.end;
x2: REAL ← xseg.rest.first.end;
y1: REAL ← end;
y2: REAL ← yseg.rest.first.end;
xInx1x2: BOOL ← (tx - x1)*(tx - x2) <= 0;
IF xInx1x2 AND (ty - y1)*(ty - y2) <= 0 THEN {
dist ← absD;
point ← [tx, ty];
}
ELSE IF xInx1x2 AND (Almost[y1, y2] AND Almost[ty, y1]) THEN {
dist ← absD;
point ← [tx, ty];
}
ELSE {
dx: REAL ← from.x - x1;
dy: REAL ← from.y - y1;
d1: REAL ← dx*dx + dy*dy;
d2: REAL;
d1Small: BOOL;
dx ← from.x - x2;
dy ← from.y - y2;
d2 ← dx*dx + dy*dy;
d1Small ← d1 < d2;
IF (absD ← RealFns.SqRt[IF d1Small THEN d1 ELSE d2]) < dist THEN {
dist ← absD;
point ← IF d1Small THEN [x1, y1] ELSE [x2, y2];
};
};
};
};
xseg ← xseg.rest;
ENDLOOP;
};
}; -- ClosestPointOnEntity
ClosestPointAndEntity: PROC[handle: GraphHandle, from: Imager.VEC, entityList: EntityList, id: INT] RETURNS [point: Imager.VEC, entity: Entity ← NIL] = {
ENABLE ABORTED => {Unlock[handle]; CONTINUE};
minDist: REAL ← Real.LargestNumber;
tDist: REAL;
tPoint: Imager.VEC;
FOR el: EntityList ← entityList, el.rest UNTIL el = NIL DO
Lock[handle];
Process.CheckForAbort[];
IF handle.selectProcId # id THEN {Unlock[handle]; EXIT};
[tPoint, tDist] ← ClosestPointOnEntity[from, el.first];
IF tDist < minDist THEN {
minDist ← tDist;
entity ← el.first;
point ← tPoint;
};
Unlock[handle];
ENDLOOP;
}; -- ClosestPointAndEntity
EraseSelEntity: PUBLIC GraphProc = {
Invoked by C-blue on graph viewer.
IF GraphViewerExits[handle] THEN RemoveEntity[handle, handle.chart.selectedEntity];
handle.chart.selectedEntity ← NIL;
}; -- EraseSelEntity
ShowSelEntityOnSpec: PUBLIC GraphProc = {
Invoked by Ctrl-C on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
entity: Entity ← chart.selectedEntity;
IF entity = NIL THEN BlinkMsg["No curve selected yet."]
ELSE IF ControllerNotNil[controller] THEN {
ViewerTools.SetContents[controller.entityId, Convert.RopeFromInt[entity.id]];
ResumeEntityFields[handle, entity];
};
};
}; -- ShowSelEntityOnSpec
JumpLeftAnyCurve: PUBLIC GraphProc = {
Invoked by H-red on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
entity: Entity ← NIL;
from, newPlace: Imager.VEC;
from ← graph.caret[primary].place;
newPlace ← [-Real.LargestNumber, from.y];
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
newX: REAL;
crossed: BOOLFALSE;
[newX, crossed] ← LeftCrossOnEntity[from, el.first];
IF crossed THEN IF newX > newPlace.x AND NOT Almost[from.x, newX] THEN {
newPlace.x ← newX; entity ← el.first};
ENDLOOP;
IF entity # NIL THEN {
MoveGraphCaret[handle, primary, newPlace, TRUE];
chart.selectedEntity ← entity;
};
};
}; -- JumpLeftAnyCurve
JumpRightAnyCurve: PUBLIC GraphProc = {
Invoked by H-blue on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
entity: Entity ← NIL;
from, newPlace: Imager.VEC;
from ← graph.caret[primary].place;
newPlace ← [Real.LargestNumber, from.y];
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
newX: REAL;
crossed: BOOLFALSE;
[newX, crossed] ← RightCrossOnEntity[from, el.first];
IF crossed THEN IF newX < newPlace.x AND NOT Almost[from.x, newX] THEN {newPlace.x ← newX; entity ← el.first};
ENDLOOP;
IF entity # NIL THEN {
MoveGraphCaret[handle, primary, newPlace, TRUE];
chart.selectedEntity ← entity;
};
};
}; -- JumpRightAnyCurve
LeftCrossOnSegment: PROC[from, vl, vr: Imager.VEC] RETURNS [crossX: REAL ← 0.0, crossed: BOOLFALSE] = {
Move left horizontally toward the segment, (v1, v2), crossing the segment at (crossX, from.y). Assuming neither from.x nor from.y is NtNan.
IF NotANumber[vl.x] OR NotANumber[vl.y] OR NotANumber[vr.x] OR NotANumber[vr.y] THEN RETURN[]
ELSE {
almost: BOOL IF Almost[vl.y, vr.y] THEN Almost[from.y, vl.y] ELSE FALSE;
IF almost OR (vl.y - from.y)*(vr.y - from.y) <= 0 THEN {
crossX ← IF almost THEN MIN[from.x, vr.x]
ELSE vl.x + (from.y - vl.y)/(vr.y - vl.y)*(vr.x - vl.x);
crossed ← crossX <= from.x;
};
};
}; -- LeftCrossOnSegment
RightCrossOnSegment: PROC[from, vl, vr: Imager.VEC] RETURNS [crossX: REAL ← 0.0, crossed: BOOLFALSE] = {
Move right horizontally toward the segment, (v1, v2), crossing the segment at (crossX, from.y). Assuming neither from.x nor from.y is NtNan.
IF NotANumber[vl.x] OR NotANumber[vl.y] OR NotANumber[vr.x] OR NotANumber[vr.y] THEN RETURN[]
ELSE {
almost: BOOL IF Almost[vl.y, vr.y] THEN Almost[from.y, vl.y] ELSE FALSE;
IF almost OR (vl.y - from.y)*(vr.y - from.y) <= 0 THEN {
crossX ← IF almost THEN MAX[from.x, vl.x]
ELSE vl.x + (from.y - vl.y)/(vr.y - vl.y)*(vr.x - vl.x);
crossed ← crossX >= from.x;
};
};
}; -- RightCrossOnSegment
LeftCrossOnEntity: PROC[from: Imager.VEC, entity: Entity, onIt: BOOLTRUE] RETURNS [newX: REAL, crossed: BOOLFALSE] = { -- all in terms of real coord.
onIt: from is already on entity, and we prefer moving away than staying.
IF entity # NIL THEN {
xvl, yvl, xvlSave, yvlSave: ValueList ← NIL;
xSegs: SegmentDataList ← entity.group.x.segments;
ySegs: SegmentDataList ← entity.segments;
UNTIL xSegs = NIL OR ySegs = NIL DO
tx: REAL ← xSegs.first.end;
IF onIt AND NotANan[tx] THEN IF tx >= from.x THEN EXIT;
not to include this segment into consideration.
xvl ← CONS[tx, xvl];
yvl ← CONS[ySegs.first.end, yvl];
IF NOT onIt AND NotANan[tx] THEN IF tx >= from.x THEN EXIT;
tx is included.
xSegs ← xSegs.rest;
ySegs ← ySegs.rest;
ENDLOOP;
xvlSave ← xvl;
yvlSave ← yvl;
UNTIL (crossed OR xvl = NIL) DO -- yvl must have the same length as xvl, see above.
IF xvl.rest # NIL AND yvl.rest # NIL THEN
[newX, crossed] ← LeftCrossOnSegment[from,
[xvl.rest.first, yvl.rest.first], [xvl.first, yvl.first]];
xvl ← xvl.rest;
yvl ← yvl.rest;
ENDLOOP;
xvlSave ← GraphCleanUp.CleanUpVL[xvlSave];
yvlSave ← GraphCleanUp.CleanUpVL[yvlSave];
};
}; -- LeftCrossOnEntity
RightCrossOnEntity: PROC[from: Imager.VEC, entity: Entity, onIt: BOOLTRUE] RETURNS [newX: REAL, crossed: BOOLFALSE] = {
onIt: from is already on entity, and we prefer moving away than staying.
IF entity # NIL THEN {
xSegs: SegmentDataList ← entity.group.x.segments;
ySegs: SegmentDataList ← entity.segments;
UNTIL xSegs = NIL OR ySegs = NIL DO
IF onIt THEN {
IF NotANan[xSegs.first.end] THEN IF xSegs.first.end >= from.x THEN EXIT;
}
ELSE {
try to include the last point to the left of from.x.
IF xSegs.rest = NIL THEN EXIT;
IF NotANan[xSegs.rest.first.end] THEN IF xSegs.rest.first.end >= from.x THEN EXIT;
};
xSegs ← xSegs.rest;
ySegs ← ySegs.rest;
ENDLOOP;
UNTIL (crossed OR xSegs = NIL OR ySegs = NIL) DO
IF xSegs.rest # NIL AND ySegs.rest # NIL THEN
[newX, crossed] ← RightCrossOnSegment[from,
[xSegs.first.end, ySegs.first.end], [xSegs.rest.first.end, ySegs.rest.first.end]];
xSegs ← xSegs.rest;
ySegs ← ySegs.rest;
ENDLOOP;
};
}; -- RightCrossOnEntity
JumpUpAnyCurve: PUBLIC GraphProc = {
Invoked by V-red on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
from: Imager.VEC ← graph.caret[primary].place;
to: Imager.VEC ← [from.x, Real.LargestNumber];
entity: Entity ← NIL;
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
ok: BOOL;
vl, vr: Imager.VEC;
[ok, vl, vr] ← CrossSegment[el.first.group.x.segments, el.first.segments, from.x];
IF ok THEN { -- vl, vr don't have NtNan as component.
y: REALIF vr.x = vl.x THEN MAX[from.y, MIN[vl.y, vr.y]]
ELSE vl.y + (vr.y - vl.y)/(vr.x - vl.x)*(from.x - vl.x);
IF y > from.y AND y < to.y THEN IF NOT Almost[to.y, from.y] THEN {
entity ← el.first;
to.y ← y;
};
};
ENDLOOP;
IF entity # NIL THEN {
MoveGraphCaret[handle, primary, to, TRUE];
chart.selectedEntity ← entity;
};
};
}; -- JumpUpAnyCurve
JumpDownAnyCurve: PUBLIC GraphProc = {
Invoked by V-blue on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
from: Imager.VEC ← graph.caret[primary].place;
to: Imager.VEC ← [from.x, -Real.LargestNumber];
entity: Entity ← NIL;
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
ok: BOOL;
vl, vr: Imager.VEC;
[ok, vl, vr] ← CrossSegment[el.first.group.x.segments, el.first.segments, from.x];
IF ok THEN { -- vl, vr don't have NtNan as component.
y: REALIF vr.x = vl.x THEN MIN[from.y, MAX[vl.y, vr.y]]
ELSE vl.y + (vr.y - vl.y)/(vr.x - vl.x)*(from.x - vl.x);
IF y < from.y AND y > to.y THEN IF NOT Almost[to.y, from.y] THEN {
entity ← el.first;
to.y ← y;
};
};
ENDLOOP;
IF entity # NIL THEN {
MoveGraphCaret[handle, primary, to, TRUE];
chart.selectedEntity ← entity;
};
};
}; -- JumpDownAnyCurve
SlideOnSameCurve: PUBLIC GraphPlaceProc = {
Invoked by S-red on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
IF chart.selectedEntity # NIL THEN {
point: Imager.VEC;
[point, ] ← ClosestPointOnEntity[
[ScreenIToReal[handle, sx, x], ScreenIToReal[handle, sy, y]], chart.selectedEntity];
MoveGraphCaret[handle, primary, point, TRUE];
};
};
}; -- SlideOnSameCurve
SlideLeftNSteps: PUBLIC GraphNumberProc = {
Invoked by n-red on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
IF chart.selectedEntity # NIL THEN {
loc: Imager.VEC ← graph.caret[primary].place;
xseg: SegmentDataList ← chart.selectedEntity.group.x.segments;
yseg: SegmentDataList ← chart.selectedEntity.segments;
reversedX, reversedY: ValueList ← NIL;
n: INT ← 0;
FOR xseg ← xseg, xseg.rest UNTIL xseg = NIL DO
IF NotANan[xseg.first.end] THEN IF xseg.first.end >= loc.x THEN EXIT;
reversedX ← CONS[xseg.first.end, reversedX];
reversedY ← CONS[yseg.first.end, reversedY];
yseg ← yseg.rest;
ENDLOOP;
UNTIL n >= number DO
IF reversedX = NIL THEN {
BlinkMsg[Rope.Cat[
"Only ", Convert.RopeFromInt[n], " steps are moved."]];
EXIT;
};
IF NotANan[reversedX.first] AND NotANan[reversedY.first] THEN {
loc ← [reversedX.first, reversedY.first];
GraphCarets.Move[handle,
RealToScreenI[handle, loc.x, x], RealToScreenI[handle, loc.y, y], primary, TRUE];
};
n ← n + 1;
reversedX ← reversedX.rest;
reversedY ← reversedY.rest;
ENDLOOP;
graph.caret[primary]^ ← [on: TRUE, place: loc];
IF controller # NIL THEN {
ResumeFromPanel[handle, $Primary];
ResumeFromPanel[handle, $Slope];
};
};
};
}; -- SlideLeftNSteps
SlideRightNSteps: PUBLIC GraphNumberProc = {
Invoked by n-blue on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
IF chart.selectedEntity # NIL THEN {
loc: Imager.VEC ← graph.caret[primary].place;
xseg: SegmentDataList ← chart.selectedEntity.group.x.segments;
yseg: SegmentDataList ← chart.selectedEntity.segments;
n: INT ← 0;
FOR xseg ← xseg, xseg.rest UNTIL xseg = NIL DO
IF NotANan[xseg.first.end] THEN IF xseg.first.end > loc.x THEN EXIT;
yseg ← yseg.rest;
ENDLOOP;
FOR xseg ← xseg, xseg.rest UNTIL n >= number DO
IF xseg = NIL THEN {
BlinkMsg[Rope.Cat[
"Only ", Convert.RopeFromInt[n], " steps are moved."]];
EXIT;
};
IF NotANan[xseg.first.end] AND NotANan[yseg.first.end] THEN {
loc ← [xseg.first.end, yseg.first.end];
IF NotANan[loc.x] AND NotANan[loc.y] THEN GraphCarets.Move[handle,
RealToScreenI[handle, loc.x, x], RealToScreenI[handle, loc.y, y], primary, TRUE];
};
n ← n + 1;
yseg ← yseg.rest;
ENDLOOP;
graph.caret[primary]^ ← [on: TRUE, place: loc];
IF controller # NIL THEN {
ResumeFromPanel[handle, $Primary];
ResumeFromPanel[handle, $Slope];
};
};
};
}; -- SlideRightNSteps
JumpLeftSameCurve: PUBLIC GraphProc = {
Invoked by S-H-red on graph viewer. Go to a closest point on the selected curve to the left of cursor, where the y value is the same as cursorY.
Special cases:
If cursor is on a horizontal line segment, cursor stays where it is.
If no other point on curve of the same y value as cursorY, cursor stays where it is.
IF GraphViewerExits[handle] THEN { OPEN handle;
found: BOOL;
oldX: REAL ← graph.caret[primary].place.x;
oldY: REAL ← graph.caret[primary].place.y;
newX: REAL;
IF NotANan[oldX] AND NotANan[oldY] THEN {
[newX, found] ← LeftCrossOnEntity[[oldX, oldY], chart.selectedEntity, TRUE];
IF found THEN IF NOT Almost[newX, oldX] THEN
MoveGraphCaret[handle, primary, [newX, oldY], TRUE];
};
};
}; -- JumpLeftSameCurve
JumpRightSameCurve: PUBLIC GraphProc = {
Invoked by S-H-blue on graph viewer. Go to a closest point on the selected curve to the left of cursor, where the y value is the same as cursorY.
Special cases:
If cursor is on a horizontal line segment, cursor stays where it is.
If no other point on curve of the same y value as cursorY, cursor stays where it is.
IF GraphViewerExits[handle] THEN { OPEN handle;
found: BOOL;
oldX: REAL ← graph.caret[primary].place.x;
oldY: REAL ← graph.caret[primary].place.y;
newX: REAL;
IF NotANan[oldX] AND NotANan[oldY] THEN {
[newX, found] ← RightCrossOnEntity[[oldX, oldY], chart.selectedEntity];
IF found THEN IF NOT Almost[newX, oldX] THEN
MoveGraphCaret[handle, primary, [newX, oldY], TRUE];
};
};
}; -- JumpRightSameCurve
text
SelectText: PUBLIC GraphPlaceProc = {
Invoked by T-red on graph viewer.
(sx, sy) is the position of cursor on screen.
IF GraphViewerExits[handle] THEN { OPEN handle;
IF RectangleValid[axesRect] THEN {
gText: Text ← NIL;
hp: Imager.VEC; -- hot point
FOR t: Texts ← graph.texts, t.rest UNTIL t = NIL DO
rect: Imager.Rectangle;
[rect, hp] ← TextRect[t.first, imagerFonts[screen][t.first.fontIndex], axesRect];
IF (rect.x - sx)*(rect.x + rect.w - sx) <= 0.0 AND
(rect.y - sy)*(rect.y + rect.h - sy) <= 0.0 THEN {gText ← t.first; EXIT};
ENDLOOP;
IF gText # NIL THEN {
MoveGraphCaret[handle, text,
[ScreenToReal[handle, hp.x, x], ScreenToReal[handle, hp.y, y]], TRUE];
chart.selectedText ← gText;
};
};
};
}; -- SelectText
EraseSelText: PUBLIC GraphProc = {
Invoked by T-blue on graph viewer.
IF GraphViewerExits[handle] THEN RemoveText[handle, handle.chart.selectedText];
handle.chart.selectedText ← NIL;
}; -- EraseSelText
RotateSelTextCW: PUBLIC GraphNumberProc = {RotateSelText[handle, -10.0 * number]};
RotateSelTextCCW: PUBLIC GraphNumberProc = {RotateSelText[handle, 10.0 * number]};
RotateSelText: PROC [handle: GraphHandle, degree: REAL] = {
IF GraphViewerExits[handle] THEN { OPEN handle;
text: Text ← chart.selectedText;
IF text # NIL THEN {
PaintText[handle, erase, text];
text.rotation ← text.rotation + degree;
PaintText[handle, paint, text];
};
};
}; -- RotateSelText
MoveSelText: PUBLIC GraphPlaceProc = {
Invoked by T-yellow on graph viewer.
(sx, sy) is the position of cursor on screen.
IF GraphViewerExits[handle] THEN { OPEN handle;
text: Text ← chart.selectedText;
IF text # NIL THEN {
MoveGraphCaret[handle, text, ScreenIToRealVec[handle, sx, sy], TRUE];
PaintText[handle, erase, text];
text.place ← ChartPosToTextPos[axesRect, [Real.Float[sx], Real.Float[sy]]];
PaintText[handle, paint, text];
};
};
}; -- MoveSelText
ShowSelTextOnSpec: PUBLIC GraphProc = {
Invoked by Ctrl-T on graph viewer.
IF HandleNotNil[handle] THEN { OPEN handle;
text: Text ← chart.selectedText;
IF text = NIL THEN BlinkMsg["No text selected yet."]
ELSE IF ControllerNotNil[controller] THEN {
ViewerTools.SetContents[controller.textId, Convert.RopeFromInt[text.id]];
ResumeTextFields[handle, text];
};
};
}; -- ShowSelectedTextOnPanel
zooming
HalfZoomInRatio: REAL = 0.707*0.5;
HalfZoomOutRatio: REAL = 1.414*0.5;
ZoomIn: PUBLIC GraphPlaceProc = {
Invoked by Z-red on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
center: Imager.VEC ← ScreenIToRealVec[handle, sx, sy];
halfW: REAL ← HalfZoomInRatio*realRect.w;
halfH: REAL ← HalfZoomInRatio*realRect.h;
graph.bounds ← [xmin: center.x - halfW, ymin: center.y - halfH,
xmax: center.x + halfW, ymax: center.y + halfH];
graph.auto[bounds] ← FALSE;
PaintAll[handle];
};
}; -- ZoomIn
ZoomOut: PUBLIC GraphPlaceProc = {
Invoked by z-blue on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
center: Imager.VEC ← ScreenIToRealVec[handle, sx, sy];
halfW: REAL ← HalfZoomOutRatio*realRect.w;
halfH: REAL ← HalfZoomOutRatio*realRect.h;
graph.bounds ← [xmin: center.x - halfW, ymin: center.y - halfH,
xmax: center.x + halfW, ymax: center.y + halfH];
graph.auto[bounds] ← FALSE;
PaintAll[handle];
};
}; -- ZoomOut
ZoomPoint: PUBLIC GraphPlaceProc = {
Invoked by z-yellow or drag z-yellow on graph viewer.
IF GraphViewerExits[handle] THEN { OPEN handle;
IF zoomPt1.set THEN {
viewer: Viewer ← chart.viewer;
screen: ViewerPrivate.Screen ← ViewerPrivate.ViewerScreen[viewer];
DrawZBox: PROC [context: Imager.Context] = {DrawZoomBox[context, handle]};
IF zoomPt2.set THEN ViewerPrivate.PaintScreen[screen: screen, action: DrawZBox, suspendCarets: FALSE];
zoomPt2 ← [TRUE, sx, sy];
ViewerPrivate.PaintScreen[screen: screen, action: DrawZBox, suspendCarets: FALSE];
}
ELSE zoomPt1 ← [TRUE, sx, sy];
};
}; -- ZoomPoint
DrawZoomBox: PROC [context: Imager.Context, handle: GraphHandle] = { OPEN handle;
path: Imager.PathProc = {
moveTo[[zoomPt1.x, zoomPt1.y]];
lineTo[[zoomPt2.x, zoomPt1.y]];
lineTo[[zoomPt2.x, zoomPt2.y]];
lineTo[[zoomPt1.x, zoomPt2.y]];
};
context.SetStrokeWidth[1.0];
context.SetColor[ImagerBackdoor.invert];
context.MaskStroke[path, TRUE];
}; -- DrawZoomBox
ZoomWithZoomPts: PUBLIC GraphProc = {
Invoked by z-(yellow up) while blue is not down, when zoomPt2 is set.
IF GraphViewerExits[handle] THEN { OPEN handle;
x1, x2, y1, y2: REAL;
x1 ← ScreenToReal[handle, zoomPt1.x, x];
x2 ← ScreenToReal[handle, zoomPt2.x, x];
y1 ← ScreenToReal[handle, zoomPt1.y, y];
y2 ← ScreenToReal[handle, zoomPt2.y, y];
graph.bounds ← [
xmin: MIN[x1, x2], xmax: MAX[x1, x2], ymin: MIN[y1, y2], ymax: MAX[y1, y2]];
PaintAll[handle];
};
}; -- ZoomWithZoomPts
ClearZoomBox: PUBLIC GraphProc = {
Invoked by z-shift.
IF GraphViewerExits[handle] THEN { OPEN handle;
IF zoomPt2.set THEN {
viewer: Viewer ← chart.viewer;
screen: ViewerPrivate.Screen ← ViewerPrivate.ViewerScreen[viewer];
DrawZBox: PROC [context: Imager.Context] = {DrawZoomBox[context, handle]};
ViewerPrivate.PaintScreen[screen: screen, action: DrawZBox, suspendCarets: FALSE];
zoomPt2 ← [];
}
ELSE zoomPt1 ← [];
};
}; -- ClearZoomBox
}.
LOG.
SChen, create at October 9, 1985 6:16:32 pm PDT