mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
#### Brief overview of PR changes/additions - "Copy as image" with nothing selected now copies the visible screen (timestamps included) instead of doing nothing. With a selection it is unchanged. - Copy, Copy HTML and Search on ... genuinely need a selection, so without one they are greyed out with a tooltip saying so rather than silently doing nothing. - Copy as image no longer wipes the clipboard when it runs out of its 3s budget, crops the drawn lines instead of squashing the whole selection to fit, and copies a run of blank lines rather than nothing. #### Motivation for adding to Mudlet Right-clicking the console and picking "Copy as image" left nothing on the clipboard, with no hint that a selection was needed. #### Other info (issues closed, discussion etc) Fixes #9715 Also fixes two ways a selection could outlive the lines it covers: clearing a console now clears its selection (`clearWindow()` then "Copy as image" aborted on Qt's bounds assert), and a selection stranded by the buffer hitting its size limit is followed down with its lines instead of copying whatever took their place. New `CopyAsImageTest` functional test, 15 cases. Verified on X11 with `xclip -selection clipboard -t image/png -o`. Assisted-by: Claude:claude-opus-5 **Test case:** Right-click the main console with nothing selected, pick "Copy as image", and paste - you get a picture of the screen. Select some text and repeat - you get just the selection.
3160 lines
121 KiB
C++
3160 lines
121 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
|
|
* Copyright (C) 2014-2024, 2026 by Stephen Lyons *
|
|
* - slysven@virginmedia.com *
|
|
* Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
|
|
* Copyright (C) 2016 by Ian Adkins - ieadkins@gmail.com *
|
|
* Copyright (C) 2021 by Vadim Peretokin - vperetokin@gmail.com *
|
|
* Copyright (C) 2022 by Thiago Jung Bauermann - bauermann@kolabnow.com *
|
|
* *
|
|
* 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., *
|
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "TConsole.h"
|
|
|
|
|
|
#include "ctelnet.h"
|
|
#include "Host.h"
|
|
#include "TCommandLine.h"
|
|
#include "THyperlinkCompactManager.h"
|
|
#include "TDebug.h"
|
|
#include "TDockWidget.h"
|
|
#include "TEvent.h"
|
|
#include "THyperlinkSelectionManager.h"
|
|
#include "THyperlinkVisibilityManager.h"
|
|
#include "TLabel.h"
|
|
#include "TMainConsole.h"
|
|
#include "TMap.h"
|
|
#include "TSplitter.h"
|
|
#include "TTextEdit.h"
|
|
#include "dlgMapper.h"
|
|
#include "mudlet.h"
|
|
|
|
#include <QAccessibleInterface>
|
|
#include <QAccessibleWidget>
|
|
#include <QFile>
|
|
#include <QHBoxLayout>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QPainter>
|
|
#include <QScrollBar>
|
|
#include <QSettings>
|
|
#include <QShortcut>
|
|
#include <QSplitter>
|
|
#include <QTextBoundaryFinder>
|
|
#include <QVideoWidget>
|
|
#include <chrono>
|
|
|
|
using namespace std::chrono_literals;
|
|
|
|
const QString TConsole::cmLuaLineVariable("line");
|
|
|
|
// A high-performance text widget with split screen ability for scrolling back
|
|
// Contains two TTextEdits, and is backed by a TBuffer
|
|
TConsole::TConsole(Host* pH, const QString& name, const ConsoleType type, QWidget* parent)
|
|
: QWidget(parent)
|
|
, mpHost(pH)
|
|
, mDisplayFontDetails(pH->fontsAntiAlias())
|
|
, buffer(pH, this)
|
|
, emergencyStop(new QToolButton)
|
|
, mConsoleName(name)
|
|
, mpBaseVFrame(new QWidget(this))
|
|
, mpTopToolBar(new QWidget(mpBaseVFrame))
|
|
, mpBaseHFrame(new QWidget(mpBaseVFrame))
|
|
, mpLeftToolBar(new QWidget(mpBaseHFrame))
|
|
, mpMainFrame(new QWidget(mpBaseHFrame))
|
|
, mpRightToolBar(new QWidget(mpBaseHFrame))
|
|
, mpMainDisplay(new QWidget(mpMainFrame))
|
|
, mpScrollBar(new QScrollBar)
|
|
, mpHScrollBar(new QScrollBar(Qt::Horizontal))
|
|
, mProfileName(mpHost ? mpHost->getName() : qsl("debug console"))
|
|
, mpBufferSearchBox(new QLineEdit)
|
|
, mpBufferSearchUp(new QToolButton)
|
|
, mpBufferSearchDown(new QToolButton)
|
|
, mControlCharacter(pH->getControlCharacterMode())
|
|
, mType(type)
|
|
{
|
|
mpHyperlinkCompactManager = std::make_unique<THyperlinkCompactManager>();
|
|
mpHyperlinkSelectionManager = std::make_unique<THyperlinkSelectionManager>(*this);
|
|
mpHyperlinkVisibilityManager = std::make_unique<THyperlinkVisibilityManager>(this);
|
|
|
|
initializeOSC8StyleFeature();
|
|
initializeOSC8MenuFeature();
|
|
initializeOSC8TooltipFeature();
|
|
initializeOSC8DisabledFeature();
|
|
initializeOSC8SpoilerFeature();
|
|
initializeOSC8SelectionFeature();
|
|
initializeOSC8VisibilityFeature();
|
|
initializeOSC8TitleFeature();
|
|
|
|
auto quitShortcut = new QShortcut(this);
|
|
quitShortcut->setKey(Qt::CTRL | Qt::Key_W);
|
|
quitShortcut->setContext(Qt::WidgetShortcut);
|
|
|
|
if (mType == CentralDebugConsole) {
|
|
// Probably will not show up as this is used inside a QMainWindow widget
|
|
// which has its own title and icon set.
|
|
setWindowTitle(tr("Debug Console"));
|
|
mWrapAt = 50;
|
|
mShowTimeStamps = true;
|
|
} else if (mType == MainConsole) {
|
|
mBorders = mpHost->borders();
|
|
mCommandBgColor = mpHost->mCommandBgColor;
|
|
mCommandFgColor = mpHost->mCommandFgColor;
|
|
}
|
|
|
|
QWidget::setFont(mDisplayFontDetails.makeFont());
|
|
|
|
setContentsMargins(0, 0, 0, 0);
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
setAttribute(Qt::WA_OpaquePaintEvent, (mType == MainConsole || mType == CentralDebugConsole));
|
|
|
|
const QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
const QSizePolicy sizePolicy3(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
const QSizePolicy sizePolicy2(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
|
const QSizePolicy sizePolicy4(QSizePolicy::Fixed, QSizePolicy::Expanding);
|
|
const QSizePolicy sizePolicy5(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
|
|
mpMainFrame->setContentsMargins(0, 0, 0, 0);
|
|
|
|
// the central debug console is a top-level window with no main console
|
|
// behind it, so it must paint its own background like the main console does
|
|
if (mType == MainConsole || mType == CentralDebugConsole) {
|
|
QPalette framePalette;
|
|
framePalette.setColor(QPalette::Text, QColor(Qt::black));
|
|
framePalette.setColor(QPalette::Highlight, QColor(55, 55, 255));
|
|
framePalette.setColor(QPalette::Window, QColor(0, 0, 0, 255));
|
|
mpMainFrame->setPalette(framePalette);
|
|
mpMainFrame->setAutoFillBackground(true);
|
|
} else {
|
|
QPalette transparentBgPalette;
|
|
transparentBgPalette.setColor(QPalette::Window, QColor(0, 0, 0, 0));
|
|
setPalette(transparentBgPalette);
|
|
mpMainFrame->setPalette(transparentBgPalette);
|
|
}
|
|
mpMainFrame->setObjectName(qsl("MainFrame"));
|
|
|
|
auto centralLayout = new QVBoxLayout;
|
|
setLayout(centralLayout);
|
|
auto baseVFrameLayout = new QVBoxLayout;
|
|
mpBaseVFrame->setLayout(baseVFrameLayout);
|
|
baseVFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
baseVFrameLayout->setSpacing(0);
|
|
centralLayout->addWidget(mpBaseVFrame);
|
|
auto baseHFrameLayout = new QHBoxLayout;
|
|
mpBaseHFrame->setLayout(baseHFrameLayout);
|
|
baseHFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
baseHFrameLayout->setSpacing(0);
|
|
layout()->setSpacing(0);
|
|
layout()->setContentsMargins(0, 0, 0, 0);
|
|
setContentsMargins(0, 0, 0, 0);
|
|
|
|
auto topBarLayout = new QHBoxLayout;
|
|
mpTopToolBar->setLayout(topBarLayout);
|
|
mpTopToolBar->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
|
|
mpTopToolBar->setContentsMargins(0, 0, 0, 0);
|
|
mpTopToolBar->setAutoFillBackground(true);
|
|
|
|
topBarLayout->setContentsMargins(0, 0, 0, 0);
|
|
topBarLayout->setSpacing(0);
|
|
auto leftBarLayout = new QVBoxLayout;
|
|
mpLeftToolBar->setLayout(leftBarLayout);
|
|
mpLeftToolBar->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding));
|
|
mpLeftToolBar->setAutoFillBackground(true);
|
|
leftBarLayout->setContentsMargins(0, 0, 0, 0);
|
|
leftBarLayout->setSpacing(0);
|
|
mpLeftToolBar->setContentsMargins(0, 0, 0, 0);
|
|
auto rightBarLayout = new QVBoxLayout;
|
|
mpRightToolBar->setLayout(rightBarLayout);
|
|
mpRightToolBar->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding));
|
|
mpRightToolBar->setAutoFillBackground(true);
|
|
rightBarLayout->setContentsMargins(0, 0, 0, 0);
|
|
rightBarLayout->setSpacing(0);
|
|
mpRightToolBar->setContentsMargins(0, 0, 0, 0);
|
|
mpBaseVFrame->setContentsMargins(0, 0, 0, 0);
|
|
baseVFrameLayout->setSpacing(0);
|
|
baseVFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
mpTopToolBar->setContentsMargins(0, 0, 0, 0);
|
|
baseVFrameLayout->addWidget(mpTopToolBar);
|
|
baseVFrameLayout->addWidget(mpBaseHFrame);
|
|
baseHFrameLayout->addWidget(mpLeftToolBar);
|
|
auto mpCorePane = new QWidget(mpBaseHFrame);
|
|
auto coreSpreadLayout = new QVBoxLayout;
|
|
mpCorePane->setLayout(coreSpreadLayout);
|
|
mpCorePane->setContentsMargins(0, 0, 0, 0);
|
|
coreSpreadLayout->setContentsMargins(0, 0, 0, 0);
|
|
coreSpreadLayout->setSpacing(0);
|
|
coreSpreadLayout->addWidget(mpMainFrame);
|
|
mpCorePane->setSizePolicy(sizePolicy);
|
|
baseHFrameLayout->addWidget(mpCorePane);
|
|
baseHFrameLayout->addWidget(mpRightToolBar);
|
|
mpTopToolBar->setContentsMargins(0, 0, 0, 0);
|
|
mpBaseHFrame->setAutoFillBackground(true);
|
|
baseHFrameLayout->setSpacing(0);
|
|
baseHFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
setContentsMargins(0, 0, 0, 0);
|
|
mpBaseHFrame->setContentsMargins(0, 0, 0, 0);
|
|
centralLayout->setSpacing(0);
|
|
centralLayout->setContentsMargins(0, 0, 0, 0);
|
|
mpMainDisplay->move(mBorders.left(), mBorders.top());
|
|
mpMainFrame->show();
|
|
mpMainDisplay->show();
|
|
mpMainFrame->setContentsMargins(0, 0, 0, 0);
|
|
mpMainDisplay->setContentsMargins(0, 0, 0, 0);
|
|
auto layout = new QVBoxLayout;
|
|
mpMainDisplay->setObjectName(qsl("MainDisplay"));
|
|
mpMainDisplay->setLayout(layout);
|
|
layout->setContentsMargins(0, 0, 0, 0);
|
|
layout->setSpacing(0);
|
|
|
|
if (mType == MainConsole) {
|
|
// Sits behind mpMainDisplay within mpMainFrame, which always spans the
|
|
// console's full pane regardless of mBorders - so this stays independent
|
|
// of setBorderSizes() without needing its own resize/border logic.
|
|
mpWindowBackground = new QWidget(mpMainFrame);
|
|
mpWindowBackground->setObjectName(qsl("WindowBackground"));
|
|
mpWindowBackground->setContentsMargins(0, 0, 0, 0);
|
|
mpWindowBackground->move(0, 0);
|
|
mpWindowBackground->resize(mpMainFrame->size());
|
|
mpWindowBackground->lower();
|
|
mpWindowBackground->show();
|
|
}
|
|
|
|
mpBaseVFrame->setSizePolicy(sizePolicy);
|
|
mpBaseHFrame->setSizePolicy(sizePolicy);
|
|
|
|
baseVFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
baseHFrameLayout->setContentsMargins(0, 0, 0, 0);
|
|
centralLayout->setContentsMargins(0, 0, 0, 0);
|
|
|
|
if (mType == MainConsole) {
|
|
mpCommandLine = new TCommandLine(pH, qsl("main"), TCommandLine::MainCommandLine, this, mpMainDisplay);
|
|
mpCommandLine->setContentsMargins(0, 0, 0, 0);
|
|
mpCommandLine->setSizePolicy(sizePolicy);
|
|
mpCommandLine->setFont(font());
|
|
// Setting the focusProxy cannot be done here because things have not
|
|
// been completed enough at this point - it has been defered to a
|
|
// zero-timer at the end of this constructor
|
|
|
|
// Connect user input trigger (command submission only, not typing)
|
|
connect(mpCommandLine, &TCommandLine::commandSubmitted, mpHyperlinkVisibilityManager.get(), &THyperlinkVisibilityManager::onUserInput);
|
|
|
|
// Connect GA/EOR prompt signal from telnet
|
|
connect(&(pH->mTelnet), &cTelnet::signal_promptReceived, mpHyperlinkVisibilityManager.get(), &THyperlinkVisibilityManager::onPromptReceived);
|
|
|
|
// Refresh display when hyperlink visibility changes
|
|
connect(mpHyperlinkVisibilityManager.get(), &THyperlinkVisibilityManager::visibilityChanged, this, [this]() {
|
|
if (mUpperPane) {
|
|
mUpperPane->forceUpdate();
|
|
}
|
|
if (mLowerPane) {
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
});
|
|
}
|
|
|
|
layer = new QWidget(mpMainDisplay);
|
|
layer->setObjectName(qsl("layer"));
|
|
layer->setStyleSheet("QWidget#layer{background-color: rgba(0,0,0,0)}");
|
|
layer->setContentsMargins(0, 0, 0, 0);
|
|
layer->setSizePolicy(sizePolicy);
|
|
layer->setFocusPolicy(Qt::NoFocus);
|
|
|
|
auto vLayoutLayer = new QVBoxLayout;
|
|
auto layoutLayer = new QHBoxLayout;
|
|
layer->setLayout(vLayoutLayer);
|
|
layoutLayer->setContentsMargins(0, 0, 0, 0);
|
|
layoutLayer->setSpacing(0);
|
|
|
|
mpScrollBar->setFixedWidth(15);
|
|
mpHScrollBar->setFixedHeight(15);
|
|
|
|
splitter = new TSplitter(Qt::Vertical, layer);
|
|
splitter->setObjectName(qsl("splitter_%1_%2").arg(mProfileName, mConsoleName));
|
|
splitter->setContentsMargins(0, 0, 0, 0);
|
|
splitter->setSizePolicy(sizePolicy);
|
|
splitter->setHandleWidth(3);
|
|
splitter->setFocusPolicy(Qt::NoFocus);
|
|
//QSplitter covers the background if not set to transparent and a new AppStyleSheet is set for example by DarkTheme
|
|
auto styleSheet = qsl("QSplitter { background-color: rgba(0,0,0,0) }");
|
|
splitter->setStyleSheet(styleSheet);
|
|
|
|
mUpperPane = new TTextEdit(this, splitter, &buffer, mpHost, false);
|
|
mUpperPane->setObjectName(qsl("upperPane_%1_%2").arg(mProfileName, mConsoleName));
|
|
mUpperPane->setContentsMargins(0, 0, 0, 0);
|
|
mUpperPane->setSizePolicy(sizePolicy3);
|
|
mUpperPane->setAccessibleName(tr("main window"));
|
|
mUpperPane->setFont(font());
|
|
|
|
mLowerPane = new TTextEdit(this, splitter, &buffer, mpHost, true);
|
|
mLowerPane->setObjectName(qsl("lowerPane_%1_%2").arg(mProfileName, mConsoleName));
|
|
mLowerPane->setContentsMargins(0, 0, 0, 0);
|
|
mLowerPane->setSizePolicy(sizePolicy3);
|
|
mLowerPane->setFont(font());
|
|
|
|
if (mType == MainConsole) {
|
|
setFocusProxy(mpCommandLine);
|
|
mUpperPane->setFocusProxy(mpCommandLine);
|
|
mLowerPane->setFocusProxy(mpCommandLine);
|
|
} else if (mType & (UserWindow | SubConsole)) {
|
|
// These will need to be changed when the built in TCommandLine is
|
|
// enabled or an additional one is added to them:
|
|
setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
mUpperPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
mLowerPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
}
|
|
|
|
splitter->addWidget(mUpperPane);
|
|
splitter->addWidget(mLowerPane);
|
|
|
|
splitter->setCollapsible(1, false);
|
|
splitter->setCollapsible(0, false);
|
|
splitter->setStretchFactor(0, 6);
|
|
splitter->setStretchFactor(1, 1);
|
|
|
|
layoutLayer->addWidget(splitter);
|
|
layoutLayer->addWidget(mpScrollBar);
|
|
layoutLayer->setContentsMargins(0, 0, 0, 0);
|
|
layoutLayer->setSpacing(1); // not closer, otherwise there could be performance problems when displaying
|
|
|
|
vLayoutLayer->addLayout(layoutLayer);
|
|
vLayoutLayer->addWidget(mpHScrollBar);
|
|
vLayoutLayer->setContentsMargins(0, 0, 0, 0);
|
|
vLayoutLayer->setSpacing(0);
|
|
|
|
layerCommandLine = new QWidget; //( mpMainFrame );//layer );
|
|
layerCommandLine->setContentsMargins(0, 0, 0, 0);
|
|
layerCommandLine->setSizePolicy(sizePolicy);
|
|
layerCommandLine->setFocusPolicy(Qt::NoFocus);
|
|
|
|
layerCommandLine->setMaximumHeight(31);
|
|
layerCommandLine->setMinimumHeight(31);
|
|
layerCommandLine->setMinimumWidth(300);
|
|
|
|
layoutLayer2 = new QHBoxLayout(layerCommandLine);
|
|
layoutLayer2->setContentsMargins(0, 0, 0, 0);
|
|
layoutLayer2->setSpacing(0);
|
|
|
|
mpButtonMainLayer = new QWidget;
|
|
mpButtonMainLayer->setObjectName(qsl("mpButtonMainLayer"));
|
|
mpButtonMainLayer->setSizePolicy(sizePolicy);
|
|
mpButtonMainLayer->setContentsMargins(0, 0, 0, 0);
|
|
auto layoutButtonMainLayer = new QVBoxLayout(mpButtonMainLayer);
|
|
layoutButtonMainLayer->setObjectName(qsl("layoutButtonMainLayer"));
|
|
layoutButtonMainLayer->setContentsMargins(0, 0, 0, 0);
|
|
|
|
layoutButtonMainLayer->setSpacing(0);
|
|
/*mpButtonMainLayer->setMinimumHeight(31);
|
|
mpButtonMainLayer->setMaximumHeight(31);*/
|
|
auto buttonLayer = new QWidget;
|
|
buttonLayer->setObjectName(qsl("buttonLayer"));
|
|
auto layoutButtonLayer = new QHBoxLayout(buttonLayer);
|
|
layoutButtonLayer->setObjectName(qsl("layoutButtonLayer"));
|
|
layoutButtonLayer->setContentsMargins(0, 0, 0, 0);
|
|
layoutButtonLayer->setSpacing(0);
|
|
|
|
auto buttonLayerSpacer = new QWidget(buttonLayer);
|
|
buttonLayerSpacer->setSizePolicy(sizePolicy4);
|
|
layoutButtonMainLayer->addWidget(buttonLayerSpacer);
|
|
layoutButtonMainLayer->addWidget(buttonLayer);
|
|
|
|
timeStampButton = new QToolButton;
|
|
timeStampButton->setCheckable(true);
|
|
timeStampButton->setMinimumSize(QSize(30, 30));
|
|
timeStampButton->setMaximumSize(QSize(30, 30));
|
|
timeStampButton->setSizePolicy(sizePolicy5);
|
|
timeStampButton->setFocusPolicy(Qt::NoFocus);
|
|
timeStampButton->setIcon(QIcon(qsl(":/icons/dialog-information.png")));
|
|
timeStampButton->setToolTip(utils::richText(tr("Toggle time stamps")));
|
|
|
|
// Using the QAbstractButton::clicked rather than QAbstractButton::toggled
|
|
// so that we can set the state of the button without getting the signal
|
|
// being raised:
|
|
connect(timeStampButton, &QAbstractButton::clicked, this, &TConsole::slot_toggleTimeStamps);
|
|
|
|
replayButton = new QToolButton;
|
|
replayButton->setCheckable(true);
|
|
replayButton->setMinimumSize(QSize(30, 30));
|
|
replayButton->setMaximumSize(QSize(30, 30));
|
|
replayButton->setSizePolicy(sizePolicy5);
|
|
replayButton->setFocusPolicy(Qt::NoFocus);
|
|
|
|
QIcon replayIcon;
|
|
replayIcon.addPixmap(QPixmap(qsl(":/icons/media-tape.png")), QIcon::Normal, QIcon::Off);
|
|
replayIcon.addPixmap(QPixmap(qsl(":/icons/media-tape-red-cross.png")), QIcon::Normal, QIcon::On);
|
|
replayButton->setIcon(replayIcon);
|
|
//: Button tooltip for the replay recording toggle button
|
|
replayButton->setToolTip(utils::richText(tr("Start recording of replay")));
|
|
connect(replayButton, &QAbstractButton::clicked, this, &TConsole::slot_toggleReplayRecording);
|
|
|
|
logButton = new QToolButton;
|
|
logButton->setMinimumSize(QSize(30, 30));
|
|
logButton->setMaximumSize(QSize(30, 30));
|
|
logButton->setCheckable(true);
|
|
logButton->setSizePolicy(sizePolicy5);
|
|
logButton->setFocusPolicy(Qt::NoFocus);
|
|
//: Button tooltip for the logging button
|
|
logButton->setToolTip(utils::richText(tr("Start logging game output to log file.")));
|
|
|
|
QIcon logIcon;
|
|
logIcon.addPixmap(QPixmap(qsl(":/icons/folder-downloads.png")), QIcon::Normal, QIcon::Off);
|
|
logIcon.addPixmap(QPixmap(qsl(":/icons/folder-downloads-red-cross.png")), QIcon::Normal, QIcon::On);
|
|
logButton->setIcon(logIcon);
|
|
connect(logButton, &QAbstractButton::clicked, this, &TConsole::slot_toggleLogging);
|
|
|
|
if (mType == MainConsole) {
|
|
mpLineEdit_networkLatency = new QLineEdit(this);
|
|
mpLineEdit_networkLatency->setReadOnly(true);
|
|
mpLineEdit_networkLatency->setSizePolicy(sizePolicy4);
|
|
mpLineEdit_networkLatency->setFocusPolicy(Qt::NoFocus);
|
|
//: Tooltip for N and S network latency indicators
|
|
mpLineEdit_networkLatency->setToolTip(utils::richText(tr("<i>N:</i> network latency in seconds (ping),<br>"
|
|
"<i>S:</i> system processing time (triggers).")));
|
|
mpLineEdit_networkLatency->setMaximumSize(120, 30);
|
|
mpLineEdit_networkLatency->setMinimumSize(120, 30);
|
|
mpLineEdit_networkLatency->setAutoFillBackground(true);
|
|
mpLineEdit_networkLatency->setContentsMargins(0, 0, 0, 0);
|
|
mpLineEdit_networkLatency->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
|
|
|
|
int latencyFontPointSize = 21;
|
|
QFont latencyFont = QFont(qsl("Bitstream Vera Sans Mono"), latencyFontPointSize, QFont::Normal);
|
|
const int latencyFontSizeMargin = 10;
|
|
/*:
|
|
The first argument 'N' represents the 'N'etwork latency; the second 'S' the
|
|
'S'ystem (processing) time
|
|
*/
|
|
const QString dummyTextA = tr("N:%1 S:%2").arg(0.0, 0, 'f', 3).arg(0.0, 0, 'f', 3);
|
|
/*:
|
|
The argument 'S' represents the 'S'ystem (processing) time, in this situation
|
|
the Game Server is not sending "GoAhead" signals so we cannot deduce the
|
|
network latency...
|
|
*/
|
|
const QString dummyTextB = tr("<no GA> S:%1").arg(0.0, 0, 'f', 3);
|
|
do {
|
|
latencyFont.setPointSize(--latencyFontPointSize);
|
|
} while (latencyFontPointSize > 6
|
|
&& qMax(QFontMetrics(latencyFont).boundingRect(dummyTextA).width(), QFontMetrics(latencyFont).boundingRect(dummyTextB).width()) + latencyFontSizeMargin
|
|
> mpLineEdit_networkLatency->maximumWidth());
|
|
|
|
mpLineEdit_networkLatency->setFont(latencyFont);
|
|
mpLineEdit_networkLatency->setFrame(false);
|
|
}
|
|
|
|
emergencyStop->setMinimumSize(QSize(30, 30));
|
|
emergencyStop->setMaximumSize(QSize(30, 30));
|
|
|
|
QIcon emergencyIcon;
|
|
emergencyIcon.addPixmap(QPixmap(qsl(":/icons/edit-bomb.png")), QIcon::Normal, QIcon::Off);
|
|
emergencyIcon.addPixmap(QPixmap(qsl(":/icons/red-bomb.png")), QIcon::Normal, QIcon::On);
|
|
emergencyStop->setIcon(emergencyIcon);
|
|
|
|
emergencyStop->setSizePolicy(sizePolicy4);
|
|
emergencyStop->setFocusPolicy(Qt::NoFocus);
|
|
emergencyStop->setCheckable(true);
|
|
emergencyStop->setToolTip(utils::richText(tr("Emergency stop! Stop all scripts")));
|
|
|
|
connect(emergencyStop, &QAbstractButton::clicked, this, &TConsole::slot_stopAllItems);
|
|
|
|
mpBufferSearchBox->setClearButtonEnabled(true);
|
|
for (auto child : mpBufferSearchBox->children()) {
|
|
auto* pAction_clear(qobject_cast<QAction*>(child));
|
|
if (pAction_clear && pAction_clear->objectName() == QLatin1String("_q_qlineeditclearaction")) {
|
|
connect(pAction_clear, &QAction::triggered, this, &TConsole::slot_clearSearchResults, Qt::QueuedConnection);
|
|
break;
|
|
}
|
|
}
|
|
|
|
mpBufferSearchBox->setMinimumSize(QSize(100, 30));
|
|
mpBufferSearchBox->setSizePolicy(sizePolicy);
|
|
mpBufferSearchBox->setFont(font());
|
|
mpBufferSearchBox->setFocusPolicy(Qt::ClickFocus);
|
|
//: search bar placeholder text
|
|
mpBufferSearchBox->setPlaceholderText(tr("Search"));
|
|
QPalette commandLinePalette;
|
|
commandLinePalette.setColor(QPalette::Text, mpHost->mCommandLineFgColor);
|
|
commandLinePalette.setColor(QPalette::Highlight, QColor(0, 0, 192));
|
|
commandLinePalette.setColor(QPalette::HighlightedText, QColor(Qt::white));
|
|
commandLinePalette.setColor(QPalette::Base, mpHost->mCommandLineBgColor);
|
|
commandLinePalette.setColor(QPalette::Window, mpHost->mCommandLineBgColor);
|
|
mpBufferSearchBox->setToolTip(utils::richText(tr("Search buffer.")));
|
|
connect(mpBufferSearchBox, &QLineEdit::returnPressed, this, &TConsole::slot_searchBufferUp);
|
|
|
|
mpAction_searchOptions = new QAction(tr("Search Options"), this);
|
|
mpAction_searchOptions->setObjectName(qsl("mpAction_searchOptions"));
|
|
|
|
QMenu* pMenu_searchOptions = new QMenu(tr("Search Options"), this);
|
|
pMenu_searchOptions->setObjectName(qsl("pMenu_searchOptions"));
|
|
pMenu_searchOptions->setToolTipsVisible(true);
|
|
|
|
mpAction_searchCaseSensitive = new QAction(tr("Case sensitive"), this);
|
|
mpAction_searchCaseSensitive->setObjectName(qsl("mpAction_searchCaseSensitive"));
|
|
mpAction_searchCaseSensitive->setToolTip(utils::richText(tr("Match case precisely")));
|
|
mpAction_searchCaseSensitive->setCheckable(true);
|
|
pMenu_searchOptions->insertAction(nullptr, mpAction_searchCaseSensitive);
|
|
|
|
setSearchOptions(mSearchOptions);
|
|
|
|
connect(mpAction_searchCaseSensitive, &QAction::triggered, this, &TConsole::slot_toggleSearchCaseSensitivity);
|
|
mpAction_searchOptions->setMenu(pMenu_searchOptions);
|
|
mpBufferSearchBox->addAction(mpAction_searchOptions, QLineEdit::LeadingPosition);
|
|
|
|
mpBufferSearchUp->setMinimumSize(QSize(30, 30));
|
|
mpBufferSearchUp->setMaximumSize(QSize(30, 30));
|
|
mpBufferSearchUp->setSizePolicy(sizePolicy5);
|
|
mpBufferSearchUp->setToolTip(utils::richText(tr("Earlier search result.")));
|
|
mpBufferSearchUp->setFocusPolicy(Qt::NoFocus);
|
|
mpBufferSearchUp->setIcon(QIcon(qsl(":/icons/export.png")));
|
|
connect(mpBufferSearchUp, &QAbstractButton::clicked, this, &TConsole::slot_searchBufferUp);
|
|
|
|
|
|
mpBufferSearchDown->setMinimumSize(QSize(30, 30));
|
|
mpBufferSearchDown->setMaximumSize(QSize(30, 30));
|
|
mpBufferSearchDown->setSizePolicy(sizePolicy5);
|
|
mpBufferSearchDown->setFocusPolicy(Qt::NoFocus);
|
|
mpBufferSearchDown->setToolTip(utils::richText(tr("Later search result.")));
|
|
mpBufferSearchDown->setIcon(QIcon(qsl(":/icons/import.png")));
|
|
connect(mpBufferSearchDown, &QAbstractButton::clicked, this, &TConsole::slot_searchBufferDown);
|
|
|
|
if (mType == MainConsole) {
|
|
setF3SearchEnabled(mpHost->getF3SearchEnabled());
|
|
}
|
|
|
|
if (mpCommandLine) {
|
|
layoutLayer2->addWidget(mpCommandLine);
|
|
}
|
|
|
|
layoutLayer2->addWidget(mpButtonMainLayer);
|
|
layoutLayer2->setContentsMargins(0, 0, 0, 0);
|
|
layout->addWidget(layer);
|
|
layerCommandLine->setAutoFillBackground(true);
|
|
|
|
if (mType == MainConsole) {
|
|
// All console control buttons should only be on MainConsole
|
|
layoutButtonLayer->addWidget(mpBufferSearchBox);
|
|
layoutButtonLayer->addWidget(mpBufferSearchUp);
|
|
layoutButtonLayer->addWidget(mpBufferSearchDown);
|
|
layoutButtonLayer->addWidget(timeStampButton);
|
|
layoutButtonLayer->addWidget(replayButton);
|
|
layoutButtonLayer->addWidget(logButton);
|
|
layoutButtonLayer->addWidget(emergencyStop);
|
|
layoutButtonLayer->addWidget(mpLineEdit_networkLatency);
|
|
|
|
commandSplitter = new QSplitter(Qt::Horizontal, this);
|
|
commandSplitter->setFocusPolicy(Qt::NoFocus);
|
|
connect(commandSplitter, &QSplitter::splitterMoved, this, &TConsole::slot_saveCommandSearchSettings);
|
|
commandSplitter->addWidget(layerCommandLine);
|
|
commandSplitter->addWidget(mpButtonMainLayer);
|
|
commandSplitter->setStretchFactor(0, 3); // command line
|
|
commandSplitter->setStretchFactor(1, 1); // search layer
|
|
|
|
commandSplitter->setCollapsible(0, false); // command line cannot collapse
|
|
commandSplitter->setCollapsible(1, false); // search layer cannot collapse
|
|
|
|
centralLayout->addWidget(commandSplitter);
|
|
restoreCommandSearchSettings();
|
|
} else {
|
|
centralLayout->addWidget(layerCommandLine);
|
|
}
|
|
|
|
QList<int> sizeList;
|
|
sizeList << 6 << 2;
|
|
splitter->setSizes(sizeList);
|
|
|
|
mUpperPane->show();
|
|
mLowerPane->hide();
|
|
|
|
connect(mpScrollBar, &QAbstractSlider::valueChanged, mUpperPane, &TTextEdit::slot_scrollBarMoved);
|
|
connect(mpHScrollBar, &QAbstractSlider::valueChanged, mUpperPane, &TTextEdit::slot_hScrollBarMoved);
|
|
|
|
mpHScrollBar->hide();
|
|
|
|
//enable horizontal scrollbar in ErrorConsole
|
|
if (mType == ErrorConsole) {
|
|
mHScrollBarEnabled = true;
|
|
}
|
|
|
|
// a Buffer is never displayed and the three types below start with their
|
|
// scroll bar hidden, so only the main and debug consoles begin with one
|
|
mScrollBarEnabled = !(mType & (ErrorConsole | SubConsole | UserWindow | Buffer));
|
|
|
|
if (mType & (ErrorConsole | SubConsole | UserWindow)) {
|
|
mpScrollBar->hide();
|
|
mLowerPane->hide();
|
|
layerCommandLine->hide();
|
|
mpMainFrame->move(0, 0);
|
|
mpMainDisplay->move(0, 0);
|
|
}
|
|
|
|
if (mType & CentralDebugConsole) {
|
|
layerCommandLine->hide();
|
|
}
|
|
|
|
mpBaseVFrame->setContentsMargins(0, 0, 0, 0);
|
|
mpBaseHFrame->setContentsMargins(0, 0, 0, 0);
|
|
mpBaseVFrame->layout()->setSpacing(0);
|
|
mpBaseHFrame->layout()->setSpacing(0);
|
|
|
|
buttonLayerSpacer->setMinimumHeight(0);
|
|
buttonLayerSpacer->setMinimumWidth(100);
|
|
buttonLayer->setMaximumHeight(31);
|
|
buttonLayer->setMinimumWidth(400);
|
|
mpButtonMainLayer->setMinimumWidth(400);
|
|
mpButtonMainLayer->setAutoFillBackground(true);
|
|
mpButtonMainLayer->setPalette(commandLinePalette);
|
|
|
|
buttonLayer->setAutoFillBackground(true);
|
|
changeColors();
|
|
|
|
// error and debug consoles inherit font of the main console
|
|
if (mType & (ErrorConsole | CentralDebugConsole)) {
|
|
// They always use "Control Pictures" to show control characters:
|
|
mControlCharacter = ControlCharacterMode::Picture;
|
|
refreshView();
|
|
} else if (mpHost) {
|
|
connect(mpHost, &Host::signal_controlCharacterHandlingChanged, this, &TConsole::slot_changeControlCharacterHandling);
|
|
}
|
|
|
|
if (mType & (MainConsole | UserWindow)) {
|
|
setAcceptDrops(true);
|
|
setMouseTracking(true);
|
|
}
|
|
|
|
if (mType & MainConsole) {
|
|
mpButtonMainLayer->setVisible(!mpHost->getCompactInputLine());
|
|
|
|
mpCommandLine->adjustHeight();
|
|
}
|
|
|
|
|
|
connect(mudlet::self(), &mudlet::signal_adjustAccessibleNames, this, &TConsole::slot_adjustAccessibleNames);
|
|
slot_adjustAccessibleNames();
|
|
// Need to delay doing this because it uses elements that may not have
|
|
// been constructed yet:
|
|
if (mType == MainConsole) {
|
|
QTimer::singleShot(0ms, this, [this]() {
|
|
setProxyForFocus(mpCommandLine);
|
|
});
|
|
}
|
|
}
|
|
|
|
TConsole::~TConsole()
|
|
{
|
|
#if defined(DEBUG_CODEPOINT_PROBLEMS)
|
|
if (mType & ~CentralDebugConsole) {
|
|
// Codepoint issues reporting is not enabled for the CDC:
|
|
mUpperPane->reportCodepointErrors();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
Host* TConsole::getHost()
|
|
{
|
|
return mpHost;
|
|
}
|
|
|
|
void TConsole::resizeConsole()
|
|
{
|
|
const QSize size = QSize(width(), height());
|
|
QResizeEvent event(size, size);
|
|
QApplication::sendEvent(this, &event);
|
|
}
|
|
|
|
|
|
void TConsole::raiseMudletSysWindowResizeEvent(const int overallWidth, const int overallHeight)
|
|
{
|
|
if (mpHost.isNull()) {
|
|
return;
|
|
}
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysWindowResizeEvent"));
|
|
mudletEvent.mArgumentList.append(QString::number(overallWidth - mBorders.left() - mBorders.right()));
|
|
mudletEvent.mArgumentList.append(QString::number(overallHeight - mBorders.top() - mBorders.bottom() - mpCommandLine->height()));
|
|
mudletEvent.mArgumentList.append(mConsoleName);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
}
|
|
|
|
void TConsole::resizeEvent(QResizeEvent* event)
|
|
{
|
|
if (mType & MainConsole) {
|
|
mBorders = mpHost->borders();
|
|
}
|
|
int x = event->size().width();
|
|
int y = event->size().height();
|
|
|
|
if (mType == MainConsole && !x) {
|
|
// When multi-view is NOT active but more than one profile is loaded
|
|
// switching between tabs causes the deselected profile to resize its
|
|
// main console to a width of zero - but that is not useful from a NAWS
|
|
// or event handling system point of view - so abort doing anything
|
|
// with the event:
|
|
return;
|
|
}
|
|
|
|
// prevents the command line from being hidden
|
|
if (layoutLayer2) {
|
|
layoutLayer2->invalidate();
|
|
layoutLayer2->activate();
|
|
}
|
|
|
|
// Move before resizing: resizing first could leave the display overrunning
|
|
// its parent frame, clipping it and making TTextEdit::updateScreenView()
|
|
// undercount the visible rows:
|
|
mpMainDisplay->move(mBorders.left(), mBorders.top());
|
|
if (mType & (MainConsole | SubConsole | UserWindow) && mpCommandLine && !mpCommandLine->isHidden()) {
|
|
mpMainFrame->resize(x, y);
|
|
mpBaseVFrame->resize(x, y);
|
|
mpBaseHFrame->resize(x, y);
|
|
x -= (mpLeftToolBar->width() + mpRightToolBar->width());
|
|
y -= mpTopToolBar->height();
|
|
// The mBorders components will be all zeros for all but the MainConsole:
|
|
mpMainDisplay->resize(x - mBorders.left() - mBorders.right(), y - mBorders.top() - mBorders.bottom() - mpCommandLine->height());
|
|
} else {
|
|
mpMainFrame->resize(x, y);
|
|
mpMainDisplay->resize(x, y);
|
|
}
|
|
|
|
if (mpWindowBackground) {
|
|
mpWindowBackground->resize(mpMainFrame->size());
|
|
if (mWindowBgImageMode == 5) {
|
|
updateWindowBackgroundCoverPixmap();
|
|
}
|
|
}
|
|
|
|
if (mType & (CentralDebugConsole | ErrorConsole)) {
|
|
layerCommandLine->hide();
|
|
} else if (mType & ~(SubConsole | UserWindow)) {
|
|
// does nothing for SubConsole or UserWindows
|
|
layerCommandLine->move(0, mpBaseVFrame->height() - layerCommandLine->height());
|
|
}
|
|
|
|
// MXP frames are positioned by hand against the space the borders leave, so
|
|
// they have to be moved whenever the window or those borders change
|
|
if ((mType & MainConsole) && !mpHost.isNull()) {
|
|
mpHost->mMxpFrameManager.scheduleRelayout();
|
|
}
|
|
|
|
// Sync Host dimensions on resize so wraps and NAWS reflect the current pane width.
|
|
if ((mType & MainConsole) && !mpHost.isNull() && mUpperPane && !mUpperPane->visibleRegion().isEmpty()) {
|
|
const int paneWidthPx = mUpperPane->visibleRegion().boundingRect().width();
|
|
auto syncHost = [paneWidthPx](Host* host, const QWidget* paneForFont) {
|
|
if (!host || !paneForFont) {
|
|
return;
|
|
}
|
|
const int fontWidth = QFontMetrics(paneForFont->font()).averageCharWidth();
|
|
if (fontWidth <= 0) {
|
|
return;
|
|
}
|
|
const int cols = qMax(40, paneWidthPx / fontWidth);
|
|
if (cols > 0 && cols != host->mScreenWidth) {
|
|
host->setScreenDimensions(cols, host->mScreenHeight);
|
|
QTimer::singleShot(0ms, host, &Host::updateDisplayDimensions);
|
|
}
|
|
};
|
|
|
|
syncHost(mpHost.data(), mUpperPane);
|
|
|
|
// Detached profiles have their own pixel width; only propagate from a main-window console.
|
|
mudlet* const app = mudlet::self();
|
|
const bool inMainWindow = app && !app->getDetachedWindows().contains(mpHost->getName());
|
|
if (inMainWindow) {
|
|
for (const auto& otherHostPtr : app->getHostManager()) {
|
|
Host* otherHost = otherHostPtr.data();
|
|
if (!otherHost || otherHost == mpHost.data()) {
|
|
continue;
|
|
}
|
|
// Skip detached profiles: different container, different width.
|
|
if (app->getDetachedWindows().contains(otherHost->getName())) {
|
|
continue;
|
|
}
|
|
if (!otherHost->mpConsole || !otherHost->mpConsole->mUpperPane) {
|
|
continue;
|
|
}
|
|
// Visible siblings (multi-view) handle their own resizeEvent.
|
|
if (!otherHost->mpConsole->mUpperPane->visibleRegion().isEmpty()) {
|
|
continue;
|
|
}
|
|
syncHost(otherHost, otherHost->mpConsole->mUpperPane);
|
|
}
|
|
}
|
|
}
|
|
|
|
emit resized(event);
|
|
QWidget::resizeEvent(event);
|
|
|
|
if (mType & MainConsole) {
|
|
// don't call event in lua if size didn't change
|
|
const bool preventLuaEvent = (getMainWindowSize() == mOldSize);
|
|
mOldSize = getMainWindowSize();
|
|
if (preventLuaEvent) {
|
|
return;
|
|
}
|
|
if (!mpHost.isNull()) {
|
|
TLuaInterpreter* pLua = mpHost->getLuaInterpreter();
|
|
const QString func = "handleWindowResizeEvent";
|
|
const QString n = "WindowResizeEvent";
|
|
pLua->call(func, n);
|
|
|
|
raiseMudletSysWindowResizeEvent(x, y);
|
|
}
|
|
}
|
|
//create the sysUserWindowResize Event for automatic resizing with Geyser
|
|
if (mType & (UserWindow) && !mpHost.isNull()) {
|
|
TLuaInterpreter* pLua = mpHost->getLuaInterpreter();
|
|
const QString func = "handleWindowResizeEvent";
|
|
const QString n = "WindowResizeEvent";
|
|
pLua->call(func, n);
|
|
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysUserWindowResizeEvent"));
|
|
mudletEvent.mArgumentList.append(QString::number(x));
|
|
mudletEvent.mArgumentList.append(QString::number(y));
|
|
mudletEvent.mArgumentList.append(mConsoleName);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
}
|
|
}
|
|
|
|
void TConsole::refresh()
|
|
{
|
|
if (mType == MainConsole) {
|
|
mBorders = mpHost->borders();
|
|
}
|
|
|
|
int x = width();
|
|
int y = height();
|
|
|
|
mpBaseVFrame->resize(x, y);
|
|
mpBaseHFrame->resize(x, y);
|
|
|
|
x = mpBaseVFrame->width();
|
|
if (!mpLeftToolBar->isHidden()) {
|
|
x -= mpLeftToolBar->width();
|
|
}
|
|
if (!mpRightToolBar->isHidden()) {
|
|
x -= mpRightToolBar->width();
|
|
}
|
|
|
|
y = mpBaseVFrame->height();
|
|
if (!mpTopToolBar->isHidden()) {
|
|
y -= mpTopToolBar->height();
|
|
}
|
|
|
|
// Move before resizing, see comment in resizeEvent():
|
|
mpMainDisplay->move(mBorders.left(), mBorders.top());
|
|
mpMainDisplay->resize(x - mBorders.left() - mBorders.right(), y - mBorders.top() - mBorders.bottom() - mpCommandLine->height());
|
|
|
|
if (!mpCommandLine.isNull()) {
|
|
mpCommandLine->adjustHeight();
|
|
}
|
|
|
|
x = width();
|
|
y = height();
|
|
const QSize s = QSize(x, y);
|
|
QResizeEvent event(s, s);
|
|
QApplication::sendEvent(this, &event);
|
|
}
|
|
|
|
void TConsole::clear()
|
|
{
|
|
mUpperPane->resetHScrollbar();
|
|
// before the buffer goes, or the selection is left pointing at lines that
|
|
// no longer exist and the copy actions work on out of range indices
|
|
clearSelection();
|
|
buffer.clear();
|
|
clearSplit();
|
|
mUpperPane->update();
|
|
mLowerPane->update();
|
|
}
|
|
|
|
void TConsole::clearSelection() const
|
|
{
|
|
mLowerPane->unHighlight();
|
|
mUpperPane->unHighlight();
|
|
mLowerPane->mSelectedRegion = QRegion(0, 0, 0, 0);
|
|
mUpperPane->mSelectedRegion = QRegion(0, 0, 0, 0);
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
|
|
void TConsole::closeEvent(QCloseEvent* event)
|
|
{
|
|
if (mType == CentralDebugConsole) {
|
|
if (mudlet::self()->isGoingDown() || mpHost->isClosingDown()) {
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
hide();
|
|
mudlet::smpDebugArea->setVisible(false);
|
|
mudlet::smDebugMode = false;
|
|
mudlet::self()->refreshTabBar();
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if (mType & (SubConsole | Buffer)) {
|
|
if (mudlet::self()->isGoingDown() || mpHost->isClosingDown()) {
|
|
auto pC = mpHost->mpConsole->mSubConsoleMap.take(mConsoleName);
|
|
if (pC) {
|
|
// As it happens pC will be identical to 'this' it is just that
|
|
// we will have removed it from the main TConsole's
|
|
// mSubConsoleMap:
|
|
mUpperPane->close();
|
|
mLowerPane->close();
|
|
}
|
|
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
hide();
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if (mType == UserWindow) {
|
|
if (mudlet::self()->isGoingDown() || mpHost->isClosingDown()) {
|
|
auto pC = mpHost->mpConsole->mSubConsoleMap.take(mConsoleName);
|
|
auto pD = mpHost->mpConsole->mDockWidgetMap.take(mConsoleName);
|
|
if (pC) {
|
|
// As it happens pC will be identical to 'this' it is just that
|
|
// we will have removed it from the main TConsole's
|
|
// mSubConsoleMap:
|
|
mUpperPane->close();
|
|
mLowerPane->close();
|
|
}
|
|
if (pD) {
|
|
pD->setAttribute(Qt::WA_DeleteOnClose);
|
|
pD->deleteLater();
|
|
} else {
|
|
qDebug() << "TConsole::closeEvent(QCloseEvent*) INFO - closing a UserWindow but the TDockWidget pointer was not found to be removed...";
|
|
}
|
|
|
|
// This will also cause the QWidget to be automatically hidden:
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
hide();
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if (mType == MainConsole) {
|
|
// The event should have been handled by the override in the TMainConsole
|
|
Q_ASSERT_X(false, "TConsole::closeEvent()", "Close event not handled by TMainConsole override.");
|
|
}
|
|
}
|
|
|
|
|
|
int TConsole::getButtonState()
|
|
{
|
|
return mButtonState;
|
|
}
|
|
|
|
// Converted into a wrapper around a separate toggleLogging() method so that
|
|
// calls to turn logging on/off via the toolbar button - which go via this
|
|
// wrapper - generate messages on the console. Requests to control logging from
|
|
// the Lua interpreter call the wrapped method directly and messages are
|
|
// generated for Lua user control by the Lua subsystem.
|
|
void TConsole::slot_toggleLogging()
|
|
{
|
|
if (mType & (CentralDebugConsole | ErrorConsole | SubConsole | UserWindow)) {
|
|
return;
|
|
// We don't support logging anything other than main console (at present?)
|
|
}
|
|
mpHost->mpConsole->toggleLogging(true);
|
|
}
|
|
|
|
// FIXME: This needs to move to the TMainConsole class but the button handling
|
|
// code is currently defined but not used for all TConsole instances - and some
|
|
// of them might be useful to have on the other ones...
|
|
void TConsole::slot_toggleReplayRecording()
|
|
{
|
|
if (mType & CentralDebugConsole) {
|
|
return;
|
|
}
|
|
mRecordReplay = !mRecordReplay;
|
|
if (mRecordReplay) {
|
|
const QString directoryLogFile = mudlet::getMudletPath(enums::profileReplayAndLogFilesPath, mProfileName);
|
|
const QString mLogFileName = qsl("%1/%2.dat").arg(directoryLogFile, QDateTime::currentDateTime().toString(qsl("yyyy-MM-dd#HH-mm-ss")));
|
|
const QDir dirLogFile;
|
|
if (!dirLogFile.exists(directoryLogFile)) {
|
|
dirLogFile.mkpath(directoryLogFile);
|
|
}
|
|
mReplayFile.setFileName(mLogFileName);
|
|
if (!mReplayFile.open(QIODevice::WriteOnly)) {
|
|
qWarning() << "TConsole: failed to open replay file for writing:" << mReplayFile.errorString();
|
|
mRecordReplay = false;
|
|
//: Informational message displayed when replay recording file could not be opened
|
|
printSystemMessage(tr("Failed to open replay recording file for writing.") % QChar::LineFeed);
|
|
return;
|
|
}
|
|
if (mudlet::scmRunTimeQtVersion >= QVersionNumber(5, 13, 0)) {
|
|
mReplayStream.setVersion(mudlet::scmQDataStreamFormat_5_12);
|
|
}
|
|
mReplayStream.setDevice(&mReplayFile);
|
|
mpHost->mTelnet.recordReplay();
|
|
printSystemMessage(tr("Replay recording has started. File: %1").arg(mReplayFile.fileName()) % QChar::LineFeed);
|
|
//: Button tooltip for the replay recording toggle button
|
|
replayButton->setToolTip(utils::richText(tr("Stop recording of replay")));
|
|
} else {
|
|
if (!mReplayFile.commit()) {
|
|
qDebug() << "TConsole::slot_toggleReplayRecording: error saving replay: " << mReplayFile.errorString();
|
|
//: Informational message displayed when replay recording is stopped but could not be saved
|
|
printSystemMessage(tr("Replay recording has been stopped, but couldn't be saved.") % QChar::LineFeed);
|
|
} else {
|
|
//: Informational message displayed when replay recording is stopped
|
|
printSystemMessage(tr("Replay recording has been stopped. File: %1").arg(mReplayFile.fileName()) % QChar::LineFeed);
|
|
}
|
|
//: Button tooltip for the replay recording toggle button
|
|
replayButton->setToolTip(utils::richText(tr("Start recording of replay")));
|
|
}
|
|
}
|
|
|
|
QString getColorCode(QColor color)
|
|
{
|
|
return qsl("%1,%2,%3,%4").arg(color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha());
|
|
}
|
|
|
|
// Builds the QSS fragment shared by setConsoleBackgroundImage()/setWindowBackgroundImage()
|
|
// for modes 1-4 ('border'/'center'/'tile'/'style'). Mode 5 ('cover') is not stylesheet-based
|
|
// (Qt's border-image/background-image have no scale-to-fill option) and is handled separately.
|
|
static QString buildBackgroundImageStyleSheet(const QString& objectName, const QColor& bgColor, int mode, const QString& imgPath)
|
|
{
|
|
if (mode == 1) {
|
|
return qsl("QWidget#%1{background-color: rgba(%2); border-image: url(%3);}").arg(objectName, getColorCode(bgColor), imgPath);
|
|
} else if (mode == 2) {
|
|
return qsl("QWidget#%1{background-color: rgba(%2); background-image: url(%3); background-repeat: no-repeat; background-position: center; background-origin: margin;}")
|
|
.arg(objectName, getColorCode(bgColor), imgPath);
|
|
} else if (mode == 3) {
|
|
return qsl("QWidget#%1{background-color: rgba(%2); background-image: url(%3);}").arg(objectName, getColorCode(bgColor), imgPath);
|
|
} else if (mode == 4) {
|
|
return qsl("QWidget#%1{background-color: rgba(%2); %3}").arg(objectName, getColorCode(bgColor), imgPath);
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
void TConsole::changeColors()
|
|
{
|
|
if (mType == CentralDebugConsole) {
|
|
// Font is managed via QWidget::font() inheritance, no font operations needed here
|
|
} else if (mType & (ErrorConsole | SubConsole | UserWindow | Buffer)) {
|
|
if (!mBgImageMode) {
|
|
auto styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1);}").arg(getColorCode(mBgColor));
|
|
mpMainDisplay->setStyleSheet(styleSheet);
|
|
|
|
QPalette transparentBgPalette;
|
|
transparentBgPalette.setColor(QPalette::Window, QColor(0, 0, 0, 0));
|
|
mpBaseVFrame->setPalette(transparentBgPalette);
|
|
mpBaseHFrame->setPalette(transparentBgPalette);
|
|
mpMainFrame->setPalette(transparentBgPalette);
|
|
mpMainDisplay->setPalette(transparentBgPalette);
|
|
setPalette(transparentBgPalette);
|
|
} else {
|
|
setConsoleBackgroundImage(mBgImagePath, mBgImageMode);
|
|
}
|
|
} else if (mType == MainConsole) {
|
|
if (mpCommandLine) {
|
|
auto styleSheet = mpCommandLine->styleSheet();
|
|
mpCommandLine->setStyleSheet(QString());
|
|
// CHECK: This seems to be a, possibly iffy, attempt to combine a
|
|
// QPalette with a style-sheet - though the Qt Documentation does
|
|
// seem to say one should not mix QPalettes with styles/stylesheets!
|
|
QPalette commandLinePalette;
|
|
commandLinePalette.setColor(QPalette::Text, mpHost->mCommandLineFgColor);
|
|
commandLinePalette.setColor(QPalette::Highlight, QColor(0, 0, 192));
|
|
commandLinePalette.setColor(QPalette::HighlightedText, QColor(Qt::white));
|
|
commandLinePalette.setColor(QPalette::Base, mpHost->mCommandLineBgColor);
|
|
commandLinePalette.setColor(QPalette::Window, mpHost->mCommandLineBgColor);
|
|
mpCommandLine->setPalette(commandLinePalette);
|
|
mpButtonMainLayer->setPalette(commandLinePalette);
|
|
mpCommandLine->mRegularPalette = commandLinePalette;
|
|
mpCommandLine->setStyleSheet(styleSheet);
|
|
}
|
|
if (!mBgImageMode) {
|
|
auto styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1);}").arg(getColorCode(mpHost->mBgColor));
|
|
mpMainDisplay->setStyleSheet(styleSheet);
|
|
} else {
|
|
setConsoleBackgroundImage(mBgImagePath, mBgImageMode);
|
|
}
|
|
mBgColor = mpHost->mBgColor;
|
|
mFgColor = mpHost->mFgColor;
|
|
mCommandFgColor = mpHost->mCommandFgColor;
|
|
mCommandBgColor = mpHost->mCommandBgColor;
|
|
mFormatCurrent.setColors(mpHost->mFgColor, mpHost->mBgColor);
|
|
updateMainFrameTransparency();
|
|
} else {
|
|
Q_ASSERT_X(false, "TConsole::changeColors()", "invalid TConsole type detected");
|
|
}
|
|
|
|
buffer.updateColors();
|
|
if (mType & (MainConsole | Buffer)) {
|
|
buffer.mWrapAt = mpHost->mWrapAt;
|
|
buffer.mWrapIndent = mpHost->mWrapIndentCount;
|
|
buffer.mWrapHangingIndent = mpHost->mWrapHangingIndentCount;
|
|
}
|
|
}
|
|
|
|
void TConsole::setConsoleBgColor(int r, int g, int b, int a)
|
|
{
|
|
mBgColor = QColor(r, g, b, a);
|
|
mUpperPane->setConsoleBgColor(r, g, b, a);
|
|
mLowerPane->setConsoleBgColor(r, g, b, a);
|
|
changeColors();
|
|
}
|
|
|
|
// Not used:
|
|
//void TConsole::setConsoleFgColor(int r, int g, int b)
|
|
//{
|
|
// mFgColor = QColor(r, g, b);
|
|
// mUpperPane->setConsoleFgColor(r, g, b);
|
|
// mLowerPane->setConsoleFgColor(r, g, b);
|
|
// changeColors();
|
|
//}
|
|
|
|
/*std::string TConsole::getCurrentTime()
|
|
{
|
|
time_t t;
|
|
time(&t);
|
|
tm lt;
|
|
ostringstream s;
|
|
s.str("");
|
|
struct timeval tv;
|
|
struct timezone tz;
|
|
gettimeofday(&tv, &tz);
|
|
localtime_r( &t, < );
|
|
s << "["<<lt.tm_hour<<":"<<lt.tm_min<<":"<<lt.tm_sec<<":"<<tv.tv_usec<<"]";
|
|
string time = s.str();
|
|
return time;
|
|
} */
|
|
|
|
/* ANSI color codes: sequence = "ESCAPE + [ code_1; ... ; code_n m"
|
|
-----------------------------------------
|
|
0 reset
|
|
1 intensity bold on
|
|
2 intensity faint
|
|
3 italics on
|
|
4 underline on
|
|
5 blink slow
|
|
6 blink fast
|
|
7 inverse on
|
|
9 strikethrough
|
|
22 intensity normal (not bold, not faint)
|
|
23 italics off
|
|
24 underline off
|
|
27 inverse off
|
|
29 strikethrough off
|
|
30 fg black
|
|
31 fg red
|
|
32 fg green
|
|
33 fg yellow
|
|
34 fg blue
|
|
35 fg magenta
|
|
36 fg cyan
|
|
37 fg white
|
|
39 bg default white
|
|
40 bg black
|
|
41 bg red
|
|
42 bg green
|
|
43 bg yellow
|
|
44 bg blue
|
|
45 bg magenta
|
|
46 bg cyan
|
|
47 bg white
|
|
49 bg black */
|
|
|
|
void TConsole::scrollDown(int lines)
|
|
{
|
|
if ((mType & (UserWindow | SubConsole)) && !mScrollingEnabled) {
|
|
return;
|
|
}
|
|
|
|
mUpperPane->scrollDown(lines);
|
|
if (!mUpperPane->mIsTailMode && (mUpperPane->imageTopLine() + mUpperPane->getScreenHeight() >= buffer.lineBuffer.size() - mLowerPane->getRowCount())) {
|
|
mUpperPane->scrollDown(mLowerPane->getRowCount() + 100); // Gets to the bottom
|
|
mUpperPane->scrollDown(100); // needs another scroll to force mIsTailMode
|
|
}
|
|
if (mUpperPane->mIsTailMode) {
|
|
mLowerPane->mCursorY = buffer.lineBuffer.size();
|
|
mLowerPane->hide();
|
|
|
|
mUpperPane->mCursorY = buffer.lineBuffer.size();
|
|
mUpperPane->updateScreenView();
|
|
mUpperPane->forceUpdate();
|
|
}
|
|
slot_adjustAccessibleNames();
|
|
}
|
|
|
|
void TConsole::scrollUp(int lines)
|
|
{
|
|
if ((mType & (UserWindow | SubConsole)) && !mScrollingEnabled) {
|
|
return;
|
|
}
|
|
|
|
const bool lowerAppears = mLowerPane->isHidden();
|
|
mLowerPane->mCursorY = buffer.size();
|
|
mLowerPane->show();
|
|
mLowerPane->updateScreenView();
|
|
mLowerPane->forceUpdate();
|
|
|
|
if (lowerAppears) {
|
|
QTimer::singleShot(0ms, this, [this, lines]() {
|
|
mUpperPane->scrollUp(mLowerPane->getRowCount() + lines);
|
|
});
|
|
if (mudlet::self()->showSplitscreenTutorial()) {
|
|
#if defined(Q_OS_MACOS)
|
|
const QString infoMsg = tr("[ INFO ] - Split-screen scrollback activated. Press <⌘>+<ENTER> to cancel.");
|
|
#else
|
|
const QString infoMsg = tr("[ INFO ] - Split-screen scrollback activated. Press <CTRL>+<ENTER> to cancel.");
|
|
#endif
|
|
mpHost->postMessage(infoMsg);
|
|
mudlet::self()->showedSplitscreenTutorial();
|
|
}
|
|
} else {
|
|
mUpperPane->scrollUp(lines);
|
|
}
|
|
slot_adjustAccessibleNames();
|
|
}
|
|
|
|
void TConsole::deselect()
|
|
{
|
|
P_begin = QPoint();
|
|
P_end = QPoint();
|
|
}
|
|
|
|
void TConsole::showEvent(QShowEvent* event)
|
|
{
|
|
if (mType & (MainConsole | Buffer)) {
|
|
if (mpHost) {
|
|
mAlertOnNewData = false;
|
|
}
|
|
}
|
|
QWidget::showEvent(event); //FIXME-refac: might cause problems
|
|
}
|
|
|
|
void TConsole::hideEvent(QHideEvent* event)
|
|
{
|
|
if (mType & (MainConsole | Buffer)) {
|
|
if (mpHost) {
|
|
if (mudlet::self()->mWindowMinimized) {
|
|
if (mpHost->mAlertOnNewData) {
|
|
mAlertOnNewData = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
QWidget::hideEvent(event); //FIXME-refac: might cause problems
|
|
}
|
|
|
|
|
|
void TConsole::reset()
|
|
{
|
|
deselect();
|
|
mFormatCurrent.setColors(mFgColor, mBgColor);
|
|
mFormatCurrent.setAllDisplayAttributes(TChar::None);
|
|
}
|
|
|
|
void TConsole::insertLink(const QString& text, QStringList& func, QStringList& hint, QPoint P, bool customFormat, QVector<int> luaReference)
|
|
{
|
|
const int x = P.x();
|
|
const int y = P.y();
|
|
QPoint P2 = P;
|
|
P2.setX(x + text.size());
|
|
|
|
const TChar standardLinkFormat = TChar(Qt::blue, mBgColor, TChar::Underline);
|
|
if (mTriggerEngineMode) {
|
|
mpHost->getLuaInterpreter()->adjustCaptureGroups(x, text.size());
|
|
|
|
if (customFormat) {
|
|
buffer.insertInLine(P, text, mFormatCurrent);
|
|
} else {
|
|
buffer.insertInLine(P, text, standardLinkFormat);
|
|
}
|
|
|
|
buffer.applyLink(P, P2, func, hint, luaReference);
|
|
|
|
if (y < mEngineCursor) {
|
|
mUpperPane->needUpdate(mUserCursor.y(), mUserCursor.y() + 1);
|
|
}
|
|
return;
|
|
}
|
|
if ((buffer.buffer.empty()) || mUserCursor == buffer.getEndPos()) {
|
|
if (customFormat) {
|
|
buffer.addLink(mTriggerEngineMode, text, func, hint, mFormatCurrent, luaReference);
|
|
} else {
|
|
buffer.addLink(mTriggerEngineMode, text, func, hint, standardLinkFormat, luaReference);
|
|
}
|
|
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
|
|
} else {
|
|
if (customFormat) {
|
|
buffer.insertInLine(mUserCursor, text, mFormatCurrent);
|
|
} else {
|
|
buffer.insertInLine(mUserCursor, text, standardLinkFormat);
|
|
}
|
|
|
|
buffer.applyLink(P, P2, func, hint, luaReference);
|
|
if (text.indexOf("\n") != -1) {
|
|
const int y_tmp = mUserCursor.y();
|
|
const int down = buffer.wrapLine(mUserCursor.y(), mpHost->mScreenWidth, mpHost->mWrapIndentCount, mpHost->mWrapHangingIndentCount);
|
|
mUpperPane->needUpdate(y_tmp, y_tmp + down + 1);
|
|
const int y_neu = y_tmp + down;
|
|
const int x_adjust = text.lastIndexOf("\n");
|
|
int x_neu = 0;
|
|
if (x_adjust != -1) {
|
|
x_neu = text.size() - x_adjust - 1 > 0 ? text.size() - x_adjust - 1 : 0;
|
|
}
|
|
moveCursor(x_neu, y_neu);
|
|
} else {
|
|
mUpperPane->needUpdate(mUserCursor.y(), mUserCursor.y() + 1);
|
|
moveCursor(mUserCursor.x() + text.size(), mUserCursor.y());
|
|
}
|
|
}
|
|
}
|
|
|
|
void TConsole::insertText(const QString& text, QPoint P)
|
|
{
|
|
const int x = P.x();
|
|
const int y = P.y();
|
|
if (mTriggerEngineMode) {
|
|
mpHost->getLuaInterpreter()->adjustCaptureGroups(x, text.size());
|
|
buffer.insertInLine(P, text, mFormatCurrent);
|
|
if (y < mEngineCursor) {
|
|
mUpperPane->needUpdate(mUserCursor.y(), mUserCursor.y() + 1);
|
|
}
|
|
|
|
} else {
|
|
if ((buffer.buffer.empty()) || mUserCursor == buffer.getEndPos()) {
|
|
buffer.append(text, 0, text.size(), mFormatCurrent);
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
} else {
|
|
buffer.insertInLine(mUserCursor, text, mFormatCurrent);
|
|
const int y_tmp = mUserCursor.y();
|
|
if (text.indexOf(QChar::LineFeed) != -1) {
|
|
const int down = buffer.wrapLine(y_tmp, mpHost->mScreenWidth, mpHost->mWrapIndentCount, mpHost->mWrapHangingIndentCount);
|
|
mUpperPane->needUpdate(y_tmp, y_tmp + down + 1);
|
|
} else {
|
|
mUpperPane->needUpdate(y_tmp, y_tmp + 1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void TConsole::replace(const QString& text)
|
|
{
|
|
const int x = P_begin.x();
|
|
const int o = P_end.x() - P_begin.x();
|
|
const int r = text.size();
|
|
|
|
if (mTriggerEngineMode) {
|
|
if (hasSelection()) {
|
|
if (r < o) {
|
|
const int a = -1 * (o - r);
|
|
mpHost->getLuaInterpreter()->adjustCaptureGroups(x, a);
|
|
}
|
|
if (r > o) {
|
|
const int a = r - o;
|
|
mpHost->getLuaInterpreter()->adjustCaptureGroups(x, a);
|
|
}
|
|
} else {
|
|
mpHost->getLuaInterpreter()->adjustCaptureGroups(x, r);
|
|
}
|
|
}
|
|
|
|
buffer.replaceInLine(P_begin, P_end, text, mFormatCurrent);
|
|
}
|
|
|
|
void TConsole::skipLine()
|
|
{
|
|
deleteLine(mUserCursor.y());
|
|
}
|
|
|
|
bool TConsole::deleteLine(int y)
|
|
{
|
|
return buffer.deleteLine(y);
|
|
}
|
|
|
|
bool TConsole::hasSelection()
|
|
{
|
|
if (P_begin != P_end) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void TConsole::insertText(const QString& msg)
|
|
{
|
|
insertText(msg, mUserCursor);
|
|
}
|
|
|
|
void TConsole::insertLink(const QString& text, QStringList& func, QStringList& hint, bool customFormat, QVector<int> luaReference)
|
|
{
|
|
insertLink(text, func, hint, mUserCursor, customFormat, luaReference);
|
|
}
|
|
|
|
void TConsole::insertHTML(const QString& text)
|
|
{
|
|
insertText(text);
|
|
}
|
|
|
|
int TConsole::getLineNumber()
|
|
{
|
|
return mUserCursor.y();
|
|
}
|
|
|
|
int TConsole::getColumnNumber()
|
|
{
|
|
return mUserCursor.x();
|
|
}
|
|
|
|
int TConsole::getWrapAt()
|
|
{
|
|
return buffer.mWrapAt;
|
|
}
|
|
|
|
int TConsole::getLineCount()
|
|
{
|
|
return buffer.getLastLineNumber();
|
|
}
|
|
|
|
QStringList TConsole::getLines(int from, int to)
|
|
{
|
|
QStringList ret;
|
|
const int delta = abs(from - to);
|
|
for (int i = 0; i < delta; i++) {
|
|
ret << buffer.line(from + i);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void TConsole::selectCurrentLine()
|
|
{
|
|
selectSection(0, buffer.line(mUserCursor.y()).size());
|
|
}
|
|
|
|
std::list<int> TConsole::getFgColor()
|
|
{
|
|
std::list<int> result;
|
|
const int x = P_begin.x();
|
|
const int y = P_begin.y();
|
|
if (y < 0) {
|
|
return result;
|
|
}
|
|
if (x < 0) {
|
|
return result;
|
|
}
|
|
if (y >= static_cast<int>(buffer.buffer.size())) {
|
|
return result;
|
|
}
|
|
|
|
auto line = buffer.buffer.at(y);
|
|
const int len = static_cast<int>(line.size());
|
|
if (len - 1 >= x) {
|
|
const QColor color(line.at(x).foreground());
|
|
result.push_back(color.red());
|
|
result.push_back(color.green());
|
|
result.push_back(color.blue());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::list<int> TConsole::getBgColor()
|
|
{
|
|
std::list<int> result;
|
|
const int x = P_begin.x();
|
|
const int y = P_begin.y();
|
|
if (y < 0) {
|
|
return result;
|
|
}
|
|
if (x < 0) {
|
|
return result;
|
|
}
|
|
if (y >= static_cast<int>(buffer.buffer.size())) {
|
|
return result;
|
|
}
|
|
|
|
auto line = buffer.buffer.at(y);
|
|
const int len = static_cast<int>(line.size());
|
|
if (len - 1 >= x) {
|
|
const QColor color(line.at(x).background());
|
|
result.push_back(color.red());
|
|
result.push_back(color.green());
|
|
result.push_back(color.blue());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QPair<quint8, TChar> TConsole::getTextAttributes() const
|
|
{
|
|
// Take snapshots of cursor/selection coordinates to avoid race conditions
|
|
const QPoint beginPoint = P_begin;
|
|
const QPoint endPoint = P_end;
|
|
const QPoint userCursorPoint = mUserCursor;
|
|
|
|
int x = beginPoint.x();
|
|
int y = beginPoint.y();
|
|
|
|
// Fallback to cursor position if no selection is active
|
|
if (beginPoint == endPoint) {
|
|
x = userCursorPoint.x();
|
|
y = userCursorPoint.y();
|
|
}
|
|
|
|
// Take a snapshot of buffer size to avoid TOCTOU issues
|
|
const int bufferSize = static_cast<int>(buffer.buffer.size());
|
|
|
|
// Early bounds check
|
|
if (y < 0 || x < 0 || y >= bufferSize) {
|
|
return qMakePair(2, TChar());
|
|
}
|
|
|
|
// Get line reference and check its bounds safely
|
|
const auto& line = buffer.buffer.at(y);
|
|
const int lineSize = static_cast<int>(line.size());
|
|
|
|
if (x >= lineSize) {
|
|
return qMakePair(2, TChar());
|
|
}
|
|
|
|
// Safe access with bounds already verified
|
|
return qMakePair(0, line.at(x));
|
|
}
|
|
|
|
void TConsole::luaWrapLine(int line)
|
|
{
|
|
if (!mpHost) {
|
|
return;
|
|
}
|
|
buffer.wrapLine(line, mWrapAt, mIndentCount, mHangingIndentCount);
|
|
}
|
|
|
|
void TConsole::setFontSize(int size)
|
|
{
|
|
if (mDisplayFontDetails.mPointSize != size) {
|
|
mDisplayFontDetails.mPointSize = size;
|
|
setFont(mDisplayFontDetails.makeFont(), true);
|
|
}
|
|
}
|
|
|
|
bool TConsole::setConsoleBackgroundImage(const QString& imgPath, int mode)
|
|
{
|
|
const QColor bgColor = (mType == MainConsole) ? mpHost->mBgColor : mBgColor;
|
|
|
|
const QString styleSheet = buildBackgroundImageStyleSheet(qsl("MainDisplay"), bgColor, mode, imgPath);
|
|
if (styleSheet.isEmpty()) {
|
|
return false;
|
|
}
|
|
mpMainDisplay->setStyleSheet(styleSheet);
|
|
mBgImageMode = mode;
|
|
mBgImagePath = imgPath;
|
|
return true;
|
|
}
|
|
|
|
bool TConsole::resetConsoleBackgroundImage()
|
|
{
|
|
mBgImageMode = 0;
|
|
changeColors();
|
|
return true;
|
|
}
|
|
|
|
bool TConsole::setWindowBackgroundImage(const QString& imgPath, int mode)
|
|
{
|
|
if (!mpWindowBackground) {
|
|
return false;
|
|
}
|
|
|
|
if (mode == 5) {
|
|
QPixmap pixmap(imgPath);
|
|
if (pixmap.isNull()) {
|
|
qWarning().nospace().noquote() << "TConsole::setWindowBackgroundImage() ERROR - could not load \"" << imgPath << "\" as an image.";
|
|
return false;
|
|
}
|
|
const QPixmap previousSource = mWindowBgSourcePixmap;
|
|
const QString previousPath = mWindowBgImagePath;
|
|
const QString previousStyleSheet = mpWindowBackground->styleSheet();
|
|
mWindowBgSourcePixmap = pixmap;
|
|
mWindowBgImagePath = imgPath;
|
|
// clearing a stylesheet repolishes the widget and drops the palette brush,
|
|
// so it has to happen before the brush is installed
|
|
mpWindowBackground->setStyleSheet(QString());
|
|
if (!updateWindowBackgroundCoverPixmap()) {
|
|
mWindowBgSourcePixmap = previousSource;
|
|
mWindowBgImagePath = previousPath;
|
|
mpWindowBackground->setStyleSheet(previousStyleSheet);
|
|
// the failed attempt dropped the brush, so rebuild the one the previous
|
|
// source was showing rather than waiting for the next resize
|
|
updateWindowBackgroundCoverPixmap();
|
|
return false;
|
|
}
|
|
} else {
|
|
const QColor bgColor = mpHost ? mpHost->mBgColor : QColorConstants::Black;
|
|
const QString styleSheet = buildBackgroundImageStyleSheet(qsl("WindowBackground"), bgColor, mode, imgPath);
|
|
if (styleSheet.isEmpty()) {
|
|
return false;
|
|
}
|
|
mWindowBgSourcePixmap = QPixmap();
|
|
mpWindowBackground->setAutoFillBackground(false);
|
|
mpWindowBackground->setPalette(QPalette());
|
|
mpWindowBackground->setStyleSheet(styleSheet);
|
|
}
|
|
|
|
mWindowBgImageMode = mode;
|
|
mWindowBgImagePath = imgPath;
|
|
updateMainFrameTransparency();
|
|
return true;
|
|
}
|
|
|
|
bool TConsole::resetWindowBackgroundImage()
|
|
{
|
|
if (!mpWindowBackground) {
|
|
return false;
|
|
}
|
|
|
|
mWindowBgImageMode = 0;
|
|
mWindowBgImagePath.clear();
|
|
mWindowBgSourcePixmap = QPixmap();
|
|
mpWindowBackground->setStyleSheet(QString());
|
|
mpWindowBackground->setAutoFillBackground(false);
|
|
mpWindowBackground->setPalette(QPalette());
|
|
updateMainFrameTransparency();
|
|
return true;
|
|
}
|
|
|
|
void TConsole::updateMainFrameTransparency()
|
|
{
|
|
if (mType != MainConsole || !mpMainFrame) {
|
|
return;
|
|
}
|
|
|
|
QPalette framePalette;
|
|
framePalette.setColor(QPalette::Text, QColor(Qt::black));
|
|
framePalette.setColor(QPalette::Highlight, QColor(55, 55, 255));
|
|
framePalette.setColor(QPalette::Window, mWindowBgImageMode ? QColor(0, 0, 0, 0) : mBorderColor);
|
|
mpMainFrame->setPalette(framePalette);
|
|
mpMainFrame->setAutoFillBackground(true);
|
|
}
|
|
|
|
void TConsole::setBorderColor(const QColor& color)
|
|
{
|
|
mBorderColor = color;
|
|
updateMainFrameTransparency();
|
|
}
|
|
|
|
void TConsole::lowerMainDisplay()
|
|
{
|
|
mpMainDisplay->lower();
|
|
if (mpWindowBackground) {
|
|
mpWindowBackground->lower();
|
|
}
|
|
}
|
|
|
|
// The largest centred rectangle of the source that has the target's aspect ratio.
|
|
QRect TConsole::coverSourceRect(const QSize& sourceSize, const QSize& targetSize)
|
|
{
|
|
QSize cropSize = targetSize;
|
|
cropSize.scale(sourceSize, Qt::KeepAspectRatio);
|
|
cropSize = cropSize.boundedTo(sourceSize).expandedTo(QSize(1, 1));
|
|
return QRect(QPoint((sourceSize.width() - cropSize.width()) / 2, (sourceSize.height() - cropSize.height()) / 2), cropSize);
|
|
}
|
|
|
|
// Simulates CSS "cover" since QT stylesheets do not support it. Crop first: the
|
|
// other order multiplies the intermediate by the aspect mismatch, so a 3000x100
|
|
// image in a 1920x1080 window builds a 32400x1080 (~140MB) one on every resize.
|
|
bool TConsole::updateWindowBackgroundCoverPixmap()
|
|
{
|
|
if (!mpWindowBackground || mWindowBgSourcePixmap.isNull()) {
|
|
return true;
|
|
}
|
|
|
|
const QSize targetSize = mpWindowBackground->size();
|
|
if (targetSize.isEmpty()) {
|
|
return true;
|
|
}
|
|
|
|
const QRect sourceRect = coverSourceRect(mWindowBgSourcePixmap.size(), targetSize);
|
|
const QPixmap cropped = (sourceRect == mWindowBgSourcePixmap.rect()) ? mWindowBgSourcePixmap : mWindowBgSourcePixmap.copy(sourceRect);
|
|
const QPixmap scaled = cropped.scaled(targetSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
|
|
if (scaled.isNull()) {
|
|
if (!mWindowBgCoverScaleFailed) {
|
|
mWindowBgCoverScaleFailed = true;
|
|
qWarning().nospace().noquote() << "TConsole::updateWindowBackgroundCoverPixmap() ERROR - could not scale \"" << mWindowBgImagePath << "\" (source area " << sourceRect << ") to "
|
|
<< targetSize << '.';
|
|
}
|
|
// a brush smaller than the widget tiles, so drop the stale one
|
|
mpWindowBackground->setAutoFillBackground(false);
|
|
mpWindowBackground->setPalette(QPalette());
|
|
return false;
|
|
}
|
|
mWindowBgCoverScaleFailed = false;
|
|
|
|
QPalette palette;
|
|
palette.setBrush(QPalette::Window, QBrush(scaled));
|
|
mpWindowBackground->setPalette(palette);
|
|
mpWindowBackground->setAutoFillBackground(true);
|
|
return true;
|
|
}
|
|
|
|
void TConsole::setCmdVisible(bool isVisible)
|
|
{
|
|
const QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
// create MiniConsole commandline if it's not existing
|
|
if (!mpCommandLine) {
|
|
if (!isVisible) {
|
|
// If we don't have one and we are being told to hide it then
|
|
// really there is nothing to do - so lets do nothing:
|
|
return;
|
|
}
|
|
mpCommandLine = new TCommandLine(mpHost, mConsoleName, TCommandLine::ConsoleCommandLine, this, mpMainDisplay);
|
|
mpCommandLine->setContentsMargins(0, 0, 0, 0);
|
|
mpCommandLine->setSizePolicy(sizePolicy);
|
|
mpCommandLine->setFocusPolicy(Qt::StrongFocus);
|
|
mpCommandLine->setFont(font());
|
|
// put this CommandLine in the mainConsoles SubCommandLineMap
|
|
// name is the console name
|
|
mpHost->mpConsole->registerSubCommandLine(mConsoleName, mpCommandLine);
|
|
layoutLayer2->addWidget(mpCommandLine);
|
|
}
|
|
if (mType == MainConsole) {
|
|
if (mpHost) {
|
|
if (!mpHost->getCompactInputLine() && isVisible) {
|
|
mpButtonMainLayer->setVisible(true);
|
|
} else {
|
|
mpButtonMainLayer->setVisible(false);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
mpButtonMainLayer->setVisible(false);
|
|
}
|
|
layerCommandLine->setVisible(isVisible);
|
|
mpCommandLine->setVisible(isVisible);
|
|
//resizes miniconsole if command line gets enabled/disabled
|
|
resizeConsole();
|
|
setProxyForFocus(isVisible ? mpCommandLine : nullptr);
|
|
// Need to remove the TCommandLine from the last used stack
|
|
// if it has been explicitly hidden:
|
|
if (!isVisible && mpHost) {
|
|
mpHost->forgetCommandLine(mpCommandLine);
|
|
}
|
|
}
|
|
|
|
void TConsole::refreshView() const
|
|
{
|
|
mUpperPane->setFont(font());
|
|
mUpperPane->updateScreenView();
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->setFont(font());
|
|
mLowerPane->updateScreenView();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
void TConsole::raiseFontChangeEvent()
|
|
{
|
|
if (!mpHost) {
|
|
return;
|
|
}
|
|
if (!(mType & (MainConsole | UserWindow | SubConsole))) {
|
|
return;
|
|
}
|
|
|
|
TEvent fontChangeEvent{};
|
|
fontChangeEvent.mArgumentList.append(QLatin1String("sysFontChangeEvent"));
|
|
fontChangeEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
fontChangeEvent.mArgumentList.append(mConsoleName);
|
|
fontChangeEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
fontChangeEvent.mArgumentList.append(font().family());
|
|
fontChangeEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
fontChangeEvent.mArgumentList.append(QString::number(font().pointSize()));
|
|
fontChangeEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mpHost->raiseEvent(fontChangeEvent);
|
|
}
|
|
|
|
void TConsole::setFont(const QFont& newFont, const bool forceChange)
|
|
{
|
|
TFontAttributes newFontDetails(newFont);
|
|
if (forceChange || (mDisplayFontDetails != newFontDetails)) {
|
|
mDisplayFontDetails = newFontDetails;
|
|
QWidget::setFont(newFont);
|
|
// Update associated TCommandLine's:
|
|
if (mType & (MainConsole | SubConsole | UserWindow)) {
|
|
if (mpHost && mpHost->mpConsole) {
|
|
for (auto& commandLine : mpHost->mpConsole->mSubCommandLineMap) {
|
|
auto pConsole = commandLine->console();
|
|
if (pConsole && (pConsole == this)) {
|
|
commandLine->setFont(font());
|
|
commandLine->adjustHeight();
|
|
}
|
|
}
|
|
}
|
|
if (!mpCommandLine.isNull()) {
|
|
mpCommandLine->setFont(font());
|
|
mpCommandLine->adjustHeight();
|
|
}
|
|
}
|
|
refreshView();
|
|
raiseFontChangeEvent();
|
|
}
|
|
}
|
|
|
|
void TConsole::setFontName(const QString& fontName)
|
|
{
|
|
mDisplayFontDetails.mName = fontName;
|
|
setFont(mDisplayFontDetails.makeFont(), true);
|
|
}
|
|
|
|
QString TConsole::getCurrentLine()
|
|
{
|
|
return buffer.line(mUserCursor.y());
|
|
}
|
|
|
|
int TConsole::getLastLineNumber()
|
|
{
|
|
return buffer.getLastLineNumber();
|
|
}
|
|
|
|
void TConsole::moveCursorEnd()
|
|
{
|
|
const int y = buffer.getLastLineNumber();
|
|
int x = buffer.line(y).size() - 1;
|
|
x = x >= 0 ? x : 0;
|
|
moveCursor(x, y);
|
|
}
|
|
|
|
bool TConsole::moveCursor(int x, int y)
|
|
{
|
|
QPoint P(x, y);
|
|
if (buffer.moveCursor(P)) {
|
|
mUserCursor.setX(x);
|
|
mUserCursor.setY(y);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int TConsole::select(const QString& text, int numOfMatch)
|
|
{
|
|
if (mUserCursor.y() < 0 || mUserCursor.y() >= buffer.size()) {
|
|
deselect();
|
|
return -1;
|
|
}
|
|
|
|
if (mudlet::smDebugMode) {
|
|
TDebug(Qt::darkMagenta, Qt::black) << "line under current user cursor: " >> mpHost;
|
|
TDebug(Qt::red, Qt::black) << TDebug::csmContinue << mUserCursor.y() << "#:" >> mpHost;
|
|
TDebug(Qt::gray, Qt::black) << TDebug::csmContinue << buffer.line(mUserCursor.y()) << "\n" >> mpHost;
|
|
}
|
|
|
|
int begin = -1;
|
|
for (int i = 0; i < numOfMatch; i++) {
|
|
const QString li = buffer.line(mUserCursor.y());
|
|
if (li.isEmpty()) {
|
|
continue;
|
|
}
|
|
begin = li.indexOf(text, begin + 1);
|
|
|
|
if (begin == -1) {
|
|
deselect();
|
|
return -1;
|
|
}
|
|
}
|
|
if (begin < 0) {
|
|
deselect();
|
|
return -1;
|
|
}
|
|
|
|
const int end = begin + text.size();
|
|
P_begin = QPoint(begin, mUserCursor.y());
|
|
P_end = QPoint(end, mUserCursor.y());
|
|
|
|
if (mudlet::smDebugMode) {
|
|
TDebug(Qt::darkRed, Qt::black) << "P_begin(" << P_begin.x() << "/" << P_begin.y() << "), P_end(" << P_end.x() << "/" << P_end.y()
|
|
<< ") selectedText = " << buffer.line(mUserCursor.y()).mid(P_begin.x(), P_end.x() - P_begin.x()) << "\n"
|
|
>> mpHost;
|
|
}
|
|
return begin;
|
|
}
|
|
|
|
bool TConsole::selectSection(int from, int to)
|
|
{
|
|
if (mudlet::smDebugMode) {
|
|
TDebug(Qt::darkMagenta, Qt::black) << "selectSection(" << from << "," << to << "): line under current user cursor: " << buffer.line(mUserCursor.y()) << "\n" >> mpHost;
|
|
}
|
|
if (from < 0) {
|
|
return false;
|
|
}
|
|
if (mUserCursor.y() >= static_cast<int>(buffer.buffer.size())) {
|
|
return false;
|
|
}
|
|
const int s = buffer.buffer[mUserCursor.y()].size();
|
|
if (from > s || from + to > s) {
|
|
return false;
|
|
}
|
|
P_begin = QPoint(from, mUserCursor.y());
|
|
P_end = QPoint(from + to, mUserCursor.y());
|
|
|
|
if (mudlet::smDebugMode) {
|
|
TDebug(Qt::darkMagenta, Qt::black) << "P_begin(" << P_begin.x() << "/" << P_begin.y() << "), P_end(" << P_end.x() << "/" << P_end.y() << ") selectedText:\n\""
|
|
<< buffer.line(mUserCursor.y()).mid(P_begin.x(), P_end.x() - P_begin.x()) << "\"\n"
|
|
>> mpHost;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// returns whenever the selection is valid, the selection text,
|
|
// start position, and the length of the selection
|
|
std::tuple<bool, QString, int, int> TConsole::getSelection()
|
|
{
|
|
if (mUserCursor.y() >= static_cast<int>(buffer.buffer.size())) {
|
|
return {false, qsl("the selection is no longer valid"), 0, 0};
|
|
}
|
|
|
|
const auto start = P_begin.x();
|
|
const auto length = P_end.x() - P_begin.x();
|
|
const auto line = buffer.line(mUserCursor.y());
|
|
if (line.size() < start) {
|
|
return {false, qsl("the selection is no longer valid"), 0, 0};
|
|
}
|
|
|
|
const auto text = line.mid(start, length);
|
|
return {true, text, start, length};
|
|
}
|
|
|
|
void TConsole::setLink(const QStringList& linkFunction, const QStringList& linkHint, const QVector<int> linkReference)
|
|
{
|
|
buffer.applyLink(P_begin, P_end, linkFunction, linkHint, linkReference);
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
// Set or Reset ALL the specified (but not others)
|
|
void TConsole::setDisplayAttributes(const TChar::AttributeFlags attributes, const bool b)
|
|
{
|
|
mFormatCurrent.setAllDisplayAttributes((mFormatCurrent.allDisplayAttributes() & ~(attributes)) | (b ? attributes : TChar::None));
|
|
buffer.applyAttribute(P_begin, P_end, attributes, b);
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
void TConsole::setFgColor(int r, int g, int b)
|
|
{
|
|
setFgColor(QColor(r, g, b));
|
|
}
|
|
|
|
void TConsole::setBgColor(int r, int g, int b, int a)
|
|
{
|
|
setBgColor(QColor(r, g, b, a));
|
|
}
|
|
|
|
void TConsole::setBgColor(const QColor& newColor)
|
|
{
|
|
mFormatCurrent.setBackground(newColor);
|
|
buffer.applyBgColor(P_begin, P_end, newColor);
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
void TConsole::setFgColor(const QColor& newColor)
|
|
{
|
|
mFormatCurrent.setForeground(newColor);
|
|
buffer.applyFgColor(P_begin, P_end, newColor);
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
void TConsole::setCommandBgColor(int r, int g, int b, int a)
|
|
{
|
|
setCommandBgColor(QColor(r, g, b, a));
|
|
}
|
|
|
|
void TConsole::setCommandBgColor(const QColor& newColor)
|
|
{
|
|
mCommandBgColor = newColor;
|
|
}
|
|
|
|
void TConsole::setCommandFgColor(int r, int g, int b, int a)
|
|
{
|
|
setCommandFgColor(QColor(r, g, b, a));
|
|
}
|
|
|
|
void TConsole::setCommandFgColor(const QColor& newColor)
|
|
{
|
|
mCommandFgColor = newColor;
|
|
}
|
|
|
|
void TConsole::setScrollBarVisible(bool isVisible)
|
|
{
|
|
if (mpScrollBar) {
|
|
mScrollBarEnabled = isVisible;
|
|
mpScrollBar->setVisible(isVisible);
|
|
}
|
|
}
|
|
|
|
// Reports what enableScrollBar()/disableScrollBar() last asked for rather than
|
|
// QWidget::isVisible(): a profile that is not the front tab has its whole
|
|
// console hidden, which would otherwise make every background profile report
|
|
// its scroll bar as gone.
|
|
bool TConsole::getScrollBarVisible() const
|
|
{
|
|
return mScrollBarEnabled;
|
|
}
|
|
|
|
void TConsole::setHorizontalScrollBar(bool isEnabled)
|
|
{
|
|
if (mpHScrollBar) {
|
|
mHScrollBarEnabled = isEnabled;
|
|
mpHScrollBar->setVisible(isEnabled);
|
|
}
|
|
}
|
|
|
|
void TConsole::setScrolling(const bool state)
|
|
{
|
|
if (mType & (UserWindow | SubConsole)) {
|
|
mScrollingEnabled = state;
|
|
if (!mScrollingEnabled) {
|
|
clearSplit();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TConsole::printCommand(QString& msg)
|
|
{
|
|
// Skip printing if remote echo is active (e.g., password mode)
|
|
if (mpHost && mpHost->isRemoteEchoingActive()) {
|
|
return;
|
|
}
|
|
|
|
if (mTriggerEngineMode) {
|
|
msg.append(QChar::LineFeed);
|
|
const int lineBeforeNewContent = buffer.getLastLineNumber();
|
|
if (lineBeforeNewContent >= 0 && !buffer.lineBuffer.back().isEmpty()) {
|
|
msg.prepend(QChar::LineFeed);
|
|
}
|
|
buffer.appendLine(msg, 0, msg.size() - 1, mCommandFgColor, mCommandBgColor);
|
|
} else {
|
|
const int lineBeforeNewContent = buffer.size() - 2;
|
|
if (lineBeforeNewContent >= 0) {
|
|
int promptEnd = buffer.buffer.at(lineBeforeNewContent).size();
|
|
if (promptEnd < 0) {
|
|
promptEnd = 0;
|
|
}
|
|
if (buffer.promptBuffer[lineBeforeNewContent]) {
|
|
QPoint P(promptEnd, lineBeforeNewContent);
|
|
const TChar format(mCommandFgColor, mCommandBgColor);
|
|
buffer.insertInLine(P, msg, format);
|
|
const int down = buffer.wrapLine(lineBeforeNewContent, mpHost->mScreenWidth, mpHost->mWrapIndentCount, mpHost->mWrapHangingIndentCount);
|
|
|
|
mUpperPane->needUpdate(lineBeforeNewContent, lineBeforeNewContent + 1 + down);
|
|
mLowerPane->needUpdate(lineBeforeNewContent, lineBeforeNewContent + 1 + down);
|
|
buffer.promptBuffer[lineBeforeNewContent] = false;
|
|
return;
|
|
}
|
|
}
|
|
msg.append("\n");
|
|
print(msg, mCommandFgColor, mCommandBgColor);
|
|
}
|
|
}
|
|
|
|
void TConsole::echoLink(const QString& text, QStringList& func, QStringList& hint, bool customFormat, QVector<int> luaReference)
|
|
{
|
|
if (customFormat) {
|
|
buffer.addLink(mTriggerEngineMode, text, func, hint, mFormatCurrent, luaReference);
|
|
} else {
|
|
const TChar f = TChar(Qt::blue, (mType == MainConsole ? mpHost->mBgColor : mBgColor), TChar::Underline);
|
|
buffer.addLink(mTriggerEngineMode, text, func, hint, f, luaReference);
|
|
}
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
}
|
|
|
|
// An overload of print(const QString& msg):
|
|
void TConsole::print(const char* txt)
|
|
{
|
|
const QString msg(txt);
|
|
print(msg);
|
|
}
|
|
|
|
// echoUserWindow(const QString& msg) was a redundant wrapper around this method:
|
|
void TConsole::print(const QString& msg)
|
|
{
|
|
buffer.append(msg, 0, msg.size(), mFormatCurrent.foreground(), mFormatCurrent.background(), mFormatCurrent.allDisplayAttributes());
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
|
|
if (Q_UNLIKELY(mudlet::self()->smMirrorToStdOut)) {
|
|
qDebug().nospace().noquote() << qsl("%1| %2").arg(mConsoleName, msg);
|
|
}
|
|
}
|
|
|
|
// printDebug(QColor& c, QColor& d, const QString& msg) was functionally the
|
|
// same as this method it was just that the arguments were in a different order
|
|
void TConsole::print(const QString& msg, const QColor fgColor, const QColor bgColor)
|
|
{
|
|
buffer.append(msg, 0, msg.size(), fgColor, bgColor);
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
|
|
if (Q_UNLIKELY(mudlet::self()->smMirrorToStdOut)) {
|
|
qDebug().nospace().noquote() << qsl("%1| %2").arg(mConsoleName, msg);
|
|
}
|
|
}
|
|
|
|
void TConsole::printFormatted(const QString& text, const std::deque<TChar>& formatting, const TLinkStore& sourceLinkStore)
|
|
{
|
|
buffer.appendFormatted(text, formatting, sourceLinkStore);
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
|
|
if (Q_UNLIKELY(mudlet::self()->smMirrorToStdOut)) {
|
|
qDebug().nospace().noquote() << qsl("%1| %2").arg(mConsoleName, text);
|
|
}
|
|
}
|
|
|
|
void TConsole::printSystemMessage(const QString& msg)
|
|
{
|
|
const QString txt = tr("System Message: %1").arg(msg);
|
|
print(txt, mSystemMessageFgColor, mSystemMessageBgColor);
|
|
}
|
|
|
|
void TConsole::echo(const QString& msg)
|
|
{
|
|
// Strip \r so that \r\n becomes \n and standalone \r disappears; without
|
|
// this, \r is stored literally in the buffer and rendered as a glyph.
|
|
QString normalizedMsg = msg;
|
|
normalizedMsg.remove(QChar::CarriageReturn);
|
|
if (mTriggerEngineMode) {
|
|
// Use insertInLine instead of appendLine so that newline characters
|
|
// are embedded in the trigger line rather than creating new buffer
|
|
// lines (which would cause subsequent echo/cecho calls to append to
|
|
// the wrong line). The embedded newlines are properly handled during
|
|
// wrapping by getWrapInfo.
|
|
const int y = buffer.size() - 1;
|
|
if (y >= 0) {
|
|
const int x = buffer.lineBuffer.at(y).size();
|
|
QPoint insertPoint(x, y);
|
|
buffer.insertInLine(insertPoint, normalizedMsg, mFormatCurrent);
|
|
} else {
|
|
buffer.appendLine(normalizedMsg, 0, normalizedMsg.size() - 1, mFormatCurrent.foreground(), mFormatCurrent.background(), mFormatCurrent.allDisplayAttributes());
|
|
}
|
|
} else {
|
|
print(normalizedMsg);
|
|
}
|
|
}
|
|
|
|
void TConsole::copy()
|
|
{
|
|
mpHost->mpConsole->mClipboard = buffer.copy(P_begin, P_end);
|
|
}
|
|
|
|
void TConsole::cut()
|
|
{
|
|
mpHost->mpConsole->mClipboard = buffer.cut(P_begin, P_end);
|
|
}
|
|
|
|
void TConsole::paste()
|
|
{
|
|
if (buffer.size() - 1 > mUserCursor.y()) {
|
|
buffer.paste(mUserCursor, mpHost->mpConsole->mClipboard);
|
|
mUpperPane->needUpdate(mUserCursor.y(), mUserCursor.y());
|
|
} else {
|
|
buffer.appendBuffer(mpHost->mpConsole->mClipboard);
|
|
}
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
}
|
|
|
|
void TConsole::pasteWindow(const TBuffer& bufferSlice)
|
|
{
|
|
mpHost->mpConsole->mClipboard = bufferSlice;
|
|
paste();
|
|
}
|
|
|
|
void TConsole::appendBuffer()
|
|
{
|
|
buffer.appendBuffer(mpHost->mpConsole->mClipboard);
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
}
|
|
|
|
void TConsole::appendBuffer(const TBuffer& bufferSlice)
|
|
{
|
|
buffer.appendBuffer(bufferSlice);
|
|
mUpperPane->showNewLines();
|
|
mLowerPane->showNewLines();
|
|
}
|
|
|
|
void TConsole::slot_stopAllItems(bool b)
|
|
{
|
|
if (b) {
|
|
mpHost->stopAllTriggers();
|
|
} else {
|
|
mpHost->reenableAllTriggers();
|
|
}
|
|
}
|
|
|
|
void TConsole::focusOnSearchResultAndAnnounce(int searchX, int searchY)
|
|
{
|
|
mpHost->setCaretEnabled(true);
|
|
mUpperPane->initializeCaret();
|
|
moveCursor(searchX, searchY);
|
|
mUpperPane->setCaretPosition(searchY, searchX);
|
|
mUpperPane->updateCaret();
|
|
mUpperPane->setFocusPolicy(Qt::StrongFocus);
|
|
mUpperPane->setFocusProxy(nullptr);
|
|
mUpperPane->setFocus();
|
|
mudlet::self()->announce(buffer.lineBuffer[searchY]);
|
|
}
|
|
|
|
void TConsole::slot_searchBufferUp()
|
|
{
|
|
if (mpHost->getF3SearchEnabled()) {
|
|
buffer.clearSearchHighlights();
|
|
}
|
|
|
|
// The search term entry box is one widget that does not pass a mouse press
|
|
// event up to the main TConsole and thus does not cause the focus to shift
|
|
// to the profile's tab when in multi-view mode - so add a call to make that
|
|
// happen:
|
|
mudlet::self()->activateProfile(mpHost);
|
|
|
|
if (mSearchQuery != mpBufferSearchBox->text()) {
|
|
mSearchQuery = mpBufferSearchBox->text();
|
|
buffer.clearSearchHighlights();
|
|
mCurrentSearchResult = buffer.lineBuffer.size();
|
|
} else {
|
|
// make sure the line to search from does not exceed the buffer, which can grow and shrink dynamically
|
|
mCurrentSearchResult = std::min<qsizetype>(mCurrentSearchResult, buffer.lineBuffer.size());
|
|
}
|
|
if (mSearchQuery.isEmpty() || buffer.lineBuffer.empty()) {
|
|
// Don't try and search for anything if the search term OR the console is empty:
|
|
return;
|
|
}
|
|
|
|
bool found = false;
|
|
for (int searchY = mCurrentSearchResult - 1; searchY >= 0; --searchY) {
|
|
int searchX = -1;
|
|
do {
|
|
searchX = buffer.lineBuffer[searchY].indexOf(mSearchQuery, searchX + 1, ((mSearchOptions & SearchOptionCaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive));
|
|
if (searchX > -1) {
|
|
buffer.applyAttribute(QPoint(searchX, searchY), QPoint(searchX + mSearchQuery.size(), searchY), TChar::Found, true);
|
|
if (mpHost->getF3SearchEnabled()) {
|
|
focusOnSearchResultAndAnnounce(searchX, searchY);
|
|
}
|
|
found = true;
|
|
}
|
|
} while (searchX > -1);
|
|
|
|
if (found) {
|
|
// Scroll to show the match
|
|
scrollUp(buffer.mCursorY - searchY - 3);
|
|
mUpperPane->forceUpdate();
|
|
mCurrentSearchResult = searchY;
|
|
return;
|
|
}
|
|
}
|
|
print(qsl("%1\n").arg(tr("No search results, sorry!")));
|
|
}
|
|
|
|
void TConsole::slot_searchBufferDown()
|
|
{
|
|
if (mpHost->getF3SearchEnabled()) {
|
|
buffer.clearSearchHighlights();
|
|
}
|
|
if (mSearchQuery != mpBufferSearchBox->text()) {
|
|
mSearchQuery = mpBufferSearchBox->text();
|
|
buffer.clearSearchHighlights();
|
|
mCurrentSearchResult = buffer.lineBuffer.size();
|
|
}
|
|
if (mSearchQuery.isEmpty() || buffer.lineBuffer.empty()) {
|
|
// Don't try and search for anything if the search term OR the console is empty:
|
|
return;
|
|
}
|
|
if (mCurrentSearchResult >= buffer.lineBuffer.size()) {
|
|
return;
|
|
}
|
|
|
|
bool found = false;
|
|
for (int searchY = mCurrentSearchResult + 1; searchY < buffer.lineBuffer.size(); ++searchY) {
|
|
int searchX = -1;
|
|
do {
|
|
searchX = buffer.lineBuffer[searchY].indexOf(mSearchQuery, searchX + 1, ((mSearchOptions & SearchOptionCaseSensitive) ? Qt::CaseSensitive : Qt::CaseInsensitive));
|
|
if (searchX > -1) {
|
|
buffer.applyAttribute(QPoint(searchX, searchY), QPoint(searchX + mSearchQuery.size(), searchY), TChar::Found, true);
|
|
if (mpHost->getF3SearchEnabled()) {
|
|
focusOnSearchResultAndAnnounce(searchX, searchY);
|
|
}
|
|
found = true;
|
|
}
|
|
} while (searchX > -1);
|
|
|
|
if (found) {
|
|
// Scroll to show the match
|
|
scrollUp(buffer.mCursorY - searchY - 3);
|
|
mUpperPane->forceUpdate();
|
|
mCurrentSearchResult = searchY;
|
|
return;
|
|
}
|
|
}
|
|
print(qsl("%1\n").arg(tr("No search results, sorry!")));
|
|
}
|
|
|
|
QSize TConsole::getMainWindowSize() const
|
|
{
|
|
if (isHidden()) {
|
|
return mOldSize;
|
|
}
|
|
const QSize consoleSize = size();
|
|
const int toolbarWidth = mpLeftToolBar->width() + mpRightToolBar->width();
|
|
const int toolbarHeight = mpTopToolBar->height();
|
|
const int commandLineHeight = mpCommandLine->height();
|
|
QSize mainWindowSize(consoleSize.width() - toolbarWidth, consoleSize.height() - (commandLineHeight + toolbarHeight));
|
|
|
|
// Reject obviously invalid or suspiciously small sizes during profile switch transitions
|
|
const int minValidWidth = 50;
|
|
if (mainWindowSize.width() < minValidWidth && mOldSize.width() >= minValidWidth) {
|
|
return mOldSize;
|
|
}
|
|
|
|
// Reject suspicious shrinkage (more than 50% reduction) - geometry may not have settled yet
|
|
if (mOldSize.width() > 0) {
|
|
const double shrinkageRatio = static_cast<double>(mainWindowSize.width()) / mOldSize.width();
|
|
if (shrinkageRatio < 0.5) {
|
|
return mOldSize;
|
|
}
|
|
}
|
|
|
|
return mainWindowSize;
|
|
}
|
|
|
|
void TConsole::setProfileName(const QString& newName)
|
|
{
|
|
mProfileName = newName;
|
|
}
|
|
|
|
void TConsole::dragEnterEvent(QDragEnterEvent* e)
|
|
{
|
|
if (e->mimeData()->hasUrls() || e->mimeData()->hasText()) {
|
|
// Use ctrl key to decide if action is link or copy
|
|
// CopyAction corresponds to installing dropped file as a package
|
|
// LinkAction corresponds to installing dropped file as a module
|
|
Qt::KeyboardModifiers modifiers = e->modifiers();
|
|
if (modifiers & Qt::ControlModifier) {
|
|
e->setDropAction(Qt::LinkAction);
|
|
} else {
|
|
e->setDropAction(Qt::CopyAction);
|
|
}
|
|
e->accept();
|
|
}
|
|
}
|
|
|
|
void TConsole::dragMoveEvent(QDragMoveEvent* e)
|
|
{
|
|
if (e->mimeData()->hasUrls() || e->mimeData()->hasText()) {
|
|
// Use ctrl key to decide if action is link or copy
|
|
// CopyAction corresponds to installing dropped file as a package
|
|
// LinkAction corresponds to installing dropped file as a module
|
|
Qt::KeyboardModifiers modifiers = e->modifiers();
|
|
if (modifiers & Qt::ControlModifier) {
|
|
e->setDropAction(Qt::LinkAction);
|
|
} else {
|
|
e->setDropAction(Qt::CopyAction);
|
|
}
|
|
e->accept();
|
|
}
|
|
}
|
|
|
|
//https://amin-ahmadi.com/2016/01/04/qt-drag-drop-files-images/
|
|
void TConsole::dropEvent(QDropEvent* e)
|
|
{
|
|
for (const auto& url : e->mimeData()->urls()) {
|
|
const QString fname = url.toLocalFile();
|
|
const QFileInfo info(fname);
|
|
if (info.exists()) {
|
|
QPoint pos = e->position().toPoint();
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysDropEvent"));
|
|
mudletEvent.mArgumentList.append(fname);
|
|
mudletEvent.mArgumentList.append(info.suffix().trimmed());
|
|
mudletEvent.mArgumentList.append(QString::number(pos.x()));
|
|
mudletEvent.mArgumentList.append(QString::number(pos.y()));
|
|
mudletEvent.mArgumentList.append(mConsoleName);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
}
|
|
}
|
|
if (e->mimeData()->hasText()) {
|
|
if (const QUrl url(e->mimeData()->text()); url.isValid()) {
|
|
QPoint pos = e->position().toPoint();
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysDropUrlEvent"));
|
|
mudletEvent.mArgumentList.append(url.toString());
|
|
mudletEvent.mArgumentList.append(url.scheme());
|
|
mudletEvent.mArgumentList.append(QString::number(pos.x()));
|
|
mudletEvent.mArgumentList.append(QString::number(pos.y()));
|
|
mudletEvent.mArgumentList.append(mConsoleName);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is also called from the TTextEdit mouse(Press|Release)Event()s:
|
|
void TConsole::raiseMudletMousePressOrReleaseEvent(QMouseEvent* event, const bool isPressEvent)
|
|
{
|
|
if (mType & (CentralDebugConsole | ErrorConsole)) {
|
|
return;
|
|
}
|
|
|
|
// Else if NOT the CentralDebugConsole or the ErrorConsole then bring the
|
|
// focus to the current profile in the main application window:
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(isPressEvent ? qsl("sysWindowMousePressEvent") : qsl("sysWindowMouseReleaseEvent"));
|
|
switch (event->button()) {
|
|
case Qt::LeftButton:
|
|
mudletEvent.mArgumentList.append(QString::number(1));
|
|
break;
|
|
case Qt::RightButton:
|
|
mudletEvent.mArgumentList.append(QString::number(2));
|
|
break;
|
|
case Qt::MiddleButton:
|
|
mudletEvent.mArgumentList.append(QString::number(3));
|
|
break;
|
|
case Qt::BackButton:
|
|
mudletEvent.mArgumentList.append(QString::number(4));
|
|
break;
|
|
case Qt::ForwardButton:
|
|
mudletEvent.mArgumentList.append(QString::number(5));
|
|
break;
|
|
case Qt::TaskButton:
|
|
mudletEvent.mArgumentList.append(QString::number(6));
|
|
break;
|
|
case Qt::ExtraButton4:
|
|
mudletEvent.mArgumentList.append(QString::number(7));
|
|
break;
|
|
case Qt::ExtraButton5:
|
|
mudletEvent.mArgumentList.append(QString::number(8));
|
|
break;
|
|
case Qt::ExtraButton6:
|
|
mudletEvent.mArgumentList.append(QString::number(9));
|
|
break;
|
|
case Qt::ExtraButton7:
|
|
mudletEvent.mArgumentList.append(QString::number(10));
|
|
break;
|
|
case Qt::ExtraButton8:
|
|
mudletEvent.mArgumentList.append(QString::number(11));
|
|
break;
|
|
case Qt::ExtraButton9:
|
|
mudletEvent.mArgumentList.append(QString::number(12));
|
|
break;
|
|
case Qt::ExtraButton10:
|
|
mudletEvent.mArgumentList.append(QString::number(13));
|
|
break;
|
|
case Qt::ExtraButton11:
|
|
mudletEvent.mArgumentList.append(QString::number(14));
|
|
break;
|
|
case Qt::ExtraButton12:
|
|
mudletEvent.mArgumentList.append(QString::number(15));
|
|
break;
|
|
case Qt::ExtraButton13:
|
|
mudletEvent.mArgumentList.append(QString::number(16));
|
|
break;
|
|
case Qt::ExtraButton14:
|
|
mudletEvent.mArgumentList.append(QString::number(17));
|
|
break;
|
|
case Qt::ExtraButton15:
|
|
mudletEvent.mArgumentList.append(QString::number(18));
|
|
break;
|
|
case Qt::ExtraButton16:
|
|
mudletEvent.mArgumentList.append(QString::number(19));
|
|
break;
|
|
case Qt::ExtraButton17:
|
|
mudletEvent.mArgumentList.append(QString::number(20));
|
|
break;
|
|
case Qt::ExtraButton18:
|
|
mudletEvent.mArgumentList.append(QString::number(21));
|
|
break;
|
|
case Qt::ExtraButton19:
|
|
mudletEvent.mArgumentList.append(QString::number(22));
|
|
break;
|
|
case Qt::ExtraButton20:
|
|
mudletEvent.mArgumentList.append(QString::number(23));
|
|
break;
|
|
case Qt::ExtraButton21:
|
|
mudletEvent.mArgumentList.append(QString::number(24));
|
|
break;
|
|
case Qt::ExtraButton22:
|
|
mudletEvent.mArgumentList.append(QString::number(25));
|
|
break;
|
|
case Qt::ExtraButton23:
|
|
mudletEvent.mArgumentList.append(QString::number(26));
|
|
break;
|
|
case Qt::ExtraButton24:
|
|
mudletEvent.mArgumentList.append(QString::number(27));
|
|
break;
|
|
default:
|
|
mudletEvent.mArgumentList.append(QString::number(0));
|
|
}
|
|
QPoint pos = event->position().toPoint();
|
|
mudletEvent.mArgumentList.append(QString::number(pos.x()));
|
|
mudletEvent.mArgumentList.append(QString::number(pos.y()));
|
|
mudletEvent.mArgumentList.append(mConsoleName);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
|
|
// Focus this console's command line, not the "active" one
|
|
// This ensures clicking on a console focuses its own command line
|
|
if (mpCommandLine && mpCommandLine->isVisible()) {
|
|
mpCommandLine->setFocus(Qt::MouseFocusReason);
|
|
mpHost->recordActiveCommandLine(mpCommandLine);
|
|
} else if (mType == MainConsole) {
|
|
// Main console always has its command line
|
|
mpHost->mpConsole->mpCommandLine->setFocus(Qt::MouseFocusReason);
|
|
mpHost->recordActiveCommandLine(mpHost->mpConsole->mpCommandLine);
|
|
} else {
|
|
// Fallback to the old behavior for other cases
|
|
mpHost->setFocusOnHostActiveCommandLine();
|
|
}
|
|
}
|
|
|
|
void TConsole::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
raiseMudletMousePressOrReleaseEvent(event, true);
|
|
}
|
|
|
|
void TConsole::slot_adjustAccessibleNames()
|
|
{
|
|
const bool multipleProfilesActive = (mudlet::self()->getHostManager().getHostCount() > 1);
|
|
switch (mType) {
|
|
case CentralDebugConsole:
|
|
setAccessibleName(tr("Debug Console."));
|
|
setAccessibleDescription(tr("Debug messages from all profiles are shown here."));
|
|
if (mLowerPane->isVisible()) {
|
|
//: accessibility-friendly name to describe the upper half of the Mudlet central debug window when you've scrolled up
|
|
mUpperPane->setAccessibleName(tr("Central debug console past content."));
|
|
//: accessibility-friendly name to describe the lower half of the Mudlet central debug when you've scrolled up
|
|
mLowerPane->setAccessibleName(tr("Central debug console live content."));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of the Mudlet central debug window when it is not scrolled up
|
|
mUpperPane->setAccessibleName(tr("Central debug console."));
|
|
mLowerPane->setAccessibleName(QString());
|
|
}
|
|
return;
|
|
case ErrorConsole:
|
|
setAccessibleName(tr("Error Console in editor."));
|
|
if (mLowerPane->isVisible()) {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of the Mudlet profile's editor error window when you've scrolled up, %1 is the name of the profile when more than one is loaded.
|
|
mUpperPane->setAccessibleName(tr("Editor's error window for profile \"%1\", past content.").arg(mProfileName));
|
|
//: accessibility-friendly name to describe the lower half of the Mudlet profile's editor error window when you've scrolled up, %1 is the name of the profile when more than one is loaded.
|
|
mLowerPane->setAccessibleName(tr("Editor's error window for profile \"%1\", live content.").arg(mProfileName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of the Mudlet profile's editor error window when you've scrolled up and only one profile is loaded.
|
|
mUpperPane->setAccessibleName(tr("Editor's error window past content."));
|
|
//: accessibility-friendly name to describe the lower half of the Mudlet profile's editor error window when you've scrolled up and only one profile is loaded.
|
|
mLowerPane->setAccessibleName(tr("Editor's error window live content."));
|
|
}
|
|
setAccessibleDescription(tr("Error messages for the \"%1\" profile are shown here in the editor.").arg(mProfileName));
|
|
} else {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of the Mudlet profile's editor error window when it is not scrolled up, %1 is the name of the profile when more than one is loaded.
|
|
mUpperPane->setAccessibleName(tr("Editor's error window for profile \"%1\".").arg(mProfileName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of the Mudlet profile's editor error window when it is not scrolled up and only one profile is loaded.
|
|
mUpperPane->setAccessibleName(tr("Editor's error window"));
|
|
}
|
|
mLowerPane->setAccessibleName(QString());
|
|
setAccessibleDescription(tr("Error messages are shown here in the editor."));
|
|
}
|
|
return;
|
|
case MainConsole:
|
|
setAccessibleDescription(tr("Game content is shown here. It may contain subconsoles and a mapper window."));
|
|
if (multipleProfilesActive) {
|
|
setAccessibleName(tr("Main Window for \"%1\" profile.").arg(mProfileName));
|
|
} else {
|
|
setAccessibleName(tr("Main Window."));
|
|
}
|
|
if (mLowerPane->isVisible()) {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's main window when you've scrolled up, %1 is the name of the profile when more than one is loaded.
|
|
mUpperPane->setAccessibleName(tr("Profile \"%1\" main window past content.").arg(mProfileName));
|
|
//: accessibility-friendly name to describe the lower half of a Mudlet profile's main window when you've scrolled up, %1 is the name of the profile when more than one is loaded.
|
|
mLowerPane->setAccessibleName(tr("Profile \"%1\" main window live content.").arg(mProfileName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's main window when you've scrolled up and only one profile is loaded.
|
|
mUpperPane->setAccessibleName(tr("Profile main window past content."));
|
|
//: accessibility-friendly name to describe the lower half of a Mudlet profile's main window when you've scrolled up and only one profile is loaded.
|
|
mLowerPane->setAccessibleName(tr("Profile main window live content."));
|
|
}
|
|
} else {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's main window when it is not scrolled up, %1 is the name of the profile when more than one is loaded.
|
|
mUpperPane->setAccessibleName(tr("Profile \"%1\" main window.").arg(mProfileName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's main window when it is not scrolled up and only one profile is loaded.
|
|
mUpperPane->setAccessibleName(tr("Profile main window."));
|
|
}
|
|
mLowerPane->setAccessibleName(QString());
|
|
}
|
|
return;
|
|
case SubConsole:
|
|
if (multipleProfilesActive) {
|
|
setAccessibleName(tr("Embedded window \"%1\" for \"%2\" profile.").arg(mConsoleName, mProfileName));
|
|
} else {
|
|
setAccessibleName(tr("Embedded window \"%1\".").arg(mConsoleName));
|
|
}
|
|
setAccessibleDescription(tr("Game content or locally generated text may be sent here."));
|
|
if (mLowerPane->isVisible()) {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's sub-console window when you've scrolled up, %1 is the name of the profile when more than one is loaded and %2 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile \"%1\" embedded window \"%2\" past content.").arg(mProfileName, mConsoleName));
|
|
//: accessibility-friendly name to describe the lower half of a Mudlet profile's sub-console window when you've scrolled up, %1 is the name of the profile when more than one is loaded and %2 is the name of the window.
|
|
mLowerPane->setAccessibleName(tr("Profile \"%1\" embedded window \"%2\" live content.").arg(mProfileName, mConsoleName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's sub-console window when you've scrolled up, %1 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile embedded window \"%1\" past content.").arg(mConsoleName));
|
|
//: accessibility-friendly name to describe the lower half of a Mudlet profile's sub-console window when you've scrolled up, %1 is the name of the window.
|
|
mLowerPane->setAccessibleName(tr("Profile embedded window \"%1\" live content.").arg(mConsoleName));
|
|
}
|
|
} else {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's sub-console window when it is not scrolled up, %1 is the name of the profile when more than one is loaded and %2 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile \"%1\" embedded window \"%2\".").arg(mProfileName, mConsoleName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's sub-console window when it is not scrolled up, %1 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile embedded window \"%1\".").arg(mConsoleName));
|
|
}
|
|
mLowerPane->setAccessibleName(QString());
|
|
}
|
|
return;
|
|
case UserWindow:
|
|
if (multipleProfilesActive) {
|
|
setAccessibleName(tr("User window \"%1\" for \"%2\" profile.").arg(mConsoleName, mProfileName));
|
|
} else {
|
|
setAccessibleName(tr("User window \"%1\".").arg(mConsoleName));
|
|
}
|
|
setAccessibleDescription(
|
|
tr("Game content or locally generated text may be sent to this window that may be floated away from the Mudlet application or docked within the main application window."));
|
|
if (mLowerPane->isVisible()) {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's floating/dockable user window when you've scrolled up, %1 is the name of the profile when more than one is loaded and %2 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile \"%1\" user window \"%2\" past content.").arg(mProfileName, mConsoleName));
|
|
//: accessibility-friendly name to describe the lower half of a Mudlet profile's floating/dockable user window window when you've scrolled up, %1 is the name of the profile when more than one is loaded and %2 is the name of the window.
|
|
mLowerPane->setAccessibleName(tr("Profile \"%1\" user window \"%2\" live content.").arg(mProfileName, mConsoleName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's sub-console window when you've scrolled up, %1 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile user window \"%1\" past content.").arg(mConsoleName));
|
|
//: accessibility-friendly name to describe the lower half of a Mudlet profile's sub-console window when you've scrolled up, %1 is the name of the window.
|
|
mLowerPane->setAccessibleName(tr("Profile user window \"%1\" live content.").arg(mConsoleName));
|
|
}
|
|
} else {
|
|
if (multipleProfilesActive) {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's floating/dockable user window window when it is not scrolled up, %1 is the name of the profile when more than one is loaded and %2 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile \"%1\" user window \"%2\".").arg(mProfileName, mConsoleName));
|
|
} else {
|
|
//: accessibility-friendly name to describe the upper half of a Mudlet profile's floating/dockable user window window when it is not scrolled up, %1 is the name of the window.
|
|
mUpperPane->setAccessibleName(tr("Profile user window \"%1\".").arg(mConsoleName));
|
|
}
|
|
mLowerPane->setAccessibleName(QString());
|
|
}
|
|
return;
|
|
case Buffer:
|
|
// This is not a visible thing so is not accessible to screen readers
|
|
return;
|
|
case UnknownType:
|
|
// Should never be used - and since we have now handled ALL enum values
|
|
// we do not need a "default:" entry
|
|
Q_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
void TConsole::mouseReleaseEvent(QMouseEvent* event)
|
|
{
|
|
raiseMudletMousePressOrReleaseEvent(event, false);
|
|
}
|
|
|
|
void TConsole::slot_changeControlCharacterHandling(const ControlCharacterMode mode)
|
|
{
|
|
if (mControlCharacter != mode) {
|
|
mControlCharacter = mode;
|
|
refreshView();
|
|
}
|
|
}
|
|
|
|
void TConsole::setProxyForFocus(TCommandLine* pCommandLine)
|
|
{
|
|
if (mType == MainConsole) {
|
|
// Update all focus proxies to the main command line
|
|
setFocusProxy(pCommandLine);
|
|
mUpperPane->setFocusProxy(pCommandLine);
|
|
mLowerPane->setFocusProxy(pCommandLine);
|
|
QAccessibleEvent event(pCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else if (mType == UserWindow) {
|
|
if (pCommandLine && pCommandLine->isVisible()) {
|
|
// Update all focus proxies to the UserWindow's command line
|
|
setFocusProxy(pCommandLine);
|
|
mUpperPane->setFocusProxy(pCommandLine);
|
|
mLowerPane->setFocusProxy(pCommandLine);
|
|
QAccessibleEvent event(pCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else {
|
|
// Revert to main console's command line
|
|
setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
mUpperPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
mLowerPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
QAccessibleEvent event(mpHost->mpConsole->mpCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
}
|
|
} else if (mType == SubConsole) {
|
|
if (pCommandLine && pCommandLine->isVisible()) {
|
|
// Update all focus proxies to the SubConsole's command line
|
|
setFocusProxy(pCommandLine);
|
|
mUpperPane->setFocusProxy(pCommandLine);
|
|
mLowerPane->setFocusProxy(pCommandLine);
|
|
QAccessibleEvent event(pCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else {
|
|
// Need to search ancestors to find the TConsole that this one
|
|
// is inserted into - and if it has a TCommandLine
|
|
auto parentConsole = mpHost->parentTConsole(this);
|
|
if (!parentConsole.isNull() && parentConsole->mpCommandLine && parentConsole->mpCommandLine->isVisible()) {
|
|
// TBH We ought to also check for any added TCommandLine but
|
|
// that can wait for a future development...
|
|
setFocusProxy(parentConsole->mpCommandLine);
|
|
mUpperPane->setFocusProxy(parentConsole->mpCommandLine);
|
|
mLowerPane->setFocusProxy(parentConsole->mpCommandLine);
|
|
QAccessibleEvent event(parentConsole->mpCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else {
|
|
// Somehow that has failed so fall back to the main console
|
|
setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
mUpperPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
mLowerPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
QAccessibleEvent event(mpHost->mpConsole->mpCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// At present this only supports/works on the main console
|
|
void TConsole::setCaretMode(bool enabled)
|
|
{
|
|
mUpperPane->updateCaret();
|
|
mLowerPane->updateCaret();
|
|
|
|
if (enabled) {
|
|
mUpperPane->initializeCaret();
|
|
// Remove the focusProxy before setting the focusPolicy otherwise
|
|
// the Policy gets sent to the Proxy!
|
|
mUpperPane->setFocusProxy(nullptr);
|
|
// This adds TabFocus to the otherwise used ClickFocus:
|
|
mUpperPane->setFocusPolicy(Qt::StrongFocus);
|
|
#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)
|
|
// windows & linux don't move keyboard focus to the main window without this
|
|
mUpperPane->setFocus(Qt::MouseFocusReason);
|
|
mUpperPane->grabKeyboard();
|
|
|
|
QAccessibleEvent event(mUpperPane, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
#endif
|
|
// The overload without an argument uses Qt::OtherFocusReason according
|
|
// to the Qt source code:
|
|
mUpperPane->setFocus();
|
|
} else {
|
|
#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)
|
|
// NVDA breaks focus reset, so do it on a timer
|
|
QTimer::singleShot(0ms, this, [this]() {
|
|
mUpperPane->releaseKeyboard();
|
|
});
|
|
#endif
|
|
Q_ASSERT_X(!mUpperPane->focusProxy(), "TConsole:setCaretMode(false) FAIL", "About to set a focusPolicy but there is a focusProxy in place that will get it instead!");
|
|
mUpperPane->setFocusPolicy(Qt::ClickFocus);
|
|
setProxyForFocus(mpCommandLine);
|
|
// Carefull - if there is a FocusProxy for this element then IT gets the policy
|
|
}
|
|
}
|
|
|
|
void TConsole::createSearchOptionIcon()
|
|
{
|
|
// When we add new search options we must create icons for each combination
|
|
// beforehand - which is simpler than having to do code to combine the
|
|
// QPixMaps...
|
|
QIcon newIcon;
|
|
switch (mSearchOptions) {
|
|
// Each combination must be handled here
|
|
case SearchOptionCaseSensitive:
|
|
newIcon.addPixmap(QPixmap(":/icons/searchOptions-caseSensitive.png"));
|
|
break;
|
|
|
|
case SearchOptionNone:
|
|
// Use the grey icon as that is appropriate for the "No options set" case
|
|
newIcon.addPixmap(QPixmap(":/icons/searchOptions-none.png"));
|
|
break;
|
|
|
|
default:
|
|
// Don't grey out this one - is a diagnositic for an uncoded combination
|
|
newIcon.addPixmap(QPixmap(":/icons/searchOptions-unspecified.png"));
|
|
}
|
|
|
|
mIcon_searchOptions = newIcon;
|
|
mpAction_searchOptions->setIcon(newIcon);
|
|
}
|
|
|
|
void TConsole::setSearchOptions(const SearchOptions optionsState)
|
|
{
|
|
mSearchOptions = optionsState;
|
|
mpAction_searchCaseSensitive->setChecked(optionsState & SearchOptionCaseSensitive);
|
|
createSearchOptionIcon();
|
|
}
|
|
|
|
void TConsole::slot_toggleSearchCaseSensitivity(const bool state)
|
|
{
|
|
if ((mSearchOptions & SearchOptionCaseSensitive) != state) {
|
|
mSearchOptions = (mSearchOptions & ~(SearchOptionCaseSensitive)) | (state ? SearchOptionCaseSensitive : SearchOptionNone);
|
|
createSearchOptionIcon();
|
|
mpHost->mBufferSearchOptions = mSearchOptions;
|
|
}
|
|
}
|
|
|
|
void TConsole::setF3SearchEnabled(const bool enabled)
|
|
{
|
|
if (mType != MainConsole) {
|
|
// Don't do anything if we are NOT the main console:
|
|
return;
|
|
}
|
|
|
|
if (mF3SearchEnabled == enabled) {
|
|
// Don't do anything if the stored setting already matches the wanted one
|
|
return;
|
|
}
|
|
|
|
mF3SearchEnabled = enabled;
|
|
if (mF3SearchEnabled) {
|
|
// Create F3/Shift+F3 shortcuts for search navigation if needed
|
|
if (mpSearchNextShortcut.isNull()) {
|
|
mpSearchNextShortcut = new QShortcut(QKeySequence(Qt::Key_F3), this);
|
|
}
|
|
if (mpSearchPrevShortcut.isNull()) {
|
|
mpSearchPrevShortcut = new QShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F3), this);
|
|
}
|
|
connect(mpSearchNextShortcut, &QShortcut::activated, this, &TConsole::slot_searchBufferDown, Qt::UniqueConnection);
|
|
connect(mpSearchPrevShortcut, &QShortcut::activated, this, &TConsole::slot_searchBufferUp, Qt::UniqueConnection);
|
|
} else {
|
|
if (!mpSearchNextShortcut.isNull()) {
|
|
disconnect(mpSearchNextShortcut, &QShortcut::activated, this, &TConsole::slot_searchBufferDown);
|
|
mpSearchNextShortcut->deleteLater();
|
|
}
|
|
if (!mpSearchPrevShortcut.isNull()) {
|
|
disconnect(mpSearchPrevShortcut, &QShortcut::activated, this, &TConsole::slot_searchBufferUp);
|
|
mpSearchPrevShortcut->deleteLater();
|
|
}
|
|
}
|
|
}
|
|
|
|
void TConsole::slot_clearSearchResults()
|
|
{
|
|
buffer.clearSearchHighlights();
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
void TConsole::handleLinesOverflowEvent(const int lineCount)
|
|
{
|
|
if (mType & ~(UserWindow | SubConsole)) {
|
|
// It isn't a type that we need to worry about the number of lines of
|
|
// text in it:
|
|
return;
|
|
}
|
|
|
|
if (mScrollingEnabled) {
|
|
// It is capable of scrolling so a "text overflow" is not a concern:
|
|
return;
|
|
}
|
|
|
|
const int linesSpare = mUpperPane->getRowCount() - lineCount;
|
|
if (linesSpare >= 0) {
|
|
// There IS space for all the lines
|
|
return;
|
|
}
|
|
|
|
// Else we do have an overflow situation so let's raise an event for it:
|
|
TEvent sysWindowOverflow{};
|
|
sysWindowOverflow.mArgumentList.append(QLatin1String("sysWindowOverflowEvent"));
|
|
sysWindowOverflow.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
sysWindowOverflow.mArgumentList.append(mConsoleName);
|
|
sysWindowOverflow.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
sysWindowOverflow.mArgumentList.append(QString::number(-linesSpare));
|
|
sysWindowOverflow.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mpHost->raiseEvent(sysWindowOverflow);
|
|
}
|
|
|
|
void TConsole::clearSplit()
|
|
{
|
|
mLowerPane->mCursorY = buffer.size();
|
|
mLowerPane->hide();
|
|
buffer.mCursorY = buffer.size();
|
|
mUpperPane->mCursorY = buffer.size();
|
|
mUpperPane->mCursorX = 0;
|
|
mUpperPane->mIsTailMode = true;
|
|
mUpperPane->updateScreenView();
|
|
mUpperPane->forceUpdate();
|
|
}
|
|
|
|
void TConsole::raiseMudletResizeEvent()
|
|
{
|
|
// Hiding the TConsole - particularly the main one, multiview is not active
|
|
// and the profile is being switched away from causes a zero column count
|
|
// even though the TConsole is not actually resized - so don't raise the
|
|
// Mudlet TEvent in that case:
|
|
auto characterDimensions = QSize(mUpperPane->getColumnCount(), mUpperPane->getRowCount());
|
|
if (!characterDimensions.width()) {
|
|
return;
|
|
}
|
|
|
|
// Showing, Hiding and then Showing the console will produce three resize
|
|
// events - whilst the prior step will prevent this method from generating
|
|
// and event for the hiding one the two successive showing ones will
|
|
// still get to here - so we also need to check that there HAS been an
|
|
// actual change in the dimensions - and abort if there hasn't:
|
|
if (mDimensions == characterDimensions) {
|
|
return;
|
|
}
|
|
mDimensions = characterDimensions;
|
|
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(qsl("sysConsoleSizeChanged"));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(mConsoleName);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(QString::number(characterDimensions.width()));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentList.append(QString::number(characterDimensions.height()));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mudletEvent.mArgumentList.append(QString::number(mShowTimeStamps ? mudlet::smTimeStampFormat.size() : 0));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_NUMBER);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
}
|
|
|
|
void TConsole::slot_toggleTimeStamps(const bool state)
|
|
{
|
|
if (mShowTimeStamps == state) {
|
|
return;
|
|
}
|
|
|
|
mShowTimeStamps = state;
|
|
if (mType == TConsole::MainConsole) {
|
|
if (timeStampButton->isChecked() != state) {
|
|
// using this will NOT cause the QAbstractButton::checked signal
|
|
// to be raised - which is why we use that rather than the
|
|
// QAbstractButton::toggled one
|
|
timeStampButton->setChecked(state);
|
|
}
|
|
const auto filePath = mudlet::getMudletPath(enums::profileDataItemPath, mpHost->getName(), qsl("autotimestamp"));
|
|
QSaveFile file(filePath);
|
|
if (state) {
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qWarning() << "TConsole: failed to open autotimestamp file for writing:" << file.errorString();
|
|
return;
|
|
}
|
|
QTextStream out(&file);
|
|
if (!file.commit()) {
|
|
qDebug() << "TConsole::slot_toggleTimeStamps: error saving timestamp state: " << file.errorString();
|
|
}
|
|
} else {
|
|
QFile::remove(filePath);
|
|
}
|
|
}
|
|
|
|
// These hardly do anything now - just forces a redraw
|
|
mUpperPane->toggleTimeStamps(state);
|
|
mLowerPane->toggleTimeStamps(state);
|
|
|
|
if (mpHost && mType == TConsole::MainConsole) {
|
|
// Update and send out the NAWS data:
|
|
mpHost->updateDisplayDimensions();
|
|
}
|
|
|
|
if (mType & (TConsole::MainConsole | TConsole::UserWindow | TConsole::SubConsole)) {
|
|
raiseMudletResizeEvent();
|
|
}
|
|
}
|
|
|
|
void TConsole::slot_saveCommandSearchSettings()
|
|
{
|
|
if (!mpHost) {
|
|
return;
|
|
}
|
|
|
|
QSettings* pQSettings = mudlet::getQSettings();
|
|
if (!pQSettings) {
|
|
return;
|
|
}
|
|
|
|
pQSettings->setValue("commandSearchSplitterState", commandSplitter->saveState());
|
|
}
|
|
|
|
void TConsole::restoreCommandSearchSettings()
|
|
{
|
|
if (!mpHost) {
|
|
return;
|
|
}
|
|
|
|
QSettings* pQSettings = mudlet::getQSettings();
|
|
if (!pQSettings) {
|
|
return;
|
|
}
|
|
|
|
commandSplitter->restoreState(pQSettings->value("commandSearchSplitterState").toByteArray());
|
|
}
|
|
|
|
void TConsole::initializeOSC8StyleFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
// Register shorthands for style property names
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("s"), qsl("style"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("c"), qsl("color"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("bg"), qsl("bg"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("b"), qsl("bold"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("i"), qsl("italic"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("u"), qsl("underline"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("o"), qsl("overline"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("st"), qsl("strikethrough"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("tdc"), qsl("text-decoration-color"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("h"), qsl("hover"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("a"), qsl("active"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("f"), qsl("focus"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("fv"), qsl("focus-visible"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("vi"), qsl("visited"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("l"), qsl("link"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("al"), qsl("any-link"));
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("sl"), qsl("selected"));
|
|
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("style"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8MenuFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
// Register shorthand for menu property
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("m"), qsl("menu"));
|
|
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("menu"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8TooltipFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
// Register shorthand for tooltip property
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("t"), qsl("tooltip"));
|
|
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("tooltip"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8VisibilityFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
// Register shorthand for visibility property
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("v"), qsl("visibility"));
|
|
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("visibility"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8SelectionFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
// Register shorthand for selection property
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("sel"), qsl("selection"));
|
|
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("selection"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8SpoilerFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("sp"), qsl("spoiler"));
|
|
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("spoiler"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8DisabledFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("d"), qsl("disabled"));
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("disabled"));
|
|
}
|
|
|
|
void TConsole::initializeOSC8TitleFeature()
|
|
{
|
|
if (!mpHyperlinkCompactManager) {
|
|
return;
|
|
}
|
|
|
|
mpHyperlinkCompactManager->registerShorthand(qsl("ti"), qsl("title"));
|
|
mpHyperlinkCompactManager->registerPresetProperty(qsl("title"));
|
|
}
|