/*
    This file is part of Konsole, a terminal emulator for KDE.

    Copyright 2006-2008 by Robert Knight <robertknight@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 "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/QTimer>
#include <QtGui/QToolTip>

// KDE
#include <KShell>
#include <KColorScheme>
#include <KCursor>
#include <KDebug>
#include <KLocale>
#include <KMenu>
#include <KNotification>
#include <KGlobalSettings>
#include <KShortcut>
#include <KIO/NetAccess>
#include <konq_operations.h>
#include <KFileItem>
#include <KLocalizedString>

// Konsole
#include "Filter.h"
#include "konsole_wcwidth.h"
#include "TerminalCharacterDecoder.h"
#include "Screen.h"
#include "ScreenWindow.h"
#include "LineFont.h"
#include "SessionController.h"

using namespace Konsole;

#ifndef loc
#define loc(X,Y) ((Y)*_columns+(X))
#endif

#define REPCHAR   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
                  "abcdefgjijklmnopqrstuvwxyz" \
                  "0123456789./+@"

// The following are almost IBM standard color codes, with some slight
// gamma correction for the dim colors to compensate for bright X screens.
// It contains the 8 ansiterm/xterm colors in 2 intensities.
const ColorEntry Konsole::base_color_table[TABLE_COLORS] =
{
  // Fixme: could add faint colors here, also.
  // normal
  ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 1), // Dfore, Dback
  ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0x18,0x18), 0), // Black, Red
  ColorEntry(QColor(0x18,0xB2,0x18), 0), ColorEntry( QColor(0xB2,0x68,0x18), 0), // Green, Yellow
  ColorEntry(QColor(0x18,0x18,0xB2), 0), ColorEntry( QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta
  ColorEntry(QColor(0x18,0xB2,0xB2), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 0), // Cyan, White
  // intensiv
  ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 1),
  ColorEntry(QColor(0x68,0x68,0x68), 0), ColorEntry( QColor(0xFF,0x54,0x54), 0),
  ColorEntry(QColor(0x54,0xFF,0x54), 0), ColorEntry( QColor(0xFF,0xFF,0x54), 0),
  ColorEntry(QColor(0x54,0x54,0xFF), 0), ColorEntry( QColor(0xFF,0x54,0xFF), 0),
  ColorEntry(QColor(0x54,0xFF,0xFF), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 0)
};

// static
bool TerminalDisplay::HAVE_TRANSPARENCY = false;

// we use this to force QPainter to display text in LTR mode
// more information can be found in: http://unicode.org/reports/tr9/ 
const QChar LTR_OVERRIDE_CHAR( 0x202D );

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                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 ( _screenWindow )
    {
        connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) );
        connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) );
        _screenWindow->setWindowLines(_lines);
    }
}

const ColorEntry* TerminalDisplay::colorTable() const
{
    return _colorTable;
}
void TerminalDisplay::setBackgroundColor(const QColor& color)
{
    _colorTable[DEFAULT_BACK_COLOR].color = color;

    QPalette p = palette();
    p.setColor( backgroundRole(), color );
    setPalette( p );

    // Avoid propagating the palette change to the scroll bar
    _scrollBar->setPalette( QApplication::palette() );

    update();
}
void TerminalDisplay::setForegroundColor(const QColor& color)
{
    _colorTable[DEFAULT_FORE_COLOR].color = color;

    update();
}
void TerminalDisplay::setColorTable(const ColorEntry table[])
{
    for (int i = 0; i < TABLE_COLORS; i++)
        _colorTable[i] = table[i];

    setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                   Font                                    */
/*                                                                           */
/* ------------------------------------------------------------------------- */

static inline bool isLineCharString(const QString& string)
{
    if ( string.length() == 0 )
        return false;

    return isSupportedLineChar( string.at(0).unicode() );
}

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 ( !QFontInfo(font).fixedPitch() )
  {
      kWarning() << "Using an unsupported variable-width font in the terminal.  This may produce display errors.";
  }

  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);

    // Konsole cannot handle non-integer font metrics
    font.setStyleStrategy(QFont::StyleStrategy(font.styleStrategy() | QFont::ForceIntegerMetrics));

    QWidget::setFont(font);
    fontChange(font);
  }
}

void TerminalDisplay::setFont(const QFont &)
{
    // ignore font change request if not coming from konsole itself
}

uint TerminalDisplay::lineSpacing() const
{
    return _lineSpacing;
}

