TerminalDisplay.cpp 143 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
// Own
24
#include "widgets/TerminalDisplay.h"
25
#include "KonsoleSettings.h"
26

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

Robert Knight's avatar
Robert Knight committed
30
// Qt
31
#include <QApplication>
32 33 34 35
#include <QClipboard>
#include <QKeyEvent>
#include <QEvent>
#include <QFileInfo>
36
#include <QVBoxLayout>
37 38
#include <QAction>
#include <QLabel>
39
#include <QMimeData>
40 41
#include <QPainter>
#include <QPixmap>
42 43
#include <QScrollBar>
#include <QStyle>
44
#include <QTimer>
Alex Richardson's avatar
Alex Richardson committed
45
#include <QDrag>
46
#include <QDesktopServices>
47
#include <QAccessible>
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 "CompositeWidgetFocusWatcher.h"
63
#include "AutoScrollHandler.h"
64 65 66 67 68

#include "filterHotSpots/Filter.h"
#include "filterHotSpots/TerminalImageFilterChain.h"
#include "filterHotSpots/HotSpot.h"
#include "filterHotSpots/FileFilterHotspot.h"
69 70
#include "filterHotSpots/EscapeSequenceUrlFilter.h"
#include "filterHotSpots/EscapeSequenceUrlFilterHotSpot.h"
71

72
#include "konsoledebug.h"
73
#include "PlainTextDecoder.h"
74
#include "Screen.h"
75
#include "ExtendedCharTable.h"
76
#include "TerminalDisplayAccessible.h"
77
#include "session/SessionController.h"
78 79
#include "session/SessionManager.h"
#include "session/Session.h"
80
#include "WindowSystemInfo.h"
81
#include "widgets/IncrementalSearchBar.h"
82
#include "profile/Profile.h"
83
#include "ViewManager.h" // for colorSchemeForProfile. // TODO: Rewrite this.
84
#include "LineBlockCharacters.h"
85 86
#include "PrintOptions.h"
#include "KonsolePrintManager.h"
87
#include "EscapeSequenceUrlExtractor.h"
88

89 90
using namespace Konsole;

Waldo Bastian's avatar
Waldo Bastian committed
91
#define REPCHAR   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
Kurt Hindenburg's avatar
Kurt Hindenburg committed
92 93
    "abcdefgjijklmnopqrstuvwxyz" \
    "0123456789./+@"
94

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

99 100 101 102 103 104 105 106 107
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;
}

108 109 110 111 112 113 114 115 116 117 118 119 120 121
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                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
*/

122
ScreenWindow* TerminalDisplay::screenWindow() const
123 124 125
{
    return _screenWindow;
}
126
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
127
{
128
    // disconnect existing screen window if any
129
    if (!_screenWindow.isNull()) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
130
        disconnect(_screenWindow , nullptr , this , nullptr);
131 132
    }

133
    _screenWindow = window;
134

135
    if (!_screenWindow.isNull()) {
136 137 138
        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);
139
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
140 141
            _filterUpdateRequired = true;
        });
142 143 144 145 146
        connect(_screenWindow.data(), &Konsole::ScreenWindow::screenAboutToChange, this, [this]() {
            _iPntSel = QPoint();
            _pntSel = QPoint();
            _tripleSelBegin = QPoint();
        });
147
        connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
148 149
            _filterUpdateRequired = true;
        });
150
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, []() {
151 152
            QGuiApplication::inputMethod()->update(Qt::ImCursorRectangle);
        });
Jekyll Wu's avatar
Jekyll Wu committed
153
        _screenWindow->setWindowLines(_lines);
154 155 156

        auto profile = SessionManager::instance()->sessionProfile(_sessionController->session());
        _screenWindow->screen()->urlExtractor()->setAllowedLinkSchema(profile->escapedLinksSchema());
157
    }
158 159
}

160
const ColorEntry* TerminalDisplay::colorTable() const
161
{
Jekyll Wu's avatar
Jekyll Wu committed
162
    return _colorTable;
163
}
164

