DIRECTORY BasicTime USING [GMT, nullGMT], Convert USING [Error, RealFromRope], FS USING [Error], Icons USING [IconFlavor, NewIconFromFile], Imager USING [Box, ClipRectangle, ConstantColor, Context, DoSave, MaskBox, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, Scale2T, ScaleT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC], ImagerBackdoor USING [GetBounds, GetCP], ImagerColor USING [GrayFromColor, RGB], ImagerColorMap USING [LoadEntries, MapEntry, SetStandardColorMap], ImagerDitheredDevice USING [ColorFromSpecialRGB], ImagerFont USING [Extents, Find, Font, FontBoundingBox, RopeBoundingBox, Scale], ImagerInterpress USING [Close, Create, DoPage, Ref], ImagerPress USING [Close, SimpleCreate], IO USING [int, PutFR, real, rope, time], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc], MessageWindow USING [Append, Blink], Plot USING [Curves, PlotSpec, PlotSpecRec, ReadPlotFile, RopeSequence, SavePlot, Vector], PlotOps USING [BackgroundType, ColorType, Handle, HandleData, MaxStep, LineState, LineStateRec, LineStep, Lock, StateSequence, Unlock], PopUpMenu USING [RequestSelection], Real USING [FixI, RoundC, RoundI, RoundLI], RealFns USING [Log, Power, SqRt], Rope USING [Cat, Equal, Find, IsEmpty, ROPE, Substr], Terminal USING [ColorCursorPresentation, Current, GetColorCursorPresentation, SetColorCursorPresentation, Virtual], UserProfile USING [CallWhenProfileChanges, ListOfTokens, ProfileChangedProc, Token], VFonts USING [EstablishFont], ViewerClasses USING [Column, PaintProc, PaintRectangle, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [CreateViewer, EnumerateViewers, EnumProc, OpenIcon, PaintViewer, RegisterViewerClass, SwapIconAndViewer], ViewerTools USING [GetSelectionContents], WindowManager USING [colorDisplayOn, RestoreCursor]; PlotViewer: CEDAR PROGRAM IMPORTS BasicTime, Convert, FS, Imager, ImagerBackdoor, ImagerColor, ImagerColorMap, ImagerDitheredDevice, ImagerFont, ImagerInterpress, ImagerPress, Icons, IO, Menus, MessageWindow, Plot, PlotOps, PopUpMenu, Real, RealFns, Rope, Terminal, UserProfile, VFonts, ViewerOps, ViewerTools, WindowManager EXPORTS Plot = { OPEN Plot, PlotOps; OutputType: TYPE = {screen, press, interpress}; FontType: TYPE = {title, normal}; VectorPair: TYPE = RECORD[start, end: Vector]; NColors: TYPE = [0..MaxNumerOfColors); TextHeightFudge: REAL = 4.0; TextWidthFudge: REAL = 4.0; minLineWidth: REAL = 1.0; MaxNumerOfColors: INTEGER = 16; coordRatio: REAL = 0.25/600.0; -- 0.25 m ~ 600 pixels plotIcon: Icons.IconFlavor _ Icons.NewIconFromFile["Plot.icons", 0]; plotViewerClass: ViewerClasses.ViewerClass _ NEW [ViewerClasses.ViewerClassRec _ [paint: Display, icon: plotIcon, cursor: textPointer]]; t12b: ImagerFont.Font = VFonts.EstablishFont["TimesRoman", 12, TRUE]; -- bold t12bp: ImagerFont.Font = ImagerFont.Scale[ ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"], 13.0]; h8: ImagerFont.Font = VFonts.EstablishFont["Helvetica", 8]; h8p: ImagerFont.Font = ImagerFont.Scale[ ImagerFont.Find["Xerox/PressFonts/Helvetica-mrr"], 9.0]; textFont: ARRAY FontType OF ARRAY OutputType OF ImagerFont.Font = [[t12b, t12bp, t12bp], [h8, h8p, h8p]]; fontHeight: ARRAY FontType OF ARRAY OutputType OF REAL; virtual: Terminal.Virtual = Terminal.Current[]; initialColumn: ViewerClasses.Column; -- {static, left, right, color} createOption: {closeOtherViewers, swapLastViewer, iconic, badCreateOption}; backgroundType: BackgroundType; -- {white, gray, darkGray, black, unknown} color: ARRAY NColors OF Imager.ConstantColor; CreateViewer: PUBLIC PROC [spec: PlotSpec _ NIL, iconic, inhibitDestroy: BOOL _ FALSE] RETURNS [viewer: ViewerClasses.Viewer _ NIL] = { handle: Handle _ NEW[HandleData _ [ plotSpec: spec, background: backgroundType ] ]; viewer _ ViewerOps.CreateViewer[ flavor: $Plot, info: [ name: spec.file, menu: MakeMenu[], icon: plotIcon, column: initialColumn, iconic: TRUE, inhibitDestroy: inhibitDestroy, data: handle], paint: FALSE]; IF iconic OR (createOption = iconic) THEN ViewerOps.PaintViewer[viewer, all, TRUE, NIL] ELSE { SELECT createOption FROM closeOtherViewers => ViewerOps.OpenIcon[ icon: viewer, closeOthers: TRUE, bottom: TRUE, paint: TRUE]; swapLastViewer => { vLast: ViewerClasses.Viewer _ NIL; LastViewer: ViewerOps.EnumProc = { IF v.column = initialColumn AND NOT v.iconic THEN {vLast _ v; RETURN[FALSE]}; }; -- LastViewer ViewerOps.EnumerateViewers[LastViewer]; IF vLast = NIL THEN ViewerOps.OpenIcon[ icon: viewer, closeOthers: TRUE, bottom: TRUE, paint: TRUE] ELSE { ViewerOps.SwapIconAndViewer[ icon: viewer, openViewer: vLast, paint: TRUE]; -- this paints the new viewer only. ViewerOps.PaintViewer[vLast, all, TRUE, NIL]; -- paint the last viewer. }; }; ENDCASE; }; }; -- CreateViewer AddVector: PUBLIC PROC [viewer: ViewerClasses.Viewer _ NIL, vector: Vector _ NIL] = { IF vector = NIL OR NOT IsPlotViewer[viewer] THEN RETURN ELSE { shouldPaint: BOOL _ FALSE; expectedSize: CARDINAL; handle: Handle _ NARROW[viewer.data]; IF handle # NIL THEN { Lock[handle]; expectedSize _ handle.plotSpec.nCurvesMax + 1; IF vector.size # expectedSize THEN MessageWindow.Append[ IO.PutFR["Only %g elements in vector when %g expected.", IO.int[vector.size], IO.int[expectedSize]], TRUE] ELSE { IF handle.curves = NIL THEN { handle.lineStates _ NEW[StateSequence[handle.plotSpec.nCurvesMax]]; FOR i: CARDINAL IN[0..handle.plotSpec.nCurvesMax) DO handle.lineStates[i] _ NEW[LineStateRec _ []]; ENDLOOP; } ELSE IF handle.curves.first # NIL THEN shouldPaint _ TRUE; handle.curves _ CONS[vector, handle.curves] }; Unlock[handle]; }; IF shouldPaint THEN ViewerOps.PaintViewer[viewer, client, FALSE, handle.curves]; }; }; -- AddVector CreateSpec: PUBLIC PROC[file, title: Rope.ROPE _ NIL, time: BasicTime.GMT _ BasicTime.nullGMT, bounds: Imager.Box _ [0, 0, 0, 0], -- [xmin, xmax, ymin, ymax] nCurvesMax: CARDINAL _ 0, legendEntries: REF RopeSequence _ NIL ] RETURNS[spec: PlotSpec _ NIL] = { spec _ NEW[PlotSpecRec _ [file, title, time, bounds, nCurvesMax, legendEntries]]; }; -- CreateSpec SetSpec: PUBLIC PROC [viewer: ViewerClasses.Viewer _ NIL, newSpec: PlotSpec _ NIL] = { IF IsPlotViewer[viewer] THEN { handle: Handle _ NARROW[viewer.data]; IF handle = NIL THEN RETURN; Lock[handle]; handle.curves _ NIL; handle.plotSpec _ newSpec; viewer.name _ handle.plotSpec.file; Unlock[handle]; ViewerOps.PaintViewer[viewer, caption, FALSE, NIL]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; -- update the display. }; }; -- SetSpec Clear: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { IF IsPlotViewer[viewer] THEN { handle: Handle _ NARROW[viewer.data]; Lock[handle]; handle.plotSpec^ _ []; handle.curves _ NIL; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- Clear Zoom: PUBLIC PROC [viewer: ViewerClasses.Viewer, newBounds: Imager.Box] = { IF IsPlotViewer[viewer] THEN { handle: Handle _ NARROW[viewer.data]; Lock[handle]; handle.plotSpec.bounds _ newBounds; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- Zoom IsPlotViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [BOOL] = { IF viewer = NIL THEN RETURN[FALSE]; RETURN[viewer.class.flavor = $Plot]; }; -- IsPlotViewer SetColor: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; IF handle # NIL THEN { newColorType: ColorType = SELECT PopUpMenu.RequestSelection[ label: "Options", choice: LIST["my colors", "black and white", "Cedar default colors"], default: 1] FROM 1 => mine, 2 => bw, 3 => cedar, ENDCASE => handle.colorType; IF handle.colorType = newColorType THEN RETURN; Lock[handle]; handle.colorType _ newColorType; SELECT newColorType FROM mine => SetMyColors[]; cedar => SetCedarColors[]; ENDCASE; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- SetColor CursorColor: Menus.MenuProc = { SELECT PopUpMenu.RequestSelection[ label: "Options", choice: LIST["black", "white"], default: 1] FROM 1 => [] _ Terminal.SetColorCursorPresentation[virtual, onesAreBlack]; 2 => [] _ Terminal.SetColorCursorPresentation[virtual, onesAreWhite]; ENDCASE; }; -- CursorColor BackgroundColor: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; IF handle # NIL THEN { newBkGndType: BackgroundType = SELECT PopUpMenu.RequestSelection[ label: "Options", choice: LIST["white", "gray", "dark gray", "black"], default: 1] FROM 1 => white, 2 => gray, 3 => darkGray, 4 => black, ENDCASE => handle.background; IF handle.background = newBkGndType THEN RETURN; Lock[handle]; handle.background _ newBkGndType; SetCursorForBackgroundType[handle.background]; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- BackgroundColor Get: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; name: Rope.ROPE _ ViewerTools.GetSelectionContents[]; msg: Rope.ROPE _ IF name.IsEmpty[] THEN "Please select a plot file name." ELSE IF handle = NIL THEN "Not a good plot viewer." ELSE ReadPlotFile[name: name, viewer: viewer, iconic: FALSE]; IF msg # NIL THEN { MessageWindow.Blink[]; MessageWindow.Append[msg, TRUE]; }; }; -- Get Store: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; name: Rope.ROPE _ ViewerTools.GetSelectionContents[]; msg: Rope.ROPE _ SavePlot[viewer, name]; -- viewer type checked there IF msg # NIL THEN { MessageWindow.Blink[]; MessageWindow.Append[message: msg, clearFirst: TRUE]; }; }; -- Store AppendExtension: PROC[old, ext: Rope.ROPE _ NIL] RETURNS [Rope.ROPE]= { excl: INT _ old.Find["!"]; RETURN[Rope.Cat[ SELECT excl FROM < 0 => old, > 0 => old.Substr[0, excl], ENDCASE => "Plot", -- unlikely, though. ".", ext]]; }; -- AppendExtension GetPrintingColor: PROC[] RETURNS [type: ColorType, ok: BOOL _ TRUE] = { colorChoice: NAT = PopUpMenu.RequestSelection[ label: "Options", choice: LIST["color", "black and white"], default: 1]; SELECT colorChoice FROM 1 => type _ mine; 2 => type _ bw; ENDCASE => { MessageWindow.Blink[]; MessageWindow.Append[ "You must choose either color or black-and-white.", TRUE]; ok _ FALSE; }; }; -- GetPrintingColor Print: PROC[viewer: ViewerClasses.Viewer, output: OutputType _ interpress] = { handle: Handle _ NARROW[viewer.data]; colorToPrint: ColorType; okToPrint: BOOL; [colorToPrint, okToPrint] _ GetPrintingColor[]; IF okToPrint THEN { context: Imager.Context _ NIL; interpress: ImagerInterpress.Ref _ NIL; fileName: Rope.ROPE; colorSave: ColorType; Lock[handle]; { colorSave _ handle.colorType; handle.colorType _ colorToPrint; SELECT output FROM interpress => { InterpressDrawMe: PROC[context: Imager.Context] ~ { context.RotateT[90]; context.ScaleT[coordRatio]; DrawMe[context, handle, NIL, interpress]; }; fileName _ AppendExtension[handle.plotSpec.file, "ip"]; interpress _ ImagerInterpress.Create[fileName ! FS.Error => { MessageWindow.Append[error.explanation, TRUE]; CONTINUE; } ]; ImagerInterpress.DoPage[interpress, InterpressDrawMe]; MessageWindow.Append[Rope.Cat[fileName, " is written."]]; }; press => { fileName _ AppendExtension[handle.plotSpec.file, "press"]; context _ ImagerPress.SimpleCreate[fileName ! FS.Error => { MessageWindow.Append[error.explanation, TRUE]; CONTINUE; } ]; context.RotateT[90]; context.ScaleT[coordRatio]; DrawMe[context, handle, NIL, press]; MessageWindow.Append[Rope.Cat[fileName, " is written."]]; }; ENDCASE; -- (error) IF output = interpress AND interpress # NIL THEN ImagerInterpress.Close[interpress] ELSE IF output = press AND context # NIL THEN ImagerPress.Close[context]; handle.colorType _ colorSave; }; Unlock[handle]; }; }; -- Print Press: Menus.MenuProc = {Print[NARROW[parent], press]}; -- Press Interpress: Menus.MenuProc = {Print[NARROW[parent], interpress]}; -- Interpress SetCedarColors: PROC [] = { ImagerColorMap.SetStandardColorMap[virtual]; WindowManager.RestoreCursor[]; IF Terminal.GetColorCursorPresentation[virtual] # onesAreBlack THEN [] _ Terminal.SetColorCursorPresentation[virtual, onesAreBlack]; }; -- SetCedarColors PaintAllPlotViewers: PROC [] = { PaintEachPlotViewer: ViewerOps.EnumProc = { IF v.column = color AND NOT v.iconic AND v.class.flavor = $Plot THEN ViewerOps.PaintViewer[v, all, TRUE, NIL]; }; ViewerOps.EnumerateViewers[PaintEachPlotViewer]; }; -- PaintAllPlotViewers SetMyColors: PROC [] = { defaultRGB: ARRAY NColors OF ImagerColor.RGB _ [ [R: 1.0, G: 1.0, B: 1.0], [R: 1.0, G: 0.0, B: 0.8], -- purple red [R: 0.4, G: 0.0, B: 1.0], -- purple [R: 0.0, G: 0.0, B: 1.0], -- blue [R: 0.0, G: 0.56, B: 1.0], -- greenish blue [R: 0.0, G: 1.0, B: 1.0], -- green blue [R: 0.0, G: 1.0, B: 0.0], -- green [R: 0.52, G: 1.0, B: 0.0], -- yellow green [R: 1.0, G: 1.0, B: 0.0], -- yellow [R: 1.0, G: 0.60, B: 0.0], -- orange yellow [R: 1.0, G: 0.42, B: 0.0], -- yellowish orange [R: 1.0, G: 0.25, B: 0.0], -- orange [R: 1.0, G: 0.0, B: 0.0], -- red [R: 0.5, G: 0.5, B: 0.5], -- gray [R: 0.17, G: 0.17, B: 0.17], -- dark gray [R: 0.0, G: 0.0, B: 0.0] -- black ]; GetColor: PROC [num: NColors] RETURNS [ImagerColor.RGB] = { ColorSpec: TYPE = LIST OF Rope.ROPE; list: ColorSpec _ UserProfile.ListOfTokens[ key: IO.PutFR["Plot.Color%g", IO.int[num]], default: NIL ]; c: ARRAY[1..3] OF REAL _ [defaultRGB[num].R, defaultRGB[num].G, defaultRGB[num].B]; i: CARDINAL _ 1; FOR lr: ColorSpec _ list, list.rest UNTIL list = NIL OR i > 3 DO c[i] _ Convert.RealFromRope[list.first ! Convert.Error => EXIT]; c[i] _ MAX[0, MIN[1.0, c[i]]]; i _ i + 1; ENDLOOP; RETURN[[R: c[1], G: c[2], B: c[3]]]; }; -- GetColor FOR i: NColors IN NColors DO c: ImagerColor.RGB _ GetColor[i]; entry: ImagerColorMap.MapEntry _ [ mapIndex: i+100, red: Real.RoundC[c.R*255], green: Real.RoundC[c.G*255], blue: Real.RoundC[c.B*255] ]; ImagerColorMap.LoadEntries[ vt: virtual, mapEntries: LIST[entry], shared: FALSE]; color[i] _ ImagerDitheredDevice.ColorFromSpecialRGB[ specialPixel: [i+100, null], rgb: c]; ENDLOOP; }; -- SetMyColors Display: ViewerClasses.PaintProc = { IF IsPlotViewer[self] AND NOT self.iconic THEN { handle: Handle _ NARROW[self.data]; IF handle # NIL THEN { Lock[handle]; DrawMe[context, handle, whatChanged, screen]; Unlock[handle]; }; }; }; -- Display DrawMe: PROC [context: Imager.Context, handle: Handle, whatChanged: REF ANY, output: OutputType] = { BoundsValid: PROC [box: Imager.Box] RETURNS [BOOL] = INLINE { RETURN[box.xmax > box.xmin AND box.ymax > box.ymin]; }; -- BoundsValid rect: Imager.Rectangle _ IF output = interpress THEN [0.0, -518.16, 670.56, 518.16] ELSE ImagerBackdoor.GetBounds[context]; box: Imager.Box _ [rect.x, rect.y, rect.x + rect.w, rect.y + rect.h]; foreground: Imager.ConstantColor _ IF output # screen THEN color[15] ELSE IF handle.background = black OR handle.background = darkGray THEN color[0] ELSE color[15]; IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN { background: Imager.ConstantColor _ IF output # screen THEN color[0] ELSE SELECT handle.background FROM gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0]; width: REAL _ rect.w; height: REAL _ rect.h; marginRatio: REAL = 0.05; innerRatio: REAL = 0.9; marginX: REAL _ width*marginRatio; marginY: REAL _ height*marginRatio; maxX: REAL _ box.xmax - marginX; maxY: REAL _ box.ymax - marginY; minX: REAL _ box.xmin + marginX; minY: REAL _ box.ymin + marginY; innerWidth: REAL _ width * innerRatio; innerHeight: REAL _ height * innerRatio; tenthHeight: REAL _ innerHeight/10.0; textLineHeight: REAL _ fontHeight[normal][output] + TextHeightFudge; windowTooLow: BOOL _ tenthHeight < textLineHeight; legendTop: REAL _ IF windowTooLow THEN minY ELSE minY + tenthHeight*1.8; axesTop: REAL _ IF windowTooLow THEN maxY ELSE maxY - tenthHeight; titleBox: Imager.Box _ [minX, axesTop, maxX, maxY]; axesBox: Imager.Box _ [minX, legendTop, maxX, axesTop]; legendBox: Imager.Box _ [minX, minY, maxX, legendTop]; IF output = screen THEN { -- clear the screen context.SetColor[background]; context.MaskBox[box]; }; context.SetColor[foreground]; IF NOT windowTooLow THEN { excl: INT _ handle.plotSpec.file.Find["!"]; pattern: Rope.ROPE = IF excl < 0 THEN handle.plotSpec.file ELSE handle.plotSpec.file.Substr[0, excl]; footNote: Rope.ROPE _ IO.PutFR["File: %g; time: %g.", IO.rope[pattern], IO.time[handle.plotSpec.time]]; DrawTitle[context, titleBox, handle.plotSpec.title, output]; DrawLegendFootnote[context, legendBox, handle.plotSpec.legendEntries, footNote, output, handle.colorType]; }; IF BoundsValid[handle.plotSpec.bounds] THEN { c: Curves _ NIL; [handle.curvesBox, handle.realBounds] _ DrawAxes[context, axesBox, handle.plotSpec.bounds, output]; context.ClipRectangle[ [handle.curvesBox.xmin, handle.curvesBox.ymin, handle.curvesBox.xmax - handle.curvesBox.xmin, handle.curvesBox.ymax - handle.curvesBox.ymin]]; FOR l: Curves _ handle.curves, l.rest UNTIL l = NIL DO c _ CONS[l.first, c]; ENDLOOP; DrawCurves[context, handle, c, output]; }; } ELSE IF ISTYPE[whatChanged, Curves] THEN { OPEN handle; cv: Curves _ NARROW[whatChanged]; IF cv # NIL AND BoundsValid[handle.realBounds] THEN { m0, m1: Vector; m1 _ cv.first; IF m1 = NIL OR cv.rest = NIL THEN RETURN; m0 _ cv.rest.first; IF m0 = NIL THEN RETURN; IF m0.size = m1.size THEN { tMin: REAL = realBounds.xmin; tFactor: REAL = (curvesBox.xmax - curvesBox.xmin + 1)/(realBounds.xmax - tMin); vMin: REAL = realBounds.ymin; vFactor: REAL = (curvesBox.ymax - curvesBox.ymin + 1)/(realBounds.ymax - vMin); t0: REAL = (m0[0] - tMin)*tFactor; t1: REAL = (m1[0] - tMin)*tFactor; context.ClipRectangle[ [handle.curvesBox.xmin, handle.curvesBox.ymin, handle.curvesBox.xmax - handle.curvesBox.xmin, handle.curvesBox.ymax - handle.curvesBox.ymin]]; context.TranslateT[[curvesBox.xmin, curvesBox.ymin]]; context.SetStrokeEnd[round]; context.SetStrokeWidth[minLineWidth]; IF handle.colorType = bw THEN { context.SetColor[foreground]; FOR index: CARDINAL IN [1..m0.size) DO ipath: CARDINAL _ index - 1; DrawLineSeg[context, [t0, (m0[index]-vMin)*vFactor], [t1, (m1[index]-vMin)*vFactor], ipath MOD 12, handle.lineStates[ipath]]; ENDLOOP; } ELSE FOR index: CARDINAL IN [1..m0.size) DO ipath: CARDINAL _ index - 1; context.SetColor[color[(ipath MOD 12) + 1]]; context.MaskVector[ [t0, (m0[index]-vMin)*vFactor], [t1, (m1[index]-vMin)*vFactor]]; ENDLOOP; }; }; }; }; -- DrawMe DrawTitle: PROC [context: Imager.Context, box: Imager.Box, title: Rope.ROPE, output: OutputType] = { xPos: REAL _ box.xmin + (box.xmax - box.xmin) / 2.0; boxH: REAL _ box.ymax - box.ymin - fontHeight[normal][output]; IF title.IsEmpty[] THEN RETURN; IF boxH >= fontHeight[title][output] THEN [] _ MyDrawText[context, title, xPos, box.ymax, center, top, title, output] ELSE IF boxH >= fontHeight[normal][output] THEN [] _ MyDrawText[context, title, xPos, box.ymax, center, top, normal, output]; }; -- DrawTitle DrawAxes: PROC [context: Imager.Context, box, bounds: Imager.Box, output: OutputType] RETURNS [innerBox, realBounds: Imager.Box] = { Border: PROC = { context.SetStrokeEnd[square]; context.SetStrokeJoint[round]; context.SetStrokeWidth[minLineWidth*2.0]; IF output = press THEN { -- Press doesn't support MaskStroke. context.MaskVector[[innerBox.xmin, innerBox.ymin], [innerBox.xmin, innerBox.ymax]]; context.MaskVector[[innerBox.xmin, innerBox.ymax], [innerBox.xmax, innerBox.ymax]]; context.MaskVector[[innerBox.xmax, innerBox.ymax], [innerBox.xmax, innerBox.ymin]]; context.MaskVector[[innerBox.xmax, innerBox.ymin], [innerBox.xmin, innerBox.ymin]]; } ELSE { borderPath: Imager.PathProc = { moveTo[[innerBox.xmin, innerBox.ymin]]; lineTo[[innerBox.xmin, innerBox.ymax]]; lineTo[[innerBox.xmax, innerBox.ymax]]; lineTo[[innerBox.xmax, innerBox.ymin]]; }; context.MaskStroke[path: borderPath, closed: TRUE]; }; }; -- Border HorizontalAxisLabels: PROC[context: Imager.Context, box: Imager.Box, min, max, scale, step: REAL] = { tickLen: REAL _ (box.ymax - box.ymin)/50.0; tickCount: CARDINAL = Real.RoundI[(max - min)/step]; textTop: REAL _ box.ymin - TextHeightFudge; tick: REAL; FOR i: CARDINAL IN [0..tickCount] DO tick _ step*scale*i; [] _ MyDrawText[context, IO.PutFR["%g", IO.real[step*i + min]], box.xmin + tick, textTop, center, top, normal, output]; IF i # 0 AND i # tickCount THEN { xPos: REAL = box.xmin + tick; yStart: REAL _ box.ymin; yEnd: REAL _ box.ymin + tickLen; Tick: Imager.PathProc = {moveTo[[xPos, yStart]]; lineTo[[xPos, yEnd]]}; proc: PROC = {context.MaskStroke[path: Tick, closed: FALSE]}; IF output = press THEN context.MaskVector[[xPos, yStart], [xPos, yEnd]] ELSE context.DoSave[proc]; yStart _ box.ymax; yEnd _ box.ymax - tickLen; IF output = press THEN context.MaskVector[[xPos, yStart], [xPos, yEnd]] ELSE context.DoSave[proc]; }; ENDLOOP; }; -- HorizontalAxisLabels VerticalAxisLabels: PROC[context: Imager.Context, box: Imager.Box, min, max, scale, step: REAL] = { tickLen: REAL _ (box.ymax - box.ymin)/50.0; tickCount: CARDINAL = Real.RoundI[(max - min)/step]; textRight: REAL _ box.xmin - TextWidthFudge; tick: REAL; FOR i: CARDINAL IN [0..tickCount] DO tick _ step*scale*i; [] _ MyDrawText[context, IO.PutFR["%g", IO.real[step*i + min]], textRight, box.ymin + tick, right, center, normal, output]; IF i # 0 AND i # tickCount THEN { yPos: REAL = box.ymin + tick; xStart: REAL _ box.xmin; xEnd: REAL _ box.xmin + tickLen; Tick: Imager.PathProc = {moveTo[[xStart, yPos]]; lineTo[[xEnd, yPos]]}; proc: PROC = {context.MaskStroke[path: Tick, closed: FALSE]}; IF output = press THEN context.MaskVector[[xStart, yPos], [xEnd, yPos]] ELSE context.DoSave[proc]; xStart _ box.xmax; xEnd _ box.xmax - tickLen; IF output = press THEN context.MaskVector[[xStart, yPos], [xEnd, yPos]] ELSE context.DoSave[proc]; }; ENDLOOP; }; -- VerticalAxisLabels xSize: REAL = box.xmax-box.xmin; ySize: REAL = box.ymax-box.ymin; xStep, yStep: REAL; -- between ticks xScale, yScale: REAL; nLabelsX, nLabelsY: CARDINAL; hLabelWidth, hLabelHeight, vLabelWidth, vLabelHeight, tLabelWidth, tLabelHeight: REAL; inBoxW, inBoxH: REAL; [tLabelWidth, tLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmin]], normal, output]; [hLabelWidth, hLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmax]], normal, output]; hLabelWidth _ MAX[tLabelWidth, hLabelWidth]; hLabelHeight _ MAX[tLabelHeight, hLabelHeight]; [tLabelWidth, tLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymin]], normal, output]; [vLabelWidth, vLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymax]], normal, output]; vLabelWidth _ MAX[tLabelWidth, vLabelWidth]; vLabelHeight _ MAX[tLabelHeight, vLabelHeight]; innerBox _ [ xmin: box.xmin + vLabelWidth + TextWidthFudge, ymin: box.ymin + hLabelHeight + TextHeightFudge*2, xmax: box.xmax, ymax: box.ymax]; IF innerBox.xmin > innerBox.xmax THEN innerBox.xmin _ box.xmin; IF innerBox.ymin > innerBox.ymax THEN innerBox.ymin _ box.ymin; inBoxW _ innerBox.xmax - innerBox.xmin; inBoxH _ innerBox.ymax - innerBox.ymin; context.DoSave[Border]; nLabelsX _ MAX[1, MIN[10, Real.RoundI[inBoxW/hLabelWidth*5/8]]]; nLabelsY _ MAX[1, MIN[10, Real.RoundI[inBoxH/vLabelHeight*5/8]]]; [realBounds.xmin, realBounds.xmax, xStep, xScale] _ ScaleAxis[ bounds.xmin, bounds.xmax, inBoxW, nLabelsX]; [realBounds.ymin, realBounds.ymax, yStep, yScale] _ ScaleAxis[ bounds.ymin, bounds.ymax, inBoxH, nLabelsY]; context.SetStrokeEnd[butt]; context.SetStrokeWidth[minLineWidth]; HorizontalAxisLabels[context, innerBox, realBounds.xmin, realBounds.xmax, xScale, xStep]; VerticalAxisLabels[context, innerBox, realBounds.ymin, realBounds.ymax, yScale, yStep]; }; -- DrawAxes GetRopeSize: PROC [text: Rope.ROPE, fontType: FontType _ normal, output: OutputType _ screen] RETURNS [dx, dy: REAL] = { font: ImagerFont.Font_ textFont[fontType][output]; extents: ImagerFont.Extents _ ImagerFont.RopeBoundingBox[font, text]; dx _ extents.rightExtent - extents.leftExtent; dy _ extents.descent + extents.ascent; }; -- GetRopeSize ScaleAxis: PROC [minDataValue, maxDataValue, innerBoxSize: REAL, nLabels: CARDINAL] RETURNS [min, max, step, scale: REAL] = { step _ FindStepSize[maxDataValue - minDataValue, nLabels]; min _ AlignEnd[minDataValue, step, FALSE]; max _ AlignEnd[maxDataValue, step, TRUE]; IF Almost[min, max] THEN {max _ max + 50.0; min _ min - 50.0}; scale _ innerBoxSize/(max - min); }; -- ScaleAxis FindStepSize: PROC [range: REAL, nSteps: CARDINAL] RETURNS[step: REAL] = { logRange: REAL; mantissa, minStep: REAL; characteristic: INTEGER; steps: ARRAY [0..6) OF REAL = [0.2, 0.5, 1.0, 2.0, 5.0, 10.0]; IF Almost[range, 0.0] THEN range _ 100.0; logRange _ RealFns.Log[10.0, range]; characteristic _ Real.FixI[logRange]; mantissa _ logRange - characteristic; IF logRange < 0.0 THEN { characteristic _ characteristic - 1; mantissa _ mantissa + 1.0}; minStep _ RealFns.Power[10.0, mantissa]/nSteps; FOR i: CARDINAL IN [0..5) DO step _ steps[i]; IF step > minStep OR Almost[step, minStep] THEN EXIT ENDLOOP; IF characteristic >= 0 THEN THROUGH [1..characteristic] DO step _ step*10.0 ENDLOOP ELSE THROUGH [1..-characteristic] DO step _ step/10.0 ENDLOOP; step _ MAX[1.0, step]; }; -- FindStepSize AlignEnd: PROC [e, step: REAL, roundUp: BOOL] RETURNS [ae: REAL] = { absE: REAL = ABS[e]; nSteps: INTEGER; xend: REAL; IF e = 0.0 THEN RETURN[0.0]; IF e < 0.0 THEN roundUp _ ~roundUp; nSteps _ Real.RoundLI[absE/step - 0.5]; xend _ step*nSteps; IF Almost[nSteps, absE/step] THEN ae _ xend ELSE IF roundUp THEN ae _ IF xend >= absE THEN xend ELSE step*(nSteps + 1) ELSE ae _ IF xend <= absE THEN xend ELSE step*(nSteps - 1); IF e < 0.0 THEN ae _ -ae; }; -- AlignEnd Almost: PROC [p, q: REAL] RETURNS [a: BOOL _ TRUE] = { max: REAL _ MAX[ABS[p], ABS[q]]; IF max # 0.0 THEN a _ (ABS[p - q]/max) < 0.00001; }; -- Almost MyDrawText: PROC[context: Imager.Context, text: Rope.ROPE, x0, y0: REAL, xJustification: {left, center, right} _ left, yJustification: {top, center, bottom} _ bottom, fontType: FontType _ normal, output: OutputType _ screen, xScale, yScale: REAL _ 1.0] RETURNS [Imager.VEC] = { proc: PROC = { context.TranslateT[[x0, y0]]; context.SetXY[[xmin, ymin]]; context.SetFont[font]; context.Scale2T[[xScale, yScale]]; context.ShowRope[text]; }; -- proc extents: ImagerFont.Extents; xmin, ymin, width, height: REAL; font: ImagerFont.Font = textFont[fontType][output]; IF text.IsEmpty[] THEN RETURN[[x0, y0]]; extents _ ImagerFont.RopeBoundingBox[font: font, rope: text]; extents _ [extents.leftExtent*xScale, extents.rightExtent*xScale, extents.descent*yScale, extents.ascent*yScale]; width _ extents.rightExtent - extents.leftExtent; height _ extents.descent + extents.ascent; xmin_ SELECT xJustification FROM right => - width - extents.leftExtent, center => - width/2.0 - extents.leftExtent, ENDCASE => - extents.leftExtent; ymin_ SELECT yJustification FROM top => - height + extents.descent, center => - height/2.0 + extents.descent, ENDCASE => extents.descent; context.DoSave[proc]; RETURN[IF output = interpress THEN [x0 + width, y0 + height] ELSE ImagerBackdoor.GetCP[context]]; }; -- MyDrawText DrawCurves: PROC [context: Imager.Context, handle: Handle, curves: Curves, output: OutputType] = { box: Imager.Box _ handle.curvesBox; bounds: Imager.Box _ handle.realBounds; colorType: ColorType _ handle.colorType; IF curves # NIL THEN { nCurvesMax: CARDINAL = curves.first.size - 1; tFactor: REAL _ (box.xmax - box.xmin)/(bounds.xmax - bounds.xmin); vFactor: REAL _ (box.ymax - box.ymin)/(bounds.ymax - bounds.ymin); drawThem: PROC = { context.TranslateT[[box.xmin, box.ymin]]; context.SetStrokeWidth[minLineWidth]; context.SetStrokeJoint[mitered]; IF colorType = bw THEN { context.SetStrokeEnd[butt]; FOR i: CARDINAL IN [1..nCurvesMax] DO firstPoint: BOOL _ TRUE; lastVec, newVec: Imager.VEC; index: CARDINAL _ i - 1; handle.lineStates[index]^ _ []; -- cf. AddVector. FOR graph: Curves _ curves, graph.rest UNTIL graph = NIL DO newVec _ [ (graph.first[0] - bounds.xmin)*tFactor, (graph.first[i] - bounds.ymin)*vFactor]; IF firstPoint THEN firstPoint _ FALSE ELSE DrawLineSeg[context, lastVec, newVec, index MOD 12, handle.lineStates[index]]; lastVec _ newVec; ENDLOOP; ENDLOOP; } ELSE { context.SetStrokeEnd[round]; FOR i: CARDINAL IN [1..nCurvesMax] DO context.SetColor[color[((i-1) MOD 12)+1]]; IF output = press THEN { normal: BOOL _ FALSE; lastVec: Imager.VEC; FOR graph: Curves _ curves, graph.rest UNTIL graph = NIL DO t: REAL _ (graph.first[0] - bounds.xmin)*tFactor; v: REAL _ (graph.first[i] - bounds.ymin)*vFactor; IF normal THEN context.MaskVector[lastVec, [t, v]] ELSE normal _ TRUE; lastVec _ [t, v]; ENDLOOP; } ELSE { oneCurve: Imager.PathProc = { start: BOOL _ TRUE; FOR graph: Curves _ curves, graph.rest UNTIL graph = NIL DO t: REAL _ (graph.first[0] - bounds.xmin)*tFactor; v: REAL _ (graph.first[i] - bounds.ymin)*vFactor; IF start THEN moveTo[[t, v]] ELSE lineTo[[t, v]]; start _ FALSE; ENDLOOP; }; -- oneCurve context.MaskStroke[path: oneCurve, closed: FALSE]; }; ENDLOOP; }; }; -- drawThem IF nCurvesMax > 0 THEN context.DoSave[drawThem]; }; }; -- DrawCurves LineShape: TYPE = {solid, dot, dash, longDash}; StepSize: ARRAY LineShape[dot..longDash] OF REAL = [3.0, 6.0, 9.0]; LineLimit: ARRAY LineShape[dot..longDash] OF REAL = [1.0, 4.0, 7.0]; -- beyond which is space. LineStyle: TYPE = NColors[0..12); shape: ARRAY LineStyle OF ARRAY LineStep OF LineShape = [ [solid, solid, solid, solid, solid, solid], -- solid, useless [dot, dot, dot, dot, dot, dot], -- dot [dash, dash, dash, dash, dash, dash], -- dash [longDash, longDash, longDash, longDash, longDash, longDash], -- longDash [dot, dash, dot, dash, dot, dash], -- dot-dash [dot, longDash, dot, longDash, dot, longDash], -- dot-longDash [dot, dot, dash, dot, dot, dash], -- dot-dot-dash [dot, dot, longDash, dot, dot, longDash], -- dot-dot-longDash [dot, dash, dash, dot, dash, dash], -- dot-dash-dash [dot, longDash, longDash, dot, longDash, longDash], -- dot-longDash-longDash [dash, longDash, dash, longDash, dash, longDash], -- dash-longDash [dot, dash, longDash, dot, dash, longDash] -- dot-dash-longDash ]; DrawLineSeg: PROC[context: Imager.Context, v0, v1: Imager.VEC _ [0.0, 0.0], style: LineStyle _ 0, state: LineState _ NIL] = { IF Almost[v0.x, v1.x] AND Almost[v0.y, v1.y] THEN RETURN; IF state = NIL THEN state _ NEW[LineStateRec _ []]; IF style = 0 THEN context.MaskVector[v0, v1] ELSE { -- Note that normally state.step and state.progress will be updated. dx: REAL = v1.x - v0.x; dy: REAL = v1.y - v0.y; lengthToGo: REAL _ RealFns.SqRt[dx*dx + dy*dy]; cosine: REAL _ dx/lengthToGo; sine: REAL _ dy/lengthToGo; newVec, oldVec: Imager.VEC _ v0; stepSize, lineLimit, increment: REAL; drawIt: BOOL; UNTIL Almost[lengthToGo, 0.0] DO stepSize_ StepSize[shape[style][state.step]]; lineLimit_ LineLimit[shape[style][state.step]]; IF state.progress < lineLimit THEN { drawIt _ TRUE; increment _ MIN[lengthToGo, lineLimit - state.progress]; } ELSE { drawIt _ FALSE; increment _ MIN[lengthToGo, stepSize - state.progress]; }; lengthToGo _ lengthToGo - increment; state.progress _ state.progress + increment; newVec _ [oldVec.x + cosine*increment, oldVec.y + sine*increment]; IF drawIt THEN context.MaskVector[oldVec, newVec] ELSE IF Almost[state.progress, stepSize] THEN { state.step _ (state.step + 1) MOD MaxStep; state.progress _ 0.0; }; oldVec _ newVec; ENDLOOP; }; }; -- DrawLineSeg DrawLegendFootnote: PROC [context: Imager.Context, box: Imager.Box, legendEntries: REF RopeSequence, footNote: Rope.ROPE, output: OutputType, colorType: ColorType] = { nEntries: CARDINAL _ legendEntries.size; MaxEntriesPerColumn: CARDINAL = 5; xmin, xmax: REAL _ 0; yIncPerRow: REAL _ MAX[-fontHeight[normal][output], (box.ymin - box.ymax) / MaxEntriesPerColumn]; -- note: they are negative. yScale: REAL = -yIncPerRow / fontHeight[normal][output]; colorSampleLineLength: REAL = 34.0; sampleLineLength: ARRAY[0..12) OF REAL = [ 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 31.0, 31.0, 34.0, 34.0]; state: LineState _ NEW[LineStateRec]; proc: PROC = { context.TranslateT[[box.xmin, box.ymax]]; IF yScale >= 0.99 THEN [] _ MyDrawText[context, footNote, 0, -(box.ymax-box.ymin), left, top, normal, output]; FOR i: CARDINAL IN [0..nEntries) DO row: CARDINAL _ i MOD MaxEntriesPerColumn; col: CARDINAL _ i / MaxEntriesPerColumn; index: CARDINAL _ i MOD 12; textStartX, textStartY, lineY, newX: REAL; IF row = 0 AND col > 0 THEN { xmin _ xmax + TextWidthFudge; IF xmin >= box.xmax THEN EXIT; }; textStartX _ xmin + 36.0; textStartY _ row*yIncPerRow; lineY _ textStartY + (yIncPerRow/2.0); context.SetStrokeWidth[minLineWidth]; context.SetStrokeEnd[butt]; IF colorType = bw THEN { state^ _ []; DrawLineSeg[context, [xmin, lineY], [xmin+sampleLineLength[index], lineY], index, state]; } ELSE { context.SetColor[color[index+1]]; context.MaskVector[[xmin, lineY], [xmin+colorSampleLineLength, lineY]]; }; [[newX, ]] _ MyDrawText [ context, legendEntries[i], textStartX, textStartY, left, top, normal, output, 1.0, yScale]; IF newX > xmax THEN xmax _ newX; ENDLOOP; }; -- proc context.DoSave[proc]; }; -- DrawLegendFootnote MakeMenu: PROC [] RETURNS [menu: Menus.Menu] = { menu _ Menus.CreateMenu[]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Color", SetColor]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Cursor", CursorColor]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Background", BackgroundColor]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry[name: "Get", proc: Get, guarded: TRUE]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Store", Store]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Press", Press]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Interpress", Interpress]]; }; -- MakeMenus ProcessUserProfile: UserProfile.ProfileChangedProc = { rope: Rope.ROPE _ UserProfile.Token[key: "Plot.Column", default: "color"]; initialColumn _ SELECT TRUE FROM rope.Equal["color", FALSE] => color, rope.Equal["left", FALSE] => left, rope.Equal["right", FALSE] => right, ENDCASE => static; IF initialColumn = static THEN { MessageWindow.Append[ Rope.Cat["Warning: Illegal entry in user profile, Plot.Column: ", rope, "."], TRUE]; initialColumn _ color; }; IF initialColumn = color AND NOT WindowManager.colorDisplayOn THEN initialColumn _ left; rope _ UserProfile.Token[key: "Plot.CreateOption", default: "closeOtherViewers"]; createOption _ SELECT TRUE FROM rope.Equal["closeOtherViewers", FALSE] => closeOtherViewers, rope.Equal["swapLastViewer", FALSE] => swapLastViewer, rope.Equal["iconic", FALSE] => iconic, ENDCASE => badCreateOption; IF createOption = badCreateOption THEN { MessageWindow.Append[ Rope.Cat["Warning: Illegal entry in user profile, Plot.CreateOption: ", rope, "."], TRUE]; createOption _ closeOtherViewers; }; rope _ UserProfile.Token[key: "Plot.Background", default: "white"]; backgroundType _ SELECT TRUE FROM rope.Equal["white", FALSE] => white, rope.Equal["gray", FALSE] => gray, rope.Equal["darkGray", FALSE] => darkGray, rope.Equal["black", FALSE] => black, ENDCASE => unknown; IF backgroundType = unknown THEN { MessageWindow.Append[ Rope.Cat["Warning: Illegal entry in user profile, Plot.Background: ", rope, "."], TRUE]; backgroundType _ white; }; SetMyColors[]; SetCursorForBackgroundType[backgroundType]; PaintAllPlotViewers[]; }; -- ProcessUserProfile SetCursorForBackgroundType: PROC [bkgnd: BackgroundType _ white] = { bkGndColor: Imager.ConstantColor _ SELECT bkgnd FROM gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0]; cursorType: Terminal.ColorCursorPresentation _ IF ImagerColor.GrayFromColor[bkGndColor] > 0.5 THEN onesAreWhite ELSE onesAreBlack; IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN [] _ Terminal.SetColorCursorPresentation[virtual, cursorType]; }; -- SetCursorForBackgroundType Init: PROC [] = { ViewerOps.RegisterViewerClass[$Plot, plotViewerClass]; FOR f: FontType IN FontType DO FOR o: OutputType IN OutputType DO extents: ImagerFont.Extents _ ImagerFont.FontBoundingBox[textFont[f][o]]; fontHeight[f][o] _ extents.descent+extents.ascent; ENDLOOP; ENDLOOP; ProcessUserProfile[firstTime]; UserProfile.CallWhenProfileChanges[ProcessUserProfile]; }; -- Init Init[]; }. CHANGE LOG. Created by: SChen in Cedar5. SChen, May 14, 1985 5:39:02 pm PDT, custom icon. SChen, July 11, 1985 11:03:15 pm PDT, => Cedar6.0 (CG, press => Imager, Interpress). SChen, cleaned up some stuff related to color display. TFile: PlotViewer.mesa, Copyright (C) 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, August 1, 1985 7:23:18 pm PDT types constants (constant) variables parameters initialized or updated by ProcessUserProfile public procedures handle Note: lineStates[n] is the state of curve # n+1. Note: if original nCurvesMax # new one, then old data will be cleared. menu commands [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN { }; white forgeround colors, all vivid gray black get color spec from user profile, or return default }; }; other private procedures [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] there will be no checking of the viewer class in DrawMe. press context has been rotated title / legend / footnote axes and curves external vars referenced: output. external vars referenced: output. Press doesn't support MaskStroke. context.SetColor[foreground]; -- already done. ... bad! initialColumn createOption backgroundType colors apply Κ(λ˜JšœU™Ušœ™Jšœ,™,J™—šΟk ˜ Jšœ œœ ˜Jšœœ˜$Jšœœ ˜Jšœœ˜*Jšœœδœ˜υJšœœ˜(Jšœ œœ˜'Jšœœ.˜BJšœœ˜1Jšœ œ@˜PJšœœ˜4Jšœ œ˜(Jšœœ ˜(Jšœœ=˜HJšœœ˜$JšœœO˜YJšœœz˜‡Jšœ œ˜#Jšœœ!˜+Jšœœ˜"Jšœœœ ˜6Jšœ œe˜sJšœ œC˜TJšœœ˜JšœœK˜^Jšœ œk˜zJšœ œ˜)Jšœœ!˜4J˜—šœ œ˜Jšœœœ‹˜ͺJšœ ˜—J˜Jšœ˜J˜J™J˜Jšœ œ˜/Jšœ œ˜!Jšœ œœ˜.Jšœ œ˜&J˜Jšœ ™ J˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœ œΟc˜5J™Jšœ™J˜JšœD˜Dšœ-œ ˜PJšœ7˜7—J˜Jšœ?œž˜Mšœ*˜*Jšœ:˜:—Jšœ;˜;šœ(˜(Jšœ8˜8—Jš œ œ œœ œ:˜iJš œ œ œœ œœ˜7Jšœ/˜/J˜Jšœ7™7Jšœ%ž˜DJšœK˜KJšœ ž*˜JJšœœ œ˜-J˜Jšœ™J˜šΟn œœœœœœœ!œ˜‡Jšœ™šœœ˜#Jšœ˜Jšœ˜Jšœ˜—J™˜ J˜šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœœ˜ J˜Jšœ˜—Jšœœ˜—J˜Jš œœœ$œœ˜Wšœ˜šœ˜šœ(˜(Jšœ ˜ Jšœ œ˜Jšœœ˜ Jšœœ˜ —šœ˜Jšœœ˜"˜"Jš œœœ œ œœ˜MJšœž ˜—J˜'J˜šœ œœ˜'Jšœ ˜ Jšœ œ˜Jšœœ˜ Jšœœ˜ —šœ˜šœ˜Jšœ ˜ Jšœ˜Jšœœž#˜1—Jšœ"œœž˜GJ˜—J˜—Jšœ˜—J˜—Jšœž˜—J˜š Ÿ œœœ!œœ˜Ušœ œœœœœœ˜>Jšœ œœ˜Jšœœ˜Jšœœ˜%šœ œœ˜J˜ Jšœ.˜.šœœ˜8Jšœ6˜8Jšœœœ˜1—šœ˜šœœœ˜šœœ,˜CJšœ0™0—šœœœ ˜4Jšœœ˜.Jšœ˜—J˜—Jšœœœœ˜:Jšœœ˜+J˜—J˜J˜—Jšœ œ'œ˜PJ˜—Jšœž ˜J˜—š Ÿ œœœœœ˜5Jšœœ˜(Jšœ#ž˜>Jšœ œ˜Jšœœ˜%Jšœœœ˜#JšœœG˜QJšœž ˜J˜—š Ÿœœœ!œœ˜VJšœF™Fšœœ˜Jšœœ˜%Jšœ œœœ˜J˜ Jšœœ˜Jšœ˜Jšœ#˜#J˜Jšœ'œœ˜3Jšœ&œœž˜HJ˜—Jšœž ˜ J˜—šŸœœœ#˜5šœœ˜Jšœœ˜%J˜ Jšœ˜Jšœœ˜J˜Jšœ&œœ˜1J˜—Jšœž˜ —J˜šŸœœœ:˜Kšœœ˜Jšœœ˜%J˜ J˜#J˜Jšœ&œœ˜1J˜—Jšœž˜ J˜—š Ÿ œœœ œœ˜KJš œ œœœœ˜#Jšœ˜$Jšœž˜J˜—Jšœ ™ J˜šœ˜JšœN™NJšœœ ˜.Jšœœ˜%šœ œœ˜šœœ˜œ ˜R——J˜Jšœœ ˜Jšœœ ˜J˜Jšœ œ˜Jšœ œ˜Jšœ œ˜"Jšœ œ˜#J˜Jšœœ˜!Jšœœ˜!Jšœœ˜!Jšœœ˜ Jšœ œ˜&Jšœ œ˜(J˜Jšœ œ˜%Jšœœ0˜DJšœœ ˜2Jš œ œœœœ˜HJš œ œœœœ˜BJ˜Jšœ3˜3Jšœ7˜7Jšœ7˜7J˜šœœž˜-J˜J˜Jšœ˜—J˜Jšœ™J˜šœœœ˜Jšœœ"˜+Jš œœœ œœ&˜eJš œœœœœ˜gJšœ=˜=šœE˜EJšœ$˜$—J˜—J˜Jšœ™šœ%œ˜-Jšœ œ˜Jšœc˜cšœ˜JšœŽ˜Ž—šœ#œœ˜6Jšœœ ˜Jšœ˜—Jšœ'˜'J˜—J˜—š œœœœœ˜7Jšœ œ˜!šœœ œ˜5J˜J˜Jš œœœ œœœ˜)J˜Jšœœœœ˜šœœ˜Jšœœ˜Jšœ œB˜OJšœœ˜Jšœ œB˜OJšœœ˜"Jšœœ˜"J˜šœ˜JšœŽ˜Ž—Jšœ5˜5Jšœ˜Jšœ%˜%šœœ˜Jšœ˜šœœœ˜&Jšœœ ˜šœ˜Jšœ?˜?Jšœœ˜(—Jšœ˜—J˜—šœœœ˜+Jšœœ ˜Jšœœ ˜,šœ˜Jšœ@˜@—Jšœ˜—J˜—J˜—J˜—Jšœž ˜ —J˜šŸ œœ8œ˜dJšœœ*˜4Jšœœ4˜>J˜Jšœœœ˜šœ#œ˜*JšœK˜K—šœœ$˜/JšœM˜M—Jšœž ˜J˜—šŸœœG˜UJšœ'˜.J˜šŸœœ˜Jšœ˜J˜Jšœ)˜)šœœ˜Jšž$˜$JšœS˜SJšœS˜SJšœS˜SJšœS˜SJ˜—šœ˜šœ˜J˜'J˜'J˜'J˜'J˜—Jšœ-œ˜3J˜—Jšœž ˜ J˜—šŸœœ+˜EJšœœ˜ Jšœ!™!Jšœ œ˜,Jšœ œ!˜4Jšœ œ˜+Jšœœ˜ J˜šœœœ˜$Jšœ˜šœ˜Jšœ œ˜&Jšœ˜Jšœ˜—šœœœ˜!Jšœœ˜Jšœœ ˜Jšœœ˜ JšœG˜GJšœœ+œ˜=Jšœœ1˜GJšœ˜Jšœ.˜.Jšœœ1˜GJšœ˜J˜—Jšœ˜Jšœž˜J˜——šŸœœ+˜CJšœœ˜ Jšœ!™!Jšœ œ˜,Jšœ œ!˜4Jšœ œ˜,Jšœœ˜ J˜šœœœ˜$Jšœ˜˜Jšœ œ˜&Jšœ˜Jšœ˜—šœœœ˜!Jšœœ˜Jšœœ ˜Jšœœ˜ JšœG˜GJšœœ+œ˜=Jšœœ1˜GJšœ˜Jšœ.˜.Jšœœ1˜GJšœ˜J˜——Jšœ˜Jšœž˜—J˜Jšœœ˜ Jšœœ˜ Jšœœž˜&Jšœœ˜Jšœœ˜JšœQœ˜VJšœœ˜J˜šœ*œ œ˜OJšœ˜—šœ*œ œ˜OJšœ˜—Jšœœ˜,Jšœœ˜/J˜šœ*œ œ˜OJšœ˜—šœ*œ œ˜OJšœ˜—Jšœœ˜,Jšœœ˜/J˜šœ ˜ Jšœ.˜.Jšœ2˜2Jšœ˜Jšœ˜—Jšœœ˜?Jšœœ˜?J˜Jšœ'˜'Jšœ'˜'J˜Jšœ˜J˜Jšœ œœ+˜@Jšœ œœ,˜AJ˜šœ>˜>Jšœ,˜,—šœ>˜>Jšœ,˜,—J˜Jšœ˜Jšœ%˜%JšœY˜YJšœW˜WJšœž ˜—J˜š Ÿ œœ œ<œ œ˜xJšœ2˜2JšœE˜EJšœ.˜.Jšœ&˜&Jšœž˜J˜—šŸ œœ,œ œ˜SJšœœ˜)J˜:Jšœ#œ˜*Jšœ#œ˜)Jšœœ&˜>J˜!šœž ˜J˜——š Ÿ œœ œ œœœ˜JJšœ œ˜Jšœœ˜Jšœœ˜Jšœœœœ#˜?J˜Jšœœ˜)J˜J˜$J˜%J˜%šœœ˜Jšœ$˜$J˜—J˜/šœœœ˜Jšœ˜Jšœœœ˜4Jšœ˜—š œœœœ˜SJšœœœœ˜>—Jšœœ ˜Jšœž˜J˜—š Ÿœœ œ œœœ˜GJšœœœ˜Jšœœ˜Jšœœ˜ J˜Jšœ œœ˜Jšœ œ˜#J˜'J˜Jšœœ ˜+šœœ˜Jšœœœœ˜:Jšœœœœ˜;—Jšœ œ ˜šœž ˜J˜——š Ÿœœœœœœ˜6Jš œœœœœ˜ Jšœ œœ˜1Jšœž ˜ —J˜šŸ œœ˜*Jšœ œ œ˜J˜-J˜/Jšœ˜Jšœ˜Jšœœ˜Jšœ œ˜J˜šœœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ"˜"Jšœ˜Jšœž˜ J˜—Jšœ˜Jšœœ˜ Jšœ3˜3J˜Jšœœœ ˜(Jšœ=˜=šœA˜AJšœ/˜/—Jšœ1˜1Jšœœ!˜*šœœ˜ Jšœ&˜&Jšœ+˜+Jšœ˜ —šœœ˜ Jšœ"˜"Jšœ)˜)Jšœ˜—Jšœ˜šœœœ˜ž ˜IJšœ#ž ˜.Jšœ/ž˜>Jšœ"ž˜1Jšœ*ž˜=Jšœ$ž˜4Jšœ4ž˜LJšœ2ž˜BJšœ+ž˜?J˜J˜—šŸ œœ˜*Jšœœœ˜ Jšœ˜šœœ˜J™—Jšœœœœ˜9Jšœ œœ œ˜3J™Jšœ œ˜,šœžD˜KJšœœ˜Jšœœ˜Jšœ œ˜/Jšœœ˜Jšœœ˜Jšœœ˜ Jšœ œ˜%Jšœœ˜ šœ˜ Jšœ-˜-Jšœ/˜/šœœ˜$Jšœ œ˜Jšœ œ)˜8J˜—šœ˜Jšœ œ˜Jšœ œ(˜7J˜—Jšœ$˜$Jšœ,˜,JšœB˜BJšœœ#˜1šœœ"œ˜/Jšœœ ˜*J˜J˜—Jšœ˜Jšœ˜—Jšœ˜—šœž˜J˜——šŸœœ+˜CJšœœœ˜5Jšœ-˜-Jšœ œ˜(Jšœœ˜"Jšœ œ˜Jšœ œœLž˜}Jšœœ,˜8Jšœœ˜#šœœœœ˜*J˜H—Jšœœ˜%šœœ˜Jšœ)˜)šœ˜JšœW˜W—šœœœ˜#Jšœœœ˜*Jšœœ˜(Jšœœœ˜Jšœ%œ˜*šœ œ œ˜Jšœ˜Jšœœœ˜J˜—Jšœ˜Jšœ˜Jšœ&˜&Jšœ%˜%Jšœ˜šœœ˜Jšœ7™7Jšœ ˜ šœJ˜JJšœ˜—J˜—šœ˜Jšœ!˜!JšœG˜GJ˜—šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ(˜(—Jšœ œ ˜ Jšœ˜—Jšœž˜ —J˜Jšœ˜Jšœž˜J˜—šŸœœœ˜0Jšœ˜˜Jšœ.˜.—˜Jšœ1˜1—˜Jšœ9˜9—šœ˜Jšœ:œ˜B—šœ˜Jšœ+˜+—˜J˜+—˜J˜5—Jšœž ˜—J˜šœ6˜6Jšœ ™ Jšœ œ;˜Jšœœœ˜ Jšœœ ˜$Jšœœ ˜"Jšœœ ˜$Jšœ ˜—šœœ˜ šœ˜JšœM˜MJšœ˜—Jšœ˜J˜—Jšœœœœ˜XJ˜Jšœ ™ JšœQ˜Qšœœœ˜Jšœ œ˜œ ˜R—šœ.˜.Jšœ-œœ˜S—šœ;˜AJšœ>˜>—Jšœž˜ J˜—šŸœœ˜Jšœ6˜6J˜šœ œ ˜šœœ ˜"JšœI˜IJšœ2˜2Jšœ˜—Jšœ˜—J˜Jšœ˜J˜7Jšœž˜ —J˜J˜Jšœ˜J˜šœœ˜ J˜J˜0J˜TJ˜6——…—ϊ»9