source: ogBrowser-Git/qtermwidget/lib/TerminalDisplay.cpp @ ffbf8ac

jenkinsmain
Last change on this file since ffbf8ac was 64efc22, checked in by Vadim Troshchinskiy <vtroshchinskiy@…>, 19 months ago

Update qtermwidget to modern version

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