view libgui/qterminal/libqterminal/win32/QWinTerminalImpl.cpp @ 32413:ee5d0bded6d4

gui: Improve support for fractional scaling in command widget on Windows. For long lines, the incorrect character might be repainted when moving the cursor. Use fractional character widths to derive the index of the character in a line at the current cursor position in pixels. * libgui/qterminal/libqterminal/win32/QWinTerminalImpl.cpp (QConsolePrivate::m_charSize): Use floating point type QSizeF. (QConsolePrivate::updateConsoleSize): Determine (approximate) average character width of used font in floating point precision. (QConsolePrivate::posToCell, QConsolePrivate::cursorRect, QConsolePrivate::boundingRect, QWinTerminalImpl::viewPaintEvent): Adapt to using average character width in floating point precision.
author Markus Mützel <markus.muetzel@gmx.de>
date Sat, 14 Oct 2023 19:13:12 +0200
parents 738dd6bcd270
children 2e484f9f1f18
line wrap: on
line source

/*

Copyright (C) 2011-2023 The Octave Project Developers

This file is part of QConsole.

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 3 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,
see <https://www.gnu.org/licenses/>.

*/

#include <algorithm>
#include <cmath>
#include <csignal>
#include <cstdio>
#include <cstdarg>
#include <cstring>

#define WIN32_LEAN_AND_MEAN
#if ! defined (_WIN32_WINNT) && ! defined (NTDDI_VERSION)
#  define _WIN32_WINNT 0x0500
#endif
#include <windows.h>
#include <fcntl.h>
#include <io.h>
#include <versionhelpers.h>

#include <QtDebug>
#include <QApplication>
#include <QClipboard>
#include <QColor>
#include <QFont>
#include <QGridLayout>
#include <QPaintEvent>
#include <QPainter>
#include <QResizeEvent>
#include <QScrollBar>
#include <QSize>
#include <QSizeF>
#include <QThread>
#include <QTimer>
#include <QToolTip>
#include <QCursor>
#include <QMessageBox>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QUrl>
#include <QMimeData>

#include "QWinTerminalImpl.h"
#include "QTerminalColors.h"

// Uncomment to log activity to LOGFILENAME
// #define DEBUG_QCONSOLE
#define LOGFILENAME "QConsole.log"
// Uncomment to create hidden console window
#define HIDDEN_CONSOLE

#ifdef _MSC_VER
#  pragma warning(disable : 4996)
#endif

//////////////////////////////////////////////////////////////////////////////

class QConsoleView : public QWidget
{
public:
  QConsoleView (QWinTerminalImpl *parent = 0) : QWidget (parent), q (parent) { }
  ~QConsoleView (void) { }

protected:
  void paintEvent (QPaintEvent *event) { q->viewPaintEvent (this, event); }
  void resizeEvent (QResizeEvent *event) { q->viewResizeEvent (this, event); }

private:
  QWinTerminalImpl *q;
};

//////////////////////////////////////////////////////////////////////////////

class QConsoleThread : public QThread
{
public:
  QConsoleThread (QWinTerminalImpl *console) : QThread (console), q (console) { }

protected:
  void run (void)
    { q->start (); }

private:
  QWinTerminalImpl *q;
};

//////////////////////////////////////////////////////////////////////////////

static QString translateKey (QKeyEvent *ev)
{
  QString esc = "\x1b";
  QString s;

  if (ev->key () == Qt::Key_Delete)
    s = esc + "[C\b";
  else if (!ev->text ().isEmpty ())
    s = ev->text ();
  else
    {

      switch (ev->key ())
        {
        case Qt::Key_Up:
          s = esc + "[A";
          break;

        case Qt::Key_Down:
          s = esc + "[B";
          break;

        case Qt::Key_Right:
          s = esc + "[C";
          break;

        case Qt::Key_Left:
          s = esc + "[D";
          break;

        case Qt::Key_Home:
          s = esc + "[H";
          break;

        case Qt::Key_End:
          s = esc + "[F";
          break;

        case Qt::Key_Insert:
          s = esc + "[2~";
          break;

        case Qt::Key_PageUp:
          s = esc + "[5~";
          break;

        case Qt::Key_PageDown:
          s = esc + "[6~";
          break;

        case Qt::Key_Escape:
          s = esc;
          break;

        default:
          break;
        }
    }

  return s;
}

class QConsolePrivate
{
  friend class QWinTerminalImpl;

public:

  enum KeyboardCursorType
    {
      BlockCursor,
      UnderlineCursor,
      IBeamCursor
    };

  QConsolePrivate (QWinTerminalImpl *parent, const QString& cmd = QString ());
  ~QConsolePrivate (void);

  void updateConsoleSize (bool sync = false, bool allow_smaller_width = false);
  void syncConsoleParameters (void);
  void grabConsoleBuffer (CHAR_INFO* buf = 0);
  void updateHorizontalScrollBar (void);
  void updateVerticalScrollBar (void);
  void setHorizontalScrollValue (int value);
  void setVerticalScrollValue (int value);
  void updateConsoleView (bool grab = true);
  void monitorConsole (void);
  void startCommand (void);
  void sendConsoleText (const QString& s);
  QRect cursorRect (void);
  QRect boundingRect (void);
  void selectAll ();
  void selectWord (const QPoint& cellPos);
  void selectLine (const QPoint& cellPos);

  void log (const char *fmt, ...);

  void closeStandardIO (int fd, DWORD stdHandleId, const char *name);
  void setupStandardIO (DWORD stdHandleId, int fd, const char *name,
                        const char *devName);

  QPoint posToCell (const QPoint& pt);
  QString getSelection (void);
  void updateSelection (void);
  void clearSelection (void);

  QColor backgroundColor (void) const;
  QColor foregroundColor (void) const;
  QColor selectionColor (void) const;
  QColor cursorColor (void) const;

  void setBackgroundColor (const QColor& color);
  void setForegroundColor (const QColor& color);
  void setSelectionColor (const QColor& color);
  void setCursorColor (bool useForegroundColor, const QColor& color);
  void setScrollBufferSize (int value);

  void drawTextBackground (QPainter& p, int cx1, int cy1, int cx2, int cy2,
                           int ch);

  void drawSelection (QPainter& p, int cx1, int cy1, int cx2, int cy2, int ch);

  void drawCursor (QPainter& p);

  void drawText (QPainter& p, int cx1, int cy1, int cx2, int cy2, int ch);

private:
  QWinTerminalImpl *q;

private:
  QFont m_font;
  QString m_command;
  QConsoleColors m_colors;
  bool m_inWheelEvent;
  QString m_title;

  QSizeF m_charSize;
  QSize m_bufferSize;
  QRect m_consoleRect;
  bool m_auto_scroll;
  QPoint m_cursorPos;
  bool m_cursorBlinking;
  bool m_hasBlinkingCursor;
  QTimer *m_blinkCursorTimer;
  KeyboardCursorType m_cursorType;

  QPoint m_beginSelection;
  QPoint m_endSelection;
  bool m_settingSelection;

  QColor m_selectionColor;
  QColor m_cursorColor;

  HANDLE m_stdOut;
  HWND m_consoleWindow;
  CHAR_INFO *m_buffer;
  CHAR_INFO *m_tmpBuffer;
  HANDLE m_process;

