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
Improve names of variables that are too short and not meaningful
#### Motivation for adding to Mudlet
So developers have an easier time working with Mudlets code - it's
crucial to be able to attract and retain folks working on the project
for its success!
#### Other info (issues closed, discussion etc)
1512 lines
56 KiB
C++
1512 lines
56 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 "TLabel.h"
|
|
#include "TMap.h"
|
|
#include "TRoomDB.h"
|
|
#include "TSplitter.h"
|
|
#include "TTextEdit.h"
|
|
#include "dlgMapper.h"
|
|
#include "mudlet.h"
|
|
|
|
#include "pre_guard.h"
|
|
#include <QLineEdit>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QScrollBar>
|
|
#include <QShortcut>
|
|
#include <QTextBoundaryFinder>
|
|
#include <QTextCodec>
|
|
#include <QTextStream>
|
|
#include <QPainter>
|
|
#include "post_guard.h"
|
|
|
|
|
|
TMainConsole::TMainConsole(Host* pH, QWidget* parent)
|
|
: TConsole(pH, TConsole::MainConsole, parent)
|
|
, mClipboard(pH)
|
|
{
|
|
// 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()
|
|
{
|
|
if (mpHunspell_system) {
|
|
Hunspell_destroy(mpHunspell_system);
|
|
mpHunspell_system = nullptr;
|
|
}
|
|
if (mpHunspell_profile) {
|
|
Hunspell_destroy(mpHunspell_profile);
|
|
mpHunspell_profile = nullptr;
|
|
// Need to commit any changes to personal dictionary
|
|
qDebug() << "TCommandLine::~TConsole(...) INFO - Saving profile's own Hunspell dictionary...";
|
|
mudlet::self()->saveDictionary(mudlet::self()->getMudletPath(mudlet::profileDataItemPath, mProfileName, qsl("profile")), mWordSet_profile);
|
|
}
|
|
}
|
|
|
|
void TMainConsole::setLabelStyleSheet(std::string& buf, std::string& stylesheet)
|
|
{
|
|
QString key{buf.c_str()};
|
|
QString sheet{stylesheet.c_str()};
|
|
if (mLabelMap.find(key) != mLabelMap.end()) {
|
|
QLabel* pC = mLabelMap[key];
|
|
if (!pC) {
|
|
return;
|
|
}
|
|
pC->setStyleSheet(sheet);
|
|
return;
|
|
}
|
|
}
|
|
|
|
std::optional<QString> TMainConsole::getLabelStyleSheet(const QString& name) const
|
|
{
|
|
QMap<QString, TLabel*>::const_iterator 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 it = mLabelMap.constFind(name);
|
|
if (it != mLabelMap.cend() && it.key() == name) {
|
|
return it.value()->sizeHint();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
// 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::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)};
|
|
}
|
|
|
|
void TMainConsole::toggleLogging(bool isMessageEnabled)
|
|
{
|
|
const auto loggingPath = mudlet::getMudletPath(mudlet::profileDataItemPath, mpHost->getName(), qsl("autolog"));
|
|
QFile file(loggingPath);
|
|
QDateTime logDateTime = QDateTime::currentDateTime();
|
|
if (!mLogToLogFile) {
|
|
file.open(QIODevice::WriteOnly | QIODevice::Text);
|
|
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(mudlet::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
|
|
logFileName = tr("logfile", "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 {2 of 2}).");
|
|
} 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...
|
|
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) {
|
|
mLogFile.open(QIODevice::ReadWrite);
|
|
} else {
|
|
mLogFile.open(QIODevice::Append);
|
|
}
|
|
mLogStream.setDevice(&mLogFile);
|
|
// We have to set a codec here to convert the QString based QTextStream
|
|
// encoding (from UTF-16) to UTF-8 - by default a local 8-Bit one would
|
|
// be used, which is problematic on Windows for non-ASCII (or Latin1?)
|
|
// characters. The default in Qt6 is UTF-8:
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
|
out.setCodec(QTextCodec::codecForName("UTF-8"));
|
|
#endif
|
|
if (isMessageEnabled) {
|
|
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) {
|
|
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, APP_BUILD) << "'>\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";
|
|
logStream << " </style>\n";
|
|
logStream << " </head>\n";
|
|
bool isAtBody = false;
|
|
bool foundBody = false;
|
|
while (!mLogStream.atEnd()) {
|
|
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")
|
|
.arg(logDateTime.toString(tr("'Log session starting at 'hh:mm:ss' on 'dddd', 'd' 'MMMM' 'yyyy'.",
|
|
"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")));
|
|
// <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")
|
|
.arg(logDateTime.toString(tr("'Log session starting at 'hh:mm:ss' on 'dddd', 'd' 'MMMM' 'yyyy'.",
|
|
"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")));
|
|
|
|
}
|
|
logButton->setToolTip(utils::richText(tr("Stop logging game output to log file.")));
|
|
} else {
|
|
// Logging is being turned off
|
|
buffer.logRemainingOutput();
|
|
QString endDateTimeLine = logDateTime.toString(tr("'Log session ending at 'hh:mm:ss' on 'dddd', 'd' 'MMMM' 'yyyy'.",
|
|
"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"));
|
|
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)
|
|
{
|
|
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(std::string& buf)
|
|
{
|
|
QString key = buf.c_str();
|
|
if (key.isEmpty() || key == QLatin1String("main")) {
|
|
return TConsole::getFgColor();
|
|
}
|
|
auto pC = mSubConsoleMap.value(key);
|
|
if (pC) {
|
|
return pC->getFgColor();
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::list<int> TMainConsole::getBgColor(std::string& buf)
|
|
{
|
|
QString key = buf.c_str();
|
|
if (key.isEmpty() || key == QLatin1String("main")) {
|
|
return TConsole::getBgColor();
|
|
}
|
|
auto pC = mSubConsoleMap.value(key);
|
|
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(std::string& buf, int line)
|
|
{
|
|
QString key = buf.c_str();
|
|
if (key.isEmpty() || key == QLatin1String("main")) {
|
|
TConsole::luaWrapLine(line);
|
|
return;
|
|
}
|
|
auto pC = mSubConsoleMap.value(key);
|
|
if (pC) {
|
|
pC->luaWrapLine(line);
|
|
}
|
|
}
|
|
|
|
QString TMainConsole::getCurrentLine(std::string& buf)
|
|
{
|
|
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, Buffer);
|
|
mSubConsoleMap[name] = pC;
|
|
pC->mConsoleName = name;
|
|
pC->setContentsMargins(0, 0, 0, 0);
|
|
pC->hide();
|
|
pC->layerCommandLine->hide();
|
|
return pC;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
void TMainConsole::resetMainConsole()
|
|
{
|
|
//resetProfile should reset also UserWindows
|
|
QMutableMapIterator<QString, TDockWidget*> itDockWidget(mDockWidgetMap);
|
|
while (itDockWidget.hasNext()) {
|
|
itDockWidget.next();
|
|
itDockWidget.value()->close();
|
|
itDockWidget.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TCommandLine*> itCommandLine(mSubCommandLineMap);
|
|
while (itCommandLine.hasNext()) {
|
|
itCommandLine.next();
|
|
itCommandLine.value()->deleteLater();
|
|
itCommandLine.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TConsole*> itSubConsole(mSubConsoleMap);
|
|
while (itSubConsole.hasNext()) {
|
|
itSubConsole.next();
|
|
// CHECK: Do we need to handle the float/dockable widgets here:
|
|
itSubConsole.value()->close();
|
|
itSubConsole.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TLabel*> itLabel(mLabelMap);
|
|
while (itLabel.hasNext()) {
|
|
itLabel.next();
|
|
itLabel.value()->close();
|
|
itLabel.remove();
|
|
}
|
|
|
|
QMutableMapIterator<QString, TScrollBox*> itScrollBox(mScrollBoxMap);
|
|
while (itScrollBox.hasNext()) {
|
|
itScrollBox.next();
|
|
itScrollBox.value()->close();
|
|
itScrollBox.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, SubConsole, pS->widget());
|
|
} else if (pW) {
|
|
pC = new TConsole(mpHost, SubConsole, pW->widget());
|
|
} else {
|
|
pC = new TConsole(mpHost, SubConsole, mpMainFrame);
|
|
}
|
|
if (!pC) {
|
|
return nullptr;
|
|
}
|
|
mSubConsoleMap[name] = pC;
|
|
pC->setObjectName(name);
|
|
pC->mConsoleName = 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;
|
|
} else {
|
|
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) {
|
|
// 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::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) {
|
|
QPixmap cursor_pixmap = QPixmap(pixMapLocation);
|
|
if (cursor_pixmap.isNull()) {
|
|
return {false, qsl("couldn't find custom cursor, is the location \"%1\" correct?").arg(pixMapLocation)};
|
|
}
|
|
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 = mpHost->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;
|
|
qDebug() << "TConsole::createMapper() - restore map case 2.";
|
|
mpHost->mpMap->pushErrorMessagesToFile(tr("Pre-Map loading(2) report"), true);
|
|
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_WIN32)
|
|
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")};
|
|
}
|
|
|
|
bool TMainConsole::setBackgroundImage(const QString& name, const QString& path)
|
|
{
|
|
auto pL = mLabelMap.value(name);
|
|
if (pL) {
|
|
QPixmap bgPixmap(path);
|
|
pL->setPixmap(bgPixmap);
|
|
return true;
|
|
} else {
|
|
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;
|
|
} else if (pL) {
|
|
QPalette mainPalette;
|
|
mainPalette.setColor(QPalette::Window, QColor(r, g, b, alpha));
|
|
pL->setPalette(mainPalette);
|
|
return true;
|
|
} else {
|
|
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);
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
|
|
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;
|
|
}
|
|
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;
|
|
} else if (pL) {
|
|
pL->show();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool TMainConsole::hideWindow(const QString& name)
|
|
{
|
|
auto pC = mSubConsoleMap.value(name);
|
|
auto pL = mLabelMap.value(name);
|
|
if (pC) {
|
|
pC->hide();
|
|
return true;
|
|
} else if (pL) {
|
|
pL->hide();
|
|
return true;
|
|
} else {
|
|
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;
|
|
} else if (pL) {
|
|
pL->setText(text);
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//getUserWindowSize for resizing in Geyser
|
|
QSize TMainConsole::getUserWindowSize(const QString& windowname) const
|
|
{
|
|
auto pW = mDockWidgetMap.value(windowname);
|
|
if (pW){
|
|
QSize windowSize = pW->widget()->size();
|
|
QSize userWindowSize(windowSize.width(), windowSize.height());
|
|
return userWindowSize;
|
|
}
|
|
return getMainWindowSize();
|
|
}
|
|
|
|
QPair<bool, QString> TMainConsole::addWordToSet(const QString& word)
|
|
{
|
|
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)
|
|
{
|
|
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;
|
|
|
|
QString path = mudlet::getMudletPath(mudlet::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_WIN32)
|
|
// 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...";
|
|
mpHunspellCodec_system = QTextCodec::codecForName(mHunspellCodecName_system);
|
|
}
|
|
}
|
|
|
|
// 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(mudlet::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;
|
|
} else {
|
|
return mudlet::self()->getWordSet();
|
|
}
|
|
}
|
|
|
|
void TMainConsole::setProfileName(const QString& newName)
|
|
{
|
|
TConsole::setProfileName(newName);
|
|
|
|
for (auto pC : mSubConsoleMap) {
|
|
pC->setProfileName(newName);
|
|
}
|
|
}
|
|
|
|
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)};
|
|
}
|
|
|
|
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();
|
|
mTriggerEngineMode = true;
|
|
buffer.translateToPlainText(incomingSocketData, isFromServer);
|
|
mTriggerEngineMode = false;
|
|
|
|
// 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);
|
|
}
|
|
|
|
double processT = mProcessingTimer.elapsed() / 1000.0;
|
|
if (mpHost->mTelnet.mGA_Driver) {
|
|
mpLineEdit_networkLatency->setText(tr("N:%1 S:%2",
|
|
// intentional comment to separate arguments
|
|
"The first argument 'N' represents the 'N'etwork latency; the second 'S' the "
|
|
"'S'ystem (processing) time")
|
|
.arg(mpHost->mTelnet.networkLatencyTime, 0, 'f', 3)
|
|
.arg(processT, 0, 'f', 3));
|
|
} else {
|
|
mpLineEdit_networkLatency->setText(tr("<no GA> S:%1",
|
|
// intentional comment to separate arguments
|
|
"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...")
|
|
.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()
|
|
{
|
|
mUpperPane->showNewLines();
|
|
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)
|
|
{
|
|
QString filename_map = location.isEmpty() ?
|
|
mudlet::getMudletPath(mudlet::profileDateTimeStampedMapPathFileName, mProfileName, QDateTime::currentDateTime().toString(qsl("yyyy-MM-dd#HH-mm-ss"))) :
|
|
location;
|
|
|
|
QDir dir_map(mudlet::getMudletPath(mudlet::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();
|
|
}
|
|
|
|
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);
|
|
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->update();
|
|
|
|
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);
|
|
QDateTime now(QDateTime::currentDateTime());
|
|
|
|
bool result = false;
|
|
|
|
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(mudlet::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) {
|
|
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) {
|
|
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) {
|
|
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->update();
|
|
|
|
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;
|
|
}
|
|
|
|
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::showStatistics()
|
|
{
|
|
auto pHost = getHost();
|
|
if (!pHost) {
|
|
return;
|
|
}
|
|
|
|
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()) {
|
|
subjects << tr("GMCP events:", "Heading for the system's statistics information displayed in the console");
|
|
tables << QLatin1String("gmcp");
|
|
}
|
|
if (pHost->mTelnet.isATCPEnabled()) {
|
|
subjects << tr("ATCP events:", "Heading for the system's statistics information displayed in the console");
|
|
tables << QLatin1String("atcp");
|
|
}
|
|
if (pHost->mTelnet.isChannel102Enabled()) {
|
|
subjects << tr("Channel102 events:", "Heading for the system's statistics information displayed in the console");
|
|
tables << QLatin1String("channel102");
|
|
}
|
|
if (pHost->mTelnet.isMSSPEnabled()) {
|
|
subjects << tr("MSSP events:", "Heading for the system's statistics information displayed in the console");
|
|
tables << QLatin1String("mssp");
|
|
}
|
|
if (pHost->mTelnet.isMSDPEnabled()) {
|
|
// This might be a nil rather than an empty table if not present:
|
|
subjects << tr("MSDP events:", "Heading for the system's statistics information displayed in the console");
|
|
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)";
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Trigger Report:", "Heading for the system's statistics information displayed in the console")));
|
|
QString itemMsg = std::get<0>(mpHost->getTriggerUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Timer Report:", "Heading for the system's statistics information displayed in the console")));
|
|
itemMsg = std::get<0>(mpHost->getTimerUnit()->assembleReport());;
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Alias Report:", "Heading for the system's statistics information displayed in the console")));
|
|
itemMsg = std::get<0>(mpHost->getAliasUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Keybinding Report:", "Heading for the system's statistics information displayed in the console")));
|
|
itemMsg = std::get<0>(mpHost->getKeyUnit()->assembleReport());
|
|
print(itemMsg, QColor(150, 120, 0), Qt::black);
|
|
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(itemScript.arg(tr("Script Report:", "Heading for the system's statistics information displayed in the console")));
|
|
itemMsg = std::get<0>(mpHost->getScriptUnit()->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
|
|
QString footer = qsl("\n+--------------------------------------------------------------+\n");
|
|
mpHost->mpConsole->print(footer, QColor(150, 120, 0), Qt::black);
|
|
|
|
mpHost->mLuaInterpreter.compileAndExecuteScript(QLatin1String("resetFormat();"));
|
|
|
|
mpHost->mpConsole->raise();
|
|
}
|