<> <> <> <> <> <<>> 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 = { <<[self: Viewer, op: ViewerClasses.ScrollOp, amount: INTEGER, shift: BOOL _ FALSE, control: BOOL _ FALSE] RETURNS [top: INTEGER, bottom: INTEGER]>> 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.