New JSON config cleanup and testing

This commit is contained in:
GriffinR 2026-02-22 03:04:02 -05:00
parent 29fe37f79e
commit 463803292f
15 changed files with 84 additions and 98 deletions

View File

@ -2289,7 +2289,7 @@ All constants are accessible via the global ``constants`` object.
.. js:attribute:: constants.base_game_version
The string value of the config setting ``base_game_version``. This will either be ``pokeruby``, ``pokefirered``, or ``pokeemerald``.
The string value of the config setting ``base_game_version``. This will either be ``pokeruby``, ``pokefirered``, ``pokeemerald``, or an empty string.
.. js:attribute:: constants.version.major

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>264</width>
<height>173</height>
<width>387</width>
<height>357</height>
</rect>
</property>
<property name="windowTitle">
@ -36,8 +36,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>240</width>
<height>109</height>
<width>363</width>
<height>293</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">

View File

@ -2,6 +2,14 @@
<ui version="4.0">
<class>NewLayoutForm</class>
<widget class="QWidget" name="NewLayoutForm">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>196</width>
<height>331</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
@ -168,6 +176,9 @@
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
@ -204,6 +215,9 @@
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>559</width>
<height>614</height>
<width>467</width>
<height>428</height>
</rect>
</property>
<property name="windowTitle">
@ -39,8 +39,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>535</width>
<height>550</height>
<width>443</width>
<height>364</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout_3">
@ -129,6 +129,9 @@
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
</widget>
</item>
<item row="8" column="1">
@ -171,6 +174,9 @@
<property name="insertPolicy">
<enum>QComboBox::InsertPolicy::NoInsert</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon</enum>
</property>
</widget>
</item>
<item row="8" column="0">

View File

@ -48,13 +48,7 @@ public:
{ };
virtual ~KeyValueConfigBase() {};
// Writes the contents of the config to disk.
// Returns true if saving was successful, false otherwise.
virtual bool save();
// Loads the contents of the config from disk.
// Returns true if saving was successful, false otherwise.
// A successful load includes initializing an empty or non-existing file.
virtual bool load();
virtual QJsonObject toJson();

View File

@ -37,5 +37,5 @@ public:
bool eventFilter(QObject *obj, QEvent *event) override;
private:
bool m_loggingEnabled = true;
QSet<QObject*> m_wasShown;
QSet<QObject*> m_shown;
};

View File

@ -71,6 +71,7 @@ private:
void addNewGlobalConstant();
void addGlobalConstant(const QString &name, const QString &expression);
QMap<QString,QString> getGlobalConstants();
BaseGame::Version getBaseGameVersion() const;
private slots:
void dialogButtonClicked(QAbstractButton *button);

View File

