TerminalDisplay.cpp 141 KB
Newer Older
1
/*
2
    This file is part of Konsole, a terminal emulator for KDE.
3

4
5
    Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
    Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
6

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    02110-1301  USA.
*/
22

23
24
25
// Own
#include "TerminalDisplay.h"

26
// Config
27
#include "config-konsole.h"
28

Robert Knight's avatar
Robert Knight committed
29
// Qt
30
#include <QApplication>
31
32
33
34
#include <QClipboard>
#include <QKeyEvent>
#include <QEvent>
#include <QFileInfo>
35
#include <QVBoxLayout>
36
37
#include <QAction>
#include <QLabel>
38
#include <QMimeData>
39
40
#include <QPainter>
#include <QPixmap>
41
42
#include <QScrollBar>
#include <QStyle>
43
#include <QTimer>
Alex Richardson's avatar
Alex Richardson committed
44
#include <QDrag>
45
#include <QDesktopServices>
46
#include <QAccessible>
47
#include <QtMath>
48

Robert Knight's avatar
Robert Knight committed
49
// KDE
50
#include <KShell>
Robert Knight's avatar
   
Robert Knight committed
51
#include <KColorScheme>
Robert Knight's avatar
Robert Knight committed
52
#include <KCursor>
53
#include <KLocalizedString>
Robert Knight's avatar
Robert Knight committed
54
#include <KNotification>
55
56
#include <KIO/DropJob>
#include <KJobWidgets>
57
#include <KMessageBox>
58
#include <KMessageWidget>
59
#include <KIO/StatJob>
Robert Knight's avatar
Robert Knight committed
60
61

// Konsole
62
#include "Filter.h"
63
#include "konsoledebug.h"
64
#include "TerminalCharacterDecoder.h"
65
#include "Screen.h"
66
#include "SessionController.h"
67
#include "ExtendedCharTable.h"
68
#include "TerminalDisplayAccessible.h"
69
70
#include "SessionManager.h"
#include "Session.h"
71
#include "WindowSystemInfo.h"
72
#include "IncrementalSearchBar.h"
73
74
#include "Profile.h"
#include "ViewManager.h" // for colorSchemeForProfile. // TODO: Rewrite this.
75
#include "LineBlockCharacters.h"
76

77
78
using namespace Konsole;

Waldo Bastian's avatar
Waldo Bastian committed
79
#define REPCHAR   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
Kurt Hindenburg's avatar
Kurt Hindenburg committed
80
81
    "abcdefgjijklmnopqrstuvwxyz" \
    "0123456789./+@"
82

83
// we use this to force QPainter to display text in LTR mode
84
// more information can be found in: https://unicode.org/reports/tr9/
Kurt Hindenburg's avatar
Kurt Hindenburg committed
85
const QChar LTR_OVERRIDE_CHAR(0x202D);
86

87
88
89
90
91
92
93
94
95
inline int TerminalDisplay::loc(int x, int y) const {
    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;
}

96
97
98
99
100
101
102
103
104
105
106
107
108
109
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                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
*/

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

121
    _screenWindow = window;
122

123
    if (!_screenWindow.isNull()) {
124
125
126
        connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateLineProperties);
        connect(_screenWindow.data() , &Konsole::ScreenWindow::outputChanged , this , &Konsole::TerminalDisplay::updateImage);
        connect(_screenWindow.data() , &Konsole::ScreenWindow::currentResultLineChanged , this , &Konsole::TerminalDisplay::updateImage);
127
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
128
129
            _filterUpdateRequired = true;
        });
130
        connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
131
132
            _filterUpdateRequired = true;
        });
Jekyll Wu's avatar
Jekyll Wu committed
133
        _screenWindow->setWindowLines(_lines);
134
    }
135
136
}

137
const ColorEntry* TerminalDisplay::colorTable() const
138
{
Jekyll Wu's avatar
Jekyll Wu committed
139
    return _colorTable;
140
}
141

