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 121 122 123 124 125
        connect(_screenWindow.data(), &Konsole::ScreenWindow::outputChanged, [this]() {
            _filterUpdateRequired = true;
        });
        connect(_screenWindow.data(), &Konsole::ScreenWindow::scrolled, [this]() {
            _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
    if (!fontInfo.fixedPitch()) {
236
        qWarning() << "Using a variable-width font - this might cause display problems";
237
    }
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 362
    , _showUrlHint(false)
    , _enableShowUrlHint(false)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
363
    , _underlineLinks(true)
364
    , _openLinksByDirectClick(false)
365
    , _ctrlRequiredForDrag(true)
366
    , _tripleClickMode(Enum::SelectWholeLine)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
367 368 369 370 371 372 373 374
    , _possibleTripleClick(false)
    , _resizeWidget(0)
    , _resizeTimer(0)
    , _flowControlWarningEnabled(false)
    , _outputSuspendedLabel(0)
    , _lineSpacing(0)
    , _blendColor(qRgba(0, 0, 0, 0xff))
    , _filterChain(new TerminalImageFilterChain())
375
    , _filterUpdateRequired(true)
376
    , _cursorShape(Enum::BlockCursor)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
377
    , _antialiasText(true)
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 431 432 433 434 435 436

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

    _gridLayout = new QGridLayout(this);
    _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
    disconnect(_blinkTextTimer);
    disconnect(_blinkCursorTimer);

    delete[] _image;
450

451 452 453
    delete _gridLayout;
    delete _outputSuspendedLabel;
    delete _filterChain;
454 455 456 457 458 459 460 461
}

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

462 463 464 465 466
/**
 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
467

468 469 470 471 472 473 474 475 476 477 478
 Then, the pixels basically have the following interpretation:
 _|||_
 -...-
 -...-
 -...-
 _|||_

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

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

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

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

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

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

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

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

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

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

    if (toDraw & Int31)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
566
        paint.drawPoint(cx - 1, cy + 1);
567
    if (toDraw & Int32)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
568
        paint.drawPoint(cx, cy + 1);
569
    if (toDraw & Int33)
Kurt Hindenburg's avatar
Kurt Hindenburg committed
570
        paint.drawPoint(cx + 1, cy + 1);
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 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!
        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;
        }
    }
}

654
void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
655
        const Character* attributes)
656
{
657 658
    const QPen& originalPen = painter.pen();

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

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
673
    painter.setPen(originalPen);
674 675
}

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

693 694 695 696
void TerminalDisplay::setOpacity(qreal opacity)
{
    QColor color(_blendColor);
    color.setAlphaF(opacity);
697
    _opacity = opacity;
698 699 700

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

710 711 712
    _blendColor = color.rgba();
}

713 714
void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p)
{
715
    _wallpaper = p;
716 717
}

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

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

Kurt Hindenburg's avatar
Kurt Hindenburg committed
754
    painter.fillRect(scrollBarArea, _scrollBar->palette().background());
755 756
}

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

767 768 769
    // 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
770

771
    QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
Jekyll Wu's avatar
Jekyll Wu committed
772 773
    painter.setPen(cursorColor);

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

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

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

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

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
814
    if (_textBlinking && (style->rendition & RE_BLINK))
815 816
        return;

Robert Knight's avatar
 
Robert Knight committed
817
    // setup bold and underline
818
    bool useBold;
819
    ColorEntry::FontWeight weight = style->fontWeight(_colorTable);
820
    if (weight == ColorEntry::UseCurrentFormat)
821
        useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
822 823
    else
        useBold = (weight == ColorEntry::Bold) ? true : false;
Jekyll Wu's avatar
Jekyll Wu committed
824
    const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
825
    const bool useItalic = style->rendition & RE_ITALIC || font().italic();
Robert Knight's avatar
 
Robert Knight committed
826

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

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

    // draw text
Kurt Hindenburg's avatar
Kurt Hindenburg committed
847 848 849
    if (isLineCharString(text)) {
        drawLineCharString(painter, rect.x(), rect.y(), text, style);
    } else {
850 851 852 853
        // 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.
854
        painter.setLayoutDirection(Qt::LeftToRight);
855

856
        // the drawText(rect,flags,string) overload is used here with null flags
857 858
        // instead of drawText(rect,string) because the (rect,string) overload causes
        // the application's default layout direction to be used instead of
859 860
        // the widget-specific layout direction, which should always be
        // Qt::LeftToRight for this widget
861 862
        //
        // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2
863
        if (_bidiEnabled) {
Kurt Hindenburg's avatar
Kurt Hindenburg committed
864
            painter.drawText(rect, 0, text);
865
        } else {
866 867 868
            // See bug 280896 for more info
            painter.drawText(rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + text);
        }
869
    }
870 871
}

Kurt Hindenburg's avatar
Kurt Hindenburg committed
872
void TerminalDisplay::drawTextFragment(QPainter& painter ,
873
                                       const QRect& rect,
Kurt Hindenburg's avatar
Kurt Hindenburg committed
874
                                       const QString& text,
875 876 877 878
                                       const Character* style)
{
    painter.save();

Kurt Hindenburg's avatar
Kurt Hindenburg committed
879
    // setup painter
880 881
    const QColor foregroundColor = style->foregroundColor.color(_colorTable);
    const QColor backgroundColor = style->backgroundColor.color(_colorTable);
882

883
    // draw background if different from the display's background color
Kurt Hindenburg's avatar
Kurt Hindenburg committed
884 885
    if (backgroundColor != palette().background().color())
        drawBackground(painter, rect, backgroundColor,
886
                       false /* do not use transparency */);
887 888 889 890

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