mirror of https://github.com/cutefishos/terminal
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4478 lines
142 KiB
C++
4478 lines
142 KiB
C++
/*
|
|
This file is part of Konsole, a terminal emulator for KDE.
|
|
|
|
Copyright 2006-2008 by Robert Knight <robertknight@gmail.com>
|
|
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301 USA.
|
|
*/
|
|
|
|
// Own
|
|
#include "TerminalDisplay.h"
|
|
|
|
// C++
|
|
#include <cmath>
|
|
|
|
// Qt
|
|
#include <QAbstractButton>
|
|
#include <QApplication>
|
|
#include <QGuiApplication>
|
|
#include <QQuickWindow>
|
|
#include <QBoxLayout>
|
|
#include <QClipboard>
|
|
#include <QKeyEvent>
|
|
#include <QEvent>
|
|
#include <QTime>
|
|
#include <QFile>
|
|
#include <QGridLayout>
|
|
#include <QLabel>
|
|
#include <QLayout>
|
|
#include <QMimeData>
|
|
#include <QPainter>
|
|
#include <QPixmap>
|
|
#include <QRegularExpression>
|
|
#include <QStyle>
|
|
#include <QTimer>
|
|
#include <QtDebug>
|
|
#include <QGuiApplication>
|
|
#include <QQuickWindow>
|
|
#include <QUrl>
|
|
#include <QDrag>
|
|
|
|
// KDE
|
|
//#include <kshell.h>
|
|
//#include <KColorScheme>
|
|
//#include <KCursor>
|
|
//#include <kdebug.h>
|
|
//#include <KLocale>
|
|
//#include <KMenu>
|
|
//#include <KNotification>
|
|
//#include <KGlobalSettings>
|
|
//#include <KShortcut>
|
|
//#include <KIO/NetAccess>
|
|
|
|
// Konsole
|
|
//#include <config-apps.h>
|
|
#include "Filter.h"
|
|
#include "konsole_wcwidth.h"
|
|
#include "ScreenWindow.h"
|
|
#include "TerminalCharacterDecoder.h"
|
|
#include "HistorySearch.h"
|
|
#include "Screen.h"
|
|
#include "LineChars.h"
|
|
#include "TerminalGpuNode.h"
|
|
#include "TerminalScroller.h"
|
|
|
|
using namespace Konsole;
|
|
using namespace Qt::Literals::StringLiterals;
|
|
|
|
#ifndef loc
|
|
#define loc(X,Y) ((Y)*_columns+(X))
|
|
#endif
|
|
|
|
#define yMouseScroll 1
|
|
|
|
#define REPCHAR "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
|
|
"abcdefgjijklmnopqrstuvwxyz" \
|
|
"0123456789./+@"
|
|
|
|
const ColorEntry Konsole::base_color_table[TABLE_COLORS] =
|
|
// The following are almost IBM standard color codes, with some slight
|
|
// gamma correction for the dim colors to compensate for bright X screens.
|
|
// It contains the 8 ansiterm/xterm colors in 2 intensities.
|
|
{
|
|
// Fixme: could add faint colors here, also.
|
|
// normal
|
|
ColorEntry(QColor(0x00,0x00,0x00), false), ColorEntry( QColor(0xB2,0xB2,0xB2), true), // Dfore, Dback
|
|
ColorEntry(QColor(0x00,0x00,0x00), false), ColorEntry( QColor(0xB2,0x18,0x18), false), // Black, Red
|
|
ColorEntry(QColor(0x18,0xB2,0x18), false), ColorEntry( QColor(0xB2,0x68,0x18), false), // Green, Yellow
|
|
ColorEntry(QColor(0x18,0x18,0xB2), false), ColorEntry( QColor(0xB2,0x18,0xB2), false), // Blue, Magenta
|
|
ColorEntry(QColor(0x18,0xB2,0xB2), false), ColorEntry( QColor(0xB2,0xB2,0xB2), false), // Cyan, White
|
|
// intensiv
|
|
ColorEntry(QColor(0x00,0x00,0x00), false), ColorEntry( QColor(0xFF,0xFF,0xFF), true),
|
|
ColorEntry(QColor(0x68,0x68,0x68), false), ColorEntry( QColor(0xFF,0x54,0x54), false),
|
|
ColorEntry(QColor(0x54,0xFF,0x54), false), ColorEntry( QColor(0xFF,0xFF,0x54), false),
|
|
ColorEntry(QColor(0x54,0x54,0xFF), false), ColorEntry( QColor(0xFF,0x54,0xFF), false),
|
|
ColorEntry(QColor(0x54,0xFF,0xFF), false), ColorEntry( QColor(0xFF,0xFF,0xFF), false)
|
|
};
|
|
|
|
// scroll increment used when dragging selection at top/bottom of window.
|
|
|
|
// static
|
|
bool TerminalDisplay::_antialiasText = true;
|
|
bool TerminalDisplay::HAVE_TRANSPARENCY = false;
|
|
|
|
// we use this to force QPainter to display text in LTR mode
|
|
// more information can be found in: http://unicode.org/reports/tr9/
|
|
const QChar LTR_OVERRIDE_CHAR( 0x202D );
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Colors */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/* Note that we use ANSI color order (bgr), while IBMPC color order is (rgb)
|
|
|
|
Code 0 1 2 3 4 5 6 7
|
|
----------- ------- ------- ------- ------- ------- ------- ------- -------
|
|
ANSI (bgr) Black Red Green Yellow Blue Magenta Cyan White
|
|
IBMPC (rgb) Black Blue Green Cyan Red Magenta Yellow White
|
|
*/
|
|
|
|
|
|
// using global statics for the unclutter feature makes tracking the override cursor simple
|
|
// there's only one cursor to override and only one terminal relevant for that at any time
|
|
// gs_deadSpot serves as flag and also allows a position check - it doesn't matter that this isn't
|
|
// correct when checking the position across instances as its only purpose is to catch judder when
|
|
// the user doesn't really touch the mouse - once the mouse moves we'll quickly be out of the deadzone
|
|
static QPoint gs_deadSpot(-1,-1);
|
|
static QPoint gs_futureDeadSpot;
|
|
std::shared_ptr<QTimer> TerminalDisplay::_hideMouseTimer;
|
|
|
|
ScreenWindow* TerminalDisplay::screenWindow() const
|
|
{
|
|
return _screenWindow;
|
|
}
|
|
void TerminalDisplay::setScreenWindow(ScreenWindow* window)
|
|
{
|
|
// disconnect existing screen window if any
|
|
if ( _screenWindow )
|
|
{
|
|
disconnect( _screenWindow , nullptr , this , nullptr );
|
|
}
|
|
|
|
_screenWindow = window;
|
|
|
|
if ( window )
|
|
{
|
|
connect(_screenWindow, &ScreenWindow::outputChanged, this, &TerminalDisplay::updateLineProperties);
|
|
connect(_screenWindow, &ScreenWindow::outputChanged, this, &TerminalDisplay::updateImage);
|
|
connect(_screenWindow, &ScreenWindow::scrollToEnd, this, &TerminalDisplay::scrollToEnd);
|
|
connect(_screenWindow, &ScreenWindow::cursorRevealRequested, this, &TerminalDisplay::revealCursor);
|
|
// Drives copyAvailable, and with it hasSelection.
|
|
connect(_screenWindow, &ScreenWindow::selectionChanged, this, &TerminalDisplay::selectionChanged);
|
|
window->setWindowLines(_lines);
|
|
}
|
|
}
|
|
|
|
const ColorEntry* TerminalDisplay::colorTable() const
|
|
{
|
|
return _colorTable;
|
|
}
|
|
void TerminalDisplay::setBackgroundColor(const QColor& color)
|
|
{
|
|
_colorTable[DEFAULT_BACK_COLOR].color = color;
|
|
QPalette p = palette();
|
|
p.setColor( backgroundRole(), color );
|
|
setPalette( p );
|
|
|
|
// Avoid propagating the palette change to the scroll bar
|
|
_scrollBar->setPalette( QApplication::palette() );
|
|
|
|
update();
|
|
}
|
|
void TerminalDisplay::setForegroundColor(const QColor& color)
|
|
{
|
|
_colorTable[DEFAULT_FORE_COLOR].color = color;
|
|
|
|
update();
|
|
}
|
|
void TerminalDisplay::setColorTable(const ColorEntry table[])
|
|
{
|
|
for (int i = 0; i < TABLE_COLORS; i++)
|
|
_colorTable[i] = table[i];
|
|
|
|
setBackgroundColor(_colorTable[DEFAULT_BACK_COLOR].color);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Font */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
The VT100 has 32 special graphical characters. The usual vt100 extended
|
|
xterm fonts have these at 0x00..0x1f.
|
|
|
|
QT's iso mapping leaves 0x00..0x7f without any changes. But the graphicals
|
|
come in here as proper unicode characters.
|
|
|
|
We treat non-iso10646 fonts as VT100 extended and do the required mapping
|
|
from unicode to 0x00..0x1f. The remaining translation is then left to the
|
|
QCodec.
|
|
*/
|
|
|
|
bool TerminalDisplay::isLineChar(Character c) const {
|
|
return _drawLineChars && c.isLineChar();
|
|
}
|
|
|
|
bool TerminalDisplay::isLineCharString(const std::wstring& string) const {
|
|
return string.length() > 0 && _drawLineChars && (string[0] & 0xFF80) == 0x2500;
|
|
}
|
|
|
|
|
|
// assert for i in [0..31] : vt100extended(vt100_graphics[i]) == i.
|
|
|
|
unsigned short Konsole::vt100_graphics[32] =
|
|
{ // 0/8 1/9 2/10 3/11 4/12 5/13 6/14 7/15
|
|
0x0020, 0x25C6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0,
|
|
0x00b1, 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c,
|
|
0xF800, 0xF801, 0x2500, 0xF803, 0xF804, 0x251c, 0x2524, 0x2534,
|
|
0x252c, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00b7
|
|
};
|
|
|
|
void TerminalDisplay::fontChange(const QFont&)
|
|
{
|
|
QFontMetrics fm(font());
|
|
_fontHeight = fm.height() + _lineSpacing;
|
|
|
|
// waba TerminalDisplay 1.123:
|
|
// "Base character width on widest ASCII character. This prevents too wide
|
|
// characters in the presence of double wide (e.g. Japanese) characters."
|
|
// Get the width from representative normal width characters
|
|
_fontWidth = qRound(static_cast<double>(fm.horizontalAdvance(QLatin1String(REPCHAR)))/static_cast<double>(qstrlen(REPCHAR)));
|
|
|
|
|
|
// On macOS QFont::PreferFullHinting does nothing can leave fractional advances that accumulate; add a small headroom.
|
|
#if defined(Q_OS_MACOS)
|
|
QFontMetricsF fmf(font());
|
|
double maxAdvance = 0.0;
|
|
for (unsigned int i = 0; i < qstrlen(REPCHAR); i++) {
|
|
maxAdvance = qMax(maxAdvance, fmf.horizontalAdvance(QLatin1Char(REPCHAR[i])));
|
|
}
|
|
|
|
_fontWidth = std::min(_fontWidth, static_cast<int>(std::ceil(maxAdvance)));
|
|
|
|
const double headway = std::abs(static_cast<double>(_fontWidth) - maxAdvance);
|
|
|
|
if (headway > 0.0001 && headway <= 0.5) {
|
|
_fontWidth += 1;
|
|
}
|
|
#endif
|
|
|
|
_fixedFont = true;
|
|
|
|
int fw = fm.horizontalAdvance(QLatin1Char(REPCHAR[0]));
|
|
for(unsigned int i=1; i< qstrlen(REPCHAR); i++)
|
|
{
|
|
if (fw != fm.horizontalAdvance(QLatin1Char(REPCHAR[i])))
|
|
{
|
|
_fixedFont = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
_fixedFont_original = _fixedFont;
|
|
|
|
if (_fontWidth < 1)
|
|
_fontWidth=1;
|
|
|
|
_fontAscent = fm.ascent();
|
|
|
|
emit changedFontMetricSignal( _fontHeight, _fontWidth );
|
|
propagateSize();
|
|
|
|
// We will run paint event testing procedure.
|
|
// Although this operation will destroy the original content,
|
|
// the content will be drawn again after the test.
|
|
_drawTextTestFlag = true;
|
|
update();
|
|
}
|
|
|
|
void TerminalDisplay::calDrawTextAdditionHeight(QPainter& painter)
|
|
{
|
|
QRect test_rect, feedback_rect;
|
|
test_rect.setRect(1, 1, _fontWidth * 4, _fontHeight);
|
|
painter.save();
|
|
painter.setOpacity(0);
|
|
painter.drawText(test_rect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + QLatin1String("Mq"), &feedback_rect);
|
|
painter.restore();
|
|
|
|
_drawTextAdditionHeight = qMax(0, (feedback_rect.height() - _fontHeight) / 2);
|
|
|
|
_drawTextTestFlag = false;
|
|
}
|
|
|
|
void TerminalDisplay::setVTFont(const QFont& f)
|
|
{
|
|
QFont font = f;
|
|
|
|
// Check if font is not fixed pitch and print a warning
|
|
if ( !QFontInfo(font).fixedPitch() )
|
|
{
|
|
qDebug() << "Using a variable-width font in the terminal. This may cause performance degradation and display/alignment errors.";
|
|
}
|
|
|
|
// hint that text should be drawn without anti-aliasing.
|
|
// depending on the user's font configuration, this may not be respected
|
|
if (!_antialiasText)
|
|
font.setStyleStrategy( QFont::NoAntialias );
|
|
|
|
// experimental optimization. Konsole assumes that the terminal is using a
|
|
// mono-spaced font, in which case kerning information should have no effect.
|
|
// Disabling kerning saves some computation when rendering text.
|
|
font.setKerning(false);
|
|
|
|
// QFont::ForceIntegerMetrics has been removed.
|
|
// Set full hinting instead to ensure the letters are aligned properly.
|
|
font.setHintingPreference(QFont::PreferFullHinting);
|
|
|
|
// "Draw intense colors in bold font" feature needs to use different font weights. StyleName
|
|
// property, when set, doesn't allow weight changes. Since all properties (weight, stretch,
|
|
// italic, etc) are stored in QFont independently, in almost all cases styleName is not needed.
|
|
font.setStyleName(QString());
|
|
|
|
m_font = font;
|
|
fontChange(font);
|
|
emit vtFontChanged();
|
|
}
|
|
|
|
void TerminalDisplay::setBoldIntense(bool value)
|
|
{
|
|
if ( _boldIntense != value ) {
|
|
_boldIntense = value;
|
|
emit boldIntenseChanged();
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::setItalicEnabled(bool value)
|
|
{
|
|
if (_italicEnabled != value) {
|
|
_italicEnabled = value;
|
|
emit italicEnabledChanged();
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::setFont(const QFont &f)
|
|
{
|
|
Q_UNUSED(f);
|
|
// ignore font change request if not coming from konsole itself
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Constructor / Destructor */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
TerminalDisplay::TerminalDisplay(QQuickItem *parent)
|
|
:QQuickPaintedItem(parent)
|
|
,_screenWindow(nullptr)
|
|
,_allowBell(true)
|
|
,_gridLayout(nullptr)
|
|
,_fontHeight(1)
|
|
,_fontWidth(1)
|
|
,_fontAscent(1)
|
|
,_boldIntense(true)
|
|
,_italicEnabled(true)
|
|
,_lines(1)
|
|
,_columns(1)
|
|
,_usedLines(1)
|
|
,_usedColumns(1)
|
|
,_contentHeight(1)
|
|
,_contentWidth(1)
|
|
,_image(nullptr)
|
|
,_randomSeed(0)
|
|
,_resizing(false)
|
|
,_terminalSizeHint(false)
|
|
,_terminalSizeStartup(true)
|
|
,_bidiEnabled(true)
|
|
,_mouseMarks(false)
|
|
,_disabledBracketedPasteMode(false)
|
|
,_actSel(0)
|
|
,_wordSelectionMode(false)
|
|
,_lineSelectionMode(false)
|
|
,_preserveLineBreaks(true)
|
|
,_columnSelectionMode(false)
|
|
,_scrollbarLocation(QTermWidget::NoScrollBar)
|
|
,_wordCharacters(QLatin1String(":@-./_~"))
|
|
,_bellMode(SystemBeepBell)
|
|
,_blinking(false)
|
|
,_hasBlinker(false)
|
|
,_cursorBlinking(false)
|
|
,_hasBlinkingCursor(false)
|
|
,_allowBlinkingText(true)
|
|
,_ctrlDrag(false)
|
|
,_tripleClickMode(SelectWholeLine)
|
|
,_isFixedSize(false)
|
|
,_possibleTripleClick(false)
|
|
,_resizeWidget(nullptr)
|
|
,_resizeTimer(nullptr)
|
|
,_flowControlWarningEnabled(false)
|
|
,_outputSuspendedLabel(nullptr)
|
|
,_lineSpacing(0)
|
|
,_colorsInverted(false)
|
|
,_opacity(static_cast<qreal>(1))
|
|
,_backgroundMode(None)
|
|
,_filterChain(new TerminalImageFilterChain())
|
|
,_cursorShape(Emulation::KeyboardCursorShape::BlockCursor)
|
|
,mMotionAfterPasting(NoMoveScreenWindow)
|
|
,_confirmMultilinePaste(false)
|
|
,_trimPastedTrailingNewlines(false)
|
|
,m_font("Monospace", 12)
|
|
,m_color_role(QPalette::Window)
|
|
,m_full_cursor_height(false)
|
|
,_leftBaseMargin(1)
|
|
,_topBaseMargin(1)
|
|
,_drawLineChars(true)
|
|
,_mouseAutohideDelay(-1)
|
|
{
|
|
// terminal applications are not designed with Right-To-Left in mind,
|
|
// so the layout is forced to Left-To-Right
|
|
//setLayoutDirection(Qt::LeftToRight);
|
|
|
|
// The offsets are not yet calculated.
|
|
// Do not calculate these too often to be more smoothly when resizing
|
|
// konsole in opaque mode.
|
|
_topMargin = _topBaseMargin;
|
|
_leftMargin = _leftBaseMargin;
|
|
|
|
m_palette = qApp->palette();
|
|
|
|
setVTFont(m_font);
|
|
|
|
// create scroll bar for scrolling output up and down
|
|
// set the scroll bar's slider to occupy the whole area of the scroll bar initially
|
|
_scrollBar = new ScrollBar(this);
|
|
// since the contrast with the terminal background may not be enough,
|
|
// the scrollbar should be auto-filled if not transient
|
|
if (!_scrollBar->isTransient())
|
|
_scrollBar->setAutoFillBackground(true);
|
|
setScroll(0,0);
|
|
_scrollBar->setCursor( Qt::ArrowCursor );
|
|
connect(_scrollBar, SIGNAL(valueChanged(int)), this,
|
|
SLOT(scrollBarPositionChanged(int)));
|
|
// qtermwidget: we have to hide it here due the _scrollbarLocation==NoScrollBar
|
|
// check in TerminalDisplay::setScrollBarPosition(ScrollBarPosition position)
|
|
_scrollBar->hide();
|
|
|
|
// setup timers for blinking cursor and text
|
|
_blinkTimer = new QTimer(this);
|
|
connect(_blinkTimer, SIGNAL(timeout()), this, SLOT(blinkEvent()));
|
|
_blinkCursorTimer = new QTimer(this);
|
|
connect(_blinkCursorTimer, SIGNAL(timeout()), this, SLOT(blinkCursorEvent()));
|
|
|
|
setUsesMouse(true);
|
|
setBracketedPasteMode(false);
|
|
setColorTable(base_color_table);
|
|
//setMouseTracking(true);
|
|
|
|
setAcceptedMouseButtons(Qt::LeftButton);
|
|
|
|
setFlags(ItemHasContents | ItemAcceptsInputMethod);
|
|
|
|
//Setup scrollbar. Be sure it is not darw on screen.
|
|
_scrollBar->setVisible(false);
|
|
connect(_scrollBar, SIGNAL(valueChanged(int)), this, SIGNAL(scrollbarParamsChanged(int)));
|
|
// A range change can clamp the value without valueChanged, e.g. when the first resize empties the scrollback.
|
|
connect(_scrollBar, &ScrollBar::rangeChanged, this, [this] { emit scrollbarParamsChanged(_scrollBar->value()); });
|
|
|
|
setUseFBORendering(true);
|
|
|
|
// Scrolled part way into a line, the lines at the edges are cut.
|
|
setClip(true);
|
|
_scroller = new TerminalScroller(this);
|
|
|
|
// setFocusPolicy( Qt::WheelFocus );
|
|
|
|
// enable input method support
|
|
// setAttribute(Qt::WA_InputMethodEnabled, true);
|
|
|
|
// this is an important optimization, it tells Qt
|
|
// that TerminalDisplay will handle repainting its entire area.
|
|
// setAttribute(Qt::WA_OpaquePaintEvent);
|
|
|
|
// _gridLayout = new QGridLayout(this);
|
|
// _gridLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
// new AutoScrollHandler(this);
|
|
}
|
|
|
|
TerminalDisplay::~TerminalDisplay()
|
|
{
|
|
disconnect(_blinkTimer);
|
|
disconnect(_blinkCursorTimer);
|
|
if (_hideMouseTimer)
|
|
disconnect(_hideMouseTimer.get());
|
|
qApp->removeEventFilter( this );
|
|
|
|
delete[] _image;
|
|
|
|
delete _gridLayout;
|
|
delete _outputSuspendedLabel;
|
|
delete _filterChain;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Display Operations */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uint8_t code)
|
|
{
|
|
//Calculate cell midpoints, end points.
|
|
int cx = x + w/2;
|
|
int cy = y + h/2;
|
|
int ex = x + w - 1;
|
|
int ey = y + h - 1;
|
|
|
|
quint32 toDraw = LineChars[code];
|
|
|
|
//Top _lines:
|
|
if (toDraw & TopL)
|
|
paint.drawLine(cx-1, y, cx-1, cy-2);
|
|
if (toDraw & TopC)
|
|
paint.drawLine(cx, y, cx, cy-2);
|
|
if (toDraw & TopR)
|
|
paint.drawLine(cx+1, y, cx+1, cy-2);
|
|
|
|
//Bot _lines:
|
|
if (toDraw & BotL)
|
|
paint.drawLine(cx-1, cy+2, cx-1, ey);
|
|
if (toDraw & BotC)
|
|
paint.drawLine(cx, cy+2, cx, ey);
|
|
if (toDraw & BotR)
|
|
paint.drawLine(cx+1, cy+2, cx+1, ey);
|
|
|
|
//Left _lines:
|
|
if (toDraw & LeftT)
|
|
paint.drawLine(x, cy-1, cx-2, cy-1);
|
|
if (toDraw & LeftC)
|
|
paint.drawLine(x, cy, cx-2, cy);
|
|
if (toDraw & LeftB)
|
|
paint.drawLine(x, cy+1, cx-2, cy+1);
|
|
|
|
//Right _lines:
|
|
if (toDraw & RightT)
|
|
paint.drawLine(cx+2, cy-1, ex, cy-1);
|
|
if (toDraw & RightC)
|
|
paint.drawLine(cx+2, cy, ex, cy);
|
|
if (toDraw & RightB)
|
|
paint.drawLine(cx+2, cy+1, ex, cy+1);
|
|
|
|
//Intersection points.
|
|
if (toDraw & Int11)
|
|
paint.drawPoint(cx-1, cy-1);
|
|
if (toDraw & Int12)
|
|
paint.drawPoint(cx, cy-1);
|
|
if (toDraw & Int13)
|
|
paint.drawPoint(cx+1, cy-1);
|
|
|
|
if (toDraw & Int21)
|
|
paint.drawPoint(cx-1, cy);
|
|
if (toDraw & Int22)
|
|
paint.drawPoint(cx, cy);
|
|
if (toDraw & Int23)
|
|
paint.drawPoint(cx+1, cy);
|
|
|
|
if (toDraw & Int31)
|
|
paint.drawPoint(cx-1, cy+1);
|
|
if (toDraw & Int32)
|
|
paint.drawPoint(cx, cy+1);
|
|
if (toDraw & Int33)
|
|
paint.drawPoint(cx+1, cy+1);
|
|
|
|
}
|
|
|
|
void Konsole::drawOtherLineChar(QPainter& paint, int x, int y, int w, int h, uchar code)
|
|
{
|
|
//Calculate cell midpoints, end points.
|
|
const int cx = x + w / 2;
|
|
const int cy = y + h / 2;
|
|
const int ex = x + w - 1;
|
|
const int ey = y + h - 1;
|
|
|
|
// Double dashes
|
|
if (0x4C <= code && code <= 0x4F) {
|
|
const int xHalfGap = qMax(w / 15, 1);
|
|
const int yHalfGap = qMax(h / 15, 1);
|
|
switch (code) {
|
|
case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
|
|
paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1);
|
|
paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1);
|
|
paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1);
|
|
paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1);
|
|
/* Falls through. */
|
|
case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
|
|
paint.drawLine(x, cy, cx - xHalfGap - 1, cy);
|
|
paint.drawLine(cx + xHalfGap, cy, ex, cy);
|
|
break;
|
|
case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
|
|
paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1);
|
|
paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1);
|
|
paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey);
|
|
paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey);
|
|
/* Falls through. */
|
|
case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
|
|
paint.drawLine(cx, y, cx, cy - yHalfGap - 1);
|
|
paint.drawLine(cx, cy + yHalfGap, cx, ey);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Rounded corner characters
|
|
else if (0x6D <= code && code <= 0x70) {
|
|
const int r = w * 3 / 8;
|
|
const int d = 2 * r;
|
|
switch (code) {
|
|
case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
|
|
paint.drawLine(cx, cy + r, cx, ey);
|
|
paint.drawLine(cx + r, cy, ex, cy);
|
|
paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16);
|
|
break;
|
|
case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
|
|
paint.drawLine(cx, cy + r, cx, ey);
|
|
paint.drawLine(x, cy, cx - r, cy);
|
|
paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16);
|
|
break;
|
|
case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT
|
|
paint.drawLine(cx, y, cx, cy - r);
|
|
paint.drawLine(x, cy, cx - r, cy);
|
|
paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16);
|
|
break;
|
|
case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT
|
|
paint.drawLine(cx, y, cx, cy - r);
|
|
paint.drawLine(cx + r, cy, ex, cy);
|
|
paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Diagonals
|
|
else if (0x71 <= code && code <= 0x73) {
|
|
switch (code) {
|
|
case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
|
|
paint.drawLine(ex, y, x, ey);
|
|
break;
|
|
case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
|
|
paint.drawLine(x, y, ex, ey);
|
|
break;
|
|
case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS
|
|
paint.drawLine(ex, y, x, ey);
|
|
paint.drawLine(x, y, ex, ey);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::drawLineCharString( QPainter& painter, int x, int y, const std::wstring& str,
|
|
const Character* attributes) const
|
|
{
|
|
const QPen& currentPen = painter.pen();
|
|
|
|
if ( (attributes->rendition & RE_BOLD) && _boldIntense )
|
|
{
|
|
QPen boldPen(currentPen);
|
|
boldPen.setWidth(3);
|
|
painter.setPen( boldPen );
|
|
}
|
|
|
|
for (size_t i=0 ; i < str.length(); i++)
|
|
{
|
|
uint8_t code = static_cast<uint8_t>(str[i] & 0xffU);
|
|
if (LineChars[code])
|
|
drawLineChar(painter, x + (_fontWidth*i), y, _fontWidth, _fontHeight, code);
|
|
else
|
|
drawOtherLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code);
|
|
}
|
|
|
|
painter.setPen( currentPen );
|
|
}
|
|
|
|
void TerminalDisplay::setKeyboardCursorShape(QTermWidget::KeyboardCursorShape shape)
|
|
{
|
|
_cursorShape = shape;
|
|
|
|
updateCursor();
|
|
}
|
|
void TerminalDisplay::setCursorShape(int shape)
|
|
{
|
|
const auto cursorShape = QTermWidget::KeyboardCursorShape(qBound(0, shape, 2));
|
|
if (cursorShape == _cursorShape)
|
|
return;
|
|
|
|
setKeyboardCursorShape(cursorShape);
|
|
emit cursorShapeChanged();
|
|
}
|
|
|
|
QTermWidget::KeyboardCursorShape TerminalDisplay::keyboardCursorShape() const
|
|
{
|
|
return _cursorShape;
|
|
}
|
|
void TerminalDisplay::setKeyboardCursorColor(bool useForegroundColor, const QColor& color)
|
|
{
|
|
if (useForegroundColor)
|
|
_cursorColor = QColor(); // an invalid color means that
|
|
// the foreground color of the
|
|
// current character should
|
|
// be used
|
|
|
|
else
|
|
_cursorColor = color;
|
|
}
|
|
QColor TerminalDisplay::keyboardCursorColor() const
|
|
{
|
|
return _cursorColor;
|
|
}
|
|
|
|
void TerminalDisplay::setOpacity(qreal opacity)
|
|
{
|
|
_opacity = qBound(static_cast<qreal>(0), opacity, static_cast<qreal>(1));
|
|
}
|
|
|
|
qreal TerminalDisplay::backgroundOpacity() const
|
|
{
|
|
return _opacity;
|
|
}
|
|
|
|
void TerminalDisplay::setBackgroundOpacity(qreal opacity)
|
|
{
|
|
if (qFuzzyCompare(_opacity, opacity))
|
|
return;
|
|
|
|
setOpacity(opacity);
|
|
|
|
QColor color = fillColor();
|
|
color.setAlphaF(_opacity);
|
|
setFillColor(color);
|
|
|
|
update();
|
|
emit backgroundOpacityChanged();
|
|
}
|
|
|
|
void TerminalDisplay::setBackgroundImage(const QString& backgroundImage)
|
|
{
|
|
if (!backgroundImage.isEmpty())
|
|
{
|
|
_backgroundImage.load(backgroundImage);
|
|
//setAttribute(Qt::WA_OpaquePaintEvent, false);
|
|
}
|
|
else
|
|
{
|
|
_backgroundImage = QPixmap();
|
|
//setAttribute(Qt::WA_OpaquePaintEvent, true);
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::setBackgroundMode(BackgroundMode mode)
|
|
{
|
|
_backgroundMode = mode;
|
|
}
|
|
|
|
void TerminalDisplay::drawBackground(QPainter& painter, const QRect& rect, const QColor& backgroundColor, bool useOpacitySetting )
|
|
{
|
|
// The whole widget rectangle is filled by the background color from
|
|
// the color scheme set in setColorTable(), while the scrollbar is
|
|
// left to the widget style for a consistent look.
|
|
if ( useOpacitySetting )
|
|
{
|
|
if (_backgroundImage.isNull()) {
|
|
QColor color(backgroundColor);
|
|
color.setAlphaF(_opacity);
|
|
|
|
painter.save();
|
|
painter.setCompositionMode(QPainter::CompositionMode_Source);
|
|
painter.fillRect(rect, color);
|
|
painter.restore();
|
|
}
|
|
}
|
|
else
|
|
painter.fillRect(rect, backgroundColor);
|
|
}
|
|
|
|
void TerminalDisplay::drawCursor(QPainter& painter,
|
|
const QRect& rect,
|
|
const QColor& foregroundColor,
|
|
const QColor& /*backgroundColor*/,
|
|
bool& invertCharacterColor)
|
|
{
|
|
QRect cursorRect = rect;
|
|
|
|
cursorRect.setHeight(_fontHeight - ((m_full_cursor_height) ? 0 : _lineSpacing - 1));
|
|
|
|
if (!_cursorBlinking)
|
|
{
|
|
if ( _cursorColor.isValid() )
|
|
painter.setPen(_cursorColor);
|
|
else
|
|
painter.setPen(foregroundColor);
|
|
|
|
if ( _cursorShape == Emulation::KeyboardCursorShape::BlockCursor )
|
|
{
|
|
// draw the cursor outline, adjusting the area so that
|
|
// it is draw entirely inside 'rect'
|
|
float penWidth = qMax(1,painter.pen().width());
|
|
|
|
//
|
|
painter.drawRect(
|
|
cursorRect.adjusted(
|
|
+ penWidth/2 + fmod(penWidth, 2),
|
|
+ penWidth/2 + fmod(penWidth, 2),
|
|
- penWidth/2 - fmod(penWidth, 2),
|
|
- penWidth/2 - fmod(penWidth, 2)
|
|
)
|
|
);
|
|
|
|
//if ( hasFocus() )
|
|
if ( true ) //QMLTermWidget: Always fill the cursor. Even when not in focus.
|
|
{
|
|
painter.fillRect(cursorRect, _cursorColor.isValid() ? _cursorColor : foregroundColor);
|
|
|
|
if ( !_cursorColor.isValid() )
|
|
{
|
|
// invert the colour used to draw the text to ensure that the character at
|
|
// the cursor position is readable
|
|
invertCharacterColor = true;
|
|
}
|
|
}
|
|
}
|
|
else if ( _cursorShape == Emulation::KeyboardCursorShape::UnderlineCursor )
|
|
painter.drawLine(cursorRect.left(),
|
|
cursorRect.bottom(),
|
|
cursorRect.right(),
|
|
cursorRect.bottom());
|
|
else if ( _cursorShape == Emulation::KeyboardCursorShape::IBeamCursor )
|
|
painter.drawLine(cursorRect.left(),
|
|
cursorRect.top(),
|
|
cursorRect.left(),
|
|
cursorRect.bottom());
|
|
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::drawCharacters(QPainter& painter,
|
|
const QRect& rect,
|
|
const std::wstring& text,
|
|
const Character* style,
|
|
bool invertCharacterColor,
|
|
bool tooWide)
|
|
{
|
|
// don't draw text which is currently blinking
|
|
if ( _blinking && (style->rendition & RE_BLINK) )
|
|
return;
|
|
|
|
// don't draw concealed characters
|
|
if (style->rendition & RE_CONCEAL)
|
|
return;
|
|
|
|
// setup bold and underline
|
|
bool useBold = ((style->rendition & RE_BOLD) && _boldIntense) || font().bold();
|
|
const bool useUnderline = style->rendition & RE_UNDERLINE || font().underline();
|
|
const bool useItalic = ((style->rendition & RE_ITALIC) && _italicEnabled) || font().italic();
|
|
const bool useStrikeOut = style->rendition & RE_STRIKEOUT || font().strikeOut();
|
|
const bool useOverline = style->rendition & RE_OVERLINE || font().overline();
|
|
|
|
painter.setFont(font());
|
|
|
|
QFont font = painter.font();
|
|
if ( font.bold() != useBold
|
|
|| font.underline() != useUnderline
|
|
|| font.italic() != useItalic
|
|
|| font.strikeOut() != useStrikeOut
|
|
|| font.overline() != useOverline) {
|
|
font.setBold(useBold);
|
|
font.setUnderline(useUnderline);
|
|
font.setItalic(useItalic);
|
|
font.setStrikeOut(useStrikeOut);
|
|
font.setOverline(useOverline);
|
|
painter.setFont(font);
|
|
}
|
|
|
|
// setup pen
|
|
const CharacterColor& textColor = ( invertCharacterColor ? style->backgroundColor : style->foregroundColor );
|
|
const QColor color = textColor.color(_colorTable);
|
|
QPen pen = painter.pen();
|
|
if ( pen.color() != color )
|
|
{
|
|
pen.setColor(color);
|
|
painter.setPen(color);
|
|
}
|
|
|
|
// draw text
|
|
if ( isLineCharString(text) )
|
|
drawLineCharString(painter,rect.x(),rect.y(),text,style);
|
|
else
|
|
{
|
|
// Force using LTR as the document layout for the terminal area, because
|
|
// there is no use cases for RTL emulator and RTL terminal application.
|
|
//
|
|
// This still allows RTL characters to be rendered in the RTL way.
|
|
painter.setLayoutDirection(Qt::LeftToRight);
|
|
|
|
if (_bidiEnabled) {
|
|
if (tooWide)
|
|
{
|
|
QRect drawRect(rect.topLeft(), rect.size());
|
|
drawRect.setHeight(rect.height() + _drawTextAdditionHeight);
|
|
painter.drawText(drawRect, Qt::AlignBottom, QString::fromStdWString(text));
|
|
}
|
|
else
|
|
{
|
|
painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing,
|
|
QString::fromStdWString(text));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QRect drawRect(rect.topLeft(), rect.size());
|
|
drawRect.setHeight(rect.height() + _drawTextAdditionHeight);
|
|
painter.drawText(drawRect, Qt::AlignBottom, LTR_OVERRIDE_CHAR + QString::fromStdWString(text));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::drawTextFragment(QPainter& painter ,
|
|
const QRect& rect,
|
|
const std::wstring& text,
|
|
const Character* style,
|
|
bool tooWide)
|
|
{
|
|
painter.save();
|
|
|
|
// setup painter
|
|
const QColor foregroundColor = style->foregroundColor.color(_colorTable);
|
|
const QColor backgroundColor = style->backgroundColor.color(_colorTable);
|
|
|
|
// draw background if different from the display's background color
|
|
if ( backgroundColor != palette().window().color() )
|
|
drawBackground(painter,rect,backgroundColor,
|
|
false /* do not use transparency */);
|
|
|
|
// draw cursor shape if the current character is the cursor
|
|
// this may alter the foreground and background colors
|
|
bool invertCharacterColor = false;
|
|
if ( style->rendition & RE_CURSOR )
|
|
drawCursor(painter,rect,foregroundColor,backgroundColor,invertCharacterColor);
|
|
|
|
// draw text
|
|
drawCharacters(painter,rect,text,style,invertCharacterColor, tooWide);
|
|
|
|
painter.restore();
|
|
}
|
|
|
|
void TerminalDisplay::setRandomSeed(uint randomSeed) { _randomSeed = randomSeed; }
|
|
uint TerminalDisplay::randomSeed() const { return _randomSeed; }
|
|
|
|
#if 0
|
|
/*!
|
|
Set XIM Position
|
|
*/
|
|
void TerminalDisplay::setCursorPos(const int curx, const int cury)
|
|
{
|
|
QPoint tL = contentsRect().topLeft();
|
|
int tLx = tL.x();
|
|
int tLy = tL.y();
|
|
|
|
int xpos, ypos;
|
|
ypos = _topMargin + tLy + _fontHeight*(cury-1) + _fontAscent;
|
|
xpos = _leftMargin + tLx + _fontWidth*curx;
|
|
//setMicroFocusHint(xpos, ypos, 0, _fontHeight); //### ???
|
|
// fprintf(stderr, "x/y = %d/%d\txpos/ypos = %d/%d\n", curx, cury, xpos, ypos);
|
|
_cursorLine = cury;
|
|
_cursorCol = curx;
|
|
}
|
|
#endif
|
|
|
|
// scrolls the image by 'lines', down if lines > 0 or up otherwise.
|
|
//
|
|
// the terminal emulation keeps track of the scrolling of the character
|
|
// image as it receives input, and when the view is updated, it calls scrollImage()
|
|
// with the final scroll amount. this improves performance because scrolling the
|
|
// display is much cheaper than re-rendering all the text for the
|
|
// part of the image which has moved up or down.
|
|
// Instead only new lines have to be drawn
|
|
void TerminalDisplay::scrollImage(int lines , const QRect& screenWindowRegion)
|
|
{
|
|
// if the flow control warning is enabled this will interfere with the
|
|
// scrolling optimizations and cause artifacts. the simple solution here
|
|
// is to just disable the optimization whilst it is visible
|
|
if ( _outputSuspendedLabel && _outputSuspendedLabel->isVisible() )
|
|
return;
|
|
|
|
// constrain the region to the display
|
|
// the bottom of the region is capped to the number of lines in the display's
|
|
// internal image - 2, so that the height of 'region' is strictly less
|
|
// than the height of the internal image.
|
|
QRect region = screenWindowRegion;
|
|
region.setBottom( qMin(region.bottom(),this->_lines-2) );
|
|
|
|
// return if there is nothing to do
|
|
if ( lines == 0
|
|
|| _image == nullptr
|
|
|| !region.isValid()
|
|
|| (region.top() + abs(lines)) >= region.bottom()
|
|
|| this->_lines <= region.height() ) return;
|
|
|
|
// hide terminal size label to prevent it being scrolled
|
|
if (_resizeWidget && _resizeWidget->isVisible())
|
|
_resizeWidget->hide();
|
|
|
|
// Note: With Qt 4.4 the left edge of the scrolled area must be at 0
|
|
// to get the correct (newly exposed) part of the widget repainted.
|
|
//
|
|
// The right edge must be before the left edge of the scroll bar to
|
|
// avoid triggering a repaint of the entire widget, the distance is
|
|
// given by SCROLLBAR_CONTENT_GAP
|
|
//
|
|
// Set the QT_FLUSH_PAINT environment variable to '1' before starting the
|
|
// application to monitor repainting.
|
|
//
|
|
int scrollBarWidth = _scrollBar->isHidden() ? 0 :
|
|
_scrollBar->isTransient() ?
|
|
0 : _scrollBar->width();
|
|
const int SCROLLBAR_CONTENT_GAP = scrollBarWidth == 0 ? 0 : 1;
|
|
QRect scrollRect;
|
|
if ( _scrollbarLocation == QTermWidget::ScrollBarLeft )
|
|
{
|
|
scrollRect.setLeft(scrollBarWidth+SCROLLBAR_CONTENT_GAP);
|
|
scrollRect.setRight(width());
|
|
}
|
|
else
|
|
{
|
|
scrollRect.setLeft(0);
|
|
scrollRect.setRight(width() - scrollBarWidth - SCROLLBAR_CONTENT_GAP);
|
|
}
|
|
void* firstCharPos = &_image[ region.top() * this->_columns ];
|
|
void* lastCharPos = &_image[ (region.top() + abs(lines)) * this->_columns ];
|
|
|
|
int top = _topMargin + (region.top() * _fontHeight);
|
|
int linesToMove = region.height() - abs(lines);
|
|
int bytesToMove = linesToMove *
|
|
this->_columns *
|
|
sizeof(Character);
|
|
|
|
Q_ASSERT( linesToMove > 0 );
|
|
Q_ASSERT( bytesToMove > 0 );
|
|
|
|
//scroll internal image
|
|
if ( lines > 0 )
|
|
{
|
|
// check that the memory areas that we are going to move are valid
|
|
Q_ASSERT( (char*)lastCharPos + bytesToMove <
|
|
(char*)(_image + (this->_lines * this->_columns)) );
|
|
|
|
Q_ASSERT( (lines*this->_columns) < _imageSize );
|
|
|
|
//scroll internal image down
|
|
memmove( firstCharPos , lastCharPos , bytesToMove );
|
|
|
|
//set region of display to scroll
|
|
scrollRect.setTop(top);
|
|
}
|
|
else
|
|
{
|
|
// check that the memory areas that we are going to move are valid
|
|
Q_ASSERT( (char*)firstCharPos + bytesToMove <
|
|
(char*)(_image + (this->_lines * this->_columns)) );
|
|
|
|
//scroll internal image up
|
|
memmove( lastCharPos , firstCharPos , bytesToMove );
|
|
|
|
//set region of the display to scroll
|
|
scrollRect.setTop(top + abs(lines) * _fontHeight);
|
|
}
|
|
scrollRect.setHeight(linesToMove * _fontHeight );
|
|
|
|
Q_ASSERT(scrollRect.isValid() && !scrollRect.isEmpty());
|
|
|
|
//scroll the display vertically to match internal _image
|
|
//scroll( 0 , _fontHeight * (-lines) , scrollRect );
|
|
}
|
|
|
|
QRegion TerminalDisplay::hotSpotRegion() const
|
|
{
|
|
QRegion region;
|
|
const auto hotSpots = _filterChain->hotSpots();
|
|
for( Filter::HotSpot* const hotSpot : hotSpots )
|
|
{
|
|
QRect r;
|
|
if (hotSpot->startLine()==hotSpot->endLine()) {
|
|
r.setLeft(hotSpot->startColumn());
|
|
r.setTop(hotSpot->startLine());
|
|
r.setRight(hotSpot->endColumn());
|
|
r.setBottom(hotSpot->endLine());
|
|
region |= imageToWidget(r);;
|
|
} else {
|
|
r.setLeft(hotSpot->startColumn());
|
|
r.setTop(hotSpot->startLine());
|
|
r.setRight(_columns);
|
|
r.setBottom(hotSpot->startLine());
|
|
region |= imageToWidget(r);;
|
|
for ( int line = hotSpot->startLine()+1 ; line < hotSpot->endLine() ; line++ ) {
|
|
r.setLeft(0);
|
|
r.setTop(line);
|
|
r.setRight(_columns);
|
|
r.setBottom(line);
|
|
region |= imageToWidget(r);;
|
|
}
|
|
r.setLeft(0);
|
|
r.setTop(hotSpot->endLine());
|
|
r.setRight(hotSpot->endColumn());
|
|
r.setBottom(hotSpot->endLine());
|
|
region |= imageToWidget(r);;
|
|
}
|
|
}
|
|
return region;
|
|
}
|
|
|
|
void TerminalDisplay::processFilters()
|
|
{
|
|
if (!_screenWindow)
|
|
return;
|
|
|
|
QRegion preUpdateHotSpots = hotSpotRegion();
|
|
|
|
// use _screenWindow->getImage() here rather than _image because
|
|
// other classes may call processFilters() when this display's
|
|
// ScreenWindow emits a scrolled() signal - which will happen before
|
|
// updateImage() is called on the display and therefore _image is
|
|
// out of date at this point
|
|
_filterChain->setImage( _screenWindow->getImage(),
|
|
_screenWindow->windowLines(),
|
|
_screenWindow->windowColumns(),
|
|
_screenWindow->getLineProperties() );
|
|
_filterChain->process();
|
|
|
|
QRegion postUpdateHotSpots = hotSpotRegion();
|
|
|
|
update( preUpdateHotSpots | postUpdateHotSpots );
|
|
}
|
|
|
|
void TerminalDisplay::updateImage()
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
// TODO QMLTermWidget at the moment I'm disabling this.
|
|
// Since this can't be scrolled we need to determine if this
|
|
// is useful or not.
|
|
|
|
// optimization - scroll the existing image where possible and
|
|
// avoid expensive text drawing for parts of the image that
|
|
// can simply be moved up or down
|
|
// scrollImage( _screenWindow->scrollCount() ,
|
|
// _screenWindow->scrollRegion() );
|
|
// _screenWindow->resetScrollCount();
|
|
|
|
if (!_image) {
|
|
// Create _image.
|
|
// The emitted changedContentSizeSignal also leads to getImage being recreated, so do this first.
|
|
updateImageSize();
|
|
}
|
|
|
|
// Scroll just far enough for output that pushed the cursor out of view.
|
|
if (_followCursor && !_screenWindow->trackOutput())
|
|
{
|
|
const int below = cursorLineInWindow() - _screenWindow->windowLines() + 1;
|
|
if (below > 0)
|
|
_screenWindow->scrollTo(_screenWindow->currentLine() + below);
|
|
if (_screenWindow->atEndOfOutput())
|
|
{
|
|
_screenWindow->setTrackOutput(true);
|
|
_followCursor = false;
|
|
}
|
|
}
|
|
|
|
Character* const newimg = _screenWindow->getImage();
|
|
int lines = _screenWindow->windowLines();
|
|
int columns = _screenWindow->windowColumns();
|
|
|
|
setScroll( _screenWindow->currentLine() , _screenWindow->lineCount() );
|
|
|
|
Q_ASSERT( this->_usedLines <= this->_lines );
|
|
Q_ASSERT( this->_usedColumns <= this->_columns );
|
|
|
|
int y,x,len;
|
|
|
|
QPoint tL = contentsRect().topLeft();
|
|
int tLx = tL.x();
|
|
int tLy = tL.y();
|
|
_hasBlinker = false;
|
|
|
|
CharacterColor cf; // undefined
|
|
CharacterColor _clipboard; // undefined
|
|
int cr = -1; // undefined
|
|
|
|
const int linesToUpdate = qMin(this->_lines, qMax(0,lines ));
|
|
const int columnsToUpdate = qMin(this->_columns,qMax(0,columns));
|
|
|
|
wchar_t *disstrU = new wchar_t[columnsToUpdate];
|
|
char *dirtyMask = new char[columnsToUpdate+2];
|
|
QRegion dirtyRegion;
|
|
|
|
for (y = 0; y < linesToUpdate; ++y)
|
|
{
|
|
const Character* currentLine = &_image[y*this->_columns];
|
|
const Character* const newLine = &newimg[y*columns];
|
|
|
|
bool updateLine = false;
|
|
|
|
// The dirty mask indicates which characters need repainting. We also
|
|
// mark surrounding neighbours dirty, in case the character exceeds
|
|
// its cell boundaries
|
|
memset(dirtyMask, 0, columnsToUpdate+2);
|
|
|
|
for( x = 0 ; x < columnsToUpdate ; ++x)
|
|
{
|
|
if ( newLine[x] != currentLine[x] )
|
|
{
|
|
dirtyMask[x] = true;
|
|
}
|
|
}
|
|
|
|
QFontMetrics fm(font());
|
|
if (!_resizing) // not while _resizing, we're expecting a paintEvent
|
|
for (x = 0; x < columnsToUpdate; ++x)
|
|
{
|
|
if ((newLine[x].rendition & RE_BLINK) != 0) {
|
|
_hasBlinker = true;
|
|
}
|
|
|
|
// Start drawing if this character or the next one differs.
|
|
// We also take the next one into account to handle the situation
|
|
// where characters exceed their cell width.
|
|
if (dirtyMask[x])
|
|
{
|
|
wchar_t c = newLine[x+0].character;
|
|
if ( !c )
|
|
continue;
|
|
int p = 0;
|
|
disstrU[p++] = c; //fontMap(c);
|
|
bool lineDraw = isLineChar(newLine[x+0]);
|
|
bool doubleWidth = (x+1 == columnsToUpdate) ? false : (newLine[x+1].character == 0);
|
|
int charWidth = fm.horizontalAdvance(QChar(c));
|
|
bool bigWidth = _fixedFont && !doubleWidth && charWidth > _fontWidth;
|
|
bool smallWidth = _fixedFont && charWidth < _fontWidth;
|
|
cr = newLine[x].rendition;
|
|
_clipboard = newLine[x].backgroundColor;
|
|
if (newLine[x].foregroundColor != cf) cf = newLine[x].foregroundColor;
|
|
int lln = columnsToUpdate - x;
|
|
for (len = 1; len < lln; ++len)
|
|
{
|
|
const Character& ch = newLine[x+len];
|
|
|
|
if (!ch.character)
|
|
continue; // Skip trailing part of multi-col chars.
|
|
|
|
bool nextIsDoubleWidth = (x+len+1 == columnsToUpdate) ? false : (newLine[x+len+1].character == 0);
|
|
int nxtCharWidth = fm.horizontalAdvance(QChar(newLine[x+len].character));
|
|
bool nextIsbigWidth = _fixedFont && !nextIsDoubleWidth && nxtCharWidth > _fontWidth;
|
|
bool nextIsSmallWidth = _fixedFont && newLine[x+len].character && nxtCharWidth < _fontWidth;
|
|
|
|
if (ch.foregroundColor != cf ||
|
|
ch.backgroundColor != _clipboard ||
|
|
ch.rendition != cr ||
|
|
!dirtyMask[x+len] ||
|
|
isLineChar(ch) != lineDraw ||
|
|
nextIsDoubleWidth != doubleWidth ||
|
|
bigWidth || nextIsbigWidth ||
|
|
smallWidth || nextIsSmallWidth)
|
|
{
|
|
break;
|
|
}
|
|
|
|
disstrU[p++] = c; //fontMap(c);
|
|
}
|
|
|
|
std::wstring unistr(disstrU, p);
|
|
|
|
bool saveFixedFont = _fixedFont;
|
|
if (lineDraw)
|
|
_fixedFont = false;
|
|
if (doubleWidth)
|
|
_fixedFont = false;
|
|
|
|
updateLine = true;
|
|
|
|
_fixedFont = saveFixedFont;
|
|
x += len - 1;
|
|
}
|
|
|
|
}
|
|
|
|
//both the top and bottom halves of double height _lines must always be redrawn
|
|
//although both top and bottom halves contain the same characters, only
|
|
//the top one is actually
|
|
//drawn.
|
|
if (_lineProperties.count() > y) {
|
|
if ((_lineProperties[y] & LINE_DOUBLEHEIGHT) != 0) {
|
|
updateLine = true;
|
|
}
|
|
}
|
|
|
|
// if the characters on the line are different in the old and the new _image
|
|
// then this line must be repainted.
|
|
if (updateLine)
|
|
{
|
|
// add the area occupied by this line to the region which needs to be
|
|
// repainted
|
|
QRect dirtyRect = QRect( _leftMargin+tLx ,
|
|
_topMargin+tLy+_fontHeight*y ,
|
|
_fontWidth * columnsToUpdate ,
|
|
_fontHeight );
|
|
|
|
dirtyRegion |= dirtyRect;
|
|
}
|
|
|
|
// replace the line of characters in the old _image with the
|
|
// current line of the new _image
|
|
memcpy((void*)currentLine,(const void*)newLine,columnsToUpdate*sizeof(Character));
|
|
}
|
|
|
|
// if the new _image is smaller than the previous _image, then ensure that the area
|
|
// outside the new _image is cleared
|
|
if ( linesToUpdate < _usedLines )
|
|
{
|
|
dirtyRegion |= QRect( _leftMargin+tLx ,
|
|
_topMargin+tLy+_fontHeight*linesToUpdate ,
|
|
_fontWidth * this->_columns ,
|
|
_fontHeight * (_usedLines-linesToUpdate) );
|
|
}
|
|
_usedLines = linesToUpdate;
|
|
|
|
if ( columnsToUpdate < _usedColumns )
|
|
{
|
|
dirtyRegion |= QRect( _leftMargin+tLx+columnsToUpdate*_fontWidth ,
|
|
_topMargin+tLy ,
|
|
_fontWidth * (_usedColumns-columnsToUpdate) ,
|
|
_fontHeight * this->_lines );
|
|
}
|
|
_usedColumns = columnsToUpdate;
|
|
|
|
dirtyRegion |= _inputMethodData.previousPreeditRect;
|
|
|
|
const int lineBelow = _screenWindow->currentLine() + lines;
|
|
_hasLineBelow = lineBelow < _screenWindow->lineCount();
|
|
if (_hasLineBelow)
|
|
{
|
|
std::vector<Character> below(columns);
|
|
_screenWindow->screen()->getImage(below.data(), columns, lineBelow, lineBelow);
|
|
memcpy((void*)&_image[linesToUpdate*this->_columns], below.data(), columnsToUpdate*sizeof(Character));
|
|
}
|
|
if (_scrollOffset != 0)
|
|
dirtyRegion = contentsRect();
|
|
|
|
// update the parts of the display which have changed
|
|
update(dirtyRegion);
|
|
|
|
if ( _hasBlinker && !_blinkTimer->isActive()) _blinkTimer->start( TEXT_BLINK_DELAY );
|
|
if (!_hasBlinker && _blinkTimer->isActive()) { _blinkTimer->stop(); _blinking = false; }
|
|
delete[] dirtyMask;
|
|
delete[] disstrU;
|
|
|
|
}
|
|
|
|
void TerminalDisplay::showResizeNotification()
|
|
{
|
|
// Disabled for QMLTermWidget: QWidget-based resize notification is not applicable.
|
|
}
|
|
|
|
void TerminalDisplay::setBlinkingCursor(bool blink)
|
|
{
|
|
if (_hasBlinkingCursor != blink)
|
|
emit blinkingCursorStateChanged();
|
|
|
|
_hasBlinkingCursor=blink;
|
|
|
|
if (blink && !_blinkCursorTimer->isActive() && hasFocus())
|
|
{
|
|
// QApplication::cursorFlashTime() may be negative, and a too fast
|
|
// blinking is not good. Also, see TerminalDisplay::keyPressEvent.
|
|
_blinkCursorTimer->start(std::max(QApplication::cursorFlashTime(), 1000) / 2);
|
|
}
|
|
|
|
if (!blink && _blinkCursorTimer->isActive())
|
|
{
|
|
_blinkCursorTimer->stop();
|
|
if (_cursorBlinking)
|
|
blinkCursorEvent();
|
|
else
|
|
_cursorBlinking = false;
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::setBlinkingTextEnabled(bool blink)
|
|
{
|
|
_allowBlinkingText = blink;
|
|
|
|
if (blink && !_blinkTimer->isActive() && hasFocus())
|
|
_blinkTimer->start(TEXT_BLINK_DELAY);
|
|
|
|
if (!blink && _blinkTimer->isActive())
|
|
{
|
|
_blinkTimer->stop();
|
|
_blinking = false;
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::focusOutEvent(QFocusEvent*)
|
|
{
|
|
// trigger a repaint of the cursor so that it is both visible (in case
|
|
// it was hidden during blinking)
|
|
// and drawn in a focused out state
|
|
_cursorBlinking = false;
|
|
updateCursor();
|
|
_blinkCursorTimer->stop();
|
|
|
|
if (_blinking)
|
|
blinkEvent();
|
|
|
|
_blinkTimer->stop();
|
|
|
|
// This signal should be emitted only in the end
|
|
// because the focus may change in response to it.
|
|
emit termLostFocus();
|
|
}
|
|
void TerminalDisplay::focusInEvent(QFocusEvent*)
|
|
{
|
|
if (_hasBlinkingCursor)
|
|
{
|
|
// see TerminalDisplay::setBlinkingCursor
|
|
_blinkCursorTimer->start(std::max(QApplication::cursorFlashTime(), 1000) / 2);
|
|
}
|
|
updateCursor();
|
|
|
|
if (_hasBlinker)
|
|
_blinkTimer->start(TEXT_BLINK_DELAY);
|
|
|
|
// This signal should be emitted only in the end
|
|
// because the focus may change in response to it.
|
|
emit termGetFocus();
|
|
}
|
|
|
|
// void TerminalDisplay::enterEvent(QEnterEvent* event)
|
|
// {
|
|
// if (gs_deadSpot.x() < 0 && _hideMouseTimer
|
|
// // NOTE: scrollBar->underMouse() doesn't work here
|
|
// && !_scrollBar->rect().contains(_scrollBar->mapFromParent(event->position().toPoint())))
|
|
// {
|
|
// gs_futureDeadSpot = event->position().toPoint();
|
|
// _hideMouseTimer->start(_mouseAutohideDelay);
|
|
// }
|
|
// }
|
|
|
|
// void TerminalDisplay::leaveEvent(QEvent* event)
|
|
// {
|
|
// if (gs_deadSpot.x() > -1)
|
|
// {
|
|
// gs_deadSpot = QPoint(-1,-1);
|
|
// QApplication::restoreOverrideCursor();
|
|
// }
|
|
// }
|
|
|
|
// QMLTermWidget version. See the upstream commented version for reference.
|
|
void TerminalDisplay::paint(QPainter *painter)
|
|
{
|
|
QRect cr = contentsRect();
|
|
|
|
if ( !_backgroundImage.isNull() )
|
|
{
|
|
QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
|
|
if (_opacity < static_cast<qreal>(1))
|
|
{
|
|
background.setAlphaF(_opacity);
|
|
painter->save();
|
|
painter->setCompositionMode(QPainter::CompositionMode_Source);
|
|
painter->fillRect(cr, background);
|
|
painter->restore();
|
|
}
|
|
else
|
|
{
|
|
painter->fillRect(cr, background);
|
|
}
|
|
|
|
painter->save();
|
|
painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
|
|
|
if (_backgroundMode == BackgroundMode::Stretch)
|
|
{ // scale the image without keeping its proportions to fill the screen
|
|
painter->drawPixmap(QRect(QPoint(0, 0), cr.size()), _backgroundImage);
|
|
}
|
|
else if (_backgroundMode == BackgroundMode::Zoom)
|
|
{ // zoom in/out the image to fit it
|
|
QRect r = _backgroundImage.rect();
|
|
qreal wRatio = static_cast<qreal>(cr.width()) / r.width();
|
|
qreal hRatio = static_cast<qreal>(cr.height()) / r.height();
|
|
if (wRatio > hRatio)
|
|
{
|
|
r.setWidth(qRound(r.width() * hRatio));
|
|
r.setHeight(cr.height());
|
|
}
|
|
else
|
|
{
|
|
r.setHeight(qRound(r.height() * wRatio));
|
|
r.setWidth(cr.width());
|
|
}
|
|
r.moveCenter(cr.center());
|
|
painter->drawPixmap(r, _backgroundImage, _backgroundImage.rect());
|
|
}
|
|
else if (_backgroundMode == BackgroundMode::Fit)
|
|
{ // if the image is bigger than the terminal, zoom it out to fit it
|
|
QRect r = _backgroundImage.rect();
|
|
qreal wRatio = static_cast<qreal>(cr.width()) / r.width();
|
|
qreal hRatio = static_cast<qreal>(cr.height()) / r.height();
|
|
if (r.width() > cr.width())
|
|
{
|
|
if (wRatio <= hRatio)
|
|
{
|
|
r.setHeight(qRound(r.height() * wRatio));
|
|
r.setWidth(cr.width());
|
|
}
|
|
else
|
|
{
|
|
r.setWidth(qRound(r.width() * hRatio));
|
|
r.setHeight(cr.height());
|
|
}
|
|
}
|
|
else if (r.height() > cr.height())
|
|
{
|
|
r.setWidth(qRound(r.width() * hRatio));
|
|
r.setHeight(cr.height());
|
|
}
|
|
r.moveCenter(cr.center());
|
|
painter->drawPixmap(r, _backgroundImage, _backgroundImage.rect());
|
|
}
|
|
else if (_backgroundMode == BackgroundMode::Center)
|
|
{ // center the image without scaling/zooming
|
|
QRect r = _backgroundImage.rect();
|
|
r.moveCenter(cr.center());
|
|
painter->drawPixmap(r.topLeft(), _backgroundImage);
|
|
}
|
|
else //if (_backgroundMode == None)
|
|
{
|
|
painter->drawPixmap(0, 0, _backgroundImage);
|
|
}
|
|
|
|
painter->restore();
|
|
}
|
|
|
|
if(_drawTextTestFlag)
|
|
{
|
|
calDrawTextAdditionHeight(*painter);
|
|
}
|
|
|
|
QRegion regToDraw = painter->clipRegion();
|
|
if (regToDraw.isEmpty())
|
|
regToDraw = QRegion(cr);
|
|
else
|
|
regToDraw &= cr;
|
|
|
|
for (auto rect = regToDraw.begin(); rect != regToDraw.end(); rect++)
|
|
{
|
|
drawBackground(*painter,*rect,palette().window().color(),
|
|
true /* use opacity setting */);
|
|
drawContents(*painter, *rect);
|
|
}
|
|
drawInputMethodPreeditString(*painter,preeditRect());
|
|
paintFilters(*painter);
|
|
}
|
|
|
|
// qtermwidget upstream version, might be useful for reference.
|
|
//void TerminalDisplay::paintEvent( QPaintEvent* pe )
|
|
//{
|
|
// QPainter paint(this);
|
|
// QRect cr = contentsRect();
|
|
|
|
// if ( !_backgroundImage.isNull() )
|
|
// {
|
|
// QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
|
|
// if (_opacity < static_cast<qreal>(1))
|
|
// {
|
|
// background.setAlphaF(_opacity);
|
|
// paint.save();
|
|
// paint.setCompositionMode(QPainter::CompositionMode_Source);
|
|
// paint.fillRect(cr, background);
|
|
// paint.restore();
|
|
// }
|
|
// else
|
|
// {
|
|
// paint.fillRect(cr, background);
|
|
// }
|
|
|
|
// paint.save();
|
|
// paint.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
|
|
|
// if (_backgroundMode == Stretch)
|
|
// { // scale the image without keeping its proportions to fill the screen
|
|
// paint.drawPixmap(cr, _backgroundImage, _backgroundImage.rect());
|
|
// }
|
|
// else if (_backgroundMode == Zoom)
|
|
// { // zoom in/out the image to fit it
|
|
// QRect r = _backgroundImage.rect();
|
|
// qreal wRatio = static_cast<qreal>(cr.width()) / r.width();
|
|
// qreal hRatio = static_cast<qreal>(cr.height()) / r.height();
|
|
// if (wRatio > hRatio)
|
|
// {
|
|
// r.setWidth(qRound(r.width() * hRatio));
|
|
// r.setHeight(cr.height());
|
|
// }
|
|
// else
|
|
// {
|
|
// r.setHeight(qRound(r.height() * wRatio));
|
|
// r.setWidth(cr.width());
|
|
// }
|
|
// r.moveCenter(cr.center());
|
|
// paint.drawPixmap(r, _backgroundImage, _backgroundImage.rect());
|
|
// }
|
|
// else if (_backgroundMode == Fit)
|
|
// { // if the image is bigger than the terminal, zoom it out to fit it
|
|
// QRect r = _backgroundImage.rect();
|
|
// qreal wRatio = static_cast<qreal>(cr.width()) / r.width();
|
|
// qreal hRatio = static_cast<qreal>(cr.height()) / r.height();
|
|
// if (r.width() > cr.width())
|
|
// {
|
|
// if (wRatio <= hRatio)
|
|
// {
|
|
// r.setHeight(qRound(r.height() * wRatio));
|
|
// r.setWidth(cr.width());
|
|
// }
|
|
// else
|
|
// {
|
|
// r.setWidth(qRound(r.width() * hRatio));
|
|
// r.setHeight(cr.height());
|
|
// }
|
|
// }
|
|
// else if (r.height() > cr.height())
|
|
// {
|
|
// r.setWidth(qRound(r.width() * hRatio));
|
|
// r.setHeight(cr.height());
|
|
// }
|
|
// r.moveCenter(cr.center());
|
|
// paint.drawPixmap(r, _backgroundImage, _backgroundImage.rect());
|
|
// }
|
|
// else if (_backgroundMode == Center)
|
|
// { // center the image without scaling/zooming
|
|
// QRect r = _backgroundImage.rect();
|
|
// r.moveCenter(cr.center());
|
|
// paint.drawPixmap(r.topLeft(), _backgroundImage);
|
|
// }
|
|
// else //if (_backgroundMode == None)
|
|
// {
|
|
// paint.drawPixmap(0, 0, _backgroundImage);
|
|
// }
|
|
|
|
// paint.restore();
|
|
// }
|
|
|
|
// if(_drawTextTestFlag)
|
|
// {
|
|
// calDrawTextAdditionHeight(paint);
|
|
// }
|
|
|
|
// const auto rects = (pe->region() & cr).rects();
|
|
// for (const QRect &rect : rects)
|
|
// {
|
|
// drawBackground(paint,rect,palette().background().color(),
|
|
// true /* use opacity setting */);
|
|
// drawContents(paint, rect);
|
|
// }
|
|
// drawInputMethodPreeditString(paint,preeditRect());
|
|
// paintFilters(paint);
|
|
|
|
QPoint TerminalDisplay::cursorPosition() const
|
|
{
|
|
if (_screenWindow)
|
|
return _screenWindow->cursorPosition();
|
|
else
|
|
return {0,0};
|
|
}
|
|
|
|
QRect TerminalDisplay::preeditRect() const
|
|
{
|
|
const int preeditLength = string_width(_inputMethodData.preeditString);
|
|
|
|
if ( preeditLength == 0 )
|
|
return {};
|
|
|
|
return QRect(_leftMargin + _fontWidth*cursorPosition().x(),
|
|
_topMargin + _fontHeight*cursorPosition().y(),
|
|
_fontWidth*preeditLength,
|
|
_fontHeight);
|
|
}
|
|
|
|
void TerminalDisplay::drawInputMethodPreeditString(QPainter& painter , const QRect& rect)
|
|
{
|
|
if ( _inputMethodData.preeditString.empty() )
|
|
return;
|
|
|
|
const QPoint cursorPos = cursorPosition();
|
|
|
|
bool invertColors = false;
|
|
const QColor background = _colorTable[DEFAULT_BACK_COLOR].color;
|
|
const QColor foreground = _colorTable[DEFAULT_FORE_COLOR].color;
|
|
const Character* style = &_image[loc(cursorPos.x(),cursorPos.y())];
|
|
|
|
drawBackground(painter,rect,background,true);
|
|
drawCursor(painter,rect,foreground,background,invertColors);
|
|
drawCharacters(painter,rect,_inputMethodData.preeditString,style,invertColors);
|
|
|
|
_inputMethodData.previousPreeditRect = rect;
|
|
}
|
|
|
|
FilterChain* TerminalDisplay::filterChain() const
|
|
{
|
|
return _filterChain;
|
|
}
|
|
|
|
void TerminalDisplay::paintFilters(QPainter& painter)
|
|
{
|
|
// get color of character under mouse and use it to draw
|
|
// lines for filters
|
|
QPoint cursorPos = mapFromScene(QCursor::pos()).toPoint();
|
|
int cursorLine;
|
|
int cursorColumn;
|
|
int leftMargin = _leftBaseMargin
|
|
+ ((_scrollbarLocation == QTermWidget::ScrollBarLeft
|
|
&& !_scrollBar->isTransient())
|
|
? _scrollBar->width() : 0);
|
|
|
|
getCharacterPosition( cursorPos , cursorLine , cursorColumn );
|
|
Character cursorCharacter = _image[loc(cursorColumn,cursorLine)];
|
|
|
|
painter.setPen( QPen(cursorCharacter.foregroundColor.color(colorTable())) );
|
|
|
|
// iterate over hotspots identified by the display's currently active filters
|
|
// and draw appropriate visuals to indicate the presence of the hotspot
|
|
|
|
QList<Filter::HotSpot*> spots = _filterChain->hotSpots();
|
|
QListIterator<Filter::HotSpot*> iter(spots);
|
|
while (iter.hasNext())
|
|
{
|
|
Filter::HotSpot* spot = iter.next();
|
|
|
|
QRegion region;
|
|
if ( spot->type() == Filter::HotSpot::Link ) {
|
|
QRect r;
|
|
if (spot->startLine()==spot->endLine()) {
|
|
r.setCoords( spot->startColumn()*_fontWidth + 1 + leftMargin,
|
|
spot->startLine()*_fontHeight + 1 + _topBaseMargin,
|
|
spot->endColumn()*_fontWidth - 1 + leftMargin,
|
|
(spot->endLine()+1)*_fontHeight - 1 + _topBaseMargin );
|
|
region |= r;
|
|
} else {
|
|
r.setCoords( spot->startColumn()*_fontWidth + 1 + leftMargin,
|
|
spot->startLine()*_fontHeight + 1 + _topBaseMargin,
|
|
_columns*_fontWidth - 1 + leftMargin,
|
|
(spot->startLine()+1)*_fontHeight - 1 + _topBaseMargin );
|
|
region |= r;
|
|
for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
|
|
r.setCoords( 0*_fontWidth + 1 + leftMargin,
|
|
line*_fontHeight + 1 + _topBaseMargin,
|
|
_columns*_fontWidth - 1 + leftMargin,
|
|
(line+1)*_fontHeight - 1 + _topBaseMargin );
|
|
region |= r;
|
|
}
|
|
r.setCoords( 0*_fontWidth + 1 + leftMargin,
|
|
spot->endLine()*_fontHeight + 1 + _topBaseMargin,
|
|
spot->endColumn()*_fontWidth - 1 + leftMargin,
|
|
(spot->endLine()+1)*_fontHeight - 1 + _topBaseMargin );
|
|
region |= r;
|
|
}
|
|
}
|
|
|
|
for ( int line = spot->startLine() ; line <= spot->endLine() ; line++ )
|
|
{
|
|
int startColumn = 0;
|
|
int endColumn = _columns-1; // TODO use number of _columns which are actually
|
|
// occupied on this line rather than the width of the
|
|
// display in _columns
|
|
|
|
// ignore whitespace at the end of the lines
|
|
while ( QChar(_image[loc(endColumn,line)].character).isSpace() && endColumn > 0 )
|
|
endColumn--;
|
|
|
|
// increment here because the column which we want to set 'endColumn' to
|
|
// is the first whitespace character at the end of the line
|
|
endColumn++;
|
|
|
|
if ( line == spot->startLine() )
|
|
startColumn = spot->startColumn();
|
|
if ( line == spot->endLine() )
|
|
endColumn = spot->endColumn();
|
|
|
|
// subtract one pixel from
|
|
// the right and bottom so that
|
|
// we do not overdraw adjacent
|
|
// hotspots
|
|
//
|
|
// subtracting one pixel from all sides also prevents an edge case where
|
|
// moving the mouse outside a link could still leave it underlined
|
|
// because the check below for the position of the cursor
|
|
// finds it on the border of the target area
|
|
QRect r;
|
|
r.setCoords( startColumn*_fontWidth + 1 + leftMargin,
|
|
line*_fontHeight + 1 + _topBaseMargin,
|
|
endColumn*_fontWidth - 1 + leftMargin,
|
|
(line+1)*_fontHeight - 1 + _topBaseMargin );
|
|
// Underline link hotspots
|
|
if ( spot->type() == Filter::HotSpot::Link )
|
|
{
|
|
QFontMetrics metrics(font());
|
|
|
|
// find the baseline (which is the invisible line that the characters in the font sit on,
|
|
// with some having tails dangling below)
|
|
int baseline = r.bottom() - metrics.descent();
|
|
// find the position of the underline below that
|
|
int underlinePos = baseline + metrics.underlinePos();
|
|
|
|
if ( region.contains( mapFromScene(QCursor::pos()).toPoint() ) ){
|
|
painter.drawLine( r.left() , underlinePos , r.right() , underlinePos );
|
|
}
|
|
}
|
|
// Marker hotspots simply have a transparent rectanglular shape
|
|
// drawn on top of them
|
|
else if ( spot->type() == Filter::HotSpot::Marker )
|
|
{
|
|
//TODO - Do not use a hardcoded colour for this
|
|
painter.fillRect(r,QBrush(QColor(255,0,0,120)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// NOTE: This should be called only when "_fixedFont" is set to "false" (temporarily).
|
|
int TerminalDisplay::textWidth(const int startColumn, const int length, const int line) const
|
|
{
|
|
QFontMetrics fm(font());
|
|
int result = 0;
|
|
for (int column = 0; column < length; column++)
|
|
{
|
|
auto c = _image[loc(startColumn + column, line)];
|
|
// Take care of double-column characters and those with small widths.
|
|
// Exclude line characters, as some of them are ambiguous ('A') [1]
|
|
// [1] http://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txt
|
|
if (_fixedFont_original && !isLineChar(c))
|
|
{ // c == 0 may happen here after a double-column character
|
|
result += fm.horizontalAdvance(QLatin1Char(REPCHAR[0]));
|
|
}
|
|
else
|
|
{
|
|
result += fm.horizontalAdvance(QChar(static_cast<uint>(c.character)));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QRect TerminalDisplay::calculateTextArea(int topLeftX, int topLeftY, int startColumn, int line, int length) {
|
|
int left = _fixedFont ? _fontWidth * startColumn : textWidth(0, startColumn, line);
|
|
int top = _fontHeight * line;
|
|
int width = _fixedFont ? _fontWidth * length : textWidth(startColumn, length, line);
|
|
return {_leftMargin + topLeftX + left,
|
|
_topMargin + topLeftY + top,
|
|
width,
|
|
_fontHeight};
|
|
}
|
|
|
|
void TerminalDisplay::drawContents(QPainter &paint, const QRect &rect)
|
|
{
|
|
QPoint tL = contentsRect().topLeft();
|
|
int tLx = tL.x();
|
|
int tLy = tL.y();
|
|
|
|
int lux = qMin(_usedColumns-1, qMax(0,(rect.left() - tLx - _leftMargin ) / _fontWidth));
|
|
int luy = qMin(drawnLines()-1, qMax(0,(rect.top() - tLy - _topMargin ) / _fontHeight));
|
|
int rlx = qMin(_usedColumns-1, qMax(0,(rect.right() - tLx - _leftMargin ) / _fontWidth));
|
|
int rly = qMin(drawnLines()-1, qMax(0,(rect.bottom() - tLy - _topMargin ) / _fontHeight));
|
|
|
|
if(!_image)
|
|
return;
|
|
|
|
QFontMetrics fm(m_font);
|
|
const int numberOfColumns = _usedColumns;
|
|
std::wstring unistr;
|
|
unistr.reserve(numberOfColumns);
|
|
for (int y = luy; y <= rly; y++)
|
|
{
|
|
quint32 c = _image[loc(lux,y)].character;
|
|
int x = lux;
|
|
if(!c && x)
|
|
x--; // Search for start of multi-column character
|
|
for (; x <= rlx; x++)
|
|
{
|
|
int len = 1;
|
|
int p = 0;
|
|
|
|
// reset our buffer to the number of columns
|
|
int bufferSize = numberOfColumns;
|
|
unistr.resize(bufferSize);
|
|
|
|
// is this a single character or a sequence of characters ?
|
|
if ( _image[loc(x,y)].rendition & RE_EXTENDED_CHAR )
|
|
{
|
|
// sequence of characters
|
|
ushort extendedCharLength = 0;
|
|
uint* chars = ExtendedCharTable::instance
|
|
.lookupExtendedChar(_image[loc(x,y)].character,extendedCharLength);
|
|
if (chars)
|
|
{
|
|
Q_ASSERT(extendedCharLength > 1);
|
|
bufferSize += extendedCharLength - 1;
|
|
unistr.resize(bufferSize);
|
|
for ( int index = 0 ; index < extendedCharLength ; index++ )
|
|
{
|
|
Q_ASSERT( p < bufferSize );
|
|
unistr[p++] = chars[index];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// single character
|
|
c = _image[loc(x,y)].character;
|
|
if (c)
|
|
{
|
|
Q_ASSERT( p < bufferSize );
|
|
unistr[p++] = c; //fontMap(c);
|
|
}
|
|
}
|
|
|
|
bool lineDraw = isLineChar(_image[loc(x,y)]);
|
|
bool doubleWidth = (_image[ qMin(loc(x,y)+1,_imageSize) ].character == 0);
|
|
int charWidth = fm.horizontalAdvance(QChar(c));
|
|
bool bigWidth = _fixedFont && !doubleWidth && charWidth > _fontWidth;
|
|
bool tooWide = bigWidth && charWidth >= 2 * _fontWidth;
|
|
bool smallWidth = _fixedFont && c && charWidth < _fontWidth;
|
|
CharacterColor currentForeground = _image[loc(x,y)].foregroundColor;
|
|
CharacterColor currentBackground = _image[loc(x,y)].backgroundColor;
|
|
quint8 currentRendition = _image[loc(x,y)].rendition;
|
|
|
|
quint32 nxtC = 0;
|
|
bool nxtDoubleWidth = false;
|
|
int nxtCharWidth = 0;
|
|
while (x+len <= rlx &&
|
|
_image[loc(x+len,y)].foregroundColor == currentForeground &&
|
|
_image[loc(x+len,y)].backgroundColor == currentBackground &&
|
|
_image[loc(x+len,y)].rendition == currentRendition &&
|
|
(nxtDoubleWidth = (_image[qMin(loc(x+len,y)+1,_imageSize)].character == 0)) == doubleWidth &&
|
|
!smallWidth &&
|
|
!(_fixedFont && (nxtC = _image[loc(x+len,y)].character) && (nxtCharWidth = fm.horizontalAdvance(QChar(nxtC))) < _fontWidth) &&
|
|
!bigWidth &&
|
|
!(_fixedFont && !nxtDoubleWidth && nxtC && nxtCharWidth > _fontWidth) &&
|
|
isLineChar(_image[loc(x+len,y)]) == lineDraw) // Assignment!
|
|
{
|
|
c = _image[loc(x+len,y)].character;
|
|
if (_image[loc(x+len,y)].rendition & RE_EXTENDED_CHAR)
|
|
{
|
|
// sequence of characters
|
|
ushort extendedCharLength = 0;
|
|
const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
|
|
if (chars)
|
|
{
|
|
Q_ASSERT(extendedCharLength > 1);
|
|
bufferSize += extendedCharLength - 1;
|
|
unistr.resize(bufferSize);
|
|
for ( int index = 0 ; index < extendedCharLength ; index++ )
|
|
{
|
|
Q_ASSERT( p < bufferSize );
|
|
unistr[p++] = chars[index];
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// single character
|
|
if (c)
|
|
{
|
|
Q_ASSERT( p < bufferSize );
|
|
unistr[p++] = c; //fontMap(c);
|
|
}
|
|
}
|
|
|
|
if (doubleWidth) // assert((_image[loc(x+len,y)+1].character == 0)), see above if condition
|
|
len++; // Skip trailing part of multi-column character
|
|
len++;
|
|
}
|
|
if ((x+len < _usedColumns) && (!_image[loc(x+len,y)].character))
|
|
len++; // Adjust for trailing part of multi-column character
|
|
|
|
bool save__fixedFont = _fixedFont;
|
|
if (lineDraw)
|
|
_fixedFont = false;
|
|
unistr.resize(p);
|
|
|
|
// Create a text scaling matrix for double width and double height lines.
|
|
QTransform textScale;
|
|
|
|
if (y < _lineProperties.size())
|
|
{
|
|
if (_lineProperties[y] & LINE_DOUBLEWIDTH)
|
|
textScale.scale(2,1);
|
|
|
|
if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
|
|
textScale.scale(1,2);
|
|
}
|
|
|
|
//Apply text scaling matrix.
|
|
paint.setWorldTransform(textScale, true);
|
|
|
|
//calculate the area in which the text will be drawn
|
|
QRect textArea = calculateTextArea(tLx, tLy, x, y, len);
|
|
|
|
//move the calculated area to take account of scaling applied to the painter.
|
|
//the position of the area from the origin (0,0) is scaled
|
|
//by the opposite of whatever
|
|
//transformation has been applied to the painter. this ensures that
|
|
//painting does actually start from textArea.topLeft()
|
|
//(instead of textArea.topLeft() * painter-scale)
|
|
textArea.moveTopLeft( textScale.inverted().map(textArea.topLeft()) );
|
|
|
|
//paint text fragment
|
|
drawTextFragment(paint,
|
|
textArea,
|
|
unistr,
|
|
&_image[loc(x,y)],
|
|
tooWide);
|
|
|
|
_fixedFont = save__fixedFont;
|
|
|
|
//reset back to single-width, single-height _lines
|
|
paint.setWorldTransform(textScale.inverted(), true);
|
|
|
|
if (y < _lineProperties.size()-1)
|
|
{
|
|
//double-height _lines are represented by two adjacent _lines
|
|
//containing the same characters
|
|
//both _lines will have the LINE_DOUBLEHEIGHT attribute.
|
|
//If the current line has the LINE_DOUBLEHEIGHT attribute,
|
|
//we can therefore skip the next line
|
|
if (_lineProperties[y] & LINE_DOUBLEHEIGHT)
|
|
y++;
|
|
}
|
|
|
|
x += len - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::blinkEvent()
|
|
{
|
|
if (!_allowBlinkingText) return;
|
|
|
|
_blinking = !_blinking;
|
|
|
|
//TODO: Optimize to only repaint the areas of the widget
|
|
// where there is blinking text
|
|
// rather than repainting the whole widget.
|
|
update();
|
|
}
|
|
|
|
QRect TerminalDisplay::imageToWidget(const QRect& imageArea) const
|
|
{
|
|
QRect result;
|
|
result.setLeft( _leftMargin + _fontWidth * imageArea.left() );
|
|
result.setTop( _topMargin + _fontHeight * imageArea.top() );
|
|
result.setWidth( _fontWidth * imageArea.width() );
|
|
result.setHeight( _fontHeight * imageArea.height() );
|
|
|
|
return result;
|
|
}
|
|
|
|
void TerminalDisplay::updateCursor()
|
|
{
|
|
QRect cursorRect = imageToWidget( QRect(cursorPosition(),QSize(1,1)) );
|
|
update(cursorRect);
|
|
}
|
|
|
|
void TerminalDisplay::blinkCursorEvent()
|
|
{
|
|
_cursorBlinking = !_cursorBlinking;
|
|
updateCursor();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Resizing */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
void TerminalDisplay::resizeEvent(QResizeEvent*)
|
|
{
|
|
updateImageSize();
|
|
processFilters();
|
|
}
|
|
|
|
void TerminalDisplay::propagateSize()
|
|
{
|
|
if (_isFixedSize)
|
|
{
|
|
setSize(_columns, _lines);
|
|
//QWidget::setFixedSize(sizeHint());
|
|
//parentWidget()->adjustSize();
|
|
//parentWidget()->setFixedSize(parentWidget()->sizeHint());
|
|
return;
|
|
}
|
|
if (_image)
|
|
updateImageSize();
|
|
}
|
|
|
|
void TerminalDisplay::updateImageSize()
|
|
{
|
|
if (_scroller) {
|
|
_scroller->cancel(false);
|
|
_scrollOffset = 0;
|
|
}
|
|
|
|
Character* oldimg = _image;
|
|
int oldlin = _lines;
|
|
int oldcol = _columns;
|
|
|
|
makeImage();
|
|
|
|
// copy the old image to reduce flicker
|
|
int lines = qMin(oldlin,_lines);
|
|
int columns = qMin(oldcol,_columns);
|
|
|
|
if (oldimg)
|
|
{
|
|
for (int line = 0; line < lines; line++)
|
|
{
|
|
memcpy((void*)&_image[_columns*line],
|
|
(void*)&oldimg[oldcol*line],columns*sizeof(Character));
|
|
}
|
|
delete[] oldimg;
|
|
}
|
|
|
|
if (_screenWindow)
|
|
_screenWindow->setWindowLines(_lines);
|
|
|
|
_resizing = (oldlin!=_lines) || (oldcol!=_columns);
|
|
|
|
if ( _resizing )
|
|
{
|
|
showResizeNotification();
|
|
emit changedContentSizeSignal(_contentHeight, _contentWidth); // expose resizeEvent
|
|
}
|
|
|
|
_resizing = false;
|
|
}
|
|
|
|
//showEvent and hideEvent are reimplemented here so that it appears to other classes that the
|
|
//display has been resized when the display is hidden or shown.
|
|
//
|
|
//TODO: Perhaps it would be better to have separate signals for show and hide instead of using
|
|
//the same signal as the one for a content size change
|
|
void TerminalDisplay::showEvent(QShowEvent*)
|
|
{
|
|
emit changedContentSizeSignal(_contentHeight,_contentWidth);
|
|
}
|
|
void TerminalDisplay::hideEvent(QHideEvent*)
|
|
{
|
|
emit changedContentSizeSignal(_contentHeight,_contentWidth);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Scrollbar */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
void TerminalDisplay::scrollBarPositionChanged(int)
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
if (!_applyingScroll) {
|
|
_scroller->cancel(false);
|
|
setScrollOffset(0);
|
|
}
|
|
_followCursor = false;
|
|
|
|
_screenWindow->scrollTo( _scrollBar->value() );
|
|
|
|
// if the thumb has been moved to the bottom of the _scrollBar then set
|
|
// the display to automatically track new output,
|
|
// that is, scroll down automatically
|
|
// to how new _lines as they are added
|
|
const bool atEndOfOutput = (_scrollBar->value() == _scrollBar->maximum());
|
|
_screenWindow->setTrackOutput( atEndOfOutput );
|
|
|
|
updateImage();
|
|
|
|
// QMLTermWidget: notify qml side of the change only when needed.
|
|
emit scrollbarValueChanged();
|
|
}
|
|
|
|
void TerminalDisplay::setScroll(int cursor, int slines)
|
|
{
|
|
// Before the first layout the display is a one-line fallback, so the whole screen would read as scrollback.
|
|
const int maxScrollValue = height() > 0 ? std::max(0, slines - _lines) : 0;
|
|
const int boundedCursor = qBound(0, cursor, maxScrollValue);
|
|
|
|
// update _scrollBar if the range or value has changed,
|
|
// otherwise return
|
|
//
|
|
// setting the range or value of a _scrollBar will always trigger
|
|
// a repaint, so it should be avoided if it is not necessary
|
|
if ( _scrollBar->minimum() == 0 &&
|
|
_scrollBar->maximum() == maxScrollValue &&
|
|
_scrollBar->value() == boundedCursor )
|
|
{
|
|
return;
|
|
}
|
|
|
|
const int previousValue = _scrollBar->value();
|
|
disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
|
|
_scrollBar->setRange(0,maxScrollValue);
|
|
_scrollBar->setSingleStep(1);
|
|
_scrollBar->setPageStep(_lines);
|
|
_scrollBar->setValue(boundedCursor);
|
|
connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
|
|
|
|
// The view moved under a resting partial scroll, or the scrollback went away.
|
|
if (maxScrollValue == 0 || (_scrollBar->value() != previousValue && !_scroller->isActive())) {
|
|
_scroller->cancel(false);
|
|
setScrollOffset(0);
|
|
}
|
|
}
|
|
|
|
int TerminalDisplay::cursorLineInWindow() const
|
|
{
|
|
Screen *screen = _screenWindow->screen();
|
|
return screen->getHistLines() + screen->getCursorY() - _screenWindow->currentLine();
|
|
}
|
|
|
|
// A short scrollback over a mostly empty screen: jumping to the end would
|
|
// push the prompt up and show the empty lines instead.
|
|
void TerminalDisplay::revealCursor()
|
|
{
|
|
if (!_screenWindow)
|
|
return;
|
|
|
|
const int line = cursorLineInWindow();
|
|
if (line < 0 || line >= _screenWindow->windowLines()) {
|
|
scrollToEnd();
|
|
return;
|
|
}
|
|
|
|
_followCursor = !_screenWindow->atEndOfOutput();
|
|
}
|
|
|
|
void TerminalDisplay::scrollToEnd()
|
|
{
|
|
_followCursor = false;
|
|
_scroller->cancel(false);
|
|
setScrollOffset(0);
|
|
|
|
disconnect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
|
|
_scrollBar->setValue( _scrollBar->maximum() );
|
|
connect(_scrollBar, SIGNAL(valueChanged(int)), this, SLOT(scrollBarPositionChanged(int)));
|
|
|
|
_screenWindow->scrollTo( _scrollBar->value() + 1 );
|
|
_screenWindow->setTrackOutput( _screenWindow->atEndOfOutput() );
|
|
}
|
|
|
|
void TerminalDisplay::setScrollBarPosition(QTermWidget::ScrollBarPosition position)
|
|
{
|
|
if (_scrollbarLocation == position)
|
|
return;
|
|
|
|
if ( position == QTermWidget::NoScrollBar )
|
|
_scrollBar->hide();
|
|
else
|
|
_scrollBar->show();
|
|
|
|
_topMargin = _leftMargin = 1;
|
|
_scrollbarLocation = position;
|
|
|
|
propagateSize();
|
|
update();
|
|
}
|
|
|
|
void TerminalDisplay::mousePressEvent(QMouseEvent* ev)
|
|
{
|
|
if ( _possibleTripleClick && (ev->button()==Qt::LeftButton) ) {
|
|
mouseTripleClickEvent(ev);
|
|
return;
|
|
}
|
|
|
|
if ( !contentsRect().contains(ev->pos()) ) return;
|
|
|
|
if ( !_screenWindow ) return;
|
|
|
|
int charLine;
|
|
int charColumn;
|
|
getCharacterPosition(ev->pos(),charLine,charColumn);
|
|
QPoint pos = QPoint(charColumn,charLine);
|
|
|
|
if ( ev->button() == Qt::LeftButton)
|
|
{
|
|
_lineSelectionMode = false;
|
|
_wordSelectionMode = false;
|
|
|
|
emit isBusySelecting(true); // Keep it steady...
|
|
// Drag only when the Control key is hold
|
|
bool selected = false;
|
|
|
|
// The receiver of the testIsSelected() signal will adjust
|
|
// 'selected' accordingly.
|
|
//emit testIsSelected(pos.x(), pos.y(), selected);
|
|
|
|
selected = _screenWindow->isSelected(pos.x(),pos.y());
|
|
|
|
if ((!_ctrlDrag || ev->modifiers() & Qt::ControlModifier) && selected ) {
|
|
// The user clicked inside selected text
|
|
dragInfo.state = diPending;
|
|
dragInfo.start = ev->pos();
|
|
}
|
|
else {
|
|
// No reason to ever start a drag event
|
|
dragInfo.state = diNone;
|
|
|
|
_preserveLineBreaks = !( ( ev->modifiers() & Qt::ControlModifier ) && !(ev->modifiers() & Qt::AltModifier) );
|
|
_columnSelectionMode = (ev->modifiers() & Qt::AltModifier) && (ev->modifiers() & Qt::ControlModifier);
|
|
|
|
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
|
|
{
|
|
_screenWindow->clearSelection();
|
|
|
|
//emit clearSelectionSignal();
|
|
pos.ry() += _scrollBar->value();
|
|
_iPntSel = _pntSel = pos;
|
|
_actSel = 1; // left mouse button pressed but nothing selected yet.
|
|
|
|
}
|
|
else
|
|
{
|
|
emit mouseSignal( 0, charColumn + 1, charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 0);
|
|
}
|
|
|
|
Filter::HotSpot *spot = _filterChain->hotSpotAt(charLine, charColumn);
|
|
if (spot && spot->type() == Filter::HotSpot::Link)
|
|
spot->activate(QLatin1String("click-action"));
|
|
}
|
|
}
|
|
else if ( ev->button() == Qt::MiddleButton )
|
|
{
|
|
if ( _mouseMarks || (ev->modifiers() & Qt::ShiftModifier) )
|
|
emitSelection(true,ev->modifiers() & Qt::ControlModifier);
|
|
else
|
|
emit mouseSignal( 1, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
|
|
}
|
|
else if ( ev->button() == Qt::RightButton )
|
|
{
|
|
if (_mouseMarks || (ev->modifiers() & Qt::ShiftModifier))
|
|
emit configureRequest(ev->pos());
|
|
else
|
|
emit mouseSignal( 2, charColumn +1, charLine +1 +_scrollBar->value() -_scrollBar->maximum() , 0);
|
|
}
|
|
}
|
|
|
|
QList<QAction*> TerminalDisplay::filterActions(const QPoint& position)
|
|
{
|
|
int charLine, charColumn;
|
|
getCharacterPosition(position,charLine,charColumn);
|
|
|
|
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
|
|
|
|
return spot ? spot->actions() : QList<QAction*>();
|
|
}
|
|
|
|
void TerminalDisplay::hideStaleMouse() const
|
|
{
|
|
if (gs_deadSpot.x() > -1) // we already have a dead spot
|
|
return;
|
|
if (gs_futureDeadSpot.x() < 0) // that's not expected nor gonna end well
|
|
return;
|
|
if (!isUnderMouse()) // we don't care about the mouse
|
|
return;
|
|
if (QGuiApplication::focusWindow() && QGuiApplication::focusWindow() != window()) // some other app window has the focus
|
|
return;
|
|
if (_scrollBar->underMouse()) // the mouse is over the scrollbar
|
|
return;
|
|
gs_deadSpot = gs_futureDeadSpot;
|
|
QApplication::setOverrideCursor(Qt::BlankCursor);
|
|
}
|
|
|
|
void TerminalDisplay::autoHideMouseAfter(int delay)
|
|
{
|
|
if (delay > -1 && !_hideMouseTimer)
|
|
{
|
|
_hideMouseTimer = std::make_shared<QTimer>();
|
|
_hideMouseTimer->setSingleShot(true);
|
|
}
|
|
if ((_mouseAutohideDelay < 0) == (delay < 0))
|
|
{
|
|
_mouseAutohideDelay = delay;
|
|
return;
|
|
}
|
|
if (delay > -1)
|
|
connect(_hideMouseTimer.get(), &QTimer::timeout, this, &TerminalDisplay::hideStaleMouse);
|
|
else if (_hideMouseTimer)
|
|
disconnect(_hideMouseTimer.get(), &QTimer::timeout, this, &TerminalDisplay::hideStaleMouse);
|
|
_mouseAutohideDelay = delay;
|
|
}
|
|
|
|
void TerminalDisplay::mouseMoveEvent(QMouseEvent* ev)
|
|
{
|
|
// unclutter
|
|
if (_mouseAutohideDelay > -1) {
|
|
if (gs_deadSpot.x() > -1 && (ev->pos() - gs_deadSpot).manhattanLength() > 8)
|
|
{
|
|
gs_deadSpot = QPoint(-1,-1);
|
|
QApplication::restoreOverrideCursor();
|
|
}
|
|
gs_futureDeadSpot = ev->position().toPoint();
|
|
Q_ASSERT(_hideMouseTimer);
|
|
_hideMouseTimer->start(_mouseAutohideDelay);
|
|
}
|
|
|
|
int charLine = 0;
|
|
int charColumn = 0;
|
|
int leftMargin = _leftBaseMargin
|
|
+ ((_scrollbarLocation == QTermWidget::ScrollBarLeft
|
|
&& !_scrollBar->isTransient())
|
|
? _scrollBar->width() : 0);
|
|
|
|
getCharacterPosition(ev->position().toPoint(),charLine,charColumn);
|
|
|
|
// handle filters
|
|
// change link hot-spot appearance on mouse-over
|
|
Filter::HotSpot* spot = _filterChain->hotSpotAt(charLine,charColumn);
|
|
if ( spot && spot->type() == Filter::HotSpot::Link)
|
|
{
|
|
QRegion previousHotspotArea = _mouseOverHotspotArea;
|
|
_mouseOverHotspotArea = QRegion();
|
|
QRect r;
|
|
if (spot->startLine()==spot->endLine()) {
|
|
r.setCoords( spot->startColumn()*_fontWidth + leftMargin,
|
|
spot->startLine()*_fontHeight + _topBaseMargin,
|
|
spot->endColumn()*_fontWidth + leftMargin,
|
|
(spot->endLine()+1)*_fontHeight - 1 + _topBaseMargin );
|
|
_mouseOverHotspotArea |= r;
|
|
} else {
|
|
r.setCoords( spot->startColumn()*_fontWidth + leftMargin,
|
|
spot->startLine()*_fontHeight + _topBaseMargin,
|
|
_columns*_fontWidth - 1 + leftMargin,
|
|
(spot->startLine()+1)*_fontHeight + _topBaseMargin );
|
|
_mouseOverHotspotArea |= r;
|
|
for ( int line = spot->startLine()+1 ; line < spot->endLine() ; line++ ) {
|
|
r.setCoords( 0*_fontWidth + leftMargin,
|
|
line*_fontHeight + _topBaseMargin,
|
|
_columns*_fontWidth + leftMargin,
|
|
(line+1)*_fontHeight + _topBaseMargin );
|
|
_mouseOverHotspotArea |= r;
|
|
}
|
|
r.setCoords( 0*_fontWidth + leftMargin,
|
|
spot->endLine()*_fontHeight + _topBaseMargin,
|
|
spot->endColumn()*_fontWidth + leftMargin,
|
|
(spot->endLine()+1)*_fontHeight + _topBaseMargin );
|
|
_mouseOverHotspotArea |= r;
|
|
}
|
|
update( _mouseOverHotspotArea | previousHotspotArea );
|
|
}
|
|
else if ( !_mouseOverHotspotArea.isEmpty() )
|
|
{
|
|
update( _mouseOverHotspotArea );
|
|
// set hotspot area to an invalid rectangle
|
|
_mouseOverHotspotArea = QRegion();
|
|
}
|
|
|
|
// for auto-hiding the cursor, we need mouseTracking
|
|
if (ev->buttons() == Qt::NoButton ) return;
|
|
|
|
// if the terminal is interested in mouse movements
|
|
// then emit a mouse movement signal, unless the shift
|
|
// key is being held down, which overrides this.
|
|
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
|
|
{
|
|
int button = 3;
|
|
if (ev->buttons() & Qt::LeftButton)
|
|
button = 0;
|
|
if (ev->buttons() & Qt::MiddleButton)
|
|
button = 1;
|
|
if (ev->buttons() & Qt::RightButton)
|
|
button = 2;
|
|
|
|
|
|
emit mouseSignal( button,
|
|
charColumn + 1,
|
|
charLine + 1 +_scrollBar->value() -_scrollBar->maximum(),
|
|
1 );
|
|
|
|
return;
|
|
}
|
|
|
|
if (dragInfo.state == diPending)
|
|
{
|
|
// we had a mouse down, but haven't confirmed a drag yet
|
|
// if the mouse has moved sufficiently, we will confirm
|
|
|
|
// int distance = KGlobalSettings::dndEventDelay();
|
|
int distance = QApplication::startDragDistance();
|
|
if ( ev->position().x() > dragInfo.start.x() + distance || ev->position().x() < dragInfo.start.x() - distance ||
|
|
ev->position().y() > dragInfo.start.y() + distance || ev->position().y() < dragInfo.start.y() - distance)
|
|
{
|
|
// we've left the drag square, we can start a real drag operation now
|
|
emit isBusySelecting(false); // Ok.. we can breath again.
|
|
|
|
doDrag();
|
|
}
|
|
return;
|
|
}
|
|
else if (dragInfo.state == diDragging)
|
|
{
|
|
// this isn't technically needed because mouseMoveEvent is suppressed during
|
|
// Qt drag operations, replaced by dragMoveEvent
|
|
return;
|
|
}
|
|
|
|
if (_actSel == 0) return;
|
|
|
|
// don't extend selection while pasting
|
|
if (ev->buttons() & Qt::MiddleButton) return;
|
|
|
|
extendSelection(ev->position().toPoint());
|
|
}
|
|
|
|
void TerminalDisplay::extendSelection( const QPoint& position )
|
|
{
|
|
QPoint pos = position;
|
|
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
//if ( !contentsRect().contains(ev->pos()) ) return;
|
|
QPoint tL = contentsRect().topLeft();
|
|
int tLx = tL.x();
|
|
int tLy = tL.y();
|
|
int scroll = _scrollBar->value();
|
|
|
|
// we're in the process of moving the mouse with the left button pressed
|
|
// the mouse cursor will kept caught within the bounds of the text in
|
|
// this widget.
|
|
|
|
int linesBeyondWidget = 0;
|
|
|
|
QRect textBounds(tLx + _leftMargin,
|
|
tLy + _topMargin,
|
|
_usedColumns*_fontWidth-1,
|
|
_usedLines*_fontHeight-1);
|
|
|
|
// Adjust position within text area bounds.
|
|
QPoint oldpos = pos;
|
|
|
|
pos.setX( qBound(textBounds.left(),pos.x(),textBounds.right()) );
|
|
pos.setY( qBound(textBounds.top(),pos.y(),textBounds.bottom()) );
|
|
|
|
if ( oldpos.y() > textBounds.bottom() )
|
|
{
|
|
linesBeyondWidget = (oldpos.y()-textBounds.bottom()) / _fontHeight;
|
|
_scrollBar->setValue(_scrollBar->value()+linesBeyondWidget+1); // scrollforward
|
|
}
|
|
if ( oldpos.y() < textBounds.top() )
|
|
{
|
|
linesBeyondWidget = (textBounds.top()-oldpos.y()) / _fontHeight;
|
|
_scrollBar->setValue(_scrollBar->value()-linesBeyondWidget-1); // history
|
|
}
|
|
|
|
int charColumn = 0;
|
|
int charLine = 0;
|
|
getCharacterPosition(pos,charLine,charColumn);
|
|
|
|
QPoint here = QPoint(charColumn,charLine); //QPoint((pos.x()-tLx-_leftMargin+(_fontWidth/2))/_fontWidth,(pos.y()-tLy-_topMargin)/_fontHeight);
|
|
QPoint ohere;
|
|
QPoint _iPntSelCorr = _iPntSel;
|
|
_iPntSelCorr.ry() -= _scrollBar->value();
|
|
QPoint _pntSelCorr = _pntSel;
|
|
_pntSelCorr.ry() -= _scrollBar->value();
|
|
bool swapping = false;
|
|
|
|
if ( _wordSelectionMode )
|
|
{
|
|
// Extend to word boundaries
|
|
int i;
|
|
QChar selClass;
|
|
|
|
bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
|
|
( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
|
|
bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
|
|
( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
|
|
swapping = left_not_right != old_left_not_right;
|
|
|
|
// Find left (left_not_right ? from here : from start)
|
|
QPoint left = left_not_right ? here : _iPntSelCorr;
|
|
i = loc(left.x(),left.y());
|
|
if (i>=0 && i<=_imageSize) {
|
|
selClass = charClass(_image[i]);
|
|
while ( ((left.x()>0) || (left.y()>0 && (_lineProperties[left.y()-1] & LINE_WRAPPED) ))
|
|
&& charClass(_image[i-1]) == selClass )
|
|
{ i--; if (left.x()>0) left.rx()--; else {left.rx()=_usedColumns-1; left.ry()--;} }
|
|
}
|
|
|
|
// Find left (left_not_right ? from start : from here)
|
|
QPoint right = left_not_right ? _iPntSelCorr : here;
|
|
i = loc(right.x(),right.y());
|
|
if (i>=0 && i<=_imageSize) {
|
|
selClass = charClass(_image[i]);
|
|
while( ((right.x()<_usedColumns-1) || (right.y()<_usedLines-1 && (_lineProperties[right.y()] & LINE_WRAPPED) ))
|
|
&& charClass(_image[i+1]) == selClass )
|
|
{ i++; if (right.x()<_usedColumns-1) right.rx()++; else {right.rx()=0; right.ry()++; } }
|
|
}
|
|
|
|
// Pick which is start (ohere) and which is extension (here)
|
|
if ( left_not_right )
|
|
{
|
|
here = left; ohere = right;
|
|
}
|
|
else
|
|
{
|
|
here = right; ohere = left;
|
|
}
|
|
ohere.rx()++;
|
|
}
|
|
|
|
if ( _lineSelectionMode )
|
|
{
|
|
// Extend to complete line
|
|
bool above_not_below = ( here.y() < _iPntSelCorr.y() );
|
|
|
|
QPoint above = above_not_below ? here : _iPntSelCorr;
|
|
QPoint below = above_not_below ? _iPntSelCorr : here;
|
|
|
|
while (above.y()>0 && (_lineProperties[above.y()-1] & LINE_WRAPPED) )
|
|
above.ry()--;
|
|
while (below.y()<_usedLines-1 && (_lineProperties[below.y()] & LINE_WRAPPED) )
|
|
below.ry()++;
|
|
|
|
above.setX(0);
|
|
below.setX(_usedColumns-1);
|
|
|
|
// Pick which is start (ohere) and which is extension (here)
|
|
if ( above_not_below )
|
|
{
|
|
here = above; ohere = below;
|
|
}
|
|
else
|
|
{
|
|
here = below; ohere = above;
|
|
}
|
|
|
|
QPoint newSelBegin = QPoint( ohere.x(), ohere.y() );
|
|
swapping = !(_tripleSelBegin==newSelBegin);
|
|
_tripleSelBegin = newSelBegin;
|
|
|
|
ohere.rx()++;
|
|
}
|
|
|
|
int offset = 0;
|
|
if ( !_wordSelectionMode && !_lineSelectionMode )
|
|
{
|
|
int i;
|
|
QChar selClass;
|
|
|
|
bool left_not_right = ( here.y() < _iPntSelCorr.y() ||
|
|
( here.y() == _iPntSelCorr.y() && here.x() < _iPntSelCorr.x() ) );
|
|
bool old_left_not_right = ( _pntSelCorr.y() < _iPntSelCorr.y() ||
|
|
( _pntSelCorr.y() == _iPntSelCorr.y() && _pntSelCorr.x() < _iPntSelCorr.x() ) );
|
|
swapping = left_not_right != old_left_not_right;
|
|
|
|
// Find left (left_not_right ? from here : from start)
|
|
QPoint left = left_not_right ? here : _iPntSelCorr;
|
|
|
|
// Find left (left_not_right ? from start : from here)
|
|
QPoint right = left_not_right ? _iPntSelCorr : here;
|
|
if ( right.x() > 0 && !_columnSelectionMode )
|
|
{
|
|
i = loc(right.x(),right.y());
|
|
if (i>=0 && i<=_imageSize) {
|
|
selClass = charClass(_image[i-1]);
|
|
/* if (selClass == ' ')
|
|
{
|
|
while ( right.x() < _usedColumns-1 && charClass(_image[i+1]) == selClass && (right.y()<_usedLines-1) &&
|
|
!(_lineProperties[right.y()] & LINE_WRAPPED))
|
|
{ i++; right.rx()++; }
|
|
if (right.x() < _usedColumns-1)
|
|
right = left_not_right ? _iPntSelCorr : here;
|
|
else
|
|
right.rx()++; // will be balanced later because of offset=-1;
|
|
}*/
|
|
}
|
|
}
|
|
|
|
// Pick which is start (ohere) and which is extension (here)
|
|
if ( left_not_right )
|
|
{
|
|
here = left; ohere = right; offset = 0;
|
|
}
|
|
else
|
|
{
|
|
here = right; ohere = left; offset = -1;
|
|
}
|
|
}
|
|
|
|
if ((here == _pntSelCorr) && (scroll == _scrollBar->value())) return; // not moved
|
|
|
|
if (here == ohere) return; // It's not left, it's not right.
|
|
|
|
if ( _actSel < 2 || swapping )
|
|
{
|
|
if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
|
|
{
|
|
_screenWindow->setSelectionStart( ohere.x() , ohere.y() , true );
|
|
}
|
|
else
|
|
{
|
|
_screenWindow->setSelectionStart( ohere.x()-1-offset , ohere.y() , false );
|
|
}
|
|
|
|
}
|
|
|
|
_actSel = 2; // within selection
|
|
_pntSel = here;
|
|
_pntSel.ry() += _scrollBar->value();
|
|
|
|
if ( _columnSelectionMode && !_lineSelectionMode && !_wordSelectionMode )
|
|
{
|
|
_screenWindow->setSelectionEnd( here.x() , here.y() );
|
|
}
|
|
else
|
|
{
|
|
_screenWindow->setSelectionEnd( here.x()+offset , here.y() );
|
|
}
|
|
|
|
}
|
|
|
|
void TerminalDisplay::mouseReleaseEvent(QMouseEvent* ev)
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
int charLine;
|
|
int charColumn;
|
|
getCharacterPosition(ev->pos(),charLine,charColumn);
|
|
|
|
if ( ev->button() == Qt::LeftButton)
|
|
{
|
|
emit isBusySelecting(false);
|
|
if(dragInfo.state == diPending)
|
|
{
|
|
// We had a drag event pending but never confirmed. Kill selection
|
|
_screenWindow->clearSelection();
|
|
//emit clearSelectionSignal();
|
|
}
|
|
else
|
|
{
|
|
if ( _actSel > 1 )
|
|
{
|
|
setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
|
|
}
|
|
|
|
_actSel = 0;
|
|
|
|
//FIXME: emits a release event even if the mouse is
|
|
// outside the range. The procedure used in `mouseMoveEvent'
|
|
// applies here, too.
|
|
|
|
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
|
|
emit mouseSignal( 0,
|
|
charColumn + 1,
|
|
charLine + 1 +_scrollBar->value() -_scrollBar->maximum() , 2);
|
|
}
|
|
dragInfo.state = diNone;
|
|
}
|
|
|
|
|
|
if ( !_mouseMarks &&
|
|
((ev->button() == Qt::RightButton && !(ev->modifiers() & Qt::ShiftModifier))
|
|
|| ev->button() == Qt::MiddleButton) )
|
|
{
|
|
emit mouseSignal( ev->button() == Qt::MiddleButton ? 1 : 2,
|
|
charColumn + 1,
|
|
charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
|
|
2);
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::getCharacterPosition(const QPointF& widgetPoint,int& line,int& column) const
|
|
{
|
|
line = (widgetPoint.y()-contentsRect().top()-_topMargin) / _fontHeight;
|
|
if (line < 0)
|
|
line = 0;
|
|
if (line >= _usedLines)
|
|
line = _usedLines - 1;
|
|
|
|
int x = widgetPoint.x() + _fontWidth / 2 - contentsRect().left() - _leftMargin;
|
|
if ( _fixedFont )
|
|
column = x / _fontWidth;
|
|
else
|
|
{
|
|
column = 0;
|
|
while(column + 1 < _usedColumns && x > textWidth(0, column + 1, line))
|
|
column++;
|
|
}
|
|
|
|
if ( column < 0 )
|
|
column = 0;
|
|
|
|
// the column value returned can be equal to _usedColumns, which
|
|
// is the position just after the last character displayed in a line.
|
|
//
|
|
// this is required so that the user can select characters in the right-most
|
|
// column (or left-most for right-to-left input)
|
|
if ( column > _usedColumns )
|
|
column = _usedColumns;
|
|
}
|
|
|
|
void TerminalDisplay::updateFilters()
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
processFilters();
|
|
}
|
|
|
|
void TerminalDisplay::updateLineProperties()
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
_lineProperties = _screenWindow->getLineProperties();
|
|
}
|
|
|
|
void TerminalDisplay::mouseDoubleClickEvent(QMouseEvent* ev)
|
|
{
|
|
if ( ev->button() != Qt::LeftButton) return;
|
|
if ( !_screenWindow ) return;
|
|
|
|
int charLine = 0;
|
|
int charColumn = 0;
|
|
|
|
getCharacterPosition(ev->pos(),charLine,charColumn);
|
|
|
|
QPoint pos(charColumn,charLine);
|
|
|
|
// pass on double click as two clicks.
|
|
if (!_mouseMarks && !(ev->modifiers() & Qt::ShiftModifier))
|
|
{
|
|
// Send just _ONE_ click event, since the first click of the double click
|
|
// was already sent by the click handler
|
|
emit mouseSignal( 0,
|
|
pos.x()+1,
|
|
pos.y()+1 +_scrollBar->value() -_scrollBar->maximum(),
|
|
0 ); // left button
|
|
return;
|
|
}
|
|
|
|
_screenWindow->clearSelection();
|
|
_iPntSel = pos;
|
|
_iPntSel.ry() += _scrollBar->value();
|
|
|
|
_wordSelectionMode = true;
|
|
|
|
QPoint bgnSel;
|
|
QPoint endSel;
|
|
findWordBounds(pos, bgnSel, endSel);
|
|
|
|
_screenWindow->setSelectionStart( bgnSel.x() , bgnSel.y() , false );
|
|
|
|
_actSel = 2; // within selection
|
|
|
|
_screenWindow->setSelectionEnd( endSel.x() , endSel.y() );
|
|
|
|
setSelection( _screenWindow->selectedText(_preserveLineBreaks) );
|
|
|
|
_possibleTripleClick=true;
|
|
|
|
QTimer::singleShot(QApplication::doubleClickInterval(),this,
|
|
SLOT(tripleClickTimeout()));
|
|
}
|
|
|
|
void TerminalDisplay::findWordBounds(const QPoint &pos, QPoint &bgnSel, QPoint &endSel) const
|
|
{
|
|
bgnSel = pos;
|
|
endSel = pos;
|
|
int i = loc(bgnSel.x(),bgnSel.y());
|
|
|
|
QChar selClass = charClass(_image[i]);
|
|
|
|
// find the start of the word
|
|
int x = bgnSel.x();
|
|
while ( ((x>0) || (bgnSel.y()>0 && (_lineProperties[bgnSel.y()-1] & LINE_WRAPPED) ))
|
|
&& charClass(_image[i-1]) == selClass )
|
|
{
|
|
i--;
|
|
if (x>0)
|
|
x--;
|
|
else
|
|
{
|
|
x=_usedColumns-1;
|
|
bgnSel.ry()--;
|
|
}
|
|
}
|
|
|
|
bgnSel.setX(x);
|
|
|
|
// find the end of the word
|
|
i = loc( endSel.x(), endSel.y() );
|
|
x = endSel.x();
|
|
while( ((x<_usedColumns-1) || (endSel.y()<_usedLines-1 && (_lineProperties[endSel.y()] & LINE_WRAPPED) ))
|
|
&& charClass(_image[i+1]) == selClass )
|
|
{
|
|
i++;
|
|
if (x<_usedColumns-1)
|
|
x++;
|
|
else
|
|
{
|
|
x=0;
|
|
endSel.ry()++;
|
|
}
|
|
}
|
|
|
|
endSel.setX(x);
|
|
|
|
// In word selection mode don't select @ (64) if at end of word.
|
|
if (QChar(_image[i].character) == QLatin1Char('@') &&
|
|
endSel.x() - bgnSel.x() > 0 &&
|
|
(_image[i].rendition & RE_EXTENDED_CHAR) == 0)
|
|
{
|
|
endSel.setX( x - 1 );
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::getCharacterCellAt(const QPointF &point, int &line, int &column) const
|
|
{
|
|
getCharacterPosition(QPointF(point.x() - _fontWidth / 2.0, point.y()), line, column);
|
|
column = qBound(0, column, qMax(0, _usedColumns - 1));
|
|
}
|
|
|
|
bool TerminalDisplay::isSelectedAt(qreal x, qreal y)
|
|
{
|
|
if (!_screenWindow)
|
|
return false;
|
|
|
|
int charLine = 0;
|
|
int charColumn = 0;
|
|
getCharacterCellAt(QPointF(x, y), charLine, charColumn);
|
|
return _screenWindow->isSelected(charColumn, charLine);
|
|
}
|
|
|
|
bool TerminalDisplay::selectWordAt(qreal x, qreal y)
|
|
{
|
|
if (!_screenWindow || !_image)
|
|
return false;
|
|
|
|
int charLine = 0;
|
|
int charColumn = 0;
|
|
getCharacterCellAt(QPointF(x, y), charLine, charColumn);
|
|
|
|
if (QChar(_image[loc(charColumn, charLine)].character).isSpace())
|
|
return false;
|
|
|
|
QPoint bgnSel;
|
|
QPoint endSel;
|
|
findWordBounds(QPoint(charColumn, charLine), bgnSel, endSel);
|
|
|
|
_screenWindow->clearSelection();
|
|
_screenWindow->setSelectionStart(bgnSel.x(), bgnSel.y(), false);
|
|
_screenWindow->setSelectionEnd(endSel.x(), endSel.y());
|
|
setSelection(_screenWindow->selectedText(_preserveLineBreaks));
|
|
return true;
|
|
}
|
|
|
|
int TerminalDisplay::wheelLines(QWheelEvent *ev)
|
|
{
|
|
// Touchpads report pixels: one line per line of text scrolled.
|
|
if (!ev->pixelDelta().isNull() && _fontHeight > 0) {
|
|
_wheelPixelRemainder += ev->pixelDelta().y();
|
|
const int lines = _wheelPixelRemainder / _fontHeight;
|
|
_wheelPixelRemainder -= lines * _fontHeight;
|
|
return lines;
|
|
}
|
|
|
|
// A wheel notch is 120; high-resolution wheels send fractions of one.
|
|
_wheelAngleRemainder += ev->angleDelta().y() * QApplication::wheelScrollLines();
|
|
const int lines = _wheelAngleRemainder / 120;
|
|
_wheelAngleRemainder -= lines * 120;
|
|
return lines;
|
|
}
|
|
|
|
void TerminalDisplay::wheelEvent( QWheelEvent* ev )
|
|
{
|
|
// A touchpad drags the scrollback by the pixel, coasts and stretches at the ends.
|
|
const bool precise = ev->phase() != Qt::NoScrollPhase || !ev->pixelDelta().isNull();
|
|
if (_mouseMarks && precise && _scrollBar->maximum() > 0)
|
|
{
|
|
_scroller->handleWheel(ev);
|
|
return;
|
|
}
|
|
|
|
if (ev->angleDelta().y() == 0)
|
|
return;
|
|
|
|
// if the terminal program is not interested mouse events
|
|
// then send the event to the scrollbar if the slider has room to move
|
|
// or otherwise send simulated up / down key presses to the terminal program
|
|
// for the benefit of programs such as 'less'
|
|
if ( _mouseMarks )
|
|
{
|
|
const int lines = wheelLines(ev);
|
|
if (lines == 0)
|
|
return;
|
|
|
|
bool canScroll = _scrollBar->maximum() > 0;
|
|
if (canScroll)
|
|
{
|
|
_scrollBar->setValue(_scrollBar->value() - lines);
|
|
}
|
|
else
|
|
{
|
|
// A program without mouse reporting, such as less, scrolls on Up / Down.
|
|
int key = lines > 0 ? Qt::Key_Up : Qt::Key_Down;
|
|
QKeyEvent keyScrollEvent(QEvent::KeyPress,key,Qt::NoModifier);
|
|
|
|
for (int i=0;i<abs(lines);i++)
|
|
emit keyPressedSignal(&keyScrollEvent, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// terminal program wants notification of mouse activity
|
|
|
|
int charLine;
|
|
int charColumn;
|
|
getCharacterPosition( ev->position() , charLine , charColumn );
|
|
|
|
emit mouseSignal( ev->angleDelta().y() > 0 ? 4 : 5,
|
|
charColumn + 1,
|
|
charLine + 1 +_scrollBar->value() -_scrollBar->maximum() ,
|
|
0);
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::tripleClickTimeout()
|
|
{
|
|
_possibleTripleClick=false;
|
|
}
|
|
|
|
void TerminalDisplay::mouseTripleClickEvent(QMouseEvent* ev)
|
|
{
|
|
if ( !_screenWindow ) return;
|
|
|
|
int charLine;
|
|
int charColumn;
|
|
getCharacterPosition(ev->pos(),charLine,charColumn);
|
|
_iPntSel = QPoint(charColumn,charLine);
|
|
|
|
_screenWindow->clearSelection();
|
|
|
|
_lineSelectionMode = true;
|
|
_wordSelectionMode = false;
|
|
|
|
_actSel = 2; // within selection
|
|
emit isBusySelecting(true); // Keep it steady...
|
|
|
|
while (_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
|
|
_iPntSel.ry()--;
|
|
|
|
if (_tripleClickMode == SelectForwardsFromCursor) {
|
|
// find word boundary start
|
|
int i = loc(_iPntSel.x(),_iPntSel.y());
|
|
QChar selClass = charClass(_image[i]);
|
|
int x = _iPntSel.x();
|
|
|
|
while ( ((x>0) ||
|
|
(_iPntSel.y()>0 && (_lineProperties[_iPntSel.y()-1] & LINE_WRAPPED) )
|
|
)
|
|
&& charClass(_image[i-1]) == selClass )
|
|
{
|
|
i--;
|
|
if (x>0)
|
|
x--;
|
|
else
|
|
{
|
|
x=_columns-1;
|
|
_iPntSel.ry()--;
|
|
}
|
|
}
|
|
|
|
_screenWindow->setSelectionStart( x , _iPntSel.y() , false );
|
|
_tripleSelBegin = QPoint( x, _iPntSel.y() );
|
|
}
|
|
else if (_tripleClickMode == SelectWholeLine) {
|
|
_screenWindow->setSelectionStart( 0 , _iPntSel.y() , false );
|
|
_tripleSelBegin = QPoint( 0, _iPntSel.y() );
|
|
}
|
|
|
|
while (_iPntSel.y()<_lines-1 && (_lineProperties[_iPntSel.y()] & LINE_WRAPPED) )
|
|
_iPntSel.ry()++;
|
|
|
|
_screenWindow->setSelectionEnd( _columns - 1 , _iPntSel.y() );
|
|
|
|
setSelection(_screenWindow->selectedText(_preserveLineBreaks));
|
|
|
|
_iPntSel.ry() += _scrollBar->value();
|
|
}
|
|
|
|
|
|
bool TerminalDisplay::focusNextPrevChild( bool next )
|
|
{
|
|
if (next)
|
|
return false; // This disables changing the active part in konqueror
|
|
// when pressing Tab
|
|
return false;
|
|
//return QWidget::focusNextPrevChild( next );
|
|
}
|
|
|
|
|
|
QChar TerminalDisplay::charClass(const Character &ch) const
|
|
{
|
|
if (ch.rendition & RE_EXTENDED_CHAR)
|
|
{
|
|
ushort extendedCharLength = 0;
|
|
const uint* chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
|
|
if (chars && extendedCharLength > 0)
|
|
{
|
|
std::wstring str;
|
|
for (ushort nchar = 0; nchar < extendedCharLength; nchar++)
|
|
{
|
|
str.push_back(chars[nchar]);
|
|
}
|
|
const QString s = QString::fromStdWString(str);
|
|
if (_wordCharacters.contains(s, Qt::CaseInsensitive))
|
|
return QLatin1Char('a');
|
|
bool allLetterOrNumber = true;
|
|
for (int i = 0; allLetterOrNumber && i < s.size(); ++i)
|
|
allLetterOrNumber = s.at(i).isLetterOrNumber();
|
|
return allLetterOrNumber ? QLatin1Char('a') : s.at(0);
|
|
}
|
|
return QChar(0);
|
|
}
|
|
else
|
|
{
|
|
const QChar qch(ch.character);
|
|
if (qch.isSpace())
|
|
return QLatin1Char(' ');
|
|
if (qch.isLetterOrNumber() || _wordCharacters.contains(qch, Qt::CaseInsensitive ))
|
|
return QLatin1Char('a');
|
|
return qch;
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::setWordCharacters(const QString& wc)
|
|
{
|
|
_wordCharacters = wc;
|
|
}
|
|
|
|
void TerminalDisplay::setUsesMouse(bool on)
|
|
{
|
|
if (_mouseMarks != on) {
|
|
_mouseMarks = on;
|
|
setCursor( _mouseMarks ? Qt::IBeamCursor : Qt::ArrowCursor );
|
|
emit usesMouseChanged();
|
|
}
|
|
}
|
|
bool TerminalDisplay::usesMouse() const
|
|
{
|
|
return _mouseMarks;
|
|
}
|
|
|
|
void TerminalDisplay::setBracketedPasteMode(bool on)
|
|
{
|
|
_bracketedPasteMode = on;
|
|
}
|
|
bool TerminalDisplay::bracketedPasteMode() const
|
|
{
|
|
return _bracketedPasteMode;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Clipboard */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
#undef KeyPress
|
|
|
|
void TerminalDisplay::emitSelection(bool useXselection,bool appendReturn)
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
// Paste Clipboard by simulating keypress events
|
|
QString text = QApplication::clipboard()->text(useXselection ? QClipboard::Selection :
|
|
QClipboard::Clipboard);
|
|
if ( ! text.isEmpty() )
|
|
{
|
|
text.replace(QLatin1String("\r\n"), QLatin1String("\n"));
|
|
text.replace(QLatin1Char('\n'), QLatin1Char('\r'));
|
|
|
|
if (_trimPastedTrailingNewlines) {
|
|
static const QRegularExpression regexp{u"\\r+$"_s};
|
|
text.replace(regexp, QString());
|
|
}
|
|
|
|
if (_confirmMultilinePaste && text.contains(QLatin1Char('\r'))) {
|
|
_pendingPaste = text;
|
|
_pendingPasteAppendReturn = appendReturn;
|
|
_pendingPasteIsDrop = false;
|
|
emit multilinePasteRequested(QString(text).replace(QLatin1Char('\r'), QLatin1Char('\n')));
|
|
return;
|
|
}
|
|
|
|
sendPaste(text, appendReturn);
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::sendPaste(QString text, bool appendReturn)
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
bracketText(text);
|
|
|
|
// appendReturn is intentionally handled _after_ enclosing texts with brackets as
|
|
// that feature is used to allow execution of commands immediately after paste.
|
|
// Ref: https://bugs.kde.org/show_bug.cgi?id=16179
|
|
// Ref: https://github.com/KDE/konsole/commit/83d365f2ebfe2e659c1e857a2f5f247c556ab571
|
|
if(appendReturn) {
|
|
text.append(QLatin1Char('\r'));
|
|
}
|
|
|
|
QKeyEvent e(QEvent::KeyPress, 0, Qt::NoModifier, text);
|
|
emit keyPressedSignal(&e, true); // expose as a big fat keypress event
|
|
|
|
_screenWindow->clearSelection();
|
|
|
|
switch(mMotionAfterPasting)
|
|
{
|
|
case MoveStartScreenWindow:
|
|
// Temporarily stop tracking output, or pasting contents triggers
|
|
// ScreenWindow::notifyOutputChanged() and the latter scrolls the
|
|
// terminal to the last line. It will be re-enabled when needed
|
|
// (e.g., scrolling to the last line).
|
|
_screenWindow->setTrackOutput(false);
|
|
_screenWindow->scrollTo(0);
|
|
break;
|
|
case MoveEndScreenWindow:
|
|
scrollToEnd();
|
|
break;
|
|
case NoMoveScreenWindow:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::bracketText(QString& text) const
|
|
{
|
|
if (bracketedPasteMode() && !_disabledBracketedPasteMode)
|
|
{
|
|
text.prepend(QLatin1String("\033[200~"));
|
|
text.append(QLatin1String("\033[201~"));
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::confirmPaste()
|
|
{
|
|
if (_pendingPaste.isEmpty())
|
|
return;
|
|
|
|
const QString text = _pendingPaste;
|
|
_pendingPaste.clear();
|
|
|
|
if (_pendingPasteIsDrop)
|
|
emit sendStringToEmu(text.toLocal8Bit().constData());
|
|
else
|
|
sendPaste(text, _pendingPasteAppendReturn);
|
|
}
|
|
|
|
void TerminalDisplay::setSelection(const QString& t)
|
|
{
|
|
if (QApplication::clipboard()->supportsSelection())
|
|
{
|
|
QApplication::clipboard()->setText(t, QClipboard::Selection);
|
|
}
|
|
}
|
|
|
|
bool TerminalDisplay::hasSelection() const
|
|
{
|
|
return _screenWindow && !_screenWindow->selectedText(false).isEmpty();
|
|
}
|
|
|
|
void TerminalDisplay::selectAll()
|
|
{
|
|
if (!_screenWindow)
|
|
return;
|
|
|
|
_screenWindow->selectAll();
|
|
setSelection(_screenWindow->selectedText(_preserveLineBreaks));
|
|
}
|
|
|
|
QString TerminalDisplay::selectedText() const
|
|
{
|
|
return _screenWindow ? _screenWindow->selectedText(_preserveLineBreaks) : QString();
|
|
}
|
|
|
|
UrlFilter::HotSpot *TerminalDisplay::linkHotSpotAt(TerminalImageFilterChain &chain, qreal x, qreal y)
|
|
{
|
|
if (!_image)
|
|
return nullptr;
|
|
|
|
int charLine = 0;
|
|
int charColumn = 0;
|
|
getCharacterCellAt(QPointF(x, y), charLine, charColumn);
|
|
|
|
// Matched on demand against the text on display: the display's own filter
|
|
// chain is not refreshed on output in this port, so its hotspots go stale.
|
|
chain.addFilter(new UrlFilter());
|
|
chain.setImage(_image, _lines, _columns, _lineProperties);
|
|
chain.process();
|
|
|
|
return dynamic_cast<UrlFilter::HotSpot *>(chain.hotSpotAt(charLine, charColumn));
|
|
}
|
|
|
|
QString TerminalDisplay::linkAt(qreal x, qreal y)
|
|
{
|
|
TerminalImageFilterChain chain;
|
|
UrlFilter::HotSpot *spot = linkHotSpotAt(chain, x, y);
|
|
return spot ? spot->url().toString() : QString();
|
|
}
|
|
|
|
QVariantList TerminalDisplay::linkUnderlineAt(qreal x, qreal y)
|
|
{
|
|
TerminalImageFilterChain chain;
|
|
UrlFilter::HotSpot *spot = linkHotSpotAt(chain, x, y);
|
|
if (!spot)
|
|
return QVariantList();
|
|
|
|
// Placed like the underline paintFilters() would draw.
|
|
const QFontMetrics metrics(font());
|
|
QVariantList segments;
|
|
for (int line = spot->startLine(); line <= spot->endLine(); ++line) {
|
|
int startColumn = line == spot->startLine() ? spot->startColumn() : 0;
|
|
int endColumn = _columns;
|
|
if (line == spot->endLine()) {
|
|
endColumn = spot->endColumn();
|
|
} else {
|
|
while (endColumn > startColumn && QChar(_image[loc(endColumn - 1, line)].character).isSpace())
|
|
endColumn--;
|
|
}
|
|
|
|
const int bottom = (line + 1) * _fontHeight - 1 + _topMargin;
|
|
const int underline = bottom - metrics.descent() + metrics.underlinePos();
|
|
segments << QVariantMap {
|
|
{ QStringLiteral("x"), startColumn * _fontWidth + _leftBaseMargin },
|
|
{ QStringLiteral("y"), underline },
|
|
{ QStringLiteral("width"), (endColumn - startColumn) * _fontWidth },
|
|
};
|
|
}
|
|
|
|
return segments;
|
|
}
|
|
|
|
void TerminalDisplay::copyClipboard()
|
|
{
|
|
if ( !_screenWindow )
|
|
return;
|
|
|
|
QString text = _screenWindow->selectedText(_preserveLineBreaks);
|
|
if (!text.isEmpty())
|
|
QApplication::clipboard()->setText(text);
|
|
}
|
|
|
|
void TerminalDisplay::pasteClipboard()
|
|
{
|
|
emitSelection(false,false);
|
|
}
|
|
|
|
void TerminalDisplay::pasteSelection()
|
|
{
|
|
emitSelection(true,false);
|
|
}
|
|
|
|
|
|
void TerminalDisplay::setConfirmMultilinePaste(bool confirmMultilinePaste) {
|
|
_confirmMultilinePaste = confirmMultilinePaste;
|
|
}
|
|
|
|
void TerminalDisplay::setTrimPastedTrailingNewlines(bool trimPastedTrailingNewlines) {
|
|
_trimPastedTrailingNewlines = trimPastedTrailingNewlines;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------- */
|
|
/* */
|
|
/* Keyboard */
|
|
/* */
|
|
/* ------------------------------------------------------------------------- */
|
|
|
|
void TerminalDisplay::setFlowControlWarningEnabled( bool enable )
|
|
{
|
|
_flowControlWarningEnabled = enable;
|
|
|
|
// if the dialog is currently visible and the flow control warning has
|
|
// been disabled then hide the dialog
|
|
if (!enable)
|
|
outputSuspended(false);
|
|
}
|
|
|
|
void TerminalDisplay::setMotionAfterPasting(MotionAfterPasting action)
|
|
{
|
|
mMotionAfterPasting = action;
|
|
}
|
|
|
|
int TerminalDisplay::motionAfterPasting()
|
|
{
|
|
return mMotionAfterPasting;
|
|
}
|
|
|
|
void TerminalDisplay::keyPressEvent( QKeyEvent* event )
|
|
{
|
|
_actSel=0; // Key stroke implies a screen update, so TerminalDisplay won't
|
|
// know where the current selection is.
|
|
|
|
if (_hasBlinkingCursor)
|
|
{
|
|
// see TerminalDisplay::setBlinkingCursor
|
|
_blinkCursorTimer->start(std::max(QApplication::cursorFlashTime(), 1000) / 2);
|
|
if (_cursorBlinking)
|
|
blinkCursorEvent();
|
|
else
|
|
_cursorBlinking = false;
|
|
}
|
|
|
|
emit keyPressedSignal(event, false);
|
|
|
|
event->accept();
|
|
}
|
|
|
|
void TerminalDisplay::inputMethodEvent( QInputMethodEvent* event )
|
|
{
|
|
QKeyEvent keyEvent(QEvent::KeyPress,0,Qt::NoModifier,event->commitString());
|
|
emit keyPressedSignal(&keyEvent, false);
|
|
|
|
_inputMethodData.preeditString = event->preeditString().toStdWString();
|
|
update(preeditRect() | _inputMethodData.previousPreeditRect);
|
|
|
|
event->accept();
|
|
}
|
|
|
|
void TerminalDisplay::inputMethodQuery(QInputMethodQueryEvent *event)
|
|
{
|
|
event->setValue(Qt::ImEnabled, true);
|
|
event->setValue(Qt::ImHints, QVariant(Qt::ImhNoPredictiveText | Qt::ImhNoAutoUppercase));
|
|
|
|
// event() answers queries here, so the virtual overload is never asked for
|
|
// the cursor rectangle and the input method popup would not follow the cursor.
|
|
for (const Qt::InputMethodQuery query : {Qt::ImCursorRectangle, Qt::ImCursorPosition, Qt::ImFont}) {
|
|
if (event->queries() & query)
|
|
event->setValue(query, inputMethodQuery(query));
|
|
}
|
|
|
|
event->accept();
|
|
}
|
|
|
|
QVariant TerminalDisplay::inputMethodQuery( Qt::InputMethodQuery query ) const
|
|
{
|
|
const QPoint cursorPos = _screenWindow ? _screenWindow->cursorPosition() : QPoint(0,0);
|
|
switch ( query )
|
|
{
|
|
case Qt::ImCursorRectangle:
|
|
return imageToWidget(QRect(cursorPos.x(),cursorPos.y(),1,1));
|
|
break;
|
|
case Qt::ImFont:
|
|
return font();
|
|
break;
|
|
case Qt::ImCursorPosition:
|
|
// return the cursor position within the current line
|
|
return cursorPos.x();
|
|
break;
|
|
case Qt::ImSurroundingText:
|
|
{
|
|
// return the text from the current line
|
|
QString lineText;
|
|
QTextStream stream(&lineText);
|
|
PlainTextDecoder decoder;
|
|
decoder.begin(&stream);
|
|
decoder.decodeLine(&_image[loc(0,cursorPos.y())],_usedColumns,0);
|
|
decoder.end();
|
|
return lineText;
|
|
}
|
|
break;
|
|
case Qt::ImCurrentSelection:
|
|
return QString();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
bool TerminalDisplay::handleShortcutOverrideEvent(QKeyEvent* keyEvent)
|
|
{
|
|
int modifiers = keyEvent->modifiers();
|
|
|
|
// When a possible shortcut combination is pressed,
|
|
// emit the overrideShortcutCheck() signal to allow the host
|
|
// to decide whether the terminal should override it or not.
|
|
if (modifiers != Qt::NoModifier)
|
|
{
|
|
int modifierCount = 0;
|
|
unsigned int currentModifier = Qt::ShiftModifier;
|
|
|
|
while (currentModifier <= Qt::KeypadModifier)
|
|
{
|
|
if (modifiers & currentModifier)
|
|
modifierCount++;
|
|
currentModifier <<= 1;
|
|
}
|
|
if (modifierCount < 2)
|
|
{
|
|
bool override = false;
|
|
emit overrideShortcutCheck(keyEvent,override);
|
|
if (override)
|
|
{
|
|
keyEvent->accept();
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Override any of the following shortcuts because
|
|
// they are needed by the terminal
|
|
int keyCode = keyEvent->key() | modifiers;
|
|
switch ( keyCode )
|
|
{
|
|
// list is taken from the QLineEdit::event() code
|
|
case Qt::Key_Tab:
|
|
case Qt::Key_Delete:
|
|
case Qt::Key_Home:
|
|
case Qt::Key_End:
|
|
case Qt::Key_Backspace:
|
|
case Qt::Key_Left:
|
|
case Qt::Key_Right:
|
|
case Qt::Key_Escape:
|
|
keyEvent->accept();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TerminalDisplay::event(QEvent* event)
|
|
{
|
|
bool eventHandled = false;
|
|
switch (event->type())
|
|
{
|
|
case QEvent::ShortcutOverride:
|
|
eventHandled = handleShortcutOverrideEvent((QKeyEvent*)event);
|
|
break;
|
|
case QEvent::PaletteChange:
|
|
case QEvent::ApplicationPaletteChange:
|
|
_scrollBar->setPalette( QApplication::palette() );
|
|
break;
|
|
case QEvent::InputMethodQuery:
|
|
inputMethodQuery(static_cast<QInputMethodQueryEvent *>(event));
|
|
eventHandled = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return eventHandled ? true : QQuickItem::event(event);
|
|
}
|
|
|
|
void TerminalDisplay::setBellMode(int mode)
|
|
{
|
|
_bellMode=mode;
|
|
}
|
|
|
|
void TerminalDisplay::enableBell()
|
|
{
|
|
_allowBell = true;
|
|
}
|
|
|
|
void TerminalDisplay::bell(const QString& message)
|
|
{
|
|
if (_bellMode==NoBell) return;
|
|
|
|
//limit the rate at which bells can occur
|
|
//...mainly for sound effects where rapid bells in sequence
|
|
//produce a horrible noise
|
|
if ( _allowBell )
|
|
{
|
|
_allowBell = false;
|
|
QTimer::singleShot(500,this,SLOT(enableBell()));
|
|
|
|
if (_bellMode==SystemBeepBell)
|
|
{
|
|
QApplication::beep();
|
|
}
|
|
else if (_bellMode==NotifyBell)
|
|
{
|
|
emit notifyBell(message);
|
|
}
|
|
else if (_bellMode==VisualBell)
|
|
{
|
|
swapColorTable();
|
|
QTimer::singleShot(200,this,SLOT(swapColorTable()));
|
|
}
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::selectionChanged()
|
|
{
|
|
emit copyAvailable(_screenWindow->selectedText(false).isEmpty() == false);
|
|
}
|
|
|
|
void TerminalDisplay::swapColorTable()
|
|
{
|
|
ColorEntry color = _colorTable[1];
|
|
_colorTable[1]=_colorTable[0];
|
|
_colorTable[0]= color;
|
|
_colorsInverted = !_colorsInverted;
|
|
update();
|
|
}
|
|
|
|
void TerminalDisplay::clearImage()
|
|
{
|
|
// We initialize _image[_imageSize] too. See makeImage()
|
|
for (int i = 0; i <= _imageSize; i++)
|
|
{
|
|
_image[i].character = ' ';
|
|
_image[i].foregroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
|
|
DEFAULT_FORE_COLOR);
|
|
_image[i].backgroundColor = CharacterColor(COLOR_SPACE_DEFAULT,
|
|
DEFAULT_BACK_COLOR);
|
|
_image[i].rendition = DEFAULT_RENDITION;
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::calcGeometry()
|
|
{
|
|
_scrollBar->resize(_scrollBar->sizeHint().width(), contentsRect().height());
|
|
int scrollBarWidth = _scrollBar->isTransient()
|
|
? 0 : _scrollBar->width();
|
|
switch(_scrollbarLocation)
|
|
{
|
|
case QTermWidget::NoScrollBar :
|
|
_leftMargin = _leftBaseMargin;
|
|
_contentWidth = contentsRect().width() - 2 * _leftBaseMargin;
|
|
break;
|
|
case QTermWidget::ScrollBarLeft :
|
|
_leftMargin = _leftBaseMargin + scrollBarWidth;
|
|
_contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth;
|
|
_scrollBar->move(contentsRect().topLeft());
|
|
break;
|
|
case QTermWidget::ScrollBarRight:
|
|
_leftMargin = _leftBaseMargin;
|
|
_contentWidth = contentsRect().width() - 2 * _leftBaseMargin - scrollBarWidth;
|
|
_scrollBar->move(contentsRect().topRight() - QPoint(_scrollBar->width()-1, 0));
|
|
break;
|
|
}
|
|
|
|
_topMargin = _topBaseMargin + qRound(_scrollOffset);
|
|
_contentHeight = contentsRect().height() - 2 * _topBaseMargin + /* mysterious */ 1;
|
|
|
|
if (!_isFixedSize)
|
|
{
|
|
// ensure that display is always at least one column wide
|
|
_columns = qMax(1,_contentWidth / _fontWidth);
|
|
_usedColumns = qMin(_usedColumns,_columns);
|
|
|
|
// ensure that display is always at least one line high
|
|
_lines = qMax(1,_contentHeight / _fontHeight);
|
|
_usedLines = qMin(_usedLines,_lines);
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::makeImage()
|
|
{
|
|
calcGeometry();
|
|
|
|
// confirm that array will be of non-zero size, since the painting code
|
|
// assumes a non-zero array length
|
|
Q_ASSERT( _lines > 0 && _columns > 0 );
|
|
Q_ASSERT( _usedLines <= _lines && _usedColumns <= _columns );
|
|
|
|
// One line more for the line below the window, see _hasLineBelow.
|
|
_imageSize=(_lines+1)*_columns;
|
|
|
|
// We over-commit one character so that we can be more relaxed in dealing with
|
|
// certain boundary conditions: _image[_imageSize] is a valid but unused position
|
|
_image = new Character[_imageSize+1];
|
|
|
|
clearImage();
|
|
}
|
|
|
|
// calculate the needed size, this must be synced with calcGeometry()
|
|
void TerminalDisplay::setSize(int columns, int lines)
|
|
{
|
|
int scrollBarWidth = (_scrollBar->isHidden()
|
|
|| _scrollBar->isTransient())
|
|
? 0 : _scrollBar->sizeHint().width();
|
|
int horizontalMargin = 2 * _leftBaseMargin;
|
|
int verticalMargin = 2 * _topBaseMargin;
|
|
|
|
QSize newSize = QSize( horizontalMargin + scrollBarWidth + (columns * _fontWidth) ,
|
|
verticalMargin + (lines * _fontHeight) );
|
|
|
|
if ( newSize != size() )
|
|
{
|
|
_size = newSize;
|
|
//updateGeometry();
|
|
//TODO Manage geometry change
|
|
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::setFixedSize(int cols, int lins)
|
|
{
|
|
_isFixedSize = true;
|
|
|
|
//ensure that display is at least one line by one column in size
|
|
_columns = qMax(1,cols);
|
|
_lines = qMax(1,lins);
|
|
_usedColumns = qMin(_usedColumns,_columns);
|
|
_usedLines = qMin(_usedLines,_lines);
|
|
|
|
if (_image)
|
|
{
|
|
delete[] _image;
|
|
makeImage();
|
|
}
|
|
setSize(cols, lins);
|
|
//QWidget::setFixedSize(_size);
|
|
}
|
|
|
|
QSize TerminalDisplay::sizeHint() const
|
|
{
|
|
return _size;
|
|
}
|
|
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
/* */
|
|
/* Drag & Drop */
|
|
/* */
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
void TerminalDisplay::dragEnterEvent(QDragEnterEvent* event)
|
|
{
|
|
if (event->mimeData()->hasFormat(QLatin1String("text/plain")))
|
|
event->acceptProposedAction();
|
|
if (event->mimeData()->urls().count())
|
|
event->acceptProposedAction();
|
|
}
|
|
|
|
void TerminalDisplay::dropEvent(QDropEvent* event)
|
|
{
|
|
//KUrl::List urls = KUrl::List::fromMimeData(event->mimeData());
|
|
QList<QUrl> urls = event->mimeData()->urls();
|
|
|
|
QString dropText;
|
|
if (!urls.isEmpty())
|
|
{
|
|
// TODO/FIXME: escape or quote pasted things if necessary...
|
|
qDebug() << "TerminalDisplay: handling urls. It can be broken. Report any errors, please";
|
|
for ( int i = 0 ; i < urls.count() ; i++ )
|
|
{
|
|
//KUrl url = KIO::NetAccess::mostLocalUrl( urls[i] , 0 );
|
|
QUrl url = urls[i];
|
|
|
|
QString urlText;
|
|
|
|
if (url.isLocalFile())
|
|
urlText = url.path();
|
|
else
|
|
urlText = url.toString();
|
|
|
|
// in future it may be useful to be able to insert file names with drag-and-drop
|
|
// without quoting them (this only affects paths with spaces in)
|
|
//urlText = KShell::quoteArg(urlText);
|
|
|
|
QChar q(QLatin1Char('\''));
|
|
dropText += q + QString(urlText).replace(q, QLatin1String("'\\''")) + q;
|
|
dropText += QLatin1Char(' ');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dropText = event->mimeData()->text();
|
|
|
|
dropText.replace(QLatin1String("\r\n"), QLatin1String("\n"));
|
|
dropText.replace(QLatin1Char('\n'), QLatin1Char('\r'));
|
|
if (_trimPastedTrailingNewlines)
|
|
{
|
|
static const QRegularExpression regexp{u"\\r+$"_s};
|
|
dropText.replace(regexp, QString());
|
|
}
|
|
if (_confirmMultilinePaste && dropText.contains(QLatin1Char('\r')))
|
|
{
|
|
_pendingPaste = dropText;
|
|
_pendingPasteIsDrop = true;
|
|
emit multilinePasteRequested(QString(dropText).replace(QLatin1Char('\r'), QLatin1Char('\n')));
|
|
return;
|
|
}
|
|
}
|
|
|
|
emit sendStringToEmu(dropText.toLocal8Bit().constData());
|
|
}
|
|
|
|
void TerminalDisplay::doDrag()
|
|
{
|
|
dragInfo.state = diDragging;
|
|
dragInfo.dragObject = new QDrag(this);
|
|
QMimeData *mimeData = new QMimeData;
|
|
mimeData->setText(QApplication::clipboard()->text(QClipboard::Selection));
|
|
dragInfo.dragObject->setMimeData(mimeData);
|
|
dragInfo.dragObject->exec(Qt::CopyAction);
|
|
// Don't delete the QTextDrag object. Qt will delete it when it's done with it.
|
|
}
|
|
|
|
void TerminalDisplay::outputSuspended(bool suspended)
|
|
{
|
|
//create the label when this function is first called
|
|
|
|
// if (!_outputSuspendedLabel)
|
|
// {
|
|
// //This label includes a link to an English language website
|
|
// //describing the 'flow control' (Xon/Xoff) feature found in almost
|
|
// //all terminal emulators.
|
|
// //If there isn't a suitable article available in the target language the link
|
|
// //can simply be removed.
|
|
// _outputSuspendedLabel = new QLabel( tr("<qt>Output has been "
|
|
// "<a href=\"http://en.wikipedia.org/wiki/Flow_control\">suspended</a>"
|
|
// " by pressing Ctrl+S."
|
|
// " Press <b>Ctrl+Q</b> to resume.</qt>"),
|
|
// this );
|
|
|
|
// QPalette palette(_outputSuspendedLabel->palette());
|
|
// //KColorScheme::adjustBackground(palette,KColorScheme::NeutralBackground);
|
|
// _outputSuspendedLabel->setPalette(palette);
|
|
// _outputSuspendedLabel->setAutoFillBackground(true);
|
|
// _outputSuspendedLabel->setBackgroundRole(QPalette::Base);
|
|
// _outputSuspendedLabel->setFont(QApplication::font());
|
|
// _outputSuspendedLabel->setContentsMargins(5, 5, 5, 5);
|
|
|
|
// //enable activation of "Xon/Xoff" link in label
|
|
// _outputSuspendedLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse |
|
|
// Qt::LinksAccessibleByKeyboard);
|
|
// _outputSuspendedLabel->setOpenExternalLinks(true);
|
|
// _outputSuspendedLabel->setVisible(false);
|
|
|
|
// _gridLayout->addWidget(_outputSuspendedLabel);
|
|
// _gridLayout->addItem( new QSpacerItem(0,0,QSizePolicy::Expanding,
|
|
// QSizePolicy::Expanding),
|
|
// 1,0);
|
|
|
|
// }
|
|
|
|
_outputSuspendedLabel->setVisible(suspended);
|
|
}
|
|
|
|
uint TerminalDisplay::lineSpacing() const
|
|
{
|
|
return _lineSpacing;
|
|
}
|
|
|
|
void TerminalDisplay::setLineSpacing(uint i)
|
|
{
|
|
if ( i != _lineSpacing ) {
|
|
_lineSpacing = i;
|
|
setVTFont(font()); // Trigger an update.
|
|
emit lineSpacingChanged();
|
|
}
|
|
}
|
|
|
|
int TerminalDisplay::margin() const
|
|
{
|
|
return _topBaseMargin;
|
|
}
|
|
|
|
void TerminalDisplay::setMargin(int i)
|
|
{
|
|
_topBaseMargin = i;
|
|
_leftBaseMargin = i;
|
|
}
|
|
|
|
AutoScrollHandler::AutoScrollHandler(QWidget* parent)
|
|
: QObject(parent)
|
|
, _timerId(0)
|
|
{
|
|
parent->installEventFilter(this);
|
|
}
|
|
void AutoScrollHandler::timerEvent(QTimerEvent* event)
|
|
{
|
|
if (event->timerId() != _timerId)
|
|
return;
|
|
|
|
QMouseEvent mouseEvent(QEvent::MouseMove,
|
|
widget()->mapFromGlobal(QCursor::pos()),
|
|
QCursor::pos(),
|
|
Qt::NoButton,
|
|
Qt::LeftButton,
|
|
Qt::NoModifier);
|
|
|
|
QApplication::sendEvent(widget(),&mouseEvent);
|
|
}
|
|
bool AutoScrollHandler::eventFilter(QObject* watched,QEvent* event)
|
|
{
|
|
Q_ASSERT( watched == parent() );
|
|
Q_UNUSED( watched )
|
|
|
|
QMouseEvent* mouseEvent = (QMouseEvent*)event;
|
|
switch (event->type())
|
|
{
|
|
case QEvent::MouseMove:
|
|
{
|
|
bool mouseInWidget = widget()->rect().contains(mouseEvent->pos());
|
|
|
|
if (mouseInWidget)
|
|
{
|
|
if (_timerId)
|
|
killTimer(_timerId);
|
|
_timerId = 0;
|
|
}
|
|
else
|
|
{
|
|
if (!_timerId && (mouseEvent->buttons() & Qt::LeftButton))
|
|
_timerId = startTimer(100);
|
|
}
|
|
break;
|
|
}
|
|
case QEvent::MouseButtonRelease:
|
|
if (_timerId && (mouseEvent->buttons() & ~Qt::LeftButton))
|
|
{
|
|
killTimer(_timerId);
|
|
_timerId = 0;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
|
|
return false;
|
|
}
|
|
|
|
// QMLTermWidget specific functions ///////////////////////////////////////////
|
|
|
|
void TerminalDisplay::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
|
|
{
|
|
if (newGeometry != oldGeometry) {
|
|
resizeEvent(NULL);
|
|
update();
|
|
}
|
|
|
|
QQuickPaintedItem::geometryChange(newGeometry,oldGeometry);
|
|
}
|
|
|
|
qreal TerminalDisplay::scrollPixelPosition() const
|
|
{
|
|
return _scrollBar->value() * qreal(_fontHeight) - _scrollOffset;
|
|
}
|
|
|
|
qreal TerminalDisplay::scrollPixelMaximum() const
|
|
{
|
|
return _scrollBar->maximum() * qreal(_fontHeight);
|
|
}
|
|
|
|
// Past either end the whole text moves with the stretch.
|
|
void TerminalDisplay::setScrollPixelPosition(qreal position)
|
|
{
|
|
const int maximum = _scrollBar->maximum();
|
|
const qreal bounded = qBound(qreal(0), position, maximum * qreal(_fontHeight));
|
|
const int line = qMin(maximum, int(bounded / _fontHeight));
|
|
|
|
_applyingScroll = true;
|
|
_scrollBar->setValue(line);
|
|
_applyingScroll = false;
|
|
setScrollOffset(line * qreal(_fontHeight) - position);
|
|
}
|
|
|
|
void TerminalDisplay::setScrollOffset(qreal offset)
|
|
{
|
|
_scrollOffset = offset;
|
|
const int topMargin = _topBaseMargin + qRound(offset);
|
|
if (topMargin == _topMargin)
|
|
return;
|
|
|
|
_topMargin = topMargin;
|
|
update();
|
|
}
|
|
|
|
int TerminalDisplay::drawnLines() const
|
|
{
|
|
return _usedLines + (_topMargin < _topBaseMargin && _hasLineBelow ? 1 : 0);
|
|
}
|
|
|
|
void TerminalDisplay::setGpuRendering(bool gpuRendering)
|
|
{
|
|
if (_gpuRendering == gpuRendering)
|
|
return;
|
|
|
|
_gpuRendering = gpuRendering;
|
|
update();
|
|
emit gpuRenderingChanged();
|
|
}
|
|
|
|
QSGNode *TerminalDisplay::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
|
|
{
|
|
auto *gpuNode = dynamic_cast<TerminalGpuNode *>(oldNode);
|
|
// The software scene graph has no custom materials.
|
|
const bool useGpu = _gpuRendering
|
|
&& window()->rendererInterface()->graphicsApi() != QSGRendererInterface::Software;
|
|
|
|
if (!useGpu) {
|
|
if (gpuNode) {
|
|
delete gpuNode;
|
|
oldNode = nullptr;
|
|
}
|
|
_gpuNodeActive = false;
|
|
return QQuickPaintedItem::updatePaintNode(oldNode, data);
|
|
}
|
|
|
|
if (oldNode && !gpuNode)
|
|
delete oldNode;
|
|
|
|
if (width() <= 0 || height() <= 0 || !_image) {
|
|
delete gpuNode;
|
|
_gpuNodeActive = false;
|
|
return nullptr;
|
|
}
|
|
|
|
if (!gpuNode)
|
|
gpuNode = new TerminalGpuNode(window());
|
|
_gpuNodeActive = true;
|
|
|
|
TerminalFrame frame;
|
|
frame.image = _image;
|
|
frame.columns = _columns;
|
|
frame.usedLines = drawnLines();
|
|
frame.usedColumns = _usedColumns;
|
|
frame.lineProperties = &_lineProperties;
|
|
frame.colorTable = _colorTable;
|
|
frame.font = font();
|
|
frame.fontWidth = _fontWidth;
|
|
frame.fontHeight = _fontHeight;
|
|
frame.fontAscent = _fontAscent;
|
|
frame.lineSpacing = int(_lineSpacing);
|
|
frame.leftMargin = _leftMargin;
|
|
frame.topMargin = _topMargin;
|
|
frame.boldIntense = _boldIntense;
|
|
frame.italicEnabled = _italicEnabled;
|
|
frame.drawLineChars = _drawLineChars;
|
|
frame.blinking = _blinking;
|
|
frame.cursorBlinking = _cursorBlinking;
|
|
frame.fullCursorHeight = m_full_cursor_height;
|
|
frame.cursorShape = int(_cursorShape);
|
|
frame.cursorColor = _cursorColor;
|
|
frame.background = palette().window().color();
|
|
frame.backgroundOpacity = _opacity;
|
|
frame.preedit = _inputMethodData.preeditString;
|
|
frame.cursorPosition = cursorPosition();
|
|
frame.size = QSizeF(width(), height());
|
|
frame.devicePixelRatio = window()->effectiveDevicePixelRatio();
|
|
frame.deviceOrigin = mapToScene(QPointF(0, 0)) * frame.devicePixelRatio;
|
|
gpuNode->update(frame);
|
|
|
|
_inputMethodData.previousPreeditRect = preeditRect();
|
|
return gpuNode;
|
|
}
|
|
|
|
// QQuickPaintedItem hands out its painter node, which the GPU node replaces.
|
|
bool TerminalDisplay::isTextureProvider() const
|
|
{
|
|
if (_gpuNodeActive)
|
|
return QQuickItem::isTextureProvider();
|
|
return QQuickPaintedItem::isTextureProvider();
|
|
}
|
|
|
|
QSGTextureProvider *TerminalDisplay::textureProvider() const
|
|
{
|
|
if (_gpuNodeActive)
|
|
return QQuickItem::textureProvider();
|
|
return QQuickPaintedItem::textureProvider();
|
|
}
|
|
|
|
void TerminalDisplay::update(const QRegion ®ion)
|
|
{
|
|
Q_UNUSED(region);
|
|
// TODO this function might be optimized
|
|
// foreach (QRect rect, region.rects()) {
|
|
// QQuickPaintedItem::update(rect.adjusted(-1, -1, +1, +1));
|
|
// }
|
|
QQuickPaintedItem::update(region.boundingRect().adjusted(-1, -1, +1, +1));
|
|
|
|
emit imagePainted();
|
|
}
|
|
|
|
void TerminalDisplay::update()
|
|
{
|
|
QQuickPaintedItem::update(contentsRect());
|
|
}
|
|
|
|
QRect TerminalDisplay::contentsRect() const
|
|
{
|
|
return QRect(0, 0, this->width(), this->height());
|
|
}
|
|
|
|
QSize TerminalDisplay::size() const
|
|
{
|
|
return QSize(this->width(), this->height());
|
|
}
|
|
|
|
void TerminalDisplay::setSession(KSession * session)
|
|
{
|
|
if (m_session != session) {
|
|
// qDebug() << "SetSession called";
|
|
// if (m_session)
|
|
// m_session->removeView(this);
|
|
|
|
m_session = session;
|
|
|
|
connect(this, SIGNAL(copyAvailable(bool)),
|
|
m_session, SLOT(selectionChanged(bool)));
|
|
connect(this, SIGNAL(termGetFocus()),
|
|
m_session, SIGNAL(termGetFocus()));
|
|
connect(this, SIGNAL(termLostFocus()),
|
|
m_session, SIGNAL(termLostFocus()));
|
|
connect(this, SIGNAL(keyPressedSignal(QKeyEvent *, bool)),
|
|
m_session, SIGNAL(termKeyPressed(QKeyEvent *, bool)));
|
|
|
|
m_session->addView(this);
|
|
|
|
setRandomSeed(m_session->getRandomSeed());
|
|
update();
|
|
emit sessionChanged();
|
|
}
|
|
}
|
|
|
|
|
|
KSession* TerminalDisplay::getSession()
|
|
{
|
|
return m_session;
|
|
}
|
|
|
|
QVariantMap TerminalDisplay::colorSchemeInfo(const QString &name) const
|
|
{
|
|
const ColorScheme *cs = ColorSchemeManager::instance()->findColorScheme(name);
|
|
if (!cs)
|
|
return QVariantMap();
|
|
|
|
ColorEntry table[TABLE_COLORS];
|
|
cs->getColorTable(table);
|
|
|
|
QVariantList colors;
|
|
for (int i = 0; i < 8; ++i)
|
|
colors << table[i + 2].color;
|
|
for (int i = 0; i < 8; ++i)
|
|
colors << table[i + 2 + BASE_COLORS].color;
|
|
|
|
return {
|
|
{ QStringLiteral("description"), cs->description() },
|
|
{ QStringLiteral("background"), table[DEFAULT_BACK_COLOR].color },
|
|
{ QStringLiteral("foreground"), table[DEFAULT_FORE_COLOR].color },
|
|
{ QStringLiteral("colors"), colors },
|
|
};
|
|
}
|
|
|
|
void TerminalDisplay::find(const QString &text, bool forwards, bool fromSelection, bool caseSensitive)
|
|
{
|
|
if (!_screenWindow || !m_session || !m_session->emulation())
|
|
return;
|
|
|
|
if (text.isEmpty()) {
|
|
clearFind();
|
|
return;
|
|
}
|
|
|
|
Emulation *emulation = m_session->emulation();
|
|
int startColumn = 0;
|
|
int startLine = emulation->lineCount();
|
|
|
|
if (fromSelection && hasSelection()) {
|
|
if (forwards) {
|
|
_screenWindow->screen()->getSelectionEnd(startColumn, startLine);
|
|
startColumn++;
|
|
} else {
|
|
_screenWindow->screen()->getSelectionStart(startColumn, startLine);
|
|
}
|
|
} else if (forwards) {
|
|
startLine = 0;
|
|
}
|
|
|
|
const QRegularExpression regExp(QRegularExpression::escape(text),
|
|
caseSensitive ? QRegularExpression::NoPatternOption
|
|
: QRegularExpression::CaseInsensitiveOption);
|
|
|
|
auto *search = new HistorySearch(QPointer<Emulation>(emulation), regExp, forwards,
|
|
startColumn, startLine, this);
|
|
|
|
connect(search, &HistorySearch::matchFound, this,
|
|
[this](int startColumn, int startLine, int endColumn, int endLine) {
|
|
if (!_screenWindow)
|
|
return;
|
|
|
|
// Centre the match rather than pinning it to the top edge.
|
|
_screenWindow->scrollTo(qMax(0, startLine - _screenWindow->windowLines() / 2));
|
|
_screenWindow->setTrackOutput(false);
|
|
_screenWindow->notifyOutputChanged();
|
|
_screenWindow->setSelectionStart(startColumn, startLine - _screenWindow->currentLine(), false);
|
|
_screenWindow->setSelectionEnd(endColumn, endLine - _screenWindow->currentLine());
|
|
emit findResult(true);
|
|
});
|
|
connect(search, &HistorySearch::noMatchFound, this, [this]() {
|
|
if (_screenWindow)
|
|
_screenWindow->clearSelection();
|
|
emit findResult(false);
|
|
});
|
|
|
|
search->search();
|
|
}
|
|
|
|
void TerminalDisplay::clearFind()
|
|
{
|
|
if (_screenWindow)
|
|
_screenWindow->clearSelection();
|
|
}
|
|
|
|
QStringList TerminalDisplay::availableColorSchemes()
|
|
{
|
|
QStringList ret;
|
|
foreach (const ColorScheme* cs, ColorSchemeManager::instance()->allColorSchemes())
|
|
ret.append(cs->name());
|
|
return ret;
|
|
}
|
|
|
|
void TerminalDisplay::setColorScheme(const QString &name)
|
|
{
|
|
if ( name != _colorScheme ) {
|
|
// Looked up by name: listing the available schemes first would parse every file.
|
|
const ColorScheme *cs = ColorSchemeManager::instance()->findColorScheme(name);
|
|
if (!cs)
|
|
cs = ColorSchemeManager::instance()->defaultColorScheme();
|
|
|
|
if (! cs)
|
|
{
|
|
qDebug() << "Cannot load color scheme: " << name;
|
|
return;
|
|
}
|
|
|
|
ColorEntry table[TABLE_COLORS];
|
|
cs->getColorTable(table);
|
|
setColorTable(table);
|
|
|
|
QColor background = cs->backgroundColor();
|
|
background.setAlphaF(_opacity);
|
|
setFillColor(background);
|
|
_colorScheme = name;
|
|
emit colorSchemeChanged();
|
|
}
|
|
}
|
|
|
|
QString TerminalDisplay::colorScheme() const
|
|
{
|
|
return _colorScheme;
|
|
}
|
|
|
|
void TerminalDisplay::simulateKeyPress(int key, int modifiers, bool pressed, quint32 nativeScanCode, const QString &text)
|
|
{
|
|
Q_UNUSED(nativeScanCode);
|
|
QEvent::Type type = pressed ? QEvent::KeyPress : QEvent::KeyRelease;
|
|
QKeyEvent event = QKeyEvent(type, key, (Qt::KeyboardModifier) modifiers, text);
|
|
keyPressedSignal(&event, false);
|
|
}
|
|
|
|
void TerminalDisplay::simulateKeySequence(const QKeySequence &keySequence)
|
|
{
|
|
for (int i = 0; i < keySequence.count(); ++i) {
|
|
const Qt::Key key = Qt::Key(keySequence[i] & ~Qt::KeyboardModifierMask);
|
|
const Qt::KeyboardModifiers modifiers = Qt::KeyboardModifiers(keySequence[i] & Qt::KeyboardModifierMask);
|
|
QKeyEvent eventPress = QKeyEvent(QEvent::KeyPress, key, modifiers, "");
|
|
keyPressedSignal(&eventPress, false);
|
|
}
|
|
}
|
|
|
|
void TerminalDisplay::simulateWheel(int x, int y, int buttons, int modifiers, QPointF angleDelta){
|
|
QPoint anglePoint(0, static_cast<int>(angleDelta.y()));
|
|
QWheelEvent event(QPointF(x, y), QPointF(x, y), QPoint(), anglePoint,
|
|
Qt::MouseButtons(buttons), Qt::KeyboardModifiers(modifiers),
|
|
Qt::NoScrollPhase, false);
|
|
wheelEvent(&event);
|
|
}
|
|
|
|
void TerminalDisplay::simulateMouseMove(int x, int y, int button, int buttons, int modifiers){
|
|
QMouseEvent event(QEvent::MouseMove, QPointF(x, y),(Qt::MouseButton) button, (Qt::MouseButtons) buttons, (Qt::KeyboardModifiers) modifiers);
|
|
mouseMoveEvent(&event);
|
|
}
|
|
|
|
void TerminalDisplay::simulateMousePress(int x, int y, int button, int buttons, int modifiers){
|
|
QMouseEvent event(QEvent::MouseButtonPress, QPointF(x, y),(Qt::MouseButton) button, (Qt::MouseButtons) buttons, (Qt::KeyboardModifiers) modifiers);
|
|
mousePressEvent(&event);
|
|
}
|
|
|
|
void TerminalDisplay::simulateMouseRelease(int x, int y, int button, int buttons, int modifiers){
|
|
QMouseEvent event(QEvent::MouseButtonRelease, QPointF(x, y),(Qt::MouseButton) button, (Qt::MouseButtons) buttons, (Qt::KeyboardModifiers) modifiers);
|
|
mouseReleaseEvent(&event);
|
|
}
|
|
|
|
void TerminalDisplay::simulateMouseDoubleClick(int x, int y, int button, int buttons, int modifiers){
|
|
QMouseEvent event(QEvent::MouseButtonDblClick, QPointF(x, y),(Qt::MouseButton) button, (Qt::MouseButtons) buttons, (Qt::KeyboardModifiers) modifiers);
|
|
mouseDoubleClickEvent(&event);
|
|
}
|
|
|
|
QSize TerminalDisplay::getTerminalSize()
|
|
{
|
|
return QSize(_lines, _columns);
|
|
}
|
|
|
|
bool TerminalDisplay::getUsesMouse()
|
|
{
|
|
return !usesMouse();
|
|
}
|
|
|
|
int TerminalDisplay::getScrollbarValue()
|
|
{
|
|
return _scrollBar->value();
|
|
}
|
|
|
|
void TerminalDisplay::setScrollbarValue(int value)
|
|
{
|
|
_scrollBar->setValue(value);
|
|
}
|
|
|
|
int TerminalDisplay::getScrollbarMaximum()
|
|
{
|
|
return _scrollBar->maximum();
|
|
}
|
|
|
|
int TerminalDisplay::getScrollbarMinimum()
|
|
{
|
|
return _scrollBar->minimum();
|
|
}
|
|
|
|
QSize TerminalDisplay::getFontMetrics()
|
|
{
|
|
return QSize(_fontWidth, _fontHeight);
|
|
}
|
|
|
|
void TerminalDisplay::setFullCursorHeight(bool val)
|
|
{
|
|
if ( m_full_cursor_height != val ) {
|
|
m_full_cursor_height = val;
|
|
emit fullCursorHeightChanged();
|
|
}
|
|
}
|
|
|
|
bool TerminalDisplay::fullCursorHeight() const
|
|
{
|
|
return m_full_cursor_height;
|
|
}
|
|
|
|
void TerminalDisplay::itemChange(ItemChange change, const ItemChangeData & value)
|
|
{
|
|
switch (change) {
|
|
case QQuickItem::ItemVisibleHasChanged:
|
|
if (value.boolValue && _screenWindow) {
|
|
if (this->columns() != _screenWindow->columnCount() ||
|
|
this->lines() != _screenWindow->lineCount()) {
|
|
|
|
emit changedContentSizeSignal(_contentHeight, _contentWidth);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
QQuickPaintedItem::itemChange(change, value);
|
|
}
|
|
|
|
ScrollBar::ScrollBar(QObject* parent) : QObject(parent) {}
|
|
|
|
void ScrollBar::setRange(int min, int max)
|
|
{
|
|
if (min == m_minimum && max == m_maximum)
|
|
return;
|
|
|
|
m_minimum = min;
|
|
m_maximum = max;
|
|
if (m_value < m_minimum) m_value = m_minimum;
|
|
if (m_value > m_maximum) m_value = m_maximum;
|
|
|
|
emit rangeChanged();
|
|
}
|
|
|
|
void ScrollBar::setSingleStep(int step)
|
|
{
|
|
m_singleStep = step;
|
|
}
|
|
|
|
void ScrollBar::setPageStep(int step)
|
|
{
|
|
m_pageStep = step;
|
|
}
|
|
|
|
void ScrollBar::setValue(int value)
|
|
{
|
|
int newVal = qBound(m_minimum, value, m_maximum);
|
|
if (newVal != m_value) {
|
|
m_value = newVal;
|
|
emit valueChanged(m_value);
|
|
}
|
|
}
|
|
|
|
int ScrollBar::value() const { return m_value; }
|
|
int ScrollBar::minimum() const { return m_minimum; }
|
|
int ScrollBar::maximum() const { return m_maximum; }
|
|
|
|
bool ScrollBar::isHidden() const { return m_hidden; }
|
|
void ScrollBar::hide() { m_hidden = true; }
|
|
void ScrollBar::show() { m_hidden = false; }
|
|
void ScrollBar::setVisible(bool vis) { m_hidden = !vis; }
|
|
|
|
int ScrollBar::width() const { return m_width; }
|
|
QSize ScrollBar::sizeHint() const { return QSize(m_width, m_height); }
|
|
void ScrollBar::resize(int w, int h) { m_width = w; m_height = h; }
|
|
void ScrollBar::move(const QPoint&) {}
|
|
|
|
void ScrollBar::setCursor(Qt::CursorShape) {}
|
|
void ScrollBar::setPalette(const QPalette&) {}
|
|
void ScrollBar::setAutoFillBackground(bool) {}
|
|
void ScrollBar::setAttribute(Qt::WidgetAttribute) {}
|
|
bool ScrollBar::underMouse() const { return false; }
|
|
void ScrollBar::handleEvent(QEvent* event)
|
|
{
|
|
auto *wheel = dynamic_cast<QWheelEvent*>(event);
|
|
if (!wheel) {
|
|
return;
|
|
}
|
|
|
|
// Prefer pixelDelta for smooth/trackpad scrolling, fall back to angleDelta
|
|
int deltaY = wheel->pixelDelta().y();
|
|
if (deltaY == 0) {
|
|
deltaY = wheel->angleDelta().y();
|
|
}
|
|
if (deltaY == 0) {
|
|
return;
|
|
}
|
|
|
|
// Qt uses positive delta for scrolling up (towards the user), which is a
|
|
// smaller scrollbar value.
|
|
const int steps = deltaY / 120; // 120 == 15 deg * 8
|
|
int stepCount = steps != 0 ? steps : (deltaY > 0 ? 1 : -1);
|
|
|
|
int increment = m_singleStep * stepCount;
|
|
if (wheel->modifiers() & Qt::ControlModifier) {
|
|
increment = (m_pageStep ? m_pageStep : m_singleStep) * stepCount;
|
|
}
|
|
|
|
setValue(m_value - increment);
|
|
wheel->accept();
|
|
}
|
|
|
|
//#include "TerminalDisplay.moc"
|