File indexing completed on 2018-01-16 12:10:23

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