mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
- Created `TTextProperties.h` so TBuffer and TTextEdit both share the
same functions for checking the width of graphemes.
- Added the functions necessary to set hanging indent for
(mini-)consoles (exposes Lua function:
`setWindowWrapHangingIndent(str:window, int:hangingIndent)`)
- Deduplicated the process of appending and wrapping text in TBuffer to
follow, generally:
- `append` calls `appendLine` and `wrapLine` and handles buffer
shrinking.
- `appendLine` adds text to buffer.
- `wrapLine` calls `getWrapInfo` then applies wrap, also logs the
wrapped lines.
- `getWrapInfo` checks the width of each grapheme for determining wrap
points instead of the previous method of treating each QChar as width
one.
<details>
<summary>More specifically</summary>
Created TTextProperties.h for one shared source of grapheme width for
both TTextEdit and TBuffer:
- `graphemeInfo::getBaseCharacter` copied over from
`TTextEdit::getGraphemeBaseCharacter`
- `graphemeInfo::getWidth(unicode, mWideAmbigousWidthGlyphs)` copied
from `TTextEdit::getGraphemeWidth(unicode)` (with some modification)
Added `TLuaInterpreter::setWindowWrapHangingIndent` /
`TConsole::setHangingIndentCount` / `TBuffer::setWrapHangingIndent` to
allow setting hanging indents for any (mini)console
Added `WrapInfo(bool isNewline, bool needsIndent, int firstChar, int
lastChar)` class to TBuffer.h for storing information on whether to
indent / where to snip a line to properly wrap it.
Added the inline method `TBuffer::getWrapInfo(const QString& lineText,
bool isNewline, const int maxWidth, const int indent, const int
hangingIndent)` which takes information on the current line of text and
returns a `QList<WrapInfo>` indicating the start / end indices and
indentation to wrap the text (empty -> no wrap).
I think the code speaks for itself, but hand-wavily the algorithm for
wrapping looks like this (glossing over how it handles edge cases and
indentation):
- Use QTextBoundaryFinder to identify each grapheme
- Use QTextBoundaryFinder to identify linebreaks
- loop through the line and track the width of each grapheme -> current
x position
- if the x position plus the expected indentation exceed the maxwidth:
- check if the current character is a space or valid linebreak
- if not, use the boundaryFinder to search backwards from the current
position for a valid linebreak
- if none found, choose current position for linebreak
- record wrap point indices and indentation by adding a new WrapInfo to
the list
- return list of WrapInfos
Removed `TBuffer::wrap` (this is replaced by wrapLine)
Changed `TBuffer::wrapLine(int startLine, int screenWidth, int
indentSize, TChar& format)` to `TBuffer::wrapLine(int startLine, int
maxWidth, int indentSize, int hangingIndentSize)`
`TBuffer::wrapLine` still loops through each line in the buffer from the
startline to the lastline, now calling getWrapInfo for each line and
applying the wrap/indentation.
- screenWidth -> maxWidth for a less misleading var name
- removed TChar& format as this serves no purpose now
- added hangingIndentSize for... hanging indents
- **Note:** wrapline only records a timestamp for the first line of a
multiline-wrap lines and also uses the resulting blank timestamps to
implicitly indicate that the line was previously wrapped (and has its
hanging indent already), i.e.: `const bool isNewline = (time !=
mudlet::smBlankTimeStamp);`.
- finally, the startline to the penultimate line are logged
(consequently, if no wrap action occurs, no logging occurs), as all
previously wrapped lines will no longer be touched by append actions.
`TBuffer::appendLine`: simplified this method to just handle appending
text to the buffer (by removing handling of shrink buffer / console
overflow).
Added `appendEmptyLine` for adding a blank line to the buffer and reduce
duplicated code.
</details>
#### Motivation for adding to Mudlet
Adds functioning hanging indents to all consoles.
Fixes wrapping for 2-width characters (emojis, asian characters, etc.).
Moving all wrapping logic into one method should be much easier to
maintain/optimize, as well as hopefully cutting down on the bugs arising
from having three different implementations being used in parallel.
#### Other info (issues closed, discussion etc)
Fixes #3674
Fixes #5564
Fixes #5936
Fixes #6390
Fixes #6432
Fixes #6970
Fixes #7846
Fixes #7892
/claim #5936
/claim #5564
Before benchmark (Mudlet 4.19.1):

