TerminalDisplay.cpp 129 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 27 28
// Config
#include <config-konsole.h>

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
#include <QAction>
37
#include <QFontDatabase>
38
#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 "Filter.h"
63
#include "konsoledebug.h"
Robert Knight's avatar
Robert Knight committed
64
#include "konsole_wcwidth.h"
65
#include "TerminalCharacterDecoder.h"
66
#include "Screen.h"
Jekyll Wu's avatar
Jekyll Wu committed
67
#include "LineFont.h"
68
#include "SessionController.h"
69
#include "ExtendedCharTable.h"
70
#include "TerminalDisplayAccessible.h"
71 72
#include "SessionManager.h"
#include "Session.h"
73
#include "WindowSystemInfo.h"
74

75 76
using namespace Konsole;

77
#ifndef loc
78
#define loc(X,Y) ((Y)*_columns+(X))
79 80
#endif

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

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

89 90 91 92 93 94 95 96 97 98 99 100 101 102
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                                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
*/

103
ScreenWindow* TerminalDisplay::screenWindow() const
104 105 106
{
    return _screenWindow;
}
107
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
108
{
109
    // disconnect existing screen window if any
110
    if (_screenWindow != nullptr) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
111
        disconnect(_screenWindow , nullptr , this , nullptr);
112 113
    }

114
    _screenWindow = window;
115

116
    if (_screenWindow != nullptr) {
117 118 119
        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);
120
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, this, [this]() {
121 122
            _filterUpdateRequired = true;
        });
123
        connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, this, [this]() {
124 125
            _filterUpdateRequired = true;
        });
Jekyll Wu's avatar
Jekyll Wu committed
126
        _screenWindow->setWindowLines(_lines);
127
    }
128 129
}

130
const ColorEntry* TerminalDisplay::colorTable() const
131
{
Jekyll Wu's avatar
Jekyll Wu committed
132
    return _colorTable;
133
}
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153

void TerminalDisplay::updateScrollBarPalette()
{
    QColor backgroundColor = _colorTable[DEFAULT_BACK_COLOR];
    backgroundColor.setAlphaF(_opacity);
    QPalette p = palette();
    p.setColor(QPalette::Window, backgroundColor);

    //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
    QColor buttonColor;
    buttonColor.setHsvF(backgroundColor.hueF(), backgroundColor.saturationF(), backgroundColor.valueF() + (backgroundColor.valueF() < 0.5 ? 0.2 : -0.2));
    p.setColor(QPalette::Button, buttonColor);

    p.setColor(QPalette::WindowText, _colorTable[DEFAULT_FORE_COLOR]);
    p.setColor(QPalette::ButtonText, _colorTable[DEFAULT_FORE_COLOR]);
    _scrollBar->setPalette(p);

}

154 155
void TerminalDisplay::setBackgroundColor(const QColor& color)
{
156
    _colorTable[DEFAULT_BACK_COLOR] = color;
Jekyll Wu's avatar
Jekyll Wu committed
157

158
    QPalette p = palette();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
159 160
    p.setColor(backgroundRole(), color);
    setPalette(p);
161

162
    updateScrollBarPalette();
163
    update();
164
}
165 166
QColor TerminalDisplay::getBackgroundColor() const
{
167 168 169
    QPalette p = palette();
    return p.color(backgroundRole());
}
170 171
void TerminalDisplay::setForegroundColor(const QColor& color)
{
172
    _colorTable[DEFAULT_FORE_COLOR] = color;
173

174
    updateScrollBarPalette();
175
    update();
176
}
177
void TerminalDisplay::setColorTable(const ColorEntry table[])
178
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
179
    for (int i = 0; i < TABLE_COLORS; i++) {
Jekyll Wu's avatar
Jekyll Wu committed
180
        _colorTable[i] = table[i];
Kurt Hindenburg's avatar
Kurt Hindenburg committed
181
    }
182

183
    setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR]);
184 185 186 187 188 189 190 191
}

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

192 193
static inline bool isLineCharString(const QString& string)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
194
    if (string.length() == 0) {
195
        return false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
196
    }
197

