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/QClipboard>
29
#include <QtGui/QKeyEvent>
30
#include <QtCore/QEvent>
31
#include <QtCore/QFileInfo>
32
#include <QtGui/QGridLayout>
33
#include <QtGui/QAction>
34
#include <QtGui/QLabel>
35
#include <QtGui/QPainter>
36
#include <QtGui/QPixmap>
37
#include <QtGui/QScrollBar>
38
#include <QtGui/QStyle>
39
#include <QtCore/QTimer>
40
#include <QtGui/QToolTip>
41
#include <QtGui/QAccessible>
45
#include <KColorScheme>
48
#include <KLocalizedString>
49
#include <KNotification>
50
#include <KGlobalSettings>
51
#include <KIO/NetAccess>
52
#include <konq_operations.h>
57
#include "konsole_wcwidth.h"
58
#include "TerminalCharacterDecoder.h"
60
#include "ScreenWindow.h"
62
#include "SessionController.h"
63
#include "ExtendedCharTable.h"
64
#include "TerminalDisplayAccessible.h"
65
#include "SessionManager.h"
68
using namespace Konsole;
71
#define loc(X,Y) ((Y)*_columns+(X))
74
#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
75
"abcdefgjijklmnopqrstuvwxyz" \
78
// we use this to force QPainter to display text in LTR mode
79
// more information can be found in: http://unicode.org/reports/tr9/
80
const QChar LTR_OVERRIDE_CHAR(0x202D);
82
/* ------------------------------------------------------------------------- */
86
/* ------------------------------------------------------------------------- */
88
/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
91
----------- ------- ------- ------- ------- ------- ------- ------- -------
92
ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
93
IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
96
ScreenWindow* TerminalDisplay::screenWindow() const
100
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
102
// disconnect existing screen window if any
104
disconnect(_screenWindow , 0 , this , 0);
107
_screenWindow = window;
110
connect(_screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateLineProperties()));
111
connect(_screenWindow , SIGNAL(outputChanged()) , this , SLOT(updateImage()));
112
_screenWindow->setWindowLines(_lines);
116
const ColorEntry* TerminalDisplay::colorTable() const
120
void TerminalDisplay::setBackgroundColor(const QColor& color)
122
_colorTable[DEFAULT_BACK_COLOR].color = color;
124
QPalette p = palette();
125
p.setColor(backgroundRole(), color);
128
// Avoid propagating the palette change to the scroll bar
129
_scrollBar->setPalette(QApplication::palette());
133
void TerminalDisplay::setForegroundColor(const QColor& color)
135
_colorTable[DEFAULT_FORE_COLOR].color = color;
139
void TerminalDisplay::setColorTable(const ColorEntry table[])
141
for (int i = 0; i < TABLE_COLORS; i++)
142
_colorTable[i] = table[i];
144
setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
147
/* ------------------------------------------------------------------------- */
151
/* ------------------------------------------------------------------------- */
153
static inline bool isLineCharString(const QString& string)
155
if (string.length() == 0)
158
return isSupportedLineChar(string.at(0).unicode());
161
void TerminalDisplay::fontChange(const QFont&)
163
QFontMetrics fm(font());
164
_fontHeight = fm.height() + _lineSpacing;
166
// waba TerminalDisplay 1.123:
167
// "Base character width on widest ASCII character. This prevents too wide
168
// characters in the presence of double wide (e.g. Japanese) characters."
169
// Get the width from representative normal width characters
170
_fontWidth = qRound((double)fm.width(REPCHAR) / (double)qstrlen(REPCHAR));
174
const int fw = fm.width(REPCHAR[0]);
175
for (unsigned int i = 1; i < qstrlen(REPCHAR); i++) {
176
if (fw != fm.width(REPCHAR[i])) {
185
_fontAscent = fm.ascent();
187
emit changedFontMetricSignal(_fontHeight, _fontWidth);
192
void TerminalDisplay::setVTFont(const QFont& f)
196
QFontMetrics metrics(font);
198
if (!QFontInfo(font).fixedPitch()) {
199
kWarning() << "Using an unsupported variable-width font in the terminal. This may produce display errors.";
202
if (metrics.height() < height() && metrics.maxWidth() < width()) {
203
// hint that text should be drawn without anti-aliasing.
204
// depending on the user's font configuration, this may not be respected
206
font.setStyleStrategy(QFont::NoAntialias);
208
// experimental optimization. Konsole assumes that the terminal is using a
209
// mono-spaced font, in which case kerning information should have an effect.
210
// Disabling kerning saves some computation when rendering text.
211
font.setKerning(false);
213
// Konsole cannot handle non-integer font metrics
214
font.setStyleStrategy(QFont::StyleStrategy(font.styleStrategy() | QFont::ForceIntegerMetrics));
216
QWidget::setFont(font);
221
void TerminalDisplay::setFont(const QFont &)
223
// ignore font change request if not coming from konsole itself
226
void TerminalDisplay::increaseFontSize()
228
QFont font = getVTFont();
229
font.setPointSizeF(font.pointSizeF() + 1);
233
void TerminalDisplay::decreaseFontSize()
235
const qreal MinimumFontSize = 6;
237
QFont font = getVTFont();
238
font.setPointSizeF(qMax(font.pointSizeF() - 1, MinimumFontSize));
242
uint TerminalDisplay::lineSpacing() const
247
void TerminalDisplay::setLineSpacing(uint i)
250
setVTFont(font()); // Trigger an update.
254
/* ------------------------------------------------------------------------- */
258
/* ------------------------------------------------------------------------- */
262
* This function installs the factory function which lets Qt instanciate the QAccessibleInterface
263
* for the TerminalDisplay.
265
QAccessibleInterface* accessibleInterfaceFactory(const QString &key, QObject *object)
268
if (TerminalDisplay *display = qobject_cast<TerminalDisplay*>(object))
269
return new TerminalDisplayAccessible(display);
274
/* ------------------------------------------------------------------------- */
276
/* Constructor / Destructor */
278
/* ------------------------------------------------------------------------- */
280
TerminalDisplay::TerminalDisplay(QWidget* parent)
298
, _showTerminalSizeHint(true)
299
, _bidiEnabled(false)
301
, _wordSelectionMode(false)
302
, _lineSelectionMode(false)
303
, _preserveLineBreaks(false)
304
, _columnSelectionMode(false)
305
, _autoCopySelectedText(false)
306
, _middleClickPasteMode(Enum::PasteFromX11Selection)
307
, _scrollbarLocation(Enum::ScrollBarRight)
308
, _wordCharacters(":@-./_~")
309
, _bellMode(Enum::NotifyBell)
310
, _allowBlinkingText(true)
311
, _allowBlinkingCursor(false)
312
, _textBlinking(false)
313
, _cursorBlinking(false)
314
, _hasTextBlinker(false)
315
, _underlineLinks(true)
316
, _isFixedSize(false)
318
, _tripleClickMode(Enum::SelectWholeLine)
319
, _possibleTripleClick(false)
322
, _flowControlWarningEnabled(false)
323
, _outputSuspendedLabel(0)
325
, _blendColor(qRgba(0, 0, 0, 0xff))
326
, _filterChain(new TerminalImageFilterChain())
327
, _cursorShape(Enum::BlockCursor)
328
, _antialiasText(true)
329
, _sessionController(0)
331
// terminal applications are not designed with Right-To-Left in mind,
332
// so the layout is forced to Left-To-Right
333
setLayoutDirection(Qt::LeftToRight);
335
_topMargin = DEFAULT_TOP_MARGIN;
336
_leftMargin = DEFAULT_LEFT_MARGIN;
338
// create scroll bar for scrolling output up and down
339
_scrollBar = new QScrollBar(this);
340
// set the scroll bar's slider to occupy the whole area of the scroll bar initially
342
_scrollBar->setCursor(Qt::ArrowCursor);
343
connect(_scrollBar, SIGNAL(valueChanged(int)),
344
this, SLOT(scrollBarPositionChanged(int)));
346
// setup timers for blinking text
347
_blinkTextTimer = new QTimer(this);
348
_blinkTextTimer->setInterval(TEXT_BLINK_DELAY);
349
connect(_blinkTextTimer, SIGNAL(timeout()), this, SLOT(blinkTextEvent()));
351
// setup timers for blinking cursor
352
_blinkCursorTimer = new QTimer(this);
353
_blinkCursorTimer->setInterval(QApplication::cursorFlashTime() / 2);
354
connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
356
// hide mouse cursor on keystroke or idle
357
KCursor::setAutoHideCursor(this, true);
358
setMouseTracking(true);
362
setColorTable(ColorScheme::defaultTable);
364
// Enable drag and drop support
365
setAcceptDrops(true);
366
_dragInfo.state = diNone;
368
setFocusPolicy(Qt::WheelFocus);
370
// enable input method support
371
setAttribute(Qt::WA_InputMethodEnabled, true);
373
// this is an important optimization, it tells Qt
374
// that TerminalDisplay will handle repainting its entire area.
375
setAttribute(Qt::WA_OpaquePaintEvent);
377
_gridLayout = new QGridLayout(this);
378
_gridLayout->setContentsMargins(0, 0, 0, 0);
380
setLayout(_gridLayout);
382
new AutoScrollHandler(this);
385
#ifndef QT_NO_ACCESSIBILITY
386
QAccessible::installFactory(Konsole::accessibleInterfaceFactory);
390
TerminalDisplay::~TerminalDisplay()
392
disconnect(_blinkTextTimer);
393
disconnect(_blinkCursorTimer);
398
delete _outputSuspendedLabel;
402
/* ------------------------------------------------------------------------- */
404
/* Display Operations */
406
/* ------------------------------------------------------------------------- */
409
A table for emulating the simple (single width) unicode drawing chars.
410
It represents the 250x - 257x glyphs. If it's zero, we can't use it.
411
if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered
412
0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit.
414
Then, the pixels basically have the following interpretation:
454
static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
456
//Calculate cell midpoints, end points.
457
const int cx = x + w / 2;
458
const int cy = y + h / 2;
459
const int ex = x + w - 1;
460
const int ey = y + h - 1;
462
const quint32 toDraw = LineChars[code];
466
paint.drawLine(cx - 1, y, cx - 1, cy - 2);
468
paint.drawLine(cx, y, cx, cy - 2);
470
paint.drawLine(cx + 1, y, cx + 1, cy - 2);
474
paint.drawLine(cx - 1, cy + 2, cx - 1, ey);
476
paint.drawLine(cx, cy + 2, cx, ey);
478
paint.drawLine(cx + 1, cy + 2, cx + 1, ey);
482
paint.drawLine(x, cy - 1, cx - 2, cy - 1);
484
paint.drawLine(x, cy, cx - 2, cy);
486
paint.drawLine(x, cy + 1, cx - 2, cy + 1);
490
paint.drawLine(cx + 2, cy - 1, ex, cy - 1);
492
paint.drawLine(cx + 2, cy, ex, cy);
494
paint.drawLine(cx + 2, cy + 1, ex, cy + 1);
496
//Intersection points.
498
paint.drawPoint(cx - 1, cy - 1);
500
paint.drawPoint(cx, cy - 1);
502
paint.drawPoint(cx + 1, cy - 1);
505
paint.drawPoint(cx - 1, cy);
507
paint.drawPoint(cx, cy);
509
paint.drawPoint(cx + 1, cy);
512
paint.drawPoint(cx - 1, cy + 1);
514
paint.drawPoint(cx, cy + 1);
516
paint.drawPoint(cx + 1, cy + 1);
519
void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str,
520
const Character* attributes)
522
const QPen& originalPen = painter.pen();
524
if ((attributes->rendition & RE_BOLD) && _boldIntense) {
525
QPen boldPen(originalPen);
527
painter.setPen(boldPen);
530
for (int i = 0 ; i < str.length(); i++) {
531
const uchar code = str[i].cell();
533
drawLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
536
painter.setPen(originalPen);
539
void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape)
541
_cursorShape = shape;
543
Enum::CursorShapeEnum TerminalDisplay::keyboardCursorShape() const
547
void TerminalDisplay::setKeyboardCursorColor(const QColor& color)
549
_cursorColor = color;
551
QColor TerminalDisplay::keyboardCursorColor() const
556
void TerminalDisplay::setOpacity(qreal opacity)
558
QColor color(_blendColor);
559
color.setAlphaF(opacity);
561
// enable automatic background filling to prevent the display
562
// flickering if there is no transparency
563
/*if ( color.alpha() == 255 )
565
setAutoFillBackground(true);
569
setAutoFillBackground(false);
572
_blendColor = color.rgba();
575
void TerminalDisplay::setWallpaper(ColorSchemeWallpaper::Ptr p)
580
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting)
582
// the area of the widget showing the contents of the terminal display is drawn
583
// using the background color from the color scheme set with setColorTable()
585
// the area of the widget behind the scroll-bar is drawn using the background
586
// brush from the scroll-bar's palette, to give the effect of the scroll-bar
587
// being outside of the terminal display and visual consistency with other KDE
590
QRect scrollBarArea = _scrollBar->isVisible() ?
591
rect.intersected(_scrollBar->geometry()) :
593
QRegion contentsRegion = QRegion(rect).subtracted(scrollBarArea);
594
QRect contentsRect = contentsRegion.boundingRect();
596
if (useOpacitySetting && !_wallpaper->isNull() &&
597
_wallpaper->draw(painter, contentsRect)) {
598
} else if (qAlpha(_blendColor) < 0xff && useOpacitySetting) {
599
// TODO - On MacOS, using CompositionMode doesn't work. Altering the
600
// transparency in the color scheme (appears to) alter the
601
// brightness(?). I'm not sure #ifdef is worthwhile ATM.
602
QColor color(backgroundColor);
603
color.setAlpha(qAlpha(_blendColor));
606
painter.setCompositionMode(QPainter::CompositionMode_Source);
607
painter.fillRect(contentsRect, color);
610
painter.fillRect(contentsRect, backgroundColor);
613
painter.fillRect(scrollBarArea, _scrollBar->palette().background());
616
void TerminalDisplay::drawCursor(QPainter& painter,
618
const QColor& foregroundColor,
619
const QColor& /*backgroundColor*/,
620
bool& invertCharacterColor)
622
// don't draw cursor which is currently blinking
626
QRect cursorRect = rect;
627
cursorRect.setHeight(_fontHeight - _lineSpacing - 1);
629
QColor cursorColor = _cursorColor.isValid() ? _cursorColor : foregroundColor;
630
painter.setPen(cursorColor);
632
if (_cursorShape == Enum::BlockCursor) {
633
// draw the cursor outline, adjusting the area so that
634
// it is draw entirely inside 'rect'
635
int penWidth = qMax(1, painter.pen().width());
636
painter.drawRect(cursorRect.adjusted(penWidth / 2,
638
- penWidth / 2 - penWidth % 2,
639
- penWidth / 2 - penWidth % 2));
641
// draw the cursor body only when the widget has focus
643
painter.fillRect(cursorRect, cursorColor);
645
if (!_cursorColor.isValid()) {
646
// invert the color used to draw the text to ensure that the character at
647
// the cursor position is readable
648
invertCharacterColor = true;
651
} else if (_cursorShape == Enum::UnderlineCursor)
652
painter.drawLine(cursorRect.left(),
655
cursorRect.bottom());
657
else if (_cursorShape == Enum::IBeamCursor)
658
painter.drawLine(cursorRect.left(),
661
cursorRect.bottom());
664
void TerminalDisplay::drawCharacters(QPainter& painter,
667
const Character* style,
668
bool invertCharacterColor)
670
// don't draw text which is currently blinking
671
if (_textBlinking && (style->rendition & RE_BLINK))
674
// setup bold and underline
676
ColorEntry::FontWeight weight = style->fontWeight(_colorTable);
677
if (weight == ColorEntry::UseCurrentFormat)
678
useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
680
useBold = (weight == ColorEntry::Bold) ? true : false;
681
const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
683
QFont font = painter.font();
684
if (font.bold() != useBold
685
|| font.underline() != useUnderline) {
686
font.setBold(useBold);
687
font.setUnderline(useUnderline);
688
painter.setFont(font);
692
const CharacterColor& textColor = (invertCharacterColor ? style->backgroundColor : style->foregroundColor);
693
const QColor color = textColor.color(_colorTable);
694
QPen pen = painter.pen();
695
if (pen.color() != color) {
697
painter.setPen(color);
701
if (isLineCharString(text)) {
702
drawLineCharString(painter, rect.x(), rect.y(), text, style);
704
// Force using LTR as the document layout for the terminal area, because
705
// there is no use cases for RTL emulator and RTL terminal application.
707
// This still allows RTL characters to be rendered in the RTL way.
708
painter.setLayoutDirection(Qt::LeftToRight);
710
// the drawText(rect,flags,string) overload is used here with null flags
711
// instead of drawText(rect,string) because the (rect,string) overload causes
712
// the application's default layout direction to be used instead of
713
// the widget-specific layout direction, which should always be
714
// Qt::LeftToRight for this widget
716
// This was discussed in: http://lists.kde.org/?t=120552223600002&r=1&w=2
718
painter.drawText(rect, 0, text);
720
// See bug 280896 for more info
721
#if QT_VERSION >= 0x040800
722
painter.drawText(rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + text);
724
painter.drawText(rect, 0, LTR_OVERRIDE_CHAR + text);
730
void TerminalDisplay::drawTextFragment(QPainter& painter ,
733
const Character* style)
738
const QColor foregroundColor = style->foregroundColor.color(_colorTable);
739
const QColor backgroundColor = style->backgroundColor.color(_colorTable);
741
// draw background if different from the display's background color
742
if (backgroundColor != palette().background().color())
743
drawBackground(painter, rect, backgroundColor,
744
false /* do not use transparency */);
746
// draw cursor shape if the current character is the cursor
747
// this may alter the foreground and background colors
748
bool invertCharacterColor = false;
749
if (style->rendition & RE_CURSOR)
750
drawCursor(painter, rect, foregroundColor, backgroundColor, invertCharacterColor);
753
drawCharacters(painter, rect, text, style, invertCharacterColor);
758
void TerminalDisplay::setRandomSeed(uint randomSeed)
760
_randomSeed = randomSeed;
762
uint TerminalDisplay::randomSeed() const
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
const int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->width();
812
const int SCROLLBAR_CONTENT_GAP = 1;
814
if (_scrollbarLocation == Enum::ScrollBarLeft) {
815
scrollRect.setLeft(scrollBarWidth + SCROLLBAR_CONTENT_GAP);
816
scrollRect.setRight(width());
818
scrollRect.setLeft(0);
819
scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
821
void* firstCharPos = &_image[ region.top() * this->_columns ];
822
void* lastCharPos = &_image[(region.top() + abs(lines)) * this->_columns ];
824
const int top = _topMargin + (region.top() * _fontHeight);
825
const int linesToMove = region.height() - abs(lines);
826
const int bytesToMove = linesToMove * this->_columns * sizeof(Character);
828
Q_ASSERT(linesToMove > 0);
829
Q_ASSERT(bytesToMove > 0);
831
//scroll internal image
833
// check that the memory areas that we are going to move are valid
834
Q_ASSERT((char*)lastCharPos + bytesToMove <
835
(char*)(_image + (this->_lines * this->_columns)));
837
Q_ASSERT((lines * this->_columns) < _imageSize);
839
//scroll internal image down
840
memmove(firstCharPos , lastCharPos , bytesToMove);
842
//set region of display to scroll
843
scrollRect.setTop(top);
845
// check that the memory areas that we are going to move are valid
846
Q_ASSERT((char*)firstCharPos + bytesToMove <
847
(char*)(_image + (this->_lines * this->_columns)));
849
//scroll internal image up
850
memmove(lastCharPos , firstCharPos , bytesToMove);
852
//set region of the display to scroll
853
scrollRect.setTop(top + abs(lines) * _fontHeight);
855
scrollRect.setHeight(linesToMove * _fontHeight);
857
Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
859
//scroll the display vertically to match internal _image
860
scroll(0 , _fontHeight * (-lines) , scrollRect);
863
QRegion TerminalDisplay::hotSpotRegion() const
866
foreach(Filter::HotSpot * hotSpot , _filterChain->hotSpots()) {
868
if (hotSpot->startLine() == hotSpot->endLine()) {
869
r.setLeft(hotSpot->startColumn());
870
r.setTop(hotSpot->startLine());
871
r.setRight(hotSpot->endColumn());
872
r.setBottom(hotSpot->endLine());
873
region |= imageToWidget(r);
875
r.setLeft(hotSpot->startColumn());
876
r.setTop(hotSpot->startLine());
877
r.setRight(_columns);
878
r.setBottom(hotSpot->startLine());
879
region |= imageToWidget(r);
880
for (int line = hotSpot->startLine() + 1 ; line < hotSpot->endLine() ; line++) {
883
r.setRight(_columns);
885
region |= imageToWidget(r);
888
r.setTop(hotSpot->endLine());
889
r.setRight(hotSpot->endColumn());
890
r.setBottom(hotSpot->endLine());
891
region |= imageToWidget(r);
897
void TerminalDisplay::processFilters()
902
QRegion preUpdateHotSpots = hotSpotRegion();
904
// use _screenWindow->getImage() here rather than _image because
905
// other classes may call processFilters() when this display's
906
// ScreenWindow emits a scrolled() signal - which will happen before
907
// updateImage() is called on the display and therefore _image is
908
// out of date at this point
909
_filterChain->setImage(_screenWindow->getImage(),
910
_screenWindow->windowLines(),
911
_screenWindow->windowColumns(),
912
_screenWindow->getLineProperties());
913
_filterChain->process();
915
QRegion postUpdateHotSpots = hotSpotRegion();
917
update(preUpdateHotSpots | postUpdateHotSpots);
920
void TerminalDisplay::updateImage()
925
// optimization - scroll the existing image where possible and
926
// avoid expensive text drawing for parts of the image that
927
// can simply be moved up or down
928
if (_wallpaper->isNull()) {
929
scrollImage(_screenWindow->scrollCount() ,
930
_screenWindow->scrollRegion());
931
_screenWindow->resetScrollCount();
936
// The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
940
Character* const newimg = _screenWindow->getImage();
941
const int lines = _screenWindow->windowLines();
942
const int columns = _screenWindow->windowColumns();
944
setScroll(_screenWindow->currentLine() , _screenWindow->lineCount());
946
Q_ASSERT(this->_usedLines <= this->_lines);
947
Q_ASSERT(this->_usedColumns <= this->_columns);
951
const QPoint tL = contentsRect().topLeft();
952
const int tLx = tL.x();
953
const int tLy = tL.y();
954
_hasTextBlinker = false;
956
CharacterColor cf; // undefined
958
const int linesToUpdate = qMin(this->_lines, qMax(0, lines));
959
const int columnsToUpdate = qMin(this->_columns, qMax(0, columns));
961
char* dirtyMask = new char[columnsToUpdate + 2];
964
// debugging variable, this records the number of lines that are found to
965
// be 'dirty' ( ie. have changed from the old _image to the new _image ) and
966
// which therefore need to be repainted
967
int dirtyLineCount = 0;
969
for (y = 0; y < linesToUpdate; ++y) {
970
const Character* currentLine = &_image[y * this->_columns];
971
const Character* const newLine = &newimg[y * columns];
973
bool updateLine = false;
975
// The dirty mask indicates which characters need repainting. We also
976
// mark surrounding neighbours dirty, in case the character exceeds
977
// its cell boundaries
978
memset(dirtyMask, 0, columnsToUpdate + 2);
980
for (x = 0 ; x < columnsToUpdate ; ++x) {
981
if (newLine[x] != currentLine[x]) {
986
if (!_resizing) // not while _resizing, we're expecting a paintEvent
987
for (x = 0; x < columnsToUpdate; ++x) {
988
_hasTextBlinker |= (newLine[x].rendition & RE_BLINK);
990
// Start drawing if this character or the next one differs.
991
// We also take the next one into account to handle the situation
992
// where characters exceed their cell width.
994
if (!newLine[x + 0].character)
996
const bool lineDraw = newLine[x + 0].isLineChar();
997
const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character == 0);
998
const quint8 cr = newLine[x].rendition;
999
const CharacterColor clipboard = newLine[x].backgroundColor;
1000
if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
1001
const int lln = columnsToUpdate - x;
1002
for (len = 1; len < lln; ++len) {
1003
const Character& ch = newLine[x + len];
1006
continue; // Skip trailing part of multi-col chars.
1008
const bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : (newLine[x + len + 1].character == 0);
1010
if (ch.foregroundColor != cf ||
1011
ch.backgroundColor != clipboard ||
1012
(ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) ||
1013
!dirtyMask[x + len] ||
1014
ch.isLineChar() != lineDraw ||
1015
nextIsDoubleWidth != doubleWidth)
1019
const bool saveFixedFont = _fixedFont;
1027
_fixedFont = saveFixedFont;
1032
//both the top and bottom halves of double height _lines must always be redrawn
1033
//although both top and bottom halves contain the same characters, only
1034
//the top one is actually
1036
if (_lineProperties.count() > y)
1037
updateLine |= (_lineProperties[y] & LINE_DOUBLEHEIGHT);
1039
// if the characters on the line are different in the old and the new _image
1040
// then this line must be repainted.
1044
// add the area occupied by this line to the region which needs to be
1046
QRect dirtyRect = QRect(_leftMargin + tLx ,
1047
_topMargin + tLy + _fontHeight * y ,
1048
_fontWidth * columnsToUpdate ,
1051
dirtyRegion |= dirtyRect;
1054
// replace the line of characters in the old _image with the
1055
// current line of the new _image
1056
memcpy((void*)currentLine, (const void*)newLine, columnsToUpdate * sizeof(Character));
1059
// if the new _image is smaller than the previous _image, then ensure that the area
1060
// outside the new _image is cleared
1061
if (linesToUpdate < _usedLines) {
1062
dirtyRegion |= QRect(_leftMargin + tLx ,
1063
_topMargin + tLy + _fontHeight * linesToUpdate ,
1064
_fontWidth * this->_columns ,
1065
_fontHeight * (_usedLines - linesToUpdate));
1067
_usedLines = linesToUpdate;
1069
if (columnsToUpdate < _usedColumns) {
1070
dirtyRegion |= QRect(_leftMargin + tLx + columnsToUpdate * _fontWidth ,
1072
_fontWidth * (_usedColumns - columnsToUpdate) ,
1073
_fontHeight * this->_lines);
1075
_usedColumns = columnsToUpdate;
1077
dirtyRegion |= _inputMethodData.previousPreeditRect;
1079
// update the parts of the display which have changed
1080
update(dirtyRegion);
1082
if (_allowBlinkingText && _hasTextBlinker && !_blinkTextTimer->isActive()) {
1083
_blinkTextTimer->start();
1085
if (!_hasTextBlinker && _blinkTextTimer->isActive()) {
1086
_blinkTextTimer->stop();
1087
_textBlinking = false;
1091
#if QT_VERSION >= 0x040800 // added in Qt 4.8.0
1092
#ifndef QT_NO_ACCESSIBILITY
1093
QAccessible::updateAccessibility(this, 0, QAccessible::TextUpdated);
1094
QAccessible::updateAccessibility(this, 0, QAccessible::TextCaretMoved);
1099
void TerminalDisplay::showResizeNotification()
1101
if (_showTerminalSizeHint && isVisible()) {
1102
if (!_resizeWidget) {
1103
_resizeWidget = new QLabel(i18n("Size: XXX x XXX"), this);
1104
_resizeWidget->setMinimumWidth(_resizeWidget->fontMetrics().width(i18n("Size: XXX x XXX")));
1105
_resizeWidget->setMinimumHeight(_resizeWidget->sizeHint().height());
1106
_resizeWidget->setAlignment(Qt::AlignCenter);
1108
_resizeWidget->setStyleSheet("background-color:palette(window);border-style:solid;border-width:1px;border-color:palette(dark)");
1110
_resizeTimer = new QTimer(this);
1111
_resizeTimer->setInterval(SIZE_HINT_DURATION);
1112
_resizeTimer->setSingleShot(true);
1113
connect(_resizeTimer, SIGNAL(timeout()), _resizeWidget, SLOT(hide()));
1115
QString sizeStr = i18n("Size: %1 x %2", _columns, _lines);
1116
_resizeWidget->setText(sizeStr);
1117
_resizeWidget->move((width() - _resizeWidget->width()) / 2,
1118
(height() - _resizeWidget->height()) / 2 + 20);
1119
_resizeWidget->show();
1120
_resizeTimer->start();
1124
void TerminalDisplay::paintEvent(QPaintEvent* pe)
1126
QPainter paint(this);
1128
foreach(const QRect & rect, (pe->region() & contentsRect()).rects()) {
1129
drawBackground(paint, rect, palette().background().color(),
1130
true /* use opacity setting */);
1131
drawContents(paint, rect);
1133
drawInputMethodPreeditString(paint, preeditRect());
1134
paintFilters(paint);
1137
QPoint TerminalDisplay::cursorPosition() const
1140
return _screenWindow->cursorPosition();
1142
return QPoint(0, 0);
1145
FilterChain* TerminalDisplay::filterChain() const
1147
return _filterChain;
1150
void TerminalDisplay::paintFilters(QPainter& painter)
1152
// get color of character under mouse and use it to draw
1153
// lines for filters
1154
QPoint cursorPos = mapFromGlobal(QCursor::pos());
1157
const int scrollBarWidth = (_scrollbarLocation == Enum::ScrollBarLeft) ? _scrollBar->width() : 0;
1159
getCharacterPosition(cursorPos , cursorLine , cursorColumn);
1160
Character cursorCharacter = _image[loc(cursorColumn, cursorLine)];
1162
painter.setPen(QPen(cursorCharacter.foregroundColor.color(colorTable())));
1164
// iterate over hotspots identified by the display's currently active filters
1165
// and draw appropriate visuals to indicate the presence of the hotspot
1167
QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
1168
foreach(Filter::HotSpot* spot, spots) {
1170
if (_underlineLinks && spot->type() == Filter::HotSpot::Link) {
1172
if (spot->startLine() == spot->endLine()) {
1173
r.setCoords(spot->startColumn()*_fontWidth + 1 + scrollBarWidth,
1174
spot->startLine()*_fontHeight + 1,
1175
(spot->endColumn() - 1)*_fontWidth - 1 + scrollBarWidth,
1176
(spot->endLine() + 1)*_fontHeight - 1);
1179
r.setCoords(spot->startColumn()*_fontWidth + 1 + scrollBarWidth,
1180
spot->startLine()*_fontHeight + 1,
1181
(_columns - 1)*_fontWidth - 1 + scrollBarWidth,
1182
(spot->startLine() + 1)*_fontHeight - 1);
1184
for (int line = spot->startLine() + 1 ; line < spot->endLine() ; line++) {
1185
r.setCoords(0 * _fontWidth + 1 + scrollBarWidth,
1186
line * _fontHeight + 1,
1187
(_columns - 1)*_fontWidth - 1 + scrollBarWidth,
1188
(line + 1)*_fontHeight - 1);
1191
r.setCoords(0 * _fontWidth + 1 + scrollBarWidth,
1192
spot->endLine()*_fontHeight + 1,
1193
(spot->endColumn() - 1)*_fontWidth - 1 + scrollBarWidth,
1194
(spot->endLine() + 1)*_fontHeight - 1);
1199
for (int line = spot->startLine() ; line <= spot->endLine() ; line++) {
1200
int startColumn = 0;
1201
int endColumn = _columns - 1; // TODO use number of _columns which are actually
1202
// occupied on this line rather than the width of the
1203
// display in _columns
1205
// ignore whitespace at the end of the lines
1206
while (_image[loc(endColumn, line)].isSpace() && endColumn > 0)
1209
// increment here because the column which we want to set 'endColumn' to
1210
// is the first whitespace character at the end of the line
1213
if (line == spot->startLine())
1214
startColumn = spot->startColumn();
1215
if (line == spot->endLine())
1216
endColumn = spot->endColumn();
1218
// subtract one pixel from
1219
// the right and bottom so that
1220
// we do not overdraw adjacent
1223
// subtracting one pixel from all sides also prevents an edge case where
1224
// moving the mouse outside a link could still leave it underlined
1225
// because the check below for the position of the cursor
1226
// finds it on the border of the target area
1228
r.setCoords(startColumn * _fontWidth + 1 + scrollBarWidth,
1229
line * _fontHeight + 1,
1230
endColumn * _fontWidth - 1 + scrollBarWidth,
1231
(line + 1)*_fontHeight - 1);
1232
// Underline link hotspots
1233
if (_underlineLinks && spot->type() == Filter::HotSpot::Link) {
1234
QFontMetrics metrics(font());
1236
// find the baseline (which is the invisible line that the characters in the font sit on,
1237
// with some having tails dangling below)
1238
const int baseline = r.bottom() - metrics.descent();
1239
// find the position of the underline below that
1240
const int underlinePos = baseline + metrics.underlinePos();
1241
if (region.contains(mapFromGlobal(QCursor::pos()))) {
1242
painter.drawLine(r.left() , underlinePos ,
1243
r.right() , underlinePos);
1245
// Marker hotspots simply have a transparent rectanglular shape
1246
// drawn on top of them
1247
} else if (spot->type() == Filter::HotSpot::Marker) {
1248
//TODO - Do not use a hardcoded color for this
1249
painter.fillRect(r, QBrush(QColor(255, 0, 0, 120)));
1254
void TerminalDisplay::drawContents(QPainter& paint, const QRect& rect)
1256
const QPoint tL = contentsRect().topLeft();
1257
const int tLx = tL.x();
1258
const int tLy = tL.y();
1260
const int lux = qMin(_usedColumns - 1, qMax(0, (rect.left() - tLx - _leftMargin) / _fontWidth));
1261
const int luy = qMin(_usedLines - 1, qMax(0, (rect.top() - tLy - _topMargin) / _fontHeight));
1262
const int rlx = qMin(_usedColumns - 1, qMax(0, (rect.right() - tLx - _leftMargin) / _fontWidth));
1263
const int rly = qMin(_usedLines - 1, qMax(0, (rect.bottom() - tLy - _topMargin) / _fontHeight));
1265
const int numberOfColumns = _usedColumns;
1267
unistr.reserve(numberOfColumns);
1268
for (int y = luy; y <= rly; y++) {
1270
if (!_image[loc(lux, y)].character && x)
1271
x--; // Search for start of multi-column character
1272
for (; x <= rlx; x++) {
1276
// reset our buffer to the number of columns
1277
int bufferSize = numberOfColumns;
1278
unistr.resize(bufferSize);
1279
QChar *disstrU = unistr.data();
1281
// is this a single character or a sequence of characters ?
1282
if (_image[loc(x, y)].rendition & RE_EXTENDED_CHAR) {
1283
// sequence of characters
1284
ushort extendedCharLength = 0;
1285
const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(_image[loc(x, y)].character, extendedCharLength);
1287
Q_ASSERT(extendedCharLength > 1);
1288
bufferSize += extendedCharLength - 1;
1289
unistr.resize(bufferSize);
1290
disstrU = unistr.data();
1291
for (int index = 0 ; index < extendedCharLength ; index++) {
1292
Q_ASSERT(p < bufferSize);
1293
disstrU[p++] = chars[index];
1298
const quint16 c = _image[loc(x, y)].character;
1300
Q_ASSERT(p < bufferSize);
1301
disstrU[p++] = c; //fontMap(c);
1305
const bool lineDraw = _image[loc(x, y)].isLineChar();
1306
const bool doubleWidth = (_image[ qMin(loc(x, y) + 1, _imageSize) ].character == 0);
1307
const CharacterColor currentForeground = _image[loc(x, y)].foregroundColor;
1308
const CharacterColor currentBackground = _image[loc(x, y)].backgroundColor;
1309
const quint8 currentRendition = _image[loc(x, y)].rendition;
1311
while (x + len <= rlx &&
1312
_image[loc(x + len, y)].foregroundColor == currentForeground &&
1313
_image[loc(x + len, y)].backgroundColor == currentBackground &&
1314
(_image[loc(x + len, y)].rendition & ~RE_EXTENDED_CHAR) == (currentRendition & ~RE_EXTENDED_CHAR) &&
1315
(_image[ qMin(loc(x + len, y) + 1, _imageSize) ].character == 0) == doubleWidth &&
1316
_image[loc(x + len, y)].isLineChar() == lineDraw) {
1317
const quint16 c = _image[loc(x + len, y)].character;
1318
if (_image[loc(x + len, y)].rendition & RE_EXTENDED_CHAR) {
1319
// sequence of characters
1320
ushort extendedCharLength = 0;
1321
const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
1323
Q_ASSERT(extendedCharLength > 1);
1324
bufferSize += extendedCharLength - 1;
1325
unistr.resize(bufferSize);
1326
disstrU = unistr.data();
1327
for (int index = 0 ; index < extendedCharLength ; index++) {
1328
Q_ASSERT(p < bufferSize);
1329
disstrU[p++] = chars[index];
1335
Q_ASSERT(p < bufferSize);
1336
disstrU[p++] = c; //fontMap(c);
1340
if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
1341
len++; // Skip trailing part of multi-column character
1344
if ((x + len < _usedColumns) && (!_image[loc(x + len, y)].character))
1345
len++; // Adjust for trailing part of multi-column character
1347
const bool save__fixedFont = _fixedFont;
1354
// Create a text scaling matrix for double width and double height lines.
1357
if (y < _lineProperties.size()) {
1358
if (_lineProperties[y] & LINE_DOUBLEWIDTH)
1359
textScale.scale(2, 1);
1361
if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1362
textScale.scale(1, 2);
1365
//Apply text scaling matrix.
1366
paint.setWorldMatrix(textScale, true);
1368
//calculate the area in which the text will be drawn
1369
QRect textArea = QRect(_leftMargin + tLx + _fontWidth * x , _topMargin + tLy + _fontHeight * y , _fontWidth * len , _fontHeight);
1371
//move the calculated area to take account of scaling applied to the painter.
1372
//the position of the area from the origin (0,0) is scaled
1373
//by the opposite of whatever
1374
//transformation has been applied to the painter. this ensures that
1375
//painting does actually start from textArea.topLeft()
1376
//(instead of textArea.topLeft() * painter-scale)
1377
textArea.moveTopLeft(textScale.inverted().map(textArea.topLeft()));
1379
//paint text fragment
1380
drawTextFragment(paint,
1383
&_image[loc(x, y)]); //,
1387
_fixedFont = save__fixedFont;
1389
//reset back to single-width, single-height _lines
1390
paint.setWorldMatrix(textScale.inverted(), true);
1392
if (y < _lineProperties.size() - 1) {
1393
//double-height _lines are represented by two adjacent _lines
1394
//containing the same characters
1395
//both _lines will have the LINE_DOUBLEHEIGHT attribute.
1396
//If the current line has the LINE_DOUBLEHEIGHT attribute,
1397
//we can therefore skip the next line
1398
if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
1407
QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
1410
result.setLeft(_leftMargin + _fontWidth * imageArea.left());
1411
result.setTop(_topMargin + _fontHeight * imageArea.top());
1412
result.setWidth(_fontWidth * imageArea.width());
1413
result.setHeight(_fontHeight * imageArea.height());
1418
/* ------------------------------------------------------------------------- */
1420
/* Blinking Text & Cursor */
1422
/* ------------------------------------------------------------------------- */
1424
void TerminalDisplay::setBlinkingCursorEnabled(bool blink)
1426
_allowBlinkingCursor = blink;
1428
if (blink && !_blinkCursorTimer->isActive())
1429
_blinkCursorTimer->start();
1431
if (!blink && _blinkCursorTimer->isActive()) {
1432
_blinkCursorTimer->stop();
1433
if (_cursorBlinking) {
1434
// if cursor is blinking(hidden), blink it again to make it show
1437
Q_ASSERT( _cursorBlinking == false );
1441
void TerminalDisplay::setBlinkingTextEnabled(bool blink)
1443
_allowBlinkingText = blink;
1445
if (blink && !_blinkTextTimer->isActive())
1446
_blinkTextTimer->start();
1448
if (!blink && _blinkTextTimer->isActive()) {
1449
_blinkTextTimer->stop();
1450
_textBlinking = false;
1454
void TerminalDisplay::focusOutEvent(QFocusEvent*)
1456
// trigger a repaint of the cursor so that it is both:
1458
// * visible (in case it was hidden during blinking)
1459
// * drawn in a focused out state
1460
_cursorBlinking = false;
1463
// suppress further cursor blinking
1464
_blinkCursorTimer->stop();
1465
Q_ASSERT( _cursorBlinking == false );
1467
// if text is blinking (hidden), blink it again to make it shown
1471
// suppress further text blinking
1472
_blinkTextTimer->stop();
1473
Q_ASSERT( _textBlinking == false );
1476
void TerminalDisplay::focusInEvent(QFocusEvent*)
1478
if (_allowBlinkingCursor)
1479
_blinkCursorTimer->start();
1483
if (_allowBlinkingText && _hasTextBlinker)
1484
_blinkTextTimer->start();
1487
void TerminalDisplay::blinkTextEvent()
1489
Q_ASSERT(_allowBlinkingText);
1491
_textBlinking = !_textBlinking;
1493
// TODO: Optimize to only repaint the areas of the widget where there is
1494
// blinking text rather than repainting the whole widget.
1498
void TerminalDisplay::blinkCursorEvent()
1500
Q_ASSERT(_allowBlinkingCursor);
1502
_cursorBlinking = !_cursorBlinking;
1506
void TerminalDisplay::updateCursor()
1508
QRect cursorRect = imageToWidget(QRect(cursorPosition(), QSize(1, 1)));
1512
/* ------------------------------------------------------------------------- */
1514
/* Geometry & Resizing */
1516
/* ------------------------------------------------------------------------- */
1518
void TerminalDisplay::resizeEvent(QResizeEvent*)
1523
void TerminalDisplay::propagateSize()
1526
setSize(_columns, _lines);
1527
QWidget::setFixedSize(sizeHint());
1528
parentWidget()->adjustSize();
1529
parentWidget()->setFixedSize(parentWidget()->sizeHint());
1536
void TerminalDisplay::updateImageSize()
1538
Character* oldImage = _image;
1539
const int oldLines = _lines;
1540
const int oldColumns = _columns;
1545
// copy the old image to reduce flicker
1546
int lines = qMin(oldLines, _lines);
1547
int columns = qMin(oldColumns, _columns);
1548
for (int line = 0; line < lines; line++) {
1549
memcpy((void*)&_image[_columns * line],
1550
(void*)&oldImage[oldColumns * line],
1551
columns * sizeof(Character));
1557
_screenWindow->setWindowLines(_lines);
1559
_resizing = (oldLines != _lines) || (oldColumns != _columns);
1562
showResizeNotification();
1563
emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
1569
void TerminalDisplay::makeImage()
1575
// confirm that array will be of non-zero size, since the painting code
1576
// assumes a non-zero array length
1577
Q_ASSERT(_lines > 0 && _columns > 0);
1578
Q_ASSERT(_usedLines <= _lines && _usedColumns <= _columns);
1580
_imageSize = _lines * _columns;
1582
// We over-commit one character so that we can be more relaxed in dealing with
1583
// certain boundary conditions: _image[_imageSize] is a valid but unused position
1584
_image = new Character[_imageSize + 1];
1589
void TerminalDisplay::clearImage()
1591
for (int i = 0; i <= _imageSize; ++i)
1592
_image[i] = Screen::DefaultChar;
1595
void TerminalDisplay::calcGeometry()
1597
_scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
1598
switch (_scrollbarLocation) {
1599
case Enum::ScrollBarHidden :
1600
_leftMargin = DEFAULT_LEFT_MARGIN;
1601
_contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN;
1603
case Enum::ScrollBarLeft :
1604
_leftMargin = DEFAULT_LEFT_MARGIN + _scrollBar->width();
1605
_contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
1606
_scrollBar->move(contentsRect().topLeft());
1608
case Enum::ScrollBarRight:
1609
_leftMargin = DEFAULT_LEFT_MARGIN;
1610
_contentWidth = contentsRect().width() - 2 * DEFAULT_LEFT_MARGIN - _scrollBar->width();
1611
_scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width() - 1, 0));
1615
_topMargin = DEFAULT_TOP_MARGIN;
1616
_contentHeight = contentsRect().height() - 2 * DEFAULT_TOP_MARGIN + /* mysterious */ 1;
1618
if (!_isFixedSize) {
1619
// ensure that display is always at least one column wide
1620
_columns = qMax(1, _contentWidth / _fontWidth);
1621
_usedColumns = qMin(_usedColumns, _columns);
1623
// ensure that display is always at least one line high
1624
_lines = qMax(1, _contentHeight / _fontHeight);
1625
_usedLines = qMin(_usedLines, _lines);
1629
// calculate the needed size, this must be synced with calcGeometry()
1630
void TerminalDisplay::setSize(int columns, int lines)
1632
const int scrollBarWidth = _scrollBar->isHidden() ? 0 : _scrollBar->sizeHint().width();
1633
const int horizontalMargin = 2 * DEFAULT_LEFT_MARGIN;
1634
const int verticalMargin = 2 * DEFAULT_TOP_MARGIN;
1636
QSize newSize = QSize(horizontalMargin + scrollBarWidth + (columns * _fontWidth) ,
1637
verticalMargin + (lines * _fontHeight));
1639
if (newSize != size()) {
1645
void TerminalDisplay::setFixedSize(int cols, int lins)
1647
_isFixedSize = true;
1649
//ensure that display is at least one line by one column in size
1650
_columns = qMax(1, cols);
1651
_lines = qMax(1, lins);
1652
_usedColumns = qMin(_usedColumns, _columns);
1653
_usedLines = qMin(_usedLines, _lines);
1659
setSize(cols, lins);
1660
QWidget::setFixedSize(_size);
1663
QSize TerminalDisplay::sizeHint() const
1668
//showEvent and hideEvent are reimplemented here so that it appears to other classes that the
1669
//display has been resized when the display is hidden or shown.
1671
//TODO: Perhaps it would be better to have separate signals for show and hide instead of using
1672
//the same signal as the one for a content size change
1673
void TerminalDisplay::showEvent(QShowEvent*)
1675
emit changedContentSizeSignal(_contentHeight, _contentWidth);
1677
void TerminalDisplay::hideEvent(QHideEvent*)
1679
emit changedContentSizeSignal(_contentHeight, _contentWidth);
1682
/* ------------------------------------------------------------------------- */
1686
/* ------------------------------------------------------------------------- */
1688
void TerminalDisplay::setScrollBarPosition(Enum::ScrollBarPositionEnum position)
1690
if (_scrollbarLocation == position)
1693
if (position == Enum::ScrollBarHidden)
1698
_topMargin = _leftMargin = 1;
1699
_scrollbarLocation = position;
1705
void TerminalDisplay::scrollBarPositionChanged(int)
1710
_screenWindow->scrollTo(_scrollBar->value());
1712
// if the thumb has been moved to the bottom of the _scrollBar then set
1713
// the display to automatically track new output,
1714
// that is, scroll down automatically
1715
// to how new _lines as they are added
1716
const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
1717
_screenWindow->setTrackOutput(atEndOfOutput);
1722
void TerminalDisplay::setScroll(int cursor, int slines)
1724
// update _scrollBar if the range or value has changed,
1727
// setting the range or value of a _scrollBar will always trigger
1728
// a repaint, so it should be avoided if it is not necessary
1729
if (_scrollBar->minimum() == 0 &&
1730
_scrollBar->maximum() == (slines - _lines) &&
1731
_scrollBar->value() == cursor) {
1735
disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1736
_scrollBar->setRange(0, slines - _lines);
1737
_scrollBar->setSingleStep(1);
1738
_scrollBar->setPageStep(_lines);
1739
_scrollBar->setValue(cursor);
1740
connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
1743
/* ------------------------------------------------------------------------- */
1747
/* ------------------------------------------------------------------------- */
1748
void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
1750
if (_possibleTripleClick && (ev->button() == Qt::LeftButton)) {
1751
mouseTripleClickEvent(ev);
1755
if (!contentsRect().contains(ev->pos())) return;
1757
if (!_screenWindow) return;
1761
getCharacterPosition(ev->pos(), charLine, charColumn);
1762
QPoint pos = QPoint(charColumn, charLine);
1764
if (ev->button() == Qt::LeftButton) {
1765
// request the software keyboard, if any
1766
if (qApp->autoSipEnabled()) {
1767
QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(
1768
style()->styleHint(QStyle::SH_RequestSoftwareInputPanel));
1769
if (hasFocus() || behavior == QStyle::RSIP_OnMouseClick) {
1770
QEvent event(QEvent::RequestSoftwareInputPanel);
1771
QApplication::sendEvent(this, &event);
1775
_lineSelectionMode = false;
1776
_wordSelectionMode = false;
1778
bool selected = false;
1779
// The user clicked inside selected text
1780
selected = _screenWindow->isSelected(pos.x(), pos.y());
1782
// Drag only when the Control key is hold
1783
if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected) {
1784
_dragInfo.state = diPending;
1785
_dragInfo.start = ev->pos();
1787
// No reason to ever start a drag event
1788
_dragInfo.state = diNone;
1790
_preserveLineBreaks = !((ev->modifiers() & Qt::ControlModifier) && !(ev->modifiers() & Qt::AltModifier));
1791
_columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
1793
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) {
1794
_screenWindow->clearSelection();
1796
pos.ry() += _scrollBar->value();
1797
_iPntSel = _pntSel = pos;
1798
_actSel = 1; // left mouse button pressed but nothing selected yet.
1801
emit mouseSignal(0, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
1804
} else if (ev->button() == Qt::MidButton) {
1805
processMidButtonClick(ev);
1806
} else if (ev->button() == Qt::RightButton) {
1807
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
1808
emit configureRequest(ev->pos());
1810
emit mouseSignal(2, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
1814
QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
1816
int charLine, charColumn;
1817
getCharacterPosition(position, charLine, charColumn);
1819
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn);
1821
return spot ? spot->actions() : QList<QAction*>();
1824
void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
1828
getCharacterPosition(ev->pos(), charLine, charColumn);
1830
const int scrollBarWidth = (_scrollbarLocation == Enum::ScrollBarLeft) ? _scrollBar->width() : 0;
1833
// change link hot-spot appearance on mouse-over
1834
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine, charColumn);
1835
if (_underlineLinks && 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;
1865
update(_mouseOverHotspotArea | previousHotspotArea);
1866
} else if (!_mouseOverHotspotArea.isEmpty()) {
1867
update(_mouseOverHotspotArea);
1868
// set hotspot area to an invalid rectangle
1869
_mouseOverHotspotArea = QRegion();
1872
// for auto-hiding the cursor, we need mouseTracking
1873
if (ev->buttons() == Qt::NoButton) return;
1875
// if the terminal is interested in mouse movements
1876
// then emit a mouse movement signal, unless the shift
1877
// key is being held down, which overrides this.
1878
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
1880
if (ev->buttons() & Qt::LeftButton)
1882
if (ev->buttons() & Qt::MidButton)
1884
if (ev->buttons() & Qt::RightButton)
1887
emit mouseSignal(button,
1889
charLine + 1 + _scrollBar->value() - _scrollBar->maximum(),
1895
if (_dragInfo.state == diPending) {
1896
// we had a mouse down, but haven't confirmed a drag yet
1897
// if the mouse has moved sufficiently, we will confirm
1899
const int distance = KGlobalSettings::dndEventDelay();
1900
if (ev->x() > _dragInfo.start.x() + distance || ev->x() < _dragInfo.start.x() - distance ||
1901
ev->y() > _dragInfo.start.y() + distance || ev->y() < _dragInfo.start.y() - distance) {
1902
// we've left the drag square, we can start a real drag operation now
1904
_screenWindow->clearSelection();
1908
} else if (_dragInfo.state == diDragging) {
1909
// this isn't technically needed because mouseMoveEvent is suppressed during
1910
// Qt drag operations, replaced by dragMoveEvent
1914
if (_actSel == 0) return;
1916
// don't extend selection while pasting
1917
if (ev->buttons() & Qt::MidButton) return;
1919
extendSelection(ev->pos());
1922
void TerminalDisplay::extendSelection(const QPoint& position)
1927
//if ( !contentsRect().contains(ev->pos()) ) return;
1928
const QPoint tL = contentsRect().topLeft();
1929
const int tLx = tL.x();
1930
const int tLy = tL.y();
1931
const int scroll = _scrollBar->value();
1933
// we're in the process of moving the mouse with the left button pressed
1934
// the mouse cursor will kept caught within the bounds of the text in
1937
int linesBeyondWidget = 0;
1939
QRect textBounds(tLx + _leftMargin,
1941
_usedColumns * _fontWidth - 1,
1942
_usedLines * _fontHeight - 1);
1944
QPoint pos = position;
1946
// Adjust position within text area bounds.
1947
const QPoint oldpos = pos;
1949
pos.setX(qBound(textBounds.left(), pos.x(), textBounds.right()));
1950
pos.setY(qBound(textBounds.top(), pos.y(), textBounds.bottom()));
1952
if (oldpos.y() > textBounds.bottom()) {
1953
linesBeyondWidget = (oldpos.y() - textBounds.bottom()) / _fontHeight;
1954
_scrollBar->setValue(_scrollBar->value() + linesBeyondWidget + 1); // scrollforward
1956
if (oldpos.y() < textBounds.top()) {
1957
linesBeyondWidget = (textBounds.top() - oldpos.y()) / _fontHeight;
1958
_scrollBar->setValue(_scrollBar->value() - linesBeyondWidget - 1); // history
1963
getCharacterPosition(pos, charLine, charColumn);
1965
QPoint here = QPoint(charColumn, charLine);
1967
QPoint _iPntSelCorr = _iPntSel;
1968
_iPntSelCorr.ry() -= _scrollBar->value();
1969
QPoint _pntSelCorr = _pntSel;
1970
_pntSelCorr.ry() -= _scrollBar->value();
1971
bool swapping = false;
1973
if (_wordSelectionMode) {
1974
// Extend to word boundaries
1978
const bool left_not_right = (here.y() < _iPntSelCorr.y() ||
1979
(here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
1980
const bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() ||
1981
(_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
1982
swapping = left_not_right != old_left_not_right;
1984
// Find left (left_not_right ? from here : from start)
1985
QPoint left = left_not_right ? here : _iPntSelCorr;
1986
i = loc(left.x(), left.y());
1987
if (i >= 0 && i <= _imageSize) {
1988
selClass = charClass(_image[i]);
1989
while (((left.x() > 0) || (left.y() > 0 && (_lineProperties[left.y() - 1] & LINE_WRAPPED)))
1990
&& charClass(_image[i - 1]) == selClass) {
1992
if (left.x() > 0) left.rx()--;
1994
left.rx() = _usedColumns - 1;
2000
// Find left (left_not_right ? from start : from here)
2001
QPoint right = left_not_right ? _iPntSelCorr : here;
2002
i = loc(right.x(), right.y());
2003
if (i >= 0 && i <= _imageSize) {
2004
selClass = charClass(_image[i]);
2005
while (((right.x() < _usedColumns - 1) || (right.y() < _usedLines - 1 && (_lineProperties[right.y()] & LINE_WRAPPED)))
2006
&& charClass(_image[i + 1]) == selClass) {
2008
if (right.x() < _usedColumns - 1) right.rx()++;
2016
// Pick which is start (ohere) and which is extension (here)
2017
if (left_not_right) {
2027
if (_lineSelectionMode) {
2028
// Extend to complete line
2029
const bool above_not_below = (here.y() < _iPntSelCorr.y());
2031
QPoint above = above_not_below ? here : _iPntSelCorr;
2032
QPoint below = above_not_below ? _iPntSelCorr : here;
2034
while (above.y() > 0 && (_lineProperties[above.y() - 1] & LINE_WRAPPED))
2036
while (below.y() < _usedLines - 1 && (_lineProperties[below.y()] & LINE_WRAPPED))
2040
below.setX(_usedColumns - 1);
2042
// Pick which is start (ohere) and which is extension (here)
2043
if (above_not_below) {
2051
const QPoint newSelBegin = QPoint(ohere.x(), ohere.y());
2052
swapping = !(_tripleSelBegin == newSelBegin);
2053
_tripleSelBegin = newSelBegin;
2059
if (!_wordSelectionMode && !_lineSelectionMode) {
2063
const bool left_not_right = (here.y() < _iPntSelCorr.y() ||
2064
(here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x()));
2065
const bool old_left_not_right = (_pntSelCorr.y() < _iPntSelCorr.y() ||
2066
(_pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x()));
2067
swapping = left_not_right != old_left_not_right;
2069
// Find left (left_not_right ? from here : from start)
2070
const QPoint left = left_not_right ? here : _iPntSelCorr;
2072
// Find left (left_not_right ? from start : from here)
2073
QPoint right = left_not_right ? _iPntSelCorr : here;
2074
if (right.x() > 0 && !_columnSelectionMode) {
2075
i = loc(right.x(), right.y());
2076
if (i >= 0 && i <= _imageSize) {
2077
selClass = charClass(_image[i - 1]);
2078
/* if (selClass == ' ')
2080
while ( right.x() < _usedColumns-1 && charClass(_image[i+1].character) == selClass && (right.y()<_usedLines-1) &&
2081
!(_lineProperties[right.y()] & LINE_WRAPPED))
2082
{ i++; right.rx()++; }
2083
if (right.x() < _usedColumns-1)
2084
right = left_not_right ? _iPntSelCorr : here;
2086
right.rx()++; // will be balanced later because of offset=-1;
2091
// Pick which is start (ohere) and which is extension (here)
2092
if (left_not_right) {
2103
if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved
2105
if (here == ohere) return; // It's not left, it's not right.
2107
if (_actSel < 2 || swapping) {
2108
if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2109
_screenWindow->setSelectionStart(ohere.x() , ohere.y() , true);
2111
_screenWindow->setSelectionStart(ohere.x() - 1 - offset , ohere.y() , false);
2115
_actSel = 2; // within selection
2117
_pntSel.ry() += _scrollBar->value();
2119
if (_columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode) {
2120
_screenWindow->setSelectionEnd(here.x() , here.y());
2122
_screenWindow->setSelectionEnd(here.x() + offset , here.y());
2126
void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
2133
getCharacterPosition(ev->pos(), charLine, charColumn);
2135
if (ev->button() == Qt::LeftButton) {
2136
if (_dragInfo.state == diPending) {
2137
// We had a drag event pending but never confirmed. Kill selection
2138
_screenWindow->clearSelection();
2141
copyToX11Selection();
2146
//FIXME: emits a release event even if the mouse is
2147
// outside the range. The procedure used in `mouseMoveEvent'
2148
// applies here, too.
2150
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
2151
emit mouseSignal(3, // release
2153
charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
2155
_dragInfo.state = diNone;
2159
(ev->button() == Qt::RightButton || ev->button() == Qt::MidButton) &&
2160
!(ev->modifiers() & Qt::ShiftModifier)) {
2163
charLine + 1 + _scrollBar->value() - _scrollBar->maximum() ,
2168
void TerminalDisplay::getCharacterPosition(const QPoint& widgetPoint, int& line, int& column) const
2170
column = (widgetPoint.x() + _fontWidth / 2 - contentsRect().left() - _leftMargin) / _fontWidth;
2171
line = (widgetPoint.y() - contentsRect().top() - _topMargin) / _fontHeight;
2178
if (line >= _usedLines)
2179
line = _usedLines - 1;
2181
// the column value returned can be equal to _usedColumns, which
2182
// is the position just after the last character displayed in a line.
2184
// this is required so that the user can select characters in the right-most
2185
// column (or left-most for right-to-left input)
2186
if (column > _usedColumns)
2187
column = _usedColumns;
2190
void TerminalDisplay::updateLineProperties()
2195
_lineProperties = _screenWindow->getLineProperties();
2198
void TerminalDisplay::processMidButtonClick(QMouseEvent* ev)
2200
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier)) {
2201
const bool appendEnter = ev->modifiers() & Qt::ControlModifier;
2203
if ( _middleClickPasteMode == Enum::PasteFromX11Selection ) {
2204
pasteFromX11Selection(appendEnter);
2205
} else if ( _middleClickPasteMode == Enum::PasteFromClipboard ) {
2206
pasteFromClipboard(appendEnter);
2213
getCharacterPosition(ev->pos(), charLine, charColumn);
2215
emit mouseSignal(1, charColumn + 1, charLine + 1 + _scrollBar->value() - _scrollBar->maximum() , 0);
2219
void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
2221
// Yes, successive middle click can trigger this event
2222
if (ev->button() == Qt::MidButton) {
2223
processMidButtonClick(ev);
2227
if (ev->button() != Qt::LeftButton) return;
2228
if (!_screenWindow) return;
2233
getCharacterPosition(ev->pos(), charLine, charColumn);
2235
QPoint pos(charColumn, charLine);
2237
// pass on double click as two clicks.
2238
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier)) {
2239
// Send just _ONE_ click event, since the first click of the double click
2240
// was already sent by the click handler
2243
pos.y() + 1 + _scrollBar->value() - _scrollBar->maximum(),
2248
_screenWindow->clearSelection();
2249
QPoint bgnSel = pos;
2250
QPoint endSel = pos;
2251
int i = loc(bgnSel.x(), bgnSel.y());
2253
_iPntSel.ry() += _scrollBar->value();
2255
_wordSelectionMode = true;
2257
// find word boundaries...
2258
const QChar selClass = charClass(_image[i]);
2260
// find the start of the word
2262
while (((x > 0) || (bgnSel.y() > 0 && (_lineProperties[bgnSel.y() - 1] & LINE_WRAPPED)))
2263
&& charClass(_image[i - 1]) == selClass) {
2268
x = _usedColumns - 1;
2274
_screenWindow->setSelectionStart(bgnSel.x() , bgnSel.y() , false);
2276
// find the end of the word
2277
i = loc(endSel.x(), endSel.y());
2279
while (((x < _usedColumns - 1) || (endSel.y() < _usedLines - 1 && (_lineProperties[endSel.y()] & LINE_WRAPPED)))
2280
&& charClass(_image[i + 1]) == selClass) {
2282
if (x < _usedColumns - 1)
2292
// In word selection mode don't select @ (64) if at end of word.
2293
if (((_image[i].rendition & RE_EXTENDED_CHAR) == 0) &&
2294
(QChar(_image[i].character) == '@') &&
2295
((endSel.x() - bgnSel.x()) > 0)) {
2299
_actSel = 2; // within selection
2301
_screenWindow->setSelectionEnd(endSel.x() , endSel.y());
2303
copyToX11Selection();
2306
_possibleTripleClick = true;
2308
QTimer::singleShot(QApplication::doubleClickInterval(), this,
2309
SLOT(tripleClickTimeout()));
2312
void TerminalDisplay::wheelEvent(QWheelEvent* ev)
2314
// Only vertical scrolling is supported
2315
if (ev->orientation() != Qt::Vertical)
2318
const int modifiers = ev->modifiers();
2319
const int delta = ev->delta();
2321
// ctrl+<wheel> for zomming, like in konqueror and firefox
2322
if (modifiers & Qt::ControlModifier) {
2324
// wheel-up for increasing font size
2327
// wheel-down for decreasing font size
2334
// if the terminal program is not interested with mouse events:
2335
// * send the event to the scrollbar if the slider has room to move
2336
// * otherwise, send simulated up / down key presses to the terminal program
2337
// for the benefit of programs such as 'less'
2339
const bool canScroll = _scrollBar->maximum() > 0;
2341
_scrollBar->event(ev);
2343
// assume that each Up / Down key event will cause the terminal application
2344
// to scroll by one line.
2346
// to get a reasonable scrolling speed, scroll by one line for every 5 degrees
2347
// of mouse wheel rotation. Mouse wheels typically move in steps of 15 degrees,
2348
// giving a scroll of 3 lines
2349
const int keyCode = delta > 0 ? Qt::Key_Up : Qt::Key_Down;
2350
QKeyEvent keyEvent(QEvent::KeyPress, keyCode, Qt::NoModifier);
2352
// QWheelEvent::delta() gives rotation in eighths of a degree
2353
const int degrees = delta / 8;
2354
const int lines = abs(degrees) / 5;
2356
for (int i = 0; i < lines; i++)
2357
emit keyPressedSignal(&keyEvent);
2360
// terminal program wants notification of mouse activity
2364
getCharacterPosition(ev->pos() , charLine , charColumn);
2366
emit mouseSignal(delta > 0 ? 4 : 5,
2368
charLine + 1 + _scrollBar->value() - _scrollBar->maximum() ,
2373
void TerminalDisplay::tripleClickTimeout()
2375
_possibleTripleClick = false;
2378
void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
2380
if (!_screenWindow) return;
2384
getCharacterPosition(ev->pos(), charLine, charColumn);
2385
_iPntSel = QPoint(charColumn, charLine);
2387
_screenWindow->clearSelection();
2389
_lineSelectionMode = true;
2390
_wordSelectionMode = false;
2392
_actSel = 2; // within selection
2394
while (_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))
2397
if (_tripleClickMode == Enum::SelectForwardsFromCursor) {
2398
// find word boundary start
2399
int i = loc(_iPntSel.x(), _iPntSel.y());
2400
const QChar selClass = charClass(_image[i]);
2401
int x = _iPntSel.x();
2404
(_iPntSel.y() > 0 && (_lineProperties[_iPntSel.y() - 1] & LINE_WRAPPED))
2406
&& charClass(_image[i - 1]) == selClass) {
2416
_screenWindow->setSelectionStart(x , _iPntSel.y() , false);
2417
_tripleSelBegin = QPoint(x, _iPntSel.y());
2418
} else if (_tripleClickMode == Enum::SelectWholeLine) {
2419
_screenWindow->setSelectionStart(0 , _iPntSel.y() , false);
2420
_tripleSelBegin = QPoint(0, _iPntSel.y());
2423
while (_iPntSel.y() < _lines - 1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED))
2426
_screenWindow->setSelectionEnd(_columns - 1 , _iPntSel.y());
2428
copyToX11Selection();
2430
_iPntSel.ry() += _scrollBar->value();
2433
bool TerminalDisplay::focusNextPrevChild(bool next)
2435
// for 'Tab', always disable focus switching among widgets
2436
// for 'Shift+Tab', leave the decision to higher level
2440
return QWidget::focusNextPrevChild(next);
2443
QChar TerminalDisplay::charClass(const Character& ch) const
2445
if (ch.rendition & RE_EXTENDED_CHAR) {
2446
ushort extendedCharLength = 0;
2447
const ushort* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
2448
if (chars && extendedCharLength > 0) {
2449
const QString s = QString::fromUtf16(chars, extendedCharLength);
2450
if (_wordCharacters.contains(s, Qt::CaseInsensitive))
2452
bool allLetterOrNumber = true;
2453
for (int i = 0; allLetterOrNumber && i < s.size(); ++i)
2454
allLetterOrNumber = s.at(i).isLetterOrNumber();
2455
return allLetterOrNumber ? 'a' : s.at(0);
2459
const QChar qch(ch.character);
2460
if (qch.isSpace()) return ' ';
2462
if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive))
2469
void TerminalDisplay::setWordCharacters(const QString& wc)
2471
_wordCharacters = wc;
2474
// FIXME: the actual value of _mouseMarks is the opposite of its semantic.
2475
// When using programs not interested with mouse(shell, less), it is true.
2476
// When using programs interested with mouse(vim,mc), it is false.
2477
void TerminalDisplay::setUsesMouse(bool on)
2480
setCursor(_mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor);
2482
bool TerminalDisplay::usesMouse() const
2487
/* ------------------------------------------------------------------------- */
2491
/* ------------------------------------------------------------------------- */
2493
void TerminalDisplay::doPaste(QString text, bool appendReturn)
2501
if (!text.isEmpty()) {
2502
text.replace('\n', '\r');
2503
// perform paste by simulating keypress events
2504
QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
2505
emit keyPressedSignal(&e);
2509
void TerminalDisplay::setAutoCopySelectedText(bool enabled)
2511
_autoCopySelectedText = enabled;
2514
void TerminalDisplay::setMiddleClickPasteMode(Enum::MiddleClickPasteModeEnum mode)
2516
_middleClickPasteMode = mode;
2519
void TerminalDisplay::copyToX11Selection()
2524
QString text = _screenWindow->selectedText(_preserveLineBreaks);
2528
QApplication::clipboard()->setText(text, QClipboard::Selection);
2530
if (_autoCopySelectedText)
2531
QApplication::clipboard()->setText(text, QClipboard::Clipboard);
2534
void TerminalDisplay::copyToClipboard()
2539
QString text = _screenWindow->selectedText(_preserveLineBreaks);
2543
QApplication::clipboard()->setText(text, QClipboard::Clipboard);
2546
void TerminalDisplay::pasteFromClipboard(bool appendEnter)
2548
QString text = QApplication::clipboard()->text(QClipboard::Clipboard);
2549
doPaste(text, appendEnter);
2552
void TerminalDisplay::pasteFromX11Selection(bool appendEnter)
2554
QString text = QApplication::clipboard()->text(QClipboard::Selection);
2555
doPaste(text, appendEnter);
2558
/* ------------------------------------------------------------------------- */
2562
/* ------------------------------------------------------------------------- */
2564
void TerminalDisplay::inputMethodEvent(QInputMethodEvent* event)
2566
if (!event->commitString().isEmpty()) {
2567
QKeyEvent keyEvent(QEvent::KeyPress, 0, Qt::NoModifier, event->commitString());
2568
emit keyPressedSignal(&keyEvent);
2571
_inputMethodData.preeditString = event->preeditString();
2572
update(preeditRect() | _inputMethodData.previousPreeditRect);
2577
QVariant TerminalDisplay::inputMethodQuery(Qt::InputMethodQuery query) const
2579
const QPoint cursorPos = cursorPosition();
2581
case Qt::ImMicroFocus:
2582
return imageToWidget(QRect(cursorPos.x(), cursorPos.y(), 1, 1));
2587
case Qt::ImCursorPosition:
2588
// return the cursor position within the current line
2589
return cursorPos.x();
2591
case Qt::ImSurroundingText: {
2592
// return the text from the current line
2594
QTextStream stream(&lineText);
2595
PlainTextDecoder decoder;
2596
decoder.begin(&stream);
2597
decoder.decodeLine(&_image[loc(0, cursorPos.y())], _usedColumns, _lineProperties[cursorPos.y()]);
2602
case Qt::ImCurrentSelection:
2612
QRect TerminalDisplay::preeditRect() const
2614
const int preeditLength = string_width(_inputMethodData.preeditString);
2616
if (preeditLength == 0)
2619
return QRect(_leftMargin + _fontWidth * cursorPosition().x(),
2620
_topMargin + _fontHeight * cursorPosition().y(),
2621
_fontWidth * preeditLength,
2625
void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
2627
if (_inputMethodData.preeditString.isEmpty())
2630
const QPoint cursorPos = cursorPosition();
2632
bool invertColors = false;
2633
const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
2634
const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
2635
const Character* style = &_image[loc(cursorPos.x(), cursorPos.y())];
2637
drawBackground(painter, rect, background, true);
2638
drawCursor(painter, rect, foreground, background, invertColors);
2639
drawCharacters(painter, rect, _inputMethodData.preeditString, style, invertColors);
2641
_inputMethodData.previousPreeditRect = rect;
2644
/* ------------------------------------------------------------------------- */
2648
/* ------------------------------------------------------------------------- */
2650
void TerminalDisplay::setFlowControlWarningEnabled(bool enable)
2652
_flowControlWarningEnabled = enable;
2654
// if the dialog is currently visible and the flow control warning has
2655
// been disabled then hide the dialog
2657
outputSuspended(false);
2660
void TerminalDisplay::outputSuspended(bool suspended)
2662
//create the label when this function is first called
2663
if (!_outputSuspendedLabel) {
2664
//This label includes a link to an English language website
2665
//describing the 'flow control' (Xon/Xoff) feature found in almost
2666
//all terminal emulators.
2667
//If there isn't a suitable article available in the target language the link
2668
//can simply be removed.
2669
_outputSuspendedLabel = new QLabel(i18n("<qt>Output has been "
2670
"<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>"
2671
" by pressing Ctrl+S."
2672
" Press <b>Ctrl+Q</b> to resume.</qt>"),
2675
QPalette palette(_outputSuspendedLabel->palette());
2676
KColorScheme::adjustBackground(palette, KColorScheme::NeutralBackground);
2677
_outputSuspendedLabel->setPalette(palette);
2678
_outputSuspendedLabel->setAutoFillBackground(true);
2679
_outputSuspendedLabel->setBackgroundRole(QPalette::Base);
2680
_outputSuspendedLabel->setFont(KGlobalSettings::generalFont());
2681
_outputSuspendedLabel->setContentsMargins(5, 5, 5, 5);
2683
//enable activation of "Xon/Xoff" link in label
2684
_outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse |
2685
Qt::LinksAccessibleByKeyboard);
2686
_outputSuspendedLabel->setOpenExternalLinks(true);
2687
_outputSuspendedLabel->setVisible(false);
2689
_gridLayout->addWidget(_outputSuspendedLabel);
2690
_gridLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding,
2691
QSizePolicy::Expanding),
2695
_outputSuspendedLabel->setVisible(suspended);
2698
void TerminalDisplay::scrollScreenWindow(enum ScreenWindow::RelativeScrollMode mode, int amount)
2700
_screenWindow->scrollBy(mode, amount);
2701
_screenWindow->setTrackOutput(_screenWindow->atEndOfOutput());
2702
updateLineProperties();
2706
void TerminalDisplay::keyPressEvent(QKeyEvent* event)
2708
_screenWindow->screen()->setCurrentTerminalDisplay(this);
2710
_actSel = 0; // Key stroke implies a screen update, so TerminalDisplay won't
2711
// know where the current selection is.
2713
if (_allowBlinkingCursor) {
2714
_blinkCursorTimer->start();
2715
if (_cursorBlinking) {
2716
// if cursor is blinking(hidden), blink it again to show it
2719
Q_ASSERT( _cursorBlinking == false );
2722
emit keyPressedSignal(event);
2724
#if QT_VERSION >= 0x040800 // added in Qt 4.8.0
2725
#ifndef QT_NO_ACCESSIBILITY
2726
QAccessible::updateAccessibility(this, 0, QAccessible::TextCaretMoved);
2727
QAccessible::updateAccessibility(this, 0, QAccessible::TextInserted);
2734
bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent)
2736
const int modifiers = keyEvent->modifiers();
2738
// When a possible shortcut combination is pressed,
2739
// emit the overrideShortcutCheck() signal to allow the host
2740
// to decide whether the terminal should override it or not.
2741
if (modifiers != Qt::NoModifier) {
2742
int modifierCount = 0;
2743
unsigned int currentModifier = Qt::ShiftModifier;
2745
while (currentModifier <= Qt::KeypadModifier) {
2746
if (modifiers & currentModifier)
2748
currentModifier <<= 1;
2750
if (modifierCount < 2) {
2751
bool override = false;
2752
emit overrideShortcutCheck(keyEvent, override);
2760
// Override any of the following shortcuts because
2761
// they are needed by the terminal
2762
int keyCode = keyEvent->key() | modifiers;
2764
// list is taken from the QLineEdit::event() code
2766
case Qt::Key_Delete:
2769
case Qt::Key_Backspace:
2773
case Qt::Key_Period:
2781
bool TerminalDisplay::event(QEvent* event)
2783
bool eventHandled = false;
2784
switch (event->type()) {
2785
case QEvent::ShortcutOverride:
2786
eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event);
2788
case QEvent::PaletteChange:
2789
case QEvent::ApplicationPaletteChange:
2790
_scrollBar->setPalette(QApplication::palette());
2795
return eventHandled ? true : QWidget::event(event);
2798
void TerminalDisplay::contextMenuEvent(QContextMenuEvent* event)
2800
// the logic for the mouse case is within MousePressEvent()
2801
if (event->reason() != QContextMenuEvent::Mouse) {
2802
emit configureRequest(mapFromGlobal(QCursor::pos()));
2806
/* --------------------------------------------------------------------- */
2810
/* --------------------------------------------------------------------- */
2812
void TerminalDisplay::setBellMode(int mode)
2817
int TerminalDisplay::bellMode() const
2822
void TerminalDisplay::unmaskBell()
2824
_bellMasked = false;
2827
void TerminalDisplay::bell(const QString& message)
2832
switch (_bellMode) {
2833
case Enum::SystemBeepBell:
2834
KNotification::beep();
2836
case Enum::NotifyBell:
2837
KNotification::event(hasFocus() ? "BellVisible" : "BellInvisible",
2838
message, QPixmap(), this);
2840
case Enum::VisualBell:
2847
// limit the rate at which bells can occur.
2848
// ...mainly for sound effects where rapid bells in sequence
2849
// produce a horrible noise.
2851
QTimer::singleShot(500, this, SLOT(unmaskBell()));
2854
void TerminalDisplay::visualBell()
2857
QTimer::singleShot(200, this, SLOT(swapFGBGColors()));
2860
void TerminalDisplay::swapFGBGColors()
2862
// swap the default foreground & backround color
2863
ColorEntry color = _colorTable[DEFAULT_BACK_COLOR];
2864
_colorTable[DEFAULT_BACK_COLOR] = _colorTable[DEFAULT_FORE_COLOR];
2865
_colorTable[DEFAULT_FORE_COLOR] = color;
2870
/* --------------------------------------------------------------------- */
2874
/* --------------------------------------------------------------------- */
2876
void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
2878
// text/plain alone is enough for KDE-apps
2879
// text/uri-list is for supporting some non-KDE apps, such as thunar
2881
// That also applies in dropEvent()
2882
if (event->mimeData()->hasFormat("text/plain") ||
2883
event->mimeData()->hasFormat("text/uri-list")) {
2884
event->acceptProposedAction();
2888
void TerminalDisplay::dropEvent(QDropEvent* event)
2890
KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
2893
if (!urls.isEmpty()) {
2894
for (int i = 0 ; i < urls.count() ; i++) {
2895
KUrl url = KIO::NetAccess::mostLocalUrl(urls[i] , 0);
2898
if (url.isLocalFile())
2899
urlText = url.path();
2901
urlText = url.url();
2903
// in future it may be useful to be able to insert file names with drag-and-drop
2904
// without quoting them (this only affects paths with spaces in)
2905
urlText = KShell::quoteArg(urlText);
2907
dropText += urlText;
2909
// Each filename(including the last) should be followed by one space.
2913
// If our target is local we will open a popup - otherwise the fallback kicks
2914
// in and the URLs will simply be pasted as text.
2915
if (_sessionController && _sessionController->url().isLocalFile()) {
2916
// A standard popup with Copy, Move and Link as options -
2917
// plus an additional Paste option.
2919
QAction* pasteAction = new QAction(i18n("&Paste Location"), this);
2920
pasteAction->setData(dropText);
2921
connect(pasteAction, SIGNAL(triggered()), this, SLOT(dropMenuPasteActionTriggered()));
2923
QList<QAction*> additionalActions;
2924
additionalActions.append(pasteAction);
2926
if (urls.count() == 1) {
2927
const KUrl url = KIO::NetAccess::mostLocalUrl(urls[0] , 0);
2929
if (url.isLocalFile()) {
2930
const QFileInfo fileInfo(url.path());
2932
if (fileInfo.isDir()) {
2933
QAction* cdAction = new QAction(i18n("Change &Directory To"), this);
2934
dropText = QLatin1String(" cd ") + dropText + QChar('\n');
2935
cdAction->setData(dropText);
2936
connect(cdAction, SIGNAL(triggered()), this, SLOT(dropMenuCdActionTriggered()));
2937
additionalActions.append(cdAction);
2942
KUrl target(_sessionController->currentDir());
2944
KonqOperations::doDrop(KFileItem(), target, event, this, additionalActions);
2950
dropText = event->mimeData()->text();
2953
if (event->mimeData()->hasFormat("text/plain") ||
2954
event->mimeData()->hasFormat("text/uri-list")) {
2955
emit sendStringToEmu(dropText.toLocal8Bit());
2959
void TerminalDisplay::dropMenuPasteActionTriggered()
2962
const QAction* action = qobject_cast<const QAction*>(sender());
2964
emit sendStringToEmu(action->data().toString().toLocal8Bit());
2969
void TerminalDisplay::dropMenuCdActionTriggered()
2972
const QAction* action = qobject_cast<const QAction*>(sender());
2974
emit sendStringToEmu(action->data().toString().toLocal8Bit());
2979
void TerminalDisplay::doDrag()
2981
_dragInfo.state = diDragging;
2982
_dragInfo.dragObject = new QDrag(this);
2983
QMimeData* mimeData = new QMimeData;
2984
mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
2985
_dragInfo.dragObject->setMimeData(mimeData);
2986
_dragInfo.dragObject->exec(Qt::CopyAction);
2989
void TerminalDisplay::setSessionController(SessionController* controller)
2991
_sessionController = controller;
2994
AutoScrollHandler::AutoScrollHandler(QWidget* parent)
2998
parent->installEventFilter(this);
3000
void AutoScrollHandler::timerEvent(QTimerEvent* event)
3002
if (event->timerId() != _timerId)
3005
QMouseEvent mouseEvent(QEvent::MouseMove,
3006
widget()->mapFromGlobal(QCursor::pos()),
3011
QApplication::sendEvent(widget(), &mouseEvent);
3013
bool AutoScrollHandler::eventFilter(QObject* watched, QEvent* event)
3015
Q_ASSERT(watched == parent());
3018
QMouseEvent* mouseEvent = (QMouseEvent*)event;
3019
switch (event->type()) {
3020
case QEvent::MouseMove: {
3021
bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
3022
if (mouseInWidget) {
3024
killTimer(_timerId);
3028
if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton))
3029
_timerId = startTimer(100);
3034
case QEvent::MouseButtonRelease: {
3035
if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton)) {
3036
killTimer(_timerId);
3048
#include "TerminalDisplay.moc"