void TerminalDisplay::setLineSpacing(uint i)
{
    _lineSpacing = i;
    setVTFont(font()); // Trigger an update.
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

TerminalDisplay::TerminalDisplay(QWidget* parent)
:QWidget(parent)
,_screenWindow(0)
,_allowBell(true)
,_gridLayout(0)
,_fontHeight(1)
,_fontWidth(1)
,_fontAscent(1)
,_boldIntense(true)
,_lines(1)
,_columns(1)
,_usedLines(1)
,_usedColumns(1)
,_contentHeight(1)
,_contentWidth(1)
,_image(0)
,_randomSeed(0)
,_resizing(false)
,_showTerminalSizeHint(true)
,_bidiEnabled(false)
,_actSel(0)
,_wordSelectionMode(false)
,_lineSelectionMode(false)
,_preserveLineBreaks(false)
,_columnSelectionMode(false)
,_scrollbarLocation(ScrollBarRight)
,_wordCharacters(":@-./_~")
,_bellMode(NotifyBell)
,_allowBlinkingText(true)
,_allowBlinkingCursor(false)
,_textBlinking(false)
,_cursorBlinking(false)
,_hasTextBlinker(false)
,_underlineLinks(true)
,_isFixedSize(false)
,_ctrlDrag(true)
,_tripleClickMode(SelectWholeLine)
,_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)
,_antialiasText(true)
,_sessionController(0)
{
  // terminal applications are not designed with Right-To-Left in mind,
  // so the layout is forced to Left-To-Right
  setLayoutDirection(Qt::LeftToRight);

  _topMargin = DEFAULT_TOP_MARGIN;
  _leftMargin = DEFAULT_LEFT_MARGIN;

  // create scroll bar for scrolling output up and down
  _scrollBar = new QScrollBar(this);
  // set the scroll bar's slider to occupy the whole area of the scroll bar initially
  setScroll(0,0);
  _scrollBar->setCursor( Qt::ArrowCursor );
  connect(_scrollBar, SIGNAL(valueChanged(int)),
          this, SLOT(scrollBarPositionChanged(int)));

  // setup timers for blinking cursor and text
  _blinkTextTimer   = new QTimer(this);
  connect(_blinkTextTimer, SIGNAL(timeout()), this, SLOT(blinkTextEvent()));
  _blinkCursorTimer   = new QTimer(this);
  connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));

  // hide mouse cursor on keystroke or idle
  KCursor::setAutoHideCursor( this, true );
  setMouseTracking(true);

  setUsesMouse(true);

  setColorTable(base_color_table);

  // Enable drag and drop support
  setAcceptDrops(true);
  _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->setContentsMargins(0, 0, 0, 0);

  setLayout( _gridLayout );

  new AutoScrollHandler(this);
}

TerminalDisplay::~TerminalDisplay()
{
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

    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)
};


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& originalPen = painter.pen();

    if ( (attributes->rendition & RE_BOLD) && _boldIntense )
    {
        QPen boldPen(originalPen);
        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( originalPen );
}

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::setWallpaper(ColorSchemeWallpaper::Ptr p)
{
    _wallpaper = p;
}

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 ( useOpacitySetting && !_wallpaper->isNull() &&
	     _wallpaper->draw(painter, contentsRect) )
	{
	}
        else 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)
{
    // don't draw cursor which is currently blinking
    if ( _cursorBlinking )
        return ;

    QRect cursorRect = rect;
    cursorRect.setHeight(_fontHeight - _lineSpacing - 1);

    QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor ;
    painter.setPen(cursorColor);

    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));

        // draw the cursor body only when the widget has focus
        if ( hasFocus() )
        {
            painter.fillRect(cursorRect, cursorColor);

            if ( !_cursorColor.isValid() )
            {
                // invert the color 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 ( _textBlinking && (style->rendition & RE_BLINK) )
        return;

    // setup bold and underline
    bool useBold;
    ColorEntry::FontWeight weight = style->fontWeight(_colorTable);
    if (weight == ColorEntry::UseCurrentFormat)
        useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
    else
        useBold = (weight == ColorEntry::Bold) ? true : false;
    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);
    }

    // setup pen
    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
    {
        // Force using LTR as the document layout for the terminal area, because
        // there is no use cases for RTL emulator and RTL terminal application.
        //
        // This still allows RTL characters to be rendered in the RTL way.
        painter.setLayoutDirection(Qt::LeftToRight) ;

        // 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
        //
        // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2
        if (_bidiEnabled) {
            painter.drawText(rect, 0, text);
        }
        else
        {
            // See bug 280896 for more info
#if QT_VERSION >= 0x040800
            painter.drawText(rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + text);
#else
            painter.drawText(rect, 0, LTR_OVERRIDE_CHAR + text);
#endif
        }
    }
}

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; }


