MauiKit Terminal

TerminalDisplay.cpp
1/*
2 * This file is part of Konsole, a terminal emulator for KDE.
3 *
4 * SPDX-FileCopyrightText: 2006-2008 Robert Knight <robertknight@gmail.com>
5 * SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20// Own
21#include "TerminalDisplay.h"
22
23// C++
24#include <cmath>
25
26// Qt
27#include <QAbstractButton>
28#include <QApplication>
29#include <QClipboard>
30#include <QDrag>
31#include <QEvent>
32#include <QFile>
33#include <QKeyEvent>
34#include <QLabel>
35#include <QLayout>
36#include <QMimeData>
37#include <QPainter>
38#include <QPixmap>
39#include <QRegularExpression>
40#include <QScrollBar>
41#include <QStyle>
42#include <QTime>
43#include <QTimer>
44#include <QUrl>
45#include <QtDebug>
46
47// Konsole
48#include "Filter.h"
49#include "ScreenWindow.h"
50#include "TerminalCharacterDecoder.h"
51#include "konsole_wcwidth.h"
52
53// std
54#include <ranges>
55
56inline void initResource()
57{
58 // Q_INIT_RESOURCE(terminal);
59}
60
61using namespace Konsole;
62
63constexpr auto REPCHAR =
64"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
65"abcdefgjijklmnopqrstuvwxyz"
66"0123456789./+@";
67
68// scroll increment used when dragging selection at top/bottom of window.
69
70// static
71bool TerminalDisplay::_antialiasText = true;
72
73// we use this to force QPainter to display text in LTR mode
74// more information can be found in: http://unicode.org/reports/tr9/
75const QChar LTR_OVERRIDE_CHAR(0x202D);
76
77/* ------------------------------------------------------------------------- */
78/* */
79/* Colors */
80/* */
81/* ------------------------------------------------------------------------- */
82
83/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
84 *
85 * Code 0 1 2 3 4 5 6 7
86 * ----------- ------- ------- ------- ------- ------- ------- ------- -------
87 * ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
88 * IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
89 */
90
92{
93 return _screenWindow;
94}
96{
97 // disconnect existing screen window if any
98 if (_screenWindow) {
99 disconnect(_screenWindow, nullptr, this, nullptr);
100 }
101
102 _screenWindow = window;
103
104 if (window) {
107 connect(_screenWindow, &ScreenWindow::scrollToEnd, this, &TerminalDisplay::scrollToEnd);
108 connect(_screenWindow, &ScreenWindow::selectionChanged, this, &TerminalDisplay::isTextSelectedChanged);
109 window->setWindowLines(_lines);
110 }
111}
112
113std::span<const ColorEntry> TerminalDisplay::colorTable() const
114{
115 return _colorTable;
116}
117
119{
120 _colorTable[DEFAULT_BACK_COLOR].color = color;
121 // _colorTable[DEFAULT_BACK_COLOR].color.setAlphaF(m_backgroundOpacity);
122 // if(m_scheme)
123 // {
124 // &m_scheme.setColor(DEFAULT_BACK_COLOR, color);
125 // }
126 //
127
128
129 qDebug() << "Setting Terminal bg and opacity" << _opacity;
130 QPalette p = palette();
131 p.setColor(backgroundRole(), _colorTable[DEFAULT_BACK_COLOR].color);
132 setPalette(p);
133
134 // Avoid propagating the palette change to the scroll bar
135 _scrollBar->setPalette(QApplication::palette());
136 update();
137 Q_EMIT backgroundColorChanged();
138}
139
140QColor TerminalDisplay::backgroundColor() const
141{
142 return _colorTable[DEFAULT_BACK_COLOR].color;
143}
144
146{
147 _colorTable[DEFAULT_FORE_COLOR].color = color;
148 update();
149 Q_EMIT foregroundColorChanged();
150}
151
152QColor TerminalDisplay::foregroundColor() const
153{
154 return _colorTable[DEFAULT_FORE_COLOR].color;
155}
156
157void TerminalDisplay::setColorTable(std::array<ColorEntry, TABLE_COLORS> &&table)
158{
159 _colorTable = std::move(table);
160 // setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
161}
162
163/* ------------------------------------------------------------------------- */
164/* */
165/* Font */
166/* */
167/* ------------------------------------------------------------------------- */
168
169/*
170 * The VT100 has 32 special graphical characters. The usual vt100 extended
171 * xterm fonts have these at 0x00..0x1f.
172 *
173 * QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
174 * come in here as proper unicode characters.
175 *
176 * We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
177 * from unicode to 0x00..0x1f. The remaining translation is then left to the
178 * QCodec.
179 */
180
181constexpr bool TerminalDisplay::isLineChar(QChar c) const
182{
183 return _drawLineChars && ((c.unicode() & 0xFF80) == 0x2500);
184}
185
186constexpr bool TerminalDisplay::isLineCharString(QStringView string) const
187{
188 return (string.size() > 0) && (isLineChar(string[0]));
189}
190
191void TerminalDisplay::fontChange(const QFont &)
192{
193 QFontMetricsF fm(font());
194 _fontHeight = fm.height() + _lineSpacing;
195
196 // waba TerminalDisplay 1.123:
197 // "Base character width on widest ASCII character. This prevents too wide
198 // characters in the presence of double wide (e.g. Japanese) characters."
199 // Get the width from representative normal width characters
200 _fontWidth = fm.horizontalAdvance(QLatin1String(REPCHAR)) / (qreal)qstrlen(REPCHAR);
201
202 _fixedFont = true;
203
204 int fw = fm.horizontalAdvance(QLatin1Char(REPCHAR[0]));
205 for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
206 if (fw != fm.horizontalAdvance(QLatin1Char(REPCHAR[i]))) {
207 _fixedFont = false;
208 break;
209 }
210 }
211
212 if (_fontWidth < 1)
213 _fontWidth = 1;
214
215 _fontAscent = fm.ascent();
216
217 Q_EMIT changedFontMetricSignal(_fontHeight, _fontWidth);
218 propagateSize();
219 update();
220}
221
222// void TerminalDisplay::calDrawTextAdditionHeight(QPainter& painter)
223//{
224// QRect test_rect, feedback_rect;
225// test_rect.setRect(1, 1, qRound(_fontWidth) * 4, _fontHeight);
226// painter.drawText(test_rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + QLatin1String("Mq"), &feedback_rect);
227
228// //qDebug() << "test_rect:" << test_rect << "feeback_rect:" << feedback_rect;
229
230// _drawTextAdditionHeight = (feedback_rect.height() - _fontHeight) / 2;
231// if(_drawTextAdditionHeight < 0) {
232// _drawTextAdditionHeight = 0;
233// }
234
235// _drawTextTestFlag = false;
236//}
237
239{
240 QFont font = f;
241
242 if (!QFontInfo(font).fixedPitch()) {
243 qDebug() << "Using a variable-width font in the terminal. This may cause performance degradation and display/alignment errors.";
244 }
245
246 // hint that text should be drawn without anti-aliasing.
247 // depending on the user's font configuration, this may not be respected
248 if (!_antialiasText)
250
251 // experimental optimization. Konsole assumes that the terminal is using a
252 // mono-spaced font, in which case kerning information should have an effect.
253 // Disabling kerning saves some computation when rendering text.
254 font.setKerning(false);
255
256 m_font = font;
257 fontChange(font);
258 Q_EMIT vtFontChanged();
259}
260
262{
263 if (_boldIntense != value) {
264 _boldIntense = value;
265 Q_EMIT boldIntenseChanged();
266 }
267}
268
269/* ------------------------------------------------------------------------- */
270/* */
271/* Constructor / Destructor */
272/* */
273/* ------------------------------------------------------------------------- */
274#include <QDir>
275
278, _screenWindow(nullptr)
279, _allowBell(true)
280, _gridLayout(nullptr)
281, _fontHeight(1)
282, _fontWidth(1)
283, _fontAscent(1)
284, _boldIntense(true)
285, _lines(1)
286, _columns(1)
287, _usedLines(1)
288, _usedColumns(1)
289, _contentHeight(1)
290, _contentWidth(1)
291, _randomSeed(0)
292, _resizing(false)
293, _terminalSizeHint(false)
294, _terminalSizeStartup(true)
295, _bidiEnabled(false)
296, _mouseMarks(false)
297, _disabledBracketedPasteMode(false)
298, _actSel(0)
299, _wordSelectionMode(false)
300, _lineSelectionMode(false)
301, _preserveLineBreaks(false)
302, _columnSelectionMode(false)
303, _scrollbarLocation(QTermWidget::NoScrollBar)
304, _wordCharacters(QLatin1String(":@-./_~"))
305, _bellMode(SystemBeepBell)
306, _blinking(false)
307, _hasBlinker(false)
308, _cursorBlinking(false)
309, _hasBlinkingCursor(false)
310, _allowBlinkingText(true)
311, _ctrlDrag(false)
312, _tripleClickMode(SelectWholeLine)
313, _isFixedSize(false)
314, _possibleTripleClick(false)
315, _resizeWidget(nullptr)
316, _resizeTimer(nullptr)
317, _flowControlWarningEnabled(false)
318, _outputSuspendedLabel(nullptr)
319, _lineSpacing(0)
320, _colorsInverted(false)
321, _opacity(static_cast<qreal>(1))
322, _filterChain(std::make_unique<TerminalImageFilterChain>())
323, _cursorShape(Emulation::KeyboardCursorShape::BlockCursor)
324, mMotionAfterPasting(NoMoveScreenWindow)
325, _leftBaseMargin(1)
326, _topBaseMargin(1)
327, m_font(QStringLiteral("Monospace"), 12)
328, m_color_role(QPalette::Window)
329, m_full_cursor_height(false)
330, _drawLineChars(true)
331, m_customColorScheme(new CustomColorScheme(this))
332, m_scheme(nullptr)
333{
334 initResource();
335
336 // terminal applications are not designed with Right-To-Left in mind,
337 // so the layout is forced to Left-To-Right
338 // setLayoutDirection(Qt::LeftToRight);
339
340 // The offsets are not yet calculated.
341 // Do not calculate these too often to be more smoothly when resizing
342 // konsole in opaque mode.
343 _topMargin = _topBaseMargin;
344 _leftMargin = _leftBaseMargin;
345
346 m_palette = qApp->palette();
347
348 setVTFont(m_font);
349
350 // create scroll bar for scrolling output up and down
351 // set the scroll bar's slider to occupy the whole area of the scroll bar initially
352
353 _scrollBar = new QScrollBar();
354 setScroll(0, 0);
355
356 _scrollBar->setCursor(Qt::ArrowCursor);
357 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
358 // qtermwidget: we have to hide it here due the _scrollbarLocation==NoScrollBar
359 // check in TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
360 _scrollBar->hide();
361
362 // setup timers for blinking cursor and text
363 _blinkTimer = new QTimer(this);
364 connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
365 _blinkCursorTimer = new QTimer(this);
366 connect(_blinkCursorTimer, &QTimer::timeout, this, &TerminalDisplay::blinkCursorEvent);
367
368 // KCursor::setAutoHideCursor( this, true );
369
370 setUsesMouse(true);
371 setBracketedPasteMode(false);
372 setColorTable(defaultColorTable());
373 // setMouseTracking(true);
374
376
378
379 // Setup scrollbar. Be sure it is not darw on screen.
380 _scrollBar->setAttribute(Qt::WA_DontShowOnScreen);
381 _scrollBar->setVisible(false);
382 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollbarParamsChanged);
383
384 // TODO Forcing rendering to Framebuffer. We need to determine if this is ok
385 // always or if we need to make this customizable.
387
388 // setFocusPolicy( Qt::WheelFocus );
389
390 // enable input method support
391 // setAttribute(Qt::WA_InputMethodEnabled, true);
392
393 // this is an important optimization, it tells Qt
394 // that TerminalDisplay will handle repainting its entire area.
395 // setAttribute(Qt::WA_OpaquePaintEvent);
396
397 // _gridLayout = new QGridLayout(this);
398 // _gridLayout->setContentsMargins(0, 0, 0, 0);
399
400 // new AutoScrollHandler(this);
401}
402
403TerminalDisplay::~TerminalDisplay()
404{
405 disconnect(_blinkTimer);
406 disconnect(_blinkCursorTimer);
407 qApp->removeEventFilter(this);
408
409 delete _gridLayout;
410 delete _outputSuspendedLabel;
411 delete _scrollBar;
412}
413
414/* ------------------------------------------------------------------------- */
415/* */
416/* Display Operations */
417/* */
418/* ------------------------------------------------------------------------- */
419
420/**
421 * A table for emulating the simple (single width) unicode drawing chars.
422 * It represents the 250x - 257x glyphs. If it's zero, we can't use it.
423 * if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
424 * 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
425 *
426 * Then, the pixels basically have the following interpretation:
427 * _|||_
428 * -...-
429 * -...-
430 * -...-
431 * _|||_
432 *
433 * where _ = none
434 * | = vertical line.
435 * - = horizontal line.
436 */
437
438enum LineEncode {
439 TopL = (1 << 1),
440 TopC = (1 << 2),
441 TopR = (1 << 3),
442
443 LeftT = (1 << 5),
444 Int11 = (1 << 6),
445 Int12 = (1 << 7),
446 Int13 = (1 << 8),
447 RightT = (1 << 9),
448
449 LeftC = (1 << 10),
450 Int21 = (1 << 11),
451 Int22 = (1 << 12),
452 Int23 = (1 << 13),
453 RightC = (1 << 14),
454
455 LeftB = (1 << 15),
456 Int31 = (1 << 16),
457 Int32 = (1 << 17),
458 Int33 = (1 << 18),
459 RightB = (1 << 19),
460
461 BotL = (1 << 21),
462 BotC = (1 << 22),
463 BotR = (1 << 23)
464};
465
466#include "LineFont.h"
467
468static void drawLineChar(QPainter &paint, int x, int y, int w, int h, uint8_t code)
469{
470 // Calculate cell midpoints, end points.
471 int cx = x + w / 2;
472 int cy = y + h / 2;
473 int ex = x + w - 1;
474 int ey = y + h - 1;
475
476 quint32 toDraw = LineChars[code];
477
478 // Top _lines:
479 if (toDraw & TopL)
480 paint.drawLine(cx - 1, y, cx - 1, cy - 2);
481 if (toDraw & TopC)
482 paint.drawLine(cx, y, cx, cy - 2);
483 if (toDraw & TopR)
484 paint.drawLine(cx + 1, y, cx + 1, cy - 2);
485
486 // Bot _lines:
487 if (toDraw & BotL)
488 paint.drawLine(cx - 1, cy + 2, cx - 1, ey);
489 if (toDraw & BotC)
490 paint.drawLine(cx, cy + 2, cx, ey);
491 if (toDraw & BotR)
492 paint.drawLine(cx + 1, cy + 2, cx + 1, ey);
493
494 // Left _lines:
495 if (toDraw & LeftT)
496 paint.drawLine(x, cy - 1, cx - 2, cy - 1);
497 if (toDraw & LeftC)
498 paint.drawLine(x, cy, cx - 2, cy);
499 if (toDraw & LeftB)
500 paint.drawLine(x, cy + 1, cx - 2, cy + 1);
501
502 // Right _lines:
503 if (toDraw & RightT)
504 paint.drawLine(cx + 2, cy - 1, ex, cy - 1);
505 if (toDraw & RightC)
506 paint.drawLine(cx + 2, cy, ex, cy);
507 if (toDraw & RightB)
508 paint.drawLine(cx + 2, cy + 1, ex, cy + 1);
509
510 // Intersection points.
511 if (toDraw & Int11)
512 paint.drawPoint(cx - 1, cy - 1);
513 if (toDraw & Int12)
514 paint.drawPoint(cx, cy - 1);
515 if (toDraw & Int13)
516 paint.drawPoint(cx + 1, cy - 1);
517
518 if (toDraw & Int21)
519 paint.drawPoint(cx - 1, cy);
520 if (toDraw & Int22)
521 paint.drawPoint(cx, cy);
522 if (toDraw & Int23)
523 paint.drawPoint(cx + 1, cy);
524
525 if (toDraw & Int31)
526 paint.drawPoint(cx - 1, cy + 1);
527 if (toDraw & Int32)
528 paint.drawPoint(cx, cy + 1);
529 if (toDraw & Int33)
530 paint.drawPoint(cx + 1, cy + 1);
531}
532
533static void drawOtherChar(QPainter &paint, int x, int y, int w, int h, uchar code)
534{
535 // Calculate cell midpoints, end points.
536 const int cx = x + w / 2;
537 const int cy = y + h / 2;
538 const int ex = x + w - 1;
539 const int ey = y + h - 1;
540
541 // Double dashes
542 if (0x4C <= code && code <= 0x4F) {
543 const int xHalfGap = qMax(w / 15, 1);
544 const int yHalfGap = qMax(h / 15, 1);
545 switch (code) {
546 case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
547 paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1);
548 paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1);
549 paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1);
550 paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1);
551 /* Falls through. */
552 case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
553 paint.drawLine(x, cy, cx - xHalfGap - 1, cy);
554 paint.drawLine(cx + xHalfGap, cy, ex, cy);
555 break;
556 case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
557 paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1);
558 paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1);
559 paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey);
560 paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey);
561 /* Falls through. */
562 case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
563 paint.drawLine(cx, y, cx, cy - yHalfGap - 1);
564 paint.drawLine(cx, cy + yHalfGap, cx, ey);
565 break;
566 }
567 }
568
569 // Rounded corner characters
570 else if (0x6D <= code && code <= 0x70) {
571 const int r = w * 3 / 8;
572 const int d = 2 * r;
573 switch (code) {
574 case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
575 paint.drawLine(cx, cy + r, cx, ey);
576 paint.drawLine(cx + r, cy, ex, cy);
577 paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16);
578 break;
579 case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
580 paint.drawLine(cx, cy + r, cx, ey);
581 paint.drawLine(x, cy, cx - r, cy);
582 paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16);
583 break;
584 case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT
585 paint.drawLine(cx, y, cx, cy - r);
586 paint.drawLine(x, cy, cx - r, cy);
587 paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16);
588 break;
589 case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT
590 paint.drawLine(cx, y, cx, cy - r);
591 paint.drawLine(cx + r, cy, ex, cy);
592 paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16);
593 break;
594 }
595 }
596
597 // Diagonals
598 else if (0x71 <= code && code <= 0x73) {
599 switch (code) {
600 case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
601 paint.drawLine(ex, y, x, ey);
602 break;
603 case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
604 paint.drawLine(x, y, ex, ey);
605 break;
606 case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS
607 paint.drawLine(ex, y, x, ey);
608 paint.drawLine(x, y, ex, ey);
609 break;
610 }
611 }
612}
613
614void TerminalDisplay::drawLineCharString(QPainter &painter, int x, int y, QStringView str, const Character *attributes) const
615{
616 const QPen &currentPen = painter.pen();
617
618 if ((attributes->rendition & RE_BOLD) && _boldIntense) {
619 QPen boldPen(currentPen);
620 boldPen.setWidth(3);
621 painter.setPen(boldPen);
622 }
623
624 for (qsizetype i = 0; i < str.size(); i++) {
625 uint8_t code = static_cast<uint8_t>(str[i].unicode() & 0xffU);
626 if (LineChars[code])
627 drawLineChar(painter, qRound(x + (_fontWidth * i)), y, qRound(_fontWidth), qRound(_fontHeight), code);
628 else
629 drawOtherChar(painter, qRound(x + (_fontWidth * i)), y, qRound(_fontWidth), qRound(_fontHeight), code);
630 }
631
632 painter.setPen(currentPen);
633}
634
636{
637 if (_cursorShape == shape) {
638 return;
639 }
640
641 _cursorShape = shape;
642 updateCursor();
643}
644
649
650void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor &color)
651{
652 if (useForegroundColor)
653 _cursorColor = QColor(); // an invalid color means that
654 // the foreground color of the
655 // current character should
656 // be used
657
658 else
659 _cursorColor = color;
660}
662{
663 return _cursorColor;
664}
665
666void TerminalDisplay::setBackgroundOpacity(qreal backgroundOpacity)
667{
668 if (_opacity != backgroundOpacity)
669 {
670 _opacity = backgroundOpacity;
671
672 QColor nColor = backgroundColor();
673 nColor.setAlphaF(_opacity);
674 setFillColor(nColor);
675
676 Q_EMIT backgroundOpacityChanged();
677 }
678}
679
680void TerminalDisplay::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting)
681{
682 // The whole widget rectangle is filled by the background color from
683 // the color scheme set in setColorTable(), while the scrollbar is
684 // left to the widget style for a consistent look.
685 if (useOpacitySetting) {
686 QColor color(backgroundColor);
687 color.setAlphaF(_opacity);
688
689 painter.save();
691 painter.fillRect(rect, color);
692 painter.restore();
693 } else
694 painter.fillRect(rect, backgroundColor);
695}
696
697void TerminalDisplay::drawCursor(QPainter &painter,
698 const QRect &rect,
699 const QColor &foregroundColor,
700 const QColor & /*backgroundColor*/,
701 bool &invertCharacterColor)
702{
703 QRect cursorRect = rect;
704
705 cursorRect.setHeight(qRound(_fontHeight) - ((m_full_cursor_height) ? 0 : _lineSpacing - 1));
706
707 if (!_cursorBlinking) {
708 if (_cursorColor.isValid())
709 painter.setPen(_cursorColor);
710 else
711 painter.setPen(foregroundColor);
712
714 // draw the cursor outline, adjusting the area so that
715 // it is draw entirely inside 'rect'
716 float penWidth = qMax(1, painter.pen().width());
717
718 //
719 painter.drawRect(cursorRect.adjusted(+penWidth / 2 + fmod(penWidth, 2),
720 +penWidth / 2 + fmod(penWidth, 2),
721 -penWidth / 2 - fmod(penWidth, 2),
722 -penWidth / 2 - fmod(penWidth, 2)));
723
724 // if ( hasFocus() )
725 if (hasActiveFocus()) {
726 painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
727
728 if (!_cursorColor.isValid()) {
729 // invert the colour used to draw the text to ensure that the character at
730 // the cursor position is readable
731 invertCharacterColor = true;
732 }
733 }
734 } else if (_cursorShape == Emulation::KeyboardCursorShape::UnderlineCursor)
735 painter.drawLine(cursorRect.left(), cursorRect.bottom(), cursorRect.right(), cursorRect.bottom());
736 else if (_cursorShape == Emulation::KeyboardCursorShape::IBeamCursor)
737 painter.drawLine(cursorRect.left(), cursorRect.top(), cursorRect.left(), cursorRect.bottom());
738 }
739}
740
741void TerminalDisplay::drawCharacters(QPainter &painter, const QRect &rect, const QString &text, const Character *style, bool invertCharacterColor)
742{
743 // don't draw text which is currently blinking
744 if (_blinking && (style->rendition & RE_BLINK))
745 return;
746
747 // don't draw concealed characters
748 if (style->rendition & RE_CONCEAL)
749 return;
750
751 // setup bold and underline
752 bool useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
753 const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
754 const bool useItalic = style->rendition & RE_ITALIC || font().italic();
755 const bool useStrikeOut = style->rendition & RE_STRIKEOUT || font().strikeOut();
756 const bool useOverline = style->rendition & RE_OVERLINE || font().overline();
757
758 painter.setFont(font());
759
760 QFont font = painter.font();
761 if (font.bold() != useBold || font.underline() != useUnderline || font.italic() != useItalic || font.strikeOut() != useStrikeOut
762 || font.overline() != useOverline) {
763 font.setBold(useBold);
764 font.setUnderline(useUnderline);
765 font.setItalic(useItalic);
766 font.setStrikeOut(useStrikeOut);
767 font.setOverline(useOverline);
768 painter.setFont(font);
769 }
770
771 // setup pen
772 const CharacterColor &textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor);
773 const QColor color = textColor.color(_colorTable);
774 QPen pen = painter.pen();
775 if (pen.color() != color) {
776 pen.setColor(color);
777 painter.setPen(color);
778 }
779
780 // draw text
781 if (isLineCharString(text))
782 drawLineCharString(painter, rect.x(), rect.y(), text, style);
783 else {
784 // Force using LTR as the document layout for the terminal area, because
785 // there is no use cases for RTL emulator and RTL terminal application.
786 //
787 // This still allows RTL characters to be rendered in the RTL way.
789
790 if (_bidiEnabled) {
791 painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text);
792 } else {
793 painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text);
794 }
795 }
796}
797
798void TerminalDisplay::drawTextFragment(QPainter &painter, const QRect &rect, const QString &text, const Character *style)
799{
800 painter.save();
801
802 // setup painter
803 const QColor foregroundColor = style->foregroundColor.color(_colorTable);
804 const QColor backgroundColor = style->backgroundColor.color(_colorTable);
805
806 // draw background if different from the display's background color
807 if (backgroundColor != this->backgroundColor())
808 drawBackground(painter, rect, backgroundColor, false /* do not use transparency */);
809
810 // draw cursor shape if the current character is the cursor
811 // this may alter the foreground and background colors
812 bool invertCharacterColor = false;
813 if (style->rendition & RE_CURSOR)
814 drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor);
815
816 // draw text
817 drawCharacters(painter, rect, text, style, invertCharacterColor);
818
819 painter.restore();
820}
821
823{
824 _randomSeed = randomSeed;
825}
827{
828 return _randomSeed;
829}
830
831// scrolls the image by 'lines', down if lines > 0 or up otherwise.
832//
833// the terminal emulation keeps track of the scrolling of the character
834// image as it receives input, and when the view is updated, it calls scrollImage()
835// with the final scroll amount. this improves performance because scrolling the
836// display is much cheaper than re-rendering all the text for the
837// part of the image which has moved up or down.
838// Instead only new lines have to be drawn
839void TerminalDisplay::scrollImage(int lines, const QRect &screenWindowRegion)
840{
841 // if the flow control warning is enabled this will interfere with the
842 // scrolling optimizations and cause artifacts. the simple solution here
843 // is to just disable the optimization whilst it is visible
844 if (_outputSuspendedLabel && _outputSuspendedLabel->isVisible())
845 return;
846
847 // constrain the region to the display
848 // the bottom of the region is capped to the number of lines in the display's
849 // internal image - 2, so that the height of 'region' is strictly less
850 // than the height of the internal image.
851 QRect region = screenWindowRegion;
852 region.setBottom(qMin(region.bottom(), this->_lines - 2));
853
854 // return if there is nothing to do
855 if (lines == 0 || _image.empty() || !region.isValid() || (region.top() + abs(lines)) >= region.bottom() || this->_lines <= region.height())
856 return;
857
858 // hide terminal size label to prevent it being scrolled
859 if (_resizeWidget && _resizeWidget->isVisible())
860 _resizeWidget->hide();
861
862 // Note: With Qt 4.4 the left edge of the scrolled area must be at 0
863 // to get the correct (newly exposed) part of the widget repainted.
864 //
865 // The right edge must be before the left edge of the scroll bar to
866 // avoid triggering a repaint of the entire widget, the distance is
867 // given by SCROLLBAR_CONTENT_GAP
868 //
869 // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
870 // application to monitor repainting.
871 //
872 int scrollBarWidth = _scrollBar->isHidden() ? 0
873 : _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar) ? 0
874 : _scrollBar->width();
875 const int SCROLLBAR_CONTENT_GAP = scrollBarWidth == 0 ? 0 : 1;
876 QRect scrollRect;
877 if (_scrollbarLocation == QTermWidget::ScrollBarLeft) {
878 scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP);
879 scrollRect.setRight(width());
880 } else {
881 scrollRect.setLeft(0);
882 scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
883 }
884 void *firstCharPos = &_image[region.top() * this->_columns];
885 void *lastCharPos = &_image[(region.top() + abs(lines)) * this->_columns];
886
887 int top = _topMargin + (region.top() * qRound(_fontHeight));
888 int linesToMove = region.height() - abs(lines);
889 int bytesToMove = linesToMove * this->_columns * sizeof(Character);
890
891 Q_ASSERT(linesToMove > 0);
892 Q_ASSERT(bytesToMove > 0);
893
894 // scroll internal image
895 if (lines > 0) {
896 // check that the memory areas that we are going to move are valid
897 Q_ASSERT((char *)lastCharPos + bytesToMove < (char *)(_image.data() + (this->_lines * this->_columns)));
898
899 Q_ASSERT((lines * this->_columns) < _imageSize);
900
901 // scroll internal image down
902 memmove(firstCharPos, lastCharPos, bytesToMove);
903
904 // set region of display to scroll
905 scrollRect.setTop(top);
906 } else {
907 // check that the memory areas that we are going to move are valid
908 Q_ASSERT((char *)firstCharPos + bytesToMove < (char *)(_image.data() + (this->_lines * this->_columns)));
909
910 // scroll internal image up
911 memmove(lastCharPos, firstCharPos, bytesToMove);
912
913 // set region of the display to scroll
914 scrollRect.setTop(top + abs(lines) * qRound(_fontHeight));
915 }
916 scrollRect.setHeight(linesToMove * qRound(_fontHeight));
917
918 Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
919
920 // scroll the display vertically to match internal _image
921 // scroll( 0 , qRound(_fontHeight) * (-lines) , scrollRect );
922}
923
924QRegion TerminalDisplay::hotSpotRegion() const
925{
926 QRegion region;
927 const auto hotSpots = _filterChain->hotSpots();
928 for (Filter::HotSpot *const hotSpot : hotSpots) {
929 QRect r;
930 if (hotSpot->startLine() == hotSpot->endLine()) {
931 r.setLeft(hotSpot->startColumn());
932 r.setTop(hotSpot->startLine());
933 r.setRight(hotSpot->endColumn());
934 r.setBottom(hotSpot->endLine());
935 region |= imageToWidget(r);
936 ;
937 } else {
938 r.setLeft(hotSpot->startColumn());
939 r.setTop(hotSpot->startLine());
940 r.setRight(_columns);
941 r.setBottom(hotSpot->startLine());
942 region |= imageToWidget(r);
943 ;
944 for (int line = hotSpot->startLine() + 1; line < hotSpot->endLine(); line++) {
945 r.setLeft(0);
946 r.setTop(line);
947 r.setRight(_columns);
948 r.setBottom(line);
949 region |= imageToWidget(r);
950 ;
951 }
952 r.setLeft(0);
953 r.setTop(hotSpot->endLine());
954 r.setRight(hotSpot->endColumn());
955 r.setBottom(hotSpot->endLine());
956 region |= imageToWidget(r);
957 ;
958 }
959 }
960 return region;
961}
962
964{
965 if (!_screenWindow)
966 return;
967
968 QRegion preUpdateHotSpots = hotSpotRegion();
969
970 // use _screenWindow->getImage() here rather than _image because
971 // other classes may call processFilters() when this display's
972 // ScreenWindow emits a scrolled() signal - which will happen before
973 // updateImage() is called on the display and therefore _image is
974 // out of date at this point
975 _filterChain->setImage(_screenWindow->getImage(), _screenWindow->windowLines(), _screenWindow->windowColumns(), _screenWindow->getLineProperties());
976 _filterChain->process();
977
978 QRegion postUpdateHotSpots = hotSpotRegion();
979
980 update(preUpdateHotSpots | postUpdateHotSpots);
981}
982
984{
985 if (!_screenWindow)
986 return;
987
988 // TODO QMLTermWidget at the moment I'm disabling this.
989 // Since this can't be scrolled we need to determine if this
990 // is useful or not.
991
992 // optimization - scroll the existing image where possible and
993 // avoid expensive text drawing for parts of the image that
994 // can simply be moved up or down
995 // scrollImage( _screenWindow->scrollCount() ,
996 // _screenWindow->scrollRegion() );
997 // _screenWindow->resetScrollCount();
998
999 if (_image.empty()) {
1000 // Create _image.
1001 // The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
1002 updateImageSize();
1003 }
1004
1005 auto newimg = _screenWindow->getImage();
1006 int lines = _screenWindow->windowLines();
1007 int columns = _screenWindow->windowColumns();
1008
1009 setScroll(_screenWindow->currentLine(), _screenWindow->lineCount());
1010
1011 Q_ASSERT(this->_usedLines <= this->_lines);
1012 Q_ASSERT(this->_usedColumns <= this->_columns);
1013
1014 int y, x, len;
1015
1016 QPoint tL = contentsRect().topLeft();
1017 int tLx = tL.x();
1018 int tLy = tL.y();
1019 _hasBlinker = false;
1020
1021 CharacterColor cf; // undefined
1022 CharacterColor _clipboard; // undefined
1023 int cr = -1; // undefined
1024
1025 const int linesToUpdate = qMin(this->_lines, qMax(0, lines));
1026 const int columnsToUpdate = qMin(this->_columns, qMax(0, columns));
1027
1028 std::vector<QChar> disstrU(columnsToUpdate);
1029 std::vector<char> dirtyMask(columnsToUpdate + 2);
1030 QRegion dirtyRegion;
1031
1032 // debugging variable, this records the number of lines that are found to
1033 // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
1034 // which therefore need to be repainted
1035 int dirtyLineCount = 0;
1036
1037 for (y = 0; y < linesToUpdate; ++y) {
1038 const Character *currentLine = &_image[y * _columns];
1039 const Character *const newLine = &newimg[y * columns];
1040
1041 bool updateLine = false;
1042
1043 // The dirty mask indicates which characters need repainting. We also
1044 // mark surrounding neighbours dirty, in case the character exceeds
1045 // its cell boundaries
1046 memset(dirtyMask.data(), 0, columnsToUpdate + 2);
1047
1048 for (x = 0; x < columnsToUpdate; ++x) {
1049 if (newLine[x] != currentLine[x]) {
1050 dirtyMask[x] = true;
1051 }
1052 }
1053
1054 if (!_resizing) // not while _resizing, we're expecting a paintEvent
1055 for (x = 0; x < columnsToUpdate; ++x) {
1056 _hasBlinker |= (newLine[x].rendition & RE_BLINK);
1057
1058 // Start drawing if this character or the next one differs.
1059 // We also take the next one into account to handle the situation
1060 // where characters exceed their cell width.
1061 if (dirtyMask[x]) {
1062 QChar c = newLine[x + 0].character;
1063 if (!c.unicode())
1064 continue;
1065 int p = 0;
1066 disstrU[p++] = c; // fontMap(c);
1067 bool lineDraw = isLineChar(c);
1068 bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character.unicode() == 0);
1069 cr = newLine[x].rendition;
1070 _clipboard = newLine[x].backgroundColor;
1071 if (newLine[x].foregroundColor != cf)
1072 cf = newLine[x].foregroundColor;
1073 int lln = columnsToUpdate - x;
1074 for (len = 1; len < lln; ++len) {
1075 const Character &ch = newLine[x + len];
1076
1077 if (!ch.character.unicode())
1078 continue; // Skip trailing part of multi-col chars.
1079
1080 bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character.unicode() == 0);
1081
1082 if (ch.foregroundColor != cf || ch.backgroundColor != _clipboard || ch.rendition != cr || !dirtyMask[x + len]
1083 || isLineChar(c) != lineDraw || nextIsDoubleWidth != doubleWidth)
1084 break;
1085
1086 disstrU[p++] = c; // fontMap(c);
1087 }
1088
1089 bool saveFixedFont = _fixedFont;
1090 if (lineDraw)
1091 _fixedFont = false;
1092 if (doubleWidth)
1093 _fixedFont = false;
1094
1095 updateLine = true;
1096
1097 _fixedFont = saveFixedFont;
1098 x += len - 1;
1099 }
1100 }
1101
1102 // both the top and bottom halves of double height _lines must always be redrawn
1103 // although both top and bottom halves contain the same characters, only
1104 // the top one is actually
1105 // drawn.
1106 if (_lineProperties.size() > y)
1107 updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
1108
1109 // if the characters on the line are different in the old and the new _image
1110 // then this line must be repainted.
1111 if (updateLine) {
1112 dirtyLineCount++;
1113
1114 // add the area occupied by this line to the region which needs to be
1115 // repainted
1116 QRect dirtyRect = QRect(_leftMargin + tLx, _topMargin + tLy + qRound(_fontHeight) * y, _fontWidth * columnsToUpdate, qRound(_fontHeight));
1117
1118 dirtyRegion |= dirtyRect;
1119 }
1120
1121 // replace the line of characters in the old _image with the
1122 // current line of the new _image
1123 memcpy((void *)currentLine, (const void *)newLine, columnsToUpdate * sizeof(Character));
1124 }
1125
1126 // if the new _image is smaller than the previous _image, then ensure that the area
1127 // outside the new _image is cleared
1128 if (linesToUpdate < _usedLines) {
1129 dirtyRegion |= QRect(_leftMargin + tLx,
1130 _topMargin + tLy + qRound(_fontHeight) * linesToUpdate,
1131 _fontWidth * this->_columns,
1132 qRound(_fontHeight) * (_usedLines - linesToUpdate));
1133 }
1134 _usedLines = linesToUpdate;
1135
1136 if (columnsToUpdate < _usedColumns) {
1137 dirtyRegion |= QRect(_leftMargin + tLx + columnsToUpdate * _fontWidth,
1138 _topMargin + tLy,
1139 _fontWidth * (_usedColumns - columnsToUpdate),
1140 qRound(_fontHeight) * this->_lines);
1141 }
1142 _usedColumns = columnsToUpdate;
1143
1144 dirtyRegion |= _inputMethodData.previousPreeditRect;
1145
1146 // update the parts of the display which have changed
1147 update(dirtyRegion);
1148
1149 if (_hasBlinker && !_blinkTimer->isActive())
1150 _blinkTimer->start(TEXT_BLINK_DELAY);
1151 if (!_hasBlinker && _blinkTimer->isActive()) {
1152 _blinkTimer->stop();
1153 _blinking = false;
1154 }
1155}
1156
1157void TerminalDisplay::showResizeNotification()
1158{
1159}
1160
1162{
1163 if (_hasBlinkingCursor != blink)
1164 Q_EMIT blinkingCursorStateChanged();
1165
1166 _hasBlinkingCursor = blink;
1167
1168 if (blink && !_blinkCursorTimer->isActive())
1169 _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
1170
1171 if (!blink && _blinkCursorTimer->isActive()) {
1172 _blinkCursorTimer->stop();
1173 if (_cursorBlinking)
1174 blinkCursorEvent();
1175 else
1176 _cursorBlinking = false;
1177 }
1178}
1179
1181{
1182 _allowBlinkingText = blink;
1183
1184 if (blink && !_blinkTimer->isActive())
1185 _blinkTimer->start(TEXT_BLINK_DELAY);
1186
1187 if (!blink && _blinkTimer->isActive()) {
1188 _blinkTimer->stop();
1189 _blinking = false;
1190 }
1191}
1192
1193void TerminalDisplay::focusOutEvent(QFocusEvent *)
1194{
1195 Q_EMIT termLostFocus();
1196 // trigger a repaint of the cursor so that it is both visible (in case
1197 // it was hidden during blinking)
1198 // and drawn in a focused out state
1199 _cursorBlinking = false;
1200 updateCursor();
1201
1202 _blinkCursorTimer->stop();
1203 if (_blinking)
1204 blinkEvent();
1205
1206 _blinkTimer->stop();
1207}
1208void TerminalDisplay::focusInEvent(QFocusEvent *)
1209{
1210 Q_EMIT termGetFocus();
1211 if (_hasBlinkingCursor) {
1212 _blinkCursorTimer->start();
1213 }
1214 updateCursor();
1215
1216 if (_hasBlinker)
1217 _blinkTimer->start();
1218}
1219
1220// QMLTermWidget version. See the upstream commented version for reference.
1221void TerminalDisplay::paint(QPainter *painter)
1222{
1223 QRect clipRect = painter->clipBoundingRect().toAlignedRect();
1224 QRect dirtyRect = clipRect.isValid() ? clipRect : contentsRect();
1225 drawContents(*painter, dirtyRect);
1226}
1227
1228QPoint TerminalDisplay::cursorPosition() const
1229{
1230 if (_screenWindow)
1231 return _screenWindow->cursorPosition();
1232 else
1233 return {0, 0};
1234}
1235
1236QRect TerminalDisplay::preeditRect() const
1237{
1238 const int preeditLength = string_width(_inputMethodData.preeditString);
1239
1240 if (preeditLength == 0)
1241 return {};
1242
1243 return QRect(_leftMargin + qRound(_fontWidth) * cursorPosition().x(),
1244 _topMargin + qRound(_fontHeight) * cursorPosition().y(),
1245 qRound(_fontWidth) * preeditLength,
1246 qRound(_fontHeight));
1247}
1248
1249void TerminalDisplay::drawInputMethodPreeditString(QPainter &painter, const QRect &rect)
1250{
1251 if (_inputMethodData.preeditString.isEmpty())
1252 return;
1253
1254 const QPoint cursorPos = cursorPosition();
1255
1256 bool invertColors = false;
1257 const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
1258 const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
1259 const Character *style = &_image[loc(cursorPos.x(), cursorPos.y())];
1260
1261 drawBackground(painter, rect, background, true);
1262 drawCursor(painter, rect, foreground, background, invertColors);
1263 drawCharacters(painter, rect, _inputMethodData.preeditString, style, invertColors);
1264
1265 _inputMethodData.previousPreeditRect = rect;
1266}
1267
1269{
1270 return _filterChain.get();
1271}
1272
1273void TerminalDisplay::paintFilters(QPainter &painter)
1274{
1275 // get color of character under mouse and use it to draw
1276 // lines for filters
1277 QPoint cursorPos = mapFromScene(QCursor::pos()).toPoint();
1278 int leftMargin = _leftBaseMargin
1279 + ((_scrollbarLocation == QTermWidget::ScrollBarLeft && !_scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar))
1280 ? _scrollBar->width()
1281 : 0);
1282
1283 auto charPos = getCharacterPosition(cursorPos);
1284 Character cursorCharacter = _image[loc(charPos.columns, charPos.lines)];
1285
1286 painter.setPen(QPen(cursorCharacter.foregroundColor.color(colorTable())));
1287
1288 // iterate over hotspots identified by the display's currently active filters
1289 // and draw appropriate visuals to indicate the presence of the hotspot
1290
1291 const QList<Filter::HotSpot *> spots = _filterChain->hotSpots();
1292 for (const auto spot : spots) {
1293 QRegion region;
1294 if (spot->type() == Filter::HotSpot::Link) {
1295 QRect r;
1296 if (spot->startLine() == spot->endLine()) {
1297 r.setCoords(spot->startColumn() * qRound(_fontWidth) + 1 + leftMargin,
1298 spot->startLine() * qRound(_fontHeight) + 1 + _topBaseMargin,
1299 spot->endColumn() * qRound(_fontWidth) - 1 + leftMargin,
1300 (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1301 region |= r;
1302 } else {
1303 r.setCoords(spot->startColumn() * qRound(_fontWidth) + 1 + leftMargin,
1304 spot->startLine() * qRound(_fontHeight) + 1 + _topBaseMargin,
1305 _columns * qRound(_fontWidth) - 1 + leftMargin,
1306 (spot->startLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1307 region |= r;
1308 for (int line = spot->startLine() + 1; line < spot->endLine(); line++) {
1309 r.setCoords(0 * qRound(_fontWidth) + 1 + leftMargin,
1310 line * qRound(_fontHeight) + 1 + _topBaseMargin,
1311 _columns * qRound(_fontWidth) - 1 + leftMargin,
1312 (line + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1313 region |= r;
1314 }
1315 r.setCoords(0 * qRound(_fontWidth) + 1 + leftMargin,
1316 spot->endLine() * qRound(_fontHeight) + 1 + _topBaseMargin,
1317 spot->endColumn() * qRound(_fontWidth) - 1 + leftMargin,
1318 (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1319 region |= r;
1320 }
1321 }
1322
1323 for (int line = spot->startLine(); line <= spot->endLine(); line++) {
1324 int startColumn = 0;
1325 int endColumn = _columns - 1; // TODO use number of _columns which are actually
1326 // occupied on this line rather than the width of the
1327 // display in _columns
1328
1329 // ignore whitespace at the end of the lines
1330 while (QChar(_image[loc(endColumn, line)].character).isSpace() && endColumn > 0)
1331 endColumn--;
1332
1333 // increment here because the column which we want to set 'endColumn' to
1334 // is the first whitespace character at the end of the line
1335 endColumn++;
1336
1337 if (line == spot->startLine())
1338 startColumn = spot->startColumn();
1339 if (line == spot->endLine())
1340 endColumn = spot->endColumn();
1341
1342 // subtract one pixel from
1343 // the right and bottom so that
1344 // we do not overdraw adjacent
1345 // hotspots
1346 //
1347 // subtracting one pixel from all sides also prevents an edge case where
1348 // moving the mouse outside a link could still leave it underlined
1349 // because the check below for the position of the cursor
1350 // finds it on the border of the target area
1351 QRect r;
1352 r.setCoords(startColumn * qRound(_fontWidth) + 1 + leftMargin,
1353 line * qRound(_fontHeight) + 1 + _topBaseMargin,
1354 endColumn * qRound(_fontWidth) - 1 + leftMargin,
1355 (line + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1356 // Underline link hotspots
1357 if (spot->type() == Filter::HotSpot::Link) {
1358 QFontMetricsF metrics(font());
1359
1360 // find the baseline (which is the invisible line that the characters in the font sit on,
1361 // with some having tails dangling below)
1362 qreal baseline = (qreal)r.bottom() - metrics.descent();
1363 // find the position of the underline below that
1364 qreal underlinePos = baseline + metrics.underlinePos();
1365
1366 if (region.contains(mapFromScene(QCursor::pos()).toPoint())) {
1367 painter.drawLine(r.left(), underlinePos, r.right(), underlinePos);
1368 }
1369 }
1370 // Marker hotspots simply have a transparent rectanglular shape
1371 // drawn on top of them
1372 else if (spot->type() == Filter::HotSpot::Marker) {
1373 // TODO - Do not use a hardcoded colour for this
1374 painter.fillRect(r, QBrush(QColor(255, 0, 0, 120)));
1375 }
1376 }
1377 }
1378}
1379
1380int TerminalDisplay::textWidth(const int startColumn, const int length, const int line) const
1381{
1382 QFontMetricsF fm(font());
1383 qreal result = 0;
1384 for (int column = 0; column < length; column++) {
1385 result += fm.horizontalAdvance(_image[loc(startColumn + column, line)].character);
1386 }
1387 return result;
1388}
1389
1390QRect TerminalDisplay::calculateTextArea(int topLeftX, int topLeftY, int startColumn, int line, int length)
1391{
1392 int left = _fixedFont ? qRound(_fontWidth) * startColumn : textWidth(0, startColumn, line);
1393 int top = qRound(_fontHeight) * line;
1394 int width = _fixedFont ? qRound(_fontWidth) * length : textWidth(startColumn, length, line);
1395 return {_leftMargin + topLeftX + left, _topMargin + topLeftY + top, width, qRound(_fontHeight)};
1396}
1397
1398void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
1399{
1400 // Draw opaque background
1401 drawBackground(paint, contentsRect(), _colorTable[DEFAULT_BACK_COLOR].color, true);
1402
1403 QPoint tL = contentsRect().topLeft();
1404 int tLx = tL.x();
1405 int tLy = tL.y();
1406
1407 int lux = qMin(_usedColumns - 1, qMax(0, qRound((rect.left() - tLx - _leftMargin) / _fontWidth)));
1408 int luy = qMin(_usedLines - 1, qMax(0, qRound((rect.top() - tLy - _topMargin) / _fontHeight)));
1409 int rlx = qMin(_usedColumns - 1, qMax(0, qRound((rect.right() - tLx - _leftMargin) / _fontWidth)));
1410 int rly = qMin(_usedLines - 1, qMax(0, qRound((rect.bottom() - tLy - _topMargin) / _fontHeight)));
1411
1412 if (_image.empty()) {
1413 return;
1414 }
1415
1416 const int bufferSize = _usedColumns;
1417 QString unistr;
1418 unistr.reserve(bufferSize);
1419 for (int y = luy; y <= rly; y++) {
1420 char16_t c = _image[loc(lux, y)].character.unicode();
1421 int x = lux;
1422 if (!c && x)
1423 x--; // Search for start of multi-column character
1424 for (; x <= rlx; x++) {
1425 int len = 1;
1426 int p = 0;
1427
1428 // reset our buffer to the maximal size
1429 unistr.resize(bufferSize);
1430
1431 // is this a single character or a sequence of characters ?
1432 if (_image[loc(x, y)].rendition & RE_EXTENDED_CHAR) {
1433 // sequence of characters
1434 ushort extendedCharLength = 0;
1435 std::span chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x, y)].charSequence, extendedCharLength);
1436 for (int index = 0; index < extendedCharLength; index++) {
1437 Q_ASSERT(p < bufferSize);
1438 unistr[p++] = chars[index];
1439 }
1440 } else {
1441 // single character
1442 c = _image[loc(x, y)].character.unicode();
1443 if (c) {
1444 Q_ASSERT(p < bufferSize);
1445 unistr[p++] = c; // fontMap(c);
1446 }
1447 }
1448
1449 bool lineDraw = isLineChar(c);
1450 bool doubleWidth = (_image[qMin(loc(x, y) + 1, _imageSize)].character.unicode() == 0);
1451 CharacterColor currentForeground = _image[loc(x, y)].foregroundColor;
1452 CharacterColor currentBackground = _image[loc(x, y)].backgroundColor;
1453 quint8 currentRendition = _image[loc(x, y)].rendition;
1454
1455 while (x + len <= rlx && _image[loc(x + len, y)].foregroundColor == currentForeground
1456 && _image[loc(x + len, y)].backgroundColor == currentBackground && _image[loc(x + len, y)].rendition == currentRendition
1457 && (_image[qMin(loc(x + len, y) + 1, _imageSize)].character.unicode() == 0) == doubleWidth
1458 && isLineChar(c = _image[loc(x + len, y)].character.unicode()) == lineDraw) // Assignment!
1459 {
1460 if (c)
1461 unistr[p++] = c; // fontMap(c);
1462 if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1463 len++; // Skip trailing part of multi-column character
1464 len++;
1465 }
1466 if ((x + len < _usedColumns) && (!_image[loc(x + len, y)].character.unicode()))
1467 len++; // Adjust for trailing part of multi-column character
1468
1469 bool save__fixedFont = _fixedFont;
1470 if (lineDraw)
1471 _fixedFont = false;
1472 unistr.resize(p);
1473
1474 // Create a text scaling matrix for double width and double height lines.
1475 QTransform textScale;
1476
1477 if (y < _lineProperties.size()) {
1478 if (_lineProperties[y] & LINE_DOUBLEWIDTH)
1479 textScale.scale(2, 1);
1480
1481 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1482 textScale.scale(1, 2);
1483 }
1484
1485 // Apply text scaling matrix.
1486 paint.setWorldTransform(textScale, true);
1487
1488 // calculate the area in which the text will be drawn
1489 QRect textArea = calculateTextArea(tLx, tLy, x, y, len);
1490
1491 // move the calculated area to take account of scaling applied to the painter.
1492 // the position of the area from the origin (0,0) is scaled
1493 // by the opposite of whatever
1494 // transformation has been applied to the painter. this ensures that
1495 // painting does actually start from textArea.topLeft()
1496 //(instead of textArea.topLeft() * painter-scale)
1497 textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft()));
1498
1499 // paint text fragment
1500 drawTextFragment(paint, textArea, unistr, &_image[loc(x, y)]); //,
1501 // 0,
1502 //!_isPrinting );
1503
1504 _fixedFont = save__fixedFont;
1505
1506 // reset back to single-width, single-height _lines
1507 paint.setWorldTransform(textScale.inverted(), true);
1508
1509 if (y < _lineProperties.size() - 1) {
1510 // double-height _lines are represented by two adjacent _lines
1511 // containing the same characters
1512 // both _lines will have the LINE_DOUBLEHEIGHT attribute.
1513 // If the current line has the LINE_DOUBLEHEIGHT attribute,
1514 // we can therefore skip the next line
1515 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1516 y++;
1517 }
1518
1519 x += len - 1;
1520 }
1521 }
1522}
1523
1524void TerminalDisplay::blinkEvent()
1525{
1526 if (!_allowBlinkingText)
1527 return;
1528
1529 _blinking = !_blinking;
1530
1531 // TODO: Optimize to only repaint the areas of the widget
1532 // where there is blinking text
1533 // rather than repainting the whole widget.
1534 update();
1535}
1536
1537QRect TerminalDisplay::imageToWidget(const QRect &imageArea) const
1538{
1539 QRect result;
1540 result.setLeft(_leftMargin + qRound(_fontWidth) * imageArea.left());
1541 result.setTop(_topMargin + qRound(_fontHeight) * imageArea.top());
1542 result.setWidth(qRound(_fontWidth) * imageArea.width());
1543 result.setHeight(qRound(_fontHeight) * imageArea.height());
1544
1545 return result;
1546}
1547
1548void TerminalDisplay::updateCursor()
1549{
1550 QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(1, 1)));
1551 update(cursorRect);
1552}
1553
1554void TerminalDisplay::blinkCursorEvent()
1555{
1556 _cursorBlinking = !_cursorBlinking;
1557 updateCursor();
1558}
1559
1560/* ------------------------------------------------------------------------- */
1561/* */
1562/* Resizing */
1563/* */
1564/* ------------------------------------------------------------------------- */
1565
1566void TerminalDisplay::resizeEvent(QResizeEvent *)
1567{
1568 updateImageSize();
1570}
1571
1572void TerminalDisplay::propagateSize()
1573{
1574 if (_isFixedSize) {
1575 setSize(_columns, _lines);
1576 return;
1577 }
1578 if (!_image.empty())
1579 updateImageSize();
1580}
1581
1582void TerminalDisplay::updateImageSize()
1583{
1584 auto oldimg = _image;
1585 int oldlin = _lines;
1586 int oldcol = _columns;
1587
1588 makeImage();
1589
1590 // copy the old image to reduce flicker
1591 int lines = qMin(oldlin, _lines);
1592 int columns = qMin(oldcol, _columns);
1593
1594 if (!oldimg.empty()) {
1595 for (int line = 0; line < lines; line++) {
1596 memcpy((void *)&_image[_columns * line], (void *)&oldimg[oldcol * line], columns * sizeof(Character));
1597 }
1598 oldimg.clear();
1599 }
1600
1601 if (_screenWindow)
1602 _screenWindow->setWindowLines(_lines);
1603
1604 _resizing = (oldlin != _lines) || (oldcol != _columns);
1605
1606 if (_resizing) {
1607 showResizeNotification();
1608 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
1609 }
1610
1611 _resizing = false;
1612}
1613
1614// showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1615// display has been resized when the display is hidden or shown.
1616//
1617// TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1618// the same signal as the one for a content size change
1619void TerminalDisplay::showEvent(QShowEvent *)
1620{
1621 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth);
1622}
1623void TerminalDisplay::hideEvent(QHideEvent *)
1624{
1625 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth);
1626}
1627
1628/* ------------------------------------------------------------------------- */
1629/* */
1630/* Scrollbar */
1631/* */
1632/* ------------------------------------------------------------------------- */
1633
1634void TerminalDisplay::scrollBarPositionChanged(int)
1635{
1636 if (!_screenWindow)
1637 return;
1638
1639 _screenWindow->scrollTo(_scrollBar->value());
1640
1641 // if the thumb has been moved to the bottom of the _scrollBar then set
1642 // the display to automatically track new output,
1643 // that is, scroll down automatically
1644 // to how new _lines as they are added
1645 const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
1646 _screenWindow->setTrackOutput(atEndOfOutput);
1647
1648 updateImage();
1649
1650 // QMLTermWidget: notify qml side of the change only when needed.
1651 Q_EMIT scrollbarValueChanged();
1652}
1653
1655{
1656 // update _scrollBar if the range or value has changed,
1657 // otherwise return
1658 //
1659 // setting the range or value of a _scrollBar will always trigger
1660 // a repaint, so it should be avoided if it is not necessary
1661 if (_scrollBar->minimum() == 0 && _scrollBar->maximum() == (slines - _lines) && _scrollBar->value() == cursor) {
1662 return;
1663 }
1664
1665 disconnect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1666 _scrollBar->setRange(0, slines - _lines);
1667 _scrollBar->setSingleStep(1);
1668 _scrollBar->setPageStep(_lines);
1669 _scrollBar->setValue(cursor);
1670 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1671}
1672
1674{
1675 disconnect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1676 _scrollBar->setValue(_scrollBar->maximum());
1677 connect(_scrollBar, &QAbstractSlider::valueChanged, this, &TerminalDisplay::scrollBarPositionChanged);
1678
1679 _screenWindow->scrollTo(_scrollBar->value() + 1);
1680 _screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
1681}
1682
1683void TerminalDisplay::setScrollBarPosition(QTermWidget::ScrollBarPosition position)
1684{
1685 if (_scrollbarLocation == position)
1686 return;
1687
1688 if (position == QTermWidget::NoScrollBar)
1689 _scrollBar->hide();
1690 else
1691 _scrollBar->show();
1692
1693 _topMargin = _leftMargin = 1;
1694 _scrollbarLocation = position;
1695
1696 propagateSize();
1697 update();
1698}
1699
1700void TerminalDisplay::mousePressEvent(QMouseEvent *ev)
1701{
1702 if (_possibleTripleClick && (ev->button() == Qt::LeftButton)) {
1703 mouseTripleClickEvent(ev);
1704 return;
1705 }
1706
1707 if (!contentsRect().contains(ev->pos()))
1708 return;
1709
1710 if (!_screenWindow)
1711 return;
1712
1713 auto charPos = getCharacterPosition(ev->pos());
1714 QPoint pos = charPos;
1715 auto [charColumn, charLine] = charPos;
1716
1717 if (ev->button() == Qt::LeftButton) {
1718 _lineSelectionMode = false;
1719 _wordSelectionMode = false;
1720
1721 Q_EMIT isBusySelecting(true); // Keep it steady...
1722 // Drag only when the Control key is hold
1723 bool selected = false;
1724
1725 // The receiver of the testIsSelected() signal will adjust
1726 // 'selected' accordingly.
1727 // emit testIsSelected(pos.x(), pos.y(), selected);
1728
1729 selected = _screenWindow->isSelected(charColumn, charLine);
1730
1731 if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected) {
1732 // The user clicked inside selected text
1733 dragInfo.state = diPending;
1734 dragInfo.start = ev->pos();
1735 } else {
1736 // No reason to ever start a drag event
1737 dragInfo.state = diNone;
1738
1739 _preserveLineBreaks = !((ev->modifiers() & Qt::ControlModifier) && !(ev->modifiers() & Qt::AltModifier));
1740 _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
1741
1742 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) {
1743 _screenWindow->clearSelection();
1744
1745 // emit clearSelectionSignal();
1746 pos.ry() += _scrollBar->value();
1747 _iPntSel = _pntSel = pos;
1748 _actSel = 1; // left mouse button pressed but nothing selected yet.
1749
1750 } else {
1751 Q_EMIT mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
1752 }
1753
1754 Filter::HotSpot *spot = _filterChain->hotSpotAt(charLine, charColumn);
1755 if (spot && spot->type() == Filter::HotSpot::Link)
1756 spot->activate(QLatin1String("click-action"));
1757 }
1758 } else if (ev->button() == Qt::MiddleButton) {
1759 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1760 emitSelection(true, ev->modifiers() & Qt::ControlModifier);
1761 else
1762 Q_EMIT mouseSignal(1, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
1763 } else if (ev->button() == Qt::RightButton) {
1764 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1766 else
1767 Q_EMIT mouseSignal(2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
1768 }
1769}
1770
1772{
1773 auto pos = getCharacterPosition(position);
1774
1775 Filter::HotSpot *spot = _filterChain->hotSpotAt(pos.lines, pos.columns);
1776
1777 return spot ? spot->actions() : QList<QAction *>();
1778}
1779
1780void TerminalDisplay::mouseMoveEvent(QMouseEvent *ev)
1781{
1782 int leftMargin = _leftBaseMargin
1783 + ((_scrollbarLocation == QTermWidget::ScrollBarLeft && !_scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar))
1784 ? _scrollBar->width()
1785 : 0);
1786
1787 auto charPos = getCharacterPosition(ev->pos());
1788
1789 // handle filters
1790 // change link hot-spot appearance on mouse-over
1791 Filter::HotSpot *spot = _filterChain->hotSpotAt(charPos.lines, charPos.columns);
1792 if (spot && spot->type() == Filter::HotSpot::Link) {
1793 QRegion previousHotspotArea = _mouseOverHotspotArea;
1794 _mouseOverHotspotArea = QRegion();
1795 QRect r;
1796 if (spot->startLine() == spot->endLine()) {
1797 r.setCoords(spot->startColumn() * qRound(_fontWidth) + leftMargin,
1798 spot->startLine() * qRound(_fontHeight) + _topBaseMargin,
1799 spot->endColumn() * qRound(_fontWidth) + leftMargin,
1800 (spot->endLine() + 1) * qRound(_fontHeight) - 1 + _topBaseMargin);
1801 _mouseOverHotspotArea |= r;
1802 } else {
1803 r.setCoords(spot->startColumn() * qRound(_fontWidth) + leftMargin,
1804 spot->startLine() * qRound(_fontHeight) + _topBaseMargin,
1805 _columns * qRound(_fontWidth) - 1 + leftMargin,
1806 (spot->startLine() + 1) * qRound(_fontHeight) + _topBaseMargin);
1807 _mouseOverHotspotArea |= r;
1808 for (int line = spot->startLine() + 1; line < spot->endLine(); line++) {
1809 r.setCoords(0 * qRound(_fontWidth) + leftMargin,
1810 line * qRound(_fontHeight) + _topBaseMargin,
1811 _columns * qRound(_fontWidth) + leftMargin,
1812 (line + 1) * qRound(_fontHeight) + _topBaseMargin);
1813 _mouseOverHotspotArea |= r;
1814 }
1815 r.setCoords(0 * qRound(_fontWidth) + leftMargin,
1816 spot->endLine() * qRound(_fontHeight) + _topBaseMargin,
1817 spot->endColumn() * qRound(_fontWidth) + leftMargin,
1818 (spot->endLine() + 1) * qRound(_fontHeight) + _topBaseMargin);
1819 _mouseOverHotspotArea |= r;
1820 }
1821 update(_mouseOverHotspotArea | previousHotspotArea);
1822 } else if (!_mouseOverHotspotArea.isEmpty()) {
1823 update(_mouseOverHotspotArea);
1824 // set hotspot area to an invalid rectangle
1825 _mouseOverHotspotArea = QRegion();
1826 }
1827
1828 // for auto-hiding the cursor, we need mouseTracking
1829 if (ev->buttons() == Qt::NoButton)
1830 return;
1831
1832 // if the terminal is interested in mouse movements
1833 // then Q_EMIT a mouse movement signal, unless the shift
1834 // key is being held down, which overrides this.
1835 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
1836 int button = 3;
1837 if (ev->buttons() & Qt::LeftButton)
1838 button = 0;
1839 if (ev->buttons() & Qt::MiddleButton)
1840 button = 1;
1841 if (ev->buttons() & Qt::RightButton)
1842 button = 2;
1843
1844 Q_EMIT mouseSignal(button, charPos.columns + 1, charPos.lines + 1 + _scrollBar->value() - _scrollBar->maximum(), 1);
1845
1846 return;
1847 }
1848
1849 if (dragInfo.state == diPending) {
1850 // we had a mouse down, but haven't confirmed a drag yet
1851 // if the mouse has moved sufficiently, we will confirm
1852
1853 // int distance = KGlobalSettings::dndEventDelay();
1855
1856 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1857 if (ev->position().x() > dragInfo.start.x() + distance || ev->position().x() < dragInfo.start.x() - distance
1858 || ev->position().y() > dragInfo.start.y() + distance || ev->position().y() < dragInfo.start.y() - distance)
1859 #else
1860 if (ev->pos().x() > dragInfo.start.x() + distance || ev->pos().x() < dragInfo.start.x() - distance
1861 || ev->pos().y() > dragInfo.start.y() + distance || ev->pos().y() < dragInfo.start.y() - distance)
1862 #endif
1863 {
1864 // we've left the drag square, we can start a real drag operation now
1865 Q_EMIT isBusySelecting(false); // Ok.. we can breath again.
1866
1867 _screenWindow->clearSelection();
1868 doDrag();
1869 }
1870 return;
1871 } else if (dragInfo.state == diDragging) {
1872 // this isn't technically needed because mouseMoveEvent is suppressed during
1873 // Qt drag operations, replaced by dragMoveEvent
1874 return;
1875 }
1876
1877 if (_actSel == 0)
1878 return;
1879
1880 // don't extend selection while pasting
1881 if (ev->buttons() & Qt::MiddleButton)
1882 return;
1883
1884 extendSelection(ev->pos());
1885}
1886
1887void TerminalDisplay::extendSelection(const QPoint &position)
1888{
1889 QPoint pos = position;
1890
1891 if (!_screenWindow)
1892 return;
1893
1894 // if ( !contentsRect().contains(ev->pos()) ) return;
1895 QPoint tL = contentsRect().topLeft();
1896 int tLx = tL.x();
1897 int tLy = tL.y();
1898 int scroll = _scrollBar->value();
1899
1900 // we're in the process of moving the mouse with the left button pressed
1901 // the mouse cursor will kept caught within the bounds of the text in
1902 // this widget.
1903
1904 int linesBeyondWidget = 0;
1905
1906 QRect textBounds(tLx + _leftMargin, tLy + _topMargin, _usedColumns * qRound(_fontWidth) - 1, _usedLines * qRound(_fontHeight) - 1);
1907
1908 // Adjust position within text area bounds.
1909 QPoint oldpos = pos;
1910
1911 pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right()));
1912 pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom()));
1913
1914 if (oldpos.y() > textBounds.bottom()) {
1915 linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / qRound(_fontHeight);
1916 _scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward
1917 }
1918 if (oldpos.y() < textBounds.top()) {
1919 linesBeyondWidget = (textBounds.top() - oldpos.y()) / qRound(_fontHeight);
1920 _scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history
1921 }
1922
1923 QPoint here =
1924 getCharacterPosition(pos); // QPoint((pos.x()-tLx-_leftMargin+(qRound(_fontWidth)/2))/qRound(_fontWidth),(pos.y()-tLy-_topMargin)/qRound(_fontHeight));
1925 QPoint ohere;
1926 QPoint _iPntSelCorr = _iPntSel;
1927 _iPntSelCorr.ry() -= _scrollBar->value();
1928 QPoint _pntSelCorr = _pntSel;
1929 _pntSelCorr.ry() -= _scrollBar->value();
1930 bool swapping = false;
1931
1932 if (_wordSelectionMode) {
1933 // Extend to word boundaries
1934 int i;
1935 QChar selClass;
1936
1937 bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
1938 bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
1939 swapping = left_not_right != old_left_not_right;
1940
1941 // Find left (left_not_right ? from here : from start)
1942 QPoint left = left_not_right ? here : _iPntSelCorr;
1943 i = loc(left.x(), left.y());
1944 if (i >= 0 && i <= _imageSize) {
1945 selClass = charClass(_image[i].character);
1946 while (((left.x() > 0) || (left.y() > 0 && (_lineProperties[left.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) {
1947 i--;
1948 if (left.x() > 0)
1949 left.rx()--;
1950 else {
1951 left.rx() = _usedColumns - 1;
1952 left.ry()--;
1953 }
1954 }
1955 }
1956
1957 // Find left (left_not_right ? from start : from here)
1958 QPoint right = left_not_right ? _iPntSelCorr : here;
1959 i = loc(right.x(), right.y());
1960 if (i >= 0 && i <= _imageSize) {
1961 selClass = charClass(_image[i].character);
1962 while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && (_lineProperties[right.y()] & LINE_WRAPPED)))
1963 && charClass(_image[i + 1].character) == selClass) {
1964 i++;
1965 if (right.x() < _usedColumns - 1)
1966 right.rx()++;
1967 else {
1968 right.rx() = 0;
1969 right.ry()++;
1970 }
1971 }
1972 }
1973
1974 // Pick which is start (ohere) and which is extension (here)
1975 if (left_not_right) {
1976 here = left;
1977 ohere = right;
1978 } else {
1979 here = right;
1980 ohere = left;
1981 }
1982 ohere.rx()++;
1983 }
1984
1985 if (_lineSelectionMode) {
1986 // Extend to complete line
1987 bool above_not_below = (here.y() < _iPntSelCorr.y());
1988
1989 QPoint above = above_not_below ? here : _iPntSelCorr;
1990 QPoint below = above_not_below ? _iPntSelCorr : here;
1991
1992 while (above.y() > 0 && (_lineProperties[above.y() - 1] & LINE_WRAPPED))
1993 above.ry()--;
1994 while (below.y() < _usedLines - 1 && (_lineProperties[below.y()] & LINE_WRAPPED))
1995 below.ry()++;
1996
1997 above.setX(0);
1998 below.setX(_usedColumns - 1);
1999
2000 // Pick which is start (ohere) and which is extension (here)
2001 if (above_not_below) {
2002 here = above;
2003 ohere = below;
2004 } else {
2005 here = below;
2006 ohere = above;
2007 }
2008
2009 QPoint newSelBegin = QPoint(ohere.x(), ohere.y());
2010 swapping = !(_tripleSelBegin == newSelBegin);
2011 _tripleSelBegin = newSelBegin;
2012
2013 ohere.rx()++;
2014 }
2015
2016 int offset = 0;
2017 if (!_wordSelectionMode && !_lineSelectionMode) {
2018 int i;
2019 QChar selClass;
2020
2021 bool left_not_right = (here.y() < _iPntSelCorr.y() || (here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
2022 bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() || (_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
2023 swapping = left_not_right != old_left_not_right;
2024
2025 // Find left (left_not_right ? from here : from start)
2026 QPoint left = left_not_right ? here : _iPntSelCorr;
2027
2028 // Find left (left_not_right ? from start : from here)
2029 QPoint right = left_not_right ? _iPntSelCorr : here;
2030 if (right.x() > 0 && !_columnSelectionMode) {
2031 i = loc(right.x(), right.y());
2032 if (i >= 0 && i <= _imageSize) {
2033 selClass = charClass(_image[i - 1].character);
2034 /* if (selClass == ' ')
2035 * {
2036 * while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
2037 * !(_lineProperties[right.y()] & LINE_WRAPPED))
2038 * { i++; right.rx()++; }
2039 * if (right.x() < _usedColumns-1)
2040 * right = left_not_right ? _iPntSelCorr : here;
2041 * else
2042 * right.rx()++; // will be balanced later because of offset=-1;
2043 }*/
2044 }
2045 }
2046
2047 // Pick which is start (ohere) and which is extension (here)
2048 if (left_not_right) {
2049 here = left;
2050 ohere = right;
2051 offset = 0;
2052 } else {
2053 here = right;
2054 ohere = left;
2055 offset = -1;
2056 }
2057 }
2058
2059 if ((here == _pntSelCorr) && (scroll == _scrollBar->value()))
2060 return; // not moved
2061
2062 if (here == ohere)
2063 return; // It's not left, it's not right.
2064
2065 if (_actSel < 2 || swapping) {
2066 if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2067 _screenWindow->setSelectionStart(ohere.x(), ohere.y(), true);
2068 } else {
2069 _screenWindow->setSelectionStart(ohere.x() - 1 - offset, ohere.y(), false);
2070 }
2071 }
2072
2073 _actSel = 2; // within selection
2074 _pntSel = here;
2075 _pntSel.ry() += _scrollBar->value();
2076
2077 if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2078 _screenWindow->setSelectionEnd(here.x(), here.y());
2079 } else {
2080 _screenWindow->setSelectionEnd(here.x() + offset, here.y());
2081 }
2082}
2083
2084void TerminalDisplay::mouseReleaseEvent(QMouseEvent *ev)
2085{
2086 if (!_screenWindow)
2087 return;
2088
2089 auto [charColumn, charLine] = getCharacterPosition(ev->pos());
2090
2091 if (ev->button() == Qt::LeftButton) {
2092 Q_EMIT isBusySelecting(false);
2093 if (dragInfo.state == diPending) {
2094 // We had a drag event pending but never confirmed. Kill selection
2095 _screenWindow->clearSelection();
2096 // emit clearSelectionSignal();
2097 } else {
2098 if (_actSel > 1) {
2099 setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2100 }
2101
2102 _actSel = 0;
2103
2104 // FIXME: emits a release event even if the mouse is
2105 // outside the range. The procedure used in `mouseMoveEvent'
2106 // applies here, too.
2107
2108 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2109 Q_EMIT mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 2);
2110 }
2111 dragInfo.state = diNone;
2112 }
2113
2114 if (!_mouseMarks && ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier)) || ev->button() == Qt::MiddleButton)) {
2115 Q_EMIT mouseSignal(ev->button() == Qt::MiddleButton ? 1 : 2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum(), 2);
2116 }
2117}
2118
2119CharPos TerminalDisplay::getCharacterPosition(const QPointF &widgetPoint) const
2120{
2121 int line, column = 0;
2122
2123 line = (widgetPoint.y() - contentsRect().top() - _topMargin) / qRound(_fontHeight);
2124 if (line < 0)
2125 line = 0;
2126 if (line >= _usedLines)
2127 line = _usedLines - 1;
2128
2129 int x = widgetPoint.x() + qRound(_fontWidth) / 2 - contentsRect().left() - _leftMargin;
2130 if (_fixedFont)
2131 column = x / qRound(_fontWidth);
2132 else {
2133 column = 0;
2134 while (column + 1 < _usedColumns && x > textWidth(0, column + 1, line))
2135 column++;
2136 }
2137
2138 if (column < 0)
2139 column = 0;
2140
2141 // the column value returned can be equal to _usedColumns, which
2142 // is the position just after the last character displayed in a line.
2143 //
2144 // this is required so that the user can select characters in the right-most
2145 // column (or left-most for right-to-left input)
2146 if (column > _usedColumns)
2147 column = _usedColumns;
2148
2149 return {column, line};
2150}
2151
2153{
2154 if (!_screenWindow)
2155 return;
2156
2158}
2159
2161{
2162 if (!_screenWindow)
2163 return;
2164
2165 _lineProperties = _screenWindow->getLineProperties();
2166}
2167
2168void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent *ev)
2169{
2170 if (ev->button() != Qt::LeftButton)
2171 return;
2172 if (!_screenWindow)
2173 return;
2174
2175 QPoint pos = getCharacterPosition(ev->pos());
2176
2177 // pass on double click as two clicks.
2178 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
2179 // Send just _ONE_ click event, since the first click of the double click
2180 // was already sent by the click handler
2181 Q_EMIT mouseSignal(0, pos.x() + 1, pos.y() + 1 + _scrollBar->value() - _scrollBar->maximum(),
2182 0); // left button
2183 return;
2184 }
2185
2186 _screenWindow->clearSelection();
2187 QPoint bgnSel = pos;
2188 QPoint endSel = pos;
2189 int i = loc(bgnSel.x(), bgnSel.y());
2190 _iPntSel = bgnSel;
2191 _iPntSel.ry() += _scrollBar->value();
2192
2193 _wordSelectionMode = true;
2194
2195 // find word boundaries...
2196 QChar selClass = charClass(_image[i].character);
2197 {
2198 // find the start of the word
2199 int x = bgnSel.x();
2200 while (((x > 0) || (bgnSel.y() > 0 && (_lineProperties[bgnSel.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) {
2201 i--;
2202 if (x > 0)
2203 x--;
2204 else {
2205 x = _usedColumns - 1;
2206 bgnSel.ry()--;
2207 }
2208 }
2209
2210 bgnSel.setX(x);
2211 _screenWindow->setSelectionStart(bgnSel.x(), bgnSel.y(), false);
2212
2213 // find the end of the word
2214 i = loc(endSel.x(), endSel.y());
2215 x = endSel.x();
2216 while (((x < _usedColumns - 1) || (endSel.y() < _usedLines - 1 && (_lineProperties[endSel.y()] & LINE_WRAPPED)))
2217 && charClass(_image[i + 1].character) == selClass) {
2218 i++;
2219 if (x < _usedColumns - 1)
2220 x++;
2221 else {
2222 x = 0;
2223 endSel.ry()++;
2224 }
2225 }
2226
2227 endSel.setX(x);
2228
2229 // In word selection mode don't select @ (64) if at end of word.
2230 if ((QChar(_image[i].character) == QLatin1Char('@')) && ((endSel.x() - bgnSel.x()) > 0))
2231 endSel.setX(x - 1);
2232
2233 _actSel = 2; // within selection
2234
2235 _screenWindow->setSelectionEnd(endSel.x(), endSel.y());
2236
2237 setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2238 }
2239
2240 _possibleTripleClick = true;
2241
2242 QTimer::singleShot(QApplication::doubleClickInterval(), this, SLOT(tripleClickTimeout()));
2243}
2244
2245void TerminalDisplay::wheelEvent(QWheelEvent *ev)
2246{
2247 if (ev->angleDelta().y() == 0) // orientation is not verical
2248 return;
2249
2250 // if the terminal program is not interested mouse events
2251 // then send the event to the scrollbar if the slider has room to move
2252 // or otherwise send simulated up / down key presses to the terminal program
2253 // for the benefit of programs such as 'less'
2254 if (_mouseMarks) {
2255 bool canScroll = _scrollBar->maximum() > 0;
2256 if (canScroll)
2257 _scrollBar->event(ev);
2258 else {
2259 // assume that each Up / Down key event will cause the terminal application
2260 // to scroll by one line.
2261 //
2262 // to get a reasonable scrolling speed, scroll by one line for every 5 degrees
2263 // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees,
2264 // giving a scroll of 3 lines
2265 int key = ev->angleDelta().y() > 0 ? Qt::Key_Up : Qt::Key_Down;
2266
2267 // QWheelEvent::delta() gives rotation in eighths of a degree
2268 int wheelDegrees = ev->angleDelta().y() / 8;
2269 int linesToScroll = abs(wheelDegrees) / 5;
2270
2271 QKeyEvent keyScrollEvent(QEvent::KeyPress, key, Qt::NoModifier);
2272
2273 for (int i = 0; i < linesToScroll; i++)
2274 Q_EMIT keyPressedSignal(&keyScrollEvent, false);
2275 }
2276 } else {
2277 // terminal program wants notification of mouse activity
2278 auto pos = getCharacterPosition(ev->position());
2279
2280 Q_EMIT mouseSignal(ev->angleDelta().y() > 0 ? 4 : 5, pos.columns + 1, pos.lines + 1 + _scrollBar->value() - _scrollBar->maximum(), 0);
2281 }
2282}
2283
2284void TerminalDisplay::tripleClickTimeout()
2285{
2286 _possibleTripleClick = false;
2287}
2288
2289void TerminalDisplay::mouseTripleClickEvent(QMouseEvent *ev)
2290{
2291 if (!_screenWindow)
2292 return;
2293
2294 _iPntSel = getCharacterPosition(ev->pos());
2295
2296 _screenWindow->clearSelection();
2297
2298 _lineSelectionMode = true;
2299 _wordSelectionMode = false;
2300
2301 _actSel = 2; // within selection
2302 Q_EMIT isBusySelecting(true); // Keep it steady...
2303
2304 while (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))
2305 _iPntSel.ry()--;
2306
2307 if (_tripleClickMode == SelectForwardsFromCursor) {
2308 // find word boundary start
2309 int i = loc(_iPntSel.x(), _iPntSel.y());
2310 QChar selClass = charClass(_image[i].character);
2311 int x = _iPntSel.x();
2312
2313 while (((x > 0) || (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))) && charClass(_image[i - 1].character) == selClass) {
2314 i--;
2315 if (x > 0)
2316 x--;
2317 else {
2318 x = _columns - 1;
2319 _iPntSel.ry()--;
2320 }
2321 }
2322
2323 _screenWindow->setSelectionStart(x, _iPntSel.y(), false);
2324 _tripleSelBegin = QPoint(x, _iPntSel.y());
2325 } else if (_tripleClickMode == SelectWholeLine) {
2326 _screenWindow->setSelectionStart(0, _iPntSel.y(), false);
2327 _tripleSelBegin = QPoint(0, _iPntSel.y());
2328 }
2329
2330 while (_iPntSel.y() < _lines - 1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED))
2331 _iPntSel.ry()++;
2332
2333 _screenWindow->setSelectionEnd(_columns - 1, _iPntSel.y());
2334
2335 setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2336
2337 _iPntSel.ry() += _scrollBar->value();
2338}
2339
2340bool TerminalDisplay::focusNextPrevChild(bool next)
2341{
2342 if (next)
2343 return false; // This disables changing the active part in konqueror
2344 // when pressing Tab
2345 return false;
2346 // return QWidget::focusNextPrevChild( next );
2347}
2348
2349QChar TerminalDisplay::charClass(QChar qch) const
2350{
2351 if (qch.isSpace())
2352 return QLatin1Char(' ');
2353
2354 if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive))
2355 return QLatin1Char('a');
2356
2357 return qch;
2358}
2359
2361{
2362 _wordCharacters = wc;
2363}
2364
2366{
2367 if (_mouseMarks != on) {
2368 _mouseMarks = on;
2369 setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor);
2370 Q_EMIT usesMouseChanged();
2371 }
2372}
2374{
2375 return _mouseMarks;
2376}
2377
2378void TerminalDisplay::setBracketedPasteMode(bool on)
2379{
2380 _bracketedPasteMode = on;
2381}
2382bool TerminalDisplay::bracketedPasteMode() const
2383{
2384 return _bracketedPasteMode;
2385}
2386
2387/* ------------------------------------------------------------------------- */
2388/* */
2389/* Clipboard */
2390/* */
2391/* ------------------------------------------------------------------------- */
2392
2393#undef KeyPress
2394
2395void TerminalDisplay::emitSelection(bool useXselection, bool appendReturn)
2396{
2397 if (!_screenWindow)
2398 return;
2399
2400 if(m_readOnly)
2401 return;
2402
2403 // Paste Clipboard by simulating keypress events
2404 QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection : QClipboard::Clipboard);
2405 if (!text.isEmpty()) {
2406 text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
2407 text.replace(QLatin1Char('\n'), QLatin1Char('\r'));
2408
2409 if (_trimPastedTrailingNewlines) {
2410 text.replace(QRegularExpression(QStringLiteral("\\r+$")), QString());
2411 }
2412
2413 // TODO QMLTERMWIDGET We should expose this as a signal.
2414 // if (_confirmMultilinePaste && text.contains(QLatin1Char('\r'))) {
2415 // QMessageBox confirmation(this);
2416 // confirmation.setWindowTitle(tr("Paste multiline text"));
2417 // confirmation.setText(tr("Are you sure you want to paste this text?"));
2418 // confirmation.setDetailedText(text);
2419 // confirmation.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
2420 // // Click "Show details..." to show those by default
2421 // const auto buttons = confirmation.buttons();
2422 // for( QAbstractButton * btn : buttons ) {
2423 // if (confirmation.buttonRole(btn) == QMessageBox::ActionRole && btn->text() == QMessageBox::tr("Show Details...")) {
2424 // Q_EMIT btn->clicked();
2425 // break;
2426 // }
2427 // }
2428 // confirmation.setDefaultButton(QMessageBox::Yes);
2429 // confirmation.exec();
2430 // if (confirmation.standardButton(confirmation.clickedButton()) != QMessageBox::Yes) {
2431 // return;
2432 // }
2433 // }
2434
2435 bracketText(text);
2436
2437 // appendReturn is intentionally handled _after_ enclosing texts with brackets as
2438 // that feature is used to allow execution of commands immediately after paste.
2439 // Ref: https://bugs.kde.org/show_bug.cgi?id=16179
2440 // Ref: https://github.com/KDE/konsole/commit/83d365f2ebfe2e659c1e857a2f5f247c556ab571
2441 if (appendReturn) {
2442 text.append(QLatin1Char('\r'));
2443 }
2444
2445 QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
2446 Q_EMIT keyPressedSignal(&e, true); // expose as a big fat keypress event
2447
2448 _screenWindow->clearSelection();
2449
2450 switch (mMotionAfterPasting) {
2451 case MoveStartScreenWindow:
2452 // Temporarily stop tracking output, or pasting contents triggers
2453 // ScreenWindow::notifyOutputChanged() and the latter scrolls the
2454 // terminal to the last line. It will be re-enabled when needed
2455 // (e.g., scrolling to the last line).
2456 _screenWindow->setTrackOutput(false);
2457 _screenWindow->scrollTo(0);
2458 break;
2459 case MoveEndScreenWindow:
2460 scrollToEnd();
2461 break;
2462 case NoMoveScreenWindow:
2463 break;
2464 }
2465 }
2466}
2467
2469{
2470 if (bracketedPasteMode() && !_disabledBracketedPasteMode) {
2471 text.prepend(QLatin1String("\033[200~"));
2472 text.append(QLatin1String("\033[201~"));
2473 }
2474}
2475
2476void TerminalDisplay::setSelection(const QString &t)
2477{
2478 if (QApplication::clipboard()->supportsSelection()) {
2480 }
2481
2482 Q_EMIT isTextSelectedChanged();
2483}
2484
2486{
2487 if (!_screenWindow)
2488 return;
2489
2490 QString text = _screenWindow->selectedText(_preserveLineBreaks);
2491 if (!text.isEmpty())
2493}
2494
2496{
2497 emitSelection(false, false);
2498}
2499
2501{
2502 emitSelection(true, false);
2503}
2504
2505void TerminalDisplay::setConfirmMultilinePaste(bool confirmMultilinePaste)
2506{
2507 _confirmMultilinePaste = confirmMultilinePaste;
2508}
2509
2510void TerminalDisplay::setTrimPastedTrailingNewlines(bool trimPastedTrailingNewlines)
2511{
2512 _trimPastedTrailingNewlines = trimPastedTrailingNewlines;
2513}
2514
2515/* ------------------------------------------------------------------------- */
2516/* */
2517/* Keyboard */
2518/* */
2519/* ------------------------------------------------------------------------- */
2520
2522{
2523 _flowControlWarningEnabled = enable;
2524
2525 // if the dialog is currently visible and the flow control warning has
2526 // been disabled then hide the dialog
2527 if (!enable)
2528 outputSuspended(false);
2529}
2530
2531void TerminalDisplay::setMotionAfterPasting(MotionAfterPasting action)
2532{
2533 mMotionAfterPasting = action;
2534}
2535
2536int TerminalDisplay::motionAfterPasting()
2537{
2538 return mMotionAfterPasting;
2539}
2540
2541void TerminalDisplay::keyPressEvent(QKeyEvent *event)
2542{
2543 if(m_readOnly)
2544 return;
2545
2546 bool emitKeyPressSignal = true;
2547
2548 // Keyboard-based navigation
2549 if ( event->modifiers() == Qt::ShiftModifier )
2550 {
2551 bool update = true;
2552
2553 if ( event->key() == Qt::Key_PageUp )
2554 {
2555 _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 );
2556 }
2557 else if ( event->key() == Qt::Key_PageDown )
2558 {
2559 _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 );
2560 }
2561 else if ( event->key() == Qt::Key_Up )
2562 {
2563 _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 );
2564 }
2565 else if ( event->key() == Qt::Key_Down )
2566 {
2567 _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 );
2568 }
2569 else if ( event->key() == Qt::Key_End)
2570 {
2571 scrollToEnd();
2572 }
2573 else if ( event->key() == Qt::Key_Home)
2574 {
2575 _screenWindow->scrollTo(0);
2576 }
2577 else
2578 update = false;
2579
2580 if ( update )
2581 {
2582 _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
2583
2585 updateImage();
2586
2587 // do not send key press to terminal
2588 emitKeyPressSignal = false;
2589 }
2590 }
2591
2592 _actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't
2593 // know where the current selection is.
2594
2595 if (_hasBlinkingCursor) {
2596 _blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
2597 if (_cursorBlinking)
2598 blinkCursorEvent();
2599 else
2600 _cursorBlinking = false;
2601 }
2602
2603 if ( emitKeyPressSignal )
2604 {
2605 Q_EMIT keyPressedSignal(event, false);
2606
2607 if(event->modifiers().testFlag(Qt::ShiftModifier)
2608 || event->modifiers().testFlag(Qt::ControlModifier)
2609 || event->modifiers().testFlag(Qt::AltModifier))
2610 {
2611 switch(mMotionAfterPasting)
2612 {
2613 case MoveStartScreenWindow:
2614 _screenWindow->scrollTo(0);
2615 break;
2616 case MoveEndScreenWindow:
2617 scrollToEnd();
2618 break;
2619 case NoMoveScreenWindow:
2620 break;
2621 }
2622 }
2623 else
2624 {
2625 scrollToEnd();
2626 }
2627 }
2628
2629 event->accept();
2630}
2631
2632void TerminalDisplay::inputMethodEvent(QInputMethodEvent *event)
2633{
2634 QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString());
2635 Q_EMIT keyPressedSignal(&keyEvent, false);
2636
2637 _inputMethodData.preeditString = event->preeditString();
2638 update(preeditRect() | _inputMethodData.previousPreeditRect);
2639
2640 event->accept();
2641}
2642
2643void TerminalDisplay::inputMethodQuery(QInputMethodQueryEvent *event)
2644{
2645 event->setValue(Qt::ImEnabled, true);
2646 event->setValue(Qt::ImHints, QVariant(Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase));
2647 event->accept();
2648}
2649
2650QVariant TerminalDisplay::inputMethodQuery(Qt::InputMethodQuery query) const
2651{
2652 const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0, 0);
2653 switch (query) {
2655 return imageToWidget(QRect(cursorPos.x(), cursorPos.y(), 1, 1));
2656 break;
2657 case Qt::ImFont:
2658 return font();
2659 break;
2661 // return the cursor position within the current line
2662 return cursorPos.x();
2663 break;
2664 case Qt::ImSurroundingText: {
2665 // return the text from the current line
2666 QString lineText;
2667 QTextStream stream(&lineText);
2668 PlainTextDecoder decoder;
2669 decoder.begin(&stream);
2670 decoder.decodeLine(std::span(&_image[loc(0, cursorPos.y())], _usedColumns), _lineProperties[cursorPos.y()]);
2671 decoder.end();
2672 return lineText;
2673 } break;
2675 return QString();
2676 break;
2677 default:
2678 break;
2679 }
2680
2681 return QVariant();
2682}
2683
2684bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent *keyEvent)
2685{
2686 int modifiers = keyEvent->modifiers();
2687
2688 // When a possible shortcut combination is pressed,
2689 // Q_EMIT the overrideShortcutCheck() signal to allow the host
2690 // to decide whether the terminal should override it or not.
2691 if (modifiers != Qt::NoModifier) {
2692 int modifierCount = 0;
2693 unsigned int currentModifier = Qt::ShiftModifier;
2694
2695 while (currentModifier <= Qt::KeypadModifier) {
2696 if (modifiers & currentModifier)
2697 modifierCount++;
2698 currentModifier <<= 1;
2699 }
2700 if (modifierCount < 2) {
2701 bool override = false;
2702 Q_EMIT overrideShortcutCheck(keyEvent, override);
2703 if (override) {
2704 keyEvent->accept();
2705 return true;
2706 }
2707 }
2708 }
2709
2710 // Override any of the following shortcuts because
2711 // they are needed by the terminal
2712 int keyCode = keyEvent->key() | modifiers;
2713 switch (keyCode) {
2714 // list is taken from the QLineEdit::event() code
2715 case Qt::Key_Tab:
2716 case Qt::Key_Delete:
2717 case Qt::Key_Home:
2718 case Qt::Key_End:
2719 case Qt::Key_Backspace:
2720 case Qt::Key_Left:
2721 case Qt::Key_Right:
2722 case Qt::Key_Escape:
2723 keyEvent->accept();
2724 return true;
2725 }
2726 return false;
2727}
2728
2729bool TerminalDisplay::event(QEvent *event)
2730{
2731 bool eventHandled = false;
2732 switch (event->type()) {
2734 eventHandled = handleShortcutOverrideEvent((QKeyEvent *)event);
2735 break;
2738 _scrollBar->setPalette(QApplication::palette());
2739 break;
2741 inputMethodQuery(static_cast<QInputMethodQueryEvent *>(event));
2742 eventHandled = true;
2743 break;
2744 default:
2745 break;
2746 }
2747 return eventHandled ? true : QQuickItem::event(event);
2748}
2749
2751{
2752 _bellMode = mode;
2753}
2754
2755void TerminalDisplay::enableBell()
2756{
2757 _allowBell = true;
2758}
2759
2760void TerminalDisplay::bell(const QString &message)
2761{
2762 if (_bellMode == NoBell)
2763 return;
2764
2765 // limit the rate at which bells can occur
2766 //...mainly for sound effects where rapid bells in sequence
2767 // produce a horrible noise
2768 if (_allowBell) {
2769 _allowBell = false;
2770 QTimer::singleShot(500, this, SLOT(enableBell()));
2771
2772 if (_bellMode == SystemBeepBell) {
2774 } else if (_bellMode == NotifyBell) {
2775 Q_EMIT notifyBell(message);
2776 } else if (_bellMode == VisualBell) {
2777 swapColorTable();
2778 QTimer::singleShot(200, this, SLOT(swapColorTable()));
2779 }
2780 }
2781}
2782
2783void TerminalDisplay::selectionChanged()
2784{
2785 Q_EMIT copyAvailable(_screenWindow->selectedText(false).isEmpty() == false);
2786}
2787
2788void TerminalDisplay::swapColorTable()
2789{
2790 ColorEntry color = _colorTable[1];
2791 _colorTable[1] = _colorTable[0];
2792 _colorTable[0] = color;
2793 _colorsInverted = !_colorsInverted;
2794 update();
2795}
2796
2797void TerminalDisplay::clearImage()
2798{
2799 // We initialize _image[_imageSize] too. See makeImage()
2800 for (int i = 0; i <= _imageSize; i++) {
2801 _image[i].character = u' ';
2802 _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
2803 _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
2804 _image[i].rendition = DEFAULT_RENDITION;
2805 }
2806}
2807
2808void TerminalDisplay::calcGeometry()
2809{
2810 _scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
2811 int scrollBarWidth = _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar) ? 0 : _scrollBar->width();
2812 switch (_scrollbarLocation) {
2813 case QTermWidget::NoScrollBar:
2814 _leftMargin = _leftBaseMargin;
2815 _contentWidth = contentsRect().width() - 2 * _leftBaseMargin;
2816 break;
2817 case QTermWidget::ScrollBarLeft:
2818 _leftMargin = _leftBaseMargin + scrollBarWidth;
2819 _contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth;
2820 _scrollBar->move(contentsRect().topLeft());
2821 break;
2822 case QTermWidget::ScrollBarRight:
2823 _leftMargin = _leftBaseMargin;
2824 _contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth;
2825 _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width() - 1, 0));
2826 break;
2827 }
2828
2829 _topMargin = _topBaseMargin;
2830 _contentHeight = contentsRect().height() - 2 * _topBaseMargin + /* mysterious */ 1;
2831
2832 if (!_isFixedSize) {
2833 // ensure that display is always at least one column wide
2834 _columns = qMax(1, qRound(_contentWidth / _fontWidth));
2835 _usedColumns = qMin(_usedColumns, _columns);
2836
2837 // ensure that display is always at least one line high
2838 _lines = qMax(1, _contentHeight / qRound(_fontHeight));
2839 _usedLines = qMin(_usedLines, _lines);
2840 }
2841}
2842
2843void TerminalDisplay::makeImage()
2844{
2845 calcGeometry();
2846
2847 // confirm that array will be of non-zero size, since the painting code
2848 // assumes a non-zero array length
2849 Q_ASSERT(_lines > 0 && _columns > 0);
2850 Q_ASSERT(_usedLines <= _lines && _usedColumns <= _columns);
2851
2852 _imageSize = _lines * _columns;
2853
2854 // We over-commit one character so that we can be more relaxed in dealing with
2855 // certain boundary conditions: _image[_imageSize] is a valid but unused position
2856 _image.resize(_imageSize + 1);
2857
2858 clearImage();
2859}
2860
2861// calculate the needed size, this must be synced with calcGeometry()
2862void TerminalDisplay::setSize(int columns, int lines)
2863{
2864 int scrollBarWidth =
2865 (_scrollBar->isHidden() || _scrollBar->style()->styleHint(QStyle::SH_ScrollBar_Transient, nullptr, _scrollBar)) ? 0 : _scrollBar->sizeHint().width();
2866 int horizontalMargin = 2 * _leftBaseMargin;
2867 int verticalMargin = 2 * _topBaseMargin;
2868
2869 QSize newSize = QSize(horizontalMargin + scrollBarWidth + (columns * _fontWidth), verticalMargin + (lines * qRound(_fontHeight)));
2870
2871 if (newSize != size()) {
2872 _size = newSize;
2873 // updateGeometry();
2874 // TODO Manage geometry change
2875 }
2876}
2877
2878void TerminalDisplay::setFixedSize(int cols, int lins)
2879{
2880 _isFixedSize = true;
2881
2882 // ensure that display is at least one line by one column in size
2883 _columns = qMax(1, cols);
2884 _lines = qMax(1, lins);
2885 _usedColumns = qMin(_usedColumns, _columns);
2886 _usedLines = qMin(_usedLines, _lines);
2887
2888 if (!_image.empty()) {
2889 _image.clear();
2890 makeImage();
2891 }
2892 setSize(cols, lins);
2893 // QWidget::setFixedSize(_size);
2894}
2895
2896QSize TerminalDisplay::sizeHint() const
2897{
2898 return _size;
2899}
2900
2901/* --------------------------------------------------------------------- */
2902/* */
2903/* Drag & Drop */
2904/* */
2905/* --------------------------------------------------------------------- */
2906
2907void TerminalDisplay::dragEnterEvent(QDragEnterEvent *event)
2908{
2909 if (event->mimeData()->hasFormat(QLatin1String("text/plain")))
2910 event->acceptProposedAction();
2911 if (event->mimeData()->urls().size())
2912 event->acceptProposedAction();
2913}
2914
2915void TerminalDisplay::dropEvent(QDropEvent *event)
2916{
2917 // KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
2918 QList<QUrl> urls = event->mimeData()->urls();
2919
2920 QString dropText;
2921 if (!urls.isEmpty()) {
2922 // TODO/FIXME: escape or quote pasted things if neccessary...
2923 qDebug() << "TerminalDisplay: handling urls. It can be broken. Report any errors, please";
2924 for (int i = 0; i < urls.size(); i++) {
2925 // KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
2926 QUrl url = urls[i];
2927
2928 QString urlText;
2929
2930 if (url.isLocalFile())
2931 urlText = url.path();
2932 else
2933 urlText = url.toString();
2934
2935 // in future it may be useful to be able to insert file names with drag-and-drop
2936 // without quoting them (this only affects paths with spaces in)
2937 // urlText = KShell::quoteArg(urlText);
2938
2939 dropText += urlText;
2940
2941 if (i != urls.size() - 1)
2942 dropText += QLatin1Char(' ');
2943 }
2944 } else {
2945 dropText = event->mimeData()->text();
2946 }
2947
2948 Q_EMIT sendStringToEmu(dropText.toLocal8Bit().constData());
2949}
2950
2951void TerminalDisplay::doDrag()
2952{
2953 dragInfo.state = diDragging;
2954 dragInfo.dragObject = new QDrag(this);
2955 QMimeData *mimeData = new QMimeData;
2957 dragInfo.dragObject->setMimeData(mimeData);
2958 dragInfo.dragObject->exec(Qt::CopyAction);
2959 // Don't delete the QTextDrag object. Qt will delete it when it's done with it.
2960}
2961
2963{
2964 _outputSuspendedLabel->setVisible(suspended);
2965}
2966
2967uint TerminalDisplay::lineSpacing() const
2968{
2969 return _lineSpacing;
2970}
2971
2972void TerminalDisplay::setLineSpacing(uint i)
2973{
2974 if (i != _lineSpacing) {
2975 _lineSpacing = i;
2976 setVTFont(font()); // Trigger an update.
2977 Q_EMIT lineSpacingChanged();
2978 }
2979}
2980
2981int TerminalDisplay::margin() const
2982{
2983 return _topBaseMargin;
2984}
2985
2986void TerminalDisplay::setMargin(int i)
2987{
2988 _topBaseMargin = i;
2989 _leftBaseMargin = i;
2990}
2991
2992// QMLTermWidget specific functions ///////////////////////////////////////////
2993
2994#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
2995void TerminalDisplay::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry)
2996#else
2997void TerminalDisplay::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
2998#endif
2999{
3000 if (newGeometry != oldGeometry) {
3001 resizeEvent(nullptr);
3002 update();
3003 }
3004
3005 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3006 QQuickPaintedItem::geometryChanged(newGeometry, oldGeometry);
3007 #else
3008 QQuickPaintedItem::geometryChange(newGeometry, oldGeometry);
3009 #endif
3010}
3011
3012void TerminalDisplay::update(const QRegion &region)
3013{
3014 Q_UNUSED(region);
3015 // TODO this function might be optimized
3016 // const rects = region.rects();
3017 // for (QRect rect : rects) {
3018 // QQuickPaintedItem::update(rect.adjusted(-1, -1, +1, +1));
3019 // }
3020 QQuickPaintedItem::update(region.boundingRect().adjusted(-1, -1, +1, +1));
3021
3022 Q_EMIT imagePainted();
3023}
3024
3025void TerminalDisplay::update()
3026{
3027 QQuickPaintedItem::update(contentsRect());
3028}
3029
3030QRect TerminalDisplay::contentsRect() const
3031{
3032 return QRect(0, 0, this->width(), this->height());
3033}
3034
3035QSize TerminalDisplay::size() const
3036{
3037 return QSize(this->width(), this->height());
3038}
3039
3040void TerminalDisplay::setSession(KSession *session)
3041{
3042 if (m_session != session) {
3043 // qDebug() << "SetSession called";
3044 // if (m_session)
3045 // m_session->removeView(this);
3046
3047 m_session = session;
3048
3049 connect(this, &TerminalDisplay::copyAvailable, m_session, &KSession::selectionChanged);
3050 connect(this, &TerminalDisplay::termGetFocus, m_session, &KSession::termGetFocus);
3051 connect(this, &TerminalDisplay::termLostFocus, m_session, &KSession::termLostFocus);
3053
3054 m_session->addView(this);
3055
3056 setRandomSeed(m_session->getRandomSeed());
3057 update();
3058 Q_EMIT sessionChanged();
3059 }
3060}
3061
3062KSession *TerminalDisplay::getSession()
3063{
3064 return m_session;
3065}
3066
3067QStringList TerminalDisplay::availableColorSchemes()
3068{
3069 QStringList ret;
3070 const auto colorSchemes = ColorSchemeManager::instance()->allColorSchemes();
3071 std::transform(colorSchemes.begin(), colorSchemes.end(), std::back_inserter(ret), [](auto cs) {
3072 return cs->name();
3073 });
3074 return ret;
3075}
3076
3077void TerminalDisplay::setColorScheme(const QString &name)
3078{
3079 if (name != _colorScheme)
3080 {
3081 if (m_scheme)
3082 {
3083 disconnect(m_scheme, nullptr, this, nullptr);
3084 }
3085
3086 m_scheme = [&, this]()
3087 {
3088 if(name == "Adaptive")
3089 {
3090 return m_customColorScheme->getScheme();
3091 }else
3092 {
3093 // avoid legacy (int) solution
3094 if (!availableColorSchemes().contains(name))
3096 else
3098 }
3099 }();
3100
3101 if (!m_scheme)
3102 {
3103 qDebug() << "Cannot load color scheme: " << name;
3104 return;
3105 }
3106
3107 connect(m_scheme, SIGNAL(colorChanged(int)), this, SLOT(applyColorScheme()));
3108 applyColorScheme();
3109
3110 _colorScheme = name;
3111 Q_EMIT colorSchemeChanged();
3112
3113 }
3114}
3115
3116void TerminalDisplay::applyColorScheme()
3117{
3118 qDebug() << "Colors CHANGED";
3119 if (!m_scheme)
3120 {
3121 qDebug() << "Cannot apply color scheme";
3122 return;
3123 }
3124
3125 setColorTable(m_scheme->getColorTable());
3126
3127 // QColor backgroundColor = m_scheme->backgroundColor();
3128 // setBackgroundColor(backgroundColor);
3129 qDebug() << "Colors CHANGED" << m_scheme->backgroundColor() << this->backgroundColor();
3130
3131 QColor nColor = m_scheme->backgroundColor();
3132 nColor.setAlphaF(_opacity);
3133 setFillColor(nColor);
3134 update();
3135
3136 Q_EMIT backgroundColorChanged();
3137 Q_EMIT foregroundColorChanged();
3138}
3139
3140QString TerminalDisplay::colorScheme() const
3141{
3142 return _colorScheme;
3143}
3144
3145void TerminalDisplay::simulateKeyPress(int key, int modifiers, bool pressed, quint32 nativeScanCode, const QString &text)
3146{
3147 Q_UNUSED(nativeScanCode);
3149 QKeyEvent event = QKeyEvent(type, key, (Qt::KeyboardModifier)modifiers, text);
3150 keyPressedSignal(&event, false);
3151}
3152
3153void TerminalDisplay::simulateKeySequence(const QKeySequence &keySequence)
3154{
3155 for (int i = 0; i < keySequence.count(); ++i) {
3156 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
3157 const Qt::Key key = Qt::Key(keySequence[i] & ~Qt::KeyboardModifierMask);
3158 const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i] & Qt::KeyboardModifierMask);
3159 #else
3160 const Qt::Key key = Qt::Key(keySequence[i].key());
3161 const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i].keyboardModifiers());
3162 #endif
3163 QKeyEvent eventPress = QKeyEvent(QEvent::KeyPress, key, modifiers, QString());
3164 keyPressedSignal(&eventPress, false);
3165 }
3166}
3167
3168void TerminalDisplay::simulateWheel(int x, int y, int buttons, int modifiers, QPoint angleDelta)
3169{
3170 QPoint pixelDelta; // pixelDelta is optional and can be null.
3171 QWheelEvent event(QPointF(x, y),
3172 mapToGlobal(QPointF(x, y)),
3173 pixelDelta,
3174 angleDelta,
3175 (Qt::MouseButtons)buttons,
3176 (Qt::KeyboardModifiers)modifiers,
3178 false);
3179 wheelEvent(&event);
3180}
3181
3182void TerminalDisplay::simulateMouseMove(int x, int y, int button, int buttons, int modifiers)
3183{
3184 QMouseEvent event(QEvent::MouseMove, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3185 mouseMoveEvent(&event);
3186}
3187
3188void TerminalDisplay::simulateMousePress(int x, int y, int button, int buttons, int modifiers)
3189{
3190 QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3191 mousePressEvent(&event);
3192}
3193
3194void TerminalDisplay::simulateMouseRelease(int x, int y, int button, int buttons, int modifiers)
3195{
3196 QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3197 mouseReleaseEvent(&event);
3198}
3199
3200void TerminalDisplay::simulateMouseDoubleClick(int x, int y, int button, int buttons, int modifiers)
3201{
3202 QMouseEvent event(QEvent::MouseButtonDblClick, QPointF(x, y), (Qt::MouseButton)button, (Qt::MouseButtons)buttons, (Qt::KeyboardModifiers)modifiers);
3203 mouseDoubleClickEvent(&event);
3204}
3205
3206QSize TerminalDisplay::getTerminalSize()
3207{
3208 return QSize(_lines, _columns);
3209}
3210
3211bool TerminalDisplay::getUsesMouse()
3212{
3213 return !usesMouse();
3214}
3215
3216int TerminalDisplay::getScrollbarValue()
3217{
3218 return _scrollBar->value();
3219}
3220
3221void TerminalDisplay::setScrollbarValue(int value)
3222{
3223 if (value != _scrollBar->value())
3224 {
3225 _scrollBar->setValue(value);
3226 }
3227}
3228
3229int TerminalDisplay::getScrollbarMaximum()
3230{
3231 return _scrollBar->maximum();
3232}
3233
3234int TerminalDisplay::getScrollbarMinimum()
3235{
3236 return _scrollBar->minimum();
3237}
3238
3239QSize TerminalDisplay::getFontMetrics()
3240{
3241 return QSize(qRound(_fontWidth), qRound(_fontHeight));
3242}
3243
3244void TerminalDisplay::setFullCursorHeight(bool val)
3245{
3246 if (m_full_cursor_height != val) {
3247 m_full_cursor_height = val;
3248 Q_EMIT fullCursorHeightChanged();
3249 }
3250}
3251
3252bool TerminalDisplay::fullCursorHeight() const
3253{
3254 return m_full_cursor_height;
3255}
3256
3257void TerminalDisplay::itemChange(ItemChange change, const ItemChangeData &value)
3258{
3259 switch (change) {
3261 if (value.boolValue && _screenWindow) {
3262 if (this->columns() != _screenWindow->columnCount() || this->lines() != _screenWindow->lineCount()) {
3263 Q_EMIT changedContentSizeSignal(_contentHeight, _contentWidth);
3264 }
3265 }
3266 break;
3267 default:
3268 break;
3269 }
3270
3271 QQuickPaintedItem::itemChange(change, value);
3272}
3273
3274qreal TerminalDisplay::backgroundOpacity() const
3275{
3276 return _opacity;
3277}
3278
3279CustomColorScheme *TerminalDisplay::customColorScheme() const
3280{
3281 return m_customColorScheme;
3282}
3283
3284bool TerminalDisplay::readOnly() const
3285{
3286 return m_readOnly;
3287}
3288
3289void TerminalDisplay::setReadOnly(bool newReadOnly)
3290{
3291 if (m_readOnly == newReadOnly)
3292 return;
3293 m_readOnly = newReadOnly;
3294 Q_EMIT readOnlyChanged();
3295}
3296
3297bool TerminalDisplay::isTextSelected() const
3298{
3299 if (!_screenWindow)
3300 return false;
3301
3302 return !_screenWindow->selectedText(false).isEmpty();
3303}
3304
3305QString TerminalDisplay::selectedText() const
3306{
3307 if (!_screenWindow)
3308 return "";
3309
3310 return _screenWindow->selectedText(_preserveLineBreaks);
3311}
3312
3313void TerminalDisplay::findNext(const QString& regexp)
3314{
3315 // int startColumn, startLine;
3316 //
3317 // screenWindow()->screen()->getSelectionEnd(startColumn, startLine);
3318 // startColumn++;
3319 //
3320 // HistorySearch *history = new HistorySearch( QPointer<Emulation>(m_session->emulation()), QRegExp(regexp), forwards, startColumn, startLine, this);
3321 // connect( history, SIGNAL(matchFound(int,int,int,int)), this, SIGNAL(matchFound(int,int,int,int)));
3322 // connect( history, SIGNAL(noMatchFound()), this, SIGNAL(noMatchFound()));
3323 // history->search();
3324}
3325
3326void TerminalDisplay::findPrevious(const QString& regexp)
3327{
3328}
3329
3330void TerminalDisplay::matchFound(int startColumn, int startLine, int endColumn, int endLine)
3331{
3332 ScreenWindow* sw = screenWindow();
3333 qDebug() << "Scroll to" << startLine;
3334 sw->scrollTo(startLine);
3335 sw->setTrackOutput(false);
3336 sw->notifyOutputChanged();
3337 sw->setSelectionStart(startColumn, startLine - sw->currentLine(), false);
3338 sw->setSelectionEnd(endColumn, endLine - sw->currentLine());
3339}
3340
3341void TerminalDisplay::noMatchFound()
3342{
3344}
void termLostFocus()
termLostFocus
void termGetFocus()
termGetFocus
void termKeyPressed(QKeyEvent *, bool)
termKeyPressed
Describes the color of a single character in the terminal.
constexpr QColor color(std::span< const ColorEntry > palette) const
Returns the color within the specified color palette.
A single character in the terminal which consists of a unicode character value, foreground and backgr...
Definition Character.h:63
QChar character
The unicode character value for this character.
Definition Character.h:86
CharacterColor foregroundColor
The foreground color used to draw this character.
Definition Character.h:101
CharacterColor backgroundColor
The color used to draw this character's background.
Definition Character.h:103
quint8 rendition
A combination of RENDITION flags which specify options for drawing the character.
Definition Character.h:98
An entry in a terminal display's color palette.
QList< ColorScheme * > allColorSchemes()
Returns a list of the all the available color schemes.
static ColorSchemeManager * instance()
Returns the global color scheme manager instance.
const ColorScheme * defaultColorScheme() const
Returns the default color scheme for Konsole.
const ColorScheme * findColorScheme(const QString &name)
Returns the color scheme with the given name or 0 if no scheme with that name exists.
Base class for terminal emulation back-ends.
Definition Emulation.h:120
KeyboardCursorShape
This enum describes the available shapes for the keyboard cursor.
Definition Emulation.h:128
@ UnderlineCursor
A single flat line which occupies the space at the bottom of the cursor character's area.
Definition Emulation.h:135
@ BlockCursor
A rectangular block which covers the entire area of the cursor character.
Definition Emulation.h:130
@ IBeamCursor
An cursor shaped like the capital letter 'I', similar to the IBeam cursor used in Qt/KDE text editors...
Definition Emulation.h:140
static ExtendedCharTable instance
The global ExtendedCharTable instance.
Definition Character.h:203
std::span< const ushort > lookupExtendedChar(ushort hash, ushort &length) const
Looks up and returns a pointer to a sequence of unicode characters which was added to the table using...
A chain which allows a group of filters to be processed as one.
Definition Filter.h:320
Represents an area of text which matched the pattern a particular filter has been looking for.
Definition Filter.h:69
virtual void activate(const QString &action=QString())=0
Causes the an action associated with a hotspot to be triggered.
int endLine() const
Returns the line where the hotspot area ends.
Definition Filter.cpp:283
int startLine() const
Returns the line when the hotspot area starts.
Definition Filter.cpp:279
int endColumn() const
Returns the column on endLine() where the hotspot area ends.
Definition Filter.cpp:291
virtual QList< QAction * > actions()
Returns a list of actions associated with the hotspot which can be used in a menu or toolbar.
Definition Filter.cpp:275
int startColumn() const
Returns the column on startLine() where the hotspot area starts.
Definition Filter.cpp:287
Type type() const
Returns the type of the hotspot.
Definition Filter.cpp:295
void decodeLine(std::span< const Character > characters, LineProperty properties) override
Converts a line of terminal characters with associated properties into a text string and writes the s...
void end() override
End decoding.
void begin(QTextStream *output) override
Begin decoding characters.
Provides a window onto a section of a terminal screen.
void setSelectionStart(int column, int line, bool columnMode)
Sets the start of the selection to the given line and column within the window.
void setSelectionEnd(int column, int line)
Sets the end of the selection to the given line and column within the window.
@ ScrollLines
Scroll the window down by a given number of lines.
@ ScrollPages
Scroll the window down by a given number of pages, where one page is windowLines() lines.
void setTrackOutput(bool trackOutput)
Specifies whether the window should automatically move to the bottom of the screen when new output is...
int currentLine() const
Returns the index of the line which is currently at the top of this window.
void clearSelection()
Clears the current selection.
void selectionChanged()
Emitted when the selection is changed.
void notifyOutputChanged()
Notifies the window that the contents of the associated terminal screen have changed.
void scrollTo(int line)
Scrolls the window so that line is at the top of the window.
void outputChanged()
Emitted when the contents of the associated terminal screen (see screen()) changes.
void setKeyboardCursorColor(bool useForegroundColor, const QColor &color)
Sets the color used to draw the keyboard cursor.
CustomColorScheme * customColorScheme
Access to the CustomColorScheme object, which allows to modify manually the colors.
void setUsesMouse(bool usesMouse)
Sets whether the program whoose output is being displayed in the view is interested in mouse events.
bool usesMouse() const
See setUsesMouse()
bool readOnly
A read only mode prevents the user from sending keyevents.
void updateFilters()
Essentially calles processFilters().
void bell(const QString &message)
Shows a notification that a bell event has occurred in the terminal.
void bracketText(QString &text) const
change and wrap text corresponding to paste mode
void outputSuspended(bool suspended)
Causes the widget to display or hide a message informing the user that terminal output has been suspe...
void pasteSelection()
Pastes the content of the selection into the display.
void setBlinkingCursor(bool blink)
Specifies whether or not the cursor blinks.
KSession * session
Register the session to handle.
void setBlinkingTextEnabled(bool blink)
Specifies whether or not text can blink.
void keyPressedSignal(QKeyEvent *e, bool fromPaste)
Emitted when the user presses a key whilst the terminal widget has focus.
void pasteClipboard()
Pastes the content of the clipboard into the display.
void updateLineProperties()
Causes the terminal display to fetch the latest line status flags from the associated terminal screen...
void setWordCharacters(const QString &wc)
Sets which characters, in addition to letters and numbers, are regarded as being part of a word for t...
void setRandomSeed(uint seed)
Sets the seed used to generate random colors for the display (in color schemes that support them).
ScreenWindow * screenWindow() const
Returns the terminal screen section which is displayed in this widget.
void setBellMode(int mode)
Sets the type of effect used to alert the user when a 'bell' occurs in the terminal session.
QColor keyboardCursorColor() const
Returns the color of the keyboard cursor, or an invalid color if the keyboard cursor color is set to ...
TerminalDisplay(QQuickItem *parent=nullptr)
Constructs a new terminal display widget with the specified parent.
void configureRequest(const QPoint &position)
Emitted when the user right clicks on the display, or right-clicks with the Shift key held down if us...
void setForegroundColor(const QColor &color)
Sets the text of the display to the specified color.
void setScrollBarPosition(QTermWidget::ScrollBarPosition position)
Specifies whether the terminal display has a vertical scroll bar, and if so whether it is shown on th...
void setBackgroundColor(const QColor &color)
Sets the background of the display to the specified color.
void setFlowControlWarningEnabled(bool enabled)
Changes whether the flow control warning box should be shown when the flow control stop key (Ctrl+S) ...
void processFilters()
Updates the filters in the display's filter chain.
void scrollToEnd()
Scroll to the bottom of the terminal (reset scrolling).
void setKeyboardCursorShape(Emulation::KeyboardCursorShape shape)
Sets the shape of the keyboard cursor.
@ NotifyBell
KDE notification.
@ SystemBeepBell
A system beep.
@ VisualBell
A silent, visual bell (eg.
@ NoScrollBar
Do not show the scroll bar.
void setBackgroundOpacity(qreal backgroundOpacity)
Sets the backgroundOpacity of the terminal display.
QList< QAction * > filterActions(const QPoint &position)
Returns a list of menu actions created by the filters for the content at the given position.
void updateImage()
Causes the terminal display to fetch the latest character image from the associated terminal screen (...
FilterChain * filterChain() const
Returns the display's filter chain.
void mouseSignal(int button, int column, int line, int eventType)
A mouse event occurred.
void overrideShortcutCheck(QKeyEvent *keyEvent, bool &override)
When a shortcut which is also a valid terminal key sequence is pressed while the terminal widget has ...
@ SelectWholeLine
Select the whole line underneath the cursor.
@ SelectForwardsFromCursor
Select from the current cursor position to the end of the line.
void setColorTable(std::array< ColorEntry, TABLE_COLORS > &&table)
Sets the terminal color palette used by the display.
void setVTFont(const QFont &font)
Sets the font used to draw the display.
void copyClipboard()
Copies the selected text to the clipboard.
void setBoldIntense(bool value)
Specifies whether characters with intense colors should be rendered as bold.
std::span< const ColorEntry > colorTable() const
Returns the terminal color palette used by the display.
Emulation::KeyboardCursorShape keyboardCursorShape() const
Returns the shape of the keyboard cursor.
uint randomSeed() const
Returns the seed used to generate random colors for the display (in color schemes that support them).
void setScroll(int cursor, int lines)
Sets the current position and range of the display's scroll bar.
void setScreenWindow(ScreenWindow *window)
Sets the terminal screen section which is displayed in this widget.
A filter chain which processes character images from terminal displays.
Definition Filter.h:351
Type type(const QSqlDatabase &db)
void update(Part *part, const QByteArray &data, qint64 dataSize)
QString name(const QVariant &location)
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
void valueChanged(int value)
QPalette palette(const QWidget *widget)
const char * constData() const const
bool isLetterOrNumber(char32_t ucs4)
bool isSpace(char32_t ucs4)
char16_t & unicode()
void setText(const QString &text, Mode mode)
QString text(Mode mode) const const
void setAlphaF(float alpha)
QPoint pos()
void setStyleStrategy(StyleStrategy s)
QClipboard * clipboard()
QPalette palette()
Qt::KeyboardModifiers modifiers() const const
bool isEmpty() const const
qsizetype size() const const
void setText(const QString &text)
QPoint pos() const const
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
CompositionMode_Source
QRectF clipBoundingRect() const const
void drawArc(const QRect &rectangle, int startAngle, int spanAngle)
void drawLine(const QLine &line)
void drawPoint(const QPoint &position)
void drawRect(const QRect &rectangle)
void drawText(const QPoint &position, const QString &text)
void fillRect(const QRect &rectangle, QGradient::Preset preset)
const QFont & font() const const
const QPen & pen() const const
void restore()
void save()
void setCompositionMode(CompositionMode mode)
void setFont(const QFont &font)
void setLayoutDirection(Qt::LayoutDirection direction)
void setPen(Qt::PenStyle style)
void setColor(ColorGroup group, ColorRole role, const QColor &color)
QColor color() const const
void setColor(const QColor &color)
int width() const const
int & rx()
int & ry()
void setX(int x)
void setY(int y)
int x() const const
int y() const const
QPoint toPoint() const const
qreal x() const const
qreal y() const const
QQuickItem(QQuickItem *parent)
bool hasActiveFocus() const const
virtual QRectF clipRect() const const
virtual bool contains(const QPointF &point) const const
QCursor cursor() const const
virtual bool event(QEvent *ev) override
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
QPointF mapFromScene(const QPointF &point) const const
QPointF mapToGlobal(const QPointF &point) const const
void setAcceptedMouseButtons(Qt::MouseButtons buttons)
void setCursor(const QCursor &cursor)
void setFlags(Flags flags)
void update()
QQuickWindow * window() const const
QQuickPaintedItem(QQuickItem *parent)
void setFillColor(const QColor &)
virtual void itemChange(ItemChange change, const ItemChangeData &value) override
void setRenderTarget(RenderTarget target)
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
int bottom() const const
int height() const const
bool isEmpty() const const
bool isValid() const const
int left() const const
void moveTopLeft(const QPoint &position)
int right() const const
void setBottom(int y)
void setCoords(int x1, int y1, int x2, int y2)
void setHeight(int height)
void setLeft(int x)
void setRight(int x)
void setTop(int y)
void setWidth(int width)
int top() const const
QPoint topLeft() const const
int width() const const
int x() const const
int y() const const
QRect toAlignedRect() const const
QRect boundingRect() const const
bool contains(const QPoint &p) const const
Qt::MouseButton button() const const
Qt::MouseButtons buttons() const const
QPointF position() const const
QString & append(QChar ch)
bool isEmpty() const const
QString & prepend(QChar ch)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
void reserve(qsizetype size)
void resize(qsizetype newSize, QChar fillChar)
QByteArray toLocal8Bit() const const
qsizetype size() const const
SH_ScrollBar_Transient
virtual int styleHint(StyleHint hint, const QStyleOption *option, const QWidget *widget, QStyleHintReturn *returnData) const const=0
CaseInsensitive
ArrowCursor
CopyAction
ImhNoPredictiveText
ImEnabled
ControlModifier
LeftToRight
LeftButton
ScrollBegin
WA_DontShowOnScreen
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
void keyEvent(KeyAction action, QWidget *widget, Qt::Key key, Qt::KeyboardModifiers modifier, int delay)
void keySequence(QWidget *widget, const QKeySequence &keySequence)
void start()
void stop()
void timeout()
QTransform inverted(bool *invertible) const const
QLine map(const QLine &l) const const
QTransform & scale(qreal sx, qreal sy)
bool isLocalFile() const const
QString path(ComponentFormattingOptions options) const const
QString toString(FormattingOptions options) const const
QPoint angleDelta() const const
void hide()
bool isHidden() const const
QStyle * style() const const
bool isVisible() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:50:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.