File indexing completed on 2024-05-19 05:28:21

0001 /*
0002     This file is part of Konsole, a terminal emulator for KDE.
0003 
0004     SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
0005     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 
0009     This program is distributed in the hope that it will be useful,
0010     but WITHOUT ANY WARRANTY; without even the implied warranty of
0011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012     GNU General Public License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program; if not, write to the Free Software
0016     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
0017     02110-1301  USA.
0018 */
0019 
0020 // Own
0021 #include "TerminalDisplay.h"
0022 
0023 // C++
0024 #include <cmath>
0025 
0026 // Qt
0027 #include <QAbstractButton>
0028 #include <QApplication>
0029 #include <QClipboard>
0030 #include <QDrag>
0031 #include <QEvent>
0032 #include <QFile>
0033 #include <QKeyEvent>
0034 #include <QLabel>
0035 #include <QLayout>
0036 #include <QMimeData>
0037 #include <QPainter>
0038 #include <QPixmap>
0039 #include <QRegularExpression>
0040 #include <QScrollBar>
0041 #include <QStyle>
0042 #include <QTime>
0043 #include <QTimer>
0044 #include <QUrl>
0045 #include <QtDebug>
0046 
0047 // Konsole
0048 #include "Filter.h"
0049 #include "ScreenWindow.h"
0050 #include "TerminalCharacterDecoder.h"
0051 #include "konsole_wcwidth.h"
0052 
0053 // std
0054 #include <ranges>
0055 
0056 inline void initResource()
0057 {
0058     Q_INIT_RESOURCE(terminal);
0059 }
0060 
0061 using namespace Konsole;
0062 
0063 constexpr auto REPCHAR =
0064     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
0065     "abcdefgjijklmnopqrstuvwxyz"
0066     "0123456789./+@";
0067 
0068 // scroll increment used when dragging selection at top/bottom of window.
0069 
0070 // static
0071 bool TerminalDisplay::_antialiasText = true;
0072 
0073 // we use this to force QPainter to display text in LTR mode
0074 // more information can be found in: http://unicode.org/reports/tr9/
0075 const QChar LTR_OVERRIDE_CHAR(0x202D);
0076 
0077 /* ------------------------------------------------------------------------- */
0078 /*                                                                           */
0079 /*                                Colors                                     */
0080 /*                                                                           */
0081 /* ------------------------------------------------------------------------- */
0082 
0083 /* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
0084 
0085    Code        0       1       2       3       4       5       6       7
0086    ----------- ------- ------- ------- ------- ------- ------- ------- -------
0087    ANSI  (bgr) Black   Red     Green   Yellow  Blue    Magenta Cyan    White
0088    IBMPC (rgb) Black   Blue    Green   Cyan    Red     Magenta Yellow  White
0089 */
0090 
0091 ScreenWindow *TerminalDisplay::screenWindow() const
0092 {
0093     return _screenWindow;
0094 }
0095 void TerminalDisplay::setScreenWindow(ScreenWindow *window)
0096 {
0097     // disconnect existing screen window if any
0098     if (_screenWindow) {
0099         disconnect(_screenWindow, nullptr, this, nullptr);
0100     }
0101 
0102     _screenWindow = window;
0103 
0104     if (window) {
0105         connect(_screenWindow, &ScreenWindow::outputChanged, this, &TerminalDisplay::updateLineProperties);
0106         connect(_screenWindow, &ScreenWindow::outputChanged, this, &TerminalDisplay::updateImage);
0107         connect(_screenWindow, &ScreenWindow::scrollToEnd, this, &TerminalDisplay::scrollToEnd);
0108         window->setWindowLines(_lines);
0109     }
0110 }
0111 
0112 std::span<const ColorEntry> TerminalDisplay::colorTable() const
0113 {
0114     return _colorTable;
0115 }
0116 
0117 void TerminalDisplay::setBackgroundColor(const QColor &color)
0118 {
0119     _colorTable[DEFAULT_BACK_COLOR].color = color;
0120     QPalette p = palette();
0121     p.setColor(backgroundRole(), color);
0122     setPalette(p);
0123 
0124     // Avoid propagating the palette change to the scroll bar
0125     _scrollBar->setPalette(QApplication::palette());
0126 
0127     update();
0128 }
0129 
0130 void TerminalDisplay::setForegroundColor(const QColor &color)
0131 {
0132     _colorTable[DEFAULT_FORE_COLOR].color = color;
0133 
0134     update();
0135 }
0136 
0137 void TerminalDisplay::setColorTable(std::array<ColorEntry, TABLE_COLORS> &&table)
0138 {
0139     _colorTable = std::move(table);
0140     setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
0141 }
0142 
0143 /* ------------------------------------------------------------------------- */
0144 /*                                                                           */
0145 /*                                   Font                                    */
0146 /*                                                                           */
0147 /* ------------------------------------------------------------------------- */
0148 
0149 /*
0150    The VT100 has 32 special graphical characters. The usual vt100 extended
0151    xterm fonts have these at 0x00..0x1f.
0152 
0153    QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
0154    come in here as proper unicode characters.
0155 
0156    We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
0157    from unicode to 0x00..0x1f. The remaining translation is then left to the
0158    QCodec.
0159 */
0160 
0161 constexpr bool TerminalDisplay::isLineChar(QChar c) const
0162 {
0163     return _drawLineChars && ((c.unicode() & 0xFF80) == 0x2500);
0164 }
0165 
0166 constexpr bool TerminalDisplay::isLineCharString(QStringView string) const
0167 {
0168     return (string.size() > 0) && (isLineChar(string[0]));
0169 }
0170 
0171 void TerminalDisplay::fontChange(const QFont &)
0172 {
0173     QFontMetricsF fm(font());
0174     _fontHeight = fm.height() + _lineSpacing;
0175 
0176     // waba TerminalDisplay 1.123:
0177     // "Base character width on widest ASCII character. This prevents too wide
0178     //  characters in the presence of double wide (e.g. Japanese) characters."
0179     // Get the width from representative normal width characters
0180     _fontWidth = fm.horizontalAdvance(QLatin1String(REPCHAR)) / (qreal)qstrlen(REPCHAR);
0181 
0182     _fixedFont = true;
0183 
0184     int fw = fm.horizontalAdvance(QLatin1Char(REPCHAR[0]));
0185     for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
0186         if (fw != fm.horizontalAdvance(QLatin1Char(REPCHAR[i]))) {
0187             _fixedFont = false;
0188             break;
0189         }
0190     }
0191 
0192     if (_fontWidth < 1)
0193         _fontWidth = 1;
0194 
0195     _fontAscent = fm.ascent();
0196 
0197     Q_EMIT changedFontMetricSignal(_fontHeight, _fontWidth);
0198     propagateSize();
0199     update();
0200 }
0201 
0202 // void TerminalDisplay::calDrawTextAdditionHeight(QPainter& painter)
0203 //{
0204 //     QRect test_rect, feedback_rect;
0205 //  test_rect.setRect(1, 1, qRound(_fontWidth) * 4, _fontHeight);
0206 //     painter.drawText(test_rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + QLatin1String("Mq"), &feedback_rect);
0207 
0208 //  //qDebug() << "test_rect:" << test_rect << "feeback_rect:" << feedback_rect;
0209 
0210 //  _drawTextAdditionHeight = (feedback_rect.height() - _fontHeight) / 2;
0211 //  if(_drawTextAdditionHeight < 0) {
0212 //    _drawTextAdditionHeight = 0;
0213 //  }
0214 
0215 //  _drawTextTestFlag = false;
0216 //}
0217 
0218 void TerminalDisplay::setVTFont(const QFont &f)
0219 {
0220     QFont font = f;
0221 
0222     if (!QFontInfo(font).fixedPitch()) {
0223         qDebug() << "Using a variable-width font in the terminal.  This may cause performance degradation and display/alignment errors.";
0224     }
0225 
0226     // hint that text should be drawn without anti-aliasing.
0227     // depending on the user's font configuration, this may not be respected
0228     if (!_antialiasText)
0229         font.setStyleStrategy(QFont::NoAntialias);
0230 
0231     // experimental optimization.  Konsole assumes that the terminal is using a
0232     // mono-spaced font, in which case kerning information should have an effect.
0233     // Disabling kerning saves some computation when rendering text.
0234     font.setKerning(false);
0235 
0236     m_font = font;
0237     fontChange(font);
0238     Q_EMIT vtFontChanged();
0239 }
0240 
0241 void TerminalDisplay::setBoldIntense(bool value)
0242 {
0243     if (_boldIntense != value) {
0244         _boldIntense = value;
0245         Q_EMIT boldIntenseChanged();
0246     }
0247 }
0248 
0249 /* ------------------------------------------------------------------------- */
0250 /*                                                                           */
0251 /*                         Constructor / Destructor                          */
0252 /*                                                                           */
0253 /* ------------------------------------------------------------------------- */
0254 #include <QDir>
0255 
0256 TerminalDisplay::TerminalDisplay(QQuickItem *parent)
0257     : QQuickPaintedItem(parent)
0258     , _screenWindow(nullptr)
0259     , _allowBell(true)
0260     , _gridLayout(nullptr)
0261     , _fontHeight(1)
0262     , _fontWidth(1)
0263     , _fontAscent(1)
0264     , _boldIntense(true)
0265     , _lines(1)
0266     , _columns(1)
0267     , _usedLines(1)
0268     , _usedColumns(1)
0269     , _contentHeight(1)
0270     , _contentWidth(1)
0271     , _randomSeed(0)
0272     , _resizing(false)
0273     , _terminalSizeHint(false)
0274     , _terminalSizeStartup(true)
0275     , _bidiEnabled(false)
0276     , _mouseMarks(false)
0277     , _disabledBracketedPasteMode(false)
0278     , _actSel(0)
0279     , _wordSelectionMode(false)
0280     , _lineSelectionMode(false)
0281     , _preserveLineBreaks(false)
0282     , _columnSelectionMode(false)
0283     , _scrollbarLocation(QTermWidget::NoScrollBar)
0284     , _wordCharacters(QLatin1String(":@-./_~"))
0285     , _bellMode(SystemBeepBell)
0286     , _blinking(false)
0287     , _hasBlinker(false)
0288     , _cursorBlinking(false)
0289     , _hasBlinkingCursor(false)
0290     , _allowBlinkingText(true)
0291     , _ctrlDrag(false)
0292     , _tripleClickMode(SelectWholeLine)
0293     , _isFixedSize(false)
0294     , _possibleTripleClick(false)
0295     , _resizeWidget(nullptr)
0296     , _resizeTimer(nullptr)
0297     , _flowControlWarningEnabled(false)
0298     , _outputSuspendedLabel(nullptr)
0299     , _lineSpacing(0)
0300     , _colorsInverted(false)
0301     , _opacity(static_cast<qreal>(1))
0302     , _filterChain(std::make_unique<TerminalImageFilterChain>())
0303     , _cursorShape(Emulation::KeyboardCursorShape::BlockCursor)
0304     , mMotionAfterPasting(NoMoveScreenWindow)
0305     , _leftBaseMargin(1)
0306     , _topBaseMargin(1)
0307     , m_font(QStringLiteral("Monospace"), 12)
0308     , m_color_role(QPalette::Window)
0309     , m_full_cursor_height(false)
0310     , _drawLineChars(true)
0311 {
0312     initResource();
0313 
0314     // terminal applications are not designed with Right-To-Left in mind,
0315     // so the layout is forced to Left-To-Right
0316     // setLayoutDirection(Qt::LeftToRight);
0317 
0318     // The offsets are not yet calculated.
0319     // Do not calculate these too often to be more smoothly when resizing
0320     // konsole in opaque mode.
0321     _topMargin = _topBaseMargin;
0322     _leftMargin = _leftBaseMargin;
0323 
0324     m_palette = qApp->palette();
0325 
0326     setVTFont(m_font);
0327 
0328     // create scroll bar for scrolling output up and down
0329     // set the scroll bar's slider to occupy the whole area of the scroll bar initially
0330 
0331     _scrollBar = new QScrollBar();
0332     setScroll(0, 0);
0333 
0334     _scrollBar->setCursor(Qt::ArrowCursor);
0335     connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
0336     // qtermwidget: we have to hide it here due the _scrollbarLocation==NoScrollBar
0337     // check in TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
0338     _scrollBar->hide();
0339 
0340     // setup timers for blinking cursor and text
0341     _blinkTimer = new QTimer(this);
0342     connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
0343     _blinkCursorTimer = new QTimer(this);
0344     connect(_blinkCursorTimer, &QTimer::timeout, this, &TerminalDisplay::blinkCursorEvent);
0345 
0346     //  KCursor::setAutoHideCursor( this, true );
0347 
0348     setUsesMouse(true);
0349     setBracketedPasteMode(false);
0350     setColorTable(defaultColorTable());
0351     // setMouseTracking(true);
0352 
0353     setAcceptedMouseButtons(Qt::LeftButton);
0354 
0355     setFlags(ItemHasContents | ItemAcceptsInputMethod);
0356 
0357     // Setup scrollbar. Be sure it is not darw on screen.
0358     _scrollBar->setAttribute(Qt::WA_DontShowOnScreen);
0359     _scrollBar->setVisible(false);
0360     connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollbarParamsChanged);
0361 
0362     // TODO Forcing rendering to Framebuffer. We need to determine if this is ok
0363     // always or if we need to make this customizable.
0364     setRenderTarget(QQuickPaintedItem::FramebufferObject);
0365 
0366     //  setFocusPolicy( Qt::WheelFocus );
0367 
0368     // enable input method support
0369     //  setAttribute(Qt::WA_InputMethodEnabled, true);
0370 
0371     // this is an important optimization, it tells Qt
0372     // that TerminalDisplay will handle repainting its entire area.
0373     //  setAttribute(Qt::WA_OpaquePaintEvent);
0374 
0375     //  _gridLayout = new QGridLayout(this);
0376     //  _gridLayout->setContentsMargins(0, 0, 0, 0);
0377 
0378     //  new AutoScrollHandler(this);
0379 }
0380 
0381 TerminalDisplay::~TerminalDisplay()
0382 {
0383     disconnect(_blinkTimer);
0384     disconnect(_blinkCursorTimer);
0385     qApp->removeEventFilter(this);
0386 
0387     delete _gridLayout;
0388     delete _outputSuspendedLabel;
0389     delete _scrollBar;
0390 }
0391 
0392 /* ------------------------------------------------------------------------- */
0393 /*                                                                           */
0394 /*                             Display Operations                            */
0395 /*                                                                           */
0396 /* ------------------------------------------------------------------------- */
0397 
0398 /**
0399  A table for emulating the simple (single width) unicode drawing chars.
0400  It represents the 250x - 257x glyphs. If it's zero, we can't use it.
0401  if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
0402  0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
0403 
0404  Then, the pixels basically have the following interpretation:
0405  _|||_
0406  -...-
0407  -...-
0408  -...-
0409  _|||_
0410 
0411 where _ = none
0412       | = vertical line.
0413       - = horizontal line.
0414  */
0415 
0416 enum LineEncode {
0417     TopL = (1 << 1),
0418     TopC = (1 << 2),
0419     TopR = (1 << 3),
0420 
0421     LeftT = (1 << 5),
0422     Int11 = (1 << 6),
0423     Int12 = (1 << 7),
0424     Int13 = (1 << 8),
0425     RightT = (1 << 9),
0426 
0427     LeftC = (1 << 10),
0428     Int21 = (1 << 11),
0429     Int22 = (1 << 12),
0430     Int23 = (1 << 13),
0431     RightC = (1 << 14),
0432 
0433     LeftB = (1 << 15),
0434     Int31 = (1 << 16),
0435     Int32 = (1 << 17),
0436     Int33 = (1 << 18),
0437     RightB = (1 << 19),
0438 
0439     BotL = (1 << 21),
0440     BotC = (1 << 22),
0441     BotR = (1 << 23)
0442 };
0443 
0444 #include "LineFont.h"
0445 
0446 static void drawLineChar(QPainter &paint, int x, int y, int w, int h, uint8_t code)
0447 {
0448     // Calculate cell midpoints, end points.
0449     int cx = x + w / 2;
0450     int cy = y + h / 2;
0451     int ex = x + w - 1;
0452     int ey = y + h - 1;
0453 
0454     quint32 toDraw = LineChars[code];
0455 
0456     // Top _lines:
0457     if (toDraw & TopL)
0458         paint.drawLine(cx - 1, y, cx - 1, cy - 2);
0459     if (toDraw & TopC)
0460         paint.drawLine(cx, y, cx, cy - 2);
0461     if (toDraw & TopR)
0462         paint.drawLine(cx + 1, y, cx + 1, cy - 2);
0463 
0464     // Bot _lines:
0465     if (toDraw & BotL)
0466         paint.drawLine(cx - 1, cy + 2, cx - 1, ey);
0467     if (toDraw & BotC)
0468         paint.drawLine(cx, cy + 2, cx, ey);
0469     if (toDraw & BotR)
0470         paint.drawLine(cx + 1, cy + 2, cx + 1, ey);
0471 
0472     // Left _lines:
0473     if (toDraw & LeftT)
0474         paint.drawLine(x, cy - 1, cx - 2, cy - 1);
0475     if (toDraw & LeftC)
0476         paint.drawLine(x, cy, cx - 2, cy);
0477     if (toDraw & LeftB)
0478         paint.drawLine(x, cy + 1, cx - 2, cy + 1);
0479 
0480     // Right _lines:
0481     if (toDraw & RightT)
0482         paint.drawLine(cx + 2, cy - 1, ex, cy - 1);
0483     if (toDraw & RightC)
0484         paint.drawLine(cx + 2, cy, ex, cy);
0485     if (toDraw & RightB)
0486         paint.drawLine(cx + 2, cy + 1, ex, cy + 1);
0487 
0488     // Intersection points.
0489     if (toDraw & Int11)
0490         paint.drawPoint(cx - 1, cy - 1);
0491     if (toDraw & Int12)
0492         paint.drawPoint(cx, cy - 1);
0493     if (toDraw & Int13)
0494         paint.drawPoint(cx + 1, cy - 1);
0495 
0496     if (toDraw & Int21)
0497         paint.drawPoint(cx - 1, cy);
0498     if (toDraw & Int22)
0499         paint.drawPoint(cx, cy);
0500     if (toDraw & Int23)
0501         paint.drawPoint(cx + 1, cy);
0502 
0503     if (toDraw & Int31)
0504         paint.drawPoint(cx - 1, cy + 1);
0505     if (toDraw & Int32)
0506         paint.drawPoint(cx, cy + 1);
0507     if (toDraw & Int33)
0508         paint.drawPoint(cx + 1, cy + 1);
0509 }
0510 
0511 static void drawOtherChar(QPainter &paint, int x, int y, int w, int h, uchar code)
0512 {
0513     // Calculate cell midpoints, end points.
0514     const int cx = x + w / 2;
0515     const int cy = y + h / 2;
0516     const int ex = x + w - 1;
0517     const int ey = y + h - 1;
0518 
0519     // Double dashes
0520     if (0x4C <= code && code <= 0x4F) {
0521         const int xHalfGap = qMax(w / 15, 1);
0522         const int yHalfGap = qMax(h / 15, 1);
0523         switch (code) {
0524         case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
0525             paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1);
0526             paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1);
0527             paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1);
0528             paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1);
0529             /* Falls through. */
0530         case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
0531             paint.drawLine(x, cy, cx - xHalfGap - 1, cy);
0532             paint.drawLine(cx + xHalfGap, cy, ex, cy);
0533             break;
0534         case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
0535             paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1);
0536             paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1);
0537             paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey);
0538             paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey);
0539             /* Falls through. */
0540         case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
0541             paint.drawLine(cx, y, cx, cy - yHalfGap - 1);
0542             paint.drawLine(cx, cy + yHalfGap, cx, ey);
0543             break;
0544         }
0545     }
0546 
0547     // Rounded corner characters
0548     else if (0x6D <= code && code <= 0x70) {
0549         const int r = w * 3 / 8;
0550         const int d = 2 * r;
0551         switch (code) {
0552         case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
0553             paint.drawLine(cx, cy + r, cx, ey);
0554             paint.drawLine(cx + r, cy, ex, cy);
0555             paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16);
0556             break;
0557         case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
0558             paint.drawLine(cx, cy + r, cx, ey);
0559             paint.drawLine(x, cy, cx - r, cy);
0560             paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16);
0561             break;
0562         case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT
0563             paint.drawLine(cx, y, cx, cy - r);
0564             paint.drawLine(x, cy, cx - r, cy);
0565             paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16);
0566             break;
0567         case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT
0568             paint.drawLine(cx, y, cx, cy - r);
0569             paint.drawLine(cx + r, cy, ex, cy);
0570             paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16);
0571             break;
0572         }
0573     }
0574 
0575     // Diagonals
0576     else if (0x71 <= code && code <= 0x73) {
0577         switch (code) {
0578         case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
0579             paint.drawLine(ex, y, x, ey);
0580             break;
0581         case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
0582             paint.drawLine(x, y, ex, ey);
0583             break;
0584         case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS
0585             paint.drawLine(ex, y, x, ey);
0586             paint.drawLine(x, y, ex, ey);
0587             break;
0588         }
0589     }
0590 }
0591 
0592 void TerminalDisplay::drawLineCharString(QPainter &painter, int x, int y, QStringView str, const Character *attributes) const
0593 {
0594     const QPen &currentPen = painter.pen();
0595 
0596     if ((attributes->rendition & RE_BOLD) && _boldIntense) {
0597         QPen boldPen(currentPen);
0598         boldPen.setWidth(3);
0599         painter.setPen(boldPen);
0600     }
0601 
0602     for (qsizetype i = 0; i < str.size(); i++) {
0603         uint8_t code = static_cast<uint8_t>(str[i].unicode() & 0xffU);
0604         if (LineChars[code])
0605             drawLineChar(painter, qRound(x + (_fontWidth * i)), y, qRound(_fontWidth), qRound(_fontHeight), code);
0606         else
0607             drawOtherChar(painter, qRound(x + (_fontWidth * i)), y, qRound(_fontWidth), qRound(_fontHeight), code);
0608     }
0609 
0610     painter.setPen(currentPen);
0611 }
0612 
0613 void TerminalDisplay::setKeyboardCursorShape(Emulation::KeyboardCursorShape shape)
0614 {
0615     if (_cursorShape == shape) {
0616         return;
0617     }
0618 
0619     _cursorShape = shape;
0620     updateCursor();
0621 }
0622 
0623 Emulation::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const
0624 {
0625     return _cursorShape;
0626 }
0627 
0628 void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor &color)
0629 {
0630     if (useForegroundColor)
0631         _cursorColor = QColor(); // an invalid color means that
0632                                  // the foreground color of the
0633                                  // current character should
0634                                  // be used
0635 
0636     else
0637         _cursorColor = color;
0638 }
0639 QColor TerminalDisplay::keyboardCursorColor() const
0640 {
0641     return _cursorColor;
0642 }
0643 
0644 void TerminalDisplay::setOpacity(qreal opacity)
0645 {
0646     _opacity = qBound(static_cast<qreal>(0), opacity, static_cast<qreal>(1));
0647     update();
0648 }
0649 
0650 void TerminalDisplay::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting)
0651 {
0652     // The whole widget rectangle is filled by the background color from
0653     // the color scheme set in setColorTable(), while the scrollbar is
0654     // left to the widget style for a consistent look.
0655     if (useOpacitySetting) {
0656         QColor color(backgroundColor);
0657         color.setAlphaF(_opacity);
0658 
0659         painter.save();
0660         painter.setCompositionMode(QPainter::CompositionMode_Source);
0661         painter.fillRect(rect, color);
0662         painter.restore();
0663     } else
0664         painter.fillRect(rect, backgroundColor);
0665 }
0666 
0667 void TerminalDisplay::drawCursor(QPainter &painter,
0668                                  const QRect &rect,
0669                                  const QColor &foregroundColor,
0670                                  const QColor & /*backgroundColor*/,
0671                                  bool &invertCharacterColor)
0672 {
0673     QRect cursorRect = rect;
0674 
0675     cursorRect.setHeight(qRound(_fontHeight) - ((m_full_cursor_height) ? 0 : _lineSpacing - 1));
0676 
0677     if (!_cursorBlinking) {
0678         if (_cursorColor.isValid())
0679             painter.setPen(_cursorColor);
0680         else
0681             painter.setPen(foregroundColor);
0682 
0683         if (_cursorShape == Emulation::KeyboardCursorShape::BlockCursor) {
0684             // draw the cursor outline, adjusting the area so that
0685             // it is draw entirely inside 'rect'
0686             float penWidth = qMax(1, painter.pen().width());
0687 
0688             //
0689             painter.drawRect(cursorRect.adjusted(+penWidth / 2 + fmod(penWidth, 2),
0690                                                  +penWidth / 2 + fmod(penWidth, 2),
0691                                                  -penWidth / 2 - fmod(penWidth, 2),
0692                                                  -penWidth / 2 - fmod(penWidth, 2)));
0693 
0694             // if ( hasFocus() )
0695             if (hasActiveFocus()) {
0696                 painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
0697 
0698                 if (!_cursorColor.isValid()) {
0699                     // invert the colour used to draw the text to ensure that the character at
0700                     // the cursor position is readable
0701                     invertCharacterColor = true;
0702                 }
0703             }
0704         } else if (_cursorShape == Emulation::KeyboardCursorShape::UnderlineCursor)
0705             painter.drawLine(cursorRect.left(), cursorRect.bottom(), cursorRect.right(), cursorRect.bottom());
0706         else if (_cursorShape == Emulation::KeyboardCursorShape::IBeamCursor)
0707             painter.drawLine(cursorRect.left(), cursorRect.top(), cursorRect.left(), cursorRect.bottom());
0708     }
0709 }
0710 
0711 void TerminalDisplay::drawCharacters(QPainter &painter, const QRect &rect, const QString &text, const Character *style, bool invertCharacterColor)
0712 {
0713     // don't draw text which is currently blinking
0714     if (_blinking && (style->rendition & RE_BLINK))
0715         return;
0716 
0717     // don't draw concealed characters
0718     if (style->rendition & RE_CONCEAL)
0719         return;
0720 
0721     // setup bold and underline
0722     bool useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
0723     const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
0724     const bool useItalic = style->rendition & RE_ITALIC || font().italic();
0725     const bool useStrikeOut = style->rendition & RE_STRIKEOUT || font().strikeOut();
0726     const bool useOverline = style->rendition & RE_OVERLINE || font().overline();
0727 
0728     painter.setFont(font());
0729 
0730     QFont font = painter.font();
0731     if (font.bold() != useBold || font.underline() != useUnderline || font.italic() != useItalic || font.strikeOut() != useStrikeOut
0732         || font.overline() != useOverline) {
0733         font.setBold(useBold);
0734         font.setUnderline(useUnderline);
0735         font.setItalic(useItalic);
0736         font.setStrikeOut(useStrikeOut);
0737         font.setOverline(useOverline);
0738         painter.setFont(font);
0739     }
0740 
0741     // setup pen
0742     const CharacterColor &textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor);
0743     const QColor color = textColor.color(_colorTable);
0744     QPen pen = painter.pen();
0745     if (pen.color() != color) {
0746         pen.setColor(color);
0747         painter.setPen(color);
0748     }
0749 
0750     // draw text
0751     if (isLineCharString(text))
0752         drawLineCharString(painter, rect.x(), rect.y(), text, style);
0753     else {
0754         // Force using LTR as the document layout for the terminal area, because
0755         // there is no use cases for RTL emulator and RTL terminal application.
0756         //
0757         // This still allows RTL characters to be rendered in the RTL way.
0758         painter.setLayoutDirection(Qt::LeftToRight);
0759 
0760         if (_bidiEnabled) {
0761             painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text);
0762         } else {
0763             painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text);
0764         }
0765     }
0766 }
0767 
0768 void TerminalDisplay::drawTextFragment(QPainter &painter, const QRect &rect, const QString &text, const Character *style)
0769 {
0770     painter.save();
0771 
0772     // setup painter
0773     const QColor foregroundColor = style->foregroundColor.color(_colorTable);
0774     const QColor backgroundColor = style->backgroundColor.color(_colorTable);
0775 
0776     // draw background if different from the display's background color
0777     if (backgroundColor != palette().window().color())
0778         drawBackground(painter, rect, backgroundColor, false /* do not use transparency */);
0779 
0780     // draw cursor shape if the current character is the cursor
0781     // this may alter the foreground and background colors
0782     bool invertCharacterColor = false;
0783     if (style->rendition & RE_CURSOR)
0784         drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor);
0785 
0786     // draw text
0787     drawCharacters(painter, rect, text, style, invertCharacterColor);
0788 
0789     painter.restore();
0790 }
0791 
0792 void TerminalDisplay::setRandomSeed(uint randomSeed)
0793 {
0794     _randomSeed = randomSeed;
0795 }
0796 uint TerminalDisplay::randomSeed() const
0797 {
0798     return _randomSeed;
0799 }
0800 
0801 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
0802 //
0803 // the terminal emulation keeps track of the scrolling of the character
0804 // image as it receives input, and when the view is updated, it calls scrollImage()
0805 // with the final scroll amount.  this improves performance because scrolling the
0806 // display is much cheaper than re-rendering all the text for the
0807 // part of the image which has moved up or down.
0808 // Instead only new lines have to be drawn
0809 void TerminalDisplay::scrollImage(int lines, const QRect &screenWindowRegion)
0810 {
0811     // if the flow control warning is enabled this will interfere with the
0812     // scrolling optimizations and cause artifacts.  the simple solution here
0813     // is to just disable the optimization whilst it is visible
0814     if (_outputSuspendedLabel && _outputSuspendedLabel->isVisible())
0815         return;
0816 
0817     // constrain the region to the display
0818     // the bottom of the region is capped to the number of lines in the display's
0819     // internal image - 2, so that the height of 'region' is strictly less
0820     // than the height of the internal image.
0821     QRect region = screenWindowRegion;
0822     region.setBottom(qMin(region.bottom(), this->_lines - 2));
0823 
0824     // return if there is nothing to do
0825     if (lines == 0 || _image.empty() || !region.isValid() || (region.top() + abs(lines)) >= region.bottom() || this->_lines <= region.height())
0826         return;
0827 
0828     // hide terminal size label to prevent it being scrolled
0829     if (_resizeWidget && _resizeWidget->isVisible())
0830         _resizeWidget->hide();
0831 
0832     // Note:  With Qt 4.4 the left edge of the scrolled area must be at 0
0833     // to get the correct (newly exposed) part of the widget repainted.
0834     //
0835     // The right edge must be before the left edge of the scroll bar to
0836     // avoid triggering a repaint of the entire widget, the distance is
0837     // given by SCROLLBAR_CONTENT_GAP
0838     //
0839     // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
0840     // application to monitor repainting.
0841     //
0842     int scrollBarWidth = _scrollBar->isHidden()                                               ? 0
0843         : _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar) ? 0
0844                                                                                               : _scrollBar->width();
0845     const int SCROLLBAR_CONTENT_GAP = scrollBarWidth == 0 ? 0 : 1;
0846     QRect scrollRect;
0847     if (_scrollbarLocation == QTermWidget::ScrollBarLeft) {
0848         scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP);
0849         scrollRect.setRight(width());
0850     } else {
0851         scrollRect.setLeft(0);
0852         scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
0853     }
0854     void *firstCharPos = &_image[region.top() * this->_columns];
0855     void *lastCharPos = &_image[(region.top() + abs(lines)) * this->_columns];
0856 
0857     int top = _topMargin + (region.top() * qRound(_fontHeight));
0858     int linesToMove = region.height() - abs(lines);
0859     int bytesToMove = linesToMove * this->_columns * sizeof(Character);
0860 
0861     Q_ASSERT(linesToMove > 0);
0862     Q_ASSERT(bytesToMove > 0);
0863 
0864     // scroll internal image
0865     if (lines > 0) {
0866         // check that the memory areas that we are going to move are valid
0867         Q_ASSERT((char *)lastCharPos + bytesToMove < (char *)(_image.data() + (this->_lines * this->_columns)));
0868 
0869         Q_ASSERT((lines * this->_columns) < _imageSize);
0870 
0871         // scroll internal image down
0872         memmove(firstCharPos, lastCharPos, bytesToMove);
0873 
0874         // set region of display to scroll
0875         scrollRect.setTop(top);
0876     } else {
0877         // check that the memory areas that we are going to move are valid
0878         Q_ASSERT((char *)firstCharPos + bytesToMove < (char *)(_image.data() + (this->_lines * this->_columns)));
0879 
0880         // scroll internal image up
0881         memmove(lastCharPos, firstCharPos, bytesToMove);
0882 
0883         // set region of the display to scroll
0884         scrollRect.setTop(top + abs(lines) * qRound(_fontHeight));
0885     }
0886     scrollRect.setHeight(linesToMove * qRound(_fontHeight));
0887 
0888     Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
0889 
0890     // scroll the display vertically to match internal _image
0891     // scroll( 0 , qRound(_fontHeight) * (-lines) , scrollRect );
0892 }
0893 
0894 QRegion TerminalDisplay::hotSpotRegion() const
0895 {
0896     QRegion region;
0897     const auto hotSpots = _filterChain->hotSpots();
0898     for (Filter::HotSpot *const hotSpot : hotSpots) {
0899         QRect r;
0900         if (hotSpot->startLine() == hotSpot->endLine()) {
0901             r.setLeft(hotSpot->startColumn());
0902             r.setTop(hotSpot->startLine());
0903             r.setRight(hotSpot->endColumn());
0904             r.setBottom(hotSpot->endLine());
0905             region |= imageToWidget(r);
0906             ;
0907         } else {
0908             r.setLeft(hotSpot->startColumn());
0909             r.setTop(hotSpot->startLine());
0910             r.setRight(_columns);
0911             r.setBottom(hotSpot->startLine());
0912             region |= imageToWidget(r);
0913             ;
0914             for (int line = hotSpot->startLine() + 1; line < hotSpot->endLine(); line++) {
0915                 r.setLeft(0);
0916                 r.setTop(line);
0917                 r.setRight(_columns);
0918                 r.setBottom(line);
0919                 region |= imageToWidget(r);
0920                 ;
0921             }
0922             r.setLeft(0);
0923             r.setTop(hotSpot->endLine());
0924             r.setRight(hotSpot->endColumn());
0925             r.setBottom(hotSpot->endLine());
0926             region |= imageToWidget(r);
0927             ;
0928         }
0929     }
0930     return region;
0931 }
0932 
0933 void TerminalDisplay::processFilters()
0934 {
0935     if (!_screenWindow)
0936         return;
0937 
0938     QRegion preUpdateHotSpots = hotSpotRegion();
0939 
0940     // use _screenWindow->getImage() here rather than _image because
0941     // other classes may call processFilters() when this display's
0942     // ScreenWindow emits a scrolled() signal - which will happen before
0943     // updateImage() is called on the display and therefore _image is
0944     // out of date at this point
0945     _filterChain->setImage(_screenWindow->getImage(), _screenWindow->windowLines(), _screenWindow->windowColumns(), _screenWindow->getLineProperties());
0946     _filterChain->process();
0947 
0948     QRegion postUpdateHotSpots = hotSpotRegion();
0949 
0950     update(preUpdateHotSpots | postUpdateHotSpots);
0951 }
0952 
0953 void TerminalDisplay::updateImage()
0954 {
0955     if (!_screenWindow)
0956         return;
0957 
0958     // TODO QMLTermWidget at the moment I'm disabling this.
0959     // Since this can't be scrolled we need to determine if this
0960     // is useful or not.
0961 
0962     // optimization - scroll the existing image where possible and
0963     // avoid expensive text drawing for parts of the image that
0964     // can simply be moved up or down
0965     //  scrollImage( _screenWindow->scrollCount() ,
0966     //               _screenWindow->scrollRegion() );
0967     //  _screenWindow->resetScrollCount();
0968 
0969     if (_image.empty()) {
0970         // Create _image.
0971         // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
0972         updateImageSize();
0973     }
0974 
0975     auto newimg = _screenWindow->getImage();
0976     int lines = _screenWindow->windowLines();
0977     int columns = _screenWindow->windowColumns();
0978 
0979     setScroll(_screenWindow->currentLine(), _screenWindow->lineCount());
0980 
0981     Q_ASSERT(this->_usedLines <= this->_lines);
0982     Q_ASSERT(this->_usedColumns <= this->_columns);
0983 
0984     int y, x, len;
0985 
0986     QPoint tL = contentsRect().topLeft();
0987     int tLx = tL.x();
0988     int tLy = tL.y();
0989     _hasBlinker = false;
0990 
0991     CharacterColor cf; // undefined
0992     CharacterColor _clipboard; // undefined
0993     int cr = -1; // undefined
0994 
0995     const int linesToUpdate = qMin(this->_lines, qMax(0, lines));
0996     const int columnsToUpdate = qMin(this->_columns, qMax(0, columns));
0997 
0998     std::vector<QChar> disstrU(columnsToUpdate);
0999     std::vector<char> dirtyMask(columnsToUpdate + 2);
1000     QRegion dirtyRegion;
1001 
1002     // debugging variable, this records the number of lines that are found to
1003     // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
1004     // which therefore need to be repainted
1005     int dirtyLineCount = 0;
1006 
1007     for (y = 0; y < linesToUpdate; ++y) {
1008         const Character *currentLine = &_image[y * _columns];
1009         const Character *const newLine = &newimg[y * columns];
1010 
1011         bool updateLine = false;
1012 
1013         // The dirty mask indicates which characters need repainting. We also
1014         // mark surrounding neighbours dirty, in case the character exceeds
1015         // its cell boundaries
1016         memset(dirtyMask.data(), 0, columnsToUpdate + 2);
1017 
1018         for (x = 0; x < columnsToUpdate; ++x) {
1019             if (newLine[x] != currentLine[x]) {
1020                 dirtyMask[x] = true;
1021             }
1022         }
1023 
1024         if (!_resizing) // not while _resizing, we're expecting a paintEvent
1025             for (x = 0; x < columnsToUpdate; ++x) {
1026                 _hasBlinker |= (newLine[x].rendition & RE_BLINK);
1027 
1028                 // Start drawing if this character or the next one differs.
1029                 // We also take the next one into account to handle the situation
1030                 // where characters exceed their cell width.
1031                 if (dirtyMask[x]) {
1032                     QChar c = newLine[x + 0].character;
1033                     if (!c.unicode())
1034                         continue;
1035                     int p = 0;
1036                     disstrU[p++] = c; // fontMap(c);
1037                     bool lineDraw = isLineChar(c);
1038                     bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character.unicode() == 0);
1039                     cr = newLine[x].rendition;
1040                     _clipboard = newLine[x].backgroundColor;
1041                     if (newLine[x].foregroundColor != cf)
1042                         cf = newLine[x].foregroundColor;
1043                     int lln = columnsToUpdate - x;
1044                     for (len = 1; len < lln; ++len) {
1045                         const Character &ch = newLine[x + len];
1046 
1047                         if (!ch.character.unicode())
1048                             continue; // Skip trailing part of multi-col chars.
1049 
1050                         bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character.unicode() == 0);
1051 
1052                         if (ch.foregroundColor != cf || ch.backgroundColor != _clipboard || ch.rendition != cr || !dirtyMask[x + len]
1053                             || isLineChar(c) != lineDraw || nextIsDoubleWidth != doubleWidth)
1054                             break;
1055 
1056                         disstrU[p++] = c; // fontMap(c);
1057                     }
1058 
1059                     bool saveFixedFont = _fixedFont;
1060                     if (lineDraw)
1061                         _fixedFont = false;
1062                     if (doubleWidth)
1063                         _fixedFont = false;
1064 
1065                     updateLine = true;
1066 
1067                     _fixedFont = saveFixedFont;
1068                     x += len - 1;
1069                 }
1070             }
1071 
1072         // both the top and bottom halves of double height _lines must always be redrawn
1073         // although both top and bottom halves contain the same characters, only
1074         // the top one is actually
1075         // drawn.
1076         if (_lineProperties.size() > y)
1077             updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
1078 
1079         // if the characters on the line are different in the old and the new _image
1080         // then this line must be repainted.
1081         if (updateLine) {
1082             dirtyLineCount++;
1083 
1084             // add the area occupied by this line to the region which needs to be
1085             // repainted
1086             QRect dirtyRect = QRect(_leftMargin + tLx, _topMargin + tLy + qRound(_fontHeight) * y, _fontWidth * columnsToUpdate, qRound(_fontHeight));
1087 
1088             dirtyRegion |= dirtyRect;
1089         }
1090 
1091         // replace the line of characters in the old _image with the
1092         // current line of the new _image
1093         memcpy((void *)currentLine, (const void *)newLine, columnsToUpdate * sizeof(Character));
1094     }
1095 
1096     // if the new _image is smaller than the previous _image, then ensure that the area
1097     // outside the new _image is cleared
1098     if (linesToUpdate < _usedLines) {
1099         dirtyRegion |= QRect(_leftMargin + tLx,
1100                              _topMargin + tLy + qRound(_fontHeight) * linesToUpdate,
1101                              _fontWidth * this->_columns,
1102                              qRound(_fontHeight) * (_usedLines - linesToUpdate));
1103     }
1104     _usedLines = linesToUpdate;
1105 
1106     if (columnsToUpdate < _usedColumns) {
1107         dirtyRegion |= QRect(_leftMargin + tLx + columnsToUpdate * _fontWidth,
1108                              _topMargin + tLy,
1109                              _fontWidth * (_usedColumns - columnsToUpdate),
1110                              qRound(_fontHeight) * this->_lines);
1111     }
1112     _usedColumns = columnsToUpdate;
1113 
1114     dirtyRegion |= _inputMethodData.previousPreeditRect;
1115 
1116     // update the parts of the display which have changed
1117     update(dirtyRegion);
1118 
1119     if (_hasBlinker && !_blinkTimer->isActive())
1120         _blinkTimer->start(TEXT_BLINK_DELAY);
1121     if (!_hasBlinker && _blinkTimer->isActive()) {
1122         _blinkTimer->stop();
1123         _blinking = false;
1124     }
1125 }
1126 
1127 void TerminalDisplay::showResizeNotification()
1128 {
1129 }
1130 
1131 void TerminalDisplay::setBlinkingCursor(bool blink)
1132 {
1133     if (_hasBlinkingCursor != blink)
1134         Q_EMIT blinkingCursorStateChanged();
1135 
1136     _hasBlinkingCursor = blink;
1137 
1138     if (blink && !_blinkCursorTimer->isActive())
1139         _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
1140 
1141     if (!blink && _blinkCursorTimer->isActive()) {
1142         _blinkCursorTimer->stop();
1143         if (_cursorBlinking)
1144             blinkCursorEvent();
1145         else
1146             _cursorBlinking = false;
1147     }
1148 }
1149 
1150 void TerminalDisplay::setBlinkingTextEnabled(bool blink)
1151 {
1152     _allowBlinkingText = blink;
1153 
1154     if (blink && !_blinkTimer->isActive())
1155         _blinkTimer->start(TEXT_BLINK_DELAY);
1156 
1157     if (!blink && _blinkTimer->isActive()) {
1158         _blinkTimer->stop();
1159         _blinking = false;
1160     }
1161 }
1162 
1163 void TerminalDisplay::focusOutEvent(QFocusEvent *)
1164 {
1165     Q_EMIT termLostFocus();
1166     // trigger a repaint of the cursor so that it is both visible (in case
1167     // it was hidden during blinking)
1168     // and drawn in a focused out state
1169     _cursorBlinking = false;
1170     updateCursor();
1171 
1172     _blinkCursorTimer->stop();
1173     if (_blinking)
1174         blinkEvent();
1175 
1176     _blinkTimer->stop();
1177 }
1178 void TerminalDisplay::focusInEvent(QFocusEvent *)
1179 {
1180     Q_EMIT termGetFocus();
1181     if (_hasBlinkingCursor) {
1182         _blinkCursorTimer->start();
1183     }
1184     updateCursor();
1185 
1186     if (_hasBlinker)
1187         _blinkTimer->start();
1188 }
1189 
1190 // QMLTermWidget version. See the upstream commented version for reference.
1191 void TerminalDisplay::paint(QPainter *painter)
1192 {
1193     QRect clipRect = painter->clipBoundingRect().toAlignedRect();
1194     QRect dirtyRect = clipRect.isValid() ? clipRect : contentsRect();
1195     drawContents(*painter, dirtyRect);
1196 }
1197 
1198 QPoint TerminalDisplay::cursorPosition() const
1199 {
1200     if (_screenWindow)
1201         return _screenWindow->cursorPosition();
1202     else
1203         return {0, 0};
1204 }
1205 
1206 QRect TerminalDisplay::preeditRect() const
1207 {
1208     const int preeditLength = string_width(_inputMethodData.preeditString);
1209 
1210     if (preeditLength == 0)
1211         return {};
1212 
1213     return QRect(_leftMargin + qRound(_fontWidth) * cursorPosition().x(),
1214                  _topMargin + qRound(_fontHeight) * cursorPosition().y(),
1215                  qRound(_fontWidth) * preeditLength,
1216                  qRound(_fontHeight));
1217 }
1218 
1219 void TerminalDisplay::drawInputMethodPreeditString(QPainter &painter, const QRect &rect)
1220 {
1221     if (_inputMethodData.preeditString.isEmpty())
1222         return;
1223 
1224     const QPoint cursorPos = cursorPosition();
1225 
1226     bool invertColors = false;
1227     const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
1228     const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
1229     const Character *style = &_image[loc(cursorPos.x(), cursorPos.y())];
1230 
1231     drawBackground(painter, rect, background, true);
1232     drawCursor(painter, rect, foreground, background, invertColors);
1233     drawCharacters(painter, rect, _inputMethodData.preeditString, style, invertColors);
1234 
1235     _inputMethodData.previousPreeditRect = rect;
1236 }
1237 
1238 FilterChain *TerminalDisplay::filterChain() const
1239 {
1240     return _filterChain.get();
1241 }
1242 
1243 void TerminalDisplay::paintFilters(QPainter &painter)
1244 {
1245     // get color of character under mouse and use it to draw
1246     // lines for filters
1247     QPoint cursorPos = mapFromScene(QCursor::pos()).toPoint();
1248     int leftMargin = _leftBaseMargin
1249         + ((_scrollbarLocation == QTermWidget::ScrollBarLeft && !_scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar))
1250                ? _scrollBar->width()
1251                : 0);
1252 
1253     auto charPos = getCharacterPosition(cursorPos);
1254     Character cursorCharacter = _image[loc(charPos.columns, charPos.lines)];
1255 
1256     painter.setPen(QPen(cursorCharacter.foregroundColor.color(colorTable())));
1257 
1258     // iterate over hotspots identified by the display's currently active filters
1259     // and draw appropriate visuals to indicate the presence of the hotspot
1260 
1261     const QList<Filter::HotSpot *> spots = _filterChain->hotSpots();
1262     for (const auto spot : spots) {
1263         QRegion region;
1264         if (spot->type() == Filter::HotSpot::Link) {
1265             QRect r;
1266             if (spot->startLine() == spot->endLine()) {
1267                 r.setCoords(spot->startColumn() * qRound(_fontWidth) + 1 + leftMargin,
1268                             spot->startLine() * qRound(_fontHeight) + 1 + _topBaseMargin,
1269                             spot->endColumn() * qRound(_fontWidth) - 1 + leftMargin,
1270                             (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1271                 region |= r;
1272             } else {
1273                 r.setCoords(spot->startColumn() * qRound(_fontWidth) + 1 + leftMargin,
1274                             spot->startLine() * qRound(_fontHeight) + 1 + _topBaseMargin,
1275                             _columns * qRound(_fontWidth) - 1 + leftMargin,
1276                             (spot->startLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1277                 region |= r;
1278                 for (int line = spot->startLine() + 1; line < spot->endLine(); line++) {
1279                     r.setCoords(0 * qRound(_fontWidth) + 1 + leftMargin,
1280                                 line * qRound(_fontHeight) + 1 + _topBaseMargin,
1281                                 _columns * qRound(_fontWidth) - 1 + leftMargin,
1282                                 (line + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1283                     region |= r;
1284                 }
1285                 r.setCoords(0 * qRound(_fontWidth) + 1 + leftMargin,
1286                             spot->endLine() * qRound(_fontHeight) + 1 + _topBaseMargin,
1287                             spot->endColumn() * qRound(_fontWidth) - 1 + leftMargin,
1288                             (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1289                 region |= r;
1290             }
1291         }
1292 
1293         for (int line = spot->startLine(); line <= spot->endLine(); line++) {
1294             int startColumn = 0;
1295             int endColumn = _columns - 1; // TODO use number of _columns which are actually
1296                                           // occupied on this line rather than the width of the
1297                                           // display in _columns
1298 
1299             // ignore whitespace at the end of the lines
1300             while (QChar(_image[loc(endColumn, line)].character).isSpace() && endColumn > 0)
1301                 endColumn--;
1302 
1303             // increment here because the column which we want to set 'endColumn' to
1304             // is the first whitespace character at the end of the line
1305             endColumn++;
1306 
1307             if (line == spot->startLine())
1308                 startColumn = spot->startColumn();
1309             if (line == spot->endLine())
1310                 endColumn = spot->endColumn();
1311 
1312             // subtract one pixel from
1313             // the right and bottom so that
1314             // we do not overdraw adjacent
1315             // hotspots
1316             //
1317             // subtracting one pixel from all sides also prevents an edge case where
1318             // moving the mouse outside a link could still leave it underlined
1319             // because the check below for the position of the cursor
1320             // finds it on the border of the target area
1321             QRect r;
1322             r.setCoords(startColumn * qRound(_fontWidth) + 1 + leftMargin,
1323                         line * qRound(_fontHeight) + 1 + _topBaseMargin,
1324                         endColumn * qRound(_fontWidth) - 1 + leftMargin,
1325                         (line + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1326             // Underline link hotspots
1327             if (spot->type() == Filter::HotSpot::Link) {
1328                 QFontMetricsF metrics(font());
1329 
1330                 // find the baseline (which is the invisible line that the characters in the font sit on,
1331                 // with some having tails dangling below)
1332                 qreal baseline = (qreal)r.bottom() - metrics.descent();
1333                 // find the position of the underline below that
1334                 qreal underlinePos = baseline + metrics.underlinePos();
1335 
1336                 if (region.contains(mapFromScene(QCursor::pos()).toPoint())) {
1337                     painter.drawLine(r.left(), underlinePos, r.right(), underlinePos);
1338                 }
1339             }
1340             // Marker hotspots simply have a transparent rectanglular shape
1341             // drawn on top of them
1342             else if (spot->type() == Filter::HotSpot::Marker) {
1343                 // TODO - Do not use a hardcoded colour for this
1344                 painter.fillRect(r, QBrush(QColor(255, 0, 0, 120)));
1345             }
1346         }
1347     }
1348 }
1349 
1350 int TerminalDisplay::textWidth(const int startColumn, const int length, const int line) const
1351 {
1352     QFontMetricsF fm(font());
1353     qreal result = 0;
1354     for (int column = 0; column < length; column++) {
1355         result += fm.horizontalAdvance(_image[loc(startColumn + column, line)].character);
1356     }
1357     return result;
1358 }
1359 
1360 QRect TerminalDisplay::calculateTextArea(int topLeftX, int topLeftY, int startColumn, int line, int length)
1361 {
1362     int left = _fixedFont ? qRound(_fontWidth) * startColumn : textWidth(0, startColumn, line);
1363     int top = qRound(_fontHeight) * line;
1364     int width = _fixedFont ? qRound(_fontWidth) * length : textWidth(startColumn, length, line);
1365     return {_leftMargin + topLeftX + left, _topMargin + topLeftY + top, width, qRound(_fontHeight)};
1366 }
1367 
1368 void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
1369 {
1370     // Draw opaque background
1371     drawBackground(paint, contentsRect(), _colorTable[DEFAULT_BACK_COLOR].color, true);
1372 
1373     QPoint tL = contentsRect().topLeft();
1374     int tLx = tL.x();
1375     int tLy = tL.y();
1376 
1377     int lux = qMin(_usedColumns - 1, qMax(0, qRound((rect.left() - tLx - _leftMargin) / _fontWidth)));
1378     int luy = qMin(_usedLines - 1, qMax(0, qRound((rect.top() - tLy - _topMargin) / _fontHeight)));
1379     int rlx = qMin(_usedColumns - 1, qMax(0, qRound((rect.right() - tLx - _leftMargin) / _fontWidth)));
1380     int rly = qMin(_usedLines - 1, qMax(0, qRound((rect.bottom() - tLy - _topMargin) / _fontHeight)));
1381 
1382     if (_image.empty()) {
1383         return;
1384     }
1385 
1386     const int bufferSize = _usedColumns;
1387     QString unistr;
1388     unistr.reserve(bufferSize);
1389     for (int y = luy; y <= rly; y++) {
1390         char16_t c = _image[loc(lux, y)].character.unicode();
1391         int x = lux;
1392         if (!c && x)
1393             x--; // Search for start of multi-column character
1394         for (; x <= rlx; x++) {
1395             int len = 1;
1396             int p = 0;
1397 
1398             // reset our buffer to the maximal size
1399             unistr.resize(bufferSize);
1400 
1401             // is this a single character or a sequence of characters ?
1402             if (_image[loc(x, y)].rendition & RE_EXTENDED_CHAR) {
1403                 // sequence of characters
1404                 ushort extendedCharLength = 0;
1405                 std::span chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x, y)].charSequence, extendedCharLength);
1406                 for (int index = 0; index < extendedCharLength; index++) {
1407                     Q_ASSERT(p < bufferSize);
1408                     unistr[p++] = chars[index];
1409                 }
1410             } else {
1411                 // single character
1412                 c = _image[loc(x, y)].character.unicode();
1413                 if (c) {
1414                     Q_ASSERT(p < bufferSize);
1415                     unistr[p++] = c; // fontMap(c);
1416                 }
1417             }
1418 
1419             bool lineDraw = isLineChar(c);
1420             bool doubleWidth = (_image[qMin(loc(x, y) + 1, _imageSize)].character.unicode() == 0);
1421             CharacterColor currentForeground = _image[loc(x, y)].foregroundColor;
1422             CharacterColor currentBackground = _image[loc(x, y)].backgroundColor;
1423             quint8 currentRendition = _image[loc(x, y)].rendition;
1424 
1425             while (x + len <= rlx && _image[loc(x + len, y)].foregroundColor == currentForeground
1426                    && _image[loc(x + len, y)].backgroundColor == currentBackground && _image[loc(x + len, y)].rendition == currentRendition
1427                    && (_image[qMin(loc(x + len, y) + 1, _imageSize)].character.unicode() == 0) == doubleWidth
1428                    && isLineChar(c = _image[loc(x + len, y)].character.unicode()) == lineDraw) // Assignment!
1429             {
1430                 if (c)
1431                     unistr[p++] = c; // fontMap(c);
1432                 if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1433                     len++; // Skip trailing part of multi-column character
1434                 len++;
1435             }
1436             if ((x + len < _usedColumns) && (!_image[loc(x + len, y)].character.unicode()))
1437                 len++; // Adjust for trailing part of multi-column character
1438 
1439             bool save__fixedFont = _fixedFont;
1440             if (lineDraw)
1441                 _fixedFont = false;
1442             unistr.resize(p);
1443 
1444             // Create a text scaling matrix for double width and double height lines.
1445             QTransform textScale;
1446 
1447             if (y < _lineProperties.size()) {
1448                 if (_lineProperties[y] & LINE_DOUBLEWIDTH)
1449                     textScale.scale(2, 1);
1450 
1451                 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1452                     textScale.scale(1, 2);
1453             }
1454 
1455             // Apply text scaling matrix.
1456             paint.setWorldTransform(textScale, true);
1457 
1458             // calculate the area in which the text will be drawn
1459             QRect textArea = calculateTextArea(tLx, tLy, x, y, len);
1460 
1461             // move the calculated area to take account of scaling applied to the painter.
1462             // the position of the area from the origin (0,0) is scaled
1463             // by the opposite of whatever
1464             // transformation has been applied to the painter.  this ensures that
1465             // painting does actually start from textArea.topLeft()
1466             //(instead of textArea.topLeft() * painter-scale)
1467             textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft()));
1468 
1469             // paint text fragment
1470             drawTextFragment(paint, textArea, unistr, &_image[loc(x, y)]); //,
1471             // 0,
1472             //!_isPrinting );
1473 
1474             _fixedFont = save__fixedFont;
1475 
1476             // reset back to single-width, single-height _lines
1477             paint.setWorldTransform(textScale.inverted(), true);
1478 
1479             if (y < _lineProperties.size() - 1) {
1480                 // double-height _lines are represented by two adjacent _lines
1481                 // containing the same characters
1482                 // both _lines will have the LINE_DOUBLEHEIGHT attribute.
1483                 // If the current line has the LINE_DOUBLEHEIGHT attribute,
1484                 // we can therefore skip the next line
1485                 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1486                     y++;
1487             }
1488 
1489             x += len - 1;
1490         }
1491     }
1492 }
1493 
1494 void TerminalDisplay::blinkEvent()
1495 {
1496     if (!_allowBlinkingText)
1497         return;
1498 
1499     _blinking = !_blinking;
1500 
1501     // TODO:  Optimize to only repaint the areas of the widget
1502     //  where there is blinking text
1503     //  rather than repainting the whole widget.
1504     update();
1505 }
1506 
1507 QRect TerminalDisplay::imageToWidget(const QRect &imageArea) const
1508 {
1509     QRect result;
1510     result.setLeft(_leftMargin + qRound(_fontWidth) * imageArea.left());
1511     result.setTop(_topMargin + qRound(_fontHeight) * imageArea.top());
1512     result.setWidth(qRound(_fontWidth) * imageArea.width());
1513     result.setHeight(qRound(_fontHeight) * imageArea.height());
1514 
1515     return result;
1516 }
1517 
1518 void TerminalDisplay::updateCursor()
1519 {
1520     QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(1, 1)));
1521     update(cursorRect);
1522 }
1523 
1524 void TerminalDisplay::blinkCursorEvent()
1525 {
1526     _cursorBlinking = !_cursorBlinking;
1527     updateCursor();
1528 }
1529 
1530 /* ------------------------------------------------------------------------- */
1531 /*                                                                           */
1532 /*                                  Resizing                                 */
1533 /*                                                                           */
1534 /* ------------------------------------------------------------------------- */
1535 
1536 void TerminalDisplay::resizeEvent(QResizeEvent *)
1537 {
1538     updateImageSize();
1539     processFilters();
1540 }
1541 
1542 void TerminalDisplay::propagateSize()
1543 {
1544     if (_isFixedSize) {
1545         setSize(_columns, _lines);
1546         return;
1547     }
1548     if (!_image.empty())
1549         updateImageSize();
1550 }
1551 
1552 void TerminalDisplay::updateImageSize()
1553 {
1554     auto oldimg = _image;
1555     int oldlin = _lines;
1556     int oldcol = _columns;
1557 
1558     makeImage();
1559 
1560     // copy the old image to reduce flicker
1561     int lines = qMin(oldlin, _lines);
1562     int columns = qMin(oldcol, _columns);
1563 
1564     if (!oldimg.empty()) {
1565         for (int line = 0; line < lines; line++) {
1566             memcpy((void *)&_image[_columns * line], (void *)&oldimg[oldcol * line], columns * sizeof(Character));
1567         }
1568         oldimg.clear();
1569     }
1570 
1571     if (_screenWindow)
1572         _screenWindow->setWindowLines(_lines);
1573 
1574     _resizing = (oldlin != _lines) || (oldcol != _columns);
1575 
1576     if (_resizing) {
1577         showResizeNotification();
1578         Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
1579     }
1580 
1581     _resizing = false;
1582 }
1583 
1584 // showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1585 // display has been resized when the display is hidden or shown.
1586 //
1587 // TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1588 // the same signal as the one for a content size change
1589 void TerminalDisplay::showEvent(QShowEvent *)
1590 {
1591     Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth);
1592 }
1593 void TerminalDisplay::hideEvent(QHideEvent *)
1594 {
1595     Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth);
1596 }
1597 
1598 /* ------------------------------------------------------------------------- */
1599 /*                                                                           */
1600 /*                                Scrollbar                                  */
1601 /*                                                                           */
1602 /* ------------------------------------------------------------------------- */
1603 
1604 void TerminalDisplay::scrollBarPositionChanged(int)
1605 {
1606     if (!_screenWindow)
1607         return;
1608 
1609     _screenWindow->scrollTo(_scrollBar->value());
1610 
1611     // if the thumb has been moved to the bottom of the _scrollBar then set
1612     // the display to automatically track new output,
1613     // that is, scroll down automatically
1614     // to how new _lines as they are added
1615     const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
1616     _screenWindow->setTrackOutput(atEndOfOutput);
1617 
1618     updateImage();
1619 
1620     // QMLTermWidget: notify qml side of the change only when needed.
1621     Q_EMIT scrollbarValueChanged();
1622 }
1623 
1624 void TerminalDisplay::setScroll(int cursor, int slines)
1625 {
1626     // update _scrollBar if the range or value has changed,
1627     // otherwise return
1628     //
1629     // setting the range or value of a _scrollBar will always trigger
1630     // a repaint, so it should be avoided if it is not necessary
1631     if (_scrollBar->minimum() == 0 && _scrollBar->maximum() == (slines - _lines) && _scrollBar->value() == cursor) {
1632         return;
1633     }
1634 
1635     disconnect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1636     _scrollBar->setRange(0, slines - _lines);
1637     _scrollBar->setSingleStep(1);
1638     _scrollBar->setPageStep(_lines);
1639     _scrollBar->setValue(cursor);
1640     connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1641 }
1642 
1643 void TerminalDisplay::scrollToEnd()
1644 {
1645     disconnect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1646     _scrollBar->setValue(_scrollBar->maximum());
1647     connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1648 
1649     _screenWindow->scrollTo(_scrollBar->value() + 1);
1650     _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
1651 }
1652 
1653 void TerminalDisplay::setScrollBarPosition(QTermWidget::ScrollBarPosition position)
1654 {
1655     if (_scrollbarLocation == position)
1656         return;
1657 
1658     if (position == QTermWidget::NoScrollBar)
1659         _scrollBar->hide();
1660     else
1661         _scrollBar->show();
1662 
1663     _topMargin = _leftMargin = 1;
1664     _scrollbarLocation = position;
1665 
1666     propagateSize();
1667     update();
1668 }
1669 
1670 void TerminalDisplay::mousePressEvent(QMouseEvent *ev)
1671 {
1672     if (_possibleTripleClick && (ev->button() == Qt::LeftButton)) {
1673         mouseTripleClickEvent(ev);
1674         return;
1675     }
1676 
1677     if (!contentsRect().contains(ev->pos()))
1678         return;
1679 
1680     if (!_screenWindow)
1681         return;
1682 
1683     auto charPos = getCharacterPosition(ev->pos());
1684     QPoint pos = charPos;
1685     auto [charColumn, charLine] = charPos;
1686 
1687     if (ev->button() == Qt::LeftButton) {
1688         _lineSelectionMode = false;
1689         _wordSelectionMode = false;
1690 
1691         Q_EMIT isBusySelecting(true); // Keep it steady...
1692         // Drag only when the Control key is hold
1693         bool selected = false;
1694 
1695         // The receiver of the testIsSelected() signal will adjust
1696         // 'selected' accordingly.
1697         // emit testIsSelected(pos.x(), pos.y(), selected);
1698 
1699         selected = _screenWindow->isSelected(charColumn, charLine);
1700 
1701         if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected) {
1702             // The user clicked inside selected text
1703             dragInfo.state = diPending;
1704             dragInfo.start = ev->pos();
1705         } else {
1706             // No reason to ever start a drag event
1707             dragInfo.state = diNone;
1708 
1709             _preserveLineBreaks = !((ev->modifiers() & Qt::ControlModifier) && !(ev->modifiers() & Qt::AltModifier));
1710             _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
1711 
1712             if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) {
1713                 _screenWindow->clearSelection();
1714 
1715                 // emit clearSelectionSignal();
1716                 pos.ry() += _scrollBar->value();
1717                 _iPntSel = _pntSel = pos;
1718                 _actSel = 1; // left mouse button pressed but nothing selected yet.
1719 
1720             } else {
1721                 Q_EMIT mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
1722             }
1723 
1724             Filter::HotSpot *spot = _filterChain->hotSpotAt(charLine, charColumn);
1725             if (spot && spot->type() == Filter::HotSpot::Link)
1726                 spot->activate(QLatin1String("click-action"));
1727         }
1728     } else if (ev->button() == Qt::MiddleButton) {
1729         if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1730             emitSelection(true, ev->modifiers() & Qt::ControlModifier);
1731         else
1732             Q_EMIT mouseSignal(1, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
1733     } else if (ev->button() == Qt::RightButton) {
1734         if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1735             Q_EMIT configureRequest(ev->pos());
1736         else
1737             Q_EMIT mouseSignal(2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
1738     }
1739 }
1740 
1741 QList<QAction *> TerminalDisplay::filterActions(const QPoint &position)
1742 {
1743     auto pos = getCharacterPosition(position);
1744 
1745     Filter::HotSpot *spot = _filterChain->hotSpotAt(pos.lines, pos.columns);
1746 
1747     return spot ? spot->actions() : QList<QAction *>();
1748 }
1749 
1750 void TerminalDisplay::mouseMoveEvent(QMouseEvent *ev)
1751 {
1752     int leftMargin = _leftBaseMargin
1753         + ((_scrollbarLocation == QTermWidget::ScrollBarLeft && !_scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar))
1754                ? _scrollBar->width()
1755                : 0);
1756 
1757     auto charPos = getCharacterPosition(ev->pos());
1758 
1759     // handle filters
1760     // change link hot-spot appearance on mouse-over
1761     Filter::HotSpot *spot = _filterChain->hotSpotAt(charPos.lines, charPos.columns);
1762     if (spot && spot->type() == Filter::HotSpot::Link) {
1763         QRegion previousHotspotArea = _mouseOverHotspotArea;
1764         _mouseOverHotspotArea = QRegion();
1765         QRect r;
1766         if (spot->startLine() == spot->endLine()) {
1767             r.setCoords(spot->startColumn() * qRound(_fontWidth) + leftMargin,
1768                         spot->startLine() * qRound(_fontHeight) + _topBaseMargin,
1769                         spot->endColumn() * qRound(_fontWidth) + leftMargin,
1770                         (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1771             _mouseOverHotspotArea |= r;
1772         } else {
1773             r.setCoords(spot->startColumn() * qRound(_fontWidth) + leftMargin,
1774                         spot->startLine() * qRound(_fontHeight) + _topBaseMargin,
1775                         _columns * qRound(_fontWidth) - 1 + leftMargin,
1776                         (spot->startLine() + 1) * qRound(_fontHeight) + _topBaseMargin);
1777             _mouseOverHotspotArea |= r;
1778             for (int line = spot->startLine() + 1; line < spot->endLine(); line++) {
1779                 r.setCoords(0 * qRound(_fontWidth) + leftMargin,
1780                             line * qRound(_fontHeight) + _topBaseMargin,
1781                             _columns * qRound(_fontWidth) + leftMargin,
1782                             (line + 1) * qRound(_fontHeight) + _topBaseMargin);
1783                 _mouseOverHotspotArea |= r;
1784             }
1785             r.setCoords(0 * qRound(_fontWidth) + leftMargin,
1786                         spot->endLine() * qRound(_fontHeight) + _topBaseMargin,
1787                         spot->endColumn() * qRound(_fontWidth) + leftMargin,
1788                         (spot->endLine() + 1) * qRound(_fontHeight) + _topBaseMargin);
1789             _mouseOverHotspotArea |= r;
1790         }
1791         update(_mouseOverHotspotArea | previousHotspotArea);
1792     } else if (!_mouseOverHotspotArea.isEmpty()) {
1793         update(_mouseOverHotspotArea);
1794         // set hotspot area to an invalid rectangle
1795         _mouseOverHotspotArea = QRegion();
1796     }
1797 
1798     // for auto-hiding the cursor, we need mouseTracking
1799     if (ev->buttons() == Qt::NoButton)
1800         return;
1801 
1802     // if the terminal is interested in mouse movements
1803     // then Q_EMIT a mouse movement signal, unless the shift
1804     // key is being held down, which overrides this.
1805     if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
1806         int button = 3;
1807         if (ev->buttons() & Qt::LeftButton)
1808             button = 0;
1809         if (ev->buttons() & Qt::MiddleButton)
1810             button = 1;
1811         if (ev->buttons() & Qt::RightButton)
1812             button = 2;
1813 
1814         Q_EMIT mouseSignal(button, charPos.columns + 1, charPos.lines + 1 + _scrollBar->value() - _scrollBar->maximum(), 1);
1815 
1816         return;
1817     }
1818 
1819     if (dragInfo.state == diPending) {
1820         // we had a mouse down, but haven't confirmed a drag yet
1821         // if the mouse has moved sufficiently, we will confirm
1822 
1823         //   int distance = KGlobalSettings::dndEventDelay();
1824         int distance = QApplication::startDragDistance();
1825         if (ev->position().x() > dragInfo.start.x() + distance || ev->position().x() < dragInfo.start.x() - distance
1826             || ev->position().y() > dragInfo.start.y() + distance || ev->position().y() < dragInfo.start.y() - distance) {
1827             // we've left the drag square, we can start a real drag operation now
1828             Q_EMIT isBusySelecting(false); // Ok.. we can breath again.
1829 
1830             _screenWindow->clearSelection();
1831             doDrag();
1832         }
1833         return;
1834     } else if (dragInfo.state == diDragging) {
1835         // this isn't technically needed because mouseMoveEvent is suppressed during
1836         // Qt drag operations, replaced by dragMoveEvent
1837         return;
1838     }
1839 
1840     if (_actSel == 0)
1841         return;
1842 
1843     // don't extend selection while pasting
1844     if (ev->buttons() & Qt::MiddleButton)
1845         return;
1846 
1847     extendSelection(ev->pos());
1848 }
1849 
1850 void TerminalDisplay::extendSelection(const QPoint &position)
1851 {
1852     QPoint pos = position;
1853 
1854     if (!_screenWindow)
1855         return;
1856 
1857     // if ( !contentsRect().contains(ev->pos()) ) return;
1858     QPoint tL = contentsRect().topLeft();
1859     int tLx = tL.x();
1860     int tLy = tL.y();
1861     int scroll = _scrollBar->value();
1862 
1863     // we're in the process of moving the mouse with the left button pressed
1864     // the mouse cursor will kept caught within the bounds of the text in
1865     // this widget.
1866 
1867     int linesBeyondWidget = 0;
1868 
1869     QRect textBounds(tLx + _leftMargin, tLy + _topMargin, _usedColumns * qRound(_fontWidth) - 1, _usedLines * qRound(_fontHeight) - 1);
1870 
1871     // Adjust position within text area bounds.
1872     QPoint oldpos = pos;
1873 
1874     pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right()));
1875     pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom()));
1876 
1877     if (oldpos.y() > textBounds.bottom()) {
1878         linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / qRound(_fontHeight);
1879         _scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward
1880     }
1881     if (oldpos.y() < textBounds.top()) {
1882         linesBeyondWidget = (textBounds.top() - oldpos.y()) / qRound(_fontHeight);
1883         _scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history
1884     }
1885 
1886     QPoint here =
1887         getCharacterPosition(pos); // QPoint((pos.x()-tLx-_leftMargin+(qRound(_fontWidth)/2))/qRound(_fontWidth),(pos.y()-tLy-_topMargin)/qRound(_fontHeight));
1888     QPoint ohere;
1889     QPoint _iPntSelCorr = _iPntSel;
1890     _iPntSelCorr.ry() -= _scrollBar->value();
1891     QPoint _pntSelCorr = _pntSel;
1892     _pntSelCorr.ry() -= _scrollBar->value();
1893     bool swapping = false;
1894 
1895     if (_wordSelectionMode) {
1896         // Extend to word boundaries
1897         int i;
1898         QChar selClass;
1899 
1900         bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
1901         bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
1902         swapping = left_not_right != old_left_not_right;
1903 
1904         // Find left (left_not_right ? from here : from start)
1905         QPoint left = left_not_right ? here : _iPntSelCorr;
1906         i = loc(left.x(), left.y());
1907         if (i >= 0 && i <= _imageSize) {
1908             selClass = charClass(_image[i].character);
1909             while (((left.x() > 0) || (left.y() > 0 && (_lineProperties[left.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) {
1910                 i--;
1911                 if (left.x() > 0)
1912                     left.rx()--;
1913                 else {
1914                     left.rx() = _usedColumns - 1;
1915                     left.ry()--;
1916                 }
1917             }
1918         }
1919 
1920         // Find left (left_not_right ? from start : from here)
1921         QPoint right = left_not_right ? _iPntSelCorr : here;
1922         i = loc(right.x(), right.y());
1923         if (i >= 0 && i <= _imageSize) {
1924             selClass = charClass(_image[i].character);
1925             while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && (_lineProperties[right.y()] & LINE_WRAPPED)))
1926                    && charClass(_image[i + 1].character) == selClass) {
1927                 i++;
1928                 if (right.x() < _usedColumns - 1)
1929                     right.rx()++;
1930                 else {
1931                     right.rx() = 0;
1932                     right.ry()++;
1933                 }
1934             }
1935         }
1936 
1937         // Pick which is start (ohere) and which is extension (here)
1938         if (left_not_right) {
1939             here = left;
1940             ohere = right;
1941         } else {
1942             here = right;
1943             ohere = left;
1944         }
1945         ohere.rx()++;
1946     }
1947 
1948     if (_lineSelectionMode) {
1949         // Extend to complete line
1950         bool above_not_below = (here.y() < _iPntSelCorr.y());
1951 
1952         QPoint above = above_not_below ? here : _iPntSelCorr;
1953         QPoint below = above_not_below ? _iPntSelCorr : here;
1954 
1955         while (above.y() > 0 && (_lineProperties[above.y() - 1] & LINE_WRAPPED))
1956             above.ry()--;
1957         while (below.y() < _usedLines - 1 && (_lineProperties[below.y()] & LINE_WRAPPED))
1958             below.ry()++;
1959 
1960         above.setX(0);
1961         below.setX(_usedColumns - 1);
1962 
1963         // Pick which is start (ohere) and which is extension (here)
1964         if (above_not_below) {
1965             here = above;
1966             ohere = below;
1967         } else {
1968             here = below;
1969             ohere = above;
1970         }
1971 
1972         QPoint newSelBegin = QPoint(ohere.x(), ohere.y());
1973         swapping = !(_tripleSelBegin == newSelBegin);
1974         _tripleSelBegin = newSelBegin;
1975 
1976         ohere.rx()++;
1977     }
1978 
1979     int offset = 0;
1980     if (!_wordSelectionMode && !_lineSelectionMode) {
1981         int i;
1982         QChar selClass;
1983 
1984         bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
1985         bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
1986         swapping = left_not_right != old_left_not_right;
1987 
1988         // Find left (left_not_right ? from here : from start)
1989         QPoint left = left_not_right ? here : _iPntSelCorr;
1990 
1991         // Find left (left_not_right ? from start : from here)
1992         QPoint right = left_not_right ? _iPntSelCorr : here;
1993         if (right.x() > 0 && !_columnSelectionMode) {
1994             i = loc(right.x(), right.y());
1995             if (i >= 0 && i <= _imageSize) {
1996                 selClass = charClass(_image[i - 1].character);
1997                 /* if (selClass == ' ')
1998                  {
1999                    while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
2000                                    !(_lineProperties[right.y()] & LINE_WRAPPED))
2001                    { i++; right.rx()++; }
2002                    if (right.x() < _usedColumns-1)
2003                      right = left_not_right ? _iPntSelCorr : here;
2004                    else
2005                      right.rx()++;  // will be balanced later because of offset=-1;
2006                  }*/
2007             }
2008         }
2009 
2010         // Pick which is start (ohere) and which is extension (here)
2011         if (left_not_right) {
2012             here = left;
2013             ohere = right;
2014             offset = 0;
2015         } else {
2016             here = right;
2017             ohere = left;
2018             offset = -1;
2019         }
2020     }
2021 
2022     if ((here == _pntSelCorr) && (scroll == _scrollBar->value()))
2023         return; // not moved
2024 
2025     if (here == ohere)
2026         return; // It's not left, it's not right.
2027 
2028     if (_actSel < 2 || swapping) {
2029         if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2030             _screenWindow->setSelectionStart(ohere.x(), ohere.y(), true);
2031         } else {
2032             _screenWindow->setSelectionStart(ohere.x() - 1 - offset, ohere.y(), false);
2033         }
2034     }
2035 
2036     _actSel = 2; // within selection
2037     _pntSel = here;
2038     _pntSel.ry() += _scrollBar->value();
2039 
2040     if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2041         _screenWindow->setSelectionEnd(here.x(), here.y());
2042     } else {
2043         _screenWindow->setSelectionEnd(here.x() + offset, here.y());
2044     }
2045 }
2046 
2047 void TerminalDisplay::mouseReleaseEvent(QMouseEvent *ev)
2048 {
2049     if (!_screenWindow)
2050         return;
2051 
2052     auto [charColumn, charLine] = getCharacterPosition(ev->pos());
2053 
2054     if (ev->button() == Qt::LeftButton) {
2055         Q_EMIT isBusySelecting(false);
2056         if (dragInfo.state == diPending) {
2057             // We had a drag event pending but never confirmed.  Kill selection
2058             _screenWindow->clearSelection();
2059             // emit clearSelectionSignal();
2060         } else {
2061             if (_actSel > 1) {
2062                 setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2063             }
2064 
2065             _actSel = 0;
2066 
2067             // FIXME: emits a release event even if the mouse is
2068             //        outside the range. The procedure used in `mouseMoveEvent'
2069             //        applies here, too.
2070 
2071             if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2072                 Q_EMIT mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 2);
2073         }
2074         dragInfo.state = diNone;
2075     }
2076 
2077     if (!_mouseMarks && ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier)) || ev->button() == Qt::MiddleButton)) {
2078         Q_EMIT mouseSignal(ev->button() == Qt::MiddleButton ? 1 : 2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 2);
2079     }
2080 }
2081 
2082 CharPos TerminalDisplay::getCharacterPosition(const QPointF &widgetPoint) const
2083 {
2084     int line, column = 0;
2085 
2086     line = (widgetPoint.y() - contentsRect().top() - _topMargin) / qRound(_fontHeight);
2087     if (line < 0)
2088         line = 0;
2089     if (line >= _usedLines)
2090         line = _usedLines - 1;
2091 
2092     int x = widgetPoint.x() + qRound(_fontWidth) / 2 - contentsRect().left() - _leftMargin;
2093     if (_fixedFont)
2094         column = x / qRound(_fontWidth);
2095     else {
2096         column = 0;
2097         while (column + 1 < _usedColumns && x > textWidth(0, column + 1, line))
2098             column++;
2099     }
2100 
2101     if (column < 0)
2102         column = 0;
2103 
2104     // the column value returned can be equal to _usedColumns, which
2105     // is the position just after the last character displayed in a line.
2106     //
2107     // this is required so that the user can select characters in the right-most
2108     // column (or left-most for right-to-left input)
2109     if (column > _usedColumns)
2110         column = _usedColumns;
2111 
2112     return {column, line};
2113 }
2114 
2115 void TerminalDisplay::updateFilters()
2116 {
2117     if (!_screenWindow)
2118         return;
2119 
2120     processFilters();
2121 }
2122 
2123 void TerminalDisplay::updateLineProperties()
2124 {
2125     if (!_screenWindow)
2126         return;
2127 
2128     _lineProperties = _screenWindow->getLineProperties();
2129 }
2130 
2131 void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent *ev)
2132 {
2133     if (ev->button() != Qt::LeftButton)
2134         return;
2135     if (!_screenWindow)
2136         return;
2137 
2138     QPoint pos = getCharacterPosition(ev->pos());
2139 
2140     // pass on double click as two clicks.
2141     if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
2142         // Send just _ONE_ click event, since the first click of the double click
2143         // was already sent by the click handler
2144         Q_EMIT mouseSignal(0, pos.x() + 1, pos.y() + 1 + _scrollBar->value() - _scrollBar->maximum(),
2145                            0); // left button
2146         return;
2147     }
2148 
2149     _screenWindow->clearSelection();
2150     QPoint bgnSel = pos;
2151     QPoint endSel = pos;
2152     int i = loc(bgnSel.x(), bgnSel.y());
2153     _iPntSel = bgnSel;
2154     _iPntSel.ry() += _scrollBar->value();
2155 
2156     _wordSelectionMode = true;
2157 
2158     // find word boundaries...
2159     QChar selClass = charClass(_image[i].character);
2160     {
2161         // find the start of the word
2162         int x = bgnSel.x();
2163         while (((x > 0) || (bgnSel.y() > 0 && (_lineProperties[bgnSel.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) {
2164             i--;
2165             if (x > 0)
2166                 x--;
2167             else {
2168                 x = _usedColumns - 1;
2169                 bgnSel.ry()--;
2170             }
2171         }
2172 
2173         bgnSel.setX(x);
2174         _screenWindow->setSelectionStart(bgnSel.x(), bgnSel.y(), false);
2175 
2176         // find the end of the word
2177         i = loc(endSel.x(), endSel.y());
2178         x = endSel.x();
2179         while (((x < _usedColumns - 1) || (endSel.y() < _usedLines - 1 && (_lineProperties[endSel.y()] & LINE_WRAPPED)))
2180                && charClass(_image[i + 1].character) == selClass) {
2181             i++;
2182             if (x < _usedColumns - 1)
2183                 x++;
2184             else {
2185                 x = 0;
2186                 endSel.ry()++;
2187             }
2188         }
2189 
2190         endSel.setX(x);
2191 
2192         // In word selection mode don't select @ (64) if at end of word.
2193         if ((QChar(_image[i].character) == QLatin1Char('@')) && ((endSel.x() - bgnSel.x()) > 0))
2194             endSel.setX(x - 1);
2195 
2196         _actSel = 2; // within selection
2197 
2198         _screenWindow->setSelectionEnd(endSel.x(), endSel.y());
2199 
2200         setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2201     }
2202 
2203     _possibleTripleClick = true;
2204 
2205     QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()));
2206 }
2207 
2208 void TerminalDisplay::wheelEvent(QWheelEvent *ev)
2209 {
2210     if (ev->angleDelta().y() == 0) // orientation is not verical
2211         return;
2212 
2213     // if the terminal program is not interested mouse events
2214     // then send the event to the scrollbar if the slider has room to move
2215     // or otherwise send simulated up / down key presses to the terminal program
2216     // for the benefit of programs such as 'less'
2217     if (_mouseMarks) {
2218         bool canScroll = _scrollBar->maximum() > 0;
2219         if (canScroll)
2220             _scrollBar->event(ev);
2221         else {
2222             // assume that each Up / Down key event will cause the terminal application
2223             // to scroll by one line.
2224             //
2225             // to get a reasonable scrolling speed, scroll by one line for every 5 degrees
2226             // of mouse wheel rotation.  Mouse wheels typically move in steps of 15 degrees,
2227             // giving a scroll of 3 lines
2228             int key = ev->angleDelta().y() > 0 ? Qt::Key_Up : Qt::Key_Down;
2229 
2230             // QWheelEvent::delta() gives rotation in eighths of a degree
2231             int wheelDegrees = ev->angleDelta().y() / 8;
2232             int linesToScroll = abs(wheelDegrees) / 5;
2233 
2234             QKeyEvent keyScrollEvent(QEvent::KeyPress, key, Qt::NoModifier);
2235 
2236             for (int i = 0; i < linesToScroll; i++)
2237                 Q_EMIT keyPressedSignal(&keyScrollEvent, false);
2238         }
2239     } else {
2240         // terminal program wants notification of mouse activity
2241         auto pos = getCharacterPosition(ev->position());
2242 
2243         Q_EMIT mouseSignal(ev->angleDelta().y() > 0 ? 4 : 5, pos.columns + 1, pos.lines + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
2244     }
2245 }
2246 
2247 void TerminalDisplay::tripleClickTimeout()
2248 {
2249     _possibleTripleClick = false;
2250 }
2251 
2252 void TerminalDisplay::mouseTripleClickEvent(QMouseEvent *ev)
2253 {
2254     if (!_screenWindow)
2255         return;
2256 
2257     _iPntSel = getCharacterPosition(ev->pos());
2258 
2259     _screenWindow->clearSelection();
2260 
2261     _lineSelectionMode = true;
2262     _wordSelectionMode = false;
2263 
2264     _actSel = 2; // within selection
2265     Q_EMIT isBusySelecting(true); // Keep it steady...
2266 
2267     while (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))
2268         _iPntSel.ry()--;
2269 
2270     if (_tripleClickMode == SelectForwardsFromCursor) {
2271         // find word boundary start
2272         int i = loc(_iPntSel.x(), _iPntSel.y());
2273         QChar selClass = charClass(_image[i].character);
2274         int x = _iPntSel.x();
2275 
2276         while (((x > 0) || (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) {
2277             i--;
2278             if (x > 0)
2279                 x--;
2280             else {
2281                 x = _columns - 1;
2282                 _iPntSel.ry()--;
2283             }
2284         }
2285 
2286         _screenWindow->setSelectionStart(x, _iPntSel.y(), false);
2287         _tripleSelBegin = QPoint(x, _iPntSel.y());
2288     } else if (_tripleClickMode == SelectWholeLine) {
2289         _screenWindow->setSelectionStart(0, _iPntSel.y(), false);
2290         _tripleSelBegin = QPoint(0, _iPntSel.y());
2291     }
2292 
2293     while (_iPntSel.y() < _lines - 1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED))
2294         _iPntSel.ry()++;
2295 
2296     _screenWindow->setSelectionEnd(_columns - 1, _iPntSel.y());
2297 
2298     setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2299 
2300     _iPntSel.ry() += _scrollBar->value();
2301 }
2302 
2303 bool TerminalDisplay::focusNextPrevChild(bool next)
2304 {
2305     if (next)
2306         return false; // This disables changing the active part in konqueror
2307                       // when pressing Tab
2308     return false;
2309     // return QWidget::focusNextPrevChild( next );
2310 }
2311 
2312 QChar TerminalDisplay::charClass(QChar qch) const
2313 {
2314     if (qch.isSpace())
2315         return QLatin1Char(' ');
2316 
2317     if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive))
2318         return QLatin1Char('a');
2319 
2320     return qch;
2321 }
2322 
2323 void TerminalDisplay::setWordCharacters(const QString &wc)
2324 {
2325     _wordCharacters = wc;
2326 }
2327 
2328 void TerminalDisplay::setUsesMouse(bool on)
2329 {
2330     if (_mouseMarks != on) {
2331         _mouseMarks = on;
2332         setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor);
2333         Q_EMIT usesMouseChanged();
2334     }
2335 }
2336 bool TerminalDisplay::usesMouse() const
2337 {
2338     return _mouseMarks;
2339 }
2340 
2341 void TerminalDisplay::setBracketedPasteMode(bool on)
2342 {
2343     _bracketedPasteMode = on;
2344 }
2345 bool TerminalDisplay::bracketedPasteMode() const
2346 {
2347     return _bracketedPasteMode;
2348 }
2349 
2350 /* ------------------------------------------------------------------------- */
2351 /*                                                                           */
2352 /*                               Clipboard                                   */
2353 /*                                                                           */
2354 /* ------------------------------------------------------------------------- */
2355 
2356 #undef KeyPress
2357 
2358 void TerminalDisplay::emitSelection(bool useXselection, bool appendReturn)
2359 {
2360     if (!_screenWindow)
2361         return;
2362 
2363     // Paste Clipboard by simulating keypress events
2364     QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection : QClipboard::Clipboard);
2365     if (!text.isEmpty()) {
2366         text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
2367         text.replace(QLatin1Char('\n'), QLatin1Char('\r'));
2368 
2369         if (_trimPastedTrailingNewlines) {
2370             text.replace(QRegularExpression(QStringLiteral("\\r+$")), QString());
2371         }
2372 
2373         // TODO QMLTERMWIDGET We should expose this as a signal.
2374         //    if (_confirmMultilinePaste && text.contains(QLatin1Char('\r'))) {
2375         //        QMessageBox confirmation(this);
2376         //        confirmation.setWindowTitle(tr("Paste multiline text"));
2377         //        confirmation.setText(tr("Are you sure you want to paste this text?"));
2378         //        confirmation.setDetailedText(text);
2379         //        confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
2380         //        // Click "Show details..." to show those by default
2381         //        const auto buttons = confirmation.buttons();
2382         //        for( QAbstractButton * btn : buttons ) {
2383         //            if (confirmation.buttonRole(btn) == QMessageBox::ActionRole && btn->text() == QMessageBox::tr("Show Details...")) {
2384         //                Q_EMIT btn->clicked();
2385         //                break;
2386         //            }
2387         //        }
2388         //        confirmation.setDefaultButton(QMessageBox::Yes);
2389         //        confirmation.exec();
2390         //        if (confirmation.standardButton(confirmation.clickedButton()) != QMessageBox::Yes) {
2391         //            return;
2392         //        }
2393         //    }
2394 
2395         bracketText(text);
2396 
2397         // appendReturn is intentionally handled _after_ enclosing texts with brackets as
2398         // that feature is used to allow execution of commands immediately after paste.
2399         // Ref: https://bugs.kde.org/show_bug.cgi?id=16179
2400         // Ref: https://github.com/KDE/konsole/commit/83d365f2ebfe2e659c1e857a2f5f247c556ab571
2401         if (appendReturn) {
2402             text.append(QLatin1Char('\r'));
2403         }
2404 
2405         QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
2406         Q_EMIT keyPressedSignal(&e, true); // expose as a big fat keypress event
2407 
2408         _screenWindow->clearSelection();
2409 
2410         switch (mMotionAfterPasting) {
2411         case MoveStartScreenWindow:
2412             // Temporarily stop tracking output, or pasting contents triggers
2413             // ScreenWindow::notifyOutputChanged() and the latter scrolls the
2414             // terminal to the last line. It will be re-enabled when needed
2415             // (e.g., scrolling to the last line).
2416             _screenWindow->setTrackOutput(false);
2417             _screenWindow->scrollTo(0);
2418             break;
2419         case MoveEndScreenWindow:
2420             scrollToEnd();
2421             break;
2422         case NoMoveScreenWindow:
2423             break;
2424         }
2425     }
2426 }
2427 
2428 void TerminalDisplay::bracketText(QString &text) const
2429 {
2430     if (bracketedPasteMode() && !_disabledBracketedPasteMode) {
2431         text.prepend(QLatin1String("\033[200~"));
2432         text.append(QLatin1String("\033[201~"));
2433     }
2434 }
2435 
2436 void TerminalDisplay::setSelection(const QString &t)
2437 {
2438     if (QApplication::clipboard()->supportsSelection()) {
2439         QApplication::clipboard()->setText(t, QClipboard::Selection);
2440     }
2441 }
2442 
2443 void TerminalDisplay::copyClipboard()
2444 {
2445     if (!_screenWindow)
2446         return;
2447 
2448     QString text = _screenWindow->selectedText(_preserveLineBreaks);
2449     if (!text.isEmpty())
2450         QApplication::clipboard()->setText(text);
2451 }
2452 
2453 void TerminalDisplay::pasteClipboard()
2454 {
2455     emitSelection(false, false);
2456 }
2457 
2458 void TerminalDisplay::pasteSelection()
2459 {
2460     emitSelection(true, false);
2461 }
2462 
2463 void TerminalDisplay::setConfirmMultilinePaste(bool confirmMultilinePaste)
2464 {
2465     _confirmMultilinePaste = confirmMultilinePaste;
2466 }
2467 
2468 void TerminalDisplay::setTrimPastedTrailingNewlines(bool trimPastedTrailingNewlines)
2469 {
2470     _trimPastedTrailingNewlines = trimPastedTrailingNewlines;
2471 }
2472 
2473 /* ------------------------------------------------------------------------- */
2474 /*                                                                           */
2475 /*                                Keyboard                                   */
2476 /*                                                                           */
2477 /* ------------------------------------------------------------------------- */
2478 
2479 void TerminalDisplay::setFlowControlWarningEnabled(bool enable)
2480 {
2481     _flowControlWarningEnabled = enable;
2482 
2483     // if the dialog is currently visible and the flow control warning has
2484     // been disabled then hide the dialog
2485     if (!enable)
2486         outputSuspended(false);
2487 }
2488 
2489 void TerminalDisplay::setMotionAfterPasting(MotionAfterPasting action)
2490 {
2491     mMotionAfterPasting = action;
2492 }
2493 
2494 int TerminalDisplay::motionAfterPasting()
2495 {
2496     return mMotionAfterPasting;
2497 }
2498 
2499 void TerminalDisplay::keyPressEvent(QKeyEvent *event)
2500 {
2501     _actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't
2502                  // know where the current selection is.
2503 
2504     if (_hasBlinkingCursor) {
2505         _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
2506         if (_cursorBlinking)
2507             blinkCursorEvent();
2508         else
2509             _cursorBlinking = false;
2510     }
2511 
2512     Q_EMIT keyPressedSignal(event, false);
2513 
2514     event->accept();
2515 }
2516 
2517 void TerminalDisplay::inputMethodEvent(QInputMethodEvent *event)
2518 {
2519     QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString());
2520     Q_EMIT keyPressedSignal(&keyEvent, false);
2521 
2522     _inputMethodData.preeditString = event->preeditString();
2523     update(preeditRect() | _inputMethodData.previousPreeditRect);
2524 
2525     event->accept();
2526 }
2527 
2528 void TerminalDisplay::inputMethodQuery(QInputMethodQueryEvent *event)
2529 {
2530     event->setValue(Qt::ImEnabled, true);
2531     event->setValue(Qt::ImHints, QVariant(Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase));
2532     event->accept();
2533 }
2534 
2535 QVariant TerminalDisplay::inputMethodQuery(Qt::InputMethodQuery query) const
2536 {
2537     const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0, 0);
2538     switch (query) {
2539     case Qt::ImCursorRectangle:
2540         return imageToWidget(QRect(cursorPos.x(), cursorPos.y(), 1, 1));
2541         break;
2542     case Qt::ImFont:
2543         return font();
2544         break;
2545     case Qt::ImCursorPosition:
2546         // return the cursor position within the current line
2547         return cursorPos.x();
2548         break;
2549     case Qt::ImSurroundingText: {
2550         // return the text from the current line
2551         QString lineText;
2552         QTextStream stream(&lineText);
2553         PlainTextDecoder decoder;
2554         decoder.begin(&stream);
2555         decoder.decodeLine(std::span(&_image[loc(0, cursorPos.y())], _usedColumns), _lineProperties[cursorPos.y()]);
2556         decoder.end();
2557         return lineText;
2558     } break;
2559     case Qt::ImCurrentSelection:
2560         return QString();
2561         break;
2562     default:
2563         break;
2564     }
2565 
2566     return QVariant();
2567 }
2568 
2569 bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent *keyEvent)
2570 {
2571     int modifiers = keyEvent->modifiers();
2572 
2573     //  When a possible shortcut combination is pressed,
2574     //  Q_EMIT the overrideShortcutCheck() signal to allow the host
2575     //  to decide whether the terminal should override it or not.
2576     if (modifiers != Qt::NoModifier) {
2577         int modifierCount = 0;
2578         unsigned int currentModifier = Qt::ShiftModifier;
2579 
2580         while (currentModifier <= Qt::KeypadModifier) {
2581             if (modifiers & currentModifier)
2582                 modifierCount++;
2583             currentModifier <<= 1;
2584         }
2585         if (modifierCount < 2) {
2586             bool override = false;
2587             Q_EMIT overrideShortcutCheck(keyEvent, override);
2588             if (override) {
2589                 keyEvent->accept();
2590                 return true;
2591             }
2592         }
2593     }
2594 
2595     // Override any of the following shortcuts because
2596     // they are needed by the terminal
2597     int keyCode = keyEvent->key() | modifiers;
2598     switch (keyCode) {
2599     // list is taken from the QLineEdit::event() code
2600     case Qt::Key_Tab:
2601     case Qt::Key_Delete:
2602     case Qt::Key_Home:
2603     case Qt::Key_End:
2604     case Qt::Key_Backspace:
2605     case Qt::Key_Left:
2606     case Qt::Key_Right:
2607     case Qt::Key_Escape:
2608         keyEvent->accept();
2609         return true;
2610     }
2611     return false;
2612 }
2613 
2614 bool TerminalDisplay::event(QEvent *event)
2615 {
2616     bool eventHandled = false;
2617     switch (event->type()) {
2618     case QEvent::ShortcutOverride:
2619         eventHandled = handleShortcutOverrideEvent((QKeyEvent *)event);
2620         break;
2621     case QEvent::PaletteChange:
2622     case QEvent::ApplicationPaletteChange:
2623         _scrollBar->setPalette(QApplication::palette());
2624         break;
2625     case QEvent::InputMethodQuery:
2626         inputMethodQuery(static_cast<QInputMethodQueryEvent *>(event));
2627         eventHandled = true;
2628         break;
2629     default:
2630         break;
2631     }
2632     return eventHandled ? true : QQuickItem::event(event);
2633 }
2634 
2635 void TerminalDisplay::setBellMode(int mode)
2636 {
2637     _bellMode = mode;
2638 }
2639 
2640 void TerminalDisplay::enableBell()
2641 {
2642     _allowBell = true;
2643 }
2644 
2645 void TerminalDisplay::bell(const QString &message)
2646 {
2647     if (_bellMode == NoBell)
2648         return;
2649 
2650     // limit the rate at which bells can occur
2651     //...mainly for sound effects where rapid bells in sequence
2652     // produce a horrible noise
2653     if (_allowBell) {
2654         _allowBell = false;
2655         QTimer::singleShot(500, this, SLOT(enableBell()));
2656 
2657         if (_bellMode == SystemBeepBell) {
2658             QApplication::beep();
2659         } else if (_bellMode == NotifyBell) {
2660             Q_EMIT notifyBell(message);
2661         } else if (_bellMode == VisualBell) {
2662             swapColorTable();
2663             QTimer::singleShot(200, this, SLOT(swapColorTable()));
2664         }
2665     }
2666 }
2667 
2668 void TerminalDisplay::selectionChanged()
2669 {
2670     Q_EMIT copyAvailable(_screenWindow->selectedText(false).isEmpty() == false);
2671 }
2672 
2673 void TerminalDisplay::swapColorTable()
2674 {
2675     ColorEntry color = _colorTable[1];
2676     _colorTable[1] = _colorTable[0];
2677     _colorTable[0] = color;
2678     _colorsInverted = !_colorsInverted;
2679     update();
2680 }
2681 
2682 void TerminalDisplay::clearImage()
2683 {
2684     // We initialize _image[_imageSize] too. See makeImage()
2685     for (int i = 0; i <= _imageSize; i++) {
2686         _image[i].character = u' ';
2687         _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
2688         _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
2689         _image[i].rendition = DEFAULT_RENDITION;
2690     }
2691 }
2692 
2693 void TerminalDisplay::calcGeometry()
2694 {
2695     _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
2696     int scrollBarWidth = _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar) ? 0 : _scrollBar->width();
2697     switch (_scrollbarLocation) {
2698     case QTermWidget::NoScrollBar:
2699         _leftMargin = _leftBaseMargin;
2700         _contentWidth = contentsRect().width() - 2 * _leftBaseMargin;
2701         break;
2702     case QTermWidget::ScrollBarLeft:
2703         _leftMargin = _leftBaseMargin + scrollBarWidth;
2704         _contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth;
2705         _scrollBar->move(contentsRect().topLeft());
2706         break;
2707     case QTermWidget::ScrollBarRight:
2708         _leftMargin = _leftBaseMargin;
2709         _contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth;
2710         _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width() - 1, 0));
2711         break;
2712     }
2713 
2714     _topMargin = _topBaseMargin;
2715     _contentHeight = contentsRect().height() - 2 * _topBaseMargin + /* mysterious */ 1;
2716 
2717     if (!_isFixedSize) {
2718         // ensure that display is always at least one column wide
2719         _columns = qMax(1, qRound(_contentWidth / _fontWidth));
2720         _usedColumns = qMin(_usedColumns, _columns);
2721 
2722         // ensure that display is always at least one line high
2723         _lines = qMax(1, _contentHeight / qRound(_fontHeight));
2724         _usedLines = qMin(_usedLines, _lines);
2725     }
2726 }
2727 
2728 void TerminalDisplay::makeImage()
2729 {
2730     calcGeometry();
2731 
2732     // confirm that array will be of non-zero size, since the painting code
2733     // assumes a non-zero array length
2734     Q_ASSERT(_lines > 0 && _columns > 0);
2735     Q_ASSERT(_usedLines <= _lines && _usedColumns <= _columns);
2736 
2737     _imageSize = _lines * _columns;
2738 
2739     // We over-commit one character so that we can be more relaxed in dealing with
2740     // certain boundary conditions: _image[_imageSize] is a valid but unused position
2741     _image.resize(_imageSize + 1);
2742 
2743     clearImage();
2744 }
2745 
2746 // calculate the needed size, this must be synced with calcGeometry()
2747 void TerminalDisplay::setSize(int columns, int lines)
2748 {
2749     int scrollBarWidth =
2750         (_scrollBar->isHidden() || _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar)) ? 0 : _scrollBar->sizeHint().width();
2751     int horizontalMargin = 2 * _leftBaseMargin;
2752     int verticalMargin = 2 * _topBaseMargin;
2753 
2754     QSize newSize = QSize(horizontalMargin + scrollBarWidth + (columns * _fontWidth), verticalMargin + (lines * qRound(_fontHeight)));
2755 
2756     if (newSize != size()) {
2757         _size = newSize;
2758         // updateGeometry();
2759         // TODO Manage geometry change
2760     }
2761 }
2762 
2763 void TerminalDisplay::setFixedSize(int cols, int lins)
2764 {
2765     _isFixedSize = true;
2766 
2767     // ensure that display is at least one line by one column in size
2768     _columns = qMax(1, cols);
2769     _lines = qMax(1, lins);
2770     _usedColumns = qMin(_usedColumns, _columns);
2771     _usedLines = qMin(_usedLines, _lines);
2772 
2773     if (!_image.empty()) {
2774         _image.clear();
2775         makeImage();
2776     }
2777     setSize(cols, lins);
2778     // QWidget::setFixedSize(_size);
2779 }
2780 
2781 QSize TerminalDisplay::sizeHint() const
2782 {
2783     return _size;
2784 }
2785 
2786 /* --------------------------------------------------------------------- */
2787 /*                                                                       */
2788 /* Drag & Drop                                                           */
2789 /*                                                                       */
2790 /* --------------------------------------------------------------------- */
2791 
2792 void TerminalDisplay::dragEnterEvent(QDragEnterEvent *event)
2793 {
2794     if (event->mimeData()->hasFormat(QLatin1String("text/plain")))
2795         event->acceptProposedAction();
2796     if (event->mimeData()->urls().size())
2797         event->acceptProposedAction();
2798 }
2799 
2800 void TerminalDisplay::dropEvent(QDropEvent *event)
2801 {
2802     // KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
2803     QList<QUrl> urls = event->mimeData()->urls();
2804 
2805     QString dropText;
2806     if (!urls.isEmpty()) {
2807         // TODO/FIXME: escape or quote pasted things if neccessary...
2808         qDebug() << "TerminalDisplay: handling urls. It can be broken. Report any errors, please";
2809         for (int i = 0; i < urls.size(); i++) {
2810             // KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
2811             QUrl url = urls[i];
2812 
2813             QString urlText;
2814 
2815             if (url.isLocalFile())
2816                 urlText = url.path();
2817             else
2818                 urlText = url.toString();
2819 
2820             // in future it may be useful to be able to insert file names with drag-and-drop
2821             // without quoting them (this only affects paths with spaces in)
2822             // urlText = KShell::quoteArg(urlText);
2823 
2824             dropText += urlText;
2825 
2826             if (i != urls.size() - 1)
2827                 dropText += QLatin1Char(' ');
2828         }
2829     } else {
2830         dropText = event->mimeData()->text();
2831     }
2832 
2833     Q_EMIT sendStringToEmu(dropText.toLocal8Bit().constData());
2834 }
2835 
2836 void TerminalDisplay::doDrag()
2837 {
2838     dragInfo.state = diDragging;
2839     dragInfo.dragObject = new QDrag(this);
2840     QMimeData *mimeData = new QMimeData;
2841     mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
2842     dragInfo.dragObject->setMimeData(mimeData);
2843     dragInfo.dragObject->exec(Qt::CopyAction);
2844     // Don't delete the QTextDrag object.  Qt will delete it when it's done with it.
2845 }
2846 
2847 void TerminalDisplay::outputSuspended(bool suspended)
2848 {
2849     _outputSuspendedLabel->setVisible(suspended);
2850 }
2851 
2852 uint TerminalDisplay::lineSpacing() const
2853 {
2854     return _lineSpacing;
2855 }
2856 
2857 void TerminalDisplay::setLineSpacing(uint i)
2858 {
2859     if (i != _lineSpacing) {
2860         _lineSpacing = i;
2861         setVTFont(font()); // Trigger an update.
2862         Q_EMIT lineSpacingChanged();
2863     }
2864 }
2865 
2866 int TerminalDisplay::margin() const
2867 {
2868     return _topBaseMargin;
2869 }
2870 
2871 void TerminalDisplay::setMargin(int i)
2872 {
2873     _topBaseMargin = i;
2874     _leftBaseMargin = i;
2875 }
2876 
2877 // QMLTermWidget specific functions ///////////////////////////////////////////
2878 
2879 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2880 void TerminalDisplay::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
2881 #else
2882 void TerminalDisplay::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2883 #endif
2884 {
2885     if (newGeometry != oldGeometry) {
2886         resizeEvent(nullptr);
2887         update();
2888     }
2889 
2890 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2891     QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
2892 #else
2893     QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
2894 #endif
2895 }
2896 
2897 void TerminalDisplay::update(const QRegion &region)
2898 {
2899     Q_UNUSED(region);
2900     // TODO this function might be optimized
2901     //        const rects = region.rects();
2902     //        for (QRect rect : rects) {
2903     //            QQuickPaintedItem::update(rect.adjusted(-1, -1, +1, +1));
2904     //        }
2905     QQuickPaintedItem::update(region.boundingRect().adjusted(-1, -1, +1, +1));
2906 
2907     Q_EMIT imagePainted();
2908 }
2909 
2910 void TerminalDisplay::update()
2911 {
2912     QQuickPaintedItem::update(contentsRect());
2913 }
2914 
2915 QRect TerminalDisplay::contentsRect() const
2916 {
2917     return QRect(0, 0, this->width(), this->height());
2918 }
2919 
2920 QSize TerminalDisplay::size() const
2921 {
2922     return QSize(this->width(), this->height());
2923 }
2924 
2925 void TerminalDisplay::setSession(TerminalSession *session)
2926 {
2927     if (m_session != session) {
2928         //        qDebug() << "SetSession called";
2929         //        if (m_session)
2930         //            m_session->removeView(this);
2931 
2932         m_session = session;
2933 
2934         connect(this, &TerminalDisplay::copyAvailable, m_session, &TerminalSession::selectionChanged);
2935         connect(this, &TerminalDisplay::termGetFocus, m_session, &TerminalSession::termGetFocus);
2936         connect(this, &TerminalDisplay::termLostFocus, m_session, &TerminalSession::termLostFocus);
2937         connect(this, &TerminalDisplay::keyPressedSignal, m_session, &TerminalSession::termKeyPressed);
2938 
2939         m_session->addView(this);
2940 
2941         setRandomSeed(m_session->getRandomSeed());
2942         update();
2943         Q_EMIT sessionChanged();
2944     }
2945 }
2946 
2947 TerminalSession *TerminalDisplay::getSession()
2948 {
2949     return m_session;
2950 }
2951 
2952 QStringList TerminalDisplay::availableColorSchemes()
2953 {
2954     QStringList ret;
2955     const auto colorSchemes = ColorSchemeManager::instance()->allColorSchemes();
2956     std::transform(colorSchemes.begin(), colorSchemes.end(), std::back_inserter(ret), [](auto cs) {
2957         return cs->name();
2958     });
2959     return ret;
2960 }
2961 
2962 void TerminalDisplay::setColorScheme(const QString &name)
2963 {
2964     if (name != _colorScheme) {
2965         const ColorScheme *cs = [&, this]() {
2966             // avoid legacy (int) solution
2967             if (!availableColorSchemes().contains(name))
2968                 return ColorSchemeManager::instance()->defaultColorScheme();
2969             else
2970                 return ColorSchemeManager::instance()->findColorScheme(name);
2971         }();
2972 
2973         if (!cs) {
2974             qDebug() << "Cannot load color scheme: " << name;
2975             return;
2976         }
2977 
2978         setColorTable(cs->getColorTable());
2979 
2980         setFillColor(cs->backgroundColor());
2981         _colorScheme = name;
2982         Q_EMIT colorSchemeChanged();
2983     }
2984 }
2985 
2986 QString TerminalDisplay::colorScheme() const
2987 {
2988     return _colorScheme;
2989 }
2990 
2991 void TerminalDisplay::simulateKeyPress(int key, int modifiers, bool pressed, quint32 nativeScanCode, const QString &text)
2992 {
2993     Q_UNUSED(nativeScanCode);
2994     QEvent::Type type = pressed ? QEvent::KeyPress : QEvent::KeyRelease;
2995     QKeyEvent event = QKeyEvent(type, key, (Qt::KeyboardModifier)modifiers, text);
2996     keyPressedSignal(&event, false);
2997 }
2998 
2999 void TerminalDisplay::simulateKeySequence(const QKeySequence &keySequence)
3000 {
3001     for (int i = 0; i < keySequence.count(); ++i) {
3002         const Qt::Key key = Qt::Key(keySequence[i].key());
3003         const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i].keyboardModifiers());
3004         QKeyEvent eventPress = QKeyEvent(QEvent::KeyPress, key, modifiers, QString());
3005         keyPressedSignal(&eventPress, false);
3006     }
3007 }
3008 
3009 void TerminalDisplay::simulateWheel(int x, int y, int buttons, int modifiers, QPoint angleDelta)
3010 {
3011     QPoint pixelDelta; // pixelDelta is optional and can be null.
3012     QWheelEvent event(QPointF(x, y),
3013                       mapToGlobal(QPointF(x, y)),
3014                       pixelDelta,
3015                       angleDelta,
3016                       (Qt::MouseButtons)buttons,
3017                       (Qt::KeyboardModifiers)modifiers,
3018                       Qt::ScrollBegin,
3019                       false);
3020     wheelEvent(&event);
3021 }
3022 
3023 void TerminalDisplay::simulateMouseMove(int x, int y, int button, int buttons, int modifiers)
3024 {
3025     QMouseEvent event(QEvent::MouseMove, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3026     mouseMoveEvent(&event);
3027 }
3028 
3029 void TerminalDisplay::simulateMousePress(int x, int y, int button, int buttons, int modifiers)
3030 {
3031     QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3032     mousePressEvent(&event);
3033 }
3034 
3035 void TerminalDisplay::simulateMouseRelease(int x, int y, int button, int buttons, int modifiers)
3036 {
3037     QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3038     mouseReleaseEvent(&event);
3039 }
3040 
3041 void TerminalDisplay::simulateMouseDoubleClick(int x, int y, int button, int buttons, int modifiers)
3042 {
3043     QMouseEvent event(QEvent::MouseButtonDblClick, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3044     mouseDoubleClickEvent(&event);
3045 }
3046 
3047 QSize TerminalDisplay::getTerminalSize()
3048 {
3049     return QSize(_lines, _columns);
3050 }
3051 
3052 bool TerminalDisplay::getUsesMouse()
3053 {
3054     return !usesMouse();
3055 }
3056 
3057 int TerminalDisplay::getScrollbarValue()
3058 {
3059     return _scrollBar->value();
3060 }
3061 
3062 int TerminalDisplay::getScrollbarMaximum()
3063 {
3064     return _scrollBar->maximum();
3065 }
3066 
3067 int TerminalDisplay::getScrollbarMinimum()
3068 {
3069     return _scrollBar->minimum();
3070 }
3071 
3072 QSize TerminalDisplay::getFontMetrics()
3073 {
3074     return QSize(qRound(_fontWidth), qRound(_fontHeight));
3075 }
3076 
3077 void TerminalDisplay::setFullCursorHeight(bool val)
3078 {
3079     if (m_full_cursor_height != val) {
3080         m_full_cursor_height = val;
3081         Q_EMIT fullCursorHeightChanged();
3082     }
3083 }
3084 
3085 bool TerminalDisplay::fullCursorHeight() const
3086 {
3087     return m_full_cursor_height;
3088 }
3089 
3090 void TerminalDisplay::itemChange(ItemChange change, const ItemChangeData &value)
3091 {
3092     switch (change) {
3093     case QQuickItem::ItemVisibleHasChanged:
3094         if (value.boolValue && _screenWindow) {
3095             if (this->columns() != _screenWindow->columnCount() || this->lines() != _screenWindow->lineCount()) {
3096                 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth);
3097             }
3098         }
3099         break;
3100     default:
3101         break;
3102     }
3103 
3104     QQuickPaintedItem::itemChange(change, value);
3105 }