// 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
void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
{
    // if the flow control warning is enabled this will interfere with the 
    // scrolling optimizations and cause artifacts.  the simple solution here
    // is to just disable the optimization 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) ); 

    // return if there is nothing to do
    if (    lines == 0 
         || _image == 0
         || !region.isValid() 
         || (region.top() + abs(lines)) >= region.bottom() 
         || this->_lines <= region.height() ) return;

    // hide terminal size label to prevent it being scrolled
    if (_resizeWidget && _resizeWidget->isVisible())
        _resizeWidget->hide();

    // Note:  With Qt 4.4 the left edge of the scrolled area must be at 0
    // to get the correct (newly exposed) part of the widget repainted.
    //
    // The right edge must be before the left edge of the scroll bar to 
    // avoid triggering a repaint of the entire widget, the distance is 
    // given by SCROLLBAR_CONTENT_GAP
    //
    // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
    // application to monitor repainting.
    //
    int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width();
    const int SCROLLBAR_CONTENT_GAP = 1;
    QRect scrollRect;
    if ( _scrollbarLocation == ScrollBarLeft )
    {
        scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP);
        scrollRect.setRight(width());
    }
    else
    {
        scrollRect.setLeft(0);
        scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
    }
    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
        scrollRect.setTop(top);
    }
    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
        scrollRect.setTop(top + abs(lines) * _fontHeight); 
    }
    scrollRect.setHeight(linesToMove * _fontHeight );

    Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());

    //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 r;
        if (hotSpot->startLine()==hotSpot->endLine()) {
            r.setLeft(hotSpot->startColumn());
            r.setTop(hotSpot->startLine());
            r.setRight(hotSpot->endColumn());
            r.setBottom(hotSpot->endLine());
            region |= imageToWidget(r);;
        } else {
            r.setLeft(hotSpot->startColumn());
            r.setTop(hotSpot->startLine());
            r.setRight(_columns);
            r.setBottom(hotSpot->startLine());
            region |= imageToWidget(r);;
            for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) {
                r.setLeft(0);
                r.setTop(line);
                r.setRight(_columns);
                r.setBottom(line);
                region |= imageToWidget(r);;
            }
            r.setLeft(0);
            r.setTop(hotSpot->endLine());
            r.setRight(hotSpot->endColumn());
            r.setBottom(hotSpot->endLine());
            region |= imageToWidget(r);;
        }
    }
    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
  if (_wallpaper->isNull())
  {
      scrollImage( _screenWindow->scrollCount() ,
		   _screenWindow->scrollRegion() );
      _screenWindow->resetScrollCount();
  }

  if (!_image) {
     // Create _image.
     // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
     updateImageSize();
  }

  Character* const newimg = _screenWindow->getImage();
  int lines = _screenWindow->windowLines();
  int columns = _screenWindow->windowColumns();

  setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );

  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();
  _hasTextBlinker = false;

  CharacterColor cf;       // undefined

  const int linesToUpdate = qMin(this->_lines, qMax(0,lines  ));
  const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));

  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)
    {
      _hasTextBlinker |= (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])
      {
        if (!newLine[x+0].character)
            continue;
        const bool lineDraw = newLine[x+0].isLineChar();
        const bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
        const quint8 cr = newLine[x].rendition;
        const CharacterColor 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 & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) ||
                  !dirtyMask[x+len] || 
                  ch.isLineChar() != lineDraw || 
                  nextIsDoubleWidth != doubleWidth )
            break;
        }

        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 ( _hasTextBlinker && !_blinkTextTimer->isActive()) _blinkTextTimer->start( TEXT_BLINK_DELAY ); 
  if (!_hasTextBlinker && _blinkTextTimer->isActive()) { _blinkTextTimer->stop(); _textBlinking = false; }
  delete[] dirtyMask;

}