After benchmark (commit e6c516e3):

Miniconsole + main console showing first line indentation & hanging
indentation for single and double width chars:

<details>
<summary>Script for generating a log to compare old wrapping vs.
new</summary>
```
function testWrap()
local echoText = {
"12x4567x90",
"12x 567x 0",
"字❤️包裝🙏😇",
"字 ❤️ 包 裝 🙏 😇",
"字a❤️🙏b😇",
"字 ❤️ test 🙏 😇"
}
setWindowWrap(200)
local success, message, filename, code = startLogging(true)
if code == 1 or code == -1 then print(f"Started logging to {filename}") end
if setWindowWrapHangingIndent then
print("Updated wrapping")
setWindowWrapHangingIndent("main",0)
else
print("Legacy wrapping")
end
for _,indent in ipairs({0,2}) do
local wrapwidth = 5
setWindowWrapIndent("main",indent)
setWindowWrap(200)
cecho(f "Wrap width: {wrapwidth}, indent: {indent}\n")
for _,str in ipairs(echoText) do
for _,action in ipairs({"feedTriggers","echo","cecho","decho","hecho","print","display"}) do
setWindowWrap(200)
cecho(f "\n<green>{action}<reset>:\n")
setWindowWrap(wrapwidth)
_G[action](f "\n{str}\n")
end
setWindowWrap(200)
cecho(f "\n\n<green>Echoed commands<reset>:\n")
setWindowWrap(wrapwidth)
send(str, true)
print("")
send(str..str, true)
end
end
setWindowWrap(200)
cecho("<green>End of log!\n")
startLogging(false)
end
```
</details>
---------
Co-authored-by: Harrison Martin <h_mart05@uni-muenster.de>
Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
Co-authored-by: Stephen Lyons <slysven@virginmedia.com>
2526 lines
97 KiB
C++
2526 lines
97 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
|
|
* Copyright (C) 2014-2024 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 "Host.h"
|
|
#include "TCommandLine.h"
|
|
#include "TDebug.h"
|
|
#include "TDockWidget.h"
|
|
#include "TEvent.h"
|
|
#include "TLabel.h"
|
|
#include "TMainConsole.h"
|
|
#include "TMap.h"
|
|
#include "TRoomDB.h"
|
|
#include "TSplitter.h"
|
|
#include "TTextEdit.h"
|
|
#include "dlgMapper.h"
|
|
#include "mudlet.h"
|
|
|
|
#include "pre_guard.h"
|
|
#include <QAccessibleInterface>
|
|
#include <QAccessibleWidget>
|
|
#include <QLineEdit>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QScrollBar>
|
|
#include <QShortcut>
|
|
#include <QTextBoundaryFinder>
|
|
#include <QTextCodec>
|
|
#include <QPainter>
|
|
#include "post_guard.h"
|
|
|
|
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)
|
|
, 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)
|
|
{
|
|
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;
|
|
}
|
|
setContentsMargins(0, 0, 0, 0);
|
|
setAttribute(Qt::WA_DeleteOnClose);
|
|
setAttribute(Qt::WA_OpaquePaintEvent); //was disabled
|
|
|
|
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);
|
|
|
|
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);
|
|
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);
|
|
|
|
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);
|
|
// 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
|
|
}
|
|
|
|
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);
|
|
|
|
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);
|
|
//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"));
|
|
|
|
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);
|
|
|
|
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(sizePolicy2);
|
|
layerCommandLine->setMaximumHeight(31);
|
|
layerCommandLine->setMinimumHeight(31);
|
|
|
|
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 QGridLayout(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);
|
|
replayButton->setIcon(QIcon(qsl(":/icons/media-tape.png")));
|
|
replayButton->setToolTip(utils::richText(tr("Toggle recording of replays")));
|
|
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);
|
|
logButton->setToolTip(utils::richText(tr("Toggle logging")));
|
|
|
|
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);
|
|
mpLineEdit_networkLatency->setToolTip(utils::richText(tr("<i>N:</i> is the latency of the game server and network (aka ping, in seconds),<br>"
|
|
"<i>S:</i> is the system processing time - how long your triggers took to process the last line(s).")));
|
|
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));
|
|
emergencyStop->setIcon(QIcon(qsl(":/icons/edit-bomb.png")));
|
|
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->setMaximumSize(QSize(150, 30));
|
|
mpBufferSearchBox->setSizePolicy(sizePolicy5);
|
|
mpBufferSearchBox->setFont(mpHost->mCommandLineFont);
|
|
mpBufferSearchBox->setFocusPolicy(Qt::ClickFocus);
|
|
mpBufferSearchBox->setPlaceholderText("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);
|
|
layoutButtonLayer->addWidget(mpBufferSearchBox, 0, 0, 0, 4);
|
|
layoutButtonLayer->addWidget(mpBufferSearchUp, 0, 5);
|
|
layoutButtonLayer->addWidget(mpBufferSearchDown, 0, 6);
|
|
layoutButtonLayer->addWidget(timeStampButton, 0, 7);
|
|
layoutButtonLayer->addWidget(replayButton, 0, 8);
|
|
layoutButtonLayer->addWidget(logButton, 0, 9);
|
|
layoutButtonLayer->addWidget(emergencyStop, 0, 10);
|
|
if (mType == MainConsole) {
|
|
// In fact a whole lot more could be inside this "if"!
|
|
layoutButtonLayer->addWidget(mpLineEdit_networkLatency, 0, 11);
|
|
}
|
|
layoutLayer2->setContentsMargins(0, 0, 0, 0);
|
|
layout->addWidget(layer);
|
|
layerCommandLine->setAutoFillBackground(true);
|
|
|
|
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;
|
|
}
|
|
|
|
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->setMaximumWidth(31);
|
|
buttonLayer->setMinimumWidth(400);
|
|
buttonLayer->setMaximumWidth(400);
|
|
mpButtonMainLayer->setMinimumWidth(400);
|
|
mpButtonMainLayer->setMaximumWidth(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)) {
|
|
mDisplayFont = mpHost->getDisplayFont();
|
|
mDisplayFontName = mDisplayFont.family();
|
|
mDisplayFontSize = mDisplayFont.pointSize();
|
|
|
|
// 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());
|
|
}
|
|
|
|
if (mType & MainConsole) {
|
|
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(0, 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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
mpMainDisplay->move(mBorders.left(), mBorders.top());
|
|
|
|
if (mType & (CentralDebugConsole|ErrorConsole)) {
|
|
layerCommandLine->hide();
|
|
} else if (mType & ~(SubConsole|UserWindow)) {
|
|
// does nothing for SubConsole or UserWindows
|
|
layerCommandLine->move(0, mpBaseVFrame->height() - layerCommandLine->height());
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
mpMainDisplay->resize(x - mBorders.left() - mBorders.right(), y - mBorders.top() - mBorders.bottom() - mpCommandLine->height());
|
|
|
|
if (mType & MainConsole) {
|
|
mpCommandLine->adjustHeight();
|
|
}
|
|
|
|
mpMainDisplay->move(mBorders.left(), mBorders.top());
|
|
x = width();
|
|
y = height();
|
|
const QSize s = QSize(x, y);
|
|
QResizeEvent event(s, s);
|
|
QApplication::sendEvent(this, &event);
|
|
}
|
|
|
|
void TConsole::clear()
|
|
{
|
|
mUpperPane->resetHScrollbar();
|
|
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);
|
|
mReplayFile.open(QIODevice::WriteOnly);
|
|
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);
|
|
} else {
|
|
if (!mReplayFile.commit()) {
|
|
qDebug() << "TConsole::slot_toggleReplayRecording: error saving replay: " << mReplayFile.errorString();
|
|
printSystemMessage(tr("Replay recording has been stopped, but couldn't be saved.") % QChar::LineFeed);
|
|
} else {
|
|
printSystemMessage(tr("Replay recording has been stopped. File: %1").arg(mReplayFile.fileName()) % QChar::LineFeed);
|
|
}
|
|
}
|
|
}
|
|
|
|
QString getColorCode(QColor color)
|
|
{
|
|
return qsl("%1,%2,%3,%4").arg(color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha());
|
|
}
|
|
|
|
void TConsole::changeColors()
|
|
{
|
|
mDisplayFont.setFixedPitch(true);
|
|
if (mType == CentralDebugConsole) {
|
|
mDisplayFont.setStyleStrategy((QFont::StyleStrategy)(QFont::NoAntialias | QFont::PreferQuality));
|
|
mDisplayFont.setFixedPitch(true);
|
|
mUpperPane->setFont(mDisplayFont);
|
|
mLowerPane->setFont(mDisplayFont);
|
|
} else if (mType & (ErrorConsole|SubConsole|UserWindow|Buffer)) {
|
|
mDisplayFont.setStyleStrategy(QFont::StyleStrategy(QFont::NoAntialias | QFont::PreferQuality));
|
|
mDisplayFont.setFixedPitch(true);
|
|
mUpperPane->setFont(mDisplayFont);
|
|
mLowerPane->setFont(mDisplayFont);
|
|
if (!mBgImageMode) {
|
|
auto styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1);}").arg(getColorCode(mBgColor));
|
|
mpMainDisplay->setStyleSheet(styleSheet);
|
|
} 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 (mpHost->mNoAntiAlias) {
|
|
mpHost->setDisplayFontStyle(QFont::NoAntialias);
|
|
} else {
|
|
mpHost->setDisplayFontStyle(QFont::StyleStrategy(QFont::PreferAntialias | QFont::PreferQuality));
|
|
}
|
|
mpHost->setDisplayFontFixedPitch(true);
|
|
mDisplayFont.setFixedPitch(true);
|
|
mUpperPane->setFont(mpHost->getDisplayFont());
|
|
mLowerPane->setFont(mpHost->getDisplayFont());
|
|
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;
|
|
if (mpCommandLine) {
|
|
mpCommandLine->setFont(mpHost->getDisplayFont());
|
|
}
|
|
mFormatCurrent.setColors(mpHost->mFgColor, mpHost->mBgColor);
|
|
} 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(0, this, [this]() { mUpperPane->scrollUp(mLowerPane->getRowCount()); });
|
|
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();
|
|
}
|
|
}
|
|
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;
|
|
|
|
} else {
|
|
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;
|
|
} else {
|
|
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
|
|
{
|
|
const int x = P_begin.x();
|
|
const int y = P_begin.y();
|
|
if (y < 0 || x < 0 || y >= static_cast<int>(buffer.buffer.size()) || x >= (static_cast<int>(buffer.buffer.at(y).size()) - 1)) {
|
|
return qMakePair(2, TChar());
|
|
}
|
|
|
|
return qMakePair(0, buffer.buffer.at(y).at(x));
|
|
}
|
|
|
|
void TConsole::luaWrapLine(int line)
|
|
{
|
|
if (!mpHost) {
|
|
return;
|
|
}
|
|
buffer.wrapLine(line, mWrapAt, mIndentCount, mHangingIndentCount);
|
|
}
|
|
|
|
bool TConsole::setFontSize(int size)
|
|
{
|
|
mDisplayFontSize = size;
|
|
|
|
refreshView();
|
|
return true;
|
|
}
|
|
|
|
bool TConsole::setConsoleBackgroundImage(const QString& imgPath, int mode)
|
|
{
|
|
QColor bgColor;
|
|
QString styleSheet;
|
|
|
|
if (mType == MainConsole) {
|
|
bgColor = mpHost->mBgColor;
|
|
} else {
|
|
bgColor = mBgColor;
|
|
}
|
|
|
|
if (mode == 1) {
|
|
styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1); border-image: url(%2);}").arg(getColorCode(bgColor)).arg(imgPath);
|
|
} else if (mode == 2) {
|
|
styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1); background-image: url(%2); background-repeat: no-repeat; background-position: center; background-origin: margin;}")
|
|
.arg(getColorCode(bgColor))
|
|
.arg(imgPath);
|
|
} else if (mode == 3) {
|
|
styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1); background-image: url(%2);}").arg(getColorCode(bgColor)).arg(imgPath);
|
|
} else if (mode == 4) {
|
|
styleSheet = qsl("QWidget#MainDisplay{background-color: rgba(%1); %2}").arg(getColorCode(bgColor)).arg(imgPath);
|
|
} else {
|
|
return false;
|
|
}
|
|
mpMainDisplay->setStyleSheet(styleSheet);
|
|
mBgImageMode = mode;
|
|
mBgImagePath = imgPath;
|
|
return true;
|
|
}
|
|
|
|
bool TConsole::resetConsoleBackgroundImage()
|
|
{
|
|
mBgImageMode = 0;
|
|
changeColors();
|
|
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);
|
|
// put this CommandLine in the mainConsoles SubCommandLineMap
|
|
// name is the console name
|
|
mpHost->mpConsole->mSubCommandLineMap[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->mDisplayFont = QFont(mDisplayFontName, mDisplayFontSize, QFont::Normal);
|
|
mUpperPane->setFont(mUpperPane->mDisplayFont);
|
|
mUpperPane->updateScreenView();
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->mDisplayFont = QFont(mDisplayFontName, mDisplayFontSize, QFont::Normal);
|
|
mLowerPane->setFont(mLowerPane->mDisplayFont);
|
|
mLowerPane->updateScreenView();
|
|
mLowerPane->forceUpdate();
|
|
}
|
|
|
|
bool TConsole::setFontName(const QString& fontName)
|
|
{
|
|
mDisplayFontName = fontName;
|
|
|
|
refreshView();
|
|
return 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;
|
|
} else {
|
|
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) {
|
|
mpScrollBar->setVisible(isVisible);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (buffer.lineBuffer.at(lineBeforeNewContent).right(1) != QChar(QChar::LineFeed)) {
|
|
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::printSystemMessage(const QString& msg)
|
|
{
|
|
const QString txt = tr("System Message: %1").arg(msg);
|
|
print(txt, mSystemMessageFgColor, mSystemMessageBgColor);
|
|
}
|
|
|
|
void TConsole::echo(const QString& msg)
|
|
{
|
|
if (mTriggerEngineMode) {
|
|
buffer.appendLine(msg, 0, msg.size() - 1, mFormatCurrent.foreground(), mFormatCurrent.background(), mFormatCurrent.allDisplayAttributes());
|
|
} else {
|
|
print(msg);
|
|
}
|
|
}
|
|
|
|
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(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();
|
|
emergencyStop->setIcon(QIcon(qsl(":/icons/red-bomb.png")));
|
|
} else {
|
|
mpHost->reenableAllTriggers();
|
|
emergencyStop->setIcon(QIcon(qsl(":/icons/edit-bomb.png")));
|
|
}
|
|
}
|
|
|
|
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));
|
|
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);
|
|
|
|
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) {
|
|
mUpperPane->setFocusProxy(pCommandLine);
|
|
QAccessibleEvent event(pCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else if (mType == UserWindow) {
|
|
if (pCommandLine && pCommandLine->isVisible()) {
|
|
mUpperPane->setFocusProxy(pCommandLine);
|
|
QAccessibleEvent event(pCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else {
|
|
mUpperPane->setFocusProxy(mpHost->mpConsole->mpCommandLine);
|
|
QAccessibleEvent event(mpHost->mpConsole->mpCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
}
|
|
} else if (mType == SubConsole) {
|
|
if (pCommandLine && pCommandLine->isVisible()) {
|
|
mUpperPane->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...
|
|
mUpperPane->setFocusProxy(parentConsole->mpCommandLine);
|
|
QAccessibleEvent event(parentConsole->mpCommandLine, QAccessible::Focus);
|
|
QAccessible::updateAccessibility(&event);
|
|
} else {
|
|
// Somehow that has failed so fall back to the main console
|
|
mUpperPane->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(0, 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) {
|
|
file.open(QIODevice::WriteOnly | QIODevice::Text);
|
|
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();
|
|
}
|
|
}
|