142
void TerminalDisplay::onColorsChanged()
143
{
144
145
146
147
148
149
150
    // Mostly just fix the scrollbar
    // this is a workaround to add some readability to old themes like Fusion
    // changing the light value for button a bit makes themes like fusion, windows and oxygen way more readable and pleasing

    QPalette p = QApplication::palette();

    QColor buttonTextColor = _colorTable[DEFAULT_FORE_COLOR];
151
152
153
    QColor backgroundColor = _colorTable[DEFAULT_BACK_COLOR];
    backgroundColor.setAlphaF(_opacity);

154
155
156
157
158
159
    QColor buttonColor = backgroundColor.toHsv();
    if (buttonColor.valueF() < 0.5) {
        buttonColor = buttonColor.lighter();
    } else {
        buttonColor = buttonColor.darker();
    }
160
    p.setColor(QPalette::Button, buttonColor);
161
    p.setColor(QPalette::Window, backgroundColor);
162
    p.setColor(QPalette::Base, backgroundColor);
163
164
165
166
    p.setColor(QPalette::WindowText, buttonTextColor);
    p.setColor(QPalette::ButtonText, buttonTextColor);

    setPalette(p);
167
168
169

    _scrollBar->setPalette(p);

170
    update();
171
172
}

173
174
void TerminalDisplay::setBackgroundColor(const QColor& color)
{
175
    _colorTable[DEFAULT_BACK_COLOR] = color;
Jekyll Wu's avatar
Jekyll Wu committed
176

177
    onColorsChanged();
178
}
179

180
181
QColor TerminalDisplay::getBackgroundColor() const
{
182
    return _colorTable[DEFAULT_BACK_COLOR];
183
}
184

185
186
void TerminalDisplay::setForegroundColor(const QColor& color)
{
187
    _colorTable[DEFAULT_FORE_COLOR] = color;
188

189
    onColorsChanged();
190
}
191

192
193
194
195
196
QColor TerminalDisplay::getForegroundColor() const
{
    return _colorTable[DEFAULT_FORE_COLOR];
}

197
void TerminalDisplay::setColorTable(const ColorEntry table[])
198
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
199
    for (int i = 0; i < TABLE_COLORS; i++) {
Jekyll Wu's avatar
Jekyll Wu committed
200
        _colorTable[i] = table[i];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
201
    }
202

203
    setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR]);
204
205

    onColorsChanged();
206
207
208
209
210
211
212
213
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                   Font                                    */
/*                                                                           */
/* ------------------------------------------------------------------------- */

214
215
static inline bool isLineCharString(const QString& string)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
216
    if (string.length() == 0) {
217
        return false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
218
    }
219

220
    return LineBlockCharacters::canDraw(string.at(0).unicode());
221
}
222

223
void TerminalDisplay::fontChange(const QFont&)
224
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
225
226
    QFontMetrics fm(font());
    _fontHeight = fm.height() + _lineSpacing;
227

228
229
    Q_ASSERT(_fontHeight > 0);

230
231
232
233
234
235
236
    /* TODO: When changing the three deprecated width() below
     *       consider the info in
     *       https://phabricator.kde.org/D23144 comments
     *       horizontalAdvance() was added in Qt 5.11 (which should be the
     *       minimum for 20.04 or 20.08 KDE Applications release)
     */

Kurt Hindenburg's avatar
Kurt Hindenburg committed
237
238
239
240
    // waba TerminalDisplay 1.123:
    // "Base character width on widest ASCII character. This prevents too wide
    //  characters in the presence of double wide (e.g. Japanese) characters."
    // Get the width from representative normal width characters
241
    _fontWidth = qRound((static_cast<double>(fm.width(QStringLiteral(REPCHAR))) / static_cast<double>(qstrlen(REPCHAR))));
Waldo Bastian's avatar
Waldo Bastian committed
242

Kurt Hindenburg's avatar
Kurt Hindenburg committed
243
    _fixedFont = true;
244

245
    const int fw = fm.width(QLatin1Char(REPCHAR[0]));
246
    for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
247
        if (fw != fm.width(QLatin1Char(REPCHAR[i]))) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
248
249
250
            _fixedFont = false;
            break;
        }
251
    }
Stephan Kulow's avatar
Stephan Kulow committed
252

Kurt Hindenburg's avatar
Kurt Hindenburg committed
253
    if (_fontWidth < 1) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