void TerminalDisplay::showResizeNotification()
{
    static bool resizeForTheFirstTime = true;
    if ( resizeForTheFirstTime )
    {
        // Do not display size hint when resizing for the first time.
        // That first resizing mostly happens on startup
        resizeForTheFirstTime = false ;
        return;
    }

    if (_showTerminalSizeHint && isVisible())
    {
        if (!_resizeWidget)
        {
            _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
            _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(i18n("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 = i18n("Size: %1 x %2", _columns, _lines);
        _resizeWidget->setText(sizeStr);
        _resizeWidget->move((width()-_resizeWidget->width())/2,
                (height()-_resizeWidget->height())/2+20);
        _resizeWidget->show();
        _resizeTimer->start(1000);
    }
}

void TerminalDisplay::setBlinkingCursorEnabled(bool blink)
{
  _allowBlinkingCursor = blink;

  if (blink && !_blinkCursorTimer->isActive()) 
      _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);

  if (!blink && _blinkCursorTimer->isActive()) 
  {
    _blinkCursorTimer->stop();
    if (_cursorBlinking)
      blinkCursorEvent();
    else
      _cursorBlinking = false;
  }
}

void TerminalDisplay::setBlinkingTextEnabled(bool blink)
{
    _allowBlinkingText = blink;

    if (blink && !_blinkTextTimer->isActive()) 
        _blinkTextTimer->start(TEXT_BLINK_DELAY);

    if (!blink && _blinkTextTimer->isActive()) 
    {
        _blinkTextTimer->stop();
        _textBlinking = false;
    }
}

void TerminalDisplay::focusOutEvent(QFocusEvent*)
{
    // trigger a repaint of the cursor so that it is both visible (in case
    // it was hidden during blinking)
    // and drawn in a focused out state
    _cursorBlinking = false;
    updateCursor();

    _blinkCursorTimer->stop();
    if (_textBlinking)
        blinkTextEvent();

    _blinkTextTimer->stop();
}
void TerminalDisplay::focusInEvent(QFocusEvent*)
{
    if (_allowBlinkingCursor)
        _blinkCursorTimer->start();

    updateCursor();

    if (_hasTextBlinker)
        _blinkTextTimer->start();
}

void TerminalDisplay::paintEvent( QPaintEvent* pe )
{
  QPainter paint(this);

  foreach (const QRect& rect, (pe->region() & contentsRect()).rects())
  {
    drawBackground(paint,rect,palette().background().color(),
                    true /* use opacity setting */);
    drawContents(paint, rect);
  }
  drawInputMethodPreeditString(paint,preeditRect());
  paintFilters(paint);
}

QPoint TerminalDisplay::cursorPosition() const
{
    if (_screenWindow)
        return _screenWindow->cursorPosition();
    else
        return QPoint(0,0);
}

FilterChain* TerminalDisplay::filterChain() const
{
    return _filterChain;
}

void TerminalDisplay::paintFilters(QPainter& painter)
{
    // get color of character under mouse and use it to draw
    // lines for filters
    QPoint cursorPos = mapFromGlobal(QCursor::pos());
    int cursorLine;
    int cursorColumn;
    int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0;

    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();

        QRegion region;
        if ( _underlineLinks && spot->type() == Filter::HotSpot::Link ) {
            QRect r;
            if (spot->startLine()==spot->endLine()) {
                r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, 
                             spot->startLine()*_fontHeight + 1,
                             (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth, 
                             (spot->endLine()+1)*_fontHeight - 1 ); 
                region |= r;
            } else {
                r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth, 
                             spot->startLine()*_fontHeight + 1,
                             (_columns-1)*_fontWidth - 1 + scrollBarWidth, 
                             (spot->startLine()+1)*_fontHeight - 1 ); 
                region |= r;
                for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
                    r.setCoords( 0*_fontWidth + 1 + scrollBarWidth, 
                                 line*_fontHeight + 1,
                                 (_columns-1)*_fontWidth - 1 + scrollBarWidth,
                                 (line+1)*_fontHeight - 1 ); 
                    region |= r;
                }
                r.setCoords( 0*_fontWidth + 1 + scrollBarWidth,
                             spot->endLine()*_fontHeight + 1,
                             (spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth,
                             (spot->endLine()+1)*_fontHeight - 1 ); 
                region |= r;
            }
        }

        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 ( _image[loc(endColumn,line)].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 + scrollBarWidth,
                         line*_fontHeight + 1,
                         endColumn*_fontWidth - 1 + scrollBarWidth,
                         (line+1)*_fontHeight - 1 ); 
            // Underline link hotspots 
            if ( _underlineLinks && 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 ( region.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 color for this
                painter.fillRect(r,QBrush(QColor(255,0,0,120)));
            }
        }
    }
}
void TerminalDisplay::drawContents(QPainter& paint, const QRect& rect)
{
  const QPoint tL  = contentsRect().topLeft();
  const int    tLx = tL.x();
  const int    tLy = tL.y();

  const int lux = qMin(_usedColumns-1, qMax(0,(rect.left()   - tLx - _leftMargin ) / _fontWidth));
  const int luy = qMin(_usedLines-1,  qMax(0,(rect.top()    - tLy - _topMargin  ) / _fontHeight));
  const int rlx = qMin(_usedColumns-1, qMax(0,(rect.right()  - tLx - _leftMargin ) / _fontWidth));
  const int rly = qMin(_usedLines-1,  qMax(0,(rect.bottom() - tLy - _topMargin  ) / _fontHeight));

  const int numberOfColumns = _usedColumns;
  QString unistr;
  unistr.reserve(numberOfColumns);
  for (int y = luy; y <= rly; y++)
  {
    int x = lux;
    if(!_image[loc(lux,y)].character && x)
      x--; // Search for start of multi-column character
    for (; x <= rlx; x++)
    {
      int len = 1;
      int p = 0;

      // reset our buffer to the number of columns
      int bufferSize = numberOfColumns;
      unistr.resize(bufferSize);
      QChar *disstrU = unistr.data();

      // 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;
        const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x,y)].character, extendedCharLength);
        if (chars)
        {
          Q_ASSERT(extendedCharLength > 1);
          bufferSize += extendedCharLength - 1;
          unistr.resize(bufferSize);
          disstrU = unistr.data();
          for ( int index = 0 ; index < extendedCharLength ; index++ ) 
          {
            Q_ASSERT( p < bufferSize );
            disstrU[p++] = chars[index];
          }
        }
      }
      else
      {
        // single character
        const quint16 c = _image[loc(x,y)].character;
        if (c)
        {
             Q_ASSERT( p < bufferSize );
             disstrU[p++] = c; //fontMap(c);
        }
      }

      const bool lineDraw = _image[loc(x,y)].isLineChar();
      const bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
      const CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
      const CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
      const 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 & ~RE_EXTENDED_CHAR) == (currentRendition & ~RE_EXTENDED_CHAR) &&
             (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth &&
             _image[loc(x+len,y)].isLineChar() == lineDraw)
      {
        const quint16 c = _image[loc(x+len,y)].character;
        if ( _image[loc(x+len,y)].rendition & RE_EXTENDED_CHAR )
        {
            // sequence of characters
            ushort extendedCharLength = 0;
            const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
            if (chars)
            {
              Q_ASSERT(extendedCharLength > 1);
              bufferSize += extendedCharLength - 1;
              unistr.resize(bufferSize);
              disstrU = unistr.data();
              for ( int index = 0 ; index < extendedCharLength ; index++ ) 
              {
                Q_ASSERT( p < bufferSize );
                disstrU[p++] = chars[index];
              }
            }
        }
        else
        {
            // single character
            if (c)
            {
                Q_ASSERT( p < bufferSize );
                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;
         unistr.resize(p);

         // Create a text scaling matrix for double width and double height lines.
         QMatrix textScale;

         if (y < _lineProperties.size())
         {
            if (_lineProperties[y] & LINE_DOUBLEWIDTH)
                textScale.scale(2,1);

            if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
                textScale.scale(1,2);
         }

         //Apply text scaling matrix.
         paint.setWorldMatrix(textScale, true);

         //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)    
         textArea.moveTopLeft( textScale.inverted().map(textArea.topLeft()) );

         //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.setWorldMatrix(textScale.inverted(), true);

         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;
    }
  }
}

