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))