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 <QApplication>
34
#include <QGridLayout>
50
//#include <KColorScheme>
55
//#include <KNotification>
56
//#include <KGlobalSettings>
57
//#include <KShortcut>
58
//#include <KIO/NetAccess>
61
//#include <config-apps.h>
63
#include "konsole_wcwidth.h"
64
#include "ScreenWindow.h"
65
#include "TerminalCharacterDecoder.h"
67
using namespace Konsole;
70
#define loc(X,Y) ((Y)*_columns+(X))
73
#define yMouseScroll 1
75
#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
76
"abcdefgjijklmnopqrstuvwxyz" \
79
const ColorEntry Konsole::base_color_table[TABLE_COLORS] =
80
// The following are almost IBM standard color codes, with some slight
81
// gamma correction for the dim colors to compensate for bright X screens.
82
// It contains the 8 ansiterm/xterm colors in 2 intensities.
84
// Fixme: could add faint colors here, also.
86
ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 1), // Dfore, Dback
87
ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xB2,0x18,0x18), 0), // Black, Red
88
ColorEntry(QColor(0x18,0xB2,0x18), 0), ColorEntry( QColor(0xB2,0x68,0x18), 0), // Green, Yellow
89
ColorEntry(QColor(0x18,0x18,0xB2), 0), ColorEntry( QColor(0xB2,0x18,0xB2), 0), // Blue, Magenta
90
ColorEntry(QColor(0x18,0xB2,0xB2), 0), ColorEntry( QColor(0xB2,0xB2,0xB2), 0), // Cyan, White
92
ColorEntry(QColor(0x00,0x00,0x00), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 1),
93
ColorEntry(QColor(0x68,0x68,0x68), 0), ColorEntry( QColor(0xFF,0x54,0x54), 0),
94
ColorEntry(QColor(0x54,0xFF,0x54), 0), ColorEntry( QColor(0xFF,0xFF,0x54), 0),
95
ColorEntry(QColor(0x54,0x54,0xFF), 0), ColorEntry( QColor(0xFF,0x54,0xFF), 0),
96
ColorEntry(QColor(0x54,0xFF,0xFF), 0), ColorEntry( QColor(0xFF,0xFF,0xFF), 0)
99
// scroll increment used when dragging selection at top/bottom of window.
102
bool TerminalDisplay::_antialiasText = true;
103
bool TerminalDisplay::HAVE_TRANSPARENCY = true;
105
// we use this to force QPainter to display text in LTR mode
106
// more information can be found in: http://unicode.org/reports/tr9/
107
const QChar LTR_OVERRIDE_CHAR( 0x202D );
109
/* ------------------------------------------------------------------------- */
113
/* ------------------------------------------------------------------------- */
115
/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
118
----------- ------- ------- ------- ------- ------- ------- ------- -------
119
ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
120
IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
123
ScreenWindow* TerminalDisplay::screenWindow() const
125
return _screenWindow;
127
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
129
// disconnect existing screen window if any
132
disconnect( _screenWindow , 0 , this , 0 );
135
_screenWindow = window;
140
// TODO: Determine if this is an issue.
141
//#warning "The order here is not specified - does it matter whether updateImage or updateLineProperties comes first?"
142
connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()) );
143
connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()) );
144
connect( _screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateFilters()) );
145
connect( _screenWindow , SIGNAL(scrolled(int)) , this , SLOT(updateFilters()) );
146
window->setWindowLines(_lines);
150
const ColorEntry* TerminalDisplay::colorTable() const
154
QColor TerminalDisplay::getBackgroundColor(){
155
return _colorTable[DEFAULT_BACK_COLOR].color;
158
void TerminalDisplay::setBackgroundColor(const QColor& color)
160
_colorTable[DEFAULT_BACK_COLOR].color = color;
161
QPalette p = palette();
162
p.setColor( backgroundRole(), color );
165
// Avoid propagating the palette change to the scroll bar
166
_scrollBar->setPalette( QApplication::palette() );
170
void TerminalDisplay::setForegroundColor(const QColor& color)
172
_colorTable[DEFAULT_FORE_COLOR].color = color;
176
void TerminalDisplay::setColorTable(const ColorEntry table[])
178
for (int i = 0; i < TABLE_COLORS; i++)
179
_colorTable[i] = table[i];
181
setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
184
/* ------------------------------------------------------------------------- */
188
/* ------------------------------------------------------------------------- */
191
The VT100 has 32 special graphical characters. The usual vt100 extended
192
xterm fonts have these at 0x00..0x1f.
194
QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
195
come in here as proper unicode characters.
197
We treat non-iso10646 fonts as VT100 extended and do the requiered mapping
198
from unicode to 0x00..0x1f. The remaining translation is then left to the
202
static inline bool isLineChar(quint16 c) { return ((c & 0xFF80) == 0x2500);}
203
static inline bool isLineCharString(const QString& string)
205
return (string.length() > 0) && (isLineChar(string.at(0).unicode()));
209
// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
211
unsigned short Konsole::vt100_graphics[32] =
212
{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15
213
0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
214
0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
215
0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
216
0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
219
void TerminalDisplay::fontChange(const QFont&)
221
QFontMetrics fm(font());
222
_fontHeight = fm.height() + _lineSpacing;
224
// waba TerminalDisplay 1.123:
225
// "Base character width on widest ASCII character. This prevents too wide
226
// characters in the presence of double wide (e.g. Japanese) characters."
227
// Get the width from representative normal width characters
228
_fontWidth = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR));
232
int fw = fm.width(REPCHAR[0]);
233
for(unsigned int i=1; i< strlen(REPCHAR); i++)
235
if (fw != fm.width(REPCHAR[i]))
245
_fontAscent = fm.ascent();
247
emit changedFontMetricSignal( _fontHeight, _fontWidth );
252
void TerminalDisplay::setVTFont(const QFont& f)
256
// This was originally set for OS X only:
257
// mac uses floats for font width specification.
258
// this ensures the same handling for all platforms
259
// but then there was revealed that various Linux distros
260
// have this problem too...
261
font.setStyleStrategy(QFont::ForceIntegerMetrics);
263
QFontMetrics metrics(font);
265
if ( !QFontInfo(font).fixedPitch() )
267
qDebug() << "Using an unsupported variable-width font in the terminal. This may produce display errors.";
270
if ( metrics.height() < height() && metrics.maxWidth() < width() )
272
// hint that text should be drawn without anti-aliasing.
273
// depending on the user's font configuration, this may not be respected
275
font.setStyleStrategy( QFont::NoAntialias );
277
// experimental optimization. Konsole assumes that the terminal is using a
278
// mono-spaced font, in which case kerning information should have an effect.
279
// Disabling kerning saves some computation when rendering text.
280
font.setKerning(false);
282
QWidget::setFont(font);
287
void TerminalDisplay::setFont(const QFont &)
289
// ignore font change request if not coming from konsole itself
292
/* ------------------------------------------------------------------------- */
294
/* Constructor / Destructor */
296
/* ------------------------------------------------------------------------- */
298
TerminalDisplay::TerminalDisplay(QWidget *parent)
316
,_terminalSizeHint(false)
317
,_terminalSizeStartup(true)
320
,_wordSelectionMode(false)
321
,_lineSelectionMode(false)
322
,_preserveLineBreaks(false)
323
,_columnSelectionMode(false)
324
,_scrollbarLocation(NoScrollBar)
325
,_wordCharacters(":@-./_~")
326
,_bellMode(SystemBeepBell)
329
,_cursorBlinking(false)
330
,_hasBlinkingCursor(false)
331
,_allowBlinkingText(true)
333
,_tripleClickMode(SelectWholeLine)
335
,_possibleTripleClick(false)
338
,_flowControlWarningEnabled(false)
339
,_outputSuspendedLabel(0)
341
,_colorsInverted(false)
342
,_blendColor(qRgba(0,0,0,0xff))
343
,_filterChain(new TerminalImageFilterChain())
344
,_cursorShape(BlockCursor)
345
,mMotionAfterPasting(NoMoveScreenWindow)
347
// terminal applications are not designed with Right-To-Left in mind,
348
// so the layout is forced to Left-To-Right
349
setLayoutDirection(Qt::LeftToRight);
351
// The offsets are not yet calculated.
352
// Do not calculate these too often to be more smoothly when resizing
353
// konsole in opaque mode.
354
_topMargin = DEFAULT_TOP_MARGIN;
355
_leftMargin = DEFAULT_LEFT_MARGIN;
357
// create scroll bar for scrolling output up and down
358
// set the scroll bar's slider to occupy the whole area of the scroll bar initially
359
_scrollBar = new QScrollBar(this);
361
_scrollBar->setCursor( Qt::ArrowCursor );
362
connect(_scrollBar, SIGNAL(valueChanged(int)), this,
363
SLOT(scrollBarPositionChanged(int)));
364
// qtermwidget: we have to hide it here due the _scrollbarLocation==NoScrollBar
365
// check in TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
368
// setup timers for blinking cursor and text
369
_blinkTimer = new QTimer(this);
370
connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
371
_blinkCursorTimer = new QTimer(this);
372
connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
374
// KCursor::setAutoHideCursor( this, true );
377
setColorTable(base_color_table);
378
setMouseTracking(true);
380
// Enable drag and drop
381
setAcceptDrops(true); // attempt
382
dragInfo.state = diNone;
384
setFocusPolicy( Qt::WheelFocus );
386
// enable input method support
387
setAttribute(Qt::WA_InputMethodEnabled, true);
389
// this is an important optimization, it tells Qt
390
// that TerminalDisplay will handle repainting its entire area.
391
setAttribute(Qt::WA_OpaquePaintEvent);
393
_gridLayout = new QGridLayout(this);
394
_gridLayout->setContentsMargins(0, 0, 0, 0);
396
setLayout( _gridLayout );
398
new AutoScrollHandler(this);
401
TerminalDisplay::~TerminalDisplay()
403
disconnect(_blinkTimer);
404
disconnect(_blinkCursorTimer);
405
qApp->removeEventFilter( this );
410
delete _outputSuspendedLabel;
414
/* ------------------------------------------------------------------------- */
416
/* Display Operations */
418
/* ------------------------------------------------------------------------- */
421
A table for emulating the simple (single width) unicode drawing chars.
422
It represents the 250x - 257x glyphs. If it's zero, we can't use it.
423
if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
424
0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
426
Then, the pixels basically have the following interpretation:
468
#include "LineFont.h"
470
static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
472
//Calculate cell midpoints, end points.
478
quint32 toDraw = LineChars[code];
482
paint.drawLine(cx-1, y, cx-1, cy-2);
484
paint.drawLine(cx, y, cx, cy-2);
486
paint.drawLine(cx+1, y, cx+1, cy-2);
490
paint.drawLine(cx-1, cy+2, cx-1, ey);
492
paint.drawLine(cx, cy+2, cx, ey);
494
paint.drawLine(cx+1, cy+2, cx+1, ey);
498
paint.drawLine(x, cy-1, cx-2, cy-1);
500
paint.drawLine(x, cy, cx-2, cy);
502
paint.drawLine(x, cy+1, cx-2, cy+1);
506
paint.drawLine(cx+2, cy-1, ex, cy-1);
508
paint.drawLine(cx+2, cy, ex, cy);
510
paint.drawLine(cx+2, cy+1, ex, cy+1);
512
//Intersection points.
514
paint.drawPoint(cx-1, cy-1);
516
paint.drawPoint(cx, cy-1);
518
paint.drawPoint(cx+1, cy-1);
521
paint.drawPoint(cx-1, cy);
523
paint.drawPoint(cx, cy);
525
paint.drawPoint(cx+1, cy);
528
paint.drawPoint(cx-1, cy+1);
530
paint.drawPoint(cx, cy+1);
532
paint.drawPoint(cx+1, cy+1);
536
void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const QString& str,
537
const Character* attributes)
539
const QPen& currentPen = painter.pen();
541
if ( (attributes->rendition & RE_BOLD) && _boldIntense )
543
QPen boldPen(currentPen);
545
painter.setPen( boldPen );
548
for (int i=0 ; i < str.length(); i++)
550
uchar code = str[i].cell();
552
drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code);
555
painter.setPen( currentPen );
558
void TerminalDisplay::setKeyboardCursorShape(KeyboardCursorShape shape)
560
_cursorShape = shape;
562
TerminalDisplay::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const
566
void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color)
568
if (useForegroundColor)
569
_cursorColor = QColor(); // an invalid color means that
570
// the foreground color of the
571
// current character should
575
_cursorColor = color;
577
QColor TerminalDisplay::keyboardCursorColor() const
582
void TerminalDisplay::setOpacity(qreal opacity)
584
QColor color(_blendColor);
585
color.setAlphaF(opacity);
587
// enable automatic background filling to prevent the display
588
// flickering if there is no transparency
589
/*if ( color.alpha() == 255 )
591
setAutoFillBackground(true);
595
setAutoFillBackground(false);
598
_blendColor = color.rgba();
601
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting )
603
// the area of the widget showing the contents of the terminal display is drawn
604
// using the background color from the color scheme set with setColorTable()
606
// the area of the widget behind the scroll-bar is drawn using the background
607
// brush from the scroll-bar's palette, to give the effect of the scroll-bar
608
// being outside of the terminal display and visual consistency with other KDE
611
QRect scrollBarArea = _scrollBar->isVisible() ?
612
rect.intersected(_scrollBar->geometry()) :
614
QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
615
QRect contentsRect = contentsRegion.boundingRect();
617
if ( HAVE_TRANSPARENCY && qAlpha(_blendColor) < 0xff && useOpacitySetting )
619
QColor color(backgroundColor);
620
color.setAlpha(qAlpha(_blendColor));
623
painter.setCompositionMode(QPainter::CompositionMode_Source);
624
painter.fillRect(contentsRect, color);
628
painter.fillRect(contentsRect, backgroundColor);
630
painter.fillRect(scrollBarArea,_scrollBar->palette().background());
633
void TerminalDisplay::drawCursor(QPainter& painter,
635
const QColor& foregroundColor,
636
const QColor& /*backgroundColor*/,
637
bool& invertCharacterColor)
639
QRect cursorRect = rect;
640
cursorRect.setHeight(_fontHeight - _lineSpacing - 1);
642
if (!_cursorBlinking)
644
if ( _cursorColor.isValid() )
645
painter.setPen(_cursorColor);
647
painter.setPen(foregroundColor);
649
if ( _cursorShape == BlockCursor )
651
// draw the cursor outline, adjusting the area so that
652
// it is draw entirely inside 'rect'
653
int penWidth = qMax(1,painter.pen().width());
655
painter.drawRect(cursorRect.adjusted(penWidth/2,
657
- penWidth/2 - penWidth%2,
658
- penWidth/2 - penWidth%2));
661
painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
663
if ( !_cursorColor.isValid() )
665
// invert the colour used to draw the text to ensure that the character at
666
// the cursor position is readable
667
invertCharacterColor = true;
671
else if ( _cursorShape == UnderlineCursor )
672
painter.drawLine(cursorRect.left(),
675
cursorRect.bottom());
676
else if ( _cursorShape == IBeamCursor )
677
painter.drawLine(cursorRect.left(),
680
cursorRect.bottom());
685
void TerminalDisplay::drawCharacters(QPainter& painter,
688
const Character* style,
689
bool invertCharacterColor)
691
// don't draw text which is currently blinking
692
if ( _blinking && (style->rendition & RE_BLINK) )
695
// setup bold and underline
697
ColorEntry::FontWeight weight = style->fontWeight(_colorTable);
698
if (weight == ColorEntry::UseCurrentFormat)
699
useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
701
useBold = (weight == ColorEntry::Bold) ? true : false;
702
bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
704
QFont font = painter.font();
705
if ( font.bold() != useBold
706
|| font.underline() != useUnderline )
708
font.setBold(useBold);
709
font.setUnderline(useUnderline);
710
painter.setFont(font);
714
const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor );
715
const QColor color = textColor.color(_colorTable);
716
QPen pen = painter.pen();
717
if ( pen.color() != color )
720
painter.setPen(color);
724
if ( isLineCharString(text) )
725
drawLineCharString(painter,rect.x(),rect.y(),text,style);
728
// the drawText(rect,flags,string) overload is used here with null flags
729
// instead of drawText(rect,string) because the (rect,string) overload causes
730
// the application's default layout direction to be used instead of
731
// the widget-specific layout direction, which should always be
732
// Qt::LeftToRight for this widget
733
// This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2
735
painter.drawText(rect,0,text);
737
#if QT_VERSION >= 0x040800
738
painter.drawText(rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + text);
740
painter.drawText(rect, 0, LTR_OVERRIDE_CHAR + text);
745
void TerminalDisplay::drawTextFragment(QPainter& painter ,
748
const Character* style)
753
const QColor foregroundColor = style->foregroundColor.color(_colorTable);
754
const QColor backgroundColor = style->backgroundColor.color(_colorTable);
756
// draw background if different from the display's background color
757
if ( backgroundColor != palette().background().color() )
758
drawBackground(painter,rect,backgroundColor,
759
false /* do not use transparency */);
761
// draw cursor shape if the current character is the cursor
762
// this may alter the foreground and background colors
763
bool invertCharacterColor = false;
764
if ( style->rendition & RE_CURSOR )
765
drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor);
768
drawCharacters(painter,rect,text,style,invertCharacterColor);
773
void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; }
774
uint TerminalDisplay::randomSeed() const { return _randomSeed; }
780
void TerminalDisplay::setCursorPos(const int curx, const int cury)
782
QPoint tL = contentsRect().topLeft();
787
ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent;
788
xpos = _leftMargin + tLx + _fontWidth*curx;
789
//setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ???
790
// fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos);
796
// scrolls the image by 'lines', down if lines > 0 or up otherwise.
798
// the terminal emulation keeps track of the scrolling of the character
799
// image as it receives input, and when the view is updated, it calls scrollImage()
800
// with the final scroll amount. this improves performance because scrolling the
801
// display is much cheaper than re-rendering all the text for the
802
// part of the image which has moved up or down.
803
// Instead only new lines have to be drawn
804
void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
806
// if the flow control warning is enabled this will interfere with the
807
// scrolling optimizations and cause artifacts. the simple solution here
808
// is to just disable the optimization whilst it is visible
809
if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() )
812
// constrain the region to the display
813
// the bottom of the region is capped to the number of lines in the display's
814
// internal image - 2, so that the height of 'region' is strictly less
815
// than the height of the internal image.
816
QRect region = screenWindowRegion;
817
region.setBottom( qMin(region.bottom(),this->_lines-2) );
819
// return if there is nothing to do
823
|| (region.top() + abs(lines)) >= region.bottom()
824
|| this->_lines <= region.height() ) return;
826
// hide terminal size label to prevent it being scrolled
827
if (_resizeWidget && _resizeWidget->isVisible())
828
_resizeWidget->hide();
830
// Note: With Qt 4.4 the left edge of the scrolled area must be at 0
831
// to get the correct (newly exposed) part of the widget repainted.
833
// The right edge must be before the left edge of the scroll bar to
834
// avoid triggering a repaint of the entire widget, the distance is
835
// given by SCROLLBAR_CONTENT_GAP
837
// Set the QT_FLUSH_PAINT environment variable to '1' before starting the
838
// application to monitor repainting.
840
int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width();
841
const int SCROLLBAR_CONTENT_GAP = 1;
843
if ( _scrollbarLocation == ScrollBarLeft )
845
scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP);
846
scrollRect.setRight(width());
850
scrollRect.setLeft(0);
851
scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
853
void* firstCharPos = &_image[ region.top() * this->_columns ];
854
void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ];
856
int top = _topMargin + (region.top() * _fontHeight);
857
int linesToMove = region.height() - abs(lines);
858
int bytesToMove = linesToMove *
862
Q_ASSERT( linesToMove > 0 );
863
Q_ASSERT( bytesToMove > 0 );
865
//scroll internal image
868
// check that the memory areas that we are going to move are valid
869
Q_ASSERT( (char*)lastCharPos + bytesToMove <
870
(char*)(_image + (this->_lines * this->_columns)) );
872
Q_ASSERT( (lines*this->_columns) < _imageSize );
874
//scroll internal image down
875
memmove( firstCharPos , lastCharPos , bytesToMove );
877
//set region of display to scroll
878
scrollRect.setTop(top);
882
// check that the memory areas that we are going to move are valid
883
Q_ASSERT( (char*)firstCharPos + bytesToMove <
884
(char*)(_image + (this->_lines * this->_columns)) );
886
//scroll internal image up
887
memmove( lastCharPos , firstCharPos , bytesToMove );
889
//set region of the display to scroll
890
scrollRect.setTop(top + abs(lines) * _fontHeight);
892
scrollRect.setHeight(linesToMove * _fontHeight );
894
Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
896
//scroll the display vertically to match internal _image
897
scroll( 0 , _fontHeight * (-lines) , scrollRect );
899
// Notify to qml that the image has changed
900
emit imageUpdated(scrollRect);
903
QRegion TerminalDisplay::hotSpotRegion() const
906
foreach( Filter::HotSpot* hotSpot , _filterChain->hotSpots() )
909
if (hotSpot->startLine()==hotSpot->endLine()) {
910
r.setLeft(hotSpot->startColumn());
911
r.setTop(hotSpot->startLine());
912
r.setRight(hotSpot->endColumn());
913
r.setBottom(hotSpot->endLine());
914
region |= imageToWidget(r);;
916
r.setLeft(hotSpot->startColumn());
917
r.setTop(hotSpot->startLine());
918
r.setRight(_columns);
919
r.setBottom(hotSpot->startLine());
920
region |= imageToWidget(r);;
921
for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) {
924
r.setRight(_columns);
926
region |= imageToWidget(r);;
929
r.setTop(hotSpot->endLine());
930
r.setRight(hotSpot->endColumn());
931
r.setBottom(hotSpot->endLine());
932
region |= imageToWidget(r);;
938
void TerminalDisplay::processFilters()
943
QRegion preUpdateHotSpots = hotSpotRegion();
945
// use _screenWindow->getImage() here rather than _image because
946
// other classes may call processFilters() when this display's
947
// ScreenWindow emits a scrolled() signal - which will happen before
948
// updateImage() is called on the display and therefore _image is
949
// out of date at this point
950
_filterChain->setImage( _screenWindow->getImage(),
951
_screenWindow->windowLines(),
952
_screenWindow->windowColumns(),
953
_screenWindow->getLineProperties() );
954
_filterChain->process();
956
QRegion postUpdateHotSpots = hotSpotRegion();
958
update( preUpdateHotSpots | postUpdateHotSpots );
961
void TerminalDisplay::updateImage()
963
if ( !_screenWindow )
966
// optimization - scroll the existing image where possible and
967
// avoid expensive text drawing for parts of the image that
968
// can simply be moved up or down
969
scrollImage( _screenWindow->scrollCount() ,
970
_screenWindow->scrollRegion() );
971
_screenWindow->resetScrollCount();
975
// The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
979
Character* const newimg = _screenWindow->getImage();
980
int lines = _screenWindow->windowLines();
981
int columns = _screenWindow->windowColumns();
983
setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );
985
Q_ASSERT( this->_usedLines <= this->_lines );
986
Q_ASSERT( this->_usedColumns <= this->_columns );
990
QPoint tL = contentsRect().topLeft();
995
CharacterColor cf; // undefined
996
CharacterColor _clipboard; // undefined
997
int cr = -1; // undefined
999
const int linesToUpdate = qMin(this->_lines, qMax(0,lines ));
1000
const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));
1002
QChar *disstrU = new QChar[columnsToUpdate];
1003
char *dirtyMask = new char[columnsToUpdate+2];
1004
QRegion dirtyRegion;
1006
// debugging variable, this records the number of lines that are found to
1007
// be 'dirty' ( ie. have changed from the old _image to the new _image ) and
1008
// which therefore need to be repainted
1009
int dirtyLineCount = 0;
1011
for (y = 0; y < linesToUpdate; ++y)
1013
const Character* currentLine = &_image[y*this->_columns];
1014
const Character* const newLine = &newimg[y*columns];
1016
bool updateLine = false;
1018
// The dirty mask indicates which characters need repainting. We also
1019
// mark surrounding neighbours dirty, in case the character exceeds
1020
// its cell boundaries
1021
memset(dirtyMask, 0, columnsToUpdate+2);
1023
for( x = 0 ; x < columnsToUpdate ; ++x)
1025
if ( newLine[x] != currentLine[x] )
1027
dirtyMask[x] = true;
1031
if (!_resizing) // not while _resizing, we're expecting a paintEvent
1032
for (x = 0; x < columnsToUpdate; ++x)
1034
_hasBlinker |= (newLine[x].rendition & RE_BLINK);
1036
// Start drawing if this character or the next one differs.
1037
// We also take the next one into account to handle the situation
1038
// where characters exceed their cell width.
1041
quint16 c = newLine[x+0].character;
1045
disstrU[p++] = c; //fontMap(c);
1046
bool lineDraw = isLineChar(c);
1047
bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
1048
cr = newLine[x].rendition;
1049
_clipboard = newLine[x].backgroundColor;
1050
if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
1051
int lln = columnsToUpdate - x;
1052
for (len = 1; len < lln; ++len)
1054
const Character& ch = newLine[x+len];
1057
continue; // Skip trailing part of multi-col chars.
1059
bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0);
1061
if ( ch.foregroundColor != cf ||
1062
ch.backgroundColor != _clipboard ||
1063
ch.rendition != cr ||
1064
!dirtyMask[x+len] ||
1065
isLineChar(c) != lineDraw ||
1066
nextIsDoubleWidth != doubleWidth )
1069
disstrU[p++] = c; //fontMap(c);
1072
QString unistr(disstrU, p);
1074
bool saveFixedFont = _fixedFont;
1082
_fixedFont = saveFixedFont;
1088
//both the top and bottom halves of double height _lines must always be redrawn
1089
//although both top and bottom halves contain the same characters, only
1090
//the top one is actually
1092
if (_lineProperties.count() > y)
1093
updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
1095
// if the characters on the line are different in the old and the new _image
1096
// then this line must be repainted.
1101
// add the area occupied by this line to the region which needs to be
1103
QRect dirtyRect = QRect( _leftMargin+tLx ,
1104
_topMargin+tLy+_fontHeight*y ,
1105
_fontWidth * columnsToUpdate ,
1108
dirtyRegion |= dirtyRect;
1111
// replace the line of characters in the old _image with the
1112
// current line of the new _image
1113
memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character));
1116
// if the new _image is smaller than the previous _image, then ensure that the area
1117
// outside the new _image is cleared
1118
if ( linesToUpdate < _usedLines )
1120
dirtyRegion |= QRect( _leftMargin+tLx ,
1121
_topMargin+tLy+_fontHeight*linesToUpdate ,
1122
_fontWidth * this->_columns ,
1123
_fontHeight * (_usedLines-linesToUpdate) );
1125
_usedLines = linesToUpdate;
1127
if ( columnsToUpdate < _usedColumns )
1129
dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth ,
1131
_fontWidth * (_usedColumns-columnsToUpdate) ,
1132
_fontHeight * this->_lines );
1134
_usedColumns = columnsToUpdate;
1136
dirtyRegion |= _inputMethodData.previousPreeditRect;
1138
// update the parts of the display which have changed
1139
update(dirtyRegion);
1141
// Notify to QML that the image has changed
1142
foreach( QRect rect, dirtyRegion.rects() )
1143
emit imageUpdated(rect);
1146
if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( TEXT_BLINK_DELAY );
1147
if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; }
1153
void TerminalDisplay::showResizeNotification()
1155
if (_terminalSizeHint && isVisible())
1157
if (_terminalSizeStartup) {
1158
_terminalSizeStartup=false;
1163
_resizeWidget = new QLabel("Size: XXX x XXX", this);
1164
_resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width("Size: XXX x XXX"));
1165
_resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
1166
_resizeWidget->setAlignment(Qt::AlignCenter);
1168
_resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)");
1170
_resizeTimer = new QTimer(this);
1171
_resizeTimer->setSingleShot(true);
1172
connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide()));
1174
QString sizeStr = QString("Size: %1 x %2").arg(_columns).arg(_lines);
1175
_resizeWidget->setText(sizeStr);
1176
_resizeWidget->move((width()-_resizeWidget->width())/2,
1177
(height()-_resizeWidget->height())/2+20);
1178
_resizeWidget->show();
1179
_resizeTimer->start(1000);
1183
void TerminalDisplay::setBlinkingCursor(bool blink)
1185
_hasBlinkingCursor=blink;
1187
if (blink && !_blinkCursorTimer->isActive())
1188
_blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
1190
if (!blink && _blinkCursorTimer->isActive())
1192
_blinkCursorTimer->stop();
1193
if (_cursorBlinking)
1196
_cursorBlinking = false;
1200
void TerminalDisplay::setBlinkingTextEnabled(bool blink)
1202
_allowBlinkingText = blink;
1204
if (blink && !_blinkTimer->isActive())
1205
_blinkTimer->start(TEXT_BLINK_DELAY);
1207
if (!blink && _blinkTimer->isActive())
1209
_blinkTimer->stop();
1214
void TerminalDisplay::focusOutEvent(QFocusEvent*)
1216
emit termLostFocus();
1217
// trigger a repaint of the cursor so that it is both visible (in case
1218
// it was hidden during blinking)
1219
// and drawn in a focused out state
1220
_cursorBlinking = false;
1223
_blinkCursorTimer->stop();
1227
_blinkTimer->stop();
1229
void TerminalDisplay::focusInEvent(QFocusEvent*)
1231
emit termGetFocus();
1232
if (_hasBlinkingCursor)
1234
_blinkCursorTimer->start();
1239
_blinkTimer->start();
1242
void TerminalDisplay::paintEvent( QPaintEvent* pe )
1244
QPainter paint(this);
1246
foreach (const QRect &rect, (pe->region() & contentsRect()).rects())
1248
drawBackground(paint,rect,palette().background().color(),
1249
true /* use opacity setting */);
1250
drawContents(paint, rect);
1252
drawInputMethodPreeditString(paint,preeditRect());
1253
paintFilters(paint);
1256
QPoint TerminalDisplay::cursorPosition() const
1259
return _screenWindow->cursorPosition();
1264
QRect TerminalDisplay::preeditRect() const
1266
const int preeditLength = string_width(_inputMethodData.preeditString);
1268
if ( preeditLength == 0 )
1271
return QRect(_leftMargin + _fontWidth*cursorPosition().x(),
1272
_topMargin + _fontHeight*cursorPosition().y(),
1273
_fontWidth*preeditLength,
1277
void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
1279
if ( _inputMethodData.preeditString.isEmpty() )
1282
const QPoint cursorPos = cursorPosition();
1284
bool invertColors = false;
1285
const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
1286
const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
1287
const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())];
1289
drawBackground(painter,rect,background,true);
1290
drawCursor(painter,rect,foreground,background,invertColors);
1291
drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors);
1293
_inputMethodData.previousPreeditRect = rect;
1296
FilterChain* TerminalDisplay::filterChain() const
1298
return _filterChain;
1301
void TerminalDisplay::paintFilters(QPainter& painter)
1303
// get color of character under mouse and use it to draw
1304
// lines for filters
1305
QPoint cursorPos = mapFromGlobal(QCursor::pos());
1308
int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0;
1310
getCharacterPosition( cursorPos , cursorLine , cursorColumn );
1311
Character cursorCharacter = _image[loc(cursorColumn,cursorLine)];
1313
painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) );
1315
// iterate over hotspots identified by the display's currently active filters
1316
// and draw appropriate visuals to indicate the presence of the hotspot
1318
QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
1319
QListIterator<Filter::HotSpot*> iter(spots);
1320
while (iter.hasNext())
1322
Filter::HotSpot* spot = iter.next();
1325
if ( spot->type() == Filter::HotSpot::Link ) {
1327
if (spot->startLine()==spot->endLine()) {
1328
r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth,
1329
spot->startLine()*_fontHeight + 1,
1330
(spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth,
1331
(spot->endLine()+1)*_fontHeight - 1 );
1334
r.setCoords( spot->startColumn()*_fontWidth + 1 + scrollBarWidth,
1335
spot->startLine()*_fontHeight + 1,
1336
(_columns-1)*_fontWidth - 1 + scrollBarWidth,
1337
(spot->startLine()+1)*_fontHeight - 1 );
1339
for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
1340
r.setCoords( 0*_fontWidth + 1 + scrollBarWidth,
1341
line*_fontHeight + 1,
1342
(_columns-1)*_fontWidth - 1 + scrollBarWidth,
1343
(line+1)*_fontHeight - 1 );
1346
r.setCoords( 0*_fontWidth + 1 + scrollBarWidth,
1347
spot->endLine()*_fontHeight + 1,
1348
(spot->endColumn()-1)*_fontWidth - 1 + scrollBarWidth,
1349
(spot->endLine()+1)*_fontHeight - 1 );
1354
for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ )
1356
int startColumn = 0;
1357
int endColumn = _columns-1; // TODO use number of _columns which are actually
1358
// occupied on this line rather than the width of the
1359
// display in _columns
1361
// ignore whitespace at the end of the lines
1362
while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 )
1365
// increment here because the column which we want to set 'endColumn' to
1366
// is the first whitespace character at the end of the line
1369
if ( line == spot->startLine() )
1370
startColumn = spot->startColumn();
1371
if ( line == spot->endLine() )
1372
endColumn = spot->endColumn();
1374
// subtract one pixel from
1375
// the right and bottom so that
1376
// we do not overdraw adjacent
1379
// subtracting one pixel from all sides also prevents an edge case where
1380
// moving the mouse outside a link could still leave it underlined
1381
// because the check below for the position of the cursor
1382
// finds it on the border of the target area
1384
r.setCoords( startColumn*_fontWidth + 1 + scrollBarWidth,
1385
line*_fontHeight + 1,
1386
endColumn*_fontWidth - 1 + scrollBarWidth,
1387
(line+1)*_fontHeight - 1 );
1388
// Underline link hotspots
1389
if ( spot->type() == Filter::HotSpot::Link )
1391
QFontMetrics metrics(font());
1393
// find the baseline (which is the invisible line that the characters in the font sit on,
1394
// with some having tails dangling below)
1395
int baseline = r.bottom() - metrics.descent();
1396
// find the position of the underline below that
1397
int underlinePos = baseline + metrics.underlinePos();
1398
if ( region.contains( mapFromGlobal(QCursor::pos()) ) ){
1399
painter.drawLine( r.left() , underlinePos ,
1400
r.right() , underlinePos );
1403
// Marker hotspots simply have a transparent rectanglular shape
1404
// drawn on top of them
1405
else if ( spot->type() == Filter::HotSpot::Marker )
1407
//TODO - Do not use a hardcoded colour for this
1408
painter.fillRect(r,QBrush(QColor(255,0,0,120)));
1413
void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
1415
QPoint tL = contentsRect().topLeft();
1419
int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth));
1420
int luy = qMin(_usedLines-1, qMax(0,(rect.top() - tLy - _topMargin ) / _fontHeight));
1421
int rlx = qMin(_usedColumns-1, qMax(0,(rect.right() - tLx - _leftMargin ) / _fontWidth));
1422
int rly = qMin(_usedLines-1, qMax(0,(rect.bottom() - tLy - _topMargin ) / _fontHeight));
1424
const int bufferSize = _usedColumns;
1426
unistr.reserve(bufferSize);
1427
for (int y = luy; y <= rly; y++)
1429
quint16 c = _image[loc(lux,y)].character;
1432
x--; // Search for start of multi-column character
1433
for (; x <= rlx; x++)
1438
// reset our buffer to the maximal size
1439
unistr.resize(bufferSize);
1440
QChar *disstrU = unistr.data();
1442
// is this a single character or a sequence of characters ?
1443
if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR )
1445
// sequence of characters
1446
ushort extendedCharLength = 0;
1447
ushort* chars = ExtendedCharTable::instance
1448
.lookupExtendedChar(_image[loc(x,y)].charSequence,extendedCharLength);
1449
for ( int index = 0 ; index < extendedCharLength ; index++ )
1451
Q_ASSERT( p < bufferSize );
1452
disstrU[p++] = chars[index];
1458
c = _image[loc(x,y)].character;
1461
Q_ASSERT( p < bufferSize );
1462
disstrU[p++] = c; //fontMap(c);
1466
bool lineDraw = isLineChar(c);
1467
bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
1468
CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
1469
CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
1470
quint8 currentRendition = _image[loc(x,y)].rendition;
1472
while (x+len <= rlx &&
1473
_image[loc(x+len,y)].foregroundColor == currentForeground &&
1474
_image[loc(x+len,y)].backgroundColor == currentBackground &&
1475
_image[loc(x+len,y)].rendition == currentRendition &&
1476
(_image[ qMin(loc(x+len,y)+1,_imageSize) ].character == 0) == doubleWidth &&
1477
isLineChar( c = _image[loc(x+len,y)].character) == lineDraw) // Assignment!
1480
disstrU[p++] = c; //fontMap(c);
1481
if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1482
len++; // Skip trailing part of multi-column character
1485
if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character))
1486
len++; // Adjust for trailing part of multi-column character
1488
bool save__fixedFont = _fixedFont;
1495
// Create a text scaling matrix for double width and double height lines.
1498
if (y < _lineProperties.size())
1500
if (_lineProperties[y] & LINE_DOUBLEWIDTH)
1501
textScale.scale(2,1);
1503
if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1504
textScale.scale(1,2);
1507
//Apply text scaling matrix.
1508
paint.setWorldMatrix(textScale, true);
1510
//calculate the area in which the text will be drawn
1511
QRect textArea = QRect( _leftMargin+tLx+_fontWidth*x , _topMargin+tLy+_fontHeight*y , _fontWidth*len , _fontHeight);
1513
//move the calculated area to take account of scaling applied to the painter.
1514
//the position of the area from the origin (0,0) is scaled
1515
//by the opposite of whatever
1516
//transformation has been applied to the painter. this ensures that
1517
//painting does actually start from textArea.topLeft()
1518
//(instead of textArea.topLeft() * painter-scale)
1519
textArea.moveTopLeft( textScale.inverted().map(textArea.topLeft()) );
1521
//paint text fragment
1522
drawTextFragment( paint,
1525
&_image[loc(x,y)] ); //,
1529
_fixedFont = save__fixedFont;
1531
//reset back to single-width, single-height _lines
1532
paint.setWorldMatrix(textScale.inverted(), true);
1534
if (y < _lineProperties.size()-1)
1536
//double-height _lines are represented by two adjacent _lines
1537
//containing the same characters
1538
//both _lines will have the LINE_DOUBLEHEIGHT attribute.
1539
//If the current line has the LINE_DOUBLEHEIGHT attribute,
1540
//we can therefore skip the next line
1541
if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1550
void TerminalDisplay::blinkEvent()
1552
if (!_allowBlinkingText) return;
1554
_blinking = !_blinking;
1556
//TODO: Optimize to only repaint the areas of the widget
1557
// where there is blinking text
1558
// rather than repainting the whole widget.
1562
QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
1565
result.setLeft( _leftMargin + _fontWidth * imageArea.left() );
1566
result.setTop( _topMargin + _fontHeight * imageArea.top() );
1567
result.setWidth( _fontWidth * imageArea.width() );
1568
result.setHeight( _fontHeight * imageArea.height() );
1573
void TerminalDisplay::updateCursor()
1575
QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) );
1577
emit imageUpdated(cursorRect);
1580
void TerminalDisplay::blinkCursorEvent()
1582
_cursorBlinking = !_cursorBlinking;
1586
/* ------------------------------------------------------------------------- */
1590
/* ------------------------------------------------------------------------- */
1592
void TerminalDisplay::resizeEvent(QResizeEvent*)
1598
void TerminalDisplay::propagateSize()
1602
setSize(_columns, _lines);
1603
QWidget::setFixedSize(sizeHint());
1604
parentWidget()->adjustSize();
1605
parentWidget()->setFixedSize(parentWidget()->sizeHint());
1612
void TerminalDisplay::updateImageSize()
1614
Character* oldimg = _image;
1615
int oldlin = _lines;
1616
int oldcol = _columns;
1620
// copy the old image to reduce flicker
1621
int lines = qMin(oldlin,_lines);
1622
int columns = qMin(oldcol,_columns);
1626
for (int line = 0; line < lines; line++)
1628
memcpy((void*)&_image[_columns*line],
1629
(void*)&oldimg[oldcol*line],columns*sizeof(Character));
1635
_screenWindow->setWindowLines(_lines);
1637
_resizing = (oldlin!=_lines) || (oldcol!=_columns);
1641
showResizeNotification();
1642
emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
1648
//showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1649
//display has been resized when the display is hidden or shown.
1651
//TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1652
//the same signal as the one for a content size change
1653
void TerminalDisplay::showEvent(QShowEvent*)
1655
emit changedContentSizeSignal(_contentHeight,_contentWidth);
1657
void TerminalDisplay::hideEvent(QHideEvent*)
1659
emit changedContentSizeSignal(_contentHeight,_contentWidth);
1662
/* ------------------------------------------------------------------------- */
1666
/* ------------------------------------------------------------------------- */
1668
void TerminalDisplay::scrollBarPositionChanged(int)
1670
if ( !_screenWindow )
1673
_screenWindow->scrollTo( _scrollBar->value() );
1675
// if the thumb has been moved to the bottom of the _scrollBar then set
1676
// the display to automatically track new output,
1677
// that is, scroll down automatically
1678
// to how new _lines as they are added
1679
const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
1680
_screenWindow->setTrackOutput( atEndOfOutput );
1685
void TerminalDisplay::setScroll(int cursor, int slines)
1687
// update _scrollBar if the range or value has changed,
1690
// setting the range or value of a _scrollBar will always trigger
1691
// a repaint, so it should be avoided if it is not necessary
1692
if ( _scrollBar->minimum() == 0 &&
1693
_scrollBar->maximum() == (slines - _lines) &&
1694
_scrollBar->value() == cursor )
1699
disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1700
_scrollBar->setRange(0,slines - _lines);
1701
_scrollBar->setSingleStep(1);
1702
_scrollBar->setPageStep(_lines);
1703
_scrollBar->setValue(cursor);
1704
connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1707
void TerminalDisplay::scrollToEnd()
1709
disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1710
_scrollBar->setValue( _scrollBar->maximum() );
1711
connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1713
_screenWindow->scrollTo( _scrollBar->value() + 1 );
1714
_screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
1717
void TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
1719
if (_scrollbarLocation == position)
1722
if ( position == NoScrollBar )
1727
_topMargin = _leftMargin = 1;
1728
_scrollbarLocation = position;
1734
void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
1736
if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) {
1737
mouseTripleClickEvent(ev);
1741
if ( !contentsRect().contains(ev->pos()) ) return;
1743
if ( !_screenWindow ) return;
1747
getCharacterPosition(ev->pos(),charLine,charColumn);
1748
QPoint pos = QPoint(charColumn,charLine);
1750
if ( ev->button() == Qt::LeftButton)
1752
_lineSelectionMode = false;
1753
_wordSelectionMode = false;
1755
emit isBusySelecting(true); // Keep it steady...
1756
// Drag only when the Control key is hold
1757
bool selected = false;
1759
// The receiver of the testIsSelected() signal will adjust
1760
// 'selected' accordingly.
1761
//emit testIsSelected(pos.x(), pos.y(), selected);
1763
selected = _screenWindow->isSelected(pos.x(),pos.y());
1765
if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
1766
// The user clicked inside selected text
1767
dragInfo.state = diPending;
1768
dragInfo.start = ev->pos();
1771
// No reason to ever start a drag event
1772
dragInfo.state = diNone;
1774
_preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) );
1775
_columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
1777
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1779
_screenWindow->clearSelection();
1781
//emit clearSelectionSignal();
1782
pos.ry() += _scrollBar->value();
1783
_iPntSel = _pntSel = pos;
1784
_actSel = 1; // left mouse button pressed but nothing selected yet.
1789
emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1792
Filter::HotSpot *spot = _filterChain->hotSpotAt(charLine, charColumn);
1793
if (spot && spot->type() == Filter::HotSpot::Link)
1794
spot->activate("open-action");
1797
else if ( ev->button() == Qt::MidButton )
1799
if ( _mouseMarks || (!_mouseMarks && (ev->modifiers() & Qt::ShiftModifier)) )
1800
emitSelection(true,ev->modifiers() & Qt::ControlModifier);
1802
emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1804
else if ( ev->button() == Qt::RightButton )
1806
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1807
emit configureRequest(ev->pos());
1809
emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
1813
QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
1815
int charLine, charColumn;
1816
getCharacterPosition(position,charLine,charColumn);
1818
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
1820
return spot ? spot->actions() : QList<QAction*>();
1823
void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
1827
int scrollBarWidth = (_scrollbarLocation == ScrollBarLeft) ? _scrollBar->width() : 0;
1829
getCharacterPosition(ev->pos(),charLine,charColumn);
1832
// change link hot-spot appearance on mouse-over
1833
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
1834
if ( spot && spot->type() == Filter::HotSpot::Link)
1836
QRegion previousHotspotArea = _mouseOverHotspotArea;
1837
_mouseOverHotspotArea = QRegion();
1839
if (spot->startLine()==spot->endLine()) {
1840
r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth,
1841
spot->startLine()*_fontHeight,
1842
spot->endColumn()*_fontWidth + scrollBarWidth,
1843
(spot->endLine()+1)*_fontHeight - 1 );
1844
_mouseOverHotspotArea |= r;
1846
r.setCoords( spot->startColumn()*_fontWidth + scrollBarWidth,
1847
spot->startLine()*_fontHeight,
1848
_columns*_fontWidth - 1 + scrollBarWidth,
1849
(spot->startLine()+1)*_fontHeight );
1850
_mouseOverHotspotArea |= r;
1851
for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
1852
r.setCoords( 0*_fontWidth + scrollBarWidth,
1854
_columns*_fontWidth + scrollBarWidth,
1855
(line+1)*_fontHeight );
1856
_mouseOverHotspotArea |= r;
1858
r.setCoords( 0*_fontWidth + scrollBarWidth,
1859
spot->endLine()*_fontHeight,
1860
spot->endColumn()*_fontWidth + scrollBarWidth,
1861
(spot->endLine()+1)*_fontHeight );
1862
_mouseOverHotspotArea |= r;
1864
// display tooltips when mousing over links
1865
// TODO: Extend this to work with filter types other than links
1866
const QString& tooltip = spot->tooltip();
1867
if ( !tooltip.isEmpty() )
1869
QToolTip::showText( mapToGlobal(ev->pos()) , tooltip , this , _mouseOverHotspotArea.boundingRect() );
1872
update( _mouseOverHotspotArea | previousHotspotArea );
1874
else if ( !_mouseOverHotspotArea.isEmpty() )
1876
update( _mouseOverHotspotArea );
1877
// set hotspot area to an invalid rectangle
1878
_mouseOverHotspotArea = QRegion();
1881
// for auto-hiding the cursor, we need mouseTracking
1882
if (ev->buttons() == Qt::NoButton ) return;
1884
// if the terminal is interested in mouse movements
1885
// then emit a mouse movement signal, unless the shift
1886
// key is being held down, which overrides this.
1887
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
1890
if (ev->buttons() & Qt::LeftButton)
1892
if (ev->buttons() & Qt::MidButton)
1894
if (ev->buttons() & Qt::RightButton)
1898
emit mouseSignal( button,
1900
charLine + 1 +_scrollBar->value() -_scrollBar->maximum(),
1906
if (dragInfo.state == diPending)
1908
// we had a mouse down, but haven't confirmed a drag yet
1909
// if the mouse has moved sufficiently, we will confirm
1911
// int distance = KGlobalSettings::dndEventDelay();
1912
int distance = QApplication::startDragDistance();
1913
if ( ev->x() > dragInfo.start.x() + distance || ev->x() < dragInfo.start.x() - distance ||
1914
ev->y() > dragInfo.start.y() + distance || ev->y() < dragInfo.start.y() - distance)
1916
// we've left the drag square, we can start a real drag operation now
1917
emit isBusySelecting(false); // Ok.. we can breath again.
1919
_screenWindow->clearSelection();
1924
else if (dragInfo.state == diDragging)
1926
// this isn't technically needed because mouseMoveEvent is suppressed during
1927
// Qt drag operations, replaced by dragMoveEvent
1931
if (_actSel == 0) return;
1933
// don't extend selection while pasting
1934
if (ev->buttons() & Qt::MidButton) return;
1936
extendSelection( ev->pos() );
1939
void TerminalDisplay::extendSelection( const QPoint& position )
1941
QPoint pos = position;
1943
if ( !_screenWindow )
1946
//if ( !contentsRect().contains(ev->pos()) ) return;
1947
QPoint tL = contentsRect().topLeft();
1950
int scroll = _scrollBar->value();
1952
// we're in the process of moving the mouse with the left button pressed
1953
// the mouse cursor will kept caught within the bounds of the text in
1956
int linesBeyondWidget = 0;
1958
QRect textBounds(tLx + _leftMargin,
1960
_usedColumns*_fontWidth-1,
1961
_usedLines*_fontHeight-1);
1963
// Adjust position within text area bounds.
1964
QPoint oldpos = pos;
1966
pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) );
1967
pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) );
1969
if ( oldpos.y() > textBounds.bottom() )
1971
linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight;
1972
_scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward
1974
if ( oldpos.y() < textBounds.top() )
1976
linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight;
1977
_scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // history
1982
getCharacterPosition(pos,charLine,charColumn);
1984
QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight);
1986
QPoint _iPntSelCorr = _iPntSel;
1987
_iPntSelCorr.ry() -= _scrollBar->value();
1988
QPoint _pntSelCorr = _pntSel;
1989
_pntSelCorr.ry() -= _scrollBar->value();
1990
bool swapping = false;
1992
if ( _wordSelectionMode )
1994
// Extend to word boundaries
1998
bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
1999
( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
2000
bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
2001
( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
2002
swapping = left_not_right != old_left_not_right;
2004
// Find left (left_not_right ? from here : from start)
2005
QPoint left = left_not_right ? here : _iPntSelCorr;
2006
i = loc(left.x(),left.y());
2007
if (i>=0 && i<=_imageSize) {
2008
selClass = charClass(_image[i].character);
2009
while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) ))
2010
&& charClass(_image[i-1].character) == selClass )
2011
{ i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} }
2014
// Find left (left_not_right ? from start : from here)
2015
QPoint right = left_not_right ? _iPntSelCorr : here;
2016
i = loc(right.x(),right.y());
2017
if (i>=0 && i<=_imageSize) {
2018
selClass = charClass(_image[i].character);
2019
while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) ))
2020
&& charClass(_image[i+1].character) == selClass )
2021
{ i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } }
2024
// Pick which is start (ohere) and which is extension (here)
2025
if ( left_not_right )
2027
here = left; ohere = right;
2031
here = right; ohere = left;
2036
if ( _lineSelectionMode )
2038
// Extend to complete line
2039
bool above_not_below = ( here.y() < _iPntSelCorr.y() );
2041
QPoint above = above_not_below ? here : _iPntSelCorr;
2042
QPoint below = above_not_below ? _iPntSelCorr : here;
2044
while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) )
2046
while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) )
2050
below.setX(_usedColumns-1);
2052
// Pick which is start (ohere) and which is extension (here)
2053
if ( above_not_below )
2055
here = above; ohere = below;
2059
here = below; ohere = above;
2062
QPoint newSelBegin = QPoint( ohere.x(), ohere.y() );
2063
swapping = !(_tripleSelBegin==newSelBegin);
2064
_tripleSelBegin = newSelBegin;
2070
if ( !_wordSelectionMode && !_lineSelectionMode )
2075
bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
2076
( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
2077
bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
2078
( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
2079
swapping = left_not_right != old_left_not_right;
2081
// Find left (left_not_right ? from here : from start)
2082
QPoint left = left_not_right ? here : _iPntSelCorr;
2084
// Find left (left_not_right ? from start : from here)
2085
QPoint right = left_not_right ? _iPntSelCorr : here;
2086
if ( right.x() > 0 && !_columnSelectionMode )
2088
i = loc(right.x(),right.y());
2089
if (i>=0 && i<=_imageSize) {
2090
selClass = charClass(_image[i-1].character);
2091
/* if (selClass == ' ')
2093
while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
2094
!(_lineProperties[right.y()] & LINE_WRAPPED))
2095
{ i++; right.rx()++; }
2096
if (right.x() < _usedColumns-1)
2097
right = left_not_right ? _iPntSelCorr : here;
2099
right.rx()++; // will be balanced later because of offset=-1;
2104
// Pick which is start (ohere) and which is extension (here)
2105
if ( left_not_right )
2107
here = left; ohere = right; offset = 0;
2111
here = right; ohere = left; offset = -1;
2115
if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved
2117
if (here == ohere) return; // It's not left, it's not right.
2119
if ( _actSel < 2 || swapping )
2121
if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
2123
_screenWindow->setSelectionStart( ohere.x() , ohere.y() , true );
2127
_screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false );
2132
_actSel = 2; // within selection
2134
_pntSel.ry() += _scrollBar->value();
2136
if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
2138
_screenWindow->setSelectionEnd( here.x() , here.y() );
2142
_screenWindow->setSelectionEnd( here.x()+offset , here.y() );
2147
void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
2149
if ( !_screenWindow )
2154
getCharacterPosition(ev->pos(),charLine,charColumn);
2156
if ( ev->button() == Qt::LeftButton)
2158
emit isBusySelecting(false);
2159
if(dragInfo.state == diPending)
2161
// We had a drag event pending but never confirmed. Kill selection
2162
_screenWindow->clearSelection();
2163
//emit clearSelectionSignal();
2169
setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
2174
//FIXME: emits a release event even if the mouse is
2175
// outside the range. The procedure used in `mouseMoveEvent'
2176
// applies here, too.
2178
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2179
emit mouseSignal( 3, // release
2181
charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
2183
dragInfo.state = diNone;
2187
if ( !_mouseMarks &&
2188
((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier))
2189
|| ev->button() == Qt::MidButton) )
2191
emit mouseSignal( 3,
2193
charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
2198
void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint,int& line,int& column) const
2200
column = (widgetPoint.x() + _fontWidth/2 -contentsRect().left()-_leftMargin) / _fontWidth;
2201
line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight;
2208
if ( line >= _usedLines )
2209
line = _usedLines-1;
2211
// the column value returned can be equal to _usedColumns, which
2212
// is the position just after the last character displayed in a line.
2214
// this is required so that the user can select characters in the right-most
2215
// column (or left-most for right-to-left input)
2216
if ( column > _usedColumns )
2217
column = _usedColumns;
2220
void TerminalDisplay::updateFilters()
2222
if ( !_screenWindow )
2228
void TerminalDisplay::updateLineProperties()
2230
if ( !_screenWindow )
2233
_lineProperties = _screenWindow->getLineProperties();
2236
void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
2238
if ( ev->button() != Qt::LeftButton) return;
2239
if ( !_screenWindow ) return;
2244
getCharacterPosition(ev->pos(),charLine,charColumn);
2246
QPoint pos(charColumn,charLine);
2248
// pass on double click as two clicks.
2249
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2251
// Send just _ONE_ click event, since the first click of the double click
2252
// was already sent by the click handler
2253
emit mouseSignal( 0,
2255
pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(),
2260
_screenWindow->clearSelection();
2261
QPoint bgnSel = pos;
2262
QPoint endSel = pos;
2263
int i = loc(bgnSel.x(),bgnSel.y());
2265
_iPntSel.ry() += _scrollBar->value();
2267
_wordSelectionMode = true;
2269
// find word boundaries...
2270
QChar selClass = charClass(_image[i].character);
2272
// find the start of the word
2274
while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) ))
2275
&& charClass(_image[i-1].character) == selClass )
2288
_screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false );
2290
// find the end of the word
2291
i = loc( endSel.x(), endSel.y() );
2293
while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) ))
2294
&& charClass(_image[i+1].character) == selClass )
2297
if (x<_usedColumns-1)
2308
// In word selection mode don't select @ (64) if at end of word.
2309
if ( ( QChar( _image[i].character ) == '@' ) && ( ( endSel.x() - bgnSel.x() ) > 0 ) )
2310
endSel.setX( x - 1 );
2313
_actSel = 2; // within selection
2315
_screenWindow->setSelectionEnd( endSel.x() , endSel.y() );
2317
setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
2320
_possibleTripleClick=true;
2322
QTimer::singleShot(QApplication::doubleClickInterval(),this,
2323
SLOT(tripleClickTimeout()));
2326
void TerminalDisplay::wheelEvent( QWheelEvent* ev )
2328
if (ev->orientation() != Qt::Vertical)
2331
// if the terminal program is not interested mouse events
2332
// then send the event to the scrollbar if the slider has room to move
2333
// or otherwise send simulated up / down key presses to the terminal program
2334
// for the benefit of programs such as 'less'
2337
bool canScroll = _scrollBar->maximum() > 0;
2339
_scrollBar->event(ev);
2342
// assume that each Up / Down key event will cause the terminal application
2343
// to scroll by one line.
2345
// to get a reasonable scrolling speed, scroll by one line for every 5 degrees
2346
// of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees,
2347
// giving a scroll of 3 lines
2348
int key = ev->delta() > 0 ? Qt::Key_Up : Qt::Key_Down;
2350
// QWheelEvent::delta() gives rotation in eighths of a degree
2351
int wheelDegrees = ev->delta() / 8;
2352
int linesToScroll = abs(wheelDegrees) / 5;
2354
QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier);
2356
for (int i=0;i<linesToScroll;i++)
2357
emit keyPressedSignal(&keyScrollEvent);
2362
// terminal program wants notification of mouse activity
2366
getCharacterPosition( ev->pos() , charLine , charColumn );
2368
emit mouseSignal( ev->delta() > 0 ? 4 : 5,
2370
charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
2375
void TerminalDisplay::tripleClickTimeout()
2377
_possibleTripleClick=false;
2380
void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
2382
if ( !_screenWindow ) return;
2386
getCharacterPosition(ev->pos(),charLine,charColumn);
2387
_iPntSel = QPoint(charColumn,charLine);
2389
_screenWindow->clearSelection();
2391
_lineSelectionMode = true;
2392
_wordSelectionMode = false;
2394
_actSel = 2; // within selection
2395
emit isBusySelecting(true); // Keep it steady...
2397
while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
2400
if (_tripleClickMode == SelectForwardsFromCursor) {
2401
// find word boundary start
2402
int i = loc(_iPntSel.x(),_iPntSel.y());
2403
QChar selClass = charClass(_image[i].character);
2404
int x = _iPntSel.x();
2407
(_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
2409
&& charClass(_image[i-1].character) == selClass )
2421
_screenWindow->setSelectionStart( x , _iPntSel.y() , false );
2422
_tripleSelBegin = QPoint( x, _iPntSel.y() );
2424
else if (_tripleClickMode == SelectWholeLine) {
2425
_screenWindow->setSelectionStart( 0 , _iPntSel.y() , false );
2426
_tripleSelBegin = QPoint( 0, _iPntSel.y() );
2429
while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) )
2432
_screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() );
2434
setSelection(_screenWindow->selectedText(_preserveLineBreaks));
2436
_iPntSel.ry() += _scrollBar->value();
2440
bool TerminalDisplay::focusNextPrevChild( bool next )
2443
return false; // This disables changing the active part in konqueror
2444
// when pressing Tab
2445
return QWidget::focusNextPrevChild( next );
2449
QChar TerminalDisplay::charClass(QChar qch) const
2451
if ( qch.isSpace() ) return ' ';
2453
if ( qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ) )
2459
void TerminalDisplay::setWordCharacters(const QString& wc)
2461
_wordCharacters = wc;
2464
void TerminalDisplay::setUsesMouse(bool on)
2467
setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
2469
bool TerminalDisplay::usesMouse() const
2474
/* ------------------------------------------------------------------------- */
2478
/* ------------------------------------------------------------------------- */
2482
void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn)
2484
if ( !_screenWindow )
2487
// Paste Clipboard by simulating keypress events
2488
QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection :
2489
QClipboard::Clipboard);
2492
if ( ! text.isEmpty() )
2494
text.replace('\n', '\r');
2495
QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
2496
emit keyPressedSignal(&e); // expose as a big fat keypress event
2498
_screenWindow->clearSelection();
2502
void TerminalDisplay::setSelection(const QString& t)
2504
QApplication::clipboard()->setText(t, QClipboard::Selection);
2507
void TerminalDisplay::copyClipboard()
2509
if ( !_screenWindow )
2512
QString text = _screenWindow->selectedText(_preserveLineBreaks);
2513
if (!text.isEmpty())
2514
QApplication::clipboard()->setText(text);
2517
void TerminalDisplay::pasteClipboard()
2519
emitSelection(false,false);
2522
void TerminalDisplay::pasteSelection()
2524
emitSelection(true,false);
2527
/* ------------------------------------------------------------------------- */
2531
/* ------------------------------------------------------------------------- */
2533
void TerminalDisplay::setFlowControlWarningEnabled( bool enable )
2535
_flowControlWarningEnabled = enable;
2537
// if the dialog is currently visible and the flow control warning has
2538
// been disabled then hide the dialog
2540
outputSuspended(false);
2543
void TerminalDisplay::setMotionAfterPasting(MotionAfterPasting action)
2545
mMotionAfterPasting = action;
2548
int TerminalDisplay::motionAfterPasting()
2550
return mMotionAfterPasting;
2553
void TerminalDisplay::keyPressEvent( QKeyEvent* event )
2555
bool emitKeyPressSignal = true;
2557
// Keyboard-based navigation
2558
if ( event->modifiers() == Qt::ShiftModifier )
2562
if ( event->key() == Qt::Key_PageUp )
2564
_screenWindow->scrollBy( ScreenWindow::ScrollPages , -1 );
2566
else if ( event->key() == Qt::Key_PageDown )
2568
_screenWindow->scrollBy( ScreenWindow::ScrollPages , 1 );
2570
else if ( event->key() == Qt::Key_Up )
2572
_screenWindow->scrollBy( ScreenWindow::ScrollLines , -1 );
2574
else if ( event->key() == Qt::Key_Down )
2576
_screenWindow->scrollBy( ScreenWindow::ScrollLines , 1 );
2578
else if ( event->key() == Qt::Key_End)
2582
else if ( event->key() == Qt::Key_Home)
2584
_screenWindow->scrollTo(0);
2591
_screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
2593
updateLineProperties();
2596
// do not send key press to terminal
2597
emitKeyPressSignal = false;
2601
_actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
2602
// know where the current selection is.
2604
if (_hasBlinkingCursor)
2606
_blinkCursorTimer->start(QApplication::cursorFlashTime() / 2);
2607
if (_cursorBlinking)
2610
_cursorBlinking = false;
2613
if ( emitKeyPressSignal )
2615
emit keyPressedSignal(event);
2617
if(event->modifiers().testFlag(Qt::ShiftModifier)
2618
|| event->modifiers().testFlag(Qt::ControlModifier)
2619
|| event->modifiers().testFlag(Qt::AltModifier))
2621
switch(mMotionAfterPasting)
2623
case MoveStartScreenWindow:
2624
_screenWindow->scrollTo(0);
2626
case MoveEndScreenWindow:
2629
case NoMoveScreenWindow:
2642
void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event )
2644
QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString());
2645
emit keyPressedSignal(&keyEvent);
2647
_inputMethodData.preeditString = event->preeditString();
2648
update(preeditRect() | _inputMethodData.previousPreeditRect);
2652
QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const
2654
const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0);
2657
case Qt::ImMicroFocus:
2658
return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1));
2663
case Qt::ImCursorPosition:
2664
// return the cursor position within the current line
2665
return cursorPos.x();
2667
case Qt::ImSurroundingText:
2669
// return the text from the current line
2671
QTextStream stream(&lineText);
2672
PlainTextDecoder decoder;
2673
decoder.begin(&stream);
2674
decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,_lineProperties[cursorPos.y()]);
2679
case Qt::ImCurrentSelection:
2689
bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent)
2691
int modifiers = keyEvent->modifiers();
2693
// When a possible shortcut combination is pressed,
2694
// emit the overrideShortcutCheck() signal to allow the host
2695
// to decide whether the terminal should override it or not.
2696
if (modifiers != Qt::NoModifier)
2698
int modifierCount = 0;
2699
unsigned int currentModifier = Qt::ShiftModifier;
2701
while (currentModifier <= Qt::KeypadModifier)
2703
if (modifiers & currentModifier)
2705
currentModifier <<= 1;
2707
if (modifierCount < 2)
2709
bool override = false;
2710
emit overrideShortcutCheck(keyEvent,override);
2719
// Override any of the following shortcuts because
2720
// they are needed by the terminal
2721
int keyCode = keyEvent->key() | modifiers;
2724
// list is taken from the QLineEdit::event() code
2726
case Qt::Key_Delete:
2729
case Qt::Key_Backspace:
2732
case Qt::Key_Escape:
2739
bool TerminalDisplay::event(QEvent* event)
2741
bool eventHandled = false;
2742
switch (event->type())
2744
case QEvent::ShortcutOverride:
2745
eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event);
2747
case QEvent::PaletteChange:
2748
case QEvent::ApplicationPaletteChange:
2749
_scrollBar->setPalette( QApplication::palette() );
2754
return eventHandled ? true : QWidget::event(event);
2757
void TerminalDisplay::setBellMode(int mode)
2762
void TerminalDisplay::enableBell()
2767
void TerminalDisplay::bell(const QString& message)
2769
if (_bellMode==NoBell) return;
2771
//limit the rate at which bells can occur
2772
//...mainly for sound effects where rapid bells in sequence
2773
//produce a horrible noise
2777
QTimer::singleShot(500,this,SLOT(enableBell()));
2779
if (_bellMode==SystemBeepBell)
2781
QApplication::beep();
2783
else if (_bellMode==NotifyBell)
2785
emit notifyBell(message);
2787
else if (_bellMode==VisualBell)
2790
QTimer::singleShot(200,this,SLOT(swapColorTable()));
2795
void TerminalDisplay::selectionChanged()
2797
emit copyAvailable(_screenWindow->selectedText(false).isEmpty() == false);
2800
void TerminalDisplay::swapColorTable()
2802
ColorEntry color = _colorTable[1];
2803
_colorTable[1]=_colorTable[0];
2804
_colorTable[0]= color;
2805
_colorsInverted = !_colorsInverted;
2809
void TerminalDisplay::clearImage()
2811
// We initialize _image[_imageSize] too. See makeImage()
2812
for (int i = 0; i <= _imageSize; i++)
2814
_image[i].character = ' ';
2815
_image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
2816
DEFAULT_FORE_COLOR);
2817
_image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
2818
DEFAULT_BACK_COLOR);
2819
_image[i].rendition = DEFAULT_RENDITION;
2823
void TerminalDisplay::calcGeometry()
2825
_scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
2826
switch(_scrollbarLocation)
2829
_leftMargin = DEFAULT_LEFT_MARGIN;
2830
_contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN;
2832
case ScrollBarLeft :
2833
_leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width();
2834
_contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
2835
_scrollBar->move(contentsRect().topLeft());
2837
case ScrollBarRight:
2838
_leftMargin = DEFAULT_LEFT_MARGIN;
2839
_contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
2840
_scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1,0));
2844
_topMargin = DEFAULT_TOP_MARGIN;
2845
_contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1;
2849
// ensure that display is always at least one column wide
2850
_columns = qMax(1,_contentWidth / _fontWidth);
2851
_usedColumns = qMin(_usedColumns,_columns);
2853
// ensure that display is always at least one line high
2854
_lines = qMax(1,_contentHeight / _fontHeight);
2855
_usedLines = qMin(_usedLines,_lines);
2859
void TerminalDisplay::makeImage()
2863
// confirm that array will be of non-zero size, since the painting code
2864
// assumes a non-zero array length
2865
Q_ASSERT( _lines > 0 && _columns > 0 );
2866
Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns );
2868
_imageSize=_lines*_columns;
2870
// We over-commit one character so that we can be more relaxed in dealing with
2871
// certain boundary conditions: _image[_imageSize] is a valid but unused position
2872
_image = new Character[_imageSize+1];
2877
// calculate the needed size, this must be synced with calcGeometry()
2878
void TerminalDisplay::setSize(int columns, int lines)
2880
int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width();
2881
int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN;
2882
int verticalMargin = 2 * DEFAULT_TOP_MARGIN;
2884
QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth) ,
2885
verticalMargin + (lines * _fontHeight) );
2887
if ( newSize != size() )
2894
void TerminalDisplay::setFixedSize(int cols, int lins)
2896
_isFixedSize = true;
2898
//ensure that display is at least one line by one column in size
2899
_columns = qMax(1,cols);
2900
_lines = qMax(1,lins);
2901
_usedColumns = qMin(_usedColumns,_columns);
2902
_usedLines = qMin(_usedLines,_lines);
2909
setSize(cols, lins);
2910
QWidget::setFixedSize(_size);
2913
QSize TerminalDisplay::sizeHint() const
2919
/* --------------------------------------------------------------------- */
2923
/* --------------------------------------------------------------------- */
2925
void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
2927
if (event->mimeData()->hasFormat("text/plain"))
2928
event->acceptProposedAction();
2929
if (event->mimeData()->urls().count());
2930
event->acceptProposedAction();
2933
void TerminalDisplay::dropEvent(QDropEvent* event)
2935
//KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
2936
QList<QUrl> urls = event->mimeData()->urls();
2939
if (!urls.isEmpty())
2941
// TODO/FIXME: escape or quote pasted things if neccessary...
2942
qDebug() << "TerminalDisplay: handling urls. It can be broken. Report any errors, please";
2943
for ( int i = 0 ; i < urls.count() ; i++ )
2945
//KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
2950
if (url.isLocalFile())
2951
urlText = url.path();
2953
urlText = url.toString();
2955
// in future it may be useful to be able to insert file names with drag-and-drop
2956
// without quoting them (this only affects paths with spaces in)
2957
//urlText = KShell::quoteArg(urlText);
2959
dropText += urlText;
2961
if ( i != urls.count()-1 )
2967
dropText = event->mimeData()->text();
2970
emit sendStringToEmu(dropText.toLocal8Bit());
2973
void TerminalDisplay::doDrag()
2975
dragInfo.state = diDragging;
2976
dragInfo.dragObject = new QDrag(this);
2977
QMimeData *mimeData = new QMimeData;
2978
mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
2979
dragInfo.dragObject->setMimeData(mimeData);
2980
dragInfo.dragObject->start(Qt::CopyAction);
2981
// Don't delete the QTextDrag object. Qt will delete it when it's done with it.
2984
void TerminalDisplay::outputSuspended(bool suspended)
2986
//create the label when this function is first called
2987
if (!_outputSuspendedLabel)
2989
//This label includes a link to an English language website
2990
//describing the 'flow control' (Xon/Xoff) feature found in almost
2991
//all terminal emulators.
2992
//If there isn't a suitable article available in the target language the link
2993
//can simply be removed.
2994
_outputSuspendedLabel = new QLabel( tr("<qt>Output has been "
2995
"<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>"
2996
" by pressing Ctrl+S."
2997
" Press <b>Ctrl+Q</b> to resume.</qt>"),
3000
QPalette palette(_outputSuspendedLabel->palette());
3001
//KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
3002
_outputSuspendedLabel->setPalette(palette);
3003
_outputSuspendedLabel->setAutoFillBackground(true);
3004
_outputSuspendedLabel->setBackgroundRole(QPalette::Base);
3005
_outputSuspendedLabel->setFont(QApplication::font());
3006
_outputSuspendedLabel->setContentsMargins(5, 5, 5, 5);
3008
//enable activation of "Xon/Xoff" link in label
3009
_outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse |
3010
Qt::LinksAccessibleByKeyboard);
3011
_outputSuspendedLabel->setOpenExternalLinks(true);
3012
_outputSuspendedLabel->setVisible(false);
3014
_gridLayout->addWidget(_outputSuspendedLabel);
3015
_gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding,
3016
QSizePolicy::Expanding),
3021
_outputSuspendedLabel->setVisible(suspended);
3024
uint TerminalDisplay::lineSpacing() const
3026
return _lineSpacing;
3029
void TerminalDisplay::setLineSpacing(uint i)
3032
setVTFont(font()); // Trigger an update.
3035
AutoScrollHandler::AutoScrollHandler(QWidget* parent)
3039
parent->installEventFilter(this);
3041
void AutoScrollHandler::timerEvent(QTimerEvent* event)
3043
if (event->timerId() != _timerId)
3046
QMouseEvent mouseEvent( QEvent::MouseMove,
3047
widget()->mapFromGlobal(QCursor::pos()),
3052
QApplication::sendEvent(widget(),&mouseEvent);
3054
bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event)
3056
Q_ASSERT( watched == parent() );
3057
Q_UNUSED( watched );
3059
QMouseEvent* mouseEvent = (QMouseEvent*)event;
3060
switch (event->type())
3062
case QEvent::MouseMove:
3064
bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
3069
killTimer(_timerId);
3074
if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton))
3075
_timerId = startTimer(100);
3079
case QEvent::MouseButtonRelease:
3080
if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton))
3082
killTimer(_timerId);
3093
// QML Bindings connections
3094
void TerminalDisplay::simulateKeyPress(QKeyEvent *event)
3096
keyPressedSignal(event);
3099
void TerminalDisplay::simulateMouseMove(QMouseEvent *event)
3101
mouseMoveEvent(event);
3104
void TerminalDisplay::simulateMousePress(QMouseEvent *event)
3106
mousePressEvent(event);
3109
void TerminalDisplay::simulateMouseRelease(QMouseEvent *event)
3111
mouseReleaseEvent(event);
3114
void TerminalDisplay::simulateMouseDoubleClick(QMouseEvent *event)
3116
mouseDoubleClickEvent(event);
3119
void TerminalDisplay::simulateWheelEvent(QWheelEvent *event)
3124
void TerminalDisplay::simulateInputMethodEvent ( QInputMethodEvent* event )
3126
inputMethodEvent(event);
3129
QVariant TerminalDisplay::simulateInputMethodQuery( Qt::InputMethodQuery query ) const
3131
return inputMethodQuery(query);
3134
void TerminalDisplay::simulateResizeEvent(int width, int height)
3136
QResizeEvent event(QSize(width, height), QSize(this->width(), this->height()));
3137
resizeEvent(&event);
3140
bool TerminalDisplay::simulateEvent( QEvent * ev)
3145
//#include "TerminalDisplay.moc"