165
void TerminalDisplay::onColorsChanged()
166
{
167 168 169 170 171 172 173
    // 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];
174 175 176
    QColor backgroundColor = _colorTable[DEFAULT_BACK_COLOR];
    backgroundColor.setAlphaF(_opacity);

177 178 179 180 181 182
    QColor buttonColor = backgroundColor.toHsv();
    if (buttonColor.valueF() < 0.5) {
        buttonColor = buttonColor.lighter();
    } else {
        buttonColor = buttonColor.darker();
    }
183
    p.setColor(QPalette::Button, buttonColor);
184
    p.setColor(QPalette::Window, backgroundColor);
185
    p.setColor(QPalette::Base, backgroundColor);
186 187 188 189
    p.setColor(QPalette::WindowText, buttonTextColor);
    p.setColor(QPalette::ButtonText, buttonTextColor);

    setPalette(p);
190 191 192

    _scrollBar->setPalette(p);

193
    update();
194 195
}

196 197
void TerminalDisplay::setBackgroundColor(const QColor& color)
{
198
    _colorTable[DEFAULT_BACK_COLOR] = color;
Jekyll Wu's avatar
Jekyll Wu committed
199

200
    onColorsChanged();
201
}
202

203 204
QColor TerminalDisplay::getBackgroundColor() const
{
205
    return _colorTable[DEFAULT_BACK_COLOR];
206
}
207

208 209
void TerminalDisplay::setForegroundColor(const QColor& color)
{
210
    _colorTable[DEFAULT_FORE_COLOR] = color;
211

212
    onColorsChanged();
213
}
214

215 216 217 218 219
QColor TerminalDisplay::getForegroundColor() const
{
    return _colorTable[DEFAULT_FORE_COLOR];
}

220
void TerminalDisplay::setColorTable(const ColorEntry table[])
221
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
222
    for (int i = 0; i < TABLE_COLORS; i++) {
Jekyll Wu's avatar
Jekyll Wu committed
223
        _colorTable[i] = table[i];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
224
    }
225

226
    setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR]);
227 228

    onColorsChanged();
229 230 231 232 233 234 235 236
}

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

237 238
static inline bool isLineCharString(const QString& string)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
239
    if (string.length() == 0) {
240
        return false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
241
    }
242

243
    return LineBlockCharacters::canDraw(string.at(0).unicode());
244
}
245

246
void TerminalDisplay::fontChange(const QFont&)
247
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
248 249
    QFontMetrics fm(font());
    _fontHeight = fm.height() + _lineSpacing;
250

251 252
    Q_ASSERT(_fontHeight > 0);

253 254 255 256 257 258 259
    /* 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
260 261 262 263
    // 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
264
    _fontWidth = qRound((static_cast<double>(fm.horizontalAdvance(QStringLiteral(REPCHAR))) / static_cast<double>(qstrlen(REPCHAR))));
Waldo Bastian's avatar
Waldo Bastian committed
265

Kurt Hindenburg's avatar
Kurt Hindenburg committed
266
    _fixedFont = true;
267

268
    const int fw = fm.horizontalAdvance(QLatin1Char(REPCHAR[0]));
269
    for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
270
        if (fw != fm.horizontalAdvance(QLatin1Char(REPCHAR[i]))) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
271 272 273
            _fixedFont = false;
            break;
        }
274
    }
Stephan Kulow's avatar
Stephan Kulow committed
275

Kurt Hindenburg's avatar
Kurt Hindenburg committed
276
    if (_fontWidth < 1) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
277
        _fontWidth = 1;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
278
    }
279

Kurt Hindenburg's avatar
Kurt Hindenburg committed
280
    _fontAscent = fm.ascent();
281

Kurt Hindenburg's avatar
Kurt Hindenburg committed
282 283 284
    emit changedFontMetricSignal(_fontHeight, _fontWidth);
    propagateSize();
    update();
285 286
}

287
void TerminalDisplay::setVTFont(const QFont& f)
288
{
289
    QFont newFont(f);
290
    int strategy = 0;
291

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

296
    // Konsole cannot handle non-integer font metrics
297 298 299 300 301
    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));
302

303 304 305 306
    // 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);
307
    if (fontMetrics2.height() < 1) {
308 309 310 311
        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"));
312 313 314
        // Set style strategy without ForceIntegerMetrics for the font
        strategy &= ~QFont::ForceIntegerMetrics;
        newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
315 316 317
        qCDebug(KonsoleDebug)<<"Font changed to "<<newFont.toString();
    }

318 319 320 321
    // 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
322

323 324 325 326 327
    // "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());

328 329 330 331 332
    if (newFont == font()) {
        // Do not process the same font again
        return;
    }

333
    QFontInfo fontInfo(newFont);
334

335
    // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch() - do not test it.
336
    // related?  https://bugreports.qt.io/browse/QTBUG-34082
337
    if (fontInfo.family() != newFont.family()
338
            || !qFuzzyCompare(fontInfo.pointSizeF(), newFont.pointSizeF())
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356
            || 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()));
357
        qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system.";
358 359
        qCDebug(KonsoleDebug) << " Selected: " << newFont.toString();
        qCDebug(KonsoleDebug) << " System  : " << nonMatching;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
360
    }
361 362 363

    QWidget::setFont(newFont);
    fontChange(newFont);
364 365
}

366 367 368 369 370 371 372 373 374 375 376 377 378
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
379 380 381 382 383 384 385 386 387 388 389 390
    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));
391 392 393
    setVTFont(font);
}

Jekyll Wu's avatar
Jekyll Wu committed
394 395 396 397 398 399 400 401
uint TerminalDisplay::lineSpacing() const
{
    return _lineSpacing;
}

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

405 406 407 408 409 410 411

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

412 413
namespace Konsole
{
Alex Richardson's avatar
Alex Richardson committed
414 415

#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
416
/**
417
 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
Kurt Hindenburg's avatar
Kurt Hindenburg committed
418 419 420 421 422
 * for the TerminalDisplay.
 */
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
{
    Q_UNUSED(key)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
423
    if (auto *display = qobject_cast<TerminalDisplay*>(object)) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
424
        return new TerminalDisplayAccessible(display);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
425
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
426
    return nullptr;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
427
}
Alex Richardson's avatar
Alex Richardson committed
428 429

