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