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