TerminalDisplay.cpp 121 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>
Dirk Mueller's avatar
Dirk Mueller committed
31 32 33
#include <QtGui/QClipboard>
#include <QtGui/QKeyEvent>
#include <QtCore/QEvent>
34
#include <QtCore/QFileInfo>
35 36
#include <QGridLayout>
#include <QAction>
37
#include <QFontDatabase>
38
#include <QLabel>
39
#include <QMimeData>
Dirk Mueller's avatar
Dirk Mueller committed
40 41
#include <QtGui/QPainter>
#include <QtGui/QPixmap>
42 43
#include <QScrollBar>
#include <QStyle>
Dirk Mueller's avatar
Dirk Mueller committed
44
#include <QtCore/QTimer>
Alex Richardson's avatar
Alex Richardson committed
45
#include <QDrag>
46
#include <QDesktopServices>
47
#include <QtGui/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>
Laurent Montel's avatar
Laurent Montel committed
53
#include <QDebug>
54
#include <KLocalizedString>
Robert Knight's avatar
Robert Knight committed
55
#include <KNotification>
56 57
#include <KIO/DropJob>
#include <KJobWidgets>
58
#include <KMessageBox>
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
Kurt Hindenburg's avatar
Kurt Hindenburg committed
110 111
    if (_screenWindow) {
        disconnect(_screenWindow , 0 , this , 0);
112 113
    }

114
    _screenWindow = window;
115

Kurt Hindenburg's avatar
Kurt Hindenburg committed
116
    if (_screenWindow) {
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
void TerminalDisplay::setBackgroundColor(const QColor& color)
{
136
    _colorTable[DEFAULT_BACK_COLOR].color = color;
Jekyll Wu's avatar
Jekyll Wu committed
137

138
    QPalette p = palette();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
139 140
    p.setColor(backgroundRole(), color);
    setPalette(p);
141

Jekyll Wu's avatar
Jekyll Wu committed
142
    // Avoid propagating the palette change to the scroll bar
Kurt Hindenburg's avatar
Kurt Hindenburg committed
143
    _scrollBar->setPalette(QApplication::palette());
144

145
    update();
146
}
147 148
QColor TerminalDisplay::getBackgroundColor() const
{
149 150 151
    QPalette p = palette();
    return p.color(backgroundRole());
}
152 153
void TerminalDisplay::setForegroundColor(const QColor& color)
{
154
    _colorTable[DEFAULT_FORE_COLOR].color = color;
155

156
    update();
157
}
158
void TerminalDisplay::setColorTable(const ColorEntry table[])
159
{
Jekyll Wu's avatar
Jekyll Wu committed
160 161
    for (int i = 0; i < TABLE_COLORS; i++)
        _colorTable[i] = table[i];
162

Jekyll Wu's avatar
Jekyll Wu committed
163
    setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
164 165 166 167 168 169 170 171
}

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

172 173
static inline bool isLineCharString(const QString& string)
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
174
    if (string.length() == 0)
175 176
        return false;

Kurt Hindenburg's avatar
Kurt Hindenburg committed
177
    return isSupportedLineChar(string.at(0).unicode());
178
}
179

180
void TerminalDisplay::fontChange(const QFont&)
181
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
182 183
    QFontMetrics fm(font());
    _fontHeight = fm.height() + _lineSpacing;
184

Kurt Hindenburg's avatar
Kurt Hindenburg committed
185 186 187 188
    // 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
189
    _fontWidth = qRound((static_cast<double>(fm.width(REPCHAR)) / static_cast<double>(qstrlen(REPCHAR))));
Waldo Bastian's avatar
Waldo Bastian committed
190

Kurt Hindenburg's avatar
Kurt Hindenburg committed
191
    _fixedFont = true;
192

Jekyll Wu's avatar
Jekyll Wu committed
193
    const int fw = fm.width(REPCHAR[0]);
