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