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
75using 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/
87const 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
103ScreenWindow* TerminalDisplay::screenWindow() const
104{
105 return _screenWindow;
106}
107void 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
124const ColorEntry* TerminalDisplay::colorTable() const
125{
126 return _colorTable;
127}
128void 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}
141QColor TerminalDisplay::getBackgroundColor() const
142{
143 QPalette p = palette();
144 return p.color(backgroundRole());
145}
146void TerminalDisplay::setForegroundColor(const QColor& color)
147{
148 _colorTable[DEFAULT_FORE_COLOR].color = color;
149
150 update();
151}
152void 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
166static 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
174void 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
205void 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
234void TerminalDisplay::setFont(const QFont &)
235{
236 // ignore font change request if not coming from konsole itself
237}
238
239void TerminalDisplay::increaseFontSize()
240{
241 QFont font = getVTFont();
242 font.setPointSizeF(font.pointSizeF() + 1);
243 setVTFont(font);
244}
245
246void 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
255uint TerminalDisplay::lineSpacing() const
256{
257 return _lineSpacing;
258}
259
260void TerminalDisplay::setLineSpacing(uint i)
261{
262 _lineSpacing = i;
263 setVTFont(font()); // Trigger an update.
264}
265
266
267/* ------------------------------------------------------------------------- */
268/* */
269/* Accessibility */
270/* */
271/* ------------------------------------------------------------------------- */
272
273namespace Konsole
274{
275/**
276 * This function installs the factory function which lets Qt instantiate the QAccessibleInterface
277 * for the TerminalDisplay.
278 */
279QAccessibleInterface* 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
294TerminalDisplay::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
410TerminalDisplay::~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
441where _ = none
442 | = vertical line.
443 - = horizontal line.
444 */
445
446enum 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
474static 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
539void 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
559void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
560{
561 _cursorShape = shape;
562}
563Enum::CursorShapeEnum TerminalDisplay::keyboardCursorShape() const
564{
565 return _cursorShape;
566}
567void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
568{
569 _cursorColor = color;
570}
571QColor TerminalDisplay::keyboardCursorColor() const
572{
573 return _cursorColor;
574}
575
576void 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
596void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p)
597{
598 _wallpaper = p;
599}
600
601void 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
640void 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
689void 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
758void 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
786void 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
805void TerminalDisplay::setRandomSeed(uint randomSeed)
806{
807 _randomSeed = randomSeed;
808}
809uint 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
822void 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
910QRegion 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
944void 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
967void 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
1146void 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
1171void 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
1185void 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
1206QPoint TerminalDisplay::cursorPosition() const
1207{
1208 if (_screenWindow)
1209 return _screenWindow->cursorPosition();
1210 else
1211 return QPoint(0, 0);
1212}
1213
1214FilterChain* TerminalDisplay::filterChain() const
1215{
1216 return _filterChain;
1217}
1218
1219void 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}
1329void 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
1487void 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
1498QRect 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
1515void 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
1535void 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
1548void 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
1570void TerminalDisplay::focusInEvent(QFocusEvent*)
1571{
1572 if (_allowBlinkingCursor)
1573 _blinkCursorTimer->start();
1574
1575 updateCursor();
1576
1577 if (_allowBlinkingText && _hasTextBlinker)
1578 _blinkTextTimer->start();
1579}
1580
1581void 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
1592void TerminalDisplay::blinkCursorEvent()
1593{
1594 Q_ASSERT(_allowBlinkingCursor);
1595
1596 _cursorBlinking = !_cursorBlinking;
1597 updateCursor();
1598}
1599
1600void 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
1612void TerminalDisplay::resizeEvent(QResizeEvent*)
1613{
1614 updateImageSize();
1615}
1616
1617void TerminalDisplay::propagateSize()
1618{
1619 if (_image)
1620 updateImageSize();
1621}
1622
1623void 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
1656void 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
1676void TerminalDisplay::clearImage()
1677{
1678 for (int i = 0; i <= _imageSize; ++i)
1679 _image[i] = Screen::DefaultChar;
1680}
1681
1682void 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()
1715void 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
1730QSize 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
1740void TerminalDisplay::showEvent(QShowEvent*)
1741{
1742 emit changedContentSizeSignal(_contentRect.height(), _contentRect.width());
1743}
1744void TerminalDisplay::hideEvent(QHideEvent*)
1745{
1746 emit changedContentSizeSignal(_contentRect.height(), _contentRect.width());
1747}
1748
1749void TerminalDisplay::setMargin(int margin)
1750{
1751 _margin = margin;
1752 updateImageSize();
1753}
1754
1755void TerminalDisplay::setCenterContents(bool enable)
1756{
1757 _centerContents = enable;
1758 calcGeometry();
1759 update();
1760}
1761
1762/* ------------------------------------------------------------------------- */
1763/* */
1764/* Scrollbar */
1765/* */
1766/* ------------------------------------------------------------------------- */
1767
1768void 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
1784void 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
1801void 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
1822void TerminalDisplay::setScrollFullPage(bool fullPage)
1823{
1824 _scrollFullPage = fullPage;
1825}
1826
1827bool TerminalDisplay::scrollFullPage() const
1828{
1829 return _scrollFullPage;
1830}
1831
1832/* ------------------------------------------------------------------------- */
1833/* */
1834/* Mouse */
1835/* */
1836/* ------------------------------------------------------------------------- */
1837void 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
1915QList<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
1925void 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
2029void 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
2038void 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
2191void 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
2233void 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
2255void TerminalDisplay::updateLineProperties()
2256{
2257 if (!_screenWindow)
2258 return;
2259
2260 _lineProperties = _screenWindow->getLineProperties();
2261}
2262
2263void 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
2284void 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
2330void 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
2392void TerminalDisplay::tripleClickTimeout()
2393{
2394 _possibleTripleClick = false;
2395}
2396
2397void 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*/
2406QPoint 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*/
2438QPoint 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
2463QPoint 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 }
2509out:
2510 if (tmp_image) {
2511 delete[] tmp_image;
2512 }
2513 return QPoint(x, y - curLine);
2514}
2515
2516QPoint 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 }
2568out:
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
2586void 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
2597void 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
2625void TerminalDisplay::selectCurrentLine()
2626{
2627 if (!_screenWindow) return;
2628
2629 selectLine(cursorPosition(), true);
2630}
2631
2632bool 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
2642QChar 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
2668void 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.
2676void TerminalDisplay::setUsesMouse(bool on)
2677{
2678 _mouseMarks = on;
2679 setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor);
2680}
2681bool TerminalDisplay::usesMouse() const
2682{
2683 return _mouseMarks;
2684}
2685
2686void TerminalDisplay::setBracketedPasteMode(bool on)
2687{
2688 _bracketedPasteMode = on;
2689}
2690bool TerminalDisplay::bracketedPasteMode() const
2691{
2692 return _bracketedPasteMode;
2693}
2694
2695/* ------------------------------------------------------------------------- */
2696/* */
2697/* Clipboard */
2698/* */
2699/* ------------------------------------------------------------------------- */
2700
2701void 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
2733void TerminalDisplay::setAutoCopySelectedText(bool enabled)
2734{
2735 _autoCopySelectedText = enabled;
2736}
2737
2738void TerminalDisplay::setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum mode)
2739{
2740 _middleClickPasteMode = mode;
2741}
2742
2743void 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
2758void 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
2770void TerminalDisplay::pasteFromClipboard(bool appendEnter)
2771{
2772 QString text = QApplication::clipboard()->text(QClipboard::Clipboard);
2773 doPaste(text, appendEnter);
2774}
2775
2776void 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
2788void 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
2801QVariant 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
2836QRect 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
2849void 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
2874void 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
2884void 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
2928void TerminalDisplay::dismissOutputSuspendedMessage()
2929{
2930 outputSuspended(false);
2931}
2932
2933void 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
2942void 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
2969bool 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
3016bool 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
3033void TerminalDisplay::contextMenuEvent(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
3047void TerminalDisplay::setBellMode(int mode)
3048{
3049 _bellMode = mode;
3050}
3051
3052int TerminalDisplay::bellMode() const
3053{
3054 return _bellMode;
3055}
3056
3057void TerminalDisplay::unmaskBell()
3058{
3059 _bellMasked = false;
3060}
3061
3062void 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
3093void TerminalDisplay::visualBell()
3094{
3095 swapFGBGColors();
3096 QTimer::singleShot(200, this, SLOT(swapFGBGColors()));
3097}
3098
3099void 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
3115void 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
3127void 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
3200void TerminalDisplay::dropMenuPasteActionTriggered()
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
3210void TerminalDisplay::dropMenuCdActionTriggered()
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
3220void 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
3230void TerminalDisplay::setSessionController(SessionController* controller)
3231{
3232 _sessionController = controller;
3233}
3234
3235SessionController* TerminalDisplay::sessionController()
3236{
3237 return _sessionController;
3238}
3239
3240AutoScrollHandler::AutoScrollHandler(QWidget* parent)
3241 : QObject(parent)
3242 , _timerId(0)
3243{
3244 parent->installEventFilter(this);
3245}
3246void 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}
3259bool 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