194
    for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
195 196 197 198
        if (fw != fm.width(REPCHAR[i])) {
            _fixedFont = false;
            break;
        }
199
    }
Stephan Kulow's avatar
Stephan Kulow committed
200

Kurt Hindenburg's avatar
Kurt Hindenburg committed
201 202
    if (_fontWidth < 1)
        _fontWidth = 1;
203

Kurt Hindenburg's avatar
Kurt Hindenburg committed
204
    _fontAscent = fm.ascent();
205

Kurt Hindenburg's avatar
Kurt Hindenburg committed
206 207 208
    emit changedFontMetricSignal(_fontHeight, _fontWidth);
    propagateSize();
    update();
209 210
}

211
void TerminalDisplay::setVTFont(const QFont& f)
212
{
213 214
    QFont newFont(f);
    QFontMetrics fontMetrics(newFont);
Robert Knight's avatar
Robert Knight committed
215

216 217 218
    // This check seems extreme and semi-random
    if ((fontMetrics.height() > height()) || (fontMetrics.maxWidth() > width()))
        return;
Robert Knight's avatar
Robert Knight committed
219

220 221 222 223
    // hint that text should be drawn without anti-aliasing.
    // depending on the user's font configuration, this may not be respected
    if (!_antialiasText)
        newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::NoAntialias));
224

225 226 227 228
    // 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);
229

230 231
    // Konsole cannot handle non-integer font metrics
    newFont.setStyleStrategy(QFont::StyleStrategy(newFont.styleStrategy() | QFont::ForceIntegerMetrics));
232

233
    QFontInfo fontInfo(newFont);
Robert Knight's avatar
 
Robert Knight committed
234

235 236 237
//    if (!fontInfo.fixedPitch()) {
//        qWarning() << "Using a variable-width font - this might cause display problems";
//    }
238

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
    // 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 %
            QString::number((int)fontInfo.styleHint()) % comma %
            QString::number(fontInfo.weight()) % comma %
            QString::number((int)fontInfo.style()) % comma %
            QString::number((int)fontInfo.underline()) % comma %
            QString::number((int)fontInfo.strikeOut()) % comma %
            QString::number((int)fontInfo.fixedPitch()) % comma %
            QString::number((int)fontInfo.rawMode());

254 255 256
        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
257
    }
258 259 260

    QWidget::setFont(newFont);
    fontChange(newFont);
261 262
}

263
void TerminalDisplay::setFont(const QFont &)
264
{
265
    // ignore font change request if not coming from konsole itself
266 267
}

268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283
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
284 285 286 287 288 289 290 291 292 293 294
uint TerminalDisplay::lineSpacing() const
{
    return _lineSpacing;
}

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

295 296 297 298 299 300 301

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

302 303
namespace Konsole
{
Alex Richardson's avatar
Alex Richardson committed
304 305

#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
306
/**
307
 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
Kurt Hindenburg's avatar
Kurt Hindenburg committed
308 309 310 311 312 313 314 315 316
 * for the TerminalDisplay.
 */
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
{
    Q_UNUSED(key)
    if (TerminalDisplay *display = qobject_cast<TerminalDisplay*>(object))
        return new TerminalDisplayAccessible(display);
    return 0;
}
Alex Richardson's avatar
Alex Richardson committed
317 318

#endif
319 320
}

321 322 323 324 325 326
/* ------------------------------------------------------------------------- */
/*                                                                           */
/*                         Constructor / Destructor                          */
/*                                                                           */
/* ------------------------------------------------------------------------- */

327
TerminalDisplay::TerminalDisplay(QWidget* parent)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
328 329
    : QWidget(parent)
    , _screenWindow(0)
