-- TEditDisplayImpl.mesa, last edit by Paxton on May 24, 1983 9:38 am
Last Edited by: Maxwell, January 6, 1983 10:36 am
Last Edited by: Plass, March 8, 1983 1:46 pm
DIRECTORY
Carets USING [pCaretViewer, sCaretViewer, StopCaret],
ColorDisplay USING [height],
EditSpan USING [afterMoved1, afterMoved2],
Graphics USING [black, Box, Context, DrawBox, GetBounds, IsRectangular, SetCP, SetColor, white],
GraphicsOps USING [DrawBitmap, MoveDeviceRectangle],
NameSymbolTable USING [RopeFromName],
NodeStyle USING [ApplyAll, Copy, Create, FontFace, GetBodyIndentI, GetBottomLeadingI, GetFirstIndentI, GetFontFace, GetFontFamily, GetFontSizeI, GetLeadingI, GetLeftIndentI, GetLineFormatting, GetRightIndentI, GetTabStopsI, GetTopIndentI, GetTopLeadingI, Ref],
Process USING [Detach],
Real USING [RoundI],
RopeReader USING [Ref, GetRopeReader, FreeRopeReader, SetPosition, Backwards, Get, GetIndex],
TextEdit USING [Offset, RefTextNode, Size],
TextNode USING [FirstChild, ForwardClipped, Level, Location, NarrowToTextNode, pZone, Ref, Root, StepForward],
TextLooks USING [allLooks],
TEditCompile USING [maxLineAscent, minAvgLineLeading],
TEditDisplay,
TEditDocument USING [LineBreak, LineTable, LineTableRec, maxClip, Selection, SelectionRec, SpinAndLock, SelectionId, TEditDocumentData, Unlock],
TEditFormat USING [Bitmap, GetLineBitmap, FormatInfo, FormatInfoRec],
TEditImpl,
TEditLocks USING [LockDocAndTdd, LockRef, UnlockDocAndTdd, WaitingForWrite],
TEditScrolling USING [AutoScroll],
TEditSelection USING [AdjustSelStates, FixUpAfterDisplay, InsertionPoint, InvalidateLineCache, pSel, sSel, fSel, SelectionRoot, ShowSelection, TakeDownForRedisplay, TakeSelectionDown],
TEditTouchup,
VFonts USING [EstablishFont, Font],
ViewerClasses USING [PaintProc, PaintRectangle, Viewer],
ViewerOps USING [UserToScreenCoords],
ViewerSpecs USING [screenH];
TEditDisplayImpl: CEDAR MONITOR
IMPORTS Carets, ColorDisplay, EditSpan, Graphics, GraphicsOps, NameSymbolTable, NodeStyle, Process, Real, TextEdit, TextNode, RopeReader, TEditDocument, TEditFormat, TEditLocks, TEditScrolling, TEditSelection, TEditTouchup, VFonts, ViewerOps
EXPORTS TEditImpl
SHARES VFonts =
BEGIN OPEN TEditSelection, TEditDocument, TEditTouchup;
PaintTEditDocument: PUBLIC ViewerClasses.PaintProc =
BEGIN
tdd: TEditDocumentData = NARROW[self.data];
lock: TEditLocks.LockRef;
moveDownLines, moveDownDistance: INTEGER ← 0; -- used with scrolling down
redisplay, refreshing: BOOLFALSE;
Cleanup: PROC = { TEditLocks.UnlockDocAndTdd[tdd]; RefreshOver[] };
IF tdd = NIL THEN RETURN;
BEGIN ENABLE UNWIND => Cleanup[];
interrupt: BOOL = clear OR whatChanged=fullUpdate OR whatChanged=NIL;
typescript: BOOL;
IF self.destroyed THEN RETURN; -- the viewer changed while this was forked
tdd.invisible ← FALSE; -- or else we wouldn't have been called
lock ← TEditLocks.LockDocAndTdd[tdd, "PaintTEditDocument", read, interrupt, TRUE];
IF lock=NIL THEN { IF ~interrupt THEN ERROR; RETURN }; -- someone else will do the display
IF Carets.pCaretViewer=self THEN Carets.StopCaret[primary];
IF Carets.sCaretViewer=self THEN Carets.StopCaret[secondary];
IF (self.ch/TEditCompile.minAvgLineLeading)+1 > tdd.lineTable.maxLines THEN BEGIN
-- make bigger line table
oldTable: LineTable ← tdd.lineTable;
tdd.lineTable ← TextNode.pZone.NEW[LineTableRec[(self.ch/TEditCompile.minAvgLineLeading)+1] ←
[lastLine: oldTable.lastLine, lastY: oldTable.lastY, lines: NULL]];
FOR n: INTEGER IN [0..oldTable.lastLine] DO-- copy old data
tdd.lineTable[n] ← oldTable[n];
ENDLOOP;
END;
typescript ← tdd.tsInfo # NIL;
refreshing ← whatChanged=refresh;
IF clear OR refreshing OR whatChanged=fullUpdate OR whatChanged=NIL THEN BEGIN
IF clear THEN AdjustSelStates[self];
IF ~refreshing THEN
FOR n: INTEGER IN [0..tdd.lineTable.lastLine] DO -- invalidate all lines
tdd.lineTable[n].valid ← FALSE;
ENDLOOP;
redisplay ← TRUE;
END
ELSE SELECT whatChanged FROM
$TakeDownPSel => TakeSelectionDown[primary, self, context];
$ShowPSel => ShowSelection[primary, self, context];
$TakeDownSSel => TakeSelectionDown[secondary, self, context];
$ShowSSel => ShowSelection[secondary, self, context];
$TakeDownFSel => TakeSelectionDown[feedback, self, context];
$ShowFSel => ShowSelection[feedback, self, context];
ENDCASE => WITH whatChanged SELECT FROM
x: REF PreScrollDownRec => { -- move down to make room for new lines at top
IF clear THEN ERROR;
moveDownLines ← x.lines;
moveDownDistance ← x.distance;
refreshing ← redisplay ← TRUE };
x: ViewerClasses.PaintRectangle => { -- fix up after Viewer blt
IF x.flavor # blt -- or if width has changed -- THEN {
Repaint all when width changes. Could try to be smart here, but probably better not to. Would have to worry about right indent and line formatting (e.g., centered lines).
FOR n: INTEGER IN [0..tdd.lineTable.lastLine] DO -- invalidate all lines
tdd.lineTable[n].valid ← FALSE;
ENDLOOP }
ELSE { -- height has changed
n: INTEGER ← 0;
h: INTEGER ← ViewerOps.UserToScreenCoords[self, 0, self.ch].sy-x.y; -- new height
UNTIL n > tdd.lineTable.lastLine OR tdd.lineTable[n].yOffset+tdd.lineTable[n].descent >= h DO
n ← n+1; ENDLOOP;
n ← MIN[n, tdd.lineTable.lastLine]; -- make sure invalidate at least one line
UNTIL n > tdd.lineTable.lastLine DO
tdd.lineTable[n].valid ← FALSE; n ← n+1; ENDLOOP };
refreshing ← redisplay ← TRUE };
ENDCASE => ERROR;
IF redisplay THEN BEGIN
TakeDownForRedisplay[primary, self, context];
TakeDownForRedisplay[secondary, self, context];
TakeDownForRedisplay[feedback, self, context];
IF typescript AND 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: INTEGER = 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 };
IF ~tdd.lineTable[0].valid THEN { -- check start location
startLoc: TextNode.Location;
node: TextEdit.RefTextNode;
CheckLocNode: PROC [loc: TextNode.Location] RETURNS [BOOL] = {
startLoc ← loc;
RETURN [ loc.node # NIL AND TextNode.Root[loc.node] = tdd.text ] };
IF CheckLocNode[tdd.lineTable[0].pos] THEN NULL
ELSE {
IF CheckLocNode[TEditSelection.InsertionPoint[]]
OR CheckLocNode[EditSpan.afterMoved1]
OR CheckLocNode[EditSpan.afterMoved2] THEN tdd.fixStart ← TRUE
ELSE startLoc ← [TextNode.FirstChild[tdd.text],0] };
IF (node ← TextNode.NarrowToTextNode[startLoc.node]) # NIL THEN {
size: TextEdit.Offset = TextEdit.Size[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 node # NIL AND startLoc.where > 0 THEN { OPEN RopeReader;
-- move startLoc to after CR or at start of node
rdr: Ref = GetRopeReader[];
cnt: INTEGER ← 0;
foundBreak: BOOLFALSE;
SetPosition[rdr,node.rope,startLoc.where];
FOR i:INT ← 0, i+1 DO
char: CHAR;
IF i=startLoc.where THEN { foundBreak ← TRUE; EXIT }; -- out of chars
IF cnt > 200 THEN EXIT; -- give up
char ← Backwards[rdr];
IF char = 15C THEN { foundBreak ← TRUE; [] ← Get[rdr]; EXIT };
cnt ← cnt+1;
ENDLOOP;
IF foundBreak THEN startLoc.where ← GetIndex[rdr];
FreeRopeReader[rdr];
};
tdd.fixStart ← FALSE;
tdd.lineTable[0].pos ← startLoc;
IF tdd.clipLevel < maxClip THEN -- check level of startLoc
tdd.clipLevel ← MAX[tdd.clipLevel, TextNode.Level[startLoc.node]]; };
InvalidateLineCache; -- since selection impl keeps accelerators
RefreshViewer[self, tdd, context, clear, (refreshing AND ~typescript),
lock, moveDownLines, moveDownDistance];
END;
IF whatChanged=$TakeDownPSel OR
whatChanged=$TakeDownSSel OR
whatChanged=$TakeDownFSel THEN NULL
ELSE IF refreshing AND ~typescript AND TEditLocks.WaitingForWrite[lock] THEN NULL
ELSE { --fix up the selections
FixUpAfterDisplay[primary, self, context, ~tdd.readOnly];
FixUpAfterDisplay[secondary, self, context, TRUE];
FixUpAfterDisplay[feedback, self, context, FALSE] };
IF ~tdd.dirty THEN SELECT tdd.scroll FROM
no => NULL;
endofdoc => {
TRUSTED {Process.Detach[FORK AutoScroll[self, tdd.scrollGlitch, TRUE, primary]]};
tdd.scroll ← no };
endofsel => {
sel: Selection = SELECT tdd.scrollSelectionId FROM
primary => pSel,
secondary => sSel,
feedback => fSel,
ENDCASE => ERROR;
IF TEditSelection.SelectionRoot[sel]=tdd.text THEN
TRUSTED {Process.Detach[FORK AutoScroll[self, tdd.scrollGlitch, FALSE, tdd.scrollSelectionId]]} };
ENDCASE => ERROR;
Cleanup[];
END; END; -- of unwind and PaintProc
AutoScroll: PROC [
self: ViewerClasses.Viewer, tryToGlitch, toEndOfDoc: BOOL, id: SelectionId] = {
ENABLE ABORTED => GOTO Quit;
tdd: TEditDocumentData ← NARROW[self.data];
IF tdd=NIL THEN RETURN;
TEditScrolling.AutoScroll[self, tryToGlitch, toEndOfDoc, id];
[] ← TEditDocument.SpinAndLock[tdd, "TEditDisplayImplAutoScroll"];
IF ~tdd.dirty THEN tdd.scroll ← no;
TEditDocument.Unlock[tdd];
RefreshOver[];
EXITS Quit => NULL };
RefreshViewer: PROCEDURE [viewer: ViewerClasses.Viewer,
tdd: TEditDocument.TEditDocumentData, dc: Graphics.Context,
displayClear, refresh: BOOL, lock: TEditLocks.LockRef,
moveDownLines, moveDownDistance: INTEGER] = {
n: INTEGER ← 0;
start, end: INTEGER;
lines: TEditDocument.LineTable ← tdd.lineTable;
UNTIL n > lines.lastLine DO -- search for an invalid line
IF lines[n].valid THEN { n ← n+1; LOOP };
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. Monitored since has reusable bitmaps.
WhileInPosRange: ENTRY PROCEDURE [viewer: ViewerClasses.Viewer,
tdd: TEditDocument.TEditDocumentData, dc: Graphics.Context, start, end: INTEGER,
displayClear, refresh: BOOL, lock: TEditLocks.LockRef,
moveDownLines, moveDownDistance: INTEGER]
RETURNS [line: INTEGER] = BEGIN OPEN Graphics;
ENABLE UNWIND => NULL;
lines: TEditDocument.LineTable ← tdd.lineTable;
node: TextEdit.RefTextNode ← TextNode.NarrowToTextNode[lines[start].pos.node];
pos: TextEdit.Offset ← lines[start].pos.where;
leading, topLeading, bottomLeading, nodeLeading: INTEGER ← 0;
knowBottomLeading: BOOL ← start=0; -- otherwise need to get it
oldBottomY: INTEGER;
y: INTEGER ← 0;
bitmap: TEditFormat.Bitmap;
nodeSize: TextEdit.Offset ← TextEdit.Size[node];
styleInit, yMatchRun: BOOLTRUE;
yMatch, pasteLine, eraseToEnd: BOOLFALSE;
GetFont: PROC [style: NodeStyle.Ref] RETURNS [font: VFonts.Font] = INLINE BEGIN
face: NodeStyle.FontFace ← NodeStyle.GetFontFace[style];
font ← VFonts.EstablishFont[
NameSymbolTable.RopeFromName[NodeStyle.GetFontFamily[style]],
NodeStyle.GetFontSizeI[style],
face=Bold OR face=BoldItalic,
face=Italic OR face=BoldItalic
];
END;
styleInfoNode: TextNode.Ref; -- 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: TextNode.Ref] = BEGIN OPEN formatInfo;
IF node=styleInfoNode THEN RETURN; -- already have the style info
styleInfoNode ← node;
IF ~knowBottomLeading AND pos=0 THEN { -- get it from previous node
NodeStyle.ApplyAll[nodeStyle, lines[line-1].pos.node];
bottomLeading ← NodeStyle.GetBottomLeadingI[nodeStyle] };
NodeStyle.ApplyAll[nodeStyle, node];
NodeStyle.Copy[charStyle, nodeStyle];
font ← GetFont[charStyle];
lineFormatting ← NodeStyle.GetLineFormatting[nodeStyle];
leftIndent ← NodeStyle.GetLeftIndentI[nodeStyle];
bodyIndent ← NodeStyle.GetBodyIndentI[nodeStyle];
firstIndent ← NodeStyle.GetFirstIndentI[nodeStyle];
lineMeasure ← viewer.cw - NodeStyle.GetRightIndentI[nodeStyle] - leftIndent;
savedLooks ← TextLooks.allLooks; -- force init in formatter
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;
tabWidth ← MAX[NodeStyle.GetTabStopsI[nodeStyle],1]; -- to avoid divide by 0
END;
FullyVisible: PROC RETURNS [BOOL] = {
box: Graphics.Box;
width, height: INTEGER;
checkedVisible ← TRUE;
IF ~Graphics.IsRectangular[dc] THEN RETURN [FALSE];
box ← Graphics.GetBounds[dc];
width ← Real.RoundI[box.xmax-box.xmin];
IF width # viewer.cw THEN RETURN [FALSE];
height ← Real.RoundI[box.ymax-box.ymin];
IF height # viewer.ch THEN RETURN [FALSE];
RETURN [TRUE] };
checkedVisible, fullyVisible: BOOLFALSE;
found: INTEGER ← 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, diff: INTEGER;
FindBelow: PROC RETURNS [ok: BOOL] = INLINE { -- 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 ~checkedVisible THEN fullyVisible ← FullyVisible[];
IF ~fullyVisible OR ~FindBelow[] THEN RETURN [FALSE];
IF found=lines.lastLine AND lines[found].yOffset+lines[found].descent >= viewer.ch
THEN RETURN [FALSE]; -- bottom is clipped
to ← y-lines[found].ascent; -- top of new line is destination for blt
from ← lines[found].yOffset-lines[found].ascent; -- 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: TRUE,
yOffset: 0,
xOffset: 0,
width: 0,
ascent: 0,
descent: 0];
ENDLOOP };
MoveScanLines: PROC [to, from, num: INTEGER] = BEGIN
dY, sY, vY, vX: INTEGER;
w: INTEGER ← viewer.cw;
IF num=0 THEN RETURN;
[vX, vY] ← ViewerOps.UserToScreenCoords[viewer, 0, viewer.ch];
screen coords for upper left of viewer
vY ← (IF viewer.column=color THEN ColorDisplay.height ELSE ViewerSpecs.screenH)-vY;
dY ← vY+to; -- destination y coord
sY ← vY+from; -- source y coord
GraphicsOps.MoveDeviceRectangle[
self: dc,
width: w, -- full width of viewer
height: num, -- height of source is num
fromX: vX,
fromY: sY,
toX: vX,
toY: dY
];
END;
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 your're interested in before you call it.
next: TextNode.Location;
from, lead, dist: INTEGER;
IF ~checkedVisible THEN fullyVisible ← FullyVisible[];
IF ~fullyVisible OR ~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, from, dist: INTEGER, clear: BOOL] = {
to, num, bottom: INTEGER;
visible: BOOLFALSE;
IF dist <= 0 THEN RETURN; -- skip this weird case
to ← from+dist;
bottom ← MIN[viewer.ch, lines.lastY+dist]; -- 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 clear THEN ClearLine[from, to]; -- clear the gap
FOR n: INTEGER DECREASING IN [startLine..lines.lastLine] DO
newOffset: INTEGER;
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 ~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 ~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 < maxClip;
last, next: TextNode.Ref;
NextNode: PROC RETURNS [TextNode.Ref] = { -- 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: INTEGER] = BEGIN
IF oldBottomY >= newBottomY THEN RETURN;
SetColor[dc, white];
DrawBox[dc, [0, viewer.ch-newBottomY, viewer.cw, viewer.ch-oldBottomY]];
SetColor[dc, black];
END;
IF (line ← start) > lines.lastLine OR end < 0 THEN RETURN; -- update not on screen
IF moveDownLines > 0 AND (fullyVisible ← 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 valid, past the range we were told to paint, matches the line table offsets (i.e. no ripple going forward), and matches in the Y coord. An exit for end of text and off end of viewer are included below.
UNTIL line>=lines.maxLines OR
(line>end AND lines[line].valid AND lines[line].pos=[node, pos] AND yMatch
AND line<=lines.lastLine) 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;
RETURN};
IF pos >= nodeSize THEN BEGIN
nothing more to display in this node. pos is where this line should start, so we are done with the node.
DO -- stay in this loop until have a non-empty node or have reached end of document
IF (pos > 0 AND (line=0 OR lines[line-1].end=cr)) OR
(nodeSize=0 AND node#tdd.text) THEN -- show blank line
BEGIN OPEN formatInfo;
line 0 starts at end of node, or CR at end of node on previous line, or empty node other than root node.
GetStyleInfo[node];
IF line=start THEN y ← IF line=0 THEN 0 ELSE lines[line-1].yOffset; -- initialize y
y ← y + (IF line=0 THEN NodeStyle.GetTopIndentI[nodeStyle] ELSE
IF pos=0 THEN nodeLeading ELSE leading);
IF y>=viewer.ch AND line>0 THEN {node ← NIL; 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;
IF ~displayClear AND TryMoveUp[] THEN NULL
ELSE {
lineStart, lineWidth, xoffset: INTEGER;
IF pos=0 THEN {
lineStart ← leftIndent + firstIndent;
lineWidth ← lineMeasure - firstIndent }
ELSE {
lineStart ← leftIndent + bodyIndent;
lineWidth ← lineMeasure - bodyIndent };
xoffset ← SELECT lineFormatting FROM
FlushLeft, Justified => lineStart,
FlushRight => lineStart + lineWidth,
Centered => lineStart + lineWidth/2,
ENDCASE => ERROR;
IF ~displayClear THEN TryMoveDown[0, eon];
Need to finish with style info before call this.
lines[line] ← [
valid: TRUE,
pos: [node, pos],
nChars: 0,
end: eon,
yOffset: y,
xOffset: xoffset,
width: 0,
ascent: font.ascent,
descent: font.height-font.ascent
];
IF ~displayClear THEN ClearLine[oldBottomY, y+lines[line].descent] };
line ← line+1;
END;
pos ← 0;
step to the next node in the tree.
node ← TextNode.NarrowToTextNode[NextNode[]];
IF node=NIL OR (nodeSize←TextEdit.Size[node]) > 0 THEN EXIT;
ENDLOOP;
IF node#NIL THEN {styleInit ← TRUE; LOOP} -- reapply termination test
ELSE {eraseToEnd ← TRUE; EXIT}; -- off end of text
END
ELSE IF styleInit THEN {GetStyleInfo[node]; styleInit ← FALSE}; -- first time for this node
y --baseline--IF line=0 THEN NodeStyle.GetTopIndentI[formatInfo.nodeStyle]
ELSE (IF line#start THEN y ELSE lines[line-1].yOffset) -- baseline --
+ (IF pos=0 THEN nodeLeading ELSE leading) -- leading --;
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.
yMatch ← y=lines[line].yOffset;
true if the new line is baseline aligned with the previous bits on the screen
oldBottomY ← IF line=0 THEN 0 ELSE lines[line-1].yOffset+lines[line-1].descent;
bottom of the previous line
IF ~displayClear AND TryMoveUp[] THEN NULL
ELSE {
pasteLine ← ~displayClear AND yMatch AND yMatchRun
AND (y-oldBottomY)<=TEditCompile.maxLineAscent;
if pasteLine is true, we can do a single BITBLT to clear the old bits and put up the new ones.
yMatchRun starts true and goes false if yMatch is ever false, so that once we no longer are aligning on baselines during a paint we will continue to conservatively whiten the entire area we are painting into.
bitmap ← TEditFormat.GetLineBitmap[viewer, tdd, node, pos, formatInfo, pasteLine];
pasteLine is passed to be sure to whiten past the last word that fit.
IF ~displayClear THEN TryMoveDown[bitmap.chars, bitmap.break];
Need to finish with style info before call this. May call GetStyleInfo for next node.
IF pasteLine AND lines[line].xOffset=bitmap.leftIndent THEN BEGIN
extraTop: INTEGER = MAX[0,
(IF line=start THEN lines[line].ascent ELSE y-oldBottomY)-bitmap.ascent];
Graphics.SetCP[dc, bitmap.leftIndent, viewer.ch-y+bitmap.ascent+extraTop];
GraphicsOps.DrawBitmap[
self: dc,
bitmap: bitmap.bits,
w: MAX[bitmap.width, lines[line].width],
h: bitmap.ascent+extraTop+bitmap.descent,
y: bitmap.yOffset-extraTop,
yorigin: bitmap.yOffset-extraTop
];
END
ELSE BEGIN-- must clear out old line contents first
IF ~displayClear THEN ClearLine[oldBottomY, y+bitmap.descent];
Graphics.SetCP[dc, bitmap.leftIndent, viewer.ch-y+bitmap.ascent];
GraphicsOps.DrawBitmap[
self: dc,
bitmap: bitmap.bits,
w: bitmap.width,
h: bitmap.ascent+bitmap.descent,
y: bitmap.yOffset,
yorigin: bitmap.yOffset
];
IF ~yMatch THEN yMatchRun ← FALSE;
END;
lines[line] ← [
valid: TRUE,
pos: [node, pos],
nChars: bitmap.chars,
end: bitmap.break,
yOffset: y,
xOffset: bitmap.leftIndent,
width: bitmap.width,
ascent: bitmap.ascent,
descent: bitmap.descent
];
};
pos ← pos + lines[line].nChars;
line ← line + 1;
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 BEGIN
eraseToEnd starts false. set true if reach end of document or stop with line that would have baseline below bottom of viewer.
bottomOfNewLines: INTEGER
IF line=0 THEN 0 ELSE 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 ~displayClear AND bottomOfNewLines < lines.lastY THEN-- white out old lines
ClearLine[bottomOfNewLines, lines.lastY];
lines.lastY ← bottomOfNewLines;
END
ELSE BEGIN -- 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[lines.lastY, y+lines[lines.lastLine].descent];
END;
END;
formatInfo: TEditFormat.FormatInfo;
Init: ENTRY PROC = {
formatInfo ← NEW[TEditFormat.FormatInfoRec ← [
nodeStyle: NodeStyle.Create[],
tabStyle: NodeStyle.Create[],
charStyle: NodeStyle.Create[]
]];
};
Init;
END.