  QConsoleView *m_consoleView;
  QScrollBar *m_horizontalScrollBar;
  QScrollBar *m_verticalScrollBar;
  QTimer *m_consoleWatcher;
  QConsoleThread *m_consoleThread;

  // The delay in milliseconds between redrawing blinking text.
  static const int BLINK_DELAY = 500;
};

static void maybeSwapPoints (QPoint& begin, QPoint& end)
{
  if (end.y () < begin.y ()
      || (end.y () == begin.y () && end.x () < begin.x ()))
    std::swap (begin, end);
}

//////////////////////////////////////////////////////////////////////////////

QConsolePrivate::QConsolePrivate (QWinTerminalImpl *parent, const QString& cmd)
  : q (parent), m_command (cmd), m_auto_scroll (true), m_cursorBlinking (false),
    m_hasBlinkingCursor (true), m_cursorType (BlockCursor),
    m_beginSelection (0, 0), m_endSelection (0, 0), m_settingSelection (false),
    m_process (nullptr), m_inWheelEvent (false)
{
  log (nullptr);

  // Possibly detach from any existing console
  log ("Detaching from existing console (if any)...\n");
  FreeConsole ();
  log ("Closing standard IO...\n");
  closeStandardIO (0, STD_INPUT_HANDLE, "STDIN");
  closeStandardIO (1, STD_OUTPUT_HANDLE, "STDOUT");
  closeStandardIO (2, STD_ERROR_HANDLE, "STDERR");

#ifdef HIDDEN_CONSOLE
  HWINSTA hOrigSta, hNewSta;

  // Create new (hidden) console
  hOrigSta = GetProcessWindowStation ();
  hNewSta = CreateWindowStation (nullptr, 0, GENERIC_ALL, nullptr);
  log ("Current Windows station: %p.\nNew Windows station: %p.\n", hOrigSta,
       hNewSta);
  if (! SetProcessWindowStation (hNewSta))
    log ("Failed to switch to new Windows station.\n");
#endif
  if (! AllocConsole ())
    log ("Failed to create new console.\n");
#ifdef HIDDEN_CONSOLE
  if (! SetProcessWindowStation (hOrigSta))
    log ("Failed to restore original Windows station.\n");
  if (! CloseWindowStation (hNewSta))
    log ("Failed to close new Windows station.\n");
#endif

  log ("New (hidden) console created.\n");

  setupStandardIO (STD_INPUT_HANDLE,  0, "STDIN",  "CONIN$");
  setupStandardIO (STD_OUTPUT_HANDLE, 1, "STDOUT", "CONOUT$");
  setupStandardIO (STD_ERROR_HANDLE,  2, "STDERR", "CONOUT$");

  log ("Standard input/output/error set up.\n");

  *stdin = *(fdopen (0, "rb"));
  *stdout = *(fdopen (1, "wb"));
  *stderr = *(fdopen (2, "wb"));

  log ("POSIX standard streams created.\n");

  setvbuf (stdin, nullptr, _IONBF, 0);
  setvbuf (stdout, nullptr, _IONBF, 0);
  setvbuf (stderr, nullptr, _IONBF, 0);

  log ("POSIX standard stream buffers adjusted.\n");

  HANDLE hStdOut = GetStdHandle (STD_OUTPUT_HANDLE);

  log ("Console allocated: hStdOut: %p\n", hStdOut);

  m_stdOut = hStdOut;
  m_consoleWindow = GetConsoleWindow ();

  // In case the console window hasn't been created hidden...
#ifdef HIDDEN_CONSOLE
  ShowWindow (m_consoleWindow, SW_HIDE);
#endif

  CONSOLE_SCREEN_BUFFER_INFO sbi;

  GetConsoleScreenBufferInfo (hStdOut, &sbi);
  m_bufferSize = QSize (sbi.dwSize.X,
                        qMax (sbi.dwSize.Y, static_cast<SHORT> (500)));
  m_consoleRect = QRect (sbi.srWindow.Left, sbi.srWindow.Top,
                         sbi.srWindow.Right - sbi.srWindow.Left + 1,
                         sbi.srWindow.Bottom - sbi.srWindow.Top + 1);
  m_cursorPos = QPoint (sbi.dwCursorPosition.X, sbi.dwCursorPosition.Y);

  log ("Initial console parameters:\n");
  log ("  buffer size: %d x %d\n", m_bufferSize.width (),
       m_bufferSize.height ());
  log ("  window: (%d, %d) -> (%d, %d) [%d x %d]\n",
       m_consoleRect.left (), m_consoleRect.top (),
       m_consoleRect.right (), m_consoleRect.bottom (),
       m_consoleRect.width (), m_consoleRect.height ());

  wchar_t titleBuf[260];
  GetConsoleTitleW (titleBuf, sizeof (titleBuf));
  q->setWindowTitle (QString::fromWCharArray (titleBuf));

  m_font.setFamily ("Lucida Console");
  m_font.setPointSize (9);
  m_font.setStyleHint (QFont::TypeWriter);

  m_buffer = nullptr;
  m_tmpBuffer = nullptr;

  m_consoleView = new QConsoleView (parent);
  m_horizontalScrollBar = new QScrollBar (Qt::Horizontal, parent);
  m_verticalScrollBar = new QScrollBar (Qt::Vertical, parent);

  QGridLayout *l = new QGridLayout (parent);
  l->setContentsMargins (0, 0, 0, 0);
  l->setSpacing (0);
  l->addWidget (m_consoleView, 0, 0);
  l->addWidget (m_horizontalScrollBar, 1, 0);
  l->addWidget (m_verticalScrollBar, 0, 1);

  if (IsWindows7OrGreater ())
    {
      SetConsoleCP (65001);
      SetConsoleOutputCP (65001);
    }

  // Choose 0 (0x0) as index into the Windows console color map for the
  // background and 7 (0x7) as the index for the foreground.  This
  // selection corresponds to the indices used in the foregroundColor,
  // setForegroundColor, backgroundColor, and SetBackgroundColor
  // functions.

  SetConsoleTextAttribute (m_stdOut, 0x07);

  // Defaults.
  setBackgroundColor (Qt::white);
  setForegroundColor (Qt::black);
  setSelectionColor (Qt::lightGray);
  setCursorColor (false, Qt::darkGray);

  // FIXME -- should we set the palette?
  QPalette palette (backgroundColor ());
  m_consoleView->setPalette (palette);

  m_consoleView->setFont (m_font);
  parent->setFocusPolicy (Qt::StrongFocus);
  parent->winId ();

  updateHorizontalScrollBar ();
  updateVerticalScrollBar ();

  m_consoleWatcher = new QTimer (parent);
  m_consoleWatcher->setInterval (10);
  m_consoleWatcher->setSingleShot (false);

  m_blinkCursorTimer = new QTimer (parent);
  QObject::connect (m_blinkCursorTimer, SIGNAL (timeout()),
                    q, SLOT (blinkCursorEvent ()));

  QObject::connect (m_horizontalScrollBar, SIGNAL (valueChanged (int)),
                    q, SLOT (horizontalScrollValueChanged (int)));

  QObject::connect (m_verticalScrollBar, SIGNAL (valueChanged (int)),
                    q, SLOT (verticalScrollValueChanged (int)));

  QObject::connect (m_consoleWatcher, SIGNAL (timeout (void)),
                    q, SLOT (monitorConsole (void)));

  m_consoleWatcher->start ();

  if (m_command.isEmpty ())
    m_consoleThread = 0;
  else
    {
      m_consoleThread = new QConsoleThread (q);
      QObject::connect (m_consoleThread, SIGNAL (finished (void)),
                        q, SIGNAL (terminated (void)));
      m_consoleThread->start ();
    }
}

