TEditScrollingImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Michael Plass, April 14, 1986 11:41:20 am PST
Russ Atkinson (RRA) June 25, 1985 11:27:42 am PDT
Doug Wyatt, June 22, 1985 11:28:49 am PDT
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;
RRA: for some reason forking the paints causes the scrollbar feedback to be highly inaccurate when perfoming explict scrolling operations. I strongly suspect that this is because the scrollbar painting is done in th wrong place. But we will not change this soon.
BackUp:
PROC [viewer: Viewer, tdd: TEditDocumentData, pos: TextNode.Location, goal:
INTEGER]
RETURNS [newPos: TextNode.Location, lines, totalLeading, topIndent:
INTEGER] = {
pos is the current top line in the viewer; goal is the distance to back up.
algorithm works by incrementally formatting lines prior to pos until it reaches goal height.
The incremental backup procedure preserves the following invariants:
newPos is the current line start
totalLeading is the leading from the baseline of newPos to the baseline of pos
topIndent is the distance from the viewer top to baseline of newPos if newPos goes at top
topLeading is the style value for topLeading corresponding to newPos
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
back up to previous text node
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 {
back up past last char before pos
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 {
no more characters in the node. shows as blank line
lines ← 1; prev ← [textNode, where]; RETURN
};
tPos ← [textNode, where];
lines ← 0;
lineInfo ← TEditFormat.Allocate[];
DO
format lines from tPos to starting pos
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;
When reach here, have found all the line breaks from [textNode, where] to initial pos.
where holds the offset for the first one
lastBreak holds the offset for the last one
breakList holds the offsets for the previous ones
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 => {
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];
};
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 {
need to get topLeading for pos.node
NodeStyleOps.ApplyAll[style, pos.node, kind];
topLeading ← NodeStyle.GetTopLeadingI[style];
Once implement minGaps between lines, will also need topAscent for this line. Should be able to get that from the line table. (Except if continue to use this for top offset in ScrollToPosition... perhaps can just change that to back up a fixed number of lines.)
};
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 {
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 < 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 {
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
doingSecondarySelect => tdd.clipLevel,
control
AND shift => 1,
first level only
control => TEditDocument.maxClip,
all levels
shift =>
MIN[TEditDocument.maxClip, TEditInput.MaxLevelShown[tdd]+1],
move levels
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 {
make room for the new stuff
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 {
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];
};
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};
make sure there's always something shown for big documents
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
find the line containing [node, where]
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 {
move goal line a bit down from the top
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] = {
OnScreen determines whether or not the given location is visible for the given viewer. It must be called when the document is locked!
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 => {
Now we know that we have a Tioga document
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];
TryToGlitch:
PROC
RETURNS [success:
BOOL ←
FALSE] = {
glitchLines: INTEGER = MIN[MAX[lines.lastLine/2, 1], TEditProfile.scrollBottomOffset];
Need to have the MAX or will fail to scroll in 1 or 2 line viewers.
tryLines: INTEGER = MAX[lines.lastLine/2-glitchLines, 2]; -- how far to search for caret
Needs to be bigger than glitchLines since burst input may move the caret several lines.
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 {
never glitch in this case
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 => {
scroll to end of document; used in typescripts
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;
selection is visible on screen
goal ← TEditSelection.InsertionPoint[sel];
IF TextNode.Root[goal.node] # tdd.text
THEN
RETURN;
make sure that selection didn't leave
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
glitch failed; use general scroll
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];
};