From 35d5851a8f5e6990e5d14fbe26b95d14f2ecce44 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 21:32:42 -0400 Subject: [PATCH 01/25] Read MAP_OFFSET_W, MAP_OFFSET_H from project --- include/config.h | 2 + include/project.h | 41 +++++++----- src/config.cpp | 2 + src/project.cpp | 119 ++++++++++------------------------- src/scriptapi/apimap.cpp | 6 +- src/ui/newlayoutform.cpp | 9 +-- src/ui/resizelayoutpopup.cpp | 17 ++--- 7 files changed, 78 insertions(+), 118 deletions(-) diff --git a/include/config.h b/include/config.h index eb9c9ac3..2ca56fd1 100644 --- a/include/config.h +++ b/include/config.h @@ -214,6 +214,8 @@ enum ProjectIdentifier { define_pals_total, define_tiles_per_metatile, define_map_size, + define_map_offset_width, + define_map_offset_height, define_mask_metatile, define_mask_collision, define_mask_elevation, diff --git a/include/project.h b/include/project.h index 9a031c0d..fe1c48f2 100644 --- a/include/project.h +++ b/include/project.h @@ -240,24 +240,27 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + int getMapDataSize(int width, int height) const; + int getMaxMapDataSize() const { return this->maxMapDataSize; } + int getMaxMapWidth() const; + int getMaxMapHeight() const; + bool mapDimensionsValid(int width, int height) const; + bool calculateDefaultMapSize(); + int getDefaultMapDimension() const { return this->defaultMapDimension; } + QSize getMapSizeAddition() const { return this->mapSizeAddition; } + + int getMaxEvents(Event::Group group) const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); - static int getNumTilesPrimary(); - static int getNumTilesTotal(); - static int getNumMetatilesPrimary(); - static int getNumMetatilesTotal(); - static int getNumPalettesPrimary(); - static int getNumPalettesTotal(); - static int getMaxMapDataSize(); - static int getDefaultMapDimension(); - static int getMaxMapWidth(); - static int getMaxMapHeight(); - static int getMapDataSize(int width, int height); - static bool mapDimensionsValid(int width, int height); - bool calculateDefaultMapSize(); - int getMaxEvents(Event::Group group); + static int getNumTilesPrimary() { return num_tiles_primary; } + static int getNumTilesTotal() { return num_tiles_total; } + static int getNumMetatilesPrimary() { return num_metatiles_primary; } + static int getNumMetatilesTotal() { return Block::getMaxMetatileId() + 1; } + static int getNumPalettesPrimary(){ return num_pals_primary; } + static int getNumPalettesTotal() { return num_pals_total; } static QString getEmptyMapsecName(); static QString getMapGroupPrefix(); @@ -302,15 +305,19 @@ private: QString findSpeciesIconPath(const QStringList &names) const; - int maxEventsPerGroup; int maxObjectEvents; + QSize mapSizeAddition; + int maxMapDataSize; + int defaultMapDimension; + + // TODO: These really shouldn't be static, they're specific to a single project. + // We're making an assumption here that we only have one project open at a single time + // (which is true, but then if that's the case we should have some global Project instance instead) static int num_tiles_primary; static int num_tiles_total; static int num_metatiles_primary; static int num_pals_primary; static int num_pals_total; - static int max_map_data_size; - static int default_map_dimension; signals: void fileChanged(const QString &filepath); diff --git a/src/config.cpp b/src/config.cpp index 42a59149..fcd7f83b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -89,6 +89,8 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::define_pals_total, {"define_pals_total", "NUM_PALS_TOTAL"}}, {ProjectIdentifier::define_tiles_per_metatile, {"define_tiles_per_metatile", "NUM_TILES_PER_METATILE"}}, {ProjectIdentifier::define_map_size, {"define_map_size", "MAX_MAP_DATA_SIZE"}}, + {ProjectIdentifier::define_map_offset_width, {"define_map_offset_width", "MAP_OFFSET_W"}}, + {ProjectIdentifier::define_map_offset_height, {"define_map_offset_height", "MAP_OFFSET_H"}}, {ProjectIdentifier::define_mask_metatile, {"define_mask_metatile", "MAPGRID_METATILE_ID_MASK"}}, {ProjectIdentifier::define_mask_collision, {"define_mask_collision", "MAPGRID_COLLISION_MASK"}}, {ProjectIdentifier::define_mask_elevation, {"define_mask_elevation", "MAPGRID_ELEVATION_MASK"}}, diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..d33063e0 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -29,8 +29,6 @@ int Project::num_tiles_total = 1024; int Project::num_metatiles_primary = 512; int Project::num_pals_primary = 6; int Project::num_pals_total = 13; -int Project::max_map_data_size = 10240; // 0x2800 -int Project::default_map_dimension = 20; Project::Project(QObject *parent) : QObject(parent), @@ -2109,7 +2107,12 @@ 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 QSet names = { + const QString mapOffsetWidthName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_width); + const QString mapOffsetHeightName = projectConfig.getIdentifier(ProjectIdentifier::define_map_offset_height); + + const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); + fileWatcher.addPath(root + "/" + filename); + const QMap defines = parser.readCDefinesByName(filename, { numTilesPrimaryName, numTilesTotalName, numMetatilesPrimaryName, @@ -2117,10 +2120,9 @@ bool Project::readFieldmapProperties() { numPalsTotalName, maxMapSizeName, numTilesPerMetatileName, - }; - const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); - fileWatcher.addPath(root + "/" + filename); - const QMap defines = parser.readCDefinesByName(filename, names); + mapOffsetWidthName, + mapOffsetHeightName, + }); auto loadDefine = [defines](const QString name, int * dest, int min, int max) { auto it = defines.find(name); @@ -2146,25 +2148,35 @@ bool Project::readFieldmapProperties() { // we don't actually know what the maximum number of metatiles is. loadDefine(numMetatilesPrimaryName, &Project::num_metatiles_primary, 1, 0xFFFF - 1); + int w = 15, h = 14; // Default values of MAP_OFFSET_W, MAP_OFFSET_H + loadDefine(mapOffsetWidthName, &w, 0, INT_MAX); + loadDefine(mapOffsetHeightName, &h, 0, INT_MAX); + this->mapSizeAddition = QSize(w, h); + + this->maxMapDataSize = 10240; // Default value of MAX_MAP_DATA_SIZE + this->defaultMapDimension = 20; // Arbitrary default of 20x20. auto it = defines.find(maxMapSizeName); if (it != defines.end()) { int min = getMapDataSize(1, 1); if (it.value() >= min) { - Project::max_map_data_size = it.value(); - calculateDefaultMapSize(); + this->maxMapDataSize = it.value(); + if (getMapDataSize(this->defaultMapDimension, this->defaultMapDimension) > this->maxMapDataSize) { + // The specified map size is too small to use the default map dimensions. + // Calculate the largest square map size that we can use instead. + this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + } } else { - // must be large enough to support a 1x1 map - logWarn(QString("Value for map property '%1' is %2, must be at least %3. Using default (%4) instead.") + logWarn(QString("Value for map property '%1' of %2 is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") .arg(maxMapSizeName) .arg(it.value()) .arg(min) - .arg(Project::max_map_data_size)); + .arg(this->maxMapDataSize)); } } else { logWarn(QString("Value for map property '%1' not found. Using default (%2) instead.") .arg(maxMapSizeName) - .arg(Project::max_map_data_size)); + .arg(this->maxMapDataSize)); } it = defines.find(numTilesPerMetatileName); @@ -3112,91 +3124,28 @@ QPixmap Project::getSpeciesIcon(const QString &species) { return pixmap; } -int Project::getNumTilesPrimary() -{ - return Project::num_tiles_primary; +int Project::getMapDataSize(int width, int height) const { + return (width + this->mapSizeAddition.width()) + * (height + this->mapSizeAddition.height()); } -int Project::getNumTilesTotal() -{ - return Project::num_tiles_total; +int Project::getMaxMapWidth() const { + return (getMaxMapDataSize() / (1 + this->mapSizeAddition.height())) - this->mapSizeAddition.width(); } -int Project::getNumMetatilesPrimary() -{ - return Project::num_metatiles_primary; +int Project::getMaxMapHeight() const { + return (getMaxMapDataSize() / (1 + this->mapSizeAddition.width())) - this->mapSizeAddition.height(); } -int Project::getNumMetatilesTotal() -{ - return Block::getMaxMetatileId() + 1; -} - -int Project::getNumPalettesPrimary() -{ - return Project::num_pals_primary; -} - -int Project::getNumPalettesTotal() -{ - return Project::num_pals_total; -} - -int Project::getMaxMapDataSize() -{ - return Project::max_map_data_size; -} - -int Project::getMapDataSize(int width, int height) -{ - // + 15 and + 14 come from fieldmap.c in pokeruby/pokeemerald/pokefirered. - return (width + 15) * (height + 14); -} - -int Project::getDefaultMapDimension() -{ - return Project::default_map_dimension; -} - -int Project::getMaxMapWidth() -{ - return (getMaxMapDataSize() / (1 + 14)) - 15; -} - -int Project::getMaxMapHeight() -{ - return (getMaxMapDataSize() / (1 + 15)) - 14; -} - -bool Project::mapDimensionsValid(int width, int height) { +bool Project::mapDimensionsValid(int width, int height) const { return getMapDataSize(width, height) <= getMaxMapDataSize(); } -// Get largest possible square dimensions for a map up to maximum of 20x20 (arbitrary) -bool Project::calculateDefaultMapSize(){ - int max = getMaxMapDataSize(); - - if (max >= getMapDataSize(20, 20)) { - default_map_dimension = 20; - } else if (max >= getMapDataSize(1, 1)) { - // Below equation derived from max >= (x + 15) * (x + 14) - // x^2 + 29x + (210 - max), then complete the square and simplify - default_map_dimension = qFloor((qSqrt(4 * getMaxMapDataSize() + 1) - 29) / 2); - } else { - logError(QString("'%1' of %2 is too small to support a 1x1 map. Must be at least %3.") - .arg(projectConfig.getIdentifier(ProjectIdentifier::define_map_size)) - .arg(max) - .arg(getMapDataSize(1, 1))); - return false; - } - return true; -} - // Object events have their own limit specified by ProjectIdentifier::define_obj_event_count. // The default value for this is 64. All events (object events included) are also limited by // the data types of the event counters in the project. This would normally be u8, so the limit is 255. // We let the users tell us this limit in case they change these data types. -int Project::getMaxEvents(Event::Group group) { +int Project::getMaxEvents(Event::Group group) const { if (group == Event::Group::Object) return qMin(this->maxObjectEvents, projectConfig.maxEventsPerGroup); return projectConfig.maxEventsPerGroup; diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 0ee3316e..08bfc9c5 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -227,7 +227,7 @@ int MainWindow::getHeight() { void MainWindow::setDimensions(int width, int height) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, height)) + if (this->editor->project && !this->editor->project->mapDimensionsValid(width, height)) return; this->editor->layout->setDimensions(width, height); this->tryCommitMapChanges(true); @@ -237,7 +237,7 @@ void MainWindow::setDimensions(int width, int height) { void MainWindow::setWidth(int width) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(width, this->editor->layout->getHeight())) + if (this->editor->project && !this->editor->project->mapDimensionsValid(width, this->editor->layout->getHeight())) return; this->editor->layout->setDimensions(width, this->editor->layout->getHeight()); this->tryCommitMapChanges(true); @@ -247,7 +247,7 @@ void MainWindow::setWidth(int width) { void MainWindow::setHeight(int height) { if (!this->editor || !this->editor->layout) return; - if (!Project::mapDimensionsValid(this->editor->layout->getWidth(), height)) + if (this->editor->project && !this->editor->project->mapDimensionsValid(this->editor->layout->getWidth(), height)) return; this->editor->layout->setDimensions(this->editor->layout->getWidth(), height); this->tryCommitMapChanges(true); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index aa8178ce..b88b4f3f 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -86,17 +86,14 @@ bool NewLayoutForm::validateMapDimensions() { int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); int maxSize = m_project->getMaxMapDataSize(); - // TODO: Get from project - const int additionalWidth = 15; - const int additionalHeight = 14; - QString errorText; if (size > maxSize) { + QSize addition = m_project->getMapSizeAddition(); errorText = QString("The specified width and height are too large.\n" "The maximum map width and height is the following: (width + %1) * (height + %2) <= %3\n" "The specified map width and height was: (%4 + %1) * (%5 + %2) = %6") - .arg(additionalWidth) - .arg(additionalHeight) + .arg(addition.width()) + .arg(addition.height()) .arg(maxSize) .arg(ui->spinBox_MapWidth->value()) .arg(ui->spinBox_MapHeight->value()) diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 5629d8e9..00790ca8 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -145,15 +145,18 @@ void ResizeLayoutPopup::setupLayoutView() { // Upper limits: maximum metatiles in a map formula: // max = (width + 15) * (height + 14) // This limit can be found in fieldmap.c in pokeruby/pokeemerald/pokefirered. - int numMetatiles = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); - int maxMetatiles = editor->project->getMaxMapDataSize(); - if (numMetatiles > maxMetatiles) { - QString errorText = QString("The maximum layout width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified layout width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) + int size = editor->project->getMapDataSize(rect.width() / 16, rect.height() / 16); + int maxSize = editor->project->getMaxMapDataSize(); + if (size > maxSize) { + QSize addition = editor->project->getMapSizeAddition(); + QString errorText = QString("The maximum layout width and height is the following: (width + %1) * (height + %2) <= %3\n" + "The specified layout width and height was: (%4 + %1) * (%5 + %2) = %6") + .arg(addition.width()) + .arg(addition.height()) + .arg(maxSize) .arg(rect.width() / 16) .arg(rect.height() / 16) - .arg(numMetatiles); + .arg(size); QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setText("The specified width and height are too large."); From 900ff0afd9b40dcbdc498df3805802f2325d3d3c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 21:57:40 -0400 Subject: [PATCH 02/25] Fix incorrect log comments, update manual --- docsrc/manual/project-files.rst | 2 ++ src/project.cpp | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index caac85c2..93715d86 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -97,6 +97,8 @@ In addition to these files, there are some specific symbol and macro names that ``define_pals_total``, ``NUM_PALS_TOTAL``, ``define_tiles_per_metatile``, ``NUM_TILES_PER_METATILE``, to determine if triple-layer metatiles are in use. Values other than 8 or 12 are ignored ``define_map_size``, ``MAX_MAP_DATA_SIZE``, to limit map dimensions + ``define_map_offset_width``, ``MAP_OFFSET_W``, to limit map dimensions + ``define_map_offset_height``, ``MAP_OFFSET_H``, to limit map dimensions ``define_mask_metatile``, ``MAPGRID_METATILE_ID_MASK``, optionally read to get settings on ``Maps`` tab ``define_mask_collision``, ``MAPGRID_COLLISION_MASK``, optionally read to get settings on ``Maps`` tab ``define_mask_elevation``, ``MAPGRID_ELEVATION_MASK``, optionally read to get settings on ``Maps`` tab diff --git a/src/project.cpp b/src/project.cpp index d33063e0..20ff806a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2129,14 +2129,14 @@ bool Project::readFieldmapProperties() { if (it != defines.end()) { *dest = it.value(); if (*dest < min) { - logWarn(QString("Value for tileset property '%1' (%2) is below the minimum (%3). Defaulting to minimum.").arg(name).arg(*dest).arg(min)); + logWarn(QString("Value for '%1' (%2) is below the minimum (%3). Defaulting to minimum.").arg(name).arg(*dest).arg(min)); *dest = min; } else if (*dest > max) { - logWarn(QString("Value for tileset property '%1' (%2) is above the maximum (%3). Defaulting to maximum.").arg(name).arg(*dest).arg(max)); + logWarn(QString("Value for '%1' (%2) is above the maximum (%3). Defaulting to maximum.").arg(name).arg(*dest).arg(max)); *dest = max; } } else { - logWarn(QString("Value for tileset property '%1' not found. Using default (%2) instead.").arg(name).arg(*dest)); + logWarn(QString("Value for '%1' not found. Using default (%2) instead.").arg(name).arg(*dest)); } }; loadDefine(numPalsTotalName, &Project::num_pals_total, 2, INT_MAX); // In reality the max would be 16, but as far as Porymap is concerned it doesn't matter. @@ -2166,7 +2166,7 @@ bool Project::readFieldmapProperties() { this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); } } else { - logWarn(QString("Value for map property '%1' of %2 is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") + logWarn(QString("Value for '%1' (%2) is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") .arg(maxMapSizeName) .arg(it.value()) .arg(min) @@ -2174,7 +2174,7 @@ bool Project::readFieldmapProperties() { } } else { - logWarn(QString("Value for map property '%1' not found. Using default (%2) instead.") + logWarn(QString("Value for '%1' not found. Using default (%2) instead.") .arg(maxMapSizeName) .arg(this->maxMapDataSize)); } From c630581453c15467d83d74633fc97ebd63a84784 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 22:22:30 -0400 Subject: [PATCH 03/25] Add setting for default map size --- forms/projectsettingseditor.ui | 162 +++++++++++++++---------------- include/config.h | 2 + include/project.h | 6 +- src/config.cpp | 10 +- src/project.cpp | 21 ++-- src/ui/projectsettingseditor.cpp | 5 + 6 files changed, 112 insertions(+), 94 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index a088d87e..00b015e1 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -369,7 +369,7 @@ 0 0 559 - 560 + 622 @@ -379,37 +379,6 @@ Map Data Defaults - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - The default metatile value that will be used to fill new maps - - - 0x - - - 16 - - - @@ -417,6 +386,44 @@ + + + + Width + + + + + + + The default elevation that will be used to fill new maps + + + + + + + Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file + + + Create separate text file + + + + + + + 1 + + + + + + + 1 + + + @@ -424,13 +431,10 @@ - - - - Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file - + + - Create separate text file + Collision @@ -441,6 +445,20 @@ + + + + The default metatile value that will be used to fill new maps + + + + + + + The default collision that will be used to fill new maps + + + @@ -481,79 +499,59 @@ 0 - + The default metatile value that will be used for the top-left border metatile on new maps. - - 0x - - - 16 - - + The default metatile value that will be used for the top-right border metatile on new maps. - - 0x - - - 16 - - + The default metatile value that will be used for the bottom-left border metatile on new maps. - - 0x - - - 16 - - + The default metatile value that will be used for the bottom-right border metatile on new maps. - - 0x - - - 16 - - - - - The default elevation that will be used to fill new maps - + + + + + 0 + + + 0 + + + 0 + + + 0 + + - - + + - Collision - - - - - - - The default collision that will be used to fill new maps + Height @@ -1084,7 +1082,7 @@ 0 0 559 - 788 + 840 diff --git a/include/config.h b/include/config.h index 2ca56fd1..d3c274e9 100644 --- a/include/config.h +++ b/include/config.h @@ -319,6 +319,7 @@ public: this->defaultMetatileId = 1; this->defaultElevation = 3; this->defaultCollision = 0; + this->defaultMapSize = QSize(20,20); this->defaultPrimaryTileset = "gTileset_General"; this->prefabFilepath = QString(); this->prefabImportPrompted = false; @@ -383,6 +384,7 @@ public: uint16_t defaultMetatileId; uint16_t defaultElevation; uint16_t defaultCollision; + QSize defaultMapSize; QList newMapBorderMetatileIds; QString defaultPrimaryTileset; QString defaultSecondaryTileset; diff --git a/include/project.h b/include/project.h index fe1c48f2..1e45fdbb 100644 --- a/include/project.h +++ b/include/project.h @@ -246,7 +246,7 @@ public: int getMaxMapHeight() const; bool mapDimensionsValid(int width, int height) const; bool calculateDefaultMapSize(); - int getDefaultMapDimension() const { return this->defaultMapDimension; } + QSize getDefaultMapSize() const { return this->defaultMapSize; } QSize getMapSizeAddition() const { return this->mapSizeAddition; } int getMaxEvents(Event::Group group) const; @@ -306,9 +306,9 @@ private: QString findSpeciesIconPath(const QStringList &names) const; int maxObjectEvents; - QSize mapSizeAddition; int maxMapDataSize; - int defaultMapDimension; + QSize defaultMapSize; + QSize mapSizeAddition; // TODO: These really shouldn't be static, they're specific to a single project. // We're making an assumption here that we only have one project open at a single time diff --git a/src/config.cpp b/src/config.cpp index fcd7f83b..a97ba5bc 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -265,7 +265,7 @@ int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, in int result = value.toInt(&ok, 0); if (!ok) { logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); - return defaultValue; + result = defaultValue; } return qMin(max, qMax(min, result)); } @@ -275,7 +275,7 @@ uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_ uint32_t result = value.toUInt(&ok, 0); if (!ok) { logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); - return defaultValue; + result = defaultValue; } return qMin(max, qMax(min, result)); } @@ -739,6 +739,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->defaultElevation = getConfigUint32(key, value, 0, Block::maxValue); } else if (key == "default_collision") { this->defaultCollision = getConfigUint32(key, value, 0, Block::maxValue); + } else if (key == "default_map_width") { + this->defaultMapSize.setWidth(getConfigInteger(key, value, 1)); + } else if (key == "default_map_height") { + this->defaultMapSize.setHeight(getConfigInteger(key, value, 1)); } else if (key == "new_map_border_metatiles") { this->newMapBorderMetatileIds.clear(); QList metatileIds = value.split(","); @@ -890,6 +894,8 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("default_metatile", Metatile::getMetatileIdString(this->defaultMetatileId)); map.insert("default_elevation", QString::number(this->defaultElevation)); map.insert("default_collision", QString::number(this->defaultCollision)); + map.insert("default_map_width", QString::number(this->defaultMapSize.width())); + map.insert("default_map_height", QString::number(this->defaultMapSize.height())); map.insert("new_map_border_metatiles", Metatile::getMetatileIdStrings(this->newMapBorderMetatileIds)); map.insert("default_primary_tileset", this->defaultPrimaryTileset); map.insert("default_secondary_tileset", this->defaultSecondaryTileset); diff --git a/src/project.cpp b/src/project.cpp index 20ff806a..6190017a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1990,8 +1990,8 @@ void Project::initNewMapSettings() { this->newMapSettings.layout.folderName = this->newMapSettings.name; this->newMapSettings.layout.name = QString(); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); - this->newMapSettings.layout.width = getDefaultMapDimension(); - this->newMapSettings.layout.height = getDefaultMapDimension(); + this->newMapSettings.layout.width = this->defaultMapSize.width(); + this->newMapSettings.layout.height = this->defaultMapSize.height(); this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH; this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); @@ -2013,8 +2013,8 @@ void Project::initNewMapSettings() { void Project::initNewLayoutSettings() { this->newLayoutSettings.name = QString(); this->newLayoutSettings.id = Layout::layoutConstantFromName(this->newLayoutSettings.name); - this->newLayoutSettings.width = getDefaultMapDimension(); - this->newLayoutSettings.height = getDefaultMapDimension(); + this->newLayoutSettings.width = this->defaultMapSize.width(); + this->newLayoutSettings.height = this->defaultMapSize.height(); this->newLayoutSettings.borderWidth = DEFAULT_BORDER_WIDTH; this->newLayoutSettings.borderHeight = DEFAULT_BORDER_HEIGHT; this->newLayoutSettings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); @@ -2154,16 +2154,23 @@ bool Project::readFieldmapProperties() { this->mapSizeAddition = QSize(w, h); this->maxMapDataSize = 10240; // Default value of MAX_MAP_DATA_SIZE - this->defaultMapDimension = 20; // Arbitrary default of 20x20. + this->defaultMapSize = projectConfig.defaultMapSize; auto it = defines.find(maxMapSizeName); if (it != defines.end()) { int min = getMapDataSize(1, 1); if (it.value() >= min) { this->maxMapDataSize = it.value(); - if (getMapDataSize(this->defaultMapDimension, this->defaultMapDimension) > this->maxMapDataSize) { + if (getMapDataSize(this->defaultMapSize.width(), this->defaultMapSize.height()) > this->maxMapDataSize) { // The specified map size is too small to use the default map dimensions. // Calculate the largest square map size that we can use instead. - this->defaultMapDimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + int dimension = qFloor((qSqrt(4 * this->maxMapDataSize + 1) - (w + h)) / 2); + logWarn(QString("Value for '%1' (%2) is too small to support the default %3x%4 map. Default changed to %5x%5.") + .arg(maxMapSizeName) + .arg(it.value()) + .arg(this->defaultMapSize.width()) + .arg(this->defaultMapSize.height()) + .arg(dimension)); + this->defaultMapSize = QSize(dimension, dimension); } } else { logWarn(QString("Value for '%1' (%2) is too small to support a 1x1 map. Must be at least %3. Using default (%4) instead.") diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 29521974..7f84c5dc 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -136,6 +136,8 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_UnusedTileCovered->setMaximum(Tile::maxValue); ui->spinBox_UnusedTileSplit->setMaximum(Tile::maxValue); ui->spinBox_MaxEvents->setMaximum(INT_MAX); + ui->spinBox_MapWidth->setMaximum(INT_MAX); + ui->spinBox_MapHeight->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -455,6 +457,8 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_Elevation->setValue(projectConfig.defaultElevation); ui->spinBox_Collision->setValue(projectConfig.defaultCollision); ui->spinBox_FillMetatile->setValue(projectConfig.defaultMetatileId); + ui->spinBox_MapWidth->setValue(projectConfig.defaultMapSize.width()); + ui->spinBox_MapHeight->setValue(projectConfig.defaultMapSize.height()); ui->spinBox_MaxElevation->setValue(projectConfig.collisionSheetHeight - 1); ui->spinBox_MaxCollision->setValue(projectConfig.collisionSheetWidth - 1); ui->spinBox_BehaviorMask->setValue(projectConfig.metatileBehaviorMask & ui->spinBox_BehaviorMask->maximum()); @@ -530,6 +534,7 @@ void ProjectSettingsEditor::save() { projectConfig.defaultElevation = ui->spinBox_Elevation->value(); projectConfig.defaultCollision = ui->spinBox_Collision->value(); projectConfig.defaultMetatileId = ui->spinBox_FillMetatile->value(); + projectConfig.defaultMapSize = QSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); projectConfig.collisionSheetHeight = ui->spinBox_MaxElevation->value() + 1; projectConfig.collisionSheetWidth = ui->spinBox_MaxCollision->value() + 1; projectConfig.metatileBehaviorMask = ui->spinBox_BehaviorMask->value(); From e19932b90ced2c7cc2e5fdb168a1490bc260e2cc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 11:43:54 -0400 Subject: [PATCH 04/25] Allow custom map connection direction input --- include/ui/connectionslistitem.h | 13 +++-- src/ui/connectionslistitem.cpp | 79 ++++++++++++++++++------------- src/ui/newmapconnectiondialog.cpp | 1 - 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index b63922a9..bce05345 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -36,19 +36,18 @@ private: protected: virtual void mousePressEvent(QMouseEvent*) override; - virtual void focusInEvent(QFocusEvent*) override; virtual void keyPressEvent(QKeyEvent*) override; + virtual bool eventFilter(QObject*, QEvent *event) override; signals: void selected(); void openMapClicked(MapConnection*); -private slots: - void on_comboBox_Direction_currentTextChanged(QString direction); - void on_comboBox_Map_currentTextChanged(QString mapName); - void on_spinBox_Offset_valueChanged(int offset); - void on_button_Delete_clicked(); - void on_button_OpenMap_clicked(); +private: + void commitDirection(); + void commitMap(const QString &mapName); + void commitMove(int offset); + void commitRemove(); }; #endif // CONNECTIONSLISTITEM_H diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index 5b21a12c..f761ba12 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -7,44 +7,58 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connection, const QStringList &mapNames) : QFrame(parent), - ui(new Ui::ConnectionsListItem) + ui(new Ui::ConnectionsListItem), + connection(connection), + map(connection->parentMap()) { ui->setupUi(this); setFocusPolicy(Qt::StrongFocus); - const QSignalBlocker blocker1(ui->comboBox_Direction); - const QSignalBlocker blocker2(ui->comboBox_Map); - const QSignalBlocker blocker3(ui->spinBox_Offset); - - ui->comboBox_Direction->setEditable(false); + // Direction + const QSignalBlocker b_Direction(ui->comboBox_Direction); ui->comboBox_Direction->setMinimumContentsLength(0); ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); + ui->comboBox_Direction->installEventFilter(this); + // We don't use QComboBox::currentTextChanged here to avoid unnecessary commits while typing. + connect(ui->comboBox_Direction, QOverload::of(&QComboBox::currentIndexChanged), this, &ConnectionsListItem::commitDirection); + connect(ui->comboBox_Direction->lineEdit(), &QLineEdit::editingFinished, this, &ConnectionsListItem::commitDirection); + + // Map + const QSignalBlocker b_Map(ui->comboBox_Map); ui->comboBox_Map->setMinimumContentsLength(6); ui->comboBox_Map->addItems(mapNames); ui->comboBox_Map->setFocusedScrollingEnabled(false); // Scrolling could cause rapid changes to many different maps ui->comboBox_Map->setInsertPolicy(QComboBox::NoInsert); + ui->comboBox_Map->installEventFilter(this); - ui->spinBox_Offset->setMinimum(INT_MIN); - ui->spinBox_Offset->setMaximum(INT_MAX); + // The map combo box only commits the change if it's a valid map name, so unlike Direction we can use QComboBox::currentTextChanged. + connect(ui->comboBox_Map, &QComboBox::currentTextChanged, this, &ConnectionsListItem::commitMap); // Invalid map names are not considered a change. If editing finishes with an invalid name, restore the previous name. connect(ui->comboBox_Map->lineEdit(), &QLineEdit::editingFinished, [this] { - const QSignalBlocker blocker(ui->comboBox_Map); - if (ui->comboBox_Map->findText(ui->comboBox_Map->currentText()) < 0) + const QSignalBlocker b(ui->comboBox_Map); + if (this->connection && ui->comboBox_Map->findText(ui->comboBox_Map->currentText()) < 0) ui->comboBox_Map->setTextItem(this->connection->targetMapName()); }); - // Distinguish between move actions for the edit history - connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); + // Offset + const QSignalBlocker b_Offset(ui->spinBox_Offset); + ui->spinBox_Offset->setMinimum(INT_MIN); + ui->spinBox_Offset->setMaximum(INT_MAX); + ui->spinBox_Offset->installEventFilter(this); + + connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); // Distinguish between move actions for the edit history + connect(ui->spinBox_Offset, &QSpinBox::valueChanged, this, &ConnectionsListItem::commitMove); // If the connection changes externally we want to update to reflect the change. connect(connection, &MapConnection::offsetChanged, this, &ConnectionsListItem::updateUI); connect(connection, &MapConnection::directionChanged, this, &ConnectionsListItem::updateUI); connect(connection, &MapConnection::targetMapNameChanged, this, &ConnectionsListItem::updateUI); - this->connection = connection; - this->map = connection->parentMap(); + connect(ui->button_Delete, &QToolButton::clicked, this, &ConnectionsListItem::commitRemove); + connect(ui->button_OpenMap, &QToolButton::clicked, [this] { emit openMapClicked(this->connection); }); + this->updateUI(); } @@ -66,13 +80,19 @@ void ConnectionsListItem::updateUI() { ui->spinBox_Offset->setValue(this->connection->offset()); } +bool ConnectionsListItem::eventFilter(QObject*, QEvent *event) { + if (event->type() == QEvent::FocusIn) + this->setSelected(true); + return false; +} + void ConnectionsListItem::setSelected(bool selected) { if (selected == this->isSelected) return; this->isSelected = selected; - this->setStyleSheet(selected ? ".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }" - : ".ConnectionsListItem { border-width: 1px; }"); + this->setStyleSheet(selected ? QStringLiteral(".ConnectionsListItem { border: 1px solid rgb(255, 0, 255); }") + : QStringLiteral(".ConnectionsListItem { border-width: 1px; }")); if (selected) emit this->selected(); } @@ -81,41 +101,32 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { this->setSelected(true); } -void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(QString direction) { - this->setSelected(true); - if (this->map) +void ConnectionsListItem::commitDirection() { + const QString direction = ui->comboBox_Direction->currentText(); + if (this->map && this->connection && this->connection->direction() != direction) { this->map->commit(new MapConnectionChangeDirection(this->connection, direction)); + } } -void ConnectionsListItem::on_comboBox_Map_currentTextChanged(QString mapName) { - this->setSelected(true); +void ConnectionsListItem::commitMap(const QString &mapName) { if (this->map && ui->comboBox_Map->findText(mapName) >= 0) this->map->commit(new MapConnectionChangeMap(this->connection, mapName)); } -void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) { - this->setSelected(true); +void ConnectionsListItem::commitMove(int offset) { if (this->map) this->map->commit(new MapConnectionMove(this->connection, offset, this->actionId)); } -void ConnectionsListItem::on_button_Delete_clicked() { +void ConnectionsListItem::commitRemove() { if (this->map) this->map->commit(new MapConnectionRemove(this->map, this->connection)); } -void ConnectionsListItem::on_button_OpenMap_clicked() { - emit openMapClicked(this->connection); -} - -void ConnectionsListItem::focusInEvent(QFocusEvent* event) { - this->setSelected(true); - QFrame::focusInEvent(event); -} - void ConnectionsListItem::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { - on_button_Delete_clicked(); + commitRemove(); + event->accept(); } else { QFrame::keyPressEvent(event); } diff --git a/src/ui/newmapconnectiondialog.cpp b/src/ui/newmapconnectiondialog.cpp index def341fd..a4f08496 100644 --- a/src/ui/newmapconnectiondialog.cpp +++ b/src/ui/newmapconnectiondialog.cpp @@ -8,7 +8,6 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ui->comboBox_Direction->setEditable(false); ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); ui->comboBox_Map->addItems(mapNames); From b6548fd49ccb66cfcfc408f180ef7d7e585f6708 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 12:40:28 -0400 Subject: [PATCH 05/25] Stop zoom behavior from regressing again --- forms/mainwindow.ui | 6 ------ src/mainwindow.cpp | 4 ++++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 86f50047..99e7d4b2 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -351,12 +351,6 @@ false - - QGraphicsView::ViewportAnchor::AnchorUnderMouse - - - QGraphicsView::ViewportAnchor::AnchorUnderMouse - diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..006546e5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -261,6 +261,10 @@ void MainWindow::initCustomUI() { // Create map header data widget this->mapHeaderForm = new MapHeaderForm(); ui->layout_HeaderData->addWidget(this->mapHeaderForm); + + // Center zooming on the mouse + ui->graphicsView_Map->setTransformationAnchor(QGraphicsView::ViewportAnchor::AnchorUnderMouse); + ui->graphicsView_Map->setResizeAnchor(QGraphicsView::ViewportAnchor::AnchorUnderMouse); } void MainWindow::initExtraSignals() { From d014eef9e8f6d4bbddc0eb4734a06e5bfec66e3e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 13:41:57 -0400 Subject: [PATCH 06/25] Add NoScrollComboBox::editingFinished, disable diving map buttons with no map --- forms/mainwindow.ui | 6 +++++ include/editor.h | 7 ++--- include/mainwindow.h | 2 -- include/ui/noscrollcombobox.h | 3 +++ src/editor.cpp | 47 ++++++++++++++++++++++++++-------- src/mainwindow.cpp | 16 ------------ src/ui/connectionslistitem.cpp | 5 +--- src/ui/divingmappixmapitem.cpp | 6 +---- src/ui/mapimageexporter.cpp | 5 +--- src/ui/newlayoutform.cpp | 4 +-- src/ui/noscrollcombobox.cpp | 5 ++++ src/ui/tileseteditor.cpp | 14 +++------- 12 files changed, 63 insertions(+), 57 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 99e7d4b2..8a8757a0 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2429,6 +2429,9 @@ + + false + Open the selected Dive Map @@ -2563,6 +2566,9 @@ + + false + Open the selected Emerge Map diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..8b8a18b0 100644 --- a/include/editor.h +++ b/include/editor.h @@ -98,8 +98,8 @@ public: void deleteWildMonGroup(); void configureEncounterJSON(QWidget *); EncounterTableModel* getCurrentWildMonTable(); - void updateDiveMap(QString mapName); - void updateEmergeMap(QString mapName); + bool setDivingMapName(const QString &mapName, const QString &direction); + QString getDivingMapName(const QString &direction) const; void setSelectedConnection(MapConnection *connection); void updatePrimaryTileset(QString tilesetLabel, bool forceLoad = false); @@ -218,8 +218,9 @@ private: void removeConnectionPixmap(MapConnection *connection); void displayConnection(MapConnection *connection); void displayDivingConnection(MapConnection *connection); - void setDivingMapName(QString mapName, QString direction); void removeDivingMapPixmap(MapConnection *connection); + void onDivingMapEditingFinished(NoScrollComboBox* combo, const QString &direction); + void updateDivingMapButton(QToolButton* button, const QString &mapName); void updateEncounterFields(EncounterFields newFields); QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); diff --git a/include/mainwindow.h b/include/mainwindow.h index 410bd6e3..abda3116 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -243,8 +243,6 @@ private slots: void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); void on_button_OpenEmergeMap_clicked(); - void on_comboBox_DiveMap_currentTextChanged(const QString &mapName); - void on_comboBox_EmergeMap_currentTextChanged(const QString &mapName); void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); diff --git a/include/ui/noscrollcombobox.h b/include/ui/noscrollcombobox.h index 32966b3a..0ae2487c 100644 --- a/include/ui/noscrollcombobox.h +++ b/include/ui/noscrollcombobox.h @@ -18,6 +18,9 @@ public: void setLineEdit(QLineEdit *edit); void setFocusedScrollingEnabled(bool enabled); +signals: + void editingFinished(); + private: void setItem(int index, const QString &text); diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..78cab8bb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -54,6 +54,19 @@ Editor::Editor(Ui::MainWindow* ui) connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this, &Editor::openProjectInTextEditor); connect(ui->checkBox_ToggleGrid, &QCheckBox::toggled, this, &Editor::toggleGrid); connect(ui->mapCustomAttributesFrame->table(), &CustomAttributesTable::edited, this, &Editor::updateCustomMapAttributes); + + connect(ui->comboBox_DiveMap, &NoScrollComboBox::editingFinished, [this] { + onDivingMapEditingFinished(this->ui->comboBox_DiveMap, "dive"); + }); + connect(ui->comboBox_EmergeMap, &NoScrollComboBox::editingFinished, [this] { + onDivingMapEditingFinished(this->ui->comboBox_EmergeMap, "emerge"); + }); + connect(ui->comboBox_DiveMap, &NoScrollComboBox::currentTextChanged, [this] { + updateDivingMapButton(this->ui->button_OpenDiveMap, this->ui->comboBox_DiveMap->currentText()); + }); + connect(ui->comboBox_EmergeMap, &NoScrollComboBox::currentTextChanged, [this] { + updateDivingMapButton(this->ui->button_OpenEmergeMap, this->ui->comboBox_EmergeMap->currentText()); + }); } Editor::~Editor() @@ -914,21 +927,18 @@ void Editor::removeDivingMapPixmap(MapConnection *connection) { updateDivingMapsVisibility(); } -void Editor::updateDiveMap(QString mapName) { - setDivingMapName(mapName, "dive"); -} +bool Editor::setDivingMapName(const QString &mapName, const QString &direction) { + if (!mapName.isEmpty() && !this->project->mapNames.contains(mapName)) + return false; + if (!MapConnection::isDiving(direction)) + return false; -void Editor::updateEmergeMap(QString mapName) { - setDivingMapName(mapName, "emerge"); -} - -void Editor::setDivingMapName(QString mapName, QString direction) { auto pixmapItem = diving_map_items.value(direction); MapConnection *connection = pixmapItem ? pixmapItem->connection() : nullptr; if (connection) { if (mapName == connection->targetMapName()) - return; // No change + return true; // No change // Update existing connection if (mapName.isEmpty()) { @@ -940,6 +950,23 @@ void Editor::setDivingMapName(QString mapName, QString direction) { // Create new connection addConnection(new MapConnection(mapName, direction)); } + return true; +} + +QString Editor::getDivingMapName(const QString &direction) const { + auto pixmapItem = diving_map_items.value(direction); + return (pixmapItem && pixmapItem->connection()) ? pixmapItem->connection()->targetMapName() : QString(); +} + +void Editor::onDivingMapEditingFinished(NoScrollComboBox *combo, const QString &direction) { + if (!setDivingMapName(combo->currentText(), direction)) { + // If user input was invalid, restore the combo to the previously-valid text. + combo->setCurrentText(getDivingMapName(direction)); + } +} + +void Editor::updateDivingMapButton(QToolButton* button, const QString &mapName) { + if (this->project) button->setDisabled(!this->project->mapNames.contains(mapName)); } void Editor::updateDivingMapsVisibility() { @@ -1722,8 +1749,6 @@ void Editor::clearMapConnections() { } connection_items.clear(); - const QSignalBlocker blocker1(ui->comboBox_DiveMap); - const QSignalBlocker blocker2(ui->comboBox_EmergeMap); ui->comboBox_DiveMap->setCurrentText(""); ui->comboBox_EmergeMap->setCurrentText(""); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 006546e5..c539b654 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1217,10 +1217,7 @@ void MainWindow::clearProjectUI() { const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); ui->comboBox_SecondaryTileset->clear(); - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); - - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_EmergeMap->clear(); const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); @@ -1381,8 +1378,6 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { // (other combo boxes like for warp destinations are repopulated when the map changes). int mapIndex = this->editor->project->mapNames.indexOf(newMap->name()); if (mapIndex >= 0) { - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_DiveMap->insertItem(mapIndex, newMap->name()); ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } @@ -2646,17 +2641,6 @@ void MainWindow::on_button_OpenEmergeMap_clicked() { userSetMap(ui->comboBox_EmergeMap->currentText()); } -void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { - // Include empty names as an update (user is deleting the connection) - if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) - editor->updateDiveMap(mapName); -} - -void MainWindow::on_comboBox_EmergeMap_currentTextChanged(const QString &mapName) { - if (mapName.isEmpty() || editor->project->mapNames.contains(mapName)) - editor->updateEmergeMap(mapName); -} - void MainWindow::on_comboBox_PrimaryTileset_currentTextChanged(const QString &tilesetLabel) { if (editor->project->primaryTilesetLabels.contains(tilesetLabel) && editor->layout) { diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index f761ba12..b0fdf581 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -20,9 +20,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->comboBox_Direction->addItems(MapConnection::cardinalDirections); ui->comboBox_Direction->installEventFilter(this); - // We don't use QComboBox::currentTextChanged here to avoid unnecessary commits while typing. - connect(ui->comboBox_Direction, QOverload::of(&QComboBox::currentIndexChanged), this, &ConnectionsListItem::commitDirection); - connect(ui->comboBox_Direction->lineEdit(), &QLineEdit::editingFinished, this, &ConnectionsListItem::commitDirection); + connect(ui->comboBox_Direction, &NoScrollComboBox::editingFinished, this, &ConnectionsListItem::commitDirection); // Map const QSignalBlocker b_Map(ui->comboBox_Map); @@ -32,7 +30,6 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->comboBox_Map->setInsertPolicy(QComboBox::NoInsert); ui->comboBox_Map->installEventFilter(this); - // The map combo box only commits the change if it's a valid map name, so unlike Direction we can use QComboBox::currentTextChanged. connect(ui->comboBox_Map, &QComboBox::currentTextChanged, this, &ConnectionsListItem::commitMap); // Invalid map names are not considered a change. If editing finishes with an invalid name, restore the previous name. diff --git a/src/ui/divingmappixmapitem.cpp b/src/ui/divingmappixmapitem.cpp index e20b8f25..b774b1e9 100644 --- a/src/ui/divingmappixmapitem.cpp +++ b/src/ui/divingmappixmapitem.cpp @@ -38,9 +38,5 @@ void DivingMapPixmapItem::onTargetMapChanged() { } void DivingMapPixmapItem::setComboText(const QString &text) { - if (!m_combo) - return; - - const QSignalBlocker blocker(m_combo); - m_combo->setCurrentText(text); + if (m_combo) m_combo->setCurrentText(text); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 58b63cbd..8d415819 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -55,10 +55,7 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage); connect(ui->pushButton_Cancel, &QPushButton::pressed, this, &MapImageExporter::close); - // Update the map selector when the text changes. - // We don't use QComboBox::currentTextChanged to avoid unnecessary re-rendering. - connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); - connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); + connect(ui->comboBox_MapSelection, &NoScrollComboBox::editingFinished, this, &MapImageExporter::updateMapSelection); connect(ui->checkBox_Objects, &QCheckBox::toggled, this, &MapImageExporter::setShowObjects); connect(ui->checkBox_Warps, &QCheckBox::toggled, this, &MapImageExporter::setShowWarps); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index aa8178ce..2bf4621f 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -18,8 +18,8 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); }); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){ validateMapDimensions(); }); - connect(ui->comboBox_PrimaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validatePrimaryTileset(true); }); - connect(ui->comboBox_SecondaryTileset->lineEdit(), &QLineEdit::editingFinished, [this]{ validateSecondaryTileset(true); }); + connect(ui->comboBox_PrimaryTileset, &NoScrollComboBox::editingFinished, [this]{ validatePrimaryTileset(true); }); + connect(ui->comboBox_SecondaryTileset, &NoScrollComboBox::editingFinished, [this]{ validateSecondaryTileset(true); }); } NewLayoutForm::~NewLayoutForm() diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index 21de55a8..bd6b438b 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -23,6 +23,11 @@ NoScrollComboBox::NoScrollComboBox(QWidget *parent) static const QRegularExpression re("[^\\s]*"); QValidator *validator = new QRegularExpressionValidator(re, this); this->setValidator(validator); + + // QComboBox (as of writing) has no 'editing finished' signal to capture + // changes made either through the text edit or the drop-down. + connect(this, &QComboBox::activated, this, &NoScrollComboBox::editingFinished); + connect(this->lineEdit(), &QLineEdit::editingFinished, this, &NoScrollComboBox::editingFinished); } // On macOS QComboBox::setEditable and QComboBox::setLineEdit will override our changes to the focus policy, so we enforce it here. diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 3d610fb9..2671f445 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -125,16 +125,10 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi } void TilesetEditor::initAttributesUi() { - // Update the metatile's attributes values when the attribute combo boxes are edited. - // We avoid using the 'currentTextChanged' signal here, we want to know when we can clean up the input field and commit changes. - connect(ui->comboBox_metatileBehaviors->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitMetatileBehavior); - connect(ui->comboBox_encounterType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitEncounterType); - connect(ui->comboBox_terrainType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitTerrainType); - connect(ui->comboBox_layerType->lineEdit(), &QLineEdit::editingFinished, this, &TilesetEditor::commitLayerType); - connect(ui->comboBox_metatileBehaviors, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitMetatileBehavior); - connect(ui->comboBox_encounterType, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitEncounterType); - connect(ui->comboBox_terrainType, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitTerrainType); - connect(ui->comboBox_layerType, QOverload::of(&QComboBox::activated), this, &TilesetEditor::commitLayerType); + connect(ui->comboBox_metatileBehaviors, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitMetatileBehavior); + connect(ui->comboBox_encounterType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitEncounterType); + connect(ui->comboBox_terrainType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitTerrainType); + connect(ui->comboBox_layerType, &NoScrollComboBox::editingFinished, this, &TilesetEditor::commitLayerType); // Behavior if (projectConfig.metatileBehaviorMask) { From d30be0b9af530e913612e092f97e8d64d3ef7dd3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 15 Apr 2025 13:49:23 -0400 Subject: [PATCH 07/25] Fix some inputs moving user's cursor while typing --- include/ui/mapheaderform.h | 2 ++ src/ui/mapheaderform.cpp | 22 ++++++++++++++++------ src/ui/maplisttoolbar.cpp | 4 +++- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 7f246d6d..4f3dc775 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -64,6 +64,8 @@ private: QPointer m_project = nullptr; bool m_allowProjectChanges = true; + void setText(QComboBox *combo, const QString &text) const; + void setText(QLineEdit *lineEdit, const QString &text) const; void setLocations(const QStringList &locations); void updateLocationName(); diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 65fe6ead..a2a0b8b5 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -174,19 +174,29 @@ void MapHeaderForm::updateLocationName() { } // Set data in UI -void MapHeaderForm::setSong(const QString &song) { ui->comboBox_Song->setCurrentText(song); } -void MapHeaderForm::setLocation(const QString &location) { ui->comboBox_Location->setCurrentText(location); } -void MapHeaderForm::setLocationName(const QString &locationName) { ui->lineEdit_LocationName->setText(locationName); } +void MapHeaderForm::setSong(const QString &song) { setText(ui->comboBox_Song, song); } +void MapHeaderForm::setLocation(const QString &location) { setText(ui->comboBox_Location, location); } +void MapHeaderForm::setLocationName(const QString &locationName) { setText(ui->lineEdit_LocationName, locationName); } void MapHeaderForm::setRequiresFlash(bool requiresFlash) { ui->checkBox_RequiresFlash->setChecked(requiresFlash); } -void MapHeaderForm::setWeather(const QString &weather) { ui->comboBox_Weather->setCurrentText(weather); } -void MapHeaderForm::setType(const QString &type) { ui->comboBox_Type->setCurrentText(type); } -void MapHeaderForm::setBattleScene(const QString &battleScene) { ui->comboBox_BattleScene->setCurrentText(battleScene); } +void MapHeaderForm::setWeather(const QString &weather) { setText(ui->comboBox_Weather, weather); } +void MapHeaderForm::setType(const QString &type) { setText(ui->comboBox_Type, type); } +void MapHeaderForm::setBattleScene(const QString &battleScene) { setText(ui->comboBox_BattleScene, battleScene); } void MapHeaderForm::setShowsLocationName(bool showsLocationName) { ui->checkBox_ShowLocationName->setChecked(showsLocationName); } void MapHeaderForm::setAllowsRunning(bool allowsRunning) { ui->checkBox_AllowRunning->setChecked(allowsRunning); } void MapHeaderForm::setAllowsBiking(bool allowsBiking) { ui->checkBox_AllowBiking->setChecked(allowsBiking); } void MapHeaderForm::setAllowsEscaping(bool allowsEscaping) { ui->checkBox_AllowEscaping->setChecked(allowsEscaping); } void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_FloorNumber->setValue(floorNumber); } +// If we always call setText / setCurrentText the user's cursor may move to the end of the text while they're typing. +void MapHeaderForm::setText(QComboBox *combo, const QString &text) const { + if (combo->currentText() != text) + combo->setCurrentText(text); +} +void MapHeaderForm::setText(QLineEdit *lineEdit, const QString &text) const { + if (lineEdit->text() != text) + lineEdit->setText(text); +} + // Read data from UI QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); } QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); } diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 2584c4fd..304d6490 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -121,7 +121,9 @@ void MapListToolBar::applyFilter(const QString &filterText) { return; const QSignalBlocker b(ui->lineEdit_filterBox); - ui->lineEdit_filterBox->setText(filterText); + if (ui->lineEdit_filterBox->text() != filterText) { + ui->lineEdit_filterBox->setText(filterText); + } // The clear button does not properly disappear when filterText is empty. // It seems like this is because blocking the QLineEdit's signals prevents From ee0f5923cebb9b0eb3c3be0b56349dac7b7c0589 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 15 Apr 2025 15:33:07 -0400 Subject: [PATCH 08/25] Better error handling if saving fails --- include/config.h | 2 +- include/core/maplayout.h | 6 +- include/core/paletteutil.h | 2 +- include/core/tileset.h | 20 +-- include/editor.h | 6 +- include/mainwindow.h | 2 +- include/project.h | 39 +++--- include/ui/tileseteditor.h | 4 +- src/config.cpp | 12 +- src/core/maplayout.cpp | 49 +++++++- src/core/paletteutil.cpp | 20 +-- src/core/tileset.cpp | 72 ++++++----- src/editor.cpp | 23 ++-- src/mainwindow.cpp | 15 +-- src/project.cpp | 250 ++++++++++++++++--------------------- src/ui/tileseteditor.cpp | 25 ++-- 16 files changed, 294 insertions(+), 253 deletions(-) diff --git a/include/config.h b/include/config.h index 6746a0f4..0f1e8706 100644 --- a/include/config.h +++ b/include/config.h @@ -26,7 +26,7 @@ static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_ class KeyValueConfigBase { public: - void save(); + bool save(); void load(); virtual ~KeyValueConfigBase(); virtual void reset() = 0; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 40a3a035..3822b5cf 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -116,9 +116,12 @@ public: void clearBorderCache(); void cacheBorder(); - void setClean(); bool hasUnsavedChanges() const; + bool save(const QString &root); + bool saveBorder(const QString &root); + bool saveBlockdata(const QString &root); + bool layoutBlockChanged(int i, const Blockdata &cache); uint16_t getBorderMetatileId(int x, int y); @@ -143,6 +146,7 @@ public: private: void setNewDimensionsBlockdata(int newWidth, int newHeight); void setNewBorderDimensionsBlockdata(int newWidth, int newHeight); + bool writeBlockdata(const QString &path, const Blockdata &blockdata) const; static int getBorderDrawDistance(int dimension, qreal minimum); diff --git a/include/core/paletteutil.h b/include/core/paletteutil.h index ce221026..34e9ae3f 100644 --- a/include/core/paletteutil.h +++ b/include/core/paletteutil.h @@ -7,7 +7,7 @@ namespace PaletteUtil { QList parse(QString filepath, bool *error); - void writeJASC(QString filepath, QVector colors, int offset, int nColors); + bool writeJASC(const QString &filepath, const QVector &colors, int offset, int nColors); } #endif // PALETTEUTIL_H diff --git a/include/core/tileset.h b/include/core/tileset.h index 32d18858..a05afdc3 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -55,17 +55,17 @@ public: static QString getExpectedDir(QString tilesetName, bool isSecondary); QString getExpectedDir(); - void load(); - void loadMetatiles(); - void loadMetatileAttributes(); - void loadTilesImage(QImage *importedImage = nullptr); - void loadPalettes(); + bool load(); + bool loadMetatiles(); + bool loadMetatileAttributes(); + bool loadTilesImage(QImage *importedImage = nullptr); + bool loadPalettes(); - void save(); - void saveMetatileAttributes(); - void saveMetatiles(); - void saveTilesImage(); - void savePalettes(); + bool save(); + bool saveMetatileAttributes(); + bool saveMetatiles(); + bool saveTilesImage(); + bool savePalettes(); bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..4d579093 100644 --- a/include/editor.h +++ b/include/editor.h @@ -57,8 +57,8 @@ public: GridSettings gridSettings; void setProject(Project * project); - void saveAll(); - void saveCurrent(); + bool saveAll(); + bool saveCurrent(); void saveEncounterTabData(); void closeProject(); @@ -199,7 +199,7 @@ private: EditMode editMode = EditMode::None; - void save(bool currentOnly); + bool save(bool currentOnly); void clearMap(); void clearMetatileSelector(); void clearMovementPermissionSelector(); diff --git a/include/mainwindow.h b/include/mainwindow.h index 410bd6e3..de790d63 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -175,7 +175,7 @@ private slots: void on_action_Reload_Project_triggered(); void on_action_Close_Project_triggered(); void on_action_Save_Project_triggered(); - void save(bool currentOnly = false); + bool save(bool currentOnly = false); void openWarpMap(QString map_name, int event_id, Event::Group event_group); diff --git a/include/project.h b/include/project.h index c5aed0a3..d8ef4c36 100644 --- a/include/project.h +++ b/include/project.h @@ -108,10 +108,6 @@ public: bool loadBlockdata(Layout *); bool loadLayoutBorder(Layout *); - void saveTextFile(QString path, QString text); - void appendTextFile(QString path, QString text); - void deleteFile(QString path); - bool readMapGroups(); void addNewMapGroup(const QString &groupName); QString mapNameToMapGroup(const QString &mapName) const; @@ -168,25 +164,20 @@ public: bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); - void loadTilesetAssets(Tileset*); + bool loadTilesetAssets(Tileset*); void loadTilesetMetatileLabels(Tileset*); void readTilesetPaths(Tileset* tileset); - void saveAll(); - void saveGlobalData(); - void saveLayout(Layout *); - void saveLayoutBlockdata(Layout *); - void saveLayoutBorder(Layout *); - void writeBlockdata(QString, const Blockdata &); - void saveMap(Map *map, bool skipLayout = false); - void saveConfig(); - void saveMapLayouts(); - void saveMapGroups(); - void saveRegionMapSections(); - void saveWildMonData(); - void saveHealLocations(); - void saveTilesets(Tileset*, Tileset*); - void saveTilesetMetatileLabels(Tileset*, Tileset*); + bool saveAll(); + bool saveGlobalData(); + bool saveConfig(); + bool saveLayout(Layout *layout); + bool saveMap(Map *map, bool skipLayout = false); + bool saveTextFile(const QString &path, const QString &text); + bool saveRegionMapSections(); + bool saveTilesets(Tileset*, Tileset*); + bool saveTilesetMetatileLabels(Tileset*, Tileset*); + void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); @@ -309,8 +300,6 @@ private: }; QHash locationData; - void updateLayout(Layout *); - void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); @@ -318,6 +307,12 @@ private: void recordFileChange(const QString &filepath); void resetFileCache(); + bool saveMapLayouts(); + bool saveMapGroups(); + bool saveWildMonData(); + bool saveHealLocations(); + bool appendTextFile(const QString &path, const QString &text); + QString findSpeciesIconPath(const QStringList &names) const; int maxEventsPerGroup; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index fdd4751c..b6a60a61 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -71,8 +71,6 @@ private slots: void on_spinBox_paletteSelector_valueChanged(int arg1); - void on_actionSave_Tileset_triggered(); - void on_actionImport_Primary_Tiles_triggered(); void on_actionImport_Secondary_Tiles_triggered(); @@ -173,6 +171,8 @@ private: bool lockSelection = false; QSet metatileReloadQueue; + bool save(); + signals: void tilesetsSaved(QString, QString); }; diff --git a/src/config.cpp b/src/config.cpp index 5c5feb54..e6ac885b 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -233,7 +233,7 @@ void KeyValueConfigBase::load() { file.close(); } -void KeyValueConfigBase::save() { +bool KeyValueConfigBase::save() { QString text = ""; QMap map = this->getKeyValueMap(); for (QMap::iterator it = map.begin(); it != map.end(); it++) { @@ -241,12 +241,14 @@ void KeyValueConfigBase::save() { } QFile file(this->getConfigFilepath()); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - file.close(); - } else { + if (!file.open(QIODevice::WriteOnly)) { logError(QString("Could not open config file '%1' for writing: ").arg(this->getConfigFilepath()) + file.errorString()); + return false; } + + file.write(text.toUtf8()); + file.close(); + return true; } bool KeyValueConfigBase::getConfigBool(QString key, QString value) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 45b35f91..e3d47422 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -467,11 +467,50 @@ QPixmap Layout::getLayoutItemPixmap() { return this->layoutItem ? this->layoutItem->pixmap() : QPixmap(); } -void Layout::setClean() { - this->editHistory.setClean(); - this->hasUnsavedDataChanges = false; -} - bool Layout::hasUnsavedChanges() const { return !this->editHistory.isClean() || this->hasUnsavedDataChanges || !this->newFolderPath.isEmpty(); } + +bool Layout::save(const QString &root) { + if (!this->newFolderPath.isEmpty()) { + // Layout directory doesn't exist yet, create it now. + const QString fullPath = QString("%1/%2").arg(root).arg(this->newFolderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); + return false; + } + this->newFolderPath = QString(); + } + + bool success = true; + if (!saveBorder(root)) success = false; + if (!saveBlockdata(root)) success = false; + if (!success) + return false; + + this->editHistory.setClean(); + this->hasUnsavedDataChanges = false; + return true; +} + +bool Layout::saveBorder(const QString &root) { + QString path = QString("%1/%2").arg(root).arg(this->border_path); + return writeBlockdata(path, this->border); +} + +bool Layout::saveBlockdata(const QString &root) { + QString path = QString("%1/%2").arg(root).arg(this->blockdata_path); + return writeBlockdata(path, this->blockdata); +} + +bool Layout::writeBlockdata(const QString &path, const Blockdata &blockdata) const { + QFile file(path); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not open '%1' for writing: %2").arg(path).arg(file.errorString())); + return false; + } + + QByteArray data = blockdata.serialize(); + file.write(data); + return true; +} diff --git a/src/core/paletteutil.cpp b/src/core/paletteutil.cpp index 929336b2..76a3d0c4 100644 --- a/src/core/paletteutil.cpp +++ b/src/core/paletteutil.cpp @@ -38,14 +38,14 @@ QList PaletteUtil::parse(QString filepath, bool *error) { return QList(); } -void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, int nColors) { +bool PaletteUtil::writeJASC(const QString &filepath, const QVector &palette, int offset, int nColors) { if (!nColors) { - logWarn(QString("Cannot save a palette with no colors.")); - return; + logError(QString("Cannot save a palette with no colors.")); + return false; } if (offset > palette.size() || offset + nColors > palette.size()) { - logWarn("Palette offset out of range for color table."); - return; + logError("Palette offset out of range for color table."); + return false; } QString text = "JASC-PAL\r\n0100\r\n"; @@ -59,11 +59,13 @@ void PaletteUtil::writeJASC(QString filepath, QVector palette, int offset, } QFile file(filepath); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - } else { - logWarn(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); + if (!file.open(QIODevice::WriteOnly)) { + logError(QString("Could not write to file '%1': ").arg(filepath) + file.errorString()); + return false; } + + file.write(text.toUtf8()); + return true; } QList parsePal(QString filepath, bool *error) { diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index f6ce6a2e..0ad43d1a 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -402,13 +402,13 @@ QHash Tileset::getHeaderMemberMap(bool usingAsm) return map; } -void Tileset::loadMetatiles() { +bool Tileset::loadMetatiles() { clearMetatiles(); QFile metatiles_file(this->metatiles_path); if (!metatiles_file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1' for reading.").arg(this->metatiles_path)); - return; + logError(QString("Could not open '%1' for reading: %2").arg(this->metatiles_path).arg(metatiles_file.errorString())); + return false; } QByteArray data = metatiles_file.readAll(); @@ -425,13 +425,14 @@ void Tileset::loadMetatiles() { } m_metatiles.append(metatile); } + return true; } -void Tileset::saveMetatiles() { +bool Tileset::saveMetatiles() { QFile metatiles_file(this->metatiles_path); if (!metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - logError(QString("Could not open '%1' for writing.").arg(this->metatiles_path)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(this->metatiles_path).arg(metatiles_file.errorString())); + return false; } QByteArray data; @@ -444,13 +445,14 @@ void Tileset::saveMetatiles() { } } metatiles_file.write(data); + return true; } -void Tileset::loadMetatileAttributes() { +bool Tileset::loadMetatileAttributes() { QFile attrs_file(this->metatile_attrs_path); if (!attrs_file.open(QIODevice::ReadOnly)) { - logError(QString("Could not open '%1' for reading.").arg(this->metatile_attrs_path)); - return; + logError(QString("Could not open '%1' for reading: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString())); + return false; } QByteArray data = attrs_file.readAll(); @@ -467,13 +469,14 @@ void Tileset::loadMetatileAttributes() { attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); m_metatiles.at(i)->setAttributes(attributes); } + return true; } -void Tileset::saveMetatileAttributes() { +bool Tileset::saveMetatileAttributes() { QFile attrs_file(this->metatile_attrs_path); if (!attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - logError(QString("Could not open '%1' for writing.").arg(this->metatile_attrs_path)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(this->metatile_attrs_path).arg(attrs_file.errorString())); + return false; } QByteArray data; @@ -483,9 +486,10 @@ void Tileset::saveMetatileAttributes() { data.append(static_cast(attributes >> (8 * i))); } attrs_file.write(data); + return true; } -void Tileset::loadTilesImage(QImage *importedImage) { +bool Tileset::loadTilesImage(QImage *importedImage) { QImage image; if (importedImage) { image = *importedImage; @@ -520,23 +524,25 @@ void Tileset::loadTilesImage(QImage *importedImage) { } this->tilesImage = image; this->tiles = tiles; + return true; } -void Tileset::saveTilesImage() { +bool Tileset::saveTilesImage() { // Only write the tiles image if it was changed. // Porymap will only ever change an existing tiles image by importing a new one. if (!m_hasUnsavedTilesImage) - return; + return true; if (!this->tilesImage.save(this->tilesImagePath, "PNG")) { logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath)); - return; + return false; } m_hasUnsavedTilesImage = false; + return true; } -void Tileset::loadPalettes() { +bool Tileset::loadPalettes() { this->palettes.clear(); this->palettePreviews.clear(); @@ -559,26 +565,34 @@ void Tileset::loadPalettes() { this->palettes.append(palette); this->palettePreviews.append(palette); } + return true; } -void Tileset::savePalettes() { +bool Tileset::savePalettes() { + bool success = true; int numPalettes = qMin(this->palettePaths.length(), this->palettes.length()); for (int i = 0; i < numPalettes; i++) { - PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16); + if (!PaletteUtil::writeJASC(this->palettePaths.at(i), this->palettes.at(i).toVector(), 0, 16)) + success = false; } + return success; } -void Tileset::load() { - loadMetatiles(); - loadMetatileAttributes(); - loadTilesImage(); - loadPalettes(); +bool Tileset::load() { + bool success = true; + if (!loadMetatiles()) success = false; + if (!loadMetatileAttributes()) success = false; + if (!loadTilesImage()) success = false; + if (!loadPalettes()) success = false; + return success; } // Because metatile labels are global (and handled by the project) we don't save them here. -void Tileset::save() { - saveMetatiles(); - saveMetatileAttributes(); - saveTilesImage(); - savePalettes(); +bool Tileset::save() { + bool success = true; + if (!saveMetatiles()) success = false; + if (!saveMetatileAttributes()) success = false; + if (!saveTilesImage()) success = false; + if (!savePalettes()) success = false; + return success; } diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..d1347cf1 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -68,30 +68,33 @@ Editor::~Editor() closeProject(); } -void Editor::saveCurrent() { - save(true); +bool Editor::saveCurrent() { + return save(true); } -void Editor::saveAll() { - save(false); +bool Editor::saveAll() { + return save(false); } -void Editor::save(bool currentOnly) { +bool Editor::save(bool currentOnly) { if (!this->project) - return; + return true; saveEncounterTabData(); + bool success = true; if (currentOnly) { if (this->map) { - this->project->saveMap(this->map); + success = this->project->saveMap(this->map); } else if (this->layout) { - this->project->saveLayout(this->layout); + success = this->project->saveLayout(this->layout); } - this->project->saveGlobalData(); + if (!this->project->saveGlobalData()) + success = false; } else { - this->project->saveAll(); + success = this->project->saveAll(); } + return success; } void Editor::setProject(Project * project) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..f0ba072b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1634,16 +1634,15 @@ void MainWindow::on_action_Save_triggered() { save(true); } -void MainWindow::save(bool currentOnly) { - if (currentOnly) { - this->editor->saveCurrent(); - } else { - this->editor->saveAll(); +bool MainWindow::save(bool currentOnly) { + bool success = currentOnly ? this->editor->saveCurrent() : this->editor->saveAll(); + if (!success) { + RecentErrorMessage::show(QStringLiteral("Failed to save some project changes."), this); } updateWindowTitle(); updateMapList(); - if (!porymapConfig.shownInGameReloadMessage) { + if (success && !porymapConfig.shownInGameReloadMessage) { // Show a one-time warning that the user may need to reload their map to see their new changes. InfoMessage::show(QStringLiteral("Reload your map in-game!\n\nIf your game is currently saved on a map you have edited, " "the changes may not appear until you leave the map and return."), @@ -1652,6 +1651,7 @@ void MainWindow::save(bool currentOnly) { } saveGlobalConfigs(); + return success; } void MainWindow::duplicate() { @@ -3048,7 +3048,8 @@ bool MainWindow::closeProject() { auto reply = msgBox.exec(); if (reply == QMessageBox::Yes) { - save(); + if (!save()) + return false; } else if (reply == QMessageBox::No) { logWarn("Closing project with unsaved changes."); } else if (reply == QMessageBox::Cancel) { diff --git a/src/project.cpp b/src/project.cpp index 6f225ff6..f478f904 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -162,8 +162,10 @@ void Project::clearTilesetCache() { Map* Project::loadMap(const QString &mapName) { Map* map = this->maps.value(mapName); - if (!map) + if (!map) { + logError(QString("Unknown map name '%1'.").arg(mapName)); return nullptr; + } if (isMapLoaded(map)) return map; @@ -445,8 +447,13 @@ bool Project::loadLayout(Layout *layout) { Layout *Project::loadLayout(QString layoutId) { Layout *layout = this->mapLayouts.value(layoutId); - if (!layout || !loadLayout(layout)) { - logError(QString("Failed to load layout '%1'").arg(layoutId)); + if (!layout) { + logError(QString("Unknown layout ID '%1'.").arg(layoutId)); + return nullptr; + } + + if (!loadLayout(layout)) { + // Error should already be logged. return nullptr; } return layout; @@ -587,12 +594,12 @@ bool Project::readMapLayouts() { return true; } -void Project::saveMapLayouts() { +bool Project::saveMapLayouts() { QString layoutsFilepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_layouts); QFile layoutsFile(layoutsFilepath); if (!layoutsFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(layoutsFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(layoutsFilepath).arg(layoutsFile.errorString())); + return false; } OrderedJson::object layoutsObj; @@ -626,6 +633,7 @@ void Project::saveMapLayouts() { OrderedJsonDoc jsonDoc(&layoutJson); jsonDoc.dump(&layoutsFile); layoutsFile.close(); + return true; } void Project::ignoreWatchedFileTemporarily(QString filepath) { @@ -651,12 +659,12 @@ void Project::recordFileChange(const QString &filepath) { emit fileChanged(filepath); } -void Project::saveMapGroups() { +bool Project::saveMapGroups() { QString mapGroupsFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups)); QFile mapGroupsFile(mapGroupsFilepath); if (!mapGroupsFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(mapGroupsFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(mapGroupsFilepath).arg(mapGroupsFile.errorString())); + return false; } OrderedJson::object mapGroupsObj; @@ -686,14 +694,15 @@ void Project::saveMapGroups() { OrderedJsonDoc jsonDoc(&mapGroupJson); jsonDoc.dump(&mapGroupsFile); mapGroupsFile.close(); + return true; } -void Project::saveRegionMapSections() { +bool Project::saveRegionMapSections() { const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); QFile file(filepath); if (!file.open(QIODevice::WriteOnly)) { - logError(QString("Could not open '%1' for writing").arg(filepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(filepath).arg(file.errorString())); + return false; } OrderedJson::array mapSectionArray; @@ -727,16 +736,17 @@ void Project::saveRegionMapSections() { OrderedJsonDoc jsonDoc(&json); jsonDoc.dump(&file); file.close(); + return true; } -void Project::saveWildMonData() { - if (!this->wildEncountersLoaded) return; +bool Project::saveWildMonData() { + if (!this->wildEncountersLoaded) return true; QString wildEncountersJsonFilepath = QString("%1/%2").arg(root).arg(projectConfig.getFilePath(ProjectFilePath::json_wild_encounters)); QFile wildEncountersFile(wildEncountersJsonFilepath); if (!wildEncountersFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(wildEncountersJsonFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(wildEncountersJsonFilepath).arg(wildEncountersFile.errorString())); + return false; } OrderedJson::object wildEncountersObject; @@ -822,6 +832,7 @@ void Project::saveWildMonData() { OrderedJsonDoc jsonDoc(&encounterJson); jsonDoc.dump(&wildEncountersFile); wildEncountersFile.close(); + return true; } // For a map with a constant of 'MAP_FOO', returns a unique 'HEAL_LOCATION_FOO'. @@ -838,12 +849,12 @@ QString Project::getNewHealLocationName(const Map* map) const { return toUniqueIdentifier(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + idName); } -void Project::saveHealLocations() { +bool Project::saveHealLocations() { const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_heal_locations)); QFile file(filepath); if (!file.open(QIODevice::WriteOnly)) { - logError(QString("Could not open '%1' for writing").arg(filepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(filepath).arg(file.errorString())); + return false; } // Build the JSON data for output. @@ -886,17 +897,21 @@ void Project::saveHealLocations() { OrderedJsonDoc jsonDoc(&json); jsonDoc.dump(&file); file.close(); + return true; } -void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { - saveTilesetMetatileLabels(primaryTileset, secondaryTileset); - if (primaryTileset) - primaryTileset->save(); - if (secondaryTileset) - secondaryTileset->save(); +bool Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { + bool success = saveTilesetMetatileLabels(primaryTileset, secondaryTileset); + if (primaryTileset && !primaryTileset->save()) + success = false; + if (secondaryTileset && !secondaryTileset->save()) + success = false; + return success; } void Project::updateTilesetMetatileLabels(Tileset *tileset) { + if (!tileset) return; + // Erase old labels, then repopulate with new labels const QString prefix = tileset->getMetatileLabelPrefix(); this->metatileLabelsMap[tileset->name].clear(); @@ -931,11 +946,11 @@ QString Project::buildMetatileLabelsText(const QMap defines) return output; } -void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *secondaryTileset) { +bool Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *secondaryTileset) { // Skip writing the file if there are no labels in both the new and old sets - if (metatileLabelsMap[primaryTileset->name].size() == 0 && primaryTileset->metatileLabels.size() == 0 - && metatileLabelsMap[secondaryTileset->name].size() == 0 && secondaryTileset->metatileLabels.size() == 0) - return; + if ((!primaryTileset || (metatileLabelsMap[primaryTileset->name].size() == 0 && primaryTileset->metatileLabels.size() == 0)) + && (!secondaryTileset || (metatileLabelsMap[secondaryTileset->name].size() == 0 && secondaryTileset->metatileLabels.size() == 0))) + return true; updateTilesetMetatileLabels(primaryTileset); updateTilesetMetatileLabels(secondaryTileset); @@ -962,42 +977,23 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_labels); ignoreWatchedFileTemporarily(root + "/" + filename); - saveTextFile(root + "/" + filename, outputText); + return saveTextFile(root + "/" + filename, outputText); } bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); - if (!layout->tileset_primary) { - QString defaultTileset = this->getDefaultPrimaryTilesetLabel(); - layout->tileset_primary_label = defaultTileset; - layout->tileset_primary = getTileset(layout->tileset_primary_label); - if (!layout->tileset_primary) { - logError(QString("%1 has invalid primary tileset '%2'.").arg(layout->name).arg(layout->tileset_primary_label)); - return false; - } - logWarn(QString("%1 has invalid primary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_primary_label).arg(defaultTileset)); - } - layout->tileset_secondary = getTileset(layout->tileset_secondary_label); - if (!layout->tileset_secondary) { - QString defaultTileset = this->getDefaultSecondaryTilesetLabel(); - layout->tileset_secondary_label = defaultTileset; - layout->tileset_secondary = getTileset(layout->tileset_secondary_label); - if (!layout->tileset_secondary) { - logError(QString("%1 has invalid secondary tileset '%2'.").arg(layout->name).arg(layout->tileset_secondary_label)); - return false; - } - logWarn(QString("%1 has invalid secondary tileset '%2'. Using default '%3'").arg(layout->name).arg(layout->tileset_secondary_label).arg(defaultTileset)); - } - return true; + return layout->tileset_primary && layout->tileset_secondary; } Tileset* Project::loadTileset(QString label, Tileset *tileset) { auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets); if (this->usingAsmTilesets) { // Read asm tileset header. Backwards compatibility - const QStringList values = parser.getLabelValues(parser.parseAsm(projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm)), label); + const QString path = projectConfig.getFilePath(ProjectFilePath::tilesets_headers_asm); + const QStringList values = parser.getLabelValues(parser.parseAsm(path), label); if (values.isEmpty()) { + logError(QString("Failed to find header data in '%1' for tileset '%2'.").arg(path).arg(label)); return nullptr; } if (tileset == nullptr) { @@ -1011,8 +1007,10 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->metatile_attrs_label = values.value(memberMap.key("metatileAttributes")); } else { // Read C tileset header - auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap); + const QString path = projectConfig.getFilePath(ProjectFilePath::tilesets_headers); + auto structs = parser.readCStructs(path, label, memberMap); if (!structs.contains(label)) { + logError(QString("Failed to find header data in '%1' for tileset '%2'.").arg(path).arg(label)); return nullptr; } if (tileset == nullptr) { @@ -1027,7 +1025,11 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->metatile_attrs_label = tilesetAttributes.value("metatileAttributes"); } - loadTilesetAssets(tileset); + if (!loadTilesetAssets(tileset)) { + // Error should already be logged. + delete tileset; + return nullptr; + } tilesetCache.insert(label, tileset); return tileset; @@ -1116,38 +1118,22 @@ void Project::setNewLayoutBorder(Layout *layout) { layout->lastCommitBlocks.borderDimensions = QSize(width, height); } -void Project::saveLayoutBorder(Layout *layout) { - QString path = QString("%1/%2").arg(root).arg(layout->border_path); - writeBlockdata(path, layout->border); -} - -void Project::saveLayoutBlockdata(Layout *layout) { - QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); - writeBlockdata(path, layout->blockdata); -} - -void Project::writeBlockdata(QString path, const Blockdata &blockdata) { - QFile file(path); - if (file.open(QIODevice::WriteOnly)) { - QByteArray data = blockdata.serialize(); - file.write(data); - } else { - logError(QString("Failed to open blockdata file for writing: '%1'").arg(path)); - } -} - -void Project::saveAll() { +bool Project::saveAll() { + bool success = true; for (auto map : this->maps) { - saveMap(map, true); // Avoid double-saving the layouts + if (!saveMap(map, true)) // Avoid double-saving the layouts + success = false; } for (auto layout : this->mapLayouts) { - saveLayout(layout); + if (!saveLayout(layout)) + success = false; } - saveGlobalData(); + if (!saveGlobalData()) success = false; + return success; } -void Project::saveMap(Map *map, bool skipLayout) { - if (!map || !isMapLoaded(map)) return; +bool Project::saveMap(Map *map, bool skipLayout) { + if (!map || !isMapLoaded(map)) return true; // Create/Modify a few collateral files for brand new maps. const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); @@ -1155,7 +1141,7 @@ void Project::saveMap(Map *map, bool skipLayout) { if (!map->isPersistedToFile()) { if (!QDir::root().mkpath(fullPath)) { logError(QString("Failed to create directory for new map: '%1'").arg(fullPath)); - return; + return false; } // Create file data/maps//scripts.inc @@ -1179,8 +1165,8 @@ void Project::saveMap(Map *map, bool skipLayout) { QString mapFilepath = fullPath + "/map.json"; QFile mapFile(mapFilepath); if (!mapFile.open(QIODevice::WriteOnly)) { - logError(QString("Error: Could not open %1 for writing").arg(mapFilepath)); - return; + logError(QString("Could not open '%1' for writing: %2").arg(mapFilepath).arg(mapFile.errorString())); + return false; } OrderedJson::object mapObj; @@ -1276,72 +1262,61 @@ void Project::saveMap(Map *map, bool skipLayout) { jsonDoc.dump(&mapFile); mapFile.close(); - if (!skipLayout) saveLayout(map->layout()); - // Try to record the MAPSEC name in case this is a new name. addNewMapsec(map->header()->location()); - map->setClean(); + + if (!skipLayout && !saveLayout(map->layout())) + return false; + return true; } -void Project::saveLayout(Layout *layout) { +bool Project::saveLayout(Layout *layout) { if (!layout || !isLayoutLoaded(layout)) - return; + return true; - if (!layout->newFolderPath.isEmpty()) { - // Layout directory doesn't exist yet, create it now. - const QString fullPath = QString("%1/%2").arg(this->root).arg(layout->newFolderPath); - if (!QDir::root().mkpath(fullPath)) { - logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); - return; - } - layout->newFolderPath = QString(); - } - - saveLayoutBorder(layout); - saveLayoutBlockdata(layout); + if (!layout->save(this->root)) + return false; // Update global data structures with current map data. - updateLayout(layout); - - layout->setClean(); -} - -void Project::updateLayout(Layout *layout) { if (!this->layoutIdsMaster.contains(layout->id)) { this->layoutIdsMaster.append(layout->id); } if (this->mapLayoutsMaster.contains(layout->id)) { this->mapLayoutsMaster[layout->id]->copyFrom(layout); - } - else { + } else { this->mapLayoutsMaster.insert(layout->id, layout->copy()); } + return true; } -void Project::saveGlobalData() { - saveMapLayouts(); - saveMapGroups(); - saveRegionMapSections(); - saveHealLocations(); - saveWildMonData(); - saveConfig(); +bool Project::saveGlobalData() { + bool success = true; + if (!saveMapLayouts()) success = false; + if (!saveMapGroups()) success = false; + if (!saveRegionMapSections()) success = false; + if (!saveHealLocations()) success = false; + if (!saveWildMonData()) success = false; + if (!saveConfig()) success = false; + if (!success) + return false; + this->hasUnsavedDataChanges = false; + return true; } -void Project::saveConfig() { - projectConfig.save(); - userConfig.save(); +bool Project::saveConfig() { + bool success = true; + if (!projectConfig.save()) success = false; + if (!userConfig.save()) success = false; + return success; } -void Project::loadTilesetAssets(Tileset* tileset) { - if (tileset->name.isNull()) { - return; - } +bool Project::loadTilesetAssets(Tileset* tileset) { readTilesetPaths(tileset); loadTilesetMetatileLabels(tileset); - tileset->load(); + return tileset->load(); } void Project::readTilesetPaths(Tileset* tileset) { @@ -1535,6 +1510,8 @@ bool Project::readTilesetMetatileLabels() { } void Project::loadTilesetMetatileLabels(Tileset* tileset) { + if (!tileset || tileset->name.isEmpty()) return; + QString metatileLabelPrefix = tileset->getMetatileLabelPrefix(); // Reverse map for faster lookup by metatile id @@ -1576,29 +1553,24 @@ Tileset* Project::getTileset(QString label, bool forceLoad) { } } -void Project::saveTextFile(QString path, QString text) { +bool Project::saveTextFile(const QString &path, const QString &text) { QFile file(path); - if (file.open(QIODevice::WriteOnly)) { - file.write(text.toUtf8()); - } else { + if (!file.open(QIODevice::WriteOnly)) { logError(QString("Could not open '%1' for writing: ").arg(path) + file.errorString()); + return false; } + file.write(text.toUtf8()); + return true; } -void Project::appendTextFile(QString path, QString text) { +bool Project::appendTextFile(const QString &path, const QString &text) { QFile file(path); - if (file.open(QIODevice::Append)) { - file.write(text.toUtf8()); - } else { + if (!file.open(QIODevice::Append)) { logError(QString("Could not open '%1' for appending: ").arg(path) + file.errorString()); + return false; } -} - -void Project::deleteFile(QString path) { - QFile file(path); - if (file.exists() && !file.remove()) { - logError(QString("Could not delete file '%1': ").arg(path) + file.errorString()); - } + file.write(text.toUtf8()); + return true; } bool Project::readWildMonData() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 3d610fb9..d21cb981 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -33,6 +33,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) this->tileYFlip = ui->checkBox_yFlip->isChecked(); this->paletteId = ui->spinBox_paletteSelector->value(); + connect(ui->actionSave_Tileset, &QAction::triggered, this, &TilesetEditor::save); + ui->actionShow_Tileset_Divider->setChecked(porymapConfig.showTilesetEditorDivider); ui->actionShow_Raw_Metatile_Attributes->setChecked(porymapConfig.showTilesetEditorRawAttributes); @@ -94,7 +96,7 @@ void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondar QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes); if (result == QMessageBox::Yes) - this->on_actionSave_Tileset_triggered(); + this->save(); } this->setTilesets(primaryTilesetLabel, secondaryTilesetLabel); this->refresh(); @@ -688,19 +690,23 @@ void TilesetEditor::commitLayerType() { this->metatileSelector->drawSelectedMetatile(); // Changing the layer type can affect how fully transparent metatiles appear } -void TilesetEditor::on_actionSave_Tileset_triggered() -{ +bool TilesetEditor::save() { // Need this temporary flag to stop selection resetting after saving. // This is a workaround; redrawing the map's metatile selector shouldn't emit the same signal as when it's selected. this->lockSelection = true; - this->project->saveTilesets(this->primaryTileset, this->secondaryTileset); + + bool success = this->project->saveTilesets(this->primaryTileset, this->secondaryTileset); emit this->tilesetsSaved(this->primaryTileset->name, this->secondaryTileset->name); if (this->paletteEditor) { this->paletteEditor->setTilesets(this->primaryTileset, this->secondaryTileset); } - this->ui->statusbar->showMessage(QString("Saved primary and secondary Tilesets!"), 5000); - this->hasUnsavedChanges = false; + this->ui->statusbar->showMessage(success ? QStringLiteral("Saved primary and secondary Tilesets!") + : QStringLiteral("Failed to save tilesets! See log for details."), 5000); + if (success) { + this->hasUnsavedChanges = false; + } this->lockSelection = false; + return success; } void TilesetEditor::on_actionImport_Primary_Tiles_triggered() @@ -812,8 +818,11 @@ void TilesetEditor::closeEvent(QCloseEvent *event) QMessageBox::Yes); if (result == QMessageBox::Yes) { - this->on_actionSave_Tileset_triggered(); - event->accept(); + if (this->save()) { + event->accept(); + } else { + event->ignore(); + } } else if (result == QMessageBox::No) { this->reset(); event->accept(); From 428693a6c9ce41b6da4fa574ce810b2a41f9cf9c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 16 Apr 2025 13:44:46 -0400 Subject: [PATCH 09/25] Remove now-unnecessary tileset loading --- src/ui/tileseteditor.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index d21cb981..f9a09ce8 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -1152,12 +1152,6 @@ void TilesetEditor::countMetatileUsage() { this->metatileSelector->usedMetatiles.fill(0); for (auto layout : this->project->mapLayouts) { - // It's possible for a layout's tileset labels to change if they are invalid, - // so we need to load all the tilesets even if they aren't the tileset we're looking for. - // Otherwise the metatile usage counts may change because the layouts with invalid tilesets - // were updated to use a tileset we were looking for. - this->project->loadLayoutTilesets(layout); - bool usesPrimary = (layout->tileset_primary_label == this->primaryTileset->name); bool usesSecondary = (layout->tileset_secondary_label == this->secondaryTileset->name); @@ -1196,10 +1190,10 @@ void TilesetEditor::countTileUsage() { QSet secondaryTilesets; for (auto &layout : this->project->mapLayouts) { - this->project->loadLayoutTilesets(layout); if (layout->tileset_primary_label == this->primaryTileset->name || layout->tileset_secondary_label == this->secondaryTileset->name) { // need to check metatiles + this->project->loadLayoutTilesets(layout); if (layout->tileset_primary && layout->tileset_secondary) { primaryTilesets.insert(layout->tileset_primary); secondaryTilesets.insert(layout->tileset_secondary); From db246873600d126f4cc6085186715988a491ac5e Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 20 Feb 2025 12:01:55 -0500 Subject: [PATCH 10/25] Add player view size settings --- forms/projectsettingseditor.ui | 43 ++++++++++++++++++++++++++++++++ include/config.h | 8 +++--- include/editor.h | 1 + include/ui/movablerect.h | 4 +-- src/config.cpp | 14 ++++++++--- src/editor.cpp | 16 ++++++++++-- src/mainwindow.cpp | 2 ++ src/project.cpp | 4 +-- src/ui/projectsettingseditor.cpp | 12 ++++++--- 9 files changed, 86 insertions(+), 18 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 614af3ea..696df9ac 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -280,6 +280,49 @@ + + + + Player View Size + + + + + + Width + + + + + + + <html><head/><body><p>The horizontal size in pixels of the area that the player can see in-game (normally, the full width of the GBA screen).</p></body></html> + + + 16 + + + + + + + Height + + + + + + + <html><head/><body><p>The vertical size in pixels of the area that the player can see in-game (normally, the full height of the GBA screen).</p></body></html> + + + 16 + + + + + + diff --git a/include/config.h b/include/config.h index 9ee3785b..18422e09 100644 --- a/include/config.h +++ b/include/config.h @@ -331,8 +331,8 @@ public: this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); this->collisionSheetPath = QString(); - this->collisionSheetWidth = 2; - this->collisionSheetHeight = 16; + this->collisionSheetSize = QSize(2, 16); + this->playerViewSize = QSize(240, 160); this->blockMetatileIdMask = 0x03FF; this->blockCollisionMask = 0x0C00; this->blockElevationMask = 0xF000; @@ -408,8 +408,8 @@ public: uint16_t unusedTileSplit; bool mapAllowFlagsEnabled; QString collisionSheetPath; - int collisionSheetWidth; - int collisionSheetHeight; + QSize collisionSheetSize; + QSize playerViewSize; QList warpBehaviors; int maxEventsPerGroup; diff --git a/include/editor.h b/include/editor.h index 48fdfc0d..b1429ef2 100644 --- a/include/editor.h +++ b/include/editor.h @@ -119,6 +119,7 @@ public: void redrawEventPixmapItem(DraggablePixmapItem *item); qreal getEventOpacity(const Event *event) const; + void setPlayerViewSize(const QSize &size); void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 56798a0c..87f7a36e 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -15,8 +15,8 @@ public: qreal penWidth = 4; return QRectF(-penWidth, -penWidth, - 30 * 8 + penWidth * 2, - 20 * 8 + penWidth * 2); + this->rect().width() + penWidth * 2, + this->rect().height() + penWidth * 2); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { diff --git a/src/config.cpp b/src/config.cpp index d6e0d329..8c139942 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -832,9 +832,13 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { - this->collisionSheetWidth = getConfigUint32(key, value, 1, Block::maxValue); + this->collisionSheetSize.setWidth(getConfigInteger(key, value, 1, Block::maxValue)); } else if (key == "collision_sheet_height") { - this->collisionSheetHeight = getConfigUint32(key, value, 1, Block::maxValue); + this->collisionSheetSize.setHeight(getConfigInteger(key, value, 1, Block::maxValue)); + } else if (key == "player_view_width") { + this->playerViewSize.setWidth(getConfigInteger(key, value, 16, INT_MAX, 240)); + } else if (key == "player_view_height") { + this->playerViewSize.setHeight(getConfigInteger(key, value, 16, INT_MAX, 160)); } else if (key == "warp_behaviors") { this->warpBehaviors.clear(); value.remove(" "); @@ -935,8 +939,10 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); } map.insert("collision_sheet_path", this->collisionSheetPath); - map.insert("collision_sheet_width", QString::number(this->collisionSheetWidth)); - map.insert("collision_sheet_height", QString::number(this->collisionSheetHeight)); + map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); + map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); + map.insert("player_view_width", QString::number(this->playerViewSize.width())); + map.insert("player_view_height", QString::number(this->playerViewSize.height())); QStringList warpBehaviorStrs; for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); diff --git a/src/editor.cpp b/src/editor.cpp index 1cb3a304..db4d4694 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1061,6 +1061,18 @@ void Editor::scaleMapView(int s) { ui->graphicsView_Connections->setTransform(transform); } +void Editor::setPlayerViewSize(const QSize &size) { + if (!this->playerViewRect) + return; + + auto rect = this->playerViewRect->rect(); + rect.setWidth(qMax(size.width(), 16)); + rect.setHeight(qMax(size.height(), 16)); + this->playerViewRect->setRect(rect); + if (ui->graphicsView_Map->scene()) + ui->graphicsView_Map->scene()->update(); +} + void Editor::updateCursorRectPos(int x, int y) { if (this->playerViewRect) this->playerViewRect->updateLocation(x, y); @@ -2338,8 +2350,8 @@ void Editor::setCollisionGraphics() { // Users are not required to provide an image that gives an icon for every elevation/collision combination. // Instead they tell us how many are provided in their image by specifying the number of columns and rows. - const int imgColumns = projectConfig.collisionSheetWidth; - const int imgRows = projectConfig.collisionSheetHeight; + const int imgColumns = projectConfig.collisionSheetSize.width(); + const int imgRows = projectConfig.collisionSheetSize.height(); // Create a pixmap for the selector on the Collision tab. If a project was previously opened we'll also need to refresh the selector. this->collisionSheetPixmap = QPixmap::fromImage(imgSheet).scaled(MovementPermissionsSelector::CellWidth * imgColumns, diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 20ecdaa8..076f8f8f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1171,6 +1171,8 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled); + this->editor->setPlayerViewSize(projectConfig.playerViewSize); + editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); ui->spinBox_SelectedCollision->setMaximum(Block::getMaxCollision()); diff --git a/src/project.cpp b/src/project.cpp index d209a890..158d4f02 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3268,8 +3268,8 @@ void Project::applyParsedLimits() { projectConfig.defaultMetatileId = qMin(projectConfig.defaultMetatileId, Block::getMaxMetatileId()); projectConfig.defaultElevation = qMin(projectConfig.defaultElevation, Block::getMaxElevation()); projectConfig.defaultCollision = qMin(projectConfig.defaultCollision, Block::getMaxCollision()); - projectConfig.collisionSheetHeight = qMin(qMax(projectConfig.collisionSheetHeight, 1), Block::getMaxElevation() + 1); - projectConfig.collisionSheetWidth = qMin(qMax(projectConfig.collisionSheetWidth, 1), Block::getMaxCollision() + 1); + projectConfig.collisionSheetSize.setHeight(qMin(qMax(projectConfig.collisionSheetSize.height(), 1), Block::getMaxElevation() + 1)); + projectConfig.collisionSheetSize.setWidth(qMin(qMax(projectConfig.collisionSheetSize.width(), 1), Block::getMaxCollision() + 1)); } bool Project::hasUnsavedChanges() { diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index e1c2becf..8b47913a 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -138,6 +138,8 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_MaxEvents->setMaximum(INT_MAX); ui->spinBox_MapWidth->setMaximum(INT_MAX); ui->spinBox_MapHeight->setMaximum(INT_MAX); + ui->spinBox_PlayerViewWidth->setMaximum(INT_MAX); + ui->spinBox_PlayerViewHeight->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -460,8 +462,8 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_FillMetatile->setValue(projectConfig.defaultMetatileId); ui->spinBox_MapWidth->setValue(projectConfig.defaultMapSize.width()); ui->spinBox_MapHeight->setValue(projectConfig.defaultMapSize.height()); - ui->spinBox_MaxElevation->setValue(projectConfig.collisionSheetHeight - 1); - ui->spinBox_MaxCollision->setValue(projectConfig.collisionSheetWidth - 1); + ui->spinBox_MaxElevation->setValue(projectConfig.collisionSheetSize.height() - 1); + ui->spinBox_MaxCollision->setValue(projectConfig.collisionSheetSize.width() - 1); ui->spinBox_BehaviorMask->setValue(projectConfig.metatileBehaviorMask & ui->spinBox_BehaviorMask->maximum()); ui->spinBox_EncounterTypeMask->setValue(projectConfig.metatileEncounterTypeMask & ui->spinBox_EncounterTypeMask->maximum()); ui->spinBox_LayerTypeMask->setValue(projectConfig.metatileLayerTypeMask & ui->spinBox_LayerTypeMask->maximum()); @@ -473,6 +475,8 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup); + ui->spinBox_PlayerViewWidth->setValue(projectConfig.playerViewSize.width()); + ui->spinBox_PlayerViewHeight->setValue(projectConfig.playerViewSize.height()); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -537,8 +541,7 @@ void ProjectSettingsEditor::save() { projectConfig.defaultCollision = ui->spinBox_Collision->value(); projectConfig.defaultMetatileId = ui->spinBox_FillMetatile->value(); projectConfig.defaultMapSize = QSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); - projectConfig.collisionSheetHeight = ui->spinBox_MaxElevation->value() + 1; - projectConfig.collisionSheetWidth = ui->spinBox_MaxCollision->value() + 1; + projectConfig.collisionSheetSize = QSize(ui->spinBox_MaxElevation->value() + 1, ui->spinBox_MaxCollision->value() + 1); projectConfig.metatileBehaviorMask = ui->spinBox_BehaviorMask->value(); projectConfig.metatileTerrainTypeMask = ui->spinBox_TerrainTypeMask->value(); projectConfig.metatileEncounterTypeMask = ui->spinBox_EncounterTypeMask->value(); @@ -550,6 +553,7 @@ void ProjectSettingsEditor::save() { projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value(); + projectConfig.playerViewSize = QSize(ui->spinBox_PlayerViewWidth->value(), ui->spinBox_PlayerViewHeight->value()); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); From a6ec91724f84e91427b8aa4faebea19d51962cb1 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 16 Apr 2025 15:01:58 -0400 Subject: [PATCH 11/25] Replace BORDER_DISTANCE with actual view distance --- include/core/map.h | 4 ---- include/core/maplayout.h | 1 + include/project.h | 2 ++ include/ui/movablerect.h | 8 ++++---- src/core/map.cpp | 9 +++++---- src/core/maplayout.cpp | 20 +++++++++++++++----- src/editor.cpp | 25 ++++++------------------- src/project.cpp | 15 +++++++++++++++ src/ui/mapimageexporter.cpp | 12 ++++++------ 9 files changed, 54 insertions(+), 42 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index aaf5b8b7..8c4bead4 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -22,10 +22,6 @@ #define MAX_BORDER_WIDTH 255 #define MAX_BORDER_HEIGHT 255 -// Number of metatiles to draw out from edge of map. Could allow modification of this in the future. -// porymap will reflect changes to it, but the value is hard-coded in the projects at the moment -#define BORDER_DISTANCE 7 - class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 40a3a035..8d1d64bc 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -98,6 +98,7 @@ public: int getBorderHeight() const { return border_height; } int getBorderDrawWidth() const; int getBorderDrawHeight() const; + QRect getVisibleRect() const; bool isWithinBounds(int x, int y) const; bool isWithinBounds(const QRect &rect) const; diff --git a/include/project.h b/include/project.h index 93e6b162..b9a94776 100644 --- a/include/project.h +++ b/include/project.h @@ -256,6 +256,8 @@ public: static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); + static QSize getViewDistance(); + static QSize getMetatileViewDistance(); static int getNumTilesPrimary() { return num_tiles_primary; } static int getNumTilesTotal() { return num_tiles_total; } static int getNumMetatilesPrimary() { return num_metatiles_primary; } diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 87f7a36e..a9f15917 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -22,10 +22,10 @@ public: void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { if (!(*enabled)) return; painter->setPen(this->color); - painter->drawRect(this->rect().x() - 2, this->rect().y() - 2, this->rect().width() + 3, this->rect().height() + 3); - painter->setPen(QColor(0, 0, 0)); - painter->drawRect(this->rect().x() - 3, this->rect().y() - 3, this->rect().width() + 5, this->rect().height() + 5); - painter->drawRect(this->rect().x() - 1, this->rect().y() - 1, this->rect().width() + 1, this->rect().height() + 1); + painter->drawRect(this->rect() + QMargins(1,1,1,1)); // Fill + painter->setPen(Qt::black); + painter->drawRect(this->rect() + QMargins(2,2,2,2)); // Outer border + painter->drawRect(this->rect()); // Inner border } void updateLocation(int x, int y); bool *enabled; diff --git a/src/core/map.cpp b/src/core/map.cpp index b9fe4c90..793090c0 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -83,16 +83,17 @@ QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) cons int x = 0, y = 0; int w = getWidth(), h = getHeight(); + QSize viewDistance = Project::getMetatileViewDistance(); if (direction == "up") { - h = qMin(h, BORDER_DISTANCE); + h = qMin(h, viewDistance.height()); y = getHeight() - h; } else if (direction == "down") { - h = qMin(h, BORDER_DISTANCE); + h = qMin(h, viewDistance.height()); } else if (direction == "left") { - w = qMin(w, BORDER_DISTANCE); + w = qMin(w, viewDistance.width()); x = getWidth() - w; } else if (direction == "right") { - w = qMin(w, BORDER_DISTANCE); + w = qMin(w, viewDistance.width()); } else if (MapConnection::isDiving(direction)) { if (fromLayout) { w = qMin(w, fromLayout->getWidth()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 45b35f91..e70d8040 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -64,16 +64,18 @@ bool Layout::isWithinBorderBounds(int x, int y) const { } int Layout::getBorderDrawWidth() const { - return getBorderDrawDistance(border_width, BORDER_DISTANCE); + return getBorderDrawDistance(border_width, Project::getMetatileViewDistance().width()); } int Layout::getBorderDrawHeight() const { - return getBorderDrawDistance(border_height, BORDER_DISTANCE); + return getBorderDrawDistance(border_height, Project::getMetatileViewDistance().height()); } -// We need to draw sufficient border blocks to fill the area that gets loaded around the player in-game (BORDER_DISTANCE). -// Note that this is not the same as the player's view distance. -// The result will be some multiple of the input dimension, because we only draw the border in increments of its full width/height. +// Calculate the distance away from the layout's edge that we need to start drawing border blocks. +// We need to fulfill two requirements here: +// - We should draw enough to fill the player's in-game view +// - The value should be some multiple of the border's dimension +// (otherwise the border won't be positioned the same as it would in-game). int Layout::getBorderDrawDistance(int dimension, qreal minimum) { if (dimension >= minimum) return dimension; @@ -82,6 +84,14 @@ int Layout::getBorderDrawDistance(int dimension, qreal minimum) { return dimension * qCeil(minimum / qMax(dimension, 1)); } +// Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border. +QRect Layout::getVisibleRect() const { + QRect area = QRect(0, 0, this->width * 16, this->height * 16); + QSize viewDistance = Project::getMetatileViewDistance() * 16; + area += QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); + return area; +} + bool Layout::getBlock(int x, int y, Block *out) { if (isWithinBounds(x, y)) { int i = y * getWidth() + x; diff --git a/src/editor.cpp b/src/editor.cpp index db4d4694..54fc9640 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1572,14 +1572,8 @@ void Editor::displayMapMetatiles() { map_item->draw(true); scene->addItem(map_item); - int tw = 16; - int th = 16; - scene->setSceneRect( - -BORDER_DISTANCE * tw, - -BORDER_DISTANCE * th, - map_item->pixmap().width() + BORDER_DISTANCE * 2 * tw, - map_item->pixmap().height() + BORDER_DISTANCE * 2 * th - ); + // Scene rect is the map plus a margin that gives enough space to scroll and see the edge of the player view rectangle. + scene->setSceneRect(this->layout->getVisibleRect() + QMargins(3,3,3,3)); } void Editor::clearMapMovementPermissions() { @@ -1772,18 +1766,13 @@ void Editor::clearConnectionMask() { } } -// Hides connected map tiles that cannot be seen from the current map (beyond BORDER_DISTANCE). +// Hides connected map tiles that cannot be seen from the current map void Editor::maskNonVisibleConnectionTiles() { clearConnectionMask(); QPainterPath mask; mask.addRect(scene->itemsBoundingRect().toRect()); - mask.addRect( - -BORDER_DISTANCE * 16, - -BORDER_DISTANCE * 16, - (layout->getWidth() + BORDER_DISTANCE * 2) * 16, - (layout->getHeight() + BORDER_DISTANCE * 2) * 16 - ); + mask.addRect(layout->getVisibleRect()); // Mask the tiles with the current theme's background color. QPen pen(ui->graphicsView_Map->palette().color(QPalette::Active, QPalette::Base)); @@ -1805,13 +1794,11 @@ void Editor::clearMapBorder() { void Editor::displayMapBorder() { clearMapBorder(); - int borderWidth = this->layout->getBorderWidth(); - int borderHeight = this->layout->getBorderHeight(); int borderHorzDist = this->layout->getBorderDrawWidth(); int borderVertDist = this->layout->getBorderDrawHeight(); QPixmap pixmap = this->layout->renderBorder(); - for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += borderHeight) - for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += borderWidth) { + for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += this->layout->getBorderHeight()) + for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += this->layout->getBorderWidth()) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); diff --git a/src/project.cpp b/src/project.cpp index 158d4f02..d91d63da 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3235,6 +3235,21 @@ QString Project::getEmptySpeciesName() { return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty); } +// Get the distance in pixels that the player is able to see from the space they're standing on. +// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 112x72. +QSize Project::getViewDistance() { + return ((projectConfig.playerViewSize) - QSize(16,16)) / 2; +} + +// Get the distance in metatiles that the player is able to see from the space they're standing on, rounded up. +// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 7x5 metatiles. +QSize Project::getMetatileViewDistance() { + QSize viewDistance = getViewDistance(); + viewDistance.setWidth(qCeil(viewDistance.width() / 16.0)); + viewDistance.setHeight(qCeil(viewDistance.height() / 16.0)); + return viewDistance; +} + // If the provided filepath is an absolute path to an existing file, return filepath. // If not, and the provided filepath is a relative path from the project dir to an existing file, return the relative path. // Otherwise return empty string. diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 58b63cbd..9a65af8f 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -606,9 +606,11 @@ QPixmap MapImageExporter::getFormattedMapPixmap() { QMargins MapImageExporter::getMargins(const Map *map) { QMargins margins; if (m_settings.showBorder) { - // The border may technically extend beyond BORDER_DISTANCE, but when the border is painted - // we will be limiting it to the visible sight range. - margins = QMargins(BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE) * 16; + // When we render map borders we render them in full increments of the border dimensions. + // This means for large border dimensions the painted area of the border may extend well beyond the area the player can see. + // When we call paintBorder we will clip the painting to this visible area, so we only need to consider the visible area here. + QSize viewDistance = m_project->getMetatileViewDistance() * 16; + margins = QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); } else if (map && connectionsEnabled()) { for (const auto &connection : map->getConnections()) { const QString dir = connection->direction(); @@ -649,10 +651,8 @@ void MapImageExporter::paintBorder(QPainter *painter, Layout *layout) { layout->renderBorder(true); // Clip parts of the border that would be beyond player visibility. - QRect visibleArea(0, 0, layout->getWidth() * 16, layout->getHeight() * 16); - visibleArea += (QMargins(BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE, BORDER_DISTANCE) * 16); painter->save(); - painter->setClipRect(visibleArea); + painter->setClipRect(layout->getVisibleRect()); int borderHorzDist = layout->getBorderDrawWidth(); int borderVertDist = layout->getBorderDrawHeight(); From 5d475513d50da50ca1dc21b70b3b97cbe90e5f4d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 11:21:23 -0400 Subject: [PATCH 12/25] Allow off-center player view size --- forms/projectsettingseditor.ui | 142 ++++++++++++++++++++----------- include/config.h | 12 +-- include/core/maplayout.h | 3 +- include/editor.h | 2 +- include/project.h | 3 +- include/ui/movablerect.h | 3 +- src/config.cpp | 26 ++++-- src/core/map.cpp | 10 +-- src/core/maplayout.cpp | 23 ++--- src/editor.cpp | 19 ++--- src/mainwindow.cpp | 2 +- src/project.cpp | 20 ++--- src/ui/mapimageexporter.cpp | 13 +-- src/ui/movablerect.cpp | 16 ++-- src/ui/projectsettingseditor.cpp | 17 ++-- 15 files changed, 180 insertions(+), 131 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 696df9ac..fe065be9 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -6,8 +6,8 @@ 0 0 - 631 - 600 + 642 + 609 @@ -38,8 +38,8 @@ 0 0 - 559 - 589 + 570 + 692 @@ -281,44 +281,86 @@ - + - Player View Size + Player View Distance - - - - - Width - - + + + + + + + North + + + + + + + South + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see North of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see South of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + + + - - - - <html><head/><body><p>The horizontal size in pixels of the area that the player can see in-game (normally, the full width of the GBA screen).</p></body></html> - - - 16 - - - - - - - Height - - - - - - - <html><head/><body><p>The vertical size in pixels of the area that the player can see in-game (normally, the full height of the GBA screen).</p></body></html> - - - 16 - - + + + + + + West + + + + + + + East + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see West of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + + + + + + 0 + + + <html><head/><body><p>The distance (in pixels) that a player is able to see East of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + + + @@ -421,7 +463,7 @@ 0 0 - 559 + 561 622 @@ -792,7 +834,7 @@ 0 0 - 559 + 561 798 @@ -1134,7 +1176,7 @@ 0 0 - 559 + 561 840 @@ -1516,8 +1558,8 @@ 0 0 - 559 - 490 + 561 + 593 @@ -1563,8 +1605,8 @@ 0 0 - 533 - 428 + 535 + 531 @@ -1605,8 +1647,8 @@ 0 0 - 559 - 490 + 561 + 593 @@ -1652,8 +1694,8 @@ 0 0 - 533 - 428 + 535 + 531 diff --git a/include/config.h b/include/config.h index 18422e09..fe62377f 100644 --- a/include/config.h +++ b/include/config.h @@ -15,11 +15,11 @@ #include "events.h" -static const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION); +extern const QVersionNumber porymapVersion; -// In both versions the default new map border is a generic tree -#define DEFAULT_BORDER_RSE (QList{0x1D4, 0x1D5, 0x1DC, 0x1DD}) -#define DEFAULT_BORDER_FRLG (QList{0x14, 0x15, 0x1C, 0x1D}) +// Distance in pixels from the edge of a GBA screen (240x160) to the center 16x16 pixels. +#define GBA_H_DIST_TO_CENTER ((240-16)/2) +#define GBA_V_DIST_TO_CENTER ((160-16)/2) #define CONFIG_BACKWARDS_COMPATABILITY @@ -332,7 +332,7 @@ public: this->pokemonIconPaths.clear(); this->collisionSheetPath = QString(); this->collisionSheetSize = QSize(2, 16); - this->playerViewSize = QSize(240, 160); + this->playerViewDistance = QMargins(GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER, GBA_H_DIST_TO_CENTER, GBA_V_DIST_TO_CENTER); this->blockMetatileIdMask = 0x03FF; this->blockCollisionMask = 0x0C00; this->blockElevationMask = 0xF000; @@ -409,7 +409,7 @@ public: bool mapAllowFlagsEnabled; QString collisionSheetPath; QSize collisionSheetSize; - QSize playerViewSize; + QMargins playerViewDistance; QList warpBehaviors; int maxEventsPerGroup; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 8d1d64bc..8d8bb2e2 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -96,8 +96,7 @@ public: int getHeight() const { return height; } int getBorderWidth() const { return border_width; } int getBorderHeight() const { return border_height; } - int getBorderDrawWidth() const; - int getBorderDrawHeight() const; + QMargins getBorderMargins() const; QRect getVisibleRect() const; bool isWithinBounds(int x, int y) const; diff --git a/include/editor.h b/include/editor.h index b1429ef2..4a945daa 100644 --- a/include/editor.h +++ b/include/editor.h @@ -119,7 +119,7 @@ public: void redrawEventPixmapItem(DraggablePixmapItem *item); qreal getEventOpacity(const Event *event) const; - void setPlayerViewSize(const QSize &size); + void setPlayerViewRect(const QRectF &rect); void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); diff --git a/include/project.h b/include/project.h index b9a94776..0c67e85d 100644 --- a/include/project.h +++ b/include/project.h @@ -256,8 +256,7 @@ public: static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static QString getEmptySpeciesName(); - static QSize getViewDistance(); - static QSize getMetatileViewDistance(); + static QMargins getMetatileViewDistance(); static int getNumTilesPrimary() { return num_tiles_primary; } static int getNumTilesTotal() { return num_tiles_total; } static int getNumMetatilesPrimary() { return num_metatiles_primary; } diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index a9f15917..21edd21d 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -10,7 +10,7 @@ class MovableRect : public QGraphicsRectItem { public: - MovableRect(bool *enabled, int width, int height, QRgb color); + MovableRect(bool *enabled, const QRectF &rect, const QRgb &color); QRectF boundingRect() const override { qreal penWidth = 4; return QRectF(-penWidth, @@ -31,6 +31,7 @@ public: bool *enabled; protected: + QRectF baseRect; QRgb color; }; diff --git a/src/config.cpp b/src/config.cpp index 8c139942..b8ad1232 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -18,6 +18,12 @@ #include #include +const QVersionNumber porymapVersion = QVersionNumber::fromString(PORYMAP_VERSION); + +// In both versions the default new map border is a generic tree +const QList defaultBorder_RSE = {0x1D4, 0x1D5, 0x1DC, 0x1DD}; +const QList defaultBorder_FRLG = {0x14, 0x15, 0x1C, 0x1D}; + const QList defaultWarpBehaviors_RSE = { 0x0E, // MB_MOSSDEEP_GYM_WARP 0x0F, // MB_MT_PYRE_HOLE @@ -835,10 +841,14 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->collisionSheetSize.setWidth(getConfigInteger(key, value, 1, Block::maxValue)); } else if (key == "collision_sheet_height") { this->collisionSheetSize.setHeight(getConfigInteger(key, value, 1, Block::maxValue)); - } else if (key == "player_view_width") { - this->playerViewSize.setWidth(getConfigInteger(key, value, 16, INT_MAX, 240)); - } else if (key == "player_view_height") { - this->playerViewSize.setHeight(getConfigInteger(key, value, 16, INT_MAX, 160)); + } else if (key == "player_view_north") { + this->playerViewDistance.setTop(getConfigInteger(key, value, 0, INT_MAX, GBA_V_DIST_TO_CENTER)); + } else if (key == "player_view_south") { + this->playerViewDistance.setBottom(getConfigInteger(key, value, 0, INT_MAX, GBA_V_DIST_TO_CENTER)); + } else if (key == "player_view_west") { + this->playerViewDistance.setLeft(getConfigInteger(key, value, 0, INT_MAX, GBA_H_DIST_TO_CENTER)); + } else if (key == "player_view_east") { + this->playerViewDistance.setRight(getConfigInteger(key, value, 0, INT_MAX, GBA_H_DIST_TO_CENTER)); } else if (key == "warp_behaviors") { this->warpBehaviors.clear(); value.remove(" "); @@ -872,7 +882,7 @@ void ProjectConfig::setUnreadKeys() { if (!readKeys.contains("enable_event_clone_object")) this->eventCloneObjectEnabled = isPokefirered; if (!readKeys.contains("enable_floor_number")) this->floorNumberEnabled = isPokefirered; if (!readKeys.contains("create_map_text_file")) this->createMapTextFileEnabled = (this->baseGameVersion != BaseGameVersion::pokeemerald); - if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? DEFAULT_BORDER_FRLG : DEFAULT_BORDER_RSE; + if (!readKeys.contains("new_map_border_metatiles")) this->newMapBorderMetatileIds = isPokefirered ? defaultBorder_FRLG : defaultBorder_RSE; if (!readKeys.contains("default_secondary_tileset")) this->defaultSecondaryTileset = isPokefirered ? "gTileset_PalletTown" : "gTileset_Petalburg"; if (!readKeys.contains("metatile_attributes_size")) this->metatileAttributesSize = Metatile::getDefaultAttributesSize(this->baseGameVersion); if (!readKeys.contains("metatile_behavior_mask")) this->metatileBehaviorMask = Metatile::getDefaultAttributesMask(this->baseGameVersion, Metatile::Attr::Behavior); @@ -941,8 +951,10 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); map.insert("collision_sheet_height", QString::number(this->collisionSheetSize.height())); - map.insert("player_view_width", QString::number(this->playerViewSize.width())); - map.insert("player_view_height", QString::number(this->playerViewSize.height())); + map.insert("player_view_north", QString::number(this->playerViewDistance.top())); + map.insert("player_view_south", QString::number(this->playerViewDistance.bottom())); + map.insert("player_view_west", QString::number(this->playerViewDistance.left())); + map.insert("player_view_east", QString::number(this->playerViewDistance.right())); QStringList warpBehaviorStrs; for (const auto &value : this->warpBehaviors) warpBehaviorStrs.append("0x" + QString("%1").arg(value, 2, 16, QChar('0')).toUpper()); diff --git a/src/core/map.cpp b/src/core/map.cpp index 793090c0..b067d6a5 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -83,17 +83,17 @@ QRect Map::getConnectionRect(const QString &direction, Layout * fromLayout) cons int x = 0, y = 0; int w = getWidth(), h = getHeight(); - QSize viewDistance = Project::getMetatileViewDistance(); + QMargins viewDistance = Project::getMetatileViewDistance(); if (direction == "up") { - h = qMin(h, viewDistance.height()); + h = qMin(h, viewDistance.top()); y = getHeight() - h; } else if (direction == "down") { - h = qMin(h, viewDistance.height()); + h = qMin(h, viewDistance.bottom()); } else if (direction == "left") { - w = qMin(w, viewDistance.width()); + w = qMin(w, viewDistance.left()); x = getWidth() - w; } else if (direction == "right") { - w = qMin(w, viewDistance.width()); + w = qMin(w, viewDistance.right()); } else if (MapConnection::isDiving(direction)) { if (fromLayout) { w = qMin(w, fromLayout->getWidth()); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index e70d8040..b8c200af 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -63,14 +63,6 @@ bool Layout::isWithinBorderBounds(int x, int y) const { return (x >= 0 && x < this->getBorderWidth() && y >= 0 && y < this->getBorderHeight()); } -int Layout::getBorderDrawWidth() const { - return getBorderDrawDistance(border_width, Project::getMetatileViewDistance().width()); -} - -int Layout::getBorderDrawHeight() const { - return getBorderDrawDistance(border_height, Project::getMetatileViewDistance().height()); -} - // Calculate the distance away from the layout's edge that we need to start drawing border blocks. // We need to fulfill two requirements here: // - We should draw enough to fill the player's in-game view @@ -83,13 +75,22 @@ int Layout::getBorderDrawDistance(int dimension, qreal minimum) { // Get first multiple of dimension >= the minimum return dimension * qCeil(minimum / qMax(dimension, 1)); } +QMargins Layout::getBorderMargins() const { + QMargins minimum = Project::getMetatileViewDistance(); + QMargins distance; + distance.setTop(getBorderDrawDistance(this->border_height, minimum.top())); + distance.setBottom(getBorderDrawDistance(this->border_height, minimum.bottom())); + distance.setLeft(getBorderDrawDistance(this->border_width, minimum.left())); + distance.setRight(getBorderDrawDistance(this->border_width, minimum.right())); + return distance; +} // Get a rectangle that represents (in pixels) the layout's map area and the visible area of its border. +// At maximum, this is equal to the map size plus the border margins. +// If the border is large (and so beyond player the view) it may be smaller than that. QRect Layout::getVisibleRect() const { QRect area = QRect(0, 0, this->width * 16, this->height * 16); - QSize viewDistance = Project::getMetatileViewDistance() * 16; - area += QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); - return area; + return area += (Project::getMetatileViewDistance() * 16); } bool Layout::getBlock(int x, int y, Block *out) { diff --git a/src/editor.cpp b/src/editor.cpp index 54fc9640..d41a33a2 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -30,7 +30,6 @@ Editor::Editor(Ui::MainWindow* ui) { this->ui = ui; this->settings = new Settings(); - this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, 30 * 8, 20 * 8, qRgb(255, 255, 255)); this->cursorMapTileRect = new CursorTileRect(&this->settings->cursorTileRectEnabled, qRgb(255, 255, 255)); this->map_ruler = new MapRuler(4); connect(this->map_ruler, &MapRuler::statusChanged, this, &Editor::mapRulerStatusChanged); @@ -1061,14 +1060,9 @@ void Editor::scaleMapView(int s) { ui->graphicsView_Connections->setTransform(transform); } -void Editor::setPlayerViewSize(const QSize &size) { - if (!this->playerViewRect) - return; - - auto rect = this->playerViewRect->rect(); - rect.setWidth(qMax(size.width(), 16)); - rect.setHeight(qMax(size.height(), 16)); - this->playerViewRect->setRect(rect); +void Editor::setPlayerViewRect(const QRectF &rect) { + delete this->playerViewRect; + this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, rect, qRgb(255, 255, 255)); if (ui->graphicsView_Map->scene()) ui->graphicsView_Map->scene()->update(); } @@ -1794,11 +1788,10 @@ void Editor::clearMapBorder() { void Editor::displayMapBorder() { clearMapBorder(); - int borderHorzDist = this->layout->getBorderDrawWidth(); - int borderVertDist = this->layout->getBorderDrawHeight(); QPixmap pixmap = this->layout->renderBorder(); - for (int y = -borderVertDist; y < this->layout->getHeight() + borderVertDist; y += this->layout->getBorderHeight()) - for (int x = -borderHorzDist; x < this->layout->getWidth() + borderHorzDist; x += this->layout->getBorderWidth()) { + const QMargins borderMargins = layout->getBorderMargins(); + for (int y = -borderMargins.top(); y < this->layout->getHeight() + borderMargins.bottom(); y += this->layout->getBorderHeight()) + for (int x = -borderMargins.left(); x < this->layout->getWidth() + borderMargins.right(); x += this->layout->getBorderWidth()) { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 076f8f8f..24495350 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1171,7 +1171,7 @@ bool MainWindow::setProjectUI() { ui->newEventToolButton->setEventTypeVisible(Event::Type::SecretBase, projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->setEventTypeVisible(Event::Type::CloneObject, projectConfig.eventCloneObjectEnabled); - this->editor->setPlayerViewSize(projectConfig.playerViewSize); + this->editor->setPlayerViewRect(QRectF(0, 0, 16, 16).marginsAdded(projectConfig.playerViewDistance)); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); diff --git a/src/project.cpp b/src/project.cpp index d91d63da..11d7b93c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -3235,18 +3235,14 @@ QString Project::getEmptySpeciesName() { return projectConfig.getIdentifier(ProjectIdentifier::define_species_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_species_empty); } -// Get the distance in pixels that the player is able to see from the space they're standing on. -// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 112x72. -QSize Project::getViewDistance() { - return ((projectConfig.playerViewSize) - QSize(16,16)) / 2; -} - -// Get the distance in metatiles that the player is able to see from the space they're standing on, rounded up. -// For the default size of the view area (i.e. the full 240x160 GBA screen) this is 7x5 metatiles. -QSize Project::getMetatileViewDistance() { - QSize viewDistance = getViewDistance(); - viewDistance.setWidth(qCeil(viewDistance.width() / 16.0)); - viewDistance.setHeight(qCeil(viewDistance.height() / 16.0)); +// Get the distance in metatiles (rounded up) that the player is able to see in each direction in-game. +// For the default view distance (i.e. assuming the player is centered in a 240x160 pixel GBA screen) this is 7x5 metatiles. +QMargins Project::getMetatileViewDistance() { + QMargins viewDistance = projectConfig.playerViewDistance; + viewDistance.setTop(qCeil(viewDistance.top() / 16.0)); + viewDistance.setBottom(qCeil(viewDistance.bottom() / 16.0)); + viewDistance.setLeft(qCeil(viewDistance.left() / 16.0)); + viewDistance.setRight(qCeil(viewDistance.right() / 16.0)); return viewDistance; } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 9a65af8f..acb1f538 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -606,11 +606,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap() { QMargins MapImageExporter::getMargins(const Map *map) { QMargins margins; if (m_settings.showBorder) { - // When we render map borders we render them in full increments of the border dimensions. - // This means for large border dimensions the painted area of the border may extend well beyond the area the player can see. - // When we call paintBorder we will clip the painting to this visible area, so we only need to consider the visible area here. - QSize viewDistance = m_project->getMetatileViewDistance() * 16; - margins = QMargins(viewDistance.width(), viewDistance.height(), viewDistance.width(), viewDistance.height()); + margins = m_project->getMetatileViewDistance() * 16; } else if (map && connectionsEnabled()) { for (const auto &connection : map->getConnections()) { const QString dir = connection->direction(); @@ -654,10 +650,9 @@ void MapImageExporter::paintBorder(QPainter *painter, Layout *layout) { painter->save(); painter->setClipRect(layout->getVisibleRect()); - int borderHorzDist = layout->getBorderDrawWidth(); - int borderVertDist = layout->getBorderDrawHeight(); - for (int y = -borderVertDist; y < layout->getHeight() + borderVertDist; y += layout->getBorderHeight()) - for (int x = -borderHorzDist; x < layout->getWidth() + borderHorzDist; x += layout->getBorderWidth()) { + const QMargins borderMargins = layout->getBorderMargins(); + for (int y = -borderMargins.top(); y < layout->getHeight() + borderMargins.bottom(); y += layout->getBorderHeight()) + for (int x = -borderMargins.left(); x < layout->getWidth() + borderMargins.right(); x += layout->getBorderWidth()) { // Skip border painting if it would be fully covered by the rest of the map if (layout->isWithinBounds(QRect(x, y, layout->getBorderWidth(), layout->getBorderHeight()))) continue; diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index fde7f820..4290d1a7 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -5,17 +5,21 @@ #include "movablerect.h" #include "utility.h" -MovableRect::MovableRect(bool *enabled, int width, int height, QRgb color) - : QGraphicsRectItem(0, 0, width, height) +MovableRect::MovableRect(bool *enabled, const QRectF &rect, const QRgb &color) + : QGraphicsRectItem(rect), + enabled(enabled), + baseRect(rect), + color(color) { - this->enabled = enabled; - this->color = color; this->setVisible(*enabled); } /// Center rect on grid position (x, y) void MovableRect::updateLocation(int x, int y) { - this->setRect((x * 16) - this->rect().width() / 2 + 8, (y * 16) - this->rect().height() / 2 + 8, this->rect().width(), this->rect().height()); + this->setRect(this->baseRect.x() + (x * 16), + this->baseRect.y() + (y * 16), + this->baseRect.width(), + this->baseRect.height()); this->setVisible(*this->enabled); } @@ -25,7 +29,7 @@ void MovableRect::updateLocation(int x, int y) { ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int height, QRgb color) : QObject(parent), - MovableRect(enabled, width * 16, height * 16, color) + MovableRect(enabled, QRect(0, 0, width * 16, height * 16), color) { setZValue(0xFFFFFFFF); // ensure on top of view setAcceptHoverEvents(true); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 8b47913a..5bcf6399 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -138,8 +138,10 @@ void ProjectSettingsEditor::initUi() { ui->spinBox_MaxEvents->setMaximum(INT_MAX); ui->spinBox_MapWidth->setMaximum(INT_MAX); ui->spinBox_MapHeight->setMaximum(INT_MAX); - ui->spinBox_PlayerViewWidth->setMaximum(INT_MAX); - ui->spinBox_PlayerViewHeight->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_West->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_North->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_East->setMaximum(INT_MAX); + ui->spinBox_PlayerViewDistance_South->setMaximum(INT_MAX); // The values for some of the settings we provide in this window can be determined using constants in the user's projects. // If the user has these constants we disable these settings in the UI -- they can modify them using their constants. @@ -475,8 +477,10 @@ void ProjectSettingsEditor::refresh() { ui->spinBox_UnusedTileCovered->setValue(projectConfig.unusedTileCovered); ui->spinBox_UnusedTileSplit->setValue(projectConfig.unusedTileSplit); ui->spinBox_MaxEvents->setValue(projectConfig.maxEventsPerGroup); - ui->spinBox_PlayerViewWidth->setValue(projectConfig.playerViewSize.width()); - ui->spinBox_PlayerViewHeight->setValue(projectConfig.playerViewSize.height()); + ui->spinBox_PlayerViewDistance_West->setValue(projectConfig.playerViewDistance.left()); + ui->spinBox_PlayerViewDistance_North->setValue(projectConfig.playerViewDistance.top()); + ui->spinBox_PlayerViewDistance_East->setValue(projectConfig.playerViewDistance.right()); + ui->spinBox_PlayerViewDistance_South->setValue(projectConfig.playerViewDistance.bottom()); // Set (and sync) border metatile IDs this->setBorderMetatileIds(false, projectConfig.newMapBorderMetatileIds); @@ -553,7 +557,10 @@ void ProjectSettingsEditor::save() { projectConfig.unusedTileCovered = ui->spinBox_UnusedTileCovered->value(); projectConfig.unusedTileSplit = ui->spinBox_UnusedTileSplit->value(); projectConfig.maxEventsPerGroup = ui->spinBox_MaxEvents->value(); - projectConfig.playerViewSize = QSize(ui->spinBox_PlayerViewWidth->value(), ui->spinBox_PlayerViewHeight->value()); + projectConfig.playerViewDistance = QMargins(ui->spinBox_PlayerViewDistance_West->value(), + ui->spinBox_PlayerViewDistance_North->value(), + ui->spinBox_PlayerViewDistance_East->value(), + ui->spinBox_PlayerViewDistance_South->value()); // Save line edit settings projectConfig.prefabFilepath = ui->lineEdit_PrefabsPath->text(); From 80024d9ce3a6d769490da0492ba6e91c36109a2b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 14:11:34 -0400 Subject: [PATCH 13/25] Add missing tooltip, menu separators --- forms/mainwindow.ui | 3 +++ forms/mapheaderform.ui | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 86f50047..7d391d3b 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2863,6 +2863,7 @@ + @@ -2887,8 +2888,10 @@ + + diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui index 8faba290..08552e2c 100644 --- a/forms/mapheaderform.ui +++ b/forms/mapheaderform.ui @@ -7,7 +7,7 @@ 0 0 407 - 349 + 380 @@ -224,7 +224,11 @@ - + + + <html><head/><body><p>The name that will be displayed in-game for this Location. This name will be shared with any other map that has the same Location.</p></body></html> + + From b1d85d32c12fac6bb02c8603a55f81747ca3741d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 15 Apr 2025 12:22:51 -0400 Subject: [PATCH 14/25] Prevent weird diving map behavior --- include/core/map.h | 1 + include/editor.h | 3 ++- include/ui/newmapconnectiondialog.h | 5 +++- src/core/map.cpp | 9 +++++++ src/editor.cpp | 23 ++++++++++++---- src/mainwindow.cpp | 3 ++- src/ui/connectionpixmapitem.cpp | 2 ++ src/ui/connectionslistitem.cpp | 12 ++++++++- src/ui/newmapconnectiondialog.cpp | 42 ++++++++++++++++++++++++++--- 9 files changed, 87 insertions(+), 13 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index aaf5b8b7..8b757b8e 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -87,6 +87,7 @@ public: void deleteConnections(); QList getConnections() const { return m_connections; } + MapConnection* getConnection(const QString &direction) const; void removeConnection(MapConnection *); void addConnection(MapConnection *); void loadConnection(MapConnection *); diff --git a/include/editor.h b/include/editor.h index 8b8a18b0..82bc5a08 100644 --- a/include/editor.h +++ b/include/editor.h @@ -92,7 +92,8 @@ public: void setConnectionsVisibility(bool visible); void updateDivingMapsVisibility(); void renderDivingConnections(); - void addConnection(MapConnection* connection); + void addNewConnection(const QString &mapName, const QString &direction); + void replaceConnection(const QString &mapName, const QString &direction); void removeConnection(MapConnection* connection); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); diff --git a/include/ui/newmapconnectiondialog.h b/include/ui/newmapconnectiondialog.h index 4781c971..db9eee49 100644 --- a/include/ui/newmapconnectiondialog.h +++ b/include/ui/newmapconnectiondialog.h @@ -20,13 +20,16 @@ public: virtual void accept() override; signals: - void accepted(MapConnection *result); + void newConnectionedAdded(const QString &mapName, const QString &direction); + void connectionReplaced(const QString &mapName, const QString &direction); private: Ui::NewMapConnectionDialog *ui; + Map *m_map; bool mapNameIsValid(); void setWarningVisible(bool visible); + bool askReplaceConnection(MapConnection *connection, const QString &newMapName); }; #endif // NEWMAPCONNECTIONDIALOG_H diff --git a/src/core/map.cpp b/src/core/map.cpp index b9fe4c90..1c958d87 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -288,6 +288,15 @@ void Map::removeConnection(MapConnection *connection) { emit connectionRemoved(connection); } +// Return the first map connection that has the given direction. +MapConnection* Map::getConnection(const QString &direction) const { + for (const auto &connection : m_connections) { + if (connection->direction() == direction) + return connection; + } + return nullptr; +} + void Map::commit(QUndoCommand *cmd) { m_editHistory->push(cmd); } diff --git a/src/editor.cpp b/src/editor.cpp index 78cab8bb..ea74c5bb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -818,19 +818,32 @@ void Editor::displayConnection(MapConnection *connection) { } } -void Editor::addConnection(MapConnection *connection) { - if (!connection) +void Editor::addNewConnection(const QString &mapName, const QString &direction) { + if (!this->map) return; + MapConnection *connection = new MapConnection(mapName, direction); + // Mark this connection to be selected once its display elements have been created. // It's possible this is a Dive/Emerge connection, but that's ok (no selection will occur). - connection_to_select = connection; + this->connection_to_select = connection; this->map->commit(new MapConnectionAdd(this->map, connection)); } +void Editor::replaceConnection(const QString &mapName, const QString &direction) { + if (!this->map) + return; + + MapConnection *connection = this->map->getConnection(direction); + if (!connection || connection->targetMapName() == mapName) + return; + + this->map->commit(new MapConnectionChangeMap(connection, mapName)); +} + void Editor::removeConnection(MapConnection *connection) { - if (!connection) + if (!this->map || !connection) return; this->map->commit(new MapConnectionRemove(this->map, connection)); } @@ -948,7 +961,7 @@ bool Editor::setDivingMapName(const QString &mapName, const QString &direction) } } else if (!mapName.isEmpty()) { // Create new connection - addConnection(new MapConnection(mapName, direction)); + addNewConnection(mapName, direction); } return true; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c539b654..feadc6e8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2588,7 +2588,8 @@ void MainWindow::on_pushButton_AddConnection_clicked() { return; auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames); - connect(dialog, &NewMapConnectionDialog::accepted, this->editor, &Editor::addConnection); + connect(dialog, &NewMapConnectionDialog::newConnectionedAdded, this->editor, &Editor::addNewConnection); + connect(dialog, &NewMapConnectionDialog::connectionReplaced, this->editor, &Editor::replaceConnection); dialog->open(); } diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index f1bceac5..ac755ff8 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -143,6 +143,8 @@ void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { emit connectionItemDoubleClicked(this->connection); } +// TODO: Rather than listening for this here and on the list item, listen for it on the connections graphics view, +// and delete whichever map connections are currently selected. This should fix our weird focus requirements in here. void ConnectionPixmapItem::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { emit deleteRequested(this->connection); diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index b0fdf581..b6c533be 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -100,7 +100,17 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { void ConnectionsListItem::commitDirection() { const QString direction = ui->comboBox_Direction->currentText(); - if (this->map && this->connection && this->connection->direction() != direction) { + if (!this->connection || this->connection->direction() == direction) + return; + + if (MapConnection::isDiving(direction)) { + // Diving maps are displayed separately, no support right now for replacing a list item with a diving map. + // For now just restore the original direction. + ui->comboBox_Direction->setCurrentText(this->connection->direction()); + return; + } + + if (this->map) { this->map->commit(new MapConnectionChangeDirection(this->connection, direction)); } } diff --git a/src/ui/newmapconnectiondialog.cpp b/src/ui/newmapconnectiondialog.cpp index a4f08496..b9938f3e 100644 --- a/src/ui/newmapconnectiondialog.cpp +++ b/src/ui/newmapconnectiondialog.cpp @@ -1,9 +1,11 @@ #include "newmapconnectiondialog.h" #include "ui_newmapconnectiondialog.h" +#include "message.h" NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const QStringList &mapNames) : QDialog(parent), - ui(new Ui::NewMapConnectionDialog) + ui(new Ui::NewMapConnectionDialog), + m_map(map) { ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); @@ -15,7 +17,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const // Choose default direction QMap directionCounts; - for (auto connection : map->getConnections()) { + for (auto connection : m_map->getConnections()) { directionCounts[connection->direction()]++; } QString defaultDirection; @@ -32,7 +34,7 @@ NewMapConnectionDialog::NewMapConnectionDialog(QWidget *parent, Map* map, const QString defaultMapName; if (mapNames.isEmpty()) { defaultMapName = QString(); - } else if (mapNames.first() == map->name() && mapNames.length() > 1) { + } else if (mapNames.first() == m_map->name() && mapNames.length() > 1) { // Prefer not to connect the map to itself defaultMapName = mapNames.at(1); } else { @@ -61,11 +63,43 @@ void NewMapConnectionDialog::setWarningVisible(bool visible) { adjustSize(); } +bool NewMapConnectionDialog::askReplaceConnection(MapConnection *connection, const QString &newMapName) { + QString message = QString("%1 already has a %2 connection to '%3'. Replace it with a %2 connection to '%4'?") + .arg(m_map->name()) + .arg(connection->direction()) + .arg(connection->targetMapName()) + .arg(newMapName); + return QuestionMessage::show(message, this) == QMessageBox::Yes; +} + void NewMapConnectionDialog::accept() { if (!mapNameIsValid()) { setWarningVisible(true); return; } - emit accepted(new MapConnection(ui->comboBox_Map->currentText(), ui->comboBox_Direction->currentText())); + + const QString direction = ui->comboBox_Direction->currentText(); + const QString targetMapName = ui->comboBox_Map->currentText(); + + // This is a very niche use case. Normally the user should add Dive/Emerge map connections using the line edits at the top of + // the Connections tab, but because we allow custom direction names in this dialog's Direction drop-down, a user could type + // in "dive" or "emerge" and we have to decide what to do. If there's no existing Dive/Emerge map we can just add it normally + // as if they had typed in the regular line edits. If there's already an existing connection we need to replace it. + if (MapConnection::isDiving(direction)) { + MapConnection *connection = m_map->getConnection(direction); + if (connection) { + if (connection->targetMapName() != targetMapName) { + if (!askReplaceConnection(connection, targetMapName)) + return; // Canceled + emit connectionReplaced(targetMapName, direction); + } + // Replaced the diving connection (or no-op, if adding a diving connection with the same map name) + QDialog::accept(); + return; + } + // Adding a new diving connection that doesn't exist yet, proceed normally. + } + + emit newConnectionedAdded(targetMapName, direction); QDialog::accept(); } From 8b85057ca5d7479b7bfec3886fda851620729ddd Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 15:55:26 -0400 Subject: [PATCH 15/25] Fix connection pixmaps being sensitive to focus --- forms/mainwindow.ui | 11 ++++++++--- include/editor.h | 1 + include/ui/connectionpixmapitem.h | 2 -- include/ui/connectionslistitem.h | 1 - include/ui/graphicsview.h | 13 +++++++++++++ src/editor.cpp | 5 +++++ src/mainwindow.cpp | 1 + src/ui/connectionpixmapitem.cpp | 24 +----------------------- src/ui/connectionslistitem.cpp | 9 --------- src/ui/graphicsview.cpp | 9 +++++++++ 10 files changed, 38 insertions(+), 38 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 8a8757a0..cbb611fe 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2589,7 +2589,7 @@ Qt::Orientation::Horizontal - + 0 @@ -3310,9 +3310,14 @@ MapView - QWidget + QGraphicsView
mapview.h
+ + ConnectionsView + QGraphicsView +
graphicsview.h
+
MapTree QTreeView @@ -3321,7 +3326,7 @@ NoScrollGraphicsView QGraphicsView -
mapview.h
+
graphicsview.h
MapListToolBar diff --git a/include/editor.h b/include/editor.h index 82bc5a08..b908b335 100644 --- a/include/editor.h +++ b/include/editor.h @@ -95,6 +95,7 @@ public: void addNewConnection(const QString &mapName, const QString &direction); void replaceConnection(const QString &mapName, const QString &direction); void removeConnection(MapConnection* connection); + void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); void configureEncounterJSON(QWidget *); diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index 26b83aa6..32e309f9 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -43,8 +43,6 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; - virtual void keyPressEvent(QKeyEvent*) override; - virtual void focusInEvent(QFocusEvent*) override; signals: void connectionItemDoubleClicked(MapConnection*); diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index bce05345..1b9713cf 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -36,7 +36,6 @@ private: protected: virtual void mousePressEvent(QMouseEvent*) override; - virtual void keyPressEvent(QKeyEvent*) override; virtual bool eventFilter(QObject*, QEvent *event) override; signals: diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index 92771cf7..0ca36239 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -32,6 +32,19 @@ signals: void clicked(QMouseEvent *event); }; +class ConnectionsView : public QGraphicsView +{ + Q_OBJECT +public: + ConnectionsView(QWidget *parent = nullptr) : QGraphicsView(parent) {} + +signals: + void pressedDelete(); + +protected: + virtual void keyPressEvent(QKeyEvent *event) override; +}; + class Editor; // TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView. diff --git a/src/editor.cpp b/src/editor.cpp index ea74c5bb..39ad3647 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -848,6 +848,11 @@ void Editor::removeConnection(MapConnection *connection) { this->map->commit(new MapConnectionRemove(this->map, connection)); } +void Editor::removeSelectedConnection() { + if (selected_connection_item) + removeConnection(selected_connection_item->connection); +} + void Editor::removeConnectionPixmap(MapConnection *connection) { if (!connection) return; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index feadc6e8..60bb3e80 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -352,6 +352,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); connect(ui->toolButton_deleteEvent, &QAbstractButton::clicked, this->editor, &Editor::deleteSelectedEvents); + connect(ui->graphicsView_Connections, &ConnectionsView::pressedDelete, this->editor, &Editor::removeSelectedConnection); this->loadUserSettings(); diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index ac755ff8..7ea5f820 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -9,7 +9,6 @@ ConnectionPixmapItem::ConnectionPixmapItem(MapConnection* connection) connection(connection) { this->setEditable(true); - setFlag(ItemIsFocusable, true); this->basePixmap = pixmap(); updateOrigin(); render(false); @@ -118,10 +117,6 @@ bool ConnectionPixmapItem::getEditable() { } void ConnectionPixmapItem::setSelected(bool selected) { - if (selected && !hasFocus()) { - setFocus(Qt::OtherFocusReason); - } - if (this->selected == selected) return; this->selected = selected; @@ -131,7 +126,7 @@ void ConnectionPixmapItem::setSelected(bool selected) { } void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { - setFocus(Qt::MouseFocusReason); + this->setSelected(true); } void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { @@ -142,20 +137,3 @@ void ConnectionPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { emit connectionItemDoubleClicked(this->connection); } - -// TODO: Rather than listening for this here and on the list item, listen for it on the connections graphics view, -// and delete whichever map connections are currently selected. This should fix our weird focus requirements in here. -void ConnectionPixmapItem::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { - emit deleteRequested(this->connection); - } else { - QGraphicsPixmapItem::keyPressEvent(event); - } -} - -void ConnectionPixmapItem::focusInEvent(QFocusEvent* event) { - if (!this->getEditable()) - return; - this->setSelected(true); - QGraphicsPixmapItem::focusInEvent(event); -} diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index b6c533be..0ba27223 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -129,12 +129,3 @@ void ConnectionsListItem::commitRemove() { if (this->map) this->map->commit(new MapConnectionRemove(this->map, this->connection)); } - -void ConnectionsListItem::keyPressEvent(QKeyEvent* event) { - if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { - commitRemove(); - event->accept(); - } else { - QFrame::keyPressEvent(event); - } -} diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index 68479e98..1297102e 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -79,3 +79,12 @@ Overlay * MapView::getOverlay(int layer) { } return overlay; } + +void ConnectionsView::keyPressEvent(QKeyEvent *event) { + if (event->key() == Qt::Key_Delete || event->key() == Qt::Key_Backspace) { + emit pressedDelete(); + event->accept(); + } else { + QGraphicsView::keyPressEvent(event); + } +} From b660ef5d3003f259e58a313daa60a606d16c3b3b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 16:16:40 -0400 Subject: [PATCH 16/25] Fix Qt5 build --- src/ui/connectionslistitem.cpp | 2 +- src/ui/noscrollcombobox.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index 0ba27223..86df9525 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -46,7 +46,7 @@ ConnectionsListItem::ConnectionsListItem(QWidget *parent, MapConnection * connec ui->spinBox_Offset->installEventFilter(this); connect(ui->spinBox_Offset, &QSpinBox::editingFinished, [this] { this->actionId++; }); // Distinguish between move actions for the edit history - connect(ui->spinBox_Offset, &QSpinBox::valueChanged, this, &ConnectionsListItem::commitMove); + connect(ui->spinBox_Offset, QOverload::of(&QSpinBox::valueChanged), this, &ConnectionsListItem::commitMove); // If the connection changes externally we want to update to reflect the change. connect(connection, &MapConnection::offsetChanged, this, &ConnectionsListItem::updateUI); diff --git a/src/ui/noscrollcombobox.cpp b/src/ui/noscrollcombobox.cpp index bd6b438b..191c5c45 100644 --- a/src/ui/noscrollcombobox.cpp +++ b/src/ui/noscrollcombobox.cpp @@ -26,7 +26,7 @@ NoScrollComboBox::NoScrollComboBox(QWidget *parent) // QComboBox (as of writing) has no 'editing finished' signal to capture // changes made either through the text edit or the drop-down. - connect(this, &QComboBox::activated, this, &NoScrollComboBox::editingFinished); + connect(this, QOverload::of(&QComboBox::activated), this, &NoScrollComboBox::editingFinished); connect(this->lineEdit(), &QLineEdit::editingFinished, this, &NoScrollComboBox::editingFinished); } From 1d6d0c6dc9c83af91588bd27c3daf5b444f487a5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:38:12 -0400 Subject: [PATCH 17/25] Fix region map tile selector palette differing from selection --- CHANGELOG.md | 1 + src/ui/regionmapeditor.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 984f3879..4f1f5a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix `Add Region Map...` not updating the region map settings file. - Fix some crashes on invalid region map tilesets. - Improve error reporting for invalid region map editor settings. +- Fix the region map editor's palette resetting between region maps. - Fix config files being written before the project is opened successfully. - Fix the map and other project info still displaying if a new project fails to open. - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index b66d442b..493ea78e 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -774,7 +774,10 @@ void RegionMapEditor::displayRegionMapTileSelector() { this->mapsquare_selector_item = new TilemapTileSelector(this->region_map->pngPath(), this->region_map->tilemapFormat(), this->region_map->palPath()); - this->mapsquare_selector_item->draw(); + // Initialize with current settings + this->mapsquare_selector_item->selectHFlip(ui->checkBox_tileHFlip->isChecked()); + this->mapsquare_selector_item->selectVFlip(ui->checkBox_tileVFlip->isChecked()); + this->mapsquare_selector_item->selectPalette(ui->spinBox_tilePalette->value()); // This will also draw the selector this->scene_region_map_tiles->addItem(this->mapsquare_selector_item); From 2df722ab4c03525049d84f3c8faea787727d84a8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:43:30 -0400 Subject: [PATCH 18/25] Fix region map tile selector swapping h/vflip --- CHANGELOG.md | 1 + include/ui/tilemaptileselector.h | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f1f5a13..a6d6b19d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Fix some crashes on invalid region map tilesets. - Improve error reporting for invalid region map editor settings. - Fix the region map editor's palette resetting between region maps. +- Fix the region map editor's h-flip and v-flip settings being swapped. - Fix config files being written before the project is opened successfully. - Fix the map and other project info still displaying if a new project fails to open. - Fix unsaved changes being ignored when quitting (such as with Cmd+Q on macOS). diff --git a/include/ui/tilemaptileselector.h b/include/ui/tilemaptileselector.h index 5c3b8dac..155957a6 100644 --- a/include/ui/tilemaptileselector.h +++ b/include/ui/tilemaptileselector.h @@ -149,10 +149,10 @@ public: void select(unsigned tileId); unsigned selectedTile = 0; - void selectVFlip(bool hFlip) { this->tile_hFlip = hFlip; } + void selectHFlip(bool hFlip) { this->tile_hFlip = hFlip; } bool tile_hFlip = false; - void selectHFlip(bool vFlip) { this->tile_vFlip = vFlip; } + void selectVFlip(bool vFlip) { this->tile_vFlip = vFlip; } bool tile_vFlip = false; void selectPalette(int palette) { From c0df85e43bb7521e015ed92c53ede606493c7d8c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 20:03:11 -0400 Subject: [PATCH 19/25] Fix dangling references, other warnings --- forms/wildmonchart.ui | 2 +- src/project.cpp | 15 ++++++++++----- src/ui/wildmonsearch.cpp | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/forms/wildmonchart.ui b/forms/wildmonchart.ui index 488e066e..8d6668e4 100644 --- a/forms/wildmonchart.ui +++ b/forms/wildmonchart.ui @@ -145,7 +145,7 @@ false
- QComboBox::AdjustToMinimumContentsLength + QComboBox::AdjustToMinimumContentsLengthWithIcon 8 diff --git a/src/project.cpp b/src/project.cpp index 6f225ff6..b8f31373 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1688,19 +1688,22 @@ bool Project::readWildMonData() { // Each element describes a type of wild encounter Porymap can expect to find, and we represent this data with an EncounterField. // They should contain a name ("type"), the number of encounter slots and the ratio at which they occur ("encounter_rates"), // and whether the encounters are divided into groups (like fishing rods). - for (const OrderedJson &fieldJson : mainArrayObject.take("fields").array_items()) { + OrderedJson::array fieldsArray = mainArrayObject.take("fields").array_items(); + for (const OrderedJson &fieldJson : fieldsArray) { OrderedJson::object fieldObject = fieldJson.object_items(); EncounterField encounterField; encounterField.name = fieldObject.take("type").string_value(); - for (auto val : fieldObject.take("encounter_rates").array_items()) { + OrderedJson::array encounterRatesArray = fieldObject.take("encounter_rates").array_items(); + for (const auto &val : encounterRatesArray) { encounterField.encounterRates.append(val.int_value()); } // Each element of the "groups" array is an object with the group name as the key (e.g. "old_rod") // and an array of slot numbers indicating which encounter slots in this encounter type belong to that group. - for (auto groupPair : fieldObject.take("groups").object_items()) { + OrderedJson::object groups = fieldObject.take("groups").object_items(); + for (auto groupPair : groups) { const QString groupName = groupPair.first; for (auto slotNum : groupPair.second.array_items()) { encounterField.groups[groupName].append(slotNum.int_value()); @@ -1716,7 +1719,8 @@ bool Project::readWildMonData() { // Each element is an object that will tell us which map it's associated with, // its symbol name (which we will display in the Groups dropdown) and a list of // pokémon associated with any of the encounter types described by the data we parsed above. - for (const auto &encounterJson : mainArrayObject.take("encounters").array_items()) { + OrderedJson::array encountersArray = mainArrayObject.take("encounters").array_items(); + for (const auto &encounterJson : encountersArray) { OrderedJson::object encounterObj = encounterJson.object_items(); WildPokemonHeader header; @@ -1738,7 +1742,8 @@ bool Project::readWildMonData() { encounterRateFrequencyMaps[field][monInfo.encounterRate]++; // Read wild pokémon list - for (const auto &monJson : encounterFieldObj.take("mons").array_items()) { + OrderedJson::array monsArray = encounterFieldObj.take("mons").array_items(); + for (const auto &monJson : monsArray) { OrderedJson::object monObj = monJson.object_items(); WildPokemon newMon; diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index 056e7565..e64fbca4 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -129,6 +129,7 @@ void WildMonSearch::updateResults(const QString &species) { .fieldName = QStringLiteral("--"), .levelRange = QStringLiteral("--"), .chance = QStringLiteral("--"), + .mapName = "", }; addTableEntry(noResults); } else { From 4b3c8abb938850d805955a7ae06f85068f8b35c5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 12:58:15 -0400 Subject: [PATCH 20/25] Remove old heal location map tracking, missing assignment in HealLocationEvent::duplicate --- src/core/events.cpp | 1 + src/project.cpp | 16 +++++----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index 7fac9ece..0175556e 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -775,6 +775,7 @@ Event *HealLocationEvent::duplicate() const { copy->setX(this->getX()); copy->setY(this->getY()); copy->setIdName(this->getIdName()); + copy->setHostMapName(this->getHostMapName()); copy->setRespawnMapName(this->getRespawnMapName()); copy->setRespawnNPC(this->getRespawnNPC()); diff --git a/src/project.cpp b/src/project.cpp index b8f31373..c9d13840 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -848,15 +848,9 @@ void Project::saveHealLocations() { // Build the JSON data for output. QMap> idNameToJson; - for (auto i = this->healLocations.constBegin(); i != this->healLocations.constEnd(); i++) { - const QString mapConstant = i.key(); - for (const auto &event : i.value()) { - // Heal location events don't need to track the "map" field, we're already tracking it either with - // the keys in the healLocations map or by virtue of the event being added to a particular Map object. - // The global JSON data needs this field, so we add it back here. - auto eventJson = event->buildEventJson(this); - eventJson["map"] = mapConstant; - idNameToJson[event->getIdName()].append(eventJson); + for (const auto &events : this->healLocations) { + for (const auto &event : events) { + idNameToJson[event->getIdName()].append(event->buildEventJson(this)); } } @@ -871,8 +865,8 @@ void Project::saveHealLocations() { } } // Save any heal locations that weren't covered above (should be any new data). - for (auto i = idNameToJson.constBegin(); i != idNameToJson.constEnd(); i++) { - for (const auto &object : i.value()) { + for (const auto &objects : idNameToJson) { + for (const auto &object : objects) { eventJsonArr.push_back(object); } } From 6e8dc8c0c4a69c6035cf82a4b6660e00f04dd63f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 17:45:18 -0400 Subject: [PATCH 21/25] Update changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d6b19d..50007ef5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add support for defining project values with `enum` where `#define` was expected. - Add a setting to specify the tile values to use for the unused metatile layer. - Add a setting to specify the maximum number of events in a group. A warning will be shown if too many events are added. +- Add a setting to customize the size and position of the player view distance. - Add `onLayoutOpened` to the scripting API. ### Changed @@ -35,7 +36,6 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Changes to the "Mirror to Connecting Maps" setting will now be saved between sessions. - A notice will be displayed when attempting to open the "Dynamic" map, rather than nothing happening. - The base game version is now auto-detected if the project name contains only one of "emerald", "firered/leafgreen", or "ruby/sapphire". -- The max encounter rate is now read from the project, rather than assuming the default value from RSE. - It's now possible to cancel quitting if there are unsaved changes in sub-windows. - The triple-layer metatiles setting can now be set automatically using a project constant. - `Export Map Stitch Image` and `Export Map Timelapse Image` now show a preview of the full image/gif, not just the current map. @@ -50,6 +50,10 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - `Script` dropdowns now autocomplete only with scripts from the current map, rather than every script in the project. The old behavior is available via a new setting. - The options for `Encounter Type` and `Terrain Type` in the Tileset Editor are not hardcoded anymore, they're now read from the project. - The `symbol_wild_encounters` setting was replaced; this value is now read from the project. +- The max encounter rate is now read from the project, rather than assuming the default value from RSE. +- `MAP_OFFSET_W` and `MAP_OFFSET_H` (used to limit the maximum map size) are now read from the project. +- The rendered area of the map border is now limited to the maximum player view distance (prior to this it included two extra rows on the top and bottom). +- An error message will now be shown when Porymap is unable to save changes (e.g. if Porymap doesn't have write permissions for your project). - A project may now be opened even if it has no maps or map groups. A minimum of one map layout is required. - The file extensions that are expected for `.png` and `.pal` data files and the extensions outputted when creating a new tileset can now be customized. - Miscellaneous performance improvements, especially for opening projects. From e8ac63370097f43fe760afe8828858d2ddd08c31 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 18:57:42 -0400 Subject: [PATCH 22/25] Save grid settings in config --- include/config.h | 11 ++++++++--- src/config.cpp | 37 ++++++++++++++++++++++++++++++++----- src/mainwindow.cpp | 3 +++ src/ui/gridsettings.cpp | 2 -- 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/include/config.h b/include/config.h index 72891be0..67a8b507 100644 --- a/include/config.h +++ b/include/config.h @@ -14,6 +14,7 @@ #include #include "events.h" +#include "gridsettings.h" extern const QVersionNumber porymapVersion; @@ -36,9 +37,11 @@ protected: virtual QMap getKeyValueMap() = 0; virtual void init() = 0; virtual void setUnreadKeys() = 0; - bool getConfigBool(QString key, QString value); - int getConfigInteger(QString key, QString value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); - uint32_t getConfigUint32(QString key, QString value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); + + static bool getConfigBool(const QString &key, const QString &value); + static int getConfigInteger(const QString &key, const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0); + static uint32_t getConfigUint32(const QString &key, const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0); + static QColor getConfigColor(const QString &key, const QString &value, const QColor &defaultValue = Qt::black); }; class PorymapConfig: public KeyValueConfigBase @@ -92,6 +95,7 @@ public: this->rateLimitTimes.clear(); this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape; this->shownInGameReloadMessage = false; + this->gridSettings = GridSettings(); } void addRecentProject(QString project); void setRecentProjects(QStringList projects); @@ -156,6 +160,7 @@ public: QByteArray newMapDialogGeometry; QByteArray newLayoutDialogGeometry; bool shownInGameReloadMessage; + GridSettings gridSettings; protected: virtual QString getConfigFilepath() override; diff --git a/src/config.cpp b/src/config.cpp index 9bc2b9a6..7e7422c7 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -259,7 +259,7 @@ bool KeyValueConfigBase::save() { return true; } -bool KeyValueConfigBase::getConfigBool(QString key, QString value) { +bool KeyValueConfigBase::getConfigBool(const QString &key, const QString &value) { bool ok; int result = value.toInt(&ok, 0); if (!ok || (result != 0 && result != 1)) { @@ -268,26 +268,35 @@ bool KeyValueConfigBase::getConfigBool(QString key, QString value) { return (result != 0); } -int KeyValueConfigBase::getConfigInteger(QString key, QString value, int min, int max, int defaultValue) { +int KeyValueConfigBase::getConfigInteger(const QString &key, const QString &value, int min, int max, int defaultValue) { bool ok; int result = value.toInt(&ok, 0); if (!ok) { - logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); result = defaultValue; } return qMin(max, qMax(min, result)); } -uint32_t KeyValueConfigBase::getConfigUint32(QString key, QString value, uint32_t min, uint32_t max, uint32_t defaultValue) { +uint32_t KeyValueConfigBase::getConfigUint32(const QString &key, const QString &value, uint32_t min, uint32_t max, uint32_t defaultValue) { bool ok; uint32_t result = value.toUInt(&ok, 0); if (!ok) { - logWarn(QString("Invalid config value for %1: '%2'. Must be an integer.").arg(key).arg(value)); + logWarn(QString("Invalid config value for %1: '%2'. Must be an integer. Using default value '%3'.").arg(key).arg(value).arg(defaultValue)); result = defaultValue; } return qMin(max, qMax(min, result)); } +QColor KeyValueConfigBase::getConfigColor(const QString &key, const QString &value, const QColor &defaultValue) { + QColor color = QColor("#" + value); + if (!color.isValid()) { + logWarn(QString("Invalid config value for %1: '%2'. Must be a color in the format 'RRGGBB'. Using default value '%3'.").arg(key).arg(value).arg(defaultValue.name())); + color = defaultValue; + } + return color; +} + PorymapConfig porymapConfig; QString PorymapConfig::getConfigFilepath() { @@ -455,6 +464,18 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { } } else if (key == "shown_in_game_reload_message") { this->shownInGameReloadMessage = getConfigBool(key, value); + } else if (key == "grid_width") { + this->gridSettings.width = getConfigUint32(key, value); + } else if (key == "grid_height") { + this->gridSettings.height = getConfigUint32(key, value); + } else if (key == "grid_x") { + this->gridSettings.offsetX = getConfigInteger(key, value, 0, 999); + } else if (key == "grid_y") { + this->gridSettings.offsetY = getConfigInteger(key, value, 0, 999); + } else if (key == "grid_style") { + this->gridSettings.style = GridSettings::getStyleFromName(value); + } else if (key == "grid_color") { + this->gridSettings.color = getConfigColor(key, value); } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } @@ -532,6 +553,12 @@ QMap PorymapConfig::getKeyValueMap() { } map.insert("event_selection_shape_mode", (this->eventSelectionShapeMode == QGraphicsPixmapItem::MaskShape) ? "mask" : "bounding_rect"); map.insert("shown_in_game_reload_message", this->shownInGameReloadMessage ? "1" : "0"); + map.insert("grid_width", QString::number(this->gridSettings.width)); + map.insert("grid_height", QString::number(this->gridSettings.height)); + map.insert("grid_x", QString::number(this->gridSettings.offsetX)); + map.insert("grid_y", QString::number(this->gridSettings.offsetY)); + map.insert("grid_style", GridSettings::getStyleName(this->gridSettings.style)); + map.insert("grid_color", this->gridSettings.color.name().remove("#")); // Our text config treats '#' as the start of a comment. return map; } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fdc325fc..d81bd2a0 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -587,6 +587,8 @@ void MainWindow::loadUserSettings() { ui->checkBox_ToggleBorder->setChecked(porymapConfig.showBorder); ui->actionShow_Events_In_Map_View->setChecked(porymapConfig.eventOverlayEnabled); + this->editor->gridSettings = porymapConfig.gridSettings; + setTheme(porymapConfig.theme); setDivingMapsVisible(porymapConfig.showDiveEmergeMaps); } @@ -1989,6 +1991,7 @@ void MainWindow::on_actionGrid_Settings_triggered() { if (!this->gridSettingsDialog) { this->gridSettingsDialog = new GridSettingsDialog(&this->editor->gridSettings, this); connect(this->gridSettingsDialog, &GridSettingsDialog::changedGridSettings, this->editor, &Editor::updateMapGrid); + connect(this->gridSettingsDialog, &GridSettingsDialog::accepted, [this] { porymapConfig.gridSettings = this->editor->gridSettings; }); } openSubWindow(this->gridSettingsDialog); } diff --git a/src/ui/gridsettings.cpp b/src/ui/gridsettings.cpp index d3346f11..87e0896a 100644 --- a/src/ui/gridsettings.cpp +++ b/src/ui/gridsettings.cpp @@ -1,8 +1,6 @@ #include "ui_gridsettingsdialog.h" #include "gridsettings.h" -// TODO: Save settings in config - const QMap GridSettings::styleToName = { {Style::Solid, "Solid"}, {Style::LargeDashes, "Large Dashes"}, From d33f0fc6f00d97001cb81354b147ec7d38c1b5d9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 21:22:29 -0400 Subject: [PATCH 23/25] Stop QTextEdit from stealing scroll focus --- forms/projectsettingseditor.ui | 7 ++++++- include/ui/noscrolltextedit.h | 25 +++++++++++++++++++++++++ porymap.pro | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 include/ui/noscrolltextedit.h diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index fe065be9..642c13a8 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -1374,7 +1374,7 @@ - + Metatile Behaviors on this list won't trigger warnings for Warp Events @@ -1744,6 +1744,11 @@ QSpinBox
noscrollspinbox.h
+ + NoScrollTextEdit + QTextEdit +
noscrolltextedit.h
+
UIntSpinBox QAbstractSpinBox diff --git a/include/ui/noscrolltextedit.h b/include/ui/noscrolltextedit.h new file mode 100644 index 00000000..dfc66789 --- /dev/null +++ b/include/ui/noscrolltextedit.h @@ -0,0 +1,25 @@ +#ifndef NOSCROLLTEXTEDIT_H +#define NOSCROLLTEXTEDIT_H + +#include +#include + +class NoScrollTextEdit : public QTextEdit +{ + Q_OBJECT +public: + explicit NoScrollTextEdit(const QString &text, QWidget *parent = nullptr) : QTextEdit(text, parent) { + setFocusPolicy(Qt::StrongFocus); + }; + explicit NoScrollTextEdit(QWidget *parent = nullptr) : NoScrollTextEdit(QString(), parent) {}; + + virtual void wheelEvent(QWheelEvent *event) override { + if (hasFocus()) { + QTextEdit::wheelEvent(event); + } else { + event->ignore(); + } + }; +}; + +#endif // NOSCROLLTEXTEDIT_H diff --git a/porymap.pro b/porymap.pro index 35fd6af2..1cdffddf 100644 --- a/porymap.pro +++ b/porymap.pro @@ -223,6 +223,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/newmapgroupdialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ + include/ui/noscrolltextedit.h \ include/ui/montabwidget.h \ include/ui/encountertablemodel.h \ include/ui/encountertabledelegates.h \ From c26c01aaff95c1883a257b07e72f9b477edd73e4 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 22 Apr 2025 14:48:15 -0400 Subject: [PATCH 24/25] Add missing tooltip formatting --- forms/connectionslistitem.ui | 14 +-- forms/customattributesdialog.ui | 6 +- forms/customscriptseditor.ui | 6 +- forms/mainwindow.ui | 50 +-------- forms/maplisttoolbar.ui | 8 +- forms/newmapconnectiondialog.ui | 12 +-- forms/preferenceeditor.ui | 16 +-- forms/projectsettingseditor.ui | 160 ++++++++++++++++------------- forms/regionmappropertiesdialog.ui | 36 +++---- include/core/utility.h | 1 + src/core/utility.cpp | 4 + src/ui/customattributestable.cpp | 3 +- src/ui/eventframes.cpp | 103 +++++++++++-------- src/ui/maplisttoolbar.cpp | 2 +- src/ui/projectsettingseditor.cpp | 5 +- 15 files changed, 215 insertions(+), 211 deletions(-) diff --git a/forms/connectionslistitem.ui b/forms/connectionslistitem.ui index bf04e8be..116da983 100644 --- a/forms/connectionslistitem.ui +++ b/forms/connectionslistitem.ui @@ -6,7 +6,7 @@ 0 0 - 178 + 188 157
@@ -20,7 +20,7 @@ .ConnectionsListItem { border-width: 1px; } - QFrame::StyledPanel + QFrame::Shape::StyledPanel @@ -65,7 +65,7 @@ - Remove this connection. + <html><head/><body><p>Remove this connection.</p></body></html> ... @@ -79,28 +79,28 @@ - Where the connected map should be positioned relative to the current map. + <html><head/><body><p>Where the connected map should be positioned relative to the current map.</p></body></html> - The name of the map to connect to the current map. + <html><head/><body><p>The name of the map to connect to the current map.</p></body></html> - The number of spaces to move the connected map perpendicular to its connected direction. + <html><head/><body><p>The number of spaces to move the connected map perpendicular to its connected direction.</p></body></html> - Open the connected map. + <html><head/><body><p>Open the connected map.</p></body></html> ... diff --git a/forms/customattributesdialog.ui b/forms/customattributesdialog.ui index b1f1ee4b..90dfba6e 100644 --- a/forms/customattributesdialog.ui +++ b/forms/customattributesdialog.ui @@ -33,7 +33,7 @@ - The key name for the new JSON field + <html><head/><body><p>The key name for the new JSON field</p></body></html> true @@ -50,7 +50,7 @@ - The data type for the new JSON field + <html><head/><body><p>The data type for the new JSON field</p></body></html> @@ -70,7 +70,7 @@ - The value for the new JSON field + <html><head/><body><p>The value for the new JSON field</p></body></html> diff --git a/forms/customscriptseditor.ui b/forms/customscriptseditor.ui index e2efa2af..7db3b208 100644 --- a/forms/customscriptseditor.ui +++ b/forms/customscriptseditor.ui @@ -60,7 +60,7 @@ - Create a new Porymap script file with a default template + <html><head/><body><p>Create a new Porymap script file with a default template</p></body></html> Create New Script... @@ -74,7 +74,7 @@ - Add an existing script file to the list below + <html><head/><body><p>Add an existing script file to the list below</p></body></html> Load Script... @@ -88,7 +88,7 @@ - Refresh all loaded scripts to account for any recent edits + <html><head/><body><p>Refresh all loaded scripts to account for any recent edits</p></body></html> Refresh Scripts diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 53224385..bf0d2608 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -260,9 +260,6 @@ false - - - 0 @@ -2380,7 +2377,7 @@ - If enabled, connections will automatically be updated on the connected map. + <html><head/><body><p>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</p></body></html> Mirror to Connecting Maps @@ -2433,7 +2430,7 @@ false - Open the selected Dive Map + <html><head/><body><p>Open the selected Dive Map</p></body></html> ... @@ -2447,7 +2444,7 @@ - If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider. + <html><head/><body><p>If enabled, the connected Emerge and/or Dive maps will be displayed with an opacity set using the slider.</p></body></html> Show Emerge/Dive Maps @@ -2570,7 +2567,7 @@ false - Open the selected Emerge Map + <html><head/><body><p>Open the selected Emerge Map</p></body></html> ... @@ -3079,45 +3076,6 @@ Ctrl+T - - - true - - - - :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - - - Sort by &Location - - - - - true - - - - :/icons/sort_number.ico:/icons/sort_number.ico - - - Sort by &Group - - - Sort by Group - - - - - true - - - - :/icons/sort_map.ico:/icons/sort_map.ico - - - Sort by &Layout - - About Porymap... diff --git a/forms/maplisttoolbar.ui b/forms/maplisttoolbar.ui index 54eb48d0..07878f0a 100644 --- a/forms/maplisttoolbar.ui +++ b/forms/maplisttoolbar.ui @@ -32,7 +32,7 @@ - Add a new folder to the list. + <html><head/><body><p>Add a new folder to the list.</p></body></html> @@ -73,7 +73,7 @@ - Expand all folders in the list. + <html><head/><body><p>Expand all folders in the list.</p></body></html> @@ -93,7 +93,7 @@ - Collapse all folders in the list. + <html><head/><body><p>Collapse all folders in the list.</p></body></html> @@ -113,7 +113,7 @@ - If enabled, folders may be renamed and items in the list may be rearranged. + <html><head/><body><p>If enabled, folders may be renamed and items in the list may be rearranged.</p></body></html> diff --git a/forms/newmapconnectiondialog.ui b/forms/newmapconnectiondialog.ui index 9b3a3b6e..85aeec72 100644 --- a/forms/newmapconnectiondialog.ui +++ b/forms/newmapconnectiondialog.ui @@ -17,10 +17,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -45,7 +45,7 @@ - The name of the map to connect to the current map. + <html><head/><body><p>The name of the map to connect to the current map.</p></body></html> @@ -59,7 +59,7 @@ - Where the connected map should be positioned relative to the current map. + <html><head/><body><p>Where the connected map should be positioned relative to the current map.</p></body></html> @@ -82,10 +82,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/forms/preferenceeditor.ui b/forms/preferenceeditor.ui index 2ce37cbe..83de6f18 100644 --- a/forms/preferenceeditor.ui +++ b/forms/preferenceeditor.ui @@ -40,7 +40,7 @@ - If checked, a prompt to reload your project will appear if relevant project files are edited + <html><head/><body><p>If checked, a prompt to reload your project will appear if relevant project files are edited</p></body></html> Monitor project files @@ -50,7 +50,7 @@ - If checked, Porymap will automatically open your most recently opened project on startup + <html><head/><body><p>If checked, Porymap will automatically open your most recently opened project on startup</p></body></html> Open recent project on launch @@ -60,7 +60,7 @@ - If checked, Porymap will automatically alert you on startup if a new release is available + <html><head/><body><p>If checked, Porymap will automatically alert you on startup if a new release is available</p></body></html> Automatically check for updates @@ -112,7 +112,7 @@ - If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted. + <html><head/><body><p>If checked, no warning will be shown when deleting an event that has an associated #define that may also be deleted.</p></body></html> Disable warning when deleting events with IDs @@ -138,7 +138,7 @@ - If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping. + <html><head/><body><p>If enabled, an event can be selected by clicking directly on the opaque pixels of its sprite. This may be preferable when events are overlapping.</p></body></html> Select by clicking on sprite @@ -148,7 +148,7 @@ - If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites. + <html><head/><body><p>If enabled, an event can be selected by clicking anywhere within its sprite dimensions. This may be preferable for events with small or mostly transparent sprites.</p></body></html> Select by clicking within bounding rectangle @@ -231,7 +231,7 @@ - The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH). + <html><head/><body><p>The shell command for your preferred text editor (possibly an absolute path if the program doesn't exist in your PATH).</p></body></html> e.g. code %D @@ -264,7 +264,7 @@ - The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH). + <html><head/><body><p>The shell command for your preferred text editor to open a file to a specific line number (possibly an absolute path if the program doesn't exist in your PATH).</p></body></html> e.g. code --goto %F:%L diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index 642c13a8..4cf06515 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -52,7 +52,7 @@ - Whether map script files should prefer using .pory + <html><head/><body><p>Whether map script files should prefer using .pory</p></body></html> Use Poryscript @@ -61,6 +61,9 @@ + + <html><head/><body><p>If enabled, Porymap will display wild encounter data on the Wild Pokémon tab.</p></body></html> + Show Wild Encounter Tables @@ -99,7 +102,7 @@ - Restore the data in the prefabs file to the version defaults. Will create a new file if one doesn't exist. + <html><head/><body><p>Restore the data in the prefabs file to the version defaults. Will create a new file if one doesn't exist.</p></body></html> Import Defaults @@ -109,7 +112,7 @@ - The file that will be used to populate the Prefabs tab + <html><head/><body><p>The file that will be used to populate the Prefabs tab</p></body></html> prefabs.json @@ -148,7 +151,7 @@ - The image sheet that will be used to represent elevation and collision on the Collision tab + <html><head/><body><p>The image sheet that will be used to represent elevation and collision on the Collision tab</p></body></html> true @@ -176,7 +179,7 @@ - The maximum collision value represented with an icon on the image sheet + <html><head/><body><p>The maximum collision value represented with an icon on the image sheet</p></body></html> @@ -197,7 +200,7 @@ - The maximum elevation value represented with an icon on the image sheet + <html><head/><body><p>The maximum elevation value represented with an icon on the image sheet</p></body></html> @@ -270,7 +273,7 @@ - The icon that will be displayed on the Wild Pokémon tab for the above species + <html><head/><body><p>The icon that will be displayed on the Wild Pokémon tab for the above species</p></body></html> true @@ -304,22 +307,22 @@ - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see North of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + 0 + - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see South of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 160 pixel tall GBA screen.</p></body></html> + + 0 + @@ -342,22 +345,22 @@ - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see West of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + 0 + - - 0 - <html><head/><body><p>The distance (in pixels) that a player is able to see East of their character's position in-game. By default this is the distance from the center 16x16 to the edge of the 240 pixel wide GBA screen.</p></body></html> + + 0 + @@ -463,7 +466,7 @@ 0 0 - 561 + 570 622 @@ -491,14 +494,14 @@ - The default elevation that will be used to fill new maps + <html><head/><body><p>The default elevation that will be used to fill new maps</p></body></html> - Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file + <html><head/><body><p>Whether a separate text.inc or text.pory file will be created for new maps, alongside the scripts file</p></body></html> Create separate text file @@ -507,6 +510,9 @@ + + <html><head/><body><p>The default layout width for new maps</p></body></html> + 1 @@ -514,6 +520,9 @@ + + <html><head/><body><p>The default layout height for new maps</p></body></html> + 1 @@ -543,14 +552,14 @@ - The default metatile value that will be used to fill new maps + <html><head/><body><p>The default metatile value that will be used to fill new maps</p></body></html> - The default collision that will be used to fill new maps + <html><head/><body><p>The default collision that will be used to fill new maps</p></body></html> @@ -573,7 +582,7 @@ - A comma-separated list of metatile values that will be used to fill new map borders + <html><head/><body><p>A comma-separated list of metatile values that will be used to fill new map borders</p></body></html> @@ -596,28 +605,28 @@ - The default metatile value that will be used for the top-left border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the top-left border metatile on new maps.</p></body></html> - The default metatile value that will be used for the top-right border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the top-right border metatile on new maps.</p></body></html> - The default metatile value that will be used for the bottom-left border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the bottom-left border metatile on new maps.</p></body></html> - The default metatile value that will be used for the bottom-right border metatile on new maps. + <html><head/><body><p>The default metatile value that will be used for the bottom-right border metatile on new maps.</p></body></html> @@ -697,7 +706,7 @@ - The mask used to read/write metatile IDs in map data. + <html><head/><body><p>The mask used to read/write metatile IDs in map data.</p></body></html> @@ -711,7 +720,7 @@ - The mask used to read/write collision values in map data. + <html><head/><body><p>The mask used to read/write collision values in map data.</p></body></html> @@ -725,7 +734,7 @@ - The mask used to read/write elevation values in map data. + <html><head/><body><p>The mask used to read/write elevation values in map data.</p></body></html> @@ -754,7 +763,7 @@ - Whether "Allow Running", "Allow Biking" and "Allow Dig & Escape Rope" are default options for Map Headers + <html><head/><body><p>Whether &quot;Allow Running&quot;, &quot;Allow Biking&quot; and &quot;Allow Dig &amp; Escape Rope&quot; are default options for Map Headers</p></body></html> Enable 'Allow Running/Biking/Escaping' @@ -764,7 +773,7 @@ - Whether "Floor Number" is a default option for Map Headers + <html><head/><body><p>Whether &quot;Floor Number&quot; is a default option for Map Headers</p></body></html> Enable 'Floor Number' @@ -774,7 +783,7 @@ - Whether the dimensions of the border can be changed. If not set, all borders are 2x2 + <html><head/><body><p>Whether the dimensions of the border can be changed. If not set, all borders are 2x2</p></body></html> Enable Custom Border Size @@ -834,7 +843,7 @@ 0 0 - 561 + 570 798 @@ -853,7 +862,11 @@ - + + + <html><head/><body><p>The default primary tileset to use for new maps/layouts.</p></body></html> + + @@ -863,7 +876,11 @@ - + + + <html><head/><body><p>The default secondary tileset to use for new maps/layouts.</p></body></html> + + @@ -877,7 +894,7 @@ - Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default) + <html><head/><body><p>Fully transparent pixels will be rendered as black pixels (the Pokémon games do this by default)</p></body></html> Render as black @@ -887,7 +904,7 @@ - Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA) + <html><head/><body><p>Fully transparent pixels will be rendered using the first palette color (this the default behavior for the GBA)</p></body></html> Render using first palette color @@ -913,7 +930,7 @@ - This raw tile value will be used to fill the unused bottom layer of Normal metatiles + <html><head/><body><p>This raw tile value will be used to fill the unused bottom layer of Normal metatiles</p></body></html> @@ -927,7 +944,7 @@ - This raw tile value will be used to fill the unused top layer of Covered metatiles + <html><head/><body><p>This raw tile value will be used to fill the unused top layer of Covered metatiles</p></body></html> @@ -941,7 +958,7 @@ - This raw tile value will be used to fill the unused middle layer of Split metatiles + <html><head/><body><p>This raw tile value will be used to fill the unused middle layer of Split metatiles</p></body></html> @@ -985,22 +1002,19 @@ - The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Layer Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> - The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Metatile Behavior from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> - - The number of bytes used per metatile for metatile attributes - Attributes size (in bytes) @@ -1031,6 +1045,9 @@ + + <html><head/><body><p>If checked, metatiles will be interpreted as having 3 layers of 4 tiles each (12 tiles total) as opposed to the default 2 layers of 4 tiles each (8 total).</p></body></html> + Enable Triple Layer Metatiles @@ -1039,7 +1056,7 @@ - The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Terrain Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> @@ -1066,7 +1083,7 @@ - The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled. + <html><head/><body><p>The mask used to read/write Encounter Type from the metatile's attributes data. If 0, this attribute is disabled.</p></body></html> @@ -1079,6 +1096,9 @@ + + <html><head/><body><p>The number of bytes each metatile has for metatile attributes. This is the metadata about each metatile like behvior, layer type, etc.</p></body></html> + false @@ -1119,7 +1139,7 @@ - Whether the C data outputted for new tilesets will include the "callback" field + <html><head/><body><p>Whether the C data outputted for new tilesets will include the &quot;callback&quot; field</p></body></html> Output 'callback' field @@ -1129,7 +1149,7 @@ - Whether the C data outputted for new tilesets will include the "isCompressed" field + <html><head/><body><p>Whether the C data outputted for new tilesets will include the &quot;isCompressed&quot; field</p></body></html> Output 'isCompressed' field @@ -1176,7 +1196,7 @@ 0 0 - 561 + 570 840 @@ -1204,7 +1224,7 @@ - The icon that will be used to represent Warp events + <html><head/><body><p>The icon that will be used to represent Warp events</p></body></html> true @@ -1214,7 +1234,7 @@ - The icon that will be used to represent Heal Location events + <html><head/><body><p>The icon that will be used to represent Heal Location events</p></body></html> true @@ -1238,7 +1258,7 @@ - The icon that will be used to represent Object events that don't have their own sprite + <html><head/><body><p>The icon that will be used to represent Object events that don't have their own sprite</p></body></html> true @@ -1255,7 +1275,7 @@ - The icon that will be used to represent Trigger events + <html><head/><body><p>The icon that will be used to represent Trigger events</p></body></html> true @@ -1265,7 +1285,7 @@ - The icon that will be used to represent BG events + <html><head/><body><p>The icon that will be used to represent BG events</p></body></html> true @@ -1339,7 +1359,7 @@ - Remove the current text from the list + <html><head/><body><p>Remove the current text from the list</p></body></html> ... @@ -1363,7 +1383,7 @@ - If checked, Warp Events will not display a warning about incompatible metatile behaviors + <html><head/><body><p>If checked, Warp Events will not display a warning about incompatible metatile behaviors</p></body></html> Disable Warning @@ -1376,7 +1396,7 @@ - Metatile Behaviors on this list won't trigger warnings for Warp Events + <html><head/><body><p>Metatile Behaviors on this list won't trigger warnings for Warp Events</p></body></html> true @@ -1392,7 +1412,7 @@ - Add the current text to the list + <html><head/><body><p>Add the current text to the list</p></body></html> ... @@ -1558,8 +1578,8 @@ 0 0 - 561 - 593 + 570 + 499 @@ -1605,8 +1625,8 @@ 0 0 - 535 - 531 + 544 + 437 @@ -1647,8 +1667,8 @@ 0 0 - 561 - 593 + 570 + 499 @@ -1694,8 +1714,8 @@ 0 0 - 535 - 531 + 544 + 437 diff --git a/forms/regionmappropertiesdialog.ui b/forms/regionmappropertiesdialog.ui index 80e7020a..88b465f4 100644 --- a/forms/regionmappropertiesdialog.ui +++ b/forms/regionmappropertiesdialog.ui @@ -21,7 +21,7 @@ - QFormLayout::AllNonFixedFieldsGrow + QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow @@ -33,7 +33,7 @@ - A nickname for this region map that will differentiate it from others (should be unique). + <html><head/><body><p>A nickname for this region map that will differentiate it from others (should be unique).</p></body></html> @@ -131,7 +131,7 @@ - The height of the tilemap + <html><head/><body><p>The height of the tilemap</p></body></html> 255 @@ -148,10 +148,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -218,10 +218,10 @@ <html><head/><body><p>Path to the tilemap binary relative to the project root.</p></body></html> - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -269,10 +269,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -392,10 +392,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Raised + QFrame::Shadow::Raised @@ -487,7 +487,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -517,7 +517,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -590,7 +590,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -617,7 +617,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -646,7 +646,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -659,10 +659,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok diff --git a/include/core/utility.h b/include/core/utility.h index 6613ee71..09caebce 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -9,6 +9,7 @@ namespace Util { int roundUp(int numToRound, int multiple); QString toDefineCase(QString input); QString toHexString(uint32_t value, int minLength = 0); + QString toHtmlParagraph(const QString &text); Qt::Orientations getOrientation(bool xflip, bool yflip); } diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 55830b8a..7c7e5435 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -43,6 +43,10 @@ QString Util::toHexString(uint32_t value, int minLength) { return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper(); } +QString Util::toHtmlParagraph(const QString &text) { + return QString("

%1

").arg(text); +} + Qt::Orientations Util::getOrientation(bool xflip, bool yflip) { Qt::Orientations flags; if (xflip) flags |= Qt::Orientation::Horizontal; diff --git a/src/ui/customattributestable.cpp b/src/ui/customattributestable.cpp index 28153b4d..ba9409c8 100644 --- a/src/ui/customattributestable.cpp +++ b/src/ui/customattributestable.cpp @@ -1,6 +1,7 @@ #include "customattributestable.h" #include "parseutil.h" #include "noscrollspinbox.h" +#include "utility.h" #include #include @@ -96,7 +97,7 @@ int CustomAttributesTable::addAttribute(const QString &key, const QJsonValue &va keyItem->setFlags(Qt::ItemIsEnabled); keyItem->setData(DataRole::JsonType, type); // Record the type for writing to the file keyItem->setTextAlignment(Qt::AlignCenter); - keyItem->setToolTip(key); // Display name as tool tip in case it's too long to see in the cell + keyItem->setToolTip(Util::toHtmlParagraph(key)); // Display name as tool tip in case it's too long to see in the cell this->setItem(rowIndex, Column::Key, keyItem); // Add value to table diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index bce55c66..d3cfdc80 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -209,15 +209,16 @@ void ObjectFrame::setup() { // sprite combo QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); - this->combo_sprite->setToolTip("The sprite graphics to use for this object."); + static const QString combo_sprite_toolTip = Util::toHtmlParagraph("The sprite graphics to use for this object."); + this->combo_sprite->setToolTip(combo_sprite_toolTip); l_form_sprite->addRow("Sprite", this->combo_sprite); this->layout_contents->addLayout(l_form_sprite); // movement QFormLayout *l_form_movement = new QFormLayout(); this->combo_movement = new NoScrollComboBox(this); - this->combo_movement->setToolTip("The object's natural movement behavior when\n" - "the player is not interacting with it."); + static const QString combo_movement_toolTip = Util::toHtmlParagraph("The object's natural movement behavior when the player is not interacting with it."); + this->combo_movement->setToolTip(combo_movement_toolTip); l_form_movement->addRow("Movement", this->combo_movement); this->layout_contents->addLayout(l_form_movement); @@ -226,15 +227,15 @@ void ObjectFrame::setup() { this->spinner_radius_x = new NoScrollSpinBox(this); this->spinner_radius_x->setMinimum(0); this->spinner_radius_x->setMaximum(255); - this->spinner_radius_x->setToolTip("The maximum number of metatiles this object\n" - "is allowed to move left or right during its\n" - "normal movement behavior actions."); + static const QString spinner_radius_x_toolTip = Util::toHtmlParagraph("The maximum number of metatiles this object is allowed to move left " + "or right during its normal movement behavior actions."); + this->spinner_radius_x->setToolTip(spinner_radius_x_toolTip); this->spinner_radius_y = new NoScrollSpinBox(this); this->spinner_radius_y->setMinimum(0); this->spinner_radius_y->setMaximum(255); - this->spinner_radius_y->setToolTip("The maximum number of metatiles this object\n" - "is allowed to move up or down during its\n" - "normal movement behavior actions."); + static const QString spinner_radius_y_toolTip = Util::toHtmlParagraph("The maximum number of metatiles this object is allowed to move up " + "or down during its normal movement behavior actions."); + this->spinner_radius_y->setToolTip(spinner_radius_y_toolTip); l_form_radii_xy->addRow("Movement Radius X", this->spinner_radius_x); l_form_radii_xy->addRow("Movement Radius Y", this->spinner_radius_y); this->layout_contents->addLayout(l_form_radii_xy); @@ -242,11 +243,13 @@ void ObjectFrame::setup() { // script QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); - this->combo_script->setToolTip("The script which is executed with this event."); + static const QString combo_script_toolTip = Util::toHtmlParagraph("The script that is executed with this event."); + this->combo_script->setToolTip(combo_script_toolTip); // Add button next to combo which opens combo's current script. this->button_script = new QToolButton(this); - this->button_script->setToolTip("Go to this script definition in text editor."); + static const QString button_script_toolTip = Util::toHtmlParagraph("Go to this script definition in text editor."); + this->button_script->setToolTip(button_script_toolTip); this->button_script->setFixedSize(this->combo_script->height(), this->combo_script->height()); this->button_script->setIcon(QFileIconProvider().icon(QFileIconProvider::File)); @@ -261,24 +264,25 @@ void ObjectFrame::setup() { // event flag QFormLayout *l_form_flag = new QFormLayout(); this->combo_flag = new NoScrollComboBox(this); - this->combo_flag->setToolTip("The flag which hides the object when set."); + static const QString combo_flag_toolTip = Util::toHtmlParagraph("The flag that hides the object when set."); + this->combo_flag->setToolTip(combo_flag_toolTip); l_form_flag->addRow("Event Flag", this->combo_flag); this->layout_contents->addLayout(l_form_flag); // trainer type QFormLayout *l_form_trainer = new QFormLayout(); this->combo_trainer_type = new NoScrollComboBox(this); - this->combo_trainer_type->setToolTip("The trainer type of this object event.\n" - "If it is not a trainer, use NONE. SEE ALL DIRECTIONS\n" - "should only be used with a sight radius of 1."); + static const QString combo_trainer_type_toolTip = Util::toHtmlParagraph("The trainer type of this object event. If it is not a trainer, use NONE. " + "SEE ALL DIRECTIONS should only be used with a sight radius of 1."); + this->combo_trainer_type->setToolTip(combo_trainer_type_toolTip); l_form_trainer->addRow("Trainer Type", this->combo_trainer_type); this->layout_contents->addLayout(l_form_trainer); // sight radius / berry tree id QFormLayout *l_form_radius_treeid = new QFormLayout(); this->combo_radius_treeid = new NoScrollComboBox(this); - this->combo_radius_treeid->setToolTip("The maximum sight range of a trainer,\n" - "OR the unique id of the berry tree."); + static const QString combo_radius_treeid_toolTip = Util::toHtmlParagraph("The maximum sight range of a trainer, OR the unique id of the berry tree."); + this->combo_radius_treeid->setToolTip(combo_radius_treeid_toolTip); l_form_radius_treeid->addRow("Sight Radius / Berry Tree ID", this->combo_radius_treeid); this->layout_contents->addLayout(l_form_radius_treeid); @@ -420,14 +424,16 @@ void CloneObjectFrame::setup() { // clone map id combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_target_map = new NoScrollComboBox(this); - this->combo_target_map->setToolTip("The name of the map that the object being cloned is on."); + static const QString combo_target_map_toolTip = Util::toHtmlParagraph("The name of the map that the object being cloned is on."); + this->combo_target_map->setToolTip(combo_target_map_toolTip); l_form_dest_map->addRow("Target Map", this->combo_target_map); this->layout_contents->addLayout(l_form_dest_map); // clone local id spinbox QFormLayout *l_form_dest_id = new QFormLayout(); this->spinner_target_id = new NoScrollSpinBox(this); - this->spinner_target_id->setToolTip("event_object ID of the object being cloned."); + static const QString spinner_target_id_toolTip = Util::toHtmlParagraph("event_object ID of the object being cloned."); + this->spinner_target_id->setToolTip(spinner_target_id_toolTip); l_form_dest_id->addRow("Target Local ID", this->spinner_target_id); this->layout_contents->addLayout(l_form_dest_id); @@ -497,21 +503,21 @@ void WarpFrame::setup() { // desination map combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_dest_map = new NoScrollComboBox(this); - this->combo_dest_map->setToolTip("The destination map name of the warp."); + static const QString combo_dest_map_toolTip = Util::toHtmlParagraph("The destination map name of the warp."); + this->combo_dest_map->setToolTip(combo_dest_map_toolTip); l_form_dest_map->addRow("Destination Map", this->combo_dest_map); this->layout_contents->addLayout(l_form_dest_map); // desination warp id QFormLayout *l_form_dest_warp = new QFormLayout(); this->combo_dest_warp = new NoScrollComboBox(this); - this->combo_dest_warp->setToolTip("The warp id on the destination map."); + static const QString combo_dest_warp_toolTip = Util::toHtmlParagraph("The warp id on the destination map."); + this->combo_dest_warp->setToolTip(combo_dest_warp_toolTip); l_form_dest_warp->addRow("Destination Warp", this->combo_dest_warp); this->layout_contents->addLayout(l_form_dest_warp); // warning - static const QString warningText = "Warning:\n" - "This warp event is not positioned on a metatile with a warp behavior.\n" - "Click this warning for more details."; + auto warningText = QStringLiteral("Warning:\nThis warp event is not positioned on a metatile with a warp behavior.\nClick this warning for more details."); QVBoxLayout *l_vbox_warning = new QVBoxLayout(); this->warning = new QPushButton(warningText, this); this->warning->setFlat(true); @@ -580,22 +586,25 @@ void TriggerFrame::setup() { // script combo QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); - this->combo_script->setToolTip("The script which is executed with this event."); + static const QString combo_script_toolTip = Util::toHtmlParagraph("The script that is executed with this event."); + this->combo_script->setToolTip(combo_script_toolTip); l_form_script->addRow("Script", this->combo_script); this->layout_contents->addLayout(l_form_script); // var combo QFormLayout *l_form_var = new QFormLayout(); this->combo_var = new NoScrollComboBox(this); - this->combo_var->setToolTip("The variable by which the script is triggered.\n" - "The script is triggered when this variable's value matches 'Var Value'."); + static const QString combo_var_toolTip = Util::toHtmlParagraph("The variable by which the script is triggered. " + "The script is triggered when this variable's value matches 'Var Value'."); + this->combo_var->setToolTip(combo_var_toolTip); l_form_var->addRow("Var", this->combo_var); this->layout_contents->addLayout(l_form_var); // var value combo QFormLayout *l_form_var_val = new QFormLayout(); this->combo_var_value = new NoScrollComboBox(this); - this->combo_var_value->setToolTip("The variable's value which triggers the script."); + static const QString combo_var_value_toolTip = Util::toHtmlParagraph("The variable's value that triggers the script."); + this->combo_var_value->setToolTip(combo_var_value_toolTip); l_form_var_val->addRow("Var Value", this->combo_var_value); this->layout_contents->addLayout(l_form_var_val); @@ -668,7 +677,8 @@ void WeatherTriggerFrame::setup() { // weather combo QFormLayout *l_form_weather = new QFormLayout(); this->combo_weather = new NoScrollComboBox(this); - this->combo_weather->setToolTip("The weather that starts when the player steps on this spot."); + static const QString combo_weather_toolTip = Util::toHtmlParagraph("The weather that starts when the player steps on this spot."); + this->combo_weather->setToolTip(combo_weather_toolTip); l_form_weather->addRow("Weather", this->combo_weather); this->layout_contents->addLayout(l_form_weather); @@ -719,15 +729,16 @@ void SignFrame::setup() { // facing dir combo QFormLayout *l_form_facing_dir = new QFormLayout(); this->combo_facing_dir = new NoScrollComboBox(this); - this->combo_facing_dir->setToolTip("The direction which the player must be facing\n" - "to be able to interact with this event."); + static const QString combo_facing_dir_toolTip = Util::toHtmlParagraph("The direction that the player must be facing to be able to interact with this event."); + this->combo_facing_dir->setToolTip(combo_facing_dir_toolTip); l_form_facing_dir->addRow("Player Facing Direction", this->combo_facing_dir); this->layout_contents->addLayout(l_form_facing_dir); // script combo QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); - this->combo_script->setToolTip("The script which is executed with this event."); + static const QString combo_script_toolTip = Util::toHtmlParagraph("The script that is executed with this event."); + this->combo_script->setToolTip(combo_script_toolTip); l_form_script->addRow("Script", this->combo_script); this->layout_contents->addLayout(l_form_script); @@ -790,14 +801,16 @@ void HiddenItemFrame::setup() { // item combo QFormLayout *l_form_item = new QFormLayout(); this->combo_item = new NoScrollComboBox(this); - this->combo_item->setToolTip("The item to be given."); + static const QString combo_item_toolTip = Util::toHtmlParagraph("The item to be given."); + this->combo_item->setToolTip(combo_item_toolTip); l_form_item->addRow("Item", this->combo_item); this->layout_contents->addLayout(l_form_item); // flag combo QFormLayout *l_form_flag = new QFormLayout(); this->combo_flag = new NoScrollComboBox(this); - this->combo_flag->setToolTip("The flag which is set when the hidden item is picked up."); + static const QString combo_flag_toolTip = Util::toHtmlParagraph("The flag that is set when the hidden item is picked up."); + this->combo_flag->setToolTip(combo_flag_toolTip); l_form_flag->addRow("Flag", this->combo_flag); this->layout_contents->addLayout(l_form_flag); @@ -806,7 +819,8 @@ void HiddenItemFrame::setup() { QFormLayout *l_form_quantity = new QFormLayout(hideable_quantity); l_form_quantity->setContentsMargins(0, 0, 0, 0); this->spinner_quantity = new NoScrollSpinBox(hideable_quantity); - this->spinner_quantity->setToolTip("The number of items received when the hidden item is picked up."); + static const QString spinner_quantity_toolTip = Util::toHtmlParagraph("The number of items received when the hidden item is picked up."); + this->spinner_quantity->setToolTip(spinner_quantity_toolTip); this->spinner_quantity->setMinimum(0x01); this->spinner_quantity->setMaximum(0xFF); l_form_quantity->addRow("Quantity", this->spinner_quantity); @@ -817,7 +831,8 @@ void HiddenItemFrame::setup() { QFormLayout *l_form_itemfinder = new QFormLayout(hideable_itemfinder); l_form_itemfinder->setContentsMargins(0, 0, 0, 0); this->check_itemfinder = new QCheckBox(hideable_itemfinder); - this->check_itemfinder->setToolTip("If checked, hidden item can only be picked up using the Itemfinder"); + static const QString check_itemfinder_toolTip = Util::toHtmlParagraph("If checked, hidden item can only be picked up using the Itemfinder"); + this->check_itemfinder->setToolTip(check_itemfinder_toolTip); l_form_itemfinder->addRow("Requires Itemfinder", this->check_itemfinder); this->layout_contents->addWidget(hideable_itemfinder); @@ -906,9 +921,9 @@ void SecretBaseFrame::setup() { // item combo QFormLayout *l_form_base_id = new QFormLayout(); this->combo_base_id = new NoScrollComboBox(this); - this->combo_base_id->setToolTip("The secret base id which is inside this secret\n" - "base entrance. Secret base ids are meant to be\n" - "unique to each and every secret base entrance."); + static const QString combo_base_id_toolTip = Util::toHtmlParagraph("The secret base id that is inside this secret base entrance. " + "Secret base ids are meant to be unique to each and every secret base entrance."); + this->combo_base_id->setToolTip(combo_base_id_toolTip); l_form_base_id->addRow("Secret Base", this->combo_base_id); this->layout_contents->addLayout(l_form_base_id); @@ -960,7 +975,8 @@ void HealLocationFrame::setup() { // ID QFormLayout *l_form_id = new QFormLayout(); this->line_edit_id = new QLineEdit(this); - this->line_edit_id->setToolTip("The unique identifier for this heal location."); + static const QString line_edit_id_toolTip = Util::toHtmlParagraph("The unique identifier for this heal location."); + this->line_edit_id->setToolTip(line_edit_id_toolTip); this->line_edit_id->setPlaceholderText(projectConfig.getIdentifier(ProjectIdentifier::define_heal_locations_prefix) + "MY_MAP"); l_form_id->addRow("ID", this->line_edit_id); this->layout_contents->addLayout(l_form_id); @@ -970,7 +986,8 @@ void HealLocationFrame::setup() { QFormLayout *l_form_respawn_map = new QFormLayout(hideable_respawn_map); l_form_respawn_map->setContentsMargins(0, 0, 0, 0); this->combo_respawn_map = new NoScrollComboBox(hideable_respawn_map); - this->combo_respawn_map->setToolTip("The map where the player will respawn after whiteout."); + static const QString combo_respawn_map_toolTip = Util::toHtmlParagraph("The map where the player will respawn after whiteout."); + this->combo_respawn_map->setToolTip(combo_respawn_map_toolTip); l_form_respawn_map->addRow("Respawn Map", this->combo_respawn_map); this->layout_contents->addWidget(hideable_respawn_map); @@ -979,8 +996,8 @@ void HealLocationFrame::setup() { QFormLayout *l_form_respawn_npc = new QFormLayout(hideable_respawn_npc); l_form_respawn_npc->setContentsMargins(0, 0, 0, 0); this->combo_respawn_npc = new NoScrollComboBox(hideable_respawn_npc); - this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" - "upon respawning after whiteout."); + static const QString combo_respawn_npc_toolTip = Util::toHtmlParagraph("event_object ID of the NPC the player interacts with upon respawning after whiteout."); + this->combo_respawn_npc->setToolTip(combo_respawn_npc_toolTip); l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); this->layout_contents->addWidget(hideable_respawn_npc); diff --git a/src/ui/maplisttoolbar.cpp b/src/ui/maplisttoolbar.cpp index 304d6490..f56be39b 100644 --- a/src/ui/maplisttoolbar.cpp +++ b/src/ui/maplisttoolbar.cpp @@ -93,7 +93,7 @@ void MapListToolBar::setEmptyFoldersVisible(bool visible) { } // Update tool tip to reflect what will happen if the button is pressed. - const QString toolTip = QString("%1 empty folders in the list.").arg(visible ? "Hide" : "Show"); + const QString toolTip = Util::toHtmlParagraph(QString("%1 empty folders in the list.").arg(visible ? "Hide" : "Show")); ui->button_ToggleEmptyFolders->setToolTip(toolTip); const QSignalBlocker b(ui->button_ToggleEmptyFolders); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 5bcf6399..ccbf1ec3 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -173,7 +173,10 @@ void ProjectSettingsEditor::initUi() { bool ProjectSettingsEditor::disableParsedSetting(QWidget * widget, const QString &identifier, const QString &filepath) { if (project && project->disabledSettingsNames.contains(identifier)) { widget->setEnabled(false); - widget->setToolTip(QString("This value has been set using '%1' in %2").arg(identifier).arg(filepath)); + QString toolTip = QString("This value has been set using '%1' in %2").arg(identifier).arg(filepath); + if (!widget->toolTip().isEmpty()) + toolTip.prepend(QString("%1\n\n").arg(widget->toolTip())); + widget->setToolTip(Util::toHtmlParagraph(toolTip)); return true; } return false; From 57545eae0a3c3cdd18eff756de76d47c1122a207 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 22 Apr 2025 14:57:27 -0400 Subject: [PATCH 25/25] Fix initializer order warning --- src/ui/wildmonsearch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/wildmonsearch.cpp b/src/ui/wildmonsearch.cpp index e64fbca4..9957f33a 100644 --- a/src/ui/wildmonsearch.cpp +++ b/src/ui/wildmonsearch.cpp @@ -125,11 +125,11 @@ void WildMonSearch::updateResults(const QString &species) { const QList results = this->resultsCache.value(species, search(species)); if (results.isEmpty()) { static const RowData noResults = { + .mapName = "", .groupName = QStringLiteral("Species not found."), .fieldName = QStringLiteral("--"), .levelRange = QStringLiteral("--"), .chance = QStringLiteral("--"), - .mapName = "", }; addTableEntry(noResults); } else {