//////////////////////////////////////////////////////////////////////////////

QConsolePrivate::~QConsolePrivate (void)
{
  if (m_consoleThread && m_consoleThread->isRunning () && m_process)
    {
      TerminateProcess (m_process, static_cast<UINT> (-1));
      m_consoleThread->wait ();
    }

  delete [] m_buffer;
  delete [] m_tmpBuffer;
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::setupStandardIO (DWORD stdHandleId, int targetFd,
                                       const char *name, const char *devName)
{
  log ("Opening %s...\n", devName);

  int fd = open (devName, _O_RDWR | _O_BINARY);

  if (fd != -1)
    {
      if (fd != targetFd)
        {
          log ("Opened %s is not at target file descriptor %d, "
               "duplicating...\n", name, targetFd);
          if (dup2 (fd, targetFd) == -1)
            log ("Failed to duplicate file descriptor: errno=%d.\n", errno);
          if (close (fd) == -1)
            log ("Failed to close original file descriptor: errno=%d.\n",
                 errno);
        }
      else
        log ("%s opened and assigned to file descriptor %d.\n", devName, fd);
      if (! SetStdHandle (stdHandleId, (HANDLE) _get_osfhandle (targetFd)))
        log ("Failed to re-assign %s: error=%08x.\n", name, GetLastError ());
    }
  else
    log ("Failed to open %s: errno=%d.\n", devName, errno);
}

QPoint QConsolePrivate::posToCell (const QPoint& p)
{
  return QPoint (m_consoleRect.left ()
                 + std::round (static_cast<qreal> (p.x ())
                               / m_charSize.width ()),
                 m_consoleRect.top ()
                 + std::round (static_cast<qreal> (p.y ())
                               / m_charSize.height ()));
}

QString QConsolePrivate::getSelection (void)
{
  QString selection;

  QPoint begin = m_beginSelection;
  QPoint end = m_endSelection;

  maybeSwapPoints (begin, end);

  if (begin != end)
    {
      CHAR_INFO *buf;
      COORD bufSize, bufCoord;
      SMALL_RECT bufRect;
      int nr;

      nr = end.y () - begin.y () + 1;
      buf =  new CHAR_INFO[m_bufferSize.width () * nr];
      bufSize.X = m_bufferSize.width ();
      bufSize.Y = nr;
      bufCoord.X = 0;
      bufCoord.Y = 0;

      bufRect.Left = 0;
      bufRect.Right = m_bufferSize.width ();
      bufRect.Top = begin.y ();
      bufRect.Bottom = end.y ();

      if (ReadConsoleOutput (m_stdOut, buf, bufSize, bufCoord, &bufRect))
        {
          int start_pos = begin.x ();
          int end_pos = (nr - 1) * m_bufferSize.width () + end.x ();
          int lastNonSpace = -1;

          for (int i = start_pos; i <= end_pos; i++)
            {
              if (i && (i % m_bufferSize.width ()) == 0)
                {
                  if (lastNonSpace >= 0)
                    selection.truncate (lastNonSpace);
                  selection.append ('\n');
                  lastNonSpace = selection.length ();
                }

              QChar c (buf[i].Char.UnicodeChar);
              if (c.isNull ())
                c = QChar (' ');

              selection.append (c);
              if (! c.isSpace ())
                lastNonSpace = selection.length ();
            }

          if (lastNonSpace >= 0)
            selection.truncate (lastNonSpace);
        }
    }

  return selection;
}

void QConsolePrivate::updateSelection (void)
{
  QPoint begin = m_beginSelection;
  QPoint end = m_endSelection;

  maybeSwapPoints (begin, end);

  begin.rx () = 0;
  end.rx () = m_consoleRect.width ();

  m_consoleView->update ();
}

void QConsolePrivate::clearSelection (void)
{
  m_beginSelection = m_endSelection = QPoint ();

  m_consoleView->update ();
}

QColor QConsolePrivate::backgroundColor (void) const
{
  return m_colors[0];
}

QColor QConsolePrivate::foregroundColor (void) const
{
  return m_colors[7];
}

QColor QConsolePrivate::selectionColor (void) const
{
  return m_selectionColor;
}

QColor QConsolePrivate::cursorColor (void) const
{
  return m_cursorColor.isValid () ? m_cursorColor : foregroundColor ();
}

void QConsolePrivate::setBackgroundColor (const QColor& color)
{
  m_colors[0] = color;

  QPalette palette (color);
  palette.setColor(QPalette::Base, color);
  m_consoleView->setPalette (palette);
}

void QConsolePrivate::setForegroundColor (const QColor& color)
{
  m_colors[7] = color;
}

void QConsolePrivate::setSelectionColor (const QColor& color)
{
  m_selectionColor = color;
}

void QConsolePrivate::setCursorColor (bool useForegroundColor,
                                      const QColor& color)
{
  m_cursorColor = useForegroundColor ? QColor () : color;
}

void QConsolePrivate::setScrollBufferSize (int value)
{
  CONSOLE_SCREEN_BUFFER_INFO sbi;
  GetConsoleScreenBufferInfo (m_stdOut, &sbi);

  m_bufferSize = QSize (sbi.dwSize.X, static_cast<SHORT> (value));

  updateConsoleSize (true);
}

void QConsolePrivate::drawTextBackground (QPainter& p, int cx1, int cy1,
                                          int cx2, int cy2, int ch)
{
  p.save ();

  QFontMetrics fm = p.fontMetrics ();
  QString sample ('m');
  sample = sample.repeated (cx2);

  int ascent = fm.ascent ();
  int stride = m_consoleRect.width ();
  int y = ascent + cy1 * ch;

  for (int j = cy1; j <= cy2; j++, y += ch)
    {
      int len = 0;
      bool hasChar = false;
      int x = fm.horizontalAdvance (sample, cx1);
      WORD attr = 0;
      int curr_cx1 = cx1;

      for (int i = cx1; i <= cx2; i++)
        {
          CHAR_INFO *ci = &(m_buffer[stride*j+i]);

          if ((ci->Attributes & 0x00ff) != attr)
            {
              // Character attributes changed
              if (len != 0)
                {
                  // String buffer not empty -> draw it
                  if (hasChar || (attr & 0x00f0))
                    {
                      if (attr & 0x00f0)
                        // Try to not paint over parts of the preceeding
                        // character.
                        p.fillRect (x, y-ascent,
                                    fm.horizontalAdvance (sample, len), ch,
                                    p.brush ());
                    }

                  curr_cx1 += len;
                  x = fm.horizontalAdvance (sample, curr_cx1);
                  len = 0;
                  hasChar = false;
                }
              // Update current brush and store current attributes
              attr = (ci->Attributes & 0x00ff);
              p.setBrush (m_colors[(attr >> 4) & 0x000f]);
            }

          // Append current character to the string buffer
          len++;
          if (ci->Char.UnicodeChar != L' ')
            hasChar = true;
        }

      if (len != 0 && (hasChar || (attr & 0x00f0)))
        {
          // Line end reached, but string buffer not empty -> draw it
          // No need to update s or x, they will be reset on the next
          // for-loop iteration

          if (attr & 0x00f0)
            // Try to not paint over parts of the preceeding character.
            p.fillRect (x, y-ascent,
                        fm.horizontalAdvance (sample, len), ch, p.brush ());
        }
    }

  p.restore ();
}

void QConsolePrivate::selectAll ()
{
  m_beginSelection = QPoint (0, 0);
  m_endSelection = QPoint (m_bufferSize.width (), m_cursorPos.y ());
  updateSelection();
}

void QConsolePrivate::selectWord (const QPoint& cellpos)
{
  QPoint begin = cellpos;
  QPoint end = cellpos;

  int stride = m_consoleRect.width ();

  int verticalScrollOffset = m_consoleRect.top ();
  int horizontalScrollOffset = m_consoleRect.left ();

  // get begin, end in buffer offsets
  begin.ry () -= verticalScrollOffset;
  end.ry () -= verticalScrollOffset;

  begin.rx () -= horizontalScrollOffset;
  end.rx () -= horizontalScrollOffset;

  // check currently clicked on character and determinate if it is whitespace
  if (QChar (m_buffer[begin.y ()*stride
                      + begin.x ()].Char.UnicodeChar).isSpace () == false)
    {
      // from current char, go back and fwd to find start and end of block
      while (begin.x () > 0
             && ! QChar (m_buffer[begin.y ()*stride
                                  + begin.x () - 1].Char.UnicodeChar).isSpace ())
        begin.rx () --;

      while (end.x () < m_consoleRect.width ()
             && ! QChar (m_buffer[end.y ()*stride
                                  + end.x () + 1].Char.UnicodeChar).isSpace ())
        end.rx () ++;
    }
    else
    {
      while (begin.x () > 0
             && QChar (m_buffer[begin.y ()*stride
                                + begin.x () - 1].Char.UnicodeChar).isSpace ())
        begin.rx () --;

      while (end.x () < m_consoleRect.width ()
             && QChar (m_buffer[end.y ()*stride
                                + end.x () + 1].Char.UnicodeChar).isSpace ())
        end.rx () ++;
    }

  // convert console  offsets to absolute cell positions
  begin.ry () += verticalScrollOffset;
  end.ry () += verticalScrollOffset;

  begin.rx () += horizontalScrollOffset;
  end.rx () += horizontalScrollOffset;

  m_beginSelection = begin;
  m_endSelection = end;

  updateSelection ();
}

void QConsolePrivate::selectLine (const QPoint& cellpos)
{
  m_beginSelection = QPoint (0, cellpos.y ());
  m_endSelection = QPoint (m_bufferSize.width ()-1, cellpos.y ());
  updateSelection ();
}


void QConsolePrivate::drawSelection (QPainter& p, int cx1, int cy1,
                                     int cx2, int cy2, int ch)
{
  p.save ();

  QPoint begin = m_beginSelection;
  QPoint end = m_endSelection;

  bool haveSelection = (begin != end);

  if (haveSelection)
    maybeSwapPoints (begin, end);

  int verticalScrollOffset = m_consoleRect.top ();
  int horizontalScrollOffset = m_consoleRect.left ();

  begin.ry () -= verticalScrollOffset;
  end.ry () -= verticalScrollOffset;

  begin.rx () -= horizontalScrollOffset;
  end.rx () -= horizontalScrollOffset;

  QFontMetrics fm = p.fontMetrics ();
  QString sample ('m');
  sample = sample.repeated (cx2);

  int ascent = fm.ascent ();
  int stride = m_consoleRect.width ();

  int y = ascent + cy1 * ch;
  for (int j = cy1; j <= cy2; j++, y += ch)
    {
      int charsThisLine = 0;
      int len = 0;
      bool hasChar = false;
      WORD attr = 0;

      for (int i = cx1; i <= cx2; i++)
        {
          CHAR_INFO* ci = &(m_buffer[stride*j+i]);

          if ((ci->Attributes & 0x00ff) != attr)
            {
              // Character attributes changed
              if (len != 0)
                {
                  charsThisLine += len;
                  len = 0;
                  hasChar = false;
                }

              // Store current attributes
              attr = (ci->Attributes & 0x00ff);
            }

          // Append current character to the string buffer
          len++;
          if (ci->Char.UnicodeChar != L' ')
            hasChar = true;
        }

      if (len != 0 && (hasChar || (attr & 0x00f0)))
        charsThisLine += len;

      if (haveSelection && j >= begin.y () && j <= end.y ())
        {
          int selectionBegin = j == begin.y () ? begin.x (): 0;

          int len = ((j == end.y () && end.x () < charsThisLine)
                     ? end.x () - selectionBegin + 1
                     : stride - selectionBegin);

          p.fillRect (fm.horizontalAdvance (sample, selectionBegin),
                      y-ascent, fm.horizontalAdvance (sample, len),
                      ch, selectionColor ());
        }
    }

  p.restore ();
}

void QConsolePrivate::drawCursor (QPainter& p)
{
  if (! m_cursorBlinking)
    {
      p.save ();

      QRect rect = cursorRect ();
      QColor color = cursorColor ();

      p.setPen (color);

      if (m_cursorType == QConsolePrivate::BlockCursor)
        {
          if (q->hasFocus ())
            p.fillRect (rect, color);
          else
            {
              // draw the cursor outline, adjusting the area so that
              // it is draw entirely inside 'rect'

              int penWidth = qMax (1, p.pen ().width ());

              p.drawRect (rect.adjusted (penWidth/2, penWidth/2,
                                         - penWidth/2 - penWidth%2,
                                         - penWidth/2 - penWidth%2));
            }
        }
      else if (m_cursorType == QConsolePrivate::UnderlineCursor)
        {
          p.drawLine (rect.left (), rect.bottom (),
                      rect.right (), rect.bottom ());
        }
      else if (m_cursorType == QConsolePrivate::IBeamCursor)
        {
          p.drawLine (rect.left (), rect.top (),
                      rect.left (), rect.bottom ());
        }

      p.restore ();
    }
}

void QConsolePrivate::drawText (QPainter& p, int cx1, int cy1,
                                int cx2, int cy2, int ch)
{
  p.save ();

  p.setFont (m_font);
  p.setPen (foregroundColor ());

  QString s;
  s.reserve (cx2 - cx1 + 1);

  QFontMetrics fm = p.fontMetrics ();
  QString sample ('m');
  sample = sample.repeated (cx2);

  int ascent = fm.ascent ();
  int stride = m_consoleRect.width ();

  int y = ascent + cy1 * ch;
  for (int j = cy1; j <= cy2; j++, y += ch)
    {
      // Reset string buffer and starting X coordinate
      s.clear ();
      bool hasChar = false;
      int x = fm.horizontalAdvance (sample, cx1);
      WORD attr = 0;
      int curr_cx1 = cx1;

      for (int i = cx1; i <= cx2; i++)
        {
          CHAR_INFO* ci = &(m_buffer[stride*j+i]);

          if ((ci->Attributes & 0x00ff) != attr)
            {
              // Character attributes changed
              if (! s.isEmpty ())
                {
                  // String buffer not empty -> draw it
                  if (hasChar || (attr & 0x00f0))
                    p.drawText (x, y, s);

                  curr_cx1 += s.length ();
                  x = fm.horizontalAdvance (sample, curr_cx1);
                  s.clear ();
                  hasChar = false;
                }
              // Update current pen and store current attributes
              attr = (ci->Attributes & 0x00ff);
              p.setPen (m_colors[attr & 0x000f]);
            }

          // Append current character to the string buffer
          s.append (ci->Char.UnicodeChar);
          if (ci->Char.UnicodeChar != L' ')
            hasChar = true;
        }

      if (! s.isEmpty () && (hasChar || (attr & 0x00f0)))
        {
          // Line end reached, but string buffer not empty -> draw it
          // No need to update s or x, they will be reset on the next
          // for-loop iteration

          p.drawText (x, y, s);
        }
    }

  p.restore ();
}

/////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::closeStandardIO (int fd, DWORD stdHandleId,
                                       const char *name)
{
  if (close (fd) == -1)
    log ("Failed to close file descriptor %d: errno=%d.\n", fd, errno);
  if (! CloseHandle (GetStdHandle (stdHandleId)))
    log ("Failed to close Win32 %s: error=%08x.\n", name, GetLastError ());
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::log (const char *fmt, ...)
{
#ifdef DEBUG_QCONSOLE
  if (fmt)
    {
      va_list l;
      FILE *flog = fopen (LOGFILENAME, "ab");

      va_start (l, fmt);
      vfprintf (flog, fmt, l);
      va_end (l);
      fclose (flog);
    }
  else
    {
      // Special case to re-initialize the log file
      FILE *flog = fopen (LOGFILENAME, "w");
      fclose (flog);
    }
#else
  Q_UNUSED (fmt);
#endif
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::updateConsoleSize (bool sync, bool allow_smaller_width)
{
  QFontMetrics fm = m_consoleView->fontMetrics ();
  QSize winSize = m_consoleView->size ();

  // QFontMetrics::averageCharWidth returns the average character width of the
  // used font rounded(?) to the nearest integer.  However, the actual
  // character width might be fractional on screens with non-scalar DPI
  // scaling.  Take a large sample and divide by the number of characters in
  // the sample to get a more accurate (fractional) average character width of
  // the used font.
  QString sample ('m');
  sample = sample.repeated (160);  // Is 160 a large enough sample?
  m_charSize.rwidth ()
    = static_cast<qreal> (fm.horizontalAdvance (sample)) / 160.;
  m_charSize.rheight () = fm.lineSpacing ();

  m_consoleRect.setWidth (std::floor (static_cast<qreal> (winSize.width ())
                                      / m_charSize.width ()));
  m_consoleRect.setHeight (std::floor (static_cast<qreal> (winSize.height ())
                                       / m_charSize.height ()));

  // Don't shrink the size of the buffer.  That way wide lines won't be
  // truncated and will reappear if the window is enlarged again later.

  if (allow_smaller_width || m_consoleRect.width () > m_bufferSize.width ())
    m_bufferSize.rwidth () = m_consoleRect.width ();

  if (qMax (m_bufferSize.height (), m_consoleRect.height ())
      > m_bufferSize.height ())
    m_bufferSize.rheight () = qMax (m_bufferSize.height (),
                                    m_consoleRect.height ());

  // Store the terminal size in the environment.  When Octave is
  // initialized, we ask the command editor (usually readline) to prefer
  // using these values rather than querying the terminal so that the
  // buffer size can be larger than the size of the window that the
  // command editor will actually use.

  qputenv ("LINES", QByteArray::number (m_consoleRect.height ()));
  qputenv ("COLUMNS", QByteArray::number (m_consoleRect.width ()));

  // Force the command line editor (usually readline) to notice the
  // change in screen size as soon as possible.

  q->setSize (m_consoleRect.height (), m_consoleRect.width ());

  m_consoleRect.moveLeft (0);
  if (m_consoleRect.bottom () >= m_bufferSize.height ())
    m_consoleRect.moveTop (m_bufferSize.height () - m_consoleRect.height ());

  log ("Console resized:\n");
  log ("  widget size: %d x %d\n", winSize.width (), winSize.height ());
  log ("  buffer size: %d x %d\n", m_bufferSize.width (),
       m_bufferSize.height ());
  log ("  window: (%d, %d) -> (%d, %d) [%d x %d]\n",
       m_consoleRect.left (), m_consoleRect.top (),
       m_consoleRect.right (), m_consoleRect.bottom (),
       m_consoleRect.width (), m_consoleRect.height ());

  if (sync)
    syncConsoleParameters ();

  updateHorizontalScrollBar ();
  updateVerticalScrollBar ();
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::syncConsoleParameters (void)
{
  CONSOLE_SCREEN_BUFFER_INFO sbi;
  HANDLE hStdOut = m_stdOut;

  GetConsoleScreenBufferInfo (hStdOut, &sbi);

  COORD bs;
  SMALL_RECT sr;

  bs.X = m_bufferSize.width ();
  bs.Y = m_bufferSize.height ();
  sr.Left = m_consoleRect.left ();
  sr.Right = m_consoleRect.right ();
  sr.Top = m_consoleRect.top ();
  sr.Bottom = m_consoleRect.bottom ();

  if (bs.X * bs.Y > sbi.dwSize.X * sbi.dwSize.Y)
    {
      SetConsoleScreenBufferSize (hStdOut, bs);
      SetConsoleWindowInfo (hStdOut, TRUE, &sr);
    }
  else
    {
      SetConsoleWindowInfo (hStdOut, TRUE, &sr);
      SetConsoleScreenBufferSize (hStdOut, bs);
    }

  log ("Sync'ing console parameters:\n");
  log ("  buffer size: %d x %d\n", bs.X, bs.Y);
  log ("  window: (%d, %d) -> (%d, %d)\n",
       sr.Left, sr.Top, sr.Right, sr.Bottom);

  delete [] m_buffer;
  delete [] m_tmpBuffer;

  std::size_t bufSize = m_consoleRect.width () * m_consoleRect.height ();

  m_buffer = new CHAR_INFO[bufSize];
  m_tmpBuffer = new CHAR_INFO[bufSize];
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::grabConsoleBuffer (CHAR_INFO* buf)
{
  COORD bs, bc;
  SMALL_RECT r;

  bs.X = m_consoleRect.width ();
  bs.Y = m_consoleRect.height ();
  bc.X = 0;
  bc.Y = 0;

  r.Left   = m_consoleRect.left ();
  r.Top    = m_consoleRect.top ();
  r.Right  = m_consoleRect.right ();
  r.Bottom = m_consoleRect.bottom ();

  log ("ReadConsoleOutput (%d,%d) -> (%d,%d)\n", r.Left, r.Top, r.Right, r.Bottom);
  if (! ReadConsoleOutput (m_stdOut, (buf ? buf : m_buffer), bs, bc, &r))
    qCritical ("cannot read console output");
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::updateHorizontalScrollBar (void)
{
  m_horizontalScrollBar->setMinimum (0);
  if (m_bufferSize.width () > m_consoleRect.width ())
    m_horizontalScrollBar->setMaximum (m_bufferSize.width () - m_consoleRect.width ());
  else
    m_horizontalScrollBar->setMaximum (0);
  m_horizontalScrollBar->setSingleStep (1);
  m_horizontalScrollBar->setPageStep (m_consoleRect.width ());
  m_horizontalScrollBar->setValue (m_consoleRect.left ());

  log ("Horizontal scrollbar parameters updated: %d/%d/%d/%d\n",
       m_horizontalScrollBar->minimum (),
       m_horizontalScrollBar->maximum (),
       m_horizontalScrollBar->singleStep (),
       m_horizontalScrollBar->pageStep ());
}

void QConsolePrivate::updateVerticalScrollBar (void)
{
  m_verticalScrollBar->setMinimum (0);
  if (m_bufferSize.height () > m_consoleRect.height ())
    m_verticalScrollBar->setMaximum (m_bufferSize.height () - m_consoleRect.height ());
  else
    m_verticalScrollBar->setMaximum (0);
  m_verticalScrollBar->setSingleStep (1);
  m_verticalScrollBar->setPageStep (m_consoleRect.height ());
  m_verticalScrollBar->setValue (m_consoleRect.top ());

  log ("Vertical scrollbar parameters updated: %d/%d/%d/%d\n",
       m_verticalScrollBar->minimum (), m_verticalScrollBar->maximum (),
       m_verticalScrollBar->singleStep (), m_verticalScrollBar->pageStep ());
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::setHorizontalScrollValue (int value)
{
  if (value == m_consoleRect.left ())
    return;

  SMALL_RECT r;
  HANDLE hStdOut = m_stdOut;

  if (value + m_consoleRect.width () > m_bufferSize.width ())
    value = m_bufferSize.width () - m_consoleRect.width ();

  r.Left = value;
  r.Top = m_consoleRect.top ();
  r.Right = value + m_consoleRect.width () - 1;
  r.Bottom = m_consoleRect.bottom ();

  log ("Scrolling window horizontally: (%d, %d) -> (%d, %d) [%d x %d]\n",
       r.Left, r.Top, r.Right, r.Bottom,
       r.Right - r.Left + 1, r.Bottom - r.Top + 1);

  if (SetConsoleWindowInfo (hStdOut, TRUE, &r))
    {
      m_consoleRect.moveLeft (value);
      updateConsoleView ();
    }
}

void QConsolePrivate::setVerticalScrollValue (int value)
{
  if (value == m_consoleRect.top ())
    return;

  SMALL_RECT r;
  HANDLE hStdOut = m_stdOut;

  if (value + m_consoleRect.height () > m_bufferSize.height ())
    value = m_bufferSize.height () - m_consoleRect.height ();

  r.Left = m_consoleRect.left ();
  r.Top = value;
  r.Right = m_consoleRect.right ();
  r.Bottom = value + m_consoleRect.height () - 1;

  log ("Scrolling window vertically: (%d, %d) -> (%d, %d) [%d x %d]\n",
       r.Left, r.Top, r.Right, r.Bottom,
       r.Right - r.Left + 1, r.Bottom - r.Top + 1);

  if (SetConsoleWindowInfo (hStdOut, TRUE, &r))
    {
      m_consoleRect.moveTop (value);

      CONSOLE_SCREEN_BUFFER_INFO sbi;
      if (GetConsoleScreenBufferInfo (hStdOut, &sbi))
        {
          if (sbi.dwCursorPosition.Y > m_consoleRect.bottom ())
            m_auto_scroll = false;
          else
            m_auto_scroll = true;
        }

      updateConsoleView ();
    }
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::updateConsoleView (bool grab)
{
  if (grab)
    grabConsoleBuffer ();
  m_consoleView->update ();
  m_consoleWatcher->start ();
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::monitorConsole (void)
{
  CONSOLE_SCREEN_BUFFER_INFO sbi;
  HANDLE hStdOut = GetStdHandle (STD_OUTPUT_HANDLE);

  static wchar_t titleBuf[260];

  GetConsoleTitleW (titleBuf, sizeof (titleBuf));
  QString title = QString::fromWCharArray (titleBuf);

  if (title != m_title)
    {
      q->setWindowTitle (title);
      emit q->titleChanged (title);
    }

  if (GetConsoleScreenBufferInfo (hStdOut, &sbi))
    {
      bool need_console_sync = false;

      if (m_bufferSize.width () != sbi.dwSize.X
          || m_bufferSize.height () != sbi.dwSize.Y)
        {
          // Buffer size changed
          m_bufferSize.rwidth () = sbi.dwSize.X;
          m_bufferSize.rheight () = sbi.dwSize.Y;
          updateHorizontalScrollBar ();
          updateVerticalScrollBar ();
          need_console_sync = true;
        }

      if (m_cursorPos.x () != sbi.dwCursorPosition.X
          || m_cursorPos.y () != sbi.dwCursorPosition.Y)
        {
          // Cursor position changed
          QFontMetrics fm = m_consoleView->fontMetrics ();
          QString sample ('m');
          sample = sample.repeated (m_bufferSize.width ());
          // "over-size" update rectangle for fractional character widths
          m_consoleView->update
            (fm.horizontalAdvance (sample,
                                   m_cursorPos.x () - sbi.srWindow.Left) - 1,
             (m_cursorPos.y () - sbi.srWindow.Top) * m_charSize.height (),
             m_charSize.width () + 2, m_charSize.height ());
          m_cursorPos.rx () = sbi.dwCursorPosition.X;
          m_cursorPos.ry () = sbi.dwCursorPosition.Y;
          m_consoleView->update
            (fm.horizontalAdvance (sample,
                                   m_cursorPos.x () - sbi.srWindow.Left) - 1,
             (m_cursorPos.y () - sbi.srWindow.Top) * m_charSize.height (),
             m_charSize.width () + 2, m_charSize.height ());
        }

      if (m_consoleRect.left () != sbi.srWindow.Left
          || m_consoleRect.right () != sbi.srWindow.Right
          || (m_auto_scroll &&
              (m_consoleRect.top () != sbi.srWindow.Top
               || m_consoleRect.bottom () != sbi.srWindow.Bottom)))
        {
          // Console window changed
          log ("--> Console window changed\n");
          m_consoleRect = QRect (sbi.srWindow.Left, sbi.srWindow.Top,
                                 sbi.srWindow.Right - sbi.srWindow.Left + 1,
                                 sbi.srWindow.Bottom - sbi.srWindow.Top + 1);
          updateHorizontalScrollBar ();
          updateVerticalScrollBar ();
          syncConsoleParameters ();
          updateConsoleView ();
          return;
        }

      if (need_console_sync)
      {
        syncConsoleParameters ();
        updateConsoleView ();
        return;
      }

      if (m_tmpBuffer && m_buffer)
        {
          grabConsoleBuffer (m_tmpBuffer);
          if (memcmp (m_tmpBuffer, m_buffer,
                      sizeof (CHAR_INFO) * m_consoleRect.width ()
                      * m_consoleRect.height ()))
            {
              // FIXME: compute the area to update based on the
              // difference between the 2 buffers.
              std::swap (m_buffer, m_tmpBuffer);
              updateConsoleView (false);
            }
        }
    }
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::startCommand (void)
{
  QString cmd = m_command;

  if (cmd.isEmpty ())
    cmd = qgetenv ("COMSPEC").constData ();

  if (! cmd.isEmpty ())
    {
      STARTUPINFOW si;
      PROCESS_INFORMATION pi;

      ZeroMemory (&si, sizeof (si));
      si.cb = sizeof (si);
      ZeroMemory (&pi, sizeof (pi));

      LPCWSTR wcmd = reinterpret_cast<LPCWSTR> (cmd.utf16 ());
      if (CreateProcessW (nullptr,
                          const_cast<LPWSTR> (wcmd),
                          nullptr,
                          nullptr,
                          TRUE,
                          0,
                          nullptr,
                          nullptr,
                          &si,
                          &pi))
        {
          CloseHandle (pi.hThread);
          m_process = pi.hProcess;
          WaitForSingleObject (m_process, INFINITE);
          CloseHandle (m_process);
          m_process = nullptr;
        }
    }
}

//////////////////////////////////////////////////////////////////////////////

void QConsolePrivate::sendConsoleText (const QString& s)
{
  // Send the string in chunks of 512 characters. Each character is
  // translated into an equivalent keypress event.

#define TEXT_CHUNK_SIZE 512

  // clear any selection on inserting text
  clearSelection();
  // enable auto-scrolling
  m_auto_scroll = true;

  int len = s.length ();
  INPUT_RECORD events[TEXT_CHUNK_SIZE];
  DWORD nEvents = 0, written;
  HANDLE hStdIn = GetStdHandle (STD_INPUT_HANDLE);

  ZeroMemory (events, sizeof (events));

  for (int i = 0; i < len; i++)
    {
      QChar c = s.at (i);

      if (c == L'\r' || c == L'\n')
        {
          if (c == L'\r' && i < (len - 1) && s.at (i+1) == L'\n')
            i++;

          // add new line
          events[nEvents].EventType                        = KEY_EVENT;
          events[nEvents].Event.KeyEvent.bKeyDown          = TRUE;
          events[nEvents].Event.KeyEvent.wRepeatCount      = 1;
          events[nEvents].Event.KeyEvent.wVirtualKeyCode   =
            VK_RETURN;
          events[nEvents].Event.KeyEvent.wVirtualScanCode  = 0;
          events[nEvents].Event.KeyEvent.uChar.UnicodeChar = c.unicode ();
          events[nEvents].Event.KeyEvent.dwControlKeyState = 0;
          nEvents++;

          WriteConsoleInput (hStdIn, events, nEvents, &written);
          nEvents = 0;
          ZeroMemory (events, sizeof (events));

        }
      else
        {
          events[nEvents].EventType                        = KEY_EVENT;
          events[nEvents].Event.KeyEvent.bKeyDown          = TRUE;
          events[nEvents].Event.KeyEvent.wRepeatCount      = 1;
          events[nEvents].Event.KeyEvent.wVirtualKeyCode   =
            LOBYTE (VkKeyScan (c.unicode ()));
          events[nEvents].Event.KeyEvent.wVirtualScanCode  = 0;
          events[nEvents].Event.KeyEvent.uChar.UnicodeChar = c.unicode ();
          events[nEvents].Event.KeyEvent.dwControlKeyState = 0;
          nEvents++;
        }

      if (nEvents == TEXT_CHUNK_SIZE
          || (nEvents > 0 && i == (len - 1)))
        {
          WriteConsoleInput (hStdIn, events, nEvents, &written);
          nEvents = 0;
          ZeroMemory (events, sizeof (events));
        }
    }
}

QRect
QConsolePrivate::cursorRect (void)
{
  // The actual character width might be fractional (with non-integer scaling -
  // high DPI).  But m_charSize.width () is integer.
  // Assume a fixed-width font and calculate cursor position by measuring the
  // width of characters starting from the first column.

  QFontMetrics fm = m_consoleView->fontMetrics ();
  QString sample ('m');
  sample = sample.repeated (m_cursorPos.x () - m_consoleRect.x ());

  // Integer precision is good enough for the line height and for the
  // dimensions of the marker.
  qreal cw = m_charSize.width ();
  qreal ch = m_charSize.height ();

  // Make sure the cursor starts *right* of the previous character.
  return QRect (fm.horizontalAdvance (sample),
                (m_cursorPos.y () - m_consoleRect.y ()) * ch,
                std::round (cw), ch);
}

QRect
QConsolePrivate::boundingRect (void)
{
  // This is slightly larger than cursorRect to make sure the entirety of a
  // character is redrawn.

  QFontMetrics fm = m_consoleView->fontMetrics ();
  QString sample ('m');
  sample = sample.repeated (m_cursorPos.x () - m_consoleRect.x ());

  // Integer precision is good enough for the line height and for the
  // dimensions of the marker.
  qreal cw = m_charSize.width ();
  qreal ch = m_charSize.height ();

  return QRect (fm.horizontalAdvance (sample)-1,
                (m_cursorPos.y () - m_consoleRect.y ()) * ch,
                std::round (cw+2), ch);
}

//////////////////////////////////////////////////////////////////////////////

QWinTerminalImpl::QWinTerminalImpl (QWidget *parent)
    : QTerminal (parent), d (new QConsolePrivate (this)),
      allowTripleClick (false)
{
    installEventFilter (this);

    setAcceptDrops (true);
}

//////////////////////////////////////////////////////////////////////////////

QWinTerminalImpl::~QWinTerminalImpl (void)
{
  delete d;
}

void QWinTerminalImpl::mouseMoveEvent (QMouseEvent *event)
{
  if (d->m_settingSelection)
    {
      d->m_endSelection = d->posToCell (event->pos ());

      updateSelection ();
    }
}

void QWinTerminalImpl::mousePressEvent (QMouseEvent *event)
{
  if (allowTripleClick)
    {
      mouseTripleClickEvent (event);
    }
  else if (event->button () == Qt::LeftButton)
    {
      d->m_settingSelection = true;

      d->m_beginSelection = d->posToCell (event->pos ());
    }
}

void QWinTerminalImpl::mouseReleaseEvent (QMouseEvent *event)
{
  if (event->button () == Qt::LeftButton && d->m_settingSelection)
    {
      d->m_endSelection = d->posToCell (event->pos ());

      updateSelection ();

      d->m_settingSelection = false;
    }
}

void QWinTerminalImpl::mouseDoubleClickEvent (QMouseEvent *event)
{
  if (event->button () == Qt::LeftButton)
    {
      // doubleclick - select word
      d->m_settingSelection = false;

      d->selectWord (d->posToCell (event->pos ()));

      allowTripleClick = true;

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

    }
}

void QWinTerminalImpl::mouseTripleClickEvent (QMouseEvent *event)
{
  if (event->button () == Qt::LeftButton)
    {
      d->selectLine (d->posToCell (event->pos ()));
    }
}

void QWinTerminalImpl::tripleClickTimeout ()
{
  allowTripleClick = false;
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::viewResizeEvent (QConsoleView *, QResizeEvent *)
{
  d->updateConsoleSize (true);
  d->grabConsoleBuffer ();
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::viewPaintEvent (QConsoleView *w, QPaintEvent *event)
{
  QPainter p (w);

  qreal cw = d->m_charSize.width ();
  qreal ch = d->m_charSize.height ();

  QRect updateRect = event->rect ();
  p.fillRect (updateRect, QBrush (d->backgroundColor ()));

  int cx1 = std::round (static_cast<qreal> (updateRect.left ()) / cw);
  int cy1 = updateRect.top () / ch;
  int cx2 = std::round (static_cast<qreal> (updateRect.right ()) / cw);
  cx2 = qMin (d->m_consoleRect.width () - 1, cx2);
  int cy2 = std::round (static_cast<qreal> (updateRect.bottom ()) / ch);
  cy2 = qMin (d->m_consoleRect.height () - 1, cy2);

  if (cx1 > d->m_consoleRect.width () - 1
      || cy1 > d->m_consoleRect.height () - 1)
    return;

  d->drawTextBackground (p, cx1, cy1, cx2, cy2, ch);
  d->drawSelection (p, cx1, cy1, cx2, cy2, ch);
  d->drawCursor (p);
  d->drawText (p, cx1, cy1, cx2, cy2, ch);
}

void QWinTerminalImpl::blinkCursorEvent (void)
{
  if (d->m_hasBlinkingCursor)
    d->m_cursorBlinking = ! d->m_cursorBlinking;
  else
    d->m_cursorBlinking = false;

  d->m_consoleView->update (d->boundingRect ());
}

void QWinTerminalImpl::setBlinkingCursor (bool blink)
{
  d->m_hasBlinkingCursor = blink;

  setBlinkingCursorState (blink);
}

void QWinTerminalImpl::setBlinkingCursorState (bool blink)
{
  if (blink && ! d->m_blinkCursorTimer->isActive ())
    d->m_blinkCursorTimer->start (d->BLINK_DELAY);

  if (! blink && d->m_blinkCursorTimer->isActive ())
    {
      d->m_blinkCursorTimer->stop ();

      if (d->m_cursorBlinking)
        blinkCursorEvent ();
    }
}

// Reset width of console buffer and terminal window to be the same.

void QWinTerminalImpl::init_terminal_size (void)
{
  d->updateConsoleSize (true, true);
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::wheelEvent (QWheelEvent *event)
{
  if (! d->m_inWheelEvent)
    {
      // Forward to the scrollbar (avoid recursion)
      d->m_inWheelEvent = true;
      QApplication::sendEvent (d->m_verticalScrollBar, event);
      d->m_inWheelEvent = false;
    }
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::horizontalScrollValueChanged (int value)
{
  d->setHorizontalScrollValue (value);
}

void QWinTerminalImpl::verticalScrollValueChanged (int value)
{
  d->setVerticalScrollValue (value);
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::monitorConsole (void)
{
  d->monitorConsole ();
}

void QWinTerminalImpl::updateSelection (void)
{
  d->updateSelection ();
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::focusInEvent (QFocusEvent *event)
{
  setBlinkingCursorState (true);

  QWidget::focusInEvent (event);
}

void QWinTerminalImpl::focusOutEvent (QFocusEvent *event)
{
  // Force the cursor to be redrawn.
  d->m_cursorBlinking = true;

  setBlinkingCursorState (false);

  QWidget::focusOutEvent (event);
}

bool QWinTerminalImpl::eventFilter (QObject *obj, QEvent *event)
{
  // if a keypress, filter out tab keys so that the next/prev tabbing is
  // disabled - but we still need to pass along to the console .
  if (event->type () == QEvent::KeyPress)
  {
    QKeyEvent *k = static_cast<QKeyEvent *> (event);
    if (k->key () == Qt::Key_Tab)
    {
      sendText ("\t");
      return true;
    }
  }
  return false;
}

void QWinTerminalImpl::keyPressEvent (QKeyEvent *event)
{
  QString s = translateKey (event);
  if (!s.isEmpty ())
    sendText (s);

  if (d->m_hasBlinkingCursor)
    {
      d->m_blinkCursorTimer->start (d->BLINK_DELAY);

      if (d->m_cursorBlinking)
        blinkCursorEvent ();
    }

  QWidget::keyPressEvent (event);
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::start (void)
{
  d->startCommand ();
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::sendText (const QString& s)
{
  d->sendConsoleText (s);
}

void QWinTerminalImpl::setCursorType (CursorType type, bool blinking)
{
  switch (type)
    {
    case UnderlineCursor:
      d->m_cursorType = QConsolePrivate::UnderlineCursor;
      break;

    case BlockCursor:
      d->m_cursorType = QConsolePrivate::BlockCursor;
      break;

    case IBeamCursor:
      d->m_cursorType = QConsolePrivate::IBeamCursor;
      break;
    }

  setBlinkingCursor (blinking);
}

void QWinTerminalImpl::setBackgroundColor (const QColor& color)
{
  d->setBackgroundColor (color);
}

void QWinTerminalImpl::setForegroundColor (const QColor& color)
{
  d->setForegroundColor (color);
}

void QWinTerminalImpl::setSelectionColor (const QColor& color)
{
  d->setSelectionColor (color);
}

void QWinTerminalImpl::setCursorColor (bool useForegroundColor,
                                       const QColor& color)
{
  d->setCursorColor (useForegroundColor, color);
}

void QWinTerminalImpl::setScrollBufferSize (int value)
{
  d->setScrollBufferSize (value);
}


//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::setTerminalFont (const QFont& f)
{
  d->m_font = f;
  d->m_consoleView->setFont (f);
  d->updateConsoleSize (true);
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::setSize (int columns, int lines)
{
  d->log ("emit set_screen_size_signal (%d, %d)\n", columns, lines);

  emit set_screen_size_signal (columns, lines);
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::copyClipboard ()
{
  if(!hasFocus()) return;

  QClipboard *clipboard = QApplication::clipboard ();

  QString selection = d->getSelection ();

  if (selection.isEmpty ())
    {
      if (! _extra_interrupt)
        terminal_interrupt ();
    }
  else
    {
      clipboard->setText (selection);
      emit report_status_message (tr ("copied selection to clipboard"));
    }
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::pasteClipboard (void)
{
  if(!hasFocus()) return;

  QString text = QApplication::clipboard()->text (QClipboard::Clipboard);

  if (! text.isEmpty ())
    {
      if (text.contains ("\t"))
        {
          qWarning ("Tabs replaced with spaces in pasted text before processing");
          text.replace ("\t", "    ");
        }

    sendText (text);
    }
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::selectAll (void)
{
  if(!hasFocus()) return;

  d->selectAll();
}



//////////////////////////////////////////////////////////////////////////////

QString QWinTerminalImpl::selectedText ()
{
  QString selection = d->getSelection ();
  return selection;
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::dragEnterEvent (QDragEnterEvent *event)
{
   if (event->mimeData ()->hasUrls ())
     {
       event->acceptProposedAction();
     }
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::dropEvent (QDropEvent *event)
{
  QString dropText;

  if (event->mimeData ()->hasUrls ())
    {
      foreach (QUrl url, event->mimeData ()->urls ())
        {
          if(dropText.length () > 0)
            dropText += '\n';
          dropText  += url.toLocalFile ();
        }
      sendText (dropText);
    }
}

//////////////////////////////////////////////////////////////////////////////

void QWinTerminalImpl::has_extra_interrupt (bool extra)
{
  _extra_interrupt = extra;
}