From 1b510b6a6eef7cd534707922c653c8e698f10857 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 14:54:44 -0500 Subject: [PATCH 1/5] Add utility.cpp, fix bug when map/layout name is just underscores --- include/core/map.h | 3 ++- include/core/maplayout.h | 4 +--- include/core/utility.h | 13 +++++++++++ include/project.h | 2 -- porymap.pro | 2 ++ src/core/map.cpp | 13 ++++------- src/core/maplayout.cpp | 11 ++++----- src/core/utility.cpp | 40 ++++++++++++++++++++++++++++++++ src/project.cpp | 24 ++++++------------- src/ui/movablerect.cpp | 9 +++---- src/ui/projectsettingseditor.cpp | 3 ++- src/ui/resizelayoutpopup.cpp | 6 ++--- src/ui/wildmonchart.cpp | 9 ++----- 13 files changed, 82 insertions(+), 57 deletions(-) create mode 100644 include/core/utility.h create mode 100644 src/core/utility.cpp diff --git a/include/core/map.h b/include/core/map.h index b223536d..3df0ed88 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -45,7 +45,8 @@ public: void setConstantName(const QString &constantName) { m_constantName = constantName; } QString constantName() const { return m_constantName; } - static QString mapConstantFromName(QString mapName, bool includePrefix = true); + static QString mapConstantFromName(const QString &name); + QString expectedConstantName() const { return Map::mapConstantFromName(m_name); } void setLayout(Layout *layout); Layout* layout() const { return m_layout; } diff --git a/include/core/maplayout.h b/include/core/maplayout.h index da58c4b7..f8fb1b1c 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -20,9 +20,7 @@ public: Layout() {} Layout(const Layout &other); - static QString layoutConstantFromName(QString mapName); - static QString defaultSuffix(); - + static QString layoutConstantFromName(const QString &name); bool loaded = false; diff --git a/include/core/utility.h b/include/core/utility.h new file mode 100644 index 00000000..b1dbe8bc --- /dev/null +++ b/include/core/utility.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef UTILITY_H +#define UTILITY_H + +#include + +namespace Util { + void numericalModeSort(QStringList &list); + int roundUp(int numToRound, int multiple); + QString toDefineCase(QString input); +} + +#endif // UTILITY_H diff --git a/include/project.h b/include/project.h index f3a1093b..5019a147 100644 --- a/include/project.h +++ b/include/project.h @@ -248,8 +248,6 @@ public: static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); - static void numericalModeSort(QStringList &list); - private: QMap mapSectionDisplayNames; QMap modifiedFileTimestamps; diff --git a/porymap.pro b/porymap.pro index 97265940..2675e700 100644 --- a/porymap.pro +++ b/porymap.pro @@ -51,6 +51,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/core/parseutil.cpp \ src/core/tile.cpp \ src/core/tileset.cpp \ + src/core/utility.cpp \ src/core/validator.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ @@ -162,6 +163,7 @@ HEADERS += include/core/advancemapparser.h \ include/core/parseutil.h \ include/core/tile.h \ include/core/tileset.h \ + include/core/utility.h \ include/core/validator.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ diff --git a/src/core/map.cpp b/src/core/map.cpp index d2ab8ef6..a77744b3 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -2,7 +2,7 @@ #include "map.h" #include "imageproviders.h" #include "scripting.h" - +#include "utility.h" #include "editcommands.h" #include @@ -56,14 +56,9 @@ void Map::setLayout(Layout *layout) { } } -QString Map::mapConstantFromName(QString mapName, bool includePrefix) { - // Transform map names of the form 'GraniteCave_B1F` into map constants like 'MAP_GRANITE_CAVE_B1F'. - static const QRegularExpression caseChange("([a-z])([A-Z])"); - QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); - const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : ""; - QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper(); - static const QRegularExpression underscores("_+"); - return withMapAndUppercase.replace(underscores, "_"); +// We don't enforce this for existing maps, but for creating new maps we need to formulaically generate a new MAP_NAME ID. +QString Map::mapConstantFromName(const QString &name) { + return projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) + Util::toDefineCase(name); } int Map::getWidth() const { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 2b52a80f..54f630f7 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -4,6 +4,7 @@ #include "scripting.h" #include "imageproviders.h" +#include "utility.h" Layout::Layout(const Layout &other) : Layout() { copyFrom(&other); @@ -32,13 +33,9 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -QString Layout::layoutConstantFromName(QString mapName) { - // Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'. - static const QRegularExpression caseChange("([a-z])([A-Z])"); - QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); - QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper(); - static const QRegularExpression underscores("_+"); - return withMapAndUppercase.replace(underscores, "_"); +QString Layout::layoutConstantFromName(const QString &name) { + // TODO: Expose "LAYOUT_" to config + return "LAYOUT_" + Util::toDefineCase(name); } Layout::Settings Layout::settings() const { diff --git a/src/core/utility.cpp b/src/core/utility.cpp new file mode 100644 index 00000000..60f0089d --- /dev/null +++ b/src/core/utility.cpp @@ -0,0 +1,40 @@ +#include "utility.h" + +#include +#include + +// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. +// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable +// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). +// We can use QCollator to sort these lists with better handling for numbers. +void Util::numericalModeSort(QStringList &list) { + static QCollator collator; + collator.setNumericMode(true); + std::sort(list.begin(), list.end(), collator); +} + +int Util::roundUp(int numToRound, int multiple) { + if (multiple <= 0) + return numToRound; + + int remainder = abs(numToRound) % multiple; + if (remainder == 0) + return numToRound; + + if (numToRound < 0) + return -(abs(numToRound) - remainder); + else + return numToRound + multiple - remainder; +} + +// Ex: input 'GraniteCave_B1F' returns 'GRANITE_CAVE_B1F'. +QString Util::toDefineCase(QString input) { + static const QRegularExpression re_CaseChange("([a-z])([A-Z])"); + input.replace(re_CaseChange, "\\1_\\2"); + + // Remove sequential underscores + static const QRegularExpression re_Underscores("_+"); + input.replace(re_Underscores, "_"); + + return input.toUpper(); +} diff --git a/src/project.cpp b/src/project.cpp index 38b1810b..5e839c8e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -10,6 +10,7 @@ #include "filedialog.h" #include "validator.h" #include "orderedjson.h" +#include "utility.h" #include #include @@ -349,7 +350,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setNeedsHealLocation(settings.canFlyTo); // Generate a unique MAP constant. - map->setConstantName(toUniqueIdentifier(Map::mapConstantFromName(map->name()))); + map->setConstantName(toUniqueIdentifier(map->expectedConstantName())); Layout *layout = this->mapLayouts.value(settings.layout.id); if (!layout) { @@ -2074,8 +2075,8 @@ bool Project::readTilesetLabels() { } } - numericalModeSort(this->primaryTilesetLabels); - numericalModeSort(this->secondaryTilesetLabels); + Util::numericalModeSort(this->primaryTilesetLabels); + Util::numericalModeSort(this->secondaryTilesetLabels); bool success = true; if (this->secondaryTilesetLabels.isEmpty()) { @@ -2348,7 +2349,7 @@ bool Project::readRegionMapSections() { if (!this->mapSectionIdNames.contains(defaultName)) { this->mapSectionIdNames.append(defaultName); } - numericalModeSort(this->mapSectionIdNames); + Util::numericalModeSort(this->mapSectionIdNames); return true; } @@ -2372,7 +2373,7 @@ void Project::addNewMapsec(const QString &idName) { } this->mapSectionIdNames.append(idName); - numericalModeSort(this->mapSectionIdNames); + Util::numericalModeSort(this->mapSectionIdNames); this->hasUnsavedDataChanges = true; @@ -2596,7 +2597,7 @@ bool Project::readSongNames() { // Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically. // The default song should be the first in the list, not the first alphabetically, so save that before sorting. this->defaultSong = this->songNames.value(0, "0"); - numericalModeSort(this->songNames); + Util::numericalModeSort(this->songNames); return true; } @@ -3058,14 +3059,3 @@ bool Project::hasUnsavedChanges() { } return false; } - -// TODO: This belongs in a more general utility file, once we have one. -// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. -// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable -// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). -// We can use QCollator to sort these lists with better handling for numbers. -void Project::numericalModeSort(QStringList &list) { - QCollator collator; - collator.setNumericMode(true); - std::sort(list.begin(), list.end(), collator); -} diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index ba323185..fde7f820 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -3,6 +3,7 @@ #include #include "movablerect.h" +#include "utility.h" MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color) : QGraphicsRectItem(0, 0, width, height) @@ -22,10 +23,6 @@ void MovableRect::updateLocation(int x, int y) { ************************************************************************ ******************************************************************************/ -int roundUp(int numToRound, int multiple) { - return (numToRound + multiple - 1) & -multiple; -} - ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) : QObject(parent), MovableRect(enabled, width * 16, height * 16, color) @@ -117,8 +114,8 @@ void ResizableRect::mousePressEvent(QGraphicsSceneMouseEvent *event) { } void ResizableRect::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { - int dx = roundUp(event->scenePos().x() - this->clickedPos.x(), 16); - int dy = roundUp(event->scenePos().y() - this->clickedPos.y(), 16); + int dx = Util::roundUp(event->scenePos().x() - this->clickedPos.x(), 16); + int dy = Util::roundUp(event->scenePos().y() - this->clickedPos.y(), 16); QRect resizedRect = this->clickedRect; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 1250e8dd..c536fd07 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 "utility.h" #include #include @@ -293,7 +294,7 @@ QStringList ProjectSettingsEditor::getWarpBehaviorsList() { void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) { list.removeDuplicates(); - Project::numericalModeSort(list); + Util::numericalModeSort(list); ui->textEdit_WarpBehaviors->setText(list.join("\n")); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 45559b70..20a378b9 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -2,12 +2,10 @@ #include "editor.h" #include "movablerect.h" #include "config.h" +#include "utility.h" #include "ui_resizelayoutpopup.h" -// TODO: put this in a util file or something -extern int roundUp(int, int); - CheckeredBgScene::CheckeredBgScene(QObject *parent) : QGraphicsScene(parent) { } void CheckeredBgScene::drawBackground(QPainter *painter, const QRectF &rect) { @@ -62,7 +60,7 @@ void BoundedPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem QVariant BoundedPixmapItem::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == ItemPositionChange && scene()) { QPointF newPos = value.toPointF(); - return QPointF(roundUp(newPos.x(), 16), roundUp(newPos.y(), 16)); + return QPointF(Util::roundUp(newPos.x(), 16), Util::roundUp(newPos.y(), 16)); } else return QGraphicsItem::itemChange(change, value); diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 69b7b17e..521706d6 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -2,6 +2,7 @@ #include "wildmonchart.h" #include "ui_wildmonchart.h" #include "config.h" +#include "utility.h" static const QString baseWindowTitle = QString("Wild Pokémon Summary Charts"); @@ -367,13 +368,7 @@ QChart* WildMonChart::createLevelDistributionChart() { series->attachAxis(axisY); // We round the y-axis max up to a multiple of 5. - auto roundUp = [](int num, int multiple) { - auto remainder = num % multiple; - if (remainder == 0) - return num; - return num + multiple - remainder; - }; - axisY->setMax(roundUp(qCeil(axisY->max()), 5)); + axisY->setMax(Util::roundUp(qCeil(axisY->max()), 5)); return chart; } From 11d9d7b7950001b7d8e38e8ae50b1224323445d6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 22 Feb 2025 15:09:06 -0500 Subject: [PATCH 2/5] Add layout prefix to config, fix species prefix hardcoded length --- docsrc/manual/project-files.rst | 1 + include/config.h | 1 + src/config.cpp | 1 + src/core/maplayout.cpp | 3 +-- src/project.cpp | 5 +++-- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 5510c61d..d4c47950 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -109,6 +109,7 @@ In addition to these files, there are some specific symbol and macro names that ``define_attribute_encounter``, ``METATILE_ATTRIBUTE_ENCOUNTER_TYPE``, name used to extract setting from ``symbol_attribute_table`` ``define_metatile_label_prefix``, ``METATILE_``, expected prefix for metatile label macro names ``define_heal_locations_prefix``, ``HEAL_LOCATION_``, default prefix for heal location macro names + ``define_layout_prefix``, ``LAYOUT_``, default prefix for layout macro names ``define_map_prefix``, ``MAP_``, expected prefix for map macro names ``define_map_dynamic``, ``DYNAMIC``, macro name after prefix for Dynamic maps ``define_map_empty``, ``UNDEFINED``, macro name after prefix for empty maps diff --git a/include/config.h b/include/config.h index b05e4387..0cdc102d 100644 --- a/include/config.h +++ b/include/config.h @@ -216,6 +216,7 @@ enum ProjectIdentifier { define_attribute_encounter, define_metatile_label_prefix, define_heal_locations_prefix, + define_layout_prefix, define_map_prefix, define_map_dynamic, define_map_empty, diff --git a/src/config.cpp b/src/config.cpp index c4221d30..d8d2b821 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -101,6 +101,7 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_attribute_encounter, {"define_attribute_encounter", "METATILE_ATTRIBUTE_ENCOUNTER_TYPE"}}, {ProjectIdentifier::define_metatile_label_prefix, {"define_metatile_label_prefix", "METATILE_"}}, {ProjectIdentifier::define_heal_locations_prefix, {"define_heal_locations_prefix", "HEAL_LOCATION_"}}, + {ProjectIdentifier::define_layout_prefix, {"define_layout_prefix", "LAYOUT_"}}, {ProjectIdentifier::define_map_prefix, {"define_map_prefix", "MAP_"}}, {ProjectIdentifier::define_map_dynamic, {"define_map_dynamic", "DYNAMIC"}}, {ProjectIdentifier::define_map_empty, {"define_map_empty", "UNDEFINED"}}, diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 54f630f7..110db91c 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -34,8 +34,7 @@ void Layout::copyFrom(const Layout *other) { } QString Layout::layoutConstantFromName(const QString &name) { - // TODO: Expose "LAYOUT_" to config - return "LAYOUT_" + Util::toDefineCase(name); + return projectConfig.getIdentifier(ProjectIdentifier::define_layout_prefix) + Util::toDefineCase(name); } Layout::Settings Layout::settings() const { diff --git a/src/project.cpp b/src/project.cpp index 5e839c8e..4ae42686 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2804,7 +2804,8 @@ bool Project::readSpeciesIconPaths() { const QMap iconIncbins = parser.readCIncbinMulti(incfilename); // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). - const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix))}; + const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); + const QStringList regexList = {QString("\\b%1").arg(speciesPrefix)}; const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); fileWatcher.addPath(root + "/" + constantsFilename); QStringList speciesNames = parser.readCDefineNames(constantsFilename, regexList); @@ -2832,7 +2833,7 @@ bool Project::readSpeciesIconPaths() { } // Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz' - possibleDirNames.append(species.mid(8).toLower()); + possibleDirNames.append(species.mid(speciesPrefix.length()).toLower()); // Permute paths with underscores. // Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo' From ded9f724dc21ab390e3a7d7f447e8237f1215edb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 13:10:57 -0500 Subject: [PATCH 3/5] Parser filter lists to QSet --- include/core/parseutil.h | 14 +++++------ src/core/parseutil.cpp | 18 +++++++------- src/project.cpp | 51 ++++++++++++++-------------------------- 3 files changed, 34 insertions(+), 49 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index b8ba9f55..b5fc5dd2 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -54,9 +54,9 @@ public: QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error = nullptr); - QMap readCDefinesByName(const QString &filename, const QStringList &names, QString *error = nullptr); - QStringList readCDefineNames(const QString &filename, const QStringList ®exList, QString *error = nullptr); + 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); tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -101,10 +101,10 @@ private: QMap expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; - ParsedDefines readCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error); - QMap evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error); - bool defineNameMatchesFilter(const QString &name, const QStringList &filterList) const; - bool defineNameMatchesFilter(const QString &name, const QList &filterList) const; + ParsedDefines readCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + QMap evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; + bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; static const QRegularExpression re_incScriptLabel; static const QRegularExpression re_globalIncScriptLabel; diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9769d86e..22880562 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -368,11 +368,11 @@ QStringList ParseUtil::readCIncbinArray(const QString &filename, const QString & return paths; } -bool ParseUtil::defineNameMatchesFilter(const QString &name, const QStringList &filterList) const { +bool ParseUtil::defineNameMatchesFilter(const QString &name, const QSet &filterList) const { return filterList.contains(name); } -bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList &filterList) const { +bool ParseUtil::defineNameMatchesFilter(const QString &name, const QSet &filterList) const { for (auto filter : filterList) { if (filter.match(name).hasMatch()) return true; @@ -380,7 +380,7 @@ bool ParseUtil::defineNameMatchesFilter(const QString &name, const QList &filterList, bool useRegex, QString *error) { ParsedDefines result; this->file = filename; @@ -402,10 +402,10 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const return result; // If necessary, construct regular expressions from filter list - QList filterList_Regex; + QSet filterList_Regex; if (useRegex) { for (auto filter : filterList) { - filterList_Regex.append(QRegularExpression(filter)); + filterList_Regex.insert(QRegularExpression(filter)); } } @@ -463,7 +463,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const } // Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on). -QMap ParseUtil::evaluateCDefines(const QString &filename, const QStringList &filterList, bool useRegex, QString *error) { +QMap ParseUtil::evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error) { ParsedDefines defines = readCDefines(filename, filterList, useRegex, error); // Evaluate defines @@ -483,19 +483,19 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS } // Find and evaluate a specific set of defines with known names. -QMap ParseUtil::readCDefinesByName(const QString &filename, const QStringList &names, QString *error) { +QMap ParseUtil::readCDefinesByName(const QString &filename, const QSet &names, QString *error) { return evaluateCDefines(filename, names, false, error); } // Find and evaluate an unknown list of defines with a known name pattern. -QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QStringList ®exList, QString *error) { +QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error) { return evaluateCDefines(filename, regexList, true, error); } // Find an unknown list of defines with a known name pattern. // Similar to readCDefinesByRegex, but for cases where we only need to show a list of define names. // We can skip evaluating any expressions (and by extension skip reporting any errors from this process). -QStringList ParseUtil::readCDefineNames(const QString &filename, const QStringList ®exList, QString *error) { +QStringList ParseUtil::readCDefineNames(const QString &filename, const QSet ®exList, QString *error) { return readCDefines(filename, regexList, true, error).filteredNames; } diff --git a/src/project.cpp b/src/project.cpp index 0832cb08..19af47c8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1496,7 +1496,7 @@ bool Project::readTilesetMetatileLabels() { QString metatileLabelsFilename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels); fileWatcher.addPath(root + "/" + metatileLabelsFilename); - const QStringList regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; + const QSet regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; QMap defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); for (QString label : defines.keys()) { @@ -2088,7 +2088,7 @@ bool Project::readFieldmapProperties() { const QString numPalsTotalName = projectConfig.getIdentifier(ProjectIdentifier::define_pals_total); const QString maxMapSizeName = projectConfig.getIdentifier(ProjectIdentifier::define_map_size); const QString numTilesPerMetatileName = projectConfig.getIdentifier(ProjectIdentifier::define_tiles_per_metatile); - const QStringList names = { + const QSet names = { numTilesPrimaryName, numTilesTotalName, numMetatilesPrimaryName, @@ -2172,7 +2172,7 @@ bool Project::readFieldmapMasks() { const QString elevationMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_elevation); const QString behaviorMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior); const QString layerTypeMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer); - const QStringList searchNames = { + const QSet searchNames = { metatileIdMaskName, collisionMaskName, elevationMaskName, @@ -2429,44 +2429,40 @@ bool Project::readHealLocations() { } bool Project::readItemNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_items); fileWatcher.addPath(root + "/" + filename); QString error; - this->itemNames = parser.readCDefineNames(filename, regexList, &error); + this->itemNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_items)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read item constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readFlagNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_flags); fileWatcher.addPath(root + "/" + filename); QString error; - this->flagNames = parser.readCDefineNames(filename, regexList, &error); + this->flagNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_flags)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read flag constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readVarNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_vars); fileWatcher.addPath(root + "/" + filename); QString error; - this->varNames = parser.readCDefineNames(filename, regexList, &error); + this->varNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_vars)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read var constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readMovementTypes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_event_movement); fileWatcher.addPath(root + "/" + filename); QString error; - this->movementTypes = parser.readCDefineNames(filename, regexList, &error); + this->movementTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_movement_types)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read movement type constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2483,33 +2479,30 @@ bool Project::readInitialFacingDirections() { } bool Project::readMapTypes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); fileWatcher.addPath(root + "/" + filename); QString error; - this->mapTypes = parser.readCDefineNames(filename, regexList, &error); + this->mapTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_map_types)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read map type constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readMapBattleScenes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_map_types); fileWatcher.addPath(root + "/" + filename); QString error; - this->mapBattleScenes = parser.readCDefineNames(filename, regexList, &error); + this->mapBattleScenes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_battle_scenes)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read map battle scene constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readWeatherNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); fileWatcher.addPath(root + "/" + filename); QString error; - this->weatherNames = parser.readCDefineNames(filename, regexList, &error); + this->weatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_weather)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read weather constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2519,11 +2512,10 @@ bool Project::readCoordEventWeatherNames() { if (!projectConfig.eventWeatherTriggerEnabled) return true; - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_weather); fileWatcher.addPath(root + "/" + filename); QString error; - this->coordEventWeatherNames = parser.readCDefineNames(filename, regexList, &error); + this->coordEventWeatherNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_coord_event_weather)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read coord event weather constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2533,33 +2525,30 @@ bool Project::readSecretBaseIds() { if (!projectConfig.eventSecretBaseEnabled) return true; - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_secret_bases); fileWatcher.addPath(root + "/" + filename); QString error; - this->secretBaseIds = parser.readCDefineNames(filename, regexList, &error); + this->secretBaseIds = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_secret_bases)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read secret base id constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readBgEventFacingDirections() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_event_bg); fileWatcher.addPath(root + "/" + filename); QString error; - this->bgEventFacingDirections = parser.readCDefineNames(filename, regexList, &error); + this->bgEventFacingDirections = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_sign_facing_directions)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read bg event facing direction constants from '%1': %2").arg(filename).arg(error)); return true; } bool Project::readTrainerTypes() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_trainer_types); fileWatcher.addPath(root + "/" + filename); QString error; - this->trainerTypes = parser.readCDefineNames(filename, regexList, &error); + this->trainerTypes = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_trainer_types)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read trainer type constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2569,11 +2558,10 @@ bool Project::readMetatileBehaviors() { this->metatileBehaviorMap.clear(); this->metatileBehaviorMapInverse.clear(); - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors); fileWatcher.addPath(root + "/" + filename); QString error; - QMap defines = parser.readCDefinesByRegex(filename, regexList, &error); + QMap defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); if (defines.isEmpty() && projectConfig.metatileBehaviorMask) { // Not having any metatile behavior names is ok (their values will be displayed instead) // but if the user's metatiles can have nonzero values then warn them, as they likely want names. @@ -2592,11 +2580,10 @@ bool Project::readMetatileBehaviors() { } bool Project::readSongNames() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}; const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_songs); fileWatcher.addPath(root + "/" + filename); QString error; - this->songNames = parser.readCDefineNames(filename, regexList, &error); + this->songNames = parser.readCDefineNames(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_music)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read song names from '%1': %2").arg(filename).arg(error)); @@ -2608,11 +2595,10 @@ bool Project::readSongNames() { } bool Project::readObjEventGfxConstants() { - const QStringList regexList = {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}; QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events); fileWatcher.addPath(root + "/" + filename); QString error; - this->gfxDefines = parser.readCDefinesByRegex(filename, regexList, &error); + this->gfxDefines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error)); return true; @@ -2952,10 +2938,9 @@ bool Project::readSpeciesIconPaths() { // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); - const QStringList regexList = {QString("\\b%1").arg(speciesPrefix)}; const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); fileWatcher.addPath(root + "/" + constantsFilename); - QStringList speciesNames = parser.readCDefineNames(constantsFilename, regexList); + QStringList speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); if (speciesNames.isEmpty()) speciesNames = monIconNames.keys(); From 6d8b4f21d8e326c290ecb83f4f4438016c09df70 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 13:32:48 -0500 Subject: [PATCH 4/5] Move hex string conversions to Util --- include/core/utility.h | 1 + include/ui/tilemaptileselector.h | 3 ++- src/config.cpp | 21 +++++++++++---------- src/core/metatile.cpp | 19 ++++++++++--------- src/core/utility.cpp | 6 +++++- src/editor.cpp | 2 +- src/project.cpp | 6 +++--- src/ui/noscrollcombobox.cpp | 3 ++- src/ui/regionmapeditor.cpp | 4 ++-- src/ui/tileseteditor.cpp | 5 ++--- 10 files changed, 39 insertions(+), 31 deletions(-) diff --git a/include/core/utility.h b/include/core/utility.h index b1dbe8bc..1b9277ab 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -8,6 +8,7 @@ namespace Util { void numericalModeSort(QStringList &list); int roundUp(int numToRound, int multiple); QString toDefineCase(QString input); + QString toHexString(uint32_t value, int minLength = 0); } #endif // UTILITY_H diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 867f6302..5c3b8dac 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -5,6 +5,7 @@ #include "selectablepixmapitem.h" #include "paletteutil.h" #include "imageproviders.h" +#include "utility.h" #include using std::shared_ptr; @@ -66,7 +67,7 @@ public: } virtual QString info() const { - return QString("Tile: 0x") + QString("%1 ").arg(this->id(), 4, 16, QChar('0')).toUpper(); + return QString("Tile: %1 ").arg(Util::toHexString(this->id(), 4)); } }; diff --git a/src/config.cpp b/src/config.cpp index 8c3e5c43..232d7e66 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -3,6 +3,7 @@ #include "shortcut.h" #include "map.h" #include "validator.h" +#include "utility.h" #include #include #include @@ -877,16 +878,16 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); - map.insert("metatile_behavior_mask", "0x" + QString::number(this->metatileBehaviorMask, 16).toUpper()); - map.insert("metatile_terrain_type_mask", "0x" + QString::number(this->metatileTerrainTypeMask, 16).toUpper()); - map.insert("metatile_encounter_type_mask", "0x" + QString::number(this->metatileEncounterTypeMask, 16).toUpper()); - map.insert("metatile_layer_type_mask", "0x" + QString::number(this->metatileLayerTypeMask, 16).toUpper()); - map.insert("block_metatile_id_mask", "0x" + QString::number(this->blockMetatileIdMask, 16).toUpper()); - map.insert("block_collision_mask", "0x" + QString::number(this->blockCollisionMask, 16).toUpper()); - map.insert("block_elevation_mask", "0x" + QString::number(this->blockElevationMask, 16).toUpper()); - map.insert("unused_tile_normal", "0x" + QString::number(this->unusedTileNormal, 16).toUpper()); - map.insert("unused_tile_covered", "0x" + QString::number(this->unusedTileCovered, 16).toUpper()); - map.insert("unused_tile_split", "0x" + QString::number(this->unusedTileSplit, 16).toUpper()); + map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); + map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask)); + map.insert("metatile_encounter_type_mask", Util::toHexString(this->metatileEncounterTypeMask)); + map.insert("metatile_layer_type_mask", Util::toHexString(this->metatileLayerTypeMask)); + map.insert("block_metatile_id_mask", Util::toHexString(this->blockMetatileIdMask)); + map.insert("block_collision_mask", Util::toHexString(this->blockCollisionMask)); + map.insert("block_elevation_mask", Util::toHexString(this->blockElevationMask)); + map.insert("unused_tile_normal", Util::toHexString(this->unusedTileNormal)); + map.insert("unused_tile_covered", Util::toHexString(this->unusedTileCovered)); + map.insert("unused_tile_split", Util::toHexString(this->unusedTileSplit)); map.insert("enable_map_allow_flags", QString::number(this->mapAllowFlagsEnabled)); map.insert("event_icon_path_object", this->eventIconPaths[Event::Group::Object]); map.insert("event_icon_path_warp", this->eventIconPaths[Event::Group::Warp]); diff --git a/src/core/metatile.cpp b/src/core/metatile.cpp index 5f79129c..09852a12 100644 --- a/src/core/metatile.cpp +++ b/src/core/metatile.cpp @@ -1,6 +1,7 @@ #include "metatile.h" #include "tileset.h" #include "project.h" +#include "utility.h" // Stores how each attribute should be laid out for all metatiles, according to the vanilla games. // Used to set default config values and import maps with AdvanceMap. @@ -42,7 +43,7 @@ QPoint Metatile::coordFromPixmapCoord(const QPointF &pixelCoord) { static int numMetatileIdChars = 4; QString Metatile::getMetatileIdString(uint16_t metatileId) { - return "0x" + QString("%1").arg(metatileId, numMetatileIdChars, 16, QChar('0')).toUpper(); + return Util::toHexString(metatileId, numMetatileIdChars); }; QString Metatile::getMetatileIdStrings(const QList metatileIds) { @@ -127,8 +128,8 @@ void Metatile::setLayout(Project * project) { if (behaviorMask && !project->metatileBehaviorMapInverse.isEmpty()) { uint32_t maxBehavior = project->metatileBehaviorMapInverse.lastKey(); if (packer.clamp(maxBehavior) != maxBehavior) - logWarn(QString("Metatile Behavior mask '0x%1' is insufficient to contain all available options.") - .arg(QString::number(behaviorMask, 16).toUpper())); + logWarn(QString("Metatile Behavior mask '%1' is insufficient to contain all available options.") + .arg(Util::toHexString(behaviorMask))); } attributePackers.insert(Metatile::Attr::Behavior, packer); @@ -136,8 +137,8 @@ void Metatile::setLayout(Project * project) { packer.setMask(terrainTypeMask); const uint32_t maxTerrainType = NUM_METATILE_TERRAIN_TYPES - 1; if (terrainTypeMask && packer.clamp(maxTerrainType) != maxTerrainType) { - logWarn(QString("Metatile Terrain Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(terrainTypeMask, 16).toUpper()) + logWarn(QString("Metatile Terrain Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(terrainTypeMask)) .arg(maxTerrainType + 1)); } attributePackers.insert(Metatile::Attr::TerrainType, packer); @@ -146,8 +147,8 @@ void Metatile::setLayout(Project * project) { packer.setMask(encounterTypeMask); const uint32_t maxEncounterType = NUM_METATILE_ENCOUNTER_TYPES - 1; if (encounterTypeMask && packer.clamp(maxEncounterType) != maxEncounterType) { - logWarn(QString("Metatile Encounter Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(encounterTypeMask, 16).toUpper()) + logWarn(QString("Metatile Encounter Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(encounterTypeMask)) .arg(maxEncounterType + 1)); } attributePackers.insert(Metatile::Attr::EncounterType, packer); @@ -156,8 +157,8 @@ void Metatile::setLayout(Project * project) { packer.setMask(layerTypeMask); const uint32_t maxLayerType = NUM_METATILE_LAYER_TYPES - 1; if (layerTypeMask && packer.clamp(maxLayerType) != maxLayerType) { - logWarn(QString("Metatile Layer Type mask '0x%1' is insufficient to contain all %2 available options.") - .arg(QString::number(layerTypeMask, 16).toUpper()) + logWarn(QString("Metatile Layer Type mask '%1' is insufficient to contain all %2 available options.") + .arg(Util::toHexString(layerTypeMask)) .arg(maxLayerType + 1)); } attributePackers.insert(Metatile::Attr::LayerType, packer); diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 60f0089d..1053f879 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -5,7 +5,7 @@ // Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. // QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable -// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). +// effect (e.g. 'ROUTE_1, ROUTE_10, ROUTE_2,...' instead of 'ROUTE_1, ROUTE_2,... ROUTE_10'). // We can use QCollator to sort these lists with better handling for numbers. void Util::numericalModeSort(QStringList &list) { static QCollator collator; @@ -38,3 +38,7 @@ QString Util::toDefineCase(QString input) { return input.toUpper(); } + +QString Util::toHexString(uint32_t value, int minLength) { + return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper(); +} diff --git a/src/editor.cpp b/src/editor.cpp index d61a4661..e9111770 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -987,7 +987,7 @@ QString Editor::getMetatileDisplayMessage(uint16_t metatileId) { if (label.size()) message += QString(" \"%1\"").arg(label); if (metatile && metatile->behavior() != 0) { // Skip MB_NORMAL - const QString behaviorStr = this->project->metatileBehaviorMapInverse.value(metatile->behavior(), "0x" + QString::number(metatile->behavior(), 16)); + const QString behaviorStr = this->project->metatileBehaviorMapInverse.value(metatile->behavior(), Util::toHexString(metatile->behavior())); message += QString(", Behavior: %1").arg(behaviorStr); } return message; diff --git a/src/project.cpp b/src/project.cpp index 19af47c8..749e2676 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2197,10 +2197,10 @@ bool Project::readFieldmapMasks() { return false; *value = static_cast(it.value()); if (*value != it.value()){ - logWarn(QString("Value for %1 truncated from '0x%2' to '0x%3'") + logWarn(QString("Value for %1 truncated from '%2' to '%3'") .arg(name) - .arg(QString::number(it.value(), 16).toUpper()) - .arg(QString::number(*value, 16).toUpper())); + .arg(Util::toHexString(it.value())) + .arg(Util::toHexString(*value))); } return true; }; diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index e6e21a4e..21de55a8 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -1,4 +1,5 @@ #include "noscrollcombobox.h" +#include "utility.h" #include #include @@ -82,7 +83,7 @@ void NoScrollComboBox::setNumberItem(int value) void NoScrollComboBox::setHexItem(uint32_t value) { - this->setItem(this->findData(value), "0x" + QString::number(value, 16).toUpper()); + this->setItem(this->findData(value), Util::toHexString(value)); } void NoScrollComboBox::setClearButtonEnabled(bool enabled) { diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 94b7c8c6..2febccd8 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -6,6 +6,7 @@ #include "shortcut.h" #include "config.h" #include "log.h" +#include "utility.h" #include #include @@ -793,8 +794,7 @@ void RegionMapEditor::onRegionMapTileSelectorSelectedTileChanged(unsigned id) { } void RegionMapEditor::onRegionMapTileSelectorHoveredTileChanged(unsigned tileId) { - QString message = QString("Tile: 0x") + QString("%1").arg(tileId, 4, 16, QChar('0')).toUpper(); - this->ui->statusbar->showMessage(message); + this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tileId, 4))); } void RegionMapEditor::onRegionMapTileSelectorHoveredTileCleared() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index f88e6205..835877da 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -10,6 +10,7 @@ #include "filedialog.h" #include "validator.h" #include "eventfilters.h" +#include "utility.h" #include #include #include @@ -420,9 +421,7 @@ void TilesetEditor::queueMetatileReload(uint16_t metatileId) { } void TilesetEditor::onHoveredTileChanged(uint16_t tile) { - QString message = QString("Tile: 0x%1") - .arg(QString("%1").arg(tile, 3, 16, QChar('0')).toUpper()); - this->ui->statusbar->showMessage(message); + this->ui->statusbar->showMessage(QString("Tile: %1").arg(Util::toHexString(tile, 3))); } void TilesetEditor::onHoveredTileCleared() { From e260c642b0ddeac9ceb47f432d01bdacc7f7357f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 27 Feb 2025 14:44:19 -0500 Subject: [PATCH 5/5] Speed improvements for Project::readSpeciesIconPaths --- include/project.h | 8 +- src/project.cpp | 167 ++++++++++++++++------------- src/ui/encountertabledelegates.cpp | 2 +- src/ui/projectsettingseditor.cpp | 8 +- src/ui/wildmonsearch.cpp | 2 +- 5 files changed, 106 insertions(+), 81 deletions(-) diff --git a/include/project.h b/include/project.h index 73bb5eeb..5330b792 100644 --- a/include/project.h +++ b/include/project.h @@ -51,6 +51,7 @@ public: QStringList itemNames; QStringList flagNames; QStringList varNames; + QStringList speciesNames; QStringList movementTypes; QStringList mapTypes; QStringList mapBattleScenes; @@ -142,8 +143,8 @@ public: QVector extraEncounterGroups; bool readSpeciesIconPaths(); - QPixmap getSpeciesIcon(const QString &species) const; - QMap speciesToIconPath; + QString getDefaultSpeciesIconPath(const QString &species); + QPixmap getSpeciesIcon(const QString &species); void addNewMapsec(const QString &idName); void removeMapsec(const QString &idName); @@ -255,6 +256,7 @@ private: QMap mapSectionDisplayNames; QMap modifiedFileTimestamps; QMap facingDirections; + QMap speciesToIconPath; struct EventGraphics { @@ -275,6 +277,8 @@ private: void ignoreWatchedFileTemporarily(QString filepath); void recordFileChange(const QString &filepath); + QString findSpeciesIconPath(const QStringList &names) const; + int maxEventsPerGroup; int maxObjectEvents; static int num_tiles_primary; diff --git a/src/project.cpp b/src/project.cpp index 749e2676..5ee65254 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2924,101 +2924,122 @@ QPixmap Project::getEventPixmap(Event::Group group) { bool Project::readSpeciesIconPaths() { this->speciesToIconPath.clear(); + this->speciesNames.clear(); // Read map of species constants to icon names const QString srcfilename = projectConfig.getFilePath(ProjectFilePath::pokemon_icon_table); - fileWatcher.addPath(root + "/" + srcfilename); + fileWatcher.addPath(this->root + "/" + srcfilename); const QString tableName = projectConfig.getIdentifier(ProjectIdentifier::symbol_pokemon_icon_table); const QMap monIconNames = parser.readNamedIndexCArray(srcfilename, tableName); - // Read map of icon names to filepaths - const QString incfilename = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx); - fileWatcher.addPath(root + "/" + incfilename); - const QMap iconIncbins = parser.readCIncbinMulti(incfilename); - // Read species constants. If this fails we can get them from the icon table (but we shouldn't rely on it). const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); const QString constantsFilename = projectConfig.getFilePath(ProjectFilePath::constants_species); - fileWatcher.addPath(root + "/" + constantsFilename); - QStringList speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); - if (speciesNames.isEmpty()) - speciesNames = monIconNames.keys(); + fileWatcher.addPath(this->root + "/" + constantsFilename); + this->speciesNames = parser.readCDefineNames(constantsFilename, {QString("\\b%1").arg(speciesPrefix)}); + if (this->speciesNames.isEmpty()) { + this->speciesNames = monIconNames.keys(); + } + this->speciesNames.sort(); - // For each species, use the information gathered above to find the icon image. - bool missingIcons = false; - for (auto species : speciesNames) { - QString path = QString(); - if (monIconNames.contains(species) && iconIncbins.contains(monIconNames.value(species))) { - // We have the icon filepath from the icon table - path = QString("%1/%2").arg(root).arg(this->fixGraphicPath(iconIncbins[monIconNames.value(species)])); - } else { - // Failed to read icon filepath from the icon table, check filepaths where icons are normally located. - // Try to use the icon name (if we have it) to determine the directory, then try the species name. - // The name permuting is overkill, but it's making up for some of the fragility in the way we find icon paths. - QStringList possibleDirNames; - if (monIconNames.contains(species)) { - // Ex: For 'gMonIcon_QuestionMark' try 'question_mark' - static const QRegularExpression re("([a-z])([A-Z0-9])"); - QString iconName = monIconNames.value(species); - iconName = iconName.mid(iconName.indexOf("_") + 1); // jump past prefix ('gMonIcon') - possibleDirNames.append(iconName.replace(re, "\\1_\\2").toLower()); - } - - // Ex: For 'SPECIES_FOO_BAR_BAZ' try 'foo_bar_baz' - possibleDirNames.append(species.mid(speciesPrefix.length()).toLower()); - - // Permute paths with underscores. - // Ex: Try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo' - QStringList permutedNames; - for (auto dir : possibleDirNames) { - if (!dir.contains("_")) continue; - for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) { - QString temp = dir; - permutedNames.prepend(temp.replace(i, 1, "/")); - permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last + // If we successfully found the species icon table we can use this data to get the filepath for each species icon. + // For any species not in the table, or if we failed to find the table at all, we will have to predict where the icon file is. + // That can require checking a lot of files (especially for projects with many species), so to save time on startup we only + // do this on request in Project::getDefaultSpeciesIconPath. + if (!monIconNames.isEmpty()) { + const QString iconGraphicsFile = projectConfig.getFilePath(ProjectFilePath::data_pokemon_gfx); + fileWatcher.addPath(this->root + "/" + iconGraphicsFile); + QMap iconNameToFilepath = parser.readCIncbinMulti(iconGraphicsFile); + + for (auto i = monIconNames.constBegin(); i != monIconNames.constEnd(); i++) { + QString path; + QString species = i.key(); + QString iconName = i.value(); + if (iconNameToFilepath.contains(iconName)) { + path = fixGraphicPath(iconNameToFilepath.value(iconName)); + } else { + // We have an icon name for this species, but we haven't found its filepath. + // Try to find the icon file using the full icon name, and the icon name if we assume it has a prefix. + // Ex: For 'gMonIcon_QuestionMark' search for files by permuting through directories using 'question_mark' and 'g_mon_icon_question_mark. + static const QRegularExpression re_caseChange("([a-z])([A-Z0-9])"); + QStringList dirNames; + if (iconName.contains("_")) { + QString iconNameNoPrefix = iconName.mid(iconName.indexOf("_") + 1); + dirNames.append(iconNameNoPrefix.replace(re_caseChange, "\\1_\\2").toLower()); } - permutedNames.prepend(dir.remove("_")); + QString iconNameWithPrefix = iconName; // Leave iconName unchanged by .replace + dirNames.append(iconNameWithPrefix.replace(re_caseChange, "\\1_\\2").toLower()); + path = iconNameToFilepath[iconName] = findSpeciesIconPath(dirNames); } - possibleDirNames.append(permutedNames); - - possibleDirNames.removeDuplicates(); - for (auto dir : possibleDirNames) { - if (dir.isEmpty()) continue; - const QString stdPath = QString("%1/%2%3/icon.png") - .arg(root) - .arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx)) - .arg(dir); - if (QFile::exists(stdPath)) { - // Icon found at a normal filepath - path = stdPath; - break; - } - } - - if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) { - // Failed to find icon, this species will use a placeholder icon. - logWarn(QString("Failed to find Pokémon icon for '%1'").arg(species)); - missingIcons = true; + if (!path.isEmpty()) { + this->speciesToIconPath.insert(species, QString("%1/%2").arg(this->root).arg(path)); } } - this->speciesToIconPath.insert(species, path); } - - // Logging this alongside every warning (if there are multiple) is obnoxious, just do it once at the end. - if (missingIcons) logInfo("Pokémon icon filepaths can be specified under 'Options->Project Settings'"); - return true; } -QPixmap Project::getSpeciesIcon(const QString &species) const { +QString Project::getDefaultSpeciesIconPath(const QString &species) { + if (this->speciesToIconPath.contains(species)) { + // We already know the icon path for this species (either because we read it from the project, or we found it already). + return this->speciesToIconPath.value(species); + } + if (!this->speciesNames.contains(species)) { + // Don't bother searching for a path if we don't recognize the species name. + return QString(); + } + + // Ex: For 'SPECIES_FOO_BAR_BAZ' search for files by permuting through directories using 'foo_bar_baz'. + const QString speciesPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix); + const QString path = findSpeciesIconPath({species.mid(speciesPrefix.length()).toLower()}); + this->speciesToIconPath.insert(species, path); + + // We failed to find a default icon path, this species will use a placeholder icon. + // If the user has no custom icon path for this species, tell them they can provide one. + if (path.isEmpty() && projectConfig.getPokemonIconPath(species).isEmpty()) { + logWarn(QString("Failed to find Pokémon icon for '%1'. The filepath can be specified under 'Options->Project Settings'").arg(species)); + } + return path; +} + +// The name permuting in here is overkill, but it's making up for some of the fragility in the way we find pokémon icon paths. +// For pokeemerald-expansion in particular this function is solely responsible for finding pokémon icons, because they have no icon table. +QString Project::findSpeciesIconPath(const QStringList &names) const { + QStringList possibleDirNames = names; + + // Permute paths with underscores. + // Ex: For a base name of 'foo_bar_baz', try 'foo_bar/baz', 'foo/bar_baz', 'foobarbaz', 'foo_bar', and 'foo'. + QStringList permutedNames; + for (auto dir : possibleDirNames) { + if (!dir.contains("_")) continue; + for (int i = dir.indexOf("_"); i > -1; i = dir.indexOf("_", i + 1)) { + QString temp = dir; + permutedNames.prepend(temp.replace(i, 1, "/")); + permutedNames.append(dir.left(i)); // Prepend the others so the most generic name ('foo') ends up last + } + permutedNames.prepend(dir.remove("_")); + } + possibleDirNames.append(permutedNames); + possibleDirNames.removeDuplicates(); + + const QString basePath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::pokemon_gfx)); + for (const auto &dir : possibleDirNames) { + if (dir.isEmpty()) continue; + + const QString path = QString("%1%2/icon.png").arg(basePath).arg(dir); + if (QFile::exists(path)) + return path; + } + return QString(); +} + +QPixmap Project::getSpeciesIcon(const QString &species) { QPixmap pixmap; if (!QPixmapCache::find(species, &pixmap)) { // Prefer path from config. If not present, use the path parsed from project files - QString path = projectConfig.getPokemonIconPath(species); + QString path = Project::getExistingFilepath(projectConfig.getPokemonIconPath(species)); if (path.isEmpty()) { - path = this->speciesToIconPath.value(species); - } else { - path = Project::getExistingFilepath(path); + path = getDefaultSpeciesIconPath(species); } QImage img(path); diff --git a/src/ui/encountertabledelegates.cpp b/src/ui/encountertabledelegates.cpp index 4ffe9e0d..2d46f54f 100644 --- a/src/ui/encountertabledelegates.cpp +++ b/src/ui/encountertabledelegates.cpp @@ -21,7 +21,7 @@ void SpeciesComboDelegate::paint(QPainter *painter, const QStyleOptionViewItem & QWidget *SpeciesComboDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { NoScrollComboBox *editor = new NoScrollComboBox(parent); editor->setFrame(false); - editor->addItems(this->project->speciesToIconPath.keys()); + editor->addItems(this->project->speciesNames); return editor; } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 54aeada2..fa84f3e0 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -104,7 +104,7 @@ void ProjectSettingsEditor::initUi() { if (project) { ui->comboBox_DefaultPrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_DefaultSecondaryTileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_IconSpecies->addItems(project->speciesToIconPath.keys()); + ui->comboBox_IconSpecies->addItems(project->speciesNames); ui->comboBox_WarpBehaviors->addItems(project->metatileBehaviorMap.keys()); } ui->comboBox_BaseGameVersion->addItems(ProjectConfig::versionStrings); @@ -278,11 +278,11 @@ void ProjectSettingsEditor::updatePokemonIconPath(const QString &newSpecies) { if (!project) return; // If user was editing a path for a valid species, record filepath text before we wipe it. - if (!this->prevIconSpecies.isEmpty() && this->project->speciesToIconPath.contains(this->prevIconSpecies)) + if (!this->prevIconSpecies.isEmpty() && this->project->speciesNames.contains(this->prevIconSpecies)) this->editedPokemonIconPaths[this->prevIconSpecies] = ui->lineEdit_PokemonIcon->text(); QString editedPath = this->editedPokemonIconPaths.value(newSpecies); - QString defaultPath = this->project->speciesToIconPath.value(newSpecies); + QString defaultPath = this->project->getDefaultSpeciesIconPath(newSpecies); ui->lineEdit_PokemonIcon->setText(this->stripProjectDir(editedPath)); ui->lineEdit_PokemonIcon->setPlaceholderText(this->stripProjectDir(defaultPath)); @@ -567,7 +567,7 @@ void ProjectSettingsEditor::save() { // Save pokemon icon paths const QString species = ui->comboBox_IconSpecies->currentText(); - if (this->project->speciesToIconPath.contains(species)) + if (this->project->speciesNames.contains(species)) this->editedPokemonIconPaths.insert(species, ui->lineEdit_PokemonIcon->text()); for (auto i = this->editedPokemonIconPaths.cbegin(), end = this->editedPokemonIconPaths.cend(); i != end; i++) projectConfig.setPokemonIconPath(i.key(), i.value()); diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index f43a0dab..056e7565 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -22,7 +22,7 @@ WildMonSearch::WildMonSearch(Project *project, QWidget *parent) : ui->setupUi(this); // Set up species combo box - ui->comboBox_Search->addItems(project->speciesToIconPath.keys()); + ui->comboBox_Search->addItems(project->speciesNames); ui->comboBox_Search->setCurrentText(QString()); ui->comboBox_Search->lineEdit()->setPlaceholderText(Project::getEmptySpeciesName()); connect(ui->comboBox_Search, &QComboBox::currentTextChanged, this, &WildMonSearch::updateResults);