DIRECTORY Imager USING [Context, SetColor, MaskRectangle, SetFont, MaskVector, Color, Font, SetXY, ShowChar, ShowRope, DoSaveAll, RotateT, Move], ImagerBackdoor USING [GetBounds, invert], ImagerColor USING [ColorFromRGB], ImagerDitherContext USING [MakeSpecialColor], ImagerFont USING [Scale, Find, RopeEscapement, FontBoundingBox], Draw2d, Menus USING [Menu, CreateMenu, AppendMenuEntry, ReplaceMenuEntry, CreateEntry, MouseButton, MenuEntry], ViewerClasses USING [Viewer, ViewerRec, ViewerClass, ViewerClassRec, NotifyProc, PaintProc, ModifyProc, DestroyProc, ScrollProc, HScrollProc, AdjustProc, GetProc], ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, DestroyViewer], TIPUser USING [InstantiateNewTIPTable ,TIPScreenCoords], Icons USING [NewIconFromFile], InputFocus USING [GetInputFocus, SetInputFocus], IO USING [PutFR, real], Convert USING [RopeFromInt], Vector2 USING [VEC], Rope USING [ROPE, Cat, Length, InlineFetch], Real USING [InlineFixI, InlineFix, Floor], PlotGraph; PlotGraphImpl: CEDAR MONITOR IMPORTS Imager, ImagerColor, ImagerDitherContext, ImagerFont, ImagerBackdoor, Draw2d, IO, Convert, Menus, ViewerOps, TIPUser, InputFocus, Rope, Real, Icons EXPORTS PlotGraph ~ BEGIN OPEN PlotGraph; VEC: TYPE ~ Vector2.VEC; PlotList: TYPE ~ LIST OF Plot; AxisList: TYPE ~ LIST OF Axis; GraphList: TYPE ~ LIST OF Graph; Viewer: TYPE ~ ViewerClasses.Viewer; Color: TYPE ~ Imager.Color; ViewerData: TYPE ~ REF ViewerDataRec; ViewerDataRec: TYPE ~ RECORD [ plot: Plot, -- circular references... liasonEntry: ARRAY BOOLEAN OF Menus.MenuEntry, magEntry: ARRAY BOOLEAN OF Menus.MenuEntry, xScale: REAL _ 0.125, --mag plot width / plot width xOr: REAL _ 0.0, --mag origin _ plot origin+ plot width*xOr frozen: BOOLEAN _ FALSE,--the display is no longer refreshed by outside requests grid: BOOLEAN _ FALSE, --an oscilloscope grid is painted on the viewer (if axis are coherent) magOn: BOOL _ FALSE, --state of the magnifier upToDate: BOOL _ FALSE --the data is valid for painting ]; DrawPtData: TYPE ~ REF DrawPtDataRec; DrawPtDataRec: TYPE ~ RECORD [ window: Rectangle, plotOr: VEC, -- physical coordinates or: VEC, -- client coordinates prevPt: VEC, mult: VEC, context: Imager.Context, visible: BOOLEAN _ TRUE, firstPoint: BOOLEAN _ TRUE, viewerData: ViewerData ]; debugPlot: Plot _ NIL; debugAxis: Axis _ NIL; debugGraph: Graph _ NIL; CreatePlot: PUBLIC PROC [name: ROPE _ NIL] RETURNS [plot: Plot] ~ { plot _ NEW[PlotRec]; plot.name _ NEW[ROPE _ name]; plot.private _ NEW[PlotGraphPrivateRec]; CreatePlotViewer[plot]; }; CreatePlotViewer: PROC [plot: Plot] ~ { menu: Menus.Menu; resetEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "Reset", proc: SetDefaults, clientData: NIL, documentation: "Reset graphViewer" ]; magOffEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "MagOff", proc: MagOnOff, clientData: false, documentation: "Magnifier switch" ]; magOnEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "MagOn", proc: MagOnOff, clientData: true, documentation: "Magnifier switch" ]; gratEntry: Menus.MenuEntry _ Menus.CreateEntry[ -- which correspond to the "grid" things name: "Grat", proc: GridOnOff, clientData: NIL, documentation: "Draw time graticule" ]; shotEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "I.P.", proc: IPBut, clientData: NIL, documentation: "Creates Interpress master" ]; activeEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "Active", proc: Freeze, clientData: true, documentation: "Stops interactive refresh" ]; frozenEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "Frozen", proc: Freeze, clientData: false, documentation: "Stops interactive refresh" ]; sizeXEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "sizeX", proc: ScaleX, clientData: NIL, documentation: "Zoom X" ]; magSizeEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "MagSizeX", proc: MagScaleX, clientData: NIL, documentation: "Zoom X mag" ]; sizeYEntry: Menus.MenuEntry _ Menus.CreateEntry[ name: "sizeY", proc: ScaleY, clientData: NIL, documentation: "Zoom Y" ]; viewerData: ViewerData _ NEW[ViewerDataRec]; viewerData.magEntry _ [magOffEntry, magOnEntry]; viewerData.liasonEntry _ [frozenEntry, activeEntry]; plot.private _ NEW[PlotGraphPrivateRec]; viewerData.plot _ plot; menu _ Menus.CreateMenu[]; Menus.AppendMenuEntry[menu: menu, entry: resetEntry]; Menus.AppendMenuEntry[menu: menu, entry: magOffEntry]; Menus.AppendMenuEntry[menu: menu, entry: gratEntry]; Menus.AppendMenuEntry[menu: menu, entry: shotEntry]; Menus.AppendMenuEntry[menu: menu, entry: activeEntry]; Menus.AppendMenuEntry[menu: menu, entry: sizeXEntry]; Menus.AppendMenuEntry[menu: menu, entry: magSizeEntry]; Menus.AppendMenuEntry[menu: menu, entry: sizeYEntry]; plot.private.viewer _ ViewerOps.CreateViewer[ flavor: $PlotGViewer, info: [ name: plot.name^, menu: menu, iconic: TRUE, column: left, scrollable: TRUE, hscrollable: TRUE, border: TRUE, guardDestroy: TRUE, -- window menu guards destroy data: viewerData ] ]; }; CreateChild: PUBLIC PROC [mom: Plot] RETURNS [baby: Plot] ~ { tempAxis: AxisList; baby _ NEW[PlotRec _ mom^]; baby.name _ NEW[ROPE _ Rope.Cat[mom.name^, "+"]]; baby.axis _ LIST[NIL]; tempAxis _ baby.axis; FOR iAxisList: AxisList _ mom.axis, iAxisList.rest UNTIL iAxisList=NIL DO tempAxis.rest _ LIST[NEW[AxisRec _ iAxisList.first^]]; tempAxis _ tempAxis.rest; ENDLOOP; baby.axis _ baby.axis.rest; }; AwakeOthers: ENTRY PROC [plot: Plot] ~ { ENABLE UNWIND => NULL; BROADCAST plot.private.unlocked; }; LockPlot: PUBLIC ENTRY PROC [plot: Plot] ~{ ENABLE UNWIND => NULL; IF plot=NIL THEN RETURN; IF plot.private=NIL THEN RETURN; WHILE plot.private.locked DO WAIT plot.private.unlocked; ENDLOOP; IF plot.private=NIL THEN RETURN; -- If somebody destroyed the plot meanwhile plot.private.locked _ TRUE; }; UnlockPlot: PUBLIC ENTRY PROC [plot: Plot]~{ ENABLE UNWIND => NULL; IF plot=NIL THEN RETURN; IF plot.private=NIL THEN RETURN; plot.private.locked _ FALSE; BROADCAST plot.private.unlocked; }; WritePtV: PROC [x, y: REAL, data: REF ANY _ NIL, rope: ROPE _ NIL] RETURNS [quit: BOOL _ FALSE] ~ { x0, y0: REAL; n: INT; r: ROPE; ptData: DrawPtData ~ NARROW[data]; IF ~ptData.viewerData.upToDate THEN RETURN[TRUE]; n _ Real.InlineFix[y]; IF (Real.InlineFix[ptData.prevPt.y] = n) AND rope=NIL THEN RETURN; x0 _ (x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x; IF x0ptData.window.x+ptData.window.w THEN RETURN; y0 _ ptData.plotOr.y; Imager.MaskVector[ptData.context, [x0, y0], [x0, y0+tinyTick]]; r _ IF rope~=NIL THEN rope ELSE Convert.RopeFromInt[n, 16, FALSE]; ShowRopeV[ptData.context, r, [x0 - 2.0, y0+spaceOverTick]]; ptData.prevPt _ [x0, y]; }; WritePtH: PROC [x, y: REAL, data: REF ANY _ NIL, rope: ROPE _ NIL] RETURNS [quit: BOOL _ FALSE] ~ { x0, y0: REAL; n: INT; r: ROPE; ptData: DrawPtData ~ NARROW[data]; IF ~ptData.viewerData.upToDate THEN RETURN[TRUE]; n _ Real.InlineFix[y]; IF (Real.InlineFix[ptData.prevPt.y] = n) AND rope=NIL THEN RETURN; x0 _ (x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x; IF x0ptData.window.x+ptData.window.w THEN RETURN; y0 _ ptData.plotOr.y; Imager.MaskVector[ptData.context, [x0, y0], [x0, y0+tinyTick]]; Imager.SetXY[ptData.context, [x0 - 2.0, y0+spaceOverTick]]; r _ IF rope~=NIL THEN rope ELSE Convert.RopeFromInt[n, 16, FALSE]; Imager.ShowRope[ptData.context, r]; ptData.prevPt _ [x0, y]; }; DrawPt: PROC [x, y: REAL, data: REF ANY _ NIL, rope: ROPE _ NIL] RETURNS [quit: BOOL _ FALSE] ~ { pt: VEC; visible: BOOLEAN; ptData: DrawPtData ~ NARROW[data]; IF ~ptData.viewerData.upToDate THEN RETURN[TRUE]; IF ptData.firstPoint THEN { ptData.prevPt _ [(x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x, (y+ptData.or.y)*ptData.mult.y+ptData.plotOr.y]; ptData.firstPoint _ FALSE; ptData.visible _ IsPtInArea[ptData.prevPt, ptData.window]; } ELSE { pt _ [(x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x, (y+ptData.or.y)*ptData.mult.y+ptData.plotOr.y]; visible _ IsPtInArea[pt, ptData.window]; SELECT TRUE FROM visible AND ptData.visible => Imager.MaskVector[ptData.context, ptData.prevPt, pt]; visible AND ~ptData.visible => Imager.MaskVector[ptData.context, ClipVect[pt, ptData.prevPt, ptData.window], pt]; ~visible AND ptData.visible => Imager.MaskVector[ptData.context, ptData.prevPt, ClipVect[ptData.prevPt, pt, ptData.window]]; ~visible AND ~ptData.visible => { pt1, pt2: VEC; IF pt.y~=ptData.prevPt.y THEN { pt1 _ ClipVect[ptData.prevPt, pt, ptData.window]; pt2 _ ClipVect[pt, ptData.prevPt, ptData.window]; IF pt1.x~=pt2.x OR pt1.y~=pt2.y THEN Imager.MaskVector[ptData.context, pt1, pt2]; } }; ENDCASE; ptData.prevPt _ pt; ptData.visible _ visible; } }; RefreshPlot: PUBLIC PROC [plot: Plot, axis: AxisList _ NIL, graphs: GraphList _ NIL, within: Rectangle _ WorldRectangle, eraseFirst: BOOL _ FALSE] ~ { viewerData: ViewerData; IF plot.private=NIL THEN CreatePlotViewer[plot]; IF plot.private.viewer.iconic THEN RETURN; viewerData _ NARROW[plot.private.viewer.data]; IF viewerData.frozen THEN RETURN; viewerData.upToDate _ FALSE; RefreshScreen[plot, axis, graphs, within, eraseFirst]; }; RefreshScreen: PROC [plot: Plot, axis: AxisList _ NIL, graphs: GraphList _ NIL, within: Rectangle _ WorldRectangle, eraseFirst: BOOL _ FALSE] ~ { DoRefreshScreen: PROC [viewer: Viewer, context: Imager.Context] ~ { axisFound, graphFound, gridOK: BOOL; quit: BOOL _ FALSE; wwhite, rred, ggreen, bblue, ppuce: Imager.Color; axisChoice: BOOLEAN = ~(axis=NIL); --test only once the presence of an axis specification graphsChoice: BOOLEAN = ~(graphs=NIL); -- the same for graphs viewerData: ViewerData ~ NARROW[viewer.data]; xoffset, inter: REAL _ 0.0; hAxis: ARRAY DrawingStyle OF REAL _ ALL[0.0]; colorList: LIST OF Color; selectedAxis: Axis _ GetSelectedAxis[plot]; ptData: DrawPtData _ NEW[DrawPtDataRec _ [ window: WorldRectangle, plotOr: [xSpace, ySpace], or: [0.0, 0.0], prevPt: [0.0, 0.0], mult: [1.0, 1.0], context: context, firstPoint: TRUE, viewerData: viewerData ]]; viewerData.upToDate _ TRUE; IF viewer.column= color THEN { wwhite _ grey; rred _ red; ggreen _ green; bblue _ blue; ppuce _ puce; } ELSE { wwhite _ white; rred _ black; ggreen _ black; bblue _ black; ppuce _ black; }; Imager.SetFont[context, currentFont]; IF eraseFirst THEN { Imager.SetColor[context, wwhite]; Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]]; -- fill screen FOR iTextList: LIST OF PlotText _ plot.texts, iTextList.rest UNTIL iTextList=NIL DO IF iTextList.first.wrt=NIL THEN { RotateAndShow: PROC ~ { Imager.SetColor[context, bblue]; Imager.SetXY[context, [ptData.plotOr.x+xScaled, ptData.plotOr.y+yScaled]]; Imager.Move[context]; Imager.RotateT[context, iTextList.first.rotation]; Imager.SetXY[context, [x, y]]; Imager.ShowRope[context, iTextList.first.contents]; }; x, y: REAL; xScaled: REAL _ iTextList.first.bounds.x*viewer.cw; yScaled: REAL _ iTextList.first.bounds.y*viewer.ch; x _ SELECT iTextList.first.justifyX FROM left => 0.0, center => MAX[0.0, (xScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].x)*0.5], right => MAX[0.0, xScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].x], ENDCASE => 0.0; y _ SELECT iTextList.first.justifyY FROM bottom => 0.0, center => MAX[0.0, (yScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].y)*0.5], top => MAX[0.0, yScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].y], ENDCASE => 0.0; Imager.DoSaveAll[context, RotateAndShow]; }; ENDLOOP; }; IF plot.axis=NIL THEN RETURN; gridOK _ viewerData.grid; hAxis _ SetHeights[plot]; IF gridOK AND eraseFirst THEN { x, y: REAL; unit: REAL _ NormalizeScaleAndOrigin[plot]; Imager.SetColor[context, ggreen]; x _ ptData.plotOr.x; y _ viewer.ch-2*ySpace; Imager.MaskVector[context, [x, y], [x+viewer.cw, y]]; y _ y-3.0; FOR i: INT IN [1..19] DO x _ ptData.plotOr.x+i*viewer.cw/20.0; Imager.MaskVector[context, [x, y], [x, y+6.0]]; ENDLOOP; ShowRope[ptData.plotOr.x+viewer.cw/20.0, y+12.0, IO.PutFR["%g", IO.real[unit]], context, bblue]; ShowRope[ptData.plotOr.x+4.0, y-12.0, IO.PutFR["%g", IO.real[plot.axis.first.bounds.x]], context, bblue] }; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO IF axisChoice THEN { axisFound _ FALSE; FOR jAxisList: AxisList _ axis, jAxisList.rest UNTIL jAxisList=NIL DO IF jAxisList.first=iAxisList.first THEN { axisFound _ TRUE; EXIT; }; ENDLOOP; } ELSE axisFound _ TRUE; IF axisFound THEN { xoffset _ 2.0; colorList _ graphColorList; inter _ AxisHeight[iAxisList.first, hAxis]; ptData.or.x _ -iAxisList.first.bounds.x; ptData.or.y _ -iAxisList.first.bounds.y; ptData.mult.x _ viewer.cw/iAxisList.first.bounds.w; ptData.mult.y _ (inter-charHeight)/iAxisList.first.bounds.h; ptData.window _ [ptData.plotOr.x, ptData.plotOr.y, viewer.cw, inter]; IF eraseFirst THEN { IF iAxisList.first.axisData[X].visible THEN { Imager.SetColor[context, ggreen]; Imager.MaskVector[context, ptData.plotOr, [ptData.plotOr.x+viewer.cw, ptData.plotOr.y]]; }; IF iAxisList.first.axisData[Y].visible THEN { Imager.SetColor[context, ggreen]; Imager.MaskVector[context, ptData.plotOr, [ptData.plotOr.x, ptData.plotOr.y+inter]]; }; IF iAxisList.first=selectedAxis THEN ShowRopeInv[ptData.plotOr.x+xAxisName, ptData.plotOr.y+yAxisName, iAxisList.first.name, context, wwhite, bblue] ELSE ShowRopeInv[ptData.plotOr.x+xAxisName, ptData.plotOr.y+yAxisName, iAxisList.first.name, context, bblue, wwhite]; }; FOR iGraphList: GraphList _ iAxisList.first.graphs, iGraphList.rest UNTIL iGraphList=NIL DO IF graphsChoice THEN { graphFound _ FALSE; FOR jGraphList: GraphList _ graphs, jGraphList.rest UNTIL jGraphList=NIL DO IF jGraphList.first=iGraphList.first THEN { graphFound _ TRUE; EXIT; }; ENDLOOP; IF ~graphFound THEN LOOP; }; ptData.firstPoint _ TRUE; Imager.SetColor[context, colorList.first]; SELECT iAxisList.first.style FROM analog => quit _ iGraphList.first.class.enumerate[plot, iGraphList.first, within, DrawPt, ptData]; hexaH => quit _ iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtH, ptData]; hexaV => quit _ iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtV, ptData]; ENDCASE; IF quit THEN RETURN; IF eraseFirst THEN ShowRope[ptData.plotOr.x+xoffset, ptData.plotOr.y+inter*0.5, iGraphList.first.name, context, colorList.first]; xoffset _ xoffset+ImagerFont.RopeEscapement[currentFont, iGraphList.first.name].x+5.0; IF colorList.rest#NIL THEN colorList _ colorList.rest ELSE colorList _ graphColorList; ENDLOOP; }; ptData.plotOr.y _ ptData.plotOr.y+inter; ENDLOOP; IF ~viewerData.magOn AND eraseFirst THEN DrawMag[viewer, context]; }; viewerData: ViewerData _ NARROW[plot.private.viewer.data]; IF plot#viewerData.plot THEN ERROR; viewerData.upToDate _ FALSE; DrawInViewer[plot.private.viewer, DoRefreshScreen, FALSE]; }; ProduceIPMaster: PUBLIC PROC [plot: Plot] ~ { }; DeletePlot: PUBLIC PROC [plot: Plot] ~ { viewer: Viewer _ plot.private.viewer; ViewerOps.DestroyViewer[viewer]; }; SetDefaults: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; plot: Plot _ viewerData.plot; IF viewerData.magOn THEN MagOnOff[viewer, true, yellow, FALSE, FALSE]; LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.x _ plot.lowerBounds.x; iAxisList.first.bounds.y _ plot.lowerBounds.y; iAxisList.first.bounds.w _ plot.upperBounds.x - plot.lowerBounds.x; iAxisList.first.bounds.h _ plot.upperBounds.y - plot.lowerBounds.y; ENDLOOP; UnlockPlot[plot]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]; }; GridOnOff: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; viewerData.grid _ ~viewerData.grid; RefreshScreen[plot: viewerData.plot, within: WorldRectangle, eraseFirst: TRUE] }; IPBut: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; ProduceIPMaster[viewerData.plot]; }; Freeze: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; plot: Plot ~ viewerData.plot; liasonState: BOOLEAN _ NARROW[clientData, REF BOOLEAN]^; Menus.ReplaceMenuEntry[viewer.menu, viewerData.liasonEntry[liasonState], viewerData.liasonEntry[~liasonState]]; viewerData.frozen _ ~viewerData.frozen; ViewerOps.PaintViewer[viewer, menu]; }; MagScaleX: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; plot: Plot ~ viewerData.plot; alpha: REAL; IF mouseButton=yellow THEN RETURN; alpha _ IF mouseButton=red THEN 0.5 ELSE 2.0; IF shift THEN alpha _ alpha*alpha; IF viewerData.xScale*alpha>1.0 THEN RETURN; IF viewerData.magOn THEN { viewerData.xScale _ viewerData.xScale*alpha; PlotScaleX[plot, alpha]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] } ELSE { MagExpand: PROC [viewer: Viewer, context: Imager.Context] ~ { zip: Draw2d.Zip; viewerData: ViewerData ~ NARROW[viewer.data]; x: REAL _ viewer.cw*viewerData.xOr+viewer.cw*viewerData.xScale; Imager.SetColor[context, tempColor]; zip _ Draw2d.GetZip[context]; Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip]; viewerData.xScale _ viewerData.xScale*alpha; Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip]; Draw2d.ReleaseZip[zip]; }; DrawInViewer[viewer, MagExpand, FALSE]; }; }; ScaleX: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; plot: Plot ~ viewerData.plot; alpha: REAL _ 2.0; IF mouseButton=yellow THEN RETURN; IF mouseButton=red THEN alpha _ 0.5; IF shift THEN alpha _ alpha*alpha; viewerData.xScale _ MIN[1.0, viewerData.xScale/alpha]; IF ~viewerData.magOn THEN { PlotScaleX[plot, alpha]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; }; ScaleY: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; plot: Plot ~ viewerData.plot; alpha: REAL _ 2.0; IF mouseButton=yellow THEN RETURN; IF mouseButton=red THEN alpha _ 0.5; IF shift THEN alpha _ alpha*alpha; LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.h _ alpha*iAxisList.first.bounds.h; ENDLOOP; UnlockPlot[plot]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; MagOnOff: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { viewer: Viewer ~ NARROW[parent]; viewerData: ViewerData ~ NARROW[viewer.data]; plot: Plot ~ viewerData.plot; magState: BOOLEAN _ NARROW[clientData, REF BOOLEAN]^; IF magState#viewerData.magOn THEN ERROR; -- ????? Menus.ReplaceMenuEntry[viewer.menu, viewerData.magEntry[magState], viewerData.magEntry[~magState]]; ViewerOps.PaintViewer[viewer, menu]; IF viewerData.magOn THEN { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.w _ iAxisList.first.bounds.w/viewerData.xScale; iAxisList.first.bounds.x _ iAxisList.first.bounds.x-iAxisList.first.bounds.w*viewerData.xOr; ENDLOOP; UnlockPlot[plot]; } ELSE { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.x _ iAxisList.first.bounds.x+iAxisList.first.bounds.w*viewerData.xOr; iAxisList.first.bounds.w _ viewerData.xScale*iAxisList.first.bounds.w; ENDLOOP; UnlockPlot[plot]; }; viewerData.magOn _ ~viewerData.magOn; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]; }; PaintProc: ViewerClasses.PaintProc ~ { -- repaint screen for updates SELECT whatChanged FROM NIL => { -- window resized, redraw viewerData: ViewerData ~ NARROW[self.data]; RefreshScreen[plot: viewerData.plot, within: WorldRectangle, eraseFirst: TRUE] }; ENDCASE => { -- call path for DrawInViewer NARROW[whatChanged, REF PROC[Viewer, Imager.Context]]^[self, context]; }; }; NotifyProc: ViewerClasses.NotifyProc ~ { x, y, t, v: REAL; viewerData: ViewerData ~ NARROW[self.data]; plot: Plot ~ viewerData.plot; IF InputFocus.GetInputFocus[].owner#self THEN InputFocus.SetInputFocus[self, plot]; IF ISTYPE[input.first, TIPUser.TIPScreenCoords] -- If input is coords from mouse THEN { mousePos: TIPUser.TIPScreenCoords _ NARROW[input.first]; x _ mousePos.mouseX; y _ mousePos.mouseY; IF ISTYPE[input.rest.first, ATOM] THEN { actionName: ATOM _ NARROW[input.rest.first]; thisAxis: Axis; [thisAxis, t, v] _ GetAxis[self, x, y]; SELECT actionName FROM $Refresh => { RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $WriteCoord => IF thisAxis#NIL THEN{ IF thisAxis.style=analog THEN DrawRope[[x, y], IO.PutFR["t:%g,v:%g", IO.real[t], IO.real[v]], self, IF self.column= color THEN red ELSE black] ELSE DrawRope[[x, y], IO.PutFR["t:%d", IO.real[t]], self, IF self.column= color THEN red ELSE black]; RETURN; }; $MoveMag => { MoveMag: PROC [viewer: Viewer, context: Imager.Context] ~ { zip: Draw2d.Zip; viewerData: ViewerData ~ NARROW[viewer.data]; xx: REAL _ viewer.cw*viewerData.xOr; Imager.SetColor[context, tempColor]; zip _ Draw2d.GetZip[context]; Draw2d.Line[context, [xx, 0.0], [xx, viewer.ch], solid, zip]; xx _ xx+viewer.cw*viewerData.xScale; Draw2d.Line[context, [xx, 0.0], [xx, viewer.ch], solid, zip]; viewerData.xOr _ x/viewer.cw; Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip]; xx _ x+viewer.cw*viewerData.xScale; Draw2d.Line[context, [xx, 0.0], [xx, viewer.ch], solid, zip]; Draw2d.ReleaseZip[zip]; }; IF ~viewerData.magOn THEN DrawInViewer[self, MoveMag, FALSE]; }; $SelectAxis => { SelectAxis[plot, thisAxis]; }; $MoveAxis => { selectedAxis: Axis _ GetSelectedAxis[plot]; [] _ MoveAxis[plot, selectedAxis, thisAxis]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $CopyAxis => { selectedAxis: Axis _ GetSelectedAxis[plot]; [] _ InsertAxis[plot, CopyAxis[thisAxis], selectedAxis]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $RevMoveAxis => { selectedAxis: Axis _ GetSelectedAxis[plot]; [] _ MoveAxis[plot, thisAxis, selectedAxis]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $DelClickedAxis => { [] _ DeleteAxis[plot, thisAxis]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $XChange => { selectedAxis: Axis _ GetSelectedAxis[plot]; [] _ XChangeAxis[plot, thisAxis, selectedAxis]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $DelSelection => { DeleteSelection[plot]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $Fork => { ForkAxis[plot, thisAxis]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; $Join => { selectedAxis: Axis _ GetSelectedAxis[plot]; thisGraph: Graph _ thisAxis.graphs.first; IF thisGraph.name=NIL THEN thisGraph.name _ thisAxis.name; [] _ MoveGraphInAxis[plot, selectedAxis, thisGraph]; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; ENDCASE => { IF plot.eventProc#NIL THEN plot.eventProc[plot, thisAxis, [t, v], actionName]; }; }; }; }; ModifyProc: ViewerClasses.ModifyProc ~ { viewerData: ViewerData ~ NARROW[self.data]; plot: Plot ~ viewerData.plot; SELECT change FROM set => NULL; push => NULL; pop => NULL; kill => SelectAxis[plot, NIL]; ENDCASE; }; DestroyProc: ViewerClasses.DestroyProc ~ { viewerData: ViewerData ~ NARROW[self.data]; plot: Plot _ viewerData.plot; LockPlot[plot]; plot.axis _ NIL; AwakeOthers[plot]; plot.private _ NIL; }; GetProc: ViewerClasses.GetProc ~ { viewerData: ViewerData ~ NARROW[self.data]; plot: Plot _ viewerData.plot; WITH plot.selection.first SELECT FROM axis: Axis => RETURN[axis.name]; graph: Graph => RETURN[graph.name]; text: PlotText => RETURN[text.contents]; ENDCASE => NULL; }; VScrollProc: ViewerClasses.ScrollProc ~ { viewerData: ViewerData ~ NARROW[self.data]; plot: Plot _ viewerData.plot; IF plot.axis=NIL THEN RETURN; SELECT op FROM up => { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.y _ iAxisList.first.bounds.y - amount*iAxisList.first.bounds.h/self.ch; ENDLOOP; UnlockPlot[plot]; }; down => { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.y _ iAxisList.first.bounds.y + amount*iAxisList.first.bounds.h/self.ch; ENDLOOP; UnlockPlot[plot]; }; thumb => { newY: REAL _ amount*0.01*(plot.upperBounds.y-plot.lowerBounds.y)+plot.lowerBounds.y; LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.y _ newY; ENDLOOP; UnlockPlot[plot]; }; query => { top _ 0; bottom _ 100; IF plot.upperBounds.y=plot.lowerBounds.y THEN RETURN; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO top _ MAX[top, Real.InlineFixI[100.0*(plot.upperBounds.y-iAxisList.first.bounds.y-iAxisList.first.bounds.h) /(plot.upperBounds.y-plot.lowerBounds.y)]]; bottom _ MIN[bottom, Real.InlineFixI[100.0*(plot.upperBounds.y-iAxisList.first.bounds.y) /(plot.upperBounds.y-plot.lowerBounds.y)]]; ENDLOOP; RETURN; }; ENDCASE; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; HScrollProc: ViewerClasses.HScrollProc ~ { viewerData: ViewerData ~ NARROW[self.data]; plot: Plot _ viewerData.plot; IF plot.axis=NIL THEN RETURN; SELECT op FROM left => { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.x _ iAxisList.first.bounds.x + amount*iAxisList.first.bounds.w/self.cw; ENDLOOP; UnlockPlot[plot]; }; right => { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.x _ iAxisList.first.bounds.x - amount*iAxisList.first.bounds.w/self.cw; ENDLOOP; UnlockPlot[plot]; }; thumb => { newX: REAL _ amount*0.01*(plot.upperBounds.x-plot.lowerBounds.x)+plot.lowerBounds.x; LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.x _ newX; ENDLOOP; UnlockPlot[plot]; }; query => { left _ 0; right _ 100; IF plot.upperBounds.x=plot.lowerBounds.x THEN RETURN; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO left _ MAX[left, Real.InlineFixI[100.0*(iAxisList.first.bounds.x-plot.lowerBounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]]; right _ MIN[right, Real.InlineFixI[100.0*(iAxisList.first.bounds.x+iAxisList.first.bounds.w-plot.lowerBounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]]; ENDLOOP; RETURN; }; ENDCASE; RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE] }; AdjustProc: ViewerClasses.AdjustProc ~ { adjusted _ TRUE; }; DeleteSelection : PUBLIC PROC [plot: Plot] ~ { axisList: AxisList _ plot.axis; IF plot.selection=NIL THEN RETURN; UNTIL plot.selection=NIL DO WITH plot.selection.first SELECT FROM axis: Axis => DeleteAxis[plot, axis]; graph: Graph => NULL; point: GraphPoint => NULL; text: PlotText => DeleteText[plot, text]; ENDCASE => NULL; plot.selection _ plot.selection.rest; ENDLOOP; }; InsertAxis: PUBLIC PROC [plot: Plot, axis, after: Axis] ~ { axisList: AxisList _ plot.axis; IF axis=NIL THEN RETURN; LockPlot[plot]; IF after=NIL THEN plot.axis _ CONS[axis, plot.axis] ELSE { UNTIL axisList.first=after OR axisList=NIL DO axisList _ axisList.rest ENDLOOP; IF axisList=NIL THEN {UnlockPlot[plot]; RETURN}; axisList.rest _ CONS[axis, axisList.rest]; }; UnlockPlot[plot]; }; DeleteAxis: PUBLIC PROC [plot: Plot, axis: Axis] ~ { axisList: AxisList _ plot.axis; IF axisList=NIL THEN RETURN; LockPlot[plot]; IF axisList.first=axis THEN {plot.axis _ plot.axis.rest; UnlockPlot[plot]; RETURN}; UNTIL axisList.rest.first=axis DO axisList _ axisList.rest; IF axisList.rest=NIL THEN {UnlockPlot[plot]; RETURN}; ENDLOOP; axisList.rest _ axisList.rest.rest; UnlockPlot[plot]; }; MoveAxis: PUBLIC PROC [plot: Plot, axis, after: Axis] ~ { DeleteAxis[plot, axis]; InsertAxis[plot, axis, after]; }; XChangeAxis: PUBLIC PROC [plot: Plot, axis1, axis2: Axis] ~ { axisList: AxisList _ plot.axis; IF axisList=NIL THEN RETURN; LockPlot[plot]; UNTIL axisList=NIL DO SELECT axisList.first FROM axis1 => axisList.first _ axis2; axis2 => axisList.first _ axis1; ENDCASE; axisList _ axisList.rest; ENDLOOP; UnlockPlot[plot]; }; CopyAxis: PUBLIC PROC [axis: Axis] RETURNS [duplicate: Axis] ~ { duplicate _ NEW[AxisRec _ [ graphs: axis.graphs, bounds: axis.bounds, name: axis.name, style: axis.style, maxChars: axis.maxChars, axisData: axis.axisData ]]; }; ForkAxis: PUBLIC PROC [plot: Plot, axis: Axis] ~ { axisList: AxisList _ plot.axis; newAxisList, firstItem: AxisList; IF axisList=NIL THEN RETURN; IF axis.graphs.rest=NIL THEN RETURN; FOR iGraphList: GraphList _ axis.graphs, iGraphList.rest UNTIL iGraphList=NIL DO thisGraph: Graph _ iGraphList.first; newAxisList _ CONS[NEW[PlotGraph.AxisRec _ [ graphs: LIST[thisGraph], bounds: axis.bounds, name: thisGraph.name, style: axis.style, axisData: axis.axisData ]], newAxisList]; thisGraph.name _ NIL; IF firstItem=NIL THEN firstItem _ newAxisList; ENDLOOP; LockPlot[plot]; IF axisList.first=axis THEN { firstItem.rest _ plot.axis.rest; plot.axis _ newAxisList; } ELSE { UNTIL axisList.rest.first=axis DO axisList _ axisList.rest; IF axisList=NIL THEN {UnlockPlot[plot]; RETURN}; ENDLOOP; IF axisList.rest#NIL THEN firstItem.rest _ axisList.rest.rest; axisList.rest _ newAxisList; }; UnlockPlot[plot]; }; MoveGraphInAxis: PUBLIC PROC [plot: Plot, axis: Axis, graph: Graph] ~ { axisList: AxisList _ plot.axis; IF axis=NIL THEN RETURN; IF graph=NIL THEN RETURN; LockPlot[plot]; IF axis.graphs.first.name=NIL THEN { axis.graphs.first.name _ axis.name; axis.name _ "#"; }; axis.graphs _ CONS[graph, axis.graphs]; UnlockPlot[plot]; }; NormalizeScaleAndOrigin: PUBLIC PROC [plot: Plot] RETURNS[unit: REAL]~ { test, excess: REAL; unit _ Normalize[plot.axis.first.bounds.w/20.0]; excess _ plot.axis.first.bounds.x-Real.Floor[plot.axis.first.bounds.x/unit]*unit; test _ plot.axis.first.bounds.w-Real.Floor[plot.axis.first.bounds.w/unit]*unit; IF test=0.0 AND excess=0.0 THEN RETURN; LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.x _ iAxisList.first.bounds.x - excess; iAxisList.first.bounds.w _ 20.0*unit; ENDLOOP; UnlockPlot[plot]; }; DrawInViewer: PROC [viewer: Viewer, proc: PROC [Viewer, Imager.Context], clear: BOOL _ FALSE] ~ { drawProc: REF PROC[Viewer, Imager.Context] _ NIL; TRUSTED { drawProc _ NEW[PROC[Viewer, Imager.Context] _ proc]; }; ViewerOps.PaintViewer[ viewer: viewer,-- pass record to viewer painter hint: client, whatChanged: drawProc, clearClient: clear ]; }; SetHeights: PROC [plot: Plot] RETURNS [hAxis: ARRAY DrawingStyle OF REAL _ ALL[0.0]] ~ { viewer: Viewer ~ plot.private.viewer; viewerData: ViewerData ~ NARROW[viewer.data]; nAxis, cAxis: ARRAY DrawingStyle OF REAL _ ALL[0.0]; yActualSize: REAL _ IF viewerData.grid THEN viewer.ch-4*ySpace ELSE viewer.ch-2*ySpace; inter: REAL; IF plot.axis=NIL THEN RETURN; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO nAxis[iAxisList.first.style] _ nAxis[iAxisList.first.style] + 1.0; cAxis[iAxisList.first.style] _ cAxis[iAxisList.first.style] + iAxisList.first.maxChars+1.0; ENDLOOP; inter _ yActualSize /(nAxis[analog]+nAxis[hexaH]+cAxis[hexaV]); hAxis[hexaH] _ MIN[inter, 2*charHeight]; hAxis[hexaV] _ MIN[inter, charHeight]; IF nAxis[analog]#0 THEN hAxis[analog] _ (yActualSize-hAxis[hexaH]*nAxis[hexaH]-hAxis[hexaV]*cAxis[hexaV]) /nAxis[analog] ELSE { IF nAxis[hexaH]#0.0 THEN hAxis[hexaH] _ (yActualSize-hAxis[hexaV]*cAxis[hexaV]) /nAxis[hexaH] ELSE hAxis[hexaV] _ inter; }; }; PlotScaleX: PROC [plot: Plot, alpha: REAL] ~ { LockPlot[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO iAxisList.first.bounds.w _ alpha*iAxisList.first.bounds.w; ENDLOOP; UnlockPlot[plot]; }; DrawMag: PROC [viewer: Viewer, context: Imager.Context] ~ { zip: Draw2d.Zip; viewerData: ViewerData ~ NARROW[viewer.data]; x: REAL _ viewer.cw*viewerData.xOr; Imager.SetColor[context, tempColor]; zip _ Draw2d.GetZip[context]; Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip]; x _ x+viewer.cw*viewerData.xScale; Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip]; Draw2d.ReleaseZip[zip]; }; ClipVect: PROC [fix, mov: VEC, area: Rectangle] RETURNS [newPt: VEC] ~ { slope: REAL; newPt _ mov; IF fix.x~=mov.x THEN { slope _ (fix.y-mov.y)/(fix.x-mov.x); IF newPt.xarea.x+area.w THEN {newPt.x _ area.x+area.w; newPt.y _ slope*(newPt.x-fix.x) + fix.y}; }; IF fix.y~=mov.y THEN { slope _ (fix.x-mov.x)/(fix.y-mov.y); IF newPt.yarea.y+area.h THEN {newPt.y _ area.y+area.h; newPt.x _ (newPt.y-fix.y)*slope + fix.x}; }; }; IsPtInArea: PROC [pt: VEC, area: Rectangle] RETURNS [inside: BOOLEAN _ FALSE] ~ { IF pt.xarea.x+area.w THEN RETURN; IF pt.y>area.y+area.h THEN RETURN; inside _ TRUE; }; ShowRopeInv: PROC [x, y: REAL, rope: Rope.ROPE, context: Imager.Context, color, background: Imager.Color] ~ { Imager.SetColor[context, background]; Imager.MaskRectangle[context, [x, y-1.0, ImagerFont.RopeEscapement[currentFont, rope].x, charHeight+2.0]]; Imager.SetXY[context, [x, y]]; Imager.SetColor[context, color]; Imager.ShowRope[context, rope]; }; ShowRopeV: PROC[context: Imager.Context, rope: Rope.ROPE, pos: VEC] ~ { IF rope=NIL THEN RETURN; FOR i: INT _ Rope.Length[rope]-1, i-1 UNTIL i=-1 DO Imager.SetXY[context, pos]; Imager.ShowChar[context, Rope.InlineFetch[rope, i]]; pos.y _ pos.y+ charHeight; ENDLOOP; }; ShowRope: PROC[x, y: REAL, rope: Rope.ROPE, context: Imager.Context, color: Imager.Color] ~ { IF rope=NIL THEN RETURN; Imager.SetColor[context, color]; Imager.SetXY[context, [x, y]]; Imager.ShowRope[context, rope]; }; DrawRopeV: PUBLIC PROC[pos: VEC, rope: Rope.ROPE, viewer: Viewer, color: Imager.Color] ~ { DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ { Imager.SetColor[context, color]; ShowRopeV[context, rope, pos]; }; DrawInViewer[viewer, DoDrawRope, FALSE];-- ask the viewer procs to call you back }; DrawRope: PUBLIC PROC[pos: VEC, rope: Rope.ROPE, viewer: Viewer, color: Imager.Color] ~ { DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ { Imager.SetFont[context, currentFont]; ShowRope[pos.x, pos.y, rope, context, color]; }; DrawInViewer[viewer, DoDrawRope, FALSE];-- ask the viewer procs to call you back }; DrawRopeInv: PUBLIC PROC[pos: VEC, rope: Rope.ROPE, viewer: Viewer, color, background: Imager.Color] ~ { DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ { Imager.SetFont[context, currentFont]; ShowRopeInv[pos.x, pos.y, rope, context, color, background]; }; DrawInViewer[viewer, DoDrawRope, FALSE];-- ask the viewer procs to call you back }; Normalize: PROC [r: REAL] RETURNS [dec: REAL] ~ { neg: BOOL _ r<0.0; unsigned: REAL _ IF neg THEN -r ELSE r; base: REAL _ IF unsigned<1.0 THEN 10.0 ELSE 0.1; dec _ 1.0; IF r#0.0 THEN UNTIL unsigned>=1.0 AND unsigned<10.0 DO dec _ dec/base; unsigned _ unsigned*base; ENDLOOP; dec _ SELECT TRUE FROM unsigned<1.5 => dec, unsigned<3.5 => 2.0*dec, unsigned<7.5 => 5.0*dec, ENDCASE => 10.0*dec; IF neg THEN dec _ -dec; }; GetSelectedAxis: PUBLIC PROC[plot: Plot] RETURNS [axis: Axis] ~ { FOR iList: LIST OF REF ANY _ plot.selection, iList.rest UNTIL iList=NIL DO IF ISTYPE[iList.first, Axis] THEN { axis _ NARROW[iList.first]; EXIT; }; ENDLOOP; }; GetSelectedAxisList: PUBLIC PROC[plot: Plot] RETURNS [axisList: LIST OF Axis] ~ { axisList _ NIL; FOR iList: LIST OF REF ANY _ plot.selection, iList.rest UNTIL iList=NIL DO IF ISTYPE[iList.first, Axis] THEN axisList _ CONS[NARROW[iList.first, Axis], axisList]; ENDLOOP; }; SelectAxis: PUBLIC PROC [plot: Plot, axis: Axis] ~ { selectedAxis: Axis _ GetSelectedAxis[plot]; self : Viewer _ plot.private.viewer; xor, yor: REAL; hAxis: ARRAY DrawingStyle OF REAL _ ALL[0.0]; bblue: Imager.Color _ IF self.column=color THEN blue ELSE black; wwhite: Imager.Color _ IF self.column=color THEN grey ELSE white; IF axis=selectedAxis THEN RETURN; plot.selection _ IF axis=NIL THEN NIL ELSE LIST[axis]; xor _ xSpace; yor _ ySpace; hAxis _ SetHeights[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO thisAxis: Axis = iAxisList.first; inter: REAL _ AxisHeight[thisAxis, hAxis]; IF thisAxis=selectedAxis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName], thisAxis.name, self, bblue, wwhite]; IF thisAxis=axis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName], thisAxis.name, self, wwhite, bblue]; yor _ yor+inter; ENDLOOP; }; AxisHeight: PROC [axis: Axis, hAxis: ARRAY DrawingStyle OF REAL] RETURNS [h: REAL] ~ { h _ IF axis.style=hexaV THEN hAxis[hexaV]*(axis.maxChars+1.0) ELSE hAxis[axis.style] }; GetAxis: PROC [self: Viewer, x, y: REAL] RETURNS [axis: Axis, localx, localy: REAL] ~ { viewerData: ViewerData ~ NARROW[self.data]; plot: Plot _ viewerData.plot; xor, yor, inter, t, v: REAL; hAxis: ARRAY DrawingStyle OF REAL _ ALL[0.0]; xor _ xSpace; yor _ ySpace; IF plot.axis=NIL THEN RETURN[NIL, 0.0, 0.0]; hAxis _ SetHeights[plot]; FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO thisAxis: Axis = iAxisList.first; inter _ AxisHeight[thisAxis, hAxis]; IF y>=yor AND y [] _ iGraphList.first.class.enumerate[plot, iGraphList.first, within, DrawPt, ptData]; hexaH => [] _ iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtH, ptData]; hexaV => [] _ iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtV, ptData]; ENDCASE; ShowRope[ptData.plotOr.x+xoffset, ptData.plotOr.y+inter*0.5, iGraphList.first.name, context, colorList.first]; xoffset _ xoffset+ImagerFont.RopeEscapement[currentFont, iGraphList.first.name].x+5.0; IF colorList.rest#NIL THEN colorList _ colorList.rest ELSE colorList _ graphColorList; ENDLOOP; ptData.plotOr.y _ ptData.plotOr.y+inter; ENDLOOP; }; fileName: Rope.ROPE _ Rope.Cat["///Temp/PlotGraph/", plot.name^, ".interpress"]; within: Rectangle _ [plot.lowerBounds.x, plot.lowerBounds.y, plot.upperBounds.x-plot.lowerBounds.x, plot.upperBounds.y - plot.lowerBounds.y]; ip: ImagerInterpress.Ref _ ImagerInterpress.Create[fileName]; rgbLinear: Imager.ColorOperator ~ ImagerColorOperator.RGBLinearColorModel[255]; ImagerInterpress.DeclareColorOperator[ip, rgbLinear]; ImagerInterpress.DeclareFont[ip, pressFont]; ImagerInterpress.DoPage[ip, DoPrint, pressScale]; ImagerInterpress.Close[ip]; MessageWindow.Append[Rope.Cat[fileName, " created"],TRUE]; }; the client way to kill the viewer, and then empty the plot. see the DestroyProc Menu Actions ViewerClasses Definitions PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] NotifyProc ~ PROC[self: Viewer, input: LIST OF REF ANY] ask the viewer procs to call you back = PROC [self: Viewer, change: ModifyAction], ModifyAction: TYPE = {set, push, pop, kill} ~ PROC [self: Viewer] This will be called when the viewer is destroyed PROC [self: Viewer, op: ATOM _ NIL] RETURNS [data: REF ANY]; Acts on scrollbar mouse hits ScrollProc ~ PROC[self: Viewer, op: ScrollOp, amount: INTEGER] RETURNS[top, bottom: INTEGER _ LAST[INTEGER]]; ScrollOp: TYPE = {query, up, down, thumb} Acts on hscrollbar mouse hits HScrollProc ~ PROC[self: Viewer, op: HScrollOp, amount: INTEGER] RETURNS[left, right: INTEGER _ LAST[INTEGER]]; HScrollOp: TYPE = {query, left, right, thumb} FOR iAxisList: AxisList _ plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO left _ MAX[left, Real.InlineFixI[100.0*(plot.upperBounds.x-iAxisList.first.bounds.x-iAxisList.first.bounds.w) /(plot.upperBounds.x-plot.lowerBounds.x)]]; right _ MIN[right, Real.InlineFixI[100.0*(plot.upperBounds.x-iAxisList.first.bounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]]; ENDLOOP; = PROC [self: Viewer] RETURNS [adjusted: BOOL _ FALSE] ViewerBLT will assume that a full repaint is required if adjusted=TRUE. Data Structure Modifications unit is 1, 2 or 5 times a power of ten in client x coordinates, which are supposed to be coherent all over the graphs. scale adequately the axis. Utilities Pass procedure to PaintProc compute the unitary heigth of each kind for this viewer fix is inside area, mov outside. newPt is the intersection of the vector [fix, mov] and the area boundaries. rounds r to the closest "round decimal number" Graphs Graph Points Texts Initialization grey: PUBLIC Imager.Color _ ImagerColor.ColorFromRGB[ [ R: 0.5, G: 0.5, B: 0.5 ] ]; -- Tip table (translates mouse events to commands) Κ9€˜codešœ™KšœB™BK™/K™$KšœŠ™Š—K™šΟk ˜ K™KšœœΈ™ΔKšœœ{˜‡Kšœœ˜)Kšœ œ˜!Kšœœ™0Kšœœ˜-Kšœ œ/˜@KšœœA™WK˜K™Kšœœ\˜gKšœœ˜£Kšœ œA˜PKšœœ+˜8Kšœœ˜Kšœ œ ˜0K™Kšœ œ™(Kšœœ˜Kšœœ˜Kšœœœ˜Kšœœœ˜,Kšœœ ˜*Kšœœ ™šœ ˜ K˜——K˜KšΠbl œœ˜šœ˜Kšœ˜Kšœ ˜ K™K˜Kšœ™Kšœ ˜ Kšœ˜K˜Kšœ˜K˜Kšœ˜Kšœ™Kšœ ˜ Kšœ˜K˜ K˜Kšœ˜Kšœ˜—K˜Kšœ ˜Kšœ œ ˜head™Kšœœ œ˜Kšœ œœœ˜Kšœ œœœ˜Kšœ œœœ˜ Kšœœ˜$Kšœœ˜Kšœ œœ˜%šœœœ˜Kšœ Οc˜&Kšœ œœœ˜.Kšœ œœœ˜+Kšœœ Ÿ˜3KšœœŸ*˜;KšœœœŸ8˜PKšœœœŸF˜]KšœœœŸ˜-Kšœ œœŸ ˜7K˜—Kšœ œœ˜%šœœœ˜K˜KšœœŸ˜%KšœœŸ˜Kšœœ˜ Kšœœ˜ Kšœ˜Kšœ œœ˜Kšœ œ˜K˜K˜—Kšœœ˜Kšœœ˜Kšœœ˜—™š Οn œœœœœœ˜CK™=Kšœœ ˜Kšœ œœ ˜Kšœœ˜(Kšœ˜K˜K˜—š œœ˜'K™-Kšœ˜šœ0˜0Kšœ˜K˜Kšœ œ˜K˜"K˜—šœ1˜1Kšœ˜K˜Kšœ˜Kšœ!˜!K˜—šœ0˜0Kšœ˜K˜Kšœ˜Kšœ!˜!K˜—šœ0Ÿ(˜XKšœ ˜ Kšœ˜Kšœ œ˜Kšœ$˜$K˜—šœ/˜/Kšœ ˜ Kšœ ˜ Kšœ œ˜Kšœ*˜*K˜—šœ1˜1Kšœ˜Kšœ ˜ Kšœ˜Kšœ*˜*K˜—šœ1˜1Kšœ˜Kšœ ˜ Kšœ˜Kšœ*˜*K˜—šœ0˜0Kšœ˜K˜ Kšœ œ˜K˜K˜—šœ2˜2Kšœ˜K˜Kšœ œ˜Kšœ˜K˜—šœ0˜0Kšœ˜K˜ Kšœ œ˜Kšœ˜K˜—Kšœœ˜,Kšœ0˜0Kšœ4˜4Kšœœ˜(Kšœ˜Kšœ˜Kšœ5˜5Kšœ6˜6Kšœ4˜4Kšœ4˜4Kšœ6˜6Kšœ5˜5Kšœ7˜7Kšœ5˜5šœ-˜-Kšœ˜šœ˜Kšœ˜K˜ Kšœœ˜ K˜ Kšœ ˜Kšœ œ˜Kšœœ˜ KšœœŸ˜1Kšœ˜K˜—K˜—K˜K˜—š  œ œ œ˜=K™DKšœ˜Kšœœ˜Kšœ œœ˜1Kšœ œœ˜Kšœ˜šœ0œ œ˜IKšœœœ˜6Kšœ˜Kšœ˜—Kšœ˜K˜K˜—š  œœœ˜(Kšœœœ˜Kš œ˜ K˜K˜—š œœœœ˜+Kšœœœ˜Kšœœœœ˜Kšœœœœ˜ Kšœœœœ˜AKš œœœœŸ+˜LKšœœ˜Kšœ˜K˜—š  œœœœ˜,Kšœœœ˜Kšœœœœ˜Kšœœœœ˜ Kšœœ˜Kš œ˜ Kšœ˜K˜—š œœœœœœœœœ˜cK™FK™BK™Kšœœ˜ Kšœœ˜Kšœœ˜Kšœœ˜"Kšœœœœ˜1Kšœ˜Kš œ'œœœœ˜BKšœ3˜3Kšœœœ˜"Kšœ$œœ˜2Kšœ˜Kšœ?˜?Kš œœœœœœ˜BKšœ;˜;Kšœ˜K˜K˜—š œœœœœœœœœ˜cK™FK™BKšœœ˜ Kšœœ˜Kšœœ˜Kšœœ˜"Kšœœœœ˜1Kšœ˜Kš œ'œœœœ˜BKšœ3˜3Kšœœœ˜"Kšœ$œœ˜2Kšœ˜Kšœ?˜?Kšœ;˜;Kš œœœœœœ˜BKšœ#˜#Kšœ˜K˜K˜—š œœœœœœœœœ˜aKšœx™xKšœœ˜Kšœ œ˜Kšœœ˜"Kšœœœœ˜1šœœ˜Kšœo˜oKšœ˜Kšœ:˜:Kšœ˜—šœ˜Kšœd˜dKšœ(˜(šœœ˜KšœœH˜SKšœœf˜qKšœ œp˜|šœ œ˜!Kšœ œ˜šœœ˜Kšœ1˜1Kšœ1˜1Kšœœœ-˜QK˜—K˜—Kšœ˜—Kšœ˜Kšœ˜Kšœ˜—K˜K˜—š  œœœœœ2œœ˜–Kšœ˜Kšœœœ˜0Kšœœœ˜*Kšœ œ˜.Kšœœœ˜!Kšœœ˜Kšœ6˜6K˜K˜—š   œœœœ2œœ˜‘š œœ.˜CKšœœ˜$Kšœœœ˜K˜1Kšœ œ œŸ6˜YKšœœ œŸ˜=Kšœœ˜-Kšœœ˜Kš œœœœœ˜-Kšœ œœ˜Kšœ+˜+šœœ˜*Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ ˜Kšœ˜Kšœ˜—K™œKšœœ˜šœœ˜K˜K˜ K˜K˜ K˜ K˜—šœ˜K˜Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜K˜—Kšœ%˜%šœ œ˜Kšœ!˜!KšœBŸ˜PK™)š œ œœ&œ œ˜Sšœœœ˜!š  œœ˜Kšœz™zKšœ ˜ KšœJ˜JKšœ˜Kšœ2˜2Kšœ˜Kšœ3˜3K˜—Kšœœ˜ K™?Kšœ œ&˜3Kšœ œ&˜3šœœ˜(Kšœ ˜ Kšœ œX˜eKšœ œR˜^Kšœ˜—K™?šœœœ˜(Kšœ˜Kšœ œX˜eKšœœR˜\Kšœ˜—K™6Kšœ)˜)K˜—Kšœ˜—K˜—K™Kšœ œœœ˜K™„Kšœ˜Kšœ˜K™Pšœœ œ˜Kšœœ˜ Kšœœ!˜+K™4Kšœ!˜!Kšœ˜Kšœ˜Kšœ5˜5K˜ K™Cšœœœ ˜Kšœ%˜%Kšœ/˜/Kšœ˜—K™sKšœ1œ œ˜`Kšœ&œ œ1˜hK˜—K™gšœ1œ œ˜JK™Sšœ œ˜Kšœ œ˜šœ,œ œ˜Ešœ œ˜)Kšœ œ˜Kšœ˜K˜—Kšœ˜—K˜—Kšœ œ˜K™šœ œ˜Kšœ/™/Kšœ˜Kšœ˜K™ZKšœ+˜+K™UKšœ(˜(Kšœ(˜(Kšœ3˜3Kšœ<˜œ˜DK˜K˜—š  œœ œœœœ!œœ˜qKšœœ ˜ Kšœœ˜-Kšœ#˜#KšœIœ˜NK˜K˜—š œœ œœœœ!œœ˜mKšœœ ˜ Kšœœ˜-Kšœ!˜!K˜K˜—š œœ œœœœ!œœ˜nKšœœ ˜ Kšœœ˜-Kšœ˜Kš œ œœ œœ˜8Kšœo˜oKšœ'˜'Kšœ$˜$K˜K˜—š  œœ œœœœ2œ˜qKšœœ ˜ Kšœœ˜-Kšœ˜Kšœœ˜ Kšœœœ˜"Kšœœœœ˜-Kšœœ˜"Kšœœœ˜+šœœ˜Kšœ,˜,Kšœ˜Kšœ>œ˜CKšœ˜—šœ˜š  œœ.˜=Kšœ˜Kšœœ˜-Kšœœ8˜?K•StartOfExpansion7[context: Imager.Context, color: ImagerColor.Color]šœ$˜$Kšœ˜Kšœ;˜;Kšœ,˜,Kšœ;˜;Kšœ˜K˜—Kšœ œ˜'K˜—K˜K˜—š œ œ œœœœ2œ˜sKšœœ ˜ Kšœœ˜-Kšœ˜Kšœœ˜Kšœœœ˜"Kšœœ ˜$Kšœœ˜"Kšœœ˜6šœœ˜Kšœ˜Kšœ>œ˜CKšœ˜—Kšœ˜K˜—š œ œ œœœœ2œ˜sKšœœ ˜ Kšœœ˜-Kšœ˜Kšœœ˜Kšœœœ˜"Kšœœ ˜$Kšœœ˜"K˜šœ1œ œ˜JKšœ:˜:Kšœ˜—K˜Kšœ>œ˜CK˜K˜—š œ œ œœœœ!œœ˜uKšœœ ˜ Kšœœ˜-Kšœ˜Kš œ œœ œœ˜5KšœœœŸ˜1Kšœc˜cKšœ$˜$šœœ˜K˜šœ1œ œ˜JKšœF˜FKšœ\˜\Kšœ˜—K˜K˜—šœ˜K˜šœ1œ œ˜JKšœ\˜\KšœF˜FKšœ˜—K˜K˜—Kšœ%˜%Kšœ>œ˜DK˜——™š‘ œŸ˜DKšœO™Ošœ ˜šœ Ÿ˜%Kšœœ ˜+KšœIœ˜NKšœ˜—šœŸ˜,Kšœœœ,˜HKšœ˜——K˜K˜—š‘ œ˜(Kšœ Ÿ-™7Kšœ œ˜Kšœœ ˜+Kšœ˜Kšœ'œ&˜SKšœœ(Ÿ ˜Qšœ˜Kšœ$œ˜8Kšœ˜Kšœ˜šœœœœ˜(Kšœ œœ˜,Kšœ˜Kšœ'˜'šœ ˜šœ ˜ Kšœ>œ˜CKšœ˜—šœœ œœ˜$Kšœœœœ œœœœ˜ŽKšœ6œœœ˜eKšœ˜Kšœ˜—šœ ˜ š œœ.˜;Kšœ˜Kšœœ˜-Kšœœ˜$K–7[context: Imager.Context, color: ImagerColor.Color]šœ$˜$Kšœ˜Kšœ=˜=Kšœ$˜$Kšœ=˜=Kšœ˜Kšœ;˜;Kšœ#˜#Kšœ=˜=Kšœ˜K˜—šœœœ˜=KšŸ%™%—Kšœ˜—˜Kšœ˜Kšœ˜—šœ˜Kšœ+˜+Kšœ,˜,Kšœ>œ˜CKšœ˜—šœ˜Kšœ+˜+Kšœ8˜8Kšœ>œ˜CKšœ˜—šœ˜Kšœ+˜+Kšœ,˜,Kšœ>œ˜CKšœ˜—šœ˜Kšœ ˜ Kšœ>œ˜CKšœ˜—šœ ˜ Kšœ+˜+Kšœ/˜/Kšœ>œ˜CKšœ˜—šœ˜Kšœ˜Kšœ>œ˜CKšœ˜—˜ Kšœ˜Kšœ>œ˜CKšœ˜—šœ ˜ Kšœ+˜+Kšœ)˜)Kšœœœ ˜:Kšœ4˜4Kšœ>œ˜CKšœ˜—šœ˜ Kšœœœ4˜NKšœ˜——K™K˜—K˜—K˜K˜—š‘ œ˜(Kšœœ5œ™XKšœœ ˜+Kšœ˜šœ˜Kšœœ˜ Kšœœ˜ Kšœœ˜ Kšœœ˜Kšœ˜—K˜K˜—š‘ œ˜*Kšœœ@™FKšœœ ˜+Kšœ˜K˜Kšœ œ˜Kšœ˜Kšœœ˜K˜K˜—š œ˜"Kš œœœœœœ™œ˜CKšœ˜K˜—š‘ œ#˜.KšœŸ™KšœŸ;œ Ÿ™oKšŸœ Ÿ™-Kšœœ ˜+Kšœ˜Kšœ œœœ˜šœ˜šœ ˜ K˜šœ1œ œ˜JKšœ^˜^Kšœ˜—Kšœ˜Kšœ˜—šœ ˜ K˜šœ1œ œ˜JKšœ^˜^Kšœ˜—Kšœ˜Kšœ˜—šœ ˜ KšœœJ˜TK˜šœ1œ œ˜JKšœ ˜ Kšœ˜—Kšœ˜Kšœ˜—šœ ˜ Kšœ ˜ Kšœ ˜ Kšœ'œœ˜5šœ1œ œ™JKšœœ™™Kšœœw™‚Kšœ™—šœ1œ œ˜JKšœœv˜€Kšœœ˜›Kšœ˜—Kš˜Kšœ˜—Kšœ˜—Kšœ>œ˜CKšœ˜K˜—š‘ œ˜(Kš œœœ œœ™6KšœBœ™GKšœ œ˜K˜K˜——™K™š œ œ˜.Kšœ˜Kšœœœœ˜"šœœ˜šœœ˜%Kšœ%˜%Kšœœ˜Kšœœ˜Kšœ)˜)Kšœœ˜—Kšœ%˜%Kšœ˜—K˜K˜—šΠbn œ œ$˜;Kšœ˜Kšœœœœ˜Kšœ˜Kšœœœ œ˜3šœ˜Kš œœ œœœ˜OKšœ œœœ˜0Kšœœ˜*K˜—Kšœ˜K˜K˜—š  œ œ˜4Kšœ˜Kšœ œœœ˜Kšœ˜Kšœœ0œ˜Sšœœ˜"Kšœ˜Kšœœœœ˜5Kšœ˜—Kšœ#˜#Kšœ˜K˜K˜—š œ œ$˜9Kšœ˜Kšœ˜K˜K˜—š‘ œ œ%˜=Kšœ˜Kšœ œœœ˜Kšœ˜šœ œœ˜šœ˜Kšœ ˜ Kšœ ˜ Kšœ˜—Kšœ˜Kšœ˜—Kšœ˜K˜K˜—š œ œœ˜@–ή -- [graphs: PlotGraph.GraphList _ NIL, bounds: ImagerTransformation.Rectangle, name: ROPE _ NIL, style: PlotGraph.DrawingStyle _ PlotGraph.DrawingStyle[analog], maxChars: NAT _ 8, axisData: ARRAY XY OF PlotGraph.AxisData]šœ œ ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜K˜—K˜K˜—š œ œ˜2Kšœ˜Kšœ!˜!Kšœ œœœ˜Kšœœœœ˜$šœ6œ œ˜PKšœ$˜$šœœœ˜,Kšœœ ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœœ˜Kšœ œœ˜.Kšœ˜—Kšœ˜šœœ˜Kšœ ˜ Kšœ˜K˜—šœ˜šœœ˜"Kšœ˜Kšœ œœœ˜0Kšœ˜—Kšœœœ%˜>Kšœ˜K˜—Kšœ˜K˜K˜—š œœœ+˜GKšœ˜Kšœœœœ˜Kšœœœœ˜Kšœ˜šœœœ˜$Kšœ#˜#Kšœ˜K˜—Kšœœ˜'Kšœ˜K˜K˜—š  œœœœœ˜HK™vKšœœ˜Kšœ0˜0KšœQ˜QKšœO˜OKšœ œ œœ˜'Kšœ˜K™šœ1œ œ˜JKšœ=˜=Kšœ%˜%Kšœ˜—Kšœ˜K˜——™ K™š  œœœ" œ˜aKšŸ™Kšœ œœœ˜1Kšœœœ$˜Ašœ˜KšœŸœ˜/Kšœ ˜ Kšœ˜Kšœ˜Kšœ˜—K˜K˜—š  œœœ œœœœ ˜XKšœ%˜%Kšœœ˜-Kš œœœœœ˜4Kš œ œœœœ˜WKšœœ˜ Kšœ œœœ˜šœ1œ œ˜JKšœB˜BKšœ[˜[Kšœ˜—K™7Kšœ?˜?Kšœœ˜(Kšœœ˜&Kšœœa˜xšœ˜KšœœE˜]Kšœ˜K˜—K˜K˜—š  œœœ˜.K˜šœ1œ œ˜JKšœ:˜:Kšœ˜—Kšœ˜K˜K˜—š œœ.˜;Kšœ˜Kšœœ˜-Kšœœ˜#K–7[context: Imager.Context, color: ImagerColor.Color]šœ$˜$Kšœ˜Kšœ;˜;Kšœ"˜"Kšœ;˜;Kšœ˜K˜K˜—š  œœ œœ œ˜HK™lKšœœ˜ Kšœ ˜ šœœ˜K˜$Kšœœ=˜SKšœœD˜aK˜—šœœ˜K˜$Kšœœ=˜SKšœœD˜aK˜—K˜K˜—š   œœœœ œ˜QKšœ œœ˜Kšœ œœ˜Kšœœœ˜"Kšœœœ˜"Kšœ œ˜K˜K˜—š  œœœ œ>˜nKšœ%˜%Kšœj˜jKšœ˜Kšœ ˜ Kšœ˜K˜K˜—š  œœ%œœ˜GKšœœœœ˜šœœœ˜3Kšœ˜Kšœ4˜4Kšœ˜Kšœ˜—K˜K˜—š œœœ œ1˜]Kšœœœœ˜Kšœ ˜ Kšœ˜Kšœ˜K˜K˜—š   œ œœ œœ ˜Zš  œœœ$˜>Kšœ ˜ Kšœ˜K˜—Kšœ!œŸ(˜PK˜K˜—š  œ œœ œœ ˜Yš  œœœ$˜>Kšœ%˜%Kšœ-˜-K˜—Kšœ!œŸ(˜PK˜K˜—š   œ œœ œœ,˜hš  œœœ$˜>Kšœ%˜%Kšœ<˜