File indexing completed on 2025-03-16 08:11:27
0001 /* 0002 This file is part of Konsole, an X terminal. 0003 0004 SPDX-FileCopyrightText: 2007-2008 Robert Knight <robert.knight@gmail.com> 0005 SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 GNU General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License 0015 along with this program; if not, write to the Free Software 0016 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301 USA. 0018 */ 0019 0020 // Own 0021 #include "Screen.h" 0022 0023 // Standard 0024 #include <cctype> 0025 #include <cstdio> 0026 #include <cstdlib> 0027 #include <cstring> 0028 #include <unistd.h> 0029 0030 // Qt 0031 #include <QDate> 0032 #include <QTextStream> 0033 0034 // KDE 0035 // #include <kdebug.h> 0036 0037 // Konsole 0038 #include "TerminalCharacterDecoder.h" 0039 #include "konsole_wcwidth.h" 0040 0041 using namespace Konsole; 0042 0043 // FIXME: this is emulation specific. Use false for xterm, true for ANSI. 0044 // FIXME: see if we can get this from terminfo. 0045 #define BS_CLEARS false 0046 0047 // Macro to convert x,y position on screen to position within an image. 0048 // 0049 // Originally the image was stored as one large contiguous block of 0050 // memory, so a position within the image could be represented as an 0051 // offset from the beginning of the block. For efficiency reasons this 0052 // is no longer the case. 0053 // Many internal parts of this class still use this representation for parameters and so on, 0054 // notably moveImage() and clearImage(). 0055 // This macro converts from an X,Y position into an image offset. 0056 #ifndef loc 0057 #define loc(X, Y) ((Y)*columns + (X)) 0058 #endif 0059 0060 Character Screen::defaultChar = 0061 Character(' ', CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION); 0062 0063 // #define REVERSE_WRAPPED_LINES // for wrapped line debug 0064 0065 Screen::Screen(int l, int c) 0066 : lines(l) 0067 , columns(c) 0068 , screenLines(lines + 1) 0069 , _scrolledLines(0) 0070 , _droppedLines(0) 0071 , history(std::make_unique<HistoryScrollNone>()) 0072 , cuX(0) 0073 , cuY(0) 0074 , currentRendition(0) 0075 , _topMargin(0) 0076 , _bottomMargin(0) 0077 , selBegin(0) 0078 , selTopLeft(0) 0079 , selBottomRight(0) 0080 , blockSelectionMode(false) 0081 , effectiveForeground(CharacterColor()) 0082 , effectiveBackground(CharacterColor()) 0083 , effectiveRendition(0) 0084 , lastPos(-1) 0085 { 0086 lineProperties.resize(lines + 1); 0087 for (int i = 0; i < lines + 1; i++) 0088 lineProperties[i] = LINE_DEFAULT; 0089 0090 initTabStops(); 0091 clearSelection(); 0092 reset(); 0093 } 0094 0095 /*! Destructor 0096 */ 0097 0098 Screen::~Screen() 0099 { 0100 } 0101 0102 void Screen::cursorUp(int n) 0103 //=CUU 0104 { 0105 if (n == 0) 0106 n = 1; // Default 0107 int stop = cuY < _topMargin ? 0 : _topMargin; 0108 cuX = qMin(columns - 1, cuX); // nowrap! 0109 cuY = qMax(stop, cuY - n); 0110 } 0111 0112 void Screen::cursorDown(int n) 0113 //=CUD 0114 { 0115 if (n == 0) 0116 n = 1; // Default 0117 int stop = cuY > _bottomMargin ? lines - 1 : _bottomMargin; 0118 cuX = qMin(columns - 1, cuX); // nowrap! 0119 cuY = qMin(stop, cuY + n); 0120 } 0121 0122 void Screen::cursorLeft(int n) 0123 //=CUB 0124 { 0125 if (n == 0) 0126 n = 1; // Default 0127 cuX = qMin(columns - 1, cuX); // nowrap! 0128 cuX = qMax(0, cuX - n); 0129 } 0130 0131 void Screen::cursorRight(int n) 0132 //=CUF 0133 { 0134 if (n == 0) 0135 n = 1; // Default 0136 cuX = qMin(columns - 1, cuX + n); 0137 } 0138 0139 void Screen::setMargins(int top, int bot) 0140 //=STBM 0141 { 0142 if (top == 0) 0143 top = 1; // Default 0144 if (bot == 0) 0145 bot = lines; // Default 0146 top = top - 1; // Adjust to internal lineno 0147 bot = bot - 1; // Adjust to internal lineno 0148 if (!(0 <= top && top < bot && bot < lines)) { // Debug()<<" setRegion("<<top<<","<<bot<<") : bad range."; 0149 return; // Default error action: ignore 0150 } 0151 _topMargin = top; 0152 _bottomMargin = bot; 0153 cuX = 0; 0154 cuY = getMode(MODE_Origin) ? top : 0; 0155 } 0156 0157 int Screen::topMargin() const 0158 { 0159 return _topMargin; 0160 } 0161 int Screen::bottomMargin() const 0162 { 0163 return _bottomMargin; 0164 } 0165 0166 void Screen::index() 0167 //=IND 0168 { 0169 if (cuY == _bottomMargin) 0170 scrollUp(1); 0171 else if (cuY < lines - 1) 0172 cuY += 1; 0173 } 0174 0175 void Screen::reverseIndex() 0176 //=RI 0177 { 0178 if (cuY == _topMargin) 0179 scrollDown(_topMargin, 1); 0180 else if (cuY > 0) 0181 cuY -= 1; 0182 } 0183 0184 void Screen::nextLine() 0185 //=NEL 0186 { 0187 toStartOfLine(); 0188 index(); 0189 } 0190 0191 void Screen::eraseChars(int n) 0192 { 0193 if (n == 0) 0194 n = 1; // Default 0195 int p = qMax(0, qMin(cuX + n - 1, columns - 1)); 0196 clearImage(loc(cuX, cuY), loc(p, cuY), ' '); 0197 } 0198 0199 void Screen::deleteChars(int n) 0200 { 0201 Q_ASSERT(n >= 0); 0202 0203 // always delete at least one char 0204 if (n == 0) 0205 n = 1; 0206 0207 // if cursor is beyond the end of the line there is nothing to do 0208 if (cuX >= screenLines[cuY].size()) 0209 return; 0210 0211 if (cuX + n > screenLines[cuY].size()) 0212 n = screenLines[cuY].size() - cuX; 0213 0214 Q_ASSERT(n >= 0); 0215 Q_ASSERT(cuX + n <= screenLines[cuY].size()); 0216 0217 screenLines[cuY].remove(cuX, n); 0218 } 0219 0220 void Screen::insertChars(int n) 0221 { 0222 if (n == 0) 0223 n = 1; // Default 0224 0225 if (screenLines[cuY].size() < cuX) 0226 screenLines[cuY].resize(cuX); 0227 0228 screenLines[cuY].insert(cuX, n, ' '); 0229 0230 if (screenLines[cuY].size() > columns) 0231 screenLines[cuY].resize(columns); 0232 } 0233 0234 void Screen::repeatChars(int count) 0235 //=REP 0236 { 0237 if (count == 0) { 0238 count = 1; 0239 } 0240 /** 0241 * From ECMA-48 version 5, section 8.3.103 0242 * If the character preceding REP is a control function or part of a 0243 * control function, the effect of REP is not defined by this Standard. 0244 * 0245 * So, a "normal" program should always use REP immediately after a visible 0246 * character (those other than escape sequences). So, lastDrawnChar can be 0247 * safely used. 0248 */ 0249 for (int i = 0; i < count; i++) { 0250 displayCharacter(lastDrawnChar); 0251 } 0252 } 0253 0254 void Screen::deleteLines(int n) 0255 { 0256 if (n == 0) 0257 n = 1; // Default 0258 scrollUp(cuY, n); 0259 } 0260 0261 void Screen::insertLines(int n) 0262 { 0263 if (n == 0) 0264 n = 1; // Default 0265 scrollDown(cuY, n); 0266 } 0267 0268 void Screen::setMode(int m) 0269 { 0270 currentModes[m] = true; 0271 switch (m) { 0272 case MODE_Origin: 0273 cuX = 0; 0274 cuY = _topMargin; 0275 break; // FIXME: home 0276 } 0277 } 0278 0279 void Screen::resetMode(int m) 0280 { 0281 currentModes[m] = false; 0282 switch (m) { 0283 case MODE_Origin: 0284 cuX = 0; 0285 cuY = 0; 0286 break; // FIXME: home 0287 } 0288 } 0289 0290 void Screen::saveMode(int m) 0291 { 0292 savedModes[m] = currentModes[m]; 0293 } 0294 0295 void Screen::restoreMode(int m) 0296 { 0297 currentModes[m] = savedModes[m]; 0298 } 0299 0300 bool Screen::getMode(int m) const 0301 { 0302 return currentModes[m]; 0303 } 0304 0305 void Screen::saveCursor() 0306 { 0307 savedState.cursorColumn = cuX; 0308 savedState.cursorLine = cuY; 0309 savedState.rendition = currentRendition; 0310 savedState.foreground = currentForeground; 0311 savedState.background = currentBackground; 0312 } 0313 0314 void Screen::restoreCursor() 0315 { 0316 cuX = qMin(savedState.cursorColumn, columns - 1); 0317 cuY = qMin(savedState.cursorLine, lines - 1); 0318 currentRendition = savedState.rendition; 0319 currentForeground = savedState.foreground; 0320 currentBackground = savedState.background; 0321 updateEffectiveRendition(); 0322 } 0323 0324 void Screen::resizeImage(int new_lines, int new_columns) 0325 { 0326 if ((new_lines == lines) && (new_columns == columns)) 0327 return; 0328 0329 if (cuY > new_lines - 1) { // attempt to preserve focus and lines 0330 _bottomMargin = lines - 1; // FIXME: margin lost 0331 for (int i = 0; i < cuY - (new_lines - 1); i++) { 0332 addHistLine(); 0333 scrollUp(0, 1); 0334 } 0335 } 0336 0337 // create new screen lines and copy from old to new 0338 0339 auto newScreenLines = QVector<ImageLine>(new_lines + 1); 0340 for (int i = 0; i < qMin(lines, new_lines + 1); i++) 0341 newScreenLines[i] = screenLines[i]; 0342 for (int i = lines; (i > 0) && (i < new_lines + 1); i++) 0343 newScreenLines[i].resize(new_columns); 0344 0345 lineProperties.resize(new_lines + 1); 0346 for (int i = lines; (i > 0) && (i < new_lines + 1); i++) 0347 lineProperties[i] = LINE_DEFAULT; 0348 0349 clearSelection(); 0350 0351 screenLines.clear(); 0352 screenLines = std::move(newScreenLines); 0353 0354 lines = new_lines; 0355 columns = new_columns; 0356 cuX = qMin(cuX, columns - 1); 0357 cuY = qMin(cuY, lines - 1); 0358 0359 // FIXME: try to keep values, evtl. 0360 _topMargin = 0; 0361 _bottomMargin = lines - 1; 0362 initTabStops(); 0363 clearSelection(); 0364 } 0365 0366 void Screen::setDefaultMargins() 0367 { 0368 _topMargin = 0; 0369 _bottomMargin = lines - 1; 0370 } 0371 0372 /* 0373 Clarifying rendition here and in the display. 0374 0375 currently, the display's color table is 0376 0 1 2 .. 9 10 .. 17 0377 dft_fg, dft_bg, dim 0..7, intensive 0..7 0378 0379 currentForeground, currentBackground contain values 0..8; 0380 - 0 = default color 0381 - 1..8 = ansi specified color 0382 0383 re_fg, re_bg contain values 0..17 0384 due to the TerminalDisplay's color table 0385 0386 rendition attributes are 0387 0388 attr widget screen 0389 -------------- ------ ------ 0390 RE_UNDERLINE XX XX affects foreground only 0391 RE_BLINK XX XX affects foreground only 0392 RE_BOLD XX XX affects foreground only 0393 RE_REVERSE -- XX 0394 RE_TRANSPARENT XX -- affects background only 0395 RE_INTENSIVE XX -- affects foreground only 0396 0397 Note that RE_BOLD is used in both widget 0398 and screen rendition. Since xterm/vt102 0399 is to poor to distinguish between bold 0400 (which is a font attribute) and intensive 0401 (which is a color attribute), we translate 0402 this and RE_BOLD in falls eventually appart 0403 into RE_BOLD and RE_INTENSIVE. 0404 */ 0405 0406 void Screen::reverseRendition(Character &p) const 0407 { 0408 CharacterColor f = p.foregroundColor; 0409 CharacterColor b = p.backgroundColor; 0410 0411 p.foregroundColor = b; 0412 p.backgroundColor = f; // p->r &= ~RE_TRANSPARENT; 0413 } 0414 0415 void Screen::updateEffectiveRendition() 0416 { 0417 effectiveRendition = currentRendition; 0418 if (currentRendition & RE_REVERSE) { 0419 effectiveForeground = currentBackground; 0420 effectiveBackground = currentForeground; 0421 } else { 0422 effectiveForeground = currentForeground; 0423 effectiveBackground = currentBackground; 0424 } 0425 0426 if (currentRendition & RE_BOLD) 0427 effectiveForeground.setIntensive(); 0428 } 0429 0430 void Screen::copyFromHistory(std::span<Character> dest, int startLine, int count) const 0431 { 0432 Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= history->getLines()); 0433 0434 for (int line = startLine; line < startLine + count; line++) { 0435 const int length = qMin(columns, history->getLineLen(line)); 0436 const int destLineOffset = (line - startLine) * columns; 0437 0438 history->getCells(line, 0, length, dest.subspan(destLineOffset)); 0439 0440 for (int column = length; column < columns; column++) 0441 dest[destLineOffset + column] = defaultChar; 0442 0443 // invert selected text 0444 if (selBegin != -1) { 0445 for (int column = 0; column < columns; column++) { 0446 if (isSelected(column, line)) { 0447 reverseRendition(dest[destLineOffset + column]); 0448 } 0449 } 0450 } 0451 } 0452 } 0453 0454 void Screen::copyFromScreen(std::span<Character> dest, int startLine, int count) const 0455 { 0456 Q_ASSERT(startLine >= 0 && count > 0 && startLine + count <= lines); 0457 0458 for (int line = startLine; line < (startLine + count); line++) { 0459 int srcLineStartIndex = line * columns; 0460 int destLineStartIndex = (line - startLine) * columns; 0461 0462 for (int column = 0; column < columns; column++) { 0463 int srcIndex = srcLineStartIndex + column; 0464 int destIndex = destLineStartIndex + column; 0465 0466 dest[destIndex] = screenLines[srcIndex / columns].value(srcIndex % columns, defaultChar); 0467 0468 // invert selected text 0469 if (selBegin != -1 && isSelected(column, line + history->getLines())) 0470 reverseRendition(dest[destIndex]); 0471 } 0472 } 0473 } 0474 0475 void Screen::getImage(std::span<Character> dest, int size, int startLine, int endLine) const 0476 { 0477 Q_ASSERT(startLine >= 0); 0478 Q_ASSERT(endLine >= startLine && endLine < history->getLines() + lines); 0479 0480 const int mergedLines = endLine - startLine + 1; 0481 0482 Q_ASSERT(size >= mergedLines * columns); 0483 Q_UNUSED(size); 0484 0485 const int linesInHistoryBuffer = qBound(0, history->getLines() - startLine, mergedLines); 0486 const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; 0487 0488 // copy lines from history buffer 0489 if (linesInHistoryBuffer > 0) 0490 copyFromHistory(dest, startLine, linesInHistoryBuffer); 0491 0492 // copy lines from screen buffer 0493 if (linesInScreenBuffer > 0) 0494 copyFromScreen(dest.subspan(linesInHistoryBuffer * columns), startLine + linesInHistoryBuffer - history->getLines(), linesInScreenBuffer); 0495 0496 // invert display when in screen mode 0497 if (getMode(MODE_Screen)) { 0498 for (int i = 0; i < mergedLines * columns; i++) 0499 reverseRendition(dest[i]); // for reverse display 0500 } 0501 0502 // mark the character at the current cursor position 0503 int cursorIndex = loc(cuX, cuY + linesInHistoryBuffer); 0504 if (getMode(MODE_Cursor) && cursorIndex < columns * mergedLines) 0505 dest[cursorIndex].rendition |= RE_CURSOR; 0506 } 0507 0508 QVector<LineProperty> Screen::getLineProperties(int startLine, int endLine) const 0509 { 0510 Q_ASSERT(startLine >= 0); 0511 Q_ASSERT(endLine >= startLine && endLine < history->getLines() + lines); 0512 0513 const int mergedLines = endLine - startLine + 1; 0514 const int linesInHistory = qBound(0, history->getLines() - startLine, mergedLines); 0515 const int linesInScreen = mergedLines - linesInHistory; 0516 0517 QVector<LineProperty> result(mergedLines); 0518 int index = 0; 0519 0520 // copy properties for lines in history 0521 for (int line = startLine; line < startLine + linesInHistory; line++) { 0522 // TODO Support for line properties other than wrapped lines 0523 if (history->isWrappedLine(line)) { 0524 result[index] = (LineProperty)(result[index] | LINE_WRAPPED); 0525 } 0526 index++; 0527 } 0528 0529 // copy properties for lines in screen buffer 0530 const int firstScreenLine = startLine + linesInHistory - history->getLines(); 0531 for (int line = firstScreenLine; line < firstScreenLine + linesInScreen; line++) { 0532 result[index] = lineProperties[line]; 0533 index++; 0534 } 0535 0536 return result; 0537 } 0538 0539 void Screen::reset(bool clearScreen) 0540 { 0541 setMode(MODE_Wrap); 0542 saveMode(MODE_Wrap); // wrap at end of margin 0543 resetMode(MODE_Origin); 0544 saveMode(MODE_Origin); // position refere to [1,1] 0545 resetMode(MODE_Insert); 0546 saveMode(MODE_Insert); // overstroke 0547 setMode(MODE_Cursor); // cursor visible 0548 resetMode(MODE_Screen); // screen not inverse 0549 resetMode(MODE_NewLine); 0550 0551 _topMargin = 0; 0552 _bottomMargin = lines - 1; 0553 0554 setDefaultRendition(); 0555 saveCursor(); 0556 0557 if (clearScreen) 0558 clear(); 0559 } 0560 0561 void Screen::clear() 0562 { 0563 clearEntireScreen(); 0564 home(); 0565 } 0566 0567 void Screen::backspace() 0568 { 0569 cuX = qMin(columns - 1, cuX); // nowrap! 0570 cuX = qMax(0, cuX - 1); 0571 0572 if (screenLines[cuY].size() < cuX + 1) 0573 screenLines[cuY].resize(cuX + 1); 0574 0575 if (BS_CLEARS) 0576 screenLines[cuY][cuX].character = u' '; 0577 } 0578 0579 void Screen::tab(int n) 0580 { 0581 // note that TAB is a format effector (does not write ' '); 0582 if (n == 0) 0583 n = 1; 0584 while ((n > 0) && (cuX < columns - 1)) { 0585 cursorRight(1); 0586 while ((cuX < columns - 1) && !tabStops[cuX]) 0587 cursorRight(1); 0588 n--; 0589 } 0590 } 0591 0592 void Screen::backtab(int n) 0593 { 0594 // note that TAB is a format effector (does not write ' '); 0595 if (n == 0) 0596 n = 1; 0597 while ((n > 0) && (cuX > 0)) { 0598 cursorLeft(1); 0599 while ((cuX > 0) && !tabStops[cuX]) 0600 cursorLeft(1); 0601 n--; 0602 } 0603 } 0604 0605 void Screen::clearTabStops() 0606 { 0607 for (int i = 0; i < columns; i++) 0608 tabStops[i] = false; 0609 } 0610 0611 void Screen::changeTabStop(bool set) 0612 { 0613 if (cuX >= columns) 0614 return; 0615 tabStops[cuX] = set; 0616 } 0617 0618 void Screen::initTabStops() 0619 { 0620 tabStops.resize(columns); 0621 0622 // Arrg! The 1st tabstop has to be one longer than the other. 0623 // i.e. the kids start counting from 0 instead of 1. 0624 // Other programs might behave correctly. Be aware. 0625 for (int i = 0; i < columns; i++) 0626 tabStops[i] = (i % 8 == 0 && i != 0); 0627 } 0628 0629 void Screen::newLine() 0630 { 0631 if (getMode(MODE_NewLine)) 0632 toStartOfLine(); 0633 index(); 0634 } 0635 0636 void Screen::checkSelection(int from, int to) 0637 { 0638 if (selBegin == -1) 0639 return; 0640 int scr_TL = loc(0, history->getLines()); 0641 // Clear entire selection if it overlaps region [from, to] 0642 if ((selBottomRight >= (from + scr_TL)) && (selTopLeft <= (to + scr_TL))) 0643 clearSelection(); 0644 } 0645 0646 void Screen::displayCharacter(QChar c) 0647 { 0648 // Note that VT100 does wrapping BEFORE putting the character. 0649 // This has impact on the assumption of valid cursor positions. 0650 // We indicate the fact that a newline has to be triggered by 0651 // putting the cursor one right to the last column of the screen. 0652 0653 int w = konsole_wcwidth(c); 0654 if (w <= 0) 0655 return; 0656 0657 if (cuX + w > columns) { 0658 if (getMode(MODE_Wrap)) { 0659 lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | LINE_WRAPPED); 0660 nextLine(); 0661 } else 0662 cuX = columns - w; 0663 } 0664 0665 // ensure current line vector has enough elements 0666 int size = screenLines[cuY].size(); 0667 if (size < cuX + w) { 0668 screenLines[cuY].resize(cuX + w); 0669 } 0670 0671 if (getMode(MODE_Insert)) 0672 insertChars(w); 0673 0674 lastPos = loc(cuX, cuY); 0675 0676 // check if selection is still valid. 0677 checkSelection(lastPos, lastPos); 0678 0679 Character ¤tChar = screenLines[cuY][cuX]; 0680 0681 currentChar.character = c; 0682 currentChar.foregroundColor = effectiveForeground; 0683 currentChar.backgroundColor = effectiveBackground; 0684 currentChar.rendition = effectiveRendition; 0685 0686 lastDrawnChar = c; 0687 0688 int i = 0; 0689 int newCursorX = cuX + w--; 0690 while (w) { 0691 i++; 0692 0693 if (screenLines[cuY].size() < cuX + i + 1) 0694 screenLines[cuY].resize(cuX + i + 1); 0695 0696 Character &ch = screenLines[cuY][cuX + i]; 0697 ch.character = QChar(0); 0698 ch.foregroundColor = effectiveForeground; 0699 ch.backgroundColor = effectiveBackground; 0700 ch.rendition = effectiveRendition; 0701 0702 w--; 0703 } 0704 cuX = newCursorX; 0705 } 0706 0707 void Screen::compose(const QString & /*compose*/) 0708 { 0709 Q_ASSERT(0 /*Not implemented yet*/); 0710 0711 /* if (lastPos == -1) 0712 return; 0713 0714 QChar c(image[lastPos].character); 0715 compose.prepend(c); 0716 //compose.compose(); ### FIXME! 0717 image[lastPos].character = compose[0].unicode();*/ 0718 } 0719 0720 int Screen::scrolledLines() const 0721 { 0722 return _scrolledLines; 0723 } 0724 int Screen::droppedLines() const 0725 { 0726 return _droppedLines; 0727 } 0728 void Screen::resetDroppedLines() 0729 { 0730 _droppedLines = 0; 0731 } 0732 void Screen::resetScrolledLines() 0733 { 0734 _scrolledLines = 0; 0735 } 0736 0737 void Screen::scrollUp(int n) 0738 { 0739 if (n == 0) 0740 n = 1; // Default 0741 if (_topMargin == 0) 0742 addHistLine(); // history.history 0743 scrollUp(_topMargin, n); 0744 } 0745 0746 QRect Screen::lastScrolledRegion() const 0747 { 0748 return _lastScrolledRegion; 0749 } 0750 0751 void Screen::scrollUp(int from, int n) 0752 { 0753 if (n <= 0) 0754 return; 0755 if (from > _bottomMargin) 0756 return; 0757 if (from + n > _bottomMargin) 0758 n = _bottomMargin + 1 - from; 0759 0760 _scrolledLines -= n; 0761 _lastScrolledRegion = QRect(0, _topMargin, columns - 1, (_bottomMargin - _topMargin)); 0762 0763 // FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. 0764 moveImage(loc(0, from), loc(0, from + n), loc(columns, _bottomMargin)); 0765 clearImage(loc(0, _bottomMargin - n + 1), loc(columns - 1, _bottomMargin), ' '); 0766 } 0767 0768 void Screen::scrollDown(int n) 0769 { 0770 if (n == 0) 0771 n = 1; // Default 0772 scrollDown(_topMargin, n); 0773 } 0774 0775 void Screen::scrollDown(int from, int n) 0776 { 0777 _scrolledLines += n; 0778 0779 // FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. 0780 if (n <= 0) 0781 return; 0782 if (from > _bottomMargin) 0783 return; 0784 if (from + n > _bottomMargin) 0785 n = _bottomMargin - from; 0786 moveImage(loc(0, from + n), loc(0, from), loc(columns - 1, _bottomMargin - n)); 0787 clearImage(loc(0, from), loc(columns - 1, from + n - 1), ' '); 0788 } 0789 0790 void Screen::setCursorYX(int y, int x) 0791 { 0792 setCursorY(y); 0793 setCursorX(x); 0794 } 0795 0796 void Screen::setCursorX(int x) 0797 { 0798 if (x == 0) 0799 x = 1; // Default 0800 x -= 1; // Adjust 0801 cuX = qMax(0, qMin(columns - 1, x)); 0802 } 0803 0804 void Screen::setCursorY(int y) 0805 { 0806 if (y == 0) 0807 y = 1; // Default 0808 y -= 1; // Adjust 0809 cuY = qMax(0, qMin(lines - 1, y + (getMode(MODE_Origin) ? _topMargin : 0))); 0810 } 0811 0812 void Screen::home() 0813 { 0814 cuX = 0; 0815 cuY = 0; 0816 } 0817 0818 void Screen::toStartOfLine() 0819 { 0820 cuX = 0; 0821 } 0822 0823 int Screen::getCursorX() const 0824 { 0825 return cuX; 0826 } 0827 0828 int Screen::getCursorY() const 0829 { 0830 return cuY; 0831 } 0832 0833 void Screen::clearImage(int loca, int loce, char c) 0834 { 0835 int scr_TL = loc(0, history->getLines()); 0836 // FIXME: check positions 0837 0838 // Clear entire selection if it overlaps region to be moved... 0839 if ((selBottomRight > (loca + scr_TL)) && (selTopLeft < (loce + scr_TL))) { 0840 clearSelection(); 0841 } 0842 0843 int topLine = loca / columns; 0844 int bottomLine = loce / columns; 0845 0846 Character clearCh(c, currentForeground, currentBackground, DEFAULT_RENDITION); 0847 0848 // if the character being used to clear the area is the same as the 0849 // default character, the affected lines can simply be shrunk. 0850 bool isDefaultCh = (clearCh == Character()); 0851 0852 for (int y = topLine; y <= bottomLine; y++) { 0853 lineProperties[y] = 0; 0854 0855 int endCol = (y == bottomLine) ? loce % columns : columns - 1; 0856 int startCol = (y == topLine) ? loca % columns : 0; 0857 0858 QVector<Character> &line = screenLines[y]; 0859 0860 if (isDefaultCh && endCol == columns - 1) { 0861 line.resize(startCol); 0862 } else { 0863 if (line.size() < endCol + 1) 0864 line.resize(endCol + 1); 0865 0866 Character *data = line.data(); 0867 for (int i = startCol; i <= endCol; i++) 0868 data[i] = clearCh; 0869 } 0870 } 0871 } 0872 0873 void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) 0874 { 0875 Q_ASSERT(sourceBegin <= sourceEnd); 0876 0877 int lines = (sourceEnd - sourceBegin) / columns; 0878 0879 // move screen image and line properties: 0880 // the source and destination areas of the image may overlap, 0881 // so it matters that we do the copy in the right order - 0882 // forwards if dest < sourceBegin or backwards otherwise. 0883 //(search the web for 'memmove implementation' for details) 0884 if (dest < sourceBegin) { 0885 for (int i = 0; i <= lines; i++) { 0886 screenLines[(dest / columns) + i] = screenLines[(sourceBegin / columns) + i]; 0887 lineProperties[(dest / columns) + i] = lineProperties[(sourceBegin / columns) + i]; 0888 } 0889 } else { 0890 for (int i = lines; i >= 0; i--) { 0891 screenLines[(dest / columns) + i] = screenLines[(sourceBegin / columns) + i]; 0892 lineProperties[(dest / columns) + i] = lineProperties[(sourceBegin / columns) + i]; 0893 } 0894 } 0895 0896 if (lastPos != -1) { 0897 int diff = dest - sourceBegin; // Scroll by this amount 0898 lastPos += diff; 0899 if ((lastPos < 0) || (lastPos >= (lines * columns))) 0900 lastPos = -1; 0901 } 0902 0903 // Adjust selection to follow scroll. 0904 if (selBegin != -1) { 0905 bool beginIsTL = (selBegin == selTopLeft); 0906 int diff = dest - sourceBegin; // Scroll by this amount 0907 int scr_TL = loc(0, history->getLines()); 0908 int srca = sourceBegin + scr_TL; // Translate index from screen to global 0909 int srce = sourceEnd + scr_TL; // Translate index from screen to global 0910 int desta = srca + diff; 0911 int deste = srce + diff; 0912 0913 if ((selTopLeft >= srca) && (selTopLeft <= srce)) 0914 selTopLeft += diff; 0915 else if ((selTopLeft >= desta) && (selTopLeft <= deste)) 0916 selBottomRight = -1; // Clear selection (see below) 0917 0918 if ((selBottomRight >= srca) && (selBottomRight <= srce)) 0919 selBottomRight += diff; 0920 else if ((selBottomRight >= desta) && (selBottomRight <= deste)) 0921 selBottomRight = -1; // Clear selection (see below) 0922 0923 if (selBottomRight < 0) { 0924 clearSelection(); 0925 } else { 0926 if (selTopLeft < 0) 0927 selTopLeft = 0; 0928 } 0929 0930 if (beginIsTL) 0931 selBegin = selTopLeft; 0932 else 0933 selBegin = selBottomRight; 0934 } 0935 } 0936 0937 void Screen::clearToEndOfScreen() 0938 { 0939 clearImage(loc(cuX, cuY), loc(columns - 1, lines - 1), ' '); 0940 } 0941 0942 void Screen::clearToBeginOfScreen() 0943 { 0944 clearImage(loc(0, 0), loc(cuX, cuY), ' '); 0945 } 0946 0947 void Screen::clearEntireScreen() 0948 { 0949 // Add entire screen to history 0950 for (int i = 0; i < (lines - 1); i++) { 0951 addHistLine(); 0952 scrollUp(0, 1); 0953 } 0954 0955 clearImage(loc(0, 0), loc(columns - 1, lines - 1), ' '); 0956 } 0957 0958 /*! fill screen with 'E' 0959 This is to aid screen alignment 0960 */ 0961 0962 void Screen::helpAlign() 0963 { 0964 clearImage(loc(0, 0), loc(columns - 1, lines - 1), 'E'); 0965 } 0966 0967 void Screen::clearToEndOfLine() 0968 { 0969 clearImage(loc(cuX, cuY), loc(columns - 1, cuY), ' '); 0970 } 0971 0972 void Screen::clearToBeginOfLine() 0973 { 0974 clearImage(loc(0, cuY), loc(cuX, cuY), ' '); 0975 } 0976 0977 void Screen::clearEntireLine() 0978 { 0979 clearImage(loc(0, cuY), loc(columns - 1, cuY), ' '); 0980 } 0981 0982 void Screen::setRendition(int re) 0983 { 0984 currentRendition |= re; 0985 updateEffectiveRendition(); 0986 } 0987 0988 void Screen::resetRendition(int re) 0989 { 0990 currentRendition &= ~re; 0991 updateEffectiveRendition(); 0992 } 0993 0994 void Screen::setDefaultRendition() 0995 { 0996 setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); 0997 setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); 0998 currentRendition = DEFAULT_RENDITION; 0999 updateEffectiveRendition(); 1000 } 1001 1002 void Screen::setForeColor(int space, int color) 1003 { 1004 currentForeground = CharacterColor(space, color); 1005 1006 if (currentForeground.isValid()) 1007 updateEffectiveRendition(); 1008 else 1009 setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); 1010 } 1011 1012 void Screen::setBackColor(int space, int color) 1013 { 1014 currentBackground = CharacterColor(space, color); 1015 1016 if (currentBackground.isValid()) 1017 updateEffectiveRendition(); 1018 else 1019 setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); 1020 } 1021 1022 void Screen::clearSelection() 1023 { 1024 selBottomRight = -1; 1025 selTopLeft = -1; 1026 selBegin = -1; 1027 } 1028 1029 void Screen::getSelectionStart(int &column, int &line) const 1030 { 1031 if (selTopLeft != -1) { 1032 column = selTopLeft % columns; 1033 line = selTopLeft / columns; 1034 } else { 1035 column = cuX + getHistLines(); 1036 line = cuY + getHistLines(); 1037 } 1038 } 1039 void Screen::getSelectionEnd(int &column, int &line) const 1040 { 1041 if (selBottomRight != -1) { 1042 column = selBottomRight % columns; 1043 line = selBottomRight / columns; 1044 } else { 1045 column = cuX + getHistLines(); 1046 line = cuY + getHistLines(); 1047 } 1048 } 1049 void Screen::setSelectionStart(const int x, const int y, const bool mode) 1050 { 1051 selBegin = loc(x, y); 1052 /* FIXME, HACK to correct for x too far to the right... */ 1053 if (x == columns) 1054 selBegin--; 1055 1056 selBottomRight = selBegin; 1057 selTopLeft = selBegin; 1058 blockSelectionMode = mode; 1059 } 1060 1061 void Screen::setSelectionEnd(const int x, const int y) 1062 { 1063 if (selBegin == -1) 1064 return; 1065 1066 int endPos = loc(x, y); 1067 1068 if (endPos < selBegin) { 1069 selTopLeft = endPos; 1070 selBottomRight = selBegin; 1071 } else { 1072 /* FIXME, HACK to correct for x too far to the right... */ 1073 if (x == columns) 1074 endPos--; 1075 1076 selTopLeft = selBegin; 1077 selBottomRight = endPos; 1078 } 1079 1080 // Normalize the selection in column mode 1081 if (blockSelectionMode) { 1082 int topRow = selTopLeft / columns; 1083 int topColumn = selTopLeft % columns; 1084 int bottomRow = selBottomRight / columns; 1085 int bottomColumn = selBottomRight % columns; 1086 1087 selTopLeft = loc(qMin(topColumn, bottomColumn), topRow); 1088 selBottomRight = loc(qMax(topColumn, bottomColumn), bottomRow); 1089 } 1090 } 1091 1092 bool Screen::isSelected(const int x, const int y) const 1093 { 1094 bool columnInSelection = true; 1095 if (blockSelectionMode) { 1096 columnInSelection = x >= (selTopLeft % columns) && x <= (selBottomRight % columns); 1097 } 1098 1099 int pos = loc(x, y); 1100 return pos >= selTopLeft && pos <= selBottomRight && columnInSelection; 1101 } 1102 1103 QString Screen::selectedText(bool preserveLineBreaks) const 1104 { 1105 QString result; 1106 QTextStream stream(&result, QIODevice::ReadWrite); 1107 1108 PlainTextDecoder decoder; 1109 decoder.begin(&stream); 1110 writeSelectionToStream(&decoder, preserveLineBreaks); 1111 decoder.end(); 1112 1113 return result; 1114 } 1115 1116 bool Screen::isSelectionValid() const 1117 { 1118 return selTopLeft >= 0 && selBottomRight >= 0; 1119 } 1120 1121 void Screen::writeSelectionToStream(TerminalCharacterDecoder *decoder, bool preserveLineBreaks) const 1122 { 1123 if (!isSelectionValid()) 1124 return; 1125 writeToStream(decoder, selTopLeft, selBottomRight, preserveLineBreaks); 1126 } 1127 1128 void Screen::writeToStream(TerminalCharacterDecoder *decoder, int startIndex, int endIndex, bool preserveLineBreaks) const 1129 { 1130 int top = startIndex / columns; 1131 int left = startIndex % columns; 1132 1133 int bottom = endIndex / columns; 1134 int right = endIndex % columns; 1135 1136 Q_ASSERT(top >= 0 && left >= 0 && bottom >= 0 && right >= 0); 1137 1138 for (int y = top; y <= bottom; y++) { 1139 int start = 0; 1140 if (y == top || blockSelectionMode) 1141 start = left; 1142 1143 int count = -1; 1144 if (y == bottom || blockSelectionMode) 1145 count = right - start + 1; 1146 1147 const bool appendNewLine = (y != bottom); 1148 int copied = copyLineToStream(y, start, count, decoder, appendNewLine, preserveLineBreaks); 1149 1150 // if the selection goes beyond the end of the last line then 1151 // append a new line character. 1152 // 1153 // this makes it possible to 'select' a trailing new line character after 1154 // the text on a line. 1155 if (y == bottom && copied < count) { 1156 Character newLineChar('\n'); 1157 decoder->decodeLine(std::span(&newLineChar, 1), 0); 1158 } 1159 } 1160 } 1161 1162 int Screen::copyLineToStream(int line, int start, int count, TerminalCharacterDecoder *decoder, bool appendNewLine, bool preserveLineBreaks) const 1163 { 1164 // buffer to hold characters for decoding 1165 // the buffer is static to avoid initialising every 1166 // element on each call to copyLineToStream 1167 //(which is unnecessary since all elements will be overwritten anyway) 1168 static const int MAX_CHARS = 1024; 1169 static std::array<Character, MAX_CHARS> characterBuffer; 1170 1171 Q_ASSERT(count < MAX_CHARS); 1172 1173 LineProperty currentLineProperties = 0; 1174 1175 // determine if the line is in the history buffer or the screen image 1176 if (line < history->getLines()) { 1177 const int lineLength = history->getLineLen(line); 1178 1179 // ensure that start position is before end of line 1180 start = qMin(start, qMax(0, lineLength - 1)); 1181 1182 // retrieve line from history buffer. It is assumed 1183 // that the history buffer does not store trailing white space 1184 // at the end of the line, so it does not need to be trimmed here 1185 if (count == -1) { 1186 count = lineLength - start; 1187 } else { 1188 count = qMin(start + count, lineLength) - start; 1189 } 1190 1191 // safety checks 1192 Q_ASSERT(start >= 0); 1193 Q_ASSERT(count >= 0); 1194 Q_ASSERT((start + count) <= history->getLineLen(line)); 1195 1196 history->getCells(line, start, count, characterBuffer); 1197 1198 if (history->isWrappedLine(line)) 1199 currentLineProperties |= LINE_WRAPPED; 1200 } else { 1201 if (count == -1) 1202 count = columns - start; 1203 1204 Q_ASSERT(count >= 0); 1205 1206 const int screenLine = line - history->getLines(); 1207 1208 auto data = screenLines[screenLine].data(); 1209 int length = screenLines[screenLine].size(); 1210 1211 // retrieve line from screen image 1212 for (int i = start; i < qMin(start + count, length); i++) { 1213 characterBuffer[i - start] = data[i]; 1214 } 1215 1216 // count cannot be any greater than length 1217 count = qBound(0, length - start, count); 1218 1219 Q_ASSERT(screenLine < lineProperties.size()); 1220 currentLineProperties |= lineProperties[screenLine]; 1221 } 1222 1223 // add new line character at end 1224 const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || !preserveLineBreaks; 1225 1226 if (!omitLineBreak && appendNewLine && (count + 1 < MAX_CHARS)) { 1227 characterBuffer[count] = '\n'; 1228 count++; 1229 } 1230 1231 // decode line and write to text stream 1232 decoder->decodeLine(std::span(characterBuffer).subspan(0, count), currentLineProperties); 1233 1234 return count; 1235 } 1236 1237 void Screen::writeLinesToStream(TerminalCharacterDecoder *decoder, int fromLine, int toLine) const 1238 { 1239 writeToStream(decoder, loc(0, fromLine), loc(columns - 1, toLine)); 1240 } 1241 1242 void Screen::addHistLine() 1243 { 1244 // add line to history buffer 1245 // we have to take care about scrolling, too... 1246 1247 if (hasScroll()) { 1248 int oldHistLines = history->getLines(); 1249 1250 history->addCellsVector(screenLines[0]); 1251 history->addLine(lineProperties[0] & LINE_WRAPPED); 1252 1253 int newHistLines = history->getLines(); 1254 1255 bool beginIsTL = (selBegin == selTopLeft); 1256 1257 // If the history is full, increment the count 1258 // of dropped lines 1259 if (newHistLines == oldHistLines) 1260 _droppedLines++; 1261 1262 // Adjust selection for the new point of reference 1263 if (newHistLines > oldHistLines) { 1264 if (selBegin != -1) { 1265 selTopLeft += columns; 1266 selBottomRight += columns; 1267 } 1268 } 1269 1270 if (selBegin != -1) { 1271 // Scroll selection in history up 1272 int top_BR = loc(0, 1 + newHistLines); 1273 1274 if (selTopLeft < top_BR) 1275 selTopLeft -= columns; 1276 1277 if (selBottomRight < top_BR) 1278 selBottomRight -= columns; 1279 1280 if (selBottomRight < 0) 1281 clearSelection(); 1282 else { 1283 if (selTopLeft < 0) 1284 selTopLeft = 0; 1285 } 1286 1287 if (beginIsTL) 1288 selBegin = selTopLeft; 1289 else 1290 selBegin = selBottomRight; 1291 } 1292 } 1293 } 1294 1295 int Screen::getHistLines() const 1296 { 1297 return history->getLines(); 1298 } 1299 1300 void Screen::setScroll(const HistoryType &t, bool copyPreviousScroll) 1301 { 1302 clearSelection(); 1303 1304 if (copyPreviousScroll) 1305 history = t.scroll(std::move(history)); 1306 else { 1307 auto oldScroll = std::move(history); 1308 history = t.scroll(nullptr); 1309 } 1310 } 1311 1312 bool Screen::hasScroll() const 1313 { 1314 return history->hasScroll(); 1315 } 1316 1317 const HistoryType &Screen::getScroll() const 1318 { 1319 return history->getType(); 1320 } 1321 1322 void Screen::setLineProperty(LineProperty property, bool enable) 1323 { 1324 if (enable) 1325 lineProperties[cuY] = (LineProperty)(lineProperties[cuY] | property); 1326 else 1327 lineProperties[cuY] = (LineProperty)(lineProperties[cuY] & ~property); 1328 } 1329 void Screen::fillWithDefaultChar(std::span<Character> dest, int count) 1330 { 1331 for (int i = 0; i < count; i++) 1332 dest[i] = defaultChar; 1333 }