-- TiogaPaintImpl.mesa; Written by S. McGregor, August 1983
-- Edited by McGregor on August 30, 1983 3:54 pm
DIRECTORY
Graphics,
NodeStyle,
Real,
TiogaDisplayTable,
TiogaDocument,
TiogaLocks,
TiogaNode,
TiogaNodeOps,
TiogaPathOps,
TiogaPaint,
ViewerClasses;
TiogaPaintImpl: CEDAR MONITOR
IMPORTS Graphics, NodeStyle, Real, TiogaDisplayTable, TiogaNodeOps, TiogaPathOps
EXPORTS TiogaPaint = BEGIN OPEN TiogaDocument, Graphics;
TiogaPaint: PUBLIC ViewerClasses.PaintProc = BEGIN
tdd: TiogaDocumentData ← NARROW[self.data];
dt: TiogaDisplayTable.DisplayTable = tdd.displayTable;
lock: TiogaLocks.LockRef;
moveDownLines, moveDownDistance: INTEGER ← 0; -- used with scrolling down
Cleanup: PROC = { --TiogaLocks.UnlockDocAndTdd[tdd]; RefreshOver[]-- };
IF tdd = NIL THEN RETURN;
BEGIN ENABLE UNWIND => Cleanup[];
interrupt: BOOL = clear OR whatChanged=NIL;
IF self.destroyed THEN RETURN; -- the viewer changed while this was forked
tdd.invisible ← FALSE; -- or else we wouldn't have been called
IF clear OR whatChanged=NIL THEN BEGIN
IF clear THEN AdjustSelStates[self];
FOR n: INTEGER IN [0..tdd.displayTable.lastIndexUsed] DO -- invalidate all lines
tdd.displayTable.objects[n].valid ← FALSE;
ENDLOOP;
END
ELSE SELECT whatChanged FROM
ENDCASE => ERROR;
RefreshViewer[self, tdd, context, clear, ~(clear OR whatChanged=NIL),
lock, moveDownLines, moveDownDistance];
Cleanup[];
END; END; -- of unwind and PaintProc
RefreshViewer: PROCEDURE [viewer: ViewerClasses.Viewer, tdd: TiogaDocumentData, dc: Graphics.Context, displayClear, refresh: BOOL, lock: TiogaLocks.LockRef, moveDownLines, moveDownDistance: INTEGER] = BEGIN
n: INTEGER ← 0;
start, end: INTEGER;
dt: TiogaDisplayTable.DisplayTable ← tdd.displayTable;
UNTIL n > dt.lastIndexUsed DO -- search for an invalid rectangle
IF dt.objects[n].valid THEN { n ← n+1; LOOP };
start ← n;
UNTIL n+1 > dt.lastIndexUsed DO
IF dt.objects[n+1].valid THEN EXIT;
n ← n+1;
ENDLOOP;
end ← n;
IF start > 0 AND dt.objects[start].startPos.path.node # dt.objects[start-1].startPos.path.node THEN {
-- need to reinitialize the start pos; may have deleted start node
dt.objects[start].startPos.where ← 0;
IF (dt.objects[start].startPos.path.node ←
TiogaPathOps.ForwardClipped[dt.objects[start-1].startPos.path,tdd.clipLevel].nx.node) = 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 TiogaLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN {
tdd.dirty ← TRUE; RETURN };
ENDLOOP;
tdd.movedIn ← tdd.movedOut ← tdd.dirty ← FALSE;
END;
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: TiogaDocumentData, dc: Graphics.Context, start, end: INTEGER,displayClear, refresh: BOOL, lock: TiogaLocks.LockRef, moveDownLines, moveDownDistance: INTEGER]
RETURNS [line: INTEGER] = BEGIN OPEN Graphics;
ENABLE UNWIND => NULL;
dt: TiogaDisplayTable.DisplayTable = tdd.displayTable;
bottomLeading, leftIndent, bodyIndent, firstIndent, lineMeasure, leading, topLeading, nodeLeading: INTEGER ← 0;
lineFormatting: NodeStyle.LineFormatting;
knowBottomLeading: BOOL ← start=0; -- otherwise need to get it
yBaseline, oldBottomY: INTEGER ← 0;
styleInit, yMatchRun: BOOLTRUE;
yMatch, pasteLine, eraseToEnd: BOOLFALSE;
nodeSize: INT;
styleInfoNode: TiogaNode.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.
pos: TiogaNode.Location ← dt.objects[start].startPos;
GetStyleInfo: PROC [node: TiogaNode.Ref] = BEGIN OPEN NodeStyle;
IF node=styleInfoNode THEN RETURN; -- already have the style info
styleInfoNode ← node;
IF ~knowBottomLeading AND pos.where=0 THEN { -- get it from previous node
ApplyAll[nodeStyle, dt.objects[line-1].startPos.path];
bottomLeading ← GetBottomLeadingI[nodeStyle] };
ApplyAll[nodeStyle, dt.objects[line].startPos.path];
lineFormatting ← GetLineFormatting[nodeStyle];
leftIndent ← GetLeftIndentI[nodeStyle];
bodyIndent ← GetBodyIndentI[nodeStyle];
firstIndent ← GetFirstIndentI[nodeStyle];
lineMeasure ← viewer.cw - GetRightIndentI[nodeStyle] - leftIndent;
leading ← GetLeadingI[nodeStyle];
topLeading ← MAX[bottomLeading, GetTopLeadingI[nodeStyle]];
use value of bottomLeading left from previous node
nodeLeading ← MAX[bottomLeading, topLeading]; -- use previous value of bottomLeading
bottomLeading ← GetBottomLeadingI[nodeStyle];
knowBottomLeading ← TRUE;
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;
ClearEntries: PROC [from, to: INTEGER] = {
FOR n: INTEGER IN [from..to] DO
dt.objects[n] ← [
startPos: TiogaNode.nullLocation,
valid: TRUE,
yBaseline: 0,
xBaseline: 0,
topExtent: 0,
bottomExtent: 0,
rightExtent: 0,
leftExtent: 0,
end: eon];
ENDLOOP };
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;
level: INTEGER ← 0; -- in case we are doing level clipping
maxLevel: INTEGER = tdd.clipLevel;
levelClipping: BOOL = maxLevel < maxClip;
NextNode: PROC = { -- computes next node in tree order
IF levelClipping THEN [pos.path, level] ←
TiogaPathOps.ForwardClipped[pos.path, maxLevel, level] -- this isn't quite right
ELSE pos.path ← TiogaPathOps.StepForwardNode[pos.path];
pos.where ← 0;
};
LeafNode: PROC RETURNS [size: INT] = { -- forces pos to a leaf node and computes node size
This is too simple as written. It only works right for the case where each branch node contains a displayable leaf. In the case of empty branch nodes, this proc should leave blank space of the appropriate leading, and make an entry into the displayTable. Until this is modified, empty branches will be invisible.
DO
IF pos.path.node=NIL THEN {size ← 0; EXIT};
WITH pos.path.node SELECT FROM
br: TiogaNode.RefBranchNode => NextNode[]; -- expand this to cover empty branches
tx: TiogaNode.RefTextNode =>
{size ← TiogaNodeOps.FetchItemClass[tx.class].length[tx]; EXIT};
bx: TiogaNode.RefBoxNode =>
{size ← TiogaNodeOps.FetchItemClass[bx.class].length[bx]; EXIT};
ls: TiogaNode.RefListNode =>
{size ← TiogaNodeOps.FetchItemClass[ls.class].length[ls]; EXIT};
bc: TiogaNode.RefBasicNode => ERROR; -- should see any of these here
ENDCASE => ERROR;
ENDLOOP;
};
yBaseline ← IF line=0 THEN NodeStyle.GetTopIndentI[nodeStyle] ELSE dt.objects[line-1].yBaseline;
IF (line ← start) > dt.lastIndexUsed OR end < 0 THEN RETURN; -- update not on screen
oldBottomY ← IF line=0 THEN 0 ELSE dt.objects[line-1].yBaseline+dt.objects[line-1].bottomExtent;
nodeSize ← LeafNode[]; -- force pos to be a displayable node
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>=dt.objects.maxRectangle OR
(line>end AND dt.objects[line].valid AND dt.objects[line].startPos=pos AND yMatch AND line<=dt.lastIndexUsed) DO
IF (refresh AND TiogaLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN {
Either an edit is pending or a complete repaint is pending. In either case, we quit.
dt.lastIndexUsed ← MAX[line, dt.lastIndexUsed];
dt.objects[line].startPos ← pos;
dt.objects[line].valid ← FALSE;
RETURN};
IF pos.where >= nodeSize THEN BEGIN
nothing more to display in this node. pos is where this line should start, so we are done with the node.
NextNode[];
nodeSize ← LeafNode[];
IF pos.path.node=NIL THEN EXIT;
GetStyleInfo[pos.path.node];
IF line=start THEN yBaseline ← IF line=0 THEN 0 ELSE dt.objects[line-1].yBaseline; -- initialize yBaseline
IF line=0 THEN yBaseline ← yBaseline + NodeStyle.GetTopIndentI[nodeStyle];
IF yBaseline>=viewer.ch AND line>0 THEN {pos.path.node ← NIL; EXIT};
if baseline would be off bottom of viewer and not the first line, don't show it.
IF pos.path.node#NIL THEN {styleInit ← TRUE; LOOP} -- reapply termination test
ELSE {eraseToEnd ← TRUE; EXIT}; -- off end of text
END
ELSE IF styleInit THEN {GetStyleInfo[pos.path.node]; styleInit ← FALSE}; -- first time for this node
yBaseline ← yBaseline + (IF pos.where=0 THEN nodeLeading ELSE leading) -- leading --;
IF yBaseline>=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 ← yBaseline=dt.objects[line].yBaseline;
true if the new line is baseline aligned with the previous bits on the screen
IF ~yMatch THEN yMatchRun ← FALSE;
IF ~displayClear THEN BEGIN
oldBottomY: INTEGERIF line=0 THEN 0 ELSE dt.objects[line-1].yBaseline+dt.objects[line-1].bottomExtent; -- bottom of previous line
thisBottomY: INTEGER9999; -- wrong! how should this work???
ClearLine[oldBottomY, thisBottomY];
END;
BEGIN-- call class display routine (make this a proc to convert to the Imager -SM)
item: TiogaNode.RefItemNode ← TiogaNodeOps.NarrowToItemNode[pos.path.node];
end: TiogaNode.Offset;
leftExtent, rightExtent, topExtent, bottomExtent: INTEGER ← 0;
break: TiogaDisplayTable.Break;
mark: Graphics.Mark ← Graphics.Save[dc];
Graphics.SetColor[dc, Graphics.black];
Graphics.Translate[dc, leftIndent, viewer.ch-yBaseline]; -- client assumes 0,0 as baseline
[end, leftExtent, rightExtent, topExtent, bottomExtent, break] ←
TiogaNodeOps.FetchItemClass[item.class].format[
item, pos.where, leftIndent, lineMeasure, yBaseline-oldBottomY, viewer.ch-yBaseline, display, dc];
pos.where ← end+1;
TiogaDisplayTable.StorePageRectangle[dt, line, [
startPos: pos,
endPos: [pos.path, end],
valid: TRUE,
end: break,
yBaseline: yBaseline,
xBaseline: leftIndent,
topExtent: topExtent,
bottomExtent: bottomExtent,
leftExtent: leftExtent,
rightExtent: rightExtent
]];
Graphics.Restore[dc, mark];
oldBottomY ← yBaseline+bottomExtent;
END;
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>=dt.lastIndexUsed 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 dt.objects[line-1].yBaseline+dt.objects[line-1].bottomExtent;
dt.lastIndexUsed ← MAX[line,1]-1; -- set end mark
ClearEntries[dt.lastIndexUsed+1, dt.objects.maxRectangle-1];
IF ~displayClear AND bottomOfNewLines < dt.lastY THEN-- white out old lines
ClearLine[bottomOfNewLines, dt.lastY];
dt.lastY ← bottomOfNewLines;
END
ELSE BEGIN -- take this branch if got back in synch during repaint
IF yBaseline>viewer.ch OR displayClear THEN ERROR;
dt.lastIndexUsed ← MAX[dt.lastIndexUsed, MAX[line,1]-1];
dt.lastY ← MAX[dt.lastY, yBaseline+dt.objects[dt.lastIndexUsed].bottomExtent];
END;
END;
nodeStyle: NodeStyle.Ref ← NodeStyle.Create[];
END.