DIRECTORY Ascii, Basics, CharDisplays, Containers, Convert, FS, Imager, ImagerBackdoor, ImagerBox, ImagerColor, ImagerFont, InputFocus, IO, IOClasses, Menus, MessageWindow, Real, Rope, Rules, RuntimeError, SimpleDisplays, TIPUser, TypeScript, ViewerClasses, ViewerForkers, ViewerIO, ViewerOps, ViewerSpecs, ViewerTools; SimpleDisplaysViewerImpl: CEDAR MONITOR LOCKS NARROW[ self.data, SDViewerData ].sd USING self: ViewerClasses.Viewer IMPORTS Ascii, Basics, Containers, Convert, FS, Imager, ImagerBackdoor, ImagerBox, ImagerColor, ImagerFont, InputFocus, IO, IOClasses, Menus, MessageWindow, Real, Rope, Rules, RuntimeError, SimpleDisplays, TIPUser, TypeScript, ViewerForkers, ViewerIO, ViewerOps, ViewerSpecs, ViewerTools EXPORTS SimpleDisplays ~ BEGIN OPEN SimpleDisplays; SDViewerData: TYPE ~ REF SDViewerDataRep; SDViewerDataRep: TYPE ~ RECORD [ sd: SimpleDisplayState, cd: CharDisplays.CharDisplay, fromDisplayWriteEnd, fromDisplayReadEnd: IO.STREAM, fonts: ARRAY BOOLEAN OF ARRAY BOOLEAN OF RECORD [ name: Rope.ROPE, scale: REAL _ 1.0, font: Imager.Font] _ [ -- ~ i -- -- i -- --~b-- [ [name: "xerox/tiogafonts/gacha10"], [name: "xerox/tiogafonts/gacha10i"] ], -- b-- [ [name: "xerox/tiogafonts/gacha10b"], [name: "xerox/tiogafonts/gacha10bi"] ] ], le, re, des, asc: REAL, topViewer: Containers.Container _ NIL, -- Parent viewerOverhead: INT, childrenInitialized: BOOLEAN _ FALSE, tViewer: ViewerClasses.Viewer _ NIL, -- Typescript transcriptLines: INT _ 5, tHeight: INT _ 0, transcriptShowing: BOOLEAN _ FALSE, rViewer: ViewerClasses.Viewer _ NIL, rHeight: INT _ 0, -- 0 or rSize rSize: INT _ 2, -- size of rule separating transcript from terminal aViewer: ViewerClasses.Viewer _ NIL, -- Array of characters, the terminal itself aHeight: INT, charH, charW: INT, interlineSpace: INT _ 1, leftExtraSpace: INT, -- number of pixels to leave blanks at left[bottom] margin bottomExtraSpace: INT _ 1, topExtraSpace: INT _ 1, leftReserve, -- # pixels from left[bottom] to origin of nearest character bottomReserve: INT, savedVH, -- viewer height and width with next info was computed savedVW: INT, viewLines, -- computed from font and viewer information viewColumns: INT, verticalUnused: INT, -- vertical space unused in the viewer, in pixels cursorX: INT _ 0, cursorY: INT _ 0, cursorChar: CHAR _ ' , inFocus: BOOLEAN _ FALSE, inFocusColor: ImagerColor.Color _ ImagerColor.ColorFromRGB[[1.0, 0.0, 0.0]], outOfFocusColor: ImagerColor.Color _ ImagerColor.ColorFromRGB[[0.0, 1.0, 1.0]], sdMenu: Menus.Menu, transcriptME: Menus.MenuEntry, logME: Menus.MenuEntry, flushME: Menus.MenuEntry ]; SimpleDisplaysViewer: PUBLIC PROC [sd: SimpleDisplayState, cd: CharDisplays.CharDisplay] RETURNS [v: ViewerClasses.Viewer, fromD: IO.STREAM] = { d: SDViewerData _ NEW[SDViewerDataRep]; tipTable: TIPUser.TIPTable; d.sdMenu _ Menus.CreateMenu[]; d.sd _ sd; d.cd _ cd; d.leftExtraSpace _ ViewerSpecs.scrollBarW + 3; CacheFontInfo[d]; IF d.transcriptShowing THEN { d.tHeight _ Real.Fix[ (d.charH + d.interlineSpace) * d.transcriptLines ]; d.rHeight _ d.rSize; } ELSE { d.tHeight _ 0; d.rHeight _ 0; }; d.aHeight _ Real.Fix[ d.cd.det.lines*(d.charH+d.interlineSpace) + d.bottomExtraSpace + d.topExtraSpace]; d.viewerOverhead _ ViewerSpecs.windowBorderSize + ViewerSpecs.menuHeight + ViewerSpecs.menuBarHeight + ViewerSpecs.captionHeight; d.topViewer _ Containers.Create[ info: [ menu: d.sdMenu, scrollable: FALSE, icon: typescript ] ]; RegisterMenus[ d, d.sdMenu ]; d.tViewer _ TypeScript.Create[ info: [ parent: d.topViewer , wh: d.tHeight, ww: d.topViewer.cw, wy: 0, wx: 0, border: FALSE ] ]; Containers.ChildXBound[d.topViewer, d.tViewer]; d.sd.out _ ViewerIO.CreateViewerStreams[viewer: d.tViewer, name: cd.name, editedStream: FALSE].out; d.sd.sdvData _ d; d.rViewer _ Rules.Create[ info: [ parent: d.topViewer, wh: d.rHeight, ww: d.topViewer.cw, wy: d.tHeight, wx: 0 ] ]; Containers.ChildXBound[d.topViewer, d.rViewer]; tipTable _ TIPUser.InstantiateNewTIPTable["SimpleDisplays.tip"]; IF cd.tipTableName#NIL THEN { first: TIPUser.TIPTable _ NIL; first _ TIPUser.InstantiateNewTIPTable[cd.tipTableName !FS.Error, TIPUser.InvalidTable => CONTINUE]; IF first#NIL THEN { first.mouseTicks _ MIN[first.mouseTicks, tipTable.mouseTicks]; first.opaque _ FALSE; first.link _ tipTable; tipTable _ first } }; d.aViewer _ ViewerOps.CreateViewer[ flavor: $SimpleDisplaysViewer, info: [ parent: d.topViewer, data: d, icon: typescript, wh: d.aHeight, ww: d.topViewer.cw, wy: d.tHeight + d.rHeight, wx: 0, border: FALSE, tipTable: tipTable ] ]; Containers.ChildXBound[d.topViewer, d.aViewer]; Containers.ChildYBound[d.topViewer, d.aViewer]; d.topViewer.openHeight _ d.aHeight + d.tHeight + d.rHeight + d.viewerOverhead; d.childrenInitialized _ TRUE; ComputeLinesInfo[ d ]; -- to initialize the variables concerned ViewerOps.OpenIcon[d.topViewer]; [d.fromDisplayWriteEnd, d.fromDisplayReadEnd] _ IOClasses.CreatePipe[]; d.sd.topViewer _ d.topViewer; d.sd.aViewer _ d.aViewer; RETURN[ d.topViewer, d.fromDisplayReadEnd ]; }; CacheFontInfo: PROC [d:SDViewerData] ~ { escapement: Imager.VEC; FOR isB: BOOLEAN IN [FALSE..TRUE] DO FOR isI: BOOLEAN IN [FALSE..TRUE] DO d.fonts[isB][isI].font _ Imager.FindFontScaled[d.fonts[isB][isI].name, d.fonts[isB][isI].scale] ENDLOOP; ENDLOOP; [[d.le, d.re, d.des, d.asc]] _ ImagerFont.FontBoundingBox[d.fonts[FALSE][FALSE].font]; escapement _ ImagerFont.Escapement[d.fonts[FALSE][FALSE].font, [0,100]]; d.charH _ FixUp[d.des + d.asc]; d.charW _ FixUp[escapement.x]; d.leftReserve _ FixUp[d.le] + d.leftExtraSpace; d.bottomReserve _ FixUp[d.des] + d.bottomExtraSpace; }; FixUp: PROC [r:REAL] RETURNS [i:INT] ~ { RETURN[ Real.Fix[ r+ 0.0001 ] ]; }; ComputeLinesInfo: PROC [d:SDViewerData] ~ { vspace: INT _ d.topViewer.ch - d.bottomExtraSpace - d.topExtraSpace - d.tHeight - d.rHeight; d.viewLines _ vspace / (d.charH+d.interlineSpace); d.verticalUnused _ vspace - (d.viewLines * (d.charH+d.interlineSpace)); d.viewColumns _ (d.topViewer.cw-d.leftExtraSpace) / d.charW; d.topViewer.name _ IO.PutFR["%g term:%gx%g view:%gx%g", IO.rope[d.cd.name], IO.int[d.cd.det.lines], IO.int[d.cd.det.columns], IO.int[d.viewLines], IO.int[d.viewColumns]]; }; RepositionViewers: PROC [d: SDViewerData, force:BOOLEAN _ FALSE] = { IF d.transcriptShowing THEN { d.tHeight _ Real.Fix[ (d.charH + d.interlineSpace) * d.transcriptLines ]; d.rHeight _ d.rSize; } ELSE { d.tHeight _ 0; d.rHeight _ 0; }; IF force THEN d.aHeight _ Real.Fix[ d.cd.det.lines*(d.charH+d.interlineSpace) + d.bottomExtraSpace + d.topExtraSpace] ELSE d.aHeight _ MAX[0, FixUp[d.topViewer.ch - d.bottomExtraSpace - d.topExtraSpace - d.tHeight - d.rHeight ]]; ViewerOps.MoveViewer[ d.tViewer, 0, 0, d.tViewer.ww, d.tHeight, FALSE]; ViewerOps.MoveViewer[ d.rViewer, 0, d.tHeight, d.rViewer.ww, d.rHeight, FALSE ]; ViewerOps.MoveViewer[ d.aViewer, 0, d.tHeight + d.rHeight, d.aViewer.ww, d.aHeight, FALSE ]; ViewerOps.SetOpenHeight[d.topViewer, FixUp[d.aHeight + d.tHeight + d.rHeight + d.bottomExtraSpace + d.topExtraSpace]]; ViewerOps.ComputeColumn[ column: ViewerOps.ViewerColumn[ d.topViewer ] ]; ComputeLinesInfo[ d ]; }; PaintSimpleDisplaysViewer: ENTRY ViewerClasses.PaintProc ~ { ENABLE { RuntimeError.BoundsFault => GO TO cantPaint; UNWIND => NULL; }; d: SDViewerData _ NARROW[self.data]; Imager.SetFont[context, d.fonts[FALSE][FALSE].font]; IF ~ clear THEN ClearCursor[ context, d ]; IF whatChanged = NIL THEN { PaintAll[ d, context ]; } ELSE { fixit: ATOM _ NARROW[ whatChanged ]; SELECT fixit FROM $LINES => { RearrangeScreenLines[ context, d ! Imager.Error => { IF d.sd.debugScroll THEN d.sd.out.PutF[ "blt failed\n" ]; FOR i: INT IN [0..d.sd.theLines.count ) DO d.sd.theLines[i].modified _ new; ENDLOOP; CONTINUE }; ]; FOR i: INT IN [ 0.. d.cd.det.lines ) DO l: REF LineRep = d.sd.theLines[i]; SELECT l.modified FROM unchanged => NULL; new => { PaintLine[ d, context, l, 0, i, d.cd.det.columns ]; }; insert1, delete1, over1, tail => { PaintLine[ d, context, l, l.tailStartColumn, i, d.cd.det.columns - l.tailStartColumn]; }; ENDCASE => ERROR; l.screenLine _ i; l.modified _ unchanged; ENDLOOP; }; $FOCUS => NULL; ENDCASE => NULL; }; PaintCursor[ context, d ]; NOTIFY d.sd.paintDone; EXITS cantPaint => { d: SDViewerData _ NARROW[self.data]; m: Rope.ROPE; m_IO.PutFR["BoundsFault- vl:%g,vc:%g,l:%g,c:%g,termL:%g", IO.int[d.viewLines], IO.int[d.viewColumns], IO.int[d.cd.line], IO.int[d.cd.col], IO.int[d.cd.det.lines] ]; MessageWindow.Append[m,TRUE]; NOTIFY d.sd.paintDone; RETURN; }; }; PaintAll: PROC [d:SDViewerData, context:Imager.Context] ~ { FOR i: INT IN [0..d.sd.theLines.count ) DO l: REF LineRep = d.sd.theLines[i]; PaintLine[ d, context, l, 0, i, l.chars.maxLength]; l.screenLine _ i; l.modified _ unchanged; ENDLOOP; }; PaintLine: PROC [ d:SDViewerData, context:Imager.Context, line:REF LineRep, x, y, count:INTEGER ] ~ { chars: REF TEXT _ line.chars; chars.length _ 0; FOR j: INTEGER DECREASING IN [0..x+count) DO IF chars[j] # Ascii.SP THEN { chars.length _ j+1; EXIT }; ENDLOOP; IF line.hasSomeEmphasis THEN { PaintLineWithEmphasis[ d, context, line, x, y, count ] } ELSE { Imager.SetGray[context, 0.0]; Imager.MaskBox[context, ImagerBox.BoundingBox[ BoxAt[d,x,y], BoxAt[d,x+count-1,y] ] ]; IF x < chars.length THEN { Imager.SetXY[context, CharacterOriginAt[d, x, y]]; Imager.SetGray[context, 1.0]; Imager.ShowText[context, chars, x, count]; }; } }; PaintLineWithEmphasis: PROC [ d:SDViewerData, context:Imager.Context, line:REF LineRep, x, y, count:INTEGER ] ~ { chars: REF TEXT _ line.chars; j: INT _ 0; Imager.SetGray[context, 0.0]; Imager.MaskBox[context, ImagerBox.BoundingBox[ BoxAt[d,x,y], BoxAt[d,x+count-1,y] ] ]; WHILE j < chars.length DO emphs: CharDisplays.Emphs _ line.emphs[j]; k: INT _ j + 1; WHILE k < chars.length AND line.emphs[k] = emphs DO k _ k + 1 ENDLOOP; PaintSegWithEmphasis[ d, context, line, j, y, k-j ]; j _ k; ENDLOOP; }; PaintSegWithEmphasis: PROC [ d:SDViewerData, context:Imager.Context, line:REF LineRep, x, y, count:INTEGER ] ~ { emphs: CharDisplays.Emphs _ line.emphs[x]; box: ImagerBox.Box _ ImagerBox.BoundingBox[ BoxAt[d, x, y], BoxAt[d, x+count-1, y] ]; Imager.SetFont[context, d.fonts[emphs[bold]][emphs[italic]].font]; Imager.SetGray[context, IF emphs[inverse] THEN 1.0 ELSE 0.0]; Imager.MaskBox[context, box ]; Imager.SetGray[context, IF emphs[inverse] THEN 0.0 ELSE 1.0]; Imager.SetXY[context, CharacterOriginAt[d, x, y]]; Imager.ShowText[context, line.chars, x, count]; IF emphs[underline] THEN { box.ymax _ box.ymin + 1; Imager.MaskBox[context, box ]; }; }; RearrangeScreenLines: PROC [c: Imager.Context, d: SDViewerData] ~ { lineCount: INT _ d.sd.theLines.count; i,j: INT; FOR i IN [ 0.. lineCount ) DO l: REF LineRep = d.sd.theLines[i]; IF l.modified # new THEN { rsp: INT = l.screenLine - i; -- positive RSP's are a scroll up IF ((rsp<0) AND (d.sd.theLines[l.screenLine].relativeScreenPosition # 0)) THEN { l.relativeScreenPosition _ 0; l.modified_ new } ELSE l.relativeScreenPosition _ rsp } ELSE l.relativeScreenPosition _ 0; ENDLOOP; i _ 0; WHILE i < lineCount DO startRSP: INT = d.sd.theLines[i].relativeScreenPosition; IF (startRSP = 0) THEN { i _ i + 1; LOOP }; j _ i + 1; WHILE (j < lineCount) AND (d.sd.theLines[j].relativeScreenPosition = startRSP) DO j _ j + 1; ENDLOOP; j _ j - 1; MoveScreenLines[ c, d, i+startRSP, j+startRSP, startRSP ]; i _ j + 1; ENDLOOP; }; MoveScreenLines: PROC [c: Imager.Context, d: SDViewerData, first, last, offset: INT] ~ { r: ImagerBox.Rectangle; startOrigin: ImagerBox.Box _ BoxAt[d, 0, last]; endOrigin: ImagerBox.Box _ BoxAt[d, 0, last-offset]; farCorner: ImagerBox.Box _ BoxAt[d, d.cd.det.columns, first]; IF d.sd.debugScroll THEN d.sd.out.PutF[ "MoveLines[first:%g, last:%g, offset:%g]\n", IO.int[first], IO.int[last], IO.int[offset] ]; IF (offset = 0) OR (first >= d.cd.det.lines) OR (offset >= d.cd.det.lines) OR (last >= d.cd.det.lines) THEN RETURN; r _ ImagerBox.RectangleFromBox[ImagerBox.BoundingBox[startOrigin, farCorner]]; ImagerBackdoor.MoveViewRectangle[ -- may generate Imager.Error, caught above context: c, width: Real.Fix[r.w], height: Real.Fix[r.h], fromX: Real.Fix[startOrigin.xmin], fromY: Real.Fix[startOrigin.ymin], toX: Real.Fix[endOrigin.xmin], toY: Real.Fix[endOrigin.ymin] ]; }; BoxAt: PROC [d:SDViewerData, x,y: INT] RETURNS [b: ImagerBox.Box] ~ { xmin: REAL _ d.leftExtraSpace + (x * d.charW); ymin: REAL _ d.verticalUnused + d.bottomExtraSpace + (((d.viewLines-1)-y) * (d.charH+d.interlineSpace)); RETURN[ [ xmin: xmin, ymin: ymin, xmax: xmin + d.charW, ymax: ymin + d.charH + d.interlineSpace ]] }; CharacterOriginAt: PROC [d:SDViewerData, x,y: INT] RETURNS [p: ImagerBox.VEC] ~ { RETURN[ [ d.leftReserve + (x * d.charW), d.verticalUnused + d.bottomReserve + (((d.viewLines-1)-y) * (d.charH+d.interlineSpace)) ] ]; }; PaintCursor: PROC [c: Imager.Context, d: SDViewerData] = { x: INT _ d.cd.col; y: INT _ d.cd.line; ch: CHAR _ d.sd.theLines[y].chars[x]; Imager.SetColor[ c, IF d.inFocus THEN d.inFocusColor ELSE d.outOfFocusColor ]; Imager.MaskBox[ c, BoxAt[ d, x, y ] ]; Imager.SetXY[c, CharacterOriginAt[d, x, y]]; Imager.SetGray[ c, 0.0 ]; Imager.ShowChar[ c, ch ]; d.cursorX _ x; d.cursorY _ y; d.cursorChar _ ch; }; ClearCursor: PROC [c: Imager.Context, d: SDViewerData] = { x: INT = d.cursorX; y: INT = d.cursorY; Imager.SetGray[ c, 0.0 ]; Imager.MaskBox[ c, BoxAt[ d, x, y ] ]; Imager.SetXY[c, CharacterOriginAt[d, x, y]]; Imager.SetGray[ c, 1.0 ]; Imager.ShowChar[ c, d.cursorChar ]; }; NotifySimpleDisplaysViewer: -- ENTRY -- ViewerClasses.NotifyProc ~ { ENABLE UNWIND => NULL; d: SDViewerData _ NARROW[self.data]; WITH input.first SELECT FROM a: ATOM => SELECT a FROM $TDInput => { r: Rope.ROPE; ctl, shift, meta: BOOL _ FALSE; FOR input _ input.rest, input.rest WHILE input # NIL DO WITH input.first SELECT FROM R: Rope.ROPE => r _ R; t: REF TEXT => r _ Rope.FromRefText[t]; b: ATOM => SELECT b FROM $Ctl => ctl _ TRUE; $Shift => shift _ TRUE; $Meta => meta _ TRUE; ENDCASE => ERROR; ENDCASE => ERROR; ENDLOOP; FOR i: INT IN [0 .. r.Length[]) DO c: CHAR _ r.Fetch[i]; IF NOT shift THEN c _ Ascii.Lower[c]; IF ctl THEN { d: NAT _ c - 0C; cd: NAT _ Basics.BITAND[d, 31]; c _ 0C + cd; }; IF meta THEN c _ c + 128; d.fromDisplayWriteEnd.PutChar[c]; ENDLOOP; RETURN; }; ENDCASE; r: Rope.ROPE => { d.fromDisplayWriteEnd.PutRope[r]; RETURN; }; r: REF TEXT => { d.fromDisplayWriteEnd.PutText[r]; RETURN; }; mouse: TIPUser.TIPScreenCoords => { InputFocus.SetInputFocus[self]; d.inFocus _ TRUE; ViewerForkers.ForkPaint[ viewer: d.aViewer, hint: ViewerClasses.PaintHint.client, clearClient: FALSE, whatChanged: $FOCUS, tryShortCuts: FALSE ]; }; ENDCASE => { MessageWindow.Append["TE:unhandled input type."]; }; }; ModifySimpleDisplaysViewer: -- ENTRY -- ViewerClasses.ModifyProc ~ { ENABLE UNWIND => NULL; d: SDViewerData _ NARROW[self.data]; SELECT change FROM ViewerClasses.ModifyAction.kill => { d.inFocus _ FALSE; ViewerForkers.ForkPaint[ viewer: d.aViewer, hint: ViewerClasses.PaintHint.client, clearClient: FALSE, whatChanged: $FOCUS, tryShortCuts: FALSE ]; }; ENDCASE => NULL; }; AdjustSimpleDisplaysViewer: -- ENTRY -- ViewerClasses.AdjustProc ~ { ENABLE UNWIND => NULL; d: SDViewerData _ NARROW[self.data]; ComputeLinesInfo[ d ]; ViewerForkers.ForkPaint[ viewer: d.topViewer, hint: ViewerClasses.PaintHint.caption, clearClient: TRUE, whatChanged: NIL, tryShortCuts: FALSE ]; RETURN[ TRUE ]; }; RegisterMenus: PROC [d: SDViewerData, sdMenu: Menus.Menu] ~ { d.flushME _ Menus.CreateEntry[ name: IF d.sd.flushMode = FlushOnChangeCount THEN IO.PutFR[ "%g%g", IO.rope[FlushCaptions[d.sd.flushMode]], IO.int[d.sd.changeCountLimit] ] ELSE FlushCaptions[d.sd.flushMode], proc: FlushingMenuProc, clientData: d ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: d.flushME ]; d.logME _ Menus.CreateEntry[ name: LoggingCaptions[d.sd.logging], proc: LoggingMenuProc, clientData: d ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: d.logME ]; d.transcriptME _ Menus.CreateEntry[ name: LogCaptions[d.transcriptShowing], proc: LogMenuProc, clientData: d ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: d.transcriptME ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: Menus.CreateEntry[ name: "SetLogLines", proc: SetLogLinesMenuProc, clientData: d ] ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: Menus.CreateEntry[ name: "SetLines", proc: SetLinesMenuProc, clientData: d ] ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: Menus.CreateEntry[ name: "SetColumns", proc: SetColumnsMenuProc, clientData: d ] ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: Menus.CreateEntry[ name: "Grab", proc: GrabMenuProc, clientData: d ] ]; Menus.AppendMenuEntry[ menu: sdMenu, entry: Menus.CreateEntry[ name: "Help", proc: HelpMenuProc, clientData: d ] ]; }; ReadIntSelection: PROC [lowBound:INT _ 0] RETURNS [nullSelection, ok: BOOLEAN, i: INT] ~ { r: Rope.ROPE _ ViewerTools.GetSelectionContents[]; nullSelection _ Rope.Length[r] <= 0; ok _ TRUE; IF ~ nullSelection THEN i _ Convert.CardFromRope[ r ! Convert.Error => { msg: Rope.ROPE_ IO.PutFR["Expecting an integer greater than %g. ", IO.int[lowBound] ]; ok _ FALSE; MessageWindow.Append[msg,TRUE]; MessageWindow.Blink[]; CONTINUE; }; ]; }; LogCaptions: ARRAY BOOLEAN OF Rope.ROPE _ [ "Log:NotVisible", "Log:Visible" ]; LogMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; oldME: Menus.MenuEntry _ d.transcriptME; d.transcriptShowing _ ~ d.transcriptShowing; d.transcriptME _ Menus.CreateEntry[ name: LogCaptions[d.transcriptShowing], proc: LogMenuProc, clientData: d ]; Menus.ReplaceMenuEntry[ d.sdMenu, oldME, d.transcriptME ]; RepositionViewers[ d:d, force: shift ]; ViewerOps.PaintViewer[ d.topViewer, ViewerOps.PaintHint.all, TRUE, NIL ]; }; SetLogLinesMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; RepaintTranscript: PROC [showing: BOOLEAN] ~ { IF showing # d.transcriptShowing THEN { LogMenuProc[ parent, clientData, mouseButton, shift, control ]; } ELSE { RepositionViewers[ d:d, force: shift ]; ViewerOps.PaintViewer[ d.topViewer, ViewerOps.PaintHint.all, TRUE, NIL ]; }; }; null, ok: BOOLEAN; i: INT; [ nullSelection: null, ok: ok, i: i ] _ ReadIntSelection[ -1 ]; IF null THEN { avail: INT _ (d.topViewer.ch - d.aHeight - d.rHeight) / FixUp[(d.charH+d.interlineSpace)]; IF avail > 0 THEN { d.transcriptLines _ avail; RepaintTranscript[showing: TRUE] } ELSE { MessageWindow.Append["Transcript would have no lines. "]; MessageWindow.Blink[]; } } ELSE IF ok THEN { IF i = 0 AND d.transcriptShowing THEN { RepaintTranscript[showing: FALSE] } ELSE { d.transcriptLines _ MIN[i, (d.topViewer.ch) / FixUp[(d.charH+d.interlineSpace)] ]; RepaintTranscript[showing: TRUE] } } }; LoggingCaptions: ARRAY BOOLEAN OF Rope.ROPE _ [ "Logging:Off", "Logging:On" ]; LoggingMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; oldME: Menus.MenuEntry _ d.logME; d.sd.logging _ ~ d.sd.logging; d.logME _ Menus.CreateEntry[ name: LoggingCaptions[d.sd.logging], proc: LoggingMenuProc, clientData: d ]; Menus.ReplaceMenuEntry[ d.sdMenu, oldME, d.logME ]; ViewerOps.PaintViewer[d.topViewer, ViewerOps.PaintHint.menu, FALSE, NIL]; }; FlushCaptions: ARRAY FlushModeType OF Rope.ROPE _ [ FlushOnBlock: "Flush:OnBlock", FlushOnScroll: "Flush:OnScroll", FlushOnChangeCount: "Flush:OnCount" ]; FlushingMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; oldME: Menus.MenuEntry _ d.flushME; null, ok: BOOLEAN; i: INT; [ nullSelection: null, ok: ok, i: i ] _ ReadIntSelection[ 0 ]; IF null THEN IF d.sd.flushMode = LAST[ FlushModeType ] THEN d.sd.flushMode _ FIRST[ FlushModeType ] ELSE d.sd.flushMode _ SUCC[ d.sd.flushMode ] ELSE IF ok THEN { d.sd.changeCountLimit _ i; d.sd.flushMode _ FlushOnChangeCount; }; d.flushME _ Menus.CreateEntry[ name: IF d.sd.flushMode = FlushOnChangeCount THEN IO.PutFR[ "%g(%g)", IO.rope[FlushCaptions[d.sd.flushMode]], IO.int[d.sd.changeCountLimit] ] ELSE FlushCaptions[d.sd.flushMode], proc: FlushingMenuProc, clientData: d ]; Menus.ReplaceMenuEntry[ d.sdMenu, oldME, d.flushME ]; ViewerOps.PaintViewer[d.topViewer, ViewerOps.PaintHint.menu, FALSE, NIL]; }; GrabMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; r: Rope.ROPE _ ViewerTools.GetSelectionContents[]; StuffCharacter: PROC [c:CHAR] RETURNS [quit: BOOL _ FALSE] ~ { d.fromDisplayWriteEnd.PutChar[c]; }; [] _ Rope.Map[ base: r, action: StuffCharacter ]; }; HelpMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; IF ~ d.transcriptShowing THEN { LogMenuProc[ parent, clientData, mouseButton, shift, control ]; }; d.sd.out.PutF["%lFlush%l: Cycle between 3 ways of buffering output to the terminal. With selection, set mode to flush every N characters. (Use the selection as the value of N.)\n", IO.rope["b"], IO.rope["B"] ]; d.sd.out.PutF["%lLogging%l: Toggle whether or not lines scrolled off top are written to the transcript.\n", IO.rope["b"], IO.rope["B"] ]; d.sd.out.PutF["%lLog%l: Toggle whether or not the transcript is visible.\n", IO.rope["b"], IO.rope["B"] ]; d.sd.out.PutF["%lSetLogLines%l: Set number of lines in transcript to number in selection, taking space from the terminal, if needed (but see about shift-clicking below). With no selection, fit transcript to unused space in the current viewer.\n", IO.rope["b"], IO.rope["B"] ]; d.sd.out.PutF["%lSetLines%l, %lSetColumns%l: Set number of lines(columns) in terminal to number in selection. If the terminal won't fit in the viewer, the bottom of the terminal will be clipped (but see about shift-clicking below). With no selection, fit lines(columns) to current viewer\n", IO.rope["b"], IO.rope["B"], IO.rope["b"], IO.rope["B"] ]; d.sd.out.PutF["%lGrab%l: Copy current selection to terminal as if typed in.\n", IO.rope["b"], IO.rope["B"] ]; d.sd.out.PutF["\n%lShift-clicking%l a Set... button or the Log button performs the given action, but then changes the containing viewer's size to fit both the transcript and terminal.\n", IO.rope["i"], IO.rope["I"] ]; }; MaxLinesAllowed: INT _ 300; MaxColumnsAllowed: INT _ 300; SetLinesMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; null, ok: BOOLEAN; i: INT; [ nullSelection: null, ok: ok, i: i ] _ ReadIntSelection[ 0 ]; IF null OR ok THEN {det: CharDisplays.DisplayDetails _ d.cd.det; det.lines _ IF null THEN d.viewLines ELSE MIN[MaxLinesAllowed,i]; IF d.cd.client.InitiateChangeDetails[d.cd.client, d.cd, det] THEN { IF shift THEN RepositionViewers[ d:d, force:shift ]; ComputeLinesInfo[ d ]; -- To update caption ViewerOps.PaintViewer[d.topViewer, ViewerOps.PaintHint.all, TRUE, NIL]; }; } }; SetColumnsMenuProc: Menus.MenuProc ~ { d: SDViewerData _ NARROW[clientData]; null, ok: BOOLEAN; i: INT; [ nullSelection: null, ok: ok, i: i ] _ ReadIntSelection[ 0 ]; IF null OR ok THEN {det: CharDisplays.DisplayDetails _ d.cd.det; det.columns _ IF null THEN d.viewColumns ELSE MIN[MaxColumnsAllowed,i]; IF d.cd.client.InitiateChangeDetails[d.cd.client, d.cd, det] THEN { IF shift THEN RepositionViewers[ d:d, force:shift ]; ComputeLinesInfo[ d ]; -- To update caption ViewerOps.PaintViewer[d.topViewer, ViewerOps.PaintHint.all, TRUE, NIL]; }; } }; SDChangeDetails: PUBLIC PROC [cd: CharDisplays.CharDisplay, new: CharDisplays.DisplayDetails] ~ { sd: SimpleDisplayState = NARROW[cd.otherInstanceData]; IF new.autoMarginsVariable # cd.det.autoMarginsVariable OR new.scrollsVariable # cd.det.scrollsVariable THEN ERROR; IF new.autoMargins # cd.det.autoMargins THEN { IF NOT cd.det.autoMarginsVariable THEN ERROR; cd.det.autoMargins _ new.autoMargins}; IF new.scrolls # cd.det.scrolls THEN { IF NOT cd.det.scrollsVariable THEN ERROR; cd.det.scrolls _ new.scrolls}; IF new.lines # cd.det.lines OR new.columns # cd.det.columns THEN { ResizeTerminal[ cd, sd, new.lines, new.columns ]; }; }; LogTranscriptLine: PUBLIC PROC [sd: SimpleDisplayState, chars: REF TEXT] ~ { d: SDViewerData _ NARROW[sd.sdvData]; chars.length _ 0; FOR j: INT DECREASING IN [0 .. chars.maxLength) DO IF chars[j] # Ascii.SP THEN { chars.length _ j+1; EXIT } ENDLOOP; TypeScript.ChangeLooks[ d.tViewer, 'f ]; TypeScript.PutText[ d.tViewer, chars ]; TypeScript.PutText[ d.tViewer, "\n" ]; }; ViewerOps.RegisterViewerClass[ $SimpleDisplaysViewer, NEW[ViewerClasses.ViewerClassRec _ [ paint: PaintSimpleDisplaysViewer, notify: NotifySimpleDisplaysViewer, modify: ModifySimpleDisplaysViewer, adjust: AdjustSimpleDisplaysViewer ]] ]; END. l SimpleDisplaysViewerImpl Copyright Σ 1990 by Xerox Corporation. All rights reserved. Norman Adams, March 22, 1990 1:48 pm PST Spreitze, April 3, 1990 1:53 pm PDT Last tweaked by Mike Spreitzer on April 4, 1990 12:20:09 pm PDT -- Font information, assume that bold and italic have the same bounding box & origin -- fonts[isBold][isItalic] -- Parent viewer -- Transcript -- Rule between transcript and terminal -- Terminal and related viewer information -- cursor information -- Menus -- To scroll a single line using bit blt s1w, s1h, s1fX, s1fY, s1tX, s1tY: INT -- Make an appropriate tip table, basically just ascii with control, shift, meta. Then layer on the terminal specific tip. ViewerOps.PaintViewer[d.topViewer, ViewerOps.PaintHint.all]; If force then try to resize the container to fit the transcript and terminal at their current sizes. When not forcing, don't change the container size. Instead, honor the size of the transcript and take away from the terminal. You need to paint after doing this. Painting -- PROC [self: Viewer, context: Imager.Context, whatChanged: REF, clear: BOOL] RETURNS [quit: BOOL _ FALSE]; -- Write from x for length of count all with the same emphasis -- For lines above the current line on the screen, relativeScreenPos = 0 (of source) means -- the source hasn't been (won't be) overwritten. It is always OK to get lines from lower -- on the screen -- i and j are first and last line, resp., of region where all lines have the same RSP -- first and last are the indices of the first and last line (inclusive) -- of the rectangle to be moved. Offset > 0 is a scroll up. Because the terminal -- emulator counts lines starting from the top of the display, last is actually the origin -- of the source rectangle. Character Positions Cursor Other Viewers Procedures -- PROC [self: Viewer, input: LIST OF REF ANY]; ViewerOps.PaintViewer[d.aViewer, ViewerOps.PaintHint.client, FALSE, $FOCUS]; ModifyProc: TYPE = PROC [self: Viewer, change: ModifyAction]; ModifyAction: TYPE = {set, push, pop, kill}; ViewerOps.PaintViewer[d.aViewer, ViewerOps.PaintHint.client, FALSE, $FOCUS]; AdjustProc: TYPE = PROC [self: Viewer] RETURNS [adjusted: BOOL _ FALSE]; MessageWindow.Append["Adjust.",TRUE]; Menus ClickProc: TYPE = PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; ClickProc: TYPE = PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; ClickProc: TYPE = PROC [parent: Viewer, clientData: REF ANY _ NIL, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE]; d.transcriptShowing _ TRUE; RepositionViewers[ d:d, force:shift ]; ViewerOps.PaintViewer[ d.topViewer, ViewerOps.PaintHint.all, TRUE, NIL ]; -- Arbitrary limits Transcript Support Initialize this viewer class ,tipTable: TIPUser.InstantiateNewTIPTable["SimpleDisplays.tip"] Κ#μ•NewlineDelimiter ™code™Kšœ=™=K™(K™#K™?—K˜šΟk œ˜ Jšœ2œJœ΅˜΅—K˜šΠlnœ ˜'Kšœœœ˜KKšœ%œJœ₯˜ŸKšœ˜—šœ˜Kšœ˜K˜Kšœ œœ˜)šœœœ˜ J˜Jšœ˜J˜J˜Jšœ˜Jšœœ˜J˜J™TJ™š œœœœœœœ˜)šœ˜Jšœ œ˜Jšœœ˜Jšœ˜—šœΟc œŸ˜&JšŸœN˜TJšŸœO˜UJ˜——Jšœœ˜J˜J™Jšœ#œŸ ˜2Jšœœ˜Jšœœœ˜%J˜Jšœ ™ Jšœ!œŸ ˜4Jšœœ˜Jšœ œ˜Jšœœœ˜#J˜J™'Jšœ œ˜$Jšœ œ Ÿ ˜%Jšœœ Ÿ3˜JJ˜J™*Jšœ!œŸ+˜RJšœ œ˜ Jšœœ˜Jšœœ˜JšœœŸ;˜UJšœœ˜Jšœœ˜Jšœ ŸC˜OJšœœ˜Jšœ%Ÿ6˜[Jšœ œ˜Jšœ Ÿ3˜=Jšœ œ˜JšœœŸ1˜IJ˜Jšœ™Jšœ œ˜Jšœ œ˜Jšœ  ˜Jšœ œœ˜JšœL˜LJšœO˜OJ˜J™Jšœ˜Jšœ˜Jšœ˜Jšœ˜J˜J™(Jšœ"™%J˜J˜—JšΟnœ˜Jšœœ8˜Cšœ!œœ˜8JšΟbœœ˜'J˜Kšœ˜J˜J˜Jš‘œ˜Jš‘œ ˜ Jš‘œ ˜ Jš‘œ-˜.J˜Jšœ‘œ˜J˜šœ‘œœ˜Jš‘œ‘œ‘œ‘œ˜IJš‘œ ‘œ˜—šœœ˜ Jš‘œ ˜Jš‘œ ˜Jšœ˜—Jš ‘œ‘œ‘œ‘œ‘œ‘œ˜iJš‘œ˜‚Jš‘œ.‘œœ˜aKšœ‘œ‘œ ˜K˜š‘œ˜šœ˜Jšœ‘œ ˜Jšœ‘œ ˜Jšœ‘œ˜Jšœ˜J˜Jšœ˜ Jšœ˜—Jšœ˜—Jšœ‘œ ‘œ ˜0Jš‘œ/‘œ'œ˜cJš‘œ‘œ˜J˜š‘œ˜šœ˜Jšœ‘œ ˜Jšœ‘œ ˜Jšœ‘œ˜Jšœ‘œ ˜J˜J˜—J˜—Jšœ‘œ ‘œ ˜0J™|J™Kšœ@˜@šœœœ˜Kšœœ˜Kšœ8œ œ˜dšœœœ˜Kšœœ(˜>Kšœœ˜Kšœ˜Kšœ˜Kšœ˜—Kšœ˜—J™š‘œ#˜$Jšœ˜šœ˜Jšœ‘œ ˜Jšœ‘œ˜Jšœ˜Jšœ‘œ ˜Jšœ‘œ˜Jšœ‘œ ‘œ ˜J˜Jšœ˜Jšœ˜Jšœ˜—Jšœ˜—Jšœ‘œ ‘œ ˜/Jšœ‘œ ‘œ ˜0Jš ‘œ‘œ ‘œ ‘œ ‘œ˜NJš‘œœ˜J˜Jšœ‘œŸ(˜@Jšœ‘œ ˜ Kšœ‘œ%™Kš€‘ œ˜šœ‘œ,œœ˜ZKšœ*˜*Kšœ2‘œ‘œ˜UK˜Kšœ‘œ)˜BKšœœœœ˜=Kšœ˜Kšœœœœ˜=Kšœ(‘œ ˜2Kšœ/˜/K˜šœœ˜K˜Kšœ˜K˜—K˜K˜—K˜š  œœ‘œ€‘œ˜CKšœ œ‘œ˜&Kšœœ˜ K˜KšœZ™ZKšœ[™[Kšœ™K˜šœœ˜Kšœœ ‘œ˜"šœœ˜KšœœŸ!˜?šœ œ‘œ8˜NKšœ1˜1—š˜Kšœ˜——šœ˜Kšœ˜—Kšœ˜K˜—K˜šœ˜Kšœ œ‘œ'˜8Kšœœœ˜,K˜KšœV™VK˜ šœœ‘œ3œ˜RKšœ ˜ Kšœ˜—˜ K˜—K˜Kšœ‘œ‘œ%˜:Kšœ ˜ K˜Kšœ˜K˜—K˜K˜—Kš’œ’œ7™HKšœ"’œ*™RKšœ?’œ™ZKšœ™K™Kš €œœ‘œ€‘œ%œ˜X˜Kšœ˜Kšœ#‘œ ˜/Kšœ!‘œ˜4Kšœ!‘œ‘œ˜>šœ‘œ‘‘˜Kš‘œ;œ œ œ˜j—Kšœœ ‘œœ ‘œœ ‘œœœ˜sKšœN˜NK˜šœ"Ÿ*˜LKšœ ‘œ˜ Kšœ˜Kšœ˜Kšœ#˜#Kšœ#˜#Kšœ˜Kšœ˜Kšœ˜—K˜K˜——š£™J˜š  œœ‘œœœ˜EKšœœ‘œ‘œ˜.Kšœœ‘œ‘œ‘œ‘œ‘œ‘œ˜hšœ˜ Kšœ ˜ Kšœ ˜ Kšœ ‘œ˜Kšœ ‘œ ‘œ˜*—K˜K˜—K˜š ‘œœ‘œœœœ˜Qšœ˜ Kš‘œ‘œ˜Kš ‘œ‘œ‘œ‘œ‘œ‘œ˜WKšœ˜—K˜——K˜Kš£™˜š   œœ‘œ€‘œ˜:Kšœœ‘œ ˜Kšœœ‘œ ˜Kšœ‘œ˜%Kšœ‘œœ‘œ œ‘œœ‘œ˜NKšœ‘œ €‘œ ˜'Kšœ ‘œ‘œ ˜,Kšœ‘œ˜Kšœ‘œ˜Kš‘œ ˜Kš‘œ ˜Kš‘œ˜K˜—Kš   œœ‘œ€‘œ˜:˜Kšœœ‘œ ˜Kšœœ‘œ ˜Kšœ‘œ˜Kšœ‘œ ‘œ ˜'Kšœ ‘œ‘œ ˜,Kšœ‘œ˜Kšœ‘œ‘œ˜#K˜J˜——š£™K˜Kš œŸ œ˜Dš œœœœœ™/Kšœœœ˜K˜Kš‘œœ ˜$J˜šœ œ˜šœœœ˜˜ Kšœœ˜ Kšœœœ˜šœ œ œ˜7šœ œ˜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˜—šœ$˜$Jšœ˜Jš‘œ œ˜šœ˜Jšœ ‘œ ˜Jšœ)˜)Jšœ œ˜Jšœ˜Jšœœ˜Jšœ˜—Kšœ‘œ&œ ™LJ˜—šœ˜ J˜1Kšœ˜——K˜K˜—Kš œŸ œ˜DKšœ œœ&™=šœœ™,Kšœœœ˜Jš‘œœ ˜$K˜šœ˜šœ$˜$Jš‘œ œ˜šœ˜Jšœ ‘œ ˜Jšœ)˜)Jšœ œ˜Jšœ˜Jšœœ˜Jšœ˜—Kšœ‘œ&œ ™LJ˜—Jšœœ˜—K˜K˜——˜Kš€œŸ œ˜Dš œ œœœ œœ™HKšœœœ˜—™Jš‘œœ ˜$Jšœœ™%Jšœ‘œ˜šœ˜Jšœ ‘œ ˜Jšœ*˜*Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœ˜J˜—Kšœœ˜K˜K˜——Kš£™™Kš‘ œœ‘œ'˜=˜š‘œ˜šœœ‘œ#˜1Kšœ&‘œ‘œ˜Y—š˜Kšœ‘œ˜—Kšœ˜Kšœ ‘˜ Kšœ˜—Kšœ,‘œ ˜8K˜š‘œ˜Kšœ‘œ˜%Kšœ˜Kšœ ‘œ˜Kšœ˜—Kšœ,‘œ ˜6K˜š‘œ"˜#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šœ ˜ šœ˜Kšœ ˜ Kšœ˜Kšœ ‘˜ Kšœ˜—Kšœ˜—K˜K˜K˜K˜—Kš œœ œ˜*šœœœ˜0Jšœœ&˜2Jšœ#˜$Jšœ˜ šœ˜šœ˜šœ˜Kšœ œœE˜WJšœœ˜ Kšœœ˜K˜Kšœ˜ Kšœ˜—Kšœ˜——K˜K˜—š   œœœœœ'˜NK˜—Kš  œ˜Kšœ œœœœœ2œœ™™Jš‘œœ ˜%Kšœ‘œ˜(—˜Kš‘œ‘œ˜,š‘œ"˜#Jšœ‘œ˜(Jšœ˜Jšœ ‘œ˜Jšœ˜—Jšœ‘œ‘œ˜:Jšœ‘œ‘œ˜'Kšœ‘œ%œœ˜IK˜—K™Kš œ˜'Kšœ œœœœœ2œœ™™Jš‘œœ ˜%J˜š œœ œ˜.šœ ‘œœ˜'Kšœ?˜?—šœœ˜Jšœ‘œ‘œ˜'Kšœ‘œ%œœ˜IK˜—K˜—K˜Kšœ œ˜K˜?K˜šœœ˜Jš œœ‘œ‘œ ‘œ‘œ‘œ˜ZJ˜šœ œ˜Jš‘œ˜Jšœœ˜ —šœœ˜Jšœ9˜9J˜J˜——šœœœœ˜šœ‘œœ˜(Jšœœ˜!—šœœ˜ Jš ‘œœ‘œ‘œ‘œ˜RJšœœ˜ J˜—J˜—J˜J˜K˜—š  œœœœœ#˜NK˜—Kš œ˜#šœ œœœœœ2œœ™Jš‘œœ ˜%Kšœ‘œ˜!K™Jš‘œ‘œ ˜š‘œ˜Jšœ‘œ˜%Jšœ˜Jšœ ‘œ˜Jšœ˜—J˜Jšœ‘œ‘œ ˜3Kšœ‘œ&œœ˜IKšœ˜—K˜š  œœœœ˜3Kšœ˜Kšœ ˜ Kšœ#˜#Kšœ˜—K˜Kš œ˜$™Jš‘œœ ˜%Kšœ‘œ ˜#Kšœ œ˜K˜K˜>K˜šœ˜ š‘œœœ˜/Jš‘œœ˜'—š˜Jš‘œœ‘œ˜'——šœœœ˜Jš‘œ˜Jš‘œ#˜$J˜J˜—š‘œ˜šœœ‘œ#˜1Kšœ(‘œ‘œ˜[—š˜Kšœ‘œ˜—Kšœ˜Kšœ ‘œ˜Kšœ˜—J˜Jšœ‘œ‘œ ˜5Kšœ‘œ&œœ˜IJšœ˜K˜—Kš  œ˜ ™Jš‘œœ ˜%Jšœœ&˜2J˜š  œœœœœœ˜>Kš‘œ ˜!K˜K˜—Kšœ1˜1Jšœ˜J˜—š  œ˜ Kš‘œœ ˜%J˜šœ‘œœ˜Kšœ?˜?Kš‘œœ™Jšœ‘œ‘œ™&Kšœ‘œ%œœ™IJ˜J˜—Jš‘œΤ˜ΥJš‘œˆ˜‰Jš‘œi˜jJš‘œ“˜”Jš‘œή˜ίJš‘œl˜mJš‘œΨ˜ΩJ˜Jšœ˜J˜—J™Jš œœ ˜Jš œœ˜J˜š œ˜$Kš‘œœ ˜%Kšœ œ˜K˜K˜>K˜šœœœ%‘œ˜@Kš œ œœ‘œ œœ˜Ašœ;œ˜CKšœœ‘œ‘œ˜4Kšœ‘œŸ˜,Kšœ‘œ%œœ˜GK˜—Kšœ˜K˜—Kšœ˜K˜—š œ˜&Kš‘œœ ˜%Kšœ œ˜K˜K˜>K˜šœœœ%‘œ˜@Kš œœœ‘œ œœ˜Gšœ;œ˜CKšœœ‘œ‘œ˜4Kšœ‘œŸ˜,Kšœ‘œ%œœ˜GK˜—Kšœ˜K˜—Kšœ˜K˜—š œœœE˜aKšœœ˜6Kšœ6œ.œœ˜sšœ&œ˜.Kšœœœœ˜-Kšœ&˜&—šœœ˜&Kšœœœœ˜)Kšœ˜—šœœœ˜BKšœ1˜1K˜—K˜—J˜J˜—š£™K˜š œ œ!œœ˜LKš‘œœ ˜%Kšœ˜šœœ œ˜2Kšœœœœ˜8Kšœ˜—Kšœ‘œ˜(Kšœ‘œ˜(Kšœ‘œ˜'K˜——K˜š£™K˜šœ˜Jšœ˜šœ!˜$Jšœ!˜!Jšœ#˜#Jšœ#˜#Jšœ"˜"Jšœ?™?Jšœ˜—Jšœ˜——J˜Kšœ‘˜—…—` d