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, Offset, RefTextNode, Size], TextNode USING [Backward, BackwardClipped, BadArgs, FirstChild, Forward, ForwardClipped, LastLocWithin, Level, Location, LocNumber, LocOffset, LocWithin, Parent, Ref, RefTextNode, 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: TextEdit.Offset; breakList: LIST OF TextEdit.Offset; -- breaks between first and last textNode: TextNode.RefTextNode; where, endOffset, size: TextEdit.Offset; 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: TextEdit.Offset _ 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 THEN { -- have enough. find correct line 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 TextEdit.Offset _ 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: TextEdit.Offset = 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.RefTextNode _ 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].char=15C 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 c 1985 by Xerox Corporation. All rights reserved. Michael Plass, April 14, 1986 11:41:20 am PST Russ Atkinson (RRA) June 25, 1985 11:27:42 am PDT Doug Wyatt, June 22, 1985 11:28:49 am PDT 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 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šœ Οmœ1™˜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˜šžœ žœ‘"˜KKšœžœ*‘˜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˜UK˜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˜—šžœžœ,ž˜FK˜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šœ žœ*˜