254
        _fontWidth = 1;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
255
    }
256

Kurt Hindenburg's avatar
Kurt Hindenburg committed
257
    _fontAscent = fm.ascent();
258

Kurt Hindenburg's avatar
Kurt Hindenburg committed
259
260
261
    emit changedFontMetricSignal(_fontHeight, _fontWidth);
    propagateSize();
    update();
262
263
}

264
void TerminalDisplay::setVTFont(const QFont& f)
265
{
266
    QFont newFont(f);
267
    int strategy = 0;
268

269
    // hint that text should be drawn with- or without anti-aliasing.
270
    // depending on the user's font configuration, this may not be respected
271
    strategy |= _antialiasText ? QFont::PreferAntialias : QFont::NoAntialias;
272

273
    // Konsole cannot handle non-integer font metrics
274
275
276
277
278
    strategy |= QFont::ForceIntegerMetrics;

    // In case the provided font doesn't have some specific characters it should
    // fall back to a Monospace fonts.
    newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
279

280
281
282
283
    // Try to check that a good font has been loaded.
    // For some fonts, ForceIntegerMetrics causes height() == 0 which
    // will cause Konsole to crash later.
    QFontMetrics fontMetrics2(newFont);
284
    if (fontMetrics2.height() < 1) {
285
286
287
288
        qCDebug(KonsoleDebug)<<"The font "<<newFont.toString()<<" has an invalid height()";
        // Ask for a generic font so at least it is usable.
        // Font listed in profile's dialog will not be updated.
        newFont = QFont(QStringLiteral("Monospace"));
289
290
291
        // Set style strategy without ForceIntegerMetrics for the font
        strategy &= ~QFont::ForceIntegerMetrics;
        newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
292
293
294
        qCDebug(KonsoleDebug)<<"Font changed to "<<newFont.toString();
    }

295
296
297
298
    // experimental optimization.  Konsole assumes that the terminal is using a
    // mono-spaced font, in which case kerning information should have an effect.
    // Disabling kerning saves some computation when rendering text.
    newFont.setKerning(false);
Robert Knight's avatar
   
Robert Knight committed
299

300
301
302
303
304
    // "Draw intense colors in bold font" feature needs to use different font weights. StyleName
    // property, when set, doesn't allow weight changes. Since all properties (weight, stretch,
    // italic, etc) are stored in QFont independently, in almost all cases styleName is not needed.
    newFont.setStyleName(QString());

305
306
307
308
309
    if (newFont == font()) {
        // Do not process the same font again
        return;
    }

310
    QFontInfo fontInfo(newFont);
311

312
    // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch() - do not test it.
313
    // related?  https://bugreports.qt.io/browse/QTBUG-34082
314
    if (fontInfo.family() != newFont.family()
315
            || !qFuzzyCompare(fontInfo.pointSizeF(), newFont.pointSizeF())
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
            || fontInfo.styleHint()  != newFont.styleHint()
            || fontInfo.weight()     != newFont.weight()
            || fontInfo.style()      != newFont.style()
            || fontInfo.underline()  != newFont.underline()
            || fontInfo.strikeOut()  != newFont.strikeOut()
            || fontInfo.rawMode()    != newFont.rawMode()) {
        const QString nonMatching = QString::asprintf("%s,%g,%d,%d,%d,%d,%d,%d,%d,%d",
                qPrintable(fontInfo.family()),
                fontInfo.pointSizeF(),
                -1, // pixelSize is not used
                static_cast<int>(fontInfo.styleHint()),
                fontInfo.weight(),
                static_cast<int>(fontInfo.style()),
                static_cast<int>(fontInfo.underline()),
                static_cast<int>(fontInfo.strikeOut()),
                // Intentional newFont use - fixedPitch is bugged, see comment above
                static_cast<int>(newFont.fixedPitch()),
                static_cast<int>(fontInfo.rawMode()));
334
        qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system.";
335
336
        qCDebug(KonsoleDebug) << " Selected: " << newFont.toString();
        qCDebug(KonsoleDebug) << " System  : " << nonMatching;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
337
    }
338
339
340

    QWidget::setFont(newFont);
    fontChange(newFont);
341
342
}

