File indexing completed on 2018-09-19 03:41:48

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