mirror of
https://github.com/Mudlet/Mudlet
synced 2026-08-13 18:26:27 -04:00
#### Brief overview of PR changes/additions - Hardens how OSC 8 link payloads and link text are handled before they are run or displayed; link commands are no longer built by string-formatting remote text into Lua source. - Adds a per-profile setting (General → Game protocols) to turn OSC 8 hyperlinks off, which also reports `0` for every `OSC_HYPERLINKS*` NEW-ENVIRON variable and sends an INFO update if toggled mid-session. - Fixes `selected=` callbacks on `send:` links, which never fired, and keeps emoji and Persian/Arabic/Indic text intact in tooltips and menu labels. #### Motivation for adding to Mudlet Inspired by [conversation](https://discord.com/channels/279748146316312576/1416447642472284261/1535109010066251890) on the MUD Discord and updates to terminal emulators. OSC 8 sequences arrive from the game server — and often from another player whose say/tell text the server relays — so they have to be treated as untrusted input rather than as content the user chose to load. #### Other info (issues closed, discussion etc) New unit tests: `LuaLiteralTest` (28 cases, including an exhaustive sweep over the bracket alphabet, each evaluated in a real Lua 5.1 state) and `UntrustedTextTest` (26 cases covering emoji sequences, non-Latin shaping and the two sanitization policies). There is no automated NEW-ENVIRON coverage anywhere in the repo, so that path was verified manually against a live server instead. **Test case:** 1. `say !osc8-docs` — every documented feature still works. 2. Send a link whose command ends in `]`, e.g. `send:say [OOC]` — clicking sends the literal text (previously the click silently did nothing). 3. Settings → General → Game protocols → uncheck "Enable OSC 8 hyperlinks from the server" — links stop rendering and the server is told without a reconnect; re-check and they return. 4. Send a tooltip or menu label containing a multi-part emoji such as 👨🍳 — it renders normally, not as its component parts. --------- Signed-off-by: Michael Conley <sousesider@gmail.com>
2156 lines
90 KiB
C++
2156 lines
90 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2008-2013 by Heiko Koehn - KoehnHeiko@googlemail.com *
|
|
* Copyright (C) 2014 by Ahmed Charles - acharles@outlook.com *
|
|
* Copyright (C) 2016-2023 by Stephen Lyons - slysven@virginmedia.com *
|
|
* Copyright (C) 2016-2017 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 "XMLimport.h"
|
|
|
|
|
|
#include "dlgMapper.h"
|
|
#include "LuaInterface.h"
|
|
#include "CredentialManager.h"
|
|
#include "SecureStringUtils.h"
|
|
#include "TConsole.h"
|
|
#include "TMap.h"
|
|
#include "TRoomDB.h"
|
|
#include "TRoom.h"
|
|
#include "VarUnit.h"
|
|
#include "mudlet.h"
|
|
|
|
#include <QBuffer>
|
|
#include <QClipboard>
|
|
#include <QGuiApplication>
|
|
#include <QtMath>
|
|
#include <QVersionNumber>
|
|
|
|
#include <memory>
|
|
|
|
XMLimport::XMLimport(Host* pH)
|
|
: mpHost(pH)
|
|
{
|
|
}
|
|
|
|
std::pair<bool, QString> XMLimport::importPackage(QFile* pfile, QString packName, int moduleFlag, QString* pVersionString)
|
|
{
|
|
mPackageName = packName;
|
|
setDevice(pfile);
|
|
|
|
module = moduleFlag;
|
|
|
|
if (!packName.isEmpty()) {
|
|
mpKey = new TKey(nullptr, mpHost);
|
|
if (module) {
|
|
mpKey->mModuleMasterFolder = true;
|
|
mpKey->mModuleMember = true;
|
|
}
|
|
mpKey->mPackageName = mPackageName;
|
|
mpKey->setIsActive(true);
|
|
mpKey->setName(mPackageName);
|
|
mpKey->setIsFolder(true);
|
|
|
|
mpTrigger = new TTrigger(nullptr, mpHost);
|
|
if (module) {
|
|
mpTrigger->mModuleMasterFolder = true;
|
|
mpTrigger->mModuleMember = true;
|
|
}
|
|
mpTrigger->mPackageName = mPackageName;
|
|
mpTrigger->setIsActive(true);
|
|
mpTrigger->setName(mPackageName);
|
|
mpTrigger->setIsFolder(true);
|
|
|
|
mpTimer = new TTimer(nullptr, mpHost);
|
|
if (module) {
|
|
mpTimer->mModuleMasterFolder = true;
|
|
mpTimer->mModuleMember = true;
|
|
}
|
|
mpTimer->mPackageName = mPackageName;
|
|
mpTimer->setIsActive(true);
|
|
mpTimer->setName(mPackageName);
|
|
mpTimer->setIsFolder(true);
|
|
|
|
mpAlias = new TAlias(nullptr, mpHost);
|
|
if (module) {
|
|
mpAlias->mModuleMasterFolder = true;
|
|
mpAlias->mModuleMember = true;
|
|
}
|
|
mpAlias->mPackageName = mPackageName;
|
|
mpAlias->setIsActive(true);
|
|
mpAlias->setName(mPackageName);
|
|
mpAlias->setScript(QString());
|
|
mpAlias->setRegexCode(QString());
|
|
mpAlias->setIsFolder(true);
|
|
|
|
mpAction = new TAction(nullptr, mpHost);
|
|
if (module) {
|
|
mpAction->mModuleMasterFolder = true;
|
|
mpAction->mModuleMember = true;
|
|
}
|
|
mpAction->mPackageName = mPackageName;
|
|
mpAction->setIsActive(true);
|
|
mpAction->setName(mPackageName);
|
|
mpAction->setIsFolder(true);
|
|
|
|
mpScript = new TScript(nullptr, mpHost);
|
|
if (module) {
|
|
mpScript->mModuleMasterFolder = true;
|
|
mpScript->mModuleMember = true;
|
|
}
|
|
mpScript->mPackageName = mPackageName;
|
|
mpScript->setIsActive(true);
|
|
mpScript->setName(mPackageName);
|
|
mpScript->setIsFolder(true);
|
|
|
|
mpHost->getTriggerUnit()->registerTrigger(mpTrigger);
|
|
mpHost->getTimerUnit()->registerTimer(mpTimer);
|
|
mpHost->getAliasUnit()->registerAlias(mpAlias);
|
|
mpHost->getActionUnit()->registerAction(mpAction);
|
|
mpHost->getKeyUnit()->registerKey(mpKey);
|
|
mpHost->getScriptUnit()->registerScript(mpScript);
|
|
}
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isStartElement()) {
|
|
if (name() == qsl("MudletPackage")) {
|
|
QString versionString;
|
|
if (attributes().hasAttribute(qsl("version"))) {
|
|
versionString = attributes().value(qsl("version")).toString();
|
|
if (!versionString.isEmpty()) {
|
|
bool isOk = false;
|
|
const float versionNumber = versionString.toFloat(&isOk);
|
|
if (isOk) {
|
|
mVersionMajor = qFloor(versionNumber);
|
|
mVersionMinor = qRound(1000.0 * versionNumber) - (1000 * mVersionMajor);
|
|
}
|
|
if (pVersionString) {
|
|
*pVersionString = versionString;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mVersionMajor > 1
|
|
/*||(mVersionMajor==1&&mVersionMinor)*/) {
|
|
// Minor check is not currently relevant, just abort on 2.000f or more
|
|
|
|
const QString moanMsg = tr("[ ALERT ] - Sorry, the file being read:\n"
|
|
"\"%1\"\n"
|
|
"reports it has a version (%2) it must have come from a later Mudlet version,\n"
|
|
"and this one cannot read it, you need a newer Mudlet!")
|
|
.arg(pfile->fileName(), versionString);
|
|
mpHost->postMessage(moanMsg);
|
|
return {false, moanMsg};
|
|
}
|
|
|
|
readPackage();
|
|
} else if (name() == qsl("map")) {
|
|
if (!packName.isEmpty()) {
|
|
qWarning() << "XMLimport::importPackage(...) WARNING: ignoring unexpected <map> element"
|
|
" - map data should not be present in package XML files";
|
|
} else {
|
|
readMap();
|
|
mpHost->mpMap->audit();
|
|
if (mpHost->mpMap->mpMapper) {
|
|
mpHost->mpMap->mpMapper->mp2dMap->init();
|
|
mpHost->mpMap->mpMapper->updateAreaComboBox();
|
|
mpHost->mpMap->mpMapper->resetAreaComboBoxToPlayerRoomArea();
|
|
mpHost->mpMap->mpMapper->show();
|
|
}
|
|
}
|
|
} else {
|
|
qDebug().nospace() << "XMLimport::importPackage(...) ERROR: "
|
|
"unrecognised element with name: "
|
|
<< name().toString() << " and content: " << text().toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!packName.isEmpty()) {
|
|
if (!gotTrigger) {
|
|
mpHost->getTriggerUnit()->unregisterTrigger(mpTrigger);
|
|
delete mpTrigger;
|
|
}
|
|
|
|
if (gotTimer) { // packName is NOT empty for modules...!
|
|
mpTimer->setIsActive(true);
|
|
mpTimer->enableTimer(mpTimer->getID());
|
|
} else {
|
|
mpHost->getTimerUnit()->unregisterTimer(mpTimer);
|
|
delete mpTimer;
|
|
}
|
|
|
|
if (gotAlias) {
|
|
mpAlias->setIsActive(true);
|
|
} else {
|
|
mpHost->getAliasUnit()->unregisterAlias(mpAlias);
|
|
delete mpAlias;
|
|
}
|
|
|
|
if (gotAction) {
|
|
mpHost->getActionUnit()->updateAllToolbars();
|
|
} else {
|
|
mpHost->getActionUnit()->unregisterAction(mpAction);
|
|
delete mpAction;
|
|
}
|
|
|
|
if (!gotKey) {
|
|
mpHost->getKeyUnit()->unregisterKey(mpKey);
|
|
delete mpKey;
|
|
}
|
|
|
|
if (!gotScript) {
|
|
mpHost->getScriptUnit()->unregisterScript(mpScript);
|
|
delete mpScript;
|
|
}
|
|
}
|
|
|
|
return {!hasError(), errorString()};
|
|
}
|
|
|
|
// returns the type of item and ID of the first (root) element
|
|
std::pair<EditorViewType, int> XMLimport::importFromClipboard()
|
|
{
|
|
QString xml;
|
|
QClipboard* clipboard = QGuiApplication::clipboard();
|
|
std::pair<EditorViewType, int> result;
|
|
|
|
xml = clipboard->text(QClipboard::Clipboard);
|
|
|
|
QByteArray ba = xml.toUtf8();
|
|
QBuffer xmlBuffer(&ba);
|
|
|
|
setDevice(&xmlBuffer);
|
|
if (!xmlBuffer.open(QIODevice::ReadOnly)) {
|
|
qWarning() << "XMLimport::importFromClipboard() ERROR: failed to open XML buffer for reading";
|
|
return {EditorViewType::cmUnknownView, 0};
|
|
}
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isStartElement()) {
|
|
if (name() == qsl("MudletPackage")) {
|
|
result = readPackage();
|
|
} else {
|
|
qDebug() << "ERROR:name=" << name().toString() << "text:" << text().toString();
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void XMLimport::readVariable(TVar* pParent)
|
|
{
|
|
auto var = new TVar(pParent);
|
|
|
|
LuaInterface* lI = mpHost->getLuaInterface();
|
|
VarUnit* vu = lI->getVarUnit();
|
|
QString keyName, value;
|
|
int keyType = 0;
|
|
int valueType;
|
|
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
keyName = readElementText();
|
|
continue;
|
|
} else if (name() == qsl("value")) { // NOLINT(readability-else-after-return)
|
|
value = readElementText();
|
|
continue;
|
|
} else if (name() == qsl("keyType")) { // NOLINT(readability-else-after-return)
|
|
keyType = readElementText().toInt();
|
|
continue;
|
|
} else if (name() == qsl("valueType")) { // NOLINT(readability-else-after-return)
|
|
valueType = readElementText().toInt();
|
|
var->setName(keyName, keyType);
|
|
var->setValue(value, valueType);
|
|
vu->addSavedVar(var);
|
|
lI->setValue(var);
|
|
continue;
|
|
} else if (name() == qsl("VariableGroup") || name() == qsl("Variable")) { // NOLINT(readability-else-after-return)
|
|
readVariable(var);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
delete var;
|
|
}
|
|
|
|
void XMLimport::readHiddenVariables()
|
|
{
|
|
LuaInterface* lI = mpHost->getLuaInterface();
|
|
VarUnit* vu = lI->getVarUnit();
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
const QString var = readElementText();
|
|
vu->addHidden(var);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readVariablePackage()
|
|
{
|
|
LuaInterface* lI = mpHost->getLuaInterface();
|
|
VarUnit* vu = lI->getVarUnit();
|
|
|
|
mpVar = vu->getBase();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isStartElement()) {
|
|
if (name() == qsl("VariableGroup") || name() == qsl("Variable")) {
|
|
readVariable(mpVar);
|
|
} else if (name() == qsl("HiddenVariables")) {
|
|
readHiddenVariables();
|
|
} else {
|
|
readUnknownElement(qsl("VariablePackage"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readMap()
|
|
{
|
|
QMultiHash<int, int> tempAreaRoomsHash; // Keys: area id, Values: a room id in that area
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isStartElement()) {
|
|
if (name() == qsl("areas")) {
|
|
mpHost->mpMap->mpRoomDB->clearMapDB();
|
|
mpHost->mpMap->reportStringToProgressDialog(tr("Parsing area data..."));
|
|
mpHost->mpMap->reportProgressToProgressDialog(0, 3);
|
|
readAreas();
|
|
} else if (name() == qsl("rooms")) {
|
|
mpHost->mpMap->reportStringToProgressDialog(tr("Parsing room data..."));
|
|
mpHost->mpMap->reportProgressToProgressDialog(1, 3);
|
|
readRooms(tempAreaRoomsHash);
|
|
} else if (name() == qsl("environments")) {
|
|
mpHost->mpMap->reportStringToProgressDialog(tr("Parsing environment data..."));
|
|
mpHost->mpMap->reportProgressToProgressDialog(2, 3);
|
|
readEnvColors();
|
|
}
|
|
mpHost->mpMap->reportProgressToProgressDialog(3, 3);
|
|
}
|
|
}
|
|
|
|
mpHost->mpMap->reportStringToProgressDialog(tr("Assigning rooms to their areas..."));
|
|
const int roomTotal = tempAreaRoomsHash.count();
|
|
int currentRoomCount = 0;
|
|
|
|
QListIterator<int> itAreaWithRooms(tempAreaRoomsHash.uniqueKeys());
|
|
while (itAreaWithRooms.hasNext()) {
|
|
const int areaId = itAreaWithRooms.next();
|
|
auto values = tempAreaRoomsHash.values(areaId);
|
|
QSet<int> const areaRoomsSet{values.begin(), values.end()};
|
|
|
|
if (!mpHost->mpMap->mpRoomDB->areas.contains(areaId)) {
|
|
// It is known for map files to have rooms with area Ids that are
|
|
// not in the listed areas - this cures that:
|
|
mpHost->mpMap->mpRoomDB->addArea(areaId);
|
|
}
|
|
|
|
mpHost->mpMap->mpRoomDB->setAreaRooms(areaId, areaRoomsSet);
|
|
currentRoomCount += areaRoomsSet.count();
|
|
mpHost->mpMap->reportProgressToProgressDialog(currentRoomCount, roomTotal);
|
|
}
|
|
}
|
|
|
|
void XMLimport::readEnvColors()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (name() == qsl("environment")) {
|
|
readEnvColor();
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readEnvColor()
|
|
{
|
|
const int id = attributes().value(qsl("id")).toString().toInt();
|
|
const int color = attributes().value(qsl("color")).toString().toInt();
|
|
|
|
mpHost->mpMap->mEnvColors[id] = color;
|
|
}
|
|
|
|
void XMLimport::readAreas()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (name() == qsl("areas")) {
|
|
break;
|
|
}
|
|
if (name() == qsl("area")) {
|
|
readArea();
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readArea()
|
|
{
|
|
if (attributes().hasAttribute(qsl("id"))) {
|
|
const int id = attributes().value(qsl("id")).toString().toInt();
|
|
const QString name = attributes().value(qsl("name")).toString();
|
|
|
|
mpHost->mpMap->mpRoomDB->addArea(id, name);
|
|
}
|
|
}
|
|
|
|
void XMLimport::readRooms(QMultiHash<int, int>& areaRoomsHash)
|
|
{
|
|
unsigned int roomCount = 0;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (Q_LIKELY(isStartElement())) {
|
|
if (Q_LIKELY(name() == qsl("room"))) {
|
|
readRoom(areaRoomsHash, &roomCount);
|
|
} else {
|
|
readUnknownMapElement();
|
|
}
|
|
} else if (isEndElement() && name() == qsl("rooms")) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readRoomFeatures(TRoom* pR)
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (Q_LIKELY(isStartElement())) {
|
|
if (name() == qsl("features")) {
|
|
continue;
|
|
}
|
|
if (Q_LIKELY(name() == qsl("feature"))) {
|
|
readRoomFeature(pR);
|
|
}
|
|
} else if (isEndElement() && name() == qsl("features")) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readRoomFeature(TRoom* pR)
|
|
{
|
|
if (Q_LIKELY(attributes().hasAttribute(qsl("type")))) {
|
|
pR->userData.insert(qsl("feature-%1").arg(attributes().value(qsl("type"))), qsl("true"));
|
|
}
|
|
}
|
|
|
|
// This is a CPU/Time hog without the non-default (true) third argument to
|
|
// TRoomDB::addRoom(...)
|
|
void XMLimport::readRoom(QMultiHash<int, int>& areamRoomMultiHash, unsigned int* roomCount)
|
|
{
|
|
auto pT = new TRoom(mpHost->mpMap->mpRoomDB.get());
|
|
|
|
pT->id = attributes().value(qsl("id")).toString().toInt();
|
|
pT->area = attributes().value(qsl("area")).toString().toInt();
|
|
pT->name = attributes().value(qsl("title")).toString();
|
|
pT->environment = attributes().value(qsl("environment")).toString().toInt();
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (Q_UNLIKELY(pT->id < 1)) {
|
|
continue; // Skip further tests on exits as we'd have to throw away
|
|
// this invalid room and it would mess up the
|
|
// entranceMultiHash
|
|
}
|
|
if (Q_LIKELY(name() == qsl("exit"))) {
|
|
QString dir = attributes().value(qsl("direction")).toString();
|
|
const int e = attributes().value(qsl("target")).toString().toInt();
|
|
// If there is a "hidden" exit mark it as a locked door, otherwise
|
|
// if there is a "door" mark it as an open/closed/locked door
|
|
// depending on the value (I.R.E. MUD maps always uses "1" for "door"
|
|
// and/or "hidden" - though the latter does not always appear with
|
|
// former):
|
|
const int door = (attributes().hasAttribute(qsl("hidden")) && attributes().value(qsl("hidden")).toString().toInt() == 1) ? 3
|
|
: (attributes().hasAttribute(qsl("door")) && attributes().value(qsl("door")).toString().toInt() >= 0 && attributes().value(qsl("door")).toString().toInt() <= 3)
|
|
? attributes().value(qsl("door")).toString().toInt()
|
|
: 0;
|
|
if (dir.isEmpty()) {
|
|
if (attributes().value(qsl("special")).toString().toInt() == 1 && !attributes().value(qsl("command")).toString().isEmpty()) {
|
|
// This is how IRE XML maps mark special exits, rather than
|
|
// by just using a different string for the direction!
|
|
dir = attributes().value(qsl("command")).toString();
|
|
pT->setSpecialExit(e, dir);
|
|
pT->setDoor(dir, door);
|
|
} else {
|
|
continue;
|
|
}
|
|
} else if (dir == qsl("north")) {
|
|
pT->north = e;
|
|
pT->setDoor(qsl("n"), door);
|
|
} else if (dir == qsl("east")) {
|
|
pT->east = e;
|
|
pT->setDoor(qsl("e"), door);
|
|
} else if (dir == qsl("south")) {
|
|
pT->south = e;
|
|
pT->setDoor(qsl("s"), door);
|
|
} else if (dir == qsl("west")) {
|
|
pT->west = e;
|
|
pT->setDoor(qsl("w"), door);
|
|
} else if (dir == qsl("up")) {
|
|
pT->up = e;
|
|
pT->setDoor(qsl("up"), door);
|
|
} else if (dir == qsl("down")) {
|
|
pT->down = e;
|
|
pT->setDoor(qsl("down"), door);
|
|
} else if (dir == qsl("northeast")) {
|
|
pT->northeast = e;
|
|
pT->setDoor(qsl("ne"), door);
|
|
} else if (dir == qsl("southwest")) {
|
|
pT->southwest = e;
|
|
pT->setDoor(qsl("sw"), door);
|
|
} else if (dir == qsl("southeast")) {
|
|
pT->southeast = e;
|
|
pT->setDoor(qsl("se"), door);
|
|
} else if (dir == qsl("northwest")) {
|
|
pT->northwest = e;
|
|
pT->setDoor(qsl("nw"), door);
|
|
} else if (dir == qsl("in")) {
|
|
pT->in = e;
|
|
pT->setDoor(qsl("in"), door);
|
|
} else if (dir == qsl("out")) {
|
|
pT->out = e;
|
|
pT->setDoor(qsl("out"), door);
|
|
}
|
|
} else if (name() == qsl("coord")) {
|
|
if (attributes().value(qsl("x")).toString().isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
pT->setCoordinates(attributes().value(qsl("x")).toString().toInt(), attributes().value(qsl("y")).toString().toInt(), attributes().value(qsl("z")).toString().toInt());
|
|
continue;
|
|
} else if (name() == qsl("features")) {
|
|
readRoomFeatures(pT);
|
|
} else if (Q_UNLIKELY(name().isEmpty())) {
|
|
continue;
|
|
}
|
|
|
|
if (isEndElement() && name() == qsl("room")) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pT->id > 0) {
|
|
if (++(*roomCount) % 100 == 0) {
|
|
mpHost->mpMap->reportStringToProgressDialog(tr("Parsing room data [count: %1]...").arg(*roomCount));
|
|
}
|
|
areamRoomMultiHash.insert(pT->area, pT->id);
|
|
// We are loading a map so can make some optimisation by setting the
|
|
// third argument as true:
|
|
mpHost->mpMap->mpRoomDB->addRoom(pT->id, pT, true);
|
|
mMaxRoomId = qMax(mMaxRoomId, pT->id); // Wasn't used but now maintains max Room Id
|
|
} else {
|
|
delete pT;
|
|
}
|
|
}
|
|
|
|
void XMLimport::readUnknownMapElement()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
readUnknownMapElement();
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns the type of item and ID of the first (root) element
|
|
std::pair<EditorViewType, int> XMLimport::readPackage()
|
|
{
|
|
EditorViewType objectType = EditorViewType::cmUnknownView;
|
|
int rootItemID = -1;
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("HostPackage")) {
|
|
readHostPackage();
|
|
} else if (name() == qsl("TriggerPackage")) {
|
|
objectType = EditorViewType::cmTriggerView;
|
|
rootItemID = readTriggerPackage();
|
|
} else if (name() == qsl("TimerPackage")) {
|
|
objectType = EditorViewType::cmTimerView;
|
|
rootItemID = readTimerPackage();
|
|
} else if (name() == qsl("AliasPackage")) {
|
|
objectType = EditorViewType::cmAliasView;
|
|
rootItemID = readAliasPackage();
|
|
} else if (name() == qsl("ActionPackage")) {
|
|
objectType = EditorViewType::cmActionView;
|
|
rootItemID = readActionPackage();
|
|
} else if (name() == qsl("ScriptPackage")) {
|
|
objectType = EditorViewType::cmScriptView;
|
|
rootItemID = readScriptPackage();
|
|
} else if (name() == qsl("KeyPackage")) {
|
|
objectType = EditorViewType::cmKeysView;
|
|
rootItemID = readKeyPackage();
|
|
} else if (name() == qsl("HelpPackage")) {
|
|
readHelpPackage();
|
|
} else if (name() == qsl("VariablePackage")) {
|
|
objectType = EditorViewType::cmVarsView;
|
|
readVariablePackage();
|
|
} else {
|
|
readUnknownElement(qsl("MudletPackage"));
|
|
}
|
|
}
|
|
}
|
|
return {objectType, rootItemID};
|
|
}
|
|
|
|
void XMLimport::readHelpPackage()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("helpURL")) {
|
|
const QString contents = readElementText();
|
|
mpHost->moduleHelp[mPackageName].insert("helpURL", contents);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Will be on a startElement on entry, and on the matching endElement
|
|
// at exit:
|
|
void XMLimport::readUnknownElement(const QString& what)
|
|
{
|
|
if (!atEnd()) {
|
|
qDebug().nospace().noquote() << "XMLimport::readUnknownElement(\"" << what << "\") ERROR - UNKNOWN Package Element name: \"" << name().toString() << "\".";
|
|
qDebug().nospace().noquote() << " This is at byte offset: " << characterOffset() << ", which is (line:column): " << lineNumber() << ":" << columnNumber() << ".";
|
|
#if !defined(QT_STRICT_ITERATORS)
|
|
if (attributes().isEmpty()) {
|
|
qDebug().nospace().noquote() << " It has no attributes.";
|
|
} else {
|
|
// This can fail if QT_STRICT_ITERATORS is defined.
|
|
// See https://bugreports.qt.io/browse/QTBUG-45368
|
|
QVectorIterator<QXmlStreamAttribute> itAttribute(attributes());
|
|
qDebug().nospace().noquote() << " It has the following attributes:";
|
|
while (itAttribute.hasNext()) {
|
|
const auto attribute = itAttribute.next();
|
|
qDebug().nospace().noquote() << " name: \"" << attribute.name() << "\", value: \"" << attribute.value() << "\".";
|
|
}
|
|
}
|
|
#endif
|
|
// The argument to readElementText(...) is required otherwise it stops
|
|
// if a child element is encountered, the third alternative
|
|
// "IncludeChildElements" is not so helpful as it might seem as it only
|
|
// includes some of the intervening content from sub-elements. As it is
|
|
// this should advance the current position to the EndElement of the
|
|
// unexpected startElement:
|
|
qDebug().nospace().noquote() << " The (text) content is: \"" << readElementText(QXmlStreamReader::SkipChildElements) << "\"";
|
|
}
|
|
}
|
|
|
|
void XMLimport::readHostPackage()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("Host")) {
|
|
readHost(mpHost);
|
|
} else {
|
|
readUnknownElement(qsl("HostPackage"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readHost(Host* pHost)
|
|
{
|
|
// This is an inline helper function to get a boolean value from a legacy attribute
|
|
// or return a default value. It also allows for inverting the result which is useful
|
|
// for attributes that have been negated in the past (e.g., mFORCE_MXP_NEGOTIATION_OFF
|
|
// which is now mEnableMXP, mFORCE_CHARSET_NEGOTIATION_OFF which is now mEnableCHARSET,
|
|
// and forceNewEnvironNegotiationOff which is now mEnableNEWENVIRON).
|
|
auto getBoolValueFromLegacyAttributeOrDefault = [&](const QString& legacyAttribute, const bool defaultsTo, bool invert = false) -> bool {
|
|
if (attributes().hasAttribute(legacyAttribute)) {
|
|
bool value = attributes().value(legacyAttribute) == YES;
|
|
return invert ? !value : value;
|
|
}
|
|
return defaultsTo;
|
|
};
|
|
|
|
auto setBoolAttributeWithDefault = [&](const QString& attribute, bool& target, const bool defaultsTo) {
|
|
target = attributes().hasAttribute(attribute) ? attributes().value(attribute) == YES : defaultsTo;
|
|
};
|
|
|
|
auto setBoolAttribute = [&](const QString& attribute, bool& target) {
|
|
target = attributes().value(attribute) == YES;
|
|
};
|
|
|
|
setBoolAttributeWithDefault(qsl("announceIncomingText"), pHost->mAnnounceIncomingText, true);
|
|
setBoolAttributeWithDefault(qsl("advertiseScreenReader"), pHost->mAdvertiseScreenReader, false);
|
|
setBoolAttributeWithDefault(qsl("enableOSC8Hyperlinks"), pHost->mEnableOSC8Hyperlinks, true);
|
|
setBoolAttributeWithDefault(qsl("enableClosedCaption"), pHost->mEnableClosedCaption, false);
|
|
setBoolAttributeWithDefault(qsl("mEnableMTTS"), pHost->mEnableMTTS, true);
|
|
setBoolAttributeWithDefault(qsl("mEnableMNES"), pHost->mEnableMNES, false);
|
|
setBoolAttributeWithDefault(qsl("mEnableMXP"), pHost->mEnableMXP, getBoolValueFromLegacyAttributeOrDefault(qsl("mFORCE_MXP_NEGOTIATION_OFF"), true, true));
|
|
setBoolAttributeWithDefault(qsl("mEnableNAWS"), pHost->mEnableNAWS, true);
|
|
setBoolAttributeWithDefault(qsl("mUndoServerWrap"), pHost->mUndoServerWrap, false);
|
|
setBoolAttributeWithDefault(qsl("mServerWrapHintShown"), pHost->mServerWrapHintShown, false);
|
|
setBoolAttributeWithDefault(qsl("mEnableCHARSET"), pHost->mEnableCHARSET, getBoolValueFromLegacyAttributeOrDefault(qsl("mFORCE_CHARSET_NEGOTIATION_OFF"), true, true));
|
|
setBoolAttributeWithDefault(qsl("mEnableNEWENVIRON"), pHost->mEnableNEWENVIRON, getBoolValueFromLegacyAttributeOrDefault(qsl("forceNewEnvironNegotiationOff"), true, true));
|
|
|
|
setBoolAttribute(qsl("autoClearCommandLineAfterSend"), pHost->mAutoClearCommandLineAfterSend);
|
|
setBoolAttributeWithDefault(qsl("disablePasswordMasking"), pHost->mDisablePasswordMasking, false);
|
|
|
|
// Handle command echo mode with backward compatibility
|
|
if (attributes().hasAttribute(qsl("commandEchoMode"))) {
|
|
// New tri-state attribute
|
|
int echoMode = attributes().value(qsl("commandEchoMode")).toInt();
|
|
pHost->mCommandEchoMode = static_cast<Host::CommandEchoMode>(qBound(0, echoMode, 2));
|
|
} else {
|
|
// Legacy boolean attribute - convert to new enum
|
|
bool legacyPrintCommand = getBoolValueFromLegacyAttributeOrDefault(qsl("printCommand"), true);
|
|
pHost->mCommandEchoMode = legacyPrintCommand ? Host::CommandEchoMode::ScriptControl : Host::CommandEchoMode::Never;
|
|
}
|
|
setBoolAttribute(qsl("mUSE_FORCE_LF_AFTER_PROMPT"), pHost->mUSE_FORCE_LF_AFTER_PROMPT);
|
|
setBoolAttribute(qsl("mUSE_UNIX_EOL"), pHost->mUSE_UNIX_EOL);
|
|
setBoolAttribute(qsl("runAllKeyMatches"), pHost->getKeyUnit()->mRunAllKeyMatches);
|
|
setBoolAttribute(qsl("mNoAntiAlias"), pHost->mNoAntiAlias);
|
|
setBoolAttribute(qsl("mEchoLuaErrors"), pHost->mEchoLuaErrors);
|
|
setBoolAttribute(qsl("mRawStreamDump"), pHost->mIsNextLogFileInHtmlFormat);
|
|
setBoolAttribute(qsl("mIsLoggingTimestamps"), pHost->mIsLoggingTimestamps);
|
|
setBoolAttribute(qsl("mAlertOnNewData"), pHost->mAlertOnNewData);
|
|
setBoolAttribute(qsl("mFORCE_NO_COMPRESSION"), pHost->mFORCE_NO_COMPRESSION);
|
|
setBoolAttribute(qsl("mFORCE_GA_OFF"), pHost->mFORCE_GA_OFF);
|
|
setBoolAttribute(qsl("mEnableGMCP"), pHost->mEnableGMCP);
|
|
setBoolAttribute(qsl("mEnableMSSP"), pHost->mEnableMSSP);
|
|
setBoolAttribute(qsl("mEnableMSDP"), pHost->mEnableMSDP);
|
|
setBoolAttribute(qsl("mEnableMSP"), pHost->mEnableMSP);
|
|
setBoolAttribute(qsl("mMapStrongHighlight"), pHost->mMapStrongHighlight);
|
|
setBoolAttribute(qsl("mEnableSpellCheck"), pHost->mEnableSpellCheck);
|
|
if (attributes().hasAttribute(QLatin1String("mShowInfo"))) {
|
|
// Old - pre Map Info versions of Mudlet (those before
|
|
// https://github.com/Mudlet/Mudlet/pull/4718) used the above
|
|
// setting to control the showing of what is now the "Full"
|
|
// map info display. So treat it as that to reproduce that
|
|
// behaviour:
|
|
if (attributes().value(qsl("mShowInfo")).toString() == YES) {
|
|
mpHost->mMapInfoContributors.insert(qsl("Full"));
|
|
}
|
|
}
|
|
setBoolAttribute(qsl("mAcceptServerGUI"), pHost->mAcceptServerGUI);
|
|
setBoolAttribute(qsl("mAcceptServerMedia"), pHost->mAcceptServerMedia);
|
|
setBoolAttribute(qsl("mMapperUseAntiAlias"), pHost->mMapperUseAntiAlias);
|
|
setBoolAttribute(qsl("mMapperShowGrid"), pHost->mMapperShowGrid);
|
|
setBoolAttribute(qsl("mEditorAutoComplete"), pHost->mEditorAutoComplete);
|
|
setBoolAttribute(qsl("mVersionInTTYPE"), pHost->mVersionInTTYPE);
|
|
setBoolAttribute(qsl("mPromptedForVersionInTTYPE"), pHost->mPromptedForVersionInTTYPE);
|
|
setBoolAttribute(qsl("mPromptedForMXPProcessorOn"), pHost->mPromptedForMXPProcessorOn);
|
|
setBoolAttribute(qsl("enableTextAnalyzer"), pHost->mEnableTextAnalyzer);
|
|
setBoolAttribute(qsl("mBubbleMode"), pHost->mBubbleMode);
|
|
setBoolAttribute(qsl("mMapViewOnly"), pHost->mMapViewOnly);
|
|
setBoolAttribute(qsl("mShowRoomIDs"), pHost->mShowRoomID);
|
|
setBoolAttribute(qsl("mShowPanel"), pHost->mShowPanel);
|
|
setBoolAttribute(qsl("mShow3DView"), pHost->mShow3DView);
|
|
setBoolAttribute(qsl("mHaveMapperScript"), pHost->mHaveMapperScript);
|
|
setBoolAttribute(qsl("mSslTsl"), pHost->mSslTsl);
|
|
setBoolAttribute(qsl("mSslIgnoreExpired"), pHost->mSslIgnoreExpired);
|
|
setBoolAttribute(qsl("mSslIgnoreSelfSigned"), pHost->mSslIgnoreSelfSigned);
|
|
setBoolAttribute(qsl("mSslIgnoreAll"), pHost->mSslIgnoreAll);
|
|
setBoolAttribute(qsl("mAskTlsAvailable"), pHost->mAskTlsAvailable);
|
|
setBoolAttribute(qsl("mUseProxy"), pHost->mUseProxy);
|
|
setBoolAttribute(qsl("f3SearchEnabled"), pHost->mF3SearchEnabled);
|
|
|
|
pHost->setForceMXPProcessorOn(attributes().value(qsl("mForceMXPProcessorOn")) == YES);
|
|
pHost->mProxyAddress = attributes().value(qsl("mProxyAddress")).toString();
|
|
|
|
if (attributes().hasAttribute(QLatin1String("mProxyPort"))) {
|
|
pHost->mProxyPort = attributes().value(qsl("mProxyPort")).toInt();
|
|
} else {
|
|
pHost->mProxyPort = 0;
|
|
}
|
|
|
|
pHost->mProxyUsername = attributes().value(qsl("mProxyUsername")).toString();
|
|
|
|
// Handle backward compatibility based on application version, not profile version
|
|
QString storedProxyPassword = attributes().value(qsl("mProxyPassword")).toString();
|
|
|
|
// For version 4.20.0+, use secure storage; for older versions, maintain plaintext in XML
|
|
// Use current application version for consistency with XMLexport behavior
|
|
const QString currentAppVersion = QString(APP_VERSION);
|
|
const QVersionNumber appVersion = QVersionNumber::fromString(currentAppVersion);
|
|
const QVersionNumber secureStorageVersion = QVersionNumber(4, 20, 0);
|
|
const bool useSecureStorage = appVersion >= secureStorageVersion;
|
|
|
|
if (!storedProxyPassword.isEmpty()) {
|
|
if (useSecureStorage) {
|
|
// Modern application: migrate plaintext password to secure storage and clear from XML
|
|
CredentialManager::storeCredential(pHost->getName(), "proxy", storedProxyPassword);
|
|
pHost->mProxyPassword = storedProxyPassword;
|
|
SecureStringUtils::secureStringClear(storedProxyPassword); // Clear after migration
|
|
} else {
|
|
// Legacy application: keep plaintext password for backward compatibility
|
|
pHost->mProxyPassword = storedProxyPassword;
|
|
}
|
|
} else if (useSecureStorage) {
|
|
// Modern application: load from secure storage if available
|
|
pHost->mProxyPassword = CredentialManager::retrieveCredential(pHost->getName(), "proxy");
|
|
}
|
|
|
|
pHost->set_USE_IRE_DRIVER_BUGFIX(attributes().value(qsl("USE_IRE_DRIVER_BUGFIX")) == YES);
|
|
pHost->mHighlightHistory = readDefaultTrueBool(qsl("HighlightHistory"));
|
|
pHost->mLogDir = attributes().value(qsl("logDirectory")).toString();
|
|
pHost->mFORCE_SAVE_ON_EXIT = readDefaultTrueBool(qsl("mFORCE_SAVE_ON_EXIT"));
|
|
const bool enableUserDictionary = attributes().value(qsl("mEnableUserDictionary")) == YES;
|
|
const bool useSharedDictionary = attributes().value(qsl("mUseSharedDictionary")) == YES;
|
|
pHost->setUserDictionaryOptions(enableUserDictionary, useSharedDictionary);
|
|
pHost->mMapperShowRoomBorders = readDefaultTrueBool(qsl("mMapperShowRoomBorders"));
|
|
pHost->mEditorTheme = attributes().value(QLatin1String("mEditorTheme")).toString();
|
|
pHost->mEditorThemeFile = attributes().value(QLatin1String("mEditorThemeFile")).toString();
|
|
if (pHost->mEditorTheme.isEmpty() || pHost->mEditorThemeFile.isEmpty()) {
|
|
pHost->mEditorTheme = qsl("Mudlet");
|
|
pHost->mEditorThemeFile = qsl("Mudlet.tmTheme");
|
|
}
|
|
pHost->mEditorThemeDark = attributes().value(QLatin1String("mEditorThemeDark")).toString();
|
|
pHost->mEditorThemeFileDark = attributes().value(QLatin1String("mEditorThemeFileDark")).toString();
|
|
pHost->mThemePreviewItemID = attributes().value(QLatin1String("mThemePreviewItemID")).toInt();
|
|
pHost->mThemePreviewType = attributes().value(QLatin1String("mThemePreviewType")).toString();
|
|
pHost->setHaveColorSpaceId(attributes().value(QLatin1String("mSGRCodeHasColSpaceId")).toString() == QLatin1String("yes"));
|
|
pHost->setMayRedefineColors(attributes().value(QLatin1String("mServerMayRedefineColors")).toString() == QLatin1String("yes"));
|
|
|
|
if (attributes().hasAttribute("AmbigousWidthGlyphsToBeWide")) {
|
|
const QStringView ambiguousWidthSetting(attributes().value(qsl("AmbigousWidthGlyphsToBeWide")));
|
|
|
|
if (ambiguousWidthSetting == YES) {
|
|
pHost->setWideAmbiguousEAsianGlyphs(Qt::Checked);
|
|
} else if (ambiguousWidthSetting == qsl("auto")) {
|
|
pHost->setWideAmbiguousEAsianGlyphs(Qt::PartiallyChecked);
|
|
} else {
|
|
pHost->setWideAmbiguousEAsianGlyphs(Qt::Unchecked);
|
|
}
|
|
} else {
|
|
// The encoding setting is stored as part of the profile details and NOT
|
|
// in the save file - probably because it is needed before the
|
|
// connection to the Server is initiated so it will already be in place
|
|
// which is just as well as it is needed for the automatic case...
|
|
pHost->setWideAmbiguousEAsianGlyphs(Qt::PartiallyChecked);
|
|
}
|
|
|
|
pHost->setEnableBlinkText(attributes().value(qsl("mEnableBlinkText")) == qsl("yes"));
|
|
|
|
if (attributes().hasAttribute("logFileNameFormat")) {
|
|
// We previously mixed "yyyy-MM-dd{#|T}hh-MM-ss" with "yyyy-MM-dd{#|T}HH-MM-ss"
|
|
// which is slightly different {always use 24-hour clock even if AM/PM is
|
|
// present (it isn't)} and that broke some code that requires an exact
|
|
// string to work with - now always change it to "HH":
|
|
pHost->mLogFileNameFormat = attributes().value(qsl("logFileNameFormat")).toString().replace(QLatin1String("hh"), QLatin1String("HH"), Qt::CaseSensitive);
|
|
pHost->mLogFileName = attributes().value(qsl("logFileName")).toString();
|
|
}
|
|
|
|
if (attributes().hasAttribute("mEditorShowBidi")) {
|
|
pHost->setEditorShowBidi(attributes().value(qsl("mEditorShowBidi")) == YES);
|
|
} else {
|
|
pHost->setEditorShowBidi(true);
|
|
}
|
|
|
|
if (attributes().hasAttribute("caretShortcut")) {
|
|
const QStringView caretShortcut(attributes().value(qsl("caretShortcut")));
|
|
if (caretShortcut == qsl("None")) {
|
|
pHost->mCaretShortcut = Host::CaretShortcut::None;
|
|
} else if (caretShortcut == qsl("Tab")) {
|
|
pHost->mCaretShortcut = Host::CaretShortcut::Tab;
|
|
} else if (caretShortcut == qsl("CtrlTab")) {
|
|
pHost->mCaretShortcut = Host::CaretShortcut::CtrlTab;
|
|
} else if (caretShortcut == qsl("F6")) {
|
|
pHost->mCaretShortcut = Host::CaretShortcut::F6;
|
|
}
|
|
}
|
|
|
|
if (attributes().hasAttribute("blankLineBehaviour")) {
|
|
const QStringView blankLineBehaviour(attributes().value(qsl("blankLineBehaviour")));
|
|
if (blankLineBehaviour == qsl("Hide")) {
|
|
pHost->mBlankLineBehaviour = Host::BlankLineBehaviour::Hide;
|
|
} else if (blankLineBehaviour == qsl("Show")) {
|
|
pHost->mBlankLineBehaviour = Host::BlankLineBehaviour::Show;
|
|
} else if (blankLineBehaviour == qsl("ReplaceWithSpace")) {
|
|
pHost->mBlankLineBehaviour = Host::BlankLineBehaviour::ReplaceWithSpace;
|
|
}
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("mSearchEngineName"))) {
|
|
pHost->mSearchEngineName = attributes().value(QLatin1String("mSearchEngineName")).toString();
|
|
} else {
|
|
pHost->mSearchEngineName = QString("Google");
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("mTimerSupressionInterval"))) {
|
|
pHost->mTimerDebugOutputSuppressionInterval = QTime::fromString(attributes().value(QLatin1String("mTimerSupressionInterval")).toString(), QLatin1String("hh:mm:ss.zzz"));
|
|
} else {
|
|
pHost->mTimerDebugOutputSuppressionInterval = QTime();
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("mDiscordAccessFlags"))) {
|
|
pHost->mDiscordAccessFlags = static_cast<Host::DiscordOptionFlags>(attributes().value(qsl("mDiscordAccessFlags")).toString().toInt());
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("mDiscordMode"))) {
|
|
const int modeInt = attributes().value(qsl("mDiscordMode")).toString().toInt();
|
|
if (modeInt >= Host::DiscordDisabled && modeInt <= Host::DiscordShowGameDetails) {
|
|
pHost->mDiscordMode = static_cast<Host::DiscordMode>(modeInt);
|
|
}
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("mRequiredDiscordUserName"))) {
|
|
pHost->mRequiredDiscordUserName = attributes().value(QLatin1String("mRequiredDiscordUserName")).toString();
|
|
} else {
|
|
pHost->mRequiredDiscordUserName.clear();
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("playerRoomStyle"))) {
|
|
quint8 styleCode = 0;
|
|
quint8 outerDiameterPercentage = 0;
|
|
quint8 innerDiameterPercentage = 0;
|
|
QColor outerColor;
|
|
QColor innerColor;
|
|
// Retrieve current (possibly default) settings:
|
|
pHost->getPlayerRoomStyleDetails(styleCode, outerDiameterPercentage, innerDiameterPercentage, outerColor, innerColor);
|
|
// Gather values from file:
|
|
styleCode = static_cast<quint8>(qBound(0, attributes().value(QLatin1String("playerRoomStyle")).toInt(), 255));
|
|
outerDiameterPercentage = static_cast<quint8>(qBound(0, attributes().value(QLatin1String("playerRoomOuterDiameter")).toInt(), 255));
|
|
innerDiameterPercentage = static_cast<quint8>(qBound(0, attributes().value(QLatin1String("playerRoomInnerDiameter")).toInt(), 255));
|
|
outerColor = QColor::fromString(attributes().value(QLatin1String("playerRoomPrimaryColor")).toString());
|
|
innerColor = QColor::fromString(attributes().value(QLatin1String("playerRoomSecondaryColor")).toString());
|
|
// Store all the settings in the Host instance:
|
|
pHost->setPlayerRoomStyleDetails(styleCode, outerDiameterPercentage, innerDiameterPercentage, outerColor, innerColor);
|
|
|
|
if (pHost->mpMap) {
|
|
// And the TMap instance:
|
|
pHost->mpMap->mPlayerRoomStyle = styleCode;
|
|
pHost->mpMap->mPlayerRoomOuterDiameterPercentage = outerDiameterPercentage;
|
|
pHost->mpMap->mPlayerRoomInnerDiameterPercentage = innerDiameterPercentage;
|
|
pHost->mpMap->mPlayerRoomOuterColor = outerColor;
|
|
pHost->mpMap->mPlayerRoomInnerColor = innerColor;
|
|
}
|
|
}
|
|
|
|
pHost->mRoomSize = attributes().value(qsl("mRoomSize")).toString().toDouble();
|
|
|
|
if (qFuzzyCompare(1.0 + pHost->mRoomSize, 1.0)) {
|
|
// The value is a float/double and the prior code using "== 0" is a BAD
|
|
// THING to do with non-integer number types!
|
|
pHost->mRoomSize = 0.5; // Same value as is in Host class initializer list
|
|
}
|
|
|
|
pHost->mLineSize = attributes().value(qsl("mLineSize")).toString().toDouble();
|
|
|
|
if (qFuzzyCompare(1.0 + pHost->mLineSize, 1.0)) {
|
|
pHost->mLineSize = 10.0; // Same value as is in Host class initializer list
|
|
}
|
|
|
|
pHost->mRoomBorderSize = attributes().value(qsl("mRoomBorderSize")).toString().toDouble();
|
|
|
|
if (qFuzzyCompare(1.0 + pHost->mRoomBorderSize, 1.0)) {
|
|
// For old profiles without border size, use mLineSize to preserve
|
|
// the previous behavior where border and exit shared the same size
|
|
pHost->mRoomBorderSize = pHost->mLineSize;
|
|
}
|
|
|
|
pHost->mMapGridLineSize = attributes().value(qsl("mMapGridLineSize")).toString().toDouble();
|
|
|
|
if (qFuzzyCompare(1.0 + pHost->mMapGridLineSize, 1.0)) {
|
|
pHost->mMapGridLineSize = 0.5; // Same value as is in Host class initializer list
|
|
}
|
|
|
|
const QStringView ignore(attributes().value(qsl("mDoubleClickIgnore")));
|
|
|
|
for (auto character : ignore) {
|
|
pHost->mDoubleClickIgnore.insert(character);
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("EditorSearchOptions"))) {
|
|
pHost->setSearchOptions(static_cast<dlgTriggerEditor::SearchOptions>(attributes().value(qsl("EditorSearchOptions")).toInt()));
|
|
}
|
|
|
|
pHost->setDebugShowAllProblemCodepoints(attributes().value(qsl("DebugShowAllProblemCodepoints")) == YES);
|
|
|
|
const bool compactInputLine = attributes().value(QLatin1String("CompactInputLine")) == YES;
|
|
pHost->setCompactInputLine(compactInputLine);
|
|
|
|
if (mudlet::self()->mpCurrentActiveHost == pHost) {
|
|
mudlet::self()->dactionInputLine->setChecked(compactInputLine);
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("CommandLineHistorySaveSize"))) {
|
|
pHost->setCommandLineHistorySaveSize(attributes().value(QLatin1String("CommandLineHistorySaveSize")).toInt());
|
|
} else {
|
|
// This is the default value, though prior to the introduction of this
|
|
// it would have effectively been zero:
|
|
pHost->setCommandLineHistorySaveSize(500);
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("NetworkPacketTimeout"))) {
|
|
// These limits are also hard coded into the QSpinBox used to adjust
|
|
// this setting in the preferences:
|
|
pHost->mTelnet.setPostingTimeout(qBound(10, attributes().value(QLatin1String("NetworkPacketTimeout")).toInt(), 500));
|
|
} else {
|
|
// The default value, also used up to Mudlet 4.12.0:
|
|
pHost->mTelnet.setPostingTimeout(300);
|
|
}
|
|
|
|
if (attributes().hasAttribute(QLatin1String("ControlCharacterHandling"))) {
|
|
switch (attributes().value(QLatin1String("ControlCharacterHandling")).toInt()) {
|
|
case 1:
|
|
pHost->setControlCharacterMode(ControlCharacterMode::Picture);
|
|
break;
|
|
case 2:
|
|
pHost->setControlCharacterMode(ControlCharacterMode::OEM);
|
|
break;
|
|
case 0:
|
|
[[fallthrough]];
|
|
default:
|
|
pHost->setControlCharacterMode(ControlCharacterMode::AsIs);
|
|
}
|
|
} else {
|
|
// The default value, also used up to Mudlet 4.14.1:
|
|
pHost->setControlCharacterMode(ControlCharacterMode::AsIs);
|
|
}
|
|
|
|
if (attributes().hasAttribute(qsl("ShowIDsInEditor"))) {
|
|
pHost->setShowIdsInEditor(attributes().value(qsl("ShowIDsInEditor")) == YES);
|
|
} else {
|
|
// The default (and for profile files from before 4.18.0):
|
|
pHost->setShowIdsInEditor(false);
|
|
}
|
|
|
|
if (attributes().hasAttribute(qsl("Large2DMapAreaExitArrows"))) {
|
|
pHost->setLargeAreaExitArrows(attributes().value(qsl("Large2DMapAreaExitArrows")) == YES);
|
|
} else {
|
|
// The default (and for map/profile files from before 4.15.0):
|
|
pHost->setLargeAreaExitArrows(false);
|
|
}
|
|
|
|
QMargins borders;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
// Only read this detail into a backup location so that it can
|
|
// be imported without changing the main setting unless it is
|
|
// needed (intended for use when importing a profile but not
|
|
// otherwise). In fact this detail is normally stored outside of
|
|
// the game save in the profile base directory:
|
|
pHost->mBackupHostName = readElementText();
|
|
} else if (name() == qsl("mInstalledModules")) {
|
|
QMap<QString, QStringList> entry;
|
|
|
|
readModulesDetailsMap(entry);
|
|
|
|
QMapIterator<QString, QStringList> it(entry);
|
|
|
|
while (it.hasNext()) {
|
|
it.next();
|
|
QStringList moduleList;
|
|
const QStringList entryList = it.value();
|
|
moduleList << entryList.at(0);
|
|
moduleList << entryList.at(1);
|
|
pHost->mInstalledModules[it.key()] = moduleList;
|
|
pHost->mModulePriorities[it.key()] = entryList.at(2).toInt();
|
|
// Also add to active modules list to match runtime state
|
|
if (!pHost->mActiveModules.contains(it.key())) {
|
|
pHost->mActiveModules.append(it.key());
|
|
}
|
|
}
|
|
} else if (name() == qsl("mInstalledPackages")) {
|
|
readStringList(pHost->mInstalledPackages, qsl("Host"));
|
|
} else if (name() == qsl("url")) {
|
|
// Only read this detail into a backup location so that it can
|
|
// be imported without changing the main setting unless it is
|
|
// needed (intended for use when importing a profile but not
|
|
// otherwise). In fact this detail is normally stored outside of
|
|
// the game save in the profile base directory:
|
|
pHost->mBackupUrl = readElementText();
|
|
} else if (name() == qsl("serverPackageName")) {
|
|
pHost->mServerGUI_Package_name = readElementText();
|
|
} else if (name() == qsl("serverPackageVersion")) {
|
|
pHost->mServerGUI_Package_version = readElementText();
|
|
} else if (name() == qsl("port")) {
|
|
// Only read this detail into a backup location so that it can
|
|
// be imported without changing the main setting unless it is
|
|
// needed (intended for use when importing a profile but not
|
|
// otherwise). In fact this detail is normally stored outside of
|
|
// the game save in the profile base directory:
|
|
pHost->mBackupPort = readElementText().toInt();
|
|
} else if (readHostBorderElement(borders, name())) {
|
|
// Handled by helper
|
|
} else if (name() == qsl("commandLineMinimumHeight")) {
|
|
pHost->commandLineMinimumHeight = readElementText().toInt();
|
|
} else if (name() == qsl("wrapAt")) {
|
|
// toInt() yields 0 for anything unparseable, and a profile that
|
|
// wraps at zero columns can show no text at all
|
|
pHost->mWrapAt = qMax(1, readElementText().toInt());
|
|
} else if (name() == qsl("wrapIndentCount")) {
|
|
pHost->mWrapIndentCount = readElementText().toInt();
|
|
} else if (name() == qsl("wrapHangingIndentCount")) {
|
|
pHost->mWrapHangingIndentCount = readElementText().toInt();
|
|
} else if (name() == qsl("undoServerWrapWidth")) {
|
|
pHost->mUndoServerWrapWidth = qBound(20, readElementText().toInt(), 500);
|
|
} else if (name() == qsl("consoleBufferSize")) {
|
|
pHost->mConsoleBufferSize = readElementText().toInt();
|
|
} else if (name() == qsl("useMaxConsoleBufferSize")) {
|
|
pHost->mUseMaxConsoleBufferSize = (readElementText() == qsl("yes"));
|
|
} else if (name() == qsl("mCommandSeparator")) {
|
|
pHost->mCommandSeparator = readElementText();
|
|
} else if (readHostColorElement(pHost, name())) {
|
|
// Handled by helper
|
|
} else if (name() == qsl("mDisplayFont")) {
|
|
pHost->setDisplayFontFromString(readElementText());
|
|
#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD)
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
|
|
// On GNU/Linux and FreeBSD ensure that emojis are displayed in
|
|
// colour even if this font doesn't support it:
|
|
QFont::insertSubstitution(pHost->getDisplayFont().family(), qsl("Noto Color Emoji"));
|
|
#endif
|
|
// For Qt 6.9+, emoji font support is handled globally in FontManager::addEmojiFont()
|
|
#endif
|
|
} else if (name() == qsl("mCommandLineFont")) {
|
|
// We use the same font as the main console now so discard this
|
|
// one silently:
|
|
Q_UNUSED(readElementText())
|
|
} else if (name() == qsl("commandSeperator")) {
|
|
// Ignore this misspelled duplicate, it has been removed from
|
|
// the Xml format but will appear in older files and trip the
|
|
// QDebug() error reporting associated with the following
|
|
// readUnknownElement(...) for "anything not otherwise parsed"
|
|
Q_UNUSED(readElementText())
|
|
} else if (name() == qsl("mSpellDic")) {
|
|
pHost->setSpellDic(readElementText());
|
|
} else if (name() == qsl("mLineSize") || name() == qsl("mRoomSize")) {
|
|
// These two have been dropped from the Xml format as these are
|
|
// duplicates of attributes that were being incorrected read in
|
|
// the parent <Host ...> element as integers {they are stored as
|
|
// decimals but for the first one at least, it is a decimal
|
|
// number n, where 0.1 <= n <= 1.1 so was being read as "0" for
|
|
// all but the greatest 2 values where it was read as "1"!}
|
|
// We still check for them so that we avoid falling into the
|
|
// QDebug() error reporting associated with the following
|
|
// readUnknownElement(...) for "anything not otherwise parsed"
|
|
Q_UNUSED(readElementText())
|
|
} else if (name() == qsl("mMapInfoContributors")) {
|
|
readLegacyMapInfoContributors();
|
|
} else if (name() == qsl("mapInfoContributor")) {
|
|
readMapInfoContributor();
|
|
} else if (name() == qsl("profileShortcut")) {
|
|
readProfileShortcut();
|
|
} else if (name() == qsl("stopwatches")) {
|
|
readStopWatchMap();
|
|
} else if (name() == qsl("MMCP")) {
|
|
readMMCPOptions();
|
|
} else if (name() == qsl("experiment")) {
|
|
QString key = attributes().value(qsl("key")).toString();
|
|
bool enabled = attributes().value(qsl("enabled")) == YES;
|
|
if (enabled && !key.isEmpty()) {
|
|
mpHost->setExperimentEnabled(key, true);
|
|
}
|
|
readElementText(); // consume the element
|
|
} else {
|
|
readUnknownElement(qsl("Host"));
|
|
}
|
|
}
|
|
}
|
|
|
|
pHost->setUserBorders(borders);
|
|
pHost->loadPackageInfo();
|
|
}
|
|
|
|
bool XMLimport::readHostColorElement(Host* pHost, QStringView elementName)
|
|
{
|
|
// Simple colors (no alpha channel)
|
|
static const QHash<QString, QColor Host::*> simpleColors = {
|
|
{qsl("mCommandLineFgColor"), &Host::mCommandLineFgColor},
|
|
{qsl("mCommandLineBgColor"), &Host::mCommandLineBgColor},
|
|
{qsl("mFgColor"), &Host::mFgColor},
|
|
{qsl("mCommandFgColor"), &Host::mCommandFgColor},
|
|
{qsl("mCommandBgColor"), &Host::mCommandBgColor},
|
|
{qsl("mBlack"), &Host::mBlack},
|
|
{qsl("mLightBlack"), &Host::mLightBlack},
|
|
{qsl("mRed"), &Host::mRed},
|
|
{qsl("mLightRed"), &Host::mLightRed},
|
|
{qsl("mBlue"), &Host::mBlue},
|
|
{qsl("mLightBlue"), &Host::mLightBlue},
|
|
{qsl("mGreen"), &Host::mGreen},
|
|
{qsl("mLightGreen"), &Host::mLightGreen},
|
|
{qsl("mYellow"), &Host::mYellow},
|
|
{qsl("mLightYellow"), &Host::mLightYellow},
|
|
{qsl("mCyan"), &Host::mCyan},
|
|
{qsl("mLightCyan"), &Host::mLightCyan},
|
|
{qsl("mMagenta"), &Host::mMagenta},
|
|
{qsl("mLightMagenta"), &Host::mLightMagenta},
|
|
{qsl("mWhite"), &Host::mWhite},
|
|
{qsl("mLightWhite"), &Host::mLightWhite},
|
|
{qsl("mFgColor2"), &Host::mFgColor_2},
|
|
{qsl("mLowerLevelColor"), &Host::mLowerLevelColor},
|
|
{qsl("mUpperLevelColor"), &Host::mUpperLevelColor},
|
|
{qsl("mRoomBorderColor"), &Host::mRoomBorderColor},
|
|
{qsl("mRoomCollisionBorderColor"), &Host::mRoomCollisionBorderColor},
|
|
{qsl("mBlack2"), &Host::mBlack_2},
|
|
{qsl("mLightBlack2"), &Host::mLightBlack_2},
|
|
{qsl("mRed2"), &Host::mRed_2},
|
|
{qsl("mLightRed2"), &Host::mLightRed_2},
|
|
{qsl("mBlue2"), &Host::mBlue_2},
|
|
{qsl("mLightBlue2"), &Host::mLightBlue_2},
|
|
{qsl("mGreen2"), &Host::mGreen_2},
|
|
{qsl("mLightGreen2"), &Host::mLightGreen_2},
|
|
{qsl("mYellow2"), &Host::mYellow_2},
|
|
{qsl("mLightYellow2"), &Host::mLightYellow_2},
|
|
{qsl("mCyan2"), &Host::mCyan_2},
|
|
{qsl("mLightCyan2"), &Host::mLightCyan_2},
|
|
{qsl("mMagenta2"), &Host::mMagenta_2},
|
|
{qsl("mLightMagenta2"), &Host::mLightMagenta_2},
|
|
{qsl("mWhite2"), &Host::mWhite_2},
|
|
{qsl("mLightWhite2"), &Host::mLightWhite_2},
|
|
};
|
|
|
|
// Colors that support alpha channel
|
|
static const QHash<QString, QColor Host::*> alphaColors = {
|
|
{qsl("mBgColor"), &Host::mBgColor},
|
|
{qsl("mBgColor2"), &Host::mBgColor_2},
|
|
{qsl("mMapGridColor"), &Host::mMapGridColor},
|
|
{qsl("mMapInfoBg"), &Host::mMapInfoBg},
|
|
};
|
|
|
|
const QString elemName = elementName.toString();
|
|
|
|
if (auto it = simpleColors.find(elemName); it != simpleColors.end()) {
|
|
pHost->*it.value() = QColor::fromString(readElementText());
|
|
return true;
|
|
}
|
|
|
|
if (auto it = alphaColors.find(elemName); it != alphaColors.end()) {
|
|
const int alpha = attributes().hasAttribute(qsl("alpha")) ? attributes().value(qsl("alpha")).toInt() : 255;
|
|
pHost->*it.value() = QColor::fromString(readElementText());
|
|
(pHost->*it.value()).setAlpha(alpha);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool XMLimport::readHostBorderElement(QMargins& borders, QStringView elementName)
|
|
{
|
|
if (elementName == qsl("borderTopHeight")) {
|
|
borders.setTop(readElementText().toInt());
|
|
return true;
|
|
}
|
|
if (elementName == qsl("borderBottomHeight")) {
|
|
borders.setBottom(readElementText().toInt());
|
|
return true;
|
|
}
|
|
if (elementName == qsl("borderLeftWidth")) {
|
|
borders.setLeft(readElementText().toInt());
|
|
return true;
|
|
}
|
|
if (elementName == qsl("borderRightWidth")) {
|
|
borders.setRight(readElementText().toInt());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool XMLimport::readDefaultTrueBool(QString name)
|
|
{
|
|
return attributes().value(name) == YES || !attributes().hasAttribute(name);
|
|
}
|
|
|
|
// returns the ID of the root imported trigger/group
|
|
int XMLimport::readTriggerPackage()
|
|
{
|
|
int parentItemID = -1;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
|
|
if (isStartElement()) {
|
|
if (name() == qsl("TriggerGroup") || name() == qsl("Trigger")) {
|
|
gotTrigger = true;
|
|
parentItemID = readTrigger(mPackageName.isEmpty() ? nullptr : mpTrigger);
|
|
} else {
|
|
readUnknownElement(qsl("TriggerPackage"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return parentItemID;
|
|
}
|
|
|
|
// imports a trigger and returns its ID - in case of a group, returns the ID
|
|
// of the top-level trigger group.
|
|
int XMLimport::readTrigger(TTrigger* pParent)
|
|
{
|
|
auto pT = new TTrigger(pParent, mpHost);
|
|
|
|
if (module) {
|
|
pT->mModuleMember = true;
|
|
}
|
|
|
|
mpHost->getTriggerUnit()->registerTrigger(pT);
|
|
|
|
pT->setIsActive(attributes().value(qsl("isActive")) == YES);
|
|
pT->setIsFolder(attributes().value(qsl("isFolder")) == YES);
|
|
pT->setTemporary(attributes().value(qsl("isTempTrigger")) == YES);
|
|
pT->mIsMultiline = attributes().value(qsl("isMultiline")) == YES;
|
|
pT->mPerlSlashGOption = attributes().value(qsl("isPerlSlashGOption")) == YES;
|
|
pT->mIsColorizerTrigger = attributes().value(qsl("isColorizerTrigger")) == YES;
|
|
pT->mFilterTrigger = attributes().value(qsl("isFilterTrigger")) == YES;
|
|
pT->mSoundTrigger = attributes().value(qsl("isSoundTrigger")) == YES;
|
|
pT->mColorTrigger = attributes().value(qsl("isColorTrigger")) == YES;
|
|
|
|
// Is this a "TriggerGroup" or a "Trigger"
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
pT->setName(readElementText());
|
|
} else if (name() == qsl("script")) {
|
|
const QString tempScript = readScriptElement();
|
|
if (!pT->setScript(tempScript)) {
|
|
qDebug().nospace() << "XMLimport::readTrigger(...): ERROR: can not compile trigger's lua code for: " << pT->getName();
|
|
}
|
|
} else if (name() == qsl("packageName")) {
|
|
pT->mPackageName = readElementText();
|
|
} else if (name() == qsl("triggerType")) {
|
|
pT->mTriggerType = readElementText().toInt();
|
|
} else if (name() == qsl("conditonLineDelta")) {
|
|
pT->mConditionLineDelta = readElementText().toInt();
|
|
} else if (name() == qsl("mStayOpen")) {
|
|
pT->mStayOpen = readElementText().toInt();
|
|
} else if (name() == qsl("mCommand")) {
|
|
pT->mCommand = readElementText();
|
|
} else if (name() == qsl("mFgColor")) {
|
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
|
pT->mFgColor.setNamedColor(readElementText());
|
|
} else if (name() == qsl("mBgColor")) {
|
|
pT->mBgColor.setNamedColor(readElementText());
|
|
} else if (name() == qsl("colorTriggerFgColor")) {
|
|
pT->mColorTriggerFgColor.setNamedColor(readElementText());
|
|
} else if (name() == qsl("colorTriggerBgColor")) {
|
|
pT->mColorTriggerBgColor.setNamedColor(readElementText());
|
|
#else
|
|
pT->mFgColor = QColor::fromString(readElementText());
|
|
} else if (name() == qsl("mBgColor")) {
|
|
pT->mBgColor = QColor::fromString(readElementText());
|
|
} else if (name() == qsl("colorTriggerFgColor")) {
|
|
pT->mColorTriggerFgColor = QColor::fromString(readElementText());
|
|
} else if (name() == qsl("colorTriggerBgColor")) {
|
|
pT->mColorTriggerBgColor = QColor::fromString(readElementText());
|
|
#endif
|
|
} else if (name() == qsl("mSoundFile")) {
|
|
pT->mSoundFile = readElementText();
|
|
} else if (name() == qsl("regexCodeList")) {
|
|
// This and the next one ought to be combined into a single element
|
|
// in the next revision - sample code for "RegexCode" elements
|
|
// inside a "patterns" container (with a "size" attribute) is
|
|
// commented out in the XMLexporter class.
|
|
readStringList(pT->mPatterns, what);
|
|
} else if (name() == qsl("regexCodePropertyList")) {
|
|
readIntegerList(pT->mPatternKinds, pT->getName(), what);
|
|
if (Q_UNLIKELY(pT->mPatterns.count() != pT->mPatternKinds.count())) {
|
|
qWarning().nospace() << "XMLimport::readTrigger(...) ERROR: "
|
|
"mismatch in regexCode details for Trigger: "
|
|
<< pT->getName() << " there were " << pT->mPatterns.count() << " 'regexCodeList' sub-elements and " << pT->mPatternKinds.count()
|
|
<< " 'regexCodePropertyList' sub-elements so "
|
|
"something is broken!";
|
|
}
|
|
// Fixup the first 16 incorrect ANSI colour numbers from old
|
|
// code if there are any
|
|
if (!pT->mPatterns.isEmpty()) {
|
|
remapColorsToAnsiNumber(pT->mPatterns, pT->mPatternKinds);
|
|
}
|
|
} else if (name() == qsl("TriggerGroup") || name() == qsl("Trigger")) {
|
|
readTrigger(pT);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pT->setRegexCodeList(pT->mPatterns, pT->mPatternKinds)) {
|
|
qDebug().nospace() << "XMLimport::readTrigger(...): ERROR: can not "
|
|
"initialize pattern list for trigger: "
|
|
<< pT->getName();
|
|
}
|
|
|
|
return pT->getID();
|
|
}
|
|
|
|
int XMLimport::readTimerPackage()
|
|
{
|
|
int lastImportedTimerID = -1;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("TimerGroup") || name() == qsl("Timer")) {
|
|
gotTimer = true;
|
|
lastImportedTimerID = readTimer(mPackageName.isEmpty() ? nullptr : mpTimer);
|
|
} else {
|
|
readUnknownElement(qsl("TimerPackage"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return lastImportedTimerID;
|
|
}
|
|
|
|
int XMLimport::readTimer(TTimer* pParent)
|
|
{
|
|
auto pT = new TTimer(pParent, mpHost);
|
|
|
|
pT->setIsFolder(attributes().value(qsl("isFolder")) == YES);
|
|
// This should not ever be set here as, by definition, temporary timers
|
|
// are not saved:
|
|
pT->setTemporary(attributes().value(qsl("isTempTimer")) == YES);
|
|
|
|
// This clears the Tree<TTimer>::mUserActiveState flag so MUST be done
|
|
// BEFORE that flag is parsed:
|
|
mpHost->getTimerUnit()->registerTimer(pT);
|
|
|
|
pT->setShouldBeActive(attributes().value(qsl("isActive")) == YES);
|
|
|
|
if (module) {
|
|
pT->mModuleMember = true;
|
|
}
|
|
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
pT->setName(readElementText());
|
|
} else if (name() == qsl("packageName")) {
|
|
pT->mPackageName = readElementText();
|
|
} else if (name() == qsl("script")) {
|
|
const QString tempScript = readScriptElement();
|
|
if (!pT->setScript(tempScript)) {
|
|
qDebug().nospace() << "XMLimport::readTimer(...): ERROR: can not compile timer's lua code for: " << pT->getName();
|
|
}
|
|
} else if (name() == qsl("command")) {
|
|
pT->mCommand = readElementText();
|
|
} else if (name() == qsl("time")) {
|
|
pT->setTime(QTime::fromString(readElementText(), "hh:mm:ss.zzz"));
|
|
} else if (name() == qsl("TimerGroup") || name() == qsl("Timer")) {
|
|
readTimer(pT);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!pT->mpParent && pT->shouldBeActive()) {
|
|
pT->setIsActive(true);
|
|
pT->enableTimer(pT->getID());
|
|
}
|
|
|
|
return pT->getID();
|
|
}
|
|
|
|
int XMLimport::readAliasPackage()
|
|
{
|
|
int lastImportedAliasID = -1;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("AliasGroup") || name() == qsl("Alias")) {
|
|
gotAlias = true;
|
|
lastImportedAliasID = readAlias(mPackageName.isEmpty() ? nullptr : mpAlias);
|
|
} else {
|
|
readUnknownElement(qsl("AliasPackage"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return lastImportedAliasID;
|
|
}
|
|
|
|
int XMLimport::readAlias(TAlias* pParent)
|
|
{
|
|
auto pT = new TAlias(pParent, mpHost);
|
|
|
|
mpHost->getAliasUnit()->registerAlias(pT);
|
|
pT->setIsActive(attributes().value(qsl("isActive")) == YES);
|
|
pT->setIsFolder(attributes().value(qsl("isFolder")) == YES);
|
|
if (module) {
|
|
pT->mModuleMember = true;
|
|
}
|
|
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
pT->setName(readElementText());
|
|
} else if (name() == qsl("packageName")) {
|
|
pT->mPackageName = readElementText();
|
|
} else if (name() == qsl("script")) {
|
|
const QString tempScript = readScriptElement();
|
|
if (!pT->setScript(tempScript)) {
|
|
qDebug().nospace() << "XMLimport::readAlias(...): ERROR: can not compile alias's lua code for: " << pT->getName();
|
|
}
|
|
} else if (name() == qsl("command")) {
|
|
pT->mCommand = readElementText();
|
|
} else if (name() == qsl("regex")) {
|
|
pT->setRegexCode(readElementText());
|
|
} else if (name() == qsl("AliasGroup") || name() == qsl("Alias")) {
|
|
readAlias(pT);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pT->getID();
|
|
}
|
|
|
|
int XMLimport::readActionPackage()
|
|
{
|
|
int lastImportedActionID = -1;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("ActionGroup") || name() == qsl("Action")) {
|
|
gotAction = true;
|
|
lastImportedActionID = readAction(mPackageName.isEmpty() ? nullptr : mpAction);
|
|
} else {
|
|
readUnknownElement(qsl("ActionPackage"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return lastImportedActionID;
|
|
}
|
|
|
|
int XMLimport::readAction(TAction* pParent)
|
|
{
|
|
auto pT = new TAction(pParent, mpHost);
|
|
|
|
pT->setIsFolder(attributes().value(qsl("isFolder")) == YES);
|
|
pT->setIsPushDownButton(attributes().value(qsl("isPushButton")) == YES);
|
|
pT->setButtonFlat(attributes().value(qsl("isFlatButton")) == YES);
|
|
pT->mUseCustomLayout = attributes().value(qsl("useCustomLayout")) == YES;
|
|
mpHost->getActionUnit()->registerAction(pT);
|
|
pT->setIsActive(attributes().value(qsl("isActive")) == YES);
|
|
|
|
if (module) {
|
|
pT->mModuleMember = true;
|
|
}
|
|
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
pT->setName(readElementText());
|
|
} else if (name() == qsl("packageName")) {
|
|
pT->mPackageName = readElementText();
|
|
} else if (name() == qsl("script")) {
|
|
const QString tempScript = readScriptElement();
|
|
if (!pT->setScript(tempScript)) {
|
|
qDebug().nospace() << "XMLimport::readAction(...): ERROR: can not compile action's lua code for: " << pT->getName();
|
|
}
|
|
} else if (name() == qsl("css")) {
|
|
pT->css = readElementText();
|
|
} else if (name() == qsl("commandButtonUp")) {
|
|
pT->setCommandButtonUp(readElementText());
|
|
} else if (name() == qsl("commandButtonDown")) {
|
|
pT->setCommandButtonDown(readElementText());
|
|
} else if (name() == qsl("icon")) {
|
|
pT->setIcon(readElementText());
|
|
} else if (name() == qsl("orientation")) {
|
|
pT->mOrientation = readElementText().toInt();
|
|
} else if (name() == qsl("location")) {
|
|
pT->mLocation = readElementText().toInt();
|
|
} else if (name() == qsl("buttonRotation")) {
|
|
pT->setButtonRotation(readElementText().toInt());
|
|
} else if (name() == qsl("sizeX")) {
|
|
pT->setSizeX(readElementText().toInt());
|
|
} else if (name() == qsl("sizeY")) {
|
|
pT->setSizeY(readElementText().toInt());
|
|
} else if (name() == qsl("mButtonState")) {
|
|
// We now use a boolean but file must use original "1" (false)
|
|
// or "2" (true) for backward compatibility
|
|
pT->mButtonState = (readElementText().toInt() == 2);
|
|
} else if (name() == qsl("buttonColor")) {
|
|
// Not longer present/used, skip over it if it is still in file:
|
|
skipCurrentElement();
|
|
} else if (name() == qsl("buttonColumn")) {
|
|
// The above ought to have been plural!
|
|
pT->setButtonColumns(readElementText().toInt());
|
|
} else if (name() == qsl("buttonFillerOffset")) {
|
|
pT->setButtonFillerOffset(readElementText().toInt());
|
|
} else if (name() == qsl("posX")) {
|
|
pT->mPosX = readElementText().toInt();
|
|
} else if (name() == qsl("posY")) {
|
|
pT->mPosY = readElementText().toInt();
|
|
} else if (name() == qsl("ActionGroup") || name() == qsl("Action")) {
|
|
readAction(pT);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pT->getID();
|
|
}
|
|
|
|
int XMLimport::readScriptPackage()
|
|
{
|
|
int lastImportedScriptID = -1;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("ScriptGroup") || name() == qsl("Script")) {
|
|
gotScript = true;
|
|
lastImportedScriptID = readScript(mPackageName.isEmpty() ? nullptr : mpScript);
|
|
} else {
|
|
readUnknownElement(qsl("ScriptPackage"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return lastImportedScriptID;
|
|
}
|
|
|
|
int XMLimport::readScript(TScript* pParent)
|
|
{
|
|
auto script = new TScript(pParent, mpHost);
|
|
|
|
script->setIsFolder(attributes().value(qsl("isFolder")) == YES);
|
|
mpHost->getScriptUnit()->registerScript(script);
|
|
script->setIsActive(attributes().value(qsl("isActive")) == YES);
|
|
|
|
if (module) {
|
|
script->mModuleMember = true;
|
|
}
|
|
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
script->mName = readElementText();
|
|
} else if (name() == qsl("packageName")) {
|
|
script->mPackageName = readElementText();
|
|
} else if (name() == qsl("script")) {
|
|
const QString tempScript = readScriptElement();
|
|
if (!script->setScript(tempScript)) {
|
|
qDebug().nospace().noquote() << "XMLimport::readScript(...) ERROR - can not compile script's lua code for \"" << script->getName() << "\"; reason: " << script->getError() << ".";
|
|
}
|
|
} else if (name() == qsl("eventHandlerList")) {
|
|
readStringList(script->mEventHandlerList, what);
|
|
script->setEventHandlerList(script->mEventHandlerList);
|
|
} else if (name() == qsl("ScriptGroup") || name() == qsl("Script")) {
|
|
readScript(script);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
return script->getID();
|
|
}
|
|
|
|
int XMLimport::readKeyPackage()
|
|
{
|
|
int lastImportedKeyID = -1;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("KeyGroup") || name() == qsl("Key")) {
|
|
gotKey = true;
|
|
lastImportedKeyID = readKey(mPackageName.isEmpty() ? nullptr : mpKey);
|
|
} else {
|
|
readUnknownElement(qsl("KeyPackage"));
|
|
}
|
|
}
|
|
}
|
|
|
|
return lastImportedKeyID;
|
|
}
|
|
|
|
int XMLimport::readKey(TKey* pParent)
|
|
{
|
|
auto pT = new TKey(pParent, mpHost);
|
|
|
|
mpHost->getKeyUnit()->registerKey(pT);
|
|
pT->setIsActive(attributes().value(qsl("isActive")) == YES);
|
|
pT->setIsFolder(attributes().value(qsl("isFolder")) == YES);
|
|
if (module) {
|
|
pT->mModuleMember = true;
|
|
}
|
|
|
|
const QString what = name().toString();
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("name")) {
|
|
pT->setName(readElementText());
|
|
} else if (name() == qsl("packageName")) {
|
|
pT->mPackageName = readElementText();
|
|
} else if (name() == qsl("script")) {
|
|
const QString tempScript = readScriptElement();
|
|
if (!pT->setScript(tempScript)) {
|
|
qDebug().nospace() << "XMLimport::readKey(...): ERROR: can not compile key's lua code for: " << pT->getName();
|
|
}
|
|
} else if (name() == qsl("command")) {
|
|
pT->mCommand = readElementText();
|
|
} else if (name() == qsl("keyCode")) {
|
|
pT->setKeyCode(readElementText().toInt());
|
|
} else if (name() == qsl("keyModifier")) {
|
|
pT->setKeyModifiers(readElementText().toInt());
|
|
} else if (name() == qsl("KeyGroup") || name() == qsl("Key")) {
|
|
readKey(pT);
|
|
} else {
|
|
readUnknownElement(what);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pT->getID();
|
|
}
|
|
|
|
void XMLimport::readModulesDetailsMap(QMap<QString, QStringList>& map)
|
|
{
|
|
QString key;
|
|
QStringList entry;
|
|
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("key")) {
|
|
key = readElementText();
|
|
} else if (name() == qsl("filepath")) {
|
|
entry << readElementText();
|
|
} else if (name() == qsl("zipSync")) {
|
|
entry << readElementText();
|
|
} else if (name() == qsl("globalSave")) {
|
|
if (entry.size() < 2) {
|
|
entry << readElementText();
|
|
} else {
|
|
skipCurrentElement();
|
|
}
|
|
} else if (name() == qsl("priority")) {
|
|
// The last expected detail for the entry - so store this
|
|
// completed entry into the QMap
|
|
entry << readElementText();
|
|
map[key] = entry;
|
|
entry.clear();
|
|
} else {
|
|
readUnknownElement(qsl("ModulesDetailsMap"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readStringList(QStringList& list, const QString& whatIsParent)
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("string")) {
|
|
list << readElementText();
|
|
} else {
|
|
readUnknownElement(whatIsParent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readIntegerList(QList<int>& list, const QString& parentName, const QString& whatIsParent)
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("integer")) {
|
|
const QString numberText = readElementText();
|
|
bool ok = false;
|
|
const int num = numberText.toInt(&ok, 10);
|
|
if (Q_LIKELY(!numberText.isEmpty() && ok)) {
|
|
switch (num) {
|
|
case REGEX_SUBSTRING:
|
|
[[fallthrough]];
|
|
case REGEX_PERL:
|
|
[[fallthrough]];
|
|
case REGEX_BEGIN_OF_LINE_SUBSTRING:
|
|
[[fallthrough]];
|
|
case REGEX_EXACT_MATCH:
|
|
[[fallthrough]];
|
|
case REGEX_LUA_CODE:
|
|
[[fallthrough]];
|
|
case REGEX_LINE_SPACER:
|
|
[[fallthrough]];
|
|
case REGEX_COLOR_PATTERN:
|
|
[[fallthrough]];
|
|
case REGEX_PROMPT:
|
|
list << num;
|
|
break;
|
|
default:
|
|
mpHost->postMessage(
|
|
qsl("[ ERROR ] - \"%1\" as a number when reading the 'regexCodePropertyList' element of the 'Trigger' or 'TriggerGroup' element \"%2\" cannot be understood by this "
|
|
"version of Mudlet, is it from a later version? Converting it to a SUBSTRING type so the data can be shown but it will probably not work as expected.")
|
|
.arg(numberText, parentName));
|
|
list << REGEX_SUBSTRING; //Set it to the default type
|
|
}
|
|
|
|
} else {
|
|
qWarning(
|
|
R"(XMLimport::readIntegerList(...) ERROR: unable to convert: "%s" to a number when reading the 'regexCodePropertyList' element of the 'Trigger' or 'TriggerGroup' element "%s"!)",
|
|
numberText.toUtf8().constData(),
|
|
parentName.toUtf8().constData());
|
|
mpHost->postMessage(qsl("[ ERROR ] - Unable to convert: \"%1\" to a number when reading the 'regexCodePropertyList' element of the 'Trigger' or 'TriggerGroup' element \"%2\"!")
|
|
.arg(numberText, parentName));
|
|
list << REGEX_SUBSTRING; //Just assume most common one
|
|
}
|
|
} else {
|
|
readUnknownElement(whatIsParent);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This will be a string representation of a decimal float with three places of
|
|
// decimals
|
|
void XMLimport::getVersionString(QString& versionString)
|
|
{
|
|
versionString = QString::number((mVersionMajor * 1000 + mVersionMinor) / 1000.0, 'f', 3);
|
|
}
|
|
|
|
QString XMLimport::readScriptElement()
|
|
{
|
|
QString localScript = readElementText();
|
|
if (Error() != NoError) {
|
|
qDebug() << "XMLimport::readScriptElement() ERROR:" << errorString();
|
|
}
|
|
|
|
if (mVersionMajor > 1 || (mVersionMajor == 1 && mVersionMinor > 0)) {
|
|
// This is NOT the original version, so it will have control characters
|
|
// encoded up using Object Replacement and Control Symbol (for relevant ASCII control code) code-points
|
|
localScript.replace(qsl("\xFFFC\x2401"), QChar('\x01')); // SOH
|
|
localScript.replace(qsl("\xFFFC\x2402"), QChar('\x02')); // STX
|
|
localScript.replace(qsl("\xFFFC\x2403"), QChar('\x03')); // ETX
|
|
localScript.replace(qsl("\xFFFC\x2404"), QChar('\x04')); // EOT
|
|
localScript.replace(qsl("\xFFFC\x2405"), QChar('\x05')); // ENQ
|
|
localScript.replace(qsl("\xFFFC\x2406"), QChar('\x06')); // ACK
|
|
localScript.replace(qsl("\xFFFC\x2407"), QChar('\x07')); // BEL
|
|
localScript.replace(qsl("\xFFFC\x2408"), QChar('\x08')); // BS
|
|
localScript.replace(qsl("\xFFFC\x240B"), QChar('\x0B')); // VT
|
|
localScript.replace(qsl("\xFFFC\x240C"), QChar('\x0C')); // FF
|
|
localScript.replace(qsl("\xFFFC\x240E"), QChar('\x0E')); // SS
|
|
localScript.replace(qsl("\xFFFC\x240F"), QChar('\x0F')); // SI
|
|
localScript.replace(qsl("\xFFFC\x2410"), QChar('\x10')); // DLE
|
|
localScript.replace(qsl("\xFFFC\x2411"), QChar('\x11')); // DC1
|
|
localScript.replace(qsl("\xFFFC\x2412"), QChar('\x12')); // DC2
|
|
localScript.replace(qsl("\xFFFC\x2413"), QChar('\x13')); // DC3
|
|
localScript.replace(qsl("\xFFFC\x2414"), QChar('\x14')); // DC4
|
|
localScript.replace(qsl("\xFFFC\x2415"), QChar('\x15')); // NAK
|
|
localScript.replace(qsl("\xFFFC\x2416"), QChar('\x16')); // SYN
|
|
localScript.replace(qsl("\xFFFC\x2417"), QChar('\x17')); // ETB
|
|
localScript.replace(qsl("\xFFFC\x2418"), QChar('\x18')); // CAN
|
|
localScript.replace(qsl("\xFFFC\x2419"), QChar('\x19')); // EM
|
|
localScript.replace(qsl("\xFFFC\x241A"), QChar('\x1A')); // SUB
|
|
localScript.replace(qsl("\xFFFC\x241B"), QChar('\x1B')); // ESC
|
|
localScript.replace(qsl("\xFFFC\x241C"), QChar('\x1C')); // FS
|
|
localScript.replace(qsl("\xFFFC\x241D"), QChar('\x1D')); // GS
|
|
localScript.replace(qsl("\xFFFC\x241E"), QChar('\x1E')); // RS
|
|
localScript.replace(qsl("\xFFFC\x241F"), QChar('\x1F')); // US
|
|
localScript.replace(qsl("\xFFFC\x2421"), QChar('\x7F')); // DEL
|
|
}
|
|
|
|
return localScript;
|
|
}
|
|
|
|
// Unlike the reverse operation in the XMLexport this can modify the supplied patternList:
|
|
void XMLimport::remapColorsToAnsiNumber(QStringList& patternList, const QList<int>& typeList)
|
|
{
|
|
// The regexp is slightly modified compared to the one we once used to allow
|
|
// it to capture a '-' sign as part of the color numbers as we use -2 for
|
|
// ignored which was/is/will not handled by code before Mudlet 3.17.x (and
|
|
// we might have more negative numbers in the future!)
|
|
const QRegularExpression regex = QRegularExpression(qsl("FG(-?\\d+)BG(-?\\d+)"));
|
|
QMutableStringListIterator itPattern(patternList);
|
|
QListIterator<int> itType(typeList);
|
|
while (itPattern.hasNext() && itType.hasNext()) {
|
|
if (itType.next() == REGEX_COLOR_PATTERN) {
|
|
const QRegularExpressionMatch match = regex.match(itPattern.next());
|
|
// Although we define two '('...')' capture groups the count/size is
|
|
// 3 (0 is the whole string)!
|
|
if (match.capturedTexts().size() == 3) {
|
|
bool isFgOk = false;
|
|
bool isBgOk = false;
|
|
int ansifg = TTrigger::scmIgnored;
|
|
int ansibg = TTrigger::scmIgnored;
|
|
int fg = match.captured(1).toInt(&isFgOk);
|
|
if (!isFgOk) {
|
|
qDebug() << "XMLimport::remapColorsToAnsiNumber(...) ERROR - failed to extract FG color code from pattern text:" << itPattern.peekPrevious()
|
|
<< " setting colour to default foreground";
|
|
fg = TTrigger::scmDefault;
|
|
} else {
|
|
// clang-format off
|
|
switch (fg) {
|
|
case -2: ansifg = TTrigger::scmIgnored; break; // Ignored colour - not handled by old code
|
|
case 0: ansifg = TTrigger::scmDefault; break; // Default colour
|
|
case 1: ansifg = 8; break; // Light black (dark gray)
|
|
case 2: ansifg = 0; break; // Black
|
|
case 3: ansifg = 9; break; // Light red
|
|
case 4: ansifg = 1; break; // Red
|
|
case 5: ansifg = 10; break; // Light green
|
|
case 6: ansifg = 2; break; // Green
|
|
case 7: ansifg = 11; break; // Light yellow
|
|
case 8: ansifg = 3; break; // Yellow
|
|
case 9: ansifg = 12; break; // Light blue
|
|
case 10: ansifg = 4; break; // Blue
|
|
case 11: ansifg = 13; break; // Light magenta
|
|
case 12: ansifg = 5; break; // Magenta
|
|
case 13: ansifg = 14; break; // Light cyan
|
|
case 14: ansifg = 6; break; // Cyan
|
|
case 15: ansifg = 15; break; // Light white
|
|
case 16: ansifg = 7; break; // White (light gray)
|
|
default:
|
|
ansifg = fg;
|
|
}
|
|
// clang-format on
|
|
}
|
|
|
|
int bg = match.captured(2).toInt(&isBgOk);
|
|
if (!isBgOk) {
|
|
qDebug() << "XMLimport::remapColorsToAnsiNumber(...) ERROR - failed to extract BG color code from pattern text:" << itPattern.peekPrevious()
|
|
<< " setting colour to default background";
|
|
bg = TTrigger::scmDefault;
|
|
} else {
|
|
// clang-format off
|
|
switch (bg) {
|
|
case -2: ansibg = TTrigger::scmIgnored; break; // Ignored colour - not handled by old code
|
|
case 0: ansibg = TTrigger::scmDefault; break; // Default colour
|
|
case 1: ansibg = 8; break; // Light black (dark gray)
|
|
case 2: ansibg = 0; break; // Black
|
|
case 3: ansibg = 9; break; // Light red
|
|
case 4: ansibg = 1; break; // Red
|
|
case 5: ansibg = 10; break; // Light green
|
|
case 6: ansibg = 2; break; // Green
|
|
case 7: ansibg = 11; break; // Light yellow
|
|
case 8: ansibg = 3; break; // Yellow
|
|
case 9: ansibg = 12; break; // Light blue
|
|
case 10: ansibg = 4; break; // Blue
|
|
case 11: ansibg = 13; break; // Light magenta
|
|
case 12: ansibg = 5; break; // Magenta
|
|
case 13: ansibg = 14; break; // Light cyan
|
|
case 14: ansibg = 6; break; // Cyan
|
|
case 15: ansibg = 15; break; // Light white
|
|
case 16: ansibg = 7; break; // White (light gray)
|
|
default:
|
|
ansibg = bg;
|
|
}
|
|
// clang-format on
|
|
}
|
|
|
|
// Use a different string than before so that we can be certain
|
|
// we have fixed up all cases where it is used - and it is more
|
|
// understandable if it gets revealed in the Editor!
|
|
itPattern.setValue(TTrigger::createColorPatternText(ansifg, ansibg));
|
|
}
|
|
|
|
} else {
|
|
// Must advance the pattern iterator if it isn't a colour pattern
|
|
itPattern.next();
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readStopWatchMap()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("stopwatch")) {
|
|
const int watchId = attributes().value(qsl("id")).toInt();
|
|
auto pStopWatch = std::make_unique<stopWatch>();
|
|
pStopWatch->setName(attributes().value(qsl("name")).toString());
|
|
pStopWatch->mIsPersistent = true;
|
|
pStopWatch->mIsInitialised = true;
|
|
if (attributes().value(qsl("running")) == YES) {
|
|
pStopWatch->mIsRunning = true;
|
|
// The stored value is the point in epoch time that the
|
|
// stopwatch appears to have been started so we need to
|
|
// make that into a QDateTime that is the equivalent:
|
|
pStopWatch->mEffectiveStartDateTime.setMSecsSinceEpoch(attributes().value(qsl("effectiveStartDateTimeEpochMSecs")).toLongLong());
|
|
} else {
|
|
pStopWatch->mIsRunning = false;
|
|
pStopWatch->mElapsedTime = attributes().value(qsl("elapsedDateTimeMSecs")).toLongLong();
|
|
}
|
|
mpHost->mStopWatchMap[watchId] = std::move(pStopWatch);
|
|
// A dummy read as there should not be any text for this element:
|
|
readElementText();
|
|
} else {
|
|
readUnknownElement("stopwatches");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readMMCPOptions()
|
|
{
|
|
mpHost->mMMCPChatName = attributes().value(qsl("chatName")).toString();
|
|
mpHost->mMMCPChatPort = attributes().value(qsl("chatPort")).toUShort();
|
|
mpHost->mMMCPChatPrefix = attributes().value(qsl("chatPrefix")).toString();
|
|
mpHost->mMMCPAutostartServer = attributes().value(qsl("autostartServer")) == YES;
|
|
mpHost->mMMCPAllowPeekRequests = attributes().value(qsl("allowPeekRequests")) == YES;
|
|
mpHost->mMMCPPrefixEmotes = attributes().value(qsl("prefixEmotes")) == YES;
|
|
mpHost->mMMCPAddChatMessageNewline = attributes().value(qsl("chatMessageNewline")) == YES;
|
|
mpHost->mMMCPAutoAcceptCalls = attributes().value(qsl("autoAcceptCalls")) == YES;
|
|
mpHost->mMMCPShowSnoopInMainConsole = attributes().value(qsl("snoopInMain")) == YES;
|
|
|
|
// MMCP is a self-closing tag, need to call readNext to move along..
|
|
readNext();
|
|
}
|
|
|
|
void XMLimport::readMapInfoContributor()
|
|
{
|
|
mpHost->mMapInfoContributors.insert(readElementText());
|
|
}
|
|
|
|
void XMLimport::readLegacyMapInfoContributors()
|
|
{
|
|
while (!atEnd()) {
|
|
readNext();
|
|
if (isEndElement()) {
|
|
break;
|
|
}
|
|
if (isStartElement()) {
|
|
if (name() == qsl("mapInfoContributor")) {
|
|
mpHost->mMapInfoContributors.insert(readElementText());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void XMLimport::readProfileShortcut()
|
|
{
|
|
auto key = attributes().value(qsl("key"));
|
|
auto sequenceString = readElementText();
|
|
if (auto it = mpHost->profileShortcuts.find(key.toString()); it != mpHost->profileShortcuts.end()) {
|
|
QKeySequence sequence = !sequenceString.isEmpty() ? QKeySequence(sequenceString) : QKeySequence();
|
|
it->second->swap(sequence);
|
|
}
|
|
}
|