void TerminalDisplay::blinkTextEvent()
{
    if (!_allowBlinkingText)
        return;

    _textBlinking = !_textBlinking;

    // TODO: Optimize 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
{
    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::updateCursor()
{
    QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) ); 
    update(cursorRect);
}

void TerminalDisplay::blinkCursorEvent()
{
    _cursorBlinking = !_cursorBlinking;
    updateCursor();
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                          Geometry & 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()
{
    Character* oldImage = _image;
    int oldLines = _lines;
    int oldColumns = _columns;

    makeImage();

    if ( oldImage )
    {
        // copy the old image to reduce flicker
        int lines = qMin(oldLines,_lines);
        int columns = qMin(oldColumns,_columns);
        for (int line = 0; line < lines; line++) 
        {
            memcpy( (void*)&_image[_columns*line],
                    (void*)&oldImage[oldColumns*line],
                    columns*sizeof(Character) );
        }
        delete[] oldImage;
    }

    if (_screenWindow)
        _screenWindow->setWindowLines(_lines);

    _resizing = (oldLines != _lines) || (oldColumns != _columns);

    if ( _resizing )
    {
        showResizeNotification();
        emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
    }

    _resizing = false;
}

void TerminalDisplay::makeImage()
{
    _wallpaper->load();

    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();
}

void TerminalDisplay::clearImage()
{
    for (int i = 0; i <= _imageSize; ++i)
        _image[i] = Screen::defaultChar;
}

void TerminalDisplay::calcGeometry()
{
    _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
    switch (_scrollbarLocation)
    {
        case ScrollBarHidden :
            _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);
    }
}

// calculate the needed size, this must be synced with calcGeometry()
void TerminalDisplay::setSize(int columns, int lines)
{
    int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width();
    int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN;
    int verticalMargin = 2 * DEFAULT_TOP_MARGIN;

    QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth)  ,
                           verticalMargin + (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;
}

//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.
//
//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::setScrollBarPosition(ScrollBarPosition position)
{
    if (_scrollbarLocation == position)
        return;

    if ( position == ScrollBarHidden )
        _scrollBar->hide();
    else
        _scrollBar->show();

    _topMargin = _leftMargin = 1;
    _scrollbarLocation = position;

    propagateSize();
    update();
}

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)
{
  // 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)));
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                  Mouse                                    */
/*                                                                           */
/* ------------------------------------------------------------------------- */
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)
  {
    // request the software keyboard, if any
    if (qApp->autoSipEnabled()) {
      QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(
                  style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
      if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) {
        QEvent event(QEvent::RequestSoftwareInputPanel);
        QApplication::sendEvent(this, &event);
      }
    }

    _lineSelectionMode = false;
    _wordSelectionMode = false;

    bool selected = false;
    // The user clicked inside selected text
    selected =  _screenWindow->isSelected(pos.x(),pos.y());

    // Drag only when the Control key is hold
    if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
      _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();

        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 || (ev->modifiers() & Qt::ShiftModifier) )
      doPaste(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(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);

  int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0;

  // handle filters
  // change link hot-spot appearance on mouse-over
  Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
  if ( _underlineLinks && spot && spot->type() == Filter::HotSpot::Link)
  {
    QRegion previousHotspotArea = _mouseOverHotspotArea;
    _mouseOverHotspotArea = QRegion();
    QRect r;
    if (spot->startLine()==spot->endLine()) {
        r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth, 
                     spot->startLine()*_fontHeight,
                     spot->endColumn()*_fontWidth + scrollBarWidth, 
                     (spot->endLine()+1)*_fontHeight - 1 ); 
        _mouseOverHotspotArea |= r;
    } else {
        r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth,
                     spot->startLine()*_fontHeight,
                     _columns*_fontWidth - 1 + scrollBarWidth,
                     (spot->startLine()+1)*_fontHeight ); 
        _mouseOverHotspotArea |= r;
        for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
            r.setCoords( 0*_fontWidth + scrollBarWidth, 
                         line*_fontHeight,
                         _columns*_fontWidth + scrollBarWidth,
                         (line+1)*_fontHeight ); 
            _mouseOverHotspotArea |= r;
        }
        r.setCoords( 0*_fontWidth + scrollBarWidth, 
                     spot->endLine()*_fontHeight,
                     spot->endColumn()*_fontWidth + scrollBarWidth, 
                     (spot->endLine()+1)*_fontHeight ); 
        _mouseOverHotspotArea |= r;
    }
    // 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.boundingRect() );
    }

    update( _mouseOverHotspotArea | previousHotspotArea );
  }
  else if ( !_mouseOverHotspotArea.isEmpty() )
  {
        update( _mouseOverHotspotArea );
        // set hotspot area to an invalid rectangle
        _mouseOverHotspotArea = QRegion();
  }

  // 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 = 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

       _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() );
}

