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