TerminalDisplay.cpp 99.8 KB
Newer Older
1
/*
2
3
    SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
    SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
4

5
    SPDX-License-Identifier: GPL-2.0-or-later
6
*/
7

8
// Own
9
#include "terminalDisplay/TerminalDisplay.h"
10
#include "KonsoleSettings.h"
11

12
// Config
13
#include "config-konsole.h"
14

Robert Knight's avatar
Robert Knight committed
15
// Qt
16
#include <QApplication>
17
18
19
20
#include <QClipboard>
#include <QKeyEvent>
#include <QEvent>
#include <QFileInfo>
21
#include <QVBoxLayout>
22
23
#include <QAction>
#include <QLabel>
24
#include <QMimeData>
25
26
#include <QPainter>
#include <QPixmap>
27
#include <QStyle>
28
#include <QTimer>
Alex Richardson's avatar
Alex Richardson committed
29
#include <QDrag>
30
#include <QDesktopServices>
31
#include <QAccessible>
32
#include <QElapsedTimer>
33

Robert Knight's avatar
Robert Knight committed
34
// KDE
35
#include <KShell>
Robert Knight's avatar
   
Robert Knight committed
36
#include <KColorScheme>
Robert Knight's avatar
Robert Knight committed
37
#include <KCursor>
38
#include <KLocalizedString>
Robert Knight's avatar
Robert Knight committed
39
#include <KNotification>
40
41
#include <KIO/DropJob>
#include <KJobWidgets>
42
#include <KMessageBox>
43
#include <KMessageWidget>
44
#include <KIO/StatJob>
Robert Knight's avatar
Robert Knight committed
45
46

// Konsole
47
#include "extras/CompositeWidgetFocusWatcher.h"
48
#include "extras/AutoScrollHandler.h"
49
50
51
52
53

#include "filterHotSpots/Filter.h"
#include "filterHotSpots/TerminalImageFilterChain.h"
#include "filterHotSpots/HotSpot.h"
#include "filterHotSpots/FileFilterHotspot.h"
54
55
#include "filterHotSpots/EscapeSequenceUrlFilter.h"
#include "filterHotSpots/EscapeSequenceUrlFilterHotSpot.h"
56

57
#include "konsoledebug.h"
58
#include "../decoders/PlainTextDecoder.h"
59
#include "Screen.h"
60
#include "../characters/ExtendedCharTable.h"
61
#include "../widgets/TerminalDisplayAccessible.h"
62
#include "session/SessionController.h"
63
64
#include "session/SessionManager.h"
#include "session/Session.h"
65
#include "WindowSystemInfo.h"
66
#include "widgets/IncrementalSearchBar.h"
67
#include "profile/Profile.h"
68
#include "ViewManager.h" // for colorSchemeForProfile. // TODO: Rewrite this.
69
#include "../characters/LineBlockCharacters.h"
70
#include "PrintOptions.h"
71
#include "../widgets/KonsolePrintManager.h"
72
#include "EscapeSequenceUrlExtractor.h"
73

74
75
76
#include "TerminalPainter.h"
#include "TerminalScrollBar.h"
#include "TerminalColor.h"
77
#include "TerminalFonts.h"
78

79
80
using namespace Konsole;

81
inline int TerminalDisplay::loc(int x, int y) const {
82
83
84
85
86
87
88
    if (y < 0 || y > _lines) {
        qDebug() << "Y: " << y << "Lines" << _lines;
    }
    if (x < 0 || x > _columns ) {
        qDebug() << "X" << x << "Columns" << _columns;
    }

89
90
91
92
93
94
95
96
    Q_ASSERT(y >= 0 && y < _lines);
    Q_ASSERT(x >= 0 && x < _columns);
    x = qBound(0, x, _columns - 1);
    y = qBound(0, y, _lines - 1);

    return y * _columns + x;
}

97
98
99
100
101
102
103
104
105
106
107
108
109
110
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                Colors                                     */
/*                                                                           */
/* ------------------------------------------------------------------------- */

/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)

   Code        0       1       2       3       4       5       6       7
   ----------- ------- ------- ------- ------- ------- ------- ------- -------
   ANSI  (bgr) Black   Red     Green   Yellow  Blue    Magenta Cyan    White
   IBMPC (rgb) Black   Blue    Green   Cyan    Red     Magenta Yellow  White
*/

111
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
112
{
113
    // disconnect existing screen window if any
114
    if (!_screenWindow.isNull()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
115
        disconnect(_screenWindow , nullptr , this , nullptr);
116
117
    }

118
    _screenWindow = window;
119

120
    if (!_screenWindow.isNull()) {
121
122
        connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateImage);
        connect(_screenWindow.data() , &Konsole::ScreenWindow::currentResultLineChanged , this , &Konsole::TerminalDisplay::updateImage);
123
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
124
125
            _filterUpdateRequired = true;
        });