void TerminalDisplay::extendSelection( const QPoint& position )
{
  if ( !_screenWindow )
      return;

  //if ( !contentsRect().contains(ev->pos()) ) return;
  const QPoint tL  = contentsRect().topLeft();
  const int    tLx = tL.x();
  const int    tLy = tL.y();
  const 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.

  int linesBeyondWidget = 0;

  QRect textBounds(tLx + _leftMargin,
                     tLy + _topMargin,
                   _usedColumns*_fontWidth-1,
                   _usedLines*_fontHeight-1);

  QPoint pos = position;

  // Adjust position within text area bounds.
  QPoint oldpos = pos;

  pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) );
  pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) );

  if ( oldpos.y() > textBounds.bottom() ) 
  {
      linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight;
    _scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward
  }
  if ( oldpos.y() < textBounds.top() )
  {
      linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight;
    _scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // history
  }

  int charColumn = 0;
  int charLine = 0;
  getCharacterPosition(pos,charLine,charColumn);

  QPoint here = QPoint(charColumn,charLine);
  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;
    QChar 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]);
      while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) )) 
                      && charClass(_image[i-1]) == 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]);
      while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) )) 
                      && charClass(_image[i+1]) == 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;
    QChar 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]);
       /* 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)
  {
    if(_dragInfo.state == diPending)
    {
      // We had a drag event pending but never confirmed.  Kill selection
       _screenWindow->clearSelection();
    }
    else
    {
      if ( _actSel > 1 )
      {
          setXSelection(  _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...
  const QChar selClass = charClass(_image[i]);
  {
     // 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]) == 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]) == 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 ( ((_image[i].rendition & RE_EXTENDED_CHAR) == 0) &&
          ( QChar( _image[i].character ) == '@' ) &&
          ( ( endSel.x() - bgnSel.x() ) > 0 ) )
     {
       endSel.setX( x - 1 );
     }


     _actSel = 2; // within selection

     _screenWindow->setSelectionEnd( endSel.x() , endSel.y() );

     setXSelection( _screenWindow->selectedText(_preserveLineBreaks) ); 
   }

  _possibleTripleClick=true;

  QTimer::singleShot(QApplication::doubleClickInterval(),this,
                     SLOT(tripleClickTimeout()));
}

void TerminalDisplay::wheelEvent( QWheelEvent* ev )
{
  if (ev->orientation() != Qt::Vertical)
    return;

  // ctrl+<wheel> for zomming, like in konqueror and firefox
  if ( ev->modifiers() & Qt::ControlModifier )
  {
      if ( ev->delta() > 0 )
          // wheel-up
          _sessionController->increaseTextSize();
      else
          // wheel-down
          _sessionController->decreaseTextSize();

      return;
  }

  // if the terminal program is not interested mouse events
  // then send the event to the scrollbar if the slider has room to move
  // or otherwise send simulated up / down key presses to the terminal program
  // for the benefit of programs such as 'less'
  if ( _mouseMarks )
  {
    bool canScroll = _scrollBar->maximum() > 0;
    if ( canScroll )
    {
        _scrollBar->event(ev);
    }
    else
    {
        // assume that each Up / Down key event will cause the terminal application
        // to scroll by one line.  
        //
        // to get a reasonable scrolling speed, scroll by one line for every 5 degrees
        // of mouse wheel rotation.  Mouse wheels typically move in steps of 15 degrees,
        // giving a scroll of 3 lines
        int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down;

        // QWheelEvent::delta() gives rotation in eighths of a degree
        int wheelDegrees = ev->delta() / 8;
        int linesToScroll = abs(wheelDegrees) / 5;

        QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier);

        for (int i=0;i<linesToScroll;i++)
            emit keyPressedSignal(&keyScrollEvent);
    }
  }
  else
  {
    // terminal program wants notification of mouse activity

    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

  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());
    const QChar selClass = charClass(_image[i]);
    int x = _iPntSel.x();

    while ( ((x>0) || 
             (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
            ) 
            && charClass(_image[i-1]) == 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() );

  setXSelection(_screenWindow->selectedText(_preserveLineBreaks));

  _iPntSel.ry() += _scrollBar->value();
}

bool TerminalDisplay::focusNextPrevChild( bool next )
{
    // for 'Tab', always disable focus switching among widgets
    // for 'Shift+Tab', leave the decision to higher level
    if (next)
        return false;
    else
        return QWidget::focusNextPrevChild( next );
}


QChar TerminalDisplay::charClass(const Character& ch) const
{
    if (ch.rendition & RE_EXTENDED_CHAR)
    {
        ushort extendedCharLength = 0;
        const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
        if (chars && extendedCharLength > 0)
        {
            const QString s = QString::fromUtf16(chars, extendedCharLength);
            if (_wordCharacters.contains(s, Qt::CaseInsensitive))
                return 'a';
            bool allLetterOrNumber = true;
            for (int i = 0; allLetterOrNumber && i < s.size(); ++i)
                allLetterOrNumber = s.at(i).isLetterOrNumber();
            return allLetterOrNumber ? 'a' : s.at(0);
        }
        return 0;
    }
    else
    {
        const QChar qch(ch.character);
        if ( qch.isSpace() ) return ' ';

        if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) )
            return 'a';

        return qch;
    }
}

void TerminalDisplay::setWordCharacters(const QString& wc)
{
    _wordCharacters = wc;
}

// FIXME: the actual value of _mouseMarks is the opposite of its semantic.
// When using programs not interested with mouse(shell, less), it is true.
// When using programs interested with mouse(vim,mc), it is false.
void TerminalDisplay::setUsesMouse(bool on)
{
    _mouseMarks = on;
    setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
}
bool TerminalDisplay::usesMouse() const
{
    return _mouseMarks;
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                               Clipboard                                   */
/*                                                                           */
/* ------------------------------------------------------------------------- */