#endif
430 431
}

432 433 434 435 436 437
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

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

528
    _contentRect = QRect(_margin, _margin, 1, 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
529 530 531

    // create scroll bar for scrolling output up and down
    _scrollBar = new QScrollBar(this);
532
    _scrollBar->setAutoFillBackground(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
533 534 535
    // set the scroll bar's slider to occupy the whole area of the scroll bar initially
    setScroll(0, 0);
    _scrollBar->setCursor(Qt::ArrowCursor);
536
    _headerBar->setCursor(Qt::ArrowCursor);
537
    connect(_headerBar, &TerminalHeaderBar::requestToggleExpansion, this, &Konsole::TerminalDisplay::requestToggleExpansion);
Laurent Montel's avatar
Laurent Montel committed
538 539
    connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
    connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
540

541 542 543
    // setup timers for blinking text
    _blinkTextTimer = new QTimer(this);
    _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
544
    connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
545 546 547 548

    // setup timers for blinking cursor
    _blinkCursorTimer = new QTimer(this);
    _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
549
    connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
550 551 552 553 554

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

555
    setUsesMouseTracking(false);
556
    setBracketedPasteMode(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
557

558
    setColorTable(ColorScheme::defaultTable);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
559 560 561 562 563 564 565 566 567 568 569 570 571 572

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

573
    // Add the stretch item once, the KMessageWidgets are inserted at index 0.
574
    _verticalLayout->addWidget(_headerBar);
575 576
    _verticalLayout->addStretch();
    _verticalLayout->setSpacing(0);
577
    _verticalLayout->setContentsMargins(0, 0, 0, 0);
578
    setLayout(_verticalLayout);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
579
    new AutoScrollHandler(this);
580 581 582 583 584 585 586 587 588 589

    // Keep this last
    auto focusWatcher = new CompositeWidgetFocusWatcher(this, this);
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            this, [this](bool focused) {_hasCompositeFocus = focused;});
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            this, &TerminalDisplay::compositeFocusChanged);
    connect(focusWatcher, &CompositeWidgetFocusWatcher::compositeFocusChanged,
            _headerBar, &TerminalHeaderBar::setFocusIndicatorState);

590
#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
591
    QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
592
#endif
593 594

    connect(KonsoleSettings::self(), &KonsoleSettings::configChanged, this, &TerminalDisplay::setupHeaderVisibility);
595 596
}

597
TerminalDisplay::~TerminalDisplay()
598
{
599 600 601
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

602 603
    delete _readOnlyMessageWidget;
    delete _outputSuspendedMessageWidget;
604 605
    delete[] _image;
    delete _filterChain;
606 607 608

    _readOnlyMessageWidget = nullptr;
    _outputSuspendedMessageWidget = nullptr;
609 610
}

611 612 613 614 615 616
void TerminalDisplay::setupHeaderVisibility()
{
    _headerBar->applyVisibilitySettings();
    calcGeometry();
}

617 618 619 620 621 622
void TerminalDisplay::hideDragTarget()
{
    _drawOverlay = false;
    update();
}

