File indexing completed on 2018-06-13 07:18:13

0001 /*
0002     This file is part of Konsole, a terminal emulator for KDE.
0003 
0004     Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
0005     Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
0006 
0007     This program is free software; you can redistribute it and/or modify
0008     it under the terms of the GNU General Public License as published by
0009     the Free Software Foundation; either version 2 of the License, or
0010     (at your option) any later version.
0011 
0012     This program is distributed in the hope that it will be useful,
0013     but WITHOUT ANY WARRANTY; without even the implied warranty of
0014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0015     GNU General Public License for more details.
0016 
0017     You should have received a copy of the GNU General Public License
0018     along with this program; if not, write to the Free Software
0019     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0020     02110-1301  USA.
0021 */
0022 
0023 // Own
0024 #include "TerminalDisplay.h"
0025 
0026 // Config
0027 #include <config-konsole.h>
0028 
0029 // Qt
0030 #include <QApplication>
0031 #include <QClipboard>
0032 #include <QKeyEvent>
0033 #include <QEvent>
0034 #include <QFileInfo>
0035 #include <QVBoxLayout>
0036 #include <QAction>
0037 #include <QFontDatabase>
0038 #include <QLabel>
0039 #include <QMimeData>
0040 #include <QPainter>
0041 #include <QPixmap>
0042 #include <QScrollBar>
0043 #include <QStyle>
0044 #include <QTimer>
0045 #include <QDrag>
0046 #include <QDesktopServices>
0047 #include <QAccessible>
0048 
0049 // KDE
0050 #include <KShell>
0051 #include <KColorScheme>
0052 #include <KCursor>
0053 #include <KLocalizedString>
0054 #include <KNotification>
0055 #include <KIO/DropJob>
0056 #include <KJobWidgets>
0057 #include <KMessageBox>
0058 #include <KMessageWidget>
0059 #include <KIO/StatJob>
0060 
0061 // Konsole
0062 #include "Filter.h"
0063 #include "konsoledebug.h"
0064 #include "konsole_wcwidth.h"
0065 #include "TerminalCharacterDecoder.h"
0066 #include "Screen.h"
0067 #include "LineFont.h"
0068 #include "SessionController.h"
0069 #include "ExtendedCharTable.h"
0070 #include "TerminalDisplayAccessible.h"
0071 #include "SessionManager.h"
0072 #include "Session.h"
0073 #include "WindowSystemInfo.h"
0074 
0075 using namespace Konsole;
0076 
0077 #ifndef loc
0078 #define loc(X,Y) ((Y)*_columns+(X))
0079 #endif
0080 
0081 #define REPCHAR   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
0082     "abcdefgjijklmnopqrstuvwxyz" \
0083     "0123456789./+@"
0084 
0085 // we use this to force QPainter to display text in LTR mode
0086 // more information can be found in: http://unicode.org/reports/tr9/
0087 const QChar LTR_OVERRIDE_CHAR(0x202D);
0088 
0089 /* ------------------------------------------------------------------------- */
0090 /*                                                                           */
0091 /*                                Colors                                     */
0092 /*                                                                           */
0093 /* ------------------------------------------------------------------------- */
0094 
0095 /* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
0096 
0097    Code        0       1       2       3       4       5       6       7
0098    ----------- ------- ------- ------- ------- ------- ------- ------- -------
0099    ANSI  (bgr) Black   Red     Green   Yellow  Blue    Magenta Cyan    White
0100    IBMPC (rgb) Black   Blue    Green   Cyan    Red     Magenta Yellow  White
0101 */
0102 
0103 ScreenWindow* TerminalDisplay::screenWindow() const
0104 {
0105     return _screenWindow;
0106 }
0107 void TerminalDisplay::setScreenWindow(ScreenWindow* window)
0108 {
0109     // disconnect existing screen window if any
0110     if (_screenWindow != nullptr) {
0111         disconnect(_screenWindow , nullptr , this , nullptr);
0112     }
0113 
0114     _screenWindow = window;
0115 
0116     if (_screenWindow != nullptr) {
0117         connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateLineProperties);
0118         connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateImage);
0119         connect(_screenWindow.data() , &Konsole::ScreenWindow::currentResultLineChanged , this , &Konsole::TerminalDisplay::updateImage);
0120         connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
0121             _filterUpdateRequired = true;
0122         });
0123         connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
0124             _filterUpdateRequired = true;
0125         });
0126         _screenWindow->setWindowLines(_lines);
0127     }
0128 }
0129 
0130 const ColorEntry* TerminalDisplay::colorTable() const
0131 {
0132     return _colorTable;
0133 }
0134 
0135 void TerminalDisplay::updateScrollBarPalette()
0136 {
0137     QColor backgroundColor = _colorTable[DEFAULT_BACK_COLOR];
0138     backgroundColor.setAlphaF(_opacity);
0139     QPalette p = palette();
0140     p.setColor(QPalette::Window, backgroundColor);
0141 
0142     //this is a workaround to add some readability to old themes like Fusion
0143     //changing the light value for button a bit makes themes like fusion, windows and oxygen way more readable and pleasing
0144     QColor buttonColor;
0145     buttonColor.setHsvF(backgroundColor.hueF(), backgroundColor.saturationF(), backgroundColor.valueF() + (backgroundColor.valueF() < 0.5 ? 0.2 : -0.2));
0146     p.setColor(QPalette::Button, buttonColor);
0147 
0148     p.setColor(QPalette::WindowText, _colorTable[DEFAULT_FORE_COLOR]);
0149     p.setColor(QPalette::ButtonText, _colorTable[DEFAULT_FORE_COLOR]);
0150     _scrollBar->setPalette(p);
0151 
0152 }
0153 
0154 void TerminalDisplay::setBackgroundColor(const QColor& color)
0155 {
0156     _colorTable[DEFAULT_BACK_COLOR] = color;
0157 
0158     QPalette p = palette();
0159     p.setColor(backgroundRole(), color);
0160     setPalette(p);
0161 
0162     updateScrollBarPalette();
0163     update();
0164 }
0165 QColor TerminalDisplay::getBackgroundColor() const
0166 {
0167     QPalette p = palette();
0168     return p.color(backgroundRole());
0169 }
0170 void TerminalDisplay::setForegroundColor(const QColor& color)
0171 {
0172     _colorTable[DEFAULT_FORE_COLOR] = color;
0173 
0174     updateScrollBarPalette();
0175     update();
0176 }
0177 void TerminalDisplay::setColorTable(const ColorEntry table[])
0178 {
0179     for (int i = 0; i < TABLE_COLORS; i++) {
0180         _colorTable[i] = table[i];
0181     }
0182 
0183     setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR]);
0184 }
0185 
0186 /* ------------------------------------------------------------------------- */
0187 /*                                                                           */
0188 /*                                   Font                                    */
0189 /*                                                                           */
0190 /* ------------------------------------------------------------------------- */
0191 
0192 static inline bool isLineCharString(const QString& string)
0193 {
0194     if (string.length() == 0) {
0195         return false;
0196     }
0197 
0198     return isSupportedLineChar(string.at(0).unicode());
0199 }
0200 
0201 void TerminalDisplay::fontChange(const QFont&)
0202 {
0203     QFontMetrics fm(font());
0204     _fontHeight = fm.height() + _lineSpacing;
0205 
0206     // waba TerminalDisplay 1.123:
0207     // "Base character width on widest ASCII character. This prevents too wide
0208     //  characters in the presence of double wide (e.g. Japanese) characters."
0209     // Get the width from representative normal width characters
0210     _fontWidth = qRound((static_cast<double>(fm.width(QStringLiteral(REPCHAR))) / static_cast<double>(qstrlen(REPCHAR))));
0211 
0212     _fixedFont = true;
0213 
0214     const int fw = fm.width(QLatin1Char(REPCHAR[0]));
0215     for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
0216         if (fw != fm.width(QLatin1Char(REPCHAR[i]))) {
0217             _fixedFont = false;
0218             break;
0219         }
0220     }
0221 
0222     if (_fontWidth < 1) {
0223         _fontWidth = 1;
0224     }
0225 
0226     _fontAscent = fm.ascent();
0227 
0228     emit changedFontMetricSignal(_fontHeight, _fontWidth);
0229     propagateSize();
0230     update();
0231 }
0232 
0233 void TerminalDisplay::setVTFont(const QFont& f)
0234 {
0235     QFont newFont(f);
0236     QFontMetrics fontMetrics(newFont);
0237 
0238     // This check seems extreme and semi-random
0239     if ((fontMetrics.height() > height()) || (fontMetrics.maxWidth() > width())) {
0240         return;
0241     }
0242 
0243     // hint that text should be drawn without anti-aliasing.
0244     // depending on the user's font configuration, this may not be respected
0245     if (!_antialiasText) {
0246         newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::NoAntialias));
0247     }
0248 
0249     // experimental optimization.  Konsole assumes that the terminal is using a
0250     // mono-spaced font, in which case kerning information should have an effect.
0251     // Disabling kerning saves some computation when rendering text.
0252     newFont.setKerning(false);
0253 
0254     // Konsole cannot handle non-integer font metrics
0255     newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::ForceIntegerMetrics));
0256 
0257     QFontInfo fontInfo(newFont);
0258 
0259 //    if (!fontInfo.fixedPitch()) {
0260 //        qWarning() << "Using a variable-width font - this might cause display problems";
0261 //    }
0262 
0263     // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch()
0264     // related?  https://bugreports.qt.io/browse/QTBUG-34082
0265     if (!fontInfo.exactMatch()) {
0266         const QChar comma(QLatin1Char(','));
0267         QString nonMatching = fontInfo.family() % comma %
0268             QString::number(fontInfo.pointSizeF()) % comma %
0269             QString::number(fontInfo.pixelSize()) % comma %
0270             QString::number(static_cast<int>(fontInfo.styleHint())) % comma %
0271             QString::number(fontInfo.weight()) % comma %
0272             QString::number(static_cast<int>(fontInfo.style())) % comma %
0273             QString::number(static_cast<int>(fontInfo.underline())) % comma %
0274             QString::number(static_cast<int>(fontInfo.strikeOut())) % comma %
0275             QString::number(static_cast<int>(fontInfo.fixedPitch())) % comma %
0276             QString::number(static_cast<int>(fontInfo.rawMode()));
0277 
0278         qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system.";
0279         qCDebug(KonsoleDebug)<<" Selected: "<<newFont.toString();
0280         qCDebug(KonsoleDebug)<<" System  : "<<nonMatching;
0281     }
0282 
0283     QWidget::setFont(newFont);
0284     fontChange(newFont);
0285 }
0286 
0287 void TerminalDisplay::setFont(const QFont &)
0288 {
0289     // ignore font change request if not coming from konsole itself
0290 }
0291 
0292 void TerminalDisplay::increaseFontSize()
0293 {
0294     QFont font = getVTFont();
0295     font.setPointSizeF(font.pointSizeF() + 1);
0296     setVTFont(font);
0297 }
0298 
0299 void TerminalDisplay::decreaseFontSize()
0300 {
0301     const qreal MinimumFontSize = 6;
0302 
0303     QFont font = getVTFont();
0304     font.setPointSizeF(qMax(font.pointSizeF() - 1, MinimumFontSize));
0305     setVTFont(font);
0306 }
0307 
0308 uint TerminalDisplay::lineSpacing() const
0309 {
0310     return _lineSpacing;
0311 }
0312 
0313 void TerminalDisplay::setLineSpacing(uint i)
0314 {
0315     _lineSpacing = i;
0316     setVTFont(font()); // Trigger an update.
0317 }
0318 
0319 
0320 /* ------------------------------------------------------------------------- */
0321 /*                                                                           */
0322 /*                         Accessibility                                     */
0323 /*                                                                           */
0324 /* ------------------------------------------------------------------------- */
0325 
0326 namespace Konsole
0327 {
0328 
0329 #ifndef QT_NO_ACCESSIBILITY
0330 /**
0331  * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
0332  * for the TerminalDisplay.
0333  */
0334 QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
0335 {
0336     Q_UNUSED(key)
0337     if (TerminalDisplay *display = qobject_cast<TerminalDisplay*>(object)) {
0338         return new TerminalDisplayAccessible(display);
0339     }
0340     return nullptr;
0341 }
0342 
0343 #endif
0344 }
0345 
0346 /* ------------------------------------------------------------------------- */
0347 /*                                                                           */
0348 /*                         Constructor / Destructor                          */
0349 /*                                                                           */
0350 /* ------------------------------------------------------------------------- */
0351 
0352 TerminalDisplay::TerminalDisplay(QWidget* parent)
0353     : QWidget(parent)
0354     , _screenWindow(nullptr)
0355     , _bellMasked(false)
0356     , _verticalLayout(new QVBoxLayout(this))
0357     , _fontHeight(1)
0358     , _fontWidth(1)
0359     , _fontAscent(1)
0360     , _boldIntense(true)
0361     , _lines(1)
0362     , _columns(1)
0363     , _usedLines(1)
0364     , _usedColumns(1)
0365     , _image(nullptr)
0366     , _randomSeed(0)
0367     , _resizing(false)
0368     , _showTerminalSizeHint(true)
0369     , _bidiEnabled(false)
0370     , _isPrimaryScreen(true)
0371     , _actSel(0)
0372     , _wordSelectionMode(false)
0373     , _lineSelectionMode(false)
0374     , _preserveLineBreaks(true)
0375     , _columnSelectionMode(false)
0376     , _autoCopySelectedText(false)
0377     , _copyTextAsHTML(true)
0378     , _middleClickPasteMode(Enum::PasteFromX11Selection)
0379     , _scrollbarLocation(Enum::ScrollBarRight)
0380     , _scrollFullPage(false)
0381     , _wordCharacters(QStringLiteral(":@-./_~"))
0382     , _bellMode(Enum::NotifyBell)
0383     , _allowBlinkingText(true)
0384     , _allowBlinkingCursor(false)
0385     , _textBlinking(false)
0386     , _cursorBlinking(false)
0387     , _hasTextBlinker(false)
0388     , _urlHintsModifiers(Qt::NoModifier)
0389     , _showUrlHint(false)
0390     , _openLinksByDirectClick(false)
0391     , _ctrlRequiredForDrag(true)
0392     , _tripleClickMode(Enum::SelectWholeLine)
0393     , _possibleTripleClick(false)
0394     , _resizeWidget(nullptr)
0395     , _resizeTimer(nullptr)
0396     , _flowControlWarningEnabled(false)
0397     , _outputSuspendedMessageWidget(nullptr)
0398     , _lineSpacing(0)
0399     , _blendColor(qRgba(0, 0, 0, 0xff))
0400     , _filterChain(new TerminalImageFilterChain())
0401     , _filterUpdateRequired(true)
0402     , _cursorShape(Enum::BlockCursor)
0403     , _antialiasText(true)
0404     , _useFontLineCharacters(false)
0405     , _printerFriendly(false)
0406     , _sessionController(nullptr)
0407     , _trimLeadingSpaces(false)
0408     , _trimTrailingSpaces(false)
0409     , _margin(1)
0410     , _centerContents(false)
0411     , _readOnlyMessageWidget(nullptr)
0412     , _readOnly(false)
0413     , _opacity(1.0)
0414 {
0415     // terminal applications are not designed with Right-To-Left in mind,
0416     // so the layout is forced to Left-To-Right
0417     setLayoutDirection(Qt::LeftToRight);
0418 
0419     _contentRect = QRect(_margin, _margin, 1, 1);
0420 
0421     // create scroll bar for scrolling output up and down
0422     _scrollBar = new QScrollBar(this);
0423     _scrollBar->setAutoFillBackground(false);
0424     // set the scroll bar's slider to occupy the whole area of the scroll bar initially
0425     setScroll(0, 0);
0426     _scrollBar->setCursor(Qt::ArrowCursor);
0427     connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
0428     connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
0429 
0430     // setup timers for blinking text
0431     _blinkTextTimer = new QTimer(this);
0432     _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
0433     connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
0434 
0435     // setup timers for blinking cursor
0436     _blinkCursorTimer = new QTimer(this);
0437     _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
0438     connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
0439 
0440     // hide mouse cursor on keystroke or idle
0441     KCursor::setAutoHideCursor(this, true);
0442     setMouseTracking(true);
0443 
0444     setUsesMouse(true);
0445     setBracketedPasteMode(false);
0446 
0447     setColorTable(ColorScheme::defaultTable);
0448 
0449     // Enable drag and drop support
0450     setAcceptDrops(true);
0451     _dragInfo.state = diNone;
0452 
0453     setFocusPolicy(Qt::WheelFocus);
0454 
0455     // enable input method support
0456     setAttribute(Qt::WA_InputMethodEnabled, true);
0457 
0458     // this is an important optimization, it tells Qt
0459     // that TerminalDisplay will handle repainting its entire area.
0460     setAttribute(Qt::WA_OpaquePaintEvent);
0461 
0462     // Add the stretch item once, the KMessageWidgets are inserted at index 0.
0463     _verticalLayout->addStretch();
0464     _verticalLayout->setSpacing(0);
0465 
0466     setLayout(_verticalLayout);
0467 
0468     // Take the scrollbar into account and add a margin to the layout. Without the timer the scrollbar width
0469     // is garbage.
0470     QTimer::singleShot(0, this, [this]() {
0471         const int scrollBarWidth = _scrollBar->isVisible() ? geometry().intersected(_scrollBar->geometry()).width() : 0;
0472         _verticalLayout->setContentsMargins(0, 0, scrollBarWidth, 0);
0473     });
0474 
0475     new AutoScrollHandler(this);
0476 
0477 
0478 #ifndef QT_NO_ACCESSIBILITY
0479     QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
0480 #endif
0481 }
0482 
0483 TerminalDisplay::~TerminalDisplay()
0484 {
0485     disconnect(_blinkTextTimer);
0486     disconnect(_blinkCursorTimer);
0487 
0488     delete _readOnlyMessageWidget;
0489     delete _outputSuspendedMessageWidget;
0490     delete[] _image;
0491     delete _filterChain;
0492 
0493     _readOnlyMessageWidget = nullptr;
0494     _outputSuspendedMessageWidget = nullptr;
0495 }
0496 
0497 /* ------------------------------------------------------------------------- */
0498 /*                                                                           */
0499 /*                             Display Operations                            */
0500 /*                                                                           */
0501 /* ------------------------------------------------------------------------- */
0502 
0503 /**
0504  A table for emulating the simple (single width) unicode drawing chars.
0505  It represents the 250x - 257x glyphs. If it's zero, we can't use it.
0506  if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
0507  0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
0508 
0509  Then, the pixels basically have the following interpretation:
0510  _|||_
0511  -...-
0512  -...-
0513  -...-
0514  _|||_
0515 
0516 where _ = none
0517       | = vertical line.
0518       - = horizontal line.
0519  */
0520 
0521 enum LineEncode {
0522     TopL  = (1 << 1),
0523     TopC  = (1 << 2),
0524     TopR  = (1 << 3),
0525 
0526     LeftT = (1 << 5),
0527     Int11 = (1 << 6),
0528     Int12 = (1 << 7),
0529     Int13 = (1 << 8),
0530     RightT = (1 << 9),
0531 
0532     LeftC = (1 << 10),
0533     Int21 = (1 << 11),
0534     Int22 = (1 << 12),
0535     Int23 = (1 << 13),
0536     RightC = (1 << 14),
0537 
0538     LeftB = (1 << 15),
0539     Int31 = (1 << 16),
0540     Int32 = (1 << 17),
0541     Int33 = (1 << 18),
0542     RightB = (1 << 19),
0543 
0544     BotL  = (1 << 21),
0545     BotC  = (1 << 22),
0546     BotR  = (1 << 23)
0547 };
0548 
0549 static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
0550 {
0551     //Calculate cell midpoints, end points.
0552     const int cx = x + w / 2;
0553     const int cy = y + h / 2;
0554     const int ex = x + w - 1;
0555     const int ey = y + h - 1;
0556 
0557     const quint32 toDraw = LineChars[code];
0558 
0559     //Top _lines:
0560     if ((toDraw & TopL) != 0u) {
0561         paint.drawLine(cx - 1, y, cx - 1, cy - 2);
0562     }
0563     if ((toDraw & TopC) != 0u) {
0564         paint.drawLine(cx, y, cx, cy - 2);
0565     }
0566     if ((toDraw & TopR) != 0u) {
0567         paint.drawLine(cx + 1, y, cx + 1, cy - 2);
0568     }
0569 
0570     //Bot _lines:
0571     if ((toDraw & BotL) != 0u) {
0572         paint.drawLine(cx - 1, cy + 2, cx - 1, ey);
0573     }
0574     if ((toDraw & BotC) != 0u) {
0575         paint.drawLine(cx, cy + 2, cx, ey);
0576     }
0577     if ((toDraw & BotR) != 0u) {
0578         paint.drawLine(cx + 1, cy + 2, cx + 1, ey);
0579     }
0580 
0581     //Left _lines:
0582     if ((toDraw & LeftT) != 0u) {
0583         paint.drawLine(x, cy - 1, cx - 2, cy - 1);
0584     }
0585     if ((toDraw & LeftC) != 0u) {
0586         paint.drawLine(x, cy, cx - 2, cy);
0587     }
0588     if ((toDraw & LeftB) != 0u) {
0589         paint.drawLine(x, cy + 1, cx - 2, cy + 1);
0590     }
0591 
0592     //Right _lines:
0593     if ((toDraw & RightT) != 0u) {
0594         paint.drawLine(cx + 2, cy - 1, ex, cy - 1);
0595     }
0596     if ((toDraw & RightC) != 0u) {
0597         paint.drawLine(cx + 2, cy, ex, cy);
0598     }
0599     if ((toDraw & RightB) != 0u) {
0600         paint.drawLine(cx + 2, cy + 1, ex, cy + 1);
0601     }
0602 
0603     //Intersection points.
0604     if ((toDraw & Int11) != 0u) {
0605         paint.drawPoint(cx - 1, cy - 1);
0606     }
0607     if ((toDraw & Int12) != 0u) {
0608         paint.drawPoint(cx, cy - 1);
0609     }
0610     if ((toDraw & Int13) != 0u) {
0611         paint.drawPoint(cx + 1, cy - 1);
0612     }
0613 
0614     if ((toDraw & Int21) != 0u) {
0615         paint.drawPoint(cx - 1, cy);
0616     }
0617     if ((toDraw & Int22) != 0u) {
0618         paint.drawPoint(cx, cy);
0619     }
0620     if ((toDraw & Int23) != 0u) {
0621         paint.drawPoint(cx + 1, cy);
0622     }
0623 
0624     if ((toDraw & Int31) != 0u) {
0625         paint.drawPoint(cx - 1, cy + 1);
0626     }
0627     if ((toDraw & Int32) != 0u) {
0628         paint.drawPoint(cx, cy + 1);
0629     }
0630     if ((toDraw & Int33) != 0u) {
0631         paint.drawPoint(cx + 1, cy + 1);
0632     }
0633 }
0634 
0635 static void drawOtherChar(QPainter& paint, int x, int y, int w, int h, uchar code)
0636 {
0637     //Calculate cell midpoints, end points.
0638     const int cx = x + w / 2;
0639     const int cy = y + h / 2;
0640     const int ex = x + w - 1;
0641     const int ey = y + h - 1;
0642 
0643     // Double dashes
0644     if (0x4C <= code && code <= 0x4F) {
0645         const int xHalfGap = qMax(w / 15, 1);
0646         const int yHalfGap = qMax(h / 15, 1);
0647         switch (code) {
0648         case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
0649             paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1);
0650             paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1);
0651             paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1);
0652             paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1);
0653             // No break!
0654 #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
0655             Q_FALLTHROUGH();
0656 #endif
0657         case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
0658             paint.drawLine(x, cy, cx - xHalfGap - 1, cy);
0659             paint.drawLine(cx + xHalfGap, cy, ex, cy);
0660             break;
0661         case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
0662             paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1);
0663             paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1);
0664             paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey);
0665             paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey);
0666             // No break!
0667 #if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
0668             Q_FALLTHROUGH();
0669 #endif
0670         case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
0671             paint.drawLine(cx, y, cx, cy - yHalfGap - 1);
0672             paint.drawLine(cx, cy + yHalfGap, cx, ey);
0673             break;
0674         }
0675     }
0676 
0677     // Rounded corner characters
0678     else if (0x6D <= code && code <= 0x70) {
0679         const int r = w * 3 / 8;
0680         const int d = 2 * r;
0681         switch (code) {
0682         case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
0683             paint.drawLine(cx, cy + r, cx, ey);
0684             paint.drawLine(cx + r, cy, ex, cy);
0685             paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16);
0686             break;
0687         case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
0688             paint.drawLine(cx, cy + r, cx, ey);
0689             paint.drawLine(x, cy, cx - r, cy);
0690             paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16);
0691             break;
0692         case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT
0693             paint.drawLine(cx, y, cx, cy - r);
0694             paint.drawLine(x, cy, cx - r, cy);
0695             paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16);
0696             break;
0697         case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT
0698             paint.drawLine(cx, y, cx, cy - r);
0699             paint.drawLine(cx + r, cy, ex, cy);
0700             paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16);
0701             break;
0702         }
0703     }
0704 
0705     // Diagonals
0706     else if (0x71 <= code && code <= 0x73) {
0707         switch (code) {
0708         case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
0709             paint.drawLine(ex, y, x, ey);
0710             break;
0711         case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
0712             paint.drawLine(x, y, ex, ey);
0713             break;
0714         case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS
0715             paint.drawLine(ex, y, x, ey);
0716             paint.drawLine(x, y, ex, ey);
0717             break;
0718         }
0719     }
0720 }
0721 
0722 void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str,
0723         const Character* attributes)
0724 {
0725     const QPen& originalPen = painter.pen();
0726 
0727     if (((attributes->rendition & RE_BOLD) != 0) && _boldIntense) {
0728         QPen boldPen(originalPen);
0729         boldPen.setWidth(3);
0730         painter.setPen(boldPen);
0731     }
0732 
0733     for (int i = 0 ; i < str.length(); i++) {
0734         const uchar code = str[i].cell();
0735         if (LineChars[code] != 0u) {
0736             drawLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
0737         } else {
0738             drawOtherChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
0739         }
0740     }
0741 
0742     painter.setPen(originalPen);
0743 }
0744 
0745 void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
0746 {
0747     _cursorShape = shape;
0748 }
0749 Enum::CursorShapeEnum TerminalDisplay::keyboardCursorShape() const
0750 {
0751     return _cursorShape;
0752 }
0753 void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
0754 {
0755     _cursorColor = color;
0756 }
0757 QColor TerminalDisplay::keyboardCursorColor() const
0758 {
0759     return _cursorColor;
0760 }
0761 
0762 void TerminalDisplay::setOpacity(qreal opacity)
0763 {
0764     QColor color(_blendColor);
0765     color.setAlphaF(opacity);
0766     _opacity = opacity;
0767 
0768     // enable automatic background filling to prevent the display
0769     // flickering if there is no transparency
0770     /*if ( color.alpha() == 255 )
0771     {
0772         setAutoFillBackground(true);
0773     }
0774     else
0775     {
0776         setAutoFillBackground(false);
0777     }*/
0778 
0779     _blendColor = color.rgba();
0780     updateScrollBarPalette();
0781 }
0782 
0783 void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p)
0784 {
0785     _wallpaper = p;
0786 }
0787 
0788 void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting)
0789 {
0790     // the area of the widget showing the contents of the terminal display is drawn
0791     // using the background color from the color scheme set with setColorTable()
0792     //
0793     // the area of the widget behind the scroll-bar is drawn using the background
0794     // brush from the scroll-bar's palette, to give the effect of the scroll-bar
0795     // being outside of the terminal display and visual consistency with other KDE
0796     // applications.
0797 
0798     if (useOpacitySetting && !_wallpaper->isNull() &&
0799             _wallpaper->draw(painter, rect, _opacity)) {
0800     } else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) {
0801 #if defined(Q_OS_MACOS)
0802         // TODO - On MacOS, using CompositionMode doesn't work.  Altering the
0803         //        transparency in the color scheme alters the brightness.
0804         painter.fillRect(rect, backgroundColor);
0805 #else
0806         QColor color(backgroundColor);
0807         color.setAlpha(qAlpha(_blendColor));
0808 
0809         painter.save();
0810         painter.setCompositionMode(QPainter::CompositionMode_Source);
0811         painter.fillRect(rect, color);
0812         painter.restore();
0813 #endif
0814     } else {
0815         painter.fillRect(rect, backgroundColor);
0816     }
0817 }
0818 
0819 void TerminalDisplay::drawCursor(QPainter& painter,
0820                                  const QRect& rect,
0821                                  const QColor& foregroundColor,
0822                                  const QColor& /*backgroundColor*/,
0823                                  bool& invertCharacterColor)
0824 {
0825     // don't draw cursor which is currently blinking
0826     if (_cursorBlinking) {
0827         return;
0828     }
0829 
0830     // shift rectangle top down one pixel to leave some space
0831     // between top and bottom
0832     QRect cursorRect = rect.adjusted(0, 1, 0, 0);
0833 
0834     QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
0835     painter.setPen(cursorColor);
0836 
0837     if (_cursorShape == Enum::BlockCursor) {
0838         // draw the cursor outline, adjusting the area so that
0839         // it is draw entirely inside 'rect'
0840         int penWidth = qMax(1, painter.pen().width());
0841         painter.drawRect(cursorRect.adjusted(penWidth / 2,
0842                                              penWidth / 2,
0843                                              - penWidth / 2 - penWidth % 2,
0844                                              - penWidth / 2 - penWidth % 2));
0845 
0846         // draw the cursor body only when the widget has focus
0847         if (hasFocus()) {
0848             painter.fillRect(cursorRect, cursorColor);
0849 
0850             if (!_cursorColor.isValid()) {
0851                 // invert the color used to draw the text to ensure that the character at
0852                 // the cursor position is readable
0853                 invertCharacterColor = true;
0854             }
0855         }
0856     } else if (_cursorShape == Enum::UnderlineCursor) {
0857         painter.drawLine(cursorRect.left(),
0858                          cursorRect.bottom(),
0859                          cursorRect.right(),
0860                          cursorRect.bottom());
0861 
0862     } else if (_cursorShape == Enum::IBeamCursor) {
0863         painter.drawLine(cursorRect.left(),
0864                          cursorRect.top(),
0865                          cursorRect.left(),
0866                          cursorRect.bottom());
0867     }
0868 }
0869 
0870 void TerminalDisplay::drawCharacters(QPainter& painter,
0871                                      const QRect& rect,
0872                                      const QString& text,
0873                                      const Character* style,
0874                                      bool invertCharacterColor)
0875 {
0876     // don't draw text which is currently blinking
0877     if (_textBlinking && ((style->rendition & RE_BLINK) != 0)) {
0878         return;
0879     }
0880 
0881     // don't draw concealed characters
0882     if ((style->rendition & RE_CONCEAL) != 0) {
0883         return;
0884     }
0885 
0886     // setup bold and underline
0887     bool useBold = (((style->rendition & RE_BOLD) != 0) && _boldIntense) || font().bold();
0888     const bool useUnderline = ((style->rendition & RE_UNDERLINE) != 0) || font().underline();
0889     const bool useItalic = ((style->rendition & RE_ITALIC) != 0) || font().italic();
0890     const bool useStrikeOut = ((style->rendition & RE_STRIKEOUT) != 0) || font().strikeOut();
0891     const bool useOverline = ((style->rendition & RE_OVERLINE) != 0) || font().overline();
0892 
0893     QFont font = painter.font();
0894     if (font.bold() != useBold
0895             || font.underline() != useUnderline
0896             || font.italic() != useItalic
0897             || font.strikeOut() != useStrikeOut
0898             || font.overline() != useOverline) {
0899         font.setBold(useBold);
0900         font.setUnderline(useUnderline);
0901         font.setItalic(useItalic);
0902         font.setStrikeOut(useStrikeOut);
0903         font.setOverline(useOverline);
0904         painter.setFont(font);
0905     }
0906 
0907     // setup pen
0908     const CharacterColor& textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor);
0909     const QColor color = textColor.color(_colorTable);
0910     QPen pen = painter.pen();
0911     if (pen.color() != color) {
0912         pen.setColor(color);
0913         painter.setPen(color);
0914     }
0915 
0916     // draw text
0917     if (isLineCharString(text) && !_useFontLineCharacters) {
0918         drawLineCharString(painter, rect.x(), rect.y(), text, style);
0919     } else {
0920         // Force using LTR as the document layout for the terminal area, because
0921         // there is no use cases for RTL emulator and RTL terminal application.
0922         //
0923         // This still allows RTL characters to be rendered in the RTL way.
0924         painter.setLayoutDirection(Qt::LeftToRight);
0925 
0926         if (_bidiEnabled) {
0927             painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text);
0928         } else {
0929             painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text);
0930         }
0931     }
0932 }
0933 
0934 void TerminalDisplay::drawTextFragment(QPainter& painter ,
0935                                        const QRect& rect,
0936                                        const QString& text,
0937                                        const Character* style)
0938 {
0939     painter.save();
0940 
0941     // setup painter
0942     const QColor foregroundColor = style->foregroundColor.color(_colorTable);
0943     const QColor backgroundColor = style->backgroundColor.color(_colorTable);
0944 
0945     // draw background if different from the display's background color
0946     if (backgroundColor != palette().background().color()) {
0947         drawBackground(painter, rect, backgroundColor,
0948                        false /* do not use transparency */);
0949     }
0950 
0951     // draw cursor shape if the current character is the cursor
0952     // this may alter the foreground and background colors
0953     bool invertCharacterColor = false;
0954     if ((style->rendition & RE_CURSOR) != 0) {
0955         drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor);
0956     }
0957 
0958     // draw text
0959     drawCharacters(painter, rect, text, style, invertCharacterColor);
0960 
0961     painter.restore();
0962 }
0963 
0964 void TerminalDisplay::drawPrinterFriendlyTextFragment(QPainter& painter,
0965         const QRect& rect,
0966         const QString& text,
0967         const Character* style)
0968 {
0969     painter.save();
0970 
0971     // Set the colors used to draw to black foreground and white
0972     // background for printer friendly output when printing
0973     Character print_style = *style;
0974     print_style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000);
0975     print_style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF);
0976 
0977     // draw text
0978     drawCharacters(painter, rect, text, &print_style, false);
0979 
0980     painter.restore();
0981 }
0982 
0983 void TerminalDisplay::setRandomSeed(uint randomSeed)
0984 {
0985     _randomSeed = randomSeed;
0986 }
0987 uint TerminalDisplay::randomSeed() const
0988 {
0989     return _randomSeed;
0990 }
0991 
0992 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
0993 //
0994 // the terminal emulation keeps track of the scrolling of the character
0995 // image as it receives input, and when the view is updated, it calls scrollImage()
0996 // with the final scroll amount.  this improves performance because scrolling the
0997 // display is much cheaper than re-rendering all the text for the
0998 // part of the image which has moved up or down.
0999 // Instead only new lines have to be drawn
1000 void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
1001 {
1002     // return if there is nothing to do
1003     if ((lines == 0) || (_image == nullptr)) {
1004         return;
1005     }
1006 
1007     // if the flow control warning is enabled this will interfere with the
1008     // scrolling optimizations and cause artifacts.  the simple solution here
1009     // is to just disable the optimization whilst it is visible
1010     if ((_outputSuspendedMessageWidget != nullptr) && _outputSuspendedMessageWidget->isVisible()) {
1011         return;
1012     }
1013 
1014     if ((_readOnlyMessageWidget != nullptr) && _readOnlyMessageWidget->isVisible()) {
1015         return;
1016     }
1017 
1018     // constrain the region to the display
1019     // the bottom of the region is capped to the number of lines in the display's
1020     // internal image - 2, so that the height of 'region' is strictly less
1021     // than the height of the internal image.
1022     QRect region = screenWindowRegion;
1023     region.setBottom(qMin(region.bottom(), this->_lines - 2));
1024 
1025     // return if there is nothing to do
1026     if (!region.isValid()
1027             || (region.top() + abs(lines)) >= region.bottom()
1028             || this->_lines <= region.height()) {
1029         return;
1030     }
1031 
1032     // hide terminal size label to prevent it being scrolled
1033     if ((_resizeWidget != nullptr) && _resizeWidget->isVisible()) {
1034         _resizeWidget->hide();
1035     }
1036 
1037     // Note:  With Qt 4.4 the left edge of the scrolled area must be at 0
1038     // to get the correct (newly exposed) part of the widget repainted.
1039     //
1040     // The right edge must be before the left edge of the scroll bar to
1041     // avoid triggering a repaint of the entire widget, the distance is
1042     // given by SCROLLBAR_CONTENT_GAP
1043     //
1044     // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
1045     // application to monitor repainting.
1046     //
1047     const int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width();
1048     const int SCROLLBAR_CONTENT_GAP = 1;
1049     QRect scrollRect;
1050     if (_scrollbarLocation == Enum::ScrollBarLeft) {
1051         scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP);
1052         scrollRect.setRight(width());
1053     } else {
1054         scrollRect.setLeft(0);
1055         scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
1056     }
1057     void* firstCharPos = &_image[ region.top() * this->_columns ];
1058     void* lastCharPos = &_image[(region.top() + abs(lines)) * this->_columns ];
1059 
1060     const int top = _contentRect.top() + (region.top() * _fontHeight);
1061     const int linesToMove = region.height() - abs(lines);
1062     const int bytesToMove = linesToMove * this->_columns * sizeof(Character);
1063 
1064     Q_ASSERT(linesToMove > 0);
1065     Q_ASSERT(bytesToMove > 0);
1066 
1067     //scroll internal image
1068     if (lines > 0) {
1069         // check that the memory areas that we are going to move are valid
1070         Q_ASSERT((char*)lastCharPos + bytesToMove <
1071                  (char*)(_image + (this->_lines * this->_columns)));
1072 
1073         Q_ASSERT((lines * this->_columns) < _imageSize);
1074 
1075         //scroll internal image down
1076         memmove(firstCharPos , lastCharPos , bytesToMove);
1077 
1078         //set region of display to scroll
1079         scrollRect.setTop(top);
1080     } else {
1081         // check that the memory areas that we are going to move are valid
1082         Q_ASSERT((char*)firstCharPos + bytesToMove <
1083                  (char*)(_image + (this->_lines * this->_columns)));
1084 
1085         //scroll internal image up
1086         memmove(lastCharPos , firstCharPos , bytesToMove);
1087 
1088         //set region of the display to scroll
1089         scrollRect.setTop(top + abs(lines) * _fontHeight);
1090     }
1091     scrollRect.setHeight(linesToMove * _fontHeight);
1092 
1093     Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
1094 
1095     //scroll the display vertically to match internal _image
1096     scroll(0 , _fontHeight * (-lines) , scrollRect);
1097 }
1098 
1099 QRegion TerminalDisplay::hotSpotRegion() const
1100 {
1101     QRegion region;
1102     foreach(Filter::HotSpot * hotSpot , _filterChain->hotSpots()) {
1103         QRect r;
1104         if (hotSpot->startLine() == hotSpot->endLine()) {
1105             r.setLeft(hotSpot->startColumn());
1106             r.setTop(hotSpot->startLine());
1107             r.setRight(hotSpot->endColumn());
1108             r.setBottom(hotSpot->endLine());
1109             region |= imageToWidget(r);
1110         } else {
1111             r.setLeft(hotSpot->startColumn());
1112             r.setTop(hotSpot->startLine());
1113             r.setRight(_columns);
1114             r.setBottom(hotSpot->startLine());
1115             region |= imageToWidget(r);
1116             for (int line = hotSpot->startLine() + 1 ; line < hotSpot->endLine() ; line++) {
1117                 r.setLeft(0);
1118                 r.setTop(line);
1119                 r.setRight(_columns);
1120                 r.setBottom(line);
1121                 region |= imageToWidget(r);
1122             }
1123             r.setLeft(0);
1124             r.setTop(hotSpot->endLine());
1125             r.setRight(hotSpot->endColumn());
1126             r.setBottom(hotSpot->endLine());
1127             region |= imageToWidget(r);
1128         }
1129     }
1130     return region;
1131 }
1132 
1133 void TerminalDisplay::processFilters()
1134 {
1135     if (_screenWindow == nullptr) {
1136         return;
1137     }
1138 
1139     if (!_filterUpdateRequired) {
1140         return;
1141     }
1142 
1143     QRegion preUpdateHotSpots = hotSpotRegion();
1144 
1145     // use _screenWindow->getImage() here rather than _image because
1146     // other classes may call processFilters() when this display's
1147     // ScreenWindow emits a scrolled() signal - which will happen before
1148     // updateImage() is called on the display and therefore _image is
1149     // out of date at this point
1150     _filterChain->setImage(_screenWindow->getImage(),
1151                            _screenWindow->windowLines(),
1152                            _screenWindow->windowColumns(),
1153                            _screenWindow->getLineProperties());
1154     _filterChain->process();
1155 
1156     QRegion postUpdateHotSpots = hotSpotRegion();
1157 
1158     update(preUpdateHotSpots | postUpdateHotSpots);
1159     _filterUpdateRequired = false;
1160 }
1161 
1162 void TerminalDisplay::updateImage()
1163 {
1164     if (_screenWindow == nullptr) {
1165         return;
1166     }
1167 
1168     // optimization - scroll the existing image where possible and
1169     // avoid expensive text drawing for parts of the image that
1170     // can simply be moved up or down
1171     // disable this shortcut for transparent konsole with scaled pixels, otherwise we get rendering artefacts, see BUG 350651
1172     if (!(WindowSystemInfo::HAVE_TRANSPARENCY && (qApp->devicePixelRatio() > 1.0)) && _wallpaper->isNull()) {
1173         scrollImage(_screenWindow->scrollCount() ,
1174                     _screenWindow->scrollRegion());
1175         _screenWindow->resetScrollCount();
1176     }
1177 
1178     if (_image == nullptr) {
1179         // Create _image.
1180         // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
1181         updateImageSize();
1182     }
1183 
1184     Character* const newimg = _screenWindow->getImage();
1185     const int lines = _screenWindow->windowLines();
1186     const int columns = _screenWindow->windowColumns();
1187 
1188     setScroll(_screenWindow->currentLine() , _screenWindow->lineCount());
1189 
1190     Q_ASSERT(this->_usedLines <= this->_lines);
1191     Q_ASSERT(this->_usedColumns <= this->_columns);
1192 
1193     int y, x, len;
1194 
1195     const QPoint tL  = contentsRect().topLeft();
1196     const int    tLx = tL.x();
1197     const int    tLy = tL.y();
1198     _hasTextBlinker = false;
1199 
1200     CharacterColor cf;       // undefined
1201 
1202     const int linesToUpdate = qMin(this->_lines, qMax(0, lines));
1203     const int columnsToUpdate = qMin(this->_columns, qMax(0, columns));
1204 
1205     auto dirtyMask = new char[columnsToUpdate + 2];
1206     QRegion dirtyRegion;
1207 
1208     // debugging variable, this records the number of lines that are found to
1209     // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
1210     // which therefore need to be repainted
1211     int dirtyLineCount = 0;
1212 
1213     for (y = 0; y < linesToUpdate; ++y) {
1214         const Character* currentLine = &_image[y * this->_columns];
1215         const Character* const newLine = &newimg[y * columns];
1216 
1217         bool updateLine = false;
1218 
1219         // The dirty mask indicates which characters need repainting. We also
1220         // mark surrounding neighbors dirty, in case the character exceeds
1221         // its cell boundaries
1222         memset(dirtyMask, 0, columnsToUpdate + 2);
1223 
1224         for (x = 0 ; x < columnsToUpdate ; ++x) {
1225             if (newLine[x] != currentLine[x]) {
1226                 dirtyMask[x] = 1;
1227             }
1228         }
1229 
1230         if (!_resizing) { // not while _resizing, we're expecting a paintEvent
1231             for (x = 0; x < columnsToUpdate; ++x) {
1232                 _hasTextBlinker |= (newLine[x].rendition & RE_BLINK);
1233 
1234                 // Start drawing if this character or the next one differs.
1235                 // We also take the next one into account to handle the situation
1236                 // where characters exceed their cell width.
1237                 if (dirtyMask[x] != 0) {
1238                     if (newLine[x + 0].character == 0u) {
1239                         continue;
1240                     }
1241                     const bool lineDraw = newLine[x + 0].isLineChar();
1242                     const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character == 0);
1243                     const RenditionFlags cr = newLine[x].rendition;
1244                     const CharacterColor clipboard = newLine[x].backgroundColor;
1245                     if (newLine[x].foregroundColor != cf) {
1246                         cf = newLine[x].foregroundColor;
1247                     }
1248                     const int lln = columnsToUpdate - x;
1249                     for (len = 1; len < lln; ++len) {
1250                         const Character& ch = newLine[x + len];
1251 
1252                         if (ch.character == 0u) {
1253                             continue; // Skip trailing part of multi-col chars.
1254                         }
1255 
1256                         const bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character == 0);
1257 
1258                         if (ch.foregroundColor != cf ||
1259                                 ch.backgroundColor != clipboard ||
1260                                 (ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) ||
1261                                 (dirtyMask[x + len] == 0) ||
1262                                 ch.isLineChar() != lineDraw ||
1263                                 nextIsDoubleWidth != doubleWidth) {
1264                             break;
1265                         }
1266                     }
1267 
1268                     const bool saveFixedFont = _fixedFont;
1269                     if (lineDraw) {
1270                         _fixedFont = false;
1271                     }
1272                     if (doubleWidth) {
1273                         _fixedFont = false;
1274                     }
1275 
1276                     updateLine = true;
1277 
1278                     _fixedFont = saveFixedFont;
1279                     x += len - 1;
1280                 }
1281             }
1282         }
1283 
1284         //both the top and bottom halves of double height _lines must always be redrawn
1285         //although both top and bottom halves contain the same characters, only
1286         //the top one is actually
1287         //drawn.
1288         if (_lineProperties.count() > y) {
1289             updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
1290         }
1291 
1292         // if the characters on the line are different in the old and the new _image
1293         // then this line must be repainted.
1294         if (updateLine) {
1295             dirtyLineCount++;
1296 
1297             // add the area occupied by this line to the region which needs to be
1298             // repainted
1299             QRect dirtyRect = QRect(_contentRect.left() + tLx ,
1300                                     _contentRect.top() + tLy + _fontHeight * y ,
1301                                     _fontWidth * columnsToUpdate ,
1302                                     _fontHeight);
1303 
1304             dirtyRegion |= dirtyRect;
1305         }
1306 
1307         // replace the line of characters in the old _image with the
1308         // current line of the new _image
1309         memcpy((void*)currentLine, (const void*)newLine, columnsToUpdate * sizeof(Character));
1310     }
1311 
1312     // if the new _image is smaller than the previous _image, then ensure that the area
1313     // outside the new _image is cleared
1314     if (linesToUpdate < _usedLines) {
1315         dirtyRegion |= QRect(_contentRect.left() + tLx ,
1316                              _contentRect.top() + tLy + _fontHeight * linesToUpdate ,
1317                              _fontWidth * this->_columns ,
1318                              _fontHeight * (_usedLines - linesToUpdate));
1319     }
1320     _usedLines = linesToUpdate;
1321 
1322     if (columnsToUpdate < _usedColumns) {
1323         dirtyRegion |= QRect(_contentRect.left() + tLx + columnsToUpdate * _fontWidth ,
1324                              _contentRect.top() + tLy ,
1325                              _fontWidth * (_usedColumns - columnsToUpdate) ,
1326                              _fontHeight * this->_lines);
1327     }
1328     _usedColumns = columnsToUpdate;
1329 
1330     dirtyRegion |= _inputMethodData.previousPreeditRect;
1331 
1332     // update the parts of the display which have changed
1333     update(dirtyRegion);
1334 
1335     if (_allowBlinkingText && _hasTextBlinker && !_blinkTextTimer->isActive()) {
1336         _blinkTextTimer->start();
1337     }
1338     if (!_hasTextBlinker && _blinkTextTimer->isActive()) {
1339         _blinkTextTimer->stop();
1340         _textBlinking = false;
1341     }
1342     delete[] dirtyMask;
1343 
1344 #ifndef QT_NO_ACCESSIBILITY
1345     QAccessibleEvent dataChangeEvent(this, QAccessible::VisibleDataChanged);
1346     QAccessible::updateAccessibility(&dataChangeEvent);
1347     QAccessibleTextCursorEvent cursorEvent(this, _usedColumns * screenWindow()->screen()->getCursorY() + screenWindow()->screen()->getCursorX());
1348     QAccessible::updateAccessibility(&cursorEvent);
1349 #endif
1350 }
1351 
1352 void TerminalDisplay::showResizeNotification()
1353 {
1354     if (_showTerminalSizeHint && isVisible()) {
1355         if (_resizeWidget == nullptr) {
1356             _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
1357             _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(i18n("Size: XXX x XXX")));
1358             _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
1359             _resizeWidget->setAlignment(Qt::AlignCenter);
1360 
1361             _resizeWidget->setStyleSheet(QStringLiteral("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)"));
1362 
1363             _resizeTimer = new QTimer(this);
1364             _resizeTimer->setInterval(SIZE_HINT_DURATION);
1365             _resizeTimer->setSingleShot(true);
1366             connect(_resizeTimer, &QTimer::timeout, _resizeWidget, &QLabel::hide);
1367         }
1368         QString sizeStr = i18n("Size: %1 x %2", _columns, _lines);
1369         _resizeWidget->setText(sizeStr);
1370         _resizeWidget->move((width() - _resizeWidget->width()) / 2,
1371                             (height() - _resizeWidget->height()) / 2 + 20);
1372         _resizeWidget->show();
1373         _resizeTimer->start();
1374     }
1375 }
1376 
1377 void TerminalDisplay::paintEvent(QPaintEvent* pe)
1378 {
1379     QPainter paint(this);
1380 
1381     foreach(const QRect & rect, (pe->region() & contentsRect()).rects()) {
1382         drawBackground(paint, rect, palette().background().color(),
1383                        true /* use opacity setting */);
1384         drawContents(paint, rect);
1385     }
1386     drawCurrentResultRect(paint);
1387     drawInputMethodPreeditString(paint, preeditRect());
1388     paintFilters(paint);
1389 }
1390 
1391 void TerminalDisplay::printContent(QPainter& painter, bool friendly)
1392 {
1393     // Reinitialize the font with the printers paint device so the font
1394     // measurement calculations will be done correctly
1395     QFont savedFont = getVTFont();
1396     QFont font(savedFont, painter.device());
1397     painter.setFont(font);
1398     setVTFont(font);
1399 
1400     QRect rect(0, 0, size().width(), size().height());
1401 
1402     _printerFriendly = friendly;
1403     if (!friendly) {
1404         drawBackground(painter, rect, getBackgroundColor(),
1405                        true /* use opacity setting */);
1406     }
1407     drawContents(painter, rect);
1408     _printerFriendly = false;
1409     setVTFont(savedFont);
1410 }
1411 
1412 QPoint TerminalDisplay::cursorPosition() const
1413 {
1414     if (_screenWindow != nullptr) {
1415         return _screenWindow->cursorPosition();
1416     } else {
1417         return QPoint(0, 0);
1418     }
1419 }
1420 
1421 FilterChain* TerminalDisplay::filterChain() const
1422 {
1423     return _filterChain;
1424 }
1425 
1426 void TerminalDisplay::paintFilters(QPainter& painter)
1427 {
1428     if (_filterUpdateRequired) {
1429         return;
1430     }
1431 
1432     // get color of character under mouse and use it to draw
1433     // lines for filters
1434     QPoint cursorPos = mapFromGlobal(QCursor::pos());
1435     int cursorLine;
1436     int cursorColumn;
1437 
1438     getCharacterPosition(cursorPos , cursorLine , cursorColumn);
1439     Character cursorCharacter = _image[loc(cursorColumn, cursorLine)];
1440 
1441     painter.setPen(QPen(cursorCharacter.foregroundColor.color(colorTable())));
1442 
1443     // iterate over hotspots identified by the display's currently active filters
1444     // and draw appropriate visuals to indicate the presence of the hotspot
1445 
1446     int urlNumber = 0;
1447     QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
1448     foreach(Filter::HotSpot* spot, spots) {
1449         urlNumber++;
1450 
1451         QRegion region;
1452         if (spot->type() == Filter::HotSpot::Link) {
1453             QRect r;
1454             if (spot->startLine() == spot->endLine()) {
1455                 r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(),
1456                             spot->startLine()*_fontHeight + _contentRect.top(),
1457                             (spot->endColumn())*_fontWidth + _contentRect.left() - 1,
1458                             (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1);
1459                 region |= r;
1460             } else {
1461                 r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(),
1462                             spot->startLine()*_fontHeight + _contentRect.top(),
1463                             (_columns)*_fontWidth + _contentRect.left() - 1,
1464                             (spot->startLine() + 1)*_fontHeight + _contentRect.top() - 1);
1465                 region |= r;
1466                 for (int line = spot->startLine() + 1 ; line < spot->endLine() ; line++) {
1467                     r.setCoords(0 * _fontWidth + _contentRect.left(),
1468                                 line * _fontHeight + _contentRect.top(),
1469                                 (_columns)*_fontWidth + _contentRect.left() - 1,
1470                                 (line + 1)*_fontHeight + _contentRect.top() - 1);
1471                     region |= r;
1472                 }
1473                 r.setCoords(0 * _fontWidth + _contentRect.left(),
1474                             spot->endLine()*_fontHeight + _contentRect.top(),
1475                             (spot->endColumn())*_fontWidth + _contentRect.left() - 1,
1476                             (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1);
1477                 region |= r;
1478             }
1479 
1480             if (_showUrlHint && urlNumber < 10) {
1481                 // Position at the beginning of the URL
1482                 const QVector<QRect> regionRects = region.rects();
1483                 QRect hintRect(regionRects.first());
1484                 hintRect.setWidth(r.height());
1485                 painter.fillRect(hintRect, QColor(0, 0, 0, 128));
1486                 painter.setPen(Qt::white);
1487                 painter.drawRect(hintRect.adjusted(0, 0, -1, -1));
1488                 painter.drawText(hintRect, Qt::AlignCenter, QString::number(urlNumber));
1489             }
1490         }
1491 
1492         for (int line = spot->startLine() ; line <= spot->endLine() ; line++) {
1493             int startColumn = 0;
1494             int endColumn = _columns - 1; // TODO use number of _columns which are actually
1495             // occupied on this line rather than the width of the
1496             // display in _columns
1497 
1498             // Check image size so _image[] is valid (see makeImage)
1499             if (loc(endColumn, line) > _imageSize) {
1500                 break;
1501             }
1502 
1503             // ignore whitespace at the end of the lines
1504             while (_image[loc(endColumn, line)].isSpace() && endColumn > 0) {
1505                 endColumn--;
1506             }
1507 
1508             // increment here because the column which we want to set 'endColumn' to
1509             // is the first whitespace character at the end of the line
1510             endColumn++;
1511 
1512             if (line == spot->startLine()) {
1513                 startColumn = spot->startColumn();
1514             }
1515             if (line == spot->endLine()) {
1516                 endColumn = spot->endColumn();
1517             }
1518 
1519             // TODO: resolve this comment with the new margin/center code
1520             // subtract one pixel from
1521             // the right and bottom so that
1522             // we do not overdraw adjacent
1523             // hotspots
1524             //
1525             // subtracting one pixel from all sides also prevents an edge case where
1526             // moving the mouse outside a link could still leave it underlined
1527             // because the check below for the position of the cursor
1528             // finds it on the border of the target area
1529             QRect r;
1530             r.setCoords(startColumn * _fontWidth + _contentRect.left(),
1531                         line * _fontHeight + _contentRect.top(),
1532                         endColumn * _fontWidth + _contentRect.left() - 1,
1533                         (line + 1)*_fontHeight + _contentRect.top() - 1);
1534             // Underline link hotspots
1535             if (spot->type() == Filter::HotSpot::Link) {
1536                 QFontMetrics metrics(font());
1537 
1538                 // find the baseline (which is the invisible line that the characters in the font sit on,
1539                 // with some having tails dangling below)
1540                 const int baseline = r.bottom() - metrics.descent();
1541                 // find the position of the underline below that
1542                 const int underlinePos = baseline + metrics.underlinePos();
1543                 if (_showUrlHint || region.contains(mapFromGlobal(QCursor::pos()))) {
1544                     painter.drawLine(r.left() , underlinePos ,
1545                                      r.right() , underlinePos);
1546                 }
1547 
1548                 // Marker hotspots simply have a transparent rectangular shape
1549                 // drawn on top of them
1550             } else if (spot->type() == Filter::HotSpot::Marker) {
1551                 //TODO - Do not use a hardcoded color for this
1552                 const bool isCurrentResultLine = (_screenWindow->currentResultLine() == (spot->startLine() + _screenWindow->currentLine()));
1553                 QColor color = isCurrentResultLine ? QColor(255, 255, 0, 120) : QColor(255, 0, 0, 120);
1554                 painter.fillRect(r, color);
1555             }
1556         }
1557     }
1558 }
1559 void TerminalDisplay::drawContents(QPainter& paint, const QRect& rect)
1560 {
1561     const QPoint tL  = contentsRect().topLeft();
1562     const int    tLx = tL.x();
1563     const int    tLy = tL.y();
1564 
1565     const int lux = qMin(_usedColumns - 1, qMax(0, (rect.left()   - tLx - _contentRect.left()) / _fontWidth));
1566     const int luy = qMin(_usedLines - 1,  qMax(0, (rect.top()    - tLy - _contentRect.top()) / _fontHeight));
1567     const int rlx = qMin(_usedColumns - 1, qMax(0, (rect.right()  - tLx - _contentRect.left()) / _fontWidth));
1568     const int rly = qMin(_usedLines - 1,  qMax(0, (rect.bottom() - tLy - _contentRect.top()) / _fontHeight));
1569 
1570     const int numberOfColumns = _usedColumns;
1571     QString unistr;
1572     unistr.reserve(numberOfColumns);
1573     for (int y = luy; y <= rly; y++) {
1574         int x = lux;
1575         if ((_image[loc(lux, y)].character == 0u) && (x != 0)) {
1576             x--; // Search for start of multi-column character
1577         }
1578         for (; x <= rlx; x++) {
1579             int len = 1;
1580             int p = 0;
1581 
1582             // reset our buffer to the number of columns
1583             int bufferSize = numberOfColumns;
1584             unistr.resize(bufferSize);
1585             QChar *disstrU = unistr.data();
1586 
1587             // is this a single character or a sequence of characters ?
1588             if ((_image[loc(x, y)].rendition & RE_EXTENDED_CHAR) != 0) {
1589                 // sequence of characters
1590                 ushort extendedCharLength = 0;
1591                 const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x, y)].character, extendedCharLength);
1592                 if (chars != nullptr) {
1593                     Q_ASSERT(extendedCharLength > 1);
1594                     bufferSize += extendedCharLength - 1;
1595                     unistr.resize(bufferSize);
1596                     disstrU = unistr.data();
1597                     for (int index = 0 ; index < extendedCharLength ; index++) {
1598                         Q_ASSERT(p < bufferSize);
1599                         disstrU[p++] = chars[index];
1600                     }
1601                 }
1602             } else {
1603                 // single character
1604                 const quint16 c = _image[loc(x, y)].character;
1605                 if (c != 0u) {
1606                     Q_ASSERT(p < bufferSize);
1607                     disstrU[p++] = c; //fontMap(c);
1608                 }
1609             }
1610 
1611             const bool lineDraw = _image[loc(x, y)].isLineChar();
1612             const bool doubleWidth = (_image[ qMin(loc(x, y) + 1, _imageSize) ].character == 0);
1613             const CharacterColor currentForeground = _image[loc(x, y)].foregroundColor;
1614             const CharacterColor currentBackground = _image[loc(x, y)].backgroundColor;
1615             const RenditionFlags currentRendition = _image[loc(x, y)].rendition;
1616 
1617             while (x + len <= rlx &&
1618                     _image[loc(x + len, y)].foregroundColor == currentForeground &&
1619                     _image[loc(x + len, y)].backgroundColor == currentBackground &&
1620                     (_image[loc(x + len, y)].rendition & ~RE_EXTENDED_CHAR) == (currentRendition & ~RE_EXTENDED_CHAR) &&
1621                     (_image[ qMin(loc(x + len, y) + 1, _imageSize) ].character == 0) == doubleWidth &&
1622                     _image[loc(x + len, y)].isLineChar() == lineDraw) {
1623                 const quint16 c = _image[loc(x + len, y)].character;
1624                 if ((_image[loc(x + len, y)].rendition & RE_EXTENDED_CHAR) != 0) {
1625                     // sequence of characters
1626                     ushort extendedCharLength = 0;
1627                     const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
1628                     if (chars != nullptr) {
1629                         Q_ASSERT(extendedCharLength > 1);
1630                         bufferSize += extendedCharLength - 1;
1631                         unistr.resize(bufferSize);
1632                         disstrU = unistr.data();
1633                         for (int index = 0 ; index < extendedCharLength ; index++) {
1634                             Q_ASSERT(p < bufferSize);
1635                             disstrU[p++] = chars[index];
1636                         }
1637                     }
1638                 } else {
1639                     // single character
1640                     if (c != 0u) {
1641                         Q_ASSERT(p < bufferSize);
1642                         disstrU[p++] = c; //fontMap(c);
1643                     }
1644                 }
1645 
1646                 if (doubleWidth) { // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1647                     len++; // Skip trailing part of multi-column character
1648                 }
1649                 len++;
1650             }
1651             if ((x + len < _usedColumns) && (_image[loc(x + len, y)].character == 0u)) {
1652                 len++; // Adjust for trailing part of multi-column character
1653             }
1654 
1655             const bool save__fixedFont = _fixedFont;
1656             if (lineDraw) {
1657                 _fixedFont = false;
1658             }
1659             if (doubleWidth) {
1660                 _fixedFont = false;
1661             }
1662             unistr.resize(p);
1663 
1664             // Create a text scaling matrix for double width and double height lines.
1665             QMatrix textScale;
1666 
1667             if (y < _lineProperties.size()) {
1668                 if ((_lineProperties[y] & LINE_DOUBLEWIDTH) != 0) {
1669                     textScale.scale(2, 1);
1670                 }
1671 
1672                 if ((_lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) {
1673                     textScale.scale(1, 2);
1674                 }
1675             }
1676 
1677             //Apply text scaling matrix.
1678             paint.setWorldMatrix(textScale, true);
1679 
1680             //calculate the area in which the text will be drawn
1681             QRect textArea = QRect(_contentRect.left() + tLx + _fontWidth * x , _contentRect.top() + tLy + _fontHeight * y , _fontWidth * len , _fontHeight);
1682 
1683             //move the calculated area to take account of scaling applied to the painter.
1684             //the position of the area from the origin (0,0) is scaled
1685             //by the opposite of whatever
1686             //transformation has been applied to the painter.  this ensures that
1687             //painting does actually start from textArea.topLeft()
1688             //(instead of textArea.topLeft() * painter-scale)
1689             textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft()));
1690 
1691             //paint text fragment
1692             if (_printerFriendly) {
1693                 drawPrinterFriendlyTextFragment(paint,
1694                                                 textArea,
1695                                                 unistr,
1696                                                 &_image[loc(x, y)]);
1697             } else {
1698                 drawTextFragment(paint,
1699                                  textArea,
1700                                  unistr,
1701                                  &_image[loc(x, y)]);
1702             }
1703 
1704             _fixedFont = save__fixedFont;
1705 
1706             //reset back to single-width, single-height _lines
1707             paint.setWorldMatrix(textScale.inverted(), true);
1708 
1709             if (y < _lineProperties.size() - 1) {
1710                 //double-height _lines are represented by two adjacent _lines
1711                 //containing the same characters
1712                 //both _lines will have the LINE_DOUBLEHEIGHT attribute.
1713                 //If the current line has the LINE_DOUBLEHEIGHT attribute,
1714                 //we can therefore skip the next line
1715                 if ((_lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) {
1716                     y++;
1717                 }
1718             }
1719 
1720             x += len - 1;
1721         }
1722     }
1723 }
1724 
1725 void TerminalDisplay::drawCurrentResultRect(QPainter& painter)
1726 {
1727     if(_screenWindow->currentResultLine() == -1) {
1728         return;
1729     }
1730 
1731     QRect r(0, _contentRect.top() + (_screenWindow->currentResultLine() - _screenWindow->currentLine()) * _fontHeight,
1732             contentsRect().width(), _fontHeight);
1733     painter.fillRect(r, QColor(0, 0, 255, 80));
1734 }
1735 
1736 QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
1737 {
1738     QRect result;
1739     result.setLeft(_contentRect.left() + _fontWidth * imageArea.left());
1740     result.setTop(_contentRect.top() + _fontHeight * imageArea.top());
1741     result.setWidth(_fontWidth * imageArea.width());
1742     result.setHeight(_fontHeight * imageArea.height());
1743 
1744     return result;
1745 }
1746 
1747 /* ------------------------------------------------------------------------- */
1748 /*                                                                           */
1749 /*                          Blinking Text & Cursor                           */
1750 /*                                                                           */
1751 /* ------------------------------------------------------------------------- */
1752 
1753 void TerminalDisplay::setBlinkingCursorEnabled(bool blink)
1754 {
1755     _allowBlinkingCursor = blink;
1756 
1757     if (blink && !_blinkCursorTimer->isActive()) {
1758         _blinkCursorTimer->start();
1759     }
1760 
1761     if (!blink && _blinkCursorTimer->isActive()) {
1762         _blinkCursorTimer->stop();
1763         if (_cursorBlinking) {
1764             // if cursor is blinking(hidden), blink it again to make it show
1765             blinkCursorEvent();
1766         }
1767         Q_ASSERT(!_cursorBlinking);
1768     }
1769 }
1770 
1771 void TerminalDisplay::setBlinkingTextEnabled(bool blink)
1772 {
1773     _allowBlinkingText = blink;
1774 
1775     if (blink && !_blinkTextTimer->isActive()) {
1776         _blinkTextTimer->start();
1777     }
1778 
1779     if (!blink && _blinkTextTimer->isActive()) {
1780         _blinkTextTimer->stop();
1781         _textBlinking = false;
1782     }
1783 }
1784 
1785 void TerminalDisplay::focusOutEvent(QFocusEvent*)
1786 {
1787     // trigger a repaint of the cursor so that it is both:
1788     //
1789     //   * visible (in case it was hidden during blinking)
1790     //   * drawn in a focused out state
1791     _cursorBlinking = false;
1792     updateCursor();
1793 
1794     // suppress further cursor blinking
1795     _blinkCursorTimer->stop();
1796     Q_ASSERT(!_cursorBlinking);
1797 
1798     // if text is blinking (hidden), blink it again to make it shown
1799     if (_textBlinking) {
1800         blinkTextEvent();
1801     }
1802 
1803     // suppress further text blinking
1804     _blinkTextTimer->stop();
1805     Q_ASSERT(!_textBlinking);
1806 
1807     _showUrlHint = false;
1808 
1809     emit focusLost();
1810 }
1811 
1812 void TerminalDisplay::focusInEvent(QFocusEvent*)
1813 {
1814     if (_allowBlinkingCursor) {
1815         _blinkCursorTimer->start();
1816     }
1817 
1818     updateCursor();
1819 
1820     if (_allowBlinkingText && _hasTextBlinker) {
1821         _blinkTextTimer->start();
1822     }
1823 
1824     emit focusGained();
1825 }
1826 
1827 void TerminalDisplay::blinkTextEvent()
1828 {
1829     Q_ASSERT(_allowBlinkingText);
1830 
1831     _textBlinking = !_textBlinking;
1832 
1833     // TODO: Optimize to only repaint the areas of the widget where there is
1834     // blinking text rather than repainting the whole widget.
1835     update();
1836 }
1837 
1838 void TerminalDisplay::blinkCursorEvent()
1839 {
1840     Q_ASSERT(_allowBlinkingCursor);
1841 
1842     _cursorBlinking = !_cursorBlinking;
1843     updateCursor();
1844 }
1845 
1846 void TerminalDisplay::updateCursor()
1847 {
1848     int cursorLocation = loc(cursorPosition().x(), cursorPosition().y());
1849     int charWidth = konsole_wcwidth(_image[cursorLocation].character);
1850     QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(charWidth, 1)));
1851     update(cursorRect);
1852 }
1853 
1854 /* ------------------------------------------------------------------------- */
1855 /*                                                                           */
1856 /*                          Geometry & Resizing                              */
1857 /*                                                                           */
1858 /* ------------------------------------------------------------------------- */
1859 
1860 void TerminalDisplay::resizeEvent(QResizeEvent*)
1861 {
1862     if (contentsRect().isValid()) {
1863         updateImageSize();
1864     }
1865 }
1866 
1867 void TerminalDisplay::propagateSize()
1868 {
1869     if (_image != nullptr) {
1870         updateImageSize();
1871     }
1872 }
1873 
1874 void TerminalDisplay::updateImageSize()
1875 {
1876     Character* oldImage = _image;
1877     const int oldLines = _lines;
1878     const int oldColumns = _columns;
1879 
1880     makeImage();
1881 
1882     if (oldImage != nullptr) {
1883         // copy the old image to reduce flicker
1884         int lines = qMin(oldLines, _lines);
1885         int columns = qMin(oldColumns, _columns);
1886         for (int line = 0; line < lines; line++) {
1887             memcpy((void*)&_image[_columns * line],
1888                    (void*)&oldImage[oldColumns * line],
1889                    columns * sizeof(Character));
1890         }
1891         delete[] oldImage;
1892     }
1893 
1894     if (_screenWindow != nullptr) {
1895         _screenWindow->setWindowLines(_lines);
1896     }
1897 
1898     _resizing = (oldLines != _lines) || (oldColumns != _columns);
1899 
1900     if (_resizing) {
1901         showResizeNotification();
1902         emit changedContentSizeSignal(_contentRect.height(), _contentRect.width()); // expose resizeEvent
1903     }
1904 
1905     _resizing = false;
1906 }
1907 
1908 void TerminalDisplay::makeImage()
1909 {
1910     _wallpaper->load();
1911 
1912     calcGeometry();
1913 
1914     // confirm that array will be of non-zero size, since the painting code
1915     // assumes a non-zero array length
1916     Q_ASSERT(_lines > 0 && _columns > 0);
1917     Q_ASSERT(_usedLines <= _lines && _usedColumns <= _columns);
1918 
1919     _imageSize = _lines * _columns;
1920 
1921     // We over-commit one character so that we can be more relaxed in dealing with
1922     // certain boundary conditions: _image[_imageSize] is a valid but unused position
1923     _image = new Character[_imageSize + 1];
1924 
1925     clearImage();
1926 }
1927 
1928 void TerminalDisplay::clearImage()
1929 {
1930     for (int i = 0; i <= _imageSize; ++i) {
1931         _image[i] = Screen::DefaultChar;
1932     }
1933 }
1934 
1935 void TerminalDisplay::calcGeometry()
1936 {
1937     _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
1938     _contentRect = contentsRect().adjusted(_margin, _margin, -_margin, -_margin);
1939 
1940     switch (_scrollbarLocation) {
1941     case Enum::ScrollBarHidden :
1942         break;
1943     case Enum::ScrollBarLeft :
1944         _contentRect.setLeft(_contentRect.left() + _scrollBar->width());
1945         _scrollBar->move(contentsRect().topLeft());
1946         break;
1947     case Enum::ScrollBarRight:
1948         _contentRect.setRight(_contentRect.right() - _scrollBar->width());
1949         _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width() - 1, 0));
1950         break;
1951     }
1952 
1953     // ensure that display is always at least one column wide
1954     _columns = qMax(1, _contentRect.width() / _fontWidth);
1955     _usedColumns = qMin(_usedColumns, _columns);
1956 
1957     // ensure that display is always at least one line high
1958     _lines = qMax(1, _contentRect.height() / _fontHeight);
1959     _usedLines = qMin(_usedLines, _lines);
1960 
1961     if(_centerContents) {
1962         QSize unusedPixels = _contentRect.size() - QSize(_columns * _fontWidth, _lines * _fontHeight);
1963         _contentRect.adjust(unusedPixels.width() / 2, unusedPixels.height() / 2, 0, 0);
1964     }
1965 }
1966 
1967 // calculate the needed size, this must be synced with calcGeometry()
1968 void TerminalDisplay::setSize(int columns, int lines)
1969 {
1970     const int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width();
1971     const int horizontalMargin = _margin * 2;
1972     const int verticalMargin = _margin * 2;
1973 
1974     QSize newSize = QSize(horizontalMargin + scrollBarWidth + (columns * _fontWidth)  ,
1975                           verticalMargin + (lines * _fontHeight));
1976 
1977     if (newSize != size()) {
1978         _size = newSize;
1979         updateGeometry();
1980     }
1981 }
1982 
1983 QSize TerminalDisplay::sizeHint() const
1984 {
1985     return _size;
1986 }
1987 
1988 //showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1989 //display has been resized when the display is hidden or shown.
1990 //
1991 //TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1992 //the same signal as the one for a content size change
1993 void TerminalDisplay::showEvent(QShowEvent*)
1994 {
1995     emit changedContentSizeSignal(_contentRect.height(), _contentRect.width());
1996 }
1997 void TerminalDisplay::hideEvent(QHideEvent*)
1998 {
1999     emit changedContentSizeSignal(_contentRect.height(), _contentRect.width());
2000 }
2001 
2002 void TerminalDisplay::setMargin(int margin)
2003 {
2004     if (margin < 0) {
2005         margin = 0;
2006     }
2007     _margin = margin;
2008     updateImageSize();
2009 }
2010 
2011 void TerminalDisplay::setCenterContents(bool enable)
2012 {
2013     _centerContents = enable;
2014     calcGeometry();
2015     update();
2016 }
2017 
2018 /* ------------------------------------------------------------------------- */
2019 /*                                                                           */
2020 /*                                Scrollbar                                  */
2021 /*                                                                           */
2022 /* ------------------------------------------------------------------------- */
2023 
2024 void TerminalDisplay::setScrollBarPosition(Enum::ScrollBarPositionEnum position)
2025 {
2026     if (_scrollbarLocation == position) {
2027         return;
2028     }
2029 
2030     if (position == Enum::ScrollBarHidden) {
2031         _scrollBar->hide();
2032     } else {
2033         _scrollBar->show();
2034     }
2035 
2036     _scrollbarLocation = position;
2037 
2038     propagateSize();
2039     update();
2040 }
2041 
2042 void TerminalDisplay::scrollBarPositionChanged(int)
2043 {
2044     if (_screenWindow == nullptr) {
2045         return;
2046     }
2047 
2048     _screenWindow->scrollTo(_scrollBar->value());
2049 
2050     // if the thumb has been moved to the bottom of the _scrollBar then set
2051     // the display to automatically track new output,
2052     // that is, scroll down automatically
2053     // to how new _lines as they are added
2054     const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
2055     _screenWindow->setTrackOutput(atEndOfOutput);
2056 
2057     updateImage();
2058 }
2059 
2060 void TerminalDisplay::setScroll(int cursor, int slines)
2061 {
2062     // update _scrollBar if the range or value has changed,
2063     // otherwise return
2064     //
2065     // setting the range or value of a _scrollBar will always trigger
2066     // a repaint, so it should be avoided if it is not necessary
2067     if (_scrollBar->minimum() == 0                 &&
2068             _scrollBar->maximum() == (slines - _lines) &&
2069             _scrollBar->value()   == cursor) {
2070         return;
2071     }
2072 
2073     disconnect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
2074     _scrollBar->setRange(0, slines - _lines);
2075     _scrollBar->setSingleStep(1);
2076     _scrollBar->setPageStep(_lines);
2077     _scrollBar->setValue(cursor);
2078     connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
2079 }
2080 
2081 void TerminalDisplay::setScrollFullPage(bool fullPage)
2082 {
2083     _scrollFullPage = fullPage;
2084 }
2085 
2086 bool TerminalDisplay::scrollFullPage() const
2087 {
2088     return _scrollFullPage;
2089 }
2090 
2091 /* ------------------------------------------------------------------------- */
2092 /*                                                                           */
2093 /*                                  Mouse                                    */
2094 /*                                                                           */
2095 /* ------------------------------------------------------------------------- */
2096 void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
2097 {
2098     if (_possibleTripleClick && (ev->button() == Qt::LeftButton)) {
2099         mouseTripleClickEvent(ev);
2100         return;
2101     }
2102 
2103     if (!contentsRect().contains(ev->pos())) {
2104         return;
2105     }
2106 
2107     if (_screenWindow == nullptr) {
2108         return;
2109     }
2110 
2111     // Ignore clicks on the message widget
2112     if (_readOnlyMessageWidget != nullptr) {
2113         if (_readOnlyMessageWidget->isVisible() && _readOnlyMessageWidget->frameGeometry().contains(ev->pos())) {
2114             return;
2115         }
2116     }
2117 
2118     if (_outputSuspendedMessageWidget != nullptr) {
2119         if (_outputSuspendedMessageWidget->isVisible() && _outputSuspendedMessageWidget->frameGeometry().contains(ev->pos())) {
2120             return;
2121         }
2122     }
2123 
2124     int charLine;
2125     int charColumn;
2126     getCharacterPosition(ev->pos(), charLine, charColumn);
2127     QPoint pos = QPoint(charColumn, charLine);
2128 
2129     if (ev->button() == Qt::LeftButton) {
2130         // request the software keyboard, if any
2131         if (qApp->autoSipEnabled()) {
2132             QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(
2133                         style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
2134             if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) {
2135                 QEvent event(QEvent::RequestSoftwareInputPanel);
2136                 QApplication::sendEvent(this, &event);
2137             }
2138         }
2139 
2140         _lineSelectionMode = false;
2141         _wordSelectionMode = false;
2142 
2143         // The user clicked inside selected text
2144         bool selected =  _screenWindow->isSelected(pos.x(), pos.y());
2145 
2146         // Drag only when the Control key is held
2147         if ((!_ctrlRequiredForDrag || ((ev->modifiers() & Qt::ControlModifier) != 0u)) && selected) {
2148             _dragInfo.state = diPending;
2149             _dragInfo.start = ev->pos();
2150         } else {
2151             // No reason to ever start a drag event
2152             _dragInfo.state = diNone;
2153 
2154             _preserveLineBreaks = !(((ev->modifiers() & Qt::ControlModifier) != 0u) && !(ev->modifiers() & Qt::AltModifier));
2155             _columnSelectionMode = ((ev->modifiers() & Qt::AltModifier) != 0u) && ((ev->modifiers() & Qt::ControlModifier) != 0u);
2156 
2157             if (_mouseMarks || (ev->modifiers() == Qt::ShiftModifier)) {
2158                 // Only extend selection for programs not interested in mouse
2159                 if (_mouseMarks && (ev->modifiers() == Qt::ShiftModifier)) {
2160                     extendSelection(ev->pos());
2161                 } else {
2162                     _screenWindow->clearSelection();
2163 
2164                     pos.ry() += _scrollBar->value();
2165                     _iPntSel = _pntSel = pos;
2166                     _actSel = 1; // left mouse button pressed but nothing selected yet.
2167                 }
2168             } else {
2169                 if(!_readOnly) {
2170                     emit mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
2171                 }
2172             }
2173 
2174             if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u))) {
2175                 Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn);
2176                 if ((spot != nullptr) && spot->type() == Filter::HotSpot::Link) {
2177                     QObject action;
2178                     action.setObjectName(QStringLiteral("open-action"));
2179                     spot->activate(&action);
2180                 }
2181             }
2182         }
2183     } else if (ev->button() == Qt::MidButton) {
2184         processMidButtonClick(ev);
2185     } else if (ev->button() == Qt::RightButton) {
2186         if (_mouseMarks || ((ev->modifiers() & Qt::ShiftModifier) != 0u)) {
2187             emit configureRequest(ev->pos());
2188         } else {
2189             if(!_readOnly) {
2190                 emit mouseSignal(2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
2191             }
2192         }
2193     }
2194 }
2195 
2196 QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
2197 {
2198     int charLine, charColumn;
2199     getCharacterPosition(position, charLine, charColumn);
2200 
2201     Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn);
2202 
2203     return spot != nullptr ? spot->actions() : QList<QAction*>();
2204 }
2205 
2206 void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
2207 {
2208     int charLine = 0;
2209     int charColumn = 0;
2210     getCharacterPosition(ev->pos(), charLine, charColumn);
2211 
2212     processFilters();
2213     // handle filters
2214     // change link hot-spot appearance on mouse-over
2215     Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn);
2216     if ((spot != nullptr) && spot->type() == Filter::HotSpot::Link) {
2217         QRegion previousHotspotArea = _mouseOverHotspotArea;
2218         _mouseOverHotspotArea = QRegion();
2219         QRect r;
2220         if (spot->startLine() == spot->endLine()) {
2221             r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(),
2222                         spot->startLine()*_fontHeight + _contentRect.top(),
2223                         (spot->endColumn())*_fontWidth + _contentRect.left() - 1,
2224                         (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1);
2225             _mouseOverHotspotArea |= r;
2226         } else {
2227             r.setCoords(spot->startColumn()*_fontWidth + _contentRect.left(),
2228                         spot->startLine()*_fontHeight + _contentRect.top(),
2229                         (_columns)*_fontWidth + _contentRect.left() - 1,
2230                         (spot->startLine() + 1)*_fontHeight + _contentRect.top() - 1);
2231             _mouseOverHotspotArea |= r;
2232             for (int line = spot->startLine() + 1 ; line < spot->endLine() ; line++) {
2233                 r.setCoords(0 * _fontWidth + _contentRect.left(),
2234                             line * _fontHeight + _contentRect.top(),
2235                             (_columns)*_fontWidth + _contentRect.left() - 1,
2236                             (line + 1)*_fontHeight + _contentRect.top() - 1);
2237                 _mouseOverHotspotArea |= r;
2238             }
2239             r.setCoords(0 * _fontWidth + _contentRect.left(),
2240                         spot->endLine()*_fontHeight + _contentRect.top(),
2241                         (spot->endColumn())*_fontWidth + _contentRect.left() - 1,
2242                         (spot->endLine() + 1)*_fontHeight + _contentRect.top() - 1);
2243             _mouseOverHotspotArea |= r;
2244         }
2245 
2246         if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u)) && (cursor().shape() != Qt::PointingHandCursor)) {
2247             setCursor(Qt::PointingHandCursor);
2248         }
2249 
2250         update(_mouseOverHotspotArea | previousHotspotArea);
2251     } else if (!_mouseOverHotspotArea.isEmpty()) {
2252         if ((_openLinksByDirectClick || ((ev->modifiers() & Qt::ControlModifier) != 0u)) || (cursor().shape() == Qt::PointingHandCursor)) {
2253             setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor);
2254         }
2255 
2256         update(_mouseOverHotspotArea);
2257         // set hotspot area to an invalid rectangle
2258         _mouseOverHotspotArea = QRegion();
2259     }
2260 
2261     // for auto-hiding the cursor, we need mouseTracking
2262     if (ev->buttons() == Qt::NoButton) {
2263         return;
2264     }
2265 
2266     // if the terminal is interested in mouse movements
2267     // then emit a mouse movement signal, unless the shift
2268     // key is being held down, which overrides this.
2269     if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
2270         int button = 3;
2271         if ((ev->buttons() & Qt::LeftButton) != 0u) {
2272             button = 0;
2273         }
2274         if ((ev->buttons() & Qt::MidButton) != 0u) {
2275             button = 1;
2276         }
2277         if ((ev->buttons() & Qt::RightButton) != 0u) {
2278             button = 2;
2279         }
2280 
2281         emit mouseSignal(button,
2282                          charColumn + 1,
2283                          charLine + 1 + _scrollBar->value() - _scrollBar->maximum(),
2284                          1);
2285 
2286         return;
2287     }
2288 
2289     if (_dragInfo.state == diPending) {
2290         // we had a mouse down, but haven't confirmed a drag yet
2291         // if the mouse has moved sufficiently, we will confirm
2292 
2293         const int distance = QApplication::startDragDistance();
2294         if (ev->x() > _dragInfo.start.x() + distance || ev->x() < _dragInfo.start.x() - distance ||
2295                 ev->y() > _dragInfo.start.y() + distance || ev->y() < _dragInfo.start.y() - distance) {
2296             // we've left the drag square, we can start a real drag operation now
2297 
2298             _screenWindow->clearSelection();
2299             doDrag();
2300         }
2301         return;
2302     } else if (_dragInfo.state == diDragging) {
2303         // this isn't technically needed because mouseMoveEvent is suppressed during
2304         // Qt drag operations, replaced by dragMoveEvent
2305         return;
2306     }
2307 
2308     if (_actSel == 0) {
2309         return;
2310     }
2311 
2312 // don't extend selection while pasting
2313     if ((ev->buttons() & Qt::MidButton) != 0u) {
2314         return;
2315     }
2316 
2317     extendSelection(ev->pos());
2318 }
2319 
2320 void TerminalDisplay::leaveEvent(QEvent *)
2321 {
2322     // remove underline from an active link when cursor leaves the widget area
2323     if(!_mouseOverHotspotArea.isEmpty()) {
2324         update(_mouseOverHotspotArea);
2325         _mouseOverHotspotArea = QRegion();
2326     }
2327 }
2328 
2329 void TerminalDisplay::extendSelection(const QPoint& position)
2330 {
2331     if (_screenWindow == nullptr) {
2332         return;
2333     }
2334 
2335     //if ( !contentsRect().contains(ev->pos()) ) return;
2336     const QPoint tL  = contentsRect().topLeft();
2337     const int    tLx = tL.x();
2338     const int    tLy = tL.y();
2339     const int    scroll = _scrollBar->value();
2340 
2341     // we're in the process of moving the mouse with the left button pressed
2342     // the mouse cursor will kept caught within the bounds of the text in
2343     // this widget.
2344 
2345     int linesBeyondWidget = 0;
2346 
2347     QRect textBounds(tLx + _contentRect.left(),
2348                      tLy + _contentRect.top(),
2349                      _usedColumns * _fontWidth - 1,
2350                      _usedLines * _fontHeight - 1);
2351 
2352     QPoint pos = position;
2353 
2354     // Adjust position within text area bounds.
2355     const QPoint oldpos = pos;
2356 
2357     pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right()));
2358     pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom()));
2359 
2360     if (oldpos.y() > textBounds.bottom()) {
2361         linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / _fontHeight;
2362         _scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward
2363     }
2364     if (oldpos.y() < textBounds.top()) {
2365         linesBeyondWidget = (textBounds.top() - oldpos.y()) / _fontHeight;
2366         _scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history
2367     }
2368 
2369     int charColumn = 0;
2370     int charLine = 0;
2371     getCharacterPosition(pos, charLine, charColumn);
2372 
2373     QPoint here = QPoint(charColumn, charLine);
2374     QPoint ohere;
2375     QPoint _iPntSelCorr = _iPntSel;
2376     _iPntSelCorr.ry() -= _scrollBar->value();
2377     QPoint _pntSelCorr = _pntSel;
2378     _pntSelCorr.ry() -= _scrollBar->value();
2379     bool swapping = false;
2380 
2381     if (_wordSelectionMode) {
2382         // Extend to word boundaries
2383         int i;
2384         QChar selClass;
2385 
2386         const bool left_not_right = (here.y() < _iPntSelCorr.y() ||
2387                                      (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
2388         const bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() ||
2389                                          (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
2390         swapping = left_not_right != old_left_not_right;
2391 
2392         // Find left (left_not_right ? from here : from start)
2393         QPoint left = left_not_right ? here : _iPntSelCorr;
2394         i = loc(left.x(), left.y());
2395         if (i >= 0 && i <= _imageSize) {
2396             selClass = charClass(_image[i]);
2397             while (((left.x() > 0) || (left.y() > 0 && (_lineProperties[left.y() - 1] & LINE_WRAPPED)))
2398                     && charClass(_image[i - 1]) == selClass) {
2399                 i--;
2400                 if (left.x() > 0) {
2401                     left.rx()--;
2402                 } else {
2403                     left.rx() = _usedColumns - 1;
2404                     left.ry()--;
2405                 }
2406             }
2407         }
2408 
2409         // Find left (left_not_right ? from start : from here)
2410         QPoint right = left_not_right ? _iPntSelCorr : here;
2411         i = loc(right.x(), right.y());
2412         if (i >= 0 && i <= _imageSize) {
2413             selClass = charClass(_image[i]);
2414             while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && (_lineProperties[right.y()] & LINE_WRAPPED)))
2415                     && charClass(_image[i + 1]) == selClass) {
2416                 i++;
2417                 if (right.x() < _usedColumns - 1) {
2418                     right.rx()++;
2419                 } else {
2420                     right.rx() = 0;
2421                     right.ry()++;
2422                 }
2423             }
2424         }
2425 
2426         // Pick which is start (ohere) and which is extension (here)
2427         if (left_not_right) {
2428             here = left;
2429             ohere = right;
2430         } else {
2431             here = right;
2432             ohere = left;
2433         }
2434         ohere.rx()++;
2435     }
2436 
2437     if (_lineSelectionMode) {
2438         // Extend to complete line
2439         const bool above_not_below = (here.y() < _iPntSelCorr.y());
2440         if (above_not_below) {
2441             ohere = findLineEnd(_iPntSelCorr);
2442             here = findLineStart(here);
2443         } else {
2444             ohere = findLineStart(_iPntSelCorr);
2445             here = findLineEnd(here);
2446         }
2447 
2448         swapping = !(_tripleSelBegin == ohere);
2449         _tripleSelBegin = ohere;
2450 
2451         ohere.rx()++;
2452     }
2453 
2454     int offset = 0;
2455     if (!_wordSelectionMode && !_lineSelectionMode) {
2456         QChar selClass;
2457 
2458         const bool left_not_right = (here.y() < _iPntSelCorr.y() ||
2459                                      (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
2460         const bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() ||
2461                                          (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
2462         swapping = left_not_right != old_left_not_right;
2463 
2464         // Find left (left_not_right ? from here : from start)
2465         const QPoint left = left_not_right ? here : _iPntSelCorr;
2466 
2467         // Find left (left_not_right ? from start : from here)
2468         QPoint right = left_not_right ? _iPntSelCorr : here;
2469         if (right.x() > 0 && !_columnSelectionMode) {
2470             int i = loc(right.x(), right.y());
2471             if (i >= 0 && i <= _imageSize) {
2472                 selClass = charClass(_image[i - 1]);
2473                 /* if (selClass == ' ')
2474                  {
2475                    while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
2476                                    !(_lineProperties[right.y()] & LINE_WRAPPED))
2477                    { i++; right.rx()++; }
2478                    if (right.x() < _usedColumns-1)
2479                      right = left_not_right ? _iPntSelCorr : here;
2480                    else
2481                      right.rx()++;  // will be balanced later because of offset=-1;
2482                  }*/
2483             }
2484         }
2485 
2486         // Pick which is start (ohere) and which is extension (here)
2487         if (left_not_right) {
2488             here = left;
2489             ohere = right;
2490             offset = 0;
2491         } else {
2492             here = right;
2493             ohere = left;
2494             offset = -1;
2495         }
2496     }
2497 
2498     if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) {
2499         return; // not moved
2500     }
2501 
2502     if (here == ohere) {
2503         return; // It's not left, it's not right.
2504     }
2505 
2506     if (_actSel < 2 || swapping) {
2507         if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2508             _screenWindow->setSelectionStart(ohere.x() , ohere.y() , true);
2509         } else {
2510             _screenWindow->setSelectionStart(ohere.x() - 1 - offset , ohere.y() , false);
2511         }
2512     }
2513 
2514     _actSel = 2; // within selection
2515     _pntSel = here;
2516     _pntSel.ry() += _scrollBar->value();
2517 
2518     if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2519         _screenWindow->setSelectionEnd(here.x() , here.y());
2520     } else {
2521         _screenWindow->setSelectionEnd(here.x() + offset , here.y());
2522     }
2523 }
2524 
2525 void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
2526 {
2527     if (_screenWindow == nullptr) {
2528         return;
2529     }
2530 
2531     int charLine;
2532     int charColumn;
2533     getCharacterPosition(ev->pos(), charLine, charColumn);
2534 
2535     if (ev->button() == Qt::LeftButton) {
2536         if (_dragInfo.state == diPending) {
2537             // We had a drag event pending but never confirmed.  Kill selection
2538             _screenWindow->clearSelection();
2539         } else {
2540             if (_actSel > 1) {
2541                 copyToX11Selection();
2542             }
2543 
2544             _actSel = 0;
2545 
2546             //FIXME: emits a release event even if the mouse is
2547             //       outside the range. The procedure used in `mouseMoveEvent'
2548             //       applies here, too.
2549 
2550             if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
2551                 emit mouseSignal(0,
2552                                  charColumn + 1,
2553                                  charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 2);
2554             }
2555         }
2556         _dragInfo.state = diNone;
2557     }
2558 
2559     if (!_mouseMarks &&
2560             (ev->button() == Qt::RightButton || ev->button() == Qt::MidButton) &&
2561             !(ev->modifiers() & Qt::ShiftModifier)) {
2562         emit mouseSignal(ev->button() == Qt::MidButton ? 1 : 2,
2563                          charColumn + 1,
2564                          charLine + 1 + _scrollBar->value() - _scrollBar->maximum() ,
2565                          2);
2566     }
2567 }
2568 
2569 void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint, int& line, int& column) const
2570 {
2571     column = (widgetPoint.x() + _fontWidth / 2 - contentsRect().left() - _contentRect.left()) / _fontWidth;
2572     line = (widgetPoint.y() - contentsRect().top() - _contentRect.top()) / _fontHeight;
2573 
2574     if (line < 0) {
2575         line = 0;
2576     }
2577     if (column < 0) {
2578         column = 0;
2579     }
2580 
2581     if (line >= _usedLines) {
2582         line = _usedLines - 1;
2583     }
2584 
2585     // the column value returned can be equal to _usedColumns, which
2586     // is the position just after the last character displayed in a line.
2587     //
2588     // this is required so that the user can select characters in the right-most
2589     // column (or left-most for right-to-left input)
2590     if (column > _usedColumns) {
2591         column = _usedColumns;
2592     }
2593 }
2594 
2595 void TerminalDisplay::updateLineProperties()
2596 {
2597     if (_screenWindow == nullptr) {
2598         return;
2599     }
2600 
2601     _lineProperties = _screenWindow->getLineProperties();
2602 }
2603 
2604 void TerminalDisplay::processMidButtonClick(QMouseEvent* ev)
2605 {
2606     if (_mouseMarks || ((ev->modifiers() & Qt::ShiftModifier) != 0u)) {
2607         const bool appendEnter = (ev->modifiers() & Qt::ControlModifier) != 0u;
2608 
2609         if (_middleClickPasteMode == Enum::PasteFromX11Selection) {
2610             pasteFromX11Selection(appendEnter);
2611         } else if (_middleClickPasteMode == Enum::PasteFromClipboard) {
2612             pasteFromClipboard(appendEnter);
2613         } else {
2614             Q_ASSERT(false);
2615         }
2616     } else {
2617         if(!_readOnly) {
2618             int charLine = 0;
2619             int charColumn = 0;
2620             getCharacterPosition(ev->pos(), charLine, charColumn);
2621 
2622             emit mouseSignal(1, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
2623         }
2624     }
2625 }
2626 
2627 void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
2628 {
2629     // Yes, successive middle click can trigger this event
2630     if (ev->button() == Qt::MidButton) {
2631         processMidButtonClick(ev);
2632         return;
2633     }
2634 
2635     if (ev->button() != Qt::LeftButton) {
2636         return;
2637     }
2638     if (_screenWindow == nullptr) {
2639         return;
2640     }
2641 
2642     int charLine = 0;
2643     int charColumn = 0;
2644 
2645     getCharacterPosition(ev->pos(), charLine, charColumn);
2646 
2647     QPoint pos(charColumn, charLine);
2648 
2649     // pass on double click as two clicks.
2650     if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
2651         if(!_readOnly) {
2652             // Send just _ONE_ click event, since the first click of the double click
2653             // was already sent by the click handler
2654             emit mouseSignal(0, charColumn + 1,
2655                              charLine + 1 + _scrollBar->value() - _scrollBar->maximum(),
2656                              0);  // left button
2657         }
2658         return;
2659     }
2660 
2661     _screenWindow->clearSelection();
2662     QPoint bgnSel = pos;
2663     QPoint endSel = pos;
2664     int i = loc(bgnSel.x(), bgnSel.y());
2665     _iPntSel = bgnSel;
2666     _iPntSel.ry() += _scrollBar->value();
2667 
2668     _wordSelectionMode = true;
2669     _actSel = 2; // within selection
2670 
2671     // find word boundaries...
2672     const QChar selClass = charClass(_image[i]);
2673     {
2674         // find the start of the word
2675         int x = bgnSel.x();
2676         while (((x > 0) || (bgnSel.y() > 0 && (_lineProperties[bgnSel.y() - 1] & LINE_WRAPPED)))
2677                 && charClass(_image[i - 1]) == selClass) {
2678             i--;
2679             if (x > 0) {
2680                 x--;
2681             } else {
2682                 x = _usedColumns - 1;
2683                 bgnSel.ry()--;
2684             }
2685         }
2686 
2687         bgnSel.setX(x);
2688         _screenWindow->setSelectionStart(bgnSel.x() , bgnSel.y() , false);
2689 
2690         // find the end of the word
2691         i = loc(endSel.x(), endSel.y());
2692         x = endSel.x();
2693         while (((x < _usedColumns - 1) || (endSel.y() < _usedLines - 1 && (_lineProperties[endSel.y()] & LINE_WRAPPED)))
2694                 && charClass(_image[i + 1]) == selClass) {
2695             i++;
2696             if (x < _usedColumns - 1) {
2697                 x++;
2698             } else {
2699                 x = 0;
2700                 endSel.ry()++;
2701             }
2702         }
2703 
2704         endSel.setX(x);
2705 
2706         // In word selection mode don't select @ (64) if at end of word.
2707         if (((_image[i].rendition & RE_EXTENDED_CHAR) == 0) &&
2708                 (QChar(_image[i].character) == QLatin1Char('@')) &&
2709                 ((endSel.x() - bgnSel.x()) > 0)) {
2710             endSel.setX(x - 1);
2711         }
2712 
2713         _actSel = 2; // within selection
2714 
2715         _screenWindow->setSelectionEnd(endSel.x() , endSel.y());
2716 
2717         copyToX11Selection();
2718     }
2719 
2720     _possibleTripleClick = true;
2721 
2722     QTimer::singleShot(QApplication::doubleClickInterval(), [this]() {
2723         _possibleTripleClick = false;
2724     });
2725 }
2726 
2727 void TerminalDisplay::wheelEvent(QWheelEvent* ev)
2728 {
2729     // Only vertical scrolling is supported
2730     if (ev->orientation() != Qt::Vertical) {
2731         return;
2732     }
2733 
2734     const int modifiers = ev->modifiers();
2735 
2736     // ctrl+<wheel> for zooming, like in konqueror and firefox
2737     if (((modifiers & Qt::ControlModifier) != 0u) && mouseWheelZoom()) {
2738         _scrollWheelState.addWheelEvent(ev);
2739 
2740         int steps = _scrollWheelState.consumeLegacySteps(ScrollState::DEFAULT_ANGLE_SCROLL_LINE);
2741         for (;steps > 0; --steps) {
2742             // wheel-up for increasing font size
2743             increaseFontSize();
2744         }
2745         for (;steps < 0; ++steps) {
2746             // wheel-down for decreasing font size
2747             decreaseFontSize();
2748         }
2749     } else if (_mouseMarks && (_scrollBar->maximum() > 0)) {
2750         // If the program running in the terminal is not interested in mouse events,
2751         // send the event to the scrollbar if the slider has room to move
2752 
2753         _scrollWheelState.addWheelEvent(ev);
2754 
2755         _scrollBar->event(ev);
2756         _sessionController->setSearchStartToWindowCurrentLine();
2757         _scrollWheelState.clearAll();
2758     } else if (!_readOnly) {
2759         _scrollWheelState.addWheelEvent(ev);
2760 
2761         if(_mouseMarks && !_isPrimaryScreen) {
2762             // Send simulated up / down key presses to the terminal program
2763             // for the benefit of programs such as 'less' (which use the alternate screen)
2764 
2765             // assume that each Up / Down key event will cause the terminal application
2766             // to scroll by one line.
2767             //
2768             // to get a reasonable scrolling speed, scroll by one line for every 5 degrees
2769             // of mouse wheel rotation.  Mouse wheels typically move in steps of 15 degrees,
2770             // giving a scroll of 3 lines
2771 
2772             const int lines = _scrollWheelState.consumeSteps(static_cast<int>(_fontHeight * qApp->devicePixelRatio()), ScrollState::degreesToAngle(5));
2773             const int keyCode = lines > 0 ? Qt::Key_Up : Qt::Key_Down;
2774             QKeyEvent keyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier);
2775 
2776             for (int i = 0; i < abs(lines); i++) {
2777                 _screenWindow->screen()->setCurrentTerminalDisplay(this);
2778                 emit keyPressedSignal(&keyEvent);
2779             }
2780         } else if (!_mouseMarks) {
2781             // terminal program wants notification of mouse activity
2782 
2783             int charLine;
2784             int charColumn;
2785             getCharacterPosition(ev->pos() , charLine , charColumn);
2786             const int steps = _scrollWheelState.consumeLegacySteps(ScrollState::DEFAULT_ANGLE_SCROLL_LINE);
2787             const int button = (steps > 0) ? 4 : 5;
2788             for (int i = 0; i < abs(steps); ++i) {
2789                 emit mouseSignal(button,
2790                                  charColumn + 1,
2791                                  charLine + 1 + _scrollBar->value() - _scrollBar->maximum() ,
2792                                  0);
2793             }
2794         }
2795     }
2796 }
2797 
2798 void TerminalDisplay::viewScrolledByUser()
2799 {
2800     _sessionController->setSearchStartToWindowCurrentLine();
2801 }
2802 
2803 /* Moving left/up from the line containing pnt, return the starting
2804    offset point which the given line is continiously wrapped
2805    (top left corner = 0,0; previous line not visible = 0,-1).
2806 */
2807 QPoint TerminalDisplay::findLineStart(const QPoint &pnt)
2808 {
2809     const int visibleScreenLines = _lineProperties.size();
2810     const int topVisibleLine = _screenWindow->currentLine();
2811     Screen *screen = _screenWindow->screen();
2812     int line = pnt.y();
2813     int lineInHistory= line + topVisibleLine;
2814 
2815     QVector<LineProperty> lineProperties = _lineProperties;
2816 
2817     while (lineInHistory > 0) {
2818         for (; line > 0; line--, lineInHistory--) {
2819             // Does previous line wrap around?
2820             if ((lineProperties[line - 1] & LINE_WRAPPED) == 0) {
2821                 return QPoint(0, lineInHistory - topVisibleLine);
2822             }
2823         }
2824 
2825         if (lineInHistory < 1) {
2826             break;
2827         }
2828 
2829         // _lineProperties is only for the visible screen, so grab new data
2830         int newRegionStart = qMax(0, lineInHistory - visibleScreenLines);
2831         lineProperties = screen->getLineProperties(newRegionStart, lineInHistory - 1);
2832         line = lineInHistory - newRegionStart;
2833     }
2834     return QPoint(0, lineInHistory - topVisibleLine);
2835 }
2836 
2837 /* Moving right/down from the line containing pnt, return the ending
2838    offset point which the given line is continiously wrapped.
2839 */
2840 QPoint TerminalDisplay::findLineEnd(const QPoint &pnt)
2841 {
2842     const int visibleScreenLines = _lineProperties.size();
2843     const int topVisibleLine = _screenWindow->currentLine();
2844     const int maxY = _screenWindow->lineCount() - 1;
2845     Screen *screen = _screenWindow->screen();
2846     int line = pnt.y();
2847     int lineInHistory= line + topVisibleLine;
2848 
2849     QVector<LineProperty> lineProperties = _lineProperties;
2850 
2851     while (lineInHistory < maxY) {
2852         for (; line < lineProperties.count() && lineInHistory < maxY; line++, lineInHistory++) {
2853             // Does current line wrap around?
2854             if ((lineProperties[line] & LINE_WRAPPED) == 0) {
2855                 return QPoint(_columns - 1, lineInHistory - topVisibleLine);
2856             }
2857         }
2858 
2859         line = 0;
2860         lineProperties = screen->getLineProperties(lineInHistory, qMin(lineInHistory + visibleScreenLines, maxY));
2861     }
2862     return QPoint(_columns - 1, lineInHistory - topVisibleLine);
2863 }
2864 
2865 QPoint TerminalDisplay::findWordStart(const QPoint &pnt)
2866 {
2867     const int regSize = qMax(_screenWindow->windowLines(), 10);
2868     const int curLine = _screenWindow->currentLine();
2869     int i = pnt.y();
2870     int x = pnt.x();
2871     int y = i + curLine;
2872     int j = loc(x, i);
2873     QVector<LineProperty> lineProperties = _lineProperties;
2874     Screen *screen = _screenWindow->screen();
2875     Character *image = _image;
2876     Character *tmp_image = nullptr;
2877     const QChar selClass = charClass(image[j]);
2878     const int imageSize = regSize * _columns;
2879 
2880     while (true) {
2881         for (;;j--, x--) {
2882             if (x > 0) {
2883                 if (charClass(image[j - 1]) == selClass) {
2884                     continue;
2885                 }
2886                 goto out;
2887             } else if (i > 0) {
2888                 if (((lineProperties[i - 1] & LINE_WRAPPED) != 0) &&
2889                     charClass(image[j - 1]) == selClass) {
2890                     x = _columns;
2891                     i--;
2892                     y--;
2893                     continue;
2894                 }
2895                 goto out;
2896             } else if (y > 0) {
2897                 break;
2898             } else {
2899                 goto out;
2900             }
2901         }
2902         int newRegStart = qMax(0, y - regSize);
2903         lineProperties = screen->getLineProperties(newRegStart, y - 1);
2904         i = y - newRegStart;
2905         if (tmp_image == nullptr) {
2906             tmp_image = new Character[imageSize];
2907             image = tmp_image;
2908         }
2909         screen->getImage(tmp_image, imageSize, newRegStart, y - 1);
2910         j = loc(x, i);
2911     }
2912 out:
2913     if (tmp_image != nullptr) {
2914         delete[] tmp_image;
2915     }
2916     return QPoint(x, y - curLine);
2917 }
2918 
2919 QPoint TerminalDisplay::findWordEnd(const QPoint &pnt)
2920 {
2921     const int regSize = qMax(_screenWindow->windowLines(), 10);
2922     const int curLine = _screenWindow->currentLine();
2923     int i = pnt.y();
2924     int x = pnt.x();
2925     int y = i + curLine;
2926     int j = loc(x, i);
2927     QVector<LineProperty> lineProperties = _lineProperties;
2928     Screen *screen = _screenWindow->screen();
2929     Character *image = _image;
2930     Character *tmp_image = nullptr;
2931     const QChar selClass = charClass(image[j]);
2932     const int imageSize = regSize * _columns;
2933     const int maxY = _screenWindow->lineCount() - 1;
2934     const int maxX = _columns - 1;
2935 
2936     while (true) {
2937         const int lineCount = lineProperties.count();
2938         for (;;j++, x++) {
2939             if (x < maxX) {
2940                 if (charClass(image[j + 1]) == selClass) {
2941                     continue;
2942                 }
2943                 goto out;
2944             } else if (i < lineCount - 1) {
2945                 if (((lineProperties[i] & LINE_WRAPPED) != 0) &&
2946                     charClass(image[j + 1]) == selClass) {
2947                     x = -1;
2948                     i++;
2949                     y++;
2950                     continue;
2951                 }
2952                 goto out;
2953             } else if (y < maxY) {
2954                 if (i < lineCount && ((lineProperties[i] & LINE_WRAPPED) == 0)) {
2955                     goto out;
2956                 }
2957                 break;
2958             } else {
2959                 goto out;
2960             }
2961         }
2962         int newRegEnd = qMin(y + regSize - 1, maxY);
2963         lineProperties = screen->getLineProperties(y, newRegEnd);
2964         i = 0;
2965         if (tmp_image == nullptr) {
2966             tmp_image = new Character[imageSize];
2967             image = tmp_image;
2968         }
2969         screen->getImage(tmp_image, imageSize, y, newRegEnd);
2970         x--;
2971         j = loc(x, i);
2972     }
2973 out:
2974     y -= curLine;
2975     // In word selection mode don't select @ (64) if at end of word.
2976     if (((image[j].rendition & RE_EXTENDED_CHAR) == 0) &&
2977         (QChar(image[j].character) == QLatin1Char('@')) &&
2978         (y > pnt.y() || x > pnt.x())) {
2979         if (x > 0) {
2980             x--;
2981         } else {
2982             y--;
2983         }
2984     }
2985     if (tmp_image != nullptr) {
2986         delete[] tmp_image;
2987     }
2988     return QPoint(x, y);
2989 }
2990 
2991 Screen::DecodingOptions TerminalDisplay::currentDecodingOptions()
2992 {
2993     Screen::DecodingOptions decodingOptions;
2994     if (_preserveLineBreaks) {
2995         decodingOptions |= Screen::PreserveLineBreaks;
2996     }
2997     if (_trimLeadingSpaces) {
2998         decodingOptions |= Screen::TrimLeadingWhitespace;
2999     }
3000     if (_trimTrailingSpaces) {
3001         decodingOptions |= Screen::TrimTrailingWhitespace;
3002     }
3003 
3004     return decodingOptions;
3005 }
3006 
3007 void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
3008 {
3009     if (_screenWindow == nullptr) {
3010         return;
3011     }
3012 
3013     int charLine;
3014     int charColumn;
3015     getCharacterPosition(ev->pos(), charLine, charColumn);
3016     selectLine(QPoint(charColumn, charLine),
3017                _tripleClickMode == Enum::SelectWholeLine);
3018 }
3019 
3020 void TerminalDisplay::selectLine(QPoint pos, bool entireLine)
3021 {
3022     _iPntSel = pos;
3023 
3024     _screenWindow->clearSelection();
3025 
3026     _lineSelectionMode = true;
3027     _wordSelectionMode = false;
3028 
3029     _actSel = 2; // within selection
3030 
3031     if (!entireLine) { // Select from cursor to end of line
3032         _tripleSelBegin = findWordStart(_iPntSel);
3033         _screenWindow->setSelectionStart(_tripleSelBegin.x(),
3034                                          _tripleSelBegin.y() , false);
3035     } else {
3036         _tripleSelBegin = findLineStart(_iPntSel);
3037         _screenWindow->setSelectionStart(0 , _tripleSelBegin.y() , false);
3038     }
3039 
3040     _iPntSel = findLineEnd(_iPntSel);
3041     _screenWindow->setSelectionEnd(_iPntSel.x() , _iPntSel.y());
3042 
3043     copyToX11Selection();
3044 
3045     _iPntSel.ry() += _scrollBar->value();
3046 }
3047 
3048 void TerminalDisplay::selectCurrentLine()
3049 {
3050     if (_screenWindow == nullptr) {
3051         return;
3052     }
3053 
3054     selectLine(cursorPosition(), true);
3055 }
3056 
3057 void TerminalDisplay::selectAll()
3058 {
3059     if (_screenWindow.isNull()) {
3060         return;
3061     }
3062 
3063     _preserveLineBreaks = true;
3064     _screenWindow->setSelectionByLineRange(0, _screenWindow->lineCount());
3065     copyToX11Selection();
3066 }
3067 
3068 bool TerminalDisplay::focusNextPrevChild(bool next)
3069 {
3070     // for 'Tab', always disable focus switching among widgets
3071     // for 'Shift+Tab', leave the decision to higher level
3072     if (next) {
3073         return false;
3074     } else {
3075         return QWidget::focusNextPrevChild(next);
3076     }
3077 }
3078 
3079 QChar TerminalDisplay::charClass(const Character& ch) const
3080 {
3081     if ((ch.rendition & RE_EXTENDED_CHAR) != 0) {
3082         ushort extendedCharLength = 0;
3083         const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
3084         if ((chars != nullptr) && extendedCharLength > 0) {
3085             const QString s = QString::fromUtf16(chars, extendedCharLength);
3086             if (_wordCharacters.contains(s, Qt::CaseInsensitive)) {
3087                 return QLatin1Char('a');
3088             }
3089             bool letterOrNumber = false;
3090             for (int i = 0; !letterOrNumber && i < s.size(); ++i) {
3091                 letterOrNumber = s.at(i).isLetterOrNumber();
3092             }
3093             return letterOrNumber ? QLatin1Char('a') : s.at(0);
3094         }
3095         return 0;
3096     } else {
3097         const QChar qch(ch.character);
3098         if (qch.isSpace()) {
3099             return QLatin1Char(' ');
3100         }
3101 
3102         if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive)) {
3103             return QLatin1Char('a');
3104         }
3105 
3106         return qch;
3107     }
3108 }
3109 
3110 void TerminalDisplay::setWordCharacters(const QString& wc)
3111 {
3112     _wordCharacters = wc;
3113 }
3114 
3115 // FIXME: the actual value of _mouseMarks is the opposite of its semantic.
3116 // When using programs not interested with mouse(shell, less), it is true.
3117 // When using programs interested with mouse(vim,mc), it is false.
3118 void TerminalDisplay::setUsesMouse(bool on)
3119 {
3120     _mouseMarks = on;
3121     setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor);
3122 }
3123 bool TerminalDisplay::usesMouse() const
3124 {
3125     return _mouseMarks;
3126 }
3127 
3128 
3129 void TerminalDisplay::usingPrimaryScreen(bool use)
3130 {
3131     _isPrimaryScreen = use;
3132 }
3133 
3134 void TerminalDisplay::setBracketedPasteMode(bool on)
3135 {
3136     _bracketedPasteMode = on;
3137 }
3138 bool TerminalDisplay::bracketedPasteMode() const
3139 {
3140     return _bracketedPasteMode;
3141 }
3142 
3143 /* ------------------------------------------------------------------------- */
3144 /*                                                                           */
3145 /*                               Clipboard                                   */
3146 /*                                                                           */
3147 /* ------------------------------------------------------------------------- */
3148 
3149 void TerminalDisplay::doPaste(QString text, bool appendReturn)
3150 {
3151     if (_screenWindow == nullptr) {
3152         return;
3153     }
3154 
3155     if (_readOnly) {
3156         return;
3157     }
3158 
3159     if (appendReturn) {
3160         text.append(QLatin1String("\r"));
3161     }
3162 
3163     if (text.length() > 8000) {
3164         if (KMessageBox::warningContinueCancel(window(),
3165                         i18np("Are you sure you want to paste %1 character?",
3166                               "Are you sure you want to paste %1 characters?",
3167                               text.length()),
3168                         i18n("Confirm Paste"),
3169                         KStandardGuiItem::cont(),
3170                         KStandardGuiItem::cancel(),
3171                         QStringLiteral("ShowPasteHugeTextWarning")) == KMessageBox::Cancel) {
3172             return;
3173         }
3174     }
3175 
3176     if (!text.isEmpty()) {
3177         text.replace(QLatin1Char('\n'), QLatin1Char('\r'));
3178         if (bracketedPasteMode()) {
3179             text.remove(QLatin1String("\033"));
3180             text.prepend(QLatin1String("\033[200~"));
3181             text.append(QLatin1String("\033[201~"));
3182         }
3183         // perform paste by simulating keypress events
3184         QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
3185         emit keyPressedSignal(&e);
3186     }
3187 }
3188 
3189 void TerminalDisplay::setAutoCopySelectedText(bool enabled)
3190 {
3191     _autoCopySelectedText = enabled;
3192 }
3193 
3194 void TerminalDisplay::setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum mode)
3195 {
3196     _middleClickPasteMode = mode;
3197 }
3198 
3199 void TerminalDisplay::setCopyTextAsHTML(bool enabled)
3200 {
3201     _copyTextAsHTML = enabled;
3202 }
3203 
3204 void TerminalDisplay::copyToX11Selection()
3205 {
3206     if (_screenWindow == nullptr) {
3207         return;
3208     }
3209 
3210 
3211     const QString &text = _screenWindow->selectedText(currentDecodingOptions());
3212     if (text.isEmpty()) {
3213         return;
3214     }
3215 
3216     auto mimeData = new QMimeData;
3217     mimeData->setText(text);
3218 
3219     if (_copyTextAsHTML) {
3220         mimeData->setHtml(_screenWindow->selectedText(currentDecodingOptions() | Screen::ConvertToHtml));
3221     }
3222 
3223     if (QApplication::clipboard()->supportsSelection()) {
3224         QApplication::clipboard()->setMimeData(mimeData, QClipboard::Selection);
3225     }
3226 
3227     if (_autoCopySelectedText) {
3228         QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
3229     }
3230 }
3231 
3232 void TerminalDisplay::copyToClipboard()
3233 {
3234     if (_screenWindow == nullptr) {
3235         return;
3236     }
3237 
3238     const QString &text = _screenWindow->selectedText(currentDecodingOptions());
3239     if (text.isEmpty()) {
3240         return;
3241     }
3242 
3243     auto mimeData = new QMimeData;
3244     mimeData->setText(text);
3245 
3246     if (_copyTextAsHTML) {
3247         mimeData->setHtml(_screenWindow->selectedText(currentDecodingOptions() | Screen::ConvertToHtml));
3248     }
3249 
3250     QApplication::clipboard()->setMimeData(mimeData, QClipboard::Clipboard);
3251 }
3252 
3253 void TerminalDisplay::pasteFromClipboard(bool appendEnter)
3254 {
3255     QString text = QApplication::clipboard()->text(QClipboard::Clipboard);
3256     doPaste(text, appendEnter);
3257 }
3258 
3259 void TerminalDisplay::pasteFromX11Selection(bool appendEnter)
3260 {
3261     if (QApplication::clipboard()->supportsSelection()) {
3262         QString text = QApplication::clipboard()->text(QClipboard::Selection);
3263         doPaste(text, appendEnter);
3264     }
3265 }
3266 
3267 /* ------------------------------------------------------------------------- */
3268 /*                                                                           */
3269 /*                                Input Method                               */
3270 /*                                                                           */
3271 /* ------------------------------------------------------------------------- */
3272 
3273 void TerminalDisplay::inputMethodEvent(QInputMethodEvent* event)
3274 {
3275     if (!event->commitString().isEmpty()) {
3276         QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString());
3277         emit keyPressedSignal(&keyEvent);
3278     }
3279 
3280     if (!_readOnly) {
3281         _inputMethodData.preeditString = event->preeditString();
3282         update(preeditRect() | _inputMethodData.previousPreeditRect);
3283     }
3284     event->accept();
3285 }
3286 
3287 QVariant TerminalDisplay::inputMethodQuery(Qt::InputMethodQuery query) const
3288 {
3289     const QPoint cursorPos = cursorPosition();
3290     switch (query) {
3291     case Qt::ImMicroFocus:
3292         return imageToWidget(QRect(cursorPos.x(), cursorPos.y(), 1, 1));
3293     case Qt::ImFont:
3294         return font();
3295     case Qt::ImCursorPosition:
3296         // return the cursor position within the current line
3297         return cursorPos.x();
3298     case Qt::ImSurroundingText: {
3299         // return the text from the current line
3300         QString lineText;
3301         QTextStream stream(&lineText);
3302         PlainTextDecoder decoder;
3303         decoder.begin(&stream);
3304         decoder.decodeLine(&_image[loc(0, cursorPos.y())], _usedColumns, LINE_DEFAULT);
3305         decoder.end();
3306         return lineText;
3307     }
3308     case Qt::ImCurrentSelection:
3309         return QString();
3310     default:
3311         break;
3312     }
3313 
3314     return QVariant();
3315 }
3316 
3317 QRect TerminalDisplay::preeditRect() const
3318 {
3319     const int preeditLength = string_width(_inputMethodData.preeditString);
3320 
3321     if (preeditLength == 0) {
3322         return QRect();
3323     }
3324 
3325     return QRect(_contentRect.left() + _fontWidth * cursorPosition().x(),
3326                  _contentRect.top() + _fontHeight * cursorPosition().y(),
3327                  _fontWidth * preeditLength,
3328                  _fontHeight);
3329 }
3330 
3331 void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
3332 {
3333     if (_inputMethodData.preeditString.isEmpty()) {
3334         return;
3335     }
3336 
3337     const QPoint cursorPos = cursorPosition();
3338 
3339     bool invertColors = false;
3340     const QColor background = _colorTable[DEFAULT_BACK_COLOR];
3341     const QColor foreground = _colorTable[DEFAULT_FORE_COLOR];
3342     const Character* style = &_image[loc(cursorPos.x(), cursorPos.y())];
3343 
3344     drawBackground(painter, rect, background, true);
3345     drawCursor(painter, rect, foreground, background, invertColors);
3346     drawCharacters(painter, rect, _inputMethodData.preeditString, style, invertColors);
3347 
3348     _inputMethodData.previousPreeditRect = rect;
3349 }
3350 
3351 /* ------------------------------------------------------------------------- */
3352 /*                                                                           */
3353 /*                                Keyboard                                   */
3354 /*                                                                           */
3355 /* ------------------------------------------------------------------------- */
3356 
3357 void TerminalDisplay::setFlowControlWarningEnabled(bool enable)
3358 {
3359     _flowControlWarningEnabled = enable;
3360 
3361     // if the dialog is currently visible and the flow control warning has
3362     // been disabled then hide the dialog
3363     if (!enable) {
3364         outputSuspended(false);
3365     }
3366 }
3367 
3368 void TerminalDisplay::outputSuspended(bool suspended)
3369 {
3370     //create the label when this function is first called
3371     if (_outputSuspendedMessageWidget == nullptr) {
3372         //This label includes a link to an English language website
3373         //describing the 'flow control' (Xon/Xoff) feature found in almost
3374         //all terminal emulators.
3375         //If there isn't a suitable article available in the target language the link
3376         //can simply be removed.
3377         _outputSuspendedMessageWidget = createMessageWidget(i18n("<qt>Output has been "
3378                                                     "<a href=\"http://en.wikipedia.org/wiki/Software_flow_control\">suspended</a>"
3379                                                     " by pressing Ctrl+S."
3380                                                     " Press <b>Ctrl+Q</b> to resume.</qt>"));
3381 
3382         connect(_outputSuspendedMessageWidget, &KMessageWidget::linkActivated, this, [](const QString &url) {
3383             QDesktopServices::openUrl(QUrl(url));
3384         });
3385 
3386         _outputSuspendedMessageWidget->setMessageType(KMessageWidget::Warning);
3387     }
3388 
3389     suspended ? _outputSuspendedMessageWidget->animatedShow() : _outputSuspendedMessageWidget->animatedHide();
3390 }
3391 
3392 void TerminalDisplay::dismissOutputSuspendedMessage()
3393 {
3394     outputSuspended(false);
3395 }
3396 
3397 KMessageWidget* TerminalDisplay::createMessageWidget(const QString &text) {
3398     auto widget = new KMessageWidget(text);
3399     widget->setWordWrap(true);
3400     widget->setFocusProxy(this);
3401     widget->setCursor(Qt::ArrowCursor);
3402 
3403     _verticalLayout->insertWidget(0, widget);
3404     return widget;
3405 }
3406 
3407 void TerminalDisplay::updateReadOnlyState(bool readonly) {
3408     if (_readOnly == readonly) {
3409         return;
3410     }
3411 
3412     if (readonly) {
3413         // Lazy create the readonly messagewidget
3414         if (_readOnlyMessageWidget == nullptr) {
3415             _readOnlyMessageWidget = createMessageWidget(i18n("This terminal is read-only."));
3416             _readOnlyMessageWidget->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
3417         }
3418     }
3419 
3420     if (_readOnlyMessageWidget != nullptr) {
3421         readonly ? _readOnlyMessageWidget->animatedShow() : _readOnlyMessageWidget->animatedHide();
3422     }
3423 
3424     _readOnly = readonly;
3425 }
3426 
3427 void TerminalDisplay::scrollScreenWindow(enum ScreenWindow::RelativeScrollMode mode, int amount)
3428 {
3429     _screenWindow->scrollBy(mode, amount, _scrollFullPage);
3430     _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
3431     updateLineProperties();
3432     updateImage();
3433     viewScrolledByUser();
3434 }
3435 
3436 void TerminalDisplay::keyPressEvent(QKeyEvent* event)
3437 {
3438     if ((_urlHintsModifiers != 0u) && event->modifiers() == _urlHintsModifiers) {
3439         int hintSelected = event->key() - 0x31;
3440         if (hintSelected >= 0 && hintSelected < 10 && hintSelected < _filterChain->hotSpots().count()) {
3441             _filterChain->hotSpots().at(hintSelected)->activate();
3442             _showUrlHint = false;
3443             update();
3444             return;
3445         }
3446 
3447         if (!_showUrlHint) {
3448             processFilters();
3449             _showUrlHint = true;
3450             update();
3451         }
3452     }
3453 
3454     _screenWindow->screen()->setCurrentTerminalDisplay(this);
3455 
3456     if (!_readOnly) {
3457         _actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't
3458                      // know where the current selection is.
3459 
3460         if (_allowBlinkingCursor) {
3461             _blinkCursorTimer->start();
3462             if (_cursorBlinking) {
3463                 // if cursor is blinking(hidden), blink it again to show it
3464                 blinkCursorEvent();
3465             }
3466             Q_ASSERT(!_cursorBlinking);
3467         }
3468     }
3469 
3470     emit keyPressedSignal(event);
3471 
3472 #ifndef QT_NO_ACCESSIBILITY
3473     if (!_readOnly) {
3474         QAccessibleTextCursorEvent textCursorEvent(this, _usedColumns * screenWindow()->screen()->getCursorY() + screenWindow()->screen()->getCursorX());
3475         QAccessible::updateAccessibility(&textCursorEvent);
3476     }
3477 #endif
3478 
3479     event->accept();
3480 }
3481 
3482 void TerminalDisplay::keyReleaseEvent(QKeyEvent *event)
3483 {
3484     if (_showUrlHint) {
3485         _showUrlHint = false;
3486         update();
3487     }
3488 
3489     if (_readOnly) {
3490         event->accept();
3491         return;
3492     }
3493 
3494     QWidget::keyReleaseEvent(event);
3495 }
3496 
3497 bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent)
3498 {
3499     const int modifiers = keyEvent->modifiers();
3500 
3501     //  When a possible shortcut combination is pressed,
3502     //  emit the overrideShortcutCheck() signal to allow the host
3503     //  to decide whether the terminal should override it or not.
3504     if (modifiers != Qt::NoModifier) {
3505         int modifierCount = 0;
3506         unsigned int currentModifier = Qt::ShiftModifier;
3507 
3508         while (currentModifier <= Qt::KeypadModifier) {
3509             if ((modifiers & currentModifier) != 0u) {
3510                 modifierCount++;
3511             }
3512             currentModifier <<= 1;
3513         }
3514         if (modifierCount < 2) {
3515             bool override = false;
3516             emit overrideShortcutCheck(keyEvent, override);
3517             if (override) {
3518                 keyEvent->accept();
3519                 return true;
3520             }
3521         }
3522     }
3523 
3524     // Override any of the following shortcuts because
3525     // they are needed by the terminal
3526     int keyCode = keyEvent->key() | modifiers;
3527     switch (keyCode) {
3528         // list is taken from the QLineEdit::event() code
3529     case Qt::Key_Tab:
3530     case Qt::Key_Delete:
3531     case Qt::Key_Home:
3532     case Qt::Key_End:
3533     case Qt::Key_Backspace:
3534     case Qt::Key_Left:
3535     case Qt::Key_Right:
3536     case Qt::Key_Slash:
3537     case Qt::Key_Period:
3538     case Qt::Key_Space:
3539         keyEvent->accept();
3540         return true;
3541     }
3542     return false;
3543 }
3544 
3545 bool TerminalDisplay::event(QEvent* event)
3546 {
3547     bool eventHandled = false;
3548     switch (event->type()) {
3549     case QEvent::ShortcutOverride:
3550         eventHandled = handleShortcutOverrideEvent(static_cast<QKeyEvent*>(event));
3551         break;
3552     case QEvent::PaletteChange:
3553     case QEvent::ApplicationPaletteChange:
3554         _scrollBar->setPalette(QApplication::palette());
3555         break;
3556     default:
3557         break;
3558     }
3559     return eventHandled ? true : QWidget::event(event);
3560 }
3561 
3562 void TerminalDisplay::contextMenuEvent(QContextMenuEvent* event)
3563 {
3564     // the logic for the mouse case is within MousePressEvent()
3565     if (event->reason() != QContextMenuEvent::Mouse) {
3566         emit configureRequest(mapFromGlobal(QCursor::pos()));
3567     }
3568 }
3569 
3570 /* --------------------------------------------------------------------- */
3571 /*                                                                       */
3572 /*                                  Bell                                 */
3573 /*                                                                       */
3574 /* --------------------------------------------------------------------- */
3575 
3576 void TerminalDisplay::setBellMode(int mode)
3577 {
3578     _bellMode = mode;
3579 }
3580 
3581 int TerminalDisplay::bellMode() const
3582 {
3583     return _bellMode;
3584 }
3585 
3586 void TerminalDisplay::bell(const QString& message)
3587 {
3588     if (_bellMasked) {
3589         return;
3590     }
3591 
3592     switch (_bellMode) {
3593     case Enum::SystemBeepBell:
3594         KNotification::beep();
3595         break;
3596     case Enum::NotifyBell:
3597         // STABLE API:
3598         //     Please note that these event names, "BellVisible" and "BellInvisible",
3599         //     should not change and should be kept stable, because other applications
3600         //     that use this code via KPart rely on these names for notifications.
3601         KNotification::event(hasFocus() ? QStringLiteral("BellVisible") : QStringLiteral("BellInvisible"),
3602                              message, QPixmap(), this);
3603         break;
3604     case Enum::VisualBell:
3605         visualBell();
3606         break;
3607     default:
3608         break;
3609     }
3610 
3611     // limit the rate at which bells can occur.
3612     // ...mainly for sound effects where rapid bells in sequence
3613     // produce a horrible noise.
3614     _bellMasked = true;
3615     QTimer::singleShot(500, [this]() {
3616         _bellMasked = false;
3617     });
3618 }
3619 
3620 void TerminalDisplay::visualBell()
3621 {
3622     swapFGBGColors();
3623     QTimer::singleShot(200, this, &Konsole::TerminalDisplay::swapFGBGColors);
3624 }
3625 
3626 void TerminalDisplay::swapFGBGColors()
3627 {
3628     // swap the default foreground & background color
3629     ColorEntry color = _colorTable[DEFAULT_BACK_COLOR];
3630     _colorTable[DEFAULT_BACK_COLOR] = _colorTable[DEFAULT_FORE_COLOR];
3631     _colorTable[DEFAULT_FORE_COLOR] = color;
3632 
3633     update();
3634 }
3635 
3636 /* --------------------------------------------------------------------- */
3637 /*                                                                       */
3638 /* Drag & Drop                                                           */
3639 /*                                                                       */
3640 /* --------------------------------------------------------------------- */
3641 
3642 void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
3643 {
3644     // text/plain alone is enough for KDE-apps
3645     // text/uri-list is for supporting some non-KDE apps, such as thunar
3646     //   and pcmanfm
3647     // That also applies in dropEvent()
3648     const auto mimeData = event->mimeData();
3649     if ((!_readOnly) && (mimeData != nullptr)
3650             && (mimeData->hasFormat(QStringLiteral("text/plain"))
3651                 || mimeData->hasFormat(QStringLiteral("text/uri-list")))) {
3652         event->acceptProposedAction();
3653     }
3654 }
3655 
3656 void TerminalDisplay::dropEvent(QDropEvent* event)
3657 {
3658     if (_readOnly) {
3659         event->accept();
3660         return;
3661     }
3662 
3663     const auto mimeData = event->mimeData();
3664     if (mimeData == nullptr) {
3665         return;
3666     }
3667     auto urls = mimeData->urls();
3668 
3669     QString dropText;
3670     if (!urls.isEmpty()) {
3671         for (int i = 0 ; i < urls.count() ; i++) {
3672             KIO::StatJob* job = KIO::mostLocalUrl(urls[i], KIO::HideProgressInfo);
3673             bool ok = job->exec();
3674             if (!ok) {
3675                 continue;
3676             }
3677 
3678             QUrl url = job->mostLocalUrl();
3679             QString urlText;
3680 
3681             if (url.isLocalFile()) {
3682                 urlText = url.path();
3683             } else {
3684                 urlText = url.url();
3685             }
3686 
3687             // in future it may be useful to be able to insert file names with drag-and-drop
3688             // without quoting them (this only affects paths with spaces in)
3689             urlText = KShell::quoteArg(urlText);
3690 
3691             dropText += urlText;
3692 
3693             // Each filename(including the last) should be followed by one space.
3694             dropText += QLatin1Char(' ');
3695         }
3696 
3697         // If our target is local we will open a popup - otherwise the fallback kicks
3698         // in and the URLs will simply be pasted as text.
3699         if (!_dropUrlsAsText && (_sessionController != nullptr) && _sessionController->url().isLocalFile()) {
3700             // A standard popup with Copy, Move and Link as options -
3701             // plus an additional Paste option.
3702 
3703             QAction* pasteAction = new QAction(i18n("&Paste Location"), this);
3704             pasteAction->setData(dropText);
3705             connect(pasteAction, &QAction::triggered, this, &TerminalDisplay::dropMenuPasteActionTriggered);
3706 
3707             QList<QAction*> additionalActions;
3708             additionalActions.append(pasteAction);
3709 
3710             if (urls.count() == 1) {
3711                 KIO::StatJob* job = KIO::mostLocalUrl(urls[0], KIO::HideProgressInfo);
3712                 bool ok = job->exec();
3713                 if (ok) {
3714                     const QUrl url = job->mostLocalUrl();
3715 
3716                     if (url.isLocalFile()) {
3717                         const QFileInfo fileInfo(url.path());
3718 
3719                         if (fileInfo.isDir()) {
3720                             QAction* cdAction = new QAction(i18n("Change &Directory To"), this);
3721                             dropText = QLatin1String(" cd ") + dropText + QLatin1Char('\n');
3722                             cdAction->setData(dropText);
3723                             connect(cdAction, &QAction::triggered, this, &TerminalDisplay::dropMenuCdActionTriggered);
3724                             additionalActions.append(cdAction);
3725                         }
3726                     }
3727                 }
3728             }
3729 
3730             QUrl target = QUrl::fromLocalFile(_sessionController->currentDir());
3731 
3732             KIO::DropJob* job = KIO::drop(event, target);
3733             KJobWidgets::setWindow(job, this);
3734             job->setApplicationActions(additionalActions);
3735             return;
3736         }
3737 
3738     } else {
3739         dropText = mimeData->text();
3740     }
3741 
3742     if (mimeData->hasFormat(QStringLiteral("text/plain")) ||
3743             mimeData->hasFormat(QStringLiteral("text/uri-list"))) {
3744         emit sendStringToEmu(dropText.toLocal8Bit());
3745     }
3746 }
3747 
3748 void TerminalDisplay::dropMenuPasteActionTriggered()
3749 {
3750     if (sender() != nullptr) {
3751         const QAction* action = qobject_cast<const QAction*>(sender());
3752         if (action != nullptr) {
3753             emit sendStringToEmu(action->data().toString().toLocal8Bit());
3754         }
3755     }
3756 }
3757 
3758 void TerminalDisplay::dropMenuCdActionTriggered()
3759 {
3760     if (sender() != nullptr) {
3761         const QAction* action = qobject_cast<const QAction*>(sender());
3762         if (action != nullptr) {
3763             emit sendStringToEmu(action->data().toString().toLocal8Bit());
3764         }
3765     }
3766 }
3767 
3768 void TerminalDisplay::doDrag()
3769 {
3770     _dragInfo.state = diDragging;
3771     _dragInfo.dragObject = new QDrag(this);
3772     auto mimeData = new QMimeData();
3773     mimeData->setText(QApplication::clipboard()->mimeData(QClipboard::Selection)->text());
3774     mimeData->setHtml(QApplication::clipboard()->mimeData(QClipboard::Selection)->html());
3775     _dragInfo.dragObject->setMimeData(mimeData);
3776     _dragInfo.dragObject->exec(Qt::CopyAction);
3777 }
3778 
3779 void TerminalDisplay::setSessionController(SessionController* controller)
3780 {
3781     _sessionController = controller;
3782 }
3783 
3784 SessionController* TerminalDisplay::sessionController()
3785 {
3786     return _sessionController;
3787 }
3788 
3789 AutoScrollHandler::AutoScrollHandler(QWidget* parent)
3790     : QObject(parent)
3791     , _timerId(0)
3792 {
3793     parent->installEventFilter(this);
3794 }
3795 void AutoScrollHandler::timerEvent(QTimerEvent* event)
3796 {
3797     if (event->timerId() != _timerId) {
3798         return;
3799     }
3800 
3801     QMouseEvent mouseEvent(QEvent::MouseMove,
3802                            widget()->mapFromGlobal(QCursor::pos()),
3803                            Qt::NoButton,
3804                            Qt::LeftButton,
3805                            Qt::NoModifier);
3806 
3807     QApplication::sendEvent(widget(), &mouseEvent);
3808 }
3809 bool AutoScrollHandler::eventFilter(QObject* watched, QEvent* event)
3810 {
3811     Q_ASSERT(watched == parent());
3812     Q_UNUSED(watched);
3813 
3814     switch (event->type()) {
3815     case QEvent::MouseMove: {
3816         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
3817         bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
3818         if (mouseInWidget) {
3819             if (_timerId != 0) {
3820                 killTimer(_timerId);
3821             }
3822 
3823             _timerId = 0;
3824         } else {
3825             if ((_timerId == 0) && ((mouseEvent->buttons() & Qt::LeftButton) != 0u)) {
3826                 _timerId = startTimer(100);
3827             }
3828         }
3829 
3830         break;
3831     }
3832     case QEvent::MouseButtonRelease: {
3833         QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
3834         if ((_timerId != 0) && ((mouseEvent->buttons() & ~Qt::LeftButton) != 0u)) {
3835             killTimer(_timerId);
3836             _timerId = 0;
3837         }
3838         break;
3839     }
3840     default:
3841         break;
3842     };
3843 
3844     return false;
3845 }
3846