<<-- TEditScrollingImpl.mesa; Edited by Paxton on December 29, 1982 8:45 am>> <<-- Edited by McGregor on July 30, 1982 10:04 am>> <> <> <> DIRECTORY NodeStyle USING [Ref, Alloc, Free, ApplyAll, GetTopLeadingI, GetTopIndentI, GetLeadingI, GetBottomLeadingI], RopeReader USING [Ref, SetPosition, Backwards, GetRopeReader, FreeRopeReader], TEditDisplay USING [EstablishLine], TEditDocument USING [LineTable, maxClip, Selection, SelectionId, SelectionPoint, TEditDocumentData], TEditFormat USING [GetLineInfo], TEditInput USING [MaxLevelShown], TEditLocks USING [LockDocAndTdd, UnlockDocAndTdd], TEditOps USING [RememberCurrentPosition], TEditProfile USING [scrollBottomOffset, scrollTopOffset], TEditSelection USING [InsertionPoint, pSel, sSel, fSel], TEditScrolling, 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, NarrowToTextNode, Parent, pZone, Ref, RefTextNode, Root, StepForward], ViewerOps USING [PaintViewer], ViewerClasses USING [ScrollProc, Viewer]; TEditScrollingImpl: CEDAR PROGRAM IMPORTS NodeStyle, RopeReader, TEditDisplay, TEditFormat, TEditLocks, TEditSelection, TextEdit, TEditInput, TEditOps, TEditProfile, TEditTouchup, TextNode, ViewerOps EXPORTS TEditScrolling = BEGIN OPEN TEditDocument, TextNode; BackUp: PROC [viewer: ViewerClasses.Viewer, tdd: TEditDocumentData, pos: Location, goal: INTEGER] RETURNS [newPos: Location, lines, totalLeading, topIndent: INTEGER] = BEGIN <> <> <> <> <> <> <> maxLevel: INTEGER _ tdd.clipLevel; parent: TextNode.Ref; level: INTEGER _ 0; -- in case we are doing level clipping levelClipping: BOOLEAN _ maxLevel < maxClip; IncrBackUp: PROC [pos: Location, goal, prevTopLeading: INTEGER] RETURNS [prev: Location, totalLeading, lines, topIndent, topLeading: INTEGER] = BEGIN tPos: Location; leading, bottomLeading: INTEGER; lastBreak: TextEdit.Offset; breakList: LIST OF TextEdit.Offset; -- breaks between first and last textNode: RefTextNode; where, endOffset, size: TextEdit.Offset; IF pos.where=0 THEN DO -- back up to previous text node tempNode: TextNode.Ref; IF levelClipping THEN [tempNode, parent, level] _ BackwardClipped[pos.node, maxLevel, parent, level] ELSE [tempNode, parent, ----] _ Backward[pos.node, parent]; IF tempNode=NIL OR parent=NIL THEN RETURN[pos, 0, 0, 0, 0]; IF (textNode _ NarrowToTextNode[tempNode])=NIL THEN LOOP; size _ endOffset _ where _ TextEdit.Size[textNode]; EXIT; ENDLOOP ELSE { -- back up past last char before pos textNode _ NarrowToTextNode[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 NodeStyle.ApplyAll[style, styleNode _ textNode]; 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 { -- no more characters in the node. shows as blank line lines _ 1; prev _ [textNode, where]; RETURN }; tPos _ [textNode, where]; lines _ 0; DO -- format lines from tPos to starting pos lastBreak _ tPos.where; tPos _ TEditFormat.GetLineInfo[viewer, tdd, tPos, style].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 _ TextNode.pZone.CONS[lastBreak, breakList]; ENDLOOP; <<>> <> <> <> <> IF totalLeading+topIndent >= goal THEN { -- have enough. find correct line discardLines: INTEGER _ (totalLeading+topIndent-goal)/leading; -- too many lines SELECT discardLines FROM <= 0 => NULL; -- don't discard any >= lines-1 => { -- discard all but one where _ lastBreak; lines _ 1; totalLeading _ bottomLeading }; ENDCASE => { -- use breakList to find correct break 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]; END; rdr: RopeReader.Ref _ RopeReader.GetRopeReader[]; style: NodeStyle.Ref _ NodeStyle.Alloc[]; styleNode: TextNode.Ref; remainder: INTEGER _ goal; dy: INTEGER _ LAST[INTEGER]; topLeading: INTEGER _ 0; IF pos.where=0 THEN { -- need to get topLeading for pos.node NodeStyle.ApplyAll[style, pos.node]; 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; NodeStyle.Free[style]; RopeReader.FreeRopeReader[rdr]; END; ScrollTEditDocument: PUBLIC ViewerClasses.ScrollProc = BEGIN tdd: TEditDocumentData = NARROW[self.data]; lines: LineTable; topLine: Location; paintOp: REF ANY _ TEditTouchup.refresh; iconic: BOOL = self.iconic; levelChange: BOOL _ FALSE; IF tdd = NIL THEN RETURN; IF iconic THEN { [] _ TEditLocks.LockDocAndTdd[tdd, "ScrollTEditDocument", read]; 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; TEditLocks.UnlockDocAndTdd[tdd]; RETURN }; IF ~TEditTouchup.LockAfterRefresh[tdd, "ScrollTEditDocument"] THEN RETURN; lines _ tdd.lineTable; topLine _ lines[0].pos; SELECT op FROM up => BEGIN line: INTEGER _ 0; newLevel: INTEGER; IF amount > 0 AND lines.lastLine = 0 THEN { -- calculate next line start pos IF lines[0].end = eon THEN { -- go to the next node, unless already at end of document next: TextNode.Ref; maxLevel: INTEGER = tdd.clipLevel; next _ IF maxLevel < 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 { -- find new topLine on screen 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 control AND shift => 1, -- first level only control => maxClip, -- all levels shift => MIN[maxClip, TEditInput.MaxLevelShown[tdd]+1], -- move levels ENDCASE => tdd.clipLevel; -- no change IF newLevel # tdd.clipLevel THEN { tdd.clipLevel _ newLevel; levelChange _ TRUE } END; down => { numLines, totalLeading, topIndent: INTEGER; [topLine, numLines, totalLeading, topIndent] _ BackUp[self, tdd, topLine, MAX[amount, lines[0].yOffset]]; IF topLine # lines[0].pos THEN { -- make room for the new stuff op: REF TEditTouchup.PreScrollDownRec _ TextNode.pZone.NEW[TEditTouchup.PreScrollDownRec]; op.lines _ numLines; op.distance _ topIndent+totalLeading-lines[0].yOffset; paintOp _ op }; }; thumb => BEGIN TEditOps.RememberCurrentPosition[self]; IF amount < 5 THEN ScrollToPosition[self, [FirstChild[tdd.text],0], FALSE] ELSE BEGIN totalChars: TextEdit.Offset = LocNumber[LastLocWithin[tdd.text]]-1; pos: TextNode.Location _ LocWithin[tdd.text, (totalChars*amount)/100]; IF tdd.clipLevel < maxClip THEN { -- check level of target 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]; END; TEditTouchup.UnlockAfterRefresh[tdd]; RETURN; END; query => BEGIN toTop, toBottom, toEnd, totalChars, t, b: INT; ll: INTEGER = lines.lastLine; toTop _ LocOffset[[TextNode.FirstChild[tdd.text], 0], topLine ! BadArgs => GOTO Bad]; toBottom _ LocOffset[topLine, [lines[ll].pos.node, lines[ll].pos.where+lines[ll].nChars] ! BadArgs => GOTO Bad]; toEnd _ LocOffset[[lines[ll].pos.node, lines[ll].pos.where+lines[ll].nChars-1], LastLocWithin[tdd.text] ! BadArgs => GOTO Bad]; totalChars _ toTop+toBottom+toEnd; TEditTouchup.UnlockAfterRefresh[tdd]; IF totalChars<=0 THEN RETURN[0, 100]; <<-- make sure there's always something shown for big documents>> t _ MIN[98, 100*toTop/totalChars]; b _ MAX[t+2, 100*(toTop+toBottom)/totalChars]; RETURN[t, b]; -- so compiler will coerce to short integers EXITS Bad => { TEditTouchup.UnlockAfterRefresh[tdd]; RETURN }; <> END; ENDCASE; IF topLine # lines[0].pos OR levelChange THEN BEGIN TEditDisplay.EstablishLine[tdd, topLine, 0]; IF levelChange THEN ViewerOps.PaintViewer[self, client] -- complete repaint ELSE ViewerOps.PaintViewer[self, client, FALSE, paintOp]; END; TEditTouchup.UnlockAfterRefresh[tdd]; END; ScrollToPosition: PUBLIC PROC [viewer: ViewerClasses.Viewer, pos: TextNode.Location, offset: BOOLEAN] = BEGIN tdd: TEditDocumentData = NARROW[viewer.data]; lines: LineTable; node: TextNode.RefTextNode _ TextNode.NarrowToTextNode[pos.node]; where: INT _ MAX[0, MIN[pos.where, TextEdit.Size[node]-1]]; start: INT _ where; icon: BOOL = viewer.iconic; repaint: BOOL _ FALSE; nChars: CARDINAL; newPos, topLine: TextNode.Location; style: NodeStyle.Ref _ NodeStyle.Alloc[]; leading: INTEGER; backStop: INT _ MAX[0, where-300]; -- limit looking back too far when searching for CR's Unlock: PROC = { IF icon THEN TEditLocks.UnlockDocAndTdd[tdd] ELSE TEditTouchup.UnlockAfterRefresh[tdd] }; IF tdd = NIL THEN RETURN; IF icon THEN [] _ TEditLocks.LockDocAndTdd[tdd, "ScrollToPosition", read] ELSE IF ~TEditTouchup.LockAfterRefresh[tdd, "ScrollToPosition"] THEN RETURN; IF tdd.clipLevel < maxClip AND tdd.clipLevel < TextNode.Level[node] THEN { repaint _ TRUE; tdd.clipLevel _ maxClip; -- turn off level clipping -- }; lines _ tdd.lineTable; UNTIL start<=backStop OR TextEdit.FetchChar[node, start-1]=15C DO start _ start - 1; ENDLOOP; topLine _ [node, start]; NodeStyle.ApplyAll[style, topLine.node]; DO -- find the line containing [node, where] [----, ----, ----, newPos, nChars, leading] _ TEditFormat.GetLineInfo[ viewer, tdd, topLine, style]; IF newPos.node#node OR newPos.where>where OR newPos=topLine THEN EXIT; topLine _ newPos; IF topLine.where+nChars>where THEN EXIT; ENDLOOP; NodeStyle.Free[style]; IF offset THEN BEGIN -- move goal line a bit down from the top goal: INTEGER = leading * TEditProfile.scrollTopOffset; IF goal*2 < viewer.ch THEN topLine _ BackUp[viewer, tdd, topLine, goal].newPos; END; IF repaint OR topLine # lines[0].pos THEN BEGIN TEditDisplay.EstablishLine[tdd, topLine, 0]; Unlock[]; IF repaint THEN ViewerOps.PaintViewer[viewer, client] ELSE ViewerOps.PaintViewer[viewer, client, FALSE, TEditTouchup.refresh]; END ELSE Unlock[]; END; OnScreen: PROC [viewer: ViewerClasses.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: TEditDocument.TEditDocumentData => { <> IF TEditTouchup.LockAfterRefresh[tdd, "OnScreen"] THEN { <> ENABLE {UNWIND => TEditTouchup.UnlockAfterRefresh[tdd]}; 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 => {}; }; TEditTouchup.UnlockAfterRefresh[tdd]; RETURN [found]; }; }; ENDCASE; RETURN [FALSE]; }; AutoScroll: PUBLIC PROC [ viewer: ViewerClasses.Viewer _ TEditSelection.pSel.viewer, tryToGlitch: BOOLEAN _ TRUE, toEndOfDoc: BOOLEAN _ FALSE, id: TEditDocument.SelectionId _ primary] = BEGIN tdd: TEditDocumentData; goal: TextNode.Location; lines: LineTable; onScreen: BOOL; sel: Selection = SELECT id FROM primary => TEditSelection.pSel, secondary => TEditSelection.sSel, feedback => TEditSelection.fSel, ENDCASE => ERROR; TryToGlitch: PROC RETURNS [success: BOOLEAN] = BEGIN 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; lineCount: INTEGER _ 0; pos: TextNode.Location _ lines[lines.lastLine].pos; foundNode: BOOLEAN; NextNode: PROC [node: TextNode.Ref] RETURNS [next: TextNode.Ref] = { next _ IF tdd.clipLevel < maxClip THEN TextNode.ForwardClipped[node,tdd.clipLevel].nx ELSE TextNode.StepForward[node] }; IF TEditProfile.scrollBottomOffset<=0 THEN { TEditTouchup.UnlockAfterRefresh[tdd]; RETURN [TRUE] }; -- never glitch in this case success _ FALSE; style _ NodeStyle.Alloc[]; IF lines[lines.lastLine].end = eon THEN { pos _ [NextNode[lines[lines.lastLine].pos.node],0]; IF pos.node=NIL THEN { NodeStyle.Free[style]; RETURN [FALSE] }} ELSE pos.where _ pos.where+lines[lines.lastLine].nChars; foundNode _ pos.node=goal.node; NodeStyle.ApplyAll[style, pos.node]; THROUGH [0..MIN[tryLines, lines.lastLine-glitchLines]) DO IF pos.node=NIL THEN { NodeStyle.Free[style]; RETURN [FALSE] }; [----, ----, ----, newPos, ----, ----] _ TEditFormat.GetLineInfo[ viewer, tdd, pos, style]; 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 NodeStyle.ApplyAll[style, newPos.node]; pos _ newPos; ENDLOOP; NodeStyle.Free[style]; IF success THEN BEGIN TEditDisplay.EstablishLine[tdd, lines[lineCount+glitchLines].pos, 0]; TEditTouchup.UnlockAfterRefresh[tdd]; ViewerOps.PaintViewer[viewer, client, FALSE, TEditTouchup.refresh]; END; END; IF viewer=NIL OR viewer.iconic OR viewer.destroyed THEN RETURN; onScreen _ OnScreen[viewer, sel.start.pos]; -- determines if selection is on the screen tdd _ IF viewer=sel.viewer THEN sel.data ELSE NARROW[viewer.data]; IF tdd = NIL THEN RETURN; IF ~TEditTouchup.LockAfterRefresh[tdd, "AutoScroll"] THEN RETURN; lines _ tdd.lineTable; IF toEndOfDoc THEN BEGIN -- scroll to end of document; used in typescripts lines: LineTable = tdd.lineTable; goal _ TextNode.LastLocWithin[TextNode.FirstChild[tdd.text]]; IF lines[lines.lastLine].pos.where+lines[lines.lastLine].nChars >= goal.where THEN {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN}; goal.where _ goal.where-1; -- last char correction IF tryToGlitch AND TryToGlitch[] THEN RETURN; END ELSE IF sel.viewer # viewer THEN {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN} ELSE BEGIN -- scroll to end of selection selPoint: TEditDocument.SelectionPoint = IF sel.insertion=before THEN sel.start ELSE sel.end; clipped: BOOL = selPoint.clipped; IF ~clipped AND onScreen THEN {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN}; -- visible on screen goal _ TEditSelection.InsertionPoint[sel]; IF TextNode.Root[goal.node] # tdd.text THEN -- make sure that selection didn't leave {TEditTouchup.UnlockAfterRefresh[tdd]; RETURN}; IF sel.insertion=before AND goal.where>0 THEN goal.where _ goal.where-1; IF ~clipped AND tryToGlitch AND selPoint.line>0 AND TryToGlitch[] THEN RETURN; END; TEditTouchup.UnlockAfterRefresh[tdd]; <<-- glitch failed; use general scroll>> ScrollToPosition[viewer, goal, TRUE]; END; END.