Jekyll Wu's avatar
Jekyll Wu committed
330
    , _bellMasked(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
    , _gridLayout(0)
    , _fontHeight(1)
    , _fontWidth(1)
    , _fontAscent(1)
    , _boldIntense(true)
    , _lines(1)
    , _columns(1)
    , _usedLines(1)
    , _usedColumns(1)
    , _image(0)
    , _randomSeed(0)
    , _resizing(false)
    , _showTerminalSizeHint(true)
    , _bidiEnabled(false)
    , _actSel(0)
    , _wordSelectionMode(false)
    , _lineSelectionMode(false)
    , _preserveLineBreaks(false)
    , _columnSelectionMode(false)
350 351
    , _autoCopySelectedText(false)
    , _middleClickPasteMode(Enum::PasteFromX11Selection)
352
    , _scrollbarLocation(Enum::ScrollBarRight)
353
    , _scrollFullPage(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
354
    , _wordCharacters(":@-./_~")
355
    , _bellMode(Enum::NotifyBell)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
356 357 358 359 360
    , _allowBlinkingText(true)
    , _allowBlinkingCursor(false)
    , _textBlinking(false)
    , _cursorBlinking(false)
    , _hasTextBlinker(false)
361
    , _urlHintsModifiers(Qt::NoModifier)
362
    , _showUrlHint(false)
363
    , _openLinksByDirectClick(false)
364
    , _ctrlRequiredForDrag(true)
365
    , _tripleClickMode(Enum::SelectWholeLine)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
366 367 368 369 370 371 372 373
    , _possibleTripleClick(false)
    , _resizeWidget(0)
    , _resizeTimer(0)
    , _flowControlWarningEnabled(false)
    , _outputSuspendedLabel(0)
    , _lineSpacing(0)
    , _blendColor(qRgba(0, 0, 0, 0xff))
    , _filterChain(new TerminalImageFilterChain())
374
    , _filterUpdateRequired(true)
375
    , _cursorShape(Enum::BlockCursor)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
376
    , _antialiasText(true)
377
    , _useFontLineCharacters(false)
378
    , _printerFriendly(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
379
    , _sessionController(0)
380
    , _trimTrailingSpaces(false)
381 382
    , _margin(1)
    , _centerContents(false)
383
    , _opacity(1.0)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
384 385 386 387 388
{
    // terminal applications are not designed with Right-To-Left in mind,
    // so the layout is forced to Left-To-Right
    setLayoutDirection(Qt::LeftToRight);

389
    _contentRect = QRect(_margin, _margin, 1, 1);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
390 391 392 393 394 395

    // create scroll bar for scrolling output up and down
    _scrollBar = new QScrollBar(this);
    // 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
396 397
    connect(_scrollBar, &QScrollBar::valueChanged, this, &Konsole::TerminalDisplay::scrollBarPositionChanged);
    connect(_scrollBar, &QScrollBar::sliderMoved, this, &Konsole::TerminalDisplay::viewScrolledByUser);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
398

399 400 401
    // setup timers for blinking text
    _blinkTextTimer = new QTimer(this);
    _blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
402
    connect(_blinkTextTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkTextEvent);
403 404 405 406

    // setup timers for blinking cursor
    _blinkCursorTimer = new QTimer(this);
    _blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
407
    connect(_blinkCursorTimer, &QTimer::timeout, this, &Konsole::TerminalDisplay::blinkCursorEvent);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
408 409 410 411 412 413

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

    setUsesMouse(true);
414
    setBracketedPasteMode(false);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
415

416
    setColorTable(ColorScheme::defaultTable);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
417 418 419 420 421 422 423 424 425 426 427 428 429 430

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

431
    _gridLayout = new QGridLayout;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
432 433 434 435 436
    _gridLayout->setContentsMargins(0, 0, 0, 0);

    setLayout(_gridLayout);

    new AutoScrollHandler(this);
437 438 439


#ifndef QT_NO_ACCESSIBILITY
Kurt Hindenburg's avatar
Kurt Hindenburg committed
440
    QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
441
#endif
442 443
}

444
TerminalDisplay::~TerminalDisplay()
445
{
446 447 448 449 450
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

    delete[] _image;
    delete _filterChain;
451 452 453 454 455 456 457 458
}

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

459 460 461 462 463
/**
 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
464

465 466 467 468 469 470 471 472 473 474 475
 Then, the pixels basically have the following interpretation:
 _|||_
 -...-
 -...-
 -...-
 _|||_

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
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)
503 504 505 506 507
};

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
508 509 510 511
    const int cx = x + w / 2;
    const int cy = y + h / 2;
    const int ex = x + w - 1;
    const int ey = y + h - 1;
512

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

515
    //Top _lines:
516
    if (toDraw & TopL)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
517
        paint.drawLine(cx - 1, y, cx - 1, cy - 2);
518
    if (toDraw & TopC)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
519
        paint.drawLine(cx, y, cx, cy - 2);
520
    if (toDraw & TopR)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
521
        paint.drawLine(cx + 1, y, cx + 1, cy - 2);
522

523
    //Bot _lines:
524
    if (toDraw & BotL)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
525
        paint.drawLine(cx - 1, cy + 2, cx - 1, ey);
526
    if (toDraw & BotC)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
527
        paint.drawLine(cx, cy + 2, cx, ey);
528
    if (toDraw & BotR)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
529
        paint.drawLine(cx + 1, cy + 2, cx + 1, ey);
530

531
    //Left _lines:
532
    if (toDraw & LeftT)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
533
        paint.drawLine(x, cy - 1, cx - 2, cy - 1);
534
    if (toDraw & LeftC)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
535
        paint.drawLine(x, cy, cx - 2, cy);
536
    if (toDraw & LeftB)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
537
        paint.drawLine(x, cy + 1, cx - 2, cy + 1);
538

539
    //Right _lines:
540
    if (toDraw & RightT)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
541
        paint.drawLine(cx + 2, cy - 1, ex, cy - 1);
542
    if (toDraw & RightC)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
543
        paint.drawLine(cx + 2, cy, ex, cy);
544
    if (toDraw & RightB)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
545
        paint.drawLine(cx + 2, cy + 1, ex, cy + 1);
Stephan Kulow's avatar
Stephan Kulow committed
546

547 548
    //Intersection points.
    if (toDraw & Int11)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
549
        paint.drawPoint(cx - 1, cy - 1);
550
    if (toDraw & Int12)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
551
        paint.drawPoint(cx, cy - 1);
552
    if (toDraw & Int13)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
553
        paint.drawPoint(cx + 1, cy - 1);
Stephan Kulow's avatar
Stephan Kulow committed
554

555
    if (toDraw & Int21)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
556
        paint.drawPoint(cx - 1, cy);
557 558 559
    if (toDraw & Int22)
        paint.drawPoint(cx, cy);
    if (toDraw & Int23)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
560
        paint.drawPoint(cx + 1, cy);
561 562

    if (toDraw & Int31)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
563
        paint.drawPoint(cx - 1, cy + 1);
564
    if (toDraw & Int32)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
565
        paint.drawPoint(cx, cy + 1);
566
    if (toDraw & Int33)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
567
        paint.drawPoint(cx + 1, cy + 1);
568 569
}

570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
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!
        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!
        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;
        }
    }
}

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
    const QPen& originalPen = painter.pen();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
656
    if ((attributes->rendition & RE_BOLD) && _boldIntense) {
657 658
        QPen boldPen(originalPen);
        boldPen.setWidth(3);
Kurt Hindenburg's avatar
Kurt Hindenburg committed
659
        painter.setPen(boldPen);
660 661
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
662
    for (int i = 0 ; i < str.length(); i++) {
Jekyll Wu's avatar
Jekyll Wu committed
663
        const uchar code = str[i].cell();
664
        if (LineChars[code])
Kurt Hindenburg's avatar
Kurt Hindenburg committed
665
            drawLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
666 667
        else
            drawOtherChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
668 669
    }

Kurt Hindenburg's avatar
Kurt Hindenburg committed
670
    painter.setPen(originalPen);
671 672
}

673
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
674 675 676
{
    _cursorShape = shape;
}
677
Enum::CursorShapeEnum TerminalDisplay::keyboardCursorShape() const
678 679 680
{
    return _cursorShape;
}
681
void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
682
{
683
    _cursorColor = color;
684 685 686 687 688
}
QColor TerminalDisplay::keyboardCursorColor() const
{
    return _cursorColor;
}
689

690 691 692 693
void TerminalDisplay::setOpacity(qreal opacity)
{
    QColor color(_blendColor);
    color.setAlphaF(opacity);
694
    _opacity = opacity;
695 696 697

    // enable automatic background filling to prevent the display
    // flickering if there is no transparency
Kurt Hindenburg's avatar
Kurt Hindenburg committed
698
    /*if ( color.alpha() == 255 )
699 700 701 702 703 704 705 706
    {
        setAutoFillBackground(true);
    }
    else
    {
        setAutoFillBackground(false);
    }*/

707 708 709
    _blendColor = color.rgba();
}

710 711
void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p)
{
712
    _wallpaper = p;
713 714
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
715
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting)
716
{
Kurt Hindenburg's avatar
Kurt Hindenburg committed
717 718 719 720 721 722 723 724 725 726 727 728 729 730 731
    // 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() &&
732
            _wallpaper->draw(painter, contentsRect, _opacity)) {
733
    } else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
734
#if defined(Q_OS_OSX)
735
        // TODO - On MacOS, using CompositionMode doesn't work.  Altering the
736 737 738
        //        transparency in the color scheme alters the brightness.
        painter.fillRect(contentsRect, backgroundColor);
#else
Kurt Hindenburg's avatar
Kurt Hindenburg committed
739 740 741 742 743 744 745
        QColor color(backgroundColor);
        color.setAlpha(qAlpha(_blendColor));

        painter.save();
        painter.setCompositionMode(QPainter::CompositionMode_Source);
        painter.fillRect(contentsRect, color);
        painter.restore();
746
#endif
Kurt Hindenburg's avatar
Kurt Hindenburg committed
747 748 749
    } else {
        painter.fillRect(contentsRect, backgroundColor);
    }
