File indexing completed on 2018-01-16 14:38:46

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