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

jenkinsmain
Last change on this file since c0cec9d was fedf2a2, checked in by Vadim Troshchinskiy Shmelev <vtroshchinskiy@…>, 18 months ago

Update Qtermwidget to Qt6 version
Remove build files

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