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

KDE's Doxygen guidelines are available online.