Mercurial > hg > octave-lyh
view gui/src/terminal/Screen.cpp @ 13506:c70511cf64ee
Reformatted to GNU Style.
author | Jacob Dawid <jacob.dawid@googlemail.com> |
---|---|
date | Sun, 17 Jul 2011 22:59:28 +0200 |
parents | 86d6c3b90ad7 |
children |
line wrap: on
line source
/* This file is part of Konsole, an X terminal. Copyright 2007-2008 by Robert Knight <robert.knight@gmail.com> Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ // Own #include "Screen.h" // Standard #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <string.h> #include <ctype.h> // Qt #include <QtCore/QTextStream> #include <QtCore/QDate> // Konsole #include "konsole_wcwidth.h" #include "TerminalCharacterDecoder.h" //FIXME: this is emulation specific. Use false for xterm, true for ANSI. //FIXME: see if we can get this from terminfo. #define BS_CLEARS false //Macro to convert x,y position on screen to position within an image. // //Originally the image was stored as one large contiguous block of //memory, so a position within the image could be represented as an //offset from the beginning of the block. For efficiency reasons this //is no longer the case. //Many internal parts of this class still use this representation for parameters and so on, //notably moveImage() and clearImage(). //This macro converts from an X,Y position into an image offset. #ifndef loc #define loc(X,Y) ((Y)*columns+(X)) #endif Character Screen::defaultChar = Character (' ', CharacterColor (COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR), CharacterColor (COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR), DEFAULT_RENDITION); //#define REVERSE_WRAPPED_LINES // for wrapped line debug Screen::Screen (int l, int c): lines (l), columns (c), screenLines (new ImageLine[lines + 1]), _scrolledLines (0), _droppedLines (0), history (new HistoryScrollNone ()), cuX (0), cuY (0), currentRendition (0), _topMargin (0), _bottomMargin (0), selBegin (0), selTopLeft (0), selBottomRight (0), blockSelectionMode (false), effectiveForeground (CharacterColor ()), effectiveBackground (CharacterColor ()), effectiveRendition (0), lastPos (-1) { lineProperties.resize (lines + 1); for (int i = 0; i < lines + 1; i++) lineProperties[i] = LINE_DEFAULT; initTabStops (); clearSelection (); reset (); } /*! Destructor */ Screen::~Screen () { delete[]screenLines; delete history; } void Screen::cursorUp (int n) //=CUU { if (n == 0) n = 1; // Default int stop = cuY < _topMargin ? 0 : _topMargin; cuX = qMin (columns - 1, cuX); // nowrap! cuY = qMax (stop, cuY - n); } void Screen::cursorDown (int n) //=CUD { if (n == 0) n = 1; // Default int stop = cuY > _bottomMargin ? lines - 1 : _bottomMargin; cuX = qMin (columns - 1, cuX); // nowrap! cuY = qMin (stop, cuY + n); } void Screen::cursorLeft (int n) //=CUB { if (n == 0) n = 1; // Default cuX = qMin (columns - 1, cuX); // nowrap! cuX = qMax (0, cuX - n); } void Screen::cursorRight (int n) //=CUF { if (n == 0) n = 1; // Default cuX = qMin (columns - 1, cuX + n); } void Screen::setMargins (int top, int bot) //=STBM { if (top == 0) top = 1; // Default if (bot == 0) bot = lines; // Default top = top - 1; // Adjust to internal lineno bot = bot - 1; // Adjust to internal lineno if (!(0 <= top && top < bot && bot < lines)) { //Debug()<<" setRegion("<<top<<","<<bot<<") : bad range."; return; // Default error action: ignore } _topMargin = top; _bottomMargin = bot; cuX = 0; cuY = getMode (MODE_Origin) ? top : 0; } int Screen::topMargin () const { return _topMargin; } int Screen::bottomMargin () const { return _bottomMargin; } void Screen::index () //=IND { if (cuY == _bottomMargin) scrollUp (1); else if (cuY < lines - 1) cuY += 1; } void Screen::reverseIndex () //=RI { if (cuY == _topMargin) scrollDown (_topMargin, 1); else if (cuY > 0) cuY -= 1; } void Screen::nextLine () //=NEL { toStartOfLine (); index (); } void Screen::eraseChars (int n) { if (n == 0) n = 1; // Default int p = qMax (0, qMin (cuX + n - 1, columns - 1)); clearImage (loc (cuX, cuY), loc (p, cuY), ' '); } void Screen::deleteChars (int n) { Q_ASSERT (n >= 0); // always delete at least one char if (n == 0) n = 1; // if cursor is beyond the end of the line there is nothing to do if (cuX >= screenLines[cuY].count ()) return; if (cuX + n > screenLines[cuY].count ()) n = screenLines[cuY].count () - cuX; Q_ASSERT (n >= 0); Q_ASSERT (cuX + n <= screenLines[cuY].count ()); screenLines[cuY].remove (cuX, n); } void Screen::insertChars (int n) { if (n == 0) n = 1; // Default if (screenLines[cuY].size () < cuX) screenLines[cuY].resize (cuX); screenLines[cuY].insert (cuX, n, ' '); if (screenLines[cuY].count () > columns) screenLines[cuY].resize (columns); } void Screen::deleteLines (int n) { if (n == 0) n = 1; // Default scrollUp (cuY, n); } void Screen::insertLines (int n) { if (n == 0) n = 1; // Default scrollDown (cuY, n); } void Screen::setMode (int m) { currentModes[m] = true; switch (m) { case MODE_Origin: cuX = 0; cuY = _topMargin; break; //FIXME: home } } void Screen::resetMode (int m) { currentModes[m] = false; switch (m) { case MODE_Origin: cuX = 0; cuY = 0; break; //FIXME: home } } void Screen::saveMode (int m) { savedModes[m] = currentModes[m]; } void Screen::restoreMode (int m) { currentModes[m] = savedModes[m]; } bool Screen::getMode (int m) const { return currentModes[m]; } void Screen::saveCursor () { savedState.cursorColumn = cuX; savedState.cursorLine = cuY; savedState.rendition = currentRendition; savedState.foreground = currentForeground; savedState.background = currentBackground; } void Screen::restoreCursor () { cuX = qMin (savedState.cursorColumn, columns - 1); cuY = qMin (savedState.cursorLine, lines - 1); currentRendition = savedState.rendition; currentForeground = savedState.foreground; currentBackground = savedState.background; updateEffectiveRendition (); } void Screen::resizeImage (int new_lines, int new_columns) { if ((new_lines == lines) && (new_columns == columns)) return; if (cuY > new_lines - 1) { // attempt to preserve focus and lines _bottomMargin = lines - 1; //FIXME: margin lost for (int i = 0; i < cuY - (new_lines - 1); i++) { addHistLine (); scrollUp (0, 1); } } // create new screen lines and copy from old to new ImageLine *newScreenLines = new ImageLine[new_lines + 1]; for (int i = 0; i < qMin (lines - 1, new_lines + 1); i++) newScreenLines[i] = screenLines[i]; for (int i = lines; (i > 0) && (i < new_lines + 1); i++) newScreenLines[i].resize (new_columns); lineProperties.resize (new_lines + 1); for (int i = lines; (i > 0) && (i < new_lines + 1); i++) lineProperties[i] = LINE_DEFAULT; clearSelection (); delete[]screenLines; screenLines = newScreenLines; lines = new_lines; columns = new_columns; cuX = qMin (cuX, columns - 1); cuY = qMin (cuY, lines - 1); // FIXME: try to keep values, evtl. _topMargin = 0; _bottomMargin = lines - 1; initTabStops (); clearSelection (); } void Screen::setDefaultMargins () { _topMargin = 0; _bottomMargin = lines - 1; } /* Clarifying rendition here and in the display. currently, the display's color table is 0 1 2 .. 9 10 .. 17 dft_fg, dft_bg, dim 0..7, intensive 0..7 currentForeground, currentBackground contain values 0..8; - 0 = default color - 1..8 = ansi specified color re_fg, re_bg contain values 0..17 due to the TerminalDisplay's color table rendition attributes are attr widget screen -------------- ------ ------ RE_UNDERLINE XX XX affects foreground only RE_BLINK XX XX affects foreground only RE_BOLD XX XX affects foreground only RE_REVERSE -- XX RE_TRANSPARENT XX -- affects background only RE_INTENSIVE XX -- affects foreground only Note that RE_BOLD is used in both widget and screen rendition. Since xterm/vt102 is to poor to distinguish between bold (which is a font attribute) and intensive (which is a color attribute), we translate this and RE_BOLD in falls eventually appart into RE_BOLD and RE_INTENSIVE. */ void Screen::reverseRendition (Character & p) const { CharacterColor f = p.foregroundColor; CharacterColor b = p.backgroundColor; p.foregroundColor = b; p.backgroundColor = f; //p->r &= ~RE_TRANSPARENT; } void Screen::updateEffectiveRendition () { effectiveRendition = currentRendition; if (currentRendition & RE_REVERSE) { effectiveForeground = currentBackground; effectiveBackground = currentForeground; } else { effectiveForeground = currentForeground; effectiveBackground = currentBackground; } if (currentRendition & RE_BOLD) effectiveForeground.toggleIntensive (); } void Screen::copyFromHistory (Character * dest, int startLine, int count) const { Q_ASSERT (startLine >= 0 && count > 0 && startLine + count <= history->getLines ()); for (int line = startLine; line < startLine + count; line++) { const int length = qMin (columns, history->getLineLen (line)); const int destLineOffset = (line - startLine) * columns; history->getCells (line, 0, length, dest + destLineOffset); for (int column = length; column < columns; column++) dest[destLineOffset + column] = defaultChar; // invert selected text if (selBegin != -1) { for (int column = 0; column < columns; column++) { if (isSelected (column, line)) { reverseRendition (dest[destLineOffset + column]); } } } } } void Screen::copyFromScreen (Character * dest, int startLine, int count) const { Q_ASSERT (startLine >= 0 && count > 0 && startLine + count <= lines); for (int line = startLine; line < (startLine + count); line++) { int srcLineStartIndex = line * columns; int destLineStartIndex = (line - startLine) * columns; for (int column = 0; column < columns; column++) { int srcIndex = srcLineStartIndex + column; int destIndex = destLineStartIndex + column; dest[destIndex] = screenLines[srcIndex / columns].value (srcIndex % columns, defaultChar); // invert selected text if (selBegin != -1 && isSelected (column, line + history->getLines ())) reverseRendition (dest[destIndex]); } } } void Screen::getImage (Character * dest, int size, int startLine, int endLine) const { Q_ASSERT (startLine >= 0); Q_ASSERT (endLine >= startLine && endLine < history->getLines () + lines); const int mergedLines = endLine - startLine + 1; Q_ASSERT (size >= mergedLines * columns); Q_UNUSED (size); const int linesInHistoryBuffer = qBound (0, history->getLines () - startLine, mergedLines); const int linesInScreenBuffer = mergedLines - linesInHistoryBuffer; // copy lines from history buffer if (linesInHistoryBuffer > 0) copyFromHistory (dest, startLine, linesInHistoryBuffer); // copy lines from screen buffer if (linesInScreenBuffer > 0) copyFromScreen (dest + linesInHistoryBuffer * columns, startLine + linesInHistoryBuffer - history->getLines (), linesInScreenBuffer); // invert display when in screen mode if (getMode (MODE_Screen)) { for (int i = 0; i < mergedLines * columns; i++) reverseRendition (dest[i]); // for reverse display } // mark the character at the current cursor position int cursorIndex = loc (cuX, cuY + linesInHistoryBuffer); if (getMode (MODE_Cursor) && cursorIndex < columns * mergedLines) dest[cursorIndex].rendition |= RE_CURSOR; } QVector < LineProperty > Screen::getLineProperties (int startLine, int endLine) const { Q_ASSERT (startLine >= 0); Q_ASSERT (endLine >= startLine && endLine < history->getLines () + lines); const int mergedLines = endLine - startLine + 1; const int linesInHistory = qBound (0, history->getLines () - startLine, mergedLines); const int linesInScreen = mergedLines - linesInHistory; QVector < LineProperty > result (mergedLines); int index = 0; // copy properties for lines in history for (int line = startLine; line < startLine + linesInHistory; line++) { //TODO Support for line properties other than wrapped lines if (history->isWrappedLine (line)) { result[index] = (LineProperty) (result[index] | LINE_WRAPPED); } index++; } // copy properties for lines in screen buffer const int firstScreenLine = startLine + linesInHistory - history->getLines (); for (int line = firstScreenLine; line < firstScreenLine + linesInScreen; line++) { result[index] = lineProperties[line]; index++; } return result; } void Screen::reset (bool clearScreen) { setMode (MODE_Wrap); saveMode (MODE_Wrap); // wrap at end of margin resetMode (MODE_Origin); saveMode (MODE_Origin); // position refere to [1,1] resetMode (MODE_Insert); saveMode (MODE_Insert); // overstroke setMode (MODE_Cursor); // cursor visible resetMode (MODE_Screen); // screen not inverse resetMode (MODE_NewLine); _topMargin = 0; _bottomMargin = lines - 1; setDefaultRendition (); saveCursor (); if (clearScreen) clear (); } void Screen::clear () { clearEntireScreen (); home (); } void Screen::backspace () { cuX = qMin (columns - 1, cuX); // nowrap! cuX = qMax (0, cuX - 1); if (screenLines[cuY].size () < cuX + 1) screenLines[cuY].resize (cuX + 1); if (BS_CLEARS) screenLines[cuY][cuX].character = ' '; } void Screen::tab (int n) { // note that TAB is a format effector (does not write ' '); if (n == 0) n = 1; while ((n > 0) && (cuX < columns - 1)) { cursorRight (1); while ((cuX < columns - 1) && !tabStops[cuX]) cursorRight (1); n--; } } void Screen::backtab (int n) { // note that TAB is a format effector (does not write ' '); if (n == 0) n = 1; while ((n > 0) && (cuX > 0)) { cursorLeft (1); while ((cuX > 0) && !tabStops[cuX]) cursorLeft (1); n--; } } void Screen::clearTabStops () { for (int i = 0; i < columns; i++) tabStops[i] = false; } void Screen::changeTabStop (bool set) { if (cuX >= columns) return; tabStops[cuX] = set; } void Screen::initTabStops () { tabStops.resize (columns); // Arrg! The 1st tabstop has to be one longer than the other. // i.e. the kids start counting from 0 instead of 1. // Other programs might behave correctly. Be aware. for (int i = 0; i < columns; i++) tabStops[i] = (i % 8 == 0 && i != 0); } void Screen::newLine () { if (getMode (MODE_NewLine)) toStartOfLine (); index (); } void Screen::checkSelection (int from, int to) { if (selBegin == -1) return; int scr_TL = loc (0, history->getLines ()); //Clear entire selection if it overlaps region [from, to] if ((selBottomRight >= (from + scr_TL)) && (selTopLeft <= (to + scr_TL))) clearSelection (); } void Screen::displayCharacter (unsigned short c) { // Note that VT100 does wrapping BEFORE putting the character. // This has impact on the assumption of valid cursor positions. // We indicate the fact that a newline has to be triggered by // putting the cursor one right to the last column of the screen. int w = konsole_wcwidth (c); if (w <= 0) return; if (cuX + w > columns) { if (getMode (MODE_Wrap)) { lineProperties[cuY] = (LineProperty) (lineProperties[cuY] | LINE_WRAPPED); nextLine (); } else cuX = columns - w; } // ensure current line vector has enough elements int size = screenLines[cuY].size (); if (size < cuX + w) { screenLines[cuY].resize (cuX + w); } if (getMode (MODE_Insert)) insertChars (w); lastPos = loc (cuX, cuY); // check if selection is still valid. checkSelection (lastPos, lastPos); Character & currentChar = screenLines[cuY][cuX]; currentChar.character = c; currentChar.foregroundColor = effectiveForeground; currentChar.backgroundColor = effectiveBackground; currentChar.rendition = effectiveRendition; int i = 0; int newCursorX = cuX + w--; while (w) { i++; if (screenLines[cuY].size () < cuX + i + 1) screenLines[cuY].resize (cuX + i + 1); Character & ch = screenLines[cuY][cuX + i]; ch.character = 0; ch.foregroundColor = effectiveForeground; ch.backgroundColor = effectiveBackground; ch.rendition = effectiveRendition; w--; } cuX = newCursorX; } void Screen::compose (const QString & /*compose */ ) { Q_ASSERT (0 /*Not implemented yet */ ); /* if (lastPos == -1) return; QChar c(image[lastPos].character); compose.prepend(c); //compose.compose(); ### FIXME! image[lastPos].character = compose[0].unicode(); */ } int Screen::scrolledLines () const { return _scrolledLines; } int Screen::droppedLines () const { return _droppedLines; } void Screen::resetDroppedLines () { _droppedLines = 0; } void Screen::resetScrolledLines () { _scrolledLines = 0; } void Screen::scrollUp (int n) { if (n == 0) n = 1; // Default if (_topMargin == 0) addHistLine (); // history.history scrollUp (_topMargin, n); } QRect Screen::lastScrolledRegion () const { return _lastScrolledRegion; } void Screen::scrollUp (int from, int n) { if (n <= 0 || from + n > _bottomMargin) return; _scrolledLines -= n; _lastScrolledRegion = QRect (0, _topMargin, columns - 1, (_bottomMargin - _topMargin)); //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. moveImage (loc (0, from), loc (0, from + n), loc (columns - 1, _bottomMargin)); clearImage (loc (0, _bottomMargin - n + 1), loc (columns - 1, _bottomMargin), ' '); } void Screen::scrollDown (int n) { if (n == 0) n = 1; // Default scrollDown (_topMargin, n); } void Screen::scrollDown (int from, int n) { _scrolledLines += n; //FIXME: make sure `topMargin', `bottomMargin', `from', `n' is in bounds. if (n <= 0) return; if (from > _bottomMargin) return; if (from + n > _bottomMargin) n = _bottomMargin - from; moveImage (loc (0, from + n), loc (0, from), loc (columns - 1, _bottomMargin - n)); clearImage (loc (0, from), loc (columns - 1, from + n - 1), ' '); } void Screen::setCursorYX (int y, int x) { setCursorY (y); setCursorX (x); } void Screen::setCursorX (int x) { if (x == 0) x = 1; // Default x -= 1; // Adjust cuX = qMax (0, qMin (columns - 1, x)); } void Screen::setCursorY (int y) { if (y == 0) y = 1; // Default y -= 1; // Adjust cuY = qMax (0, qMin (lines - 1, y + (getMode (MODE_Origin) ? _topMargin : 0))); } void Screen::home () { cuX = 0; cuY = 0; } void Screen::toStartOfLine () { cuX = 0; } int Screen::getCursorX () const { return cuX; } int Screen::getCursorY () const { return cuY; } void Screen::clearImage (int loca, int loce, char c) { int scr_TL = loc (0, history->getLines ()); //FIXME: check positions //Clear entire selection if it overlaps region to be moved... if ((selBottomRight > (loca + scr_TL)) && (selTopLeft < (loce + scr_TL))) { clearSelection (); } int topLine = loca / columns; int bottomLine = loce / columns; Character clearCh (c, currentForeground, currentBackground, DEFAULT_RENDITION); //if the character being used to clear the area is the same as the //default character, the affected lines can simply be shrunk. bool isDefaultCh = (clearCh == Character ()); for (int y = topLine; y <= bottomLine; y++) { lineProperties[y] = 0; int endCol = (y == bottomLine) ? loce % columns : columns - 1; int startCol = (y == topLine) ? loca % columns : 0; QVector < Character > &line = screenLines[y]; if (isDefaultCh && endCol == columns - 1) { line.resize (startCol); } else { if (line.size () < endCol + 1) line.resize (endCol + 1); Character *data = line.data (); for (int i = startCol; i <= endCol; i++) data[i] = clearCh; } } } void Screen::moveImage (int dest, int sourceBegin, int sourceEnd) { Q_ASSERT (sourceBegin <= sourceEnd); int lines = (sourceEnd - sourceBegin) / columns; //move screen image and line properties: //the source and destination areas of the image may overlap, //so it matters that we do the copy in the right order - //forwards if dest < sourceBegin or backwards otherwise. //(search the web for 'memmove implementation' for details) if (dest < sourceBegin) { for (int i = 0; i <= lines; i++) { screenLines[(dest / columns) + i] = screenLines[(sourceBegin / columns) + i]; lineProperties[(dest / columns) + i] = lineProperties[(sourceBegin / columns) + i]; } } else { for (int i = lines; i >= 0; i--) { screenLines[(dest / columns) + i] = screenLines[(sourceBegin / columns) + i]; lineProperties[(dest / columns) + i] = lineProperties[(sourceBegin / columns) + i]; } } if (lastPos != -1) { int diff = dest - sourceBegin; // Scroll by this amount lastPos += diff; if ((lastPos < 0) || (lastPos >= (lines * columns))) lastPos = -1; } // Adjust selection to follow scroll. if (selBegin != -1) { bool beginIsTL = (selBegin == selTopLeft); int diff = dest - sourceBegin; // Scroll by this amount int scr_TL = loc (0, history->getLines ()); int srca = sourceBegin + scr_TL; // Translate index from screen to global int srce = sourceEnd + scr_TL; // Translate index from screen to global int desta = srca + diff; int deste = srce + diff; if ((selTopLeft >= srca) && (selTopLeft <= srce)) selTopLeft += diff; else if ((selTopLeft >= desta) && (selTopLeft <= deste)) selBottomRight = -1; // Clear selection (see below) if ((selBottomRight >= srca) && (selBottomRight <= srce)) selBottomRight += diff; else if ((selBottomRight >= desta) && (selBottomRight <= deste)) selBottomRight = -1; // Clear selection (see below) if (selBottomRight < 0) { clearSelection (); } else { if (selTopLeft < 0) selTopLeft = 0; } if (beginIsTL) selBegin = selTopLeft; else selBegin = selBottomRight; } } void Screen::clearToEndOfScreen () { clearImage (loc (cuX, cuY), loc (columns - 1, lines - 1), ' '); } void Screen::clearToBeginOfScreen () { clearImage (loc (0, 0), loc (cuX, cuY), ' '); } void Screen::clearEntireScreen () { // Add entire screen to history for (int i = 0; i < (lines - 1); i++) { addHistLine (); scrollUp (0, 1); } clearImage (loc (0, 0), loc (columns - 1, lines - 1), ' '); } /*! fill screen with 'E' This is to aid screen alignment */ void Screen::helpAlign () { clearImage (loc (0, 0), loc (columns - 1, lines - 1), 'E'); } void Screen::clearToEndOfLine () { clearImage (loc (cuX, cuY), loc (columns - 1, cuY), ' '); } void Screen::clearToBeginOfLine () { clearImage (loc (0, cuY), loc (cuX, cuY), ' '); } void Screen::clearEntireLine () { clearImage (loc (0, cuY), loc (columns - 1, cuY), ' '); } void Screen::setRendition (int re) { currentRendition |= re; updateEffectiveRendition (); } void Screen::resetRendition (int re) { currentRendition &= ~re; updateEffectiveRendition (); } void Screen::setDefaultRendition () { setForeColor (COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); setBackColor (COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); currentRendition = DEFAULT_RENDITION; updateEffectiveRendition (); } void Screen::setForeColor (int space, int color) { currentForeground = CharacterColor (space, color); if (currentForeground.isValid ()) updateEffectiveRendition (); else setForeColor (COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); } void Screen::setBackColor (int space, int color) { currentBackground = CharacterColor (space, color); if (currentBackground.isValid ()) updateEffectiveRendition (); else setBackColor (COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); } void Screen::clearSelection () { selBottomRight = -1; selTopLeft = -1; selBegin = -1; } void Screen::getSelectionStart (int &column, int &line) const { if (selTopLeft != -1) { column = selTopLeft % columns; line = selTopLeft / columns; } else { column = cuX + getHistLines (); line = cuY + getHistLines (); } } void Screen::getSelectionEnd (int &column, int &line) const { if (selBottomRight != -1) { column = selBottomRight % columns; line = selBottomRight / columns; } else { column = cuX + getHistLines (); line = cuY + getHistLines (); } } void Screen::setSelectionStart (const int x, const int y, const bool mode) { selBegin = loc (x, y); /* FIXME, HACK to correct for x too far to the right... */ if (x == columns) selBegin--; selBottomRight = selBegin; selTopLeft = selBegin; blockSelectionMode = mode; } void Screen::setSelectionEnd (const int x, const int y) { if (selBegin == -1) return; int endPos = loc (x, y); if (endPos < selBegin) { selTopLeft = endPos; selBottomRight = selBegin; } else { /* FIXME, HACK to correct for x too far to the right... */ if (x == columns) endPos--; selTopLeft = selBegin; selBottomRight = endPos; } // Normalize the selection in column mode if (blockSelectionMode) { int topRow = selTopLeft / columns; int topColumn = selTopLeft % columns; int bottomRow = selBottomRight / columns; int bottomColumn = selBottomRight % columns; selTopLeft = loc (qMin (topColumn, bottomColumn), topRow); selBottomRight = loc (qMax (topColumn, bottomColumn), bottomRow); } } bool Screen::isSelected (const int x, const int y) const { bool columnInSelection = true; if (blockSelectionMode) { columnInSelection = x >= (selTopLeft % columns) && x <= (selBottomRight % columns); } int pos = loc (x, y); return pos >= selTopLeft && pos <= selBottomRight && columnInSelection; } QString Screen::selectedText (bool preserveLineBreaks) const { QString result; QTextStream stream (&result, QIODevice::ReadWrite); PlainTextDecoder decoder; decoder.begin (&stream); writeSelectionToStream (&decoder, preserveLineBreaks); decoder.end (); return result; } bool Screen::isSelectionValid () const { return selTopLeft >= 0 && selBottomRight >= 0; } void Screen::writeSelectionToStream (TerminalCharacterDecoder * decoder, bool preserveLineBreaks) const { if (!isSelectionValid ()) return; writeToStream (decoder, selTopLeft, selBottomRight, preserveLineBreaks); } void Screen::writeToStream (TerminalCharacterDecoder * decoder, int startIndex, int endIndex, bool preserveLineBreaks) const { int top = startIndex / columns; int left = startIndex % columns; int bottom = endIndex / columns; int right = endIndex % columns; Q_ASSERT (top >= 0 && left >= 0 && bottom >= 0 && right >= 0); for (int y = top; y <= bottom; y++) { int start = 0; if (y == top || blockSelectionMode) start = left; int count = -1; if (y == bottom || blockSelectionMode) count = right - start + 1; const bool appendNewLine = (y != bottom); int copied = copyLineToStream (y, start, count, decoder, appendNewLine, preserveLineBreaks); // if the selection goes beyond the end of the last line then // append a new line character. // // this makes it possible to 'select' a trailing new line character after // the text on a line. if (y == bottom && copied < count) { Character newLineChar ('\n'); decoder->decodeLine (&newLineChar, 1, 0); } } } int Screen::copyLineToStream (int line, int start, int count, TerminalCharacterDecoder * decoder, bool appendNewLine, bool preserveLineBreaks) const { //buffer to hold characters for decoding //the buffer is static to avoid initialising every //element on each call to copyLineToStream //(which is unnecessary since all elements will be overwritten anyway) static const int MAX_CHARS = 1024; static Character characterBuffer[MAX_CHARS]; assert (count < MAX_CHARS); LineProperty currentLineProperties = 0; //determine if the line is in the history buffer or the screen image if (line < history->getLines ()) { const int lineLength = history->getLineLen (line); // ensure that start position is before end of line start = qMin (start, qMax (0, lineLength - 1)); // retrieve line from history buffer. It is assumed // that the history buffer does not store trailing white space // at the end of the line, so it does not need to be trimmed here if (count == -1) { count = lineLength - start; } else { count = qMin (start + count, lineLength) - start; } // safety checks assert (start >= 0); assert (count >= 0); assert ((start + count) <= history->getLineLen (line)); history->getCells (line, start, count, characterBuffer); if (history->isWrappedLine (line)) currentLineProperties |= LINE_WRAPPED; } else { if (count == -1) count = columns - start; assert (count >= 0); const int screenLine = line - history->getLines (); Character *data = screenLines[screenLine].data (); int length = screenLines[screenLine].count (); //retrieve line from screen image for (int i = start; i < qMin (start + count, length); i++) { characterBuffer[i - start] = data[i]; } // count cannot be any greater than length count = qBound (0, count, length - start); Q_ASSERT (screenLine < lineProperties.count ()); currentLineProperties |= lineProperties[screenLine]; } // add new line character at end const bool omitLineBreak = (currentLineProperties & LINE_WRAPPED) || !preserveLineBreaks; if (!omitLineBreak && appendNewLine && (count + 1 < MAX_CHARS)) { characterBuffer[count] = '\n'; count++; } //decode line and write to text stream decoder->decodeLine ((Character *) characterBuffer, count, currentLineProperties); return count; } void Screen::writeLinesToStream (TerminalCharacterDecoder * decoder, int fromLine, int toLine) const { writeToStream (decoder, loc (0, fromLine), loc (columns - 1, toLine)); } void Screen::addHistLine () { // add line to history buffer // we have to take care about scrolling, too... if (hasScroll ()) { int oldHistLines = history->getLines (); history->addCellsVector (screenLines[0]); history->addLine (lineProperties[0] & LINE_WRAPPED); int newHistLines = history->getLines (); bool beginIsTL = (selBegin == selTopLeft); // If the history is full, increment the count // of dropped lines if (newHistLines == oldHistLines) _droppedLines++; // Adjust selection for the new point of reference if (newHistLines > oldHistLines) { if (selBegin != -1) { selTopLeft += columns; selBottomRight += columns; } } if (selBegin != -1) { // Scroll selection in history up int top_BR = loc (0, 1 + newHistLines); if (selTopLeft < top_BR) selTopLeft -= columns; if (selBottomRight < top_BR) selBottomRight -= columns; if (selBottomRight < 0) clearSelection (); else { if (selTopLeft < 0) selTopLeft = 0; } if (beginIsTL) selBegin = selTopLeft; else selBegin = selBottomRight; } } } int Screen::getHistLines () const { return history->getLines (); } void Screen::setScroll (const HistoryType & t, bool copyPreviousScroll) { clearSelection (); if (copyPreviousScroll) history = t.scroll (history); else { HistoryScroll *oldScroll = history; history = t.scroll (0); delete oldScroll; } } bool Screen::hasScroll () const { return history->hasScroll (); } const HistoryType & Screen::getScroll () const { return history->getType (); } void Screen::setLineProperty (LineProperty property, bool enable) { if (enable) lineProperties[cuY] = (LineProperty) (lineProperties[cuY] | property); else lineProperties[cuY] = (LineProperty) (lineProperties[cuY] & ~property); } void Screen::fillWithDefaultChar (Character * dest, int count) { for (int i = 0; i < count; i++) dest[i] = defaultChar; }