SimpleDisplays.mesa
Copyright Ó 1990, 1992 by Xerox Corporation. All rights reserved.
Norman Adams, March 4, 1990 3:38 pm PST
Spreitze, April 3, 1990 2:23 pm PDT
Last tweaked by Mike Spreitzer on April 4, 1990 12:20:06 pm PDT
Willie-s, June 12, 1992 1:26 pm PDT
Known bugs and shortcomings:
Smooth interaction of selection/pasting with tioga viewers.
Emphasis is not written to the transcript.
Add UserProfile opotions:
default terminal type,
initial lines in terminal and in transcript,
font,
transcript visibility
Blink the cursor?
Use DoWithBuffer in the paint proc?
Reduce number of repaints on size changes
Add mouse support -- Notify proc translates mouse clicks to terminal coordinates then uses a User Profile specified pattern string to generate terminal type-in.
The display is represented as a sequence of REF TEXTs. The length field of the TEXT is always ignored, and the TEXT is always padded with space characters. The paint code always strips trailing blanks before painting the line. The problem with this is that emphasis on trailing spaces does not get painted. It also seems cruftier than maintaining an accurate length for each TEXT.
When the transcript in not visible, the space between the menu rule and the terminal is whatever was left over after allocating a whole number of lines. It would be better if this slop appeared at the bottom of the terminal viewer rather than at the top. That would make the full repaint out of the adjust proc unnecessary sometimes. As it is now, the position of the characters on the screen changes by up to +-slop whenever the size of the viewer gets smaller, so we must do a repaint then. If the slop were at the bottom the characters would not move when the viewer got smaller.
DIRECTORY Ascii, CharDisplays, SimpleDisplays, IO, ViewerOps, ViewerClasses, ViewerForkers, MessageWindow, Beeps;
SimpleDisplaysImpl: CEDAR MONITOR
LOCKS NARROW[cd.otherInstanceData, SimpleDisplayState] USING cd: CharDisplay
IMPORTS CharDisplays, SimpleDisplays, IO, ViewerOps, ViewerForkers, MessageWindow, Beeps
EXPORTS SimpleDisplays
~ BEGIN
OPEN CharDisplays, SimpleDisplays;
SimpleCharDisplayClass: CharDisplayClass =
NEW [CharDisplayClassRep ¬ [
name: "Simple",
Init: SDInit,
ChangeDetails: SDChangeDetails,
DeleteChar: SDDeleteChar,
TakeChar: SDTakeChar,
CursorMove: SDCursorMove,
Line: SDLine,
ClearTo: SDClearTo,
ClearAll: SDClearAll,
SetEmph: SDSetEmph,
Emphasize: SDEmphasize,
SetFont: SDSetFont,
Beep: SDBeep,
Flush: SDFlush,
Destroyed: SDDestroyed
]];
SDInit: PROC [cd: CharDisplay, initData: REF ANY] = {
sd: SimpleDisplayState ¬ NEW [ SimpleDisplayStateRep ¬ [driver: NARROW[initData]] ];
cd.otherInstanceData ¬ sd;
InitCharArray[ sd, cd.det.lines, cd.det.columns ];
[cd.viewer, cd.fromDisplay] ¬ SimpleDisplaysViewer[sd, cd];
};
SDDeleteChar: ENTRY PROC [cd: CharDisplay] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
line: REF LineRep = sd.theLines[cd.line];
chars: REF TEXT = line.chars;
IF sd.debugChars THEN { sd.out.PutRope["<dc>"]; };
FOR i: INT IN [cd.col .. cd.det.columns-1) DO
chars[i] ¬ chars[i+1];
ENDLOOP;
chars[cd.det.columns-1] ¬ Ascii.SP;
IF line.hasSomeEmphasis THEN {
emphs: REF LineEmphsRep ¬ line.emphs;
FOR i: INT IN [cd.col .. cd.det.columns-1) DO
emphs[i] ¬ emphs[i+1];
ENDLOOP;
emphs[cd.det.columns-1] ¬ ALL[FALSE];
};
NoteChange[ cd, sd, line, delete1, cd.col ];
};
SDTakeChar: ENTRY PROC [cd: CharDisplay, char: CHAR, insert: BOOL ¬ FALSE] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
line: REF LineRep = sd.theLines[cd.line];
chars: REF TEXT ¬ line.chars;
IF sd.debugChars THEN {
IF insert THEN {sd.out.PutChar['←]; };
SELECT char FROM
IN [Ascii.SP .. Ascii.DEL) => {sd.out.PutChar[char]; };
ENDCASE => {sd.out.PutF1["<%h>", IO.char[char]]; };
};
IF insert THEN {
FOR i: INT DECREASING IN [cd.col+1 .. cd.det.columns) DO
chars[i] ¬ chars[i-1];
ENDLOOP;
IF line.hasSomeEmphasis THEN {
emphs: REF LineEmphsRep ¬ line.emphs;
FOR i: INT DECREASING IN [cd.col+1 .. cd.det.columns) DO
emphs[i] ¬ emphs[i-1];
ENDLOOP;
};
};
chars[cd.col] ¬ char;
line.emphs[cd.col] ¬ sd.emphs;
line.hasSomeEmphasis ¬ sd.hasSomeEmphasis OR line.hasSomeEmphasis;
NoteChange[ cd, sd, line, (IF insert THEN insert1 ELSE over1), cd.col ];
CursorMoveAux[cd, 0, 1, TRUE, TRUE, TRUE, FALSE];
};
SDCursorMove: ENTRY PROC [cd:CharDisplay, line,col:INT, relative:BOOL¬FALSE, doLine,doCol: BOOL ¬ TRUE] = {
ENABLE UNWIND => NULL;
CursorMoveAux[cd,line,col,relative,doLine,doCol,TRUE]
} ;
CursorMoveAux: INTERNAL PROC [cd:CharDisplay, line,col:INT, relative,doLine,doCol,report: BOOL] = {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
L:INTEGER = cd.det.lines;
C:INTEGER = cd.det.columns;
IF sd.debugCM AND report THEN {
IF relative THEN sd.out.PutRope["<cm+" ] ELSE sd.out.PutRope["<cm "];
IF doLine THEN sd.out.PutF1["%g,", IO.int[line]] ELSE sd.out.PutRope[".,"];
IF doCol THEN sd.out.PutF1["%g>", IO.int[col]] ELSE sd.out.PutRope[".>"];
};
IF relative THEN {line ¬ line + cd.line; col ¬ col + cd.col};
IF NOT doLine THEN line ¬ cd.line;
IF NOT doCol THEN col ¬ cd.col;
IF cd.det.autoMargins THEN {
dl: INT = col / C;
line ¬ line + dl;
col ¬ col - dl * C;
WHILE col < 0 DO col ¬ col + C; line ¬ line - 1 ENDLOOP;
} ELSE
col ¬ MAX[MIN[col, C-1], 0];
IF line < 0 THEN line ¬ 0;
IF cd.det.scrolls THEN
IF line >= L THEN {
Scroll[cd, 0, L, MIN[ (line-L)+1, L ] ];
line ¬ L - 1
}
ELSE
line¬line
ELSE
line ¬ line MOD L;
cd.line ¬ line;
cd.col ¬ col;
};
Scroll: INTERNAL PROC [cd: CharDisplay, start, size, scrollReq:INTEGER] ~ {
-- start is the index in the lines array of the topmost line to be moved by the scroll
-- size is the index 1 more than the last line in the region being scrolled
-- ABS[scrollReq] may be [0 .. size - start]
-- if scrollReq<0 then insert blank lines at start, remove lines from EOS (scroll down)
-- if scrollReq>0 then delete lines at start, insert blank lines at EOS (scroll up)
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
shift: INTEGER = IF scrollReq < 0 THEN (size-start)+scrollReq ELSE scrollReq;
-- this loop really is just scrolling up, for scrolling down shift is hacked to get
-- the equivalent of the scroll down by scrolling up.
upFillBase: INTEGER = size - shift;
IF sd.debugScroll THEN
sd.out.PutF["Scroll[start:%g,size:%g,req:%g]\n",IO.int[start],IO.int[size],IO.int[scrollReq] ];
IF shift IN (0..size-start) THEN {
open: INTEGER ¬ start + shift; -- this line will eventually be in position start
headLine: REF LineRep = sd.theLines[open];
WHILE open # start DO
source:INTEGER = IF (open<upFillBase) THEN (open+shift) ELSE (start+open-upFillBase);
sd.theLines[open] ¬ sd.theLines[source];
open ¬ source;
ENDLOOP;
sd.theLines[start] ¬ headLine;
};
-- Log and clear filled lines
{
fillBase:INTEGER = IF scrollReq < 0 THEN start ELSE upFillBase;
logLine:BOOLEAN = start = 0 AND scrollReq = 1 AND sd.logging;
FOR i: INTEGER IN [fillBase .. fillBase+ABS[scrollReq]) DO
line: REF LineRep = sd.theLines[i];
chars: REF TEXT = line.chars;
emphs: REF LineEmphsRep = line.emphs;
line.modified ¬ new;
line.hasSomeEmphasis ¬ FALSE;
IF logLine THEN LogTranscriptLine[sd, chars];
FOR j: INTEGER IN [0 .. chars.maxLength) DO
chars[j] ¬ Ascii.SP;
emphs[j] ¬ ALL[ FALSE ];
ENDLOOP;
ENDLOOP;
};
-- Flush changes to display
IF sd.flushMode = FlushOnScroll THEN {
ViewerForkers.ForkPaint[
viewer:   sd.aViewer,
hint:    ViewerClasses.PaintHint.client,
clearClient:  FALSE,
whatChanged: $LINES,
tryShortCuts: FALSE
];
WAIT sd.paintDone;
};
};
SDLine: ENTRY PROC [cd: CharDisplay, insert: BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugOps THEN {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
msg: ROPE ¬ IF insert THEN "<insert line>" ELSE "<delete line>";
sd.out.PutRope[msg];
};
Scroll[ cd, cd.line, cd.det.lines, IF insert THEN -1 ELSE 1 ];
};
SDClearTo: ENTRY PROC [cd: CharDisplay, where: Where] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
line: REF LineRep = sd.theLines[cd.line];
chars: REF TEXT ¬ line.chars;
IF sd.debugOps THEN {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
msg: ROPE ¬ SELECT where FROM
EndOfLine => "<clr eol>",
EndOfScreen => "<clr eos>",
ENDCASE => ERROR;
sd.out.PutRope[msg];
};
FOR j: INT IN [cd.col .. cd.det.columns) DO chars[j] ¬ Ascii.SP; ENDLOOP;
IF line.hasSomeEmphasis THEN {
emphs: REF LineEmphsRep ¬ line.emphs;
FOR j: INT IN [cd.col .. cd.det.columns) DO emphs[j] ¬ ALL[ FALSE ]; ENDLOOP;
};
IF cd.col = 0 THEN {
line.modified ¬ new;
line.hasSomeEmphasis ¬ FALSE;
} ELSE
NoteChange[ cd, sd, line, tail, cd.col ];
IF where = EndOfScreen AND cd.line+1 < cd.det.lines THEN
Scroll[ cd, cd.line+1, cd.det.lines, cd.det.lines - (cd.line+1) ]
};
SDClearAll: ENTRY PROC [cd: CharDisplay] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugOps THEN { sd.out.PutRope["<clear all>"] };
Scroll[ cd, 0, cd.det.lines, cd.det.lines ];
};
EmphNames: ARRAY Emph OF ROPE ¬ [
underline: "underline",
bold: "bold",
italic: "italic",
inverse: "inverse"];
SDSetEmph: ENTRY PROC [cd: CharDisplay, emph: Emph, on: BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugEmph THEN sd.out.PutF["<%g %g>", IO.rope[EmphNames[emph]], IO.bool[on]];
sd.emphs[emph] ¬ on;
sd.hasSomeEmphasis ¬ sd.emphs[underline] OR sd.emphs[bold] OR sd.emphs[italic] OR sd.emphs[inverse] ;
};
SDEmphasize: ENTRY PROC [cd: CharDisplay, emph: Emph, on: BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugEmph THEN sd.out.PutF["<%g %g@>", IO.rope[EmphNames[emph]], IO.bool[on]];
sd.theLines[cd.line].emphs[cd.col][emph] ¬ on;
NoteChange[ cd, sd, sd.theLines[cd.line], over1, cd.col ];
};
SDSetFont: ENTRY PROC [cd: CharDisplay, font: ROPE] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
IF sd.debugOps THEN sd.out.PutF1["<font %g>", IO.rope[font]];
};
SDBeep: ENTRY PROC [cd: CharDisplay] = {
ENABLE UNWIND => NULL;
IF Beeps.can THEN Beeps.Beep[ frequency:1000, duration:65 ] -- Hz, milliseconds
ELSE {
MessageWindow.Append["Beep", TRUE];
MessageWindow.Blink[];
MessageWindow.Clear[];
};
};
SDFlush: PROC [cd: CharDisplay] ~ {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
ViewerOps.PaintViewer[sd.aViewer, ViewerOps.PaintHint.client, FALSE, $LINES];
sd.changeCount ¬ 0;
};
SDDestroyed: ENTRY PROC [cd: CharDisplay] RETURNS [b: BOOL] = {
ENABLE UNWIND => NULL;
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
b ¬ sd.aViewer.destroyed;
};
Helpers
InitSimpleDisplayClass: PROC ~ {
RegClass[SimpleCharDisplayClass];
};
InitCharArray: PROC [sd: SimpleDisplayState, r,c: INT] ~ {
sd.theLines ¬ NEW[LinesRep[r]];
FOR i: INT IN [0..r) DO
lineEmphs: REF LineEmphsRep ¬ NEW[LineEmphsRep[c]];
lineChars: REF TEXT ¬ NEW[TEXT[c]];
FOR j: INT IN [0..c) DO
lineEmphs[j] ← ALL[FALSE]; -- defaults to false
lineChars[j] ¬ Ascii.SP;
ENDLOOP;
sd.theLines[i] ¬ NEW [LineRep ¬ [ emphs: lineEmphs, chars: lineChars, screenLine: i ]];
ENDLOOP;
};
ResizeTerminal: PUBLIC PROC [ cd: CharDisplay, sd: SimpleDisplayState, r,c: INT ] ~ {
oldLines: REF LinesRep ¬ sd.theLines;
oldr: INT ¬ cd.det.lines;
oldc: INT ¬ cd.det.columns;
oldCopyBase : INT ¬ IF oldr > r THEN oldr - r ELSE 0;
InitCharArray[ sd, r, c ];
FOR i: INT IN [0..MIN[r,oldr]) DO
oldes: REF LineEmphsRep ¬ oldLines[oldCopyBase + i].emphs;
newes: REF LineEmphsRep ¬ sd.theLines[i].emphs;
oldcs: REF TEXT ¬ oldLines[oldCopyBase + i].chars;
newcs: REF TEXT ¬ sd.theLines[i].chars;
sd.theLines[i].hasSomeEmphasis ¬ oldLines[oldCopyBase + i].hasSomeEmphasis;
FOR j: INT IN [0..MIN[c,oldc]) DO
newes[j] ¬ oldes[j];
newcs[j] ¬ oldcs[j];
ENDLOOP;
ENDLOOP;
cd.det.lines ¬ r;
cd.det.columns ¬ c;
cd.line ¬ MIN[ cd.line, r-1 ];
cd.col ¬ MIN[ cd.col, c-1 ];
};
DumpCharArray: PROC [cd: CharDisplay] ~ {
sd: SimpleDisplayState = NARROW[cd.otherInstanceData];
r: INTEGER = cd.det.lines;
c: INTEGER = cd.det.columns;
sd.out.PutF["--%g--\n", IO.int[sd.theLines.count]];
FOR i:INTEGER IN [0..r) DO
es: REF LineEmphsRep ← sd.theLines[i].emphs;
cs: REF TEXT ← sd.theLines[i].chars;
sd.out.PutF["%g(%g): ", IO.int[i], IO.int[cs.maxLength]];
FOR j: INTEGER IN [0..c) DO
ch:CHAR ← cs[j];
sd.out.PutChar[ch];
ENDLOOP;
sd.out.PutF["\n"];
ENDLOOP;
};
NoteChange: INTERNAL PROC [cd: CharDisplay, sd: SimpleDisplayState, line:REF LineRep, mod:ModificationType, col:INT] ~ {
IF line.modified = unchanged THEN {
line.modified ¬ mod;
line.tailStartColumn ¬ col
} ELSE IF line.modified = new THEN
NULL
ELSE {
line.modified ¬ tail;
line.tailStartColumn ¬ MIN[ line.tailStartColumn, col ];
};
IF sd.flushMode = FlushOnChangeCount THEN
IF sd.changeCount >= sd.changeCountLimit THEN {
sd.changeCount ¬ 0;
ViewerForkers.ForkPaint[
viewer:   sd.aViewer,
hint:    ViewerClasses.PaintHint.client,
clearClient:  FALSE,
whatChanged: $LINES,
tryShortCuts: FALSE
];
WAIT sd.paintDone;
ViewerOps.PaintViewer[sd.aViewer, ViewerOps.PaintHint.client, FALSE, $LINES]
} ELSE {
sd.changeCount ¬ sd.changeCount + 1;
}
};
InitSimpleDisplayClass[];
END.