1 /*
2    This file is part of Konsole, an X terminal.
3 
4    Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com>
5    Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
6 
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11 
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16 
17    You should have received a copy of the GNU General Public License
18    along with this program; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20    02110-1301  USA.
21    */
22 
23 // Own
24 #include "Screen.h"
25 
26 // Standard
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <unistd.h>
30 #include <assert.h>
31 #include <string.h>
32 #include <ctype.h>
33 
34 // Qt
35 #include <QTextStream>
36 #include <QDate>
37 
38 // KDE
39 //#include <kdebug.h>
40 
41 // Konsole
42 #include "konsole_wcwidth.h"
43 #include "TerminalCharacterDecoder.h"
44 
45 using namespace Konsole;
46 
47 //FIXME: this is emulation specific. Use false for xterm, true for ANSI.
48 //FIXME: see if we can get this from terminfo.
49 #define BS_CLEARS false
50 
51 //Macro to convert x,y position on screen to position within an image.
52 //
53 //Originally the image was stored as one large contiguous block of
54 //memory, so a position within the image could be represented as an
55 //offset from the beginning of the block.  For efficiency reasons this
56 //is no longer the case.
57 //Many internal parts of this class still use this representation for parameters and so on,
58 //notably moveImage() and clearImage().
59 //This macro converts from an X,Y position into an image offset.
60 #ifndef loc
61 #define loc(X,Y) ((Y)*columns+(X))
62 #endif
63 
64 
65 Character Screen::defaultChar = Character(' ',
66         CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR),
67         CharacterColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR),
68         DEFAULT_RENDITION);
69 
70 //#define REVERSE_WRAPPED_LINES  // for wrapped line debug
71 
Screen(int l,int c)72     Screen::Screen(int l, int c)
73 : lines(l),
74     columns(c),
75     screenLines(new ImageLine[lines+1] ),
76     _scrolledLines(0),
77     _droppedLines(0),
78     history(new HistoryScrollNone()),
79     cuX(0), cuY(0),
80     currentRendition(0),
81     _topMargin(0), _bottomMargin(0),
82     selBegin(0), selTopLeft(0), selBottomRight(0),
83     blockSelectionMode(false),
84     effectiveForeground(CharacterColor()), effectiveBackground(CharacterColor()), effectiveRendition(0),
85     lastPos(-1)
86 {
87     lineProperties.resize(lines+1);
88     for (int i=0;i<lines+1;i++)
89         lineProperties[i]=LINE_DEFAULT;
90 
91     initTabStops();
92     clearSelection();
93     reset();
94 }
95 
96 /*! Destructor
97 */
98 
~Screen()99 Screen::~Screen()
100 {
101     delete[] screenLines;
102     delete history;
103 }
104 
cursorUp(int n)105 void Screen::cursorUp(int n)
106     //=CUU
107 {
108     if (n == 0) n = 1; // Default
109     int stop = cuY < _topMargin ? 0 : _topMargin;
110     cuX = std::min(columns-1,cuX); // nowrap!
111     cuY = std::max(stop,cuY-n);
112 }
113 
cursorDown(int n)114 void Screen::cursorDown(int n)
115     //=CUD
116 {
117     if (n == 0) n = 1; // Default
118     int stop = cuY > _bottomMargin ? lines-1 : _bottomMargin;
119     cuX = std::min(columns-1,cuX); // nowrap!
120     cuY = std::min(stop,cuY+n);
121 }
122 
cursorLeft(int n)123 void Screen::cursorLeft(int n)
124     //=CUB
125 {
126     if (n == 0) n = 1; // Default
127     cuX = std::min(columns-1,cuX); // nowrap!
128     cuX = std::max(0,cuX-n);
129 }
130 
cursorRight(int n)131 void Screen::cursorRight(int n)
132     //=CUF
133 {
134     if (n == 0) n = 1; // Default
135     cuX = std::min(columns-1,cuX+n);
136 }
137 
setMargins(int top,int bot)138 void Screen::setMargins(int top, int bot)
139     //=STBM
140 {
141     if (top == 0) top = 1;      // Default
142     if (bot == 0) bot = lines;  // Default
143     top = top - 1;              // Adjust to internal lineno
144     bot = bot - 1;              // Adjust to internal lineno
145     if ( !( 0 <= top && top < bot && bot < lines ) )
146     { //Debug()<<" setRegion("<<top<<","<<bot<<") : bad range.";
147         return;                   // Default error action: ignore
148     }
149     _topMargin = top;
150     _bottomMargin = bot;
151     cuX = 0;
152     cuY = getMode(MODE_Origin) ? top : 0;
153 
154 }
155 
topMargin() const156 int Screen::topMargin() const
157 {
158     return _topMargin;
159 }
bottomMargin() const160 int Screen::bottomMargin() const
161 {
162     return _bottomMargin;
163 }
164 
index()165 void Screen::index()
166     //=IND
167 {
168     if (cuY == _bottomMargin)
169         scrollUp(1);
170     else if (cuY < lines-1)
171         cuY += 1;
172 }
173 
reverseIndex()174 void Screen::reverseIndex()
175     //=RI
176 {
177     if (cuY == _topMargin)
178         scrollDown(_topMargin,1);
179     else if (cuY > 0)
180         cuY -= 1;
181 }
182 
nextLine()183 void Screen::nextLine()
184     //=NEL
185 {
186     toStartOfLine(); index();
187 }
188 
eraseChars(int n)189 void Screen::eraseChars(int n)
190 {
191     if (n == 0) n = 1; // Default
192     int p = std::max(0,std::min(cuX+n-1,columns-1));
193     clearImage(loc(cuX,cuY),loc(p,cuY),' ');
194 }
195 
deleteChars(int n)196 void Screen::deleteChars(int n)
197 {
198     Q_ASSERT( n >= 0 );
199 
200     // always delete at least one char
201     if (n == 0)
202         n = 1;
203 
204     // if cursor is beyond the end of the line there is nothing to do
205     if ( cuX >= screenLines[cuY].count() )
206         return;
207 
208     if ( cuX+n > screenLines[cuY].count() )
209         n = screenLines[cuY].count() - cuX;
210 
211     Q_ASSERT( n >= 0 );
212     Q_ASSERT( cuX+n <= screenLines[cuY].count() );
213 
214     screenLines[cuY].remove(cuX,n);
215 }
216 
insertChars(int n)217 void Screen::insertChars(int n)
218 {
219     if (n == 0) n = 1; // Default
220 
221     if ( screenLines[cuY].size() < cuX )
222         screenLines[cuY].resize(cuX);
223 
224     screenLines[cuY].insert(cuX,n,' ');
225 
226     if ( screenLines[cuY].count() > columns )
227         screenLines[cuY].resize(columns);
228 }
229 
deleteLines(int n)230 void Screen::deleteLines(int n)
231 {
232     if (n == 0) n = 1; // Default
233     scrollUp(cuY,n);
234 }
235 
insertLines(int n)236 void Screen::insertLines(int n)
237 {
238     if (n == 0) n = 1; // Default
239     scrollDown(cuY,n);
240 }
241 
setMode(int m)242 void Screen::setMode(int m)
243 {
244     currentModes[m] = true;
245     switch(m)
246     {
247         case MODE_Origin : cuX = 0; cuY = _topMargin; break; //FIXME: home
248     }
249 }
250 
resetMode(int m)251 void Screen::resetMode(int m)
252 {
253     currentModes[m] = false;
254     switch(m)
255     {
256         case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home
257     }
258 }
259 
saveMode(int m)260 void Screen::saveMode(int m)
261 {
262     savedModes[m] = currentModes[m];
263 }
264 
restoreMode(int m)265 void Screen::restoreMode(int m)
266 {
267     currentModes[m] = savedModes[m];
268 }
269 
getMode(int m) const270 bool Screen::getMode(int m) const
271 {
272     return currentModes[m];
273 }
274 
saveCursor()275 void Screen::saveCursor()
276 {
277     savedState.cursorColumn = cuX;
278     savedState.cursorLine  = cuY;
279     savedState.rendition = currentRendition;
280     savedState.foreground = currentForeground;
281     savedState.background = currentBackground;
282 }
283 
restoreCursor()284 void Screen::restoreCursor()
285 {
286     cuX     = std::min(savedState.cursorColumn,columns-1);
287     cuY     = std::min(savedState.cursorLine,lines-1);
288     currentRendition   = savedState.rendition;
289     currentForeground   = savedState.foreground;
290     currentBackground   = savedState.background;
291     updateEffectiveRendition();
292 }
293 
resizeImage(int new_lines,int new_columns)294 void Screen::resizeImage(int new_lines, int new_columns)
295 {
296     if ((new_lines==lines) && (new_columns==columns)) return;
297 
298     if (cuY > new_lines-1)
299     { // attempt to preserve focus and lines
300         _bottomMargin = lines-1; //FIXME: margin lost
301         for (int i = 0; i < cuY-(new_lines-1); i++)
302         {
303             addHistLine(); scrollUp(0,1);
304         }
305     }
306 
307     // create new screen lines and copy from old to new
308 
309     ImageLine* newScreenLines = new ImageLine[new_lines+1];
310     for (int i=0; i < std::min(lines,new_lines+1) ;i++)
311         newScreenLines[i]=screenLines[i];
312     for (int i=lines;(i > 0) && (i<new_lines+1);i++)
313         newScreenLines[i].resize( new_columns );
314 
315     lineProperties.resize(new_lines+1);
316     for (int i=lines;(i > 0) && (i<new_lines+1);i++)
317         lineProperties[i] = LINE_DEFAULT;
318 
319     clearSelection();
320 
321     delete[] screenLines;
322     screenLines = newScreenLines;
323 
324     lines = new_lines;
325     columns = new_columns;
326     cuX = std::min(cuX,columns-1);
327     cuY = std::min(cuY,lines-1);
328 
329     // FIXME: try to keep values, evtl.
330     _topMargin=0;
331     _bottomMargin=lines-1;
332     initTabStops();
333     clearSelection();
334 }
335 
setDefaultMargins()336 void Screen::setDefaultMargins()
337 {
338     _topMargin = 0;
339     _bottomMargin = lines-1;
340 }
341 
342 
343 /*
344    Clarifying rendition here and in the display.
345 
346    currently, the display's color table is
347    0       1       2 .. 9    10 .. 17
348    dft_fg, dft_bg, dim 0..7, intensive 0..7
349 
350    currentForeground, currentBackground contain values 0..8;
351    - 0    = default color
352    - 1..8 = ansi specified color
353 
354    re_fg, re_bg contain values 0..17
355    due to the TerminalDisplay's color table
356 
357    rendition attributes are
358 
359    attr           widget screen
360    -------------- ------ ------
361    RE_UNDERLINE     XX     XX    affects foreground only
362    RE_BLINK         XX     XX    affects foreground only
363    RE_BOLD          XX     XX    affects foreground only
364    RE_REVERSE       --     XX
365    RE_TRANSPARENT   XX     --    affects background only
366    RE_INTENSIVE     XX     --    affects foreground only
367 
368    Note that RE_BOLD is used in both widget
369    and screen rendition. Since xterm/vt102
370    is to poor to distinguish between bold
371    (which is a font attribute) and intensive
372    (which is a color attribute), we translate
373    this and RE_BOLD in falls eventually appart
374    into RE_BOLD and RE_INTENSIVE.
375    */
376 
reverseRendition(Character & p) const377 void Screen::reverseRendition(Character& p) const
378 {
379     CharacterColor f = p.foregroundColor;
380     CharacterColor b = p.backgroundColor;
381 
382     p.foregroundColor = b;
383     p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT;
384 }
385 
updateEffectiveRendition()386 void Screen::updateEffectiveRendition()
387 {
388     effectiveRendition = currentRendition;
389     if (currentRendition & RE_REVERSE)
390     {
391         effectiveForeground = currentBackground;
392         effectiveBackground = currentForeground;
393     }
394     else
395     {
396         effectiveForeground = currentForeground;
397         effectiveBackground = currentBackground;
398     }
399 
400     if (currentRendition & RE_BOLD)
401         effectiveForeground.toggleIntensive();
402 }
403 
copyFromHistory(Character * dest,int startLine,int count) const404 void Screen::copyFromHistory(Character* dest, int startLine, int count) const
405 {
406     // cppcheck-suppress assertWithSideEffect
407     Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= history->getLines() );
408 
409     for (int line = startLine; line < startLine + count; line++)
410     {
411         const int length = std::min(columns,history->getLineLen(line));
412         const int destLineOffset  = (line-startLine)*columns;
413 
414         history->getCells(line,0,length,dest + destLineOffset);
415 
416         for (int column = length; column < columns; column++)
417             dest[destLineOffset+column] = defaultChar;
418 
419         // invert selected text
420         if (selBegin !=-1)
421         {
422             for (int column = 0; column < columns; column++)
423             {
424                 if (isSelected(column,line))
425                 {
426                     reverseRendition(dest[destLineOffset + column]);
427                 }
428             }
429         }
430     }
431 }
432 
copyFromScreen(Character * dest,int startLine,int count) const433 void Screen::copyFromScreen(Character* dest , int startLine , int count) const
434 {
435     Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= lines );
436 
437     for (int line = startLine; line < (startLine+count) ; line++)
438     {
439         int srcLineStartIndex  = line*columns;
440         int destLineStartIndex = (line-startLine)*columns;
441 
442         for (int column = 0; column < columns; column++)
443         {
444             int srcIndex = srcLineStartIndex + column;
445             int destIndex = destLineStartIndex + column;
446 
447             dest[destIndex] = screenLines[srcIndex/columns].value(srcIndex%columns,defaultChar);
448 
449             // invert selected text
450             if (selBegin != -1 && isSelected(column,line + history->getLines()))
451                 reverseRendition(dest[destIndex]);
452         }
453 
454     }
455 }
456 
getImage(Character * dest,int size,int startLine,int endLine) const457 void Screen::getImage( Character* dest, int size, int startLine, int endLine ) const
458 {
459     Q_ASSERT( startLine >= 0 );
460     // cppcheck-suppress assertWithSideEffect
461     Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines );
462 
463     const int mergedLines = endLine - startLine + 1;
464 
465     Q_ASSERT( size >= mergedLines * columns );
466     Q_UNUSED( size )
467 
468     const int linesInHistoryBuffer = qBound(0,history->getLines()-startLine,mergedLines);
469     const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer;
470 
471     // copy lines from history buffer
472     if (linesInHistoryBuffer > 0)
473         copyFromHistory(dest,startLine,linesInHistoryBuffer);
474 
475     // copy lines from screen buffer
476     if (linesInScreenBuffer > 0)
477         copyFromScreen(dest + linesInHistoryBuffer*columns,
478                 startLine + linesInHistoryBuffer - history->getLines(),
479                 linesInScreenBuffer);
480 
481     // invert display when in screen mode
482     if (getMode(MODE_Screen))
483     {
484         for (int i = 0; i < mergedLines*columns; i++)
485             reverseRendition(dest[i]); // for reverse display
486     }
487 
488     // mark the character at the current cursor position
489     int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer);
490     if(getMode(MODE_Cursor) && cursorIndex < columns*mergedLines)
491         dest[cursorIndex].rendition |= RE_CURSOR;
492 }
493 
getLineProperties(int startLine,int endLine) const494 QVector<LineProperty> Screen::getLineProperties( int startLine , int endLine ) const
495 {
496     Q_ASSERT( startLine >= 0 );
497     // cppcheck-suppress assertWithSideEffect
498     Q_ASSERT( endLine >= startLine && endLine < history->getLines() + lines );
499 
500     const int mergedLines = endLine-startLine+1;
501     const int linesInHistory = qBound(0,history->getLines()-startLine,mergedLines);
502     const int linesInScreen = mergedLines - linesInHistory;
503 
504     QVector<LineProperty> result(mergedLines);
505     int index = 0;
506 
507     // copy properties for lines in history
508     for (int line = startLine; line < startLine + linesInHistory; line++)
509     {
510         //TODO Support for line properties other than wrapped lines
511         if (history->isWrappedLine(line))
512         {
513             result[index] = (LineProperty)(result[index] | LINE_WRAPPED);
514         }
515         index++;
516     }
517 
518     // copy properties for lines in screen buffer
519     const int firstScreenLine = startLine + linesInHistory - history->getLines();
520     for (int line = firstScreenLine; line < firstScreenLine+linesInScreen; line++)
521     {
522         result[index]=lineProperties[line];
523         index++;
524     }
525 
526     return result;
527 }
528 
reset(bool clearScreen)529 void Screen::reset(bool clearScreen)
530 {
531     setMode(MODE_Wrap  ); saveMode(MODE_Wrap  );  // wrap at end of margin
532     resetMode(MODE_Origin); saveMode(MODE_Origin);  // position refers to [1,1]
533     resetMode(MODE_Insert); saveMode(MODE_Insert);  // overstroke
534     setMode(MODE_Cursor);                         // cursor visible
535     resetMode(MODE_Screen);                         // screen not inverse
536     resetMode(MODE_NewLine);
537 
538     _topMargin=0;
539     _bottomMargin=lines-1;
540 
541     setDefaultRendition();
542     saveCursor();
543 
544     if ( clearScreen )
545         clear();
546 }
547 
clear()548 void Screen::clear()
549 {
550     clearEntireScreen();
551     home();
552 }
553 
backspace()554 void Screen::backspace()
555 {
556     cuX = std::min(columns-1,cuX); // nowrap!
557     cuX = std::max(0,cuX-1);
558 
559     if (screenLines[cuY].size() < cuX+1)
560         screenLines[cuY].resize(cuX+1);
561 
562     if (BS_CLEARS)
563         screenLines[cuY][cuX].character = ' ';
564 }
565 
tab(int n)566 void Screen::tab(int n)
567 {
568     // note that TAB is a format effector (does not write ' ');
569     if (n == 0) n = 1;
570     while((n > 0) && (cuX < columns-1))
571     {
572         cursorRight(1);
573         while((cuX < columns-1) && !tabStops[cuX])
574             cursorRight(1);
575         n--;
576     }
577 }
578 
backtab(int n)579 void Screen::backtab(int n)
580 {
581     // note that TAB is a format effector (does not write ' ');
582     if (n == 0) n = 1;
583     while((n > 0) && (cuX > 0))
584     {
585         cursorLeft(1); while((cuX > 0) && !tabStops[cuX]) cursorLeft(1);
586         n--;
587     }
588 }
589 
clearTabStops()590 void Screen::clearTabStops()
591 {
592     for (int i = 0; i < columns; i++) tabStops[i] = false;
593 }
594 
changeTabStop(bool set)595 void Screen::changeTabStop(bool set)
596 {
597     if (cuX >= columns) return;
598     tabStops[cuX] = set;
599 }
600 
initTabStops()601 void Screen::initTabStops()
602 {
603     tabStops.resize(columns);
604 
605     // Arrg! The 1st tabstop has to be one longer than the other.
606     // i.e. the kids start counting from 0 instead of 1.
607     // Other programs might behave correctly. Be aware.
608     for (int i = 0; i < columns; i++)
609         tabStops[i] = (i%8 == 0 && i != 0);
610 }
611 
newLine()612 void Screen::newLine()
613 {
614     if (getMode(MODE_NewLine))
615         toStartOfLine();
616     index();
617 }
618 
checkSelection(int from,int to)619 void Screen::checkSelection(int from, int to)
620 {
621     if (selBegin == -1)
622         return;
623     int scr_TL = loc(0, history->getLines());
624     //Clear entire selection if it overlaps region [from, to]
625     if ( (selBottomRight >= (from+scr_TL)) && (selTopLeft <= (to+scr_TL)) )
626         clearSelection();
627 }
628 
displayCharacter(unsigned short c)629 void Screen::displayCharacter(unsigned short c)
630 {
631     // Note that VT100 does wrapping BEFORE putting the character.
632     // This has impact on the assumption of valid cursor positions.
633     // We indicate the fact that a newline has to be triggered by
634     // putting the cursor one right to the last column of the screen.
635 
636     int w = konsole_wcwidth(c);
637     if (w <= 0)
638         return;
639 
640     if (cuX+w > columns) {
641         if (getMode(MODE_Wrap)) {
642             lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED);
643             nextLine();
644         }
645         else
646             cuX = columns-w;
647     }
648 
649     // ensure current line vector has enough elements
650     int size = screenLines[cuY].size();
651     if (size < cuX+w)
652     {
653         screenLines[cuY].resize(cuX+w);
654     }
655 
656     if (getMode(MODE_Insert)) insertChars(w);
657 
658     lastPos = loc(cuX,cuY);
659 
660     // check if selection is still valid.
661     checkSelection(lastPos, lastPos);
662 
663     Character& currentChar = screenLines[cuY][cuX];
664 
665     currentChar.character = c;
666     currentChar.foregroundColor = effectiveForeground;
667     currentChar.backgroundColor = effectiveBackground;
668     currentChar.rendition = effectiveRendition;
669 
670     int i = 0;
671     int newCursorX = cuX + w--;
672     while(w)
673     {
674         i++;
675 
676         if ( screenLines[cuY].size() < cuX + i + 1 )
677             screenLines[cuY].resize(cuX+i+1);
678 
679         Character& ch = screenLines[cuY][cuX + i];
680         ch.character = 0;
681         ch.foregroundColor = effectiveForeground;
682         ch.backgroundColor = effectiveBackground;
683         ch.rendition = effectiveRendition;
684 
685         w--;
686     }
687     cuX = newCursorX;
688 }
689 
compose(const QString &)690 void Screen::compose(const QString& /*compose*/)
691 {
692     Q_ASSERT( 0 /*Not implemented yet*/ );
693 
694     /*  if (lastPos == -1)
695         return;
696 
697         QChar c(image[lastPos].character);
698         compose.prepend(c);
699     //compose.compose(); ### FIXME!
700     image[lastPos].character = compose[0].unicode();*/
701 }
702 
scrolledLines() const703 int Screen::scrolledLines() const
704 {
705     return _scrolledLines;
706 }
droppedLines() const707 int Screen::droppedLines() const
708 {
709     return _droppedLines;
710 }
resetDroppedLines()711 void Screen::resetDroppedLines()
712 {
713     _droppedLines = 0;
714 }
resetScrolledLines()715 void Screen::resetScrolledLines()
716 {
717     _scrolledLines = 0;
718 }
719 
scrollUp(int n)720 void Screen::scrollUp(int n)
721 {
722     if (n == 0) n = 1; // Default
723     if (_topMargin == 0) addHistLine(); // history.history
724     scrollUp(_topMargin, n);
725 }
726 
lastScrolledRegion() const727 QRect Screen::lastScrolledRegion() const
728 {
729     return _lastScrolledRegion;
730 }
731 
scrollUp(int from,int n)732 void Screen::scrollUp(int from, int n)
733 {
734     if (n <= 0 || from + n > _bottomMargin) return;
735 
736     _scrolledLines -= n;
737     _lastScrolledRegion = QRect(0,_topMargin,columns-1,(_bottomMargin-_topMargin));
738 
739     //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
740     moveImage(loc(0,from),loc(0,from+n),loc(columns-1,_bottomMargin));
741     clearImage(loc(0,_bottomMargin-n+1),loc(columns-1,_bottomMargin),' ');
742 }
743 
scrollDown(int n)744 void Screen::scrollDown(int n)
745 {
746     if (n == 0) n = 1; // Default
747     scrollDown(_topMargin, n);
748 }
749 
scrollDown(int from,int n)750 void Screen::scrollDown(int from, int n)
751 {
752     _scrolledLines += n;
753 
754     //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds.
755     if (n <= 0)
756         return;
757     if (from > _bottomMargin)
758         return;
759     if (from + n > _bottomMargin)
760         n = _bottomMargin - from;
761     moveImage(loc(0,from+n),loc(0,from),loc(columns-1,_bottomMargin-n));
762     clearImage(loc(0,from),loc(columns-1,from+n-1),' ');
763 }
764 
setCursorYX(int y,int x)765 void Screen::setCursorYX(int y, int x)
766 {
767     setCursorY(y); setCursorX(x);
768 }
769 
setCursorX(int x)770 void Screen::setCursorX(int x)
771 {
772     if (x == 0) x = 1; // Default
773     x -= 1; // Adjust
774     cuX = std::max(0,std::min(columns-1, x));
775 }
776 
setCursorY(int y)777 void Screen::setCursorY(int y)
778 {
779     if (y == 0) y = 1; // Default
780     y -= 1; // Adjust
781     cuY = std::max(0,std::min(lines  -1, y + (getMode(MODE_Origin) ? _topMargin : 0) ));
782 }
783 
home()784 void Screen::home()
785 {
786     cuX = 0;
787     cuY = 0;
788 }
789 
toStartOfLine()790 void Screen::toStartOfLine()
791 {
792     cuX = 0;
793 }
794 
getCursorX() const795 int Screen::getCursorX() const
796 {
797     return cuX;
798 }
799 
getCursorY() const800 int Screen::getCursorY() const
801 {
802     return cuY;
803 }
804 
clearImage(int loca,int loce,char c)805 void Screen::clearImage(int loca, int loce, char c)
806 {
807     int scr_TL=loc(0,history->getLines());
808     //FIXME: check positions
809 
810     //Clear entire selection if it overlaps region to be moved...
811     if ( (selBottomRight > (loca+scr_TL) )&&(selTopLeft < (loce+scr_TL)) )
812     {
813         clearSelection();
814     }
815 
816     int topLine = loca/columns;
817     int bottomLine = loce/columns;
818 
819     Character clearCh(c,currentForeground,currentBackground,DEFAULT_RENDITION);
820 
821     //if the character being used to clear the area is the same as the
822     //default character, the affected lines can simply be shrunk.
823     bool isDefaultCh = (clearCh == Character());
824 
825     for (int y=topLine;y<=bottomLine;y++)
826     {
827         lineProperties[y] = 0;
828 
829         int endCol = ( y == bottomLine) ? loce%columns : columns-1;
830         int startCol = ( y == topLine ) ? loca%columns : 0;
831 
832         QVector<Character>& line = screenLines[y];
833 
834         if ( isDefaultCh && endCol == columns-1 )
835         {
836             line.resize(startCol);
837         }
838         else
839         {
840             if (line.size() < endCol + 1)
841                 line.resize(endCol+1);
842 
843             Character* data = line.data();
844             for (int i=startCol;i<=endCol;i++)
845                 data[i]=clearCh;
846         }
847     }
848 }
849 
moveImage(int dest,int sourceBegin,int sourceEnd)850 void Screen::moveImage(int dest, int sourceBegin, int sourceEnd)
851 {
852     Q_ASSERT( sourceBegin <= sourceEnd );
853 
854     int lines=(sourceEnd-sourceBegin)/columns;
855 
856     //move screen image and line properties:
857     //the source and destination areas of the image may overlap,
858     //so it matters that we do the copy in the right order -
859     //forwards if dest < sourceBegin or backwards otherwise.
860     //(search the web for 'memmove implementation' for details)
861     if (dest < sourceBegin)
862     {
863         for (int i=0;i<=lines;i++)
864         {
865             screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ];
866             lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i];
867         }
868     }
869     else
870     {
871         for (int i=lines;i>=0;i--)
872         {
873             screenLines[ (dest/columns)+i ] = screenLines[ (sourceBegin/columns)+i ];
874             lineProperties[(dest/columns)+i]=lineProperties[(sourceBegin/columns)+i];
875         }
876     }
877 
878     if (lastPos != -1)
879     {
880         int diff = dest - sourceBegin; // Scroll by this amount
881         lastPos += diff;
882         if ((lastPos < 0) || (lastPos >= (lines*columns)))
883             lastPos = -1;
884     }
885 
886     // Adjust selection to follow scroll.
887     if (selBegin != -1)
888     {
889         bool beginIsTL = (selBegin == selTopLeft);
890         int diff = dest - sourceBegin; // Scroll by this amount
891         int scr_TL=loc(0,history->getLines());
892         int srca = sourceBegin+scr_TL; // Translate index from screen to global
893         int srce = sourceEnd+scr_TL; // Translate index from screen to global
894         int desta = srca+diff;
895         int deste = srce+diff;
896 
897         if ((selTopLeft >= srca) && (selTopLeft <= srce))
898             selTopLeft += diff;
899         else if ((selTopLeft >= desta) && (selTopLeft <= deste))
900             selBottomRight = -1; // Clear selection (see below)
901 
902         if ((selBottomRight >= srca) && (selBottomRight <= srce))
903             selBottomRight += diff;
904         else if ((selBottomRight >= desta) && (selBottomRight <= deste))
905             selBottomRight = -1; // Clear selection (see below)
906 
907         if (selBottomRight < 0)
908         {
909             clearSelection();
910         }
911         else
912         {
913             if (selTopLeft < 0)
914                 selTopLeft = 0;
915         }
916 
917         if (beginIsTL)
918             selBegin = selTopLeft;
919         else
920             selBegin = selBottomRight;
921     }
922 }
923 
clearToEndOfScreen()924 void Screen::clearToEndOfScreen()
925 {
926     clearImage(loc(cuX,cuY),loc(columns-1,lines-1),' ');
927 }
928 
clearToBeginOfScreen()929 void Screen::clearToBeginOfScreen()
930 {
931     clearImage(loc(0,0),loc(cuX,cuY),' ');
932 }
933 
clearEntireScreen()934 void Screen::clearEntireScreen()
935 {
936     // Add entire screen to history
937     for (int i = 0; i < (lines-1); i++)
938     {
939         addHistLine(); scrollUp(0,1);
940     }
941 
942     clearImage(loc(0,0),loc(columns-1,lines-1),' ');
943 }
944 
945 /*! fill screen with 'E'
946   This is to aid screen alignment
947   */
948 
helpAlign()949 void Screen::helpAlign()
950 {
951     clearImage(loc(0,0),loc(columns-1,lines-1),'E');
952 }
953 
clearToEndOfLine()954 void Screen::clearToEndOfLine()
955 {
956     clearImage(loc(cuX,cuY),loc(columns-1,cuY),' ');
957 }
958 
clearToBeginOfLine()959 void Screen::clearToBeginOfLine()
960 {
961     clearImage(loc(0,cuY),loc(cuX,cuY),' ');
962 }
963 
clearEntireLine()964 void Screen::clearEntireLine()
965 {
966     clearImage(loc(0,cuY),loc(columns-1,cuY),' ');
967 }
968 
setRendition(int re)969 void Screen::setRendition(int re)
970 {
971     currentRendition |= re;
972     updateEffectiveRendition();
973 }
974 
resetRendition(int re)975 void Screen::resetRendition(int re)
976 {
977     currentRendition &= ~re;
978     updateEffectiveRendition();
979 }
980 
setDefaultRendition()981 void Screen::setDefaultRendition()
982 {
983     setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR);
984     setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR);
985     currentRendition   = DEFAULT_RENDITION;
986     updateEffectiveRendition();
987 }
988 
setForeColor(int space,int color)989 void Screen::setForeColor(int space, int color)
990 {
991     currentForeground = CharacterColor(space, color);
992 
993     if ( currentForeground.isValid() )
994         updateEffectiveRendition();
995     else
996         setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR);
997 }
998 
setBackColor(int space,int color)999 void Screen::setBackColor(int space, int color)
1000 {
1001     currentBackground = CharacterColor(space, color);
1002 
1003     if ( currentBackground.isValid() )
1004         updateEffectiveRendition();
1005     else
1006         setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR);
1007 }
1008 
clearSelection()1009 void Screen::clearSelection()
1010 {
1011     selBottomRight = -1;
1012     selTopLeft = -1;
1013     selBegin = -1;
1014 }
1015 
getSelectionStart(int & column,int & line) const1016 void Screen::getSelectionStart(int& column , int& line) const
1017 {
1018     if ( selTopLeft != -1 )
1019     {
1020         column = selTopLeft % columns;
1021         line = selTopLeft / columns;
1022     }
1023     else
1024     {
1025         column = cuX + getHistLines();
1026         line = cuY + getHistLines();
1027     }
1028 }
getSelectionEnd(int & column,int & line) const1029 void Screen::getSelectionEnd(int& column , int& line) const
1030 {
1031     if ( selBottomRight != -1 )
1032     {
1033         column = selBottomRight % columns;
1034         line = selBottomRight / columns;
1035     }
1036     else
1037     {
1038         column = cuX + getHistLines();
1039         line = cuY + getHistLines();
1040     }
1041 }
setSelectionStart(const int x,const int y,const bool mode)1042 void Screen::setSelectionStart(const int x, const int y, const bool mode)
1043 {
1044     selBegin = loc(x,y);
1045     /* FIXME, HACK to correct for x too far to the right... */
1046     if (x == columns) selBegin--;
1047 
1048     selBottomRight = selBegin;
1049     selTopLeft = selBegin;
1050     blockSelectionMode = mode;
1051 }
1052 
setSelectionEnd(const int x,const int y)1053 void Screen::setSelectionEnd( const int x, const int y)
1054 {
1055     if (selBegin == -1)
1056         return;
1057 
1058     int endPos =  loc(x,y);
1059 
1060     if (endPos < selBegin)
1061     {
1062         selTopLeft = endPos;
1063         selBottomRight = selBegin;
1064     }
1065     else
1066     {
1067         /* FIXME, HACK to correct for x too far to the right... */
1068         if (x == columns)
1069             endPos--;
1070 
1071         selTopLeft = selBegin;
1072         selBottomRight = endPos;
1073     }
1074 
1075     // Normalize the selection in column mode
1076     if (blockSelectionMode)
1077     {
1078         int topRow = selTopLeft / columns;
1079         int topColumn = selTopLeft % columns;
1080         int bottomRow = selBottomRight / columns;
1081         int bottomColumn = selBottomRight % columns;
1082 
1083         selTopLeft = loc(std::min(topColumn,bottomColumn),topRow);
1084         selBottomRight = loc(std::max(topColumn,bottomColumn),bottomRow);
1085     }
1086 }
1087 
isSelected(const int x,const int y) const1088 bool Screen::isSelected( const int x,const int y) const
1089 {
1090     bool columnInSelection = true;
1091     if (blockSelectionMode)
1092     {
1093         columnInSelection = x >= (selTopLeft % columns) &&
1094             x <= (selBottomRight % columns);
1095     }
1096 
1097     int pos = loc(x,y);
1098     return pos >= selTopLeft && pos <= selBottomRight && columnInSelection;
1099 }
1100 
selectedText(bool preserveLineBreaks) const1101 QString Screen::selectedText(bool preserveLineBreaks) const
1102 {
1103     QString result;
1104     QTextStream stream(&result, QIODevice::ReadWrite);
1105 
1106     PlainTextDecoder decoder;
1107     decoder.begin(&stream);
1108     writeSelectionToStream(&decoder , preserveLineBreaks);
1109     decoder.end();
1110 
1111     return result;
1112 }
1113 
isSelectionValid() const1114 bool Screen::isSelectionValid() const
1115 {
1116     return selTopLeft >= 0 && selBottomRight >= 0;
1117 }
1118 
writeSelectionToStream(TerminalCharacterDecoder * decoder,bool preserveLineBreaks) const1119 void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder ,
1120         bool preserveLineBreaks) const
1121 {
1122     if (!isSelectionValid())
1123         return;
1124     writeToStream(decoder,selTopLeft,selBottomRight,preserveLineBreaks);
1125 }
1126 
writeToStream(TerminalCharacterDecoder * decoder,int startIndex,int endIndex,bool preserveLineBreaks) const1127 void Screen::writeToStream(TerminalCharacterDecoder* decoder,
1128         int startIndex, int endIndex,
1129         bool preserveLineBreaks) const
1130 {
1131     int top = startIndex / columns;
1132     int left = startIndex % columns;
1133 
1134     int bottom = endIndex / columns;
1135     int right = endIndex % columns;
1136 
1137     Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 );
1138 
1139     for (int y=top;y<=bottom;y++)
1140     {
1141         int start = 0;
1142         if ( y == top || blockSelectionMode ) start = left;
1143 
1144         int count = -1;
1145         if ( y == bottom || blockSelectionMode ) count = right - start + 1;
1146 
1147         const bool appendNewLine = ( y != bottom );
1148         int copied = copyLineToStream( y,
1149                 start,
1150                 count,
1151                 decoder,
1152                 appendNewLine,
1153                 preserveLineBreaks );
1154 
1155         // if the selection goes beyond the end of the last line then
1156         // append a new line character.
1157         //
1158         // this makes it possible to 'select' a trailing new line character after
1159         // the text on a line.
1160         if ( y == bottom &&
1161                 copied < count    )
1162         {
1163             Character newLineChar('\n');
1164             decoder->decodeLine(&newLineChar,1,0);
1165         }
1166     }
1167 }
1168 
copyLineToStream(int line,int start,int count,TerminalCharacterDecoder * decoder,bool appendNewLine,bool preserveLineBreaks) const1169 int Screen::copyLineToStream(int line ,
1170         int start,
1171         int count,
1172         TerminalCharacterDecoder* decoder,
1173         bool appendNewLine,
1174         bool preserveLineBreaks) const
1175 {
1176     //buffer to hold characters for decoding
1177     //the buffer is static to avoid initialising every
1178     //element on each call to copyLineToStream
1179     //(which is unnecessary since all elements will be overwritten anyway)
1180     static const int MAX_CHARS = 1024;
1181     static Character characterBuffer[MAX_CHARS];
1182 
1183     assert( count < MAX_CHARS );
1184 
1185     LineProperty currentLineProperties = 0;
1186 
1187     //determine if the line is in the history buffer or the screen image
1188     if (line < history->getLines())
1189     {
1190         const int lineLength = history->getLineLen(line);
1191 
1192         // ensure that start position is before end of line
1193         start = std::min(start,std::max(0,lineLength-1));
1194 
1195         // retrieve line from history buffer.  It is assumed
1196         // that the history buffer does not store trailing white space
1197         // at the end of the line, so it does not need to be trimmed here
1198         if (count == -1)
1199         {
1200             count = lineLength-start;
1201         }
1202         else
1203         {
1204             count = std::min(start+count,lineLength)-start;
1205         }
1206 
1207         // safety checks
1208         assert( start >= 0 );
1209         assert( count >= 0 );
1210         // cppcheck-suppress assertWithSideEffect
1211         assert( (start+count) <= history->getLineLen(line) );
1212 
1213         history->getCells(line,start,count,characterBuffer);
1214 
1215         if ( history->isWrappedLine(line) )
1216             currentLineProperties |= LINE_WRAPPED;
1217     }
1218     else
1219     {
1220         if ( count == -1 )
1221             count = columns - start;
1222 
1223         assert( count >= 0 );
1224 
1225         const int screenLine = line-history->getLines();
1226 
1227         Character* data = screenLines[screenLine].data();
1228         int length = screenLines[screenLine].count();
1229 
1230         //retrieve line from screen image
1231         for (int i=start;i < std::min(start+count,length);i++)
1232         {
1233             characterBuffer[i-start] = data[i];
1234         }
1235 
1236         // count cannot be any greater than length
1237         count = qBound(0,count,length-start);
1238 
1239         Q_ASSERT( screenLine < lineProperties.count() );
1240         currentLineProperties |= lineProperties[screenLine];
1241     }
1242 
1243     // add new line character at end
1244     const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) ||
1245         !preserveLineBreaks;
1246 
1247     if ( !omitLineBreak && appendNewLine && (count+1 < MAX_CHARS) )
1248     {
1249         characterBuffer[count] = '\n';
1250         count++;
1251     }
1252 
1253     //decode line and write to text stream
1254     decoder->decodeLine( (Character*) characterBuffer ,
1255             count, currentLineProperties );
1256 
1257     return count;
1258 }
1259 
writeLinesToStream(TerminalCharacterDecoder * decoder,int fromLine,int toLine) const1260 void Screen::writeLinesToStream(TerminalCharacterDecoder* decoder, int fromLine, int toLine) const
1261 {
1262     writeToStream(decoder,loc(0,fromLine),loc(columns-1,toLine));
1263 }
1264 
addHistLine()1265 void Screen::addHistLine()
1266 {
1267     // add line to history buffer
1268     // we have to take care about scrolling, too...
1269 
1270     if (hasScroll())
1271     {
1272         int oldHistLines = history->getLines();
1273 
1274         history->addCellsVector(screenLines[0]);
1275         history->addLine( lineProperties[0] & LINE_WRAPPED );
1276 
1277         int newHistLines = history->getLines();
1278 
1279         bool beginIsTL = (selBegin == selTopLeft);
1280 
1281         // If the history is full, increment the count
1282         // of dropped lines
1283         if ( newHistLines == oldHistLines )
1284             _droppedLines++;
1285 
1286         // Adjust selection for the new point of reference
1287         if (newHistLines > oldHistLines)
1288         {
1289             if (selBegin != -1)
1290             {
1291                 selTopLeft += columns;
1292                 selBottomRight += columns;
1293             }
1294         }
1295 
1296         if (selBegin != -1)
1297         {
1298             // Scroll selection in history up
1299             int top_BR = loc(0, 1+newHistLines);
1300 
1301             if (selTopLeft < top_BR)
1302                 selTopLeft -= columns;
1303 
1304             if (selBottomRight < top_BR)
1305                 selBottomRight -= columns;
1306 
1307             if (selBottomRight < 0)
1308                 clearSelection();
1309             else
1310             {
1311                 if (selTopLeft < 0)
1312                     selTopLeft = 0;
1313             }
1314 
1315             if (beginIsTL)
1316                 selBegin = selTopLeft;
1317             else
1318                 selBegin = selBottomRight;
1319         }
1320     }
1321 
1322 }
1323 
getHistLines() const1324 int Screen::getHistLines() const
1325 {
1326     return history->getLines();
1327 }
1328 
setScroll(const HistoryType & t,bool copyPreviousScroll)1329 void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll)
1330 {
1331     clearSelection();
1332 
1333     if ( copyPreviousScroll )
1334         history = t.scroll(history);
1335     else
1336     {
1337         HistoryScroll* oldScroll = history;
1338         history = t.scroll(0);
1339         delete oldScroll;
1340     }
1341 }
1342 
hasScroll() const1343 bool Screen::hasScroll() const
1344 {
1345     return history->hasScroll();
1346 }
1347 
getScroll() const1348 const HistoryType& Screen::getScroll() const
1349 {
1350     return history->getType();
1351 }
1352 
setLineProperty(LineProperty property,bool enable)1353 void Screen::setLineProperty(LineProperty property , bool enable)
1354 {
1355     if ( enable )
1356         lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property);
1357     else
1358         lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property);
1359 }
fillWithDefaultChar(Character * dest,int count)1360 void Screen::fillWithDefaultChar(Character* dest, int count)
1361 {
1362     for (int i=0;i<count;i++)
1363         dest[i] = defaultChar;
1364 }
1365