You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qtermwidget-packaging/lib/Screen.cpp

1389 lines
35 KiB

/*
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 <string.h>
#include <ctype.h>
// Qt
#include <QTextStream>
#include <QDate>
// KDE
//#include <kdebug.h>
// 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),
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::repeatChars(int count)
//=REP
{
if (count == 0)
{
count = 1;
}
/**
* From ECMA-48 version 5, section 8.3.103
* If the character preceding REP is a control function or part of a
* control function, the effect of REP is not defined by this Standard.
*
* So, a "normal" program should always use REP immediately after a visible
* character (those other than escape sequences). So, lastDrawnChar can be
* safely used.
*/
for (int i = 0; i < count; i++)
{
displayCharacter(lastDrawnChar);
}
}
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,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.setIntensive();
}
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(wchar_t 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;
lastDrawnChar = c;
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)
return;
if (from > _bottomMargin)
return;
if (from + n > _bottomMargin)
n = _bottomMargin + 1 - from;
_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,_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];
Q_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
Q_ASSERT( start >= 0 );
Q_ASSERT( count >= 0 );
Q_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;
Q_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;
}