DIRECTORY NodeStyle USING [Ref, GetTopLeadingI, GetTopIndentI, GetLeadingI, GetBottomLeadingI], NodeStyleOps USING [Alloc, Free, ApplyAll, OfStyle], RopeReader USING [Ref, SetPosition, Backwards, GetRopeReader, FreeRopeReader], Scaled USING [FromInt], TEditDisplay USING [EstablishLine], TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, TEditDocumentData], TEditFormat USING [Allocate, FormatLine, LineInfo, Release], TEditInput USING [MaxLevelShown], TEditLocks USING [LockDocAndTdd, UnlockDocAndTdd], TEditOps USING [RememberCurrentPosition], TEditProfile USING [scrollBottomOffset, scrollTopOffset], TEditSelection USING [InsertionPoint, pSel, sSel, fSel], TEditScrolling USING [], TEditTouchup USING [LockAfterRefresh, PreScrollDownRec, refresh, UnlockAfterRefresh], TextEdit USING [FetchChar, Size], TextNode USING [Backward, BackwardClipped, BadArgs, FirstChild, Forward, ForwardClipped, LastLocWithin, Level, Location, LocNumber, LocOffset, LocWithin, Parent, Ref, Root, StepForward], ViewerForkers USING [ForkPaint], ViewerLocks USING [CallUnderWriteLock], ViewerOps USING [FetchProp, PaintViewer], ViewerClasses USING [ScrollProc, Viewer]; TEditScrollingImpl: CEDAR PROGRAM IMPORTS NodeStyle, NodeStyleOps, RopeReader, Scaled, TEditDisplay, TEditFormat, TEditLocks, TEditSelection, TextEdit, TEditInput, TEditOps, TEditProfile, TEditTouchup, TextNode, ViewerForkers, ViewerLocks, ViewerOps EXPORTS TEditScrolling = BEGIN TEditDocumentData: TYPE = TEditDocument.TEditDocumentData; Viewer: TYPE = ViewerClasses.Viewer; forkPaints: BOOL _ FALSE; BackUp: PROC [viewer: Viewer, tdd: TEditDocumentData, pos: TextNode.Location, goal: INTEGER] RETURNS [newPos: TextNode.Location, lines, totalLeading, topIndent: INTEGER] = { kind: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen; maxLevel: INTEGER _ tdd.clipLevel; parent: TextNode.Ref; level: INTEGER _ 0; -- in case we are doing level clipping levelClipping: BOOL _ maxLevel < TEditDocument.maxClip; IncrBackUp: PROC [pos: TextNode.Location, goal, prevTopLeading: INTEGER] RETURNS [prev: TextNode.Location, totalLeading, lines, topIndent, topLeading: INTEGER] = { tPos: TextNode.Location; leading, bottomLeading: INTEGER; lastBreak: INT; breakList: LIST OF INT; -- breaks between first and last textNode: TextNode.Ref; where, endOffset, size: INT; lineInfo: TEditFormat.LineInfo; IF pos.where=0 THEN DO tempNode: TextNode.Ref; IF levelClipping THEN [tempNode, parent, level] _ TextNode.BackwardClipped[pos.node, maxLevel, parent, level] ELSE [tempNode, parent, ----] _ TextNode.Backward[pos.node, parent]; IF tempNode=NIL OR parent=NIL THEN RETURN[pos, 0, 0, 0, 0]; IF (textNode _ tempNode)=NIL THEN LOOP; size _ endOffset _ where _ TextEdit.Size[textNode]; EXIT; ENDLOOP ELSE { textNode _ pos.node; size _ TextEdit.Size[textNode]; endOffset _ where _ pos.where-1 }; IF where < 4*MAX[12,goal] THEN where _ 0 -- don't bother to search backwards for CR ELSE { stop: INT _ MAX[0, where-5000]; -- limit reading to 5000 characters RopeReader.SetPosition[rdr, textNode.rope, where]; UNTIL (where _ where-1)<=stop DO IF RopeReader.Backwards[rdr]=15C THEN {where _ where+1; EXIT}; ENDLOOP }; IF styleNode # textNode THEN NodeStyleOps.ApplyAll[style, styleNode _ textNode, kind]; leading _ NodeStyle.GetLeadingI[style]; topLeading _ IF where=0 THEN NodeStyle.GetTopLeadingI[style] ELSE leading; bottomLeading _ totalLeading _ IF pos.node = textNode THEN leading ELSE MAX[prevTopLeading, NodeStyle.GetBottomLeadingI[style]]; topIndent _ NodeStyle.GetTopIndentI[style]; -- in case this line appears at top of viewer IF where=size THEN { lines _ 1; prev _ [textNode, where]; RETURN }; tPos _ [textNode, where]; lines _ 0; lineInfo _ TEditFormat.Allocate[]; DO lastBreak _ tPos.where; TEditFormat.FormatLine[lineInfo: lineInfo, node: tPos.node, startOffset: tPos.where, nodeStyle: style, lineWidth: Scaled.FromInt[viewer.cw], kind: kind]; tPos _ lineInfo.nextPos; IF lines > 0 THEN totalLeading _ totalLeading+leading; lines _ lines+1; IF tPos.node#textNode OR tPos.where>=endOffset THEN EXIT; IF lastBreak # where THEN breakList _ CONS[lastBreak, breakList]; ENDLOOP; lineInfo.Release[]; lineInfo _ NIL; IF totalLeading+topIndent >= goal AND leading > 0 THEN { discardLines: INTEGER _ (totalLeading+topIndent-goal)/leading; -- too many lines SELECT discardLines FROM <= 0 => {}; -- don't discard any >= lines-1 => { -- discard all but one where _ lastBreak; lines _ 1; totalLeading _ bottomLeading ; }; ENDCASE => { count: INTEGER; -- how far to go on list to find the break lines _ lines-discardLines; count _ lines-1; -- subtract 1 because lastBreak is not on list totalLeading _ totalLeading-discardLines*leading; FOR list: LIST OF INT _ breakList, list.rest DO IF (count _ count-1) = 0 THEN { where _ list.first; EXIT }; ENDLOOP; }; }; prev _ [textNode, where]; }; rdr: RopeReader.Ref _ RopeReader.GetRopeReader[]; style: NodeStyle.Ref _ NodeStyleOps.Alloc[]; styleNode: TextNode.Ref; remainder: INTEGER _ goal; dy: INTEGER _ LAST[INTEGER]; topLeading: INTEGER _ 0; IF pos.where=0 THEN { NodeStyleOps.ApplyAll[style, pos.node, kind]; topLeading _ NodeStyle.GetTopLeadingI[style]; }; newPos _ pos; lines _ totalLeading _ topIndent _ 0; UNTIL remainder<=0 DO leading, newLines, newTopIndent: INTEGER; [newPos, leading, newLines, newTopIndent, topLeading] _ IncrBackUp[newPos, remainder, topLeading]; IF newLines <= 0 THEN EXIT; -- didn't get anything that time. at start of document. totalLeading _ totalLeading+leading; lines _ lines+newLines; topIndent _ newTopIndent; IF totalLeading+topIndent >= goal THEN EXIT; -- don't need to back up any farther remainder _ remainder - leading; ENDLOOP; NodeStyleOps.Free[style]; RopeReader.FreeRopeReader[rdr]; }; ScrollTEditDocument: PUBLIC ViewerClasses.ScrollProc = { inner: PROC [tdd: TEditDocumentData] = { lines: TEditDocument.LineTable _ tdd.lineTable; topLine: TextNode.Location _ lines[0].pos; paintOp: REF ANY _ TEditTouchup.refresh; iconic: BOOL = self.iconic; levelChange: BOOL _ FALSE; IF self.iconic THEN { SELECT op FROM up => TEditDisplay.EstablishLine[tdd, TextNode.LastLocWithin[tdd.text], 0]; down => TEditDisplay.EstablishLine[tdd, [TextNode.FirstChild[tdd.text],0], 0]; thumb => NULL; query => NULL; ENDCASE => ERROR; RETURN }; SELECT op FROM up => { line: INTEGER _ 0; newLevel: INTEGER; sSel: TEditDocument.Selection ~ TEditSelection.sSel; doingSecondarySelect: BOOL ~ sSel # NIL AND sSel.viewer # NIL; IF amount > 0 AND lines.lastLine = 0 THEN { IF lines[0].end = eon THEN { next: TextNode.Ref; maxLevel: INTEGER = tdd.clipLevel; next _ IF maxLevel < TEditDocument.maxClip THEN TextNode.ForwardClipped[topLine.node,maxLevel].nx ELSE TextNode.StepForward[topLine.node]; IF next # NIL THEN topLine _ [next, 0]; } ELSE topLine.where _ topLine.where+lines[0].nChars } ELSE { UNTIL lines[line].yOffset+lines[line].descent >= amount OR lines[line+1].pos.node=NIL DO line _ line+1; IF line >= lines.lastLine THEN EXIT; -- off end ENDLOOP; topLine _ lines[line].pos }; newLevel _ SELECT TRUE FROM doingSecondarySelect => tdd.clipLevel, control AND shift => 1, control => TEditDocument.maxClip, shift => MIN[TEditDocument.maxClip, TEditInput.MaxLevelShown[tdd]+1], ENDCASE => tdd.clipLevel; -- no change IF newLevel # tdd.clipLevel THEN { tdd.clipLevel _ newLevel; levelChange _ TRUE; }; }; down => { numLines, totalLeading, topIndent: INTEGER; [topLine, numLines, totalLeading, topIndent] _ BackUp[self, tdd, topLine, MAX[amount, lines[0].yOffset]]; IF topLine # lines[0].pos THEN { op: REF TEditTouchup.PreScrollDownRec _ NEW[TEditTouchup.PreScrollDownRec]; op.lines _ numLines; op.distance _ topIndent+totalLeading-lines[0].yOffset; paintOp _ op }; }; thumb => { TEditOps.RememberCurrentPosition[self]; IF amount < 5 THEN ScrollToPosition[self, [TextNode.FirstChild[tdd.text],0], FALSE] ELSE { totalChars: INT = TextNode.LocNumber[TextNode.LastLocWithin[tdd.text]]-1; pos: TextNode.Location _ TextNode.LocWithin[tdd.text, (totalChars*amount)/100]; IF tdd.clipLevel < TEditDocument.maxClip THEN { delta: INTEGER _ TextNode.Level[pos.node]-tdd.clipLevel; FOR i:INTEGER IN [0..delta) DO -- only do this if pos is too deep pos _ [TextNode.Parent[pos.node],0]; ENDLOOP; }; ScrollToPosition[self, pos, FALSE]; }; RETURN; }; query => { toTop, toBottom, toEnd, totalChars, t, b: INT; ll: INTEGER = lines.lastLine; toTop _ TextNode.LocOffset[[TextNode.FirstChild[tdd.text], 0], topLine ! TextNode.BadArgs => GO TO Bad]; toBottom _ TextNode.LocOffset[topLine, [lines[ll].pos.node, lines[ll].pos.where+lines[ll].nChars] ! TextNode.BadArgs => GO TO Bad]; toEnd _ TextNode.LocOffset[[lines[ll].pos.node, lines[ll].pos.where+lines[ll].nChars-1], TextNode.LastLocWithin[tdd.text] ! TextNode.BadArgs => GO TO Bad]; totalChars _ toTop+toBottom+toEnd; IF totalChars<=0 THEN {bottom _ 100; RETURN}; t _ MIN[98, 100*toTop/totalChars]; b _ MAX[t+2, 100*(toTop+toBottom)/totalChars]; top _ t; bottom _ b; EXITS Bad => RETURN; }; ENDCASE; IF topLine # lines[0].pos OR levelChange THEN { TEditDisplay.EstablishLine[tdd, topLine, 0]; DoPaint[self, IF levelChange THEN NIL ELSE paintOp]; }; }; LockAndDoIt[inner, self, NIL, FALSE]; }; ScrollToPosition: PUBLIC PROC [viewer: Viewer, pos: TextNode.Location, offset: BOOL] = { kind: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen; inner: PROC [tdd: TEditDocumentData] = { node: TextNode.Ref _ pos.node; where: INT _ MAX[0, MIN[pos.where, TextEdit.Size[node]-1]]; start: INT _ where; lines: TEditDocument.LineTable _ tdd.lineTable; repaint: BOOL _ FALSE; topLine: TextNode.Location; style: NodeStyle.Ref _ NodeStyleOps.Alloc[]; lineInfo: TEditFormat.LineInfo; backStop: INT _ MAX[0, where-300]; -- limit looking back too far when searching for CR's IF tdd.clipLevel < TEditDocument.maxClip AND tdd.clipLevel < TextNode.Level[node] THEN { repaint _ TRUE; tdd.clipLevel _ TEditDocument.maxClip; -- turn off level clipping -- }; UNTIL start<=backStop OR TextEdit.FetchChar[node, start-1]=[0,15B] DO start _ start - 1; ENDLOOP; topLine _ [node, start]; NodeStyleOps.ApplyAll[style, topLine.node, kind]; lineInfo _ TEditFormat.Allocate[]; DO TEditFormat.FormatLine[lineInfo: lineInfo, node: topLine.node, startOffset: topLine.where, nodeStyle: style, lineWidth: Scaled.FromInt[viewer.cw], kind: kind]; IF lineInfo.nextPos.node#node OR lineInfo.nextPos.where>where OR lineInfo.nextPos=topLine THEN EXIT; topLine _ lineInfo.nextPos; IF topLine.where+lineInfo.nChars>where THEN EXIT; ENDLOOP; IF offset THEN { goal: INTEGER = style.GetLeadingI[] * TEditProfile.scrollTopOffset; IF goal*2 < viewer.ch THEN topLine _ BackUp[viewer, tdd, topLine, goal].newPos; }; lineInfo.Release[]; lineInfo _ NIL; NodeStyleOps.Free[style]; IF repaint OR topLine # lines[0].pos THEN { TEditDisplay.EstablishLine[tdd, topLine, 0]; DoPaint[viewer, IF repaint THEN NIL ELSE TEditTouchup.refresh]; }; }; IF pos.node # NIL THEN LockAndDoIt[inner, viewer, NIL, FALSE]; }; OnScreen: PROC [viewer: Viewer, point: TextNode.Location] RETURNS [BOOL] = { IF viewer = NIL OR point.node = NIL THEN RETURN [FALSE]; IF viewer.destroyed OR viewer.iconic THEN RETURN [FALSE]; WITH viewer.data SELECT FROM tdd: TEditDocumentData => { lines: TEditDocument.LineTable _ tdd.lineTable; found: BOOL _ FALSE; IF lines # NIL AND lines.lastLine >= 0 THEN { first: TextNode.Location _ lines[0].pos; last: TextNode.Location _ lines[lines.lastLine].pos; each: TextNode.Ref _ first.node; last.where _ last.where + lines[lines.lastLine].nChars; IF point.node = first.node AND point.where < first.where THEN GO TO quickOut; IF point.node = last.node AND point.where > last.where THEN GO TO quickOut; WHILE each # NIL DO IF each = point.node THEN {found _ TRUE; EXIT}; IF each = last.node THEN EXIT; each _ TextNode.Forward[each].nx; ENDLOOP; EXITS quickOut => {}; }; RETURN [found]; }; ENDCASE; RETURN [FALSE]; }; AutoScroll: PUBLIC PROC [viewer: Viewer, tryToGlitch: BOOL, toEndOfDoc: BOOL, id: TEditDocument.SelectionId] = { kind: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen; sel: TEditDocument.Selection = SELECT id FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR; inner: PROC [tdd: TEditDocumentData] = { lines: TEditDocument.LineTable _ tdd.lineTable; goal: TextNode.Location _ [NIL, 0]; glitchOK: BOOL _ FALSE; TryToGlitch: PROC RETURNS [success: BOOL _ FALSE] = { glitchLines: INTEGER = MIN[MAX[lines.lastLine/2, 1], TEditProfile.scrollBottomOffset]; tryLines: INTEGER = MAX[lines.lastLine/2-glitchLines, 2]; -- how far to search for caret newPos: TextNode.Location; style: NodeStyle.Ref; lineInfo: TEditFormat.LineInfo; lineCount: INTEGER _ 0; pos: TextNode.Location _ lines[lines.lastLine].pos; foundNode: BOOL; NextNode: PROC [node: TextNode.Ref] RETURNS [next: TextNode.Ref] = { next _ IF tdd.clipLevel < TEditDocument.maxClip THEN TextNode.ForwardClipped[node,tdd.clipLevel].nx ELSE TextNode.StepForward[node] }; IF TEditProfile.scrollBottomOffset<=0 THEN { RETURN [TRUE] }; success _ FALSE; style _ NodeStyleOps.Alloc[]; IF lines[lines.lastLine].end = eon THEN { pos _ [NextNode[lines[lines.lastLine].pos.node],0]; IF pos.node=NIL THEN { NodeStyleOps.Free[style]; RETURN [FALSE] } } ELSE pos.where _ pos.where+lines[lines.lastLine].nChars; foundNode _ pos.node=goal.node; NodeStyleOps.ApplyAll[style, pos.node, kind]; lineInfo _ TEditFormat.Allocate[]; THROUGH [0..MIN[tryLines, lines.lastLine-glitchLines]) DO IF pos.node=NIL THEN { NodeStyleOps.Free[style]; lineInfo.Release[]; RETURN [FALSE]; }; TEditFormat.FormatLine[lineInfo: lineInfo, node: pos.node, startOffset: pos.where, nodeStyle: style, lineWidth: Scaled.FromInt[viewer.cw], kind: kind]; newPos _ lineInfo.nextPos; IF newPos.node=goal.node THEN foundNode _ TRUE; IF foundNode AND (newPos.node#goal.node OR newPos.where>goal.where) THEN { success _ TRUE; EXIT}; lineCount _ lineCount+1; IF newPos.node#pos.node THEN NodeStyleOps.ApplyAll[style, newPos.node, kind]; pos _ newPos; ENDLOOP; NodeStyleOps.Free[style]; lineInfo.Release[]; lineInfo _ NIL; IF success THEN { TEditDisplay.EstablishLine[tdd, lines[lineCount+glitchLines].pos, 0]; DoPaint[viewer, TEditTouchup.refresh]; }; }; { SELECT TRUE FROM toEndOfDoc => { lines: TEditDocument.LineTable = tdd.lineTable; goal _ TextNode.LastLocWithin[TextNode.FirstChild[tdd.text]]; IF lines[lines.lastLine].pos.where+lines[lines.lastLine].nChars >= goal.where THEN RETURN; goal.where _ goal.where-1; -- last char correction IF tryToGlitch THEN glitchOK _ TryToGlitch[]; }; sel.viewer # viewer => {}; ENDCASE => { selPoint: TEditDocument.SelectionPoint = IF sel.insertion=before THEN sel.start ELSE sel.end; clipped: BOOL = selPoint.clipped; IF NOT clipped AND OnScreen[viewer, sel.end.pos] THEN RETURN; goal _ TEditSelection.InsertionPoint[sel]; IF TextNode.Root[goal.node] # tdd.text THEN RETURN; IF sel.insertion=before AND goal.where>0 THEN goal.where _ goal.where-1; IF NOT clipped AND tryToGlitch AND selPoint.line>0 THEN glitchOK _ TryToGlitch[]; }; }; IF NOT glitchOK AND goal.node # NIL THEN ScrollToPosition[viewer, goal, TRUE]; }; IF viewer=NIL THEN viewer _ TEditSelection.pSel.viewer; LockAndDoIt[inner, viewer, sel, TRUE]; }; LockAndDoIt: PROC [inner: PROC [tdd: TEditDocumentData], viewer: Viewer, sel: TEditDocument.Selection _ NIL, ignoreIcon: BOOL _ TRUE] = { withViewer: PROC = { tdd: TEditDocumentData _ NIL; IF viewer = NIL OR viewer.destroyed OR viewer.paintingWedged THEN RETURN; IF ignoreIcon AND viewer.iconic THEN RETURN; IF sel # NIL AND viewer = sel.viewer THEN tdd _ sel.data; IF tdd = NIL THEN WITH viewer.data SELECT FROM vtdd: TEditDocumentData => tdd _ vtdd; ENDCASE; IF tdd = NIL THEN RETURN; IF viewer.iconic THEN { [] _ TEditLocks.LockDocAndTdd[tdd, "Scroll"]; inner[tdd ! UNWIND => TEditLocks.UnlockDocAndTdd[tdd]]; TEditLocks.UnlockDocAndTdd[tdd]; } ELSE { IF NOT TEditTouchup.LockAfterRefresh[tdd, "Scroll"] THEN RETURN; inner[tdd ! UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]]; TEditTouchup.UnlockAfterRefresh[tdd]; }; }; IF viewer = NIL OR viewer.destroyed OR viewer.paintingWedged THEN RETURN; IF ignoreIcon AND viewer.iconic THEN RETURN; ViewerLocks.CallUnderWriteLock[withViewer, viewer]; }; DoPaint: PROC [viewer: Viewer, op: REF _ NIL] = { clearClient: BOOL _ op = NIL; IF forkPaints THEN ViewerForkers.ForkPaint[viewer: viewer, hint: client, clearClient: clearClient, whatChanged: op, tryShortCuts: TRUE] ELSE ViewerOps.PaintViewer[viewer: viewer, hint: client, clearClient: clearClient, whatChanged: op]; }; END. ΚTEditScrollingImpl.mesa Copyright Σ 1985, 1987, 1988 by Xerox Corporation. All rights reserved. Michael Plass, November 25, 1987 9:35:15 am PST Russ Atkinson (RRA) June 25, 1985 11:27:42 am PDT Doug Wyatt, February 17, 1988 6:10:53 pm PST RRA: for some reason forking the paints causes the scrollbar feedback to be highly inaccurate when perfoming explict scrolling operations. I strongly suspect that this is because the scrollbar painting is done in th wrong place. But we will not change this soon. pos is the current top line in the viewer; goal is the distance to back up. algorithm works by incrementally formatting lines prior to pos until it reaches goal height. The incremental backup procedure preserves the following invariants: newPos is the current line start totalLeading is the leading from the baseline of newPos to the baseline of pos topIndent is the distance from the viewer top to baseline of newPos if newPos goes at top topLeading is the style value for topLeading corresponding to newPos back up to previous text node back up past last char before pos no more characters in the node. shows as blank line format lines from tPos to starting pos When reach here, have found all the line breaks from [textNode, where] to initial pos. where holds the offset for the first one lastBreak holds the offset for the last one breakList holds the offsets for the previous ones have enough. find correct line use breakList to find correct break need to get topLeading for pos.node Once implement minGaps between lines, will also need topAscent for this line. Should be able to get that from the line table. (Except if continue to use this for top offset in ScrollToPosition... perhaps can just change that to back up a fixed number of lines.) [self: Viewer, op: ViewerClasses.ScrollOp, amount: INTEGER, shift: BOOL _ FALSE, control: BOOL _ FALSE] RETURNS [top: INTEGER, bottom: INTEGER] calculate next line start pos go to the next node, unless already at end of document find new topLine on screen first level only all levels move levels make room for the new stuff check level of target make sure there's always something shown for big documents find the line containing [node, where] move goal line a bit down from the top OnScreen determines whether or not the given location is visible for the given viewer. It must be called when the document is locked! Now we know that we have a Tioga document Need to have the MAX or will fail to scroll in 1 or 2 line viewers. Needs to be bigger than glitchLines since burst input may move the caret several lines. never glitch in this case scroll to end of document; used in typescripts selection is visible on screen make sure that selection didn't leave glitch failed; use general scroll ΚΗ˜codešœ™KšœH™HK™/K™1K™,K™—šΟk ˜ Kšœ œF˜UKšœ œ"˜4Kšœ œ>˜NKšœœ ˜Kšœ œ˜#KšœœQ˜dKšœ œ+˜Kšœ˜—K˜—K˜—Kšœœ:˜VK˜K˜'Kšœ œ œ!œ ˜JKš œœœœœ5˜€Kšœ, -˜YK˜šœ œ˜Kšœ4™4Kšœ%œ˜,K˜—K˜K˜ K˜"š˜Kšœ&™&K˜Kšœ™˜™K˜Kšœ œ%˜6K˜Kšœœœœ˜9Kšœœ œ˜AKšœ˜—Kšœ˜Kšœ œ˜K™KšœV™VKšœ(™(Kšœ+™+Kšœ2™2K˜šœ œ œ˜8Kšœ™Kšœœ* ˜Pšœ˜Kšœ  ˜ šœ ˜&K˜K˜ K˜K˜—šœ˜ Kšœ#™#Kšœœ *˜:K˜Kšœ .˜?K˜1š œœœœ˜/Kšœœœ˜;Kšœ˜—K˜——K˜K˜—K˜K˜K˜—K˜1Kšœ,˜,K˜Kšœ œ˜Kšœœœœ˜Kšœ œ˜K˜šœ œ˜Kšœ#™#Kšœ-˜-K˜-Kšœ‡™‡K˜K˜—K˜ K˜%šœ˜Kšœ!œ˜)˜7K˜*—Kšœœœ 8˜TK˜$K˜K˜Kšœ œœ $˜QK˜ Kšœ˜K˜—Kšœ˜K˜˜K˜——•StartOfExpansion‘ -- [self: ViewerClasses.Viewer, op: ViewerClasses.ScrollOp, amount: INTEGER, shift: BOOL _ FALSE, control: BOOL _ FALSE] RETURNS [top: INTEGER, bottom: INTEGER]šŸœœ˜8KšΠck™šœœ˜(K˜/K˜*Kšœ œœ˜(Kšœœ˜Kšœ œœ˜K˜šœ œ˜šœ˜K˜KK˜NKšœ œ˜Kšœ œ˜Kšœœ˜—Kšœ˜K˜K˜—Kšœ˜˜˜Kšœœ˜Kšœ œ˜Kšœ4˜4Kš œœ œœœ˜>šœ œ˜$šœ˜Kšœ™šœ˜šœ˜Kšœ6™6K˜Kšœ œ˜"šœœ"˜/K˜1Kšœ$˜(—Kšœœœ˜'K˜—Kšœ/˜3—K˜—šœ˜Kšœ™šœ3œœ˜XK˜Kšœœœ  ˜/Kšœ˜—K˜K˜——šœ œœ˜Kšœ&˜&šœœ ˜Kšœ™—šœ!˜!Kšœ ™ —šœ œ9˜EKšœ ™ —Kšœ  ˜&—šœœ˜"Kšœ˜Kšœœ˜Kšœ˜—˜K˜——˜ Kšœ#œ˜+KšœJœ˜išœœ˜ Kšœ™Kšœœ!œ ˜KK˜K˜6K˜ K˜—K˜K˜—˜ K˜'šœ ˜ Kšœ;œ˜Ešœ˜Kšœ œ:˜IK˜Ošœ'œ˜/Kšœ™Kšœœ*˜8š œœœ œ "˜AK˜$Kšœ˜—K˜—Kšœœ˜#K˜——Kšœ˜K˜K˜—˜ Kšœ*œ˜.Kšœœ˜šœF˜FKšœœœ˜!—˜aKšœœœ˜!—šœy˜yKšœœœ˜!—K˜"Kšœœœ˜-Kšœ:™:Kšœœ˜"Kšœœ'˜.K˜K˜ Kšœœ˜K˜K˜—Kšœ˜K˜—šœœ œ˜/K˜,Kš œœ œœœ ˜4K˜—˜K˜——Kšœœœ˜%˜K˜——šŸœœœ2œ˜XKšœœ2œœ˜hšœœ˜(K˜Kšœœœœ$˜;Kšœœ ˜K˜/Kšœ œœ˜K˜Kšœ,˜,K˜Kšœ œœ 5˜Xšœ'œ&œ˜XKšœ œ˜Kšœ' œ˜EK˜K˜—šœœ+˜EK˜Kšœ˜K˜—K˜Kšœ1˜1K˜"š˜Kšœ&™&KšœŸ˜ŸKš œœœœœ˜dK˜Kšœ%œœ˜1Kšœ˜K˜—šœœ˜Kšœ&™&Kšœœ6˜CKšœœ5˜OK˜—Kšœœ˜#Kšœ˜K˜šœ œœ˜+K˜,Kš œœ œœœ˜?K˜—K˜—Kš œ œœœœ˜>K˜K˜—šŸœœ,œœ˜LKšœ†™†Kšœ œœœœœœ˜8Kš œœœœœ˜9šœ œ˜šœ˜Kšœ)™)K˜/Kšœœœ˜šœ œœœ˜-K˜(K˜4K˜ K˜7Kš œœœœœ ˜MKš œœœœœ ˜Kšœœ˜Kšœœ œœ˜/Kšœœœ˜K˜!Kšœ˜—Kšœ˜K˜—Kšœ ˜K˜—Kšœ˜—Kšœœ˜K˜K˜—š Ÿ œœœœœ$˜pKšœœ2œœ˜hšœœ˜-K˜K˜!K˜ Kšœœ˜—šœœ˜(K˜/Kšœœ˜#šœ œœ˜K˜—š Ÿ œœœ œœ˜5Kšœ œœœ8˜VKšœC™CKšœ œœ# ˜XKšœW™WK˜K˜K˜Kšœ œ˜K˜3Kšœ œ˜šŸœœœ˜Dšœœ&˜/Kšœ/˜3Kšœ˜ —˜K˜——šœ$œ˜,Kšœ™Kšœœ˜Kšœ˜K˜—Kšœ œ˜Kšœ˜šœ ˜"šœ˜K˜3Kš œ œœœœ˜AK˜—Kšœ4˜8—K˜Kšœ-˜-K˜"šœœ(˜9šœ œœ˜Kšœ˜Kšœ˜Kšœœ˜Kšœ˜—Kšœ—˜—K˜Kšœœ œ˜/šœ œœœ˜JKšœ œ˜Kšœ˜—K˜Kšœœ1˜MK˜ Kšœ˜—Kšœ˜Kšœ˜Kšœ œ˜šœ œ˜K˜EKšœ&˜&K˜—K˜K˜—˜šœœ˜šœ˜Kšœ.™.K˜/K˜=šœL˜RKšœ˜—Kšœ ˜2Kšœ œ˜-K˜—Kšœ˜šœ˜ Kšœ)œœ œ ˜]Kšœ œ˜!š œœ œœœ˜=Kšœ™—K˜*šœ%œœ˜3Kšœ%™%—Kšœœœ˜Hš œœ œ œ˜7Kšœ˜—K˜——K˜—K˜š œœ œ œ˜(Kšœ!™!Kšœœ˜%—K˜K˜K˜—Kšœœœ%˜7K˜Kšœ œ˜&K˜K˜K˜—š Ÿ œœ œJœœœ˜‰šœ œ˜Kšœœ˜Kš œ œœœœœ˜IKšœ œœœ˜,Kšœœœœ˜9š œœœœ œ˜.Kšœ&˜&Kšœ˜—Kšœœœœ˜šœ˜šœ˜Kšœ-˜-Kšœ œ%˜7Kšœ ˜ K˜—šœ˜Kšœœ.œœ˜@Kšœ œ*˜