<> <> <> 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; <> SwitchAuto: PUBLIC PROC[handle: GraphHandle _ NIL, type: AutoType _ bounds] = { <> 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 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] = { <> 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] = { <> 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 = { <> 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 = { <> 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 = { <> IF GraphViewerExits[handle] THEN MoveGraphCaret[handle, secondary, handle.graph.caret[primary].place, TRUE]; }; -- MoveXhair2To1 <> SelectEntity: PUBLIC GraphPlaceProc = { <> 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 = { <> IF GraphViewerExits[handle] THEN RemoveEntity[handle, handle.chart.selectedEntity]; handle.chart.selectedEntity _ NIL; }; -- EraseSelEntity ShowSelEntityOnSpec: PUBLIC GraphProc = { <> 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 = { <> 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 = { <> 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] = { <> 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] = { <> 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. <> 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; <> xvl _ CONS[tx, xvl]; yvl _ CONS[ySegs.first.end, yvl]; IF NOT onIt AND NotANan[tx] THEN IF tx >= from.x THEN EXIT; <> 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] = { <> 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 { <> 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 = { <> 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 = { <> 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 = { <> 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 = { <> 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 = { <> 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 = { <> <> <> <> 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 = { <> <> <> <> 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 <> SelectText: PUBLIC GraphPlaceProc = { <> <<(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 = { <> 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 = { <> <<(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 = { <> 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 <> HalfZoomInRatio: REAL = 0.707*0.5; HalfZoomOutRatio: REAL = 1.414*0.5; ZoomIn: PUBLIC GraphPlaceProc = { <> 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 = { <> 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 = { <> 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 = { <> 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 = { <> 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