/* This file is part of Konsole, an X terminal. Copyright (C) 1997,1998 by Lars Doelle Rewritten for QT4 by e_k , Copyright (C)2008 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 #include #include #include #include #include // Qt #include #include // Konsole #include "konsole_wcwidth.h" #include "TerminalCharacterDecoder.h" using namespace Konsole; //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), hist(new HistoryScrollNone()), cuX(0), cuY(0), cu_re(0), tmargin(0), bmargin(0), tabstops(0), sel_begin(0), sel_TL(0), sel_BR(0), sel_busy(false), columnmode(false), ef_fg(CharacterColor()), ef_bg(CharacterColor()), ef_re(0), sa_cuX(0), sa_cuY(0), sa_cu_re(0), lastPos(-1) { lineProperties.resize(lines+1); for (int i=0;i bmargin ? lines-1 : bmargin; cuX = qMin(columns-1,cuX); // nowrap! cuY = qMin(stop,cuY+n); } /*! Move the cursor left. The cursor will not move beyond the first column. */ void Screen::cursorLeft(int n) //=CUB { if (n == 0) n = 1; // Default cuX = qMin(columns-1,cuX); // nowrap! cuX = qMax(0,cuX-n); } /*! Move the cursor left. The cursor will not move beyond the rightmost column. */ 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 ) ) { qDebug()<<" setRegion("< 0) cuY -= 1; } /*! Move the cursor to the begin of the next line. If cursor is on bottom margin, the region between the actual top and bottom margin is scrolled up. */ void Screen::NextLine() //=NEL { Return(); 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() - 1 - 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); } /*! insert `n' lines at the cursor position. The cursor is not moved by the operation. */ void Screen::insertLines(int n) { if (n == 0) n = 1; // Default scrollDown(cuY,n); } // Mode Operations ----------------------------------------------------------- /*! Set a specific mode. */ void Screen::setMode(int m) { currParm.mode[m] = true; switch(m) { case MODE_Origin : cuX = 0; cuY = tmargin; break; //FIXME: home } } /*! Reset a specific mode. */ void Screen::resetMode(int m) { currParm.mode[m] = false; switch(m) { case MODE_Origin : cuX = 0; cuY = 0; break; //FIXME: home } } /*! Save a specific mode. */ void Screen::saveMode(int m) { saveParm.mode[m] = currParm.mode[m]; } /*! Restore a specific mode. */ void Screen::restoreMode(int m) { currParm.mode[m] = saveParm.mode[m]; } bool Screen::getMode(int m) const { return currParm.mode[m]; } void Screen::saveCursor() { sa_cuX = cuX; sa_cuY = cuY; sa_cu_re = cu_re; sa_cu_fg = cu_fg; sa_cu_bg = cu_bg; } void Screen::restoreCursor() { cuX = qMin(sa_cuX,columns-1); cuY = qMin(sa_cuY,lines-1); cu_re = sa_cu_re; cu_fg = sa_cu_fg; cu_bg = sa_cu_bg; effectiveRendition(); } /* ------------------------------------------------------------------------- */ /* */ /* Screen Operations */ /* */ /* ------------------------------------------------------------------------- */ /*! Resize the screen image The topmost left position is maintained, while lower lines or right hand side columns might be removed or filled with spaces to fit the new size. The region setting is reset to the whole screen and the tab positions reinitialized. If the new image is narrower than the old image then text on lines which extends past the end of the new image is preserved so that it becomes visible again if the screen is later resized to make it larger. */ 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 bmargin = 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 0) && (ir &= ~RE_TRANSPARENT; } void Screen::effectiveRendition() // calculate rendition { //copy "current rendition" straight into "effective rendition", which is then later copied directly //into the image[] array which holds the characters and their appearance properties. //- The old version below filtered out all attributes other than underline and blink at this stage, //so that they would not be copied into the image[] array and hence would not be visible by TerminalDisplay //which actually paints the screen using the information from the image[] array. //I don't know why it did this, but I'm fairly sure it was the wrong thing to do. The net result //was that bold text wasn't printed in bold by Konsole. ef_re = cu_re; //OLD VERSION: //ef_re = cu_re & (RE_UNDERLINE | RE_BLINK); if (cu_re & RE_REVERSE) { ef_fg = cu_bg; ef_bg = cu_fg; } else { ef_fg = cu_fg; ef_bg = cu_bg; } if (cu_re & RE_BOLD) ef_fg.toggleIntensive(); } /*! returns the image. Get the size of the image by \sa getLines and \sa getColumns. NOTE that the image returned by this function must later be freed. */ void Screen::copyFromHistory(Character* dest, int startLine, int count) const { Q_ASSERT( startLine >= 0 && count > 0 && startLine + count <= hist->getLines() ); for (int line = startLine; line < startLine + count; line++) { const int length = qMin(columns,hist->getLineLen(line)); const int destLineOffset = (line-startLine)*columns; hist->getCells(line,0,length,dest + destLineOffset); for (int column = length; column < columns; column++) dest[destLineOffset+column] = defaultChar; // invert selected text if (sel_begin !=-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 (sel_begin != -1 && isSelected(column,line + hist->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 < hist->getLines() + lines ); const int mergedLines = endLine - startLine + 1; Q_ASSERT( size >= mergedLines * columns ); const int linesInHistoryBuffer = qBound(0,hist->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 - hist->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 Screen::getLineProperties( int startLine , int endLine ) const { Q_ASSERT( startLine >= 0 ); Q_ASSERT( endLine >= startLine && endLine < hist->getLines() + lines ); const int mergedLines = endLine-startLine+1; const int linesInHistory = qBound(0,hist->getLines()-startLine,mergedLines); const int linesInScreen = mergedLines - linesInHistory; QVector 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 (hist->isWrappedLine(line)) { result[index] = (LineProperty)(result[index] | LINE_WRAPPED); } index++; } // copy properties for lines in screen buffer const int firstScreenLine = startLine + linesInHistory - hist->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); tmargin=0; bmargin=lines-1; setDefaultRendition(); saveCursor(); if ( clearScreen ) clear(); } /*! Clear the entire screen and home the cursor. */ void Screen::clear() { clearEntireScreen(); home(); } void Screen::BackSpace() { cuX = qMin(columns-1,cuX); // nowrap! cuX = qMax(0,cuX-1); // if (BS_CLEARS) image[loc(cuX,cuY)].character = ' '; if (screenLines[cuY].size() < cuX+1) screenLines[cuY].resize(cuX+1); if (BS_CLEARS) screenLines[cuY][cuX].character = ' '; } void Screen::Tabulate(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::backTabulate(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() { delete[] tabstops; tabstops = new bool[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); } /*! This behaves either as IND (Screen::Index) or as NEL (Screen::NextLine) depending on the NewLine Mode (LNM). This mode also affects the key sequence returned for newline ([CR]LF). */ void Screen::NewLine() { if (getMode(MODE_NewLine)) Return(); index(); } /*! put `c' literally onto the screen at the current cursor position. VT100 uses the convention to produce an automatic newline (am) with the *first* character that would fall onto the next line (xenl). */ void Screen::checkSelection(int from, int to) { if (sel_begin == -1) return; int scr_TL = loc(0, hist->getLines()); //Clear entire selection if it overlaps region [from, to] if ( (sel_BR > (from+scr_TL) )&&(sel_TL < (to+scr_TL)) ) { clearSelection(); } } void Screen::ShowCharacter(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 == 0 && cuY > 0) { screenLines[cuY].resize( qMax(screenLines[cuY-1].size() , cuX+w) ); } else { 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(cuX,cuY); Character& currentChar = screenLines[cuY][cuX]; currentChar.character = c; currentChar.foregroundColor = ef_fg; currentChar.backgroundColor = ef_bg; currentChar.rendition = ef_re; 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 = ef_fg; ch.backgroundColor = ef_bg; ch.rendition = ef_re; 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() { //kDebug() << "scrolled lines reset"; _scrolledLines = 0; } // Region commands ------------------------------------------------------------- void Screen::scrollUp(int n) { if (n == 0) n = 1; // Default if (tmargin == 0) addHistLine(); // hist.history scrollUp(tmargin, n); } /*! scroll up `n' lines within current region. The `n' new lines are cleared. \sa setRegion \sa scrollDown */ QRect Screen::lastScrolledRegion() const { return _lastScrolledRegion; } void Screen::scrollUp(int from, int n) { if (n <= 0 || from + n > bmargin) return; _scrolledLines -= n; _lastScrolledRegion = QRect(0,tmargin,columns-1,(bmargin-tmargin)); //FIXME: make sure `tmargin', `bmargin', `from', `n' is in bounds. moveImage(loc(0,from),loc(0,from+n),loc(columns-1,bmargin)); clearImage(loc(0,bmargin-n+1),loc(columns-1,bmargin),' '); } void Screen::scrollDown(int n) { if (n == 0) n = 1; // Default scrollDown(tmargin, n); } /*! scroll down `n' lines within current region. The `n' new lines are cleared. \sa setRegion \sa scrollUp */ void Screen::scrollDown(int from, int n) { //kDebug() << "Screen::scrollDown( from: " << from << " , n: " << n << ")"; _scrolledLines += n; //FIXME: make sure `tmargin', `bmargin', `from', `n' is in bounds. if (n <= 0) return; if (from > bmargin) return; if (from + n > bmargin) n = bmargin - from; moveImage(loc(0,from+n),loc(0,from),loc(columns-1,bmargin-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) ? tmargin : 0) )); } void Screen::home() { cuX = 0; cuY = 0; } void Screen::Return() { cuX = 0; } int Screen::getCursorX() const { return cuX; } int Screen::getCursorY() const { return cuY; } // Erasing --------------------------------------------------------------------- /*! \section Erasing This group of operations erase parts of the screen contents by filling it with spaces colored due to the current rendition settings. Althought the cursor position is involved in most of these operations, it is never modified by them. */ /*! fill screen between (including) `loca' (start) and `loce' (end) with spaces. This is an internal helper functions. The parameter types are internal addresses of within the screen image and make use of the way how the screen matrix is mapped to the image vector. */ void Screen::clearImage(int loca, int loce, char c) { int scr_TL=loc(0,hist->getLines()); //FIXME: check positions //Clear entire selection if it overlaps region to be moved... if ( (sel_BR > (loca+scr_TL) )&&(sel_TL < (loce+scr_TL)) ) { clearSelection(); } int topLine = loca/columns; int bottomLine = loce/columns; Character clearCh(c,cu_fg,cu_bg,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& 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; } } } /*! move image between (including) `sourceBegin' and `sourceEnd' to 'dest'. The 'dest', 'sourceBegin' and 'sourceEnd' parameters can be generated using the loc(column,line) macro. NOTE: moveImage() can only move whole lines. This is an internal helper functions. The parameter types are internal addresses of within the screen image and make use of the way how the screen matrix is mapped to the image vector. */ void Screen::moveImage(int dest, int sourceBegin, int sourceEnd) { //kDebug() << "moving image from (" << (sourceBegin/columns) // << "," << (sourceEnd/columns) << ") to " << // (dest/columns); 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 (sel_begin != -1) { bool beginIsTL = (sel_begin == sel_TL); int diff = dest - sourceBegin; // Scroll by this amount int scr_TL=loc(0,hist->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 ((sel_TL >= srca) && (sel_TL <= srce)) sel_TL += diff; else if ((sel_TL >= desta) && (sel_TL <= deste)) sel_BR = -1; // Clear selection (see below) if ((sel_BR >= srca) && (sel_BR <= srce)) sel_BR += diff; else if ((sel_BR >= desta) && (sel_BR <= deste)) sel_BR = -1; // Clear selection (see below) if (sel_BR < 0) { clearSelection(); } else { if (sel_TL < 0) sel_TL = 0; } if (beginIsTL) sel_begin = sel_TL; else sel_begin = sel_BR; } } 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) { cu_re |= re; effectiveRendition(); } void Screen::resetRendition(int re) { cu_re &= ~re; effectiveRendition(); } void Screen::setDefaultRendition() { setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); cu_re = DEFAULT_RENDITION; effectiveRendition(); } void Screen::setForeColor(int space, int color) { cu_fg = CharacterColor(space, color); if ( cu_fg.isValid() ) effectiveRendition(); else setForeColor(COLOR_SPACE_DEFAULT,DEFAULT_FORE_COLOR); } void Screen::setBackColor(int space, int color) { cu_bg = CharacterColor(space, color); if ( cu_bg.isValid() ) effectiveRendition(); else setBackColor(COLOR_SPACE_DEFAULT,DEFAULT_BACK_COLOR); } /* ------------------------------------------------------------------------- */ /* */ /* Marking & Selection */ /* */ /* ------------------------------------------------------------------------- */ void Screen::clearSelection() { sel_BR = -1; sel_TL = -1; sel_begin = -1; } void Screen::getSelectionStart(int& column , int& line) { if ( sel_TL != -1 ) { column = sel_TL % columns; line = sel_TL / columns; } else { column = cuX + getHistLines(); line = cuY + getHistLines(); } } void Screen::getSelectionEnd(int& column , int& line) { if ( sel_BR != -1 ) { column = sel_BR % columns; line = sel_BR / columns; } else { column = cuX + getHistLines(); line = cuY + getHistLines(); } } void Screen::setSelectionStart(/*const ScreenCursor& viewCursor ,*/ const int x, const int y, const bool mode) { // kDebug(1211) << "setSelBeginXY(" << x << "," << y << ")"; sel_begin = loc(x,y); //+histCursor) ; /* FIXME, HACK to correct for x too far to the right... */ if (x == columns) sel_begin--; sel_BR = sel_begin; sel_TL = sel_begin; columnmode = mode; } void Screen::setSelectionEnd( const int x, const int y) { // kDebug(1211) << "setSelExtentXY(" << x << "," << y << ")"; if (sel_begin == -1) return; int l = loc(x,y); // + histCursor); if (l < sel_begin) { sel_TL = l; sel_BR = sel_begin; } else { /* FIXME, HACK to correct for x too far to the right... */ if (x == columns) l--; sel_TL = sel_begin; sel_BR = l; } } bool Screen::isSelected( const int x,const int y) const { if (columnmode) { int sel_Left,sel_Right; if ( sel_TL % columns < sel_BR % columns ) { sel_Left = sel_TL; sel_Right = sel_BR; } else { sel_Left = sel_BR; sel_Right = sel_TL; } return ( x >= sel_Left % columns ) && ( x <= sel_Right % columns ) && ( y >= sel_TL / columns ) && ( y <= sel_BR / columns ); //( y+histCursor >= sel_TL / columns ) && ( y+histCursor <= sel_BR / columns ); } else { //int pos = loc(x,y+histCursor); int pos = loc(x,y); return ( pos >= sel_TL && pos <= sel_BR ); } } QString Screen::selectedText(bool preserveLineBreaks) { QString result; QTextStream stream(&result, QIODevice::ReadWrite); PlainTextDecoder decoder; decoder.begin(&stream); writeSelectionToStream(&decoder , preserveLineBreaks); decoder.end(); return result; } bool Screen::isSelectionValid() const { return ( sel_TL >= 0 && sel_BR >= 0 ); } void Screen::writeSelectionToStream(TerminalCharacterDecoder* decoder , bool preserveLineBreaks) { // do nothing if selection is invalid if ( !isSelectionValid() ) return; int top = sel_TL / columns; int left = sel_TL % columns; int bottom = sel_BR / columns; int right = sel_BR % columns; Q_ASSERT( top >= 0 && left >= 0 && bottom >= 0 && right >= 0 ); //kDebug() << "sel_TL = " << sel_TL; //kDebug() << "columns = " << columns; for (int y=top;y<=bottom;y++) { int start = 0; if ( y == top || columnmode ) start = left; int count = -1; if ( y == bottom || columnmode ) count = right - start + 1; const bool appendNewLine = ( y != bottom ); copyLineToStream( y, start, count, decoder, appendNewLine, preserveLineBreaks ); } } void Screen::copyLineToStream(int line , int start, int count, TerminalCharacterDecoder* decoder, bool appendNewLine, bool preserveLineBreaks) { //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 < hist->getLines()) { const int lineLength = hist->getLineLen(line); // ensure that start position is before end of line start = qMin(start,qMax(0,lineLength-1)); //retrieve line from history buffer if (count == -1) { count = lineLength-start; } else { count = qMin(start+count,lineLength)-start; } // safety checks assert( start >= 0 ); assert( count >= 0 ); assert( (start+count) <= hist->getLineLen(line) ); hist->getCells(line,start,count,characterBuffer); if ( hist->isWrappedLine(line) ) currentLineProperties |= LINE_WRAPPED; } else { if ( count == -1 ) count = columns - start; assert( count >= 0 ); const int screenLine = line-hist->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]; } //do not decode trailing whitespace characters for (int i=count-1 ; i >= 0; i--) if (QChar(characterBuffer[i].character).isSpace()) count--; else break; // 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 ); } // Method below has been removed because of its reliance on 'histCursor' // and I want to restrict the methods which have knowledge of the scroll position // to just those which deal with selection and supplying final screen images. // /*void Screen::writeToStream(QTextStream* stream , TerminalCharacterDecoder* decoder) { sel_begin = 0; sel_BR = sel_begin; sel_TL = sel_begin; setSelectionEnd(columns-1,lines-1+hist->getLines()-histCursor); writeSelectionToStream(stream,decoder); clearSelection(); }*/ void Screen::writeToStream(TerminalCharacterDecoder* decoder, int from, int to) { sel_begin = loc(0,from); sel_TL = sel_begin; sel_BR = loc(columns-1,to); writeSelectionToStream(decoder); clearSelection(); } QString Screen::getHistoryLine(int no) { sel_begin = loc(0,no); sel_TL = sel_begin; sel_BR = loc(columns-1,no); return selectedText(false); } void Screen::addHistLine() { // add line to history buffer // we have to take care about scrolling, too... if (hasScroll()) { int oldHistLines = hist->getLines(); hist->addCellsVector(screenLines[0]); hist->addLine( lineProperties[0] & LINE_WRAPPED ); int newHistLines = hist->getLines(); bool beginIsTL = (sel_begin == sel_TL); // 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 (sel_begin != -1) { sel_TL += columns; sel_BR += columns; } } if (sel_begin != -1) { // Scroll selection in history up int top_BR = loc(0, 1+newHistLines); if (sel_TL < top_BR) sel_TL -= columns; if (sel_BR < top_BR) sel_BR -= columns; if (sel_BR < 0) { clearSelection(); } else { if (sel_TL < 0) sel_TL = 0; } if (beginIsTL) sel_begin = sel_TL; else sel_begin = sel_BR; } } } int Screen::getHistLines() { return hist->getLines(); } void Screen::setScroll(const HistoryType& t , bool copyPreviousScroll) { clearSelection(); if ( copyPreviousScroll ) hist = t.scroll(hist); else { HistoryScroll* oldScroll = hist; hist = t.scroll(0); delete oldScroll; } } bool Screen::hasScroll() { return hist->hasScroll(); } const HistoryType& Screen::getScroll() { return hist->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