750

Kurt Hindenburg's avatar
Kurt Hindenburg committed
751
    painter.fillRect(scrollBarArea, _scrollBar->palette().background());
752 753
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
754
void TerminalDisplay::drawCursor(QPainter& painter,
755 756 757 758 759
                                 const QRect& rect,
                                 const QColor& foregroundColor,
                                 const QColor& /*backgroundColor*/,
                                 bool& invertCharacterColor)
{
Jekyll Wu's avatar
Jekyll Wu committed
760
    // don't draw cursor which is currently blinking
Kurt Hindenburg's avatar
Kurt Hindenburg committed
761
    if (_cursorBlinking)
762
        return;
Jekyll Wu's avatar
Jekyll Wu committed
763

764 765 766
    // 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
767

768
    QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
Jekyll Wu's avatar
Jekyll Wu committed
769 770
    painter.setPen(cursorColor);

771
    if (_cursorShape == Enum::BlockCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
772 773
        // draw the cursor outline, adjusting the area so that
        // it is draw entirely inside 'rect'
Kurt Hindenburg's avatar
Kurt Hindenburg committed
774 775 776 777 778
        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
779 780

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
784
            if (!_cursorColor.isValid()) {
Jekyll Wu's avatar
Jekyll Wu committed
785
                // invert the color used to draw the text to ensure that the character at
Jekyll Wu's avatar
Jekyll Wu committed
786 787
                // the cursor position is readable
                invertCharacterColor = true;
788
            }
Jekyll Wu's avatar
Jekyll Wu committed
789
        }
790
    } else if (_cursorShape == Enum::UnderlineCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
791 792 793 794 795
        painter.drawLine(cursorRect.left(),
                         cursorRect.bottom(),
                         cursorRect.right(),
                         cursorRect.bottom());

796
    } else if (_cursorShape == Enum::IBeamCursor) {
Jekyll Wu's avatar
Jekyll Wu committed
797 798 799 800
        painter.drawLine(cursorRect.left(),
                         cursorRect.top(),
                         cursorRect.left(),
                         cursorRect.bottom());
801
    }
