File indexing completed on 2018-12-13 10:55:41

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