File indexing completed on 2025-03-16 08:11:30
0001 /* 0002 This file is part of Konsole, a terminal emulator for KDE. 0003 0004 SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com> 0005 SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de> 0006 0007 SPDX-License-Identifier: GPL-2.0-or-later 0008 0009 This program is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 GNU General Public License for more details. 0013 0014 You should have received a copy of the GNU General Public License 0015 along with this program; if not, write to the Free Software 0016 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 02110-1301 USA. 0018 */ 0019 0020 // Own 0021 #include "TerminalDisplay.h" 0022 0023 // C++ 0024 #include <cmath> 0025 0026 // Qt 0027 #include <QAbstractButton> 0028 #include <QApplication> 0029 #include <QClipboard> 0030 #include <QDrag> 0031 #include <QEvent> 0032 #include <QFile> 0033 #include <QKeyEvent> 0034 #include <QLabel> 0035 #include <QLayout> 0036 #include <QMimeData> 0037 #include <QPainter> 0038 #include <QPixmap> 0039 #include <QRegularExpression> 0040 #include <QScrollBar> 0041 #include <QStyle> 0042 #include <QTime> 0043 #include <QTimer> 0044 #include <QUrl> 0045 #include <QtDebug> 0046 0047 // Konsole 0048 #include "Filter.h" 0049 #include "ScreenWindow.h" 0050 #include "TerminalCharacterDecoder.h" 0051 #include "konsole_wcwidth.h" 0052 0053 // std 0054 #include <ranges> 0055 0056 inline void initResource() 0057 { 0058 Q_INIT_RESOURCE(terminal); 0059 } 0060 0061 using namespace Konsole; 0062 0063 constexpr auto REPCHAR = 0064 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 0065 "abcdefgjijklmnopqrstuvwxyz" 0066 "0123456789./+@"; 0067 0068 // scroll increment used when dragging selection at top/bottom of window. 0069 0070 // static 0071 bool TerminalDisplay::_antialiasText = true; 0072 0073 // we use this to force QPainter to display text in LTR mode 0074 // more information can be found in: http://unicode.org/reports/tr9/ 0075 const QChar LTR_OVERRIDE_CHAR(0x202D); 0076 0077 /* ------------------------------------------------------------------------- */ 0078 /* */ 0079 /* Colors */ 0080 /* */ 0081 /* ------------------------------------------------------------------------- */ 0082 0083 /* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb) 0084 0085 Code 0 1 2 3 4 5 6 7 0086 ----------- ------- ------- ------- ------- ------- ------- ------- ------- 0087 ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White 0088 IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White 0089 */ 0090 0091 ScreenWindow *TerminalDisplay::screenWindow() const 0092 { 0093 return _screenWindow; 0094 } 0095 void TerminalDisplay::setScreenWindow(ScreenWindow *window) 0096 { 0097 // disconnect existing screen window if any 0098 if (_screenWindow) { 0099 disconnect(_screenWindow, nullptr, this, nullptr); 0100 } 0101 0102 _screenWindow = window; 0103 0104 if (window) { 0105 connect(_screenWindow, &ScreenWindow::outputChanged, this, &TerminalDisplay::updateLineProperties); 0106 connect(_screenWindow, &ScreenWindow::outputChanged, this, &TerminalDisplay::updateImage); 0107 connect(_screenWindow, &ScreenWindow::scrollToEnd, this, &TerminalDisplay::scrollToEnd); 0108 window->setWindowLines(_lines); 0109 } 0110 } 0111 0112 std::span<const ColorEntry> TerminalDisplay::colorTable() const 0113 { 0114 return _colorTable; 0115 } 0116 0117 void TerminalDisplay::setBackgroundColor(const QColor &color) 0118 { 0119 _colorTable[DEFAULT_BACK_COLOR].color = color; 0120 QPalette p = palette(); 0121 p.setColor(backgroundRole(), color); 0122 setPalette(p); 0123 0124 // Avoid propagating the palette change to the scroll bar 0125 _scrollBar->setPalette(QApplication::palette()); 0126 0127 update(); 0128 } 0129 0130 void TerminalDisplay::setForegroundColor(const QColor &color) 0131 { 0132 _colorTable[DEFAULT_FORE_COLOR].color = color; 0133 0134 update(); 0135 } 0136 0137 void TerminalDisplay::setColorTable(std::array<ColorEntry, TABLE_COLORS> &&table) 0138 { 0139 _colorTable = std::move(table); 0140 setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color); 0141 } 0142 0143 /* ------------------------------------------------------------------------- */ 0144 /* */ 0145 /* Font */ 0146 /* */ 0147 /* ------------------------------------------------------------------------- */ 0148 0149 /* 0150 The VT100 has 32 special graphical characters. The usual vt100 extended 0151 xterm fonts have these at 0x00..0x1f. 0152 0153 QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals 0154 come in here as proper unicode characters. 0155 0156 We treat non-iso10646 fonts as VT100 extended and do the requiered mapping 0157 from unicode to 0x00..0x1f. The remaining translation is then left to the 0158 QCodec. 0159 */ 0160 0161 constexpr bool TerminalDisplay::isLineChar(QChar c) const 0162 { 0163 return _drawLineChars && ((c.unicode() & 0xFF80) == 0x2500); 0164 } 0165 0166 constexpr bool TerminalDisplay::isLineCharString(QStringView string) const 0167 { 0168 return (string.size() > 0) && (isLineChar(string[0])); 0169 } 0170 0171 void TerminalDisplay::fontChange(const QFont &) 0172 { 0173 QFontMetricsF fm(font()); 0174 _fontHeight = fm.height() + _lineSpacing; 0175 0176 // waba TerminalDisplay 1.123: 0177 // "Base character width on widest ASCII character. This prevents too wide 0178 // characters in the presence of double wide (e.g. Japanese) characters." 0179 // Get the width from representative normal width characters 0180 _fontWidth = fm.horizontalAdvance(QLatin1String(REPCHAR)) / (qreal)qstrlen(REPCHAR); 0181 0182 _fixedFont = true; 0183 0184 int fw = fm.horizontalAdvance(QLatin1Char(REPCHAR[0])); 0185 for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) { 0186 if (fw != fm.horizontalAdvance(QLatin1Char(REPCHAR[i]))) { 0187 _fixedFont = false; 0188 break; 0189 } 0190 } 0191 0192 if (_fontWidth < 1) 0193 _fontWidth = 1; 0194 0195 _fontAscent = fm.ascent(); 0196 0197 Q_EMIT changedFontMetricSignal(_fontHeight, _fontWidth); 0198 propagateSize(); 0199 update(); 0200 } 0201 0202 // void TerminalDisplay::calDrawTextAdditionHeight(QPainter& painter) 0203 //{ 0204 // QRect test_rect, feedback_rect; 0205 // test_rect.setRect(1, 1, qRound(_fontWidth) * 4, _fontHeight); 0206 // painter.drawText(test_rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + QLatin1String("Mq"), &feedback_rect); 0207 0208 // //qDebug() << "test_rect:" << test_rect << "feeback_rect:" << feedback_rect; 0209 0210 // _drawTextAdditionHeight = (feedback_rect.height() - _fontHeight) / 2; 0211 // if(_drawTextAdditionHeight < 0) { 0212 // _drawTextAdditionHeight = 0; 0213 // } 0214 0215 // _drawTextTestFlag = false; 0216 //} 0217 0218 void TerminalDisplay::setVTFont(const QFont &f) 0219 { 0220 QFont font = f; 0221 0222 if (!QFontInfo(font).fixedPitch()) { 0223 qDebug() << "Using a variable-width font in the terminal. This may cause performance degradation and display/alignment errors."; 0224 } 0225 0226 // hint that text should be drawn without anti-aliasing. 0227 // depending on the user's font configuration, this may not be respected 0228 if (!_antialiasText) 0229 font.setStyleStrategy(QFont::NoAntialias); 0230 0231 // experimental optimization. Konsole assumes that the terminal is using a 0232 // mono-spaced font, in which case kerning information should have an effect. 0233 // Disabling kerning saves some computation when rendering text. 0234 font.setKerning(false); 0235 0236 m_font = font; 0237 fontChange(font); 0238 Q_EMIT vtFontChanged(); 0239 } 0240 0241 void TerminalDisplay::setBoldIntense(bool value) 0242 { 0243 if (_boldIntense != value) { 0244 _boldIntense = value; 0245 Q_EMIT boldIntenseChanged(); 0246 } 0247 } 0248 0249 /* ------------------------------------------------------------------------- */ 0250 /* */ 0251 /* Constructor / Destructor */ 0252 /* */ 0253 /* ------------------------------------------------------------------------- */ 0254 #include <QDir> 0255 0256 TerminalDisplay::TerminalDisplay(QQuickItem *parent) 0257 : QQuickPaintedItem(parent) 0258 , _screenWindow(nullptr) 0259 , _allowBell(true) 0260 , _gridLayout(nullptr) 0261 , _fontHeight(1) 0262 , _fontWidth(1) 0263 , _fontAscent(1) 0264 , _boldIntense(true) 0265 , _lines(1) 0266 , _columns(1) 0267 , _usedLines(1) 0268 , _usedColumns(1) 0269 , _contentHeight(1) 0270 , _contentWidth(1) 0271 , _randomSeed(0) 0272 , _resizing(false) 0273 , _terminalSizeHint(false) 0274 , _terminalSizeStartup(true) 0275 , _bidiEnabled(false) 0276 , _mouseMarks(false) 0277 , _disabledBracketedPasteMode(false) 0278 , _actSel(0) 0279 , _wordSelectionMode(false) 0280 , _lineSelectionMode(false) 0281 , _preserveLineBreaks(false) 0282 , _columnSelectionMode(false) 0283 , _scrollbarLocation(QTermWidget::NoScrollBar) 0284 , _wordCharacters(QLatin1String(":@-./_~")) 0285 , _bellMode(SystemBeepBell) 0286 , _blinking(false) 0287 , _hasBlinker(false) 0288 , _cursorBlinking(false) 0289 , _hasBlinkingCursor(false) 0290 , _allowBlinkingText(true) 0291 , _ctrlDrag(false) 0292 , _tripleClickMode(SelectWholeLine) 0293 , _isFixedSize(false) 0294 , _possibleTripleClick(false) 0295 , _resizeWidget(nullptr) 0296 , _resizeTimer(nullptr) 0297 , _flowControlWarningEnabled(false) 0298 , _outputSuspendedLabel(nullptr) 0299 , _lineSpacing(0) 0300 , _colorsInverted(false) 0301 , _opacity(static_cast<qreal>(1)) 0302 , _filterChain(std::make_unique<TerminalImageFilterChain>()) 0303 , _cursorShape(Emulation::KeyboardCursorShape::BlockCursor) 0304 , mMotionAfterPasting(NoMoveScreenWindow) 0305 , _leftBaseMargin(1) 0306 , _topBaseMargin(1) 0307 , m_font(QStringLiteral("Monospace"), 12) 0308 , m_color_role(QPalette::Window) 0309 , m_full_cursor_height(false) 0310 , _drawLineChars(true) 0311 { 0312 initResource(); 0313 0314 // terminal applications are not designed with Right-To-Left in mind, 0315 // so the layout is forced to Left-To-Right 0316 // setLayoutDirection(Qt::LeftToRight); 0317 0318 // The offsets are not yet calculated. 0319 // Do not calculate these too often to be more smoothly when resizing 0320 // konsole in opaque mode. 0321 _topMargin = _topBaseMargin; 0322 _leftMargin = _leftBaseMargin; 0323 0324 m_palette = qApp->palette(); 0325 0326 setVTFont(m_font); 0327 0328 // create scroll bar for scrolling output up and down 0329 // set the scroll bar's slider to occupy the whole area of the scroll bar initially 0330 0331 _scrollBar = new QScrollBar(); 0332 setScroll(0, 0); 0333 0334 _scrollBar->setCursor(Qt::ArrowCursor); 0335 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged); 0336 // qtermwidget: we have to hide it here due the _scrollbarLocation==NoScrollBar 0337 // check in TerminalDisplay::setScrollBarPosition(ScrollBarPosition position) 0338 _scrollBar->hide(); 0339 0340 // setup timers for blinking cursor and text 0341 _blinkTimer = new QTimer(this); 0342 connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent())); 0343 _blinkCursorTimer = new QTimer(this); 0344 connect(_blinkCursorTimer, &QTimer::timeout, this, &TerminalDisplay::blinkCursorEvent); 0345 0346 // KCursor::setAutoHideCursor( this, true ); 0347 0348 setUsesMouse(true); 0349 setBracketedPasteMode(false); 0350 setColorTable(defaultColorTable()); 0351 // setMouseTracking(true); 0352 0353 setAcceptedMouseButtons(Qt::LeftButton); 0354 0355 setFlags(ItemHasContents | ItemAcceptsInputMethod); 0356 0357 // Setup scrollbar. Be sure it is not darw on screen. 0358 _scrollBar->setAttribute(Qt::WA_DontShowOnScreen); 0359 _scrollBar->setVisible(false); 0360 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollbarParamsChanged); 0361 0362 // TODO Forcing rendering to Framebuffer. We need to determine if this is ok 0363 // always or if we need to make this customizable. 0364 setRenderTarget(QQuickPaintedItem::FramebufferObject); 0365 0366 // setFocusPolicy( Qt::WheelFocus ); 0367 0368 // enable input method support 0369 // setAttribute(Qt::WA_InputMethodEnabled, true); 0370 0371 // this is an important optimization, it tells Qt 0372 // that TerminalDisplay will handle repainting its entire area. 0373 // setAttribute(Qt::WA_OpaquePaintEvent); 0374 0375 // _gridLayout = new QGridLayout(this); 0376 // _gridLayout->setContentsMargins(0, 0, 0, 0); 0377 0378 // new AutoScrollHandler(this); 0379 } 0380 0381 TerminalDisplay::~TerminalDisplay() 0382 { 0383 disconnect(_blinkTimer); 0384 disconnect(_blinkCursorTimer); 0385 qApp->removeEventFilter(this); 0386 0387 delete _gridLayout; 0388 delete _outputSuspendedLabel; 0389 delete _scrollBar; 0390 } 0391 0392 /* ------------------------------------------------------------------------- */ 0393 /* */ 0394 /* Display Operations */ 0395 /* */ 0396 /* ------------------------------------------------------------------------- */ 0397 0398 /** 0399 A table for emulating the simple (single width) unicode drawing chars. 0400 It represents the 250x - 257x glyphs. If it's zero, we can't use it. 0401 if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered 0402 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit. 0403 0404 Then, the pixels basically have the following interpretation: 0405 _|||_ 0406 -...- 0407 -...- 0408 -...- 0409 _|||_ 0410 0411 where _ = none 0412 | = vertical line. 0413 - = horizontal line. 0414 */ 0415 0416 enum LineEncode { 0417 TopL = (1 << 1), 0418 TopC = (1 << 2), 0419 TopR = (1 << 3), 0420 0421 LeftT = (1 << 5), 0422 Int11 = (1 << 6), 0423 Int12 = (1 << 7), 0424 Int13 = (1 << 8), 0425 RightT = (1 << 9), 0426 0427 LeftC = (1 << 10), 0428 Int21 = (1 << 11), 0429 Int22 = (1 << 12), 0430 Int23 = (1 << 13), 0431 RightC = (1 << 14), 0432 0433 LeftB = (1 << 15), 0434 Int31 = (1 << 16), 0435 Int32 = (1 << 17), 0436 Int33 = (1 << 18), 0437 RightB = (1 << 19), 0438 0439 BotL = (1 << 21), 0440 BotC = (1 << 22), 0441 BotR = (1 << 23) 0442 }; 0443 0444 #include "LineFont.h" 0445 0446 static void drawLineChar(QPainter &paint, int x, int y, int w, int h, uint8_t code) 0447 { 0448 // Calculate cell midpoints, end points. 0449 int cx = x + w / 2; 0450 int cy = y + h / 2; 0451 int ex = x + w - 1; 0452 int ey = y + h - 1; 0453 0454 quint32 toDraw = LineChars[code]; 0455 0456 // Top _lines: 0457 if (toDraw & TopL) 0458 paint.drawLine(cx - 1, y, cx - 1, cy - 2); 0459 if (toDraw & TopC) 0460 paint.drawLine(cx, y, cx, cy - 2); 0461 if (toDraw & TopR) 0462 paint.drawLine(cx + 1, y, cx + 1, cy - 2); 0463 0464 // Bot _lines: 0465 if (toDraw & BotL) 0466 paint.drawLine(cx - 1, cy + 2, cx - 1, ey); 0467 if (toDraw & BotC) 0468 paint.drawLine(cx, cy + 2, cx, ey); 0469 if (toDraw & BotR) 0470 paint.drawLine(cx + 1, cy + 2, cx + 1, ey); 0471 0472 // Left _lines: 0473 if (toDraw & LeftT) 0474 paint.drawLine(x, cy - 1, cx - 2, cy - 1); 0475 if (toDraw & LeftC) 0476 paint.drawLine(x, cy, cx - 2, cy); 0477 if (toDraw & LeftB) 0478 paint.drawLine(x, cy + 1, cx - 2, cy + 1); 0479 0480 // Right _lines: 0481 if (toDraw & RightT) 0482 paint.drawLine(cx + 2, cy - 1, ex, cy - 1); 0483 if (toDraw & RightC) 0484 paint.drawLine(cx + 2, cy, ex, cy); 0485 if (toDraw & RightB) 0486 paint.drawLine(cx + 2, cy + 1, ex, cy + 1); 0487 0488 // Intersection points. 0489 if (toDraw & Int11) 0490 paint.drawPoint(cx - 1, cy - 1); 0491 if (toDraw & Int12) 0492 paint.drawPoint(cx, cy - 1); 0493 if (toDraw & Int13) 0494 paint.drawPoint(cx + 1, cy - 1); 0495 0496 if (toDraw & Int21) 0497 paint.drawPoint(cx - 1, cy); 0498 if (toDraw & Int22) 0499 paint.drawPoint(cx, cy); 0500 if (toDraw & Int23) 0501 paint.drawPoint(cx + 1, cy); 0502 0503 if (toDraw & Int31) 0504 paint.drawPoint(cx - 1, cy + 1); 0505 if (toDraw & Int32) 0506 paint.drawPoint(cx, cy + 1); 0507 if (toDraw & Int33) 0508 paint.drawPoint(cx + 1, cy + 1); 0509 } 0510 0511 static void drawOtherChar(QPainter &paint, int x, int y, int w, int h, uchar code) 0512 { 0513 // Calculate cell midpoints, end points. 0514 const int cx = x + w / 2; 0515 const int cy = y + h / 2; 0516 const int ex = x + w - 1; 0517 const int ey = y + h - 1; 0518 0519 // Double dashes 0520 if (0x4C <= code && code <= 0x4F) { 0521 const int xHalfGap = qMax(w / 15, 1); 0522 const int yHalfGap = qMax(h / 15, 1); 0523 switch (code) { 0524 case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL 0525 paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1); 0526 paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1); 0527 paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1); 0528 paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1); 0529 /* Falls through. */ 0530 case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL 0531 paint.drawLine(x, cy, cx - xHalfGap - 1, cy); 0532 paint.drawLine(cx + xHalfGap, cy, ex, cy); 0533 break; 0534 case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL 0535 paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1); 0536 paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1); 0537 paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey); 0538 paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey); 0539 /* Falls through. */ 0540 case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL 0541 paint.drawLine(cx, y, cx, cy - yHalfGap - 1); 0542 paint.drawLine(cx, cy + yHalfGap, cx, ey); 0543 break; 0544 } 0545 } 0546 0547 // Rounded corner characters 0548 else if (0x6D <= code && code <= 0x70) { 0549 const int r = w * 3 / 8; 0550 const int d = 2 * r; 0551 switch (code) { 0552 case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT 0553 paint.drawLine(cx, cy + r, cx, ey); 0554 paint.drawLine(cx + r, cy, ex, cy); 0555 paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16); 0556 break; 0557 case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT 0558 paint.drawLine(cx, cy + r, cx, ey); 0559 paint.drawLine(x, cy, cx - r, cy); 0560 paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16); 0561 break; 0562 case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT 0563 paint.drawLine(cx, y, cx, cy - r); 0564 paint.drawLine(x, cy, cx - r, cy); 0565 paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16); 0566 break; 0567 case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT 0568 paint.drawLine(cx, y, cx, cy - r); 0569 paint.drawLine(cx + r, cy, ex, cy); 0570 paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16); 0571 break; 0572 } 0573 } 0574 0575 // Diagonals 0576 else if (0x71 <= code && code <= 0x73) { 0577 switch (code) { 0578 case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT 0579 paint.drawLine(ex, y, x, ey); 0580 break; 0581 case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT 0582 paint.drawLine(x, y, ex, ey); 0583 break; 0584 case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS 0585 paint.drawLine(ex, y, x, ey); 0586 paint.drawLine(x, y, ex, ey); 0587 break; 0588 } 0589 } 0590 } 0591 0592 void TerminalDisplay::drawLineCharString(QPainter &painter, int x, int y, QStringView str, const Character *attributes) const 0593 { 0594 const QPen ¤tPen = painter.pen(); 0595 0596 if ((attributes->rendition & RE_BOLD) && _boldIntense) { 0597 QPen boldPen(currentPen); 0598 boldPen.setWidth(3); 0599 painter.setPen(boldPen); 0600 } 0601 0602 for (qsizetype i = 0; i < str.size(); i++) { 0603 uint8_t code = static_cast<uint8_t>(str[i].unicode() & 0xffU); 0604 if (LineChars[code]) 0605 drawLineChar(painter, qRound(x + (_fontWidth * i)), y, qRound(_fontWidth), qRound(_fontHeight), code); 0606 else 0607 drawOtherChar(painter, qRound(x + (_fontWidth * i)), y, qRound(_fontWidth), qRound(_fontHeight), code); 0608 } 0609 0610 painter.setPen(currentPen); 0611 } 0612 0613 void TerminalDisplay::setKeyboardCursorShape(Emulation::KeyboardCursorShape shape) 0614 { 0615 if (_cursorShape == shape) { 0616 return; 0617 } 0618 0619 _cursorShape = shape; 0620 updateCursor(); 0621 } 0622 0623 Emulation::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const 0624 { 0625 return _cursorShape; 0626 } 0627 0628 void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor &color) 0629 { 0630 if (useForegroundColor) 0631 _cursorColor = QColor(); // an invalid color means that 0632 // the foreground color of the 0633 // current character should 0634 // be used 0635 0636 else 0637 _cursorColor = color; 0638 } 0639 QColor TerminalDisplay::keyboardCursorColor() const 0640 { 0641 return _cursorColor; 0642 } 0643 0644 void TerminalDisplay::setOpacity(qreal opacity) 0645 { 0646 _opacity = qBound(static_cast<qreal>(0), opacity, static_cast<qreal>(1)); 0647 update(); 0648 } 0649 0650 void TerminalDisplay::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting) 0651 { 0652 // The whole widget rectangle is filled by the background color from 0653 // the color scheme set in setColorTable(), while the scrollbar is 0654 // left to the widget style for a consistent look. 0655 if (useOpacitySetting) { 0656 QColor color(backgroundColor); 0657 color.setAlphaF(_opacity); 0658 0659 painter.save(); 0660 painter.setCompositionMode(QPainter::CompositionMode_Source); 0661 painter.fillRect(rect, color); 0662 painter.restore(); 0663 } else 0664 painter.fillRect(rect, backgroundColor); 0665 } 0666 0667 void TerminalDisplay::drawCursor(QPainter &painter, 0668 const QRect &rect, 0669 const QColor &foregroundColor, 0670 const QColor & /*backgroundColor*/, 0671 bool &invertCharacterColor) 0672 { 0673 QRect cursorRect = rect; 0674 0675 cursorRect.setHeight(qRound(_fontHeight) - ((m_full_cursor_height) ? 0 : _lineSpacing - 1)); 0676 0677 if (!_cursorBlinking) { 0678 if (_cursorColor.isValid()) 0679 painter.setPen(_cursorColor); 0680 else 0681 painter.setPen(foregroundColor); 0682 0683 if (_cursorShape == Emulation::KeyboardCursorShape::BlockCursor) { 0684 // draw the cursor outline, adjusting the area so that 0685 // it is draw entirely inside 'rect' 0686 float penWidth = qMax(1, painter.pen().width()); 0687 0688 // 0689 painter.drawRect(cursorRect.adjusted(+penWidth / 2 + fmod(penWidth, 2), 0690 +penWidth / 2 + fmod(penWidth, 2), 0691 -penWidth / 2 - fmod(penWidth, 2), 0692 -penWidth / 2 - fmod(penWidth, 2))); 0693 0694 // if ( hasFocus() ) 0695 if (hasActiveFocus()) { 0696 painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor); 0697 0698 if (!_cursorColor.isValid()) { 0699 // invert the colour used to draw the text to ensure that the character at 0700 // the cursor position is readable 0701 invertCharacterColor = true; 0702 } 0703 } 0704 } else if (_cursorShape == Emulation::KeyboardCursorShape::UnderlineCursor) 0705 painter.drawLine(cursorRect.left(), cursorRect.bottom(), cursorRect.right(), cursorRect.bottom()); 0706 else if (_cursorShape == Emulation::KeyboardCursorShape::IBeamCursor) 0707 painter.drawLine(cursorRect.left(), cursorRect.top(), cursorRect.left(), cursorRect.bottom()); 0708 } 0709 } 0710 0711 void TerminalDisplay::drawCharacters(QPainter &painter, const QRect &rect, const QString &text, const Character *style, bool invertCharacterColor) 0712 { 0713 // don't draw text which is currently blinking 0714 if (_blinking && (style->rendition & RE_BLINK)) 0715 return; 0716 0717 // don't draw concealed characters 0718 if (style->rendition & RE_CONCEAL) 0719 return; 0720 0721 // setup bold and underline 0722 bool useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold(); 0723 const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline(); 0724 const bool useItalic = style->rendition & RE_ITALIC || font().italic(); 0725 const bool useStrikeOut = style->rendition & RE_STRIKEOUT || font().strikeOut(); 0726 const bool useOverline = style->rendition & RE_OVERLINE || font().overline(); 0727 0728 painter.setFont(font()); 0729 0730 QFont font = painter.font(); 0731 if (font.bold() != useBold || font.underline() != useUnderline || font.italic() != useItalic || font.strikeOut() != useStrikeOut 0732 || font.overline() != useOverline) { 0733 font.setBold(useBold); 0734 font.setUnderline(useUnderline); 0735 font.setItalic(useItalic); 0736 font.setStrikeOut(useStrikeOut); 0737 font.setOverline(useOverline); 0738 painter.setFont(font); 0739 } 0740 0741 // setup pen 0742 const CharacterColor &textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor); 0743 const QColor color = textColor.color(_colorTable); 0744 QPen pen = painter.pen(); 0745 if (pen.color() != color) { 0746 pen.setColor(color); 0747 painter.setPen(color); 0748 } 0749 0750 // draw text 0751 if (isLineCharString(text)) 0752 drawLineCharString(painter, rect.x(), rect.y(), text, style); 0753 else { 0754 // Force using LTR as the document layout for the terminal area, because 0755 // there is no use cases for RTL emulator and RTL terminal application. 0756 // 0757 // This still allows RTL characters to be rendered in the RTL way. 0758 painter.setLayoutDirection(Qt::LeftToRight); 0759 0760 if (_bidiEnabled) { 0761 painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text); 0762 } else { 0763 painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text); 0764 } 0765 } 0766 } 0767 0768 void TerminalDisplay::drawTextFragment(QPainter &painter, const QRect &rect, const QString &text, const Character *style) 0769 { 0770 painter.save(); 0771 0772 // setup painter 0773 const QColor foregroundColor = style->foregroundColor.color(_colorTable); 0774 const QColor backgroundColor = style->backgroundColor.color(_colorTable); 0775 0776 // draw background if different from the display's background color 0777 if (backgroundColor != palette().window().color()) 0778 drawBackground(painter, rect, backgroundColor, false /* do not use transparency */); 0779 0780 // draw cursor shape if the current character is the cursor 0781 // this may alter the foreground and background colors 0782 bool invertCharacterColor = false; 0783 if (style->rendition & RE_CURSOR) 0784 drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor); 0785 0786 // draw text 0787 drawCharacters(painter, rect, text, style, invertCharacterColor); 0788 0789 painter.restore(); 0790 } 0791 0792 void TerminalDisplay::setRandomSeed(uint randomSeed) 0793 { 0794 _randomSeed = randomSeed; 0795 } 0796 uint TerminalDisplay::randomSeed() const 0797 { 0798 return _randomSeed; 0799 } 0800 0801 // scrolls the image by 'lines', down if lines > 0 or up otherwise. 0802 // 0803 // the terminal emulation keeps track of the scrolling of the character 0804 // image as it receives input, and when the view is updated, it calls scrollImage() 0805 // with the final scroll amount. this improves performance because scrolling the 0806 // display is much cheaper than re-rendering all the text for the 0807 // part of the image which has moved up or down. 0808 // Instead only new lines have to be drawn 0809 void TerminalDisplay::scrollImage(int lines, const QRect &screenWindowRegion) 0810 { 0811 // if the flow control warning is enabled this will interfere with the 0812 // scrolling optimizations and cause artifacts. the simple solution here 0813 // is to just disable the optimization whilst it is visible 0814 if (_outputSuspendedLabel && _outputSuspendedLabel->isVisible()) 0815 return; 0816 0817 // constrain the region to the display 0818 // the bottom of the region is capped to the number of lines in the display's 0819 // internal image - 2, so that the height of 'region' is strictly less 0820 // than the height of the internal image. 0821 QRect region = screenWindowRegion; 0822 region.setBottom(qMin(region.bottom(), this->_lines - 2)); 0823 0824 // return if there is nothing to do 0825 if (lines == 0 || _image.empty() || !region.isValid() || (region.top() + abs(lines)) >= region.bottom() || this->_lines <= region.height()) 0826 return; 0827 0828 // hide terminal size label to prevent it being scrolled 0829 if (_resizeWidget && _resizeWidget->isVisible()) 0830 _resizeWidget->hide(); 0831 0832 // Note: With Qt 4.4 the left edge of the scrolled area must be at 0 0833 // to get the correct (newly exposed) part of the widget repainted. 0834 // 0835 // The right edge must be before the left edge of the scroll bar to 0836 // avoid triggering a repaint of the entire widget, the distance is 0837 // given by SCROLLBAR_CONTENT_GAP 0838 // 0839 // Set the QT_FLUSH_PAINT environment variable to '1' before starting the 0840 // application to monitor repainting. 0841 // 0842 int scrollBarWidth = _scrollBar->isHidden() ? 0 0843 : _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar) ? 0 0844 : _scrollBar->width(); 0845 const int SCROLLBAR_CONTENT_GAP = scrollBarWidth == 0 ? 0 : 1; 0846 QRect scrollRect; 0847 if (_scrollbarLocation == QTermWidget::ScrollBarLeft) { 0848 scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP); 0849 scrollRect.setRight(width()); 0850 } else { 0851 scrollRect.setLeft(0); 0852 scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP); 0853 } 0854 void *firstCharPos = &_image[region.top() * this->_columns]; 0855 void *lastCharPos = &_image[(region.top() + abs(lines)) * this->_columns]; 0856 0857 int top = _topMargin + (region.top() * qRound(_fontHeight)); 0858 int linesToMove = region.height() - abs(lines); 0859 int bytesToMove = linesToMove * this->_columns * sizeof(Character); 0860 0861 Q_ASSERT(linesToMove > 0); 0862 Q_ASSERT(bytesToMove > 0); 0863 0864 // scroll internal image 0865 if (lines > 0) { 0866 // check that the memory areas that we are going to move are valid 0867 Q_ASSERT((char *)lastCharPos + bytesToMove < (char *)(_image.data() + (this->_lines * this->_columns))); 0868 0869 Q_ASSERT((lines * this->_columns) < _imageSize); 0870 0871 // scroll internal image down 0872 memmove(firstCharPos, lastCharPos, bytesToMove); 0873 0874 // set region of display to scroll 0875 scrollRect.setTop(top); 0876 } else { 0877 // check that the memory areas that we are going to move are valid 0878 Q_ASSERT((char *)firstCharPos + bytesToMove < (char *)(_image.data() + (this->_lines * this->_columns))); 0879 0880 // scroll internal image up 0881 memmove(lastCharPos, firstCharPos, bytesToMove); 0882 0883 // set region of the display to scroll 0884 scrollRect.setTop(top + abs(lines) * qRound(_fontHeight)); 0885 } 0886 scrollRect.setHeight(linesToMove * qRound(_fontHeight)); 0887 0888 Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty()); 0889 0890 // scroll the display vertically to match internal _image 0891 // scroll( 0 , qRound(_fontHeight) * (-lines) , scrollRect ); 0892 } 0893 0894 QRegion TerminalDisplay::hotSpotRegion() const 0895 { 0896 QRegion region; 0897 const auto hotSpots = _filterChain->hotSpots(); 0898 for (Filter::HotSpot *const hotSpot : hotSpots) { 0899 QRect r; 0900 if (hotSpot->startLine() == hotSpot->endLine()) { 0901 r.setLeft(hotSpot->startColumn()); 0902 r.setTop(hotSpot->startLine()); 0903 r.setRight(hotSpot->endColumn()); 0904 r.setBottom(hotSpot->endLine()); 0905 region |= imageToWidget(r); 0906 ; 0907 } else { 0908 r.setLeft(hotSpot->startColumn()); 0909 r.setTop(hotSpot->startLine()); 0910 r.setRight(_columns); 0911 r.setBottom(hotSpot->startLine()); 0912 region |= imageToWidget(r); 0913 ; 0914 for (int line = hotSpot->startLine() + 1; line < hotSpot->endLine(); line++) { 0915 r.setLeft(0); 0916 r.setTop(line); 0917 r.setRight(_columns); 0918 r.setBottom(line); 0919 region |= imageToWidget(r); 0920 ; 0921 } 0922 r.setLeft(0); 0923 r.setTop(hotSpot->endLine()); 0924 r.setRight(hotSpot->endColumn()); 0925 r.setBottom(hotSpot->endLine()); 0926 region |= imageToWidget(r); 0927 ; 0928 } 0929 } 0930 return region; 0931 } 0932 0933 void TerminalDisplay::processFilters() 0934 { 0935 if (!_screenWindow) 0936 return; 0937 0938 QRegion preUpdateHotSpots = hotSpotRegion(); 0939 0940 // use _screenWindow->getImage() here rather than _image because 0941 // other classes may call processFilters() when this display's 0942 // ScreenWindow emits a scrolled() signal - which will happen before 0943 // updateImage() is called on the display and therefore _image is 0944 // out of date at this point 0945 _filterChain->setImage(_screenWindow->getImage(), _screenWindow->windowLines(), _screenWindow->windowColumns(), _screenWindow->getLineProperties()); 0946 _filterChain->process(); 0947 0948 QRegion postUpdateHotSpots = hotSpotRegion(); 0949 0950 update(preUpdateHotSpots | postUpdateHotSpots); 0951 } 0952 0953 void TerminalDisplay::updateImage() 0954 { 0955 if (!_screenWindow) 0956 return; 0957 0958 // TODO QMLTermWidget at the moment I'm disabling this. 0959 // Since this can't be scrolled we need to determine if this 0960 // is useful or not. 0961 0962 // optimization - scroll the existing image where possible and 0963 // avoid expensive text drawing for parts of the image that 0964 // can simply be moved up or down 0965 // scrollImage( _screenWindow->scrollCount() , 0966 // _screenWindow->scrollRegion() ); 0967 // _screenWindow->resetScrollCount(); 0968 0969 if (_image.empty()) { 0970 // Create _image. 0971 // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first. 0972 updateImageSize(); 0973 } 0974 0975 auto newimg = _screenWindow->getImage(); 0976 int lines = _screenWindow->windowLines(); 0977 int columns = _screenWindow->windowColumns(); 0978 0979 setScroll(_screenWindow->currentLine(), _screenWindow->lineCount()); 0980 0981 Q_ASSERT(this->_usedLines <= this->_lines); 0982 Q_ASSERT(this->_usedColumns <= this->_columns); 0983 0984 int y, x, len; 0985 0986 QPoint tL = contentsRect().topLeft(); 0987 int tLx = tL.x(); 0988 int tLy = tL.y(); 0989 _hasBlinker = false; 0990 0991 CharacterColor cf; // undefined 0992 CharacterColor _clipboard; // undefined 0993 int cr = -1; // undefined 0994 0995 const int linesToUpdate = qMin(this->_lines, qMax(0, lines)); 0996 const int columnsToUpdate = qMin(this->_columns, qMax(0, columns)); 0997 0998 std::vector<QChar> disstrU(columnsToUpdate); 0999 std::vector<char> dirtyMask(columnsToUpdate + 2); 1000 QRegion dirtyRegion; 1001 1002 // debugging variable, this records the number of lines that are found to 1003 // be 'dirty' ( ie. have changed from the old _image to the new _image ) and 1004 // which therefore need to be repainted 1005 int dirtyLineCount = 0; 1006 1007 for (y = 0; y < linesToUpdate; ++y) { 1008 const Character *currentLine = &_image[y * _columns]; 1009 const Character *const newLine = &newimg[y * columns]; 1010 1011 bool updateLine = false; 1012 1013 // The dirty mask indicates which characters need repainting. We also 1014 // mark surrounding neighbours dirty, in case the character exceeds 1015 // its cell boundaries 1016 memset(dirtyMask.data(), 0, columnsToUpdate + 2); 1017 1018 for (x = 0; x < columnsToUpdate; ++x) { 1019 if (newLine[x] != currentLine[x]) { 1020 dirtyMask[x] = true; 1021 } 1022 } 1023 1024 if (!_resizing) // not while _resizing, we're expecting a paintEvent 1025 for (x = 0; x < columnsToUpdate; ++x) { 1026 _hasBlinker |= (newLine[x].rendition & RE_BLINK); 1027 1028 // Start drawing if this character or the next one differs. 1029 // We also take the next one into account to handle the situation 1030 // where characters exceed their cell width. 1031 if (dirtyMask[x]) { 1032 QChar c = newLine[x + 0].character; 1033 if (!c.unicode()) 1034 continue; 1035 int p = 0; 1036 disstrU[p++] = c; // fontMap(c); 1037 bool lineDraw = isLineChar(c); 1038 bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character.unicode() == 0); 1039 cr = newLine[x].rendition; 1040 _clipboard = newLine[x].backgroundColor; 1041 if (newLine[x].foregroundColor != cf) 1042 cf = newLine[x].foregroundColor; 1043 int lln = columnsToUpdate - x; 1044 for (len = 1; len < lln; ++len) { 1045 const Character &ch = newLine[x + len]; 1046 1047 if (!ch.character.unicode()) 1048 continue; // Skip trailing part of multi-col chars. 1049 1050 bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character.unicode() == 0); 1051 1052 if (ch.foregroundColor != cf || ch.backgroundColor != _clipboard || ch.rendition != cr || !dirtyMask[x + len] 1053 || isLineChar(c) != lineDraw || nextIsDoubleWidth != doubleWidth) 1054 break; 1055 1056 disstrU[p++] = c; // fontMap(c); 1057 } 1058 1059 bool saveFixedFont = _fixedFont; 1060 if (lineDraw) 1061 _fixedFont = false; 1062 if (doubleWidth) 1063 _fixedFont = false; 1064 1065 updateLine = true; 1066 1067 _fixedFont = saveFixedFont; 1068 x += len - 1; 1069 } 1070 } 1071 1072 // both the top and bottom halves of double height _lines must always be redrawn 1073 // although both top and bottom halves contain the same characters, only 1074 // the top one is actually 1075 // drawn. 1076 if (_lineProperties.size() > y) 1077 updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT); 1078 1079 // if the characters on the line are different in the old and the new _image 1080 // then this line must be repainted. 1081 if (updateLine) { 1082 dirtyLineCount++; 1083 1084 // add the area occupied by this line to the region which needs to be 1085 // repainted 1086 QRect dirtyRect = QRect(_leftMargin + tLx, _topMargin + tLy + qRound(_fontHeight) * y, _fontWidth * columnsToUpdate, qRound(_fontHeight)); 1087 1088 dirtyRegion |= dirtyRect; 1089 } 1090 1091 // replace the line of characters in the old _image with the 1092 // current line of the new _image 1093 memcpy((void *)currentLine, (const void *)newLine, columnsToUpdate * sizeof(Character)); 1094 } 1095 1096 // if the new _image is smaller than the previous _image, then ensure that the area 1097 // outside the new _image is cleared 1098 if (linesToUpdate < _usedLines) { 1099 dirtyRegion |= QRect(_leftMargin + tLx, 1100 _topMargin + tLy + qRound(_fontHeight) * linesToUpdate, 1101 _fontWidth * this->_columns, 1102 qRound(_fontHeight) * (_usedLines - linesToUpdate)); 1103 } 1104 _usedLines = linesToUpdate; 1105 1106 if (columnsToUpdate < _usedColumns) { 1107 dirtyRegion |= QRect(_leftMargin + tLx + columnsToUpdate * _fontWidth, 1108 _topMargin + tLy, 1109 _fontWidth * (_usedColumns - columnsToUpdate), 1110 qRound(_fontHeight) * this->_lines); 1111 } 1112 _usedColumns = columnsToUpdate; 1113 1114 dirtyRegion |= _inputMethodData.previousPreeditRect; 1115 1116 // update the parts of the display which have changed 1117 update(dirtyRegion); 1118 1119 if (_hasBlinker && !_blinkTimer->isActive()) 1120 _blinkTimer->start(TEXT_BLINK_DELAY); 1121 if (!_hasBlinker && _blinkTimer->isActive()) { 1122 _blinkTimer->stop(); 1123 _blinking = false; 1124 } 1125 } 1126 1127 void TerminalDisplay::showResizeNotification() 1128 { 1129 } 1130 1131 void TerminalDisplay::setBlinkingCursor(bool blink) 1132 { 1133 if (_hasBlinkingCursor != blink) 1134 Q_EMIT blinkingCursorStateChanged(); 1135 1136 _hasBlinkingCursor = blink; 1137 1138 if (blink && !_blinkCursorTimer->isActive()) 1139 _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); 1140 1141 if (!blink && _blinkCursorTimer->isActive()) { 1142 _blinkCursorTimer->stop(); 1143 if (_cursorBlinking) 1144 blinkCursorEvent(); 1145 else 1146 _cursorBlinking = false; 1147 } 1148 } 1149 1150 void TerminalDisplay::setBlinkingTextEnabled(bool blink) 1151 { 1152 _allowBlinkingText = blink; 1153 1154 if (blink && !_blinkTimer->isActive()) 1155 _blinkTimer->start(TEXT_BLINK_DELAY); 1156 1157 if (!blink && _blinkTimer->isActive()) { 1158 _blinkTimer->stop(); 1159 _blinking = false; 1160 } 1161 } 1162 1163 void TerminalDisplay::focusOutEvent(QFocusEvent *) 1164 { 1165 Q_EMIT termLostFocus(); 1166 // trigger a repaint of the cursor so that it is both visible (in case 1167 // it was hidden during blinking) 1168 // and drawn in a focused out state 1169 _cursorBlinking = false; 1170 updateCursor(); 1171 1172 _blinkCursorTimer->stop(); 1173 if (_blinking) 1174 blinkEvent(); 1175 1176 _blinkTimer->stop(); 1177 } 1178 void TerminalDisplay::focusInEvent(QFocusEvent *) 1179 { 1180 Q_EMIT termGetFocus(); 1181 if (_hasBlinkingCursor) { 1182 _blinkCursorTimer->start(); 1183 } 1184 updateCursor(); 1185 1186 if (_hasBlinker) 1187 _blinkTimer->start(); 1188 } 1189 1190 // QMLTermWidget version. See the upstream commented version for reference. 1191 void TerminalDisplay::paint(QPainter *painter) 1192 { 1193 QRect clipRect = painter->clipBoundingRect().toAlignedRect(); 1194 QRect dirtyRect = clipRect.isValid() ? clipRect : contentsRect(); 1195 drawContents(*painter, dirtyRect); 1196 } 1197 1198 QPoint TerminalDisplay::cursorPosition() const 1199 { 1200 if (_screenWindow) 1201 return _screenWindow->cursorPosition(); 1202 else 1203 return {0, 0}; 1204 } 1205 1206 QRect TerminalDisplay::preeditRect() const 1207 { 1208 const int preeditLength = string_width(_inputMethodData.preeditString); 1209 1210 if (preeditLength == 0) 1211 return {}; 1212 1213 return QRect(_leftMargin + qRound(_fontWidth) * cursorPosition().x(), 1214 _topMargin + qRound(_fontHeight) * cursorPosition().y(), 1215 qRound(_fontWidth) * preeditLength, 1216 qRound(_fontHeight)); 1217 } 1218 1219 void TerminalDisplay::drawInputMethodPreeditString(QPainter &painter, const QRect &rect) 1220 { 1221 if (_inputMethodData.preeditString.isEmpty()) 1222 return; 1223 1224 const QPoint cursorPos = cursorPosition(); 1225 1226 bool invertColors = false; 1227 const QColor background = _colorTable[DEFAULT_BACK_COLOR].color; 1228 const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color; 1229 const Character *style = &_image[loc(cursorPos.x(), cursorPos.y())]; 1230 1231 drawBackground(painter, rect, background, true); 1232 drawCursor(painter, rect, foreground, background, invertColors); 1233 drawCharacters(painter, rect, _inputMethodData.preeditString, style, invertColors); 1234 1235 _inputMethodData.previousPreeditRect = rect; 1236 } 1237 1238 FilterChain *TerminalDisplay::filterChain() const 1239 { 1240 return _filterChain.get(); 1241 } 1242 1243 void TerminalDisplay::paintFilters(QPainter &painter) 1244 { 1245 // get color of character under mouse and use it to draw 1246 // lines for filters 1247 QPoint cursorPos = mapFromScene(QCursor::pos()).toPoint(); 1248 int leftMargin = _leftBaseMargin 1249 + ((_scrollbarLocation == QTermWidget::ScrollBarLeft && !_scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar)) 1250 ? _scrollBar->width() 1251 : 0); 1252 1253 auto charPos = getCharacterPosition(cursorPos); 1254 Character cursorCharacter = _image[loc(charPos.columns, charPos.lines)]; 1255 1256 painter.setPen(QPen(cursorCharacter.foregroundColor.color(colorTable()))); 1257 1258 // iterate over hotspots identified by the display's currently active filters 1259 // and draw appropriate visuals to indicate the presence of the hotspot 1260 1261 const QList<Filter::HotSpot *> spots = _filterChain->hotSpots(); 1262 for (const auto spot : spots) { 1263 QRegion region; 1264 if (spot->type() == Filter::HotSpot::Link) { 1265 QRect r; 1266 if (spot->startLine() == spot->endLine()) { 1267 r.setCoords(spot->startColumn() * qRound(_fontWidth) + 1 + leftMargin, 1268 spot->startLine() * qRound(_fontHeight) + 1 + _topBaseMargin, 1269 spot->endColumn() * qRound(_fontWidth) - 1 + leftMargin, 1270 (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin); 1271 region |= r; 1272 } else { 1273 r.setCoords(spot->startColumn() * qRound(_fontWidth) + 1 + leftMargin, 1274 spot->startLine() * qRound(_fontHeight) + 1 + _topBaseMargin, 1275 _columns * qRound(_fontWidth) - 1 + leftMargin, 1276 (spot->startLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin); 1277 region |= r; 1278 for (int line = spot->startLine() + 1; line < spot->endLine(); line++) { 1279 r.setCoords(0 * qRound(_fontWidth) + 1 + leftMargin, 1280 line * qRound(_fontHeight) + 1 + _topBaseMargin, 1281 _columns * qRound(_fontWidth) - 1 + leftMargin, 1282 (line + 1) * qRound(_fontHeight) - 1 + _topBaseMargin); 1283 region |= r; 1284 } 1285 r.setCoords(0 * qRound(_fontWidth) + 1 + leftMargin, 1286 spot->endLine() * qRound(_fontHeight) + 1 + _topBaseMargin, 1287 spot->endColumn() * qRound(_fontWidth) - 1 + leftMargin, 1288 (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin); 1289 region |= r; 1290 } 1291 } 1292 1293 for (int line = spot->startLine(); line <= spot->endLine(); line++) { 1294 int startColumn = 0; 1295 int endColumn = _columns - 1; // TODO use number of _columns which are actually 1296 // occupied on this line rather than the width of the 1297 // display in _columns 1298 1299 // ignore whitespace at the end of the lines 1300 while (QChar(_image[loc(endColumn, line)].character).isSpace() && endColumn > 0) 1301 endColumn--; 1302 1303 // increment here because the column which we want to set 'endColumn' to 1304 // is the first whitespace character at the end of the line 1305 endColumn++; 1306 1307 if (line == spot->startLine()) 1308 startColumn = spot->startColumn(); 1309 if (line == spot->endLine()) 1310 endColumn = spot->endColumn(); 1311 1312 // subtract one pixel from 1313 // the right and bottom so that 1314 // we do not overdraw adjacent 1315 // hotspots 1316 // 1317 // subtracting one pixel from all sides also prevents an edge case where 1318 // moving the mouse outside a link could still leave it underlined 1319 // because the check below for the position of the cursor 1320 // finds it on the border of the target area 1321 QRect r; 1322 r.setCoords(startColumn * qRound(_fontWidth) + 1 + leftMargin, 1323 line * qRound(_fontHeight) + 1 + _topBaseMargin, 1324 endColumn * qRound(_fontWidth) - 1 + leftMargin, 1325 (line + 1) * qRound(_fontHeight) - 1 + _topBaseMargin); 1326 // Underline link hotspots 1327 if (spot->type() == Filter::HotSpot::Link) { 1328 QFontMetricsF metrics(font()); 1329 1330 // find the baseline (which is the invisible line that the characters in the font sit on, 1331 // with some having tails dangling below) 1332 qreal baseline = (qreal)r.bottom() - metrics.descent(); 1333 // find the position of the underline below that 1334 qreal underlinePos = baseline + metrics.underlinePos(); 1335 1336 if (region.contains(mapFromScene(QCursor::pos()).toPoint())) { 1337 painter.drawLine(r.left(), underlinePos, r.right(), underlinePos); 1338 } 1339 } 1340 // Marker hotspots simply have a transparent rectanglular shape 1341 // drawn on top of them 1342 else if (spot->type() == Filter::HotSpot::Marker) { 1343 // TODO - Do not use a hardcoded colour for this 1344 painter.fillRect(r, QBrush(QColor(255, 0, 0, 120))); 1345 } 1346 } 1347 } 1348 } 1349 1350 int TerminalDisplay::textWidth(const int startColumn, const int length, const int line) const 1351 { 1352 QFontMetricsF fm(font()); 1353 qreal result = 0; 1354 for (int column = 0; column < length; column++) { 1355 result += fm.horizontalAdvance(_image[loc(startColumn + column, line)].character); 1356 } 1357 return result; 1358 } 1359 1360 QRect TerminalDisplay::calculateTextArea(int topLeftX, int topLeftY, int startColumn, int line, int length) 1361 { 1362 int left = _fixedFont ? qRound(_fontWidth) * startColumn : textWidth(0, startColumn, line); 1363 int top = qRound(_fontHeight) * line; 1364 int width = _fixedFont ? qRound(_fontWidth) * length : textWidth(startColumn, length, line); 1365 return {_leftMargin + topLeftX + left, _topMargin + topLeftY + top, width, qRound(_fontHeight)}; 1366 } 1367 1368 void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect) 1369 { 1370 // Draw opaque background 1371 drawBackground(paint, contentsRect(), _colorTable[DEFAULT_BACK_COLOR].color, true); 1372 1373 QPoint tL = contentsRect().topLeft(); 1374 int tLx = tL.x(); 1375 int tLy = tL.y(); 1376 1377 int lux = qMin(_usedColumns - 1, qMax(0, qRound((rect.left() - tLx - _leftMargin) / _fontWidth))); 1378 int luy = qMin(_usedLines - 1, qMax(0, qRound((rect.top() - tLy - _topMargin) / _fontHeight))); 1379 int rlx = qMin(_usedColumns - 1, qMax(0, qRound((rect.right() - tLx - _leftMargin) / _fontWidth))); 1380 int rly = qMin(_usedLines - 1, qMax(0, qRound((rect.bottom() - tLy - _topMargin) / _fontHeight))); 1381 1382 if (_image.empty()) { 1383 return; 1384 } 1385 1386 const int bufferSize = _usedColumns; 1387 QString unistr; 1388 unistr.reserve(bufferSize); 1389 for (int y = luy; y <= rly; y++) { 1390 char16_t c = _image[loc(lux, y)].character.unicode(); 1391 int x = lux; 1392 if (!c && x) 1393 x--; // Search for start of multi-column character 1394 for (; x <= rlx; x++) { 1395 int len = 1; 1396 int p = 0; 1397 1398 // reset our buffer to the maximal size 1399 unistr.resize(bufferSize); 1400 1401 // is this a single character or a sequence of characters ? 1402 if (_image[loc(x, y)].rendition & RE_EXTENDED_CHAR) { 1403 // sequence of characters 1404 ushort extendedCharLength = 0; 1405 std::span chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x, y)].charSequence, extendedCharLength); 1406 for (int index = 0; index < extendedCharLength; index++) { 1407 Q_ASSERT(p < bufferSize); 1408 unistr[p++] = chars[index]; 1409 } 1410 } else { 1411 // single character 1412 c = _image[loc(x, y)].character.unicode(); 1413 if (c) { 1414 Q_ASSERT(p < bufferSize); 1415 unistr[p++] = c; // fontMap(c); 1416 } 1417 } 1418 1419 bool lineDraw = isLineChar(c); 1420 bool doubleWidth = (_image[qMin(loc(x, y) + 1, _imageSize)].character.unicode() == 0); 1421 CharacterColor currentForeground = _image[loc(x, y)].foregroundColor; 1422 CharacterColor currentBackground = _image[loc(x, y)].backgroundColor; 1423 quint8 currentRendition = _image[loc(x, y)].rendition; 1424 1425 while (x + len <= rlx && _image[loc(x + len, y)].foregroundColor == currentForeground 1426 && _image[loc(x + len, y)].backgroundColor == currentBackground && _image[loc(x + len, y)].rendition == currentRendition 1427 && (_image[qMin(loc(x + len, y) + 1, _imageSize)].character.unicode() == 0) == doubleWidth 1428 && isLineChar(c = _image[loc(x + len, y)].character.unicode()) == lineDraw) // Assignment! 1429 { 1430 if (c) 1431 unistr[p++] = c; // fontMap(c); 1432 if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition 1433 len++; // Skip trailing part of multi-column character 1434 len++; 1435 } 1436 if ((x + len < _usedColumns) && (!_image[loc(x + len, y)].character.unicode())) 1437 len++; // Adjust for trailing part of multi-column character 1438 1439 bool save__fixedFont = _fixedFont; 1440 if (lineDraw) 1441 _fixedFont = false; 1442 unistr.resize(p); 1443 1444 // Create a text scaling matrix for double width and double height lines. 1445 QTransform textScale; 1446 1447 if (y < _lineProperties.size()) { 1448 if (_lineProperties[y] & LINE_DOUBLEWIDTH) 1449 textScale.scale(2, 1); 1450 1451 if (_lineProperties[y] & LINE_DOUBLEHEIGHT) 1452 textScale.scale(1, 2); 1453 } 1454 1455 // Apply text scaling matrix. 1456 paint.setWorldTransform(textScale, true); 1457 1458 // calculate the area in which the text will be drawn 1459 QRect textArea = calculateTextArea(tLx, tLy, x, y, len); 1460 1461 // move the calculated area to take account of scaling applied to the painter. 1462 // the position of the area from the origin (0,0) is scaled 1463 // by the opposite of whatever 1464 // transformation has been applied to the painter. this ensures that 1465 // painting does actually start from textArea.topLeft() 1466 //(instead of textArea.topLeft() * painter-scale) 1467 textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft())); 1468 1469 // paint text fragment 1470 drawTextFragment(paint, textArea, unistr, &_image[loc(x, y)]); //, 1471 // 0, 1472 //!_isPrinting ); 1473 1474 _fixedFont = save__fixedFont; 1475 1476 // reset back to single-width, single-height _lines 1477 paint.setWorldTransform(textScale.inverted(), true); 1478 1479 if (y < _lineProperties.size() - 1) { 1480 // double-height _lines are represented by two adjacent _lines 1481 // containing the same characters 1482 // both _lines will have the LINE_DOUBLEHEIGHT attribute. 1483 // If the current line has the LINE_DOUBLEHEIGHT attribute, 1484 // we can therefore skip the next line 1485 if (_lineProperties[y] & LINE_DOUBLEHEIGHT) 1486 y++; 1487 } 1488 1489 x += len - 1; 1490 } 1491 } 1492 } 1493 1494 void TerminalDisplay::blinkEvent() 1495 { 1496 if (!_allowBlinkingText) 1497 return; 1498 1499 _blinking = !_blinking; 1500 1501 // TODO: Optimize to only repaint the areas of the widget 1502 // where there is blinking text 1503 // rather than repainting the whole widget. 1504 update(); 1505 } 1506 1507 QRect TerminalDisplay::imageToWidget(const QRect &imageArea) const 1508 { 1509 QRect result; 1510 result.setLeft(_leftMargin + qRound(_fontWidth) * imageArea.left()); 1511 result.setTop(_topMargin + qRound(_fontHeight) * imageArea.top()); 1512 result.setWidth(qRound(_fontWidth) * imageArea.width()); 1513 result.setHeight(qRound(_fontHeight) * imageArea.height()); 1514 1515 return result; 1516 } 1517 1518 void TerminalDisplay::updateCursor() 1519 { 1520 QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(1, 1))); 1521 update(cursorRect); 1522 } 1523 1524 void TerminalDisplay::blinkCursorEvent() 1525 { 1526 _cursorBlinking = !_cursorBlinking; 1527 updateCursor(); 1528 } 1529 1530 /* ------------------------------------------------------------------------- */ 1531 /* */ 1532 /* Resizing */ 1533 /* */ 1534 /* ------------------------------------------------------------------------- */ 1535 1536 void TerminalDisplay::resizeEvent(QResizeEvent *) 1537 { 1538 updateImageSize(); 1539 processFilters(); 1540 } 1541 1542 void TerminalDisplay::propagateSize() 1543 { 1544 if (_isFixedSize) { 1545 setSize(_columns, _lines); 1546 return; 1547 } 1548 if (!_image.empty()) 1549 updateImageSize(); 1550 } 1551 1552 void TerminalDisplay::updateImageSize() 1553 { 1554 auto oldimg = _image; 1555 int oldlin = _lines; 1556 int oldcol = _columns; 1557 1558 makeImage(); 1559 1560 // copy the old image to reduce flicker 1561 int lines = qMin(oldlin, _lines); 1562 int columns = qMin(oldcol, _columns); 1563 1564 if (!oldimg.empty()) { 1565 for (int line = 0; line < lines; line++) { 1566 memcpy((void *)&_image[_columns * line], (void *)&oldimg[oldcol * line], columns * sizeof(Character)); 1567 } 1568 oldimg.clear(); 1569 } 1570 1571 if (_screenWindow) 1572 _screenWindow->setWindowLines(_lines); 1573 1574 _resizing = (oldlin != _lines) || (oldcol != _columns); 1575 1576 if (_resizing) { 1577 showResizeNotification(); 1578 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent 1579 } 1580 1581 _resizing = false; 1582 } 1583 1584 // showEvent and hideEvent are reimplemented here so that it appears to other classes that the 1585 // display has been resized when the display is hidden or shown. 1586 // 1587 // TODO: Perhaps it would be better to have separate signals for show and hide instead of using 1588 // the same signal as the one for a content size change 1589 void TerminalDisplay::showEvent(QShowEvent *) 1590 { 1591 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth); 1592 } 1593 void TerminalDisplay::hideEvent(QHideEvent *) 1594 { 1595 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth); 1596 } 1597 1598 /* ------------------------------------------------------------------------- */ 1599 /* */ 1600 /* Scrollbar */ 1601 /* */ 1602 /* ------------------------------------------------------------------------- */ 1603 1604 void TerminalDisplay::scrollBarPositionChanged(int) 1605 { 1606 if (!_screenWindow) 1607 return; 1608 1609 _screenWindow->scrollTo(_scrollBar->value()); 1610 1611 // if the thumb has been moved to the bottom of the _scrollBar then set 1612 // the display to automatically track new output, 1613 // that is, scroll down automatically 1614 // to how new _lines as they are added 1615 const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum()); 1616 _screenWindow->setTrackOutput(atEndOfOutput); 1617 1618 updateImage(); 1619 1620 // QMLTermWidget: notify qml side of the change only when needed. 1621 Q_EMIT scrollbarValueChanged(); 1622 } 1623 1624 void TerminalDisplay::setScroll(int cursor, int slines) 1625 { 1626 // update _scrollBar if the range or value has changed, 1627 // otherwise return 1628 // 1629 // setting the range or value of a _scrollBar will always trigger 1630 // a repaint, so it should be avoided if it is not necessary 1631 if (_scrollBar->minimum() == 0 && _scrollBar->maximum() == (slines - _lines) && _scrollBar->value() == cursor) { 1632 return; 1633 } 1634 1635 disconnect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged); 1636 _scrollBar->setRange(0, slines - _lines); 1637 _scrollBar->setSingleStep(1); 1638 _scrollBar->setPageStep(_lines); 1639 _scrollBar->setValue(cursor); 1640 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged); 1641 } 1642 1643 void TerminalDisplay::scrollToEnd() 1644 { 1645 disconnect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged); 1646 _scrollBar->setValue(_scrollBar->maximum()); 1647 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged); 1648 1649 _screenWindow->scrollTo(_scrollBar->value() + 1); 1650 _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput()); 1651 } 1652 1653 void TerminalDisplay::setScrollBarPosition(QTermWidget::ScrollBarPosition position) 1654 { 1655 if (_scrollbarLocation == position) 1656 return; 1657 1658 if (position == QTermWidget::NoScrollBar) 1659 _scrollBar->hide(); 1660 else 1661 _scrollBar->show(); 1662 1663 _topMargin = _leftMargin = 1; 1664 _scrollbarLocation = position; 1665 1666 propagateSize(); 1667 update(); 1668 } 1669 1670 void TerminalDisplay::mousePressEvent(QMouseEvent *ev) 1671 { 1672 if (_possibleTripleClick && (ev->button() == Qt::LeftButton)) { 1673 mouseTripleClickEvent(ev); 1674 return; 1675 } 1676 1677 if (!contentsRect().contains(ev->pos())) 1678 return; 1679 1680 if (!_screenWindow) 1681 return; 1682 1683 auto charPos = getCharacterPosition(ev->pos()); 1684 QPoint pos = charPos; 1685 auto [charColumn, charLine] = charPos; 1686 1687 if (ev->button() == Qt::LeftButton) { 1688 _lineSelectionMode = false; 1689 _wordSelectionMode = false; 1690 1691 Q_EMIT isBusySelecting(true); // Keep it steady... 1692 // Drag only when the Control key is hold 1693 bool selected = false; 1694 1695 // The receiver of the testIsSelected() signal will adjust 1696 // 'selected' accordingly. 1697 // emit testIsSelected(pos.x(), pos.y(), selected); 1698 1699 selected = _screenWindow->isSelected(charColumn, charLine); 1700 1701 if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected) { 1702 // The user clicked inside selected text 1703 dragInfo.state = diPending; 1704 dragInfo.start = ev->pos(); 1705 } else { 1706 // No reason to ever start a drag event 1707 dragInfo.state = diNone; 1708 1709 _preserveLineBreaks = !((ev->modifiers() & Qt::ControlModifier) && !(ev->modifiers() & Qt::AltModifier)); 1710 _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier); 1711 1712 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) { 1713 _screenWindow->clearSelection(); 1714 1715 // emit clearSelectionSignal(); 1716 pos.ry() += _scrollBar->value(); 1717 _iPntSel = _pntSel = pos; 1718 _actSel = 1; // left mouse button pressed but nothing selected yet. 1719 1720 } else { 1721 Q_EMIT mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0); 1722 } 1723 1724 Filter::HotSpot *spot = _filterChain->hotSpotAt(charLine, charColumn); 1725 if (spot && spot->type() == Filter::HotSpot::Link) 1726 spot->activate(QLatin1String("click-action")); 1727 } 1728 } else if (ev->button() == Qt::MiddleButton) { 1729 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) 1730 emitSelection(true, ev->modifiers() & Qt::ControlModifier); 1731 else 1732 Q_EMIT mouseSignal(1, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0); 1733 } else if (ev->button() == Qt::RightButton) { 1734 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) 1735 Q_EMIT configureRequest(ev->pos()); 1736 else 1737 Q_EMIT mouseSignal(2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0); 1738 } 1739 } 1740 1741 QList<QAction *> TerminalDisplay::filterActions(const QPoint &position) 1742 { 1743 auto pos = getCharacterPosition(position); 1744 1745 Filter::HotSpot *spot = _filterChain->hotSpotAt(pos.lines, pos.columns); 1746 1747 return spot ? spot->actions() : QList<QAction *>(); 1748 } 1749 1750 void TerminalDisplay::mouseMoveEvent(QMouseEvent *ev) 1751 { 1752 int leftMargin = _leftBaseMargin 1753 + ((_scrollbarLocation == QTermWidget::ScrollBarLeft && !_scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar)) 1754 ? _scrollBar->width() 1755 : 0); 1756 1757 auto charPos = getCharacterPosition(ev->pos()); 1758 1759 // handle filters 1760 // change link hot-spot appearance on mouse-over 1761 Filter::HotSpot *spot = _filterChain->hotSpotAt(charPos.lines, charPos.columns); 1762 if (spot && spot->type() == Filter::HotSpot::Link) { 1763 QRegion previousHotspotArea = _mouseOverHotspotArea; 1764 _mouseOverHotspotArea = QRegion(); 1765 QRect r; 1766 if (spot->startLine() == spot->endLine()) { 1767 r.setCoords(spot->startColumn() * qRound(_fontWidth) + leftMargin, 1768 spot->startLine() * qRound(_fontHeight) + _topBaseMargin, 1769 spot->endColumn() * qRound(_fontWidth) + leftMargin, 1770 (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin); 1771 _mouseOverHotspotArea |= r; 1772 } else { 1773 r.setCoords(spot->startColumn() * qRound(_fontWidth) + leftMargin, 1774 spot->startLine() * qRound(_fontHeight) + _topBaseMargin, 1775 _columns * qRound(_fontWidth) - 1 + leftMargin, 1776 (spot->startLine() + 1) * qRound(_fontHeight) + _topBaseMargin); 1777 _mouseOverHotspotArea |= r; 1778 for (int line = spot->startLine() + 1; line < spot->endLine(); line++) { 1779 r.setCoords(0 * qRound(_fontWidth) + leftMargin, 1780 line * qRound(_fontHeight) + _topBaseMargin, 1781 _columns * qRound(_fontWidth) + leftMargin, 1782 (line + 1) * qRound(_fontHeight) + _topBaseMargin); 1783 _mouseOverHotspotArea |= r; 1784 } 1785 r.setCoords(0 * qRound(_fontWidth) + leftMargin, 1786 spot->endLine() * qRound(_fontHeight) + _topBaseMargin, 1787 spot->endColumn() * qRound(_fontWidth) + leftMargin, 1788 (spot->endLine() + 1) * qRound(_fontHeight) + _topBaseMargin); 1789 _mouseOverHotspotArea |= r; 1790 } 1791 update(_mouseOverHotspotArea | previousHotspotArea); 1792 } else if (!_mouseOverHotspotArea.isEmpty()) { 1793 update(_mouseOverHotspotArea); 1794 // set hotspot area to an invalid rectangle 1795 _mouseOverHotspotArea = QRegion(); 1796 } 1797 1798 // for auto-hiding the cursor, we need mouseTracking 1799 if (ev->buttons() == Qt::NoButton) 1800 return; 1801 1802 // if the terminal is interested in mouse movements 1803 // then Q_EMIT a mouse movement signal, unless the shift 1804 // key is being held down, which overrides this. 1805 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) { 1806 int button = 3; 1807 if (ev->buttons() & Qt::LeftButton) 1808 button = 0; 1809 if (ev->buttons() & Qt::MiddleButton) 1810 button = 1; 1811 if (ev->buttons() & Qt::RightButton) 1812 button = 2; 1813 1814 Q_EMIT mouseSignal(button, charPos.columns + 1, charPos.lines + 1 + _scrollBar->value() - _scrollBar->maximum(), 1); 1815 1816 return; 1817 } 1818 1819 if (dragInfo.state == diPending) { 1820 // we had a mouse down, but haven't confirmed a drag yet 1821 // if the mouse has moved sufficiently, we will confirm 1822 1823 // int distance = KGlobalSettings::dndEventDelay(); 1824 int distance = QApplication::startDragDistance(); 1825 if (ev->position().x() > dragInfo.start.x() + distance || ev->position().x() < dragInfo.start.x() - distance 1826 || ev->position().y() > dragInfo.start.y() + distance || ev->position().y() < dragInfo.start.y() - distance) { 1827 // we've left the drag square, we can start a real drag operation now 1828 Q_EMIT isBusySelecting(false); // Ok.. we can breath again. 1829 1830 _screenWindow->clearSelection(); 1831 doDrag(); 1832 } 1833 return; 1834 } else if (dragInfo.state == diDragging) { 1835 // this isn't technically needed because mouseMoveEvent is suppressed during 1836 // Qt drag operations, replaced by dragMoveEvent 1837 return; 1838 } 1839 1840 if (_actSel == 0) 1841 return; 1842 1843 // don't extend selection while pasting 1844 if (ev->buttons() & Qt::MiddleButton) 1845 return; 1846 1847 extendSelection(ev->pos()); 1848 } 1849 1850 void TerminalDisplay::extendSelection(const QPoint &position) 1851 { 1852 QPoint pos = position; 1853 1854 if (!_screenWindow) 1855 return; 1856 1857 // if ( !contentsRect().contains(ev->pos()) ) return; 1858 QPoint tL = contentsRect().topLeft(); 1859 int tLx = tL.x(); 1860 int tLy = tL.y(); 1861 int scroll = _scrollBar->value(); 1862 1863 // we're in the process of moving the mouse with the left button pressed 1864 // the mouse cursor will kept caught within the bounds of the text in 1865 // this widget. 1866 1867 int linesBeyondWidget = 0; 1868 1869 QRect textBounds(tLx + _leftMargin, tLy + _topMargin, _usedColumns * qRound(_fontWidth) - 1, _usedLines * qRound(_fontHeight) - 1); 1870 1871 // Adjust position within text area bounds. 1872 QPoint oldpos = pos; 1873 1874 pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right())); 1875 pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom())); 1876 1877 if (oldpos.y() > textBounds.bottom()) { 1878 linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / qRound(_fontHeight); 1879 _scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward 1880 } 1881 if (oldpos.y() < textBounds.top()) { 1882 linesBeyondWidget = (textBounds.top() - oldpos.y()) / qRound(_fontHeight); 1883 _scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history 1884 } 1885 1886 QPoint here = 1887 getCharacterPosition(pos); // QPoint((pos.x()-tLx-_leftMargin+(qRound(_fontWidth)/2))/qRound(_fontWidth),(pos.y()-tLy-_topMargin)/qRound(_fontHeight)); 1888 QPoint ohere; 1889 QPoint _iPntSelCorr = _iPntSel; 1890 _iPntSelCorr.ry() -= _scrollBar->value(); 1891 QPoint _pntSelCorr = _pntSel; 1892 _pntSelCorr.ry() -= _scrollBar->value(); 1893 bool swapping = false; 1894 1895 if (_wordSelectionMode) { 1896 // Extend to word boundaries 1897 int i; 1898 QChar selClass; 1899 1900 bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x())); 1901 bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x())); 1902 swapping = left_not_right != old_left_not_right; 1903 1904 // Find left (left_not_right ? from here : from start) 1905 QPoint left = left_not_right ? here : _iPntSelCorr; 1906 i = loc(left.x(), left.y()); 1907 if (i >= 0 && i <= _imageSize) { 1908 selClass = charClass(_image[i].character); 1909 while (((left.x() > 0) || (left.y() > 0 && (_lineProperties[left.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) { 1910 i--; 1911 if (left.x() > 0) 1912 left.rx()--; 1913 else { 1914 left.rx() = _usedColumns - 1; 1915 left.ry()--; 1916 } 1917 } 1918 } 1919 1920 // Find left (left_not_right ? from start : from here) 1921 QPoint right = left_not_right ? _iPntSelCorr : here; 1922 i = loc(right.x(), right.y()); 1923 if (i >= 0 && i <= _imageSize) { 1924 selClass = charClass(_image[i].character); 1925 while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && (_lineProperties[right.y()] & LINE_WRAPPED))) 1926 && charClass(_image[i + 1].character) == selClass) { 1927 i++; 1928 if (right.x() < _usedColumns - 1) 1929 right.rx()++; 1930 else { 1931 right.rx() = 0; 1932 right.ry()++; 1933 } 1934 } 1935 } 1936 1937 // Pick which is start (ohere) and which is extension (here) 1938 if (left_not_right) { 1939 here = left; 1940 ohere = right; 1941 } else { 1942 here = right; 1943 ohere = left; 1944 } 1945 ohere.rx()++; 1946 } 1947 1948 if (_lineSelectionMode) { 1949 // Extend to complete line 1950 bool above_not_below = (here.y() < _iPntSelCorr.y()); 1951 1952 QPoint above = above_not_below ? here : _iPntSelCorr; 1953 QPoint below = above_not_below ? _iPntSelCorr : here; 1954 1955 while (above.y() > 0 && (_lineProperties[above.y() - 1] & LINE_WRAPPED)) 1956 above.ry()--; 1957 while (below.y() < _usedLines - 1 && (_lineProperties[below.y()] & LINE_WRAPPED)) 1958 below.ry()++; 1959 1960 above.setX(0); 1961 below.setX(_usedColumns - 1); 1962 1963 // Pick which is start (ohere) and which is extension (here) 1964 if (above_not_below) { 1965 here = above; 1966 ohere = below; 1967 } else { 1968 here = below; 1969 ohere = above; 1970 } 1971 1972 QPoint newSelBegin = QPoint(ohere.x(), ohere.y()); 1973 swapping = !(_tripleSelBegin == newSelBegin); 1974 _tripleSelBegin = newSelBegin; 1975 1976 ohere.rx()++; 1977 } 1978 1979 int offset = 0; 1980 if (!_wordSelectionMode && !_lineSelectionMode) { 1981 int i; 1982 QChar selClass; 1983 1984 bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x())); 1985 bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x())); 1986 swapping = left_not_right != old_left_not_right; 1987 1988 // Find left (left_not_right ? from here : from start) 1989 QPoint left = left_not_right ? here : _iPntSelCorr; 1990 1991 // Find left (left_not_right ? from start : from here) 1992 QPoint right = left_not_right ? _iPntSelCorr : here; 1993 if (right.x() > 0 && !_columnSelectionMode) { 1994 i = loc(right.x(), right.y()); 1995 if (i >= 0 && i <= _imageSize) { 1996 selClass = charClass(_image[i - 1].character); 1997 /* if (selClass == ' ') 1998 { 1999 while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) && 2000 !(_lineProperties[right.y()] & LINE_WRAPPED)) 2001 { i++; right.rx()++; } 2002 if (right.x() < _usedColumns-1) 2003 right = left_not_right ? _iPntSelCorr : here; 2004 else 2005 right.rx()++; // will be balanced later because of offset=-1; 2006 }*/ 2007 } 2008 } 2009 2010 // Pick which is start (ohere) and which is extension (here) 2011 if (left_not_right) { 2012 here = left; 2013 ohere = right; 2014 offset = 0; 2015 } else { 2016 here = right; 2017 ohere = left; 2018 offset = -1; 2019 } 2020 } 2021 2022 if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) 2023 return; // not moved 2024 2025 if (here == ohere) 2026 return; // It's not left, it's not right. 2027 2028 if (_actSel < 2 || swapping) { 2029 if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) { 2030 _screenWindow->setSelectionStart(ohere.x(), ohere.y(), true); 2031 } else { 2032 _screenWindow->setSelectionStart(ohere.x() - 1 - offset, ohere.y(), false); 2033 } 2034 } 2035 2036 _actSel = 2; // within selection 2037 _pntSel = here; 2038 _pntSel.ry() += _scrollBar->value(); 2039 2040 if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) { 2041 _screenWindow->setSelectionEnd(here.x(), here.y()); 2042 } else { 2043 _screenWindow->setSelectionEnd(here.x() + offset, here.y()); 2044 } 2045 } 2046 2047 void TerminalDisplay::mouseReleaseEvent(QMouseEvent *ev) 2048 { 2049 if (!_screenWindow) 2050 return; 2051 2052 auto [charColumn, charLine] = getCharacterPosition(ev->pos()); 2053 2054 if (ev->button() == Qt::LeftButton) { 2055 Q_EMIT isBusySelecting(false); 2056 if (dragInfo.state == diPending) { 2057 // We had a drag event pending but never confirmed. Kill selection 2058 _screenWindow->clearSelection(); 2059 // emit clearSelectionSignal(); 2060 } else { 2061 if (_actSel > 1) { 2062 setSelection(_screenWindow->selectedText(_preserveLineBreaks)); 2063 } 2064 2065 _actSel = 0; 2066 2067 // FIXME: emits a release event even if the mouse is 2068 // outside the range. The procedure used in `mouseMoveEvent' 2069 // applies here, too. 2070 2071 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) 2072 Q_EMIT mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 2); 2073 } 2074 dragInfo.state = diNone; 2075 } 2076 2077 if (!_mouseMarks && ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier)) || ev->button() == Qt::MiddleButton)) { 2078 Q_EMIT mouseSignal(ev->button() == Qt::MiddleButton ? 1 : 2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 2); 2079 } 2080 } 2081 2082 CharPos TerminalDisplay::getCharacterPosition(const QPointF &widgetPoint) const 2083 { 2084 int line, column = 0; 2085 2086 line = (widgetPoint.y() - contentsRect().top() - _topMargin) / qRound(_fontHeight); 2087 if (line < 0) 2088 line = 0; 2089 if (line >= _usedLines) 2090 line = _usedLines - 1; 2091 2092 int x = widgetPoint.x() + qRound(_fontWidth) / 2 - contentsRect().left() - _leftMargin; 2093 if (_fixedFont) 2094 column = x / qRound(_fontWidth); 2095 else { 2096 column = 0; 2097 while (column + 1 < _usedColumns && x > textWidth(0, column + 1, line)) 2098 column++; 2099 } 2100 2101 if (column < 0) 2102 column = 0; 2103 2104 // the column value returned can be equal to _usedColumns, which 2105 // is the position just after the last character displayed in a line. 2106 // 2107 // this is required so that the user can select characters in the right-most 2108 // column (or left-most for right-to-left input) 2109 if (column > _usedColumns) 2110 column = _usedColumns; 2111 2112 return {column, line}; 2113 } 2114 2115 void TerminalDisplay::updateFilters() 2116 { 2117 if (!_screenWindow) 2118 return; 2119 2120 processFilters(); 2121 } 2122 2123 void TerminalDisplay::updateLineProperties() 2124 { 2125 if (!_screenWindow) 2126 return; 2127 2128 _lineProperties = _screenWindow->getLineProperties(); 2129 } 2130 2131 void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent *ev) 2132 { 2133 if (ev->button() != Qt::LeftButton) 2134 return; 2135 if (!_screenWindow) 2136 return; 2137 2138 QPoint pos = getCharacterPosition(ev->pos()); 2139 2140 // pass on double click as two clicks. 2141 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) { 2142 // Send just _ONE_ click event, since the first click of the double click 2143 // was already sent by the click handler 2144 Q_EMIT mouseSignal(0, pos.x() + 1, pos.y() + 1 + _scrollBar->value() - _scrollBar->maximum(), 2145 0); // left button 2146 return; 2147 } 2148 2149 _screenWindow->clearSelection(); 2150 QPoint bgnSel = pos; 2151 QPoint endSel = pos; 2152 int i = loc(bgnSel.x(), bgnSel.y()); 2153 _iPntSel = bgnSel; 2154 _iPntSel.ry() += _scrollBar->value(); 2155 2156 _wordSelectionMode = true; 2157 2158 // find word boundaries... 2159 QChar selClass = charClass(_image[i].character); 2160 { 2161 // find the start of the word 2162 int x = bgnSel.x(); 2163 while (((x > 0) || (bgnSel.y() > 0 && (_lineProperties[bgnSel.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) { 2164 i--; 2165 if (x > 0) 2166 x--; 2167 else { 2168 x = _usedColumns - 1; 2169 bgnSel.ry()--; 2170 } 2171 } 2172 2173 bgnSel.setX(x); 2174 _screenWindow->setSelectionStart(bgnSel.x(), bgnSel.y(), false); 2175 2176 // find the end of the word 2177 i = loc(endSel.x(), endSel.y()); 2178 x = endSel.x(); 2179 while (((x < _usedColumns - 1) || (endSel.y() < _usedLines - 1 && (_lineProperties[endSel.y()] & LINE_WRAPPED))) 2180 && charClass(_image[i + 1].character) == selClass) { 2181 i++; 2182 if (x < _usedColumns - 1) 2183 x++; 2184 else { 2185 x = 0; 2186 endSel.ry()++; 2187 } 2188 } 2189 2190 endSel.setX(x); 2191 2192 // In word selection mode don't select @ (64) if at end of word. 2193 if ((QChar(_image[i].character) == QLatin1Char('@')) && ((endSel.x() - bgnSel.x()) > 0)) 2194 endSel.setX(x - 1); 2195 2196 _actSel = 2; // within selection 2197 2198 _screenWindow->setSelectionEnd(endSel.x(), endSel.y()); 2199 2200 setSelection(_screenWindow->selectedText(_preserveLineBreaks)); 2201 } 2202 2203 _possibleTripleClick = true; 2204 2205 QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout())); 2206 } 2207 2208 void TerminalDisplay::wheelEvent(QWheelEvent *ev) 2209 { 2210 if (ev->angleDelta().y() == 0) // orientation is not verical 2211 return; 2212 2213 // if the terminal program is not interested mouse events 2214 // then send the event to the scrollbar if the slider has room to move 2215 // or otherwise send simulated up / down key presses to the terminal program 2216 // for the benefit of programs such as 'less' 2217 if (_mouseMarks) { 2218 bool canScroll = _scrollBar->maximum() > 0; 2219 if (canScroll) 2220 _scrollBar->event(ev); 2221 else { 2222 // assume that each Up / Down key event will cause the terminal application 2223 // to scroll by one line. 2224 // 2225 // to get a reasonable scrolling speed, scroll by one line for every 5 degrees 2226 // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees, 2227 // giving a scroll of 3 lines 2228 int key = ev->angleDelta().y() > 0 ? Qt::Key_Up : Qt::Key_Down; 2229 2230 // QWheelEvent::delta() gives rotation in eighths of a degree 2231 int wheelDegrees = ev->angleDelta().y() / 8; 2232 int linesToScroll = abs(wheelDegrees) / 5; 2233 2234 QKeyEvent keyScrollEvent(QEvent::KeyPress, key, Qt::NoModifier); 2235 2236 for (int i = 0; i < linesToScroll; i++) 2237 Q_EMIT keyPressedSignal(&keyScrollEvent, false); 2238 } 2239 } else { 2240 // terminal program wants notification of mouse activity 2241 auto pos = getCharacterPosition(ev->position()); 2242 2243 Q_EMIT mouseSignal(ev->angleDelta().y() > 0 ? 4 : 5, pos.columns + 1, pos.lines + 1 + _scrollBar->value() - _scrollBar->maximum(), 0); 2244 } 2245 } 2246 2247 void TerminalDisplay::tripleClickTimeout() 2248 { 2249 _possibleTripleClick = false; 2250 } 2251 2252 void TerminalDisplay::mouseTripleClickEvent(QMouseEvent *ev) 2253 { 2254 if (!_screenWindow) 2255 return; 2256 2257 _iPntSel = getCharacterPosition(ev->pos()); 2258 2259 _screenWindow->clearSelection(); 2260 2261 _lineSelectionMode = true; 2262 _wordSelectionMode = false; 2263 2264 _actSel = 2; // within selection 2265 Q_EMIT isBusySelecting(true); // Keep it steady... 2266 2267 while (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED)) 2268 _iPntSel.ry()--; 2269 2270 if (_tripleClickMode == SelectForwardsFromCursor) { 2271 // find word boundary start 2272 int i = loc(_iPntSel.x(), _iPntSel.y()); 2273 QChar selClass = charClass(_image[i].character); 2274 int x = _iPntSel.x(); 2275 2276 while (((x > 0) || (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) { 2277 i--; 2278 if (x > 0) 2279 x--; 2280 else { 2281 x = _columns - 1; 2282 _iPntSel.ry()--; 2283 } 2284 } 2285 2286 _screenWindow->setSelectionStart(x, _iPntSel.y(), false); 2287 _tripleSelBegin = QPoint(x, _iPntSel.y()); 2288 } else if (_tripleClickMode == SelectWholeLine) { 2289 _screenWindow->setSelectionStart(0, _iPntSel.y(), false); 2290 _tripleSelBegin = QPoint(0, _iPntSel.y()); 2291 } 2292 2293 while (_iPntSel.y() < _lines - 1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED)) 2294 _iPntSel.ry()++; 2295 2296 _screenWindow->setSelectionEnd(_columns - 1, _iPntSel.y()); 2297 2298 setSelection(_screenWindow->selectedText(_preserveLineBreaks)); 2299 2300 _iPntSel.ry() += _scrollBar->value(); 2301 } 2302 2303 bool TerminalDisplay::focusNextPrevChild(bool next) 2304 { 2305 if (next) 2306 return false; // This disables changing the active part in konqueror 2307 // when pressing Tab 2308 return false; 2309 // return QWidget::focusNextPrevChild( next ); 2310 } 2311 2312 QChar TerminalDisplay::charClass(QChar qch) const 2313 { 2314 if (qch.isSpace()) 2315 return QLatin1Char(' '); 2316 2317 if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive)) 2318 return QLatin1Char('a'); 2319 2320 return qch; 2321 } 2322 2323 void TerminalDisplay::setWordCharacters(const QString &wc) 2324 { 2325 _wordCharacters = wc; 2326 } 2327 2328 void TerminalDisplay::setUsesMouse(bool on) 2329 { 2330 if (_mouseMarks != on) { 2331 _mouseMarks = on; 2332 setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor); 2333 Q_EMIT usesMouseChanged(); 2334 } 2335 } 2336 bool TerminalDisplay::usesMouse() const 2337 { 2338 return _mouseMarks; 2339 } 2340 2341 void TerminalDisplay::setBracketedPasteMode(bool on) 2342 { 2343 _bracketedPasteMode = on; 2344 } 2345 bool TerminalDisplay::bracketedPasteMode() const 2346 { 2347 return _bracketedPasteMode; 2348 } 2349 2350 /* ------------------------------------------------------------------------- */ 2351 /* */ 2352 /* Clipboard */ 2353 /* */ 2354 /* ------------------------------------------------------------------------- */ 2355 2356 #undef KeyPress 2357 2358 void TerminalDisplay::emitSelection(bool useXselection, bool appendReturn) 2359 { 2360 if (!_screenWindow) 2361 return; 2362 2363 // Paste Clipboard by simulating keypress events 2364 QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection : QClipboard::Clipboard); 2365 if (!text.isEmpty()) { 2366 text.replace(QLatin1String("\r\n"), QLatin1String("\n")); 2367 text.replace(QLatin1Char('\n'), QLatin1Char('\r')); 2368 2369 if (_trimPastedTrailingNewlines) { 2370 text.replace(QRegularExpression(QStringLiteral("\\r+$")), QString()); 2371 } 2372 2373 // TODO QMLTERMWIDGET We should expose this as a signal. 2374 // if (_confirmMultilinePaste && text.contains(QLatin1Char('\r'))) { 2375 // QMessageBox confirmation(this); 2376 // confirmation.setWindowTitle(tr("Paste multiline text")); 2377 // confirmation.setText(tr("Are you sure you want to paste this text?")); 2378 // confirmation.setDetailedText(text); 2379 // confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No); 2380 // // Click "Show details..." to show those by default 2381 // const auto buttons = confirmation.buttons(); 2382 // for( QAbstractButton * btn : buttons ) { 2383 // if (confirmation.buttonRole(btn) == QMessageBox::ActionRole && btn->text() == QMessageBox::tr("Show Details...")) { 2384 // Q_EMIT btn->clicked(); 2385 // break; 2386 // } 2387 // } 2388 // confirmation.setDefaultButton(QMessageBox::Yes); 2389 // confirmation.exec(); 2390 // if (confirmation.standardButton(confirmation.clickedButton()) != QMessageBox::Yes) { 2391 // return; 2392 // } 2393 // } 2394 2395 bracketText(text); 2396 2397 // appendReturn is intentionally handled _after_ enclosing texts with brackets as 2398 // that feature is used to allow execution of commands immediately after paste. 2399 // Ref: https://bugs.kde.org/show_bug.cgi?id=16179 2400 // Ref: https://github.com/KDE/konsole/commit/83d365f2ebfe2e659c1e857a2f5f247c556ab571 2401 if (appendReturn) { 2402 text.append(QLatin1Char('\r')); 2403 } 2404 2405 QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text); 2406 Q_EMIT keyPressedSignal(&e, true); // expose as a big fat keypress event 2407 2408 _screenWindow->clearSelection(); 2409 2410 switch (mMotionAfterPasting) { 2411 case MoveStartScreenWindow: 2412 // Temporarily stop tracking output, or pasting contents triggers 2413 // ScreenWindow::notifyOutputChanged() and the latter scrolls the 2414 // terminal to the last line. It will be re-enabled when needed 2415 // (e.g., scrolling to the last line). 2416 _screenWindow->setTrackOutput(false); 2417 _screenWindow->scrollTo(0); 2418 break; 2419 case MoveEndScreenWindow: 2420 scrollToEnd(); 2421 break; 2422 case NoMoveScreenWindow: 2423 break; 2424 } 2425 } 2426 } 2427 2428 void TerminalDisplay::bracketText(QString &text) const 2429 { 2430 if (bracketedPasteMode() && !_disabledBracketedPasteMode) { 2431 text.prepend(QLatin1String("\033[200~")); 2432 text.append(QLatin1String("\033[201~")); 2433 } 2434 } 2435 2436 void TerminalDisplay::setSelection(const QString &t) 2437 { 2438 if (QApplication::clipboard()->supportsSelection()) { 2439 QApplication::clipboard()->setText(t, QClipboard::Selection); 2440 } 2441 } 2442 2443 void TerminalDisplay::copyClipboard() 2444 { 2445 if (!_screenWindow) 2446 return; 2447 2448 QString text = _screenWindow->selectedText(_preserveLineBreaks); 2449 if (!text.isEmpty()) 2450 QApplication::clipboard()->setText(text); 2451 } 2452 2453 void TerminalDisplay::pasteClipboard() 2454 { 2455 emitSelection(false, false); 2456 } 2457 2458 void TerminalDisplay::pasteSelection() 2459 { 2460 emitSelection(true, false); 2461 } 2462 2463 void TerminalDisplay::setConfirmMultilinePaste(bool confirmMultilinePaste) 2464 { 2465 _confirmMultilinePaste = confirmMultilinePaste; 2466 } 2467 2468 void TerminalDisplay::setTrimPastedTrailingNewlines(bool trimPastedTrailingNewlines) 2469 { 2470 _trimPastedTrailingNewlines = trimPastedTrailingNewlines; 2471 } 2472 2473 /* ------------------------------------------------------------------------- */ 2474 /* */ 2475 /* Keyboard */ 2476 /* */ 2477 /* ------------------------------------------------------------------------- */ 2478 2479 void TerminalDisplay::setFlowControlWarningEnabled(bool enable) 2480 { 2481 _flowControlWarningEnabled = enable; 2482 2483 // if the dialog is currently visible and the flow control warning has 2484 // been disabled then hide the dialog 2485 if (!enable) 2486 outputSuspended(false); 2487 } 2488 2489 void TerminalDisplay::setMotionAfterPasting(MotionAfterPasting action) 2490 { 2491 mMotionAfterPasting = action; 2492 } 2493 2494 int TerminalDisplay::motionAfterPasting() 2495 { 2496 return mMotionAfterPasting; 2497 } 2498 2499 void TerminalDisplay::keyPressEvent(QKeyEvent *event) 2500 { 2501 _actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't 2502 // know where the current selection is. 2503 2504 if (_hasBlinkingCursor) { 2505 _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2); 2506 if (_cursorBlinking) 2507 blinkCursorEvent(); 2508 else 2509 _cursorBlinking = false; 2510 } 2511 2512 Q_EMIT keyPressedSignal(event, false); 2513 2514 event->accept(); 2515 } 2516 2517 void TerminalDisplay::inputMethodEvent(QInputMethodEvent *event) 2518 { 2519 QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString()); 2520 Q_EMIT keyPressedSignal(&keyEvent, false); 2521 2522 _inputMethodData.preeditString = event->preeditString(); 2523 update(preeditRect() | _inputMethodData.previousPreeditRect); 2524 2525 event->accept(); 2526 } 2527 2528 void TerminalDisplay::inputMethodQuery(QInputMethodQueryEvent *event) 2529 { 2530 event->setValue(Qt::ImEnabled, true); 2531 event->setValue(Qt::ImHints, QVariant(Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase)); 2532 event->accept(); 2533 } 2534 2535 QVariant TerminalDisplay::inputMethodQuery(Qt::InputMethodQuery query) const 2536 { 2537 const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0, 0); 2538 switch (query) { 2539 case Qt::ImCursorRectangle: 2540 return imageToWidget(QRect(cursorPos.x(), cursorPos.y(), 1, 1)); 2541 break; 2542 case Qt::ImFont: 2543 return font(); 2544 break; 2545 case Qt::ImCursorPosition: 2546 // return the cursor position within the current line 2547 return cursorPos.x(); 2548 break; 2549 case Qt::ImSurroundingText: { 2550 // return the text from the current line 2551 QString lineText; 2552 QTextStream stream(&lineText); 2553 PlainTextDecoder decoder; 2554 decoder.begin(&stream); 2555 decoder.decodeLine(std::span(&_image[loc(0, cursorPos.y())], _usedColumns), _lineProperties[cursorPos.y()]); 2556 decoder.end(); 2557 return lineText; 2558 } break; 2559 case Qt::ImCurrentSelection: 2560 return QString(); 2561 break; 2562 default: 2563 break; 2564 } 2565 2566 return QVariant(); 2567 } 2568 2569 bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent *keyEvent) 2570 { 2571 int modifiers = keyEvent->modifiers(); 2572 2573 // When a possible shortcut combination is pressed, 2574 // Q_EMIT the overrideShortcutCheck() signal to allow the host 2575 // to decide whether the terminal should override it or not. 2576 if (modifiers != Qt::NoModifier) { 2577 int modifierCount = 0; 2578 unsigned int currentModifier = Qt::ShiftModifier; 2579 2580 while (currentModifier <= Qt::KeypadModifier) { 2581 if (modifiers & currentModifier) 2582 modifierCount++; 2583 currentModifier <<= 1; 2584 } 2585 if (modifierCount < 2) { 2586 bool override = false; 2587 Q_EMIT overrideShortcutCheck(keyEvent, override); 2588 if (override) { 2589 keyEvent->accept(); 2590 return true; 2591 } 2592 } 2593 } 2594 2595 // Override any of the following shortcuts because 2596 // they are needed by the terminal 2597 int keyCode = keyEvent->key() | modifiers; 2598 switch (keyCode) { 2599 // list is taken from the QLineEdit::event() code 2600 case Qt::Key_Tab: 2601 case Qt::Key_Delete: 2602 case Qt::Key_Home: 2603 case Qt::Key_End: 2604 case Qt::Key_Backspace: 2605 case Qt::Key_Left: 2606 case Qt::Key_Right: 2607 case Qt::Key_Escape: 2608 keyEvent->accept(); 2609 return true; 2610 } 2611 return false; 2612 } 2613 2614 bool TerminalDisplay::event(QEvent *event) 2615 { 2616 bool eventHandled = false; 2617 switch (event->type()) { 2618 case QEvent::ShortcutOverride: 2619 eventHandled = handleShortcutOverrideEvent((QKeyEvent *)event); 2620 break; 2621 case QEvent::PaletteChange: 2622 case QEvent::ApplicationPaletteChange: 2623 _scrollBar->setPalette(QApplication::palette()); 2624 break; 2625 case QEvent::InputMethodQuery: 2626 inputMethodQuery(static_cast<QInputMethodQueryEvent *>(event)); 2627 eventHandled = true; 2628 break; 2629 default: 2630 break; 2631 } 2632 return eventHandled ? true : QQuickItem::event(event); 2633 } 2634 2635 void TerminalDisplay::setBellMode(int mode) 2636 { 2637 _bellMode = mode; 2638 } 2639 2640 void TerminalDisplay::enableBell() 2641 { 2642 _allowBell = true; 2643 } 2644 2645 void TerminalDisplay::bell(const QString &message) 2646 { 2647 if (_bellMode == NoBell) 2648 return; 2649 2650 // limit the rate at which bells can occur 2651 //...mainly for sound effects where rapid bells in sequence 2652 // produce a horrible noise 2653 if (_allowBell) { 2654 _allowBell = false; 2655 QTimer::singleShot(500, this, SLOT(enableBell())); 2656 2657 if (_bellMode == SystemBeepBell) { 2658 QApplication::beep(); 2659 } else if (_bellMode == NotifyBell) { 2660 Q_EMIT notifyBell(message); 2661 } else if (_bellMode == VisualBell) { 2662 swapColorTable(); 2663 QTimer::singleShot(200, this, SLOT(swapColorTable())); 2664 } 2665 } 2666 } 2667 2668 void TerminalDisplay::selectionChanged() 2669 { 2670 Q_EMIT copyAvailable(_screenWindow->selectedText(false).isEmpty() == false); 2671 } 2672 2673 void TerminalDisplay::swapColorTable() 2674 { 2675 ColorEntry color = _colorTable[1]; 2676 _colorTable[1] = _colorTable[0]; 2677 _colorTable[0] = color; 2678 _colorsInverted = !_colorsInverted; 2679 update(); 2680 } 2681 2682 void TerminalDisplay::clearImage() 2683 { 2684 // We initialize _image[_imageSize] too. See makeImage() 2685 for (int i = 0; i <= _imageSize; i++) { 2686 _image[i].character = u' '; 2687 _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR); 2688 _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR); 2689 _image[i].rendition = DEFAULT_RENDITION; 2690 } 2691 } 2692 2693 void TerminalDisplay::calcGeometry() 2694 { 2695 _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height()); 2696 int scrollBarWidth = _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar) ? 0 : _scrollBar->width(); 2697 switch (_scrollbarLocation) { 2698 case QTermWidget::NoScrollBar: 2699 _leftMargin = _leftBaseMargin; 2700 _contentWidth = contentsRect().width() - 2 * _leftBaseMargin; 2701 break; 2702 case QTermWidget::ScrollBarLeft: 2703 _leftMargin = _leftBaseMargin + scrollBarWidth; 2704 _contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth; 2705 _scrollBar->move(contentsRect().topLeft()); 2706 break; 2707 case QTermWidget::ScrollBarRight: 2708 _leftMargin = _leftBaseMargin; 2709 _contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth; 2710 _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width() - 1, 0)); 2711 break; 2712 } 2713 2714 _topMargin = _topBaseMargin; 2715 _contentHeight = contentsRect().height() - 2 * _topBaseMargin + /* mysterious */ 1; 2716 2717 if (!_isFixedSize) { 2718 // ensure that display is always at least one column wide 2719 _columns = qMax(1, qRound(_contentWidth / _fontWidth)); 2720 _usedColumns = qMin(_usedColumns, _columns); 2721 2722 // ensure that display is always at least one line high 2723 _lines = qMax(1, _contentHeight / qRound(_fontHeight)); 2724 _usedLines = qMin(_usedLines, _lines); 2725 } 2726 } 2727 2728 void TerminalDisplay::makeImage() 2729 { 2730 calcGeometry(); 2731 2732 // confirm that array will be of non-zero size, since the painting code 2733 // assumes a non-zero array length 2734 Q_ASSERT(_lines > 0 && _columns > 0); 2735 Q_ASSERT(_usedLines <= _lines && _usedColumns <= _columns); 2736 2737 _imageSize = _lines * _columns; 2738 2739 // We over-commit one character so that we can be more relaxed in dealing with 2740 // certain boundary conditions: _image[_imageSize] is a valid but unused position 2741 _image.resize(_imageSize + 1); 2742 2743 clearImage(); 2744 } 2745 2746 // calculate the needed size, this must be synced with calcGeometry() 2747 void TerminalDisplay::setSize(int columns, int lines) 2748 { 2749 int scrollBarWidth = 2750 (_scrollBar->isHidden() || _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar)) ? 0 : _scrollBar->sizeHint().width(); 2751 int horizontalMargin = 2 * _leftBaseMargin; 2752 int verticalMargin = 2 * _topBaseMargin; 2753 2754 QSize newSize = QSize(horizontalMargin + scrollBarWidth + (columns * _fontWidth), verticalMargin + (lines * qRound(_fontHeight))); 2755 2756 if (newSize != size()) { 2757 _size = newSize; 2758 // updateGeometry(); 2759 // TODO Manage geometry change 2760 } 2761 } 2762 2763 void TerminalDisplay::setFixedSize(int cols, int lins) 2764 { 2765 _isFixedSize = true; 2766 2767 // ensure that display is at least one line by one column in size 2768 _columns = qMax(1, cols); 2769 _lines = qMax(1, lins); 2770 _usedColumns = qMin(_usedColumns, _columns); 2771 _usedLines = qMin(_usedLines, _lines); 2772 2773 if (!_image.empty()) { 2774 _image.clear(); 2775 makeImage(); 2776 } 2777 setSize(cols, lins); 2778 // QWidget::setFixedSize(_size); 2779 } 2780 2781 QSize TerminalDisplay::sizeHint() const 2782 { 2783 return _size; 2784 } 2785 2786 /* --------------------------------------------------------------------- */ 2787 /* */ 2788 /* Drag & Drop */ 2789 /* */ 2790 /* --------------------------------------------------------------------- */ 2791 2792 void TerminalDisplay::dragEnterEvent(QDragEnterEvent *event) 2793 { 2794 if (event->mimeData()->hasFormat(QLatin1String("text/plain"))) 2795 event->acceptProposedAction(); 2796 if (event->mimeData()->urls().size()) 2797 event->acceptProposedAction(); 2798 } 2799 2800 void TerminalDisplay::dropEvent(QDropEvent *event) 2801 { 2802 // KUrl::List urls = KUrl::List::fromMimeData(event->mimeData()); 2803 QList<QUrl> urls = event->mimeData()->urls(); 2804 2805 QString dropText; 2806 if (!urls.isEmpty()) { 2807 // TODO/FIXME: escape or quote pasted things if neccessary... 2808 qDebug() << "TerminalDisplay: handling urls. It can be broken. Report any errors, please"; 2809 for (int i = 0; i < urls.size(); i++) { 2810 // KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 ); 2811 QUrl url = urls[i]; 2812 2813 QString urlText; 2814 2815 if (url.isLocalFile()) 2816 urlText = url.path(); 2817 else 2818 urlText = url.toString(); 2819 2820 // in future it may be useful to be able to insert file names with drag-and-drop 2821 // without quoting them (this only affects paths with spaces in) 2822 // urlText = KShell::quoteArg(urlText); 2823 2824 dropText += urlText; 2825 2826 if (i != urls.size() - 1) 2827 dropText += QLatin1Char(' '); 2828 } 2829 } else { 2830 dropText = event->mimeData()->text(); 2831 } 2832 2833 Q_EMIT sendStringToEmu(dropText.toLocal8Bit().constData()); 2834 } 2835 2836 void TerminalDisplay::doDrag() 2837 { 2838 dragInfo.state = diDragging; 2839 dragInfo.dragObject = new QDrag(this); 2840 QMimeData *mimeData = new QMimeData; 2841 mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection)); 2842 dragInfo.dragObject->setMimeData(mimeData); 2843 dragInfo.dragObject->exec(Qt::CopyAction); 2844 // Don't delete the QTextDrag object. Qt will delete it when it's done with it. 2845 } 2846 2847 void TerminalDisplay::outputSuspended(bool suspended) 2848 { 2849 _outputSuspendedLabel->setVisible(suspended); 2850 } 2851 2852 uint TerminalDisplay::lineSpacing() const 2853 { 2854 return _lineSpacing; 2855 } 2856 2857 void TerminalDisplay::setLineSpacing(uint i) 2858 { 2859 if (i != _lineSpacing) { 2860 _lineSpacing = i; 2861 setVTFont(font()); // Trigger an update. 2862 Q_EMIT lineSpacingChanged(); 2863 } 2864 } 2865 2866 int TerminalDisplay::margin() const 2867 { 2868 return _topBaseMargin; 2869 } 2870 2871 void TerminalDisplay::setMargin(int i) 2872 { 2873 _topBaseMargin = i; 2874 _leftBaseMargin = i; 2875 } 2876 2877 // QMLTermWidget specific functions /////////////////////////////////////////// 2878 2879 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 2880 void TerminalDisplay::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) 2881 #else 2882 void TerminalDisplay::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) 2883 #endif 2884 { 2885 if (newGeometry != oldGeometry) { 2886 resizeEvent(nullptr); 2887 update(); 2888 } 2889 2890 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 2891 QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry); 2892 #else 2893 QQuickPaintedItem::geometryChange(newGeometry, oldGeometry); 2894 #endif 2895 } 2896 2897 void TerminalDisplay::update(const QRegion ®ion) 2898 { 2899 Q_UNUSED(region); 2900 // TODO this function might be optimized 2901 // const rects = region.rects(); 2902 // for (QRect rect : rects) { 2903 // QQuickPaintedItem::update(rect.adjusted(-1, -1, +1, +1)); 2904 // } 2905 QQuickPaintedItem::update(region.boundingRect().adjusted(-1, -1, +1, +1)); 2906 2907 Q_EMIT imagePainted(); 2908 } 2909 2910 void TerminalDisplay::update() 2911 { 2912 QQuickPaintedItem::update(contentsRect()); 2913 } 2914 2915 QRect TerminalDisplay::contentsRect() const 2916 { 2917 return QRect(0, 0, this->width(), this->height()); 2918 } 2919 2920 QSize TerminalDisplay::size() const 2921 { 2922 return QSize(this->width(), this->height()); 2923 } 2924 2925 void TerminalDisplay::setSession(TerminalSession *session) 2926 { 2927 if (m_session != session) { 2928 // qDebug() << "SetSession called"; 2929 // if (m_session) 2930 // m_session->removeView(this); 2931 2932 m_session = session; 2933 2934 connect(this, &TerminalDisplay::copyAvailable, m_session, &TerminalSession::selectionChanged); 2935 connect(this, &TerminalDisplay::termGetFocus, m_session, &TerminalSession::termGetFocus); 2936 connect(this, &TerminalDisplay::termLostFocus, m_session, &TerminalSession::termLostFocus); 2937 connect(this, &TerminalDisplay::keyPressedSignal, m_session, &TerminalSession::termKeyPressed); 2938 2939 m_session->addView(this); 2940 2941 setRandomSeed(m_session->getRandomSeed()); 2942 update(); 2943 Q_EMIT sessionChanged(); 2944 } 2945 } 2946 2947 TerminalSession *TerminalDisplay::getSession() 2948 { 2949 return m_session; 2950 } 2951 2952 QStringList TerminalDisplay::availableColorSchemes() 2953 { 2954 QStringList ret; 2955 const auto colorSchemes = ColorSchemeManager::instance()->allColorSchemes(); 2956 std::transform(colorSchemes.begin(), colorSchemes.end(), std::back_inserter(ret), [](auto cs) { 2957 return cs->name(); 2958 }); 2959 return ret; 2960 } 2961 2962 void TerminalDisplay::setColorScheme(const QString &name) 2963 { 2964 if (name != _colorScheme) { 2965 const ColorScheme *cs = [&, this]() { 2966 // avoid legacy (int) solution 2967 if (!availableColorSchemes().contains(name)) 2968 return ColorSchemeManager::instance()->defaultColorScheme(); 2969 else 2970 return ColorSchemeManager::instance()->findColorScheme(name); 2971 }(); 2972 2973 if (!cs) { 2974 qDebug() << "Cannot load color scheme: " << name; 2975 return; 2976 } 2977 2978 setColorTable(cs->getColorTable()); 2979 2980 setFillColor(cs->backgroundColor()); 2981 _colorScheme = name; 2982 Q_EMIT colorSchemeChanged(); 2983 } 2984 } 2985 2986 QString TerminalDisplay::colorScheme() const 2987 { 2988 return _colorScheme; 2989 } 2990 2991 void TerminalDisplay::simulateKeyPress(int key, int modifiers, bool pressed, quint32 nativeScanCode, const QString &text) 2992 { 2993 Q_UNUSED(nativeScanCode); 2994 QEvent::Type type = pressed ? QEvent::KeyPress : QEvent::KeyRelease; 2995 QKeyEvent event = QKeyEvent(type, key, (Qt::KeyboardModifier)modifiers, text); 2996 keyPressedSignal(&event, false); 2997 } 2998 2999 void TerminalDisplay::simulateKeySequence(const QKeySequence &keySequence) 3000 { 3001 for (int i = 0; i < keySequence.count(); ++i) { 3002 const Qt::Key key = Qt::Key(keySequence[i].key()); 3003 const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i].keyboardModifiers()); 3004 QKeyEvent eventPress = QKeyEvent(QEvent::KeyPress, key, modifiers, QString()); 3005 keyPressedSignal(&eventPress, false); 3006 } 3007 } 3008 3009 void TerminalDisplay::simulateWheel(int x, int y, int buttons, int modifiers, QPoint angleDelta) 3010 { 3011 QPoint pixelDelta; // pixelDelta is optional and can be null. 3012 QWheelEvent event(QPointF(x, y), 3013 mapToGlobal(QPointF(x, y)), 3014 pixelDelta, 3015 angleDelta, 3016 (Qt::MouseButtons)buttons, 3017 (Qt::KeyboardModifiers)modifiers, 3018 Qt::ScrollBegin, 3019 false); 3020 wheelEvent(&event); 3021 } 3022 3023 void TerminalDisplay::simulateMouseMove(int x, int y, int button, int buttons, int modifiers) 3024 { 3025 QMouseEvent event(QEvent::MouseMove, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers); 3026 mouseMoveEvent(&event); 3027 } 3028 3029 void TerminalDisplay::simulateMousePress(int x, int y, int button, int buttons, int modifiers) 3030 { 3031 QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers); 3032 mousePressEvent(&event); 3033 } 3034 3035 void TerminalDisplay::simulateMouseRelease(int x, int y, int button, int buttons, int modifiers) 3036 { 3037 QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers); 3038 mouseReleaseEvent(&event); 3039 } 3040 3041 void TerminalDisplay::simulateMouseDoubleClick(int x, int y, int button, int buttons, int modifiers) 3042 { 3043 QMouseEvent event(QEvent::MouseButtonDblClick, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers); 3044 mouseDoubleClickEvent(&event); 3045 } 3046 3047 QSize TerminalDisplay::getTerminalSize() 3048 { 3049 return QSize(_lines, _columns); 3050 } 3051 3052 bool TerminalDisplay::getUsesMouse() 3053 { 3054 return !usesMouse(); 3055 } 3056 3057 int TerminalDisplay::getScrollbarValue() 3058 { 3059 return _scrollBar->value(); 3060 } 3061 3062 int TerminalDisplay::getScrollbarMaximum() 3063 { 3064 return _scrollBar->maximum(); 3065 } 3066 3067 int TerminalDisplay::getScrollbarMinimum() 3068 { 3069 return _scrollBar->minimum(); 3070 } 3071 3072 QSize TerminalDisplay::getFontMetrics() 3073 { 3074 return QSize(qRound(_fontWidth), qRound(_fontHeight)); 3075 } 3076 3077 void TerminalDisplay::setFullCursorHeight(bool val) 3078 { 3079 if (m_full_cursor_height != val) { 3080 m_full_cursor_height = val; 3081 Q_EMIT fullCursorHeightChanged(); 3082 } 3083 } 3084 3085 bool TerminalDisplay::fullCursorHeight() const 3086 { 3087 return m_full_cursor_height; 3088 } 3089 3090 void TerminalDisplay::itemChange(ItemChange change, const ItemChangeData &value) 3091 { 3092 switch (change) { 3093 case QQuickItem::ItemVisibleHasChanged: 3094 if (value.boolValue && _screenWindow) { 3095 if (this->columns() != _screenWindow->columnCount() || this->lines() != _screenWindow->lineCount()) { 3096 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth); 3097 } 3098 } 3099 break; 3100 default: 3101 break; 3102 } 3103 3104 QQuickPaintedItem::itemChange(change, value); 3105 }