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