126
        connect(_screenWindow.data(), &Konsole::ScreenWindow::screenAboutToChange, this, [this]() {
127
128
129
            _iPntSel = QPoint(-1, -1);
            _pntSel = QPoint(-1, -1);
            _tripleSelBegin = QPoint(-1, -1);
130
        });
131
        connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
132
133
            _filterUpdateRequired = true;
        });
134
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, []() {
135
136
            QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
        });
Jekyll Wu's avatar
Jekyll Wu committed
137
        _screenWindow->setWindowLines(_lines);
138
139
140

        auto profile = SessionManager::instance()->sessionProfile(_sessionController->session());
        _screenWindow->screen()->urlExtractor()->setAllowedLinkSchema(profile->escapedLinksSchema());
141
        _screenWindow->screen()->setReflowLines(profile->property<bool>(Profile::ReflowLines));
142
    }
143
144
}

145
146
147
148
149
150
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Accessibility                                     */
/*                                                                           */
/* ------------------------------------------------------------------------- */

151
152
namespace Konsole
{
Alex Richardson's avatar
Alex Richardson committed
153
154

#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
155
/**
156
 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
Kurt Hindenburg's avatar
Kurt Hindenburg committed
157
158
159
160
161
 * for the TerminalDisplay.
 */
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
{
    Q_UNUSED(key)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
162
    if (auto *display = qobject_cast<TerminalDisplay*>(object)) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
163
        return new TerminalDisplayAccessible(display);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
164
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
165
    return nullptr;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
166
}
Alex Richardson's avatar
Alex Richardson committed
167
168

#endif
169
170
}

171
172
173
174
175
176
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

