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
24 #include "TerminalDisplay.h"
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>
46 #include <KColorScheme>
51 #include <KNotification>
52 #include <KGlobalSettings>
54 #include <KIO/NetAccess>
57 #include <config-apps.h>
59 #include "konsole_wcwidth.h"
60 #include "ScreenWindow.h"
61 #include "TerminalCharacterDecoder.h"
63 using namespace Konsole
;
66 #define loc(X,Y) ((Y)*_columns+(X))
69 #define yMouseScroll 1
71 #define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
72 "abcdefgjijklmnopqrstuvwxyz" \
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.
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
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.
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 /* ------------------------------------------------------------------------- */
109 /* ------------------------------------------------------------------------- */
111 /* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
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
128 disconnect( _screenWindow
, 0 , this , 0 );
131 _screenWindow
= window
;
136 #warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?"
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
148 void TerminalDisplay::setBackgroundColor(const QColor
& color
)
150 _colorTable
[DEFAULT_BACK_COLOR
].color
= color
;
151 QPalette p
= palette();
152 p
.setColor( backgroundRole(), color
);
155 // Avoid propagating the palette change to the scroll bar
156 _scrollBar
->setPalette( QApplication::palette() );
160 void TerminalDisplay::setForegroundColor(const QColor
& color
)
162 _colorTable
[DEFAULT_FORE_COLOR
].color
= color
;
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 /* ------------------------------------------------------------------------- */
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
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
));
222 int fw
= fm
.width(REPCHAR
[0]);
223 for(unsigned int i
=1; i
< strlen(REPCHAR
); i
++)
225 if (fw
!= fm
.width(REPCHAR
[i
]))
235 _fontAscent
= fm
.ascent();
237 emit
changedFontMetricSignal( _fontHeight
, _fontWidth
);
242 void TerminalDisplay::setVTFont(const QFont
& 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
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
);
270 void TerminalDisplay::setFont(const QFont
&)
272 // ignore font change request if not coming from konsole itself
275 /* ------------------------------------------------------------------------- */
277 /* Constructor / Destructor */
279 /* ------------------------------------------------------------------------- */
281 TerminalDisplay::TerminalDisplay(QWidget
*parent
)
298 ,_terminalSizeHint(false)
299 ,_terminalSizeStartup(true)
302 ,_wordSelectionMode(false)
303 ,_lineSelectionMode(false)
304 ,_preserveLineBreaks(false)
305 ,_columnSelectionMode(false)
306 ,_scrollbarLocation(NoScrollBar
)
307 ,_wordCharacters(":@-./_~")
308 ,_bellMode(SystemBeepBell
)
311 ,_cursorBlinking(false)
312 ,_hasBlinkingCursor(false)
314 ,_tripleClickMode(SelectWholeLine
)
316 ,_possibleTripleClick(false)
319 ,_flowControlWarningEnabled(false)
320 ,_outputSuspendedLabel(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);
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 );
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 );
385 delete _outputSuspendedLabel
;
389 /* ------------------------------------------------------------------------- */
391 /* Display Operations */
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:
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.
453 quint32 toDraw
= LineChars
[code
];
457 paint
.drawLine(cx
-1, y
, cx
-1, cy
-2);
459 paint
.drawLine(cx
, y
, cx
, cy
-2);
461 paint
.drawLine(cx
+1, y
, cx
+1, cy
-2);
465 paint
.drawLine(cx
-1, cy
+2, cx
-1, ey
);
467 paint
.drawLine(cx
, cy
+2, cx
, ey
);
469 paint
.drawLine(cx
+1, cy
+2, cx
+1, ey
);
473 paint
.drawLine(x
, cy
-1, cx
-2, cy
-1);
475 paint
.drawLine(x
, cy
, cx
-2, cy
);
477 paint
.drawLine(x
, cy
+1, cx
-2, cy
+1);
481 paint
.drawLine(cx
+2, cy
-1, ex
, cy
-1);
483 paint
.drawLine(cx
+2, cy
, ex
, cy
);
485 paint
.drawLine(cx
+2, cy
+1, ex
, cy
+1);
487 //Intersection points.
489 paint
.drawPoint(cx
-1, cy
-1);
491 paint
.drawPoint(cx
, cy
-1);
493 paint
.drawPoint(cx
+1, cy
-1);
496 paint
.drawPoint(cx
-1, cy
);
498 paint
.drawPoint(cx
, cy
);
500 paint
.drawPoint(cx
+1, cy
);
503 paint
.drawPoint(cx
-1, cy
+1);
505 paint
.drawPoint(cx
, cy
+1);
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
);
520 painter
.setPen( boldPen
);
523 for (int i
=0 ; i
< str
.length(); i
++)
525 uchar code
= str
[i
].cell();
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
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
550 _cursorColor
= color
;
552 QColor
TerminalDisplay::keyboardCursorColor() const
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);
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
586 QRect scrollBarArea
= _scrollBar
->isVisible() ?
587 rect
.intersected(_scrollBar
->geometry()) :
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
));
598 painter
.setCompositionMode(QPainter::CompositionMode_Source
);
599 painter
.fillRect(contentsRect
, color
);
603 painter
.fillRect(contentsRect
, backgroundColor
);
605 painter
.fillRect(scrollBarArea
,_scrollBar
->palette().background());
608 void TerminalDisplay::drawCursor(QPainter
& painter
,
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
);
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,
632 - penWidth
/2 - penWidth
%2,
633 - penWidth
/2 - penWidth
%2));
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(),
650 cursorRect
.bottom());
651 else if ( _cursorShape
== IBeamCursor
)
652 painter
.drawLine(cursorRect
.left(),
655 cursorRect
.bottom());
660 void TerminalDisplay::drawCharacters(QPainter
& painter
,
663 const Character
* style
,
664 bool invertCharacterColor
)
666 // don't draw text which is currently blinking
667 if ( _blinking
&& (style
->rendition
& RE_BLINK
) )
670 // setup bold and underline
672 ColorEntry::FontWeight weight
= style
->fontWeight(_colorTable
);
673 if (weight
== ColorEntry::UseCurrentFormat
)
674 useBold
= style
->rendition
& RE_BOLD
|| font().bold();
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
);
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
)
695 painter
.setPen(color
);
699 if ( isLineCharString(text
) )
700 drawLineCharString(painter
,rect
.x(),rect
.y(),text
,style
);
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
710 painter
.drawText(rect
,0,text
);
712 painter
.drawText(rect
,0,LTR_OVERRIDE_CHAR
+text
);
716 void TerminalDisplay::drawTextFragment(QPainter
& painter
,
719 const Character
* style
)
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
);
739 drawCharacters(painter
,rect
,text
,style
,invertCharacterColor
);
744 void TerminalDisplay::setRandomSeed(uint randomSeed
) { _randomSeed
= randomSeed
; }
745 uint
TerminalDisplay::randomSeed() const { return _randomSeed
; }
751 void TerminalDisplay::setCursorPos(const int curx
, const int cury
)
753 QPoint tL
= contentsRect().topLeft();
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);
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() )
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
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;
814 if ( _scrollbarLocation
== ScrollBarLeft
)
816 scrollRect
.setLeft(scrollBarWidth
+SCROLLBAR_CONTENT_GAP
);
817 scrollRect
.setRight(width());
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
*
833 Q_ASSERT( linesToMove
> 0 );
834 Q_ASSERT( bytesToMove
> 0 );
836 //scroll internal image
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
);
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
874 foreach( Filter::HotSpot
* hotSpot
, _filterChain
->hotSpots() )
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
);;
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
++ ) {
892 r
.setRight(_columns
);
894 region
|= imageToWidget(r
);;
897 r
.setTop(hotSpot
->endLine());
898 r
.setRight(hotSpot
->endColumn());
899 r
.setBottom(hotSpot
->endLine());
900 region
|= imageToWidget(r
);;
906 void TerminalDisplay::processFilters()
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
)
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() );
948 updateImageSize(); // Create _image
950 Q_ASSERT( this->_usedLines
<= this->_lines
);
951 Q_ASSERT( this->_usedColumns
<= this->_columns
);
955 QPoint tL
= contentsRect().topLeft();
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];
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
] )
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.
1006 quint16 c
= newLine
[x
+0].character
;
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
];
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
)
1034 disstrU
[p
++] = c
; //fontMap(c);
1037 QString
unistr(disstrU
, p
);
1039 bool saveFixedFont
= _fixedFont
;
1047 _fixedFont
= saveFixedFont
;
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
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.
1066 // add the area occupied by this line to the region which needs to be
1068 QRect dirtyRect
= QRect( _leftMargin
+tLx
,
1069 _topMargin
+tLy
+_fontHeight
*y
,
1070 _fontWidth
* columnsToUpdate
,
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
,
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; }
1113 void TerminalDisplay::showResizeNotification()
1115 if (_terminalSizeHint
&& isVisible())
1117 if (_terminalSizeStartup
) {
1118 _terminalSizeStartup
=false;
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
)
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;
1168 _blinkCursorTimer
->stop();
1172 _blinkTimer
->stop();
1174 void TerminalDisplay::focusInEvent(QFocusEvent
*)
1176 if (_hasBlinkingCursor
)
1178 _blinkCursorTimer
->start();
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
1203 return _screenWindow
->cursorPosition();
1208 QRect
TerminalDisplay::preeditRect() const
1210 const int preeditLength
= string_width(_inputMethodData
.preeditString
);
1212 if ( preeditLength
== 0 )
1215 return QRect(_leftMargin
+ _fontWidth
*cursorPosition().x(),
1216 _topMargin
+ _fontHeight
*cursorPosition().y(),
1217 _fontWidth
*preeditLength
,
1221 void TerminalDisplay::drawInputMethodPreeditString(QPainter
& painter
, const QRect
& rect
)
1223 if ( _inputMethodData
.preeditString
.isEmpty() )
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());
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();
1267 if ( spot
->type() == Filter::HotSpot::Link
) {
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 );
1274 r
.setCoords( spot
->startColumn()*_fontWidth
+ 1, spot
->startLine()*_fontHeight
+ 1,
1275 (_columns
-1)*_fontWidth
- 1, (spot
->startLine()+1)*_fontHeight
- 1 );
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 );
1282 r
.setCoords( 0*_fontWidth
+ 1, spot
->endLine()*_fontHeight
+ 1,
1283 (spot
->endColumn()-1)*_fontWidth
- 1, (spot
->endLine()+1)*_fontHeight
- 1 );
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 )
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
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
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
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();
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
;
1364 x
--; // Search for start of multi-column character
1365 for (; x
<= rlx
; x
++)
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
];
1386 c
= _image
[loc(x
,y
)].character
;
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!
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
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
;
1421 QString
unistr(disstrU
,p
);
1423 if (y
< _lineProperties
.size())
1425 if (_lineProperties
[y
] & LINE_DOUBLEWIDTH
)
1428 if (_lineProperties
[y
] & LINE_DOUBLEHEIGHT
)
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
,
1448 &_image
[loc(x
,y
)] ); //,
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
)
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.
1484 QRect
TerminalDisplay::imageToWidget(const QRect
& imageArea
) const
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() );
1495 void TerminalDisplay::updateCursor()
1497 QRect cursorRect
= imageToWidget( QRect(cursorPosition(),QSize(1,1)) );
1501 void TerminalDisplay::blinkCursorEvent()
1503 _cursorBlinking
= !_cursorBlinking
;
1507 /* ------------------------------------------------------------------------- */
1511 /* ------------------------------------------------------------------------- */
1513 void TerminalDisplay::resizeEvent(QResizeEvent
*)
1518 void TerminalDisplay::propagateSize()
1522 setSize(_columns
, _lines
);
1523 QWidget::setFixedSize(sizeHint());
1524 parentWidget()->adjustSize();
1525 parentWidget()->setFixedSize(parentWidget()->sizeHint());
1532 void TerminalDisplay::updateImageSize()
1534 Character
* oldimg
= _image
;
1535 int oldlin
= _lines
;
1536 int oldcol
= _columns
;
1540 // copy the old image to reduce flicker
1541 int lines
= qMin(oldlin
,_lines
);
1542 int columns
= qMin(oldcol
,_columns
);
1546 for (int line
= 0; line
< lines
; line
++)
1548 memcpy((void*)&_image
[_columns
*line
],
1549 (void*)&oldimg
[oldcol
*line
],columns
*sizeof(Character
));
1555 _screenWindow
->setWindowLines(_lines
);
1557 _resizing
= (oldlin
!=_lines
) || (oldcol
!=_columns
);
1561 showResizeNotification();
1562 emit
changedContentSizeSignal(_contentHeight
, _contentWidth
); // expose resizeEvent
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 /* ------------------------------------------------------------------------- */
1586 /* ------------------------------------------------------------------------- */
1588 void TerminalDisplay::scrollBarPositionChanged(int)
1590 if ( !_screenWindow
)
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
);
1605 void TerminalDisplay::setScroll(int cursor
, int slines
)
1607 // update _scrollBar if the range or value has changed,
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
)
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
)
1632 if ( position
== NoScrollBar
)
1637 _topMargin
= _leftMargin
= 1;
1638 _scrollbarLocation
= position
;
1644 void TerminalDisplay::mousePressEvent(QMouseEvent
* ev
)
1646 if ( _possibleTripleClick
&& (ev
->button()==Qt::LeftButton
) ) {
1647 mouseTripleClickEvent(ev
);
1651 if ( !contentsRect().contains(ev
->pos()) ) return;
1653 if ( !_screenWindow
) return;
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();
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.
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
);
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());
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
)
1734 getCharacterPosition(ev
->pos(),charLine
,charColumn
);
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();
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
;
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
))
1787 if (ev
->buttons() & Qt::LeftButton
)
1789 if (ev
->buttons() & Qt::MidButton
)
1791 if (ev
->buttons() & Qt::RightButton
)
1795 emit
mouseSignal( button
,
1797 charLine
+ 1 +_scrollBar
->value() -_scrollBar
->maximum(),
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();
1820 else if (dragInfo
.state
== diDragging
)
1822 // this isn't technically needed because mouseMoveEvent is suppressed during
1823 // Qt drag operations, replaced by dragMoveEvent
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
)
1842 //if ( !contentsRect().contains(ev->pos()) ) return;
1843 QPoint tL
= contentsRect().topLeft();
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
1852 int linesBeyondWidget
= 0;
1854 QRect
textBounds(tLx
+ _leftMargin
,
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
1878 getCharacterPosition(pos
,charLine
,charColumn
);
1880 QPoint here
= QPoint(charColumn
,charLine
); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight);
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
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
;
1927 here
= right
; ohere
= left
;
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
) )
1942 while (below
.y()<_usedLines
-1 && (_lineProperties
[below
.y()] & LINE_WRAPPED
) )
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
;
1955 here
= below
; ohere
= above
;
1958 QPoint newSelBegin
= QPoint( ohere
.x(), ohere
.y() );
1959 swapping
= !(_tripleSelBegin
==newSelBegin
);
1960 _tripleSelBegin
= newSelBegin
;
1966 if ( !_wordSelectionMode
&& !_lineSelectionMode
)
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;
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;
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 );
2023 _screenWindow
->setSelectionStart( ohere
.x()-1-offset
, ohere
.y() , false );
2028 _actSel
= 2; // within selection
2030 _pntSel
.ry() += _scrollBar
->value();
2032 if ( _columnSelectionMode
&& !_lineSelectionMode
&& !_wordSelectionMode
)
2034 _screenWindow
->setSelectionEnd( here
.x() , here
.y() );
2038 _screenWindow
->setSelectionEnd( here
.x()+offset
, here
.y() );
2043 void TerminalDisplay::mouseReleaseEvent(QMouseEvent
* ev
)
2045 if ( !_screenWindow
)
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();
2065 setSelection( _screenWindow
->selectedText(_preserveLineBreaks
) );
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
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,
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
;
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
)
2121 _lineProperties
= _screenWindow
->getLineProperties();
2124 void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent
* ev
)
2126 if ( ev
->button() != Qt::LeftButton
) return;
2127 if ( !_screenWindow
) return;
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,
2143 pos
.y()+1 +_scrollBar
->value() -_scrollBar
->maximum(),
2148 _screenWindow
->clearSelection();
2149 QPoint bgnSel
= pos
;
2150 QPoint endSel
= pos
;
2151 int i
= loc(bgnSel
.x(),bgnSel
.y());
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
2162 while ( ((x
>0) || (bgnSel
.y()>0 && (_lineProperties
[bgnSel
.y()-1] & LINE_WRAPPED
) ))
2163 && charClass(_image
[i
-1].character
) == selClass
)
2176 _screenWindow
->setSelectionStart( bgnSel
.x() , bgnSel
.y() , false );
2178 // find the end of the word
2179 i
= loc( endSel
.x(), endSel
.y() );
2181 while( ((x
<_usedColumns
-1) || (endSel
.y()<_usedLines
-1 && (_lineProperties
[endSel
.y()] & LINE_WRAPPED
) ))
2182 && charClass(_image
[i
+1].character
) == selClass
)
2185 if (x
<_usedColumns
-1)
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
)
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'
2225 bool canScroll
= _scrollBar
->maximum() > 0;
2227 _scrollBar
->event(ev
);
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
);
2250 // terminal program wants notification of mouse activity
2254 getCharacterPosition( ev
->pos() , charLine
, charColumn
);
2256 emit
mouseSignal( ev
->delta() > 0 ? 4 : 5,
2258 charLine
+ 1 +_scrollBar
->value() -_scrollBar
->maximum() ,
2263 void TerminalDisplay::tripleClickTimeout()
2265 _possibleTripleClick
=false;
2268 void TerminalDisplay::mouseTripleClickEvent(QMouseEvent
* ev
)
2270 if ( !_screenWindow
) return;
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
) )
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();
2295 (_iPntSel
.y()>0 && (_lineProperties
[_iPntSel
.y()-1] & LINE_WRAPPED
) )
2297 && charClass(_image
[i
-1].character
) == selClass
)
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
) )
2320 _screenWindow
->setSelectionEnd( _columns
- 1 , _iPntSel
.y() );
2322 setSelection(_screenWindow
->selectedText(_preserveLineBreaks
));
2324 _iPntSel
.ry() += _scrollBar
->value();
2328 bool TerminalDisplay::focusNextPrevChild( bool 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
) )
2347 void TerminalDisplay::setWordCharacters(const QString
& wc
)
2349 _wordCharacters
= wc
;
2352 void TerminalDisplay::setUsesMouse(bool on
)
2355 setCursor( _mouseMarks
? Qt::IBeamCursor
: Qt::ArrowCursor
);
2357 bool TerminalDisplay::usesMouse() const
2362 /* ------------------------------------------------------------------------- */
2366 /* ------------------------------------------------------------------------- */
2370 void TerminalDisplay::emitSelection(bool useXselection
,bool appendReturn
)
2372 if ( !_screenWindow
)
2375 // Paste Clipboard by simulating keypress events
2376 QString text
= QApplication::clipboard()->text(useXselection
? QClipboard::Selection
:
2377 QClipboard::Clipboard
);
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
)
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 /* ------------------------------------------------------------------------- */
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
2427 outputSuspended(false);
2430 void TerminalDisplay::keyPressEvent( QKeyEvent
* event
)
2432 bool emitKeyPressSignal
= true;
2434 // Keyboard-based navigation
2435 if ( event
->modifiers() == Qt::ShiftModifier
)
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 );
2460 _screenWindow
->setTrackOutput( _screenWindow
->atEndOfOutput() );
2462 updateLineProperties();
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
)
2479 _cursorBlinking
= false;
2482 if ( emitKeyPressSignal
)
2483 emit
keyPressedSignal(event
);
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
);
2498 QVariant
TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query
) const
2500 const QPoint cursorPos
= _screenWindow
? _screenWindow
->cursorPosition() : QPoint(0,0);
2503 case Qt::ImMicroFocus
:
2504 return imageToWidget(QRect(cursorPos
.x(),cursorPos
.y(),1,1));
2509 case Qt::ImCursorPosition
:
2510 // return the cursor position within the current line
2511 return cursorPos
.x();
2513 case Qt::ImSurroundingText
:
2515 // return the text from the current line
2517 QTextStream
stream(&lineText
);
2518 PlainTextDecoder decoder
;
2519 decoder
.begin(&stream
);
2520 decoder
.decodeLine(&_image
[loc(0,cursorPos
.y())],_usedColumns
,_lineProperties
[cursorPos
.y()]);
2525 case Qt::ImCurrentSelection
:
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
)
2549 currentModifier
<<= 1;
2551 if (modifierCount
< 2)
2553 bool override
= false;
2554 emit
overrideShortcutCheck(keyEvent
,override
);
2563 // Override any of the following shortcuts because
2564 // they are needed by the terminal
2565 int keyCode
= keyEvent
->key() | modifiers
;
2568 // list is taken from the QLineEdit::event() code
2570 case Qt::Key_Delete
:
2573 case Qt::Key_Backspace
:
2582 bool TerminalDisplay::event(QEvent
* event
)
2584 bool eventHandled
= false;
2585 switch (event
->type())
2587 case QEvent::ShortcutOverride
:
2588 eventHandled
= handleShortcutOverrideEvent((QKeyEvent
*)event
);
2593 return eventHandled
? true : QWidget::event(event
);
2596 void TerminalDisplay::setBellMode(int mode
)
2601 void TerminalDisplay::enableBell()
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
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
)
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
;
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
)
2664 _leftMargin
= DEFAULT_LEFT_MARGIN
;
2665 _contentWidth
= contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN
;
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());
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));
2679 _topMargin
= DEFAULT_TOP_MARGIN
;
2680 _contentHeight
= contentsRect().height() - 2 * DEFAULT_TOP_MARGIN
+ /* mysterious */ 1;
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()
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];
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() )
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
);
2745 setSize(cols
, lins
);
2746 QWidget::setFixedSize(_size
);
2749 QSize
TerminalDisplay::sizeHint() const
2755 /* --------------------------------------------------------------------- */
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());
2772 if (!urls
.isEmpty())
2774 for ( int i
= 0 ; i
< urls
.count() ; i
++ )
2776 KUrl url
= KIO::NetAccess::mostLocalUrl( urls
[i
] , 0 );
2779 if (url
.isLocalFile())
2780 urlText
= url
.path();
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 )
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>"),
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
),
2853 _outputSuspendedLabel
->setVisible(suspended
);
2856 uint
TerminalDisplay::lineSpacing() const
2858 return _lineSpacing
;
2861 void TerminalDisplay::setLineSpacing(uint i
)
2864 setVTFont(font()); // Trigger an update.
2867 AutoScrollHandler::AutoScrollHandler(QWidget
* parent
)
2871 parent
->installEventFilter(this);
2873 void AutoScrollHandler::timerEvent(QTimerEvent
* event
)
2875 if (event
->timerId() != _timerId
)
2878 QMouseEvent
mouseEvent( QEvent::MouseMove
,
2879 widget()->mapFromGlobal(QCursor::pos()),
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());
2900 killTimer(_timerId
);
2905 if (!_timerId
&& (mouseEvent
->buttons() & Qt::LeftButton
))
2906 _timerId
= startTimer(100);
2910 case QEvent::MouseButtonRelease
:
2911 if (_timerId
&& (mouseEvent
->buttons() & ~Qt::LeftButton
))
2913 killTimer(_timerId
);
2924 #include "TerminalDisplay.moc"