343
344
345
346
347
348
349
350
351
352
353
354
355
void TerminalDisplay::increaseFontSize()
{
    QFont font = getVTFont();
    font.setPointSizeF(font.pointSizeF() + 1);
    setVTFont(font);
}

void TerminalDisplay::decreaseFontSize()
{
    const qreal MinimumFontSize = 6;

    QFont font = getVTFont();
    font.setPointSizeF(qMax(font.pointSizeF() - 1, MinimumFontSize));
Lukas Bergdoll's avatar
Lukas Bergdoll committed
356
357
358
359
360
361
362
363
364
365
366
367
    setVTFont(font);
}

void TerminalDisplay::resetFontSize()
{
    const qreal MinimumFontSize = 6;

    QFont font = getVTFont();
    Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(_sessionController->session());
    const qreal defaultFontSize = currentProfile->font().pointSizeF();

    font.setPointSizeF(qMax(defaultFontSize, MinimumFontSize));
368
369
370
    setVTFont(font);
}

Jekyll Wu's avatar
Jekyll Wu committed
371
372
373
374
375
376
377
378
uint TerminalDisplay::lineSpacing() const
{
    return _lineSpacing;
}

void TerminalDisplay::setLineSpacing(uint i)
{
    _lineSpacing = i;
379
    fontChange(font()); // Trigger an update.
Jekyll Wu's avatar
Jekyll Wu committed
380
381
}

382
383
384
385
386
387
388

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Accessibility                                     */
/*                                                                           */
/* ------------------------------------------------------------------------- */

389
390
namespace Konsole
{
Alex Richardson's avatar
Alex Richardson committed
391
392

#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
393
/**
394
 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
Kurt Hindenburg's avatar
Kurt Hindenburg committed
395
396
397
398
399
 * for the TerminalDisplay.
 */
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
{
    Q_UNUSED(key)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
400
    if (auto *display = qobject_cast<TerminalDisplay*>(object)) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
401
        return new TerminalDisplayAccessible(display);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
402
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
403
    return nullptr;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
404
}
Alex Richardson's avatar
Alex Richardson committed
405
406

#endif
407
408
}

409
410
411
412
413
414
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