void TerminalDisplay::doPaste(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::setXSelection(const QString& text)
{
    QApplication::clipboard()->setText(text, QClipboard::Selection);
}

void TerminalDisplay::copyToClipboard()
{
  if ( !_screenWindow )
      return;

  QString text = _screenWindow->selectedText(_preserveLineBreaks);
  if (!text.isEmpty())
    QApplication::clipboard()->setText(text);
}

void TerminalDisplay::pasteFromClipboard()
{
    doPaste(false,false);
}

void TerminalDisplay::pasteFromXSelection()
{
    doPaste(true,false);
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                Input Method                               */
/*                                                                           */
/* ------------------------------------------------------------------------- */

void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event )
{
    if ( !event->commitString().isEmpty() )
    {
        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 = cursorPosition();
    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;
        default:
            break;
    }

    return QVariant();
}

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; 
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                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::scrollScreenWindow( enum ScreenWindow::RelativeScrollMode mode, int amount )
{
    _screenWindow->scrollBy(mode, amount);
    _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
    updateLineProperties();
    updateImage();
}

void TerminalDisplay::keyPressEvent( QKeyEvent* event )
{
    _screenWindow->screen()->setCurrentTerminalDisplay(this);

    _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
              // know where the current selection is.

    if (_allowBlinkingCursor) 
    {
      _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
      if (_cursorBlinking)
        blinkCursorEvent();
      else
        _cursorBlinking = false;
    }

    emit keyPressedSignal(event);

    event->accept();
}


bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent)
{
    int modifiers = keyEvent->modifiers();

    //  When a possible shortcut combination is pressed, 
    //  emit the overrideShortcutCheck() signal to allow the host
    //  to decide whether the terminal should override it or not.
    if (modifiers != Qt::NoModifier) 
    {
        int modifierCount = 0;
        unsigned int currentModifier = Qt::ShiftModifier;

        while (currentModifier <= Qt::KeypadModifier)
        {
            if (modifiers & currentModifier)
                modifierCount++;
            currentModifier <<= 1;
        }
        if (modifierCount < 2) 
        {
            bool override = false;
            emit overrideShortcutCheck(keyEvent,override);
            if (override)
            {
                keyEvent->accept();
                return true;
            }
        }
    }

    // Override any of the following shortcuts because
    // they are needed by the terminal
    int keyCode = keyEvent->key() | 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 false;
}

bool TerminalDisplay::event(QEvent* event)
{
  bool eventHandled = false;
  switch (event->type())
  {
    case QEvent::ShortcutOverride:
        eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event);
        break;
    case QEvent::PaletteChange:
    case QEvent::ApplicationPaletteChange:
        _scrollBar->setPalette( QApplication::palette() );
        break;
    default:
        break;
  }
  return eventHandled ? true : QWidget::event(event); 
}