802 803 804 805 806 807 808 809 810
}

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
811
    if (_textBlinking && (style->rendition & RE_BLINK))
812 813
        return;

814 815 816 817
    // don't draw concealed characters
    if (style->rendition & RE_CONCEAL)
        return;

Robert Knight's avatar
 
Robert Knight committed
818
    // setup bold and underline
819
    bool useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
Jekyll Wu's avatar
Jekyll Wu committed
820
    const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
821
    const bool useItalic = style->rendition & RE_ITALIC || font().italic();
822 823
    const bool useStrikeOut = style->rendition & RE_STRIKEOUT || font().strikeOut();
    const bool useOverline = style->rendition & RE_OVERLINE || font().overline();
Robert Knight's avatar
 
Robert Knight committed
824

825
    QFont font = painter.font();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
826
    if (font.bold() != useBold
827
            || font.underline() != useUnderline
828 829 830
            || font.italic() != useItalic
            || font.strikeOut() != useStrikeOut
            || font.overline() != useOverline) {
831 832
        font.setBold(useBold);
        font.setUnderline(useUnderline);
833
        font.setItalic(useItalic);
834 835
        font.setStrikeOut(useStrikeOut);
        font.setOverline(useOverline);
836
        painter.setFont(font);
837 838 839
    }

    // setup pen
