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

KDE's Doxygen guidelines are available online.