summaryrefslogtreecommitdiff
path: root/qtermwidget/TerminalDisplay.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'qtermwidget/TerminalDisplay.cpp')
-rw-r--r--qtermwidget/TerminalDisplay.cpp2724
1 files changed, 2724 insertions, 0 deletions
diff --git a/qtermwidget/TerminalDisplay.cpp b/qtermwidget/TerminalDisplay.cpp
new file mode 100644
index 0000000..1fcbd09
--- /dev/null
+++ b/qtermwidget/TerminalDisplay.cpp
@@ -0,0 +1,2724 @@
+/*
+ This file is part of Konsole, a terminal emulator for KDE.
+
+ Copyright (C) 2006-7 by Robert Knight <robertknight@gmail.com>
+ Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
+
+ Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, 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 "TerminalDisplay.h"
+
+// Qt
+#include <QtGui/QApplication>
+#include <QtGui/QBoxLayout>
+#include <QtGui/QClipboard>
+#include <QtGui/QKeyEvent>
+#include <QtCore/QEvent>
+#include <QtCore/QTime>
+#include <QtCore/QFile>
+#include <QtGui/QGridLayout>
+#include <QtGui/QLabel>
+#include <QtGui/QLayout>
+#include <QtGui/QPainter>
+#include <QtGui/QPixmap>
+#include <QtGui/QScrollBar>
+#include <QtGui/QStyle>
+#include <QtCore>
+#include <QtGui>
+
+#include "Filter.h"
+#include "konsole_wcwidth.h"
+#include "ScreenWindow.h"
+#include "TerminalCharacterDecoder.h"
+#include "ColorTables.h"
+
+using namespace Konsole;
+
+#ifndef loc
+#define loc(X,Y) ((Y)*_columns+(X))
+#endif
+
+#define yMouseScroll 1
+
+#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ "abcdefgjijklmnopqrstuvwxyz" \
+ "0123456789./+@"
+
+// scroll increment used when dragging selection at top/bottom of window.
+
+// static
+bool TerminalDisplay::_antialiasText = true;
+bool TerminalDisplay::HAVE_TRANSPARENCY = false;
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Colors */
+/* */
+/* ------------------------------------------------------------------------- */
+
+/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
+
+ Code 0 1 2 3 4 5 6 7
+ ----------- ------- ------- ------- ------- ------- ------- ------- -------
+ ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
+ IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
+*/
+
+ScreenWindow* TerminalDisplay::screenWindow() const
+{
+ return _screenWindow;
+}
+void TerminalDisplay::setScreenWindow(ScreenWindow* window)
+{
+ // disconnect existing screen window if any
+ if ( _screenWindow )
+ {
+ disconnect( _screenWindow , 0 , this , 0 );
+ }
+
+ _screenWindow = window;
+
+ if ( window )
+ {
+//#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?"
+ connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) );
+ connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) );
+ window->setWindowLines(_lines);
+ }
+}
+
+const ColorEntry* TerminalDisplay::colorTable() const
+{
+ return _colorTable;
+}
+
+void TerminalDisplay::setColorTable(const ColorEntry table[])
+{
+ for (int i = 0; i < TABLE_COLORS; i++)
+ _colorTable[i] = table[i];
+
+ QPalette p = palette();
+ p.setColor( backgroundRole(), _colorTable[DEFAULT_BACK_COLOR].color );
+ setPalette( p );
+
+ // Avoid propagating the palette change to the scroll bar
+ _scrollBar->setPalette( QApplication::palette() );
+
+ update();
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Font */
+/* */
+/* ------------------------------------------------------------------------- */
+
+/*
+ The VT100 has 32 special graphical characters. The usual vt100 extended
+ xterm fonts have these at 0x00..0x1f.
+
+ QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
+ come in here as proper unicode characters.
+
+ We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
+ from unicode to 0x00..0x1f. The remaining translation is then left to the
+ QCodec.
+*/
+
+static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);}
+static inline bool isLineCharString(const QString& string)
+{
+ return (string.length() > 0) && (isLineChar(string.at(0).unicode()));
+}
+
+
+// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
+
+unsigned short Konsole::vt100_graphics[32] =
+{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15
+ 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
+ 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
+ 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
+ 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
+};
+
+void TerminalDisplay::fontChange(const QFont&)
+{
+ QFontMetrics fm(font());
+ _fontHeight = fm.height() + _lineSpacing;
+
+
+ // waba TerminalDisplay 1.123:
+ // "Base character width on widest ASCII character. This prevents too wide
+ // characters in the presence of double wide (e.g. Japanese) characters."
+ // Get the width from representative normal width characters
+ _fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR));
+
+ _fixedFont = true;
+
+ int fw = fm.width(REPCHAR[0]);
+ for(unsigned int i=1; i< strlen(REPCHAR); i++)
+ {
+ if (fw != fm.width(REPCHAR[i]))
+ {
+ _fixedFont = false;
+ break;
+ }
+ }
+
+ if (_fontWidth < 1)
+ _fontWidth=1;
+
+ _fontAscent = fm.ascent();
+
+ emit changedFontMetricSignal( _fontHeight, _fontWidth );
+ propagateSize();
+ update();
+}
+
+void TerminalDisplay::setVTFont(const QFont& f)
+{
+ QFont font = f;
+
+ QFontMetrics metrics(font);
+
+ if ( metrics.height() < height() && metrics.maxWidth() < width() )
+ {
+ // hint that text should be drawn without anti-aliasing.
+ // depending on the user's font configuration, this may not be respected
+ if (!_antialiasText)
+ font.setStyleStrategy( QFont::NoAntialias );
+
+ // experimental optimization. Konsole assumes that the terminal is using a
+ // mono-spaced font, in which case kerning information should have an effect.
+ // Disabling kerning saves some computation when rendering text.
+ font.setKerning(false);
+
+ QWidget::setFont(font);
+ fontChange(font);
+ }
+}
+
+void TerminalDisplay::setFont(const QFont &)
+{
+ // ignore font change request if not coming from konsole itself
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Constructor / Destructor */
+/* */
+/* ------------------------------------------------------------------------- */
+
+TerminalDisplay::TerminalDisplay(QWidget *parent)
+:QWidget(parent)
+,_screenWindow(0)
+,_allowBell(true)
+,_gridLayout(0)
+,_fontHeight(1)
+,_fontWidth(1)
+,_fontAscent(1)
+,_lines(1)
+,_columns(1)
+,_usedLines(1)
+,_usedColumns(1)
+,_contentHeight(1)
+,_contentWidth(1)
+,_image(0)
+,_randomSeed(0)
+,_resizing(false)
+,_terminalSizeHint(false)
+,_terminalSizeStartup(true)
+,_bidiEnabled(false)
+,_actSel(0)
+,_wordSelectionMode(false)
+,_lineSelectionMode(false)
+,_preserveLineBreaks(false)
+,_columnSelectionMode(false)
+,_scrollbarLocation(NoScrollBar)
+,_wordCharacters(":@-./_~")
+,_bellMode(SystemBeepBell)
+,_blinking(false)
+,_cursorBlinking(false)
+,_hasBlinkingCursor(false)
+,_ctrlDrag(false)
+,_tripleClickMode(SelectWholeLine)
+,_isFixedSize(false)
+,_possibleTripleClick(false)
+,_resizeWidget(0)
+,_resizeTimer(0)
+,_flowControlWarningEnabled(false)
+,_outputSuspendedLabel(0)
+,_lineSpacing(0)
+,_colorsInverted(false)
+,_blendColor(qRgba(0,0,0,0xff))
+,_filterChain(new TerminalImageFilterChain())
+,_cursorShape(BlockCursor)
+{
+ // terminal applications are not designed with Right-To-Left in mind,
+ // so the layout is forced to Left-To-Right
+ setLayoutDirection(Qt::LeftToRight);
+
+ // The offsets are not yet calculated.
+ // Do not calculate these too often to be more smoothly when resizing
+ // konsole in opaque mode.
+ _topMargin = DEFAULT_TOP_MARGIN;
+ _leftMargin = DEFAULT_LEFT_MARGIN;
+
+ // create scroll bar for scrolling output up and down
+ // set the scroll bar's slider to occupy the whole area of the scroll bar initially
+ _scrollBar = new QScrollBar(this);
+ setScroll(0,0);
+ _scrollBar->setCursor( Qt::ArrowCursor );
+ connect(_scrollBar, SIGNAL(valueChanged(int)), this,
+ SLOT(scrollBarPositionChanged(int)));
+
+ // setup timers for blinking cursor and text
+ _blinkTimer = new QTimer(this);
+ connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
+ _blinkCursorTimer = new QTimer(this);
+ connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
+
+// QCursor::setAutoHideCursor( this, true );
+
+ setUsesMouse(true);
+ setColorTable(whiteonblack_color_table);
+// setColorTable(blackonlightyellow_color_table);
+ setMouseTracking(true);
+
+ // Enable drag and drop
+ setAcceptDrops(true); // attempt
+ dragInfo.state = diNone;
+
+ setFocusPolicy( Qt::WheelFocus );
+
+ // enable input method support
+ setAttribute(Qt::WA_InputMethodEnabled, true);
+
+ // this is an important optimization, it tells Qt
+ // that TerminalDisplay will handle repainting its entire area.
+ setAttribute(Qt::WA_OpaquePaintEvent);
+
+ _gridLayout = new QGridLayout(this);
+ _gridLayout->setMargin(0);
+
+ setLayout( _gridLayout );
+
+ //set up a warning message when the user presses Ctrl+S to avoid confusion
+ connect( this,SIGNAL(flowControlKeyPressed(bool)),this,SLOT(outputSuspended(bool)) );
+}
+
+TerminalDisplay::~TerminalDisplay()
+{
+ qApp->removeEventFilter( this );
+
+ delete[] _image;
+
+ delete _gridLayout;
+ delete _outputSuspendedLabel;
+ delete _filterChain;
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Display Operations */
+/* */
+/* ------------------------------------------------------------------------- */
+
+/**
+ A table for emulating the simple (single width) unicode drawing chars.
+ It represents the 250x - 257x glyphs. If it's zero, we can't use it.
+ if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
+ 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
+
+ Then, the pixels basically have the following interpretation:
+ _|||_
+ -...-
+ -...-
+ -...-
+ _|||_
+
+where _ = none
+ | = vertical line.
+ - = horizontal line.
+ */
+
+
+enum LineEncode
+{
+ TopL = (1<<1),
+ TopC = (1<<2),
+ TopR = (1<<3),
+
+ LeftT = (1<<5),
+ Int11 = (1<<6),
+ Int12 = (1<<7),
+ Int13 = (1<<8),
+ RightT = (1<<9),
+
+ LeftC = (1<<10),
+ Int21 = (1<<11),
+ Int22 = (1<<12),
+ Int23 = (1<<13),
+ RightC = (1<<14),
+
+ LeftB = (1<<15),
+ Int31 = (1<<16),
+ Int32 = (1<<17),
+ Int33 = (1<<18),
+ RightB = (1<<19),
+
+ BotL = (1<<21),
+ BotC = (1<<22),
+ BotR = (1<<23)
+};
+
+#include "LineFont.h"
+
+static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
+{
+ //Calculate cell midpoints, end points.
+ int cx = x + w/2;
+ int cy = y + h/2;
+ int ex = x + w - 1;
+ int ey = y + h - 1;
+
+ quint32 toDraw = LineChars[code];
+
+ //Top _lines:
+ if (toDraw & TopL)
+ paint.drawLine(cx-1, y, cx-1, cy-2);
+ if (toDraw & TopC)
+ paint.drawLine(cx, y, cx, cy-2);
+ if (toDraw & TopR)
+ paint.drawLine(cx+1, y, cx+1, cy-2);
+
+ //Bot _lines:
+ if (toDraw & BotL)
+ paint.drawLine(cx-1, cy+2, cx-1, ey);
+ if (toDraw & BotC)
+ paint.drawLine(cx, cy+2, cx, ey);
+ if (toDraw & BotR)
+ paint.drawLine(cx+1, cy+2, cx+1, ey);
+
+ //Left _lines:
+ if (toDraw & LeftT)
+ paint.drawLine(x, cy-1, cx-2, cy-1);
+ if (toDraw & LeftC)
+ paint.drawLine(x, cy, cx-2, cy);
+ if (toDraw & LeftB)
+ paint.drawLine(x, cy+1, cx-2, cy+1);
+
+ //Right _lines:
+ if (toDraw & RightT)
+ paint.drawLine(cx+2, cy-1, ex, cy-1);
+ if (toDraw & RightC)
+ paint.drawLine(cx+2, cy, ex, cy);
+ if (toDraw & RightB)
+ paint.drawLine(cx+2, cy+1, ex, cy+1);
+
+ //Intersection points.
+ if (toDraw & Int11)
+ paint.drawPoint(cx-1, cy-1);
+ if (toDraw & Int12)
+ paint.drawPoint(cx, cy-1);
+ if (toDraw & Int13)
+ paint.drawPoint(cx+1, cy-1);
+
+ if (toDraw & Int21)
+ paint.drawPoint(cx-1, cy);
+ if (toDraw & Int22)
+ paint.drawPoint(cx, cy);
+ if (toDraw & Int23)
+ paint.drawPoint(cx+1, cy);
+
+ if (toDraw & Int31)
+ paint.drawPoint(cx-1, cy+1);
+ if (toDraw & Int32)
+ paint.drawPoint(cx, cy+1);
+ if (toDraw & Int33)
+ paint.drawPoint(cx+1, cy+1);
+
+}
+
+void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const QString& str,
+ const Character* attributes)
+{
+ const QPen& currentPen = painter.pen();
+
+ if ( attributes->rendition & RE_BOLD )
+ {
+ QPen boldPen(currentPen);
+ boldPen.setWidth(3);
+ painter.setPen( boldPen );
+ }
+
+ for (int i=0 ; i < str.length(); i++)
+ {
+ uchar code = str[i].cell();
+ if (LineChars[code])
+ drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code);
+ }
+
+ painter.setPen( currentPen );
+}
+
+void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape)
+{
+ _cursorShape = shape;
+}
+TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const
+{
+ return _cursorShape;
+}
+void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color)
+{
+ if (useForegroundColor)
+ _cursorColor = QColor(); // an invalid color means that
+ // the foreground color of the
+ // current character should
+ // be used
+
+ else
+ _cursorColor = color;
+}
+QColor TerminalDisplay::keyboardCursorColor() const
+{
+ return _cursorColor;
+}
+
+void TerminalDisplay::setOpacity(qreal opacity)
+{
+ QColor color(_blendColor);
+ color.setAlphaF(opacity);
+
+ // enable automatic background filling to prevent the display
+ // flickering if there is no transparency
+ if ( color.alpha() == 255 )
+ {
+ setAutoFillBackground(true);
+ }
+ else
+ {
+ setAutoFillBackground(false);
+ }
+
+ _blendColor = color.rgba();
+}
+
+void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting )
+{
+ // the area of the widget showing the contents of the terminal display is drawn
+ // using the background color from the color scheme set with setColorTable()
+ //
+ // the area of the widget behind the scroll-bar is drawn using the background
+ // brush from the scroll-bar's palette, to give the effect of the scroll-bar
+ // being outside of the terminal display and visual consistency with other KDE
+ // applications.
+ //
+ QRect scrollBarArea = _scrollBar->isVisible() ?
+ rect.intersected(_scrollBar->geometry()) :
+ QRect();
+ QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
+ QRect contentsRect = contentsRegion.boundingRect();
+
+ if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting )
+ {
+ QColor color(backgroundColor);
+ color.setAlpha(qAlpha(_blendColor));
+
+ painter.save();
+ painter.setCompositionMode(QPainter::CompositionMode_Source);
+ painter.fillRect(contentsRect, color);
+ painter.restore();
+ }
+ else {
+ painter.fillRect(contentsRect, backgroundColor);
+ }
+
+ painter.fillRect(scrollBarArea,_scrollBar->palette().background());
+}
+
+void TerminalDisplay::drawCursor(QPainter& painter,
+ const QRect& rect,
+ const QColor& foregroundColor,
+ const QColor& /*backgroundColor*/,
+ bool& invertCharacterColor)
+{
+ QRect cursorRect = rect;
+ cursorRect.setHeight(_fontHeight - _lineSpacing - 1);
+
+ if (!_cursorBlinking)
+ {
+ if ( _cursorColor.isValid() )
+ painter.setPen(_cursorColor);
+ else {
+ painter.setPen(foregroundColor);
+ }
+
+ if ( _cursorShape == BlockCursor )
+ {
+ // draw the cursor outline, adjusting the area so that
+ // it is draw entirely inside 'rect'
+ int penWidth = qMax(1,painter.pen().width());
+
+ painter.drawRect(cursorRect.adjusted(penWidth/2,
+ penWidth/2,
+ - penWidth/2 - penWidth%2,
+ - penWidth/2 - penWidth%2));
+ if ( hasFocus() )
+ {
+ painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
+
+ if ( !_cursorColor.isValid() )
+ {
+ // invert the colour used to draw the text to ensure that the character at
+ // the cursor position is readable
+ invertCharacterColor = true;
+ }
+ }
+ }
+ else if ( _cursorShape == UnderlineCursor )
+ painter.drawLine(cursorRect.left(),
+ cursorRect.bottom(),
+ cursorRect.right(),
+ cursorRect.bottom());
+ else if ( _cursorShape == IBeamCursor )
+ painter.drawLine(cursorRect.left(),
+ cursorRect.top(),
+ cursorRect.left(),
+ cursorRect.bottom());
+
+ }
+}
+
+void TerminalDisplay::drawCharacters(QPainter& painter,
+ const QRect& rect,
+ const QString& text,
+ const Character* style,
+ bool invertCharacterColor)
+{
+ // don't draw text which is currently blinking
+ if ( _blinking && (style->rendition & RE_BLINK) )
+ return;
+
+ // setup bold and underline
+ bool useBold = style->rendition & RE_BOLD || style->isBold(_colorTable) || font().bold();
+ bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
+
+ QFont font = painter.font();
+ if ( font.bold() != useBold
+ || font.underline() != useUnderline )
+ {
+ font.setBold(useBold);
+ font.setUnderline(useUnderline);
+ painter.setFont(font);
+ }
+
+ const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor );
+ const QColor color = textColor.color(_colorTable);
+
+ QPen pen = painter.pen();
+ if ( pen.color() != color )
+ {
+ pen.setColor(color);
+ painter.setPen(color);
+ }
+ // draw text
+ if ( isLineCharString(text) ) {
+ drawLineCharString(painter,rect.x(),rect.y(),text,style);
+ }
+ else
+ {
+ // the drawText(rect,flags,string) overload is used here with null flags
+ // instead of drawText(rect,string) because the (rect,string) overload causes
+ // the application's default layout direction to be used instead of
+ // the widget-specific layout direction, which should always be
+ // Qt::LeftToRight for this widget
+ painter.drawText(rect,0,text);
+ }
+}
+
+void TerminalDisplay::drawTextFragment(QPainter& painter ,
+ const QRect& rect,
+ const QString& text,
+ const Character* style)
+{
+ painter.save();
+
+ // setup painter
+ const QColor foregroundColor = style->foregroundColor.color(_colorTable);
+ const QColor backgroundColor = style->backgroundColor.color(_colorTable);
+
+ // draw background if different from the display's background color
+ if ( backgroundColor != palette().background().color() )
+ drawBackground(painter,rect,backgroundColor, false /* do not use transparency */);
+
+ // draw cursor shape if the current character is the cursor
+ // this may alter the foreground and background colors
+ bool invertCharacterColor = false;
+
+ if ( style->rendition & RE_CURSOR )
+ drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor);
+ // draw text
+ drawCharacters(painter,rect,text,style,invertCharacterColor);
+
+ painter.restore();
+}
+
+void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; }
+uint TerminalDisplay::randomSeed() const { return _randomSeed; }
+
+#if 0
+/*!
+ Set XIM Position
+*/
+void TerminalDisplay::setCursorPos(const int curx, const int cury)
+{
+ QPoint tL = contentsRect().topLeft();
+ int tLx = tL.x();
+ int tLy = tL.y();
+
+ int xpos, ypos;
+ ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent;
+ xpos = _leftMargin + tLx + _fontWidth*curx;
+ //setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ???
+ // fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos);
+ _cursorLine = cury;
+ _cursorCol = curx;
+}
+#endif
+
+// scrolls the image by 'lines', down if lines > 0 or up otherwise.
+//
+// the terminal emulation keeps track of the scrolling of the character
+// image as it receives input, and when the view is updated, it calls scrollImage()
+// with the final scroll amount. this improves performance because scrolling the
+// display is much cheaper than re-rendering all the text for the
+// part of the image which has moved up or down.
+// Instead only new lines have to be drawn
+//
+// note: it is important that the area of the display which is
+// scrolled aligns properly with the character grid -
+// which has a top left point at (_leftMargin,_topMargin) ,
+// a cell width of _fontWidth and a cell height of _fontHeight).
+void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
+{
+ // if the flow control warning is enabled this will interfere with the
+ // scrolling optimisations and cause artifacts. the simple solution here
+ // is to just disable the optimisation whilst it is visible
+ if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() ) {
+ return;
+ }
+
+ // constrain the region to the display
+ // the bottom of the region is capped to the number of lines in the display's
+ // internal image - 2, so that the height of 'region' is strictly less
+ // than the height of the internal image.
+ QRect region = screenWindowRegion;
+ region.setBottom( qMin(region.bottom(),this->_lines-2) );
+
+ if ( lines == 0
+ || _image == 0
+ || !region.isValid()
+ || (region.top() + abs(lines)) >= region.bottom()
+ || this->_lines <= region.height() ) return;
+
+ QRect scrollRect;
+
+ void* firstCharPos = &_image[ region.top() * this->_columns ];
+ void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ];
+
+ int top = _topMargin + (region.top() * _fontHeight);
+ int linesToMove = region.height() - abs(lines);
+ int bytesToMove = linesToMove *
+ this->_columns *
+ sizeof(Character);
+
+ Q_ASSERT( linesToMove > 0 );
+ Q_ASSERT( bytesToMove > 0 );
+
+ //scroll internal image
+ if ( lines > 0 )
+ {
+ // check that the memory areas that we are going to move are valid
+ Q_ASSERT( (char*)lastCharPos + bytesToMove <
+ (char*)(_image + (this->_lines * this->_columns)) );
+
+ Q_ASSERT( (lines*this->_columns) < _imageSize );
+
+ //scroll internal image down
+ memmove( firstCharPos , lastCharPos , bytesToMove );
+
+ //set region of display to scroll, making sure that
+ //the region aligns correctly to the character grid
+ scrollRect = QRect( _leftMargin , top,
+ this->_usedColumns * _fontWidth ,
+ linesToMove * _fontHeight );
+ }
+ else
+ {
+ // check that the memory areas that we are going to move are valid
+ Q_ASSERT( (char*)firstCharPos + bytesToMove <
+ (char*)(_image + (this->_lines * this->_columns)) );
+
+ //scroll internal image up
+ memmove( lastCharPos , firstCharPos , bytesToMove );
+
+ //set region of the display to scroll, making sure that
+ //the region aligns correctly to the character grid
+ QPoint topPoint( _leftMargin , top + abs(lines)*_fontHeight );
+
+ scrollRect = QRect( topPoint ,
+ QSize( this->_usedColumns*_fontWidth ,
+ linesToMove * _fontHeight ));
+ }
+
+ //scroll the display vertically to match internal _image
+ scroll( 0 , _fontHeight * (-lines) , scrollRect );
+}
+
+QRegion TerminalDisplay::hotSpotRegion() const
+{
+ QRegion region;
+ foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() )
+ {
+ QRect rect;
+ rect.setLeft(hotSpot->startColumn());
+ rect.setTop(hotSpot->startLine());
+ rect.setRight(hotSpot->endColumn());
+ rect.setBottom(hotSpot->endLine());
+
+ region |= imageToWidget(rect);
+ }
+ return region;
+}
+
+void TerminalDisplay::processFilters()
+{
+ if (!_screenWindow)
+ return;
+
+ QRegion preUpdateHotSpots = hotSpotRegion();
+
+ // use _screenWindow->getImage() here rather than _image because
+ // other classes may call processFilters() when this display's
+ // ScreenWindow emits a scrolled() signal - which will happen before
+ // updateImage() is called on the display and therefore _image is
+ // out of date at this point
+ _filterChain->setImage( _screenWindow->getImage(),
+ _screenWindow->windowLines(),
+ _screenWindow->windowColumns(),
+ _screenWindow->getLineProperties() );
+ _filterChain->process();
+
+ QRegion postUpdateHotSpots = hotSpotRegion();
+
+ update( preUpdateHotSpots | postUpdateHotSpots );
+}
+
+void TerminalDisplay::updateImage()
+{
+ if ( !_screenWindow )
+ return;
+
+ // optimization - scroll the existing image where possible and
+ // avoid expensive text drawing for parts of the image that
+ // can simply be moved up or down
+ scrollImage( _screenWindow->scrollCount() ,
+ _screenWindow->scrollRegion() );
+ _screenWindow->resetScrollCount();
+
+ Character* const newimg = _screenWindow->getImage();
+ int lines = _screenWindow->windowLines();
+ int columns = _screenWindow->windowColumns();
+
+ setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );
+
+ if (!_image)
+ updateImageSize(); // Create _image
+
+ Q_ASSERT( this->_usedLines <= this->_lines );
+ Q_ASSERT( this->_usedColumns <= this->_columns );
+
+ int y,x,len;
+
+ QPoint tL = contentsRect().topLeft();
+
+ int tLx = tL.x();
+ int tLy = tL.y();
+ _hasBlinker = false;
+
+ CharacterColor cf; // undefined
+ CharacterColor _clipboard; // undefined
+ int cr = -1; // undefined
+
+ const int linesToUpdate = qMin(this->_lines, qMax(0,lines ));
+ const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));
+
+ QChar *disstrU = new QChar[columnsToUpdate];
+ char *dirtyMask = new char[columnsToUpdate+2];
+ QRegion dirtyRegion;
+
+ // debugging variable, this records the number of lines that are found to
+ // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
+ // which therefore need to be repainted
+ int dirtyLineCount = 0;
+
+ for (y = 0; y < linesToUpdate; y++)
+ {
+ const Character* currentLine = &_image[y*this->_columns];
+ const Character* const newLine = &newimg[y*columns];
+
+ bool updateLine = false;
+
+ // The dirty mask indicates which characters need repainting. We also
+ // mark surrounding neighbours dirty, in case the character exceeds
+ // its cell boundaries
+ memset(dirtyMask, 0, columnsToUpdate+2);
+
+ for( x = 0 ; x < columnsToUpdate ; x++)
+ {
+ if ( newLine[x] != currentLine[x] )
+ {
+ dirtyMask[x] = true;
+ }
+ }
+
+ if (!_resizing) // not while _resizing, we're expecting a paintEvent
+ for (x = 0; x < columnsToUpdate; x++)
+ {
+ _hasBlinker |= (newLine[x].rendition & RE_BLINK);
+
+ // Start drawing if this character or the next one differs.
+ // We also take the next one into account to handle the situation
+ // where characters exceed their cell width.
+ if (dirtyMask[x])
+ {
+ quint16 c = newLine[x+0].character;
+ if ( !c )
+ continue;
+ int p = 0;
+ disstrU[p++] = c; //fontMap(c);
+ bool lineDraw = isLineChar(c);
+ bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
+ cr = newLine[x].rendition;
+ _clipboard = newLine[x].backgroundColor;
+ if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
+ int lln = columnsToUpdate - x;
+ for (len = 1; len < lln; len++)
+ {
+ const Character& ch = newLine[x+len];
+
+ if (!ch.character)
+ continue; // Skip trailing part of multi-col chars.
+
+ bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0);
+
+ if ( ch.foregroundColor != cf ||
+ ch.backgroundColor != _clipboard ||
+ ch.rendition != cr ||
+ !dirtyMask[x+len] ||
+ isLineChar(c) != lineDraw ||
+ nextIsDoubleWidth != doubleWidth )
+ break;
+
+ disstrU[p++] = c; //fontMap(c);
+ }
+
+ QString unistr(disstrU, p);
+
+ bool saveFixedFont = _fixedFont;
+ if (lineDraw)
+ _fixedFont = false;
+ if (doubleWidth)
+ _fixedFont = false;
+
+ updateLine = true;
+
+ _fixedFont = saveFixedFont;
+ x += len - 1;
+ }
+
+ }
+
+ //both the top and bottom halves of double height _lines must always be redrawn
+ //although both top and bottom halves contain the same characters, only
+ //the top one is actually
+ //drawn.
+ if (_lineProperties.count() > y)
+ updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
+
+ // if the characters on the line are different in the old and the new _image
+ // then this line must be repainted.
+ if (updateLine)
+ {
+ dirtyLineCount++;
+
+ // add the area occupied by this line to the region which needs to be
+ // repainted
+ QRect dirtyRect = QRect( _leftMargin+tLx ,
+ _topMargin+tLy+_fontHeight*y ,
+ _fontWidth * columnsToUpdate ,
+ _fontHeight );
+
+ dirtyRegion |= dirtyRect;
+ }
+
+ // replace the line of characters in the old _image with the
+ // current line of the new _image
+ memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character));
+ }
+
+ // if the new _image is smaller than the previous _image, then ensure that the area
+ // outside the new _image is cleared
+ if ( linesToUpdate < _usedLines )
+ {
+ dirtyRegion |= QRect( _leftMargin+tLx ,
+ _topMargin+tLy+_fontHeight*linesToUpdate ,
+ _fontWidth * this->_columns ,
+ _fontHeight * (_usedLines-linesToUpdate) );
+ }
+ _usedLines = linesToUpdate;
+
+ if ( columnsToUpdate < _usedColumns )
+ {
+ dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth ,
+ _topMargin+tLy ,
+ _fontWidth * (_usedColumns-columnsToUpdate) ,
+ _fontHeight * this->_lines );
+ }
+ _usedColumns = columnsToUpdate;
+
+ dirtyRegion |= _inputMethodData.previousPreeditRect;
+
+ // update the parts of the display which have changed
+ update(dirtyRegion);
+
+ if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( BLINK_DELAY );
+ if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; }
+ delete[] dirtyMask;
+ delete[] disstrU;
+
+}
+
+void TerminalDisplay::showResizeNotification()
+{
+ if (_terminalSizeHint && isVisible())
+ {
+ if (_terminalSizeStartup) {
+ _terminalSizeStartup=false;
+ return;
+ }
+ if (!_resizeWidget)
+ {
+ _resizeWidget = new QLabel(("Size: XXX x XXX"), this);
+ _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(("Size: XXX x XXX")));
+ _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
+ _resizeWidget->setAlignment(Qt::AlignCenter);
+
+ _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)");
+
+ _resizeTimer = new QTimer(this);
+ _resizeTimer->setSingleShot(true);
+ connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide()));
+
+ }
+ QString sizeStr;
+ sizeStr.sprintf("Size: %d x %d", _columns, _lines);
+ _resizeWidget->setText(sizeStr);
+ _resizeWidget->move((width()-_resizeWidget->width())/2,
+ (height()-_resizeWidget->height())/2+20);
+ _resizeWidget->show();
+ _resizeTimer->start(1000);
+ }
+}
+
+void TerminalDisplay::setBlinkingCursor(bool blink)
+{
+ _hasBlinkingCursor=blink;
+
+ if (blink && !_blinkCursorTimer->isActive())
+ _blinkCursorTimer->start(BLINK_DELAY);
+
+ if (!blink && _blinkCursorTimer->isActive())
+ {
+ _blinkCursorTimer->stop();
+ if (_cursorBlinking)
+ blinkCursorEvent();
+ else
+ _cursorBlinking = false;
+ }
+}
+
+void TerminalDisplay::paintEvent( QPaintEvent* pe )
+{
+//qDebug("%s %d paintEvent", __FILE__, __LINE__);
+ QPainter paint(this);
+
+ foreach (QRect rect, (pe->region() & contentsRect()).rects())
+ {
+ drawBackground(paint,rect,palette().background().color(), true /* use opacity setting */);
+ drawContents(paint, rect);
+ }
+// drawBackground(paint,contentsRect(),palette().background().color(), true /* use opacity setting */);
+// drawContents(paint, contentsRect());
+ drawInputMethodPreeditString(paint,preeditRect());
+ paintFilters(paint);
+
+ paint.end();
+}
+
+QPoint TerminalDisplay::cursorPosition() const
+{
+ if (_screenWindow)
+ return _screenWindow->cursorPosition();
+ else
+ return QPoint(0,0);
+}
+
+QRect TerminalDisplay::preeditRect() const
+{
+ const int preeditLength = string_width(_inputMethodData.preeditString);
+
+ if ( preeditLength == 0 )
+ return QRect();
+
+ return QRect(_leftMargin + _fontWidth*cursorPosition().x(),
+ _topMargin + _fontHeight*cursorPosition().y(),
+ _fontWidth*preeditLength,
+ _fontHeight);
+}
+
+void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
+{
+ if ( _inputMethodData.preeditString.isEmpty() ) {
+ return;
+ }
+ const QPoint cursorPos = cursorPosition();
+
+ bool invertColors = false;
+ const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
+ const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
+ const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())];
+
+ drawBackground(painter,rect,background,true);
+ drawCursor(painter,rect,foreground,background,invertColors);
+ drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors);
+
+ _inputMethodData.previousPreeditRect = rect;
+}
+
+FilterChain* TerminalDisplay::filterChain() const
+{
+ return _filterChain;
+}
+
+void TerminalDisplay::paintFilters(QPainter& painter)
+{
+//qDebug("%s %d paintFilters", __FILE__, __LINE__);
+
+ // get color of character under mouse and use it to draw
+ // lines for filters
+ QPoint cursorPos = mapFromGlobal(QCursor::pos());
+ int cursorLine;
+ int cursorColumn;
+ getCharacterPosition( cursorPos , cursorLine , cursorColumn );
+ Character cursorCharacter = _image[loc(cursorColumn,cursorLine)];
+
+ painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) );
+
+ // iterate over hotspots identified by the display's currently active filters
+ // and draw appropriate visuals to indicate the presence of the hotspot
+
+ QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
+ QListIterator<Filter::HotSpot*> iter(spots);
+ while (iter.hasNext())
+ {
+ Filter::HotSpot* spot = iter.next();
+
+ for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ )
+ {
+ int startColumn = 0;
+ int endColumn = _columns-1; // TODO use number of _columns which are actually
+ // occupied on this line rather than the width of the
+ // display in _columns
+
+ // ignore whitespace at the end of the lines
+ while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 )
+ endColumn--;
+
+ // increment here because the column which we want to set 'endColumn' to
+ // is the first whitespace character at the end of the line
+ endColumn++;
+
+ if ( line == spot->startLine() )
+ startColumn = spot->startColumn();
+ if ( line == spot->endLine() )
+ endColumn = spot->endColumn();
+
+ // subtract one pixel from
+ // the right and bottom so that
+ // we do not overdraw adjacent
+ // hotspots
+ //
+ // subtracting one pixel from all sides also prevents an edge case where
+ // moving the mouse outside a link could still leave it underlined
+ // because the check below for the position of the cursor
+ // finds it on the border of the target area
+ QRect r;
+ r.setCoords( startColumn*_fontWidth + 1, line*_fontHeight + 1,
+ endColumn*_fontWidth - 1, (line+1)*_fontHeight - 1 );
+
+ // Underline link hotspots
+ if ( spot->type() == Filter::HotSpot::Link )
+ {
+ QFontMetrics metrics(font());
+
+ // find the baseline (which is the invisible line that the characters in the font sit on,
+ // with some having tails dangling below)
+ int baseline = r.bottom() - metrics.descent();
+ // find the position of the underline below that
+ int underlinePos = baseline + metrics.underlinePos();
+
+ if ( r.contains( mapFromGlobal(QCursor::pos()) ) )
+ painter.drawLine( r.left() , underlinePos ,
+ r.right() , underlinePos );
+ }
+ // Marker hotspots simply have a transparent rectanglular shape
+ // drawn on top of them
+ else if ( spot->type() == Filter::HotSpot::Marker )
+ {
+ //TODO - Do not use a hardcoded colour for this
+ painter.fillRect(r,QBrush(QColor(255,0,0,120)));
+ }
+ }
+ }
+}
+void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
+{
+//qDebug("%s %d drawContents and rect x=%d y=%d w=%d h=%d", __FILE__, __LINE__, rect.x(), rect.y(),rect.width(),rect.height());
+
+ QPoint tL = contentsRect().topLeft();
+// int tLx = tL.x();
+ int tLy = tL.y();
+
+ int tLx = (_contentWidth - _usedColumns * _fontWidth)/2;
+// int tLy = (_contentHeight - _usedLines * _fontHeight)/2;
+//qDebug("%d %d %d %d", tLx, tLy, _contentWidth, _usedColumns * _fontWidth);
+
+ int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth));
+ int luy = qMin(_usedLines-1, qMax(0, (rect.top() - tLy - _topMargin ) / _fontHeight));
+ int rlx = qMin(_usedColumns-1, qMax(0, (rect.right() - tLx - _leftMargin ) / _fontWidth));
+ int rly = qMin(_usedLines-1, qMax(0, (rect.bottom() - tLy - _topMargin ) / _fontHeight));
+
+ const int bufferSize = _usedColumns;
+ QChar *disstrU = new QChar[bufferSize];
+ for (int y = luy; y <= rly; y++)
+ {
+ quint16 c = _image[loc(lux,y)].character;
+ int x = lux;
+ if(!c && x)
+ x--; // Search for start of multi-column character
+ for (; x <= rlx; x++)
+ {
+ int len = 1;
+ int p = 0;
+
+ // is this a single character or a sequence of characters ?
+ if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR )
+ {
+ // sequence of characters
+ ushort extendedCharLength = 0;
+ ushort* chars = ExtendedCharTable::instance
+ .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength);
+ for ( int index = 0 ; index < extendedCharLength ; index++ )
+ {
+ Q_ASSERT( p < bufferSize );
+ disstrU[p++] = chars[index];
+ }
+ }
+ else
+ {
+ // single character
+ c = _image[loc(x,y)].character;
+ if (c)
+ {
+ Q_ASSERT( p < bufferSize );
+ disstrU[p++] = c; //fontMap(c);
+ }
+ }
+
+ bool lineDraw = isLineChar(c);
+ bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
+ CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
+ CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
+ quint8 currentRendition = _image[loc(x,y)].rendition;
+
+ while (x+len <= rlx &&
+ _image[loc(x+len,y)].foregroundColor == currentForeground &&
+ _image[loc(x+len,y)].backgroundColor == currentBackground &&
+ _image[loc(x+len,y)].rendition == currentRendition &&
+ (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth &&
+ isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment!
+ {
+ if (c)
+ disstrU[p++] = c; //fontMap(c);
+ if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
+ len++; // Skip trailing part of multi-column character
+ len++;
+ }
+ if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character))
+ len++; // Adjust for trailing part of multi-column character
+
+ bool save__fixedFont = _fixedFont;
+ if (lineDraw)
+ _fixedFont = false;
+ if (doubleWidth)
+ _fixedFont = false;
+ QString unistr(disstrU,p);
+
+ if (y < _lineProperties.size())
+ {
+ if (_lineProperties[y] & LINE_DOUBLEWIDTH) {
+ paint.scale(2,1);
+ }
+
+ if (_lineProperties[y] & LINE_DOUBLEHEIGHT) {
+ paint.scale(1,2);
+ }
+ }
+
+ //calculate the area in which the text will be drawn
+ QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x ,
+ _topMargin+tLy+_fontHeight*y ,
+ _fontWidth*len,
+ _fontHeight);
+
+ //move the calculated area to take account of scaling applied to the painter.
+ //the position of the area from the origin (0,0) is scaled
+ //by the opposite of whatever
+ //transformation has been applied to the painter. this ensures that
+ //painting does actually start from textArea.topLeft()
+ //(instead of textArea.topLeft() * painter-scale)
+ QMatrix inverted = paint.matrix().inverted();
+// textArea.moveTopLeft( inverted.map(textArea.topLeft()) );
+ textArea.moveCenter( inverted.map(textArea.center()) );
+
+
+ //paint text fragment
+ drawTextFragment( paint,
+ textArea,
+ unistr,
+ &_image[loc(x,y)] ); //,
+ //0,
+ //!_isPrinting );
+
+ _fixedFont = save__fixedFont;
+
+ //reset back to single-width, single-height _lines
+ paint.resetMatrix();
+
+ if (y < _lineProperties.size()-1)
+ {
+ //double-height _lines are represented by two adjacent _lines
+ //containing the same characters
+ //both _lines will have the LINE_DOUBLEHEIGHT attribute.
+ //If the current line has the LINE_DOUBLEHEIGHT attribute,
+ //we can therefore skip the next line
+ if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
+ y++;
+ }
+
+ x += len - 1;
+ }
+ }
+ delete [] disstrU;
+}
+
+void TerminalDisplay::blinkEvent()
+{
+ _blinking = !_blinking;
+
+ //TODO: Optimise to only repaint the areas of the widget
+ // where there is blinking text
+ // rather than repainting the whole widget.
+ update();
+}
+
+QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
+{
+//qDebug("%s %d imageToWidget", __FILE__, __LINE__);
+ QRect result;
+ result.setLeft( _leftMargin + _fontWidth * imageArea.left() );
+ result.setTop( _topMargin + _fontHeight * imageArea.top() );
+ result.setWidth( _fontWidth * imageArea.width() );
+ result.setHeight( _fontHeight * imageArea.height() );
+
+ return result;
+}
+
+void TerminalDisplay::blinkCursorEvent()
+{
+ _cursorBlinking = !_cursorBlinking;
+
+ QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) );
+
+ update(cursorRect);
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Resizing */
+/* */
+/* ------------------------------------------------------------------------- */
+
+void TerminalDisplay::resizeEvent(QResizeEvent*)
+{
+ updateImageSize();
+}
+
+void TerminalDisplay::propagateSize()
+{
+ if (_isFixedSize)
+ {
+ setSize(_columns, _lines);
+ QWidget::setFixedSize(sizeHint());
+ parentWidget()->adjustSize();
+ parentWidget()->setFixedSize(parentWidget()->sizeHint());
+ return;
+ }
+ if (_image)
+ updateImageSize();
+}
+
+void TerminalDisplay::updateImageSize()
+{
+//qDebug("%s %d updateImageSize", __FILE__, __LINE__);
+ Character* oldimg = _image;
+ int oldlin = _lines;
+ int oldcol = _columns;
+
+ makeImage();
+
+
+ // copy the old image to reduce flicker
+ int lines = qMin(oldlin,_lines);
+ int columns = qMin(oldcol,_columns);
+
+ if (oldimg)
+ {
+ for (int line = 0; line < lines; line++)
+ {
+ memcpy((void*)&_image[_columns*line],
+ (void*)&oldimg[oldcol*line],columns*sizeof(Character));
+ }
+ delete[] oldimg;
+ }
+
+ if (_screenWindow)
+ _screenWindow->setWindowLines(_lines);
+
+ _resizing = (oldlin!=_lines) || (oldcol!=_columns);
+
+ if ( _resizing )
+ {
+ showResizeNotification();
+ emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
+ }
+
+ _resizing = false;
+}
+
+//showEvent and hideEvent are reimplemented here so that it appears to other classes that the
+//display has been resized when the display is hidden or shown.
+//
+//this allows
+//TODO: Perhaps it would be better to have separate signals for show and hide instead of using
+//the same signal as the one for a content size change
+void TerminalDisplay::showEvent(QShowEvent*)
+{
+ emit changedContentSizeSignal(_contentHeight,_contentWidth);
+}
+void TerminalDisplay::hideEvent(QHideEvent*)
+{
+ emit changedContentSizeSignal(_contentHeight,_contentWidth);
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Scrollbar */
+/* */
+/* ------------------------------------------------------------------------- */
+
+void TerminalDisplay::scrollBarPositionChanged(int)
+{
+ if ( !_screenWindow )
+ return;
+
+ _screenWindow->scrollTo( _scrollBar->value() );
+
+ // if the thumb has been moved to the bottom of the _scrollBar then set
+ // the display to automatically track new output,
+ // that is, scroll down automatically
+ // to how new _lines as they are added
+ const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
+ _screenWindow->setTrackOutput( atEndOfOutput );
+
+ updateImage();
+}
+
+void TerminalDisplay::setScroll(int cursor, int slines)
+{
+//qDebug("%s %d setScroll", __FILE__, __LINE__);
+ // update _scrollBar if the range or value has changed,
+ // otherwise return
+ //
+ // setting the range or value of a _scrollBar will always trigger
+ // a repaint, so it should be avoided if it is not necessary
+ if ( _scrollBar->minimum() == 0 &&
+ _scrollBar->maximum() == (slines - _lines) &&
+ _scrollBar->value() == cursor )
+ {
+ return;
+ }
+
+ disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
+ _scrollBar->setRange(0,slines - _lines);
+ _scrollBar->setSingleStep(1);
+ _scrollBar->setPageStep(_lines);
+ _scrollBar->setValue(cursor);
+ connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
+}
+
+void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
+{
+ if (_scrollbarLocation == position) {
+// return;
+ }
+
+ if ( position == NoScrollBar )
+ _scrollBar->hide();
+ else
+ _scrollBar->show();
+
+ _topMargin = _leftMargin = 1;
+ _scrollbarLocation = position;
+
+ propagateSize();
+ update();
+}
+
+void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
+{
+ if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) {
+ mouseTripleClickEvent(ev);
+ return;
+ }
+
+ if ( !contentsRect().contains(ev->pos()) ) return;
+
+ if ( !_screenWindow ) return;
+
+ int charLine;
+ int charColumn;
+ getCharacterPosition(ev->pos(),charLine,charColumn);
+ QPoint pos = QPoint(charColumn,charLine);
+
+ if ( ev->button() == Qt::LeftButton)
+ {
+ _lineSelectionMode = false;
+ _wordSelectionMode = false;
+
+ emit isBusySelecting(true); // Keep it steady...
+ // Drag only when the Control key is hold
+ bool selected = false;
+
+ // The receiver of the testIsSelected() signal will adjust
+ // 'selected' accordingly.
+ //emit testIsSelected(pos.x(), pos.y(), selected);
+
+ selected = _screenWindow->isSelected(pos.x(),pos.y());
+
+ if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
+ // The user clicked inside selected text
+ dragInfo.state = diPending;
+ dragInfo.start = ev->pos();
+ }
+ else {
+ // No reason to ever start a drag event
+ dragInfo.state = diNone;
+
+ _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) );
+ _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
+
+ if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
+ {
+ _screenWindow->clearSelection();
+
+ //emit clearSelectionSignal();
+ pos.ry() += _scrollBar->value();
+ _iPntSel = _pntSel = pos;
+ _actSel = 1; // left mouse button pressed but nothing selected yet.
+
+ }
+ else
+ {
+ emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
+ }
+ }
+ }
+ else if ( ev->button() == Qt::MidButton )
+ {
+ if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) )
+ emitSelection(true,ev->modifiers() & Qt::ControlModifier);
+ else
+ emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
+ }
+ else if ( ev->button() == Qt::RightButton )
+ {
+ if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
+ {
+ emit configureRequest( this,
+ ev->modifiers() & (Qt::ShiftModifier|Qt::ControlModifier),
+ ev->pos()
+ );
+ }
+ else
+ emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
+ }
+}
+
+QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
+{
+ int charLine, charColumn;
+ getCharacterPosition(position,charLine,charColumn);
+
+ Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
+
+ return spot ? spot->actions() : QList<QAction*>();
+}
+
+void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
+{
+ int charLine = 0;
+ int charColumn = 0;
+
+ getCharacterPosition(ev->pos(),charLine,charColumn);
+
+ // handle filters
+ // change link hot-spot appearance on mouse-over
+ Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
+ if ( spot && spot->type() == Filter::HotSpot::Link)
+ {
+ QRect previousHotspotArea = _mouseOverHotspotArea;
+ _mouseOverHotspotArea.setCoords( qMin(spot->startColumn() , spot->endColumn()) * _fontWidth,
+ spot->startLine() * _fontHeight,
+ qMax(spot->startColumn() , spot->endColumn()) * _fontHeight,
+ (spot->endLine()+1) * _fontHeight );
+
+ // display tooltips when mousing over links
+ // TODO: Extend this to work with filter types other than links
+ const QString& tooltip = spot->tooltip();
+ if ( !tooltip.isEmpty() )
+ {
+ QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea );
+ }
+
+ update( _mouseOverHotspotArea | previousHotspotArea );
+ }
+ else if ( _mouseOverHotspotArea.isValid() )
+ {
+ update( _mouseOverHotspotArea );
+ // set hotspot area to an invalid rectangle
+ _mouseOverHotspotArea = QRect();
+ }
+
+ // for auto-hiding the cursor, we need mouseTracking
+ if (ev->buttons() == Qt::NoButton ) return;
+
+ // if the terminal is interested in mouse movements
+ // then emit a mouse movement signal, unless the shift
+ // key is being held down, which overrides this.
+ if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
+ {
+ int button = 3;
+ if (ev->buttons() & Qt::LeftButton)
+ button = 0;
+ if (ev->buttons() & Qt::MidButton)
+ button = 1;
+ if (ev->buttons() & Qt::RightButton)
+ button = 2;
+
+
+ emit mouseSignal( button,
+ charColumn + 1,
+ charLine + 1 +_scrollBar->value() -_scrollBar->maximum(),
+ 1 );
+
+ return;
+ }
+
+ if (dragInfo.state == diPending)
+ {
+ // we had a mouse down, but haven't confirmed a drag yet
+ // if the mouse has moved sufficiently, we will confirm
+
+ int distance = 10; //KGlobalSettings::dndEventDelay();
+ if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance ||
+ ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance)
+ {
+ // we've left the drag square, we can start a real drag operation now
+ emit isBusySelecting(false); // Ok.. we can breath again.
+
+ _screenWindow->clearSelection();
+ doDrag();
+ }
+ return;
+ }
+ else if (dragInfo.state == diDragging)
+ {
+ // this isn't technically needed because mouseMoveEvent is suppressed during
+ // Qt drag operations, replaced by dragMoveEvent
+ return;
+ }
+
+ if (_actSel == 0) return;
+
+ // don't extend selection while pasting
+ if (ev->buttons() & Qt::MidButton) return;
+
+ extendSelection( ev->pos() );
+}
+
+#if 0
+void TerminalDisplay::setSelectionEnd()
+{
+ extendSelection( _configureRequestPoint );
+}
+#endif
+
+void TerminalDisplay::extendSelection( const QPoint& position )
+{
+ QPoint pos = position;
+
+ if ( !_screenWindow )
+ return;
+
+ //if ( !contentsRect().contains(ev->pos()) ) return;
+ QPoint tL = contentsRect().topLeft();
+ int tLx = tL.x();
+ int tLy = tL.y();
+ int scroll = _scrollBar->value();
+
+ // we're in the process of moving the mouse with the left button pressed
+ // the mouse cursor will kept caught within the bounds of the text in
+ // this widget.
+
+ // Adjust position within text area bounds. See FIXME above.
+ QPoint oldpos = pos;
+ if ( pos.x() < tLx+_leftMargin )
+ pos.setX( tLx+_leftMargin );
+ if ( pos.x() > tLx+_leftMargin+_usedColumns*_fontWidth-1 )
+ pos.setX( tLx+_leftMargin+_usedColumns*_fontWidth );
+ if ( pos.y() < tLy+_topMargin )
+ pos.setY( tLy+_topMargin );
+ if ( pos.y() > tLy+_topMargin+_usedLines*_fontHeight-1 )
+ pos.setY( tLy+_topMargin+_usedLines*_fontHeight-1 );
+
+ if ( pos.y() == tLy+_topMargin+_usedLines*_fontHeight-1 )
+ {
+ _scrollBar->setValue(_scrollBar->value()+yMouseScroll); // scrollforward
+ }
+ if ( pos.y() == tLy+_topMargin )
+ {
+ _scrollBar->setValue(_scrollBar->value()-yMouseScroll); // scrollback
+ }
+
+ int charColumn = 0;
+ int charLine = 0;
+ getCharacterPosition(pos,charLine,charColumn);
+
+ QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight);
+ QPoint ohere;
+ QPoint _iPntSelCorr = _iPntSel;
+ _iPntSelCorr.ry() -= _scrollBar->value();
+ QPoint _pntSelCorr = _pntSel;
+ _pntSelCorr.ry() -= _scrollBar->value();
+ bool swapping = false;
+
+ if ( _wordSelectionMode )
+ {
+ // Extend to word boundaries
+ int i;
+ int selClass;
+
+ bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
+ here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() );
+ bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
+ _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() );
+ swapping = left_not_right != old_left_not_right;
+
+ // Find left (left_not_right ? from here : from start)
+ QPoint left = left_not_right ? here : _iPntSelCorr;
+ i = loc(left.x(),left.y());
+ if (i>=0 && i<=_imageSize) {
+ selClass = charClass(_image[i].character);
+ while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) ))
+ && charClass(_image[i-1].character) == selClass )
+ { i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} }
+ }
+
+ // Find left (left_not_right ? from start : from here)
+ QPoint right = left_not_right ? _iPntSelCorr : here;
+ i = loc(right.x(),right.y());
+ if (i>=0 && i<=_imageSize) {
+ selClass = charClass(_image[i].character);
+ while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) ))
+ && charClass(_image[i+1].character) == selClass )
+ { i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } }
+ }
+
+ // Pick which is start (ohere) and which is extension (here)
+ if ( left_not_right )
+ {
+ here = left; ohere = right;
+ }
+ else
+ {
+ here = right; ohere = left;
+ }
+ ohere.rx()++;
+ }
+
+ if ( _lineSelectionMode )
+ {
+ // Extend to complete line
+ bool above_not_below = ( here.y() < _iPntSelCorr.y() );
+
+ QPoint above = above_not_below ? here : _iPntSelCorr;
+ QPoint below = above_not_below ? _iPntSelCorr : here;
+
+ while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) )
+ above.ry()--;
+ while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) )
+ below.ry()++;
+
+ above.setX(0);
+ below.setX(_usedColumns-1);
+
+ // Pick which is start (ohere) and which is extension (here)
+ if ( above_not_below )
+ {
+ here = above; ohere = below;
+ }
+ else
+ {
+ here = below; ohere = above;
+ }
+
+ QPoint newSelBegin = QPoint( ohere.x(), ohere.y() );
+ swapping = !(_tripleSelBegin==newSelBegin);
+ _tripleSelBegin = newSelBegin;
+
+ ohere.rx()++;
+ }
+
+ int offset = 0;
+ if ( !_wordSelectionMode && !_lineSelectionMode )
+ {
+ int i;
+ int selClass;
+
+ bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
+ here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() );
+ bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
+ _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() );
+ swapping = left_not_right != old_left_not_right;
+
+ // Find left (left_not_right ? from here : from start)
+ QPoint left = left_not_right ? here : _iPntSelCorr;
+
+ // Find left (left_not_right ? from start : from here)
+ QPoint right = left_not_right ? _iPntSelCorr : here;
+ if ( right.x() > 0 && !_columnSelectionMode )
+ {
+ i = loc(right.x(),right.y());
+ if (i>=0 && i<=_imageSize) {
+ selClass = charClass(_image[i-1].character);
+ if (selClass == ' ')
+ {
+ while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
+ !(_lineProperties[right.y()] & LINE_WRAPPED))
+ { i++; right.rx()++; }
+ if (right.x() < _usedColumns-1)
+ right = left_not_right ? _iPntSelCorr : here;
+ else
+ right.rx()++; // will be balanced later because of offset=-1;
+ }
+ }
+ }
+
+ // Pick which is start (ohere) and which is extension (here)
+ if ( left_not_right )
+ {
+ here = left; ohere = right; offset = 0;
+ }
+ else
+ {
+ here = right; ohere = left; offset = -1;
+ }
+ }
+
+ if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved
+
+ if (here == ohere) return; // It's not left, it's not right.
+
+ if ( _actSel < 2 || swapping )
+ {
+ if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
+ {
+ _screenWindow->setSelectionStart( ohere.x() , ohere.y() , true );
+ }
+ else
+ {
+ _screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false );
+ }
+
+ }
+
+ _actSel = 2; // within selection
+ _pntSel = here;
+ _pntSel.ry() += _scrollBar->value();
+
+ if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
+ {
+ _screenWindow->setSelectionEnd( here.x() , here.y() );
+ }
+ else
+ {
+ _screenWindow->setSelectionEnd( here.x()+offset , here.y() );
+ }
+
+}
+
+void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
+{
+ if ( !_screenWindow )
+ return;
+
+ int charLine;
+ int charColumn;
+ getCharacterPosition(ev->pos(),charLine,charColumn);
+
+ if ( ev->button() == Qt::LeftButton)
+ {
+ emit isBusySelecting(false);
+ if(dragInfo.state == diPending)
+ {
+ // We had a drag event pending but never confirmed. Kill selection
+ _screenWindow->clearSelection();
+ //emit clearSelectionSignal();
+ }
+ else
+ {
+ if ( _actSel > 1 )
+ {
+ setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
+ }
+
+ _actSel = 0;
+
+ //FIXME: emits a release event even if the mouse is
+ // outside the range. The procedure used in `mouseMoveEvent'
+ // applies here, too.
+
+ if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
+ emit mouseSignal( 3, // release
+ charColumn + 1,
+ charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
+ }
+ dragInfo.state = diNone;
+ }
+
+
+ if ( !_mouseMarks &&
+ ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier))
+ || ev->button() == Qt::MidButton) )
+ {
+ emit mouseSignal( 3,
+ charColumn + 1,
+ charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
+ 0);
+ }
+}
+
+void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const
+{
+
+ column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth;
+ line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight;
+
+ if ( line < 0 )
+ line = 0;
+ if ( column < 0 )
+ column = 0;
+
+ if ( line >= _usedLines )
+ line = _usedLines-1;
+
+ // the column value returned can be equal to _usedColumns, which
+ // is the position just after the last character displayed in a line.
+ //
+ // this is required so that the user can select characters in the right-most
+ // column (or left-most for right-to-left input)
+ if ( column > _usedColumns )
+ column = _usedColumns;
+}
+
+void TerminalDisplay::updateLineProperties()
+{
+ if ( !_screenWindow )
+ return;
+
+ _lineProperties = _screenWindow->getLineProperties();
+}
+
+void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
+{
+ if ( ev->button() != Qt::LeftButton) return;
+ if ( !_screenWindow ) return;
+
+ int charLine = 0;
+ int charColumn = 0;
+
+ getCharacterPosition(ev->pos(),charLine,charColumn);
+
+ QPoint pos(charColumn,charLine);
+
+ // pass on double click as two clicks.
+ if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
+ {
+ // Send just _ONE_ click event, since the first click of the double click
+ // was already sent by the click handler
+ emit mouseSignal( 0,
+ pos.x()+1,
+ pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(),
+ 0 ); // left button
+ return;
+ }
+
+ _screenWindow->clearSelection();
+ QPoint bgnSel = pos;
+ QPoint endSel = pos;
+ int i = loc(bgnSel.x(),bgnSel.y());
+ _iPntSel = bgnSel;
+ _iPntSel.ry() += _scrollBar->value();
+
+ _wordSelectionMode = true;
+
+ // find word boundaries...
+ int selClass = charClass(_image[i].character);
+ {
+ // find the start of the word
+ int x = bgnSel.x();
+ while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) ))
+ && charClass(_image[i-1].character) == selClass )
+ {
+ i--;
+ if (x>0)
+ x--;
+ else
+ {
+ x=_usedColumns-1;
+ bgnSel.ry()--;
+ }
+ }
+
+ bgnSel.setX(x);
+ _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false );
+
+ // find the end of the word
+ i = loc( endSel.x(), endSel.y() );
+ x = endSel.x();
+ while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) ))
+ && charClass(_image[i+1].character) == selClass )
+ {
+ i++;
+ if (x<_usedColumns-1)
+ x++;
+ else
+ {
+ x=0;
+ endSel.ry()++;
+ }
+ }
+
+ endSel.setX(x);
+
+ // In word selection mode don't select @ (64) if at end of word.
+ if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) )
+ endSel.setX( x - 1 );
+
+
+ _actSel = 2; // within selection
+
+ _screenWindow->setSelectionEnd( endSel.x() , endSel.y() );
+
+ setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
+ }
+
+ _possibleTripleClick=true;
+
+ QTimer::singleShot(QApplication::doubleClickInterval(),this,
+ SLOT(tripleClickTimeout()));
+}
+
+void TerminalDisplay::wheelEvent( QWheelEvent* ev )
+{
+ if (ev->orientation() != Qt::Vertical)
+ return;
+
+ if ( _mouseMarks )
+ _scrollBar->event(ev);
+ else
+ {
+ int charLine;
+ int charColumn;
+ getCharacterPosition( ev->pos() , charLine , charColumn );
+
+ emit mouseSignal( ev->delta() > 0 ? 4 : 5,
+ charColumn + 1,
+ charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
+ 0);
+ }
+}
+
+void TerminalDisplay::tripleClickTimeout()
+{
+ _possibleTripleClick=false;
+}
+
+void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
+{
+ if ( !_screenWindow ) return;
+
+ int charLine;
+ int charColumn;
+ getCharacterPosition(ev->pos(),charLine,charColumn);
+ _iPntSel = QPoint(charColumn,charLine);
+
+ _screenWindow->clearSelection();
+
+ _lineSelectionMode = true;
+ _wordSelectionMode = false;
+
+ _actSel = 2; // within selection
+ emit isBusySelecting(true); // Keep it steady...
+
+ while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
+ _iPntSel.ry()--;
+
+ if (_tripleClickMode == SelectForwardsFromCursor) {
+ // find word boundary start
+ int i = loc(_iPntSel.x(),_iPntSel.y());
+ int selClass = charClass(_image[i].character);
+ int x = _iPntSel.x();
+
+ while ( ((x>0) ||
+ (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
+ )
+ && charClass(_image[i-1].character) == selClass )
+ {
+ i--;
+ if (x>0)
+ x--;
+ else
+ {
+ x=_columns-1;
+ _iPntSel.ry()--;
+ }
+ }
+
+ _screenWindow->setSelectionStart( x , _iPntSel.y() , false );
+ _tripleSelBegin = QPoint( x, _iPntSel.y() );
+ }
+ else if (_tripleClickMode == SelectWholeLine) {
+ _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false );
+ _tripleSelBegin = QPoint( 0, _iPntSel.y() );
+ }
+
+ while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) )
+ _iPntSel.ry()++;
+
+ _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() );
+
+ setSelection(_screenWindow->selectedText(_preserveLineBreaks));
+
+ _iPntSel.ry() += _scrollBar->value();
+}
+
+
+bool TerminalDisplay::focusNextPrevChild( bool next )
+{
+ if (next)
+ return false; // This disables changing the active part in konqueror
+ // when pressing Tab
+ return QWidget::focusNextPrevChild( next );
+}
+
+
+int TerminalDisplay::charClass(quint16 ch) const
+{
+ QChar qch=QChar(ch);
+ if ( qch.isSpace() ) return ' ';
+
+ if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) )
+ return 'a';
+
+ // Everything else is weird
+ return 1;
+}
+
+void TerminalDisplay::setWordCharacters(const QString& wc)
+{
+ _wordCharacters = wc;
+}
+
+void TerminalDisplay::setUsesMouse(bool on)
+{
+ _mouseMarks = on;
+ setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
+}
+bool TerminalDisplay::usesMouse() const
+{
+ return _mouseMarks;
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Clipboard */
+/* */
+/* ------------------------------------------------------------------------- */
+
+#undef KeyPress
+
+void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn)
+{
+ if ( !_screenWindow )
+ return;
+
+ // Paste Clipboard by simulating keypress events
+ QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection :
+ QClipboard::Clipboard);
+ if(appendReturn)
+ text.append("\r");
+ if ( ! text.isEmpty() )
+ {
+ text.replace("\n", "\r");
+ QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
+ emit keyPressedSignal(&e); // expose as a big fat keypress event
+
+ _screenWindow->clearSelection();
+ }
+}
+
+void TerminalDisplay::setSelection(const QString& t)
+{
+ QApplication::clipboard()->setText(t, QClipboard::Selection);
+}
+
+void TerminalDisplay::copyClipboard()
+{
+ if ( !_screenWindow )
+ return;
+
+ QString text = _screenWindow->selectedText(_preserveLineBreaks);
+ QApplication::clipboard()->setText(text);
+}
+
+void TerminalDisplay::pasteClipboard()
+{
+ emitSelection(false,false);
+}
+
+void TerminalDisplay::pasteSelection()
+{
+ emitSelection(true,false);
+}
+
+/* ------------------------------------------------------------------------- */
+/* */
+/* Keyboard */
+/* */
+/* ------------------------------------------------------------------------- */
+
+void TerminalDisplay::setFlowControlWarningEnabled( bool enable )
+{
+ _flowControlWarningEnabled = enable;
+
+ // if the dialog is currently visible and the flow control warning has
+ // been disabled then hide the dialog
+ if (!enable)
+ outputSuspended(false);
+}
+
+void TerminalDisplay::keyPressEvent( QKeyEvent* event )
+{
+//qDebug("%s %d keyPressEvent and key is %d", __FILE__, __LINE__, event->key());
+
+ bool emitKeyPressSignal = true;
+
+ // XonXoff flow control
+ if (event->modifiers() & Qt::ControlModifier && _flowControlWarningEnabled)
+ {
+ if ( event->key() == Qt::Key_S ) {
+ //qDebug("%s %d keyPressEvent, output suspended", __FILE__, __LINE__);
+ emit flowControlKeyPressed(true /*output suspended*/);
+ }
+ else if ( event->key() == Qt::Key_Q ) {
+ //qDebug("%s %d keyPressEvent, output enabled", __FILE__, __LINE__);
+ emit flowControlKeyPressed(false /*output enabled*/);
+ }
+ }
+
+ // Keyboard-based navigation
+ if ( event->modifiers() == Qt::ShiftModifier )
+ {
+ bool update = true;
+
+ if ( event->key() == Qt::Key_PageUp )
+ {
+ //qDebug("%s %d pageup", __FILE__, __LINE__);
+ _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 );
+ }
+ else if ( event->key() == Qt::Key_PageDown )
+ {
+ //qDebug("%s %d pagedown", __FILE__, __LINE__);
+ _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 );
+ }
+ else if ( event->key() == Qt::Key_Up )
+ {
+ //qDebug("%s %d keyup", __FILE__, __LINE__);
+ _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 );
+ }
+ else if ( event->key() == Qt::Key_Down )
+ {
+ //qDebug("%s %d keydown", __FILE__, __LINE__);
+ _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 );
+ }
+ else {
+ update = false;
+ }
+
+ if ( update )
+ {
+ //qDebug("%s %d updating", __FILE__, __LINE__);
+ _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
+
+ updateLineProperties();
+ updateImage();
+
+ // do not send key press to terminal
+ emitKeyPressSignal = false;
+ }
+ }
+
+ _screenWindow->setTrackOutput( true );
+
+ _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
+ // know where the current selection is.
+
+ if (_hasBlinkingCursor)
+ {
+ _blinkCursorTimer->start(BLINK_DELAY);
+ if (_cursorBlinking)
+ blinkCursorEvent();
+ else
+ _cursorBlinking = false;
+ }
+
+ if ( emitKeyPressSignal )
+ emit keyPressedSignal(event);
+
+ event->accept();
+}
+
+void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event )
+{
+ QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString());
+ emit keyPressedSignal(&keyEvent);
+
+ _inputMethodData.preeditString = event->preeditString();
+ update(preeditRect() | _inputMethodData.previousPreeditRect);
+
+ event->accept();
+}
+QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const
+{
+ const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0);
+ switch ( query )
+ {
+ case Qt::ImMicroFocus:
+ return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1));
+ break;
+ case Qt::ImFont:
+ return font();
+ break;
+ case Qt::ImCursorPosition:
+ // return the cursor position within the current line
+ return cursorPos.x();
+ break;
+ case Qt::ImSurroundingText:
+ {
+ // return the text from the current line
+ QString lineText;
+ QTextStream stream(&lineText);
+ PlainTextDecoder decoder;
+ decoder.begin(&stream);
+ decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]);
+ decoder.end();
+ return lineText;
+ }
+ break;
+ case Qt::ImCurrentSelection:
+ return QString();
+ break;
+ }
+
+ return QVariant();
+}
+
+bool TerminalDisplay::event( QEvent *e )
+{
+ if ( e->type() == QEvent::ShortcutOverride )
+ {
+ QKeyEvent* keyEvent = static_cast<QKeyEvent *>( e );
+
+ // a check to see if keyEvent->text() is empty is used
+ // to avoid intercepting the press of the modifier key on its own.
+ //
+ // this is important as it allows a press and release of the Alt key
+ // on its own to focus the menu bar, making it possible to
+ // work with the menu without using the mouse
+ if ( (keyEvent->modifiers() == Qt::AltModifier) &&
+ !keyEvent->text().isEmpty() )
+ {
+ keyEvent->accept();
+ return true;
+ }
+
+ // Override any of the following shortcuts because
+ // they are needed by the terminal
+ int keyCode = keyEvent->key() | keyEvent->modifiers();
+ switch ( keyCode )
+ {
+ // list is taken from the QLineEdit::event() code
+ case Qt::Key_Tab:
+ case Qt::Key_Delete:
+ case Qt::Key_Home:
+ case Qt::Key_End:
+ case Qt::Key_Backspace:
+ case Qt::Key_Left:
+ case Qt::Key_Right:
+ keyEvent->accept();
+ return true;
+ }
+ }
+ return QWidget::event( e );
+}
+
+void TerminalDisplay::setBellMode(int mode)
+{
+ _bellMode=mode;
+}
+
+void TerminalDisplay::enableBell()
+{
+ _allowBell = true;
+}
+
+void TerminalDisplay::bell(const QString&)
+{
+ if (_bellMode==NoBell) return;
+
+ //limit the rate at which bells can occur
+ //...mainly for sound effects where rapid bells in sequence
+ //produce a horrible noise
+ if ( _allowBell )
+ {
+ _allowBell = false;
+ QTimer::singleShot(500,this,SLOT(enableBell()));
+
+ if (_bellMode==SystemBeepBell)
+ {
+// KNotification::beep();
+ }
+ else if (_bellMode==NotifyBell)
+ {
+// KNotification::event("BellVisible", message,QPixmap(),this);
+ }
+ else if (_bellMode==VisualBell)
+ {
+ swapColorTable();
+ QTimer::singleShot(200,this,SLOT(swapColorTable()));
+ }
+ }
+}
+
+void TerminalDisplay::swapColorTable()
+{
+ ColorEntry color = _colorTable[1];
+ _colorTable[1]=_colorTable[0];
+ _colorTable[0]= color;
+ _colorsInverted = !_colorsInverted;
+ update();
+}
+
+void TerminalDisplay::clearImage()
+{
+ // We initialize _image[_imageSize] too. See makeImage()
+ for (int i = 0; i <= _imageSize; i++)
+ {
+ _image[i].character = ' ';
+ _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
+ DEFAULT_FORE_COLOR);
+ _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
+ DEFAULT_BACK_COLOR);
+ _image[i].rendition = DEFAULT_RENDITION;
+ }
+}
+
+void TerminalDisplay::calcGeometry()
+{
+ _scrollBar->resize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent),
+ contentsRect().height());
+ switch(_scrollbarLocation)
+ {
+ case NoScrollBar :
+ _leftMargin = DEFAULT_LEFT_MARGIN;
+ _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN;
+ break;
+ case ScrollBarLeft :
+ _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width();
+ _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
+ _scrollBar->move(contentsRect().topLeft());
+ break;
+ case ScrollBarRight:
+ _leftMargin = DEFAULT_LEFT_MARGIN;
+ _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
+ _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0));
+ break;
+ }
+
+ _topMargin = DEFAULT_TOP_MARGIN;
+ _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1;
+
+ if (!_isFixedSize)
+ {
+ // ensure that display is always at least one column wide
+ _columns = qMax(1,_contentWidth / _fontWidth);
+ _usedColumns = qMin(_usedColumns,_columns);
+
+ // ensure that display is always at least one line high
+ _lines = qMax(1,_contentHeight / _fontHeight);
+ _usedLines = qMin(_usedLines,_lines);
+ }
+}
+
+void TerminalDisplay::makeImage()
+{
+//qDebug("%s %d makeImage", __FILE__, __LINE__);
+ calcGeometry();
+
+ // confirm that array will be of non-zero size, since the painting code
+ // assumes a non-zero array length
+ Q_ASSERT( _lines > 0 && _columns > 0 );
+ Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns );
+
+ _imageSize=_lines*_columns;
+
+ // We over-commit one character so that we can be more relaxed in dealing with
+ // certain boundary conditions: _image[_imageSize] is a valid but unused position
+ _image = new Character[_imageSize+1];
+
+ clearImage();
+}
+
+// calculate the needed size
+void TerminalDisplay::setSize(int columns, int lines)
+{
+ //FIXME - Not quite correct, a small amount of additional space
+ // will be used for margins, the scrollbar etc.
+ // we need to allow for this so that '_size' does allow
+ // enough room for the specified number of columns and lines to fit
+
+ QSize newSize = QSize( columns * _fontWidth ,
+ lines * _fontHeight );
+
+ if ( newSize != size() )
+ {
+ _size = newSize;
+ updateGeometry();
+ }
+}
+
+void TerminalDisplay::setFixedSize(int cols, int lins)
+{
+ _isFixedSize = true;
+
+ //ensure that display is at least one line by one column in size
+ _columns = qMax(1,cols);
+ _lines = qMax(1,lins);
+ _usedColumns = qMin(_usedColumns,_columns);
+ _usedLines = qMin(_usedLines,_lines);
+
+ if (_image)
+ {
+ delete[] _image;
+ makeImage();
+ }
+ setSize(cols, lins);
+ QWidget::setFixedSize(_size);
+}
+
+QSize TerminalDisplay::sizeHint() const
+{
+ return _size;
+}
+
+
+/* --------------------------------------------------------------------- */
+/* */
+/* Drag & Drop */
+/* */
+/* --------------------------------------------------------------------- */
+
+void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
+{
+ if (event->mimeData()->hasFormat("text/plain"))
+ event->acceptProposedAction();
+}
+
+void TerminalDisplay::dropEvent(QDropEvent* event)
+{
+// KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
+
+ QString dropText;
+/* if (!urls.isEmpty())
+ {
+ for ( int i = 0 ; i < urls.count() ; i++ )
+ {
+ KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
+ QString urlText;
+
+ if (url.isLocalFile())
+ urlText = url.path();
+ else
+ urlText = url.url();
+
+ // in future it may be useful to be able to insert file names with drag-and-drop
+ // without quoting them (this only affects paths with spaces in)
+ urlText = KShell::quoteArg(urlText);
+
+ dropText += urlText;
+
+ if ( i != urls.count()-1 )
+ dropText += ' ';
+ }
+ }
+ else
+ {
+ dropText = event->mimeData()->text();
+ }
+*/
+ if(event->mimeData()->hasFormat("text/plain"))
+ {
+ emit sendStringToEmu(dropText.toLocal8Bit());
+ }
+}
+
+void TerminalDisplay::doDrag()
+{
+ dragInfo.state = diDragging;
+ dragInfo.dragObject = new QDrag(this);
+ QMimeData *mimeData = new QMimeData;
+ mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
+ dragInfo.dragObject->setMimeData(mimeData);
+ dragInfo.dragObject->start(Qt::CopyAction);
+ // Don't delete the QTextDrag object. Qt will delete it when it's done with it.
+}
+
+void TerminalDisplay::outputSuspended(bool suspended)
+{
+ //create the label when this function is first called
+ if (!_outputSuspendedLabel)
+ {
+ //This label includes a link to an English language website
+ //describing the 'flow control' (Xon/Xoff) feature found in almost
+ //all terminal emulators.
+ //If there isn't a suitable article available in the target language the link
+ //can simply be removed.
+ _outputSuspendedLabel = new QLabel( ("<qt>Output has been "
+ "<a href=\"http://en.wikipedia.org/wiki/XON\">suspended</a>"
+ " by pressing Ctrl+S."
+ " Press <b>Ctrl+Q</b> to resume.</qt>"),
+ this );
+
+ QPalette palette(_outputSuspendedLabel->palette());
+
+ palette.setColor(QPalette::Normal, QPalette::WindowText, QColor(Qt::white));
+ palette.setColor(QPalette::Normal, QPalette::Window, QColor(Qt::black));
+// KColorScheme::adjustForeground(palette,KColorScheme::NeutralText);
+// KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
+ _outputSuspendedLabel->setPalette(palette);
+ _outputSuspendedLabel->setAutoFillBackground(true);
+ _outputSuspendedLabel->setBackgroundRole(QPalette::Base);
+ _outputSuspendedLabel->setFont(QApplication::font());
+ _outputSuspendedLabel->setMargin(5);
+
+ //enable activation of "Xon/Xoff" link in label
+ _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse |
+ Qt::LinksAccessibleByKeyboard);
+ _outputSuspendedLabel->setOpenExternalLinks(true);
+ _outputSuspendedLabel->setVisible(false);
+
+ _gridLayout->addWidget(_outputSuspendedLabel);
+ _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding,
+ QSizePolicy::Expanding),
+ 1,0);
+
+ }
+
+ _outputSuspendedLabel->setVisible(suspended);
+}
+
+uint TerminalDisplay::lineSpacing() const
+{
+ return _lineSpacing;
+}
+
+void TerminalDisplay::setLineSpacing(uint i)
+{
+ _lineSpacing = i;
+ setVTFont(font()); // Trigger an update.
+}
+
+//#include "moc_TerminalDisplay.cpp"