Warning, file /kde/applications/konsole/src/TerminalDisplay.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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