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