623
void TerminalDisplay::showDragTarget(const QPoint& cursorPos)
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644
{
    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();
}

645 646 647 648 649 650
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                             Display Operations                            */
/*                                                                           */
/* ------------------------------------------------------------------------- */

651
void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
652
        const Character* attributes)
653
{
654 655 656 657 658 659
    // 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);

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

662
    QRect cellRect = {x, y, _fontWidth, _fontHeight};
Kurt Hindenburg's avatar
Kurt Hindenburg committed
663
    for (int i = 0 ; i < str.length(); i++) {
664 665
        LineBlockCharacters::draw(painter, cellRect.translated(i * _fontWidth, 0), str[i],
                                        useBoldPen);
666
    }
667 668

    painter.setRenderHint(QPainter::Antialiasing, false);
669 670
}

671
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
672 673 674
{
    _cursorShape = shape;
}
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692

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()
{
693
    Q_ASSERT(_sessionController != nullptr);
694
    Q_ASSERT(!_sessionController->session().isNull());
695

696
    Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(_sessionController->session());
697

698
    if (currentProfile != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
699
        auto shape = static_cast<Enum::CursorShapeEnum>(currentProfile->property<int>(Profile::CursorShape));
700 701 702

        setKeyboardCursorShape(shape);
        setBlinkingCursorEnabled(currentProfile->blinkingCursorEnabled());
703 704 705
    }
}

706
void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
707
{
708
    _cursorColor = color;
709
}
710

711 712 713 714 715
void TerminalDisplay::setKeyboardCursorTextColor(const QColor& color)
{
    _cursorTextColor = color;
}

716 717 718 719
void TerminalDisplay::setOpacity(qreal opacity)
{
    QColor color(_blendColor);
    color.setAlphaF(opacity);
720
    _opacity = opacity;
721

722
    _blendColor = color.rgba();
723
    onColorsChanged();
724 725
}

726
void TerminalDisplay::setWallpaper(const ColorSchemeWallpaper::Ptr &p)
727
{
728
    _wallpaper = p;
729 730
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
731
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting)
732
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
733 734 735 736 737 738 739 740 741
    // 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() &&
742
            _wallpaper->draw(painter, rect, _opacity)) {
743
    } else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) {
744
#if defined(Q_OS_MACOS)
745
        // TODO - On MacOS, using CompositionMode doesn't work.  Altering the
746
        //        transparency in the color scheme alters the brightness.
747
        painter.fillRect(rect, backgroundColor);
748
#else
Kurt Hindenburg's avatar
Kurt Hindenburg committed
749 750 751
        QColor color(backgroundColor);
        color.setAlpha(qAlpha(_blendColor));

752
        const QPainter::CompositionMode originalMode = painter.compositionMode();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
753
        painter.setCompositionMode(QPainter::CompositionMode_Source);
754
        painter.fillRect(rect, color);
755
        painter.setCompositionMode(originalMode);
756
#endif
Kurt Hindenburg's avatar
Kurt Hindenburg committed
757
    } else {
758
        painter.fillRect(rect, backgroundColor);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
759
    }
760 761
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
762
void TerminalDisplay::drawCursor(QPainter& painter,
763 764
                                 const QRect& rect,
                                 const QColor& foregroundColor,
765 766
                                 const QColor& backgroundColor,
                                 QColor& characterColor)
767
{
Jekyll Wu's avatar
Jekyll Wu committed
768
    // don't draw cursor which is currently blinking
Kurt Hindenburg's avatar
Kurt Hindenburg committed
769
    if (_cursorBlinking) {
770
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
771
    }
Jekyll Wu's avatar
Jekyll Wu committed
772

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

777
    QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
778 779 780 781 782 783 784 785
    QPen pen(cursorColor);
    // TODO: the relative pen width to draw the cursor is a bit hacky
    // and set to 1/12 of the font width. Visually it seems to work at
    // all scales but there must be better ways to do it
    const qreal width = qMax(_fontWidth / 12.0, 1.0);
    const qreal halfWidth = width / 2.0;
    pen.setWidthF(width);
    painter.setPen(pen);
Jekyll Wu's avatar
Jekyll Wu committed
786

787
    if (_cursorShape == Enum::BlockCursor) {
788 789
        // draw the cursor outline, adjusting the area so that it is draw entirely inside 'rect'
        painter.drawRect(cursorRect.adjusted(halfWidth, halfWidth, -halfWidth, -halfWidth));
Jekyll Wu's avatar
Jekyll Wu committed
790 791

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