Kurt Hindenburg's avatar
Kurt Hindenburg committed
840
    const CharacterColor& textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor);
841 842
    const QColor color = textColor.color(_colorTable);
    QPen pen = painter.pen();
Kurt Hindenburg's avatar
Kurt Hindenburg committed
843
    if (pen.color() != color) {
844 845 846 847 848
        pen.setColor(color);
        painter.setPen(color);
    }

    // draw text
849
    if (isLineCharString(text) && !_useFontLineCharacters) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
850 851
        drawLineCharString(painter, rect.x(), rect.y(), text, style);
    } else {
852 853 854 855
        // Force using LTR as the document layout for the terminal area, because
        // there is no use cases for RTL emulator and RTL terminal application.
        //
        // This still allows RTL characters to be rendered in the RTL way.
856
        painter.setLayoutDirection(Qt::LeftToRight);
857

858
        if (_bidiEnabled) {
859
            painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text);
860
        } else {
861
            painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text);
862
        }
863
    }
864 865
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
866
void TerminalDisplay::drawTextFragment(QPainter& painter ,
867
                                       const QRect& rect,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
868
                                       const QString& text,
869 870 871 872
                                       const Character* style)
{
    painter.save();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
873
    // setup painter
874 875
    const QColor foregroundColor = style->foregroundColor.color(_colorTable);
    const QColor backgroundColor = style->backgroundColor.color(_colorTable);
876

877
    // draw background if different from the display's background color
Kurt Hindenburg's avatar
Kurt Hindenburg committed
878 879
    if (backgroundColor != palette().background().color())
        drawBackground(painter, rect, backgroundColor,
880
                       false /* do not use transparency */);
881 882 883 884

    // draw cursor shape if the current character is the cursor
    // this may alter the foreground and background colors
    bool invertCharacterColor = false;
Kurt Hindenburg's avatar
Kurt Hindenburg committed
885 886
    if (style->rendition & RE_CURSOR)
        drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor);
Robert Knight's avatar