File indexing completed on 2018-06-17 11:14:20

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