void TerminalDisplay::setBellMode(int mode)
{
    _bellMode=mode;
}

void TerminalDisplay::enableBell()
{
    _allowBell = true;
}

void TerminalDisplay::bell(const QString& message)
{
  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(hasFocus() ? "BellVisible" : "BellInvisible",
                             message,QPixmap(),this);
    } 
    else if (_bellMode==VisualBell) 
    {
        visualBell();
    }
  }
}

void TerminalDisplay::visualBell( )
{
    swapColorTable();
    QTimer::singleShot(200,this,SLOT(swapColorTable()));
}

void TerminalDisplay::swapColorTable()
{
    // swap the default foreground & backround color
    ColorEntry color = _colorTable[DEFAULT_BACK_COLOR];
    _colorTable[DEFAULT_BACK_COLOR]=_colorTable[DEFAULT_FORE_COLOR];
    _colorTable[DEFAULT_FORE_COLOR]= color;
    _colorsInverted = !_colorsInverted;

    update();
}


/* --------------------------------------------------------------------- */
/*                                                                       */
/* Drag & Drop                                                           */
/*                                                                       */
/* --------------------------------------------------------------------- */

void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
{
  // text/plain alone is enough for KDE-apps
  // text/uri-list is for supporting some non-KDE apps, such as thunar
  //   and pcmanfm
  // That also applies in dropEvent()
  if (event->mimeData()->hasFormat("text/plain") ||
      event->mimeData()->hasFormat("text/uri-list"))
  {
      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;

        // Each filename(including the last) should be followed by one space.
        dropText += ' ';
    }

    // If our target is local we will open a popup - otherwise the fallback kicks
    // in and the URLs will simply be pasted as text.
    if(_sessionController && _sessionController->url().isLocalFile())
    {
      // A standard popup with Copy, Move and Link as options -
      // plus an additional Paste option.

      QAction* pasteAction = new QAction(i18n("&Paste as text"), this);
      pasteAction->setData(dropText);
      connect(pasteAction, SIGNAL(triggered()), this, SLOT(dropMenuPasteTriggered()));

      QList<QAction*> additionalActions;
      additionalActions.append(pasteAction);

      KUrl target(_sessionController->currentDir());

      KonqOperations::doDrop(KFileItem(), target, event, this, additionalActions);

      return;
    }

  }
  else 
  {
    dropText = event->mimeData()->text();
  }

  if (event->mimeData()->hasFormat("text/plain") ||
      event->mimeData()->hasFormat("text/uri-list"))
  {
    emit sendStringToEmu(dropText.toLocal8Bit());
  }
}

void TerminalDisplay::dropMenuPasteTriggered()
{
    if ( sender() )
    {
        const QAction* action = dynamic_cast<const QAction*>(sender());
        if ( action )
        {
            emit sendStringToEmu(action->data().toString().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->exec(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( i18n("<qt>Output has been "
                                                "<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>"
                                                " by pressing Ctrl+S."
                                               "  Press <b>Ctrl+Q</b> to resume.</qt>"),
                                               this );

            QPalette palette(_outputSuspendedLabel->palette());
            KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
            _outputSuspendedLabel->setPalette(palette);
            _outputSuspendedLabel->setAutoFillBackground(true);
            _outputSuspendedLabel->setBackgroundRole(QPalette::Base);
            _outputSuspendedLabel->setFont(QApplication::font());
            _outputSuspendedLabel->setContentsMargins(5, 5, 5, 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);
}

void TerminalDisplay::setSessionController(SessionController* controller)
{
    _sessionController = controller;
}

AutoScrollHandler::AutoScrollHandler(QWidget* parent)
: QObject(parent)
, _timerId(0)
{
    parent->installEventFilter(this);
}
void AutoScrollHandler::timerEvent(QTimerEvent* event)
{
    if (event->timerId() != _timerId)
        return;

    QMouseEvent mouseEvent(    QEvent::MouseMove,
                              widget()->mapFromGlobal(QCursor::pos()),
                              Qt::NoButton,
                              Qt::LeftButton,
                              Qt::NoModifier);

    QApplication::sendEvent(widget(),&mouseEvent);    
}
bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event)
{
    Q_ASSERT( watched == parent() );
    Q_UNUSED( watched );

    QMouseEvent* mouseEvent = (QMouseEvent*)event;
    switch ( event->type() )
    {
        case QEvent::MouseMove:
        {
            bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
            if ( mouseInWidget )
            {
                if (_timerId)
                    killTimer(_timerId);

                _timerId = 0;
            }
            else
            {
                if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton))
                    _timerId = startTimer(100);
            }

            break;
        }
        case QEvent::MouseButtonRelease:
        {
            if ( _timerId && (mouseEvent->buttons() & ~Qt::LeftButton) )
            {
                killTimer(_timerId);
                _timerId = 0;
            }
            break;
        }
        default:
            break;
    };

    return false;
}

#include "TerminalDisplay.moc"