415
TerminalDisplay::TerminalDisplay(QWidget* parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
416
    : QWidget(parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
417
    , _screenWindow(nullptr)
Jekyll Wu's avatar
Jekyll Wu committed
418
    , _bellMasked(false)
419
    , _verticalLayout(new QVBoxLayout(this))
420
    , _fixedFont(true)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
421
422
423
424
425
426
427
428
    , _fontHeight(1)
    , _fontWidth(1)
    , _fontAscent(1)
    , _boldIntense(true)
    , _lines(1)
    , _columns(1)
    , _usedLines(1)
    , _usedColumns(1)
429
    , _contentRect(QRect())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
430
    , _image(nullptr)
431
432
    , _imageSize(0)
    , _lineProperties(QVector<LineProperty>())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
433
434
435
436
    , _randomSeed(0)
    , _resizing(false)
    , _showTerminalSizeHint(true)
    , _bidiEnabled(false)
437
    , _usesMouseTracking(false)
438
    , _alternateScrolling(true)
439
440
441
442
    , _bracketedPasteMode(false)
    , _iPntSel(QPoint())
    , _pntSel(QPoint())
    , _tripleSelBegin(QPoint())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
443
444
445
    , _actSel(0)
    , _wordSelectionMode(false)
    , _lineSelectionMode(false)
446
    , _preserveLineBreaks(true)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
447
    , _columnSelectionMode(false)
448
    , _autoCopySelectedText(false)
449
    , _copyTextAsHTML(true)
450
    , _middleClickPasteMode(Enum::PasteFromX11Selection)
451
    , _scrollBar(nullptr)
452
    , _scrollbarLocation(Enum::ScrollBarRight)
453
    , _scrollFullPage(false)
454
    , _wordCharacters(QStringLiteral(":@-./_~"))
455
    , _bellMode(Enum::NotifyBell)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
456
457
458
459
460
    , _allowBlinkingText(true)
    , _allowBlinkingCursor(false)
    , _textBlinking(false)
    , _cursorBlinking(false)
    , _hasTextBlinker(false)
461
    , _urlHintsModifiers(Qt::NoModifier)
462
    , _showUrlHint(false)
463
    , _reverseUrlHints(false)
464
    , _openLinksByDirectClick(false)
465
    , _ctrlRequiredForDrag(true)
466
    , _dropUrlsAsText(false)
467
    , _tripleClickMode(Enum::SelectWholeLine)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
468
    , _possibleTripleClick(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
469
470
    , _resizeWidget(nullptr)
    , _resizeTimer(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
471
    , _flowControlWarningEnabled(false)
472
    , _outputSuspendedMessageWidget(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
473
    , _lineSpacing(0)
474
    , _size(QSize())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
475
    , _blendColor(qRgba(0, 0, 0, 0xff))
476
    , _wallpaper(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
477
    , _filterChain(new TerminalImageFilterChain())
478
    , _mouseOverHotspotArea(QRegion())
479
    , _filterUpdateRequired(true)
480
    , _cursorShape(Enum::BlockCursor)
481
    , _cursorColor(QColor())
482
    , _cursorTextColor(QColor())
Kurt Hindenburg's avatar
Kurt Hindenburg committed
483
    , _antialiasText(true)
484
    , _useFontLineCharacters(false)
485
    , _printerFriendly(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
486
    , _sessionController(nullptr)
487
    , _trimLeadingSpaces(false)
488
    , _trimTrailingSpaces(false)
489
    , _mouseWheelZoom(false)
490
491
    , _margin(1)
    , _centerContents(false)
492
    , _readOnlyMessageWidget(nullptr)
493
    , _readOnly(false)
494
    , _opacity(1.0)
495
    , _dimWhenInactive(false)
496
    , _scrollWheelState(ScrollState())
497
    , _searchBar(new IncrementalSearchBar(this))
498
    , _headerBar(new TerminalHeaderBar(this))
499
    , _searchResultRect(QRect())
500
    , _drawOverlay(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
501
502
503
504
505
{
    // terminal applications are not designed with Right-To-Left in mind,
    // so the layout is forced to Left-To-Right
    setLayoutDirection(Qt::LeftToRight);

506
    _contentRect = QRect(_margin, _margin, 1, 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
507
508
509

    // create scroll bar for scrolling output up and down
    _scrollBar = new QScrollBar(this);
510
    _scrollBar->setAutoFillBackground(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
511
512
513
    // set the scroll bar's slider to occupy the whole area of the scroll bar initially
    setScroll(0, 0);
    _scrollBar->setCursor(Qt::ArrowCursor);
514
    _headerBar->setCursor(Qt::ArrowCursor);
515
    connect(_headerBar, &TerminalHeaderBar::requestToggleExpansion, this, &Konsole::TerminalDisplay::requestToggleExpansion);
Laurent Montel's avatar
Laurent Montel committed
516
517
    connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
    connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
518

519
520
521
    // setup timers for blinking text
    _blinkTextTimer = new QTimer(this);
    _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
522
    connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
523
524
525
526

    // setup timers for blinking cursor
    _blinkCursorTimer = new QTimer(this);
    _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
527
    connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
528
529
530
531
532

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

533
    setUsesMouseTracking(false);
534
    setBracketedPasteMode(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
535

536
    setColorTable(ColorScheme::defaultTable);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
537
538
539
540
541
542
543
544
545
546
547
548
549
550

    // 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);

551
    // Add the stretch item once, the KMessageWidgets are inserted at index 0.
552
    _verticalLayout->addWidget(_headerBar);
553
554
    _verticalLayout->addStretch();
    _verticalLayout->setSpacing(0);
555
    _verticalLayout->setContentsMargins(0, 0, 0, 0);
556
    setLayout(_verticalLayout);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
557
    new AutoScrollHandler(this);
558
#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
559
    QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
560
#endif
561
562
}

563
TerminalDisplay::~TerminalDisplay()
564
{
565
566
567
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

568
569
    delete _readOnlyMessageWidget;
    delete _outputSuspendedMessageWidget;
570
571
    delete[] _image;
    delete _filterChain;
572
573
574

    _readOnlyMessageWidget = nullptr;
    _outputSuspendedMessageWidget = nullptr;
575
576
}

577
578
579
580
581
582
void TerminalDisplay::hideDragTarget()
{
    _drawOverlay = false;
    update();
}

583
void TerminalDisplay::showDragTarget(const QPoint& cursorPos)
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
{
    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();
}

605
606
607
608
609
610
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                             Display Operations                            */
/*                                                                           */
/* ------------------------------------------------------------------------- */

611
void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
612
        const Character* attributes)
613
{
614
615
616
617
618
619
    // only turn on anti-aliasing during this short time for the "text"
    // for the normal text we have TextAntialiasing on demand on
    // otherwise we have rendering artifacts
    // set https://bugreports.qt.io/browse/QTBUG-66036
    painter.setRenderHint(QPainter::Antialiasing, _antialiasText);

620
    const bool useBoldPen = (attributes->rendition & RE_BOLD) != 0 && _boldIntense;
621

622
    QRect cellRect = {x, y, _fontWidth, _fontHeight};
Kurt Hindenburg's avatar
Kurt Hindenburg committed
623
    for (int i = 0 ; i < str.length(); i++) {
624
625
        LineBlockCharacters::draw(painter, cellRect.translated(i * _fontWidth, 0), str[i],
                                        useBoldPen);
626
    }
627
628

    painter.setRenderHint(QPainter::Antialiasing, false);
629
630
}

631
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
632
633
634
{
    _cursorShape = shape;
}
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652

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()
{
653
    Q_ASSERT(_sessionController != nullptr);
654
    Q_ASSERT(!_sessionController->session().isNull());
655

656
    Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(_sessionController->session());
657

658
    if (currentProfile != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
659
        auto shape = static_cast<Enum::CursorShapeEnum>(currentProfile->property<int>(Profile::CursorShape));
660
661
662

        setKeyboardCursorShape(shape);
        setBlinkingCursorEnabled(currentProfile->blinkingCursorEnabled());
663
664
665
    }
}

666
void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
667
{
668
    _cursorColor = color;
669
}
670

671
672
673
674
675
void TerminalDisplay::setKeyboardCursorTextColor(const QColor& color)
{
    _cursorTextColor = color;
}

676
677
678
679
void TerminalDisplay::setOpacity(qreal opacity)
{
    QColor color(_blendColor);
    color.setAlphaF(opacity);
680
    _opacity = opacity;
681

682
    _blendColor = color.rgba();
683
    onColorsChanged();
684
685
}

686
void TerminalDisplay::setWallpaper(const ColorSchemeWallpaper::Ptr &p)
687
{
688
    _wallpaper = p;
689
690
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
691
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting)
692
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
693
694
695
696
697
698
699
700
701
    // the area of the widget showing the contents of the terminal display is drawn
    // using the background color from the color scheme set with setColorTable()
    //
    // the area of the widget behind the scroll-bar is drawn using the background
    // brush from the scroll-bar's palette, to give the effect of the scroll-bar
    // being outside of the terminal display and visual consistency with other KDE
    // applications.

    if (useOpacitySetting && !_wallpaper->isNull() &&
702
            _wallpaper->draw(painter, rect, _opacity)) {
703
    } else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) {
704
#if defined(Q_OS_MACOS)
705
        // TODO - On MacOS, using CompositionMode doesn't work.  Altering the
706
        //        transparency in the color scheme alters the brightness.
707
        painter.fillRect(rect, backgroundColor);
708
#else
Kurt Hindenburg's avatar
Kurt Hindenburg committed
709
710
711
        QColor color(backgroundColor);
        color.setAlpha(qAlpha(_blendColor));

712
        const QPainter::CompositionMode originalMode = painter.compositionMode();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
713
        painter.setCompositionMode(QPainter::CompositionMode_Source);
714
        painter.fillRect(rect, color);
715
        painter.setCompositionMode(originalMode);
716
#endif
Kurt Hindenburg's avatar
Kurt Hindenburg committed
717
    } else {
718
        painter.fillRect(rect, backgroundColor);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
719
    }
720
721
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
722
void TerminalDisplay::drawCursor(QPainter& painter,
723
724
                                 const QRect& rect,
                                 const QColor& foregroundColor,
725
726
                                 const QColor& backgroundColor,
                                 QColor& characterColor)
727
{
Jekyll Wu's avatar
Jekyll Wu committed
728
    // don't draw cursor which is currently blinking
Kurt Hindenburg's avatar
Kurt Hindenburg committed
729
    if (_cursorBlinking) {
730
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
731
    }
Jekyll Wu's avatar
Jekyll Wu committed
732

733
734
    // shift rectangle top down one pixel to leave some space
    // between top and bottom
735
    QRectF cursorRect = rect.adjusted(0, 1, 0, 0);
Jekyll Wu's avatar
Jekyll Wu committed
736

737
    QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
Jekyll Wu's avatar
Jekyll Wu committed
738
739
    painter.setPen(cursorColor);

740
    if (_cursorShape == Enum::BlockCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
741
742
        // draw the cursor outline, adjusting the area so that
        // it is draw entirely inside 'rect'
Kurt Hindenburg's avatar
Kurt Hindenburg committed
743
        int penWidth = qMax(1, painter.pen().width());
744
745
746
747
        painter.drawRect(cursorRect.adjusted(int(penWidth / 2) + 0.5,
                                             int(penWidth / 2) + 0.5,
                                             - int(penWidth / 2) - penWidth % 2 + 0.5,
                                             - int(penWidth / 2) - penWidth % 2 + 0.5));
Jekyll Wu's avatar
Jekyll Wu committed
748
749

        // draw the cursor body only when the widget has focus
Kurt Hindenburg's avatar
Kurt Hindenburg committed
750
        if (hasFocus()) {
Jekyll Wu's avatar
Jekyll Wu committed
751
752
            painter.fillRect(cursorRect, cursorColor);

753
754
755
756
            // if the cursor text color is valid then use it to draw the character under the cursor,
            // otherwise invert the color used to draw the text to ensure that the character at
            // the cursor position is readable
            characterColor = _cursorTextColor.isValid() ? _cursorTextColor : backgroundColor;
Jekyll Wu's avatar
Jekyll Wu committed
757
        }
758
    } else if (_cursorShape == Enum::UnderlineCursor) {
759
760
761
762
763
        QLineF line(cursorRect.left() + 0.5,
                    cursorRect.bottom() - 0.5,
                    cursorRect.right() - 0.5,
                    cursorRect.bottom() - 0.5);
        painter.drawLine(line);
Jekyll Wu's avatar
Jekyll Wu committed
764

765
    } else if (_cursorShape == Enum::IBeamCursor) {
766
767
768
769
770
        QLineF line(cursorRect.left() + 0.5,
                    cursorRect.top() + 0.5,
                    cursorRect.left() + 0.5,
                    cursorRect.bottom() - 0.5);
        painter.drawLine(line);
771
    }
772
773
774
775
776
777
}

void TerminalDisplay::drawCharacters(QPainter& painter,
                                     const QRect& rect,
                                     const QString& text,
                                     const Character* style,
778
                                     const QColor& characterColor)
779
780
{
    // don't draw text which is currently blinking
Kurt Hindenburg's avatar
Kurt Hindenburg committed
781
    if (_textBlinking && ((style->rendition & RE_BLINK) != 0)) {
782
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
783
    }
784

785
    // don't draw concealed characters
Kurt Hindenburg's avatar
Kurt Hindenburg committed
786
    if ((style->rendition & RE_CONCEAL) != 0) {
787
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
788
    }
789

790
791
    static constexpr int MaxFontWeight = 99; // https://doc.qt.io/qt-5/qfont.html#Weight-enum

792
793
794
    const int normalWeight = font().weight();
    // +26 makes "bold" from "normal", "normal" from "light", etc. It is 26 instead of not 25 to prefer
    // bolder weight when 25 falls in the middle between two weights. See QFont::Weight
795
    const int boldWeight = qMin(normalWeight + 26, MaxFontWeight);
796
797
798
799

    const auto isBold = [boldWeight](const QFont &font) { return font.weight() >= boldWeight; };

    const bool useBold = (((style->rendition & RE_BOLD) != 0) && _boldIntense);
800
801
802
803
    const bool useUnderline = ((style->rendition & RE_UNDERLINE) != 0) || font().underline();
    const bool useItalic = ((style->rendition & RE_ITALIC) != 0) || font().italic();
    const bool useStrikeOut = ((style->rendition & RE_STRIKEOUT) != 0) || font().strikeOut();
    const bool useOverline = ((style->rendition & RE_OVERLINE) != 0) || font().overline();
Robert Knight's avatar
   
Robert Knight committed
804

805
806
807
808
809
810
811
812
813
814
815
816
817
    QFont currentFont = painter.font();

    if (isBold(currentFont) != useBold
            || currentFont.underline() != useUnderline
            || currentFont.italic() != useItalic
            || currentFont.strikeOut() != useStrikeOut
            || currentFont.overline() != useOverline) {
        currentFont.setWeight(useBold ? boldWeight : normalWeight);
        currentFont.setUnderline(useUnderline);
        currentFont.setItalic(useItalic);
        currentFont.setStrikeOut(useStrikeOut);
        currentFont.setOverline(useOverline);
        painter.setFont(currentFont);
818
819
820
    }

    // setup pen
821
822
    const QColor foregroundColor = style->foregroundColor.color(_colorTable);
    const QColor color = characterColor.isValid() ? characterColor : foregroundColor;
823
    QPen pen = painter.pen();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
824
    if (pen.color() != color) {
825
826
827
828
        pen.setColor(color);
        painter.setPen(color);
    }

829
830
831
    const bool origClipping = painter.hasClipping();
    const auto origClipRegion = painter.clipRegion();
    painter.setClipRect(rect);
832
    // draw text
833
    if (isLineCharString(text) && !_useFontLineCharacters) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
834
835
        drawLineCharString(painter, rect.x(), rect.y(), text, style);
    } else {
836
837
838
839
        // Force using LTR as the document layout for the terminal area, because
        // there is no use cases for RTL emulator and RTL terminal application.
        //
        // This still allows RTL characters to be rendered in the RTL way.
840
        painter.setLayoutDirection(Qt::LeftToRight);
841

842
        if (_bidiEnabled) {
843
            painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text);
844
        } else {
845
            painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text);
846
        }
847
    }
848
849
    painter.setClipRegion(origClipRegion);
    painter.setClipping(origClipping);
850
851
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
852
void TerminalDisplay::drawTextFragment(QPainter& painter ,
853
                                       const QRect& rect,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
854
                                       const QString& text,
855
856
                                       const Character* style)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
857
    // setup painter
858
859
    const QColor foregroundColor = style->foregroundColor.color(_colorTable);
    const QColor backgroundColor = style->backgroundColor.color(_colorTable);
860

861
    // draw background if different from the display's background color
862
    if (backgroundColor != getBackgroundColor()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
863
        drawBackground(painter, rect, backgroundColor,
864
                       false /* do not use transparency */);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
865
    }
866
867
868

    // draw cursor shape if the current character is the cursor
    // this may alter the foreground and background colors
869
    QColor characterColor;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
870
    if ((style->rendition & RE_CURSOR) != 0) {
871
        drawCursor(painter, rect, foregroundColor, backgroundColor, characterColor);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
872
    }
873
874

    // draw text
875
    drawCharacters(painter, rect, text, style, characterColor);
876
877
}

878
void TerminalDisplay::drawPrinterFriendlyTextFragment(QPainter& painter,
879
880
881
        const QRect& rect,
        const QString& text,
        const Character* style)
882
883
884
885
886
887
888
889
{
    // Set the colors used to draw to black foreground and white
    // background for printer friendly output when printing
    Character print_style = *style;
    print_style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000);
    print_style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF);

    // draw text
890
    drawCharacters(painter, rect, text, &print_style, QColor());
891
892
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
893
894
895
896
897
898
899
900
void TerminalDisplay::setRandomSeed(uint randomSeed)
{
    _randomSeed = randomSeed;
}
uint TerminalDisplay::randomSeed() const
{
    return _randomSeed;
}
901

902
// scrolls the image by 'lines', down if lines > 0 or up otherwise.