@ -434,10 +434,8 @@ void ProjectConfig::setVersionSpecificDefaults(BaseGame::Version version) {
0x9B, // MB_SECRET_BASE_SPOT_BLUE_CAVE_OPEN
0x9D, // MB_SECRET_BASE_SPOT_TREE_RIGHT_OPEN
};
if (this->baseGameVersion == BaseGame::Version::pokeruby) {
this->mapAllowFlagsEnabled = false;
}
}
this->mapAllowFlagsEnabled = (this->baseGameVersion != BaseGame::Version::pokeruby);
}
bool ProjectConfig::save() {
@ -472,6 +470,7 @@ QJsonObject ProjectConfig::getDefaultJson() const {
return defaultConfig.toJson();
}
// TODO: Replace with a new prompt that allows choosing either the defaults for each version, or customizing settings.
void ProjectConfig::initializeFromEmpty() {
const QString dirName = QDir(projectDir()).dirName();
BaseGame::Version version = BaseGame::stringToVersion(dirName);
@ -485,21 +484,18 @@ void ProjectConfig::initializeFromEmpty() {
QFormLayout form(&dialog);
QComboBox *baseGameVersionComboBox = new QComboBox();
// TODO: Populate dynamically, same as project settings editor
baseGameVersionComboBox->addItem("pokeruby", BaseGame::Version::pokeruby);
baseGameVersionComboBox->addItem("pokefirered", BaseGame::Version::pokefirered);
baseGameVersionComboBox->addItem("pokeemerald", BaseGame::Version::pokeemerald);
form.addRow(new QLabel("Game Version"), baseGameVersionComboBox);
// TODO: Add an 'Advanced' button to open the project settings window (with some settings disabled)
auto comboBox = new QComboBox();
comboBox->addItem(BaseGame::versionToString(BaseGame::Version::pokeruby), BaseGame::Version::pokeruby);
comboBox->addItem(BaseGame::versionToString(BaseGame::Version::pokefirered), BaseGame::Version::pokefirered);
comboBox->addItem(BaseGame::versionToString(BaseGame::Version::pokeemerald), BaseGame::Version::pokeemerald);
form.addRow(new QLabel("Game Version"), comboBox);
QDialogButtonBox buttonBox(QDialogButtonBox::Ok, Qt::Horizontal, &dialog);
QObject::connect(&buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
form.addRow(&buttonBox);
if (dialog.exec() == QDialog::Accepted) {
this->baseGameVersion = static_cast<BaseGame::Version>(baseGameVersionComboBox->currentData().toInt());
this->baseGameVersion = static_cast<BaseGame::Version>(comboBox->currentData().toInt());
} else {
logWarn(QString("No base_game_version selected, using default '%1'").arg(BaseGame::versionToString(this->baseGameVersion)));
}

View File

@ -156,9 +156,9 @@ bool PorymapConfig::parseLegacyKeyValue(const QString &key, const QString &value
} else if (key == "text_editor_goto_line") {
this->textEditorGotoLine = value;
} else if (key == "palette_editor_bit_depth") {
this->paletteEditorBitDepth = toInt(value, 15, 24, 24);
if (this->paletteEditorBitDepth != 15 && this->paletteEditorBitDepth != 24){
this->paletteEditorBitDepth = 24;
int bitDepth = toInt(value, 15, 24, 24);
if (bitDepth == 15 || bitDepth == 24){
this->paletteEditorBitDepth = bitDepth;
}
} else if (key == "project_settings_tab") {
this->projectSettingsTab = toInt(value, 0);
@ -180,9 +180,7 @@ bool PorymapConfig::parseLegacyKeyValue(const QString &key, const QString &value
this->lastUpdateCheckTime = QDateTime::fromString(value).toLocalTime();
} else if (key == "last_update_check_version") {
auto version = QVersionNumber::fromString(value);
if (version.segmentCount() != 3) {
this->lastUpdateCheckVersion = porymapVersion;
} else {
if (version.segmentCount() == 3) {
this->lastUpdateCheckVersion = version;
}
} else if (key.startsWith("rate_limit_time/")) {

View File

@ -13,14 +13,14 @@ BaseGame::Version BaseGame::stringToVersion(const QString &input_) {
};
const QString input(input_.toLower());
BaseGame::Version version = BaseGame::Version::none;
Version version = Version::none;
for (auto it = versionDetectNames.begin(); it != versionDetectNames.end(); it++) {
// Compare the given string to all the possible names for this game version
for (const auto &name : it.value()) {
if (input.contains(name)) {
if (version != BaseGame::Version::none) {
if (version != Version::none) {
// The given string matches multiple versions, so we can't be sure which it is.
return BaseGame::Version::none;
return Version::none;
}
version = it.key();
break;
@ -31,7 +31,6 @@ BaseGame::Version BaseGame::stringToVersion(const QString &input_) {
return version;
}
// TODO: Make sure empty string is ok everywhere this is used
QString BaseGame::versionToString(BaseGame::Version version) {
static const QMap<Version, QString> map = {
{Version::pokeruby, "pokeruby"},
@ -42,15 +41,15 @@ QString BaseGame::versionToString(BaseGame::Version version) {
}
QString BaseGame::getPlayerIconPath(BaseGame::Version version, int character) {
if (version == BaseGame::Version::pokeemerald) {
if (version == Version::pokeemerald) {
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_em.ico"),
QStringLiteral(":/icons/player/may_em.ico"), };
return paths.value(character);
} else if (version == BaseGame::Version::pokefirered) {
} else if (version == Version::pokefirered) {
static const QStringList paths = { QStringLiteral(":/icons/player/red.ico"),
QStringLiteral(":/icons/player/green.ico"), };
return paths.value(character);
} else if (version == BaseGame::Version::pokeruby) {
} else if (version == Version::pokeruby) {
static const QStringList paths = { QStringLiteral(":/icons/player/brendan_rs.ico"),
QStringLiteral(":/icons/player/may_rs.ico"), };
return paths.value(character);

View File

@ -895,7 +895,8 @@ bool MainWindow::checkProjectVersion(Project *project) {
logInfo(QString("Successfully checked project version. Supports at least Porymap v%1").arg(minimumVersion.toString()));
}
if (minimumVersion > porymapVersion || minimumVersion.majorVersion() != porymapVersion.majorVersion()) {
// We were unable to find the necessary changes for Porymap's current major version.
// Porymap is incompatible with the project if its version is below the specified minimum version,
// or if Porymap is so new that it exceeds the major version of the specified minimum version.
// Unless they have explicitly suppressed this message, warn the user that this might mean their project is missing breaking changes.
// Note: Do not report 'minimumVersion' to the user in this message. We've already logged it for troubleshooting.
// It is very plausible that the user may have reproduced the required changes in an

View File

@ -40,14 +40,15 @@ bool GeometrySaver::eventFilter(QObject *object, QEvent *event) {
if (m_loggingEnabled && !w->windowTitle().isEmpty()) {
logInfo(QString("Opening window: %1").arg(w->windowTitle()));
}
m_wasShown.insert(object);
} else if (event->type() == QEvent::Close && m_wasShown.contains(object)) {
m_shown.insert(object);
} else if (event->type() == QEvent::Close || event->type() == QEvent::DeferredDelete) {
// There are situations where a window might be 'closed' without
// ever actually having been opened (for example, the Shortcuts Editor
// will quietly construct windows to get their shortcuts, and those windows
// can later be closed without having been displayed).
// We don't want to save the geometry of these windows, or log that they closed,
// so we've checked to make sure the widget was displayed before proceeding.
// so we checked to make sure the widget was displayed before proceeding.
if (!m_shown.remove(object)) return false;
porymapConfig.saveGeometry(w);
if (m_loggingEnabled && !w->windowTitle().isEmpty()) {
logInfo(QString("Closing window: %1").arg(w->windowTitle()));

View File

@ -3,6 +3,7 @@
#include "ui_newlayoutdialog.h"
#include "config.h"
#include "validator.h"
#include "eventfilters.h"
#include <QMap>
#include <QSet>
@ -40,17 +41,12 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked);
refresh();
if (!porymapConfig.restoreGeometry(this)) {
// On first display resize to fit contents a little better
adjustSize();
}
installEventFilter(new GeometrySaver(this));
ui->lineEdit_Name->setFocus();
}
NewLayoutDialog::~NewLayoutDialog()
{
porymapConfig.saveGeometry(this);
saveSettings();
delete ui;
}

View File

@ -111,12 +111,10 @@ void ProjectSettingsEditor::initUi() {
ui->comboBox_IconSpecies->addItems(project->speciesNames);
ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys());
}
// TODO: We don't need to keep converting these to/from strings, just include the value as data.
ui->comboBox_BaseGameVersion->addItems({
BaseGame::versionToString(BaseGame::Version::pokeruby),
BaseGame::versionToString(BaseGame::Version::pokefirered),
BaseGame::versionToString(BaseGame::Version::pokeemerald),
});
ui->comboBox_BaseGameVersion->addItem("Custom", BaseGame::Version::none);
ui->comboBox_BaseGameVersion->addItem(BaseGame::versionToString(BaseGame::Version::pokeruby), BaseGame::Version::pokeruby);
ui->comboBox_BaseGameVersion->addItem(BaseGame::versionToString(BaseGame::Version::pokefirered), BaseGame::Version::pokefirered);
ui->comboBox_BaseGameVersion->addItem(BaseGame::versionToString(BaseGame::Version::pokeemerald), BaseGame::Version::pokeemerald);
ui->comboBox_AttributesSize->addItems({"1", "2", "4"});
ui->comboBox_EventsTabIcon->addItem("Automatic", "");
@ -207,6 +205,10 @@ bool ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString
return false;
}
BaseGame::Version ProjectSettingsEditor::getBaseGameVersion() const {
return static_cast<BaseGame::Version>(ui->comboBox_BaseGameVersion->currentData().toInt());
}
// Remember the current settings tab for future sessions
void ProjectSettingsEditor::on_mainTabs_tabBarClicked(int index) {
porymapConfig.projectSettingsTab = index;
@ -447,7 +449,7 @@ void ProjectSettingsEditor::refresh() {
// Set combo box texts
ui->comboBox_DefaultPrimaryTileset->setTextItem(projectConfig.defaultPrimaryTileset);
ui->comboBox_DefaultSecondaryTileset->setTextItem(projectConfig.defaultSecondaryTileset);
ui->comboBox_BaseGameVersion->setTextItem(BaseGame::versionToString(projectConfig.baseGameVersion));
ui->comboBox_BaseGameVersion->setNumberItem(projectConfig.baseGameVersion);
ui->comboBox_AttributesSize->setTextItem(QString::number(projectConfig.metatileAttributesSize));
this->updateAttributeLimits(ui->comboBox_AttributesSize->currentText());
@ -555,7 +557,7 @@ void ProjectSettingsEditor::save() {
// Save combo box settings
projectConfig.defaultPrimaryTileset = ui->comboBox_DefaultPrimaryTileset->currentText();
projectConfig.defaultSecondaryTileset = ui->comboBox_DefaultSecondaryTileset->currentText();
projectConfig.baseGameVersion = BaseGame::stringToVersion(ui->comboBox_BaseGameVersion->currentText());
projectConfig.baseGameVersion = getBaseGameVersion();
projectConfig.metatileAttributesSize = ui->comboBox_AttributesSize->currentText().toInt();
// Save check box settings
@ -772,7 +774,7 @@ QString ProjectSettingsEditor::stripProjectDir(QString s) {
void ProjectSettingsEditor::importDefaultPrefabsClicked(bool) {
// If the prompt is accepted the prefabs file will be created and its filepath will be saved in the config.
BaseGame::Version version = BaseGame::stringToVersion(ui->comboBox_BaseGameVersion->currentText());
BaseGame::Version version = getBaseGameVersion();
if (prefab.tryImportDefaultPrefabs(this, version, ui->lineEdit_PrefabsPath->text())) {
ui->lineEdit_PrefabsPath->setText(userConfig.prefabsFilepath); // Refresh with new filepath
this->hasUnsavedChanges = true;
@ -804,19 +806,20 @@ bool ProjectSettingsEditor::promptSaveChanges() {
return true;
}
// TODO: Changing the base game version implicitly changes defaults.
// If the user declines this prompt, it should revert to the previous setting.
bool ProjectSettingsEditor::promptRestoreDefaults() {
if (this->refreshing)
return false;
const QString versionText = ui->comboBox_BaseGameVersion->currentText();
if (this->prompt(QString("Restore default config settings for %1?").arg(versionText)) == QMessageBox::No)
if (this->prompt(QString("Restore default config settings for %1?").arg(ui->comboBox_BaseGameVersion->currentText())) == QMessageBox::No)
return false;
// Restore defaults by resetting config in memory, refreshing the UI, then restoring the config.
// Don't want to save changes until user accepts them.
// TODO: Maybe give the project settings editor it's own copy of the config then.
ProjectConfig tempProject = projectConfig;
projectConfig.setVersionSpecificDefaults(BaseGame::stringToVersion(versionText));
projectConfig.setVersionSpecificDefaults(getBaseGameVersion());
this->refresh();
projectConfig = tempProject;

View File

@ -115,42 +115,15 @@ bool RegionMapEditor::saveRegionMapEntries() {
return true;
}
void buildEmeraldDefaults(poryjson::Json &json) {
ParseUtil parser;
QString emeraldDefault = parser.readTextFile(":/text/region_map_default_emerald.json");
json = poryjson::Json::parse(emeraldDefault);
}
void buildRubyDefaults(poryjson::Json &json) {
ParseUtil parser;
QString emeraldDefault = parser.readTextFile(":/text/region_map_default_ruby.json");
json = poryjson::Json::parse(emeraldDefault);
}
void buildFireredDefaults(poryjson::Json &json) {
ParseUtil parser;
QString fireredDefault = parser.readTextFile(":/text/region_map_default_firered.json");
json = poryjson::Json::parse(fireredDefault);
}
poryjson::Json RegionMapEditor::buildDefaultJson() {
poryjson::Json defaultJson;
switch (projectConfig.baseGameVersion) {
case BaseGame::Version::pokeemerald:
buildEmeraldDefaults(defaultJson);
break;
case BaseGame::Version::pokeruby:
buildRubyDefaults(defaultJson);
break;
case BaseGame::Version::pokefirered:
buildFireredDefaults(defaultJson);
break;
default:
break;
}
return defaultJson;
static const QMap<BaseGame::Version, QString> versionToJsonPath = {
{BaseGame::Version::pokeemerald, QStringLiteral(":/text/region_map_default_emerald.json")},
{BaseGame::Version::pokeruby, QStringLiteral(":/text/region_map_default_ruby.json")},
{BaseGame::Version::pokefirered, QStringLiteral(":/text/region_map_default_firered.json")},
};
const QString path = versionToJsonPath.value(projectConfig.baseGameVersion);
if (path.isEmpty()) { Q_ASSERT(false); return OrderedJson::object(); }
return OrderedJson::parse(ParseUtil::readTextFile(path));
}
bool RegionMapEditor::buildConfigDialog() {
@ -276,6 +249,8 @@ bool RegionMapEditor::buildConfigDialog() {
// for sake of convenience, option to just use defaults for each basegame version
// TODO: Each version's default settings should be available regardless of the base game version,
// it can just suggest which default settings to use depending on the base game version.
QPushButton *config_useProjectDefault = nullptr;
switch (projectConfig.baseGameVersion) {
case BaseGame::Version::pokefirered:
@ -288,6 +263,8 @@ bool RegionMapEditor::buildConfigDialog() {
config_useProjectDefault = new QPushButton("\nUse pokeruby defaults\n");
break;
default:
config_useProjectDefault = new QPushButton("\nNo default settings available\n");
config_useProjectDefault->setEnabled(false);
break;
}
form.addRow(config_useProjectDefault);