GraphEdit.mesa, Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, November 25, 1985 10:42:38 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, TRUE, TRUE, TRUE];
};
}; -- 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,
xy, IF graph.target[xy].on THEN paint ELSE erase];
};
}; -- 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, xy, graph.grids[xy], erase];
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, xy, graph.grids[xy], paint];
};
}; -- 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:
BOOL ←
TRUE] = {
-- 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: REAL ← ABS[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: BOOL ← FALSE;
[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: BOOL ← FALSE;
[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:
BOOL ←
FALSE] = {
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:
BOOL ←
FALSE] = {
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:
BOOL ←
TRUE]
RETURNS [newX:
REAL, crossed:
BOOL ←
FALSE] = {
-- 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:
BOOL ←
TRUE]
RETURNS [newX:
REAL, crossed:
BOOL ←
FALSE] = {
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:
REAL ←
IF 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:
REAL ←
IF 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, text, erase];
text.rotation ← text.rotation + degree;
PaintText[handle, text, paint];
};
};
}; -- 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, text, erase];
text.place ← ChartPosToTextPos[axesRect, [Real.Float[sx], Real.Float[sy]]];
PaintText[handle, text, paint];
};
};
}; -- 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, TRUE, TRUE, TRUE];
};
}; -- 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, TRUE, TRUE, TRUE];
};
}; -- 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]];
graph.auto[bounds] ← FALSE;
PaintAll[handle, TRUE, TRUE, TRUE];
};
}; -- 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