Kurt Hindenburg's avatar
Kurt Hindenburg committed
198
    return isSupportedLineChar(string.at(0).unicode());
199
}
200

201
void TerminalDisplay::fontChange(const QFont&)
202
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
203 204
    QFontMetrics fm(font());
    _fontHeight = fm.height() + _lineSpacing;
205

Kurt Hindenburg's avatar
Kurt Hindenburg committed
206 207 208 209
    // 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
210
    _fontWidth = qRound((static_cast<double>(fm.width(QStringLiteral(REPCHAR))) / static_cast<double>(qstrlen(REPCHAR))));
Waldo Bastian's avatar
Waldo Bastian committed
211

Kurt Hindenburg's avatar
Kurt Hindenburg committed
212
    _fixedFont = true;
213

214
    const int fw = fm.width(QLatin1Char(REPCHAR[0]));
215
    for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
216
        if (fw != fm.width(QLatin1Char(REPCHAR[i]))) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
217 218 219
            _fixedFont = false;
            break;
        }
220
    }
Stephan Kulow's avatar
Stephan Kulow committed
221

Kurt Hindenburg's avatar
Kurt Hindenburg committed
222
    if (_fontWidth < 1) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
223
        _fontWidth = 1;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
224
    }
225

Kurt Hindenburg's avatar
Kurt Hindenburg committed
226
    _fontAscent = fm.ascent();
227

Kurt Hindenburg's avatar
Kurt Hindenburg committed
228 229 230
    emit changedFontMetricSignal(_fontHeight, _fontWidth);
    propagateSize();
    update();
231 232
}

233
void TerminalDisplay::setVTFont(const QFont& f)
234
{
235 236
    QFont newFont(f);
    QFontMetrics fontMetrics(newFont);
Robert Knight's avatar
Robert Knight committed
237

238
    // This check seems extreme and semi-random
Kurt Hindenburg's avatar
Kurt Hindenburg committed
239
    if ((fontMetrics.height() > height()) || (fontMetrics.maxWidth() > width())) {
240
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
241
    }
Robert Knight's avatar
Robert Knight committed
242

243 244
    // hint that text should be drawn without anti-aliasing.
    // depending on the user's font configuration, this may not be respected
Kurt Hindenburg's avatar
Kurt Hindenburg committed
245
    if (!_antialiasText) {
246
        newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::NoAntialias));
Kurt Hindenburg's avatar
Kurt Hindenburg committed
247
    }
248

249 250 251 252
    // 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);
253

254 255
    // Konsole cannot handle non-integer font metrics
    newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::ForceIntegerMetrics));
256

257
    QFontInfo fontInfo(newFont);
Robert Knight's avatar
 
Robert Knight committed
258

259 260 261
//    if (!fontInfo.fixedPitch()) {
//        qWarning() << "Using a variable-width font - this might cause display problems";
//    }
262

263 264 265 266 267 268 269
    // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch()
    // related?  https://bugreports.qt.io/browse/QTBUG-34082
    if (!fontInfo.exactMatch()) {
        const QChar comma(QLatin1Char(','));
        QString nonMatching = fontInfo.family() % comma %
            QString::number(fontInfo.pointSizeF()) % comma %
            QString::number(fontInfo.pixelSize()) % comma %
270
            QString::number(static_cast<int>(fontInfo.styleHint())) % comma %
271
            QString::number(fontInfo.weight()) % comma %
272 273 274 275 276
            QString::number(static_cast<int>(fontInfo.style())) % comma %
            QString::number(static_cast<int>(fontInfo.underline())) % comma %
            QString::number(static_cast<int>(fontInfo.strikeOut())) % comma %
            QString::number(static_cast<int>(fontInfo.fixedPitch())) % comma %
            QString::number(static_cast<int>(fontInfo.rawMode()));
277

278 279 280
        qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system.";
        qCDebug(KonsoleDebug)<<" Selected: "<<newFont.toString();
        qCDebug(KonsoleDebug)<<" System  : "<<nonMatching;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
281
    }
282 283 284

    QWidget::setFont(newFont);
    fontChange(newFont);
285 286
}