177
TerminalDisplay::TerminalDisplay(QWidget* parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
178
    : QWidget(parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
179
    , _screenWindow(nullptr)
Jekyll Wu's avatar
Jekyll Wu committed
180
    , _bellMasked(false)
181
    , _verticalLayout(new QVBoxLayout(this))
Kurt Hindenburg's avatar
Kurt Hindenburg committed
182
183
    , _lines(1)
    , _columns(1)
184
185
    , _prevCharacterLine(-1)
    , _prevCharacterColumn(-1)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
186
187
    , _usedLines(1)
    , _usedColumns(1)
188
    , _contentRect(QRect())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
189
    , _image(nullptr)
190
191
    , _imageSize(0)
    , _lineProperties(QVector<LineProperty>())
192
    , _randomSeed(0)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
193
194
195
    , _resizing(false)
    , _showTerminalSizeHint(true)
    , _bidiEnabled(false)
196
    , _usesMouseTracking(false)
197
    , _bracketedPasteMode(false)
198
199
200
    , _iPntSel(QPoint(-1, -1))
    , _pntSel(QPoint(-1, -1))
    , _tripleSelBegin(QPoint(-1, -1))
Kurt Hindenburg's avatar
Kurt Hindenburg committed
201
202
203
    , _actSel(0)
    , _wordSelectionMode(false)
    , _lineSelectionMode(false)
204
    , _preserveLineBreaks(true)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
205
    , _columnSelectionMode(false)
206
    , _autoCopySelectedText(false)
207
    , _copyTextAsHTML(true)
208
    , _middleClickPasteMode(Enum::PasteFromX11Selection)
209
    , _wordCharacters(QStringLiteral(":@-./_~"))
210
    , _bellMode(Enum::NotifyBell)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
211
212
213
214
215
    , _allowBlinkingText(true)
    , _allowBlinkingCursor(false)
    , _textBlinking(false)
    , _cursorBlinking(false)
    , _hasTextBlinker(false)
216
    , _openLinksByDirectClick(false)
217
    , _ctrlRequiredForDrag(true)
218
    , _dropUrlsAsText(false)
219
    , _tripleClickMode(Enum::SelectWholeLine)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
220
    , _possibleTripleClick(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
221
222
    , _resizeWidget(nullptr)
    , _resizeTimer(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
223
    , _flowControlWarningEnabled(false)
224
    , _outputSuspendedMessageWidget(nullptr)
225
226
    , _size(QSize())
    , _wallpaper(nullptr)
227
    , _filterChain(new TerminalImageFilterChain(this))
228
    , _filterUpdateRequired(true)
229
    , _cursorShape(Enum::BlockCursor)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
230
    , _sessionController(nullptr)
231
    , _trimLeadingSpaces(false)
232
    , _trimTrailingSpaces(false)
233
    , _mouseWheelZoom(false)
234
235
    , _margin(1)
    , _centerContents(false)
236
    , _readOnlyMessageWidget(nullptr)
237
    , _readOnly(false)
238
    , _dimWhenInactive(false)
239
    , _scrollWheelState(ScrollState())
240
    , _searchBar(new IncrementalSearchBar(this))
241
    , _headerBar(new TerminalHeaderBar(this))
242
    , _searchResultRect(QRect())
243
    , _drawOverlay(false)
244
    , _scrollBar(nullptr)
245
    , _terminalColor(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
246
247
248
249
250
{
    // terminal applications are not designed with Right-To-Left in mind,
    // so the layout is forced to Left-To-Right
    setLayoutDirection(Qt::LeftToRight);

251
    _contentRect = QRect(_margin, _margin, 1, 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
252
253

    // create scroll bar for scrolling output up and down
254
    _scrollBar = new TerminalScrollBar(this);
255
    _scrollBar->setAutoFillBackground(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
256
    // set the scroll bar's slider to occupy the whole area of the scroll bar initially
257
    _scrollBar->setScroll(0, 0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
258
    _scrollBar->setCursor(Qt::ArrowCursor);
259
    _headerBar->setCursor(Qt::ArrowCursor);
260
    connect(_headerBar, &TerminalHeaderBar::requestToggleExpansion, this, &Konsole::TerminalDisplay::requestToggleExpansion);
261
    connect(_headerBar, &TerminalHeaderBar::requestMoveToNewTab, this, [this]{requestMoveToNewTab(this);});
Laurent Montel's avatar
Laurent Montel committed
262
    connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
263

264
265
266
    // setup timers for blinking text
    _blinkTextTimer = new QTimer(this);
    _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
267
    connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
268
269
270
271

    // setup timers for blinking cursor
    _blinkCursorTimer = new QTimer(this);
    _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
272
    connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
273
274
275
276
277

    // hide mouse cursor on keystroke or idle
    KCursor::setAutoHideCursor(this, true);
    setMouseTracking(true);

278
    setUsesMouseTracking(false);
279
    setBracketedPasteMode(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
280
281
282
283
284
285
286
287
288
289
290
291
292
293

    // Enable drag and drop support
    setAcceptDrops(true);
    _dragInfo.state = diNone;

    setFocusPolicy(Qt::WheelFocus);

    // enable input method support
    setAttribute(Qt::WA_InputMethodEnabled, true);

    // this is an important optimization, it tells Qt
    // that TerminalDisplay will handle repainting its entire area.
    setAttribute(Qt::WA_OpaquePaintEvent);

294
    // Add the stretch item once, the KMessageWidgets are inserted at index 0.
295
    _verticalLayout->addWidget(_headerBar);
296
297
    _verticalLayout->addStretch();
    _verticalLayout->setSpacing(0);
298
    _verticalLayout->setContentsMargins(0, 0, 0, 0);
299
    setLayout(_verticalLayout);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
300
    new AutoScrollHandler(this);
301
302

    // Keep this last
303
    CompositeWidgetFocusWatcher *focusWatcher = new CompositeWidgetFocusWatcher(this);
304
305
306
307
308
309
310
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            this, [this](bool focused) {_hasCompositeFocus = focused;});
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            this, &TerminalDisplay::compositeFocusChanged);
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            _headerBar, &TerminalHeaderBar::setFocusIndicatorState);

311
#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
312
    QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
313
#endif
314
315

    connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TerminalDisplay::setupHeaderVisibility);
316

317
318
319
    _terminalColor = new TerminalColor(this);
    connect(_terminalColor, &TerminalColor::onPalette, _scrollBar, &TerminalScrollBar::setPalette);

320
321
    _terminalFont = new TerminalFont(this);

322
    _terminalPainter = new TerminalPainter(this);
323
324
325
326
327
328
329
    connect(this, &TerminalDisplay::drawContents, _terminalPainter, &TerminalPainter::drawContents);
    connect(this, &TerminalDisplay::drawCurrentResultRect, _terminalPainter, &TerminalPainter::drawCurrentResultRect);
    connect(this, &TerminalDisplay::highlightScrolledLines, _terminalPainter, &TerminalPainter::highlightScrolledLines);
    connect(this, &TerminalDisplay::highlightScrolledLinesRegion, _terminalPainter, &TerminalPainter::highlightScrolledLinesRegion);
    connect(this, &TerminalDisplay::drawBackground, _terminalPainter, &TerminalPainter::drawBackground);
    connect(this, &TerminalDisplay::drawCharacters, _terminalPainter, &TerminalPainter::drawCharacters);
    connect(this, &TerminalDisplay::drawInputMethodPreeditString, _terminalPainter, &TerminalPainter::drawInputMethodPreeditString);
330

331
332
    auto ldrawBackground = [this](QPainter &painter,
            const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting) {
Ahmad Samir's avatar
Ahmad Samir committed
333
        Q_EMIT drawBackground(painter, rect, backgroundColor, useOpacitySetting);
334
    };
335
    auto ldrawContents = [this](QPainter &paint, const QRect &rect, bool friendly) {
Ahmad Samir's avatar
Ahmad Samir committed
336
        Q_EMIT drawContents(_image, paint, rect, friendly, _imageSize, _bidiEnabled, _lineProperties);
337
338
    };
    auto lgetBackgroundColor = [this]() {
339
        return _terminalColor->backgroundColor();
340
    };
341
342

    _printManager.reset(new KonsolePrintManager(ldrawBackground, ldrawContents, lgetBackgroundColor));
343
344
}

345
TerminalDisplay::~TerminalDisplay()
346
{
347
348
349
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

350
351
    delete[] _image;
    delete _filterChain;
352
353
}

354
355
356
357
358
359
void TerminalDisplay::setupHeaderVisibility()
{
    _headerBar->applyVisibilitySettings();
    calcGeometry();
}

360
361
362
363
364
365
void TerminalDisplay::hideDragTarget()
{
    _drawOverlay = false;
    update();
}

366
void TerminalDisplay::showDragTarget(const QPoint& cursorPos)
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
{
    using EdgeDistance = std::pair<int, Qt::Edge>;
    auto closerToEdge = std::min<EdgeDistance>(
        {
            {cursorPos.x(), Qt::LeftEdge},
            {cursorPos.y(), Qt::TopEdge},
            {width() - cursorPos.x(), Qt::RightEdge},
            {height() - cursorPos.y(), Qt::BottomEdge}
        },
        [](const EdgeDistance& left, const EdgeDistance& right) -> bool {
            return left.first < right.first;
        }
    );
    if (_overlayEdge == closerToEdge.second) {
        return;
    }
    _overlayEdge = closerToEdge.second;
    _drawOverlay = true;
    update();
}

388
389
390
391
392
393
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                             Display Operations                            */
/*                                                                           */
/* ------------------------------------------------------------------------- */

394
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
395
396
397
{
    _cursorShape = shape;
}
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415

void TerminalDisplay::setCursorStyle(Enum::CursorShapeEnum shape, bool isBlinking)
{
    setKeyboardCursorShape(shape);

    setBlinkingCursorEnabled(isBlinking);

    // when the cursor shape and blinking state are changed via the
    // Set Cursor Style (DECSCUSR) escape sequences in vim, and if the
    // cursor isn't set to blink, the cursor shape doesn't actually
    // change until the cursor is moved by the user; calling update()
    // makes the cursor shape get updated sooner.
    if (!isBlinking) {
        update();
    }
}
void TerminalDisplay::resetCursorStyle()
{
416
    Q_ASSERT(_sessionController != nullptr);
417
    Q_ASSERT(!_sessionController->session().isNull());
418

419
    Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(_sessionController->session());
420

421
    if (currentProfile != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
422
        auto shape = static_cast<Enum::CursorShapeEnum>(currentProfile->property<int>(Profile::CursorShape));
423
424
425

        setKeyboardCursorShape(shape);
        setBlinkingCursorEnabled(currentProfile->blinkingCursorEnabled());
426
427
428
    }
}

429
void TerminalDisplay::setWallpaper(const ColorSchemeWallpaper::Ptr &p)
430
{
431
    _wallpaper = p;
432
433
}

434
void TerminalDisplay::scrollScreenWindow(enum ScreenWindow::RelativeScrollMode mode, int amount)
435
436
437
438
439
440
441
{
    _screenWindow->scrollBy(mode, amount, _scrollBar->scrollFullPage());
    _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
    updateImage();
    viewScrolledByUser();
}

442
443
444
445
446
447
448
449
450
void TerminalDisplay::setRandomSeed(uint randomSeed)
{
    _randomSeed = randomSeed;
}
uint TerminalDisplay::randomSeed() const
{
    return _randomSeed;
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
451
void TerminalDisplay::processFilters()
452
{
453

454
    if (_screenWindow.isNull()) {
455
456
457
458
        return;
    }

    if (!_filterUpdateRequired) {
459
        return;
460
    }
461

462
    const QRegion preUpdateHotSpots = _filterChain->hotSpotRegion();
463
464
465
466

    // use _screenWindow->getImage() here rather than _image because
    // other classes may call processFilters() when this display's
    // ScreenWindow emits a scrolled() signal - which will happen before
Kurt Hindenburg's avatar
Kurt Hindenburg committed
467
    // updateImage() is called on the display and therefore _image is
468
    // out of date at this point
Kurt Hindenburg's avatar
Kurt Hindenburg committed
469
470
471
472
    _filterChain->setImage(_screenWindow->getImage(),
                           _screenWindow->windowLines(),
                           _screenWindow->windowColumns(),
                           _screenWindow->getLineProperties());
473
    _filterChain->process();
474

475
    const QRegion postUpdateHotSpots = _filterChain->hotSpotRegion();
476

Kurt Hindenburg's avatar
Kurt Hindenburg committed
477
    update(preUpdateHotSpots | postUpdateHotSpots);
478
    _filterUpdateRequired = false;
479
480
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
481
void TerminalDisplay::updateImage()
482
{
483
    if (_screenWindow.isNull()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
484
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
485
    }
486

487
488
489
    // Better control over screen resizing visual glitches
    _screenWindow->updateCurrentLine();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
490
491
492
    // optimization - scroll the existing image where possible and
    // avoid expensive text drawing for parts of the image that
    // can simply be moved up or down
Yuri Chornoivan's avatar
Yuri Chornoivan committed
493
    // disable this shortcut for transparent konsole with scaled pixels, otherwise we get rendering artifacts, see BUG 350651
494
    if (!(WindowSystemInfo::HAVE_TRANSPARENCY && (qApp->devicePixelRatio() > 1.0)) && _wallpaper->isNull() && !_searchBar->isVisible()) {
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
        // if the flow control warning is enabled this will interfere with the
        // scrolling optimizations and cause artifacts.  the simple solution here
        // is to just disable the optimization whilst it is visible
        if (!((_outputSuspendedMessageWidget != nullptr) && _outputSuspendedMessageWidget->isVisible()) &&
            !((_readOnlyMessageWidget != nullptr) && _readOnlyMessageWidget->isVisible())) {

            // hide terminal size label to prevent it being scrolled and show again after scroll
            const bool viewResizeWidget = (_resizeWidget != nullptr) && _resizeWidget->isVisible();
            if (viewResizeWidget) {
                _resizeWidget->hide();
            }
            _scrollBar->scrollImage(_screenWindow->scrollCount(), _screenWindow->scrollRegion(), _image, _imageSize);
            if (viewResizeWidget) {
                _resizeWidget->show();
            }
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
511
    }
512

513
    if (_image == nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
514
515
516
517
        // Create _image.
        // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
        updateImageSize();
    }
518

Kurt Hindenburg's avatar
Kurt Hindenburg committed
519
    Character* const newimg = _screenWindow->getImage();
Jekyll Wu's avatar
Jekyll Wu committed
520
521
    const int lines = _screenWindow->windowLines();
    const int columns = _screenWindow->windowColumns();
522
    QVector<LineProperty> newLineProperties = _screenWindow->getLineProperties();
523

524
    _scrollBar->setScroll(_screenWindow->currentLine() , _screenWindow->lineCount());
525

Tomaz  Canabrava's avatar
Tomaz Canabrava committed
526
527
    Q_ASSERT(_usedLines <= _lines);
    Q_ASSERT(_usedColumns <= _columns);
528

529
530
531
    int y;
    int x;
    int len;
532

Jekyll Wu's avatar
Jekyll Wu committed
533
534
535
    const QPoint tL  = contentsRect().topLeft();
    const int    tLx = tL.x();
    const int    tLy = tL.y();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
536
    _hasTextBlinker = false;
537

Kurt Hindenburg's avatar
Kurt Hindenburg committed
538
    CharacterColor cf;       // undefined
539

Carlos Alves's avatar
Carlos Alves committed
540
541
    const int linesToUpdate = qBound(0, lines, _lines);
    const int columnsToUpdate = qBound(0, columns, _columns);
542

Kurt Hindenburg's avatar
Kurt Hindenburg committed
543
    auto dirtyMask = new char[columnsToUpdate + 2];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
544
    QRegion dirtyRegion;
545

Kurt Hindenburg's avatar
Kurt Hindenburg committed
546
547
548
549
    // debugging variable, this records the number of lines that are found to
    // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
    // which therefore need to be repainted
    int dirtyLineCount = 0;
550

Kurt Hindenburg's avatar
Kurt Hindenburg committed
551
    for (y = 0; y < linesToUpdate; ++y) {
Tomaz  Canabrava's avatar
Tomaz Canabrava committed
552
        const Character* currentLine = &_image[y * _columns];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
553
        const Character* const newLine = &newimg[y * columns];
554

Kurt Hindenburg's avatar
Kurt Hindenburg committed
555
        bool updateLine = false;
556

Kurt Hindenburg's avatar
Kurt Hindenburg committed
557
        // The dirty mask indicates which characters need repainting. We also
558
        // mark surrounding neighbors dirty, in case the character exceeds
Kurt Hindenburg's avatar
Kurt Hindenburg committed
559
560
        // its cell boundaries
        memset(dirtyMask, 0, columnsToUpdate + 2);
561

Kurt Hindenburg's avatar
Kurt Hindenburg committed
562
563
        for (x = 0 ; x < columnsToUpdate ; ++x) {
            if (newLine[x] != currentLine[x]) {
564
                dirtyMask[x] = 1;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
565
            }
566
        }
567

Kurt Hindenburg's avatar
Kurt Hindenburg committed
568
        if (!_resizing) { // not while _resizing, we're expecting a paintEvent
Kurt Hindenburg's avatar
Kurt Hindenburg committed
569
570
571
572
573
574
            for (x = 0; x < columnsToUpdate; ++x) {
                _hasTextBlinker |= (newLine[x].rendition & RE_BLINK);

                // Start drawing if this character or the next one differs.
                // We also take the next one into account to handle the situation
                // where characters exceed their cell width.
575
                if (dirtyMask[x] != 0) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
576
                    if (newLine[x + 0].character == 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
577
                        continue;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
578
                    }
579
                    const bool lineDraw = LineBlockCharacters::canDraw(newLine[x + 0].character);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
580
                    const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character == 0);
581
                    const RenditionFlags cr = newLine[x].rendition;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
582
                    const CharacterColor clipboard = newLine[x].backgroundColor;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
583
584
585
                    if (newLine[x].foregroundColor != cf) {
                        cf = newLine[x].foregroundColor;
                    }
Jekyll Wu's avatar
Jekyll Wu committed
586
                    const int lln = columnsToUpdate - x;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
587
588
589
                    for (len = 1; len < lln; ++len) {
                        const Character& ch = newLine[x + len];

Kurt Hindenburg's avatar
Kurt Hindenburg committed
590
                        if (ch.character == 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
591
                            continue; // Skip trailing part of multi-col chars.
Kurt Hindenburg's avatar
Kurt Hindenburg committed
592
                        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
593

Jekyll Wu's avatar
Jekyll Wu committed
594
                        const bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character == 0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
595
596
597
598

                        if (ch.foregroundColor != cf ||
                                ch.backgroundColor != clipboard ||
                                (ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) ||
599
                                (dirtyMask[x + len] == 0) ||
600
                                LineBlockCharacters::canDraw(ch.character) != lineDraw ||
Kurt Hindenburg's avatar
Kurt Hindenburg committed
601
                                nextIsDoubleWidth != doubleWidth) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
602
                            break;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
603
                        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
604
605
606
607
608
                    }
                    updateLine = true;
                    x += len - 1;
                }
            }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
609
        }
610

611
612
        if (y >= _lineProperties.count() || y >= newLineProperties.count() || _lineProperties[y] != newLineProperties[y]) {
            updateLine = true;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
613
        }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
614
615
616
617
618
619
620
621

        // if the characters on the line are different in the old and the new _image
        // then this line must be repainted.
        if (updateLine) {
            dirtyLineCount++;

            // add the area occupied by this line to the region which needs to be
            // repainted
622
            QRect dirtyRect = QRect(_contentRect.left() + tLx ,
623
624
625
                                    _contentRect.top() + tLy + _terminalFont->fontHeight() * y ,
                                    _terminalFont->fontWidth() * columnsToUpdate ,
                                    _terminalFont->fontHeight());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
626
627
628

            dirtyRegion |= dirtyRect;
        }
629

Kurt Hindenburg's avatar
Kurt Hindenburg committed
630
631
632
        // replace the line of characters in the old _image with the
        // current line of the new _image
        memcpy((void*)currentLine, (const void*)newLine, columnsToUpdate * sizeof(Character));
633
    }
634
    _lineProperties = newLineProperties;
635

Kurt Hindenburg's avatar
Kurt Hindenburg committed
636
637
638
    // if the new _image is smaller than the previous _image, then ensure that the area
    // outside the new _image is cleared
    if (linesToUpdate < _usedLines) {
639
        dirtyRegion |= QRect(_contentRect.left() + tLx ,
640
641
642
                             _contentRect.top() + tLy + _terminalFont->fontHeight() * linesToUpdate ,
                             _terminalFont->fontWidth() * _columns ,
                             _terminalFont->fontHeight() * (_usedLines - linesToUpdate));
643
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
644
    _usedLines = linesToUpdate;
645

Kurt Hindenburg's avatar
Kurt Hindenburg committed
646
    if (columnsToUpdate < _usedColumns) {
647
        dirtyRegion |= QRect(_contentRect.left() + tLx + columnsToUpdate * _terminalFont->fontWidth(),
648
                             _contentRect.top() + tLy ,
649
650
                             _terminalFont->fontWidth() * (_usedColumns - columnsToUpdate) ,
                             _terminalFont->fontHeight() * _lines);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
651
652
    }
    _usedColumns = columnsToUpdate;
653

Kurt Hindenburg's avatar
Kurt Hindenburg committed
654
    dirtyRegion |= _inputMethodData.previousPreeditRect;
655

656
    if ((_screenWindow->currentResultLine() != -1) && (_screenWindow->scrollCount() != 0)) {
657
658
659
        // De-highlight previous result region
        dirtyRegion |= _searchResultRect;
        // Highlight new result region
660
661
        dirtyRegion |= QRect(0, _contentRect.top() + (_screenWindow->currentResultLine() - _screenWindow->currentLine()) * _terminalFont->fontHeight(),
                             _columns * _terminalFont->fontWidth(), _terminalFont->fontHeight());
662
663
    }

664
    if (_scrollBar->highlightScrolledLines().isEnabled()) {
Carlos Alves's avatar
Carlos Alves committed
665
        dirtyRegion |= Q_EMIT highlightScrolledLinesRegion(dirtyRegion.isEmpty(), _scrollBar);
666
667
    }
    _screenWindow->resetScrollCount();
668

Kurt Hindenburg's avatar
Kurt Hindenburg committed
669
670
    // update the parts of the display which have changed
    update(dirtyRegion);
Stephan Kulow's avatar
Stephan Kulow committed
671

672
673
674
    if (_allowBlinkingText && _hasTextBlinker && !_blinkTextTimer->isActive()) {
        _blinkTextTimer->start();
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
675
676
677
678
679
    if (!_hasTextBlinker && _blinkTextTimer->isActive()) {
        _blinkTextTimer->stop();
        _textBlinking = false;
    }
    delete[] dirtyMask;
680

681
#ifndef QT_NO_ACCESSIBILITY
682
683
684
685
    QAccessibleEvent dataChangeEvent(this, QAccessible::VisibleDataChanged);
    QAccessible::updateAccessibility(&dataChangeEvent);
    QAccessibleTextCursorEvent cursorEvent(this, _usedColumns * screenWindow()->screen()->getCursorY() + screenWindow()->screen()->getCursorX());
    QAccessible::updateAccessibility(&cursorEvent);
686
#endif
687
688
}

689
void TerminalDisplay::showResizeNotification()
690
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
691
    if (_showTerminalSizeHint && isVisible()) {
692
        if (_resizeWidget == nullptr) {
693
            _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
694
            _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().boundingRect(i18n("Size: XXX x XXX")).width());
695
696
697
            _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
            _resizeWidget->setAlignment(Qt::AlignCenter);

698
            _resizeWidget->setStyleSheet(QStringLiteral("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)"));
699
700

            _resizeTimer = new QTimer(this);
Jekyll Wu's avatar
Jekyll Wu committed
701
            _resizeTimer->setInterval(SIZE_HINT_DURATION);
702
            _resizeTimer->setSingleShot(true);
703
            connect(_resizeTimer, &QTimer::timeout, _resizeWidget, &QLabel::hide);
704
705
706
        }
        QString sizeStr = i18n("Size: %1 x %2", _columns, _lines);
        _resizeWidget->setText(sizeStr);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
707
708
        _resizeWidget->move((width() - _resizeWidget->width()) / 2,
                            (height() - _resizeWidget->height()) / 2 + 20);
709
        _resizeWidget->show();
Jekyll Wu's avatar
Jekyll Wu committed
710
        _resizeTimer->start();
711
    }
712
713
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
714
void TerminalDisplay::paintEvent(QPaintEvent* pe)
715
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
716
    QPainter paint(this);
717

718
719
    // Determine which characters should be repainted (1 region unit = 1 character)
    QRegion dirtyImageRegion;
720
721
722
    const QRegion region = pe->region() & contentsRect();

    for (const QRect &rect : region) {
723
        dirtyImageRegion += widgetToImage(rect);
Ahmad Samir's avatar
Ahmad Samir committed
724
        Q_EMIT drawBackground(paint, rect, _terminalColor->backgroundColor(), true /* use opacity setting */);
725
    }
726

727
    if (_displayVerticalLine) {
728
729
        const int fontWidth = _terminalFont->fontWidth();
        const int x = (fontWidth/2) + (fontWidth * _displayVerticalLineAtChar);
730
        const QColor lineColor = _terminalColor->foregroundColor();
731
732
733
734
735

        paint.setPen(lineColor);
        paint.drawLine(QPoint(x, 0), QPoint(x, height()));
    }

736
737
    // only turn on text anti-aliasing, never turn on normal antialiasing
    // set https://bugreports.qt.io/browse/QTBUG-66036
738
    paint.setRenderHint(QPainter::TextAntialiasing, _terminalFont->antialiasText());
739

740
    for (const QRect &rect : qAsConst(dirtyImageRegion)) {
Ahmad Samir's avatar
Ahmad Samir committed
741
        Q_EMIT drawContents(_image, paint, rect, false, _imageSize, _bidiEnabled, _lineProperties);
742
    }
Ahmad Samir's avatar
Ahmad Samir committed
743
    Q_EMIT drawCurrentResultRect(paint, _searchResultRect);
744
    if (_scrollBar->highlightScrolledLines().isEnabled()) {
Ahmad Samir's avatar
Ahmad Samir committed
745
        Q_EMIT highlightScrolledLines(paint, _scrollBar->highlightScrolledLines().isTimerActive(), _scrollBar->highlightScrolledLines().rect());
Kurt Hindenburg's avatar
Kurt Hindenburg committed
746
    }
Ahmad Samir's avatar
Ahmad Samir committed
747
    Q_EMIT drawInputMethodPreeditString(paint, preeditRect(), _inputMethodData, _image);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
748
    paintFilters(paint);
749
750

    const bool drawDimmed = _dimWhenInactive && !hasFocus();
751
752
753
    if (drawDimmed) {
        const QColor dimColor(0, 0, 0, _dimValue);
        for (const QRect &rect : region) {
754
755
756
            paint.fillRect(rect, dimColor);
        }
    }
757
758
759
760
761
762
763
764
765
766
767
768
769

    if (_drawOverlay) {
        const auto y = _headerBar->isVisible() ? _headerBar->height() : 0;
        const auto rect = _overlayEdge == Qt::LeftEdge ? QRect(0, y, width() / 2, height())
                  : _overlayEdge == Qt::TopEdge ? QRect(0, y, width(), height() / 2)
                  : _overlayEdge == Qt::RightEdge ? QRect(width() - width() / 2, y, width() / 2, height())
                  : QRect(0, height() - height() / 2, width(), height() / 2);

        paint.setRenderHint(QPainter::Antialiasing);
        paint.setPen(Qt::NoPen);
        paint.setBrush(QColor(100,100,100, 127));
        paint.drawRect(rect);
    }
770
771
}

772
773
QPoint TerminalDisplay::cursorPosition() const
{
774
    if (!_screenWindow.isNull()) {
775
        return _screenWindow->cursorPosition();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
776
    } else {
777
        return {0, 0};
Kurt Hindenburg's avatar
Kurt Hindenburg committed
778
    }
779
780
}

781
bool TerminalDisplay::isCursorOnDisplay() const
782
783
784
785
786
{
    return cursorPosition().x() < _columns &&
           cursorPosition().y() < _lines;
}

787
FilterChain* TerminalDisplay::filterChain() const
788
789
790
791
{
    return _filterChain;
}

792
void TerminalDisplay::paintFilters(QPainter& painter)
793
{
794
795
796
797
    if (_filterUpdateRequired) {
        return;
    }

798

799
    _filterChain->paint(this, painter);
800
}
801

Jekyll Wu's avatar
Jekyll Wu committed
802
803
804
QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
{
    QRect result;
805
806
807
808
809
810
    const int fontWidth = _terminalFont->fontWidth();
    const int fontHeight = _terminalFont->fontHeight();
    result.setLeft(_contentRect.left() + fontWidth * imageArea.left());
    result.setTop(_contentRect.top() + fontHeight * imageArea.top());
    result.setWidth(fontWidth * imageArea.width());
    result.setHeight(fontHeight * imageArea.height());
Jekyll Wu's avatar
Jekyll Wu committed
811
812
813
814

    return result;
}

815
816
817
QRect TerminalDisplay::widgetToImage(const QRect &widgetArea) const
{
    QRect result;
818
819
    const int fontWidth = _terminalFont->fontWidth();
    const int fontHeight = _terminalFont->fontHeight();
Carlos Alves's avatar
Carlos Alves committed
820
821
822
823
    result.setLeft(  qBound(0, (widgetArea.left()   - contentsRect().left() - _contentRect.left()) / fontWidth,  _usedColumns - 1));
    result.setTop(   qBound(0, (widgetArea.top()    - contentsRect().top()  - _contentRect.top() ) / fontHeight, _usedLines   - 1));
    result.setRight( qBound(0, (widgetArea.right()  - contentsRect().left() - _contentRect.left()) / fontWidth,  _usedColumns - 1));
    result.setBottom(qBound(0, (widgetArea.bottom() - contentsRect().top()  - _contentRect.top() ) / fontHeight, _usedLines   - 1));
824
825
826
    return result;
}

Jekyll Wu's avatar
Jekyll Wu committed
827
828
829
830
831
832
833
834
835
836
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                          Blinking Text & Cursor                           */
/*                                                                           */
/* ------------------------------------------------------------------------- */

void TerminalDisplay::setBlinkingCursorEnabled(bool blink)
{
    _allowBlinkingCursor = blink;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
837
    if (blink && !_blinkCursorTimer->isActive()) {
Jekyll Wu's avatar
Jekyll Wu committed
838
        _blinkCursorTimer->start();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
839
    }
Jekyll Wu's avatar
Jekyll Wu committed
840
841
842
843
844

    if (!blink && _blinkCursorTimer->isActive()) {
        _blinkCursorTimer->stop();
        if (_cursorBlinking) {
            // if cursor is blinking(hidden), blink it again to make it show
845
846
            _cursorBlinking = false;
            updateCursor();
Jekyll Wu's avatar
Jekyll Wu committed
847
        }
848
        Q_ASSERT(!_cursorBlinking);
Jekyll Wu's avatar
Jekyll Wu committed
849
850
851
852
853
854
855
    }
}

void TerminalDisplay::setBlinkingTextEnabled(bool blink)
{
    _allowBlinkingText = blink;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
856
    if (blink && !_blinkTextTimer->isActive()) {
Jekyll Wu's avatar
Jekyll Wu committed
857
        _blinkTextTimer->start();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
858
    }