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