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, Ref], 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], TEditImpl USING [], TEditLocks USING [LockDocAndTdd, LockRef, UnlockDocAndTdd, WaitingForWrite], 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 [Offset, Size], TextNode USING [FirstChild, ForwardClipped, Level, Location, Ref, RefTextNode, 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 TEditImpl = BEGIN LineTable: TYPE ~ TEditDocument.LineTable; TEditDocumentData: TYPE ~ TEditDocument.TEditDocumentData; Viewer: TYPE ~ ViewerClasses.Viewer; maxAscent: NAT ~ 512-1; maxDescent: NAT ~ 128-1; 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.xmin AND a.xmin+a.w 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 => { 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; 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 => { 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 { 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 ] }; 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: TextEdit.Offset = 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 { 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 { 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; }; 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: INT _ MAX[INT[lines[start].yOffset] - lines[start].ascent, 0]; kindOfStyle: NodeStyleOps.OfStyle ~ IF ViewerOps.FetchProp[viewer, $StyleKind] = $Print THEN print ELSE screen; IF lines[start].ascent = maxAscent THEN oldBottomY _ 0; lines[start].valid _ FALSE; WHILE start>0 AND (lines[start-1].yOffset + lines[start-1].descent > oldBottomY OR lines[start-1].descent = maxDescent) DO start _ start-1; lines[start].valid _ FALSE; ENDLOOP; { node: TextNode.RefTextNode _ lines[start].pos.node; pos: TextEdit.Offset _ 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: BOOL _ TRUE; yMatchRun: BOOL _ TRUE; yMatch: BOOL _ FALSE; eraseToEnd: BOOL _ FALSE; lineInfo: TEditFormat.LineInfo _ TEditFormat.Allocate[]; nodeStyle: NodeStyle.Ref _ NodeStyleOps.Alloc[]; Cleanup: PROC = { TEditFormat.Release[lineInfo]; lineInfo _ NIL; NodeStyleOps.Free[nodeStyle]; nodeStyle _ NIL; }; styleInfoNode: TextNode.Ref _ NIL; -- node for which we were last called to get style info GetStyleInfo: PROC [node: TextNode.Ref] = { 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]]; nodeLeading _ MAX[bottomLeading, topLeading]; bottomLeading _ NodeStyle.GetBottomLeadingI[nodeStyle]; knowBottomLeading _ TRUE; }; checkedVisible, fullyVisible: BOOL _ FALSE; 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] = { to, from, num, dist, bottom: INT; diff: INTEGER; dirtyScanlinesInFirstTextline: NAT _ 0; FindBelow: PROC RETURNS [ok: BOOL] = { 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; from _ lines[found].yOffset-lines[found].ascent + dirtyScanlinesInFirstTextline; 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; 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] = { 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: BOOL _ FALSE; 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 { 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 { 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 { 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: TextNode.Ref; NextNode: PROC RETURNS [TextNode.Ref] = { 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].ascent = maxAscent OR lines[line-1].descent = maxDescent OR lines[line].yOffset-lines[line].ascent < lines[line-1].yOffset+lines[line-1].descent)) => cr _ whitewashed; ENDCASE => cr _ dont; continueReason _ cr; }; IF node = NIL OR (line _ start) > lines.lastLine OR end < 0 THEN { Cleanup[]; RETURN}; checkedVisible _ FALSE; IF moveDownLines > 0 AND FullyVisible[] THEN { MoveLinesDown[0, moveDownLines, 0, moveDownDistance, FALSE]; }; UNTIL WhyGoOn[line] = dont DO IF (refresh AND TEditLocks.WaitingForWrite[lock]) OR tdd.interrupt > 0 THEN { 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; IF y>=viewer.ch AND line>0 THEN {eraseToEnd _ TRUE; EXIT}; oldBottomY _ IF line=0 THEN 0 ELSE lines[line-1].yOffset+lines[line-1].descent; 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]; 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: MIN[maxAscent, MAX[0, lineInfo.ymax]], descent: MIN[maxDescent, 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 { node _ NextNode[]; IF node = NIL THEN {eraseToEnd _ TRUE; EXIT}; nodeSize _ TextEdit.Size[node]; pos _ 0; }; ENDLOOP; IF eraseToEnd OR line>=lines.maxLines THEN { bottomOfNewLines: INT _ 0; IF line # 0 THEN { descent: NAT _ lines[line-1].descent; IF descent = maxDescent THEN bottomOfNewLines _ viewer.ch ELSE bottomOfNewLines _ lines[line-1].yOffset + 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 ClearLine[bottomOfNewLines, lines.lastY]; lines.lastY _ bottomOfNewLines; } ELSE { 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. .TEditDisplayImpl.mesa Copyright c 1985 by Xerox Corporation. All rights reserved. Michael Plass, April 14, 1986 2:58:39 pm PST Doug Wyatt, May 25, 1985 10:44:08 am PDT Russ Atkinson (RRA) July 2, 1985 4:03:26 pm PDT Due to bit-bumming in TEditDocument.LineRec, the ascent and descent fields cannot be very big; if they are set at their max value, we assume that they got clipped and enlarge the bounding box accordingly. When TEditDocument changes, the size of these fields ought to be enlarged, and these constants put in that interface. move down to make room for new lines at top Mark the last line as invalid so that the lines after it will be painted as needed. fix up the selections If a typescript viewer gets smaller such that bottom line is no longer visible, automatically scroll it up to keep bottom line on screen. N.B. CheckLocNode has the side effect of setting startLoc. move startLoc to after CR or space or at start of node need to reinitialize the start pos; may have deleted start node 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. This is used to avoid redoing GetStyleInfo after have peeked at next node style as part of trying to move lines down. use value of bottomLeading left from previous node use previous value of bottomLeading 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. 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. top of clean part of new line is destination for blt uppermost source scan line Set the last line invalid to force repaint to fill in gap at bottom of viewer. 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. Repaint the top line if it got dirtied by descenders. first fully visible line line must be partly visible at bottom of screen 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. Main body starts here update not on screen move things down before start repainting. This accelerates scrolling down. 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. Either an edit is pending or a complete repaint is pending. In either case, we quit. true if the new line is baseline aligned with the previous bits on the screen if baseline would be off bottom of viewer and not the first line, don't show it. bottom of the previous line Need to finish with style info before call this. May call GetStyleInfo for next node. nothing more to display in this node. clear former bottom lines if we run off end of text or viewer (partial line must be clipped) eraseToEnd starts false. set true if reach end of document or stop with line that would have baseline below bottom of viewer. white out old lines take this branch if got back in synch during repaint ʪ˜codešœ™Kšœ Ïmœ1™šžœž˜ Kš žœžœžœ žœžœ˜$šžœ˜šœ?˜?Kšœžœ˜—Kšœ˜——Kšœ ˜ Kšœ˜—Kšžœžœ˜—K˜—K˜š œžœ žœžœ˜\šœ žœ&˜5Kšœ<žœ˜C—š žœžœžœžœ¡˜K˜=Kšžœ žœ˜1Kšžœžœžœ˜AKšœ-¡*˜WKšœ5žœžœ5˜qK˜—šžœ ž˜Kšœ2˜2šžœ˜ šžœžœž˜Kšœ žœžœ žœ(˜Išžœ˜ Kšœ™K˜HKšœ;žœ˜AKšœ:žœ˜AK˜————š žœžœ žœžœ ž˜,Kšœžœ˜ šœ ˜ Kšœ#žœ ˜2—˜ šœžœž˜@K˜K˜!K˜ Kšžœžœ˜—šžœ,ž˜2Kšœ#žœ˜A—K˜—Kšžœžœ˜—Kšœ˜K˜—š œžœ+˜Cšžœ9žœ˜AKšœ‰™‰Kšœžœ;˜EKšœžœ˜šžœžœ"ž˜EKšœ˜Kšžœ˜—Kšœ!žœ˜CKšœžœ˜K˜—Kšœ˜K˜—š œžœ+˜CKšœžœ˜'š  œžœžœžœ˜>K˜Kšžœžœžœ&˜AK˜—Kšœ:™:šžœžœ$žœ˜0šžœ*žœ0žœ$žœ#˜ªKšžœž˜Kšžœ/˜3—K˜—šžœžœžœ˜Kšœ5˜5šžœžœžœ žœ˜)Kšœžœ˜Kšœžœ˜Kšœžœžœ˜5K˜—K˜—š žœžœžœžœžœ˜GKšœ6™6K˜1Kšœ žœžœžœ˜0Kšœ žœ˜Kšœ žœ˜!Kšœ@˜@š žœžœž œžœž˜6Kšœžœ˜'Kšžœ žœžœ˜.Kšžœ žœžœ˜&Kšžœžœžœ˜&Kšžœ˜—Kšœžœ˜+K˜K˜—Kšœžœ˜K˜ Kšžœ'žœ¡˜HKšœžœ0˜CKšœ˜K˜—š  œžœ)žœ$˜aKšžœžœžœ˜š žœžœžœžœ žœž˜-šœ˜K˜=Kšžœžœ žœ˜&K˜Kšœ˜—Kšžœžœ˜—Kšžœ žœ˜K˜K˜—š  œžœUžœ=žœ˜±Kšœžœ˜Kšœ žœ˜K˜/Kšœ"˜"šžœžœ¡˜9šžœ˜Kšžœ˜ šžœ˜K˜ šžœž˜Kšžœžœžœ˜K˜Kšžœ˜—K˜šžœ žœ1žœ˜GKšœ?™?K˜šžœ_žœž˜iKšœ¡ œ˜1—K˜—˜>K˜0—Kšœ&¡˜Dšžœ žœ#žœžœ˜MKšœ žœ˜Kšžœ˜K˜—K˜——Kšžœ˜—Kšœ)žœ˜/K˜K˜—šœÞ™ÞK™—š œžœJžœžœ=žœžœžœ˜àK˜/Kšœ žœžœžœ1˜JKšœ$žœ2žœžœ˜oKšžœ!žœ˜7Kšœžœ˜šžœ žœ?žœ&ž˜zK˜Kšœžœ˜Kšžœ˜—˜K˜3K˜.Kšœ žœ˜Kšœ žœ˜Kšœžœ˜Kšœ žœ˜Kšœžœ¡˜BKšœžœ˜ Kšœ žœ˜$Kšœ žœžœ˜Kšœ žœžœ˜Kšœžœžœ˜Kšœ žœžœ˜K˜8K˜0š œžœ˜Kšœ*žœ˜.Kšœ*žœ˜.Kšœ˜—Kšœžœ¡7˜ZKšœu™uš  œžœ˜+Kšžœžœžœ¡˜CK˜š žœžœžœžœ¡˜FK˜FK˜7K˜—K˜4K˜+šœ žœ5˜EKšœ2™2—šœžœ˜-Kšœ#™#—K˜7Kšœžœ˜K˜—Kšœžœžœ˜+š  œžœžœžœ˜%šžœžœžœ˜KšœT˜TKšœžœ˜Kšœ˜—Kšžœ˜K˜—Kšœžœ¡˜#š  œžœžœžœ˜&Kšœ¨™¨Kšœžœ˜!Kšœžœ˜Kšœžœ˜'š  œžœžœžœ˜&Kšœ&™&Kšœ’™’šžœžœžœž˜+šžœžœžœ˜4Kšœ ˜ Kšžœ1˜7K˜—Kšžœ˜—Kšžœžœ˜K˜—Kšžœžœžœžœ žœžœžœ˜=Kšžœžœ7˜RKšžœžœžœ¡˜)šžœ žœ˜˜šžœ˜K˜XK˜—Kšœžœžœ˜&——šœ;˜;Kšœ4™4—šœP˜PKšœ™—šžœžœ¡˜*Kšœ¡+˜;K˜K˜—Kšœ¡˜'Kš žœ žœžœžœ¡˜:Kšœ žœ˜%Kšœ¡˜2Kšžœ žœžœžœ˜ Kšœ¡˜9Kšœ¡˜Kšœ¡˜)Kšœ¡˜#K˜'K˜ K˜—K˜—š  œžœ žœ#˜EKšœÿ™ÿKšœk™kK˜Kšœžœ˜Kš žœžœžœžœžœžœ˜;K˜.Kšžœžœžœ¡˜;Kšœžœ žœžœ˜?Kšžœžœžœ˜&šžœ žœ¡-˜?K˜,K˜—Kšžœ˜Kšœ#¡˜=Kšœ#žœ˜)K˜—š   œžœžœžœ žœ˜TKšœžœ˜Kšœžœ˜ Kšœžœ˜ Kšœ žœžœ˜Kšœžœ˜#Kšžœ žœžœ¡˜1K˜Kšœ žœ¡˜BKšœ¡"˜3Kšžœ žœžœ¡&˜?K˜$šžœžœ!žœ7žœ5˜¤šžœ˜Kšœ5™5Kšœžœ˜K˜4K˜—Kšžœ¡˜*—š žœžœž œžœž˜;Kšœ žœ˜Kšžœžœžœ¡˜@Kšœ#¡˜6Kšžœ'žœžœ¡˜Ošžœžœ žœ)žœ˜AKšœ™Kšœ žœ˜K˜Kšœžœ¡$˜