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)