287
void TerminalDisplay::setFont(const QFont &)
288
{
289
    // ignore font change request if not coming from konsole itself
290 291
}

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
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));
    setVTFont(font);
}

Jekyll Wu's avatar
Jekyll Wu committed
308 309 310 311 312 313 314 315 316 317 318
uint TerminalDisplay::lineSpacing() const
{
    return _lineSpacing;
}

void TerminalDisplay::setLineSpacing(uint i)
{
    _lineSpacing = i;
    setVTFont(font()); // Trigger an update.
}

319 320 321 322 323 324 325

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

326 327
namespace Konsole
{
Alex Richardson's avatar
Alex Richardson committed
328 329

#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
330
/**
331
 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
Kurt Hindenburg's avatar
Kurt Hindenburg committed
332 333 334 335 336
 * for the TerminalDisplay.
 */
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
{
    Q_UNUSED(key)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
337
    if (TerminalDisplay *display = qobject_cast<TerminalDisplay*>(object)) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
338
        return new TerminalDisplayAccessible(display);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
339
    }
Kurt Hindenburg's avatar
Kurt Hindenburg committed
340
    return nullptr;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
341
}
Alex Richardson's avatar
Alex Richardson committed
342 343

#endif
344 345
}

346 347 348 349 350 351
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

352
TerminalDisplay::TerminalDisplay(QWidget* parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
353
    : QWidget(parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
354
    , _screenWindow(nullptr)
Jekyll Wu's avatar
Jekyll Wu committed
355
    , _bellMasked(false)
356
    , _verticalLayout(new QVBoxLayout(this))
Kurt Hindenburg's avatar
Kurt Hindenburg committed
357 358 359 360 361 362 363 364
    , _fontHeight(1)
    , _fontWidth(1)
    , _fontAscent(1)
    , _boldIntense(true)
    , _lines(1)
    , _columns(1)
    , _usedLines(1)
    , _usedColumns(1)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
365
    , _image(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
366 367 368 369
    , _randomSeed(0)
    , _resizing(false)
    , _showTerminalSizeHint(true)
    , _bidiEnabled(false)
Ahmad Samir's avatar
Ahmad Samir committed
370
    , _isPrimaryScreen(true)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
371 372 373 374 375
    , _actSel(0)
    , _wordSelectionMode(false)
    , _lineSelectionMode(false)
    , _preserveLineBreaks(false)
    , _columnSelectionMode(false)
376
    , _autoCopySelectedText(false)
377
    , _copyTextAsHTML(true)
378
    , _middleClickPasteMode(Enum::PasteFromX11Selection)
379
    , _scrollbarLocation(Enum::ScrollBarRight)
380
    , _scrollFullPage(false)
381
    , _wordCharacters(QStringLiteral(":@-./_~"))
382
    , _bellMode(Enum::NotifyBell)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
383 384 385 386 387
    , _allowBlinkingText(true)
    , _allowBlinkingCursor(false)
    , _textBlinking(false)
    , _cursorBlinking(false)
    , _hasTextBlinker(false)
388
    , _urlHintsModifiers(Qt::NoModifier)
389
    , _showUrlHint(false)
390
    , _openLinksByDirectClick(false)
391
    , _ctrlRequiredForDrag(true)
392
    , _tripleClickMode(Enum::SelectWholeLine)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
393
    , _possibleTripleClick(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
394 395
    , _resizeWidget(nullptr)
    , _resizeTimer(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
396
    , _flowControlWarningEnabled(false)
397
    , _outputSuspendedMessageWidget(nullptr)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
398 399 400
    , _lineSpacing(0)
    , _blendColor(qRgba(0, 0, 0, 0xff))
    , _filterChain(new TerminalImageFilterChain())
401
    , _filterUpdateRequired(true)
402
    , _cursorShape(Enum::BlockCursor)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
403
    , _antialiasText(true)
404
    , _useFontLineCharacters(false)
405
    , _printerFriendly(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
406
    , _sessionController(nullptr)
407
    , _trimLeadingSpaces(false)
408
    , _trimTrailingSpaces(false)
409 410
    , _margin(1)
    , _centerContents(false)
411
    , _readOnlyMessageWidget(nullptr)
412
    , _readOnly(false)
413
    , _opacity(1.0)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
414 415 416 417 418
{
    // terminal applications are not designed with Right-To-Left in mind,
    // so the layout is forced to Left-To-Right
    setLayoutDirection(Qt::LeftToRight);

419
    _contentRect = QRect(_margin, _margin, 1, 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
420 421 422

    // create scroll bar for scrolling output up and down
    _scrollBar = new QScrollBar(this);
423
    _scrollBar->setAutoFillBackground(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
424 425 426
    // set the scroll bar's slider to occupy the whole area of the scroll bar initially
    setScroll(0, 0);
    _scrollBar->setCursor(Qt::ArrowCursor);
Laurent Montel's avatar
Laurent Montel committed
427 428
    connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
    connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
429

430 431 432
    // setup timers for blinking text
    _blinkTextTimer = new QTimer(this);
    _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
433
    connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
434 435 436 437

    // setup timers for blinking cursor
    _blinkCursorTimer = new QTimer(this);
    _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
438
    connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
439 440 441 442 443 444

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

    setUsesMouse(true);
445
    setBracketedPasteMode(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
446

447
    setColorTable(ColorScheme::defaultTable);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
448 449 450 451 452 453 454 455 456 457 458 459 460 461

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

462 463 464
    // Add the stretch item once, the KMessageWidgets are inserted at index 0.
    _verticalLayout->addStretch();
    _verticalLayout->setSpacing(0);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
465

466 467 468 469 470 471 472 473
    setLayout(_verticalLayout);

    // Take the scrollbar into account and add a margin to the layout. Without the timer the scrollbar width
    // is garbage.
    QTimer::singleShot(0, this, [this]() {
        const int scrollBarWidth = _scrollBar->isVisible() ? geometry().intersected(_scrollBar->geometry()).width() : 0;
        _verticalLayout->setContentsMargins(0, 0, scrollBarWidth, 0);
    });
Kurt Hindenburg's avatar
Kurt Hindenburg committed
474 475

    new AutoScrollHandler(this);
476 477 478


#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
479
    QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
480
#endif
481 482
}

483
TerminalDisplay::~TerminalDisplay()
484
{
485 486 487 488 489
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

    delete[] _image;
    delete _filterChain;
490 491 492 493 494
    delete _readOnlyMessageWidget;
    delete _outputSuspendedMessageWidget;

    _readOnlyMessageWidget = nullptr;
    _outputSuspendedMessageWidget = nullptr;
495 496 497 498 499 500 501 502
}

/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                             Display Operations                            */
/*                                                                           */
/* ------------------------------------------------------------------------- */

503 504 505 506 507
/**
 A table for emulating the simple (single width) unicode drawing chars.
 It represents the 250x - 257x glyphs. If it's zero, we can't use it.
 if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
Stephan Kulow's avatar
Stephan Kulow committed
508

509 510 511 512 513 514 515 516 517 518 519
 Then, the pixels basically have the following interpretation:
 _|||_
 -...-
 -...-
 -...-
 _|||_

where _ = none
      | = vertical line.
      - = horizontal line.
 */
Stephan Kulow's avatar
Stephan Kulow committed
520

Kurt Hindenburg's avatar
Kurt Hindenburg committed
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
enum LineEncode {
    TopL  = (1 << 1),
    TopC  = (1 << 2),
    TopR  = (1 << 3),

    LeftT = (1 << 5),
    Int11 = (1 << 6),
    Int12 = (1 << 7),
    Int13 = (1 << 8),
    RightT = (1 << 9),

    LeftC = (1 << 10),
    Int21 = (1 << 11),
    Int22 = (1 << 12),
    Int23 = (1 << 13),
    RightC = (1 << 14),

    LeftB = (1 << 15),
    Int31 = (1 << 16),
    Int32 = (1 << 17),
    Int33 = (1 << 18),
    RightB = (1 << 19),

    BotL  = (1 << 21),
    BotC  = (1 << 22),
    BotR  = (1 << 23)
547 548 549 550 551
};

static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
{
    //Calculate cell midpoints, end points.
Jekyll Wu's avatar
Jekyll Wu committed
552 553 554 555
    const int cx = x + w / 2;
    const int cy = y + h / 2;
    const int ex = x + w - 1;
    const int ey = y + h - 1;
556

Jekyll Wu's avatar
Jekyll Wu committed
557
    const quint32 toDraw = LineChars[code];
Stephan Kulow's avatar
Stephan Kulow committed
558

559
    //Top _lines:
Kurt Hindenburg's avatar
Kurt Hindenburg committed
560
    if ((toDraw & TopL) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
561
        paint.drawLine(cx - 1, y, cx - 1, cy - 2);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
562 563
    }
    if ((toDraw & TopC) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
564
        paint.drawLine(cx, y, cx, cy - 2);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
565 566
    }
    if ((toDraw & TopR) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
567
        paint.drawLine(cx + 1, y, cx + 1, cy - 2);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
568
    }
569

570
    //Bot _lines:
Kurt Hindenburg's avatar
Kurt Hindenburg committed
571
    if ((toDraw & BotL) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
572
        paint.drawLine(cx - 1, cy + 2, cx - 1, ey);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
573 574
    }
    if ((toDraw & BotC) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
575
        paint.drawLine(cx, cy + 2, cx, ey);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
576 577
    }
    if ((toDraw & BotR) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
578
        paint.drawLine(cx + 1, cy + 2, cx + 1, ey);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
579
    }
580

581
    //Left _lines:
Kurt Hindenburg's avatar
Kurt Hindenburg committed
582
    if ((toDraw & LeftT) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
583
        paint.drawLine(x, cy - 1, cx - 2, cy - 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
584 585
    }
    if ((toDraw & LeftC) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
586
        paint.drawLine(x, cy, cx - 2, cy);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
587 588
    }
    if ((toDraw & LeftB) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
589
        paint.drawLine(x, cy + 1, cx - 2, cy + 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
590
    }
591

592
    //Right _lines:
Kurt Hindenburg's avatar
Kurt Hindenburg committed
593
    if ((toDraw & RightT) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
594
        paint.drawLine(cx + 2, cy - 1, ex, cy - 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
595 596
    }
    if ((toDraw & RightC) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
597
        paint.drawLine(cx + 2, cy, ex, cy);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
598 599
    }
    if ((toDraw & RightB) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
600
        paint.drawLine(cx + 2, cy + 1, ex, cy + 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
601
    }
Stephan Kulow's avatar
Stephan Kulow committed
602

603
    //Intersection points.
Kurt Hindenburg's avatar
Kurt Hindenburg committed
604
    if ((toDraw & Int11) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
605
        paint.drawPoint(cx - 1, cy - 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
606 607
    }
    if ((toDraw & Int12) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
608
        paint.drawPoint(cx, cy - 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
609 610
    }
    if ((toDraw & Int13) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
611
        paint.drawPoint(cx + 1, cy - 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
612
    }
Stephan Kulow's avatar
Stephan Kulow committed
613

Kurt Hindenburg's avatar
Kurt Hindenburg committed
614
    if ((toDraw & Int21) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
615
        paint.drawPoint(cx - 1, cy);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
616 617
    }
    if ((toDraw & Int22) != 0u) {
618
        paint.drawPoint(cx, cy);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
619 620
    }
    if ((toDraw & Int23) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
621
        paint.drawPoint(cx + 1, cy);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
622
    }
623

Kurt Hindenburg's avatar
Kurt Hindenburg committed
624
    if ((toDraw & Int31) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
625
        paint.drawPoint(cx - 1, cy + 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
626 627
    }
    if ((toDraw & Int32) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
628
        paint.drawPoint(cx, cy + 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
629 630
    }
    if ((toDraw & Int33) != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
631
        paint.drawPoint(cx + 1, cy + 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
632
    }
633 634
}

635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
static void drawOtherChar(QPainter& paint, int x, int y, int w, int h, uchar code)
{
    //Calculate cell midpoints, end points.
    const int cx = x + w / 2;
    const int cy = y + h / 2;
    const int ex = x + w - 1;
    const int ey = y + h - 1;

    // Double dashes
    if (0x4C <= code && code <= 0x4F) {
        const int xHalfGap = qMax(w / 15, 1);
        const int yHalfGap = qMax(h / 15, 1);
        switch (code) {
        case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
            paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1);
            paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1);
            paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1);
            paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1);
            // No break!
654
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
655
            Q_FALLTHROUGH();
656
#endif
657 658 659 660 661 662 663 664 665 666
        case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
            paint.drawLine(x, cy, cx - xHalfGap - 1, cy);
            paint.drawLine(cx + xHalfGap, cy, ex, cy);
            break;
        case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
            paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1);
            paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1);
            paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey);
            paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey);
            // No break!
667
#if (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
668
            Q_FALLTHROUGH();
669
#endif
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721
        case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
            paint.drawLine(cx, y, cx, cy - yHalfGap - 1);
            paint.drawLine(cx, cy + yHalfGap, cx, ey);
            break;
        }
    }

    // Rounded corner characters
    else if (0x6D <= code && code <= 0x70) {
        const int r = w * 3 / 8;
        const int d = 2 * r;
        switch (code) {
        case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
            paint.drawLine(cx, cy + r, cx, ey);
            paint.drawLine(cx + r, cy, ex, cy);
            paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16);
            break;
        case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
            paint.drawLine(cx, cy + r, cx, ey);
            paint.drawLine(x, cy, cx - r, cy);
            paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16);
            break;
        case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT
            paint.drawLine(cx, y, cx, cy - r);
            paint.drawLine(x, cy, cx - r, cy);
            paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16);
            break;
        case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT
            paint.drawLine(cx, y, cx, cy - r);
            paint.drawLine(cx + r, cy, ex, cy);
            paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16);
            break;
        }
    }

    // Diagonals
    else if (0x71 <= code && code <= 0x73) {
        switch (code) {
        case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
            paint.drawLine(ex, y, x, ey);
            break;
        case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
            paint.drawLine(x, y, ex, ey);
            break;
        case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS
            paint.drawLine(ex, y, x, ey);
            paint.drawLine(x, y, ex, ey);
            break;
        }
    }
}

722
void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
723
        const Character* attributes)
724
{
725 726
    const QPen& originalPen = painter.pen();

727
    if (((attributes->rendition & RE_BOLD) != 0) && _boldIntense) {
728 729
        QPen boldPen(originalPen);
        boldPen.setWidth(3);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
730
        painter.setPen(boldPen);
731 732
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
733
    for (int i = 0 ; i < str.length(); i++) {
Jekyll Wu's avatar
Jekyll Wu committed
734
        const uchar code = str[i].cell();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
735
        if (LineChars[code] != 0u) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
736
            drawLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
737
        } else {
738
            drawOtherChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
739
        }
740 741
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
742
    painter.setPen(originalPen);
743 744
}

745
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
746 747 748
{
    _cursorShape = shape;
}
749
Enum::CursorShapeEnum TerminalDisplay::keyboardCursorShape() const
750 751 752
{
    return _cursorShape;
}
753
void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
754
{
755
    _cursorColor = color;
756 757 758 759 760
}
QColor TerminalDisplay::keyboardCursorColor() const
{
    return _cursorColor;
}
761

762 763 764 765
void TerminalDisplay::setOpacity(qreal opacity)
{
    QColor color(_blendColor);
    color.setAlphaF(opacity);
766
    _opacity = opacity;
767 768 769

    // enable automatic background filling to prevent the display
    // flickering if there is no transparency
Kurt Hindenburg's avatar
Kurt Hindenburg committed
770
    /*if ( color.alpha() == 255 )
771 772 773 774 775 776 777 778
    {
        setAutoFillBackground(true);
    }
    else
    {
        setAutoFillBackground(false);
    }*/

779
    _blendColor = color.rgba();
780
    updateScrollBarPalette();
781 782
}

783 784
void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p)
{
785
    _wallpaper = p;
786 787
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
788
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting)
789
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
790 791 792 793 794 795 796 797 798 799 800 801 802 803 804
    // 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.
    //
    QRect scrollBarArea = _scrollBar->isVisible() ?
                          rect.intersected(_scrollBar->geometry()) :
                          QRect();
    QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
    QRect contentsRect = contentsRegion.boundingRect();

    if (useOpacitySetting && !_wallpaper->isNull() &&
805
            _wallpaper->draw(painter, contentsRect, _opacity)) {
806
    } else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) {
807
#if defined(Q_OS_MACOS)
808
        // TODO - On MacOS, using CompositionMode doesn't work.  Altering the
809 810 811
        //        transparency in the color scheme alters the brightness.
        painter.fillRect(contentsRect, backgroundColor);
#else
Kurt Hindenburg's avatar
Kurt Hindenburg committed
812 813 814 815 816 817 818
        QColor color(backgroundColor);
        color.setAlpha(qAlpha(_blendColor));

        painter.save();
        painter.setCompositionMode(QPainter::CompositionMode_Source);
        painter.fillRect(contentsRect, color);
        painter.restore();
819
#endif
Kurt Hindenburg's avatar
Kurt Hindenburg committed
820 821 822
    } else {
        painter.fillRect(contentsRect, backgroundColor);
    }
823

Kurt Hindenburg's avatar
Kurt Hindenburg committed
824
    painter.fillRect(scrollBarArea, _scrollBar->palette().background());
825 826
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
827
void TerminalDisplay::drawCursor(QPainter& painter,
828 829 830 831 832
                                 const QRect& rect,
                                 const QColor& foregroundColor,
                                 const QColor& /*backgroundColor*/,
                                 bool& invertCharacterColor)
{
Jekyll Wu's avatar
Jekyll Wu committed
833
    // don't draw cursor which is currently blinking
Kurt Hindenburg's avatar
Kurt Hindenburg committed
834
    if (_cursorBlinking) {
835
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
836
    }
Jekyll Wu's avatar
Jekyll Wu committed
837

838 839 840
    // shift rectangle top down one pixel to leave some space
    // between top and bottom
    QRect cursorRect = rect.adjusted(0, 1, 0, 0);
Jekyll Wu's avatar
Jekyll Wu committed
841

842
    QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
Jekyll Wu's avatar
Jekyll Wu committed
843 844
    painter.setPen(cursorColor);

845
    if (_cursorShape == Enum::BlockCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
846 847
        // draw the cursor outline, adjusting the area so that
        // it is draw entirely inside 'rect'
Kurt Hindenburg's avatar
Kurt Hindenburg committed
848 849 850 851 852
        int penWidth = qMax(1, painter.pen().width());
        painter.drawRect(cursorRect.adjusted(penWidth / 2,
                                             penWidth / 2,
                                             - penWidth / 2 - penWidth % 2,
                                             - penWidth / 2 - penWidth % 2));
Jekyll Wu's avatar
Jekyll Wu committed
853 854

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
858
            if (!_cursorColor.isValid()) {
Jekyll Wu's avatar
Jekyll Wu committed
859
                // invert the color used to draw the text to ensure that the character at
Jekyll Wu's avatar
Jekyll Wu committed
860 861
                // the cursor position is readable
                invertCharacterColor = true;
862
            }
Jekyll Wu's avatar
Jekyll Wu committed
863
        }
864
    } else if (_cursorShape == Enum::UnderlineCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
865 866 867 868 869
        painter.drawLine(cursorRect.left(),
                         cursorRect.bottom(),
                         cursorRect.right(),
                         cursorRect.bottom());

870
    } else if (_cursorShape == Enum::IBeamCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
871 872 873 874
        painter.drawLine(cursorRect.left(),
                         cursorRect.top(),
                         cursorRect.left(),
                         cursorRect.bottom());
875
    }
876 877 878 879 880 881 882 883 884
}

void TerminalDisplay::drawCharacters(QPainter& painter,
                                     const QRect& rect,
                                     const QString& text,
                                     const Character* style,
                                     bool invertCharacterColor)
{
    // don't draw text which is currently blinking
Kurt Hindenburg's avatar
Kurt Hindenburg committed
885
    if (_textBlinking && ((style->rendition & RE_BLINK) != 0)) {
886
        return;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
887
    }
888

Kurt Hindenburg's avatar