mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
#### Brief overview of PR changes/additions - Seven state getters that are the inverse of setters we already ship: `getUserWindowTitle`, `getUserWindowStyleSheet`, `getCmdLineStyleSheet`, `getLabelToolTip`, `getScrollBarVisible`, `getMapWindowTitle` and `getMapWidgetGeometry` - Each returns nil plus a message when the window, label or map widget it names does not exist, reusing the matching setter's wording so the pair reports the same problems the same way - 39 specs added to the existing `UI_spec.lua` and `Mapper_spec.lua` #### Motivation for adding to Mudlet #9630's audit left 11 Geyser/UI rows untestable purely because the state those functions set could not be read back; this tranche unblocks them exactly as #9528's getters unblocked the geometry specs. Scripts get the same readback symmetry as a side effect. #### Other info (issues closed, discussion etc) One deliberate behaviour change: `enableScrollBar`/`disableScrollBar` now record what they were asked for, so `getScrollBarVisible` answers for a profile that is not the front tab (whose whole console Mudlet hides) instead of reporting every background profile's scroll bar as gone. Wiki pages for the seven functions to follow in Area 51. **Test case:** busted 1868 passed / 0 failed / 0 errors / 17 pending (baseline 1829), green twice on the same isolated profile, plus ctest; 31 of the new specs verified by breaking the getters - wrong return values fail 20, making the not-found branches succeed fails 7, and dropping the empty-name and nil handling fails 8 more. Assisted-by: Claude:claude-opus-5
2280 lines
84 KiB
C++
2280 lines
84 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
|
|
* Copyright (C) 2014-2023 by Stephen Lyons - slysven@virginmedia.com *
|
|
* Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
|
|
* Copyright (C) 2016 by Ian Adkins - ieadkins@gmail.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 "THyperlinkVisibilityManager.h"
|
|
#include "TLabel.h"
|
|
#include "TMap.h"
|
|
#include "TMedia.h"
|
|
#include "TRoomDB.h"
|
|
#include "TTextBox.h"
|
|
#include "TTextEdit.h"
|
|
#include "dlgMapper.h"
|
|
#include "mudlet.h"
|
|
#include "GifTracker.h"
|
|
|
|
#include <QDialog>
|
|
#include <QDockWidget>
|
|
#include <QIcon>
|
|
#include <QLabel>
|
|
#include <QLineEdit>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QProgressDialog>
|
|
#include <QUiLoader>
|
|
#include <QScrollBar>
|
|
#include <QShortcut>
|
|
#include <QSizePolicy>
|
|
#include <QTextBoundaryFinder>
|
|
#include <QTextCodec>
|
|
#include <QTextStream>
|
|
#include <QPainter>
|
|
#include <QVideoWidget>
|
|
|
|
|
|
TMainConsole::TMainConsole(Host* pH, QWidget* parent)
|
|
: TConsole(pH, qsl("main"), TConsole::MainConsole, parent)
|
|
, mClipboard(pH)
|
|
{
|
|
setFont(pH->getAndClearTempDisplayFont());
|
|
|
|
// During first use where mIsDebugConsole IS true mudlet::self() is null
|
|
// then - but we rely on that flag to avoid having to also test for a
|
|
// non-null mudlet::self() - the connect(...) will produce a debug
|
|
// message and not make THAT connection should it indeed be null but it
|
|
// is not fatal...
|
|
connect(mudlet::self(), &mudlet::signal_profileMapReloadRequested, this, &TMainConsole::slot_reloadMap, Qt::UniqueConnection);
|
|
connect(this, &TMainConsole::signal_newDataAlert, mudlet::self(), &mudlet::slot_newDataOnHost, Qt::UniqueConnection);
|
|
|
|
// Load up the spelling dictionary from the system:
|
|
setSystemSpellDictionary(mpHost->getSpellDic());
|
|
|
|
// Load up the spelling dictionary for the profile - needs to handle the
|
|
// absence of files for the first run in a new profile or from an older
|
|
// Mudlet version:
|
|
setProfileSpellDictionary();
|
|
|
|
// Ensure the QWidget has the profile name embedded into it
|
|
setProperty("HostName", pH->getName());
|
|
}
|
|
|
|
TMainConsole::~TMainConsole()
|
|
{
|
|
// Neither is a child of this console: the map dock is reparented onto the main
|
|
// window by addDockWidget(), and the unpacking dialog is parentless. So neither
|
|
// dies with the console automatically.
|
|
if (mpDockableMapWidget) {
|
|
mpDockableMapWidget->deleteLater();
|
|
}
|
|
if (mpUnpackingDialog) {
|
|
mpUnpackingDialog->deleteLater();
|
|
}
|
|
if (mpHunspell_system) {
|
|
Hunspell_destroy(mpHunspell_system);
|
|
mpHunspell_system = nullptr;
|
|
}
|
|
if (mpHunspell_profile) {
|
|
Hunspell_destroy(mpHunspell_profile);
|
|
mpHunspell_profile = nullptr;
|
|
if (mudlet::self()) {
|
|
// Need to commit any changes to personal dictionary
|
|
qDebug() << "TCommandLine::~TConsole(...) INFO - Saving profile's own Hunspell dictionary...";
|
|
mudlet::self()->saveDictionary(mudlet::self()->getMudletPath(enums::profileDataItemPath, mProfileName, qsl("profile")), mWordSet_profile);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::setLabelStyleSheet(const QString& name, const QString& stylesheet)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a label cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pL = mLabelMap.value(name);
|
|
if (pL) {
|
|
pL->setStyleSheet(stylesheet);
|
|
return {true, QString()};
|
|
}
|
|
return {false, qsl("label name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::optional<QString> TMainConsole::getLabelStyleSheet(const QString& name) const
|
|
{
|
|
QMap<QString, TLabel*>::const_iterator const it = mLabelMap.constFind(name);
|
|
if (it != mLabelMap.cend() && it.key() == name) {
|
|
return it.value()->styleSheet();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::optional<QSize> TMainConsole::getLabelSizeHint(const QString& name) const
|
|
{
|
|
QMap<QString, TLabel*>::const_iterator const it = mLabelMap.constFind(name);
|
|
if (it != mLabelMap.cend() && it.key() == name) {
|
|
return it.value()->sizeHint();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::optional<QString> TMainConsole::getLabelToolTip(const QString& name) const
|
|
{
|
|
auto pL = mLabelMap.value(name);
|
|
if (!pL) {
|
|
return {};
|
|
}
|
|
|
|
return {pL->toolTip()};
|
|
}
|
|
|
|
// NOLINTNEXTLINE(readability-make-member-function-const)
|
|
std::pair<bool, QString> TMainConsole::setUserWindowStyleSheet(const QString& name, const QString& userWindowStyleSheet)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a userwindow cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pW = mDockWidgetMap.value(name);
|
|
if (pW) {
|
|
pW->setStyleSheet(userWindowStyleSheet);
|
|
return {true, QString()};
|
|
}
|
|
return {false, qsl("userwindow name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::optional<QString> TMainConsole::getUserWindowStyleSheet(const QString& name) const
|
|
{
|
|
auto pW = mDockWidgetMap.value(name);
|
|
if (!pW) {
|
|
return {};
|
|
}
|
|
|
|
return {pW->styleSheet()};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::setCmdLineStyleSheet(const QString& name, const QString& styleSheet)
|
|
{
|
|
if (name.isEmpty() || !name.compare(qsl("main"))) {
|
|
mpHost->mpConsole->mpCommandLine->setStyleSheet(styleSheet);
|
|
return {true, QString()};
|
|
}
|
|
|
|
auto pN = mSubCommandLineMap.value(name);
|
|
if (pN) {
|
|
pN->setStyleSheet(styleSheet);
|
|
return {true, QString()};
|
|
}
|
|
return {false, qsl("command-line name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::optional<QString> TMainConsole::getCmdLineStyleSheet(const QString& name) const
|
|
{
|
|
if (name.isEmpty() || !name.compare(qsl("main"))) {
|
|
if (auto pMain = mpHost->mpConsole->mpCommandLine) {
|
|
return {pMain->styleSheet()};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
auto pN = mSubCommandLineMap.value(name);
|
|
if (!pN) {
|
|
return {};
|
|
}
|
|
|
|
return {pN->styleSheet()};
|
|
}
|
|
|
|
void TMainConsole::toggleLogging(bool isMessageEnabled)
|
|
{
|
|
const auto loggingPath = mudlet::getMudletPath(enums::profileDataItemPath, mpHost->getName(), qsl("autolog"));
|
|
QFile file(loggingPath);
|
|
const QDateTime logDateTime = QDateTime::currentDateTime();
|
|
if (!mLogToLogFile) {
|
|
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
|
qWarning() << "TMainConsole: failed to open autolog file for writing:" << file.errorString();
|
|
return;
|
|
}
|
|
QTextStream out(&file);
|
|
file.close();
|
|
|
|
QString directoryLogFile;
|
|
QString logFileName;
|
|
// If no log directory is set, default to Mudlet's replay and log files path
|
|
if (mpHost->mLogDir == nullptr || mpHost->mLogDir.isEmpty()) {
|
|
directoryLogFile = mudlet::getMudletPath(enums::profileReplayAndLogFilesPath, mProfileName);
|
|
} else {
|
|
directoryLogFile = mpHost->mLogDir;
|
|
}
|
|
// The format being empty is a signal value that means use a specified
|
|
// name:
|
|
if (mpHost->mLogFileNameFormat.isEmpty()) {
|
|
if (mpHost->mLogFileName.isEmpty()) {
|
|
// If no log name is set, use the default placeholder
|
|
//: Must be a valid default filename for a log-file and is used if the user does not enter any other value (Ensure all instances have the same translation {one of two copies}).
|
|
logFileName = tr("logfile");
|
|
} else {
|
|
// Otherwise a specific name as one is given
|
|
logFileName = mpHost->mLogFileName;
|
|
}
|
|
} else {
|
|
logFileName = logDateTime.toString(mpHost->mLogFileNameFormat);
|
|
}
|
|
|
|
// The preset file name formats are derived from date/times so that
|
|
// alphabetical filename and date sort order are the same...
|
|
const QDir dirLogFile;
|
|
if (!dirLogFile.exists(directoryLogFile)) {
|
|
dirLogFile.mkpath(directoryLogFile);
|
|
}
|
|
|
|
mpHost->mIsCurrentLogFileInHtmlFormat = mpHost->mIsNextLogFileInHtmlFormat;
|
|
if (mpHost->mIsCurrentLogFileInHtmlFormat) {
|
|
mLogFileName = qsl("%1/%2.html").arg(directoryLogFile, logFileName);
|
|
} else {
|
|
mLogFileName = qsl("%1/%2.txt").arg(directoryLogFile, logFileName);
|
|
}
|
|
mLogFile.setFileName(mLogFileName);
|
|
// We do not want to use WriteOnly here:
|
|
// Append = "The device is opened in append mode so that all data is
|
|
// written to the end of the file."
|
|
// WriteOnly = "The device is open for writing. Note that this mode
|
|
// implies Truncate."
|
|
if (mpHost->mIsCurrentLogFileInHtmlFormat) {
|
|
if (!mLogFile.open(QIODevice::ReadWrite)) {
|
|
qWarning() << "TMainConsole: failed to open log file for reading/writing:" << mLogFile.errorString();
|
|
return;
|
|
}
|
|
} else {
|
|
if (!mLogFile.open(QIODevice::Append)) {
|
|
qWarning() << "TMainConsole: failed to open log file for appending:" << mLogFile.errorString();
|
|
return;
|
|
}
|
|
}
|
|
mLogStream.setDevice(&mLogFile);
|
|
|
|
if (isMessageEnabled) {
|
|
const QString message = qsl("%1\n").arg(tr("Logging has started. Log file is %1").arg(mLogFile.fileName()));
|
|
printSystemMessage(message);
|
|
// This puts text onto console that is IMMEDIATELY POSTED into log file so
|
|
// must be done BEFORE logging starts - or actually mLogToLogFile gets set!
|
|
}
|
|
mLogToLogFile = true;
|
|
} else {
|
|
QFile::remove(loggingPath);
|
|
mLogToLogFile = false;
|
|
if (isMessageEnabled) {
|
|
const QString message = qsl("%1\n").arg(tr("Logging has been stopped. Log file is %1").arg(mLogFile.fileName()));
|
|
printSystemMessage(message);
|
|
// This puts text onto console that is IMMEDIATELY POSTED into log file so
|
|
// must be done AFTER logging ends - or actually mLogToLogFile gets reset!
|
|
}
|
|
}
|
|
|
|
if (mLogToLogFile) {
|
|
// Logging is being turned on
|
|
if (mpHost->mIsCurrentLogFileInHtmlFormat) {
|
|
QString log;
|
|
QTextStream logStream(&log);
|
|
// No setting a QTextCodec here, they don't work on QString based QTextStreams
|
|
QStringList fontsList; // List of fonts to become the font-family entry for
|
|
// the master css in the header
|
|
fontsList << this->fontInfo().family(); // Seems to be the best way to get the
|
|
// font in use, as different TConsole
|
|
// instances within the same profile
|
|
// might have different fonts
|
|
fontsList << qsl("Courier New");
|
|
fontsList << qsl("Monospace");
|
|
fontsList << qsl("Courier");
|
|
fontsList.removeDuplicates(); // In case the actual one is one of the defaults here
|
|
|
|
logStream << "<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.01//EN' 'http://www.w3.org/TR/html4/strict.dtd'>\n";
|
|
logStream << "<html>\n";
|
|
logStream << " <head>\n";
|
|
logStream << " <meta http-equiv='content-type' content='text/html; charset=utf-8'>";
|
|
// put the charset as early as possible as the parser MUST restart when it
|
|
// switches away from the ASCII default
|
|
logStream << " <meta name='generator' content='" << tr("Mudlet MUD Client version: %1%2").arg(APP_VERSION, mudlet::self()->mAppBuild) << "'>\n";
|
|
// Nice to identify what made the file!
|
|
logStream << " <title>" << tr("Mudlet, log from %1 profile").arg(mProfileName) << "</title>\n";
|
|
// Web-page title
|
|
logStream << " <style type='text/css'>\n";
|
|
logStream << " <!-- body { font-family: '" << fontsList.join("', '") << "'; font-size: 100%; line-height: 1.125em; white-space: nowrap; color:rgb(" << mpHost->mFgColor.red() << ","
|
|
<< mpHost->mFgColor.green() << "," << mpHost->mFgColor.blue() << "); background-color:rgb(" << mpHost->mBgColor.red() << "," << mpHost->mBgColor.green() << ","
|
|
<< mpHost->mBgColor.blue() << ");}\n";
|
|
logStream << " span { white-space: pre-wrap; }\n";
|
|
|
|
if (mpHost->getEnableBlinkText()) {
|
|
logStream << " @keyframes blink-slow { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }\n";
|
|
logStream << " @keyframes blink-fast { 0%, 100% { opacity: 0.4; } 50% { opacity: 1; } }\n";
|
|
logStream << " .blink-slow { animation: blink-slow 2s ease-in-out infinite; }\n";
|
|
logStream << " .blink-fast { animation: blink-fast 1s ease-in-out infinite; }\n";
|
|
}
|
|
|
|
logStream << " -->\n";
|
|
logStream << " </style>\n";
|
|
logStream << " </head>\n";
|
|
bool isAtBody = false;
|
|
bool foundBody = false;
|
|
while (!mLogStream.atEnd()) {
|
|
const QString line = mLogStream.readLine();
|
|
if (line.contains("<body><div>")) {
|
|
// Begin writing old log to the current log when the body is
|
|
// found.
|
|
isAtBody = true;
|
|
foundBody = true;
|
|
} else if (line.contains("</div></body>")) {
|
|
// Stop writing to current log once the end of the old log's
|
|
// <body> is reached.
|
|
isAtBody = false;
|
|
}
|
|
|
|
if (isAtBody) {
|
|
logStream << line << "\n";
|
|
}
|
|
}
|
|
if (!foundBody) {
|
|
logStream << " <body><div>\n";
|
|
} else {
|
|
// Put a horizontal line between separate log sessions
|
|
logStream << " </div><hr><div>\n";
|
|
}
|
|
logStream
|
|
<< qsl("<p>%1</p>\n")
|
|
//: This is the format argument to QDateTime::toString(...) and needs to follow the rules for that function {literal text must be single quoted} as well as being suitable for the translation locale
|
|
.arg(logDateTime.toString(tr("'Log session starting at 'hh:mm:ss' on 'dddd', 'd' 'MMMM' 'yyyy'.")));
|
|
// <div></div> tags required around outside of the body <span></spans> for
|
|
// strict HTML 4 as we do not use <p></p>s or anything else
|
|
|
|
if (!mLogFile.resize(0)) {
|
|
qWarning() << "TConsole::toggleLogging(...) ERROR - Failed to resize HTML Logfile - it may now be corrupted...!";
|
|
}
|
|
mLogStream << log;
|
|
mLogFile.flush();
|
|
} else {
|
|
// File is NOT an HTML one but pure text:
|
|
// Put a horizontal line between separate log sessions
|
|
// Unfortunately QLatin1String does not have a repeated() method,
|
|
// but it does mean we can use non-ASCII/Latin1 characters:
|
|
// Using 10x U+23AF Horizontal line extension from "Box drawing characters":
|
|
if (mLogFile.size() > 5) {
|
|
// Allow a few junk characters ("BOM"???) at the very start of
|
|
// file to not trigger the insertion of this line:
|
|
mLogStream << qsl("⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯").repeated(8).append(QChar::LineFeed);
|
|
}
|
|
mLogStream
|
|
<< qsl("%1\n")
|
|
//: This is the format argument to QDateTime::toString(...) and needs to follow the rules for that function {literal text must be single quoted} as well as being suitable for the translation locale
|
|
.arg(logDateTime.toString(tr("'Log session starting at 'hh:mm:ss' on 'dddd', 'd' 'MMMM' 'yyyy'.")));
|
|
}
|
|
logButton->setToolTip(utils::richText(tr("Stop logging game output to log file.")));
|
|
} else {
|
|
// Logging is being turned off
|
|
buffer.logRemainingOutput();
|
|
//: This is the format argument to QDateTime::toString(...) and needs to follow the rules for that function {literal text must be single quoted} as well as being suitable for the translation locale
|
|
const QString endDateTimeLine = logDateTime.toString(tr("'Log session ending at 'hh:mm:ss' on 'dddd', 'd' 'MMMM' 'yyyy'."));
|
|
if (mpHost->mIsCurrentLogFileInHtmlFormat) {
|
|
mLogStream << qsl("<p>%1</p>\n").arg(endDateTimeLine);
|
|
mLogStream << " </div></body>\n";
|
|
mLogStream << "</html>\n";
|
|
} else {
|
|
// File is NOT an HTML one but pure text:
|
|
mLogStream << endDateTimeLine << "\n";
|
|
}
|
|
mLogFile.flush();
|
|
mLogFile.close();
|
|
logButton->setToolTip(utils::richText(tr("Start logging game output to log file.")));
|
|
}
|
|
}
|
|
|
|
void TMainConsole::selectCurrentLine(std::string& buf)
|
|
{
|
|
const QString key = buf.c_str();
|
|
if (key.isEmpty() || key == QLatin1String("main")) {
|
|
TConsole::selectCurrentLine();
|
|
return;
|
|
}
|
|
auto pC = mSubConsoleMap.value(key);
|
|
if (pC) {
|
|
pC->selectCurrentLine();
|
|
}
|
|
}
|
|
|
|
std::list<int> TMainConsole::getFgColor(QString& buf)
|
|
{
|
|
if (buf.isEmpty() || buf == QLatin1String("main")) {
|
|
return TConsole::getFgColor();
|
|
}
|
|
auto pC = mSubConsoleMap.value(buf);
|
|
if (pC) {
|
|
return pC->getFgColor();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::list<int> TMainConsole::getBgColor(QString& buf)
|
|
{
|
|
if (buf.isEmpty() || buf == QLatin1String("main")) {
|
|
return TConsole::getBgColor();
|
|
}
|
|
auto pC = mSubConsoleMap.value(buf);
|
|
if (pC) {
|
|
return pC->getBgColor();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
QPair<quint8, TChar> TMainConsole::getTextAttributes(const QString& name) const
|
|
{
|
|
if (name.isEmpty() || name == QLatin1String("main")) {
|
|
return TConsole::getTextAttributes();
|
|
}
|
|
|
|
auto pC = mSubConsoleMap.value(name);
|
|
if (pC) {
|
|
return pC->getTextAttributes();
|
|
}
|
|
|
|
return qMakePair(1, TChar());
|
|
}
|
|
|
|
void TMainConsole::luaWrapLine(QString& buf, int line)
|
|
{
|
|
if (buf.isEmpty() || buf == QLatin1String("main")) {
|
|
TConsole::luaWrapLine(line);
|
|
return;
|
|
}
|
|
auto pC = mSubConsoleMap.value(buf);
|
|
if (pC) {
|
|
pC->luaWrapLine(line);
|
|
}
|
|
}
|
|
|
|
QString TMainConsole::getCurrentLine(const std::string& buf)
|
|
{
|
|
const QString key = buf.c_str();
|
|
if (key.isEmpty() || key == QLatin1String("main")) {
|
|
return TConsole::getCurrentLine();
|
|
}
|
|
auto pC = mSubConsoleMap.value(key);
|
|
if (pC) {
|
|
return pC->getCurrentLine();
|
|
}
|
|
return qsl("ERROR: mini console does not exist");
|
|
}
|
|
|
|
|
|
TConsole* TMainConsole::createBuffer(const QString& name)
|
|
{
|
|
if (!mSubConsoleMap.contains(name)) {
|
|
auto pC = new TConsole(mpHost, name, Buffer);
|
|
mSubConsoleMap[name] = pC;
|
|
pC->setContentsMargins(0, 0, 0, 0);
|
|
pC->hide();
|
|
pC->layerCommandLine->hide();
|
|
return pC;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void TMainConsole::resetMainConsole()
|
|
{
|
|
// Delete DockWidgets first — their child UserWindow TConsoles will be
|
|
// cascade-deleted by Qt's parent-child ownership. Remove the corresponding
|
|
// TConsole entries from mSubConsoleMap to avoid dangling pointers.
|
|
QMutableMapIterator<QString, TDockWidget*> itDockWidget(mDockWidgetMap);
|
|
while (itDockWidget.hasNext()) {
|
|
itDockWidget.next();
|
|
mSubConsoleMap.remove(itDockWidget.key());
|
|
itDockWidget.value()->deleteLater();
|
|
itDockWidget.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TCommandLine*> itCommandLine(mSubCommandLineMap);
|
|
while (itCommandLine.hasNext()) {
|
|
itCommandLine.next();
|
|
itCommandLine.value()->deleteLater();
|
|
itCommandLine.remove();
|
|
}
|
|
|
|
// Remaining SubConsole/Buffer entries (UserWindow ones were already removed above)
|
|
QMutableMapIterator<QString, TConsole*> itSubConsole(mSubConsoleMap);
|
|
while (itSubConsole.hasNext()) {
|
|
itSubConsole.next();
|
|
itSubConsole.value()->deleteLater();
|
|
itSubConsole.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TLabel*> itLabel(mLabelMap);
|
|
while (itLabel.hasNext()) {
|
|
itLabel.next();
|
|
if (itLabel.value()->mpMovie) {
|
|
mpHost->getGifTracker()->unregisterGif(itLabel.value()->mpMovie);
|
|
}
|
|
itLabel.value()->deleteLater();
|
|
itLabel.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TScrollBox*> itScrollBox(mScrollBoxMap);
|
|
while (itScrollBox.hasNext()) {
|
|
itScrollBox.next();
|
|
itScrollBox.value()->deleteLater();
|
|
itScrollBox.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TTextBox*> itTextBox(mTextBoxMap);
|
|
while (itTextBox.hasNext()) {
|
|
itTextBox.next();
|
|
itTextBox.value()->deleteLater();
|
|
itTextBox.remove();
|
|
}
|
|
}
|
|
|
|
// This is a sub-console overlaid on to the main or other console
|
|
TConsole* TMainConsole::createMiniConsole(const QString& windowname, const QString& name, int x, int y, int width, int height)
|
|
{
|
|
//if pW then add Console as Overlay to the Userwindow
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pS = mScrollBoxMap.value(windowname);
|
|
if (!pC) {
|
|
if (pS) {
|
|
pC = new TConsole(mpHost, name, SubConsole, pS->widget());
|
|
} else if (pW) {
|
|
pC = new TConsole(mpHost, name, SubConsole, pW->widget());
|
|
} else {
|
|
pC = new TConsole(mpHost, name, SubConsole, mpMainFrame);
|
|
}
|
|
if (!pC) {
|
|
return nullptr;
|
|
}
|
|
mSubConsoleMap[name] = pC;
|
|
pC->setObjectName(name);
|
|
const auto& hostCommandLine = mpHost->mpConsole->mpCommandLine;
|
|
pC->setFocusProxy(hostCommandLine);
|
|
pC->mUpperPane->setFocusProxy(hostCommandLine);
|
|
pC->mLowerPane->setFocusProxy(hostCommandLine);
|
|
pC->resize(width, height);
|
|
pC->mOldX = x;
|
|
pC->mOldY = y;
|
|
pC->setContentsMargins(0, 0, 0, 0);
|
|
pC->move(x, y);
|
|
|
|
pC->setFontSize(12);
|
|
pC->show();
|
|
|
|
return pC;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
// This is a scrollBox overlaid on to the main console
|
|
TScrollBox* TMainConsole::createScrollBox(const QString& windowname, const QString& name, int x, int y, int width, int height)
|
|
{
|
|
//if pW then add ScrollBox as Overlay to the Userwindow
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
auto pSW = mScrollBoxMap.value(windowname);
|
|
auto pS = mScrollBoxMap.value(name);
|
|
if (!pS) {
|
|
if (pW) {
|
|
pS = new TScrollBox(mpHost, pW->widget());
|
|
} else if (pSW) {
|
|
pS = new TScrollBox(mpHost, pSW->widget());
|
|
} else {
|
|
pS = new TScrollBox(mpHost, mpMainFrame);
|
|
}
|
|
mScrollBoxMap[name] = pS;
|
|
pS->setObjectName(name);
|
|
pS->resize(width, height);
|
|
pS->setContentsMargins(0, 0, 0, 0);
|
|
pS->move(x, y);
|
|
pS->show();
|
|
|
|
return pS;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TLabel* TMainConsole::createLabel(const QString& windowname, const QString& name, int x, int y, int width, int height, bool fillBackground, bool clickThrough)
|
|
{
|
|
//if pW put Label in Userwindow
|
|
auto pL = mLabelMap.value(name);
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
auto pS = mScrollBoxMap.value(windowname);
|
|
if (!pL) {
|
|
if (pW) {
|
|
pL = new TLabel(mpHost, name, pW->widget());
|
|
} else if (pS) {
|
|
pL = new TLabel(mpHost, name, pS->widget());
|
|
} else {
|
|
pL = new TLabel(mpHost, name, mpMainFrame);
|
|
}
|
|
mLabelMap[name] = pL;
|
|
pL->setAutoFillBackground(fillBackground);
|
|
pL->setClickThrough(clickThrough);
|
|
pL->resize(width, height);
|
|
pL->setContentsMargins(0, 0, 0, 0);
|
|
pL->move(x, y);
|
|
pL->show();
|
|
mpHost->setBackgroundColor(name, 32, 32, 32, 255);
|
|
return pL;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::deleteLabel(const QString& name)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a label cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pL = mLabelMap.take(name);
|
|
if (pL) {
|
|
if (pL->mpMovie) {
|
|
mpHost->getGifTracker()->unregisterGif(pL->mpMovie);
|
|
}
|
|
|
|
// Using deleteLater() rather than delete as it seems a safer option
|
|
// given that this item is likely to be linked to some events and
|
|
// suchlike:
|
|
pL->deleteLater();
|
|
|
|
// It remains to be seen if the label has "gone" as a result of the
|
|
// above by the time the Lua subsystem processes the following:
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysLabelDeleted"));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(name);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
return {true, QString()};
|
|
}
|
|
|
|
// Message is of the form needed for a Lua API function call run-time error
|
|
return {false, qsl("label name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::deleteMiniConsole(const QString& name)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a miniconsole cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pConsole = mSubConsoleMap.take(name);
|
|
if (pConsole) {
|
|
mCachedWindowSizes.remove(name);
|
|
|
|
// A UserWindow's TConsole lives *inside* a TDockWidget. Deleting only the
|
|
// console (as for an ordinary miniconsole) leaves the dock orphaned in
|
|
// mDockWidgetMap with a now-null widget(), which later crashes - e.g. in
|
|
// getUserWindowSize() - when a window of the same name is recreated. Tear
|
|
// the dock down too; it owns the console as its child widget and deletes
|
|
// it along with itself (mirrors the shutdown path in TConsole::closeEvent).
|
|
if (pConsole->getType() == TConsole::UserWindow) {
|
|
if (auto pDock = mDockWidgetMap.take(name)) {
|
|
// deleteLater() alone is sufficient: the console is the dock's
|
|
// child widget, so destroying the dock destroys the console with
|
|
// it. (No WA_DeleteOnClose - we delete programmatically here, not
|
|
// in response to a close event.)
|
|
pDock->deleteLater();
|
|
} else {
|
|
pConsole->deleteLater();
|
|
}
|
|
} else {
|
|
// Using deleteLater() rather than delete as it seems a safer option
|
|
// given that this item is likely to be linked to some events and
|
|
// suchlike:
|
|
pConsole->deleteLater();
|
|
}
|
|
|
|
// It remains to be seen if the miniconsole has "gone" as a result of the
|
|
// above by the time the Lua subsystem processes the following:
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysMiniConsoleDeleted"));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(name);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
return {true, QString()};
|
|
}
|
|
|
|
// Message is of the form needed for a Lua API function call run-time error
|
|
return {false, qsl("miniconsole name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::deleteCommandLine(const QString& name)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a command line cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pCmdLine = mSubCommandLineMap.take(name);
|
|
if (pCmdLine) {
|
|
// Using deleteLater() rather than delete as it seems a safer option
|
|
// given that this item is likely to be linked to some events and
|
|
// suchlike:
|
|
pCmdLine->deleteLater();
|
|
|
|
// It remains to be seen if the command line has "gone" as a result of the
|
|
// above by the time the Lua subsystem processes the following:
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysCommandLineDeleted"));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(name);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
return {true, QString()};
|
|
}
|
|
|
|
// Message is of the form needed for a Lua API function call run-time error
|
|
return {false, qsl("command line name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::deleteTextBox(const QString& name)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a text edit cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pTextBox = mTextBoxMap.take(name);
|
|
if (pTextBox) {
|
|
pTextBox->deleteLater();
|
|
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysTextEditDeleted"));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(name);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
return {true, QString()};
|
|
}
|
|
|
|
return {false, qsl("text edit name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::deleteScrollBox(const QString& name)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a scrollbox cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pScrollBox = mScrollBoxMap.take(name);
|
|
if (pScrollBox) {
|
|
// Using deleteLater() rather than delete as it seems a safer option
|
|
// given that this item is likely to be linked to some events and
|
|
// suchlike:
|
|
pScrollBox->deleteLater();
|
|
|
|
// It remains to be seen if the scrollbox has "gone" as a result of the
|
|
// above by the time the Lua subsystem processes the following:
|
|
TEvent mudletEvent{};
|
|
mudletEvent.mArgumentList.append(QLatin1String("sysScrollBoxDeleted"));
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mudletEvent.mArgumentList.append(name);
|
|
mudletEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mudletEvent);
|
|
return {true, QString()};
|
|
}
|
|
|
|
// Message is of the form needed for a Lua API function call run-time error
|
|
return {false, qsl("scrollbox name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::setLabelToolTip(const QString& name, const QString& text, double duration)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a label cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pL = mLabelMap.value(name);
|
|
if (pL) {
|
|
duration = duration * 1000;
|
|
pL->setToolTip(text);
|
|
pL->setToolTipDuration(duration);
|
|
return {true, QString()};
|
|
}
|
|
|
|
// Message is of the form needed for a Lua API function call run-time error
|
|
return {false, qsl("label name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::setLabelCursor(const QString& name, int shape)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a label cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pL = mLabelMap.value(name);
|
|
if (pL) {
|
|
if (shape > -1 && shape < 22) {
|
|
pL->setCursor(static_cast<Qt::CursorShape>(shape));
|
|
} else if (shape == -1) {
|
|
pL->unsetCursor();
|
|
} else {
|
|
return {false, qsl("cursor shape '%1' not found. see https://doc.qt.io/qt-5/qt.html#CursorShape-enum").arg(shape)};
|
|
}
|
|
return {true, QString()};
|
|
}
|
|
return {false, qsl("label name '%1' not found").arg(name)};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::setLabelCustomCursor(const QString& name, const QString& pixMapLocation, int hotX, int hotY)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a label cannot have an empty string as its name")};
|
|
}
|
|
|
|
if (pixMapLocation.isEmpty()) {
|
|
return {false, qsl("custom cursor location cannot be an empty string")};
|
|
}
|
|
|
|
auto pL = mLabelMap.value(name);
|
|
if (pL) {
|
|
const QPixmap cursor_pixmap = QPixmap(pixMapLocation);
|
|
if (cursor_pixmap.isNull()) {
|
|
return {false, qsl("couldn't find custom cursor, is the location \"%1\" correct?").arg(pixMapLocation)};
|
|
}
|
|
const QCursor custom_cursor = QCursor(cursor_pixmap, hotX, hotY);
|
|
pL->setCursor(custom_cursor);
|
|
return {true, QString()};
|
|
}
|
|
|
|
return {false, qsl("label name '%1' not found").arg(name)};
|
|
}
|
|
|
|
// Called from TLuaInterpreter::createMapper(...) to create a map in a TConsole,
|
|
// Host::showHideOrCreateMapper(...) {formerly also called
|
|
// createMapper(...)} is used in other cases to make a map in a QDockWidget:
|
|
std::pair<bool, QString> TMainConsole::createMapper(const QString& windowname, int x, int y, int width, int height)
|
|
{
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
auto pM = mpDockableMapWidget;
|
|
if (pM) {
|
|
return {false, qsl("cannot create mapper. Do you already use a map window?")};
|
|
}
|
|
if (!mpMapper) {
|
|
// Arrange for TMap member values to be copied from the Host masters so they
|
|
// are in place when the 2D mapper is created:
|
|
mpHost->getPlayerRoomStyleDetails(mpHost->mpMap->mPlayerRoomStyle,
|
|
mpHost->mpMap->mPlayerRoomOuterDiameterPercentage,
|
|
mpHost->mpMap->mPlayerRoomInnerDiameterPercentage,
|
|
mpHost->mpMap->mPlayerRoomOuterColor,
|
|
mpHost->mpMap->mPlayerRoomInnerColor);
|
|
if (!pW) {
|
|
mpMapper = new dlgMapper(mpMainFrame, mpHost, mpHost->mpMap.data());
|
|
} else {
|
|
mpMapper = new dlgMapper(pW->widget(), mpHost, mpHost->mpMap.data());
|
|
}
|
|
mpHost->mpMap->mpHost = mpHost;
|
|
mpHost->mpMap->mpMapper = mpMapper;
|
|
|
|
if (mpHost->mpMap->mpRoomDB->isEmpty()) {
|
|
// Don't load a map if we already have one around!
|
|
qDebug() << "TConsole::createMapper() - restore map case 2.";
|
|
mpHost->mpMap->pushErrorMessagesToFile(tr("Pre-Map loading(2) report"), true);
|
|
const QDateTime now(QDateTime::currentDateTime());
|
|
|
|
if (mpHost->mpMap->restore(QString())) {
|
|
mpHost->mpMap->audit();
|
|
mpMapper->mp2dMap->init();
|
|
mpMapper->updateAreaComboBox();
|
|
mpMapper->resetAreaComboBoxToPlayerRoomArea();
|
|
mpMapper->show();
|
|
}
|
|
|
|
mpHost->mpMap->pushErrorMessagesToFile(tr("Loading map(2) at %1 report").arg(now.toString(Qt::ISODate)), true);
|
|
|
|
TEvent mapOpenEvent{};
|
|
mapOpenEvent.mArgumentList.append(QLatin1String("mapOpenEvent"));
|
|
mapOpenEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(mapOpenEvent);
|
|
}
|
|
}
|
|
mpMapper->resize(width, height);
|
|
mpMapper->move(x, y);
|
|
|
|
// Qt bug workaround: on Windows and during profile load only, if the mapper widget is created
|
|
// it gives a height and width to mpLeftToolBar, mpRightToolBar, and mpTopToolBar for
|
|
// some reason. Those widgets size back down immediately after on their own (?!), however if
|
|
// getMainWindowSize() is called right after map create, the sizes reported will be wrong
|
|
#if defined(Q_OS_WINDOWS)
|
|
mpLeftToolBar->setHidden(true);
|
|
mpRightToolBar->setHidden(true);
|
|
mpTopToolBar->setHidden(true);
|
|
mpMapper->show();
|
|
mpLeftToolBar->setVisible(true);
|
|
mpRightToolBar->setVisible(true);
|
|
mpTopToolBar->setVisible(true);
|
|
#else
|
|
mpMapper->show();
|
|
#endif
|
|
return {true, QString()};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::createCommandLine(const QString& windowname, const QString& name, int x, int y, int width, int height)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a commandLine cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pN = mSubCommandLineMap.value(name);
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
auto pS = mScrollBoxMap.value(windowname);
|
|
|
|
if (!pN) {
|
|
if (pS) {
|
|
pN = new TCommandLine(mpHost, name, TCommandLine::SubCommandLine, this, pS->widget());
|
|
} else if (pW) {
|
|
pN = new TCommandLine(mpHost, name, TCommandLine::SubCommandLine, this, pW->widget());
|
|
} else {
|
|
pN = new TCommandLine(mpHost, name, TCommandLine::SubCommandLine, this, mpMainFrame);
|
|
}
|
|
mSubCommandLineMap[name] = pN;
|
|
pN->resize(width, height);
|
|
pN->move(x, y);
|
|
pN->show();
|
|
return {true, QString()};
|
|
}
|
|
return {false, QLatin1String("couldn't create commandLine")};
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::createTextBox(const QString& windowname, const QString& name, int x, int y, int width, int height)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, QLatin1String("a text edit cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pT = mTextBoxMap.value(name);
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
auto pS = mScrollBoxMap.value(windowname);
|
|
|
|
if (!pT) {
|
|
if (pS) {
|
|
pT = new TTextBox(mpHost, name, pS->widget());
|
|
} else if (pW) {
|
|
pT = new TTextBox(mpHost, name, pW->widget());
|
|
} else {
|
|
pT = new TTextBox(mpHost, name, mpMainFrame);
|
|
}
|
|
mTextBoxMap[name] = pT;
|
|
pT->resize(width, height);
|
|
pT->move(x, y);
|
|
pT->show();
|
|
return {true, QString()};
|
|
}
|
|
return {false, QLatin1String("couldn't create text edit")};
|
|
}
|
|
|
|
bool TMainConsole::setBackgroundImage(const QString& name, const QString& path)
|
|
{
|
|
auto pL = mLabelMap.value(name);
|
|
if (pL) {
|
|
const QPixmap bgPixmap(path);
|
|
pL->setPixmap(bgPixmap);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Does NOT act on the TMainConsole itself:
|
|
bool TMainConsole::setBackgroundColor(const QString& name, int r, int g, int b, int alpha)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
if (pC) {
|
|
QPalette mainPalette;
|
|
mainPalette.setColor(QPalette::Window, QColor(r, g, b, alpha));
|
|
pC->setPalette(mainPalette);
|
|
pC->mUpperPane->mBgColor = QColor(r, g, b, alpha);
|
|
pC->mLowerPane->mBgColor = QColor(r, g, b, alpha);
|
|
// update the display properly when color selections change.
|
|
pC->mUpperPane->updateScreenView();
|
|
pC->mUpperPane->forceUpdate();
|
|
if (!pC->mUpperPane->mIsTailMode) {
|
|
// The upper pane having mIsTailMode true means lower pane is hidden
|
|
pC->mLowerPane->updateScreenView();
|
|
pC->mLowerPane->forceUpdate();
|
|
}
|
|
return true;
|
|
}
|
|
if (pL) {
|
|
QPalette mainPalette;
|
|
mainPalette.setColor(QPalette::Window, QColor(r, g, b, alpha));
|
|
pL->setPalette(mainPalette);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TMainConsole::raiseWindow(const QString& name)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
auto pM = mpMapper;
|
|
auto pN = mSubCommandLineMap.value(name);
|
|
auto pS = mScrollBoxMap.value(name);
|
|
auto pT = mTextBoxMap.value(name);
|
|
|
|
if (pC) {
|
|
pC->raise();
|
|
return true;
|
|
}
|
|
if (pL) {
|
|
pL->raise();
|
|
return true;
|
|
}
|
|
if (pM && !name.compare(QLatin1String("mapper"), Qt::CaseInsensitive)) {
|
|
pM->raise();
|
|
return true;
|
|
}
|
|
if (pS) {
|
|
pS->raise();
|
|
return true;
|
|
}
|
|
if (pN) {
|
|
pN->raise();
|
|
return true;
|
|
}
|
|
if (pT) {
|
|
pT->raise();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool TMainConsole::lowerWindow(const QString& name)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
auto pM = mpMapper;
|
|
auto pN = mSubCommandLineMap.value(name);
|
|
auto pS = mScrollBoxMap.value(name);
|
|
auto pT = mTextBoxMap.value(name);
|
|
|
|
if (pC) {
|
|
pC->lower();
|
|
mpMainDisplay->lower();
|
|
return true;
|
|
}
|
|
if (pL) {
|
|
pL->lower();
|
|
mpMainDisplay->lower();
|
|
return true;
|
|
}
|
|
if (pM && !name.compare(QLatin1String("mapper"), Qt::CaseInsensitive)) {
|
|
pM->lower();
|
|
mpMainDisplay->lower();
|
|
return true;
|
|
}
|
|
if (pS) {
|
|
pS->lower();
|
|
mpMainDisplay->lower();
|
|
return true;
|
|
}
|
|
if (pN) {
|
|
pN->lower();
|
|
mpMainDisplay->lower();
|
|
return true;
|
|
}
|
|
if (pT) {
|
|
pT->lower();
|
|
mpMainDisplay->lower();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TMainConsole::showWindow(const QString& name)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
if (pC) {
|
|
pC->mUpperPane->updateScreenView();
|
|
pC->mUpperPane->forceUpdate();
|
|
pC->show();
|
|
|
|
pC->mLowerPane->updateScreenView();
|
|
pC->mLowerPane->forceUpdate();
|
|
return true;
|
|
}
|
|
if (pL) {
|
|
pL->show();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TMainConsole::hideWindow(const QString& name)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
if (pC) {
|
|
pC->hide();
|
|
return true;
|
|
}
|
|
if (pL) {
|
|
pL->hide();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TMainConsole::printWindow(const QString& name, const QString& text)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
if (pC) {
|
|
pC->print(text);
|
|
return true;
|
|
}
|
|
if (pL) {
|
|
pL->setText(text);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//getUserWindowSize for resizing in Geyser
|
|
QSize TMainConsole::getUserWindowSize(const QString& windowname) const
|
|
{
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
|
|
// Guard pW->widget(): a dock can briefly outlive its console (the console is
|
|
// deleted via deleteLater()), during which widget() is null - dereferencing
|
|
// it segfaults. Fall back to the main window size until the dock is gone.
|
|
if (pW && pW->widget()) {
|
|
const QSize windowSize = pW->widget()->size();
|
|
const int minValidWidth = 50;
|
|
|
|
// Reject obviously invalid sizes
|
|
if (windowSize.width() < minValidWidth) {
|
|
if (mCachedWindowSizes.contains(windowname)) {
|
|
return mCachedWindowSizes.value(windowname);
|
|
}
|
|
return windowSize;
|
|
}
|
|
|
|
// Reject suspicious shrinkage (more than 50% reduction suggests profile
|
|
// is transitioning and geometry isn't settled yet)
|
|
if (mCachedWindowSizes.contains(windowname)) {
|
|
const QSize cachedSize = mCachedWindowSizes.value(windowname);
|
|
const double shrinkageRatio = static_cast<double>(windowSize.width()) / cachedSize.width();
|
|
if (shrinkageRatio < 0.5) {
|
|
return cachedSize;
|
|
}
|
|
}
|
|
|
|
// Size looks valid, cache and return it
|
|
mCachedWindowSizes[windowname] = windowSize;
|
|
return windowSize;
|
|
}
|
|
|
|
return getMainWindowSize();
|
|
}
|
|
|
|
QPair<bool, QString> TMainConsole::addWordToSet(const QString& word)
|
|
{
|
|
const QString errMsg = qsl("the word \"%1\" already seems to be in the user dictionary");
|
|
QPair<bool, QString> result{};
|
|
if (!mEnableUserDictionary) {
|
|
return qMakePair(false, QLatin1String("a user dictionary is not enable for this profile"));
|
|
}
|
|
|
|
if (!mUseSharedDictionary) {
|
|
// The return value from this function is unclear - it does not seems to
|
|
// indicate anything useful
|
|
Hunspell_add(mpHunspell_profile, word.toUtf8().constData());
|
|
if (!mWordSet_profile.contains(word)) {
|
|
mWordSet_profile.insert(word);
|
|
qDebug().noquote().nospace() << "TConsole::addWordToSet(\"" << word << "\") INFO - word added to profile mWordSet.";
|
|
result.first = true;
|
|
} else {
|
|
result.second = errMsg.arg(word);
|
|
}
|
|
|
|
} else {
|
|
auto pMudlet = mudlet::self();
|
|
QPair<bool, bool> sharedDictionaryResult = pMudlet->addWordToSet(word);
|
|
while (!sharedDictionaryResult.first) {
|
|
qDebug() << "TConsole::addWordToSet(...) ALERT - failed to get a write lock to access mWordSet_shared and loaded shared hunspell dictionary, retrying...";
|
|
sharedDictionaryResult = pMudlet->addWordToSet(word);
|
|
}
|
|
|
|
if (sharedDictionaryResult.second) {
|
|
// Successfully added word:
|
|
result.first = true;
|
|
} else {
|
|
// Word already present
|
|
result.second = errMsg.arg(word);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QPair<bool, QString> TMainConsole::removeWordFromSet(const QString& word)
|
|
{
|
|
const QString errMsg = qsl("the word \"%1\" does not seem to be in the user dictionary");
|
|
QPair<bool, QString> result{};
|
|
if (!mEnableUserDictionary) {
|
|
return qMakePair(false, QLatin1String("a user dictionary is not enable for this profile"));
|
|
}
|
|
|
|
if (!mUseSharedDictionary) {
|
|
// The return value from this function is unclear - it does not seems to
|
|
// indicate anything useful
|
|
Hunspell_remove(mpHunspell_profile, word.toUtf8().constData());
|
|
if (mWordSet_profile.remove(word)) {
|
|
qDebug().noquote().nospace() << "TConsole::removeWordFromSet(\"" << word << "\") INFO - word removed from profile mWordSet.";
|
|
result.first = true;
|
|
} else {
|
|
result.second = errMsg.arg(word);
|
|
}
|
|
|
|
} else {
|
|
auto pMudlet = mudlet::self();
|
|
QPair<bool, bool> sharedDictionaryResult = pMudlet->removeWordFromSet(word);
|
|
while (!sharedDictionaryResult.first) {
|
|
qDebug() << "TConsole::removeWordFromSet(...) ALERT - failed to get a write lock to access mWordSet_shared and loaded shared hunspell dictionary, retrying...";
|
|
sharedDictionaryResult = pMudlet->removeWordFromSet(word);
|
|
}
|
|
|
|
if (sharedDictionaryResult.second) {
|
|
// Successfully added word:
|
|
result.first = true;
|
|
} else {
|
|
// Word already present
|
|
result.second = errMsg.arg(word);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void TMainConsole::setSystemSpellDictionary(const QString& newDict)
|
|
{
|
|
if (newDict.isEmpty() || mSpellDic == newDict) {
|
|
return;
|
|
}
|
|
|
|
mSpellDic = newDict;
|
|
|
|
const QString path = mudlet::getMudletPath(enums::hunspellDictionaryPath, mpHost->getSpellDic());
|
|
QString spell_aff = qsl("%1%2.aff").arg(path, newDict);
|
|
QString spell_dic = qsl("%1%2.dic").arg(path, newDict);
|
|
|
|
if (mpHunspell_system) {
|
|
Hunspell_destroy(mpHunspell_system);
|
|
}
|
|
|
|
#if defined(Q_OS_WINDOWS)
|
|
// strip non-ASCII characters from the path because hunspell can't handle them
|
|
// when compiled with MinGW 7.3.0
|
|
mudlet::self()->sanitizeUtf8Path(spell_aff, qsl("%1.aff").arg(newDict));
|
|
mudlet::self()->sanitizeUtf8Path(spell_dic, qsl("%1.dic").arg(newDict));
|
|
#endif
|
|
|
|
mpHunspell_system = Hunspell_create(spell_aff.toUtf8().constData(), spell_dic.toUtf8().constData());
|
|
if (mpHunspell_system) {
|
|
mHunspellCodecName_system = QByteArray(Hunspell_get_dic_encoding(mpHunspell_system));
|
|
qDebug().noquote().nospace() << "TMainConsole::setSystemSpellDictionary(\"" << newDict << "\") INFO - System Hunspell dictionary loaded for profile, it uses a \""
|
|
<< Hunspell_get_dic_encoding(mpHunspell_system) << "\" encoding...";
|
|
}
|
|
}
|
|
|
|
// NOTE: mEnabledUserDictionary has been wedged on (it will never be false)
|
|
void TMainConsole::setProfileSpellDictionary()
|
|
{
|
|
// Determine and copy the configuration settings from the Host instance:
|
|
mpHost->getUserDictionaryOptions(mEnableUserDictionary, mUseSharedDictionary);
|
|
if (!mEnableUserDictionary) {
|
|
if (mpHunspell_profile) {
|
|
Hunspell_destroy(mpHunspell_profile);
|
|
mpHunspell_profile = nullptr;
|
|
// Need to commit any changes to personal dictionary
|
|
qDebug() << "TMainConsole::setProfileSpellDictionary() INFO - Saving profile's own Hunspell dictionary...";
|
|
mudlet::self()->saveDictionary(mudlet::self()->getMudletPath(enums::profileDataItemPath, mProfileName, qsl("profile")), mWordSet_profile);
|
|
}
|
|
// Nothing else to do if not using the shared one
|
|
|
|
} else {
|
|
if (!mUseSharedDictionary) {
|
|
// Want to use per profile dictionary, is it loaded?
|
|
if (!mpHunspell_profile) {
|
|
// No - so load it
|
|
qDebug() << "TMainConsole::setProfileSpellDictionary() INFO - Preparing profile's own Hunspell dictionary...";
|
|
mpHunspell_profile = mudlet::self()->prepareProfileDictionary(mpHost->getName(), mWordSet_profile);
|
|
}
|
|
// Else no need to load it
|
|
|
|
} else {
|
|
// Want to use the shared dictionary - this will open it if needed:
|
|
mpHunspell_shared = mudlet::self()->prepareSharedDictionary();
|
|
}
|
|
}
|
|
}
|
|
|
|
QSet<QString> TMainConsole::getWordSet() const
|
|
{
|
|
if (!mEnableUserDictionary) {
|
|
return QSet<QString>();
|
|
}
|
|
|
|
if (!mUseSharedDictionary) {
|
|
return mWordSet_profile;
|
|
}
|
|
return mudlet::self()->getWordSet();
|
|
}
|
|
|
|
void TMainConsole::setProfileName(const QString& newName)
|
|
{
|
|
TConsole::setProfileName(newName);
|
|
|
|
for (const auto pC : std::as_const(mSubConsoleMap)) {
|
|
pC->setProfileName(newName);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::refreshSubconsoles()
|
|
{
|
|
for (const auto pC : std::as_const(mSubConsoleMap)) {
|
|
if (pC) {
|
|
pC->refreshView();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::pair<bool, QString> TMainConsole::setUserWindowTitle(const QString& name, const QString& text)
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a user window cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pC = mSubConsoleMap.value(name);
|
|
if (!pC) {
|
|
return {false, qsl("user window name '%1' not found").arg(name)};
|
|
}
|
|
|
|
// If it does not have an mType of UserWindow then it does not in a
|
|
// floatable/dockable widget - so it can't have a titlebar...!
|
|
if (pC->getType() != UserWindow) {
|
|
return {false, qsl("\"%1\" is not a user window").arg(name)};
|
|
}
|
|
|
|
auto pD = mDockWidgetMap.value(name);
|
|
if (Q_LIKELY(pD)) {
|
|
if (text.isEmpty()) {
|
|
// Reset to default text:
|
|
pD->setWindowTitle(tr("User window - %1 - %2").arg(mpHost->getName(), name));
|
|
return {true, QString()};
|
|
}
|
|
|
|
pD->setWindowTitle(text);
|
|
return {true, QString()};
|
|
}
|
|
|
|
// This should be:
|
|
Q_UNREACHABLE();
|
|
// as it means that the TConsole is flagged as being a user window yet
|
|
// it does not have a TDockWidget to hold it...
|
|
return {false, qsl("internal error: TConsole \"%1\" is marked as a user window but does not have a TDockWidget to contain it").arg(name)};
|
|
}
|
|
|
|
// The title is in .second when .first is true, otherwise .second is why there
|
|
// is none. Mirrors setUserWindowTitle's checks in the same order and words, so
|
|
// that a miniconsole sharing the name is not reported as a missing window.
|
|
std::pair<bool, QString> TMainConsole::getUserWindowTitle(const QString& name) const
|
|
{
|
|
if (name.isEmpty()) {
|
|
return {false, qsl("a user window cannot have an empty string as its name")};
|
|
}
|
|
|
|
auto pC = mSubConsoleMap.value(name);
|
|
if (!pC) {
|
|
return {false, qsl("user window name '%1' not found").arg(name)};
|
|
}
|
|
|
|
if (pC->getType() != UserWindow) {
|
|
return {false, qsl("\"%1\" is not a user window").arg(name)};
|
|
}
|
|
|
|
auto pD = mDockWidgetMap.value(name);
|
|
if (!pD) {
|
|
return {false, qsl("internal error: TConsole \"%1\" is marked as a user window but does not have a TDockWidget to contain it").arg(name)};
|
|
}
|
|
|
|
return {true, pD->windowTitle()};
|
|
}
|
|
|
|
bool TMainConsole::setTextFormat(const QString& name, const QColor& fgColor, const QColor& bgColor, const TChar::AttributeFlags& flags)
|
|
{
|
|
if (name.isEmpty() || name.compare(qsl("main"), Qt::CaseSensitive) == 0) {
|
|
mFormatCurrent.setTextFormat(fgColor, bgColor, flags);
|
|
return true;
|
|
}
|
|
|
|
auto pC = mSubConsoleMap.value(name);
|
|
if (pC) {
|
|
pC->mFormatCurrent.setTextFormat(fgColor, bgColor, flags);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void TMainConsole::printOnDisplay(std::string& incomingSocketData, const bool isFromServer)
|
|
{
|
|
Q_ASSERT_X(mpLineEdit_networkLatency, "TMainConsole::printOnDisplay(...)", "mpLineEdit_networkLatency does not point to a valid QLineEdit");
|
|
mProcessingTimer.restart();
|
|
|
|
// Notify visibility manager of incoming data (for output gap detection)
|
|
if (isFromServer) {
|
|
getHyperlinkVisibilityManager().onDataReceived();
|
|
}
|
|
|
|
mTriggerEngineMode = true;
|
|
const int beforeTranslateLastLineNumber = buffer.getLastLineNumber();
|
|
const auto beforeTranslateLastLine = buffer.line(beforeTranslateLastLineNumber - 1);
|
|
buffer.translateToPlainText(incomingSocketData, isFromServer);
|
|
mTriggerEngineMode = false;
|
|
|
|
const int lastLineNumber = buffer.getLastLineNumber();
|
|
const bool bufferChanged = lastLineNumber != beforeTranslateLastLineNumber || buffer.line(lastLineNumber - 1) != beforeTranslateLastLine;
|
|
if (mAlertOnNewData && isFromServer && bufferChanged) {
|
|
QApplication::alert(mudlet::self(), 0);
|
|
}
|
|
|
|
// dequeues MXP events and raise them through the LuaInterpreter
|
|
// TODO: move this somewhere else more appropriate
|
|
auto& mxpEventQueue = mpHost->mMxpClient.mMxpEvents;
|
|
while (!mxpEventQueue.isEmpty()) {
|
|
const auto& event = mxpEventQueue.dequeue();
|
|
mpHost->mLuaInterpreter.signalMXPEvent(event.name, event.attrs, event.actions, event.caption);
|
|
}
|
|
|
|
const double processT = mProcessingTimer.elapsed() / 1000.0;
|
|
if (mpHost->mTelnet.mGA_Driver) {
|
|
/*:
|
|
The first argument 'N' represents the 'N'etwork latency; the second 'S' the
|
|
'S'ystem (processing) time
|
|
*/
|
|
mpLineEdit_networkLatency->setText(tr("N:%1 S:%2").arg(mpHost->mTelnet.networkLatencyTime, 0, 'f', 3).arg(processT, 0, 'f', 3));
|
|
} else {
|
|
/*:
|
|
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...
|
|
*/
|
|
mpLineEdit_networkLatency->setText(tr("<no GA> S:%1").arg(processT, 0, 'f', 3));
|
|
}
|
|
// Modify the tab text if this is not the currently active host - this
|
|
// method is only used on the "main" console so no need to filter depending
|
|
// on TConsole types:
|
|
|
|
emit signal_newDataAlert(mProfileName);
|
|
}
|
|
|
|
void TMainConsole::runTriggers(int line)
|
|
{
|
|
mUserCursor.setY(line);
|
|
mIsPromptLine = buffer.promptBuffer.at(line);
|
|
mEngineCursor = line;
|
|
mUserCursor.setX(0);
|
|
mCurrentLine = buffer.line(line);
|
|
mpHost->getLuaInterpreter()->set_lua_string(cmLuaLineVariable, mCurrentLine);
|
|
mCurrentLine.append('\n');
|
|
|
|
if (mudlet::smDebugMode) {
|
|
TDebug(Qt::darkGreen, Qt::black) << "new line arrived:" >> mpHost;
|
|
TDebug(Qt::lightGray, Qt::black) << TDebug::csmContinue << mCurrentLine << "\n" >> mpHost;
|
|
}
|
|
mpHost->incomingStreamProcessor(mCurrentLine, line);
|
|
mIsPromptLine = false;
|
|
|
|
//FIXME: rewrite: if lines above the current line get deleted -> redraw clean slice
|
|
// otherwise just delete
|
|
}
|
|
|
|
void TMainConsole::finalize()
|
|
{
|
|
if (mUpperPane) {
|
|
mUpperPane->showNewLines();
|
|
}
|
|
if (mLowerPane) {
|
|
mLowerPane->showNewLines();
|
|
}
|
|
}
|
|
|
|
// TODO: It may be worth considering moving the (now) three following methods
|
|
// to the TMap class...?
|
|
bool TMainConsole::saveMap(const QString& location, int saveVersion)
|
|
{
|
|
const QString filename_map =
|
|
location.isEmpty() ? mudlet::getMudletPath(enums::profileDateTimeStampedMapPathFileName, mProfileName, QDateTime::currentDateTime().toString(qsl("yyyy-MM-dd#HH-mm-ss"))) : location;
|
|
|
|
const QDir dir_map(mudlet::getMudletPath(enums::profileMapsPath, mProfileName));
|
|
if (!dir_map.exists() && !dir_map.mkpath(dir_map.path())) {
|
|
return false;
|
|
}
|
|
|
|
QSaveFile file_map(filename_map);
|
|
if (!file_map.open(QIODevice::WriteOnly)) {
|
|
return false;
|
|
}
|
|
|
|
QDataStream out(&file_map);
|
|
if (mudlet::scmRunTimeQtVersion >= QVersionNumber(5, 13, 0)) {
|
|
out.setVersion(mudlet::scmQDataStreamFormat_5_12);
|
|
}
|
|
|
|
bool saved = mpHost->mpMap->serialize(out, saveVersion);
|
|
if (saved && !file_map.commit()) {
|
|
qDebug() << "Error saving map: " << (file_map.error() == QFile::NoError ? "issue with serializing" : file_map.errorString());
|
|
saved = false;
|
|
}
|
|
|
|
if (saved) {
|
|
mpHost->mpMap->resetUnsaved();
|
|
mpHost->mpMap->setSaveError(false);
|
|
} else {
|
|
mpHost->mpMap->setSaveError(true);
|
|
}
|
|
|
|
return saved;
|
|
}
|
|
|
|
bool TMainConsole::loadMap(const QString& location)
|
|
{
|
|
Host* pHost = mpHost;
|
|
if (!pHost) {
|
|
// Check for valid mpHost pointer (mpHost was/is/will be a QPoint<Host>
|
|
// in later software versions and is a weak pointer until used
|
|
// (I think - Slysven ?)
|
|
return false;
|
|
}
|
|
|
|
if (!pHost->mpMap || !pHost->mpMap->mpMapper) {
|
|
// No map or map currently loaded - so try and created mapper
|
|
// but don't load a map here by default, we do that below and it may not
|
|
// be the default map anyhow
|
|
pHost->showHideOrCreateMapper(false);
|
|
}
|
|
|
|
if (!pHost->mpMap || !pHost->mpMap->mpMapper) {
|
|
// And that failed so give up
|
|
return false;
|
|
}
|
|
|
|
pHost->mpMap->mapClear();
|
|
|
|
qDebug() << "TMainConsole::loadMap() - restore map case 1.";
|
|
pHost->mpMap->pushErrorMessagesToFile(tr("Pre-Map loading(1) report"), true);
|
|
const QDateTime now(QDateTime::currentDateTime());
|
|
|
|
bool result = false;
|
|
if (pHost->mpMap->restore(location)) {
|
|
pHost->mpMap->audit();
|
|
pHost->mpMap->mpMapper->mp2dMap->init();
|
|
pHost->mpMap->mpMapper->updateAreaComboBox();
|
|
pHost->mpMap->mpMapper->resetAreaComboBoxToPlayerRoomArea();
|
|
pHost->mpMap->mpMapper->show();
|
|
result = true;
|
|
} else {
|
|
pHost->mpMap->mpMapper->mp2dMap->init();
|
|
pHost->mpMap->mpMapper->updateAreaComboBox();
|
|
pHost->mpMap->mpMapper->show();
|
|
}
|
|
|
|
if (location.isEmpty()) {
|
|
pHost->mpMap->pushErrorMessagesToFile(tr("Loading map(1) at %1 report").arg(now.toString(Qt::ISODate)), true);
|
|
} else {
|
|
pHost->mpMap->pushErrorMessagesToFile(tr(R"(Loading map(1) "%1" at %2 report)").arg(location, now.toString(Qt::ISODate)), true);
|
|
}
|
|
|
|
pHost->mpMap->updateArea(-1);
|
|
|
|
return result;
|
|
}
|
|
|
|
// Used by TLuaInterpreter::loadMap() and dlgProfilePreferences for import/load
|
|
// of files ending in ".xml"
|
|
// The TLuaInterpreter::loadMap() supplies a pointer to an error Message which
|
|
// it requires in the event of an error (it should be written in a structure
|
|
// to match "loadMap: XXXXX." format) - the presence of a non-null pointer here
|
|
// should be used to suppress the writing of error messages direct to the
|
|
// console - if possible!
|
|
bool TMainConsole::importMap(const QString& location, QString* errMsg)
|
|
{
|
|
Host* pHost = mpHost;
|
|
if (!pHost) {
|
|
// Check for valid mpHost pointer (mpHost was/is/will be a QPoint<Host>
|
|
// in later software versions and is a weak pointer until used
|
|
// (I think - Slysven ?)
|
|
if (errMsg) {
|
|
*errMsg = qsl("loadMap: NULL Host pointer {in TConsole::importMap(...)} - something is wrong!");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!pHost->mpMap || !pHost->mpMap->mpMapper) {
|
|
// No map or mapper currently loaded/present - so try and create mapper
|
|
pHost->showHideOrCreateMapper(false);
|
|
}
|
|
|
|
if (!pHost->mpMap || !pHost->mpMap->mpMapper) {
|
|
// And that failed so give up
|
|
if (errMsg) {
|
|
*errMsg = qsl("loadMap: unable to initialise mapper {in TConsole::importMap(...)} - something is wrong!");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Dump any outstanding map errors from past activities that had not yet
|
|
// been logged...
|
|
qDebug() << "TMainConsole::importingMap() - importing map case 1.";
|
|
pHost->mpMap->pushErrorMessagesToFile(tr("Pre-Map importing(1) report"), true);
|
|
const QDateTime now(QDateTime::currentDateTime());
|
|
|
|
bool result = false;
|
|
|
|
const QFileInfo fileInfo(location);
|
|
QString filePathNameString;
|
|
if (!fileInfo.filePath().isEmpty()) {
|
|
if (fileInfo.isRelative()) {
|
|
// Resolve the name relative to the profile home directory:
|
|
filePathNameString = QDir::cleanPath(mudlet::getMudletPath(enums::profileDataItemPath, mProfileName, fileInfo.filePath()));
|
|
} else {
|
|
if (fileInfo.exists()) {
|
|
filePathNameString = fileInfo.canonicalFilePath(); // Cannot use canonical path if file doesn't exist!
|
|
} else {
|
|
filePathNameString = fileInfo.absoluteFilePath();
|
|
}
|
|
}
|
|
}
|
|
|
|
QFile file(filePathNameString);
|
|
if (!file.exists()) {
|
|
if (!errMsg) {
|
|
const QString infoMsg = tr("[ ERROR ] - Map file not found, path and name used was:\n"
|
|
"%1.")
|
|
.arg(filePathNameString);
|
|
pHost->postMessage(infoMsg);
|
|
} else {
|
|
// error message for lua loadMap()
|
|
*errMsg = tr("loadMap: bad argument #1 value (filename used: \n"
|
|
"\"%1\" was not found).")
|
|
.arg(filePathNameString);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (file.open(QFile::ReadOnly | QFile::Text)) {
|
|
if (!errMsg) {
|
|
const QString infoMsg = tr("[ INFO ] - Map file located and opened, now parsing it...");
|
|
pHost->postMessage(infoMsg);
|
|
}
|
|
|
|
result = pHost->mpMap->importMap(file, errMsg);
|
|
|
|
file.close();
|
|
pHost->mpMap->pushErrorMessagesToFile(tr(R"(Importing map(1) "%1" at %2 report)").arg(location, now.toString(Qt::ISODate)));
|
|
} else {
|
|
if (!errMsg) {
|
|
const QString infoMsg = tr(R"([ INFO ] - Map file located but it could not opened, please check permissions on:"%1".)").arg(filePathNameString);
|
|
pHost->postMessage(infoMsg);
|
|
} else {
|
|
*errMsg = tr("loadMap: bad argument #1 value (filename used: \n"
|
|
"\"%1\" could not be opened for reading).")
|
|
.arg(filePathNameString);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pHost->mpMap->updateArea(-1);
|
|
|
|
return result;
|
|
}
|
|
|
|
void TMainConsole::slot_reloadMap(QList<QString> profilesList)
|
|
{
|
|
Host* pHost = getHost();
|
|
if (!pHost) {
|
|
return;
|
|
}
|
|
|
|
if (!profilesList.contains(mProfileName)) {
|
|
qDebug() << "TMainConsole::slot_reloadMap(" << profilesList << ") request received but we:" << mProfileName << "are not mentioned - so we are ignoring it...!";
|
|
return;
|
|
}
|
|
|
|
const QString infoMsg = tr("[ INFO ] - Map reload request received from system...");
|
|
pHost->postMessage(infoMsg);
|
|
|
|
QString outcomeMsg;
|
|
if (loadMap(QString())) {
|
|
outcomeMsg = tr("[ OK ] - ... System Map reload request completed.");
|
|
} else {
|
|
outcomeMsg = tr("[ WARN ] - ... System Map reload request failed.");
|
|
}
|
|
|
|
pHost->postMessage(outcomeMsg);
|
|
}
|
|
|
|
void TMainConsole::resizeEvent(QResizeEvent* event)
|
|
{
|
|
auto pHost = getHost();
|
|
if (!pHost) {
|
|
return;
|
|
}
|
|
|
|
// Process the event like other TConsoles
|
|
TConsole::resizeEvent(event);
|
|
|
|
// Update the record of the text area size for NAWS purposes:
|
|
pHost->updateDisplayDimensions();
|
|
}
|
|
|
|
void TMainConsole::showPackageDownloadProgress(const QString& title, const QString& cancelText)
|
|
{
|
|
auto pHost = getHost();
|
|
if (!pHost) {
|
|
qWarning() << "TMainConsole::showPackageDownloadProgress() WARNING - called with no host; ignoring the download-progress request.";
|
|
return;
|
|
}
|
|
// A second server-triggered download can arrive mid-download (e.g. a
|
|
// reconnect re-sends Client.GUI). QProgressDialog::close() emits canceled(),
|
|
// so closing the superseded dialog while it is still wired to
|
|
// slot_cancelPackageDownload() would abort the download this new dialog is
|
|
// about to track; detach it before closing.
|
|
if (mpPackageDownloadProgressDialog) {
|
|
mpPackageDownloadProgressDialog->disconnect();
|
|
mpPackageDownloadProgressDialog->close();
|
|
}
|
|
// placeholder range; reset by the first download-progress update
|
|
mpPackageDownloadProgressDialog = new QProgressDialog(title, cancelText, 0, 4000000, this);
|
|
connect(mpPackageDownloadProgressDialog, &QProgressDialog::canceled, &pHost->mTelnet, &cTelnet::slot_cancelPackageDownload);
|
|
mpPackageDownloadProgressDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
mpPackageDownloadProgressDialog->show();
|
|
}
|
|
|
|
void TMainConsole::updatePackageDownloadProgress(qint64 got, qint64 total)
|
|
{
|
|
if (mpPackageDownloadProgressDialog) {
|
|
// total is -1 for chunked HTTP responses of unknown length; a (0, 0)
|
|
// range turns the dialog into a busy indicator
|
|
mpPackageDownloadProgressDialog->setRange(0, total > 0 ? static_cast<int>(total) : 0);
|
|
mpPackageDownloadProgressDialog->setValue(static_cast<int>(got));
|
|
}
|
|
}
|
|
|
|
void TMainConsole::closePackageDownloadProgress()
|
|
{
|
|
if (mpPackageDownloadProgressDialog) {
|
|
mpPackageDownloadProgressDialog->close();
|
|
}
|
|
}
|
|
|
|
void TMainConsole::createMapProgressDialog(const QString& title, const QString& label, const QString& cancelButtonText, int minimum, int maximum)
|
|
{
|
|
if (mpMapProgressDialog) {
|
|
mpMapProgressDialog->hide();
|
|
mpMapProgressDialog->deleteLater();
|
|
}
|
|
auto pHost = getHost();
|
|
// If canceled() cannot be wired to the map, omit the cancel button rather
|
|
// than show one that does nothing.
|
|
const bool cancelWirable = pHost && !pHost->mpMap.isNull();
|
|
// Deliberately not WA_DeleteOnClose: the JSON import keeps updating this
|
|
// dialog from a processEvents loop, so it must outlive a mid-operation
|
|
// dismissal; we delete it explicitly instead.
|
|
mpMapProgressDialog = new QProgressDialog(label, cancelWirable ? cancelButtonText : QString(), minimum, maximum, this);
|
|
mpMapProgressDialog->setWindowTitle(title);
|
|
mpMapProgressDialog->setWindowIcon(QIcon(qsl(":/icons/mudlet_map_download.png")));
|
|
mpMapProgressDialog->setAutoClose(false);
|
|
mpMapProgressDialog->setAutoReset(false);
|
|
// QProgressDialog still emits canceled() on Escape or window-close even with
|
|
// no cancel button, so only connect it when the operation is cancelable;
|
|
// otherwise a non-cancelable import could be aborted by a spurious cancel.
|
|
if (cancelWirable && !cancelButtonText.isEmpty()) {
|
|
connect(mpMapProgressDialog, &QProgressDialog::canceled, pHost->mpMap.data(), &TMap::slot_mapProgressDialogCancelled);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::showMapTransferProgress(const QString& title, const QString& label, const QString& cancelButtonText)
|
|
{
|
|
createMapProgressDialog(title, label, cancelButtonText, 0, 0);
|
|
mpMapProgressDialog->setMinimumWidth(300);
|
|
mpMapProgressDialog->setMinimumDuration(0);
|
|
mpMapProgressDialog->show();
|
|
}
|
|
|
|
void TMainConsole::showMapJsonProgress(const QString& title, const QString& label, const QString& cancelButtonText, int maximum)
|
|
{
|
|
createMapProgressDialog(title, label, cancelButtonText, 0, maximum);
|
|
mpMapProgressDialog->setWindowModality(Qt::NonModal);
|
|
mpMapProgressDialog->setMinimumWidth(500);
|
|
mpMapProgressDialog->setMinimumDuration(1);
|
|
}
|
|
|
|
void TMainConsole::setMapProgressDialogLabel(const QString& text)
|
|
{
|
|
if (mpMapProgressDialog) {
|
|
mpMapProgressDialog->setLabelText(text);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::setMapProgressDialogRange(int minimum, int maximum)
|
|
{
|
|
if (mpMapProgressDialog) {
|
|
mpMapProgressDialog->setRange(minimum, maximum);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::setMapProgressDialogValue(int value)
|
|
{
|
|
if (mpMapProgressDialog) {
|
|
mpMapProgressDialog->setValue(value);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::disableMapProgressDialogCancel()
|
|
{
|
|
if (mpMapProgressDialog) {
|
|
// Taking the button away does not stop a window-close from emitting
|
|
// canceled(), so drop the connection as well - by this point the
|
|
// operation can no longer be stopped. Only ours goes, leaving
|
|
// QProgressDialog's own canceled() -> cancel() wiring intact.
|
|
if (auto pHost = getHost(); pHost && !pHost->mpMap.isNull()) {
|
|
disconnect(mpMapProgressDialog, &QProgressDialog::canceled, pHost->mpMap.data(), &TMap::slot_mapProgressDialogCancelled);
|
|
}
|
|
mpMapProgressDialog->setCancelButton(nullptr);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::closeMapProgressDialog()
|
|
{
|
|
if (mpMapProgressDialog) {
|
|
// hide() rather than close() so we don't re-enter QProgressDialog's
|
|
// closeEvent -> cancel() while a cancel is already being handled.
|
|
mpMapProgressDialog->hide();
|
|
mpMapProgressDialog->deleteLater();
|
|
// deleteLater() leaves the QPointer set until the event loop gets to
|
|
// run, which a synchronous JSON operation will not let it do, so forget
|
|
// the dialog now and make any late writes to it no-ops.
|
|
mpMapProgressDialog = nullptr;
|
|
}
|
|
}
|
|
|
|
void TMainConsole::createMapperDock(const QString& title, const QString& objectName)
|
|
{
|
|
mpDockableMapWidget = new QDockWidget(title);
|
|
mpDockableMapWidget->setObjectName(objectName);
|
|
}
|
|
|
|
void TMainConsole::showMapperScriptReminder()
|
|
{
|
|
QUiLoader loader;
|
|
QFile file(qsl(":/ui/lacking_mapper_script.ui"));
|
|
if (!file.open(QFile::ReadOnly)) {
|
|
qWarning() << "TMainConsole::showMapperScriptReminder() WARNING - failed to open lacking_mapper_script.ui for reading:" << file.errorString();
|
|
return;
|
|
}
|
|
|
|
auto dialog = qobject_cast<QDialog*>(loader.load(&file, mudlet::self()));
|
|
file.close();
|
|
if (!dialog) {
|
|
qWarning() << "TMainConsole::showMapperScriptReminder() WARNING - could not load the mapping-script reminder dialog.";
|
|
return;
|
|
}
|
|
|
|
connect(dialog, &QDialog::accepted, mudlet::self(), &mudlet::slot_openMappingScriptsPage);
|
|
|
|
dialog->show();
|
|
dialog->raise();
|
|
dialog->activateWindow();
|
|
}
|
|
|
|
void TMainConsole::showUnpackingProgress(const QString& message, const QString& title)
|
|
{
|
|
// deleteLater() not close(): the dialog is parentless with no WA_DeleteOnClose,
|
|
// so closing it would leak it once we overwrite the pointer below.
|
|
if (mpUnpackingDialog) {
|
|
mpUnpackingDialog->deleteLater();
|
|
}
|
|
|
|
QUiLoader loader;
|
|
QFile uiFile(qsl(":/ui/package_manager_unpack.ui"));
|
|
if (!uiFile.open(QFile::ReadOnly)) {
|
|
qWarning() << "TMainConsole::showUnpackingProgress() WARNING - failed to open package_manager_unpack.ui for reading:" << uiFile.errorString();
|
|
return;
|
|
}
|
|
auto* pDialog = qobject_cast<QDialog*>(loader.load(&uiFile, nullptr));
|
|
uiFile.close();
|
|
if (!pDialog) {
|
|
qWarning() << "TMainConsole::showUnpackingProgress() WARNING - could not load the unpacking progress dialog.";
|
|
return;
|
|
}
|
|
mpUnpackingDialog = pDialog;
|
|
|
|
// Trap: processEvents() below can deliver a re-entrant install (or its
|
|
// matching hide) that replaces or clears mpUnpackingDialog and disposes of
|
|
// this frame's dialog. Drive a local pointer, never the member, and bail if
|
|
// our dialog is taken out from under us.
|
|
QPointer<QDialog> dialog = pDialog;
|
|
|
|
if (auto* pLabel = dialog->findChild<QLabel*>(qsl("label"))) {
|
|
pLabel->setText(message);
|
|
}
|
|
dialog->hide(); // Must hide to change WindowModality
|
|
dialog->setWindowTitle(title);
|
|
dialog->setWindowModality(Qt::ApplicationModal);
|
|
dialog->show();
|
|
QCoreApplication::processEvents();
|
|
if (!dialog) {
|
|
return;
|
|
}
|
|
dialog->raise();
|
|
dialog->repaint(); // Force a redraw
|
|
QCoreApplication::processEvents(); // Try to ensure we are on top of any other dialogs and freshly drawn
|
|
}
|
|
|
|
void TMainConsole::closeUnpackingProgress()
|
|
{
|
|
if (mpUnpackingDialog) {
|
|
mpUnpackingDialog->deleteLater();
|
|
mpUnpackingDialog = nullptr;
|
|
}
|
|
}
|
|
|
|
void TMainConsole::setupVideoOutput(TMediaPlayer* player, bool& setupSucceeded)
|
|
{
|
|
setupSucceeded = false;
|
|
|
|
if (!player || !player->mediaPlayer()) {
|
|
return;
|
|
}
|
|
|
|
const QString target = player->mediaData().mediaKey();
|
|
|
|
if (target.isEmpty()) {
|
|
qWarning() << qsl("TMainConsole::setupVideoOutput() ERROR - 'key' not specified for video.");
|
|
return;
|
|
}
|
|
|
|
QString widgetType = TMediaData::MediaWidgetLabel;
|
|
QWidget* targetWidget = mLabelMap.value(target);
|
|
|
|
if (!targetWidget) {
|
|
targetWidget = mSubConsoleMap.value(target);
|
|
if (targetWidget) {
|
|
widgetType = TMediaData::MediaWidgetWindow;
|
|
}
|
|
}
|
|
|
|
if (!targetWidget) {
|
|
qWarning() << qsl("TMainConsole::setupVideoOutput() ERROR - No matching widget for 'key' = %1 to present video.").arg(target);
|
|
return;
|
|
}
|
|
|
|
player->mediaData().setMediaWidget(widgetType);
|
|
|
|
QVideoWidget* myVideoWidget = nullptr;
|
|
if (widgetType == TMediaData::MediaWidgetLabel) {
|
|
myVideoWidget = qobject_cast<TLabel*>(targetWidget)->mpVideoWidget;
|
|
} else if (widgetType == TMediaData::MediaWidgetWindow) {
|
|
myVideoWidget = qobject_cast<TConsole*>(targetWidget)->mpVideoWidget;
|
|
}
|
|
|
|
if (!myVideoWidget) {
|
|
myVideoWidget = new QVideoWidget();
|
|
myVideoWidget->setParent(targetWidget);
|
|
myVideoWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
|
|
|
if (widgetType == TMediaData::MediaWidgetLabel) {
|
|
QObject::connect(qobject_cast<TLabel*>(targetWidget), &TLabel::resized, myVideoWidget, [targetWidget, myVideoWidget]() {
|
|
myVideoWidget->resize(targetWidget->size());
|
|
});
|
|
} else if (widgetType == TMediaData::MediaWidgetWindow) {
|
|
QObject::connect(qobject_cast<TConsole*>(targetWidget), &TConsole::resized, myVideoWidget, [targetWidget, myVideoWidget]() {
|
|
myVideoWidget->resize(targetWidget->size());
|
|
});
|
|
}
|
|
}
|
|
|
|
if (targetWidget->isHidden()) {
|
|
targetWidget->show();
|
|
}
|
|
|
|
myVideoWidget->resize(targetWidget->size());
|
|
player->mediaPlayer()->setVideoOutput(myVideoWidget);
|
|
myVideoWidget->show();
|
|
|
|
setupSucceeded = true;
|
|
}
|
|
|
|
void TMainConsole::hideVideoOutput(TMediaPlayer* player)
|
|
{
|
|
if (!player || !player->mediaPlayer()) {
|
|
return;
|
|
}
|
|
|
|
auto* videoOutput = qobject_cast<QVideoWidget*>(player->mediaPlayer()->videoOutput());
|
|
|
|
if (!videoOutput) {
|
|
return;
|
|
}
|
|
|
|
QWidget* parent = videoOutput->parentWidget();
|
|
|
|
if (parent && parent->isVisible()) {
|
|
parent->hide();
|
|
}
|
|
}
|
|
|
|
void TMainConsole::showStatistics()
|
|
{
|
|
auto pHost = getHost();
|
|
if (!pHost) {
|
|
return;
|
|
}
|
|
|
|
const QString header = qsl("%1\n").arg(tr("+--------------------------------------------------------------+\n"
|
|
"| system statistics |\n"
|
|
"+--------------------------------------------------------------+",
|
|
"Header for the system's statistics information displayed in the console, it is 64 'narrow' characters wide"));
|
|
print(header, QColor(150, 120, 0), Qt::black);
|
|
|
|
QStringList subjects;
|
|
QStringList tables;
|
|
if (pHost->mTelnet.isGMCPEnabled()) {
|
|
//: Heading for the system's statistics information displayed in the console
|
|
subjects << tr("GMCP events:");
|
|
tables << QLatin1String("gmcp");
|
|
}
|
|
if (pHost->mTelnet.isATCPEnabled()) {
|
|
//: Heading for the system's statistics information displayed in the console
|
|
subjects << tr("ATCP events:");
|
|
tables << QLatin1String("atcp");
|
|
}
|
|
if (pHost->mTelnet.isChannel102Enabled()) {
|
|
//: Heading for the system's statistics information displayed in the console
|
|
subjects << tr("Channel102 events:");
|
|
tables << QLatin1String("channel102");
|
|
}
|
|
if (pHost->mTelnet.isMXPEnabled()) {
|
|
//: Heading for the system's statistics information displayed in the console
|
|
subjects << tr("MXP events:");
|
|
tables << QLatin1String("mxp");
|
|
}
|
|
if (pHost->mTelnet.isMSSPEnabled()) {
|
|
//: Heading for the system's statistics information displayed in the console
|
|
subjects << tr("MSSP events:");
|
|
tables << QLatin1String("mssp");
|
|
}
|
|
if (pHost->mTelnet.isMSDPEnabled()) {
|
|
// This might be a nil rather than an empty table if not present:
|
|
//: Heading for the system's statistics information displayed in the console
|
|
subjects << tr("MSDP events:");
|
|
tables << QLatin1String("msdp");
|
|
}
|
|
|
|
Q_ASSERT_X(subjects.count() == tables.count(), "TMainConsole::showStatistics()", "mismatch in titles and built-in tables to show");
|
|
for (int i = 0, total = subjects.count(); i < total; ++i) {
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(
|
|
qsl("setFgColor(190,150,0); setUnderline(true); echo([[\n\n%1\n]]);setUnderline(false);setFgColor(150,120,0);display( %2 );").arg(subjects.at(i), tables.at(i)));
|
|
}
|
|
|
|
const QString itemScript = "setFgColor(190,150,0); setUnderline(true); echo([[\n\n%1\n]]); setBold(false);setUnderline(false);setFgColor(150,120,0)";
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Telnet Options:")));
|
|
print(pHost->mTelnet.assembleTelnetOptionsReport(), QColor(150, 120, 0), Qt::black);
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Trigger Report:")));
|
|
QString itemMsg = std::get<0>(mpHost->getTriggerUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Timer Report:")));
|
|
itemMsg = std::get<0>(mpHost->getTimerUnit()->assembleReport());
|
|
;
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Alias Report:")));
|
|
itemMsg = std::get<0>(mpHost->getAliasUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Keybinding Report:")));
|
|
itemMsg = std::get<0>(mpHost->getKeyUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Script Report:")));
|
|
itemMsg = std::get<0>(mpHost->getScriptUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
//: Heading for the system's statistics information displayed in the console
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Gif Report:")));
|
|
itemMsg = std::get<0>(mpHost->getGifTracker()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
// Footer for the system's statistics information displayed in the console, it should be 64 'narrow' characters wide
|
|
const QString footer = qsl("\n+--------------------------------------------------------------+\n");
|
|
mpHost->mpConsole->print(footer, QColor(150, 120, 0), Qt::black);
|
|
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(QLatin1String("resetFormat();"));
|
|
|
|
mpHost->mpConsole->raise();
|
|
}
|
|
|
|
void TMainConsole::closeEvent(QCloseEvent* event)
|
|
{
|
|
// Guard against duplicate close events during widget destruction.
|
|
// The first close comes from explicit close(), the second can come
|
|
// from Qt during widget deletion. Processing the event twice can
|
|
// cause crashes in TBuffer destruction.
|
|
if (mEnableClose) {
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
qDebug().nospace().noquote() << "TMainConsole::closeEvent(...) INFO - received by \"" << mpHost->getName() << "\".";
|
|
TEvent conCloseEvent{};
|
|
conCloseEvent.mArgumentList.append(qsl("sysExitEvent"));
|
|
conCloseEvent.mArgumentTypeList.append(ARGUMENT_TYPE_STRING);
|
|
mpHost->raiseEvent(conCloseEvent);
|
|
|
|
if (mpHost->mFORCE_SAVE_ON_EXIT || mpHost->isClosingForced()) {
|
|
mudlet::self()->saveWindowLayout();
|
|
mpHost->modulesToWrite.clear();
|
|
// We are not checking the status result from here!
|
|
mpHost->saveProfile();
|
|
|
|
if (mpHost->mpMap && mpHost->mpMap->mpRoomDB) {
|
|
// There is a map loaded - but it *could* have no rooms at all!
|
|
if (!saveMap(QString())) {
|
|
qWarning() << "TMainConsole::closeEvent(...) WARNING - forced close map save failed";
|
|
}
|
|
}
|
|
mpHost->waitForProfileSave();
|
|
mEnableClose = true;
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
if (!mEnableClose) {
|
|
ASK_PROFILE:
|
|
const int choice = QMessageBox::question(this, tr("Save profile?"), tr("Do you want to save the profile %1?").arg(mProfileName), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
|
if (choice == QMessageBox::Cancel) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if (choice == QMessageBox::Yes) {
|
|
mudlet::self()->saveWindowLayout();
|
|
|
|
mpHost->modulesToWrite.clear();
|
|
auto [ok, filename, error] = mpHost->saveProfile();
|
|
|
|
if (!ok) {
|
|
QMessageBox::critical(
|
|
this, tr("Could not save profile"), tr("Sorry, could not save your profile as \"%1\" - got the following error: \"%2\".").arg(filename, error), QMessageBox::Retry);
|
|
goto ASK_PROFILE;
|
|
}
|
|
|
|
if (mpHost->mpMap && mpHost->mpMap->mpRoomDB) {
|
|
// There is a map loaded - but it *could* have no rooms at all!
|
|
ASK_MAP:
|
|
if (!saveMap(QString())) {
|
|
const int mapChoice = QMessageBox::warning(this,
|
|
tr("Could not save map"),
|
|
tr("Sorry, could not save the map. Would you like to retry or close without saving the map?"),
|
|
QMessageBox::Retry | QMessageBox::Ignore | QMessageBox::Cancel);
|
|
if (mapChoice == QMessageBox::Retry) {
|
|
goto ASK_MAP;
|
|
}
|
|
if (mapChoice == QMessageBox::Cancel) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
// QMessageBox::Ignore - continue without saving map
|
|
}
|
|
}
|
|
|
|
mpHost->waitForProfileSave();
|
|
mEnableClose = true;
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
if (choice == QMessageBox::No) {
|
|
mudlet::self()->saveWindowLayout();
|
|
mEnableClose = true;
|
|
event->accept();
|
|
return;
|
|
}
|
|
|
|
if (!mudlet::self()->isGoingDown()) {
|
|
QMessageBox::warning(this, "Aborting exit", "Session exit aborted on user request.");
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
mEnableClose = true;
|
|
event->accept();
|
|
}
|
|
}
|
|
|
|
bool TMainConsole::clear(const QString& name)
|
|
{
|
|
if (name.isEmpty() || !name.compare(QLatin1String("main"))) {
|
|
TConsole::clear();
|
|
mUpperPane->showNewLines();
|
|
mUpperPane->forceUpdate();
|
|
mLowerPane->forceUpdate();
|
|
return true;
|
|
}
|
|
|
|
auto pC = mSubConsoleMap.value(name);
|
|
if (pC) {
|
|
pC->clear();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|