File indexing completed on 2018-12-12 02:02:46

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