TEditDisplayImpl.mesa
Copyright © 1985, 1986 by Xerox Corporation. All rights reserved.
Michael Plass, April 14, 1986 2:58:39 pm PST
Russ Atkinson (RRA) July 2, 1985 4:03:26 pm PDT
Doug Wyatt, September 2, 1986 4:02:48 pm PDT
DIRECTORY
Carets USING [StopCaretsInViewer],
EditSpan USING [afterMoved1, afterMoved2],
Imager USING [black, Color, Context, MaskRectangleI, SetColor, SetXY, white],
ImagerBackdoor USING [MoveViewRectangle, TestViewRectangle],
ImagerOps USING [DoWithBuffer],
InputFocus USING [GetInputFocus],
NodeStyle USING [GetBottomLeadingI, GetLeadingI, GetTopIndentI, GetTopLeadingI, Style],
NodeStyleOps USING [Alloc, ApplyAll, Free, OfStyle],
RopeReader USING [Backwards, FreeRopeReader, GetIndex, GetRopeReader, Ref, SetPosition],
Scaled USING [Float, FromInt, Round],
TEditCompile USING [minAvgLineLeading],
TEditDocument USING [LineBreak, LineTable, LineTableRec, LineRec, maxClip, Selection, SelectionId, SelectionRec, TEditDocumentData],
TEditFormat USING [Allocate, FormatLine, LineInfo, Paint, Release],
TEditLocks USING [LockDocAndTdd, LockRef, UnlockDocAndTdd, WaitingForWrite],
TEditPrivate USING [],
TEditScrolling USING [AutoScroll],
TEditSelection USING [AdjustSelStates, FixUpAfterDisplay, fSel, InsertionPoint, pSel, SelectionRoot, ShowSelection, sSel, TakeDownForRedisplay, TakeSelectionDown],
TEditSelectionPrivate USING [InvalidateLineCache],
TEditTouchup USING [fullUpdate, PreScrollDownRec, refresh, RefreshOver],
TextEdit USING [Size],
TextNode USING [FirstChild, ForwardClipped, Level, Location, Node, Root, StepForward],
ViewerClasses USING [PaintProc, PaintRectangle, Viewer],
ViewerOps USING [FetchProp, UserToScreenCoords];
TEditDisplayImpl: CEDAR PROGRAM
IMPORTS Carets, EditSpan, Imager, ImagerBackdoor, ImagerOps, InputFocus, NodeStyle, NodeStyleOps, Scaled, TextEdit, TextNode, RopeReader, TEditFormat, TEditLocks, TEditScrolling, TEditSelection, TEditSelectionPrivate, TEditTouchup, ViewerOps
EXPORTS TEditPrivate
= BEGIN
Node: TYPE ~ TextNode.Node;
LineTable: TYPE ~ TEditDocument.LineTable;
TEditDocumentData: TYPE ~ TEditDocument.TEditDocumentData;
Viewer: TYPE ~ ViewerClasses.Viewer;
PaintTEditDocument: PUBLIC ViewerClasses.PaintProc = {
WITH self.data SELECT FROM
tdd: TEditDocumentData => {
interrupt: BOOL ← clear OR whatChanged=TEditTouchup.fullUpdate OR whatChanged=NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle];
lock: TEditLocks.LockRef ← NIL;
Cleanup: PROC = {
IF lock # NIL THEN {
TEditLocks.UnlockDocAndTdd[tdd];
TEditTouchup.RefreshOver[];
lock ← NIL;
};
};
lock ← TEditLocks.LockDocAndTdd[tdd, "PaintTEditDocument", read, interrupt, TRUE];
tdd.invisible ← FALSE; -- or else we wouldn't have been called
IF lock=NIL
THEN { IF NOT interrupt THEN ERROR }
ELSE {
quit ← LockedPaint[self, context, whatChanged, clear, tdd, lock
! UNWIND => Cleanup[]];
};
Cleanup[];
};
ENDCASE => NULL;
};
BiggerLineTable: PROC [oldTable: LineTable, newSize: INT] RETURNS [lineTable: LineTable] ~ {
lineTable ← NEW[TEditDocument.LineTableRec[newSize] ←
[lastLine: oldTable.lastLine, lastY: oldTable.lastY, lines: NULL]];
FOR n: INTEGER IN [0..oldTable.lastLine] DO -- copy old data
lineTable[n] ← oldTable[n];
ENDLOOP;
};
InvalidateLines: PROC [lineTable: LineTable, start, end: INT] ~ {
FOR i: INT IN [start..end) DO
lineTable[i].valid ← FALSE;
ENDLOOP;
};
Rect: TYPE ~ RECORD [xmin, ymin, w, h: INT];
Intersects: PROC [a, b: Rect] RETURNS [BOOL] ~ {
ymin: INT ~ MAX[a.ymin, b.ymin];
ymax: INT ~ MIN[a.ymin+a.h, b.ymin+b.h];
xmin: INT ~ MAX[a.xmin, b.xmin];
xmax: INT ~ MIN[a.xmin+a.w, b.xmin+b.w];
RETURN [xmax > xmin AND ymax > ymin];
};
Inside: PROC [a, b: Rect] RETURNS [BOOL] ~ {
RETURN [
a.ymin>=b.ymin AND a.ymin+a.h<b.ymin+b.h AND
a.xmin>=b.xmin AND a.xmin+a.w<b.xmin+b.w
];
};
LineRectangle: PROC [lineTable: LineTable, i: INT, ch: INT] RETURNS [Rect] ~ {
lineRec: TEditDocument.LineRec ~ lineTable[i];
ascent: INTEGER ~ lineRec.ascent;
descent: INTEGER ~ lineRec.descent;
RETURN [[lineRec.xOffset, ch-lineRec.yOffset-descent, lineRec.width, ascent+descent]];
};
InvalidateLinesOutsideOfRectangle: PROC [lineTable: LineTable, rect: Rect, ch: INT] ~ {
FOR i: INTEGER IN [0..lineTable.lastLine] DO
IF NOT Inside[LineRectangle[lineTable, i, ch], rect] THEN lineTable[i].valid ← FALSE;
ENDLOOP;
};
InvalidateLinesTouchingRectangle: PROC [lineTable: LineTable, rect: Rect, ch: INT] ~ {
FOR i: INT IN [0..lineTable.lastLine] DO
IF Intersects[LineRectangle[lineTable, i, ch], rect] THEN lineTable[i].valid ← FALSE;
ENDLOOP;
};
LockedPaint: PROC [self: Viewer, context: Imager.Context, whatChanged: REF, clear: BOOL, tdd: TEditDocumentData, lock: TEditLocks.LockRef]
RETURNS [quit: BOOLFALSE] ~ {
newSize: INT ~ (self.ch/TEditCompile.minAvgLineLeading)+1;
typescript: BOOL ~ tdd.tsInfo # NIL;
refreshing: BOOL ← whatChanged=TEditTouchup.refresh;
redisplay: BOOL ← clear OR refreshing OR whatChanged=TEditTouchup.fullUpdate OR whatChanged=NIL;
moveDownLines: INTEGER ← 0; -- used with scrolling down
moveDownDistance: INT ← 0; -- used with scrolling down
Carets.StopCaretsInViewer[self];
IF newSize > tdd.lineTable.maxLines THEN {
tdd.lineTable ← BiggerLineTable[tdd.lineTable, newSize];
};
IF redisplay
THEN {
IF clear THEN TEditSelection.AdjustSelStates[self];
IF NOT refreshing THEN InvalidateLines[tdd.lineTable, 0, tdd.lineTable.lastLine+1];
}
ELSE {
WITH whatChanged SELECT FROM
x: REF TEditTouchup.PreScrollDownRec => {
move down to make room for new lines at top
IF clear THEN ERROR;
moveDownLines ← x.lines;
moveDownDistance ← x.distance;
refreshing ← redisplay ← TRUE;
};
rect: ViewerClasses.PaintRectangle => {
sx, sy: INT ← 0;
[sx, sy] ← ViewerOps.UserToScreenCoords[self, 0, 0];
InvalidateLinesOutsideOfRectangle[tdd.lineTable, [rect.x-sx, rect.y-sy, rect.w, rect.h], self.ch];
tdd.lineTable[tdd.lineTable.lastLine].valid ← FALSE;
Mark the last line as invalid so that the lines after it will be painted as needed.
refreshing ← redisplay ← TRUE;
};
atom: ATOM => {
SELECT atom FROM
$TakeDownPSel => TEditSelection.TakeSelectionDown[primary, self, context];
$ShowPSel => TEditSelection.ShowSelection[primary, self, context];
$TakeDownSSel => TEditSelection.TakeSelectionDown[secondary, self, context];
$ShowSSel => TEditSelection.ShowSelection[secondary, self, context];
$TakeDownFSel => TEditSelection.TakeSelectionDown[feedback, self, context];
$ShowFSel => TEditSelection.ShowSelection[feedback, self, context];
ENDCASE => ERROR;
};
ENDCASE => ERROR;
};
IF redisplay THEN {
TEditSelection.TakeDownForRedisplay[primary, self, context];
TEditSelection.TakeDownForRedisplay[secondary, self, context];
TEditSelection.TakeDownForRedisplay[feedback, self, context];
IF typescript THEN CheckBottomVisible[self, tdd];
IF NOT tdd.lineTable[0].valid THEN CheckStartLocation[self, tdd];
TEditSelectionPrivate.InvalidateLineCache[]; -- since selection impl keeps accelerators
RefreshViewer[self, tdd, context, clear, (refreshing AND NOT typescript), lock, moveDownLines, moveDownDistance];
};
SELECT whatChanged FROM
$TakeDownPSel, $TakeDownSSel, $TakeDownFSel => {};
ENDCASE =>
SELECT TRUE FROM
refreshing AND NOT typescript AND TEditLocks.WaitingForWrite[lock] => {};
ENDCASE => {
fix up the selections
TEditSelection.FixUpAfterDisplay[primary, self, context, ~tdd.readOnly];
TEditSelection.FixUpAfterDisplay[secondary, self, context, TRUE];
TEditSelection.FixUpAfterDisplay[feedback, self, context, FALSE]
};
IF NOT tdd.dirty THEN SELECT tdd.scroll FROM
no => NULL;
endofdoc =>
AutoScroll[self, tdd.scrollGlitch, TRUE, primary];
endofsel => {
sel: TEditDocument.Selection = SELECT tdd.scrollSelectionId FROM
primary => TEditSelection.pSel,
secondary => TEditSelection.sSel,
feedback => TEditSelection.fSel,
ENDCASE => ERROR;
IF TEditSelection.SelectionRoot[sel]=tdd.text THEN
AutoScroll[self, tdd.scrollGlitch, FALSE, tdd.scrollSelectionId];
};
ENDCASE => ERROR;
};
CheckBottomVisible: PROC [self: Viewer, tdd: TEditDocumentData] ~ {
IF self.ch < tdd.lineTable[tdd.lineTable.lastLine].yOffset THEN {
If a typescript viewer gets smaller such that bottom line is no longer visible, automatically scroll it up to keep bottom line on screen.
delta: INT = tdd.lineTable[tdd.lineTable.lastLine].yOffset - self.ch;
n: INTEGER ← 0;
UNTIL n=tdd.lineTable.lastLine OR tdd.lineTable[n].yOffset > delta DO
n ← n+1;
ENDLOOP;
tdd.lineTable[0] ← tdd.lineTable[MIN[tdd.lineTable.lastLine, n+1]];
tdd.lineTable[0].valid ← FALSE;
};
};
CheckStartLocation: PROC [self: Viewer, tdd: TEditDocumentData] ~ {
startLoc: TextNode.Location ← [NIL, 0];
CheckLocNode: PROC [loc: TextNode.Location] RETURNS [BOOL] = {
startLoc ← loc;
RETURN [ loc.node # NIL AND TextNode.Root[loc.node] = tdd.text ]
};
N.B. CheckLocNode has the side effect of setting startLoc.
IF NOT CheckLocNode[tdd.lineTable[0].pos] THEN {
IF (InputFocus.GetInputFocus[].owner = self AND CheckLocNode[TEditSelection.InsertionPoint[]])
OR CheckLocNode[EditSpan.afterMoved1]
OR CheckLocNode[EditSpan.afterMoved2]
THEN tdd.fixStart ← TRUE
ELSE startLoc ← [TextNode.FirstChild[tdd.text], 0]
};
IF startLoc.node # NIL THEN {
size: INT = TextEdit.Size[startLoc.node];
IF startLoc.where NOT IN [0..size) THEN {
tdd.lineTable[0].valid ← FALSE;
tdd.fixStart ← TRUE;
startLoc.where ← MAX[MIN[size-1, startLoc.where], 0]
}
};
IF tdd.fixStart AND startLoc.node # NIL AND startLoc.where > 0 THEN {
move startLoc to after CR or space or at start of node
rdr: RopeReader.Ref = RopeReader.GetRopeReader[];
minIndex: INT ~ MAX[INT[startLoc.where]-200, 0];
fallback: INT ← startLoc.where;
foundBreak: INT ← startLoc.where;
RopeReader.SetPosition[rdr, startLoc.node.rope, startLoc.where];
FOR i: INT DECREASING IN [minIndex..startLoc.where) DO
char: CHAR ~ RopeReader.Backwards[rdr];
IF char = 15C THEN { foundBreak ← i+1; EXIT};
IF char = ' THEN { fallback ← i+1 };
IF i = 0 THEN { foundBreak ← i; EXIT};
ENDLOOP;
startLoc.where ← MIN[foundBreak, fallback];
RopeReader.FreeRopeReader[rdr];
};
tdd.fixStart ← FALSE;
tdd.lineTable[0].pos ← startLoc;
IF tdd.clipLevel < TEditDocument.maxClip THEN -- check level of startLoc
tdd.clipLevel ← MAX[tdd.clipLevel, TextNode.Level[startLoc.node]];
};
AutoScroll: PROC [self: Viewer, tryToGlitch, toEndOfDoc: BOOL, id: TEditDocument.SelectionId] = {
ENABLE ABORTED => GOTO Quit;
IF self # NIL THEN WITH self.data SELECT FROM
tdd: TEditDocumentData => {
TEditScrolling.AutoScroll[self, tryToGlitch, toEndOfDoc, id];
IF NOT tdd.dirty THEN tdd.scroll ← no;
TEditTouchup.RefreshOver[];
};
ENDCASE => NULL;
EXITS Quit => NULL
};
RefreshViewer: PROC [viewer: Viewer, tdd: TEditDocumentData, dc: Imager.Context, displayClear, refresh: BOOL, lock: TEditLocks.LockRef, moveDownLines, moveDownDistance: INT] = {
n: INTEGER ← 0;
start, end: INTEGER ← 0;
lines: TEditDocument.LineTable ← tdd.lineTable;
Imager.SetColor[dc, Imager.black];
UNTIL n > lines.lastLine DO -- search for an invalid line
IF lines[n].valid
THEN n ← n+1
ELSE {
start ← n;
UNTIL n+1 > lines.lastLine DO
IF lines[n+1].valid THEN EXIT;
n ← n+1;
ENDLOOP;
end ← n;
IF start > 0 AND lines[start].pos.node # lines[start-1].pos.node THEN {
need to reinitialize the start pos; may have deleted start node
lines[start].pos.where ← 0;
IF (lines[start].pos.node ← TextNode.ForwardClipped[lines[start-1].pos.node,tdd.clipLevel].nx) = NIL THEN
start ← start-1 --have deleted end of document--
};
n ← WhileInPosRange[viewer, tdd, dc, start, end, displayClear,
refresh, lock, moveDownLines, moveDownDistance];
moveDownLines ← moveDownDistance ← 0; -- only do this the first time
IF (refresh AND TEditLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN {
tdd.dirty ← TRUE;
RETURN;
};
};
ENDLOOP;
tdd.movedIn ← tdd.movedOut ← tdd.dirty ← FALSE;
};
The following procedure is passed two document line positions; the meaning is to paint all the lines marked as invalid and then continue painting within each node until the ripple has subsided. Returns line where stopped.
WhileInPosRange: PROC [viewer: Viewer, tdd: TEditDocumentData, dc: Imager.Context, start, end: INTEGER, displayClear, refresh: BOOL, lock: TEditLocks.LockRef, moveDownLines, moveDownDistance: INT] RETURNS [line: INTEGER] = {
lines: TEditDocument.LineTable ← tdd.lineTable;
oldBottomY: INTMAX[INT[lines[start].yOffset] - lines[start].ascent, 0];
kindOfStyle: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen;
lines[start].valid ← FALSE;
WHILE start>0 AND (lines[start-1].yOffset + lines[start-1].descent > oldBottomY) DO
start ← start-1;
lines[start].valid ← FALSE;
ENDLOOP;
{
node: Node ← lines[start].pos.node;
pos: INT ← lines[start].pos.where;
leading: INT ← 0;
topLeading: INT ← 0;
bottomLeading: INT ← 0;
nodeLeading: INT ← 0;
knowBottomLeading: BOOL ← (start = 0); -- otherwise need to get it
y: INT ← 0;
nodeSize: INT ← TextEdit.Size[node];
styleInit: BOOLTRUE;
yMatchRun: BOOLTRUE;
yMatch: BOOLFALSE;
eraseToEnd: BOOLFALSE;
lineInfo: TEditFormat.LineInfo ← TEditFormat.Allocate[];
nodeStyle: NodeStyle.Style ← NodeStyleOps.Alloc[];
Cleanup: PROC = {
TEditFormat.Release[lineInfo]; lineInfo ← NIL;
NodeStyleOps.Free[nodeStyle]; nodeStyle ← NIL;
};
styleInfoNode: Node ← NIL; -- node for which we were last called to get style info
This is used to avoid redoing GetStyleInfo after have peeked at next node style as part of trying to move lines down.
GetStyleInfo: PROC [node: Node] = {
IF node = styleInfoNode THEN RETURN; -- already have the style info
styleInfoNode ← node;
IF NOT knowBottomLeading AND pos=0 THEN { -- get it from previous node
NodeStyleOps.ApplyAll[nodeStyle, lines[line-1].pos.node, kindOfStyle];
bottomLeading ← NodeStyle.GetBottomLeadingI[nodeStyle];
};
NodeStyleOps.ApplyAll[nodeStyle, node, kindOfStyle];
leading ← NodeStyle.GetLeadingI[nodeStyle];
topLeading ← MAX[bottomLeading, NodeStyle.GetTopLeadingI[nodeStyle]];
use value of bottomLeading left from previous node
nodeLeading ← MAX[bottomLeading, topLeading];
use previous value of bottomLeading
bottomLeading ← NodeStyle.GetBottomLeadingI[nodeStyle];
knowBottomLeading ← TRUE;
};
checkedVisible, fullyVisible: BOOLFALSE;
FullyVisible: PROC RETURNS [BOOL] = {
IF NOT checkedVisible THEN {
fullyVisible ← ImagerBackdoor.TestViewRectangle[dc, 0, 0, viewer.cw, viewer.ch]=all;
checkedVisible ← TRUE;
};
RETURN [fullyVisible];
};
found: INT ← 0; -- set by FindBelow
TryMoveUp: PROC RETURNS [ok: BOOL] = {
Returns true if found the desired line at a lower location on the screen. Blts the line and all following lines, clears out screen below them, then updates line table.
to, from, num, dist, bottom: INT;
diff: INTEGER;
dirtyScanlinesInFirstTextline: NAT ← 0;
FindBelow: PROC RETURNS [ok: BOOL] = {
returns true if finds the desired line
Looks for valid line at or below current line with desired start location. Assumes oldBottomY has been set to bottom of previous line, so if found line starts below that it will be ok on the screen still. If finds such a line, stores its index in "found" and returns true.
FOR n: INTEGER IN [line..lines.lastLine] DO
IF lines[n].valid AND lines[n].pos=[node,pos] THEN {
found ← n;
RETURN [lines[n].yOffset-lines[n].ascent > oldBottomY];
};
ENDLOOP;
RETURN [FALSE];
};
IF NOT FullyVisible[] OR NOT FindBelow[] THEN RETURN [FALSE];
IF found=lines.lastLine AND lines[found].yOffset+lines[found].descent >= viewer.ch
THEN RETURN [FALSE]; -- bottom is clipped
IF found>0 AND (
dirtyScanlinesInFirstTextline ←
MAX[0,
lines[found-1].yOffset+lines[found-1].descent - lines[found].yOffset-lines[found].ascent
]
) > 0 THEN lines[found].valid ← FALSE;
to ← y-lines[found].ascent + dirtyScanlinesInFirstTextline;
top of clean part of new line is destination for blt
from ← lines[found].yOffset-lines[found].ascent + dirtyScanlinesInFirstTextline;
uppermost source scan line
IF to < 0 THEN { -- top of line is clipped
from ← from-to; -- move source down by amount to be clipped
to ← 0;
};
dist ← from-to; -- the distance to move
IF dist < 0 THEN RETURN [FALSE]; -- skip this bizarre case
bottom ← MIN[viewer.ch, lines.lastY];
num ← bottom-from; -- number of scan lines to move
IF num <= 0 THEN RETURN [FALSE];
ClearLine[oldBottomY, to]; -- clear gap above destination
MoveScanLines[to, from, num]; -- move the good scan lines up
ClearLine[bottom-dist, bottom]; -- clear the space at the bottom
lines.lastY ← lines.lastY-dist; -- lastY tells last used scan line in viewer
diff ← found-line; -- number of lines skipped
IF diff # 0 THEN { -- update the line table info
FOR n: INTEGER IN [line..lines.lastLine-diff] DO
lines[n] ← lines[n+diff]; -- move all the info up to new location
ENDLOOP;
ClearEntries[lines.lastLine-diff+1, lines.lastLine]; -- these lines no longer in use
lines.lastLine ← lines.lastLine-diff;
end ← end-diff;
};
FOR n: INTEGER IN [line..lines.lastLine] DO -- adjust the baselines
lines[n].yOffset ← lines[n].yOffset-dist;
ENDLOOP;
lines[lines.lastLine].valid ← FALSE;
Set the last line invalid to force repaint to fill in gap at bottom of viewer.
RETURN [TRUE]
};
ClearEntries: PROC [from, to: INTEGER] = {
FOR n: INTEGER IN [from..to] DO
lines[n] ← [pos: [NIL, 0], valid: FALSE, yOffset: 0, xOffset: 0, width: 0, ascent: 0, descent: 0];
ENDLOOP
};
MoveScanLines: PROC [to, from, height: INT] = {
IF height>0 THEN ImagerBackdoor.MoveViewRectangle[context: dc,
width: viewer.cw, -- full width of viewer
height: height, -- height of source
fromX: 0, fromY: viewer.ch-from-height,
toX: 0, toY: viewer.ch-to-height
];
};
TryMoveDown: PROC [nchars: INTEGER, end: TEditDocument.LineBreak] = {
We are about to put [node, pos] in line. It will contain nchars with break specified by end. This procedure looks to see if we will overwrite what we will next want to put in line+1. If so, move it down now. This happens whenever insert a single line.
This proc may need to call GetStyleInfo, so use all the style info you're interested in before you call it.
next: TextNode.Location;
from, lead, dist: INT;
IF NOT FullyVisible[] OR NOT lines[line].valid THEN RETURN;
from ← lines[line].yOffset-lines[line].ascent;
IF oldBottomY > from THEN RETURN; -- overwritten it already
next ← IF end=eon THEN [NextNode[], 0] ELSE [node, pos+nchars];
IF lines[line].pos # next THEN RETURN;
IF end=eon THEN { -- need to get leading info for the next node
GetStyleInfo[next.node]; lead ← nodeLeading;
}
ELSE lead ← leading;
dist ← y+lead-lines[line].yOffset; -- how far down to move it
MoveLinesDown[line, 1, from, dist, TRUE]
};
MoveLinesDown: PROC [startLine, numLines: INTEGER, from, dist: INT, clear: BOOL] = {
to: INT;
num: INT;
bottom: INT;
visible: BOOLFALSE;
dirtyScanlinesInFirstTextline: INT;
IF dist <= 0 THEN RETURN; -- skip this weird case
to ← from+dist;
bottom ← MIN[lines.lastY+dist, viewer.ch]; -- new bottom after blt
num ← bottom-to; -- the number of scan lines to blt
IF num <= 0 THEN RETURN; -- stuff to move down won't be visible
MoveScanLines[from+dist, from, num];
IF startLine > 0 AND (dirtyScanlinesInFirstTextline ← lines[startLine-1].yOffset+lines[startLine-1].descent - lines[startLine].yOffset-lines[startLine].ascent) > 0
THEN {
Repaint the top line if it got dirtied by descenders.
lines[startLine].valid ← FALSE;
ClearLine[from, to + dirtyScanlinesInFirstTextline];
}
ELSE ClearLine[from, to]; -- clear the gap
FOR n: INTEGER DECREASING IN [startLine..lines.lastLine] DO
newOffset: INT;
IF n+numLines >= lines.maxLines THEN LOOP; -- avoid bounds fault
newOffset ← lines[n].yOffset+dist; -- the new baseline
IF newOffset-lines[n].ascent > viewer.ch THEN LOOP; -- off the bottom of viewer
IF NOT visible AND newOffset+lines[n].descent <= viewer.ch THEN {
first fully visible line
visible ← TRUE;
lines.lastLine ← n+numLines;
lines[n].valid ← FALSE; -- force repaint at bottom of viewer
lines.lastY ← newOffset+lines[n].descent;
ClearLine[lines.lastY, viewer.ch]
};
lines[n+numLines] ← lines[n]; -- move all the info down by numLines
lines[n+numLines].yOffset ← newOffset; -- adjust the baseline
IF NOT visible THEN {
line must be partly visible at bottom of screen
lines.lastLine ← n+numLines;
lines.lastY ← newOffset+lines[n].descent;
lines[n+numLines].valid ← FALSE;
};
ENDLOOP;
};
level: INTEGER ← 0; -- in case we are doing level clipping
maxLevel: INTEGER = tdd.clipLevel;
levelClipping: BOOL = maxLevel < TEditDocument.maxClip;
last, next: Node;
NextNode: PROC RETURNS [Node] = {
returns next node to display
For future reference, TEditScrollingImpl.ScrollTEditDocument also has code to calculate next node for display to handle scrolling up in 1 line viewers.
IF node=last THEN RETURN [next]; -- 1 entry cache
last ← node;
IF levelClipping THEN [next, level] ← TextNode.ForwardClipped[node,maxLevel,level]
ELSE next ← TextNode.StepForward[node];
RETURN [next]
};
ClearLine: PROC [oldBottomY, newBottomY: INT] = {
IF oldBottomY >= newBottomY THEN RETURN;
Imager.SetColor[dc, Imager.white];
Imager.MaskRectangleI[dc, 0, viewer.ch-newBottomY, viewer.cw, newBottomY-oldBottomY];
Imager.SetColor[dc, Imager.black];
};
ContinueReason: TYPE ~ {dont, inRange, invalidLine, whitewashed};
continueReason: ContinueReason ← dont;
WhyGoOn: PROC [lineNumber: INTEGER] RETURNS [cr: ContinueReason] ~ {
SELECT TRUE FROM
(line >= lines.maxLines) => cr ← dont;
(line <= end) => cr ← inRange;
NOT (line <= lines.lastLine AND lines[line].valid AND lines[line].pos=[node, pos] AND yMatch) => cr ← invalidLine;
((NOT displayClear) AND line > 0 AND (lines[line].yOffset-lines[line].ascent < lines[line-1].yOffset+lines[line-1].descent)) => cr ← whitewashed;
ENDCASE => cr ← dont;
continueReason ← cr;
};
Main body starts here
IF node = NIL OR (line ← start) > lines.lastLine OR end < 0 THEN {
update not on screen
Cleanup[];
RETURN};
checkedVisible ← FALSE;
IF moveDownLines > 0 AND FullyVisible[] THEN {
move things down before start repainting. This accelerates scrolling down.
MoveLinesDown[0, moveDownLines, 0, moveDownDistance, FALSE];
};
Paint lines until the line we would paint is:
(1) valid,
(2) past the range we were told to paint,
(3) matches the line table offsets (i.e. no ripple going forward), and
(4) matches in the Y coord.
(5) not partially whitened by paint of earlier lines.
An exit for end of text and off end of viewer are included below.
UNTIL WhyGoOn[line] = dont DO
IF (refresh AND TEditLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN {
Either an edit is pending or a complete repaint is pending. In either case, we quit.
lines.lastLine ← MAX[line, lines.lastLine];
lines[line].pos ← [node, pos];
lines[line].valid ← FALSE;
Cleanup[];
RETURN;
};
GetStyleInfo[node];
y --baseline-- ← (
IF line=0 THEN NodeStyle.GetTopIndentI[nodeStyle]
ELSE (IF line#start THEN y ELSE lines[line-1].yOffset) -- baseline --
+ (IF pos=0 THEN nodeLeading ELSE leading) -- leading --
);
yMatch ← y=lines[line].yOffset;
true if the new line is baseline aligned with the previous bits on the screen
IF y>=viewer.ch AND line>0 THEN {eraseToEnd ← TRUE; EXIT};
if baseline would be off bottom of viewer and not the first line, don't show it.
oldBottomY ← IF line=0 THEN 0 ELSE lines[line-1].yOffset+lines[line-1].descent;
bottom of the previous line
IF displayClear OR NOT TryMoveUp[] THEN {
backgroundColor: Imager.Color ← NIL;
painter: PROC ~ {
Imager.SetXY[dc, [lineInfo.xOffset.Float[], viewer.ch-y]];
SELECT TRUE FROM
displayClear, backgroundColor#NIL => {};
ENDCASE => ClearLine[oldBottomY, y-lineInfo.ymin];
oldBottomY ← y-lineInfo.ymin;
TEditFormat.Paint[lineInfo, dc];
};
TEditFormat.FormatLine[lineInfo: lineInfo, node: node, startOffset: pos, nodeStyle: nodeStyle, lineWidth: Scaled.FromInt[viewer.cw], kind: kindOfStyle];
IF NOT displayClear THEN TryMoveDown[lineInfo.nChars, lineInfo.break];
Need to finish with style info before call this. May call GetStyleInfo for next node.
IF oldBottomY <= y-lineInfo.ymax THEN {
backgroundColor ← Imager.white;
IF NOT displayClear THEN ClearLine[oldBottomY, y-lineInfo.ymax];
oldBottomY ← y-lineInfo.ymax;
};
IF displayClear THEN painter[]
ELSE {
ymin: INT ← viewer.ch-y+lineInfo.ymin;
ymax: INT ← viewer.ch-y+lineInfo.ymax;
IF continueReason = whitewashed THEN {
ymin ← MAX[viewer.ch-oldBottomY, ymin];
backgroundColor ← NIL;
};
IF ymin < ymax THEN {
ImagerOps.DoWithBuffer[
context: dc,
action: painter,
x: 0, y: ymin,
w: viewer.cw, h: ymax-ymin,
backgroundColor: backgroundColor
];
};
};
lines[line] ← [valid: TRUE, pos: [node, pos], nChars: lineInfo.nChars, end: lineInfo.break, yOffset: y, xOffset: lineInfo.xOffset.Round[], width: lineInfo.xmax-lineInfo.xmin, ascent: MAX[0, lineInfo.ymax], descent: MAX[0, -lineInfo.ymin]];
};
pos ← pos + lines[line].nChars;
line ← line + 1;
IF continueReason = whitewashed THEN EXIT;
IF pos >= nodeSize AND lines[line-1].end#cr THEN {
nothing more to display in this node.
node ← NextNode[];
IF node = NIL THEN {eraseToEnd ← TRUE; EXIT};
nodeSize ← TextEdit.Size[node];
pos ← 0;
};
ENDLOOP;
clear former bottom lines if we run off end of text or viewer (partial line must be clipped)
IF eraseToEnd OR line>=lines.maxLines
THEN {
eraseToEnd starts false. set true if reach end of document or stop with line that would have baseline below bottom of viewer.
bottomOfNewLines: INT ← 0;
IF line#0 THEN bottomOfNewLines ← lines[line-1].yOffset + lines[line-1].descent;
lines.lastLine ← MAX[line,1]-1; -- set end mark
ClearEntries[lines.lastLine+1, lines.maxLines-1];
IF NOT displayClear AND bottomOfNewLines < lines.lastY THEN
white out old lines
ClearLine[bottomOfNewLines, lines.lastY];
lines.lastY ← bottomOfNewLines;
}
ELSE {
take this branch if got back in synch during repaint
IF y>viewer.ch OR displayClear THEN ERROR;
lines.lastLine ← MAX[lines.lastLine, MAX[line,1]-1];
lines.lastY ← MAX[y+lines[lines.lastLine].descent, lines.lastY];
};
Cleanup[];
};
};
END.