Fix crash if key bindings specified in profile cannot be found. Improve
[personal-kdebase.git] / apps / konsole / src / TerminalDisplay.cpp
blobe39918adf90440c350e9006be17f97bae8d46b4b
1 /*
2 This file is part of Konsole, a terminal emulator for KDE.
4 Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
5 Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
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.
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.
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.
23 // Own
24 #include "TerminalDisplay.h"
26 // Qt
27 #include <QtGui/QApplication>
28 #include <QtGui/QBoxLayout>
29 #include <QtGui/QClipboard>
30 #include <QtGui/QKeyEvent>
31 #include <QtCore/QEvent>
32 #include <QtCore/QTime>
33 #include <QtCore/QFile>
34 #include <QtGui/QGridLayout>
35 #include <QtGui/QLabel>
36 #include <QtGui/QLayout>
37 #include <QtGui/QPainter>
38 #include <QtGui/QPixmap>
39 #include <QtGui/QScrollBar>
40 #include <QtGui/QStyle>
41 #include <QtCore/QTimer>
42 #include <QtGui/QToolTip>
44 // KDE
45 #include <kshell.h>
46 #include <KColorScheme>
47 #include <KCursor>
48 #include <kdebug.h>
49 #include <KLocale>
50 #include <KMenu>
51 #include <KNotification>
52 #include <KGlobalSettings>
53 #include <KShortcut>
54 #include <KIO/NetAccess>
56 // Konsole
57 #include <config-apps.h>
58 #include "Filter.h"
59 #include "konsole_wcwidth.h"
60 #include "ScreenWindow.h"
61 #include "TerminalCharacterDecoder.h"
63 using namespace Konsole;
65 #ifndef loc
66 #define loc(X,Y) ((Y)*_columns+(X))
67 #endif
69 #define yMouseScroll 1
71 #define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
72 "abcdefgjijklmnopqrstuvwxyz" \
73 "0123456789./+@"
75 const ColorEntry Konsole::base_color_table[TABLE_COLORS] =
76 // The following are almost IBM standard color codes, with some slight
77 // gamma correction for the dim colors to compensate for bright X screens.
78 // It contains the 8 ansiterm/xterm colors in 2 intensities.
80 // Fixme: could add faint colors here, also.
81 // normal
82 ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 1), // Dfore, Dback
83 ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0x18,0x18), 0), // Black, Red
84 ColorEntry(QColor(0x18,0xB2,0x18), 0), ColorEntry( QColor(0xB2,0x68,0x18), 0), // Green, Yellow
85 ColorEntry(QColor(0x18,0x18,0xB2), 0), ColorEntry( QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta
86 ColorEntry(QColor(0x18,0xB2,0xB2), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 0), // Cyan, White
87 // intensiv
88 ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 1),
89 ColorEntry(QColor(0x68,0x68,0x68), 0), ColorEntry( QColor(0xFF,0x54,0x54), 0),
90 ColorEntry(QColor(0x54,0xFF,0x54), 0), ColorEntry( QColor(0xFF,0xFF,0x54), 0),
91 ColorEntry(QColor(0x54,0x54,0xFF), 0), ColorEntry( QColor(0xFF,0x54,0xFF), 0),
92 ColorEntry(QColor(0x54,0xFF,0xFF), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 0)
95 // scroll increment used when dragging selection at top/bottom of window.
97 // static
98 bool TerminalDisplay::_antialiasText = true;
99 bool TerminalDisplay::HAVE_TRANSPARENCY = false;
101 // we use this to force QPainter to display text in LTR mode
102 // more information can be found in: http://unicode.org/reports/tr9/
103 const QChar LTR_OVERRIDE_CHAR( 0x202D );
105 /* ------------------------------------------------------------------------- */
106 /* */
107 /* Colors */
108 /* */
109 /* ------------------------------------------------------------------------- */
111 /* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
113 Code 0 1 2 3 4 5 6 7
114 ----------- ------- ------- ------- ------- ------- ------- ------- -------
115 ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
116 IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
119 ScreenWindow* TerminalDisplay::screenWindow() const
121 return _screenWindow;
123 void TerminalDisplay::setScreenWindow(ScreenWindow* window)
125 // disconnect existing screen window if any
126 if ( _screenWindow )
128 disconnect( _screenWindow , 0 , this , 0 );
131 _screenWindow = window;
133 if ( window )
135 #ifdef __GNUC__
136 #warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?"
137 #endif
138 connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) );
139 connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) );
140 window->setWindowLines(_lines);
144 const ColorEntry* TerminalDisplay::colorTable() const
146 return _colorTable;
148 void TerminalDisplay::setBackgroundColor(const QColor& color)
150 _colorTable[DEFAULT_BACK_COLOR].color = color;
151 QPalette p = palette();
152 p.setColor( backgroundRole(), color );
153 setPalette( p );
155 // Avoid propagating the palette change to the scroll bar
156 _scrollBar->setPalette( QApplication::palette() );
158 update();
160 void TerminalDisplay::setForegroundColor(const QColor& color)
162 _colorTable[DEFAULT_FORE_COLOR].color = color;
164 update();
166 void TerminalDisplay::setColorTable(const ColorEntry table[])
168 for (int i = 0; i < TABLE_COLORS; i++)
169 _colorTable[i] = table[i];
171 setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
174 /* ------------------------------------------------------------------------- */
175 /* */
176 /* Font */
177 /* */
178 /* ------------------------------------------------------------------------- */
181 The VT100 has 32 special graphical characters. The usual vt100 extended
182 xterm fonts have these at 0x00..0x1f.
184 QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
185 come in here as proper unicode characters.
187 We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
188 from unicode to 0x00..0x1f. The remaining translation is then left to the
189 QCodec.
192 static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);}
193 static inline bool isLineCharString(const QString& string)
195 return (string.length() > 0) && (isLineChar(string.at(0).unicode()));
199 // assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
201 unsigned short Konsole::vt100_graphics[32] =
202 { // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15
203 0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
204 0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
205 0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
206 0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
209 void TerminalDisplay::fontChange(const QFont&)
211 QFontMetrics fm(font());
212 _fontHeight = fm.height() + _lineSpacing;
214 // waba TerminalDisplay 1.123:
215 // "Base character width on widest ASCII character. This prevents too wide
216 // characters in the presence of double wide (e.g. Japanese) characters."
217 // Get the width from representative normal width characters
218 _fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR));
220 _fixedFont = true;
222 int fw = fm.width(REPCHAR[0]);
223 for(unsigned int i=1; i< strlen(REPCHAR); i++)
225 if (fw != fm.width(REPCHAR[i]))
227 _fixedFont = false;
228 break;
232 if (_fontWidth < 1)
233 _fontWidth=1;
235 _fontAscent = fm.ascent();
237 emit changedFontMetricSignal( _fontHeight, _fontWidth );
238 propagateSize();
239 update();
242 void TerminalDisplay::setVTFont(const QFont& f)
244 QFont font = f;
246 QFontMetrics metrics(font);
248 if ( !QFontInfo(font).fixedPitch() )
250 kWarning() << "Using an unsupported variable-width font in the terminal. This may produce display errors.";
253 if ( metrics.height() < height() && metrics.maxWidth() < width() )
255 // hint that text should be drawn without anti-aliasing.
256 // depending on the user's font configuration, this may not be respected
257 if (!_antialiasText)
258 font.setStyleStrategy( QFont::NoAntialias );
260 // experimental optimization. Konsole assumes that the terminal is using a
261 // mono-spaced font, in which case kerning information should have an effect.
262 // Disabling kerning saves some computation when rendering text.
263 font.setKerning(false);
265 QWidget::setFont(font);
266 fontChange(font);
270 void TerminalDisplay::setFont(const QFont &)
272 // ignore font change request if not coming from konsole itself
275 /* ------------------------------------------------------------------------- */
276 /* */
277 /* Constructor / Destructor */
278 /* */
279 /* ------------------------------------------------------------------------- */
281 TerminalDisplay::TerminalDisplay(QWidget *parent)
282 :QWidget(parent)
283 ,_screenWindow(0)
284 ,_allowBell(true)
285 ,_gridLayout(0)
286 ,_fontHeight(1)
287 ,_fontWidth(1)
288 ,_fontAscent(1)
289 ,_lines(1)
290 ,_columns(1)
291 ,_usedLines(1)
292 ,_usedColumns(1)
293 ,_contentHeight(1)
294 ,_contentWidth(1)
295 ,_image(0)
296 ,_randomSeed(0)
297 ,_resizing(false)
298 ,_terminalSizeHint(false)
299 ,_terminalSizeStartup(true)
300 ,_bidiEnabled(false)
301 ,_actSel(0)
302 ,_wordSelectionMode(false)
303 ,_lineSelectionMode(false)
304 ,_preserveLineBreaks(false)
305 ,_columnSelectionMode(false)
306 ,_scrollbarLocation(NoScrollBar)
307 ,_wordCharacters(":@-./_~")
308 ,_bellMode(SystemBeepBell)
309 ,_blinking(false)
310 ,_hasBlinker(false)
311 ,_cursorBlinking(false)
312 ,_hasBlinkingCursor(false)
313 ,_ctrlDrag(false)
314 ,_tripleClickMode(SelectWholeLine)
315 ,_isFixedSize(false)
316 ,_possibleTripleClick(false)
317 ,_resizeWidget(0)
318 ,_resizeTimer(0)
319 ,_flowControlWarningEnabled(false)
320 ,_outputSuspendedLabel(0)
321 ,_lineSpacing(0)
322 ,_colorsInverted(false)
323 ,_blendColor(qRgba(0,0,0,0xff))
324 ,_filterChain(new TerminalImageFilterChain())
325 ,_cursorShape(BlockCursor)
327 // terminal applications are not designed with Right-To-Left in mind,
328 // so the layout is forced to Left-To-Right
329 setLayoutDirection(Qt::LeftToRight);
331 // The offsets are not yet calculated.
332 // Do not calculate these too often to be more smoothly when resizing
333 // konsole in opaque mode.
334 _topMargin = DEFAULT_TOP_MARGIN;
335 _leftMargin = DEFAULT_LEFT_MARGIN;
337 // create scroll bar for scrolling output up and down
338 // set the scroll bar's slider to occupy the whole area of the scroll bar initially
339 _scrollBar = new QScrollBar(this);
340 setScroll(0,0);
341 _scrollBar->setCursor( Qt::ArrowCursor );
342 connect(_scrollBar, SIGNAL(valueChanged(int)), this,
343 SLOT(scrollBarPositionChanged(int)));
345 // setup timers for blinking cursor and text
346 _blinkTimer = new QTimer(this);
347 connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
348 _blinkCursorTimer = new QTimer(this);
349 connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
351 KCursor::setAutoHideCursor( this, true );
353 setUsesMouse(true);
354 setColorTable(base_color_table);
355 setMouseTracking(true);
357 // Enable drag and drop
358 setAcceptDrops(true); // attempt
359 dragInfo.state = diNone;
361 setFocusPolicy( Qt::WheelFocus );
363 // enable input method support
364 setAttribute(Qt::WA_InputMethodEnabled, true);
366 // this is an important optimization, it tells Qt
367 // that TerminalDisplay will handle repainting its entire area.
368 setAttribute(Qt::WA_OpaquePaintEvent);
370 _gridLayout = new QGridLayout(this);
371 _gridLayout->setMargin(0);
373 setLayout( _gridLayout );
375 new AutoScrollHandler(this);
378 TerminalDisplay::~TerminalDisplay()
380 qApp->removeEventFilter( this );
382 delete[] _image;
384 delete _gridLayout;
385 delete _outputSuspendedLabel;
386 delete _filterChain;
389 /* ------------------------------------------------------------------------- */
390 /* */
391 /* Display Operations */
392 /* */
393 /* ------------------------------------------------------------------------- */
396 A table for emulating the simple (single width) unicode drawing chars.
397 It represents the 250x - 257x glyphs. If it's zero, we can't use it.
398 if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
399 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
401 Then, the pixels basically have the following interpretation:
402 _|||_
403 -...-
404 -...-
405 -...-
406 _|||_
408 where _ = none
409 | = vertical line.
410 - = horizontal line.
414 enum LineEncode
416 TopL = (1<<1),
417 TopC = (1<<2),
418 TopR = (1<<3),
420 LeftT = (1<<5),
421 Int11 = (1<<6),
422 Int12 = (1<<7),
423 Int13 = (1<<8),
424 RightT = (1<<9),
426 LeftC = (1<<10),
427 Int21 = (1<<11),
428 Int22 = (1<<12),
429 Int23 = (1<<13),
430 RightC = (1<<14),
432 LeftB = (1<<15),
433 Int31 = (1<<16),
434 Int32 = (1<<17),
435 Int33 = (1<<18),
436 RightB = (1<<19),
438 BotL = (1<<21),
439 BotC = (1<<22),
440 BotR = (1<<23)
443 #include "LineFont.h"
445 static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
447 //Calculate cell midpoints, end points.
448 int cx = x + w/2;
449 int cy = y + h/2;
450 int ex = x + w - 1;
451 int ey = y + h - 1;
453 quint32 toDraw = LineChars[code];
455 //Top _lines:
456 if (toDraw & TopL)
457 paint.drawLine(cx-1, y, cx-1, cy-2);
458 if (toDraw & TopC)
459 paint.drawLine(cx, y, cx, cy-2);
460 if (toDraw & TopR)
461 paint.drawLine(cx+1, y, cx+1, cy-2);
463 //Bot _lines:
464 if (toDraw & BotL)
465 paint.drawLine(cx-1, cy+2, cx-1, ey);
466 if (toDraw & BotC)
467 paint.drawLine(cx, cy+2, cx, ey);
468 if (toDraw & BotR)
469 paint.drawLine(cx+1, cy+2, cx+1, ey);
471 //Left _lines:
472 if (toDraw & LeftT)
473 paint.drawLine(x, cy-1, cx-2, cy-1);
474 if (toDraw & LeftC)
475 paint.drawLine(x, cy, cx-2, cy);
476 if (toDraw & LeftB)
477 paint.drawLine(x, cy+1, cx-2, cy+1);
479 //Right _lines:
480 if (toDraw & RightT)
481 paint.drawLine(cx+2, cy-1, ex, cy-1);
482 if (toDraw & RightC)
483 paint.drawLine(cx+2, cy, ex, cy);
484 if (toDraw & RightB)
485 paint.drawLine(cx+2, cy+1, ex, cy+1);
487 //Intersection points.
488 if (toDraw & Int11)
489 paint.drawPoint(cx-1, cy-1);
490 if (toDraw & Int12)
491 paint.drawPoint(cx, cy-1);
492 if (toDraw & Int13)
493 paint.drawPoint(cx+1, cy-1);
495 if (toDraw & Int21)
496 paint.drawPoint(cx-1, cy);
497 if (toDraw & Int22)
498 paint.drawPoint(cx, cy);
499 if (toDraw & Int23)
500 paint.drawPoint(cx+1, cy);
502 if (toDraw & Int31)
503 paint.drawPoint(cx-1, cy+1);
504 if (toDraw & Int32)
505 paint.drawPoint(cx, cy+1);
506 if (toDraw & Int33)
507 paint.drawPoint(cx+1, cy+1);
511 void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const QString& str,
512 const Character* attributes)
514 const QPen& currentPen = painter.pen();
516 if ( attributes->rendition & RE_BOLD )
518 QPen boldPen(currentPen);
519 boldPen.setWidth(3);
520 painter.setPen( boldPen );
523 for (int i=0 ; i < str.length(); i++)
525 uchar code = str[i].cell();
526 if (LineChars[code])
527 drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code);
530 painter.setPen( currentPen );
533 void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape)
535 _cursorShape = shape;
537 TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const
539 return _cursorShape;
541 void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color)
543 if (useForegroundColor)
544 _cursorColor = QColor(); // an invalid color means that
545 // the foreground color of the
546 // current character should
547 // be used
549 else
550 _cursorColor = color;
552 QColor TerminalDisplay::keyboardCursorColor() const
554 return _cursorColor;
557 void TerminalDisplay::setOpacity(qreal opacity)
559 QColor color(_blendColor);
560 color.setAlphaF(opacity);
562 // enable automatic background filling to prevent the display
563 // flickering if there is no transparency
564 /*if ( color.alpha() == 255 )
566 setAutoFillBackground(true);
568 else
570 setAutoFillBackground(false);
573 _blendColor = color.rgba();
576 void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting )
578 // the area of the widget showing the contents of the terminal display is drawn
579 // using the background color from the color scheme set with setColorTable()
581 // the area of the widget behind the scroll-bar is drawn using the background
582 // brush from the scroll-bar's palette, to give the effect of the scroll-bar
583 // being outside of the terminal display and visual consistency with other KDE
584 // applications.
586 QRect scrollBarArea = _scrollBar->isVisible() ?
587 rect.intersected(_scrollBar->geometry()) :
588 QRect();
589 QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
590 QRect contentsRect = contentsRegion.boundingRect();
592 if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting )
594 QColor color(backgroundColor);
595 color.setAlpha(qAlpha(_blendColor));
597 painter.save();
598 painter.setCompositionMode(QPainter::CompositionMode_Source);
599 painter.fillRect(contentsRect, color);
600 painter.restore();
602 else
603 painter.fillRect(contentsRect, backgroundColor);
605 painter.fillRect(scrollBarArea,_scrollBar->palette().background());
608 void TerminalDisplay::drawCursor(QPainter& painter,
609 const QRect& rect,
610 const QColor& foregroundColor,
611 const QColor& /*backgroundColor*/,
612 bool& invertCharacterColor)
614 QRect cursorRect = rect;
615 cursorRect.setHeight(_fontHeight - _lineSpacing - 1);
617 if (!_cursorBlinking)
619 if ( _cursorColor.isValid() )
620 painter.setPen(_cursorColor);
621 else
622 painter.setPen(foregroundColor);
624 if ( _cursorShape == BlockCursor )
626 // draw the cursor outline, adjusting the area so that
627 // it is draw entirely inside 'rect'
628 int penWidth = qMax(1,painter.pen().width());
630 painter.drawRect(cursorRect.adjusted(penWidth/2,
631 penWidth/2,
632 - penWidth/2 - penWidth%2,
633 - penWidth/2 - penWidth%2));
634 if ( hasFocus() )
636 painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
638 if ( !_cursorColor.isValid() )
640 // invert the colour used to draw the text to ensure that the character at
641 // the cursor position is readable
642 invertCharacterColor = true;
646 else if ( _cursorShape == UnderlineCursor )
647 painter.drawLine(cursorRect.left(),
648 cursorRect.bottom(),
649 cursorRect.right(),
650 cursorRect.bottom());
651 else if ( _cursorShape == IBeamCursor )
652 painter.drawLine(cursorRect.left(),
653 cursorRect.top(),
654 cursorRect.left(),
655 cursorRect.bottom());
660 void TerminalDisplay::drawCharacters(QPainter& painter,
661 const QRect& rect,
662 const QString& text,
663 const Character* style,
664 bool invertCharacterColor)
666 // don't draw text which is currently blinking
667 if ( _blinking && (style->rendition & RE_BLINK) )
668 return;
670 // setup bold and underline
671 bool useBold;
672 ColorEntry::FontWeight weight = style->fontWeight(_colorTable);
673 if (weight == ColorEntry::UseCurrentFormat)
674 useBold = style->rendition & RE_BOLD || font().bold();
675 else
676 useBold = (weight == ColorEntry::Bold) ? true : false;
677 bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
679 QFont font = painter.font();
680 if ( font.bold() != useBold
681 || font.underline() != useUnderline )
683 font.setBold(useBold);
684 font.setUnderline(useUnderline);
685 painter.setFont(font);
688 // setup pen
689 const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor );
690 const QColor color = textColor.color(_colorTable);
691 QPen pen = painter.pen();
692 if ( pen.color() != color )
694 pen.setColor(color);
695 painter.setPen(color);
698 // draw text
699 if ( isLineCharString(text) )
700 drawLineCharString(painter,rect.x(),rect.y(),text,style);
701 else
703 // the drawText(rect,flags,string) overload is used here with null flags
704 // instead of drawText(rect,string) because the (rect,string) overload causes
705 // the application's default layout direction to be used instead of
706 // the widget-specific layout direction, which should always be
707 // Qt::LeftToRight for this widget
708 // This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2
709 if (_bidiEnabled)
710 painter.drawText(rect,0,text);
711 else
712 painter.drawText(rect,0,LTR_OVERRIDE_CHAR+text);
716 void TerminalDisplay::drawTextFragment(QPainter& painter ,
717 const QRect& rect,
718 const QString& text,
719 const Character* style)
721 painter.save();
723 // setup painter
724 const QColor foregroundColor = style->foregroundColor.color(_colorTable);
725 const QColor backgroundColor = style->backgroundColor.color(_colorTable);
727 // draw background if different from the display's background color
728 if ( backgroundColor != palette().background().color() )
729 drawBackground(painter,rect,backgroundColor,
730 false /* do not use transparency */);
732 // draw cursor shape if the current character is the cursor
733 // this may alter the foreground and background colors
734 bool invertCharacterColor = false;
735 if ( style->rendition & RE_CURSOR )
736 drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor);
738 // draw text
739 drawCharacters(painter,rect,text,style,invertCharacterColor);
741 painter.restore();
744 void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; }
745 uint TerminalDisplay::randomSeed() const { return _randomSeed; }
747 #if 0
749 Set XIM Position
751 void TerminalDisplay::setCursorPos(const int curx, const int cury)
753 QPoint tL = contentsRect().topLeft();
754 int tLx = tL.x();
755 int tLy = tL.y();
757 int xpos, ypos;
758 ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent;
759 xpos = _leftMargin + tLx + _fontWidth*curx;
760 //setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ???
761 // fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos);
762 _cursorLine = cury;
763 _cursorCol = curx;
765 #endif
767 // scrolls the image by 'lines', down if lines > 0 or up otherwise.
769 // the terminal emulation keeps track of the scrolling of the character
770 // image as it receives input, and when the view is updated, it calls scrollImage()
771 // with the final scroll amount. this improves performance because scrolling the
772 // display is much cheaper than re-rendering all the text for the
773 // part of the image which has moved up or down.
774 // Instead only new lines have to be drawn
775 void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
777 // if the flow control warning is enabled this will interfere with the
778 // scrolling optimizations and cause artifacts. the simple solution here
779 // is to just disable the optimization whilst it is visible
780 if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() )
781 return;
783 // constrain the region to the display
784 // the bottom of the region is capped to the number of lines in the display's
785 // internal image - 2, so that the height of 'region' is strictly less
786 // than the height of the internal image.
787 QRect region = screenWindowRegion;
788 region.setBottom( qMin(region.bottom(),this->_lines-2) );
790 // return if there is nothing to do
791 if ( lines == 0
792 || _image == 0
793 || !region.isValid()
794 || (region.top() + abs(lines)) >= region.bottom()
795 || this->_lines <= region.height() ) return;
797 // hide terminal size label to prevent it being scrolled
798 if (_resizeWidget && _resizeWidget->isVisible())
799 _resizeWidget->hide();
801 // Note: With Qt 4.4 the left edge of the scrolled area must be at 0
802 // to get the correct (newly exposed) part of the widget repainted.
804 // The right edge must be before the left edge of the scroll bar to
805 // avoid triggering a repaint of the entire widget, the distance is
806 // given by SCROLLBAR_CONTENT_GAP
808 // Set the QT_FLUSH_PAINT environment variable to '1' before starting the
809 // application to monitor repainting.
811 int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width();
812 const int SCROLLBAR_CONTENT_GAP = 1;
813 QRect scrollRect;
814 if ( _scrollbarLocation == ScrollBarLeft )
816 scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP);
817 scrollRect.setRight(width());
819 else
821 scrollRect.setLeft(0);
822 scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
824 void* firstCharPos = &_image[ region.top() * this->_columns ];
825 void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ];
827 int top = _topMargin + (region.top() * _fontHeight);
828 int linesToMove = region.height() - abs(lines);
829 int bytesToMove = linesToMove *
830 this->_columns *
831 sizeof(Character);
833 Q_ASSERT( linesToMove > 0 );
834 Q_ASSERT( bytesToMove > 0 );
836 //scroll internal image
837 if ( lines > 0 )
839 // check that the memory areas that we are going to move are valid
840 Q_ASSERT( (char*)lastCharPos + bytesToMove <
841 (char*)(_image + (this->_lines * this->_columns)) );
843 Q_ASSERT( (lines*this->_columns) < _imageSize );
845 //scroll internal image down
846 memmove( firstCharPos , lastCharPos , bytesToMove );
848 //set region of display to scroll
849 scrollRect.setTop(top);
851 else
853 // check that the memory areas that we are going to move are valid
854 Q_ASSERT( (char*)firstCharPos + bytesToMove <
855 (char*)(_image + (this->_lines * this->_columns)) );
857 //scroll internal image up
858 memmove( lastCharPos , firstCharPos , bytesToMove );
860 //set region of the display to scroll
861 scrollRect.setTop(top + abs(lines) * _fontHeight);
863 scrollRect.setHeight(linesToMove * _fontHeight );
865 Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
867 //scroll the display vertically to match internal _image
868 scroll( 0 , _fontHeight * (-lines) , scrollRect );
871 QRegion TerminalDisplay::hotSpotRegion() const
873 QRegion region;
874 foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() )
876 QRect r;
877 if (hotSpot->startLine()==hotSpot->endLine()) {
878 r.setLeft(hotSpot->startColumn());
879 r.setTop(hotSpot->startLine());
880 r.setRight(hotSpot->endColumn());
881 r.setBottom(hotSpot->endLine());
882 region |= imageToWidget(r);;
883 } else {
884 r.setLeft(hotSpot->startColumn());
885 r.setTop(hotSpot->startLine());
886 r.setRight(_columns);
887 r.setBottom(hotSpot->startLine());
888 region |= imageToWidget(r);;
889 for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) {
890 r.setLeft(0);
891 r.setTop(line);
892 r.setRight(_columns);
893 r.setBottom(line);
894 region |= imageToWidget(r);;
896 r.setLeft(0);
897 r.setTop(hotSpot->endLine());
898 r.setRight(hotSpot->endColumn());
899 r.setBottom(hotSpot->endLine());
900 region |= imageToWidget(r);;
903 return region;
906 void TerminalDisplay::processFilters()
908 if (!_screenWindow)
909 return;
911 QRegion preUpdateHotSpots = hotSpotRegion();
913 // use _screenWindow->getImage() here rather than _image because
914 // other classes may call processFilters() when this display's
915 // ScreenWindow emits a scrolled() signal - which will happen before
916 // updateImage() is called on the display and therefore _image is
917 // out of date at this point
918 _filterChain->setImage( _screenWindow->getImage(),
919 _screenWindow->windowLines(),
920 _screenWindow->windowColumns(),
921 _screenWindow->getLineProperties() );
922 _filterChain->process();
924 QRegion postUpdateHotSpots = hotSpotRegion();
926 update( preUpdateHotSpots | postUpdateHotSpots );
929 void TerminalDisplay::updateImage()
931 if ( !_screenWindow )
932 return;
934 // optimization - scroll the existing image where possible and
935 // avoid expensive text drawing for parts of the image that
936 // can simply be moved up or down
937 scrollImage( _screenWindow->scrollCount() ,
938 _screenWindow->scrollRegion() );
939 _screenWindow->resetScrollCount();
941 Character* const newimg = _screenWindow->getImage();
942 int lines = _screenWindow->windowLines();
943 int columns = _screenWindow->windowColumns();
945 setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );
947 if (!_image)
948 updateImageSize(); // Create _image
950 Q_ASSERT( this->_usedLines <= this->_lines );
951 Q_ASSERT( this->_usedColumns <= this->_columns );
953 int y,x,len;
955 QPoint tL = contentsRect().topLeft();
956 int tLx = tL.x();
957 int tLy = tL.y();
958 _hasBlinker = false;
960 CharacterColor cf; // undefined
961 CharacterColor _clipboard; // undefined
962 int cr = -1; // undefined
964 const int linesToUpdate = qMin(this->_lines, qMax(0,lines ));
965 const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));
967 QChar *disstrU = new QChar[columnsToUpdate];
968 char *dirtyMask = new char[columnsToUpdate+2];
969 QRegion dirtyRegion;
971 // debugging variable, this records the number of lines that are found to
972 // be 'dirty' ( ie. have changed from the old _image to the new _image ) and
973 // which therefore need to be repainted
974 int dirtyLineCount = 0;
976 for (y = 0; y < linesToUpdate; ++y)
978 const Character* currentLine = &_image[y*this->_columns];
979 const Character* const newLine = &newimg[y*columns];
981 bool updateLine = false;
983 // The dirty mask indicates which characters need repainting. We also
984 // mark surrounding neighbours dirty, in case the character exceeds
985 // its cell boundaries
986 memset(dirtyMask, 0, columnsToUpdate+2);
988 for( x = 0 ; x < columnsToUpdate ; ++x)
990 if ( newLine[x] != currentLine[x] )
992 dirtyMask[x] = true;
996 if (!_resizing) // not while _resizing, we're expecting a paintEvent
997 for (x = 0; x < columnsToUpdate; ++x)
999 _hasBlinker |= (newLine[x].rendition & RE_BLINK);
1001 // Start drawing if this character or the next one differs.
1002 // We also take the next one into account to handle the situation
1003 // where characters exceed their cell width.
1004 if (dirtyMask[x])
1006 quint16 c = newLine[x+0].character;
1007 if ( !c )
1008 continue;
1009 int p = 0;
1010 disstrU[p++] = c; //fontMap(c);
1011 bool lineDraw = isLineChar(c);
1012 bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
1013 cr = newLine[x].rendition;
1014 _clipboard = newLine[x].backgroundColor;
1015 if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
1016 int lln = columnsToUpdate - x;
1017 for (len = 1; len < lln; ++len)
1019 const Character& ch = newLine[x+len];
1021 if (!ch.character)
1022 continue; // Skip trailing part of multi-col chars.
1024 bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0);
1026 if ( ch.foregroundColor != cf ||
1027 ch.backgroundColor != _clipboard ||
1028 ch.rendition != cr ||
1029 !dirtyMask[x+len] ||
1030 isLineChar(c) != lineDraw ||
1031 nextIsDoubleWidth != doubleWidth )
1032 break;
1034 disstrU[p++] = c; //fontMap(c);
1037 QString unistr(disstrU, p);
1039 bool saveFixedFont = _fixedFont;
1040 if (lineDraw)
1041 _fixedFont = false;
1042 if (doubleWidth)
1043 _fixedFont = false;
1045 updateLine = true;
1047 _fixedFont = saveFixedFont;
1048 x += len - 1;
1053 //both the top and bottom halves of double height _lines must always be redrawn
1054 //although both top and bottom halves contain the same characters, only
1055 //the top one is actually
1056 //drawn.
1057 if (_lineProperties.count() > y)
1058 updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
1060 // if the characters on the line are different in the old and the new _image
1061 // then this line must be repainted.
1062 if (updateLine)
1064 dirtyLineCount++;
1066 // add the area occupied by this line to the region which needs to be
1067 // repainted
1068 QRect dirtyRect = QRect( _leftMargin+tLx ,
1069 _topMargin+tLy+_fontHeight*y ,
1070 _fontWidth * columnsToUpdate ,
1071 _fontHeight );
1073 dirtyRegion |= dirtyRect;
1076 // replace the line of characters in the old _image with the
1077 // current line of the new _image
1078 memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character));
1081 // if the new _image is smaller than the previous _image, then ensure that the area
1082 // outside the new _image is cleared
1083 if ( linesToUpdate < _usedLines )
1085 dirtyRegion |= QRect( _leftMargin+tLx ,
1086 _topMargin+tLy+_fontHeight*linesToUpdate ,
1087 _fontWidth * this->_columns ,
1088 _fontHeight * (_usedLines-linesToUpdate) );
1090 _usedLines = linesToUpdate;
1092 if ( columnsToUpdate < _usedColumns )
1094 dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth ,
1095 _topMargin+tLy ,
1096 _fontWidth * (_usedColumns-columnsToUpdate) ,
1097 _fontHeight * this->_lines );
1099 _usedColumns = columnsToUpdate;
1101 dirtyRegion |= _inputMethodData.previousPreeditRect;
1103 // update the parts of the display which have changed
1104 update(dirtyRegion);
1106 if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( BLINK_DELAY );
1107 if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; }
1108 delete[] dirtyMask;
1109 delete[] disstrU;
1113 void TerminalDisplay::showResizeNotification()
1115 if (_terminalSizeHint && isVisible())
1117 if (_terminalSizeStartup) {
1118 _terminalSizeStartup=false;
1119 return;
1121 if (!_resizeWidget)
1123 _resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
1124 _resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(i18n("Size: XXX x XXX")));
1125 _resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
1126 _resizeWidget->setAlignment(Qt::AlignCenter);
1128 _resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)");
1130 _resizeTimer = new QTimer(this);
1131 _resizeTimer->setSingleShot(true);
1132 connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide()));
1134 QString sizeStr = i18n("Size: %1 x %2", _columns, _lines);
1135 _resizeWidget->setText(sizeStr);
1136 _resizeWidget->move((width()-_resizeWidget->width())/2,
1137 (height()-_resizeWidget->height())/2+20);
1138 _resizeWidget->show();
1139 _resizeTimer->start(1000);
1143 void TerminalDisplay::setBlinkingCursor(bool blink)
1145 _hasBlinkingCursor=blink;
1147 if (blink && !_blinkCursorTimer->isActive())
1148 _blinkCursorTimer->start(BLINK_DELAY);
1150 if (!blink && _blinkCursorTimer->isActive())
1152 _blinkCursorTimer->stop();
1153 if (_cursorBlinking)
1154 blinkCursorEvent();
1155 else
1156 _cursorBlinking = false;
1160 void TerminalDisplay::focusOutEvent(QFocusEvent*)
1162 // trigger a repaint of the cursor so that it is both visible (in case
1163 // it was hidden during blinking)
1164 // and drawn in a focused out state
1165 _cursorBlinking = false;
1166 updateCursor();
1168 _blinkCursorTimer->stop();
1169 if (_blinking)
1170 blinkEvent();
1172 _blinkTimer->stop();
1174 void TerminalDisplay::focusInEvent(QFocusEvent*)
1176 if (_hasBlinkingCursor)
1178 _blinkCursorTimer->start();
1180 updateCursor();
1182 if (_hasBlinker)
1183 _blinkTimer->start();
1186 void TerminalDisplay::paintEvent( QPaintEvent* pe )
1188 QPainter paint(this);
1190 foreach (const QRect &rect, (pe->region() & contentsRect()).rects())
1192 drawBackground(paint,rect,palette().background().color(),
1193 true /* use opacity setting */);
1194 drawContents(paint, rect);
1196 drawInputMethodPreeditString(paint,preeditRect());
1197 paintFilters(paint);
1200 QPoint TerminalDisplay::cursorPosition() const
1202 if (_screenWindow)
1203 return _screenWindow->cursorPosition();
1204 else
1205 return QPoint(0,0);
1208 QRect TerminalDisplay::preeditRect() const
1210 const int preeditLength = string_width(_inputMethodData.preeditString);
1212 if ( preeditLength == 0 )
1213 return QRect();
1215 return QRect(_leftMargin + _fontWidth*cursorPosition().x(),
1216 _topMargin + _fontHeight*cursorPosition().y(),
1217 _fontWidth*preeditLength,
1218 _fontHeight);
1221 void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
1223 if ( _inputMethodData.preeditString.isEmpty() )
1224 return;
1226 const QPoint cursorPos = cursorPosition();
1228 bool invertColors = false;
1229 const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
1230 const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
1231 const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())];
1233 drawBackground(painter,rect,background,true);
1234 drawCursor(painter,rect,foreground,background,invertColors);
1235 drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors);
1237 _inputMethodData.previousPreeditRect = rect;
1240 FilterChain* TerminalDisplay::filterChain() const
1242 return _filterChain;
1245 void TerminalDisplay::paintFilters(QPainter& painter)
1247 // get color of character under mouse and use it to draw
1248 // lines for filters
1249 QPoint cursorPos = mapFromGlobal(QCursor::pos());
1250 int cursorLine;
1251 int cursorColumn;
1252 getCharacterPosition( cursorPos , cursorLine , cursorColumn );
1253 Character cursorCharacter = _image[loc(cursorColumn,cursorLine)];
1255 painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) );
1257 // iterate over hotspots identified by the display's currently active filters
1258 // and draw appropriate visuals to indicate the presence of the hotspot
1260 QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
1261 QListIterator<Filter::HotSpot*> iter(spots);
1262 while (iter.hasNext())
1264 Filter::HotSpot* spot = iter.next();
1266 QRegion region;
1267 if ( spot->type() == Filter::HotSpot::Link ) {
1268 QRect r;
1269 if (spot->startLine()==spot->endLine()) {
1270 r.setCoords( spot->startColumn()*_fontWidth + 1, spot->startLine()*_fontHeight + 1,
1271 (spot->endColumn()-1)*_fontWidth - 1, (spot->endLine()+1)*_fontHeight - 1 );
1272 region |= r;
1273 } else {
1274 r.setCoords( spot->startColumn()*_fontWidth + 1, spot->startLine()*_fontHeight + 1,
1275 (_columns-1)*_fontWidth - 1, (spot->startLine()+1)*_fontHeight - 1 );
1276 region |= r;
1277 for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
1278 r.setCoords( 0*_fontWidth + 1, line*_fontHeight + 1,
1279 (_columns-1)*_fontWidth - 1, (line+1)*_fontHeight - 1 );
1280 region |= r;
1282 r.setCoords( 0*_fontWidth + 1, spot->endLine()*_fontHeight + 1,
1283 (spot->endColumn()-1)*_fontWidth - 1, (spot->endLine()+1)*_fontHeight - 1 );
1284 region |= r;
1288 for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ )
1290 int startColumn = 0;
1291 int endColumn = _columns-1; // TODO use number of _columns which are actually
1292 // occupied on this line rather than the width of the
1293 // display in _columns
1295 // ignore whitespace at the end of the lines
1296 while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 )
1297 endColumn--;
1299 // increment here because the column which we want to set 'endColumn' to
1300 // is the first whitespace character at the end of the line
1301 endColumn++;
1303 if ( line == spot->startLine() )
1304 startColumn = spot->startColumn();
1305 if ( line == spot->endLine() )
1306 endColumn = spot->endColumn();
1308 // subtract one pixel from
1309 // the right and bottom so that
1310 // we do not overdraw adjacent
1311 // hotspots
1313 // subtracting one pixel from all sides also prevents an edge case where
1314 // moving the mouse outside a link could still leave it underlined
1315 // because the check below for the position of the cursor
1316 // finds it on the border of the target area
1317 QRect r;
1318 r.setCoords( startColumn*_fontWidth + 1, line*_fontHeight + 1,
1319 endColumn*_fontWidth - 1, (line+1)*_fontHeight - 1 );
1321 // Underline link hotspots
1322 if ( spot->type() == Filter::HotSpot::Link )
1324 QFontMetrics metrics(font());
1326 // find the baseline (which is the invisible line that the characters in the font sit on,
1327 // with some having tails dangling below)
1328 int baseline = r.bottom() - metrics.descent();
1329 // find the position of the underline below that
1330 int underlinePos = baseline + metrics.underlinePos();
1331 if ( region.contains( mapFromGlobal(QCursor::pos()) ) ){
1332 painter.drawLine( r.left() , underlinePos ,
1333 r.right() , underlinePos );
1336 // Marker hotspots simply have a transparent rectanglular shape
1337 // drawn on top of them
1338 else if ( spot->type() == Filter::HotSpot::Marker )
1340 //TODO - Do not use a hardcoded colour for this
1341 painter.fillRect(r,QBrush(QColor(255,0,0,120)));
1346 void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
1348 QPoint tL = contentsRect().topLeft();
1349 int tLx = tL.x();
1350 int tLy = tL.y();
1352 int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth));
1353 int luy = qMin(_usedLines-1, qMax(0,(rect.top() - tLy - _topMargin ) / _fontHeight));
1354 int rlx = qMin(_usedColumns-1, qMax(0,(rect.right() - tLx - _leftMargin ) / _fontWidth));
1355 int rly = qMin(_usedLines-1, qMax(0,(rect.bottom() - tLy - _topMargin ) / _fontHeight));
1357 const int bufferSize = _usedColumns;
1358 QChar *disstrU = new QChar[bufferSize];
1359 for (int y = luy; y <= rly; y++)
1361 quint16 c = _image[loc(lux,y)].character;
1362 int x = lux;
1363 if(!c && x)
1364 x--; // Search for start of multi-column character
1365 for (; x <= rlx; x++)
1367 int len = 1;
1368 int p = 0;
1370 // is this a single character or a sequence of characters ?
1371 if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR )
1373 // sequence of characters
1374 ushort extendedCharLength = 0;
1375 ushort* chars = ExtendedCharTable::instance
1376 .lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength);
1377 for ( int index = 0 ; index < extendedCharLength ; index++ )
1379 Q_ASSERT( p < bufferSize );
1380 disstrU[p++] = chars[index];
1383 else
1385 // single character
1386 c = _image[loc(x,y)].character;
1387 if (c)
1389 Q_ASSERT( p < bufferSize );
1390 disstrU[p++] = c; //fontMap(c);
1394 bool lineDraw = isLineChar(c);
1395 bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
1396 CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
1397 CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
1398 quint8 currentRendition = _image[loc(x,y)].rendition;
1400 while (x+len <= rlx &&
1401 _image[loc(x+len,y)].foregroundColor == currentForeground &&
1402 _image[loc(x+len,y)].backgroundColor == currentBackground &&
1403 _image[loc(x+len,y)].rendition == currentRendition &&
1404 (_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth &&
1405 isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment!
1407 if (c)
1408 disstrU[p++] = c; //fontMap(c);
1409 if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1410 len++; // Skip trailing part of multi-column character
1411 len++;
1413 if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character))
1414 len++; // Adjust for trailing part of multi-column character
1416 bool save__fixedFont = _fixedFont;
1417 if (lineDraw)
1418 _fixedFont = false;
1419 if (doubleWidth)
1420 _fixedFont = false;
1421 QString unistr(disstrU,p);
1423 if (y < _lineProperties.size())
1425 if (_lineProperties[y] & LINE_DOUBLEWIDTH)
1426 paint.scale(2,1);
1428 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1429 paint.scale(1,2);
1432 //calculate the area in which the text will be drawn
1433 QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x , _topMargin+tLy+_fontHeight*y , _fontWidth*len , _fontHeight);
1435 //move the calculated area to take account of scaling applied to the painter.
1436 //the position of the area from the origin (0,0) is scaled
1437 //by the opposite of whatever
1438 //transformation has been applied to the painter. this ensures that
1439 //painting does actually start from textArea.topLeft()
1440 //(instead of textArea.topLeft() * painter-scale)
1441 QTransform inverted = paint.worldTransform().inverted();
1442 textArea.moveTopLeft( inverted.map(textArea.topLeft()) );
1444 //paint text fragment
1445 drawTextFragment( paint,
1446 textArea,
1447 unistr,
1448 &_image[loc(x,y)] ); //,
1449 //0,
1450 //!_isPrinting );
1452 _fixedFont = save__fixedFont;
1454 //reset back to single-width, single-height _lines
1455 paint.resetMatrix();
1457 if (y < _lineProperties.size()-1)
1459 //double-height _lines are represented by two adjacent _lines
1460 //containing the same characters
1461 //both _lines will have the LINE_DOUBLEHEIGHT attribute.
1462 //If the current line has the LINE_DOUBLEHEIGHT attribute,
1463 //we can therefore skip the next line
1464 if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1465 y++;
1468 x += len - 1;
1471 delete [] disstrU;
1474 void TerminalDisplay::blinkEvent()
1476 _blinking = !_blinking;
1478 //TODO: Optimise to only repaint the areas of the widget
1479 // where there is blinking text
1480 // rather than repainting the whole widget.
1481 update();
1484 QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
1486 QRect result;
1487 result.setLeft( _leftMargin + _fontWidth * imageArea.left() );
1488 result.setTop( _topMargin + _fontHeight * imageArea.top() );
1489 result.setWidth( _fontWidth * imageArea.width() );
1490 result.setHeight( _fontHeight * imageArea.height() );
1492 return result;
1495 void TerminalDisplay::updateCursor()
1497 QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) );
1498 update(cursorRect);
1501 void TerminalDisplay::blinkCursorEvent()
1503 _cursorBlinking = !_cursorBlinking;
1504 updateCursor();
1507 /* ------------------------------------------------------------------------- */
1508 /* */
1509 /* Resizing */
1510 /* */
1511 /* ------------------------------------------------------------------------- */
1513 void TerminalDisplay::resizeEvent(QResizeEvent*)
1515 updateImageSize();
1518 void TerminalDisplay::propagateSize()
1520 if (_isFixedSize)
1522 setSize(_columns, _lines);
1523 QWidget::setFixedSize(sizeHint());
1524 parentWidget()->adjustSize();
1525 parentWidget()->setFixedSize(parentWidget()->sizeHint());
1526 return;
1528 if (_image)
1529 updateImageSize();
1532 void TerminalDisplay::updateImageSize()
1534 Character* oldimg = _image;
1535 int oldlin = _lines;
1536 int oldcol = _columns;
1538 makeImage();
1540 // copy the old image to reduce flicker
1541 int lines = qMin(oldlin,_lines);
1542 int columns = qMin(oldcol,_columns);
1544 if (oldimg)
1546 for (int line = 0; line < lines; line++)
1548 memcpy((void*)&_image[_columns*line],
1549 (void*)&oldimg[oldcol*line],columns*sizeof(Character));
1551 delete[] oldimg;
1554 if (_screenWindow)
1555 _screenWindow->setWindowLines(_lines);
1557 _resizing = (oldlin!=_lines) || (oldcol!=_columns);
1559 if ( _resizing )
1561 showResizeNotification();
1562 emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
1565 _resizing = false;
1568 //showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1569 //display has been resized when the display is hidden or shown.
1571 //TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1572 //the same signal as the one for a content size change
1573 void TerminalDisplay::showEvent(QShowEvent*)
1575 emit changedContentSizeSignal(_contentHeight,_contentWidth);
1577 void TerminalDisplay::hideEvent(QHideEvent*)
1579 emit changedContentSizeSignal(_contentHeight,_contentWidth);
1582 /* ------------------------------------------------------------------------- */
1583 /* */
1584 /* Scrollbar */
1585 /* */
1586 /* ------------------------------------------------------------------------- */
1588 void TerminalDisplay::scrollBarPositionChanged(int)
1590 if ( !_screenWindow )
1591 return;
1593 _screenWindow->scrollTo( _scrollBar->value() );
1595 // if the thumb has been moved to the bottom of the _scrollBar then set
1596 // the display to automatically track new output,
1597 // that is, scroll down automatically
1598 // to how new _lines as they are added
1599 const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
1600 _screenWindow->setTrackOutput( atEndOfOutput );
1602 updateImage();
1605 void TerminalDisplay::setScroll(int cursor, int slines)
1607 // update _scrollBar if the range or value has changed,
1608 // otherwise return
1610 // setting the range or value of a _scrollBar will always trigger
1611 // a repaint, so it should be avoided if it is not necessary
1612 if ( _scrollBar->minimum() == 0 &&
1613 _scrollBar->maximum() == (slines - _lines) &&
1614 _scrollBar->value() == cursor )
1616 return;
1619 disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1620 _scrollBar->setRange(0,slines - _lines);
1621 _scrollBar->setSingleStep(1);
1622 _scrollBar->setPageStep(_lines);
1623 _scrollBar->setValue(cursor);
1624 connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1627 void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
1629 if (_scrollbarLocation == position)
1630 return;
1632 if ( position == NoScrollBar )
1633 _scrollBar->hide();
1634 else
1635 _scrollBar->show();
1637 _topMargin = _leftMargin = 1;
1638 _scrollbarLocation = position;
1640 propagateSize();
1641 update();
1644 void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
1646 if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) {
1647 mouseTripleClickEvent(ev);
1648 return;
1651 if ( !contentsRect().contains(ev->pos()) ) return;
1653 if ( !_screenWindow ) return;
1655 int charLine;
1656 int charColumn;
1657 getCharacterPosition(ev->pos(),charLine,charColumn);
1658 QPoint pos = QPoint(charColumn,charLine);
1660 if ( ev->button() == Qt::LeftButton)
1662 _lineSelectionMode = false;
1663 _wordSelectionMode = false;
1665 emit isBusySelecting(true); // Keep it steady...
1666 // Drag only when the Control key is hold
1667 bool selected = false;
1669 // The receiver of the testIsSelected() signal will adjust
1670 // 'selected' accordingly.
1671 //emit testIsSelected(pos.x(), pos.y(), selected);
1673 selected = _screenWindow->isSelected(pos.x(),pos.y());
1675 if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
1676 // The user clicked inside selected text
1677 dragInfo.state = diPending;
1678 dragInfo.start = ev->pos();
1680 else {
1681 // No reason to ever start a drag event
1682 dragInfo.state = diNone;
1684 _preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) );
1685 _columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
1687 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1689 _screenWindow->clearSelection();
1691 //emit clearSelectionSignal();
1692 pos.ry() += _scrollBar->value();
1693 _iPntSel = _pntSel = pos;
1694 _actSel = 1; // left mouse button pressed but nothing selected yet.
1697 else
1699 emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1703 else if ( ev->button() == Qt::MidButton )
1705 if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) )
1706 emitSelection(true,ev->modifiers() & Qt::ControlModifier);
1707 else
1708 emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1710 else if ( ev->button() == Qt::RightButton )
1712 if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1713 emit configureRequest(ev->pos());
1714 else
1715 emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1719 QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
1721 int charLine, charColumn;
1722 getCharacterPosition(position,charLine,charColumn);
1724 Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
1726 return spot ? spot->actions() : QList<QAction*>();
1729 void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
1731 int charLine = 0;
1732 int charColumn = 0;
1734 getCharacterPosition(ev->pos(),charLine,charColumn);
1736 // handle filters
1737 // change link hot-spot appearance on mouse-over
1738 Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
1739 if ( spot && spot->type() == Filter::HotSpot::Link)
1741 QRegion previousHotspotArea = _mouseOverHotspotArea;
1742 _mouseOverHotspotArea = QRegion();
1743 QRect r;
1744 if (spot->startLine()==spot->endLine()) {
1745 r.setCoords( spot->startColumn()*_fontWidth, spot->startLine()*_fontHeight,
1746 spot->endColumn()*_fontWidth, (spot->endLine()+1)*_fontHeight - 1 );
1747 _mouseOverHotspotArea |= r;
1748 } else {
1749 r.setCoords( spot->startColumn()*_fontWidth, spot->startLine()*_fontHeight,
1750 _columns*_fontWidth - 1, (spot->startLine()+1)*_fontHeight );
1751 _mouseOverHotspotArea |= r;
1752 for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
1753 r.setCoords( 0*_fontWidth, line*_fontHeight,
1754 _columns*_fontWidth, (line+1)*_fontHeight );
1755 _mouseOverHotspotArea |= r;
1757 r.setCoords( 0*_fontWidth, spot->endLine()*_fontHeight,
1758 spot->endColumn()*_fontWidth, (spot->endLine()+1)*_fontHeight );
1759 _mouseOverHotspotArea |= r;
1761 // display tooltips when mousing over links
1762 // TODO: Extend this to work with filter types other than links
1763 const QString& tooltip = spot->tooltip();
1764 if ( !tooltip.isEmpty() )
1766 QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea.boundingRect() );
1769 update( _mouseOverHotspotArea | previousHotspotArea );
1771 else if ( !_mouseOverHotspotArea.isEmpty() )
1773 update( _mouseOverHotspotArea );
1774 // set hotspot area to an invalid rectangle
1775 _mouseOverHotspotArea = QRegion();
1778 // for auto-hiding the cursor, we need mouseTracking
1779 if (ev->buttons() == Qt::NoButton ) return;
1781 // if the terminal is interested in mouse movements
1782 // then emit a mouse movement signal, unless the shift
1783 // key is being held down, which overrides this.
1784 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
1786 int button = 3;
1787 if (ev->buttons() & Qt::LeftButton)
1788 button = 0;
1789 if (ev->buttons() & Qt::MidButton)
1790 button = 1;
1791 if (ev->buttons() & Qt::RightButton)
1792 button = 2;
1795 emit mouseSignal( button,
1796 charColumn + 1,
1797 charLine + 1 +_scrollBar->value() -_scrollBar->maximum(),
1798 1 );
1800 return;
1803 if (dragInfo.state == diPending)
1805 // we had a mouse down, but haven't confirmed a drag yet
1806 // if the mouse has moved sufficiently, we will confirm
1808 int distance = KGlobalSettings::dndEventDelay();
1809 if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance ||
1810 ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance)
1812 // we've left the drag square, we can start a real drag operation now
1813 emit isBusySelecting(false); // Ok.. we can breath again.
1815 _screenWindow->clearSelection();
1816 doDrag();
1818 return;
1820 else if (dragInfo.state == diDragging)
1822 // this isn't technically needed because mouseMoveEvent is suppressed during
1823 // Qt drag operations, replaced by dragMoveEvent
1824 return;
1827 if (_actSel == 0) return;
1829 // don't extend selection while pasting
1830 if (ev->buttons() & Qt::MidButton) return;
1832 extendSelection( ev->pos() );
1835 void TerminalDisplay::extendSelection( const QPoint& position )
1837 QPoint pos = position;
1839 if ( !_screenWindow )
1840 return;
1842 //if ( !contentsRect().contains(ev->pos()) ) return;
1843 QPoint tL = contentsRect().topLeft();
1844 int tLx = tL.x();
1845 int tLy = tL.y();
1846 int scroll = _scrollBar->value();
1848 // we're in the process of moving the mouse with the left button pressed
1849 // the mouse cursor will kept caught within the bounds of the text in
1850 // this widget.
1852 int linesBeyondWidget = 0;
1854 QRect textBounds(tLx + _leftMargin,
1855 tLy + _topMargin,
1856 _usedColumns*_fontWidth-1,
1857 _usedLines*_fontHeight-1);
1859 // Adjust position within text area bounds.
1860 QPoint oldpos = pos;
1862 pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) );
1863 pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) );
1865 if ( oldpos.y() > textBounds.bottom() )
1867 linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight;
1868 _scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward
1870 if ( oldpos.y() < textBounds.top() )
1872 linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight;
1873 _scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // scrollback
1876 int charColumn = 0;
1877 int charLine = 0;
1878 getCharacterPosition(pos,charLine,charColumn);
1880 QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight);
1881 QPoint ohere;
1882 QPoint _iPntSelCorr = _iPntSel;
1883 _iPntSelCorr.ry() -= _scrollBar->value();
1884 QPoint _pntSelCorr = _pntSel;
1885 _pntSelCorr.ry() -= _scrollBar->value();
1886 bool swapping = false;
1888 if ( _wordSelectionMode )
1890 // Extend to word boundaries
1891 int i;
1892 QChar selClass;
1894 bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
1895 ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
1896 bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
1897 ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
1898 swapping = left_not_right != old_left_not_right;
1900 // Find left (left_not_right ? from here : from start)
1901 QPoint left = left_not_right ? here : _iPntSelCorr;
1902 i = loc(left.x(),left.y());
1903 if (i>=0 && i<=_imageSize) {
1904 selClass = charClass(_image[i].character);
1905 while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) ))
1906 && charClass(_image[i-1].character) == selClass )
1907 { i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} }
1910 // Find left (left_not_right ? from start : from here)
1911 QPoint right = left_not_right ? _iPntSelCorr : here;
1912 i = loc(right.x(),right.y());
1913 if (i>=0 && i<=_imageSize) {
1914 selClass = charClass(_image[i].character);
1915 while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) ))
1916 && charClass(_image[i+1].character) == selClass )
1917 { i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } }
1920 // Pick which is start (ohere) and which is extension (here)
1921 if ( left_not_right )
1923 here = left; ohere = right;
1925 else
1927 here = right; ohere = left;
1929 ohere.rx()++;
1932 if ( _lineSelectionMode )
1934 // Extend to complete line
1935 bool above_not_below = ( here.y() < _iPntSelCorr.y() );
1937 QPoint above = above_not_below ? here : _iPntSelCorr;
1938 QPoint below = above_not_below ? _iPntSelCorr : here;
1940 while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) )
1941 above.ry()--;
1942 while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) )
1943 below.ry()++;
1945 above.setX(0);
1946 below.setX(_usedColumns-1);
1948 // Pick which is start (ohere) and which is extension (here)
1949 if ( above_not_below )
1951 here = above; ohere = below;
1953 else
1955 here = below; ohere = above;
1958 QPoint newSelBegin = QPoint( ohere.x(), ohere.y() );
1959 swapping = !(_tripleSelBegin==newSelBegin);
1960 _tripleSelBegin = newSelBegin;
1962 ohere.rx()++;
1965 int offset = 0;
1966 if ( !_wordSelectionMode && !_lineSelectionMode )
1968 int i;
1969 QChar selClass;
1971 bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
1972 ( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
1973 bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
1974 ( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
1975 swapping = left_not_right != old_left_not_right;
1977 // Find left (left_not_right ? from here : from start)
1978 QPoint left = left_not_right ? here : _iPntSelCorr;
1980 // Find left (left_not_right ? from start : from here)
1981 QPoint right = left_not_right ? _iPntSelCorr : here;
1982 if ( right.x() > 0 && !_columnSelectionMode )
1984 i = loc(right.x(),right.y());
1985 if (i>=0 && i<=_imageSize) {
1986 selClass = charClass(_image[i-1].character);
1987 /* if (selClass == ' ')
1989 while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
1990 !(_lineProperties[right.y()] & LINE_WRAPPED))
1991 { i++; right.rx()++; }
1992 if (right.x() < _usedColumns-1)
1993 right = left_not_right ? _iPntSelCorr : here;
1994 else
1995 right.rx()++; // will be balanced later because of offset=-1;
2000 // Pick which is start (ohere) and which is extension (here)
2001 if ( left_not_right )
2003 here = left; ohere = right; offset = 0;
2005 else
2007 here = right; ohere = left; offset = -1;
2011 if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved
2013 if (here == ohere) return; // It's not left, it's not right.
2015 if ( _actSel < 2 || swapping )
2017 if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
2019 _screenWindow->setSelectionStart( ohere.x() , ohere.y() , true );
2021 else
2023 _screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false );
2028 _actSel = 2; // within selection
2029 _pntSel = here;
2030 _pntSel.ry() += _scrollBar->value();
2032 if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
2034 _screenWindow->setSelectionEnd( here.x() , here.y() );
2036 else
2038 _screenWindow->setSelectionEnd( here.x()+offset , here.y() );
2043 void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
2045 if ( !_screenWindow )
2046 return;
2048 int charLine;
2049 int charColumn;
2050 getCharacterPosition(ev->pos(),charLine,charColumn);
2052 if ( ev->button() == Qt::LeftButton)
2054 emit isBusySelecting(false);
2055 if(dragInfo.state == diPending)
2057 // We had a drag event pending but never confirmed. Kill selection
2058 _screenWindow->clearSelection();
2059 //emit clearSelectionSignal();
2061 else
2063 if ( _actSel > 1 )
2065 setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
2068 _actSel = 0;
2070 //FIXME: emits a release event even if the mouse is
2071 // outside the range. The procedure used in `mouseMoveEvent'
2072 // applies here, too.
2074 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2075 emit mouseSignal( 3, // release
2076 charColumn + 1,
2077 charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
2079 dragInfo.state = diNone;
2083 if ( !_mouseMarks &&
2084 ((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier))
2085 || ev->button() == Qt::MidButton) )
2087 emit mouseSignal( 3,
2088 charColumn + 1,
2089 charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
2094 void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const
2096 column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth;
2097 line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight;
2099 if ( line < 0 )
2100 line = 0;
2101 if ( column < 0 )
2102 column = 0;
2104 if ( line >= _usedLines )
2105 line = _usedLines-1;
2107 // the column value returned can be equal to _usedColumns, which
2108 // is the position just after the last character displayed in a line.
2110 // this is required so that the user can select characters in the right-most
2111 // column (or left-most for right-to-left input)
2112 if ( column > _usedColumns )
2113 column = _usedColumns;
2116 void TerminalDisplay::updateLineProperties()
2118 if ( !_screenWindow )
2119 return;
2121 _lineProperties = _screenWindow->getLineProperties();
2124 void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
2126 if ( ev->button() != Qt::LeftButton) return;
2127 if ( !_screenWindow ) return;
2129 int charLine = 0;
2130 int charColumn = 0;
2132 getCharacterPosition(ev->pos(),charLine,charColumn);
2134 QPoint pos(charColumn,charLine);
2136 // pass on double click as two clicks.
2137 if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2139 // Send just _ONE_ click event, since the first click of the double click
2140 // was already sent by the click handler
2141 emit mouseSignal( 0,
2142 pos.x()+1,
2143 pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(),
2144 0 ); // left button
2145 return;
2148 _screenWindow->clearSelection();
2149 QPoint bgnSel = pos;
2150 QPoint endSel = pos;
2151 int i = loc(bgnSel.x(),bgnSel.y());
2152 _iPntSel = bgnSel;
2153 _iPntSel.ry() += _scrollBar->value();
2155 _wordSelectionMode = true;
2157 // find word boundaries...
2158 QChar selClass = charClass(_image[i].character);
2160 // find the start of the word
2161 int x = bgnSel.x();
2162 while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) ))
2163 && charClass(_image[i-1].character) == selClass )
2165 i--;
2166 if (x>0)
2167 x--;
2168 else
2170 x=_usedColumns-1;
2171 bgnSel.ry()--;
2175 bgnSel.setX(x);
2176 _screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false );
2178 // find the end of the word
2179 i = loc( endSel.x(), endSel.y() );
2180 x = endSel.x();
2181 while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) ))
2182 && charClass(_image[i+1].character) == selClass )
2184 i++;
2185 if (x<_usedColumns-1)
2186 x++;
2187 else
2189 x=0;
2190 endSel.ry()++;
2194 endSel.setX(x);
2196 // In word selection mode don't select @ (64) if at end of word.
2197 if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) )
2198 endSel.setX( x - 1 );
2201 _actSel = 2; // within selection
2203 _screenWindow->setSelectionEnd( endSel.x() , endSel.y() );
2205 setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
2208 _possibleTripleClick=true;
2210 QTimer::singleShot(QApplication::doubleClickInterval(),this,
2211 SLOT(tripleClickTimeout()));
2214 void TerminalDisplay::wheelEvent( QWheelEvent* ev )
2216 if (ev->orientation() != Qt::Vertical)
2217 return;
2219 // if the terminal program is not interested mouse events
2220 // then send the event to the scrollbar if the slider has room to move
2221 // or otherwise send simulated up / down key presses to the terminal program
2222 // for the benefit of programs such as 'less'
2223 if ( _mouseMarks )
2225 bool canScroll = _scrollBar->maximum() > 0;
2226 if (canScroll)
2227 _scrollBar->event(ev);
2228 else
2230 // assume that each Up / Down key event will cause the terminal application
2231 // to scroll by one line.
2233 // to get a reasonable scrolling speed, scroll by one line for every 5 degrees
2234 // of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees,
2235 // giving a scroll of 3 lines
2236 int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down;
2238 // QWheelEvent::delta() gives rotation in eighths of a degree
2239 int wheelDegrees = ev->delta() / 8;
2240 int linesToScroll = abs(wheelDegrees) / 5;
2242 QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier);
2244 for (int i=0;i<linesToScroll;i++)
2245 emit keyPressedSignal(&keyScrollEvent);
2248 else
2250 // terminal program wants notification of mouse activity
2252 int charLine;
2253 int charColumn;
2254 getCharacterPosition( ev->pos() , charLine , charColumn );
2256 emit mouseSignal( ev->delta() > 0 ? 4 : 5,
2257 charColumn + 1,
2258 charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
2263 void TerminalDisplay::tripleClickTimeout()
2265 _possibleTripleClick=false;
2268 void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
2270 if ( !_screenWindow ) return;
2272 int charLine;
2273 int charColumn;
2274 getCharacterPosition(ev->pos(),charLine,charColumn);
2275 _iPntSel = QPoint(charColumn,charLine);
2277 _screenWindow->clearSelection();
2279 _lineSelectionMode = true;
2280 _wordSelectionMode = false;
2282 _actSel = 2; // within selection
2283 emit isBusySelecting(true); // Keep it steady...
2285 while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
2286 _iPntSel.ry()--;
2288 if (_tripleClickMode == SelectForwardsFromCursor) {
2289 // find word boundary start
2290 int i = loc(_iPntSel.x(),_iPntSel.y());
2291 QChar selClass = charClass(_image[i].character);
2292 int x = _iPntSel.x();
2294 while ( ((x>0) ||
2295 (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
2297 && charClass(_image[i-1].character) == selClass )
2299 i--;
2300 if (x>0)
2301 x--;
2302 else
2304 x=_columns-1;
2305 _iPntSel.ry()--;
2309 _screenWindow->setSelectionStart( x , _iPntSel.y() , false );
2310 _tripleSelBegin = QPoint( x, _iPntSel.y() );
2312 else if (_tripleClickMode == SelectWholeLine) {
2313 _screenWindow->setSelectionStart( 0 , _iPntSel.y() , false );
2314 _tripleSelBegin = QPoint( 0, _iPntSel.y() );
2317 while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) )
2318 _iPntSel.ry()++;
2320 _screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() );
2322 setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2324 _iPntSel.ry() += _scrollBar->value();
2328 bool TerminalDisplay::focusNextPrevChild( bool next )
2330 if (next)
2331 return false; // This disables changing the active part in konqueror
2332 // when pressing Tab
2333 return QWidget::focusNextPrevChild( next );
2337 QChar TerminalDisplay::charClass(QChar qch) const
2339 if ( qch.isSpace() ) return ' ';
2341 if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) )
2342 return 'a';
2344 return qch;
2347 void TerminalDisplay::setWordCharacters(const QString& wc)
2349 _wordCharacters = wc;
2352 void TerminalDisplay::setUsesMouse(bool on)
2354 _mouseMarks = on;
2355 setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
2357 bool TerminalDisplay::usesMouse() const
2359 return _mouseMarks;
2362 /* ------------------------------------------------------------------------- */
2363 /* */
2364 /* Clipboard */
2365 /* */
2366 /* ------------------------------------------------------------------------- */
2368 #undef KeyPress
2370 void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn)
2372 if ( !_screenWindow )
2373 return;
2375 // Paste Clipboard by simulating keypress events
2376 QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection :
2377 QClipboard::Clipboard);
2378 if(appendReturn)
2379 text.append("\r");
2380 if ( ! text.isEmpty() )
2382 text.replace('\n', '\r');
2383 QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
2384 emit keyPressedSignal(&e); // expose as a big fat keypress event
2386 _screenWindow->clearSelection();
2390 void TerminalDisplay::setSelection(const QString& t)
2392 QApplication::clipboard()->setText(t, QClipboard::Selection);
2395 void TerminalDisplay::copyClipboard()
2397 if ( !_screenWindow )
2398 return;
2400 QString text = _screenWindow->selectedText(_preserveLineBreaks);
2401 QApplication::clipboard()->setText(text);
2404 void TerminalDisplay::pasteClipboard()
2406 emitSelection(false,false);
2409 void TerminalDisplay::pasteSelection()
2411 emitSelection(true,false);
2414 /* ------------------------------------------------------------------------- */
2415 /* */
2416 /* Keyboard */
2417 /* */
2418 /* ------------------------------------------------------------------------- */
2420 void TerminalDisplay::setFlowControlWarningEnabled( bool enable )
2422 _flowControlWarningEnabled = enable;
2424 // if the dialog is currently visible and the flow control warning has
2425 // been disabled then hide the dialog
2426 if (!enable)
2427 outputSuspended(false);
2430 void TerminalDisplay::keyPressEvent( QKeyEvent* event )
2432 bool emitKeyPressSignal = true;
2434 // Keyboard-based navigation
2435 if ( event->modifiers() == Qt::ShiftModifier )
2437 bool update = true;
2439 if ( event->key() == Qt::Key_PageUp )
2441 _screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 );
2443 else if ( event->key() == Qt::Key_PageDown )
2445 _screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 );
2447 else if ( event->key() == Qt::Key_Up )
2449 _screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 );
2451 else if ( event->key() == Qt::Key_Down )
2453 _screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 );
2455 else
2456 update = false;
2458 if ( update )
2460 _screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
2462 updateLineProperties();
2463 updateImage();
2465 // do not send key press to terminal
2466 emitKeyPressSignal = false;
2470 _actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
2471 // know where the current selection is.
2473 if (_hasBlinkingCursor)
2475 _blinkCursorTimer->start(BLINK_DELAY);
2476 if (_cursorBlinking)
2477 blinkCursorEvent();
2478 else
2479 _cursorBlinking = false;
2482 if ( emitKeyPressSignal )
2483 emit keyPressedSignal(event);
2485 event->accept();
2488 void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event )
2490 QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString());
2491 emit keyPressedSignal(&keyEvent);
2493 _inputMethodData.preeditString = event->preeditString();
2494 update(preeditRect() | _inputMethodData.previousPreeditRect);
2496 event->accept();
2498 QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const
2500 const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0);
2501 switch ( query )
2503 case Qt::ImMicroFocus:
2504 return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1));
2505 break;
2506 case Qt::ImFont:
2507 return font();
2508 break;
2509 case Qt::ImCursorPosition:
2510 // return the cursor position within the current line
2511 return cursorPos.x();
2512 break;
2513 case Qt::ImSurroundingText:
2515 // return the text from the current line
2516 QString lineText;
2517 QTextStream stream(&lineText);
2518 PlainTextDecoder decoder;
2519 decoder.begin(&stream);
2520 decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]);
2521 decoder.end();
2522 return lineText;
2524 break;
2525 case Qt::ImCurrentSelection:
2526 return QString();
2527 break;
2530 return QVariant();
2533 bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent)
2535 int modifiers = keyEvent->modifiers();
2537 // When a possible shortcut combination is pressed,
2538 // emit the overrideShortcutCheck() signal to allow the host
2539 // to decide whether the terminal should override it or not.
2540 if (modifiers != Qt::NoModifier)
2542 int modifierCount = 0;
2543 unsigned int currentModifier = Qt::ShiftModifier;
2545 while (currentModifier <= Qt::KeypadModifier)
2547 if (modifiers & currentModifier)
2548 modifierCount++;
2549 currentModifier <<= 1;
2551 if (modifierCount < 2)
2553 bool override = false;
2554 emit overrideShortcutCheck(keyEvent,override);
2555 if (override)
2557 keyEvent->accept();
2558 return true;
2563 // Override any of the following shortcuts because
2564 // they are needed by the terminal
2565 int keyCode = keyEvent->key() | modifiers;
2566 switch ( keyCode )
2568 // list is taken from the QLineEdit::event() code
2569 case Qt::Key_Tab:
2570 case Qt::Key_Delete:
2571 case Qt::Key_Home:
2572 case Qt::Key_End:
2573 case Qt::Key_Backspace:
2574 case Qt::Key_Left:
2575 case Qt::Key_Right:
2576 keyEvent->accept();
2577 return true;
2579 return false;
2582 bool TerminalDisplay::event(QEvent* event)
2584 bool eventHandled = false;
2585 switch (event->type())
2587 case QEvent::ShortcutOverride:
2588 eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event);
2589 break;
2590 default:
2591 break;
2593 return eventHandled ? true : QWidget::event(event);
2596 void TerminalDisplay::setBellMode(int mode)
2598 _bellMode=mode;
2601 void TerminalDisplay::enableBell()
2603 _allowBell = true;
2606 void TerminalDisplay::bell(const QString& message)
2608 if (_bellMode==NoBell) return;
2610 //limit the rate at which bells can occur
2611 //...mainly for sound effects where rapid bells in sequence
2612 //produce a horrible noise
2613 if ( _allowBell )
2615 _allowBell = false;
2616 QTimer::singleShot(500,this,SLOT(enableBell()));
2618 if (_bellMode==SystemBeepBell)
2620 KNotification::beep();
2622 else if (_bellMode==NotifyBell)
2624 KNotification::event("BellVisible", message,QPixmap(),this);
2626 else if (_bellMode==VisualBell)
2628 swapColorTable();
2629 QTimer::singleShot(200,this,SLOT(swapColorTable()));
2634 void TerminalDisplay::swapColorTable()
2636 ColorEntry color = _colorTable[1];
2637 _colorTable[1]=_colorTable[0];
2638 _colorTable[0]= color;
2639 _colorsInverted = !_colorsInverted;
2640 update();
2643 void TerminalDisplay::clearImage()
2645 // We initialize _image[_imageSize] too. See makeImage()
2646 for (int i = 0; i <= _imageSize; i++)
2648 _image[i].character = ' ';
2649 _image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
2650 DEFAULT_FORE_COLOR);
2651 _image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
2652 DEFAULT_BACK_COLOR);
2653 _image[i].rendition = DEFAULT_RENDITION;
2657 void TerminalDisplay::calcGeometry()
2659 _scrollBar->resize(QApplication::style()->pixelMetric(QStyle::PM_ScrollBarExtent),
2660 contentsRect().height());
2661 switch(_scrollbarLocation)
2663 case NoScrollBar :
2664 _leftMargin = DEFAULT_LEFT_MARGIN;
2665 _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN;
2666 break;
2667 case ScrollBarLeft :
2668 _leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width();
2669 _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
2670 _scrollBar->move(contentsRect().topLeft());
2671 break;
2672 case ScrollBarRight:
2673 _leftMargin = DEFAULT_LEFT_MARGIN;
2674 _contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
2675 _scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0));
2676 break;
2679 _topMargin = DEFAULT_TOP_MARGIN;
2680 _contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1;
2682 if (!_isFixedSize)
2684 // ensure that display is always at least one column wide
2685 _columns = qMax(1,_contentWidth / _fontWidth);
2686 _usedColumns = qMin(_usedColumns,_columns);
2688 // ensure that display is always at least one line high
2689 _lines = qMax(1,_contentHeight / _fontHeight);
2690 _usedLines = qMin(_usedLines,_lines);
2694 void TerminalDisplay::makeImage()
2696 calcGeometry();
2698 // confirm that array will be of non-zero size, since the painting code
2699 // assumes a non-zero array length
2700 Q_ASSERT( _lines > 0 && _columns > 0 );
2701 Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns );
2703 _imageSize=_lines*_columns;
2705 // We over-commit one character so that we can be more relaxed in dealing with
2706 // certain boundary conditions: _image[_imageSize] is a valid but unused position
2707 _image = new Character[_imageSize+1];
2709 clearImage();
2712 // calculate the needed size, this must be synced with calcGeometry()
2713 void TerminalDisplay::setSize(int columns, int lines)
2715 int scrollBarWidth = _scrollBar->isHidden() ? 0 :
2716 style()->pixelMetric(QStyle::PM_ScrollBarExtent);
2717 int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN;
2718 int verticalMargin = 2 * DEFAULT_TOP_MARGIN;
2720 QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth) ,
2721 verticalMargin + (lines * _fontHeight) );
2723 if ( newSize != size() )
2725 _size = newSize;
2726 updateGeometry();
2730 void TerminalDisplay::setFixedSize(int cols, int lins)
2732 _isFixedSize = true;
2734 //ensure that display is at least one line by one column in size
2735 _columns = qMax(1,cols);
2736 _lines = qMax(1,lins);
2737 _usedColumns = qMin(_usedColumns,_columns);
2738 _usedLines = qMin(_usedLines,_lines);
2740 if (_image)
2742 delete[] _image;
2743 makeImage();
2745 setSize(cols, lins);
2746 QWidget::setFixedSize(_size);
2749 QSize TerminalDisplay::sizeHint() const
2751 return _size;
2755 /* --------------------------------------------------------------------- */
2756 /* */
2757 /* Drag & Drop */
2758 /* */
2759 /* --------------------------------------------------------------------- */
2761 void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
2763 if (event->mimeData()->hasFormat("text/plain"))
2764 event->acceptProposedAction();
2767 void TerminalDisplay::dropEvent(QDropEvent* event)
2769 KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
2771 QString dropText;
2772 if (!urls.isEmpty())
2774 for ( int i = 0 ; i < urls.count() ; i++ )
2776 KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
2777 QString urlText;
2779 if (url.isLocalFile())
2780 urlText = url.path();
2781 else
2782 urlText = url.url();
2784 // in future it may be useful to be able to insert file names with drag-and-drop
2785 // without quoting them (this only affects paths with spaces in)
2786 urlText = KShell::quoteArg(urlText);
2788 dropText += urlText;
2790 if ( i != urls.count()-1 )
2791 dropText += ' ';
2794 else
2796 dropText = event->mimeData()->text();
2799 if(event->mimeData()->hasFormat("text/plain"))
2801 emit sendStringToEmu(dropText.toLocal8Bit());
2805 void TerminalDisplay::doDrag()
2807 dragInfo.state = diDragging;
2808 dragInfo.dragObject = new QDrag(this);
2809 QMimeData *mimeData = new QMimeData;
2810 mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
2811 dragInfo.dragObject->setMimeData(mimeData);
2812 dragInfo.dragObject->start(Qt::CopyAction);
2813 // Don't delete the QTextDrag object. Qt will delete it when it's done with it.
2816 void TerminalDisplay::outputSuspended(bool suspended)
2818 //create the label when this function is first called
2819 if (!_outputSuspendedLabel)
2821 //This label includes a link to an English language website
2822 //describing the 'flow control' (Xon/Xoff) feature found in almost
2823 //all terminal emulators.
2824 //If there isn't a suitable article available in the target language the link
2825 //can simply be removed.
2826 _outputSuspendedLabel = new QLabel( i18n("<qt>Output has been "
2827 "<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>"
2828 " by pressing Ctrl+S."
2829 " Press <b>Ctrl+Q</b> to resume.</qt>"),
2830 this );
2832 QPalette palette(_outputSuspendedLabel->palette());
2833 KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
2834 _outputSuspendedLabel->setPalette(palette);
2835 _outputSuspendedLabel->setAutoFillBackground(true);
2836 _outputSuspendedLabel->setBackgroundRole(QPalette::Base);
2837 _outputSuspendedLabel->setFont(QApplication::font());
2838 _outputSuspendedLabel->setMargin(5);
2840 //enable activation of "Xon/Xoff" link in label
2841 _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse |
2842 Qt::LinksAccessibleByKeyboard);
2843 _outputSuspendedLabel->setOpenExternalLinks(true);
2844 _outputSuspendedLabel->setVisible(false);
2846 _gridLayout->addWidget(_outputSuspendedLabel);
2847 _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding,
2848 QSizePolicy::Expanding),
2849 1,0);
2853 _outputSuspendedLabel->setVisible(suspended);
2856 uint TerminalDisplay::lineSpacing() const
2858 return _lineSpacing;
2861 void TerminalDisplay::setLineSpacing(uint i)
2863 _lineSpacing = i;
2864 setVTFont(font()); // Trigger an update.
2867 AutoScrollHandler::AutoScrollHandler(QWidget* parent)
2868 : QObject(parent)
2869 , _timerId(0)
2871 parent->installEventFilter(this);
2873 void AutoScrollHandler::timerEvent(QTimerEvent* event)
2875 if (event->timerId() != _timerId)
2876 return;
2878 QMouseEvent mouseEvent( QEvent::MouseMove,
2879 widget()->mapFromGlobal(QCursor::pos()),
2880 Qt::NoButton,
2881 Qt::LeftButton,
2882 Qt::NoModifier);
2884 QApplication::sendEvent(widget(),&mouseEvent);
2886 bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event)
2888 Q_ASSERT( watched == parent() );
2890 QMouseEvent* mouseEvent = (QMouseEvent*)event;
2891 switch (event->type())
2893 case QEvent::MouseMove:
2895 bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
2897 if (mouseInWidget)
2899 if (_timerId)
2900 killTimer(_timerId);
2901 _timerId = 0;
2903 else
2905 if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton))
2906 _timerId = startTimer(100);
2908 break;
2910 case QEvent::MouseButtonRelease:
2911 if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton))
2913 killTimer(_timerId);
2914 _timerId = 0;
2916 break;
2917 default:
2918 break;
2921 return false;
2924 #include "TerminalDisplay.moc"