From b7e19a427db992b1c2f7c066b809d4b540ff4b86 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 22 Apr 2025 15:57:17 -0400 Subject: [PATCH] Add global constants settings --- forms/newdefinedialog.ui | 89 ++++++++++++++++++++++++ forms/projectsettingseditor.ui | 92 +++++++++++++++++++----- include/config.h | 2 + include/core/parseutil.h | 3 +- include/ui/newdefinedialog.h | 32 +++++++++ include/ui/projectsettingseditor.h | 6 ++ porymap.pro | 3 + src/config.cpp | 22 ++++-- src/core/parseutil.cpp | 10 ++- src/project.cpp | 3 +- src/ui/newdefinedialog.cpp | 60 ++++++++++++++++ src/ui/projectsettingseditor.cpp | 108 +++++++++++++++++++++++++++++ 12 files changed, 402 insertions(+), 28 deletions(-) create mode 100644 forms/newdefinedialog.ui create mode 100644 include/ui/newdefinedialog.h create mode 100644 src/ui/newdefinedialog.cpp diff --git a/forms/newdefinedialog.ui b/forms/newdefinedialog.ui new file mode 100644 index 00000000..c816c6e3 --- /dev/null +++ b/forms/newdefinedialog.ui @@ -0,0 +1,89 @@ + + + NewDefineDialog + + + + 0 + 0 + 252 + 124 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Name + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Value + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 4cf06515..fbcaa312 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -21,7 +21,7 @@ - 0 + 4 @@ -1582,19 +1582,21 @@ 499 - - - - - ... + + + + + Qt::Orientation::Horizontal - - - :/icons/help.ico:/icons/help.ico + + + 40 + 20 + - + - + @@ -1626,7 +1628,7 @@ 0 0 544 - 437 + 338 @@ -1646,6 +1648,34 @@ + + + + <html><head/><body><p>Add additional C files containing #defines or enums. These will be used to resolve unknown symbols during project launch.</p></body></html> + + + Add Global Constants File... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + + + + @@ -1671,8 +1701,8 @@ 499 - - + + ... @@ -1683,7 +1713,7 @@ - + @@ -1715,7 +1745,7 @@ 0 0 544 - 437 + 421 @@ -1735,6 +1765,36 @@ + + + + <html><head/><body><p>Add an additional #define name and expression. This may be used to evaluate other #defines during project launch.</p></body></html> + + + Add Global Constant... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + diff --git a/include/config.h b/include/config.h index 4f1aeada..26d2a353 100644 --- a/include/config.h +++ b/include/config.h @@ -346,6 +346,7 @@ public: this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; this->globalConstantsFilepaths.clear(); + this->globalConstants.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -419,6 +420,7 @@ public: QList warpBehaviors; int maxEventsPerGroup; QStringList globalConstantsFilepaths; + QMap globalConstants; protected: virtual QString getConfigFilepath() override; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 58b5d0ab..59d4ae49 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -58,7 +58,8 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); - void loadGlobalCDefines(const QString &filename, QString *error = nullptr); + void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); + void loadGlobalCDefines(const QMap &defines); void resetGlobalCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); diff --git a/include/ui/newdefinedialog.h b/include/ui/newdefinedialog.h new file mode 100644 index 00000000..2107dcec --- /dev/null +++ b/include/ui/newdefinedialog.h @@ -0,0 +1,32 @@ +#ifndef NEWDEFINEDIALOG_H +#define NEWDEFINEDIALOG_H + +#include +#include + +namespace Ui { +class NewDefineDialog; +} + +class NewDefineDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewDefineDialog(QWidget *parent = nullptr); + ~NewDefineDialog(); + + virtual void accept() override; + +signals: + void createdDefine(const QString &name, const QString &expression); + +private: + Ui::NewDefineDialog *ui; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWDEFINEDIALOG_H diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index e4a6ae94..579aec21 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -67,6 +67,12 @@ private: void setWarpBehaviorsList(QStringList list); void openFilesHelp(); void openIdentifiersHelp(); + void addNewGlobalConstantsFilepath(); + void addGlobalConstantsFilepath(const QString &filepath); + QStringList getGlobalConstantsFilepaths(); + void addNewGlobalConstant(); + void addGlobalConstant(const QString &name, const QString &expression); + QMap getGlobalConstants(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/porymap.pro b/porymap.pro index 1cdffddf..601b2f81 100644 --- a/porymap.pro +++ b/porymap.pro @@ -104,6 +104,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ + src/ui/newdefinedialog.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ @@ -216,6 +217,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ + include/ui/newdefinedialog.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ @@ -269,6 +271,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newdefinedialog.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ forms/newlocationdialog.ui \ diff --git a/src/config.cpp b/src/config.cpp index 7e7422c7..0e22840e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -234,7 +234,7 @@ void KeyValueConfigBase::load() { continue; } - this->parseConfigKeyValue(match.captured("key").trimmed().toLower(), match.captured("value").trimmed()); + this->parseConfigKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed()); } this->setUnreadKeys(); @@ -840,6 +840,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } + } else if (key.startsWith("global_constant/")) { + this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value); + } else if (key == "global_constants_filepaths") { + this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts); } else if (key == "prefabs_filepath") { this->prefabFilepath = value; } else if (key == "prefabs_import_prompted") { @@ -863,7 +867,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "event_icon_path_heal") { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { - this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()).toUpper(), value); + this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { @@ -970,12 +974,16 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("event_icon_path_coord", this->eventIconPaths[Event::Group::Coord]); map.insert("event_icon_path_bg", this->eventIconPaths[Event::Group::Bg]); map.insert("event_icon_path_heal", this->eventIconPaths[Event::Group::Heal]); - for (auto i = this->pokemonIconPaths.cbegin(), end = this->pokemonIconPaths.cend(); i != end; i++){ - const QString path = i.value(); - if (!path.isEmpty()) map.insert("pokemon_icon_path/" + i.key(), path); + for (auto it = this->pokemonIconPaths.constBegin(); it != this->pokemonIconPaths.constEnd(); it++) { + const QString path = it.value(); + if (!path.isEmpty()) map.insert("pokemon_icon_path/" + it.key(), path); } - for (auto i = this->identifiers.cbegin(), end = this->identifiers.cend(); i != end; i++) { - map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); + for (auto it = this->globalConstants.constBegin(); it != this->globalConstants.constEnd(); it++) { + map.insert("global_constant/" + it.key(), it.value()); + } + map.insert("global_constants_filepaths", this->globalConstantsFilepaths.join(",")); + for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { + map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); } map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 30146284..3a8adf93 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -483,7 +483,7 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS const QString expression = defines.expressions.take(name); if (expression == " ") continue; this->curDefine = name; - filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); + filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); // TODO: Unite map with global expressions? Allows users to overwrite project defines logRecordedErrors(); // Only log errors for defines that Porymap is looking for } @@ -509,8 +509,12 @@ QStringList ParseUtil::readCDefineNames(const QString &filename, const QSetglobalDefineExpressions.insert(readCDefines(filename, {}, false, error).expressions); +void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *error) { + loadGlobalCDefines(readCDefines(filename, {}, false, error).expressions); +} + +void ParseUtil::loadGlobalCDefines(const QMap &defines) { + this->globalDefineExpressions.insert(defines); } void ParseUtil::resetGlobalCDefines() { diff --git a/src/project.cpp b/src/project.cpp index 5abc2036..33cac9ce 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2747,11 +2747,12 @@ bool Project::readGlobalConstants() { this->parser.resetGlobalCDefines(); for (const auto &path : projectConfig.globalConstantsFilepaths) { QString error; - this->parser.loadGlobalCDefines(path, &error); + this->parser.loadGlobalCDefinesFromFile(path, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read global constants file '%1': %2").arg(path).arg(error)); } } + this->parser.loadGlobalCDefines(projectConfig.globalConstants); return true; } diff --git a/src/ui/newdefinedialog.cpp b/src/ui/newdefinedialog.cpp new file mode 100644 index 00000000..567e4985 --- /dev/null +++ b/src/ui/newdefinedialog.cpp @@ -0,0 +1,60 @@ +#include "newdefinedialog.h" +#include "ui_newdefinedialog.h" +#include "validator.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewDefineDialog::NewDefineDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewDefineDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + ui->lineEdit_Name->setValidator(new IdentifierValidator(this)); + + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewDefineDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewDefineDialog::dialogButtonClicked); + + adjustSize(); +} + +NewDefineDialog::~NewDefineDialog() +{ + delete ui; +} + +void NewDefineDialog::onNameChanged(const QString &) { + validateName(true); +} + +bool NewDefineDialog::validateName(bool allowEmpty) { + const QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (name.isEmpty() && !allowEmpty) { + errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewDefineDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewDefineDialog::accept() { + if (!validateName()) + return; + emit createdDefine(ui->lineEdit_Name->text(), ui->lineEdit_Value->text()); + QDialog::accept(); +} diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index ccbf1ec3..13b6198f 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -3,6 +3,7 @@ #include "noscrollcombobox.h" #include "prefab.h" #include "filedialog.h" +#include "newdefinedialog.h" #include "utility.h" #include @@ -54,6 +55,9 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_AddWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(true); }); connect(ui->button_RemoveWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(false); }); + connect(ui->button_AddGlobalConstantsFile, &QAbstractButton::clicked, this, &ProjectSettingsEditor::addNewGlobalConstantsFilepath); + connect(ui->button_AddGlobalConstant, &QAbstractButton::clicked, this, &ProjectSettingsEditor::addNewGlobalConstant); + // Connect file selection buttons connect(ui->button_ChoosePrefabs, &QAbstractButton::clicked, [this](bool) { this->choosePrefabsFile(); }); connect(ui->button_CollisionGraphics, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_CollisionGraphics); }); @@ -501,6 +505,12 @@ void ProjectSettingsEditor::refresh() { lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName())); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) lineEdit->setText(projectConfig.getCustomIdentifier(lineEdit->objectName())); + for (const auto &path : projectConfig.globalConstantsFilepaths) { + addGlobalConstantsFilepath(path); + } + for (auto it = projectConfig.globalConstants.constBegin(); it != projectConfig.globalConstants.constEnd(); it++) { + addGlobalConstant(it.key(), it.value()); + } // Set warp behaviors QStringList behaviorNames; @@ -578,6 +588,10 @@ void ProjectSettingsEditor::save() { for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) projectConfig.setIdentifier(lineEdit->objectName(), lineEdit->text()); + // Save global constants + projectConfig.globalConstantsFilepaths = getGlobalConstantsFilepaths(); + projectConfig.globalConstants = getGlobalConstants(); + // Save warp behaviors projectConfig.warpBehaviors.clear(); const QStringList behaviorNames = this->getWarpBehaviorsList(); @@ -624,6 +638,100 @@ void ProjectSettingsEditor::chooseFile(QLineEdit * filepathEdit, const QString & this->hasUnsavedChanges = true; } +void ProjectSettingsEditor::addNewGlobalConstantsFilepath() { + QString filepath = stripProjectDir(FileDialog::getOpenFileName(this, "Choose Global Constants File")); + if (filepath.isEmpty() || getGlobalConstantsFilepaths().contains(filepath)) + return; + + addGlobalConstantsFilepath(filepath); + this->hasUnsavedChanges = true; +} + +void ProjectSettingsEditor::addGlobalConstantsFilepath(const QString &filepath) { + auto filepathLabel = new QLabel(filepath, this); + filepathLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); + filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + + // TODO: Tool tips + + int newRow = ui->gridLayout_GlobalConstantsFiles->rowCount(); + ui->gridLayout_GlobalConstantsFiles->addWidget(filepathLabel, newRow, 0); + + auto deleteButton = new QToolButton(); + deleteButton->setIcon(QIcon(":/icons/delete.ico")); + connect(deleteButton, &QAbstractButton::clicked, [this, filepathLabel, deleteButton](bool) { + ui->gridLayout_GlobalConstantsFiles->removeWidget(filepathLabel); + ui->gridLayout_GlobalConstantsFiles->removeWidget(deleteButton); + delete filepathLabel; + delete deleteButton; + this->hasUnsavedChanges = true; + }); + ui->gridLayout_GlobalConstantsFiles->addWidget(deleteButton, newRow, 1); +} + +QStringList ProjectSettingsEditor::getGlobalConstantsFilepaths() { + QStringList paths; + for (int row = 1; row < ui->gridLayout_GlobalConstantsFiles->rowCount(); row++) { + auto item = ui->gridLayout_GlobalConstantsFiles->itemAtPosition(row, 0); + if (!item) continue; + auto pathLabel = dynamic_cast(item->widget()); + if (!pathLabel) continue; + paths.append(pathLabel->text()); + } + return paths; +} + +void ProjectSettingsEditor::addNewGlobalConstant() { + auto dialog = new NewDefineDialog(this); + connect(dialog, &NewDefineDialog::createdDefine, [this](const QString &name, const QString &expression) { + if (!getGlobalConstants().contains(name)) { + addGlobalConstant(name, expression); + this->hasUnsavedChanges = true; + } + }); + dialog->open(); +} + +void ProjectSettingsEditor::addGlobalConstant(const QString &name, const QString &expression) { + // TODO: Tool tips + auto nameLabel = new QLabel(name, this); + nameLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); + nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + + auto expressionLineEdit = new QLineEdit(expression, this); + + int newRow = ui->gridLayout_GlobalConstants->rowCount(); + ui->gridLayout_GlobalConstants->addWidget(nameLabel, newRow, 0); + ui->gridLayout_GlobalConstants->addWidget(expressionLineEdit, newRow, 1); + + auto deleteButton = new QToolButton(); + deleteButton->setIcon(QIcon(":/icons/delete.ico")); + connect(deleteButton, &QAbstractButton::clicked, [this, nameLabel, expressionLineEdit, deleteButton](bool) { + ui->gridLayout_GlobalConstants->removeWidget(nameLabel); + ui->gridLayout_GlobalConstants->removeWidget(expressionLineEdit); + ui->gridLayout_GlobalConstants->removeWidget(deleteButton); + delete nameLabel; + delete expressionLineEdit; + delete deleteButton; + this->hasUnsavedChanges = true; + }); + ui->gridLayout_GlobalConstants->addWidget(deleteButton, newRow, 2); +} + +QMap ProjectSettingsEditor::getGlobalConstants() { + QMap constants; + for (int row = 1; row < ui->gridLayout_GlobalConstants->rowCount(); row++) { + auto nameItem = ui->gridLayout_GlobalConstants->itemAtPosition(row, 0); + auto expressionItem = ui->gridLayout_GlobalConstants->itemAtPosition(row, 1); + if (!nameItem || !expressionItem) continue; + auto nameLabel = dynamic_cast(nameItem->widget()); + auto expressionLineEdit = dynamic_cast(expressionItem->widget()); + if (!nameLabel || !expressionLineEdit) continue; + constants.insert(nameLabel->text(), expressionLineEdit->text()); + } + return constants; +} + // Display relative path if this file is in the project folder QString ProjectSettingsEditor::stripProjectDir(QString s) { if (s.startsWith(this->baseDir))