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 = { 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 = { 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 ¨GraphEdit.mesa, Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, November 25, 1985 10:42:38 pm PST procs in this module may affect either the graph viewer or the controller, or both. invoked by b or d on chart, or AutoDiv or AutoBounds on controller. IF NOT graph.auto[type] THEN Update[handle, IF type = divisions THEN $Divisions ELSE $Bounds]; invoked by T - X or T - Y on graph viewer; or menus on controller. Invoked by G - X or G - Y on graph viewer; or menus on controller. Invoked by plain red click or dragging red on graph viewer. (place is in real coordinates instead of screen coordinates.) Invoked by plain blue click on graph viewer. Invoked by 2-1 or 1-2 on graph viewer. entity Invoked by C-red on graph viewer. Invoked by C-blue on graph viewer. Invoked by Ctrl-C on graph viewer. Invoked by H-red on graph viewer. Invoked by H-blue on graph viewer. Move left horizontally toward the segment, (v1, v2), crossing the segment at (crossX, from.y). Assuming neither from.x nor from.y is NtNan. Move right horizontally toward the segment, (v1, v2), crossing the segment at (crossX, from.y). Assuming neither from.x nor from.y is NtNan. onIt: from is already on entity, and we prefer moving away than staying. not to include this segment into consideration. tx is included. onIt: from is already on entity, and we prefer moving away than staying. try to include the last point to the left of from.x. Invoked by V-red on graph viewer. Invoked by V-blue on graph viewer. Invoked by S-red on graph viewer. Invoked by n-red on graph viewer. Invoked by n-blue on graph viewer. 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. 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. text Invoked by T-red on graph viewer. (sx, sy) is the position of cursor on screen. Invoked by T-blue on graph viewer. Invoked by T-yellow on graph viewer. (sx, sy) is the position of cursor on screen. Invoked by Ctrl-T on graph viewer. zooming Invoked by Z-red on graph viewer. Invoked by z-blue on graph viewer. Invoked by z-yellow or drag z-yellow on graph viewer. Invoked by z-(yellow up) while blue is not down, when zoomPt2 is set. Invoked by z-shift. ÊטJšœÏmœ0Ïc™Lšœ™Icode™0—J˜šÏk ˜ JšœŸœ˜JšœŸœ‹Ÿœ˜šJšœ Ÿœ6˜GJšœ Ÿœ˜!Jšœ Ÿœ ˜Jšœ Ÿœ˜Jšœ Ÿœ®˜ÀJšœ Ÿœò˜JšœŸœFŸœ˜WJšœŸœ ˜JšœŸœ ˜-JšœŸœ˜"JšœŸœ˜JšœŸœ˜JšœŸœ%˜8Jšœ Ÿœ˜ —J˜šœ ŸœŸ˜JšŸœ¥˜­JšŸœ˜—J˜JšŸœ8˜<™SJ˜—šÏn œŸœŸœŸœ˜OJšœC™CšŸœŸœŸœ˜+Jšœ%˜%šŸœŸœŸœ˜šŸœ*Ÿ˜0JšœŸœŸœ Ÿœ˜G—šŸœŸœŸœ™+JšŸœŸœ Ÿœ ™2—J˜—Jš ŸœŸœŸœŸœŸœŸœ˜>J˜—Jšœž ˜J˜—š  œŸœŸœŸœ#žI˜¨šŸœŸœŸœ˜+JšŸœŸœŸœ/˜IšŸ˜JšŸœŸœ#˜@JšŸœ#˜'—šŸœŸœŸ˜šŸœ3Ÿœ˜:šœŸœŸ˜$Jšœ.Ÿœ˜E———J˜—Jšœž˜J˜—š  œŸœŸœŸœ#žQ˜®šŸœŸœŸœ˜+JšŸœŸœŸœC˜]šŸ˜JšŸœ!Ÿœ(˜OJšŸœ*˜.—šŸœŸœŸ˜šŸœ3Ÿœ˜:šœŸœŸ˜$Jšœ.Ÿœ˜E———J˜—Jšœž˜J˜—šœ ŸœŸœ˜.Jšœ#˜#šŸœŸœŸœ˜JšŸœ&Ÿœ˜Hšœ)˜)šŸœŸ˜JšœR˜R—JšŸœŸœ˜ —J˜—Jšœž˜J˜—š   œŸœŸœŸœŸœ ˜DJšœB™BšŸœŸœŸœ˜+Jšœ+˜+šŸœŸœŸ˜šŸœ/Ÿ˜5JšœŸœŸœ Ÿœ ˜>——šŸœŸœŸœ˜.JšœŸœŸœŸœ˜2—J˜—Jšœž˜J˜—š   œŸœŸœŸœŸœ ˜BJšœB™BšŸœŸœŸœ˜+JšŸœŸœŸœ0˜JJšœ#˜#šŸœŸœŸœ)Ÿ˜HJšœŸœŸœŸœ ˜6—JšŸœŸœŸœ0˜JJ˜—Jšœž ˜J˜—š  œŸœ˜&Jšœy™yšŸœŸœ˜"JšŸœ˜ JšœŸœ+˜LšŸœŸœŸœ˜Jšœ"˜"Jšœ ˜ J˜—JšŸœŸœŸœ+Ÿœ˜KJšœ˜—Jšœž˜ J˜š  œŸœ7ŸœŸœŸœž ˜…JšŸœ˜ šœŸœŸœ˜AJšŸœY˜]—šŸœŸœŸœ˜JšœŸœŸœ.Ÿœ˜nJšŸœŸœ!˜5J˜—šŸœŸœŸœ˜4JšœQ˜Q—Jšœž˜J™——š  œŸœ˜"J™,š ŸœŸœŸœ#ŸœŸœ˜UJšœŸœ˜ šŸœŸœŸ˜JšœJŸœ˜Q—JšŸœŸœŸœ&˜GJ˜—Jšœž˜J˜—š  œŸœ˜#J™&šŸœŸ˜ JšœEŸœ˜K—Jšœž˜—J˜J™š  œŸœ˜'J™!šŸœŸœ˜"šœŸœŸœ Ÿœ˜JJšœŸœ˜Jšœ˜šœ+˜+Jšœ;˜;—J˜ šŸœŸœ ŸœŸœ˜3Jšœ#Ÿœ˜)J˜!J˜—JšœŸœ˜J˜ Jšœž ˜J˜—Jšœ0Ÿœ˜8šŸœ˜ JšŸœŸœŸœ"˜Ašœ"˜"JšŸœ8˜<—Jšœ˜—J˜—Jšœž˜J˜š  œŸœŸœŸœŸœŸœ˜‡Jšœ0˜0šŸœ ŸœŸœž>˜SJš ŸœŸœŸœŸœŸœ˜.šŸœ4Ÿœ ŸœŸ˜OšŸœŸœŸœ ˜'Jšœ Ÿœ˜+JšœŸœŸœ ˜šŸœ Ÿœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜Jšœ Ÿœ˜)šŸœ ŸœŸœ˜.Jšœ ˜ Jšœ˜J˜—š ŸœŸœ ŸœŸœŸœ˜>Jšœ ˜ Jšœ˜J˜—šŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜JšœŸœ˜ Jšœ Ÿœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜š ŸœŸœ ŸœŸœ Ÿœ˜BJšœ ˜ JšœŸœ Ÿœ Ÿœ ˜/J˜—J˜—J˜—J˜—J˜JšŸœ˜—J˜—Jšœž˜—J˜š œŸœ#ŸœŸœŸœŸœŸœ˜™JšŸœŸœŸœ˜-Jšœ Ÿœ˜#JšœŸœ˜ JšœŸœ˜šŸœ&ŸœŸœŸ˜:J˜ J˜JšŸœŸœŸœ˜8Jšœ7˜7šŸœŸœ˜Jšœ˜J˜Jšœ˜J˜—J˜JšŸœ˜—Jšœž˜—J™—š œŸœ˜$J™"JšŸœŸœ3˜SJšœŸœ˜"JšŸž˜J˜—š œŸœ˜)J™"šŸœŸœŸœ˜/J˜&JšŸœ ŸœŸœ#˜7šŸœŸœŸœ˜+JšœM˜MJ˜#J˜—J˜—Jšœž˜J˜—š œŸœ˜&J™!šŸœŸœŸœ˜/JšœŸœ˜JšœŸœ˜J˜"Jšœ)˜)šŸœ,ŸœŸœŸ˜@JšœŸœ˜ Jšœ ŸœŸœ˜Jšœ4˜4š Ÿœ ŸœŸœŸœŸœŸœ˜HJšœ&˜&—JšŸœ˜—šŸœ ŸœŸœ˜Jšœ*Ÿœ˜0J˜J˜—Jšœ˜—Jšœž˜J˜—š œŸœ˜'J™"šŸœŸœŸœ˜/JšœŸœ˜JšœŸœ˜J˜"Jšœ(˜(šŸœ,ŸœŸœŸ˜@JšœŸœ˜ Jšœ ŸœŸœ˜Jšœ5˜5Jš Ÿœ ŸœŸœŸœŸœŸœ(˜nJšŸœ˜—šŸœ ŸœŸœ˜Jšœ*Ÿœ˜0J˜J˜—Jšœ˜—Jšœž˜J˜š œŸœŸœŸœ ŸœŸœŸœ˜jJšœ‹™‹Jš ŸœŸœŸœŸœŸœŸœ˜^šŸœ˜Jš œŸœŸœŸœŸœŸœ˜JšŸœŸœ&Ÿœ˜8šœ ŸœŸœŸœ˜)JšŸœ4˜8—Jšœ˜J˜—J˜—Jšœž˜—J˜š œŸœŸœŸœ ŸœŸœŸœ˜kJšœŒ™ŒJš ŸœŸœŸœŸœŸœŸœ˜^šŸœ˜Jš œŸœŸœŸœŸœŸœ˜JšŸœŸœ&Ÿœ˜8šœ ŸœŸœŸœ˜)JšŸœ4˜8—Jšœ˜J˜—J˜—Jšœž˜—J˜š œŸœŸœŸœŸœŸœŸœ ŸœŸœž˜›JšœH™HšŸœ ŸœŸœ˜Jšœ(Ÿœ˜,Jšœ1˜1Jšœ)˜)š Ÿœ ŸœŸœ ŸœŸ˜#JšœŸœ˜š ŸœŸœ ŸœŸœŸœŸœ˜7Jšœ/™/—JšœŸœ ˜JšœŸœ˜!šŸœŸœŸœ ŸœŸœŸœŸœ˜;J™—Jšœ˜Jšœ˜JšŸœ˜—Jšœ˜Jšœ˜š Ÿœ ŸœŸœŸœž3˜Sš Ÿœ ŸœŸœ ŸœŸ˜)šœ*˜*Jšœ:˜:——Jšœ˜J˜JšŸœ˜—Jšœ*˜*Jšœ*˜*J˜—Jšœž˜J˜—š œŸœŸœŸœŸœŸœŸœ ŸœŸœ˜}JšœH™HšŸœ ŸœŸœ˜Jšœ1˜1Jšœ)˜)š Ÿœ ŸœŸœ ŸœŸ˜#šŸœŸœ˜Jš ŸœŸœŸœŸœŸœ˜HJ˜—šŸœ˜J™5JšŸœŸœŸœŸœ˜Jš ŸœŸœŸœ ŸœŸœ˜RJ˜—Jšœ˜Jšœ˜JšŸœ˜—š Ÿœ Ÿœ ŸœŸœ ŸœŸ˜0š ŸœŸœŸœŸœŸ˜-šœ+˜+JšœR˜R——Jšœ˜J˜JšŸœ˜—J˜—Jšœž˜—J˜—š œŸœ˜$J™!šŸœŸœŸœ˜/Jšœ Ÿœ˜.Jšœ Ÿœ ˜.JšœŸœ˜šŸœ,ŸœŸœŸ˜@JšœŸœ˜ JšœŸœ˜JšœR˜RšŸœŸœž(˜5š œŸœŸœ ŸœŸœ Ÿœ ˜:JšŸœ4˜8—š Ÿœ Ÿœ ŸœŸœŸœŸœ˜BJ˜J˜ J˜—J˜—JšŸœ˜—šŸœ ŸœŸœ˜Jšœ$Ÿœ˜*J˜J˜—J˜—Jšœž˜J˜—š œŸœ˜&J™"šŸœŸœŸœ˜/Jšœ Ÿœ˜.Jšœ Ÿœ!˜/JšœŸœ˜šŸœ,ŸœŸœŸ˜@JšœŸœ˜ JšœŸœ˜JšœR˜RšŸœŸœž(˜5š œŸœŸœ ŸœŸœ Ÿœ ˜:JšŸœ4˜8—š Ÿœ Ÿœ ŸœŸœŸœŸœ˜BJ˜J˜ J˜—J˜—JšŸœ˜—šŸœ ŸœŸœ˜Jšœ$Ÿœ˜*J˜J˜—J˜—Jšœž˜J˜—š œŸœ˜+J™!šŸœŸœŸœ˜/šŸœŸœŸœ˜$JšœŸœ˜šœ!˜!JšœT˜T—Jšœ'Ÿœ˜-J˜—J˜—Jšœž˜J™—š œŸœ˜+J™!šŸœŸœŸœ˜/šŸœŸœŸœ˜$Jšœ Ÿœ˜-Jšœ>˜>Jšœ6˜6Jšœ"Ÿœ˜&JšœŸœ˜ šŸœŸœŸœŸ˜.Jš ŸœŸœŸœŸœŸœ˜EJšœ Ÿœ˜,Jšœ Ÿœ˜,Jšœ˜JšŸœ˜—šŸœ Ÿ˜šŸœ ŸœŸœ˜šœ˜J˜7—JšŸœ˜J˜—šŸœŸœŸœ˜?Jšœ)˜)šœ˜JšœKŸœ˜Q—J˜—J˜ Jšœ˜Jšœ˜JšŸœ˜ —JšœŸœ˜/šŸœŸœŸœ˜Jšœ"˜"Jšœ ˜ J˜—J˜—J˜—Jšœž˜J˜—š œŸœ˜,J™#šŸœŸœŸœ˜/šŸœŸœŸœ˜$Jšœ Ÿœ˜-Jšœ>˜>Jšœ6˜6JšœŸœ˜ šŸœŸœŸœŸ˜.Jš ŸœŸœŸœŸœŸœ˜DJ˜JšŸœ˜—šŸœŸœ Ÿ˜/šŸœŸœŸœ˜šœ˜J˜7—JšŸœ˜J˜—šŸœŸœŸœ˜=Jšœ'˜'šŸœŸœŸœ˜BJšœKŸœ˜Q—J˜—J˜ Jšœ˜JšŸœ˜ —JšœŸœ˜/šŸœŸœŸœ˜Jšœ"˜"Jšœ ˜ J˜—J˜—J˜—Jšœž˜J˜—š œŸœ˜'J™™J™DJ™T—šŸœŸœŸœ˜/JšœŸœ˜ JšœŸœ ˜*JšœŸœ ˜*JšœŸœ˜ šŸœŸœŸœ˜)JšœFŸœ˜Lš ŸœŸœŸœŸœŸ˜,Jšœ.Ÿœ˜4—J˜—J˜—Jšœž˜J˜—š œŸœ˜(J™‘™J™DJ™T—šŸœŸœŸœ˜/JšœŸœ˜ JšœŸœ ˜*JšœŸœ ˜*JšœŸœ˜ šŸœŸœŸœ˜)JšœG˜Gš ŸœŸœŸœŸœŸ˜,Jšœ.Ÿœ˜4—J˜—J˜—Jšœž˜—J˜J™š  œŸœ˜%J™!J™.šŸœŸœŸœ˜/šŸœŸœ˜"JšœŸœ˜Jšœ Ÿœž ˜šŸœ ŸœŸœŸ˜3Jšœ˜JšœQ˜QšŸœ-Ÿœ˜3Jšœ,ŸœŸœ˜I—JšŸœ˜—šŸœ ŸœŸœ˜šœ˜Jšœ@Ÿœ˜F—Jšœ˜J˜—J˜—J˜—Jšœž ˜J˜—š  œŸœ˜"J™"JšŸœŸœ/˜OJšœŸœ˜ Jšœž˜J˜—š œŸœ;˜RJ˜—š œŸœ:˜RJ˜š  œŸœŸœ˜;šŸœŸœŸœ˜/J˜ šŸœŸœŸœ˜Jšœ˜Jšœ'˜'Jšœ˜J˜—J˜—Jšœž˜J˜——š  œŸœ˜&J™$Jšœ-™-šŸœŸœŸœ˜/J˜ šŸœŸœŸœ˜Jšœ?Ÿœ˜EJšœ˜JšœK˜KJšœ˜J˜—J˜—Jšœž˜J˜—š œŸœ˜'J™"šŸœŸœŸœ˜+J˜ JšŸœŸœŸœ"˜4šŸœŸœŸœ˜+JšœI˜IJšœ˜J˜—J˜—Jšœž˜—J˜J™JšœŸœ ˜"JšœŸœ ˜#š œŸœ˜!J™!šŸœŸœŸœ˜/JšœŸœ$˜6JšœŸœ˜)JšœŸœ˜)šœ?˜?Jšœ0˜0—JšœŸœ˜JšœŸœŸœŸœ˜#J˜—Jšœž ˜ J™—š œŸœ˜"J™"šŸœŸœŸœ˜/JšœŸœ$˜6JšœŸœ˜*JšœŸœ˜*šœ?˜?Jšœ0˜0—JšœŸœ˜JšœŸœŸœŸœ˜#J˜—Jšœž ˜ J™—š  œŸœ˜$J™5šŸœŸœŸœ˜/šŸœ Ÿœ˜Jšœ˜J˜BJš œŸœ<˜JKšŸœ ŸœLŸœ˜fKšœ Ÿœ ˜KšœKŸœ˜RJ˜—KšŸœ Ÿœ ˜K˜—Kšœž ˜K˜š  œŸœ4Ÿœ˜Q˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜—Jšœ˜Kšœ(˜(KšœŸœ˜Kšœž˜—K˜—š œŸœ˜%KšœE™EšŸœŸœŸœ˜/JšœŸœ˜Jšœ(˜(Jšœ(˜(Jšœ(˜(Jšœ(˜(˜Jš œŸœŸœŸœŸœ ˜L—JšœŸœ˜JšœŸœŸœŸœ˜#J˜—Jšœž˜K™—š  œŸœ˜"Kšœ™šŸœŸœŸœ˜/šŸœ Ÿœ˜Jšœ˜J˜BJš œŸœ<˜JKšœKŸœ˜RKšœ ˜ J˜—KšŸœ˜K˜—Kšœž˜—J˜J˜šŸœ˜J˜/——…—YÌK