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