From c9b0d139b26e8c10dacb4ae0bf816b6a9a987631 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 29 Mar 2025 22:54:13 -0400 Subject: [PATCH 01/71] Preserve custom fields in the map_sections, layouts, and connections arrays --- include/core/mapconnection.h | 5 +++ include/core/maplayout.h | 2 ++ include/project.h | 1 + src/core/maplayout.cpp | 1 + src/project.cpp | 70 +++++++++++++++++++++++------------- 5 files changed, 54 insertions(+), 25 deletions(-) diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 21c00ac6..e95c25df 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -5,6 +5,7 @@ #include #include #include +#include class Project; class Map; @@ -29,6 +30,9 @@ public: int offset() const { return m_offset; } void setOffset(int offset, bool mirror = true); + QJsonObject customData() const { return m_customData; } + void setCustomData(const QJsonObject &customData) { m_customData = customData; } + MapConnection* findMirror(); MapConnection* createMirror(); @@ -49,6 +53,7 @@ private: QString m_targetMapName; QString m_direction; int m_offset; + QJsonObject m_customData; void markMapEdited(); Map* getMap(const QString& mapName) const; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 57da8840..119a2232 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -42,6 +42,8 @@ public: Tileset *tileset_primary = nullptr; Tileset *tileset_secondary = nullptr; + QJsonObject customData; + Blockdata blockdata; QImage image; diff --git a/include/project.h b/include/project.h index 9a031c0d..09a39bb6 100644 --- a/include/project.h +++ b/include/project.h @@ -263,6 +263,7 @@ public: private: QHash mapSectionDisplayNames; + QHash mapSectionCustomData; QMap modifiedFileTimestamps; QMap facingDirections; QHash speciesToIconPath; diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index e443a635..33408f6a 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -31,6 +31,7 @@ void Layout::copyFrom(const Layout *other) { this->tileset_secondary = other->tileset_secondary; this->blockdata = other->blockdata; this->border = other->border; + this->customData = other->customData; } QString Layout::layoutConstantFromName(const QString &name) { diff --git a/src/project.cpp b/src/project.cpp index bbbc052c..8841cd7f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -308,10 +308,12 @@ bool Project::loadMapData(Map* map) { if (!connectionsArr.isEmpty()) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); - const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); - const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); - const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset)); + const QString direction = ParseUtil::jsonToQString(connectionObj.take("direction")); + const int offset = ParseUtil::jsonToInt(connectionObj.take("offset")); + const QString mapConstant = ParseUtil::jsonToQString(connectionObj.take("map")); + auto connection = new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset); + connection->setCustomData(connectionObj); + map->loadConnection(connection); } } @@ -503,7 +505,7 @@ bool Project::readMapLayouts() { if (layoutObj.isEmpty()) continue; Layout *layout = new Layout(); - layout->id = ParseUtil::jsonToQString(layoutObj["id"]); + layout->id = ParseUtil::jsonToQString(layoutObj.take("id")); if (layout->id.isEmpty()) { logError(QString("Missing 'id' value on layout %1 in %2").arg(i).arg(layoutsFilepath)); delete layout; @@ -514,20 +516,20 @@ bool Project::readMapLayouts() { delete layout; continue; } - layout->name = ParseUtil::jsonToQString(layoutObj["name"]); + layout->name = ParseUtil::jsonToQString(layoutObj.take("name")); if (layout->name.isEmpty()) { logError(QString("Missing 'name' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - int lwidth = ParseUtil::jsonToInt(layoutObj["width"]); + int lwidth = ParseUtil::jsonToInt(layoutObj.take("width")); if (lwidth <= 0) { logError(QString("Invalid 'width' value '%1' for %2 in %3. Must be greater than 0.").arg(lwidth).arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } layout->width = lwidth; - int lheight = ParseUtil::jsonToInt(layoutObj["height"]); + int lheight = ParseUtil::jsonToInt(layoutObj.take("height")); if (lheight <= 0) { logError(QString("Invalid 'height' value '%1' for %2 in %3. Must be greater than 0.").arg(lheight).arg(layout->id).arg(layoutsFilepath)); delete layout; @@ -535,12 +537,12 @@ bool Project::readMapLayouts() { } layout->height = lheight; if (projectConfig.useCustomBorderSize) { - int bwidth = ParseUtil::jsonToInt(layoutObj["border_width"]); + int bwidth = ParseUtil::jsonToInt(layoutObj.take("border_width")); if (bwidth <= 0) { // 0 is an expected border width/height that should be handled, GF used it for the RS layouts in FRLG bwidth = DEFAULT_BORDER_WIDTH; } layout->border_width = bwidth; - int bheight = ParseUtil::jsonToInt(layoutObj["border_height"]); + int bheight = ParseUtil::jsonToInt(layoutObj.take("border_height")); if (bheight <= 0) { bheight = DEFAULT_BORDER_HEIGHT; } @@ -549,30 +551,31 @@ bool Project::readMapLayouts() { layout->border_width = DEFAULT_BORDER_WIDTH; layout->border_height = DEFAULT_BORDER_HEIGHT; } - layout->tileset_primary_label = ParseUtil::jsonToQString(layoutObj["primary_tileset"]); + layout->tileset_primary_label = ParseUtil::jsonToQString(layoutObj.take("primary_tileset")); if (layout->tileset_primary_label.isEmpty()) { logError(QString("Missing 'primary_tileset' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->tileset_secondary_label = ParseUtil::jsonToQString(layoutObj["secondary_tileset"]); + layout->tileset_secondary_label = ParseUtil::jsonToQString(layoutObj.take("secondary_tileset")); if (layout->tileset_secondary_label.isEmpty()) { logError(QString("Missing 'secondary_tileset' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->border_path = ParseUtil::jsonToQString(layoutObj["border_filepath"]); + layout->border_path = ParseUtil::jsonToQString(layoutObj.take("border_filepath")); if (layout->border_path.isEmpty()) { logError(QString("Missing 'border_filepath' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } - layout->blockdata_path = ParseUtil::jsonToQString(layoutObj["blockdata_filepath"]); + layout->blockdata_path = ParseUtil::jsonToQString(layoutObj.take("blockdata_filepath")); if (layout->blockdata_path.isEmpty()) { logError(QString("Missing 'blockdata_filepath' value for %1 in %2").arg(layout->id).arg(layoutsFilepath)); delete layout; return false; } + layout->customData = layoutObj; this->mapLayouts.insert(layout->id, layout); this->mapLayoutsMaster.insert(layout->id, layout->copy()); @@ -615,6 +618,9 @@ void Project::saveMapLayouts() { layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; layoutObj["blockdata_filepath"] = layout->blockdata_path; + for (auto it = layout->customData.constBegin(); it != layout->customData.constEnd(); it++) { + layoutObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } layoutsArr.push_back(layoutObj); } @@ -711,6 +717,11 @@ void Project::saveRegionMapSections() { mapSectionObj["height"] = entry.height; } + QJsonObject customData = this->mapSectionCustomData.value(idName); + for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { + mapSectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } + mapSectionArray.append(mapSectionObj); } @@ -1203,6 +1214,10 @@ void Project::saveMap(Map *map, bool skipLayout) { connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); + auto customData = connection->customData(); + for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { + connectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; @@ -2320,6 +2335,7 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); this->mapSectionDisplayNames.clear(); + this->mapSectionCustomData.clear(); this->regionMapEntries.clear(); const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); @@ -2351,7 +2367,7 @@ bool Project::readRegionMapSections() { continue; } } - const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); + const QString idName = ParseUtil::jsonToQString(mapSectionObj.take(idField)); if (!idName.startsWith(requiredPrefix)) { logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(filepath).arg(requiredPrefix)); continue; @@ -2361,7 +2377,7 @@ bool Project::readRegionMapSections() { this->mapSectionIdNamesSaveOrder.append(idName); if (mapSectionObj.contains("name")) - this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj["name"])); + this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj.take("name"))); // Map sections may have additional data indicating their position on the region map. // If they have this data, we can add them to the region map entry list. @@ -2373,16 +2389,20 @@ bool Project::readRegionMapSections() { break; } } - if (!hasRegionMapData) - continue; + if (hasRegionMapData) { + MapSectionEntry entry; + entry.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); + entry.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); + entry.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); + entry.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); + entry.valid = true; + this->regionMapEntries[idName] = entry; + } - MapSectionEntry entry; - entry.x = ParseUtil::jsonToInt(mapSectionObj["x"]); - entry.y = ParseUtil::jsonToInt(mapSectionObj["y"]); - entry.width = ParseUtil::jsonToInt(mapSectionObj["width"]); - entry.height = ParseUtil::jsonToInt(mapSectionObj["height"]); - entry.valid = true; - this->regionMapEntries[idName] = entry; + // Preserve any remaining fields for when we save. + if (!mapSectionObj.isEmpty()) { + this->mapSectionCustomData[idName] = mapSectionObj; + } } // Make sure the default name is present in the list. From a4508918a17c4bc820538cf2deb70ac1ae4939b9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 31 Mar 2025 19:33:31 -0400 Subject: [PATCH 02/71] Preserve custom global fields in layouts, heal_locations, region_map_sections, and map_groups json files --- include/project.h | 6 +++++ src/project.cpp | 56 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 55 insertions(+), 7 deletions(-) diff --git a/include/project.h b/include/project.h index 09a39bb6..4ed95f06 100644 --- a/include/project.h +++ b/include/project.h @@ -269,6 +269,12 @@ private: QHash speciesToIconPath; QHash maps; + // Fields for preserving top-level JSON data that Porymap isn't expecting. + QJsonObject customLayoutsData; + QJsonObject customMapSectionsData; + QJsonObject customMapGroupsData; + QJsonObject customHealLocationsData; + // Maps/layouts represented in these sets have been fully loaded from the project. // If a valid map name / layout id is not in these sets, a Map / Layout object exists // for it in Project::maps / Project::mapLayouts, but it has been minimally populated diff --git a/src/project.cpp b/src/project.cpp index 8841cd7f..98bd7215 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -475,6 +475,7 @@ void Project::clearMapLayouts() { this->layoutIds.clear(); this->layoutIdsMaster.clear(); this->loadedLayoutIds.clear(); + this->customLayoutsData = QJsonObject(); } bool Project::readMapLayouts() { @@ -491,7 +492,7 @@ bool Project::readMapLayouts() { QJsonObject layoutsObj = layoutsDoc.object(); - this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); + this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj.take("layouts_table_label")); if (this->layoutsLabel.isEmpty()) { this->layoutsLabel = "gMapLayouts"; logWarn(QString("'layouts_table_label' value is missing from %1. Defaulting to %2") @@ -499,7 +500,7 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - QJsonArray layouts = layoutsObj["layouts"].toArray(); + QJsonArray layouts = layoutsObj.take("layouts").toArray(); for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) @@ -588,6 +589,8 @@ bool Project::readMapLayouts() { return false; } + this->customLayoutsData = layoutsObj; + return true; } @@ -623,10 +626,14 @@ void Project::saveMapLayouts() { } layoutsArr.push_back(layoutObj); } + layoutsObj["layouts"] = layoutsArr; + + for (auto it = this->customLayoutsData.constBegin(); it != this->customLayoutsData.constEnd(); it++) { + layoutsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(layoutsFilepath); - layoutsObj["layouts"] = layoutsArr; OrderedJson layoutJson(layoutsObj); OrderedJsonDoc jsonDoc(&layoutJson); jsonDoc.dump(&layoutsFile); @@ -683,6 +690,9 @@ void Project::saveMapGroups() { } mapGroupsObj[groupName] = groupArr; } + for (auto it = this->customMapGroupsData.constBegin(); it != this->customMapGroupsData.constEnd(); it++) { + mapGroupsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -727,6 +737,9 @@ void Project::saveRegionMapSections() { OrderedJson::object object; object["map_sections"] = mapSectionArray; + for (auto it = this->customMapSectionsData.constBegin(); it != this->customMapSectionsData.constEnd(); it++) { + object[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -881,6 +894,9 @@ void Project::saveHealLocations() { OrderedJson::object object; object["heal_locations"] = eventJsonArr; + for (auto it = this->customHealLocationsData.constBegin(); it != this->customHealLocationsData.constEnd(); it++) { + object[it.key()] = OrderedJson::fromQJsonValue(it.value()); + } ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -1787,6 +1803,7 @@ bool Project::readMapGroups() { this->mapNames.clear(); this->groupNames.clear(); this->groupNameToMapNames.clear(); + this->customMapGroupsData = QJsonObject(); this->initTopLevelMapFields(); @@ -1809,7 +1826,12 @@ bool Project::readMapGroups() { QStringList failedMapNames; for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); - const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); + if (this->groupNames.contains(groupName)) { + logWarn(QString("Ignoring repeated map group name '%1'.").arg(groupName)); + continue; + } + + const QJsonArray mapNamesJson = mapGroupsObj.take(groupName).toArray(); this->groupNames.append(groupName); // Process the names in this map group @@ -1903,6 +1925,12 @@ bool Project::readMapGroups() { this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); this->mapNames.append(dynamicMapName); + // Save custom JSON data. + // Chuck the "connections_include_order" field, this is only for matching. + // TODO: Setting not to do this, on the off chance someone wants this field. + mapGroupsObj.remove("connections_include_order"); + this->customMapGroupsData = mapGroupsObj; + return true; } @@ -2335,11 +2363,16 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); this->mapSectionDisplayNames.clear(); - this->mapSectionCustomData.clear(); this->regionMapEntries.clear(); const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); + // The first of these is the custom data for each individual map section object, + // the second is the custom top-level data in the map sections file. + // TODO: Clarify this by relocating the various map section data maps to a single class? + this->mapSectionCustomData.clear(); + this->customMapSectionsData = QJsonObject(); + QJsonDocument doc; const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); QString error; @@ -2349,7 +2382,8 @@ bool Project::readRegionMapSections() { } fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); - QJsonArray mapSections = doc.object()["map_sections"].toArray(); + QJsonObject mapSectionsGlobalObj = doc.object(); + QJsonArray mapSections = mapSectionsGlobalObj.take("map_sections").toArray(); for (int i = 0; i < mapSections.size(); i++) { QJsonObject mapSectionObj = mapSections.at(i).toObject(); @@ -2399,11 +2433,16 @@ bool Project::readRegionMapSections() { this->regionMapEntries[idName] = entry; } + // Chuck the "name_clone" field, this is only for matching. + // TODO: Setting not to do this, on the off chance someone wants this field. + mapSectionObj.remove("name_clone"); + // Preserve any remaining fields for when we save. if (!mapSectionObj.isEmpty()) { this->mapSectionCustomData[idName] = mapSectionObj; } } + this->customMapSectionsData = mapSectionsGlobalObj; // Make sure the default name is present in the list. if (!this->mapSectionIdNames.contains(defaultName)) { @@ -2477,6 +2516,7 @@ void Project::clearHealLocations() { } this->healLocations.clear(); this->healLocationSaveOrder.clear(); + this->customHealLocationsData = QJsonObject(); } bool Project::readHealLocations() { @@ -2491,7 +2531,8 @@ bool Project::readHealLocations() { } fileWatcher.addPath(QString("%1/%2").arg(this->root).arg(filepath)); - QJsonArray healLocations = doc.object()["heal_locations"].toArray(); + QJsonObject healLocationsObj = doc.object(); + QJsonArray healLocations = healLocationsObj.take("heal_locations").toArray(); for (int i = 0; i < healLocations.size(); i++) { QJsonObject healLocationObj = healLocations.at(i).toObject(); static const QString mapField = QStringLiteral("map"); @@ -2505,6 +2546,7 @@ bool Project::readHealLocations() { this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); this->healLocationSaveOrder.append(event->getIdName()); } + this->customHealLocationsData = healLocationsObj; return true; } From e94fce0c8d78100492fc58c7ca11c9679aa49814 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 13:24:50 -0400 Subject: [PATCH 03/71] Combine the 3 QMaps for MAPSEC data --- include/core/regionmap.h | 6 +-- include/core/regionmapeditcommands.h | 4 +- include/project.h | 17 +++++-- include/ui/regionmapeditor.h | 2 +- src/core/regionmapeditcommands.cpp | 2 +- src/project.cpp | 73 ++++++++++++++++------------ src/ui/regionmapeditor.cpp | 4 +- 7 files changed, 63 insertions(+), 45 deletions(-) diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 18362663..c8afb1b2 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -56,8 +56,8 @@ public: bool loadLayout(poryjson::Json); bool loadEntries(); - void setEntries(QMap *entries) { this->region_map_entries = entries; } - void setEntries(const QMap &entries) { *(this->region_map_entries) = entries; } + void setEntries(QHash *entries) { this->region_map_entries = entries; } + void setEntries(const QHash &entries) { *(this->region_map_entries) = entries; } void clearEntries() { this->region_map_entries->clear(); } MapSectionEntry getEntry(QString section); void setEntry(QString section, MapSectionEntry entry); @@ -151,7 +151,7 @@ signals: void mapNeedsDisplaying(); private: - QMap *region_map_entries = nullptr; + QHash *region_map_entries = nullptr; QString alias = ""; diff --git a/include/core/regionmapeditcommands.h b/include/core/regionmapeditcommands.h index 05b12bc3..e47fdb7b 100644 --- a/include/core/regionmapeditcommands.h +++ b/include/core/regionmapeditcommands.h @@ -153,7 +153,7 @@ private: /// ClearEntries class ClearEntries : public QUndoCommand { public: - ClearEntries(RegionMap *map, QMap, QUndoCommand *parent = nullptr); + ClearEntries(RegionMap *map, QHash, QUndoCommand *parent = nullptr); void undo() override; void redo() override; @@ -163,7 +163,7 @@ public: private: RegionMap *map; - QMap entries; + QHash entries; }; #endif // REGIONMAPEDITCOMMANDS_H diff --git a/include/project.h b/include/project.h index 4ed95f06..44094aaa 100644 --- a/include/project.h +++ b/include/project.h @@ -62,7 +62,6 @@ public: QStringList mapSectionIdNames; QMap encounterTypeToName; QMap terrainTypeToName; - QMap regionMapEntries; QMap> metatileLabelsMap; QMap unusedMetatileLabels; QMap metatileBehaviorMap; @@ -157,7 +156,7 @@ public: bool addNewMapsec(const QString &idName, const QString &displayName = QString()); void removeMapsec(const QString &idName); - QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); } + QString getMapsecDisplayName(const QString &idName) const { return this->locationData.value(idName).displayName; } void setMapsecDisplayName(const QString &idName, const QString &displayName); bool hasUnsavedChanges(); @@ -240,6 +239,9 @@ public: static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + void setRegionMapEntries(const QHash &entries); + QHash getRegionMapEntries() const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); @@ -262,8 +264,6 @@ public: static QString getMapGroupPrefix(); private: - QHash mapSectionDisplayNames; - QHash mapSectionCustomData; QMap modifiedFileTimestamps; QMap facingDirections; QHash speciesToIconPath; @@ -298,6 +298,15 @@ private: }; QMap eventGraphicsMap; + // The extra data that can be associated with each MAPSEC name. + struct LocationData + { + MapSectionEntry map; + QString displayName; + QJsonObject custom; + }; + QHash locationData; + void updateLayout(Layout *); void setNewLayoutBlockdata(Layout *layout); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 9a838827..490324af 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -95,7 +95,7 @@ private: void saveConfig(); bool loadRegionMapEntries(); bool saveRegionMapEntries(); - QMap region_map_entries; + QHash region_map_entries; bool buildConfigDialog(); poryjson::Json configRegionMapDialog(); diff --git a/src/core/regionmapeditcommands.cpp b/src/core/regionmapeditcommands.cpp index 7a12cbc6..f21f1d72 100644 --- a/src/core/regionmapeditcommands.cpp +++ b/src/core/regionmapeditcommands.cpp @@ -260,7 +260,7 @@ void ResizeTilemap::undo() { /// -ClearEntries::ClearEntries(RegionMap *map, QMap entries, QUndoCommand *parent) +ClearEntries::ClearEntries(RegionMap *map, QHash entries, QUndoCommand *parent) : QUndoCommand(parent) { setText("Clear Entries"); diff --git a/src/project.cpp b/src/project.cpp index 98bd7215..891f709f 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -712,23 +712,23 @@ void Project::saveRegionMapSections() { OrderedJson::array mapSectionArray; for (const auto &idName : this->mapSectionIdNamesSaveOrder) { + const LocationData location = this->locationData.value(idName); + OrderedJson::object mapSectionObj; mapSectionObj["id"] = idName; - if (this->mapSectionDisplayNames.contains(idName)) { - mapSectionObj["name"] = this->mapSectionDisplayNames.value(idName); + if (!location.displayName.isEmpty()) { + mapSectionObj["name"] = location.displayName; } - if (this->regionMapEntries.contains(idName)) { - MapSectionEntry entry = this->regionMapEntries.value(idName); - mapSectionObj["x"] = entry.x; - mapSectionObj["y"] = entry.y; - mapSectionObj["width"] = entry.width; - mapSectionObj["height"] = entry.height; + if (location.map.valid) { + mapSectionObj["x"] = location.map.x; + mapSectionObj["y"] = location.map.y; + mapSectionObj["width"] = location.map.width; + mapSectionObj["height"] = location.map.height; } - QJsonObject customData = this->mapSectionCustomData.value(idName); - for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { + for (auto it = location.custom.constBegin(); it != location.custom.constEnd(); it++) { mapSectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); } @@ -2360,19 +2360,14 @@ bool Project::readFieldmapMasks() { } bool Project::readRegionMapSections() { + this->locationData.clear(); this->mapSectionIdNames.clear(); this->mapSectionIdNamesSaveOrder.clear(); - this->mapSectionDisplayNames.clear(); - this->regionMapEntries.clear(); + this->customMapSectionsData = QJsonObject(); + const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); - // The first of these is the custom data for each individual map section object, - // the second is the custom top-level data in the map sections file. - // TODO: Clarify this by relocating the various map section data maps to a single class? - this->mapSectionCustomData.clear(); - this->customMapSectionsData = QJsonObject(); - QJsonDocument doc; const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); QString error; @@ -2410,8 +2405,10 @@ bool Project::readRegionMapSections() { this->mapSectionIdNames.append(idName); this->mapSectionIdNamesSaveOrder.append(idName); - if (mapSectionObj.contains("name")) - this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj.take("name"))); + LocationData location; + if (mapSectionObj.contains("name")) { + location.displayName = ParseUtil::jsonToQString(mapSectionObj.take("name")); + } // Map sections may have additional data indicating their position on the region map. // If they have this data, we can add them to the region map entry list. @@ -2424,13 +2421,11 @@ bool Project::readRegionMapSections() { } } if (hasRegionMapData) { - MapSectionEntry entry; - entry.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); - entry.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); - entry.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); - entry.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); - entry.valid = true; - this->regionMapEntries[idName] = entry; + location.map.x = ParseUtil::jsonToInt(mapSectionObj.take("x")); + location.map.y = ParseUtil::jsonToInt(mapSectionObj.take("y")); + location.map.width = ParseUtil::jsonToInt(mapSectionObj.take("width")); + location.map.height = ParseUtil::jsonToInt(mapSectionObj.take("height")); + location.map.valid = true; } // Chuck the "name_clone" field, this is only for matching. @@ -2438,9 +2433,9 @@ bool Project::readRegionMapSections() { mapSectionObj.remove("name_clone"); // Preserve any remaining fields for when we save. - if (!mapSectionObj.isEmpty()) { - this->mapSectionCustomData[idName] = mapSectionObj; - } + location.custom = mapSectionObj; + + this->locationData.insert(idName, location); } this->customMapSectionsData = mapSectionsGlobalObj; @@ -2453,6 +2448,20 @@ bool Project::readRegionMapSections() { return true; } +void Project::setRegionMapEntries(const QHash &entries) { + for (auto it = entries.constBegin(); it != entries.constEnd(); it++) { + this->locationData[it.key()].map = it.value(); + } +} + +QHash Project::getRegionMapEntries() const { + QHash entries; + for (auto it = this->locationData.constBegin(); it != this->locationData.constEnd(); it++) { + entries[it.key()] = it.value().map; + } + return entries; +} + QString Project::getEmptyMapsecName() { return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); } @@ -2503,9 +2512,9 @@ void Project::removeMapsec(const QString &idName) { } void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { - if (this->mapSectionDisplayNames.value(idName) == displayName) + if (getMapsecDisplayName(idName) == displayName) return; - this->mapSectionDisplayNames[idName] = displayName; + this->locationData[idName].displayName = displayName; this->hasUnsavedDataChanges = true; emit mapSectionDisplayNameChanged(idName, displayName); } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 2febccd8..27fb5f2f 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -108,12 +108,12 @@ void RegionMapEditor::applyUserShortcuts() { } bool RegionMapEditor::loadRegionMapEntries() { - this->region_map_entries = this->project->regionMapEntries; + this->region_map_entries = this->project->getRegionMapEntries(); return true; } bool RegionMapEditor::saveRegionMapEntries() { - this->project->regionMapEntries = this->region_map_entries; + this->project->setRegionMapEntries(this->region_map_entries); this->project->saveRegionMapSections(); return true; } From 011f6196b56ede6207df724d70887f0dafe10ed5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 13:43:54 -0400 Subject: [PATCH 04/71] Add setting to keep data only needed for matching --- forms/projectsettingseditor.ui | 14 ++++++++++++-- include/config.h | 2 ++ src/config.cpp | 3 +++ src/project.cpp | 13 ++++++++----- src/ui/projectsettingseditor.cpp | 2 ++ 5 files changed, 27 insertions(+), 7 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index a088d87e..179392dd 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -39,7 +39,7 @@ 0 0 559 - 568 + 589 @@ -66,6 +66,16 @@ + + + + <html><head/><body><p>If enabled, Porymap will not discard data like &quot;connections_include_order&quot; or &quot;name_clone&quot;, which serve no purpose other than recreating the original game.</p></body></html> + + + Preserve data only needed to match the original game + + + @@ -1084,7 +1094,7 @@ 0 0 559 - 788 + 840 diff --git a/include/config.h b/include/config.h index eb9c9ac3..6746a0f4 100644 --- a/include/config.h +++ b/include/config.h @@ -323,6 +323,7 @@ public: this->tilesetsHaveCallback = true; this->tilesetsHaveIsCompressed = true; this->setTransparentPixelsBlack = true; + this->preserveMatchingOnlyData = false; this->filePaths.clear(); this->eventIconPaths.clear(); this->pokemonIconPaths.clear(); @@ -389,6 +390,7 @@ public: bool tilesetsHaveCallback; bool tilesetsHaveIsCompressed; bool setTransparentPixelsBlack; + bool preserveMatchingOnlyData; int metatileAttributesSize; uint32_t metatileBehaviorMask; uint32_t metatileTerrainTypeMask; diff --git a/src/config.cpp b/src/config.cpp index d74adbcb..d76f17a5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -809,6 +809,8 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { this->tilesetsHaveIsCompressed = getConfigBool(key, value); } else if (key == "set_transparent_pixels_black") { this->setTransparentPixelsBlack = getConfigBool(key, value); + } else if (key == "preserve_matching_only_data") { + this->preserveMatchingOnlyData = getConfigBool(key, value); } else if (key == "event_icon_path_object") { this->eventIconPaths[Event::Group::Object] = value; } else if (key == "event_icon_path_warp") { @@ -899,6 +901,7 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("tilesets_have_callback", QString::number(this->tilesetsHaveCallback)); map.insert("tilesets_have_is_compressed", QString::number(this->tilesetsHaveIsCompressed)); map.insert("set_transparent_pixels_black", QString::number(this->setTransparentPixelsBlack)); + map.insert("preserve_matching_only_data", QString::number(this->preserveMatchingOnlyData)); map.insert("metatile_attributes_size", QString::number(this->metatileAttributesSize)); map.insert("metatile_behavior_mask", Util::toHexString(this->metatileBehaviorMask)); map.insert("metatile_terrain_type_mask", Util::toHexString(this->metatileTerrainTypeMask)); diff --git a/src/project.cpp b/src/project.cpp index 891f709f..bef26614 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1925,10 +1925,12 @@ bool Project::readMapGroups() { this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); this->mapNames.append(dynamicMapName); - // Save custom JSON data. // Chuck the "connections_include_order" field, this is only for matching. - // TODO: Setting not to do this, on the off chance someone wants this field. - mapGroupsObj.remove("connections_include_order"); + if (!projectConfig.preserveMatchingOnlyData) { + mapGroupsObj.remove("connections_include_order"); + } + + // Preserve any remaining fields for when we save. this->customMapGroupsData = mapGroupsObj; return true; @@ -2429,8 +2431,9 @@ bool Project::readRegionMapSections() { } // Chuck the "name_clone" field, this is only for matching. - // TODO: Setting not to do this, on the off chance someone wants this field. - mapSectionObj.remove("name_clone"); + if (!projectConfig.preserveMatchingOnlyData) { + mapSectionObj.remove("name_clone"); + } // Preserve any remaining fields for when we save. location.custom = mapSectionObj; diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index fa84f3e0..90951dd7 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -443,6 +443,7 @@ void ProjectSettingsEditor::refresh() { ui->checkBox_OutputCallback->setChecked(projectConfig.tilesetsHaveCallback); ui->checkBox_OutputIsCompressed->setChecked(projectConfig.tilesetsHaveIsCompressed); ui->checkBox_DisableWarning->setChecked(porymapConfig.warpBehaviorWarningDisabled); + ui->checkBox_PreserveMatchingOnlyData->setChecked(projectConfig.preserveMatchingOnlyData); // Radio buttons if (projectConfig.setTransparentPixelsBlack) @@ -524,6 +525,7 @@ void ProjectSettingsEditor::save() { projectConfig.tilesetsHaveIsCompressed = ui->checkBox_OutputIsCompressed->isChecked(); porymapConfig.warpBehaviorWarningDisabled = ui->checkBox_DisableWarning->isChecked(); projectConfig.setTransparentPixelsBlack = ui->radioButton_RenderBlack->isChecked(); + projectConfig.preserveMatchingOnlyData = ui->checkBox_PreserveMatchingOnlyData->isChecked(); // Save spin box settings projectConfig.defaultElevation = ui->spinBox_Elevation->value(); From 029e959bfefd3fafe5b9364052ea84add7e6055f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 3 Apr 2025 13:53:33 -0400 Subject: [PATCH 05/71] Simplify fromQJsonValue loops --- include/core/events.h | 42 ++-- include/core/map.h | 6 +- include/lib/orderedjson.h | 4 +- include/project.h | 2 +- include/ui/customattributestable.h | 4 +- src/core/events.cpp | 368 +++++++++++++---------------- src/lib/orderedjson.cpp | 35 +-- src/project.cpp | 85 +++---- src/ui/customattributestable.cpp | 8 +- 9 files changed, 250 insertions(+), 304 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index fc0b90d8..22b76434 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -10,6 +10,7 @@ #include #include "orderedjson.h" +#include "parseutil.h" class Project; @@ -139,15 +140,14 @@ public: Event::Type getEventType() const { return this->eventType; } virtual OrderedJson::object buildEventJson(Project *project) = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) = 0; virtual void setDefaultValues(Project *project); virtual QSet getExpectedFields() = 0; - void readCustomAttributes(const QJsonObject &json); - void addCustomAttributesTo(OrderedJson::object *obj) const; - const QMap getCustomAttributes() const { return this->customAttributes; } - void setCustomAttributes(const QMap newCustomAttributes) { this->customAttributes = newCustomAttributes; } + + QJsonObject getCustomAttributes() const { return this->customAttributes; } + void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } virtual void loadPixmap(Project *project); @@ -190,12 +190,16 @@ protected: // When deleting events like this we want to warn the user that the #define may also be deleted. QString idName; - QMap customAttributes; + QJsonObject customAttributes; QPixmap pixmap; DraggablePixmapItem *pixmapItem = nullptr; QPointer eventFrame; + + static QString readString(QJsonObject *object, const QString &key) { return ParseUtil::jsonToQString(object->take(key)); } + static int readInt(QJsonObject *object, const QString &key) { return ParseUtil::jsonToInt(object->take(key)); } + static bool readBool(QJsonObject *object, const QString &key) { return ParseUtil::jsonToBool(object->take(key)); } }; @@ -218,7 +222,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -285,7 +289,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -323,7 +327,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -358,7 +362,7 @@ public: virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -386,7 +390,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -426,7 +430,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -457,7 +461,7 @@ public: virtual EventFrame *createEventFrame() override = 0; virtual OrderedJson::object buildEventJson(Project *project) override = 0; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override = 0; + virtual bool loadFromJson(QJsonObject json, Project *project) override = 0; virtual void setDefaultValues(Project *project) override = 0; @@ -484,7 +488,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -519,7 +523,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -564,7 +568,7 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &json, Project *project) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; @@ -596,12 +600,15 @@ public: virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; - virtual bool loadFromJson(const QJsonObject &, Project *) override; + virtual bool loadFromJson(QJsonObject json, Project *project) override; virtual void setDefaultValues(Project *project) override; virtual QSet getExpectedFields() override; + void setHostMapName(QString newHostMapName) { this->hostMapName = newHostMapName; } + QString getHostMapName() const; + void setRespawnMapName(QString newRespawnMapName) { this->respawnMapName = newRespawnMapName; } QString getRespawnMapName() const { return this->respawnMapName; } @@ -611,6 +618,7 @@ public: private: QString respawnMapName; QString respawnNPC; + QString hostMapName; // Only needed if the host map fails to load. }; diff --git a/include/core/map.h b/include/core/map.h index c1a14b04..c2078134 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -100,8 +100,8 @@ public: bool hasUnsavedChanges() const; void pruneEditHistory(); - void setCustomAttributes(const QMap &attributes) { m_customAttributes = attributes; } - QMap customAttributes() const { return m_customAttributes; } + void setCustomAttributes(const QJsonObject &attributes) { m_customAttributes = attributes; } + QJsonObject customAttributes() const { return m_customAttributes; } private: QString m_name; @@ -110,7 +110,7 @@ private: QString m_sharedScriptsMap = ""; QStringList m_scriptsFileLabels; - QMap m_customAttributes; + QJsonObject m_customAttributes; MapHeader *m_header = nullptr; Layout *m_layout = nullptr; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 544112f1..73422a2b 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -132,7 +132,9 @@ public: int>::type = 0> Json(const V & v) : Json(array(v.begin(), v.end())) {} - static const Json fromQJsonValue(QJsonValue value); + static Json fromQJsonValue(const QJsonValue &value); + static void append(Json::array *array, const QJsonArray &qArray); + static void append(Json::object *object, const QJsonObject &qObject); // This prevents Json(some_pointer) from accidentally producing a bool. Use // Json(bool(some_pointer)) if that behavior is desired. diff --git a/include/project.h b/include/project.h index 44094aaa..5f37c5e7 100644 --- a/include/project.h +++ b/include/project.h @@ -164,7 +164,7 @@ public: void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); - bool loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType = Event::Type::None); + bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); diff --git a/include/ui/customattributestable.h b/include/ui/customattributestable.h index 21cac4de..780f441d 100644 --- a/include/ui/customattributestable.h +++ b/include/ui/customattributestable.h @@ -13,8 +13,8 @@ public: explicit CustomAttributesTable(QWidget *parent = nullptr); ~CustomAttributesTable() {}; - QMap getAttributes() const; - void setAttributes(const QMap &attributes); + QJsonObject getAttributes() const; + void setAttributes(const QJsonObject &attributes); void addNewAttribute(const QString &key, const QJsonValue &value); bool deleteSelectedAttributes(); diff --git a/src/core/events.cpp b/src/core/events.cpp index f9828dc1..ebb1ac49 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -51,24 +51,6 @@ void Event::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); } -void Event::readCustomAttributes(const QJsonObject &json) { - this->customAttributes.clear(); - const QSet expectedFields = this->getExpectedFields(); - for (auto i = json.constBegin(); i != json.constEnd(); i++) { - if (!expectedFields.contains(i.key())) { - this->customAttributes[i.key()] = i.value(); - } - } -} - -void Event::addCustomAttributesTo(OrderedJson::object *obj) const { - for (auto i = this->customAttributes.constBegin(); i != this->customAttributes.constEnd(); i++) { - if (!obj->contains(i.key())) { - (*obj)[i.key()] = OrderedJson::fromQJsonValue(i.value()); - } - } -} - void Event::modify() { this->map->modify(); } @@ -181,27 +163,26 @@ OrderedJson::object ObjectEvent::buildEventJson(Project *) { objectJson["trainer_sight_or_berry_tree_id"] = this->getSightRadiusBerryTreeID(); objectJson["script"] = this->getScript(); objectJson["flag"] = this->getFlag(); - this->addCustomAttributesTo(&objectJson); + OrderedJson::append(&objectJson, this->getCustomAttributes()); return objectJson; } -bool ObjectEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setIdName(ParseUtil::jsonToQString(json["local_id"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setMovement(ParseUtil::jsonToQString(json["movement_type"])); - this->setRadiusX(ParseUtil::jsonToInt(json["movement_range_x"])); - this->setRadiusY(ParseUtil::jsonToInt(json["movement_range_y"])); - this->setTrainerType(ParseUtil::jsonToQString(json["trainer_type"])); - this->setSightRadiusBerryTreeID(ParseUtil::jsonToQString(json["trainer_sight_or_berry_tree_id"])); - this->setScript(ParseUtil::jsonToQString(json["script"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); +bool ObjectEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setMovement(readString(&json, "movement_type")); + this->setRadiusX(readInt(&json, "movement_range_x")); + this->setRadiusY(readInt(&json, "movement_range_y")); + this->setTrainerType(readString(&json, "trainer_type")); + this->setSightRadiusBerryTreeID(readString(&json, "trainer_sight_or_berry_tree_id")); + this->setScript(readString(&json, "script")); + this->setFlag(readString(&json, "flag")); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -216,26 +197,24 @@ void ObjectEvent::setDefaultValues(Project *project) { this->setSightRadiusBerryTreeID("0"); } -const QSet expectedObjectFields = { - "local_id", - "graphics_id", - "elevation", - "movement_type", - "movement_range_x", - "movement_range_y", - "trainer_type", - "trainer_sight_or_berry_tree_id", - "script", - "flag", -}; - QSet ObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedObjectFields; + QSet expectedFields = { + "x", + "y", + "local_id", + "graphics_id", + "elevation", + "movement_type", + "movement_range_x", + "movement_range_y", + "trainer_type", + "trainer_sight_or_berry_tree_id", + "script", + "flag", + }; if (projectConfig.eventCloneObjectEnabled) { expectedFields.insert("type"); } - expectedFields << "x" << "y"; return expectedFields; } @@ -286,26 +265,25 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["target_local_id"] = this->getTargetID(); const QString mapName = this->getTargetMap(); cloneJson["target_map"] = project->getMapConstant(mapName, mapName); - this->addCustomAttributesTo(&cloneJson); + OrderedJson::append(&cloneJson, this->getCustomAttributes()); return cloneJson; } -bool CloneObjectEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setIdName(ParseUtil::jsonToQString(json["local_id"])); - this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); - this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); +bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "local_id")); + this->setGfx(readString(&json, "graphics_id")); + this->setTargetID(readInt(&json, "target_local_id")); // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); + const QString mapConstant = readString(&json, "target_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -315,18 +293,16 @@ void CloneObjectEvent::setDefaultValues(Project *project) { if (this->getMap()) this->setTargetMap(this->getMap()->name()); } -const QSet expectedCloneObjectFields = { - "type", - "local_id", - "graphics_id", - "target_local_id", - "target_map", -}; - QSet CloneObjectEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedCloneObjectFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "local_id", + "graphics_id", + "target_local_id", + "target_map", + }; return expectedFields; } @@ -383,25 +359,23 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["dest_map"] = project->getMapConstant(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); - this->addCustomAttributesTo(&warpJson); - + OrderedJson::append(&warpJson, this->getCustomAttributes()); return warpJson; } -bool WarpEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"])); +bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setDestinationWarpID(readString(&json, "dest_warp_id")); // Log a warning if "dest_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); + const QString mapConstant = readString(&json, "dest_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -411,16 +385,14 @@ void WarpEvent::setDefaultValues(Project *) { this->setElevation(0); } -const QSet expectedWarpFields = { - "elevation", - "dest_map", - "dest_warp_id", -}; - QSet WarpEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWarpFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "elevation", + "dest_map", + "dest_warp_id", + }; return expectedFields; } @@ -466,21 +438,19 @@ OrderedJson::object TriggerEvent::buildEventJson(Project *) { triggerJson["var_value"] = this->getScriptVarValue(); triggerJson["script"] = this->getScriptLabel(); - this->addCustomAttributesTo(&triggerJson); - + OrderedJson::append(&triggerJson, this->getCustomAttributes()); return triggerJson; } -bool TriggerEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setScriptVar(ParseUtil::jsonToQString(json["var"])); - this->setScriptVarValue(ParseUtil::jsonToQString(json["var_value"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomAttributes(json); +bool TriggerEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setScriptVar(readString(&json, "var")); + this->setScriptVarValue(readString(&json, "var_value")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -491,18 +461,16 @@ void TriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedTriggerFields = { - "type", - "elevation", - "var", - "var_value", - "script", -}; - QSet TriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "var", + "var_value", + "script", + }; return expectedFields; } @@ -538,19 +506,17 @@ OrderedJson::object WeatherTriggerEvent::buildEventJson(Project *) { weatherJson["elevation"] = this->getElevation(); weatherJson["weather"] = this->getWeather(); - this->addCustomAttributesTo(&weatherJson); - + OrderedJson::append(&weatherJson, this->getCustomAttributes()); return weatherJson; } -bool WeatherTriggerEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setWeather(ParseUtil::jsonToQString(json["weather"])); - - this->readCustomAttributes(json); +bool WeatherTriggerEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setWeather(readString(&json, "weather")); + this->setCustomAttributes(json); return true; } @@ -559,16 +525,14 @@ void WeatherTriggerEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedWeatherTriggerFields = { - "type", - "elevation", - "weather", -}; - QSet WeatherTriggerEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedWeatherTriggerFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "weather", + }; return expectedFields; } @@ -606,20 +570,18 @@ OrderedJson::object SignEvent::buildEventJson(Project *) { signJson["player_facing_dir"] = this->getFacingDirection(); signJson["script"] = this->getScriptLabel(); - this->addCustomAttributesTo(&signJson); - + OrderedJson::append(&signJson, this->getCustomAttributes()); return signJson; } -bool SignEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setFacingDirection(ParseUtil::jsonToQString(json["player_facing_dir"])); - this->setScriptLabel(ParseUtil::jsonToQString(json["script"])); - - this->readCustomAttributes(json); +bool SignEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setFacingDirection(readString(&json, "player_facing_dir")); + this->setScriptLabel(readString(&json, "script")); + this->setCustomAttributes(json); return true; } @@ -629,17 +591,15 @@ void SignEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSignFields = { - "type", - "elevation", - "player_facing_dir", - "script", -}; - QSet SignEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSignFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "player_facing_dir", + "script", + }; return expectedFields; } @@ -685,26 +645,24 @@ OrderedJson::object HiddenItemEvent::buildEventJson(Project *) { hiddenItemJson["underfoot"] = this->getUnderfoot(); } - this->addCustomAttributesTo(&hiddenItemJson); - + OrderedJson::append(&hiddenItemJson, this->getCustomAttributes()); return hiddenItemJson; } -bool HiddenItemEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setItem(ParseUtil::jsonToQString(json["item"])); - this->setFlag(ParseUtil::jsonToQString(json["flag"])); +bool HiddenItemEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setItem(readString(&json, "item")); + this->setFlag(readString(&json, "flag")); if (projectConfig.hiddenItemQuantityEnabled) { - this->setQuantity(ParseUtil::jsonToInt(json["quantity"])); + this->setQuantity(readInt(&json, "quantity")); } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { - this->setUnderfoot(ParseUtil::jsonToBool(json["underfoot"])); + this->setUnderfoot(readBool(&json, "underfoot")); } - this->readCustomAttributes(json); - + this->setCustomAttributes(json); return true; } @@ -719,23 +677,21 @@ void HiddenItemEvent::setDefaultValues(Project *project) { } } -const QSet expectedHiddenItemFields = { - "type", - "elevation", - "item", - "flag", -}; - QSet HiddenItemEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedHiddenItemFields; + QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "item", + "flag", + }; if (projectConfig.hiddenItemQuantityEnabled) { expectedFields << "quantity"; } if (projectConfig.hiddenItemRequiresItemfinderEnabled) { expectedFields << "underfoot"; } - expectedFields << "x" << "y"; return expectedFields; } @@ -771,19 +727,17 @@ OrderedJson::object SecretBaseEvent::buildEventJson(Project *) { secretBaseJson["elevation"] = this->getElevation(); secretBaseJson["secret_base_id"] = this->getBaseID(); - this->addCustomAttributesTo(&secretBaseJson); - + OrderedJson::append(&secretBaseJson, this->getCustomAttributes()); return secretBaseJson; } -bool SecretBaseEvent::loadFromJson(const QJsonObject &json, Project *) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setElevation(ParseUtil::jsonToInt(json["elevation"])); - this->setBaseID(ParseUtil::jsonToQString(json["secret_base_id"])); - - this->readCustomAttributes(json); +bool SecretBaseEvent::loadFromJson(QJsonObject json, Project *) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setElevation(readInt(&json, "elevation")); + this->setBaseID(readString(&json, "secret_base_id")); + this->setCustomAttributes(json); return true; } @@ -792,16 +746,14 @@ void SecretBaseEvent::setDefaultValues(Project *project) { this->setElevation(0); } -const QSet expectedSecretBaseFields = { - "type", - "elevation", - "secret_base_id", -}; - QSet SecretBaseEvent::getExpectedFields() { - QSet expectedFields = QSet(); - expectedFields = expectedSecretBaseFields; - expectedFields << "x" << "y"; + static const QSet expectedFields = { + "x", + "y", + "type", + "elevation", + "secret_base_id", + }; return expectedFields; } @@ -829,12 +781,15 @@ EventFrame *HealLocationEvent::createEventFrame() { return this->eventFrame; } +QString HealLocationEvent::getHostMapName() const { + return this->getMap() ? this->getMap()->constantName() : this->hostMapName; +} + OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { OrderedJson::object healLocationJson; healLocationJson["id"] = this->getIdName(); - // This field doesn't need to be stored in the Event itself, so it's output only. - healLocationJson["map"] = this->getMap() ? this->getMap()->constantName() : QString(); + healLocationJson["map"] = this->getHostMapName(); healLocationJson["x"] = this->getX(); healLocationJson["y"] = this->getY(); if (projectConfig.healLocationRespawnDataEnabled) { @@ -843,26 +798,26 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *project) { healLocationJson["respawn_npc"] = this->getRespawnNPC(); } - this->addCustomAttributesTo(&healLocationJson); - + OrderedJson::append(&healLocationJson, this->getCustomAttributes()); return healLocationJson; } -bool HealLocationEvent::loadFromJson(const QJsonObject &json, Project *project) { - this->setX(ParseUtil::jsonToInt(json["x"])); - this->setY(ParseUtil::jsonToInt(json["y"])); - this->setIdName(ParseUtil::jsonToQString(json["id"])); +bool HealLocationEvent::loadFromJson(QJsonObject json, Project *project) { + this->setX(readInt(&json, "x")); + this->setY(readInt(&json, "y")); + this->setIdName(readString(&json, "id")); + this->setHostMapName(readString(&json, "map")); if (projectConfig.healLocationRespawnDataEnabled) { // Log a warning if "respawn_map" isn't a known map ID, but don't overwrite user data. - const QString mapConstant = ParseUtil::jsonToQString(json["respawn_map"]); + const QString mapConstant = readString(&json, "respawn_map"); if (!project->mapConstantsToMapNames.contains(mapConstant)) logWarn(QString("Unknown Respawn Map constant '%1'.").arg(mapConstant)); this->setRespawnMapName(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); - this->setRespawnNPC(ParseUtil::jsonToQString(json["respawn_npc"])); + this->setRespawnNPC(readString(&json, "respawn_npc")); } - this->readCustomAttributes(json); + this->setCustomAttributes(json); return true; } @@ -875,16 +830,19 @@ void HealLocationEvent::setDefaultValues(Project *project) { } const QSet expectedHealLocationFields = { - "id", - "map" + }; QSet HealLocationEvent::getExpectedFields() { - QSet expectedFields = expectedHealLocationFields; + QSet expectedFields = { + "x", + "y", + "id", + "map", + }; if (projectConfig.healLocationRespawnDataEnabled) { expectedFields.insert("respawn_map"); expectedFields.insert("respawn_npc"); } - expectedFields << "x" << "y"; return expectedFields; } diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index 24bb264a..c501a596 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -311,32 +311,37 @@ const Json & JsonArray::operator[] (int i) const { else return m_value[i]; } -const Json Json::fromQJsonValue(QJsonValue value) { +Json Json::fromQJsonValue(const QJsonValue &value) { switch (value.type()) { case QJsonValue::String: return value.toString(); case QJsonValue::Double: return value.toInt(); case QJsonValue::Bool: return value.toBool(); - case QJsonValue::Array: - { - QJsonArray qArr = value.toArray(); - Json::array arr; - for (const auto &i: qArr) - arr.push_back(Json::fromQJsonValue(i)); - return arr; + case QJsonValue::Array: { + Json::array array; + Json::append(&array, value.toArray()); + return array; } - case QJsonValue::Object: - { - QJsonObject qObj = value.toObject(); - Json::object obj; - for (auto it = qObj.constBegin(); it != qObj.constEnd(); it++) - obj[it.key()] = Json::fromQJsonValue(it.value()); - return obj; + case QJsonValue::Object: { + Json::object object; + Json::append(&object, value.toObject()); + return object; } default: return static_null(); } } +void Json::append(Json::array *array, const QJsonArray &qArray) { + for (const auto &i: qArray) { + array->push_back(fromQJsonValue(i)); + } +} + +void Json::append(Json::object *object, const QJsonObject &qObject) { + for (auto it = qObject.constBegin(); it != qObject.constEnd(); it++) { + (*object)[it.key()] = fromQJsonValue(it.value()); + } +} /* * * * * * * * * * * * * * * * * * * * * Comparison diff --git a/src/project.cpp b/src/project.cpp index bef26614..a942d6fc 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -218,8 +218,8 @@ bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { return true; } -bool Project::loadMapEvent(Map *map, const QJsonObject &json, Event::Type defaultType) { - QString typeString = ParseUtil::jsonToQString(json["type"]); +bool Project::loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType) { + QString typeString = ParseUtil::jsonToQString(json.take("type")); Event::Type type = typeString.isEmpty() ? defaultType : Event::typeFromJsonKey(typeString); Event* event = Event::create(type); if (!event) { @@ -245,10 +245,10 @@ bool Project::loadMapData(Map* map) { QJsonObject mapObj = mapDoc.object(); // We should already know the map constant ID from the initial project launch, but we'll ensure it's correct here anyway. - map->setConstantName(ParseUtil::jsonToQString(mapObj["id"])); + map->setConstantName(ParseUtil::jsonToQString(mapObj.take("id"))); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + const QString layoutId = ParseUtil::jsonToQString(mapObj.take("layout")); Layout* layout = this->mapLayouts.value(layoutId); if (!layout) { // We've already verified layout IDs on project launch and ignored maps with invalid IDs, so this shouldn't happen. @@ -257,24 +257,24 @@ bool Project::loadMapData(Map* map) { } map->setLayout(layout); - map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); - map->header()->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); - map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); - map->header()->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); - map->header()->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); - map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); + map->header()->setSong(ParseUtil::jsonToQString(mapObj.take("music"))); + map->header()->setLocation(ParseUtil::jsonToQString(mapObj.take("region_map_section"))); + map->header()->setRequiresFlash(ParseUtil::jsonToBool(mapObj.take("requires_flash"))); + map->header()->setWeather(ParseUtil::jsonToQString(mapObj.take("weather"))); + map->header()->setType(ParseUtil::jsonToQString(mapObj.take("map_type"))); + map->header()->setShowsLocationName(ParseUtil::jsonToBool(mapObj.take("show_map_name"))); + map->header()->setBattleScene(ParseUtil::jsonToQString(mapObj.take("battle_scene"))); if (projectConfig.mapAllowFlagsEnabled) { - map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); - map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); - map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); + map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj.take("allow_cycling"))); + map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj.take("allow_escaping"))); + map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj.take("allow_running"))); } if (projectConfig.floorNumberEnabled) { - map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); + map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj.take("floor_number"))); } - map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); - map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); + map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj.take("shared_events_map"))); + map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj.take("shared_scripts_map"))); // Events map->resetEvents(); @@ -289,7 +289,7 @@ bool Project::loadMapData(Map* map) { for (auto i = defaultEventTypes.constBegin(); i != defaultEventTypes.constEnd(); i++) { QString eventGroupKey = i.key(); Event::Type defaultType = i.value(); - const QJsonArray eventsJsonArr = mapObj[eventGroupKey].toArray(); + const QJsonArray eventsJsonArr = mapObj.take(eventGroupKey).toArray(); for (int i = 0; i < eventsJsonArr.size(); i++) { if (!loadMapEvent(map, eventsJsonArr.at(i).toObject(), defaultType)) { logError(QString("Failed to load event for %1, in %2 at index %3.").arg(map->name()).arg(eventGroupKey).arg(i)); @@ -304,7 +304,7 @@ bool Project::loadMapData(Map* map) { } map->deleteConnections(); - QJsonArray connectionsArr = mapObj["connections"].toArray(); + QJsonArray connectionsArr = mapObj.take("connections").toArray(); if (!connectionsArr.isEmpty()) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); @@ -316,14 +316,7 @@ bool Project::loadMapData(Map* map) { map->loadConnection(connection); } } - - QMap customAttributes; - for (auto i = mapObj.constBegin(); i != mapObj.constEnd(); i++) { - if (!this->topLevelMapFields.contains(i.key())) { - customAttributes.insert(i.key(), i.value()); - } - } - map->setCustomAttributes(customAttributes); + map->setCustomAttributes(mapObj); return true; } @@ -621,16 +614,11 @@ void Project::saveMapLayouts() { layoutObj["secondary_tileset"] = layout->tileset_secondary_label; layoutObj["border_filepath"] = layout->border_path; layoutObj["blockdata_filepath"] = layout->blockdata_path; - for (auto it = layout->customData.constBegin(); it != layout->customData.constEnd(); it++) { - layoutObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&layoutObj, layout->customData); layoutsArr.push_back(layoutObj); } layoutsObj["layouts"] = layoutsArr; - - for (auto it = this->customLayoutsData.constBegin(); it != this->customLayoutsData.constEnd(); it++) { - layoutsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&layoutsObj, this->customLayoutsData); ignoreWatchedFileTemporarily(layoutsFilepath); @@ -690,9 +678,7 @@ void Project::saveMapGroups() { } mapGroupsObj[groupName] = groupArr; } - for (auto it = this->customMapGroupsData.constBegin(); it != this->customMapGroupsData.constEnd(); it++) { - mapGroupsObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&mapGroupsObj, this->customMapGroupsData); ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -727,19 +713,14 @@ void Project::saveRegionMapSections() { mapSectionObj["width"] = location.map.width; mapSectionObj["height"] = location.map.height; } - - for (auto it = location.custom.constBegin(); it != location.custom.constEnd(); it++) { - mapSectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&mapSectionObj, location.custom); mapSectionArray.append(mapSectionObj); } OrderedJson::object object; object["map_sections"] = mapSectionArray; - for (auto it = this->customMapSectionsData.constBegin(); it != this->customMapSectionsData.constEnd(); it++) { - object[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&object, this->customMapSectionsData); ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -894,9 +875,7 @@ void Project::saveHealLocations() { OrderedJson::object object; object["heal_locations"] = eventJsonArr; - for (auto it = this->customHealLocationsData.constBegin(); it != this->customHealLocationsData.constEnd(); it++) { - object[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&object, this->customHealLocationsData); ignoreWatchedFileTemporarily(filepath); OrderedJson json(object); @@ -1230,10 +1209,7 @@ void Project::saveMap(Map *map, bool skipLayout) { connectionObj["map"] = getMapConstant(connection->targetMapName(), connection->targetMapName()); connectionObj["offset"] = connection->offset(); connectionObj["direction"] = connection->direction(); - auto customData = connection->customData(); - for (auto it = customData.constBegin(); it != customData.constEnd(); it++) { - connectionObj[it.key()] = OrderedJson::fromQJsonValue(it.value()); - } + OrderedJson::append(&connectionObj, connection->customData()); connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; @@ -1289,10 +1265,7 @@ void Project::saveMap(Map *map, bool skipLayout) { this->healLocations[map->constantName()] = hlEvents; // Custom header fields. - const auto customAttributes = map->customAttributes(); - for (auto i = customAttributes.constBegin(); i != customAttributes.constEnd(); i++) { - mapObj[i.key()] = OrderedJson::fromQJsonValue(i.value()); - } + OrderedJson::append(&mapObj, map->customAttributes()); OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); @@ -2555,7 +2528,7 @@ bool Project::readHealLocations() { auto event = new HealLocationEvent(); event->loadFromJson(healLocationObj, this); - this->healLocations[ParseUtil::jsonToQString(healLocationObj["map"])].append(event); + this->healLocations[event->getHostMapName()].append(event); this->healLocationSaveOrder.append(event->getIdName()); } this->customHealLocationsData = healLocationsObj; diff --git a/src/ui/customattributestable.cpp b/src/ui/customattributestable.cpp index 65381443..28153b4d 100644 --- a/src/ui/customattributestable.cpp +++ b/src/ui/customattributestable.cpp @@ -39,8 +39,8 @@ CustomAttributesTable::CustomAttributesTable(QWidget *parent) : }); } -QMap CustomAttributesTable::getAttributes() const { - QMap fields; +QJsonObject CustomAttributesTable::getAttributes() const { + QJsonObject fields; for (int row = 0; row < this->rowCount(); row++) { auto keyValuePair = this->getAttribute(row); if (!keyValuePair.first.isEmpty()) @@ -145,10 +145,10 @@ void CustomAttributesTable::addNewAttribute(const QString &key, const QJsonValue } // For programmatically populating the table -void CustomAttributesTable::setAttributes(const QMap &attributes) { +void CustomAttributesTable::setAttributes(const QJsonObject &attributes) { m_keys.clear(); this->setRowCount(0); // Clear old values - for (auto it = attributes.cbegin(); it != attributes.cend(); it++) + for (auto it = attributes.constBegin(); it != attributes.constEnd(); it++) this->addAttribute(it.key(), it.value()); this->resizeVertically(); } From f3a28848b9dcd226688b01805deb5a8d45e3ac08 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 11:54:39 -0400 Subject: [PATCH 06/71] Preserve custom fields in wild_encounters.json --- include/core/parseutil.h | 2 +- include/core/wildmoninfo.h | 10 +++-- include/lib/orderedjson.h | 19 +++++++-- include/lib/orderedmap.h | 11 ++++++ include/project.h | 6 ++- include/ui/regionmapeditor.h | 2 +- src/core/parseutil.cpp | 4 +- src/editor.cpp | 4 +- src/lib/orderedjson.cpp | 12 ------ src/project.cpp | 76 ++++++++++++++++++++++-------------- 10 files changed, 90 insertions(+), 56 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 4c19a27c..e02d0504 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -58,7 +58,7 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); - tsl::ordered_map> readCStructs(const QString &, const QString & = "", const QHash& = {}); + OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); bool tryParseJsonFile(QJsonDocument *out, const QString &filepath, QString *error = nullptr); diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index 3c94fb17..a3665b7d 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -3,7 +3,7 @@ #define GUARD_WILDMONINFO_H #include -#include "orderedmap.h" +#include "orderedjson.h" class WildPokemon { public: @@ -13,22 +13,26 @@ public: int minLevel; int maxLevel; QString species; + OrderedJson::object customData; }; struct WildMonInfo { bool active = false; int encounterRate = 0; QVector wildPokemon; + OrderedJson::object customData; }; struct WildPokemonHeader { - tsl::ordered_map wildMons; + OrderedMap wildMons; + OrderedJson::object customData; }; struct EncounterField { QString name; // Ex: "fishing_mons" QVector encounterRates; - tsl::ordered_map> groups; // Ex: "good_rod", {2, 3, 4} + OrderedMap> groups; // Ex: "good_rod", {2, 3, 4} + OrderedJson::object customData; }; typedef QVector EncounterFields; diff --git a/include/lib/orderedjson.h b/include/lib/orderedjson.h index 73422a2b..cb5139d3 100644 --- a/include/lib/orderedjson.h +++ b/include/lib/orderedjson.h @@ -99,7 +99,7 @@ public: // Array and object typedefs typedef QVector array; - typedef tsl::ordered_map object; + typedef OrderedMap object; // Constructors for the various types of JSON value. Json() noexcept; // NUL @@ -133,8 +133,21 @@ public: Json(const V & v) : Json(array(v.begin(), v.end())) {} static Json fromQJsonValue(const QJsonValue &value); - static void append(Json::array *array, const QJsonArray &qArray); - static void append(Json::object *object, const QJsonObject &qObject); + + static void append(Json::array *array, const QJsonArray &addendum) { + for (const auto &i : addendum) array->push_back(fromQJsonValue(i)); + } + static void append(Json::array *array, const Json::array &addendum) { + for (const auto &i : addendum) array->push_back(i); + } + static void append(Json::object *object, const QJsonObject &addendum) { + for (auto it = addendum.constBegin(); it != addendum.constEnd(); it++) + (*object)[it.key()] = fromQJsonValue(it.value()); + } + static void append(Json::object *object, const Json::object &addendum) { + for (auto it = addendum.cbegin(); it != addendum.cend(); it++) + (*object)[it.key()] = it.value(); + } // This prevents Json(some_pointer) from accidentally producing a bool. Use // Json(bool(some_pointer)) if that behavior is desired. diff --git a/include/lib/orderedmap.h b/include/lib/orderedmap.h index 40882d9f..33fcfc50 100644 --- a/include/lib/orderedmap.h +++ b/include/lib/orderedmap.h @@ -1977,6 +1977,14 @@ public: size_type erase(const K& key, std::size_t precalculated_hash) { return m_ht.erase(key, precalculated_hash); } + + // Naive solution for take, should probably be replaced with one that does a single lookup and no unnecessary insertion. + // We want to mirror the behavior of QMap::take, which returns a default-constructed value if the key is not present. + T take(const key_type& key) { + typename ValueSelect::value_type value = m_ht[key]; + m_ht.erase(key); + return value; + } @@ -2404,4 +2412,7 @@ private: } // end namespace tsl +template +using OrderedMap = tsl::ordered_map; + #endif diff --git a/include/project.h b/include/project.h index 5f37c5e7..bcc53ae7 100644 --- a/include/project.h +++ b/include/project.h @@ -143,12 +143,11 @@ public: QString getNewHealLocationName(const Map* map) const; bool readWildMonData(); - tsl::ordered_map> wildMonData; + OrderedMap> wildMonData; QString wildMonTableName; QVector wildMonFields; QVector encounterGroupLabels; - QVector extraEncounterGroups; bool readSpeciesIconPaths(); QString getDefaultSpeciesIconPath(const QString &species); @@ -274,6 +273,9 @@ private: QJsonObject customMapSectionsData; QJsonObject customMapGroupsData; QJsonObject customHealLocationsData; + OrderedJson::object customWildMonData; + OrderedJson::object customWildMonGroupData; + OrderedJson::array extraEncounterGroups; // Maps/layouts represented in these sets have been fully loaded from the project. // If a valid map name / layout id is not in these sets, a Map / Layout object exists diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 490324af..8ecf0a76 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -54,7 +54,7 @@ private: Project *project; RegionMap *region_map = nullptr; - tsl::ordered_map region_maps; + OrderedMap region_maps; QString configFilepath; diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index e172ea42..da2a8f8e 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -610,12 +610,12 @@ bool ParseUtil::gameStringToBool(const QString &gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } -tsl::ordered_map> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { +OrderedMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { QString filePath = pathWithRoot(filename); auto cParser = fex::Parser(); auto tokens = fex::Lexer().LexFile(filePath); auto topLevelObjects = cParser.ParseTopLevelObjects(tokens); - tsl::ordered_map> structs; + OrderedMap> structs; for (auto it = topLevelObjects.begin(); it != topLevelObjects.end(); it++) { QString structLabel = QString::fromStdString(it->first); if (structLabel.isEmpty()) continue; diff --git a/src/editor.cpp b/src/editor.cpp index b29a47bc..19acbe60 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -596,7 +596,7 @@ void Editor::configureEncounterJSON(QWidget *window) { if (newNameDialog.exec() == QDialog::Accepted) { QString newFieldName = newNameEdit->text(); QVector newFieldRates(1, 100); - tempFields.append({newFieldName, newFieldRates, {}}); + tempFields.append({newFieldName, newFieldRates, {}, {}}); fieldChoices->addItem(newFieldName); fieldChoices->setCurrentIndex(fieldChoices->count() - 1); } @@ -675,7 +675,7 @@ void Editor::saveEncounterTabData() { if (!stack->count()) return; - tsl::ordered_map &encounterMap = project->wildMonData[map->constantName()]; + OrderedMap &encounterMap = project->wildMonData[map->constantName()]; for (int groupIndex = 0; groupIndex < stack->count(); groupIndex++) { MonTabWidget *tabWidget = static_cast(stack->widget(groupIndex)); diff --git a/src/lib/orderedjson.cpp b/src/lib/orderedjson.cpp index c501a596..d6ba4dbd 100644 --- a/src/lib/orderedjson.cpp +++ b/src/lib/orderedjson.cpp @@ -331,18 +331,6 @@ Json Json::fromQJsonValue(const QJsonValue &value) { } } -void Json::append(Json::array *array, const QJsonArray &qArray) { - for (const auto &i: qArray) { - array->push_back(fromQJsonValue(i)); - } -} - -void Json::append(Json::object *object, const QJsonObject &qObject) { - for (auto it = qObject.constBegin(); it != qObject.constEnd(); it++) { - (*object)[it.key()] = fromQJsonValue(it.value()); - } -} - /* * * * * * * * * * * * * * * * * * * * * Comparison */ diff --git a/src/project.cpp b/src/project.cpp index a942d6fc..871b8e2c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -747,7 +747,7 @@ void Project::saveWildMonData() { monHeadersObject["for_maps"] = true; OrderedJson::array fieldsInfoArray; - for (EncounterField fieldInfo : wildMonFields) { + for (EncounterField fieldInfo : this->wildMonFields) { OrderedJson::object fieldObject; OrderedJson::array rateArray; @@ -770,48 +770,52 @@ void Project::saveWildMonData() { } if (!groupsObject.empty()) fieldObject["groups"] = groupsObject; + OrderedJson::append(&fieldObject, fieldInfo.customData); fieldsInfoArray.append(fieldObject); } monHeadersObject["fields"] = fieldsInfoArray; OrderedJson::array encountersArray; - for (auto keyPair : wildMonData) { + for (auto keyPair : this->wildMonData) { QString key = keyPair.first; - for (auto grouplLabelPair : wildMonData[key]) { + for (auto grouplLabelPair : this->wildMonData[key]) { QString groupLabel = grouplLabelPair.first; OrderedJson::object encounterObject; encounterObject["map"] = key; encounterObject["base_label"] = groupLabel; - WildPokemonHeader encounterHeader = wildMonData[key][groupLabel]; + WildPokemonHeader encounterHeader = this->wildMonData[key][groupLabel]; for (auto fieldNamePair : encounterHeader.wildMons) { QString fieldName = fieldNamePair.first; - OrderedJson::object fieldObject; + OrderedJson::object monInfoObject; WildMonInfo monInfo = encounterHeader.wildMons[fieldName]; - fieldObject["encounter_rate"] = monInfo.encounterRate; + monInfoObject["encounter_rate"] = monInfo.encounterRate; OrderedJson::array monArray; for (WildPokemon wildMon : monInfo.wildPokemon) { OrderedJson::object monEntry; monEntry["min_level"] = wildMon.minLevel; monEntry["max_level"] = wildMon.maxLevel; monEntry["species"] = wildMon.species; + OrderedJson::append(&monEntry, wildMon.customData); monArray.push_back(monEntry); } - fieldObject["mons"] = monArray; - encounterObject[fieldName] = fieldObject; + monInfoObject["mons"] = monArray; + OrderedJson::append(&monInfoObject, monInfo.customData); + + encounterObject[fieldName] = monInfoObject; + OrderedJson::append(&encounterObject, encounterHeader.customData); } encountersArray.push_back(encounterObject); } } monHeadersObject["encounters"] = encountersArray; - wildEncounterGroups.push_back(monHeadersObject); + OrderedJson::append(&monHeadersObject, this->customWildMonGroupData); - // add extra Json objects that are not associated with maps to the file - for (auto extraObject : extraEncounterGroups) { - wildEncounterGroups.push_back(extraObject); - } + wildEncounterGroups.push_back(monHeadersObject); + OrderedJson::append(&wildEncounterGroups, this->extraEncounterGroups); wildEncountersObject["wild_encounter_groups"] = wildEncounterGroups; + OrderedJson::append(&wildEncountersObject, this->customWildMonData); ignoreWatchedFileTemporarily(wildEncountersJsonFilepath); OrderedJson encounterJson(wildEncountersObject); @@ -1607,6 +1611,8 @@ bool Project::readWildMonData() { this->pokemonMaxLevel = 100; this->maxEncounterRate = 2880/16; this->wildEncountersLoaded = false; + this->customWildMonData = OrderedJson::object(); + this->customWildMonGroupData = OrderedJson::object(); if (!userConfig.useEncounterJson) { return true; } @@ -1652,7 +1658,8 @@ bool Project::readWildMonData() { QMap> encounterRateFrequencyMaps; // Parse "wild_encounter_groups". This is the main object array containing all the data in this file. - for (OrderedJson mainArrayJson : wildMonObj["wild_encounter_groups"].array_items()) { + OrderedJson::array mainArray = wildMonObj.take("wild_encounter_groups").array_items(); + for (const OrderedJson &mainArrayJson : mainArray) { OrderedJson::object mainArrayObject = mainArrayJson.object_items(); // We're only interested in wild encounter data that's associated with maps ("for_maps" == true). @@ -1661,10 +1668,14 @@ bool Project::readWildMonData() { if (!mainArrayObject["for_maps"].bool_value()) { this->extraEncounterGroups.push_back(mainArrayObject); continue; + } else { + // Note: We don't call 'take' above, we don't want to strip data from extraEncounterGroups. + // We do want to strip it from the main group, because it shouldn't be treated as custom data. + mainArrayObject.erase("for_maps"); } // If multiple "for_maps" data sets are found they will be collapsed into a single set. - QString label = mainArrayObject["label"].string_value(); + QString label = mainArrayObject.take("label").string_value(); if (this->wildMonTableName.isEmpty()) { this->wildMonTableName = label; } else { @@ -1677,24 +1688,25 @@ 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["fields"].array_items()) { + for (const OrderedJson &fieldJson : mainArrayObject.take("fields").array_items()) { OrderedJson::object fieldObject = fieldJson.object_items(); EncounterField encounterField; - encounterField.name = fieldObject["type"].string_value(); + encounterField.name = fieldObject.take("type").string_value(); - for (auto val : fieldObject["encounter_rates"].array_items()) { + for (auto val : fieldObject.take("encounter_rates").array_items()) { 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["groups"].object_items()) { + for (auto groupPair : fieldObject.take("groups").object_items()) { const QString groupName = groupPair.first; for (auto slotNum : groupPair.second.array_items()) { encounterField.groups[groupName].append(slotNum.int_value()); } } + encounterField.customData = fieldObject; encounterRateFrequencyMaps.insert(encounterField.name, QMap()); this->wildMonFields.append(encounterField); @@ -1704,7 +1716,7 @@ 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["encounters"].array_items()) { + for (const auto &encounterJson : mainArrayObject.take("encounters").array_items()) { OrderedJson::object encounterObj = encounterJson.object_items(); WildPokemonHeader header; @@ -1712,29 +1724,31 @@ bool Project::readWildMonData() { // Check for each possible encounter type. for (const EncounterField &monField : this->wildMonFields) { const QString field = monField.name; - if (encounterObj[field].is_null()) { + if (!encounterObj.contains(field)) { // Encounter type isn't present continue; } - OrderedJson::object encounterFieldObj = encounterObj[field].object_items(); + OrderedJson::object encounterFieldObj = encounterObj.take(field).object_items(); WildMonInfo monInfo; monInfo.active = true; // Read encounter rate - monInfo.encounterRate = encounterFieldObj["encounter_rate"].int_value(); + monInfo.encounterRate = encounterFieldObj.take("encounter_rate").int_value(); encounterRateFrequencyMaps[field][monInfo.encounterRate]++; // Read wild pokémon list - for (auto monJson : encounterFieldObj["mons"].array_items()) { + for (const auto &monJson : encounterFieldObj.take("mons").array_items()) { OrderedJson::object monObj = monJson.object_items(); WildPokemon newMon; - newMon.minLevel = monObj["min_level"].int_value(); - newMon.maxLevel = monObj["max_level"].int_value(); - newMon.species = monObj["species"].string_value(); + newMon.minLevel = monObj.take("min_level").int_value(); + newMon.maxLevel = monObj.take("max_level").int_value(); + newMon.species = monObj.take("species").string_value(); + newMon.customData = monObj; monInfo.wildPokemon.append(newMon); } + monInfo.customData = encounterFieldObj; // If the user supplied too few pokémon for this group then we fill in the rest with default values. for (int i = monInfo.wildPokemon.length(); i < monField.encounterRates.length(); i++) { @@ -1742,13 +1756,15 @@ bool Project::readWildMonData() { } header.wildMons[field] = monInfo; } - - const QString mapConstant = encounterObj["map"].string_value(); - const QString baseLabel = encounterObj["base_label"].string_value(); + const QString mapConstant = encounterObj.take("map").string_value(); + const QString baseLabel = encounterObj.take("base_label").string_value(); + header.customData = encounterObj; this->wildMonData[mapConstant].insert({baseLabel, header}); this->encounterGroupLabels.append(baseLabel); } + this->customWildMonGroupData = mainArrayObject; } + this->customWildMonData = wildMonObj; // For each encounter type, set default encounter rate to most common value. // Iterate over map of encounter type names to frequency maps... From db9e9d6f65077b230d14df7bfc779a7489956fde Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 13:09:14 -0400 Subject: [PATCH 07/71] Remove Project::topLevelMapFields --- include/project.h | 4 ++-- src/mainwindow.cpp | 2 +- src/project.cpp | 16 +++++++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/include/project.h b/include/project.h index bcc53ae7..c5aed0a3 100644 --- a/include/project.h +++ b/include/project.h @@ -71,7 +71,6 @@ public: QSet modifiedFiles; bool usingAsmTilesets; QSet disabledSettingsNames; - QSet topLevelMapFields; int pokemonMinLevel; int pokemonMaxLevel; int maxEncounterRate; @@ -161,7 +160,6 @@ public: bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; - void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); bool loadMapEvent(Map *map, QJsonObject json, Event::Type defaultType = Event::Type::None); bool loadMapData(Map*); @@ -241,6 +239,8 @@ public: void setRegionMapEntries(const QHash &entries); QHash getRegionMapEntries() const; + QSet getTopLevelMapFields() const; + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index af7eaabe..28d7f996 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1192,7 +1192,7 @@ bool MainWindow::setProjectUI() { ui->layoutList->setModel(layoutListProxyModel); ui->layoutList->sortByColumn(0, Qt::SortOrder::AscendingOrder); - ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->topLevelMapFields); + ui->mapCustomAttributesFrame->table()->setRestrictedKeys(project->getTopLevelMapFields()); return true; } diff --git a/src/project.cpp b/src/project.cpp index 871b8e2c..f5499730 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -177,8 +177,8 @@ Map* Project::loadMap(const QString &mapName) { return map; } -void Project::initTopLevelMapFields() { - static const QSet defaultTopLevelMapFields = { +QSet Project::getTopLevelMapFields() const { + QSet fields = { "id", "name", "layout", @@ -197,15 +197,15 @@ void Project::initTopLevelMapFields() { "shared_events_map", "shared_scripts_map", }; - this->topLevelMapFields = defaultTopLevelMapFields; if (projectConfig.mapAllowFlagsEnabled) { - this->topLevelMapFields.insert("allow_cycling"); - this->topLevelMapFields.insert("allow_escaping"); - this->topLevelMapFields.insert("allow_running"); + fields.insert("allow_cycling"); + fields.insert("allow_escaping"); + fields.insert("allow_running"); } if (projectConfig.floorNumberEnabled) { - this->topLevelMapFields.insert("floor_number"); + fields.insert("floor_number"); } + return fields; } bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { @@ -1794,8 +1794,6 @@ bool Project::readMapGroups() { this->groupNameToMapNames.clear(); this->customMapGroupsData = QJsonObject(); - this->initTopLevelMapFields(); - const QString filepath = projectConfig.getFilePath(ProjectFilePath::json_map_groups); fileWatcher.addPath(root + "/" + filepath); QJsonDocument mapGroupsDoc; From 5c9e84b4c0d0ee4f96d960d43f9730fe605f2fff Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 13:37:15 -0400 Subject: [PATCH 08/71] Add missing key take --- src/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index f5499730..6f225ff6 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1804,7 +1804,7 @@ bool Project::readMapGroups() { } QJsonObject mapGroupsObj = mapGroupsDoc.object(); - QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + QJsonArray mapGroupOrder = mapGroupsObj.take("group_order").toArray(); const QString dynamicMapName = getDynamicMapName(); const QString dynamicMapConstant = getDynamicMapDefineName(); From ecad60843c9245ce2ceb885d30d91763a9de3e37 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 10:41:57 -0400 Subject: [PATCH 09/71] Remove old DraggablePixmapItem signals/slots --- include/ui/draggablepixmapitem.h | 24 ++++-------------------- src/ui/draggablepixmapitem.cpp | 1 - 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/include/ui/draggablepixmapitem.h b/include/ui/draggablepixmapitem.h index aeda4daf..debf1e41 100644 --- a/include/ui/draggablepixmapitem.h +++ b/include/ui/draggablepixmapitem.h @@ -42,29 +42,13 @@ signals: void positionChanged(Event *event); void xChanged(int); void yChanged(int); - void elevationChanged(int); void spriteChanged(QPixmap pixmap); - void onPropertyChanged(QString key, QString value); - -public slots: - void set_x(int x) { - event->setX(x); - updatePosition(); - } - void set_y(int y) { - event->setY(y); - updatePosition(); - } - void set_elevation(int z) { - event->setElevation(z); - updatePosition(); - } protected: - void mousePressEvent(QGraphicsSceneMouseEvent*); - void mouseMoveEvent(QGraphicsSceneMouseEvent*); - void mouseReleaseEvent(QGraphicsSceneMouseEvent*); - void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*); + virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; }; #endif // DRAGGABLEPIXMAPITEM_H diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index f2fdeb91..31068fee 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -23,7 +23,6 @@ void DraggablePixmapItem::updatePosition() { void DraggablePixmapItem::emitPositionChanged() { emit xChanged(event->getX()); emit yChanged(event->getY()); - emit elevationChanged(event->getElevation()); } void DraggablePixmapItem::updatePixmap() { From 2d827f62f786f2de2b8ad7a4e08a41a23dc01fcc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 11:44:54 -0400 Subject: [PATCH 10/71] Support event lookup by ID name --- include/core/events.h | 6 ++-- include/core/map.h | 1 + include/editor.h | 2 +- include/mainwindow.h | 2 +- include/ui/draggablepixmapitem.h | 5 +-- src/core/events.cpp | 7 ++-- src/core/map.cpp | 15 +++++++++ src/editor.cpp | 1 + src/mainwindow.cpp | 57 ++++++++++++++++++++++++++------ src/project.cpp | 11 ++++-- src/ui/draggablepixmapitem.cpp | 28 ---------------- src/ui/eventframes.cpp | 5 ++- 12 files changed, 88 insertions(+), 52 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 22b76434..cff233cc 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -300,12 +300,12 @@ public: void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } QString getTargetMap() const { return this->targetMap; } - void setTargetID(int newTargetID) { this->targetID = newTargetID; } - int getTargetID() const { return this->targetID; } + void setTargetID(QString newTargetID) { this->targetID = newTargetID; } + QString getTargetID() const { return this->targetID; } private: QString targetMap; - int targetID = 0; + QString targetID; }; diff --git a/include/core/map.h b/include/core/map.h index c2078134..a3bb1bbf 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -76,6 +76,7 @@ public: void resetEvents(); QList getEvents(Event::Group group = Event::Group::None) const; Event* getEvent(Event::Group group, int index) const; + Event* getEvent(Event::Group group, const QString &idName) const; int getNumEvents(Event::Group group = Event::Group::None) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilePath() const; diff --git a/include/editor.h b/include/editor.h index 4c7bff02..b56f42d4 100644 --- a/include/editor.h +++ b/include/editor.h @@ -251,11 +251,11 @@ private slots: signals: void eventsChanged(); + void openEventMap(Event*); void openConnectedMap(MapConnection*); void wildMonTableOpened(EncounterTableModel*); void wildMonTableClosed(); void wildMonTableEdited(); - void warpEventDoubleClicked(QString, int, Event::Group); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); void tilesetUpdated(QString); diff --git a/include/mainwindow.h b/include/mainwindow.h index d8fe18a3..e3cdd62f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -177,7 +177,7 @@ private slots: void on_action_Save_Project_triggered(); void save(bool currentOnly = false); - void openWarpMap(QString map_name, int event_id, Event::Group event_group); + void openEventMap(Event *event); void duplicate(); void setClipboardData(poryjson::Json::object); diff --git a/include/ui/draggablepixmapitem.h b/include/ui/draggablepixmapitem.h index debf1e41..5c617099 100644 --- a/include/ui/draggablepixmapitem.h +++ b/include/ui/draggablepixmapitem.h @@ -42,13 +42,14 @@ signals: void positionChanged(Event *event); void xChanged(int); void yChanged(int); - void spriteChanged(QPixmap pixmap); + void spriteChanged(const QPixmap &pixmap); + void doubleClicked(Event *event); protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override; + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(this->event); } }; #endif // DRAGGABLEPIXMAPITEM_H diff --git a/src/core/events.cpp b/src/core/events.cpp index ebb1ac49..e132f277 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -275,7 +275,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { this->setY(readInt(&json, "y")); this->setIdName(readString(&json, "local_id")); this->setGfx(readString(&json, "graphics_id")); - this->setTargetID(readInt(&json, "target_local_id")); + this->setTargetID(readString(&json, "target_local_id")); // Log a warning if "target_map" isn't a known map ID, but don't overwrite user data. const QString mapConstant = readString(&json, "target_map"); @@ -289,7 +289,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { void CloneObjectEvent::setDefaultValues(Project *project) { this->setGfx(project->gfxDefines.key(0, "0")); - this->setTargetID(1); + this->setTargetID(QString::number(Event::getIndexOffset(Event::Group::Object))); if (this->getMap()) this->setTargetMap(this->getMap()->name()); } @@ -308,9 +308,8 @@ QSet CloneObjectEvent::getExpectedFields() { void CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone - int eventIndex = this->targetID - 1; Map *clonedMap = project->loadMap(this->targetMap); - Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr; + Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { // Get graphics data from cloned object diff --git a/src/core/map.cpp b/src/core/map.cpp index 330132a1..5e5d443f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -194,6 +194,21 @@ Event* Map::getEvent(Event::Group group, int index) const { return m_events[group].value(index, nullptr); } +Event* Map::getEvent(Event::Group group, const QString &idName) const { + bool idIsNumber; + int id = idName.toInt(&idIsNumber, 0); + if (idIsNumber) + return getEvent(group, id - Event::getIndexOffset(group)); + + auto events = getEvents(group); + for (const auto &event : events) { + if (event->getIdName() == idName) { + return event; + } + } + return nullptr; +} + int Map::getNumEvents(Event::Group group) const { if (group == Event::Group::None) { // Total number of events diff --git a/src/editor.cpp b/src/editor.cpp index 19acbe60..282ec52c 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1699,6 +1699,7 @@ void Editor::displayMapEvents() { DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); auto item = new DraggablePixmapItem(event, this); + connect(item, &DraggablePixmapItem::doubleClicked, this, &Editor::openEventMap); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 28d7f996..237ca5c4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -338,7 +338,7 @@ void MainWindow::initEditor() { this->editor = new Editor(ui); connect(this->editor, &Editor::eventsChanged, this, &MainWindow::updateEvents); connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); - connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap); + connect(this->editor, &Editor::openEventMap, this, &MainWindow::openEventMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::wildMonTableEdited, this, &MainWindow::markMapEdited); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); @@ -1055,19 +1055,56 @@ void MainWindow::refreshCollisionSelector() { on_horizontalSlider_CollisionZoom_valueChanged(ui->horizontalSlider_CollisionZoom->value()); } -void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) { - // Open the destination map. - if (!userSetMap(map_name)) +// Some events (like warps) have data that refers to an event on a different map. +// This function opens that map, and selects the event it's referring to. +void MainWindow::openEventMap(Event *sourceEvent) { + if (!sourceEvent || !this->editor->map) return; + + QString targetMapName; + QString targetEventIdName; + Event::Group targetEventGroup; + + Event::Type eventType = sourceEvent->getEventType(); + if (eventType == Event::Type::Warp) { + // Warp events open to their destination warp event. + WarpEvent *warp = dynamic_cast(sourceEvent); + targetMapName = warp->getDestinationMap(); + targetEventIdName = warp->getDestinationWarpID(); + targetEventGroup = Event::Group::Warp; + } else if (eventType == Event::Type::CloneObject) { + // Clone object events open to their target object event. + CloneObjectEvent *clone = dynamic_cast(sourceEvent); + targetMapName = clone->getTargetMap(); + targetEventIdName = clone->getTargetID(); + targetEventGroup = Event::Group::Object; + } else if (eventType == Event::Type::SecretBase) { + // Secret Bases open to their secret base entrance + const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + SecretBaseEvent *base = dynamic_cast(sourceEvent); + QString baseId = base->getBaseID(); + targetMapName = this->editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); + targetEventIdName = "0"; + targetEventGroup = Event::Group::Warp; + } else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { + // Heal location events open to their respawn NPC + HealLocationEvent *heal = dynamic_cast(sourceEvent); + targetMapName = heal->getRespawnMapName(); + targetEventIdName = heal->getRespawnNPC(); + targetEventGroup = Event::Group::Object; + } else { + // Other event types have no target map to open. + return; + } + if (!userSetMap(targetMapName)) return; - // Select the target event. - int index = event_id - Event::getIndexOffset(event_group); - Event* event = this->editor->map->getEvent(event_group, index); - if (event) { - this->editor->selectMapEvent(event); + // Map opened successfully, now try to select the targeted event on that map. + Event* targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName); + if (targetEvent) { + this->editor->selectMapEvent(targetEvent); } else { // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 %2 doesn't exist on map '%3'").arg(Event::groupToString(event_group)).arg(event_id).arg(map_name)); + logWarn(QString("%1 '%2' doesn't exist on map '%3'").arg(Event::groupToString(targetEventGroup)).arg(targetEventIdName).arg(targetMapName)); } } diff --git a/src/project.cpp b/src/project.cpp index 6f225ff6..e02db858 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,7 +447,12 @@ bool Project::loadLayout(Layout *layout) { Layout *Project::loadLayout(QString layoutId) { Layout *layout = this->mapLayouts.value(layoutId); - if (!layout || !loadLayout(layout)) { + if (!layout) { + logError(QString("Unknown layout ID '%1'.").arg(layoutId)); + return nullptr; + } + + if (!loadLayout(layout)) { logError(QString("Failed to load layout '%1'").arg(layoutId)); return nullptr; } diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 31068fee..53cdd90a 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -103,31 +103,3 @@ void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { this->editor->selectMapEvent(this->event); } } - -// Events with properties that specify a map will open that map when double-clicked. -void DraggablePixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { - Event::Type eventType = this->event->getEventType(); - if (eventType == Event::Type::Warp) { - WarpEvent *warp = dynamic_cast(this->event); - QString destMap = warp->getDestinationMap(); - int warpId = ParseUtil::gameStringToInt(warp->getDestinationWarpID()); - emit editor->warpEventDoubleClicked(destMap, warpId, Event::Group::Warp); - } - else if (eventType == Event::Type::CloneObject) { - CloneObjectEvent *clone = dynamic_cast(this->event); - emit editor->warpEventDoubleClicked(clone->getTargetMap(), clone->getTargetID(), Event::Group::Object); - } - else if (eventType == Event::Type::SecretBase) { - const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - SecretBaseEvent *base = dynamic_cast(this->event); - QString baseId = base->getBaseID(); - QString destMap = editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); - emit editor->warpEventDoubleClicked(destMap, 0, Event::Group::Warp); - } - else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { - HealLocationEvent *heal = dynamic_cast(this->event); - const QString localIdName = heal->getRespawnNPC(); - int localId = 0; // TODO: Get value from localIdName - emit editor->warpEventDoubleClicked(heal->getRespawnMapName(), localId, Event::Group::Object); - } -} diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 82334722..c69d35da 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -453,13 +453,16 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { }); // target id + // TODO: Replace spinner with combo box populated with local IDs from target map. this->spinner_target_id->disconnect(); + /* connect(this->spinner_target_id, QOverload::of(&QSpinBox::valueChanged), [this](int value) { this->clone->setTargetID(value); this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); + */ } void CloneObjectFrame::initialize() { @@ -474,7 +477,7 @@ void CloneObjectFrame::initialize() { // target id this->spinner_target_id->setMinimum(1); this->spinner_target_id->setMaximum(126); - this->spinner_target_id->setValue(this->clone->getTargetID()); + //this->spinner_target_id->setValue(this->clone->getTargetID()); // target map this->combo_target_map->setTextItem(this->clone->getTargetMap()); From 2256ded6c285f2d12eeefe99b7a6e3cd50b42862 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 12:31:45 -0400 Subject: [PATCH 11/71] Some event frame updates for local IDs --- include/ui/eventframes.h | 2 +- src/core/events.cpp | 2 +- src/core/map.cpp | 3 +++ src/mainwindow.cpp | 5 +++++ src/ui/eventframes.cpp | 48 ++++++++++++++-------------------------- 5 files changed, 26 insertions(+), 34 deletions(-) diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 763d6baf..a5ae6765 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -109,7 +109,7 @@ public: public: NoScrollComboBox *combo_sprite; - NoScrollSpinBox *spinner_target_id; + NoScrollComboBox *combo_target_id; NoScrollComboBox *combo_target_map; private: diff --git a/src/core/events.cpp b/src/core/events.cpp index e132f277..8c5e5077 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -99,7 +99,7 @@ QString Event::typeToString(Event::Type type) { {Event::Type::CloneObject, "Clone Object"}, {Event::Type::Warp, "Warp"}, {Event::Type::Trigger, "Trigger"}, - {Event::Type::WeatherTrigger, "Weather"}, + {Event::Type::WeatherTrigger, "Weather Trigger"}, {Event::Type::Sign, "Sign"}, {Event::Type::HiddenItem, "Hidden Item"}, {Event::Type::SecretBase, "Secret Base"}, diff --git a/src/core/map.cpp b/src/core/map.cpp index 5e5d443f..fa69427b 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -195,6 +195,9 @@ Event* Map::getEvent(Event::Group group, int index) const { } Event* Map::getEvent(Event::Group group, const QString &idName) const { + if (idName.isEmpty()) + return nullptr; + bool idIsNumber; int id = idName.toInt(&idIsNumber, 0); if (idIsNumber) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 237ca5c4..1ed4ca04 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1081,8 +1081,13 @@ void MainWindow::openEventMap(Event *sourceEvent) { // Secret Bases open to their secret base entrance const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); SecretBaseEvent *base = dynamic_cast(sourceEvent); + + // Extract the map name from the secret base ID. QString baseId = base->getBaseID(); targetMapName = this->editor->project->mapConstantsToMapNames.value(mapPrefix + baseId.left(baseId.lastIndexOf("_"))); + + // Just select the first warp. Normally the only warp event on every secret base map is the entrance/exit, so this is usually correct. + // The warp IDs for secret bases are specified in the project's C code, not in the map data, so we don't have an easy way to read the actual IDs. targetEventIdName = "0"; targetEventGroup = Event::Group::Warp; } else if (eventType == Event::Type::HealLocation && projectConfig.healLocationRespawnDataEnabled) { diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index c69d35da..ea0cccae 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -74,6 +74,7 @@ void EventFrame::setup() { this->label_id = new QLabel("event_type"); l_vbox_1->addWidget(this->label_id); l_vbox_1->addLayout(l_layout_xyz); + this->label_id->setText(Event::typeToString(this->event->getEventType())); // icon / pixmap label this->label_icon = new QLabel(this); @@ -204,8 +205,6 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj void ObjectFrame::setup() { EventFrame::setup(); - this->label_id->setText("Object"); - // sprite combo QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); @@ -406,8 +405,6 @@ void ObjectFrame::populate(Project *project) { void CloneObjectFrame::setup() { EventFrame::setup(); - this->label_id->setText("Clone Object"); - this->spinner_z->setEnabled(false); // sprite combo (edits disabled) @@ -424,11 +421,12 @@ void CloneObjectFrame::setup() { l_form_dest_map->addRow("Target Map", this->combo_target_map); this->layout_contents->addLayout(l_form_dest_map); - // clone local id spinbox + // clone local id combo 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."); - l_form_dest_id->addRow("Target Local ID", this->spinner_target_id); + this->combo_target_id = new NoScrollComboBox(this); + // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field + this->combo_target_id->setToolTip("event_object ID of the object being cloned."); + l_form_dest_id->addRow("Target Local ID", this->combo_target_id); this->layout_contents->addLayout(l_form_dest_id); // custom attributes @@ -450,19 +448,17 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); + // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. }); // target id - // TODO: Replace spinner with combo box populated with local IDs from target map. - this->spinner_target_id->disconnect(); - /* - connect(this->spinner_target_id, QOverload::of(&QSpinBox::valueChanged), [this](int value) { - this->clone->setTargetID(value); + this->combo_target_id->disconnect(); + connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) { + this->clone->setTargetID(text); this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); - */ } void CloneObjectFrame::initialize() { @@ -475,9 +471,7 @@ void CloneObjectFrame::initialize() { this->combo_sprite->setCurrentText(this->clone->getGfx()); // target id - this->spinner_target_id->setMinimum(1); - this->spinner_target_id->setMaximum(126); - //this->spinner_target_id->setValue(this->clone->getTargetID()); + this->combo_target_id->setCurrentText(this->clone->getTargetID()); // target map this->combo_target_map->setTextItem(this->clone->getTargetMap()); @@ -490,13 +484,12 @@ void CloneObjectFrame::populate(Project *project) { EventFrame::populate(project); this->combo_target_map->addItems(project->mapNames); + // TODO: Populate combo_target_id with local IDs from target map. } void WarpFrame::setup() { EventFrame::setup(); - this->label_id->setText("Warp"); - // desination map combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_dest_map = new NoScrollComboBox(this); @@ -537,6 +530,7 @@ void WarpFrame::connectSignals(MainWindow *window) { connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &text) { this->warp->setDestinationMap(text); this->warp->modify(); + // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. }); // dest id @@ -571,6 +565,7 @@ void WarpFrame::populate(Project *project) { EventFrame::populate(project); this->combo_dest_map->addItems(project->mapNames); + // TODO: Populate combo_dest_warp with local IDs from target map. } @@ -578,8 +573,6 @@ void WarpFrame::populate(Project *project) { void TriggerFrame::setup() { EventFrame::setup(); - this->label_id->setText("Trigger"); - // script combo QFormLayout *l_form_script = new QFormLayout(); this->combo_script = new NoScrollComboBox(this); @@ -666,8 +659,6 @@ void TriggerFrame::populate(Project *project) { void WeatherTriggerFrame::setup() { EventFrame::setup(); - this->label_id->setText("Weather Trigger"); - // weather combo QFormLayout *l_form_weather = new QFormLayout(); this->combo_weather = new NoScrollComboBox(this); @@ -717,8 +708,6 @@ void WeatherTriggerFrame::populate(Project *project) { void SignFrame::setup() { EventFrame::setup(); - this->label_id->setText("Sign"); - // facing dir combo QFormLayout *l_form_facing_dir = new QFormLayout(); this->combo_facing_dir = new NoScrollComboBox(this); @@ -788,8 +777,6 @@ void SignFrame::populate(Project *project) { void HiddenItemFrame::setup() { EventFrame::setup(); - this->label_id->setText("Hidden Item"); - // item combo QFormLayout *l_form_item = new QFormLayout(); this->combo_item = new NoScrollComboBox(this); @@ -902,8 +889,6 @@ void HiddenItemFrame::populate(Project *project) { void SecretBaseFrame::setup() { EventFrame::setup(); - this->label_id->setText("Secret Base"); - this->spinner_z->setEnabled(false); // item combo @@ -955,8 +940,6 @@ void SecretBaseFrame::populate(Project *project) { void HealLocationFrame::setup() { EventFrame::setup(); - this->label_id->setText("Heal Location"); - this->hideable_label_z->setVisible(false); this->spinner_z->setVisible(false); @@ -982,6 +965,7 @@ 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); + // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" "upon respawning after whiteout."); l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); @@ -1006,6 +990,7 @@ void HealLocationFrame::connectSignals(MainWindow *window) { connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { this->healLocation->setRespawnMapName(text); this->healLocation->modify(); + // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. }); this->combo_respawn_npc->disconnect(); @@ -1038,5 +1023,4 @@ void HealLocationFrame::populate(Project *project) { this->combo_respawn_map->addItems(project->mapNames); // TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map - // Same for warp IDs. } From 17949055f6d2fc364da091a5ace49619759fd0fe Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 13:49:26 -0400 Subject: [PATCH 12/71] Fix local ID being reordered in output JSON --- src/core/events.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index 8c5e5077..2dce4192 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -146,12 +146,13 @@ EventFrame *ObjectEvent::createEventFrame() { OrderedJson::object ObjectEvent::buildEventJson(Project *) { OrderedJson::object objectJson; - if (projectConfig.eventCloneObjectEnabled) { - objectJson["type"] = Event::typeToJsonKey(Event::Type::Object); - } QString idName = this->getIdName(); if (!idName.isEmpty()) objectJson["local_id"] = idName; + + if (projectConfig.eventCloneObjectEnabled) { + objectJson["type"] = Event::typeToJsonKey(Event::Type::Object); + } objectJson["graphics_id"] = this->getGfx(); objectJson["x"] = this->getX(); objectJson["y"] = this->getY(); @@ -255,10 +256,11 @@ EventFrame *CloneObjectEvent::createEventFrame() { OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { OrderedJson::object cloneJson; - cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject); QString idName = this->getIdName(); if (!idName.isEmpty()) cloneJson["local_id"] = idName; + + cloneJson["type"] = Event::typeToJsonKey(Event::Type::CloneObject); cloneJson["graphics_id"] = this->getGfx(); cloneJson["x"] = this->getX(); cloneJson["y"] = this->getY(); From 374a2b67b83a985ef038546fde1685fbb89c75ae Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 4 Apr 2025 23:33:38 -0400 Subject: [PATCH 13/71] Remove some redundant event pixmap loading --- src/editor.cpp | 49 +++++++++++++++++++--------------- src/ui/draggablepixmapitem.cpp | 7 ----- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 282ec52c..9afc761b 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1987,31 +1987,36 @@ qreal Editor::getEventOpacity(const Event *event) const { } void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { - if (item && item->event && !item->event->getPixmap().isNull()) { - item->setOpacity(getEventOpacity(item->event)); - project->loadEventPixmap(item->event, true); - item->setPixmap(item->event->getPixmap()); - item->setShapeMode(porymapConfig.eventSelectionShapeMode); + if (!item || !item->event) + return; - if (this->editMode == EditMode::Events) { - if (this->selectedEvents.contains(item->event)) { - // Draw the selection rectangle - QImage image = item->pixmap().toImage(); - QPainter painter(&image); - painter.setPen(QColor(255, 0, 255)); - painter.drawRect(0, 0, image.width() - 1, image.height() - 1); - painter.end(); - item->setPixmap(QPixmap::fromImage(image)); - } - item->setAcceptedMouseButtons(Qt::AllButtons); - } else { - // Can't interact with event pixmaps outside of event editing mode. - // We could do setEnabled(false), but rather than ignoring the mouse events this - // would reject them, which would prevent painting on the map behind the events. - item->setAcceptedMouseButtons(Qt::NoButton); + project->loadEventPixmap(item->event, true); + + QPixmap pixmap = item->event->getPixmap(); + if (pixmap.isNull()) + return; + + qreal zValue = item->event->getY(); + if (this->editMode == EditMode::Events) { + if (this->selectedEvents.contains(item->event)) { + // Draw the selection rectangle + QPainter painter(&pixmap); + painter.setPen(Qt::magenta); + painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); + zValue++; } - item->updatePosition(); + item->setAcceptedMouseButtons(Qt::AllButtons); + } else { + // Can't interact with event pixmaps outside of event editing mode. + // We could do setEnabled(false), but rather than ignoring the mouse events this + // would reject them, which would prevent painting on the map behind the events. + item->setAcceptedMouseButtons(Qt::NoButton); } + item->setPixmap(pixmap); + item->setZValue(zValue); + item->setOpacity(getEventOpacity(item->event)); + item->setShapeMode(porymapConfig.eventSelectionShapeMode); + item->updatePosition(); } // Warp events display a warning if they're not positioned on a metatile with a warp behavior. diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 53cdd90a..a73a7ace 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -12,11 +12,6 @@ void DraggablePixmapItem::updatePosition() { int y = this->event->getPixelY(); setX(x); setY(y); - if (this->editor->selectedEvents.contains(this->event)) { - setZValue(event->getY() + 1); - } else { - setZValue(event->getY()); - } editor->updateWarpEventWarning(event); } @@ -26,8 +21,6 @@ void DraggablePixmapItem::emitPositionChanged() { } void DraggablePixmapItem::updatePixmap() { - editor->project->loadEventPixmap(event, true); - this->updatePosition(); editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); } From c53e9fcb284bafe9e77e33c5cadd1ce5243a839d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 7 Apr 2025 21:11:58 -0400 Subject: [PATCH 14/71] Remove incorrect comment --- src/editor.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index a18e59da..93085d63 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1673,9 +1673,6 @@ void Editor::clearMapEvents() { if (events_group->scene()) { events_group->scene()->removeItem(events_group); } - // events_group does not own its children, the childrens' parent - // is set to the group's parent (and our group has no parent). - qDeleteAll(events_group->childItems()); delete events_group; events_group = nullptr; } From 714cce670fdbb2682e3f49f8402f10635ee02d8a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 8 Apr 2025 12:42:23 -0400 Subject: [PATCH 15/71] DraggaglePixmapItem -> EventPixmapItem --- include/core/editcommands.h | 2 +- include/core/events.h | 8 ++++---- include/editor.h | 6 +++--- ...draggablepixmapitem.h => eventpixmapitem.h} | 12 ++++++------ porymap.pro | 4 ++-- src/core/editcommands.cpp | 2 +- src/core/events.cpp | 2 +- src/editor.cpp | 14 +++++++------- src/mainwindow.cpp | 2 +- src/ui/eventframes.cpp | 10 +++++----- ...gablepixmapitem.cpp => eventpixmapitem.cpp} | 18 +++++++++--------- src/ui/mapimageexporter.cpp | 2 +- 12 files changed, 41 insertions(+), 41 deletions(-) rename include/ui/{draggablepixmapitem.h => eventpixmapitem.h} (77%) rename src/ui/{draggablepixmapitem.cpp => eventpixmapitem.cpp} (85%) diff --git a/include/core/editcommands.h b/include/core/editcommands.h index 9a54063c..5dc4bf0c 100644 --- a/include/core/editcommands.h +++ b/include/core/editcommands.h @@ -14,7 +14,7 @@ class Map; class Layout; class Blockdata; class Event; -class DraggablePixmapItem; +class EventPixmapItem; class Editor; enum CommandId { diff --git a/include/core/events.h b/include/core/events.h index 2b2f5e40..3963e022 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -19,7 +19,7 @@ class EventFrame; class ObjectFrame; class CloneObjectFrame; class WarpFrame; -class DraggablePixmapItem; +class EventPixmapItem; class Event; class ObjectEvent; @@ -154,8 +154,8 @@ public: void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } QPixmap getPixmap() const { return this->pixmap; } - void setPixmapItem(DraggablePixmapItem *item); - DraggablePixmapItem *getPixmapItem() const { return this->pixmapItem; } + void setPixmapItem(EventPixmapItem *item); + EventPixmapItem *getPixmapItem() const { return this->pixmapItem; } void setUsesDefaultPixmap(bool newUsesDefaultPixmap) { this->usesDefaultPixmap = newUsesDefaultPixmap; } bool getUsesDefaultPixmap() const { return this->usesDefaultPixmap; } @@ -194,7 +194,7 @@ protected: QJsonObject customAttributes; QPixmap pixmap; - DraggablePixmapItem *pixmapItem = nullptr; + EventPixmapItem *pixmapItem = nullptr; QPointer eventFrame; diff --git a/include/editor.h b/include/editor.h index a4d3d6c3..60ead193 100644 --- a/include/editor.h +++ b/include/editor.h @@ -30,7 +30,7 @@ #include "mapruler.h" #include "encountertablemodel.h" -class DraggablePixmapItem; +class EventPixmapItem; class MetatilesPixmapItem; class Editor : public QObject @@ -107,7 +107,7 @@ public: void toggleBorderVisibility(bool visible, bool enableScriptCallback = true); void updateCustomMapAttributes(); - DraggablePixmapItem *addEventPixmapItem(Event *event); + EventPixmapItem *addEventPixmapItem(Event *event); void removeEventPixmapItem(Event *event); bool canAddEvents(const QList &events); void selectMapEvent(Event *event, bool toggle = false); @@ -116,7 +116,7 @@ public: void duplicateSelectedEvents(); void redrawAllEvents(); void redrawEvents(const QList &events); - void redrawEventPixmapItem(DraggablePixmapItem *item); + void redrawEventPixmapItem(EventPixmapItem *item); qreal getEventOpacity(const Event *event) const; void updateCursorRectPos(int x, int y); diff --git a/include/ui/draggablepixmapitem.h b/include/ui/eventpixmapitem.h similarity index 77% rename from include/ui/draggablepixmapitem.h rename to include/ui/eventpixmapitem.h index 5c617099..18813bc0 100644 --- a/include/ui/draggablepixmapitem.h +++ b/include/ui/eventpixmapitem.h @@ -1,5 +1,5 @@ -#ifndef DRAGGABLEPIXMAPITEM_H -#define DRAGGABLEPIXMAPITEM_H +#ifndef EVENTPIXMAPITEM_H +#define EVENTPIXMAPITEM_H #include #include @@ -12,12 +12,12 @@ class Editor; -class DraggablePixmapItem : public QObject, public QGraphicsPixmapItem { +class EventPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - DraggablePixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} + EventPixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} - DraggablePixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { + EventPixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { this->event = event; event->setPixmapItem(this); this->editor = editor; @@ -52,4 +52,4 @@ protected: virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(this->event); } }; -#endif // DRAGGABLEPIXMAPITEM_H +#endif // EVENTPIXMAPITEM_H diff --git a/porymap.pro b/porymap.pro index 35fd6af2..7374ac30 100644 --- a/porymap.pro +++ b/porymap.pro @@ -73,7 +73,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/customscriptseditor.cpp \ src/ui/customscriptslistitem.cpp \ src/ui/divingmappixmapitem.cpp \ - src/ui/draggablepixmapitem.cpp \ + src/ui/eventpixmapitem.cpp \ src/ui/bordermetatilespixmapitem.cpp \ src/ui/collisionpixmapitem.cpp \ src/ui/connectionpixmapitem.cpp \ @@ -184,7 +184,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/customscriptseditor.h \ include/ui/customscriptslistitem.h \ include/ui/divingmappixmapitem.h \ - include/ui/draggablepixmapitem.h \ + include/ui/eventpixmapitem.h \ include/ui/bordermetatilespixmapitem.h \ include/ui/collisionpixmapitem.h \ include/ui/connectionpixmapitem.h \ diff --git a/src/core/editcommands.cpp b/src/core/editcommands.cpp index 8850448d..684d98c7 100644 --- a/src/core/editcommands.cpp +++ b/src/core/editcommands.cpp @@ -1,5 +1,5 @@ #include "editcommands.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "bordermetatilespixmapitem.h" #include "editor.h" diff --git a/src/core/events.cpp b/src/core/events.cpp index fbd4e568..694919c0 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -34,7 +34,7 @@ void Event::destroyEventFrame() { this->eventFrame = nullptr; } -void Event::setPixmapItem(DraggablePixmapItem *item) { +void Event::setPixmapItem(EventPixmapItem *item) { this->pixmapItem = item; if (this->eventFrame) { this->eventFrame->invalidateConnections(); diff --git a/src/editor.cpp b/src/editor.cpp index 93085d63..f618361c 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1,5 +1,5 @@ #include "editor.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "imageproviders.h" #include "log.h" #include "connectionslistitem.h" @@ -1692,10 +1692,10 @@ void Editor::displayMapEvents() { events_group->setHandlesChildEvents(false); } -DraggablePixmapItem *Editor::addEventPixmapItem(Event *event) { +EventPixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); - auto item = new DraggablePixmapItem(event, this); - connect(item, &DraggablePixmapItem::doubleClicked, this, &Editor::openEventMap); + auto item = new EventPixmapItem(event, this); + connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; @@ -1971,7 +1971,7 @@ qreal Editor::getEventOpacity(const Event *event) const { return event->getUsesDefaultPixmap() ? 0.7 : 1.0; } -void Editor::redrawEventPixmapItem(DraggablePixmapItem *item) { +void Editor::redrawEventPixmapItem(EventPixmapItem *item) { if (!item || !item->event) return; @@ -2287,8 +2287,8 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working } // It doesn't seem to be possible to prevent the mousePress event -// from triggering both event's DraggablePixmapItem and the background mousePress. -// Since the DraggablePixmapItem's event fires first, we can set a temp +// from triggering both event's EventPixmapItem and the background mousePress. +// Since the EventPixmapItem's event fires first, we can set a temp // variable "selectingEvent" so that we can detect whether or not the user // is clicking on the background instead of an event. void Editor::eventsView_onMousePress(QMouseEvent *event) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9bea0f4a..67f48be7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -10,7 +10,7 @@ #include "customattributesframe.h" #include "scripting.h" #include "adjustingstackedwidget.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "editcommands.h" #include "flowlayout.h" #include "shortcut.h" diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index d392017b..c8682aa7 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -1,7 +1,7 @@ #include "eventframes.h" #include "customattributesframe.h" #include "editcommands.h" -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include using std::numeric_limits; @@ -114,7 +114,7 @@ void EventFrame::connectSignals(MainWindow *) { } }); - connect(this->event->getPixmapItem(), &DraggablePixmapItem::xChanged, this->spinner_x, &NoScrollSpinBox::setValue); + connect(this->event->getPixmapItem(), &EventPixmapItem::xChanged, this->spinner_x, &NoScrollSpinBox::setValue); this->spinner_y->disconnect(); connect(this->spinner_y, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -123,7 +123,7 @@ void EventFrame::connectSignals(MainWindow *) { this->event->getMap()->commit(new EventMove(QList() << this->event, 0, delta, this->spinner_y->getActionId())); } }); - connect(this->event->getPixmapItem(), &DraggablePixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue); + connect(this->event->getPixmapItem(), &EventPixmapItem::yChanged, this->spinner_y, &NoScrollSpinBox::setValue); this->spinner_z->disconnect(); connect(this->spinner_z, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -297,7 +297,7 @@ void ObjectFrame::connectSignals(MainWindow *window) { this->object->getPixmapItem()->updatePixmap(); this->object->modify(); }); - connect(this->object->getPixmapItem(), &DraggablePixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->object->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); // movement this->combo_movement->disconnect(); @@ -439,7 +439,7 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { EventFrame::connectSignals(window); // update icon displayed in frame with target - connect(this->clone->getPixmapItem(), &DraggablePixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->clone->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); // target map this->combo_target_map->disconnect(); diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/eventpixmapitem.cpp similarity index 85% rename from src/ui/draggablepixmapitem.cpp rename to src/ui/eventpixmapitem.cpp index a73a7ace..cc0d76e1 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -1,4 +1,4 @@ -#include "draggablepixmapitem.h" +#include "eventpixmapitem.h" #include "editor.h" #include "editcommands.h" #include "mapruler.h" @@ -7,7 +7,7 @@ static unsigned currentActionId = 0; -void DraggablePixmapItem::updatePosition() { +void EventPixmapItem::updatePosition() { int x = this->event->getPixelX(); int y = this->event->getPixelY(); setX(x); @@ -15,17 +15,17 @@ void DraggablePixmapItem::updatePosition() { editor->updateWarpEventWarning(event); } -void DraggablePixmapItem::emitPositionChanged() { +void EventPixmapItem::emitPositionChanged() { emit xChanged(event->getX()); emit yChanged(event->getY()); } -void DraggablePixmapItem::updatePixmap() { +void EventPixmapItem::updatePixmap() { editor->redrawEventPixmapItem(this); emit spriteChanged(event->getPixmap()); } -void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { if (this->active) return; this->active = true; @@ -49,21 +49,21 @@ void DraggablePixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { this->editor->selectingEvent = true; } -void DraggablePixmapItem::move(int dx, int dy) { +void EventPixmapItem::move(int dx, int dy) { event->setX(event->getX() + dx); event->setY(event->getY() + dy); updatePosition(); emitPositionChanged(); } -void DraggablePixmapItem::moveTo(const QPoint &pos) { +void EventPixmapItem::moveTo(const QPoint &pos) { event->setX(pos.x()); event->setY(pos.y()); updatePosition(); emitPositionChanged(); } -void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { if (!this->active) return; @@ -85,7 +85,7 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { this->releaseSelectionQueued = false; } -void DraggablePixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { if (!this->active) return; this->active = false; diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 39607bca..fe2865b0 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -96,7 +96,7 @@ void MapImageExporter::setModeSpecificUi() { } if (m_mode == ImageExporterMode::Timelapse) { - // TODO: At the moment edit history for events (and the DraggablePixmapItem class) + // TODO: At the moment edit history for events (and the EventPixmapItem class) // explicitly depend on the editor and assume their map is currently open. // Other edit commands rely on this more subtly, like triggering API callbacks or // spending time rendering their layout (which can make creating timelapses very slow). From 3d47d6b7e71c594a843c3406e2efff1674abb383 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 8 Apr 2025 13:12:10 -0400 Subject: [PATCH 16/71] Remove some Editor usage from EventPixmapItem --- include/editor.h | 7 ++-- include/ui/eventpixmapitem.h | 4 ++- include/ui/graphicsview.h | 21 ------------ include/ui/mapview.h | 11 +++++-- src/editor.cpp | 59 +++++++++++++++++----------------- src/ui/eventpixmapitem.cpp | 62 +++++++++++++++--------------------- src/ui/graphicsview.cpp | 17 +--------- src/ui/layoutpixmapitem.cpp | 15 +++++---- 8 files changed, 78 insertions(+), 118 deletions(-) diff --git a/include/editor.h b/include/editor.h index 60ead193..07a5dc68 100644 --- a/include/editor.h +++ b/include/editor.h @@ -122,6 +122,8 @@ public: void updateCursorRectPos(int x, int y); void setCursorRectVisible(bool visible); + void onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); + void onEventReleased(Event *event, const QPoint &position); void updateWarpEventWarning(Event *event); void updateWarpEventWarnings(); @@ -172,10 +174,7 @@ public: static QList> collisionIcons; int eventShiftActionId = 0; - - void eventsView_onMousePress(QMouseEvent *event); - - bool selectingEvent = false; + int eventMoveActionId = 0; void deleteSelectedEvents(); void shouldReselectEvents(); diff --git a/include/ui/eventpixmapitem.h b/include/ui/eventpixmapitem.h index 18813bc0..c44fc6bf 100644 --- a/include/ui/eventpixmapitem.h +++ b/include/ui/eventpixmapitem.h @@ -39,10 +39,12 @@ private: bool releaseSelectionQueued = false; signals: - void positionChanged(Event *event); void xChanged(int); void yChanged(int); void spriteChanged(const QPixmap &pixmap); + void selected(Event *event, bool toggle); + void dragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); + void released(Event *event, const QPoint &position); void doubleClicked(Event *event); protected: diff --git a/include/ui/graphicsview.h b/include/ui/graphicsview.h index 92771cf7..cac812b2 100644 --- a/include/ui/graphicsview.h +++ b/include/ui/graphicsview.h @@ -32,25 +32,4 @@ signals: void clicked(QMouseEvent *event); }; -class Editor; - -// TODO: This should just be MapView. It makes map-based assumptions, and no other class inherits GraphicsView. -class GraphicsView : public QGraphicsView -{ -public: - GraphicsView() : QGraphicsView() {} - GraphicsView(QWidget *parent) : QGraphicsView(parent) {} - -public: -// GraphicsView_Object object; - Editor *editor; -protected: - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent *event) override; - virtual void moveEvent(QMoveEvent *event) override; -}; - -//Q_DECLARE_METATYPE(GraphicsView) - #endif // GRAPHICSVIEW_H diff --git a/include/ui/mapview.h b/include/ui/mapview.h index aa271757..d53e5cce 100644 --- a/include/ui/mapview.h +++ b/include/ui/mapview.h @@ -5,13 +5,17 @@ #include "graphicsview.h" #include "overlay.h" -class MapView : public GraphicsView +class Editor; + +class MapView : public QGraphicsView { Q_OBJECT public: - MapView() : GraphicsView() {} - MapView(QWidget *parent) : GraphicsView(parent) {} + MapView() : QGraphicsView() {} + MapView(QWidget *parent) : QGraphicsView(parent) {} + + Editor *editor; Overlay * getOverlay(int layer); void clearOverlayMap(); @@ -73,6 +77,7 @@ public: protected: virtual void drawForeground(QPainter *painter, const QRectF &rect) override; virtual void keyPressEvent(QKeyEvent*) override; + virtual void moveEvent(QMoveEvent *event) override; private: QMap overlayMap; diff --git a/src/editor.cpp b/src/editor.cpp index f618361c..cf9dd632 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1289,7 +1289,6 @@ void Editor::setStraightPathCursorMode(QGraphicsSceneMouseEvent *event) { } void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *item) { - // TODO: add event tab event painting tool buttons stuff here if (!item->getEditsEnabled()) { return; } @@ -1363,8 +1362,11 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i if (event && event->getPixmapItem()) event->getPixmapItem()->moveTo(pos); } - } else if (eventEditAction == EditAction::Select) { - // do nothing here, at least for now + } else if (eventEditAction == EditAction::Select && event->type() == QEvent::GraphicsSceneMousePress) { + if (!(event->modifiers() & Qt::ControlModifier) && this->selectedEvents.length() > 1) { + // User is clearing group selection by clicking on the background + selectMapEvent(this->selectedEvents.first()); + } } else if (eventEditAction == EditAction::Shift) { static QPoint selection_origin; @@ -1696,6 +1698,9 @@ EventPixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); auto item = new EventPixmapItem(event, this); connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap); + connect(item, &EventPixmapItem::dragged, this, &Editor::onEventDragged); + connect(item, &EventPixmapItem::released, this, &Editor::onEventReleased); + connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; @@ -2004,6 +2009,28 @@ void Editor::redrawEventPixmapItem(EventPixmapItem *item) { item->updatePosition(); } +void Editor::onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition) { + if (!this->map || !this->map_item) + return; + + this->map_item->hoveredMapMetatileChanged(newPosition); + + // Drag all the other selected events (if any) with it + QList draggedEvents; + if (this->selectedEvents.contains(event)) { + draggedEvents = this->selectedEvents; + } else { + draggedEvents.append(event); + } + + QPoint moveDistance = newPosition - oldPosition; + this->map->commit(new EventMove(draggedEvents, moveDistance.x(), moveDistance.y(), this->eventMoveActionId)); +} + +void Editor::onEventReleased(Event *, const QPoint &) { + this->eventMoveActionId++; +} + // Warp events display a warning if they're not positioned on a metatile with a warp behavior. void Editor::updateWarpEventWarning(Event *event) { if (porymapConfig.warpBehaviorWarningDisabled) @@ -2286,32 +2313,6 @@ bool Editor::startDetachedProcess(const QString &command, const QString &working return process.startDetached(pid); } -// It doesn't seem to be possible to prevent the mousePress event -// from triggering both event's EventPixmapItem and the background mousePress. -// Since the EventPixmapItem's event fires first, we can set a temp -// variable "selectingEvent" so that we can detect whether or not the user -// is clicking on the background instead of an event. -void Editor::eventsView_onMousePress(QMouseEvent *event) { - // make sure we are in event editing mode - if (map_item && this->editMode != EditMode::Events) { - return; - } - if (this->eventEditAction == EditAction::Paint && event->buttons() & Qt::RightButton) { - this->eventEditAction = EditAction::Select; - this->settings->mapCursor = QCursor(); - this->cursorMapTileRect->setSingleTileMode(); - this->ui->toolButton_Paint->setChecked(false); - this->ui->toolButton_Select->setChecked(true); - } - - bool multiSelect = event->modifiers() & Qt::ControlModifier; - if (!selectingEvent && !multiSelect && this->selectedEvents.length() > 1) { - // User is clearing group selection by clicking on the background - this->selectMapEvent(this->selectedEvents.first()); - } - selectingEvent = false; -} - void Editor::setCollisionTabSpinBoxes(uint16_t collision, uint16_t elevation) { const QSignalBlocker blocker1(ui->spinBox_SelectedCollision); const QSignalBlocker blocker2(ui->spinBox_SelectedElevation); diff --git a/src/ui/eventpixmapitem.cpp b/src/ui/eventpixmapitem.cpp index cc0d76e1..1face67c 100644 --- a/src/ui/eventpixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -4,8 +4,19 @@ #include "mapruler.h" #include "metatile.h" -static unsigned currentActionId = 0; +void EventPixmapItem::move(int dx, int dy) { + event->setX(event->getX() + dx); + event->setY(event->getY() + dy); + updatePosition(); + emitPositionChanged(); +} +void EventPixmapItem::moveTo(const QPoint &pos) { + event->setX(pos.x()); + event->setY(pos.y()); + updatePosition(); + emitPositionChanged(); +} void EventPixmapItem::updatePosition() { int x = this->event->getPixelX(); @@ -25,17 +36,17 @@ void EventPixmapItem::updatePixmap() { emit spriteChanged(event->getPixmap()); } -void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (this->active) return; this->active = true; - this->lastPos = Metatile::coordFromPixmapCoord(mouse->scenePos()); + this->lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); - bool selectionToggle = mouse->modifiers() & Qt::ControlModifier; + bool selectionToggle = mouseEvent->modifiers() & Qt::ControlModifier; if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. - this->editor->selectMapEvent(this->event, selectionToggle); + emit selected(this->event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: // 1. This is the only selected event, and the selection is pointless. @@ -46,53 +57,30 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouse) { // To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred. this->releaseSelectionQueued = true; } - this->editor->selectingEvent = true; + mouseEvent->accept(); } -void EventPixmapItem::move(int dx, int dy) { - event->setX(event->getX() + dx); - event->setY(event->getY() + dy); - updatePosition(); - emitPositionChanged(); -} - -void EventPixmapItem::moveTo(const QPoint &pos) { - event->setX(pos.x()); - event->setY(pos.y()); - updatePosition(); - emitPositionChanged(); -} - -void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (!this->active) return; - QPoint pos = Metatile::coordFromPixmapCoord(mouse->scenePos()); + QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); if (pos == this->lastPos) return; - QPoint moveDistance = pos - this->lastPos; - this->lastPos = pos; - emit this->editor->map_item->hoveredMapMetatileChanged(pos); - - QList selectedEvents; - if (this->editor->selectedEvents.contains(this->event)) { - selectedEvents = this->editor->selectedEvents; - } else { - selectedEvents.append(this->event); - } - editor->map->commit(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId)); this->releaseSelectionQueued = false; + emit dragged(this->event, this->lastPos, pos); + this->lastPos = pos; } -void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouse) { +void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (!this->active) return; this->active = false; - currentActionId++; if (this->releaseSelectionQueued) { this->releaseSelectionQueued = false; - if (Metatile::coordFromPixmapCoord(mouse->scenePos()) == this->lastPos) - this->editor->selectMapEvent(this->event); + if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == this->lastPos) + emit selected(this->event, false); } + emit released(this->event, this->lastPos); } diff --git a/src/ui/graphicsview.cpp b/src/ui/graphicsview.cpp index 68479e98..6c4ccdb3 100644 --- a/src/ui/graphicsview.cpp +++ b/src/ui/graphicsview.cpp @@ -2,22 +2,7 @@ #include "mapview.h" #include "editor.h" -void GraphicsView::mousePressEvent(QMouseEvent *event) { - QGraphicsView::mousePressEvent(event); - if (editor) { - editor->eventsView_onMousePress(event); - } -} - -void GraphicsView::mouseMoveEvent(QMouseEvent *event) { - QGraphicsView::mouseMoveEvent(event); -} - -void GraphicsView::mouseReleaseEvent(QMouseEvent *event) { - QGraphicsView::mouseReleaseEvent(event); -} - -void GraphicsView::moveEvent(QMoveEvent *event) { +void MapView::moveEvent(QMoveEvent *event) { QGraphicsView::moveEvent(event); QLabel *label_MapRulerStatus = findChild("label_MapRulerStatus", Qt::FindDirectChildrenOnly); if (label_MapRulerStatus && label_MapRulerStatus->isVisible()) diff --git a/src/ui/layoutpixmapitem.cpp b/src/ui/layoutpixmapitem.cpp index f53cf275..3417a4ce 100644 --- a/src/ui/layoutpixmapitem.cpp +++ b/src/ui/layoutpixmapitem.cpp @@ -714,19 +714,20 @@ void LayoutPixmapItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *) { } void LayoutPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *event) { - QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - this->paint_tile_initial_x = this->straight_path_initial_x = pos.x(); - this->paint_tile_initial_y = this->straight_path_initial_y = pos.y(); + this->metatilePos = Metatile::coordFromPixmapCoord(event->pos()); + this->paint_tile_initial_x = this->straight_path_initial_x = this->metatilePos.x(); + this->paint_tile_initial_y = this->straight_path_initial_y = this->metatilePos.y(); emit startPaint(event, this); emit mouseEvent(event, this); } void LayoutPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QPoint pos = Metatile::coordFromPixmapCoord(event->pos()); - if (pos != this->metatilePos) { - this->metatilePos = pos; - emit this->hoveredMapMetatileChanged(pos); - } + if (pos == this->metatilePos) + return; + + this->metatilePos = pos; + emit hoveredMapMetatileChanged(pos); emit mouseEvent(event, this); } From 41d0b4261fd8d1fe878314cc3701a520c3896029 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 10 Apr 2025 12:33:56 -0400 Subject: [PATCH 17/71] Fix deprecated code as of Qt 6.9 --- include/core/utility.h | 7 ++++ include/mainwindow.h | 7 ++-- include/ui/mapheaderform.h | 10 ++--- include/ui/mapimageexporter.h | 37 ++++++++--------- include/ui/regionmapeditor.h | 5 ++- include/ui/shortcut.h | 3 -- include/ui/tileseteditor.h | 6 +-- src/core/utility.cpp | 7 ++++ src/editor.cpp | 4 ++ src/mainwindow.cpp | 30 ++++++++------ src/scriptapi/apioverlay.cpp | 5 +++ src/ui/customscriptseditor.cpp | 4 ++ src/ui/eventframes.cpp | 4 ++ src/ui/imageproviders.cpp | 7 +++- src/ui/mapheaderform.cpp | 18 ++++++--- src/ui/mapimageexporter.cpp | 68 ++++++++++++++++++++++++-------- src/ui/metatilelayersitem.cpp | 5 +++ src/ui/projectsettingseditor.cpp | 10 ++++- src/ui/regionmapeditor.cpp | 22 ++++++++--- src/ui/shortcut.cpp | 13 +----- src/ui/tilemaptileselector.cpp | 4 ++ src/ui/tileseteditor.cpp | 25 +++++++++--- src/ui/updatepromoter.cpp | 4 ++ 23 files changed, 210 insertions(+), 95 deletions(-) diff --git a/include/core/utility.h b/include/core/utility.h index 1b9277ab..d5b7900b 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -9,6 +9,13 @@ namespace Util { int roundUp(int numToRound, int multiple); QString toDefineCase(QString input); QString toHexString(uint32_t value, int minLength = 0); + Qt::Orientations getOrientation(bool xflip, bool yflip); } +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + typedef Qt::CheckState CheckState; +#else + typedef int CheckState; +#endif + #endif // UTILITY_H diff --git a/include/mainwindow.h b/include/mainwindow.h index cdc641c2..9b1be78a 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -248,8 +248,6 @@ private slots: void on_comboBox_PrimaryTileset_currentTextChanged(const QString &arg1); void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); - void on_checkBox_smartPaths_stateChanged(int selected); - void on_checkBox_ToggleBorder_stateChanged(int selected); void resetMapViewScale(); @@ -260,7 +258,6 @@ private slots: void eventTabChanged(int index); - void on_checkBox_MirrorConnections_stateChanged(int selected); void on_actionDive_Emerge_Map_triggered(); void on_actionShow_Events_In_Map_View_triggered(); void on_groupBox_DiveMapOpacity_toggled(bool on); @@ -437,6 +434,10 @@ private: void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); + + void setSmartPathsEnabled(CheckState state); + void setBorderVisibility(CheckState state); + void setMirrorConnectionsEnabled(CheckState state); }; // These are namespaced in a struct to avoid colliding with e.g. class Map. diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 79f4f6c8..1b7bd66a 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -73,11 +73,11 @@ private: void onWeatherChanged(const QString &weather); void onTypeChanged(const QString &type); void onBattleSceneChanged(const QString &battleScene); - void onRequiresFlashChanged(int selected); - void onShowLocationNameChanged(int selected); - void onAllowRunningChanged(int selected); - void onAllowBikingChanged(int selected); - void onAllowEscapingChanged(int selected); + void onRequiresFlashChanged(CheckState selected); + void onShowLocationNameChanged(CheckState selected); + void onAllowRunningChanged(CheckState selected); + void onAllowBikingChanged(CheckState selected); + void onAllowEscapingChanged(CheckState selected); void onFloorNumberChanged(int offset); }; diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index ffc4d272..7b85beb9 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -91,30 +91,27 @@ protected: virtual void showEvent(QShowEvent *) override; virtual void resizeEvent(QResizeEvent *) override; -private slots: - void on_checkBox_Objects_stateChanged(int state); - void on_checkBox_Warps_stateChanged(int state); - void on_checkBox_BGs_stateChanged(int state); - void on_checkBox_Triggers_stateChanged(int state); - void on_checkBox_HealLocations_stateChanged(int state); - void on_checkBox_AllEvents_stateChanged(int state); - - void on_checkBox_ConnectionUp_stateChanged(int state); - void on_checkBox_ConnectionDown_stateChanged(int state); - void on_checkBox_ConnectionLeft_stateChanged(int state); - void on_checkBox_ConnectionRight_stateChanged(int state); - void on_checkBox_AllConnections_stateChanged(int state); - - void on_checkBox_Collision_stateChanged(int state); - void on_checkBox_Grid_stateChanged(int state); - void on_checkBox_Border_stateChanged(int state); +private: + void setShowGrid(CheckState state); + void setShowBorder(CheckState state); + void setShowObjects(CheckState state); + void setShowWarps(CheckState state); + void setShowBgs(CheckState state); + void setShowTriggers(CheckState state); + void setShowHealLocations(CheckState state); + void setShowAllEvents(CheckState state); + void setShowConnectionUp(CheckState state); + void setShowConnectionDown(CheckState state); + void setShowConnectionLeft(CheckState state); + void setShowConnectionRight(CheckState state); + void setShowAllConnections(CheckState state); + void setShowCollision(CheckState state); + void setDisablePreviewScaling(CheckState state); + void setDisablePreviewUpdates(CheckState state); void on_pushButton_Reset_pressed(); void on_spinBox_TimelapseDelay_editingFinished(); void on_spinBox_FrameSkip_editingFinished(); - - void on_checkBox_DisablePreviewScaling_stateChanged(int state); - void on_checkBox_DisablePreviewUpdates_stateChanged(int state); }; #endif // MAPIMAGEEXPORTER_H diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 9a838827..969b6e3a 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -121,6 +121,9 @@ private: void restoreWindowState(); void closeEvent(QCloseEvent* event); + void setTileHFlip(CheckState); + void setTileVFlip(CheckState); + private slots: void on_action_RegionMap_Save_triggered(); void on_actionSave_All_triggered(); @@ -145,8 +148,6 @@ private slots: void on_spinBox_RM_LayoutWidth_valueChanged(int); void on_spinBox_RM_LayoutHeight_valueChanged(int); void on_spinBox_tilePalette_valueChanged(int); - void on_checkBox_tileHFlip_stateChanged(int); - void on_checkBox_tileVFlip_stateChanged(int); void on_verticalSlider_Zoom_Map_Image_valueChanged(int); void on_verticalSlider_Zoom_Image_Tiles_valueChanged(int); void onHoveredRegionMapTileChanged(int x, int y); diff --git a/include/ui/shortcut.h b/include/ui/shortcut.h index 8989401d..5fdbfaee 100644 --- a/include/ui/shortcut.h +++ b/include/ui/shortcut.h @@ -49,9 +49,6 @@ public: void setAutoRepeat(bool on); bool autoRepeat() const; - int id() const; - QList ids() const; - inline QWidget *parentWidget() const { return static_cast(QObject::parent()); } diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index d659390a..ff2999ff 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -71,10 +71,6 @@ private slots: void on_spinBox_paletteSelector_valueChanged(int arg1); - void on_checkBox_xFlip_stateChanged(int arg1); - - void on_checkBox_yFlip_stateChanged(int arg1); - void on_actionSave_Tileset_triggered(); void on_actionImport_Primary_Tiles_triggered(); @@ -149,6 +145,8 @@ private: void commitTerrainType(); void commitLayerType(); void setRawAttributesVisible(bool visible); + void setXFlip(CheckState state); + void setYFlip(CheckState state); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/src/core/utility.cpp b/src/core/utility.cpp index 1053f879..55830b8a 100644 --- a/src/core/utility.cpp +++ b/src/core/utility.cpp @@ -42,3 +42,10 @@ QString Util::toDefineCase(QString input) { QString Util::toHexString(uint32_t value, int minLength) { return "0x" + QString("%1").arg(value, minLength, 16, QChar('0')).toUpper(); } + +Qt::Orientations Util::getOrientation(bool xflip, bool yflip) { + Qt::Orientations flags; + if (xflip) flags |= Qt::Orientation::Horizontal; + if (yflip) flags |= Qt::Orientation::Vertical; + return flags; +} diff --git a/src/editor.cpp b/src/editor.cpp index 29b6cb5d..a50e5622 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -296,7 +296,11 @@ void Editor::addNewWildMonGroup(QWidget *window) { form.addRow(new QLabel(monField.name), fieldCheckbox); } // Reading from ui here so not saving to disk before user. +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(copyCheckbox, &QCheckBox::checkStateChanged, [=](Qt::CheckState state){ +#else connect(copyCheckbox, &QCheckBox::stateChanged, [=](int state){ +#endif if (state == Qt::Checked) { int fieldIndex = 0; MonTabWidget *monWidget = static_cast(stack->widget(stack->currentIndex())); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 650acdaf..42c070f8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -299,6 +299,16 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); connect(ui->comboBox_LayoutSelector->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); + +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_smartPaths, &QCheckBox::checkStateChanged, this, &MainWindow::setSmartPathsEnabled); + connect(ui->checkBox_ToggleBorder, &QCheckBox::checkStateChanged, this, &MainWindow::setBorderVisibility); + connect(ui->checkBox_MirrorConnections, &QCheckBox::checkStateChanged, this, &MainWindow::setMirrorConnectionsEnabled); +#else + connect(ui->checkBox_smartPaths, &QCheckBox::stateChanged, this, &MainWindow::setSmartPathsEnabled); + connect(ui->checkBox_ToggleBorder, &QCheckBox::stateChanged, this, &MainWindow::setBorderVisibility); + connect(ui->checkBox_MirrorConnections, &QCheckBox::stateChanged, this, &MainWindow::setMirrorConnectionsEnabled); +#endif } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -2711,25 +2721,21 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { } } -void MainWindow::on_checkBox_smartPaths_stateChanged(int selected) +void MainWindow::setSmartPathsEnabled(CheckState state) { - bool enabled = selected == Qt::Checked; - editor->settings->smartPathsEnabled = enabled; - if (enabled) { - editor->cursorMapTileRect->setSmartPathMode(true); - } else { - editor->cursorMapTileRect->setSmartPathMode(false); - } + bool enabled = (state == Qt::Checked); + this->editor->settings->smartPathsEnabled = enabled; + this->editor->cursorMapTileRect->setSmartPathMode(enabled); } -void MainWindow::on_checkBox_ToggleBorder_stateChanged(int selected) +void MainWindow::setBorderVisibility(CheckState state) { - editor->toggleBorderVisibility(selected != 0); + editor->toggleBorderVisibility(state == Qt::Checked); } -void MainWindow::on_checkBox_MirrorConnections_stateChanged(int selected) +void MainWindow::setMirrorConnectionsEnabled(CheckState state) { - porymapConfig.mirrorConnectingMaps = (selected == Qt::Checked); + porymapConfig.mirrorConnectingMaps = (state == Qt::Checked); } void MainWindow::on_actionTileset_Editor_triggered() diff --git a/src/scriptapi/apioverlay.cpp b/src/scriptapi/apioverlay.cpp index 4b1f75b8..00a34495 100644 --- a/src/scriptapi/apioverlay.cpp +++ b/src/scriptapi/apioverlay.cpp @@ -276,7 +276,12 @@ void MapView::addTileImage(int x, int y, int tileId, bool xflip, bool yflip, int this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary, paletteId) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + .flipped(Util::getOrientation(xflip, yflip)); +#else .mirrored(xflip, yflip); +#endif + if (setTransparency) image.setColor(0, qRgba(0, 0, 0, 0)); if (this->getOverlay(layer)->addImage(x, y, image)) diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index eddbc7ae..9c166d43 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -105,7 +105,11 @@ void CustomScriptsEditor::displayScript(const QString &filepath, bool enabled) { connect(widget->ui->b_Choose, &QAbstractButton::clicked, [this, item](bool) { this->replaceScript(item); }); connect(widget->ui->b_Edit, &QAbstractButton::clicked, [this, item](bool) { this->openScript(item); }); connect(widget->ui->b_Delete, &QAbstractButton::clicked, [this, item](bool) { this->removeScript(item); }); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(widget->ui->checkBox_Enable, &QCheckBox::checkStateChanged, this, &CustomScriptsEditor::markEdited); +#else connect(widget->ui->checkBox_Enable, &QCheckBox::stateChanged, this, &CustomScriptsEditor::markEdited); +#endif connect(widget->ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsEditor::markEdited); // Per the Qt manual, for performance reasons QListWidget::setItemWidget shouldn't be used with non-static items. diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 065dbf19..e9c108d3 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -853,7 +853,11 @@ void HiddenItemFrame::connectSignals(MainWindow *window) { // underfoot this->check_itemfinder->disconnect(); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(this->check_itemfinder, &QCheckBox::checkStateChanged, [=](Qt::CheckState state) { +#else connect(this->check_itemfinder, &QCheckBox::stateChanged, [=](int state) { +#endif this->hiddenItem->setUnderfoot(state == Qt::Checked); this->hiddenItem->modify(); }); diff --git a/src/ui/imageproviders.cpp b/src/ui/imageproviders.cpp index 33ec3596..b1510195 100644 --- a/src/ui/imageproviders.cpp +++ b/src/ui/imageproviders.cpp @@ -127,7 +127,12 @@ QImage getMetatileImage( color.setAlpha(0); tile_image.setColor(0, color.rgba()); - metatile_painter.drawImage(origin, tile_image.mirrored(tile.xflip, tile.yflip)); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + tile_image.flip(Util::getOrientation(tile.xflip, tile.yflip)); +#else + tile_image = tile_image.mirrored(tile.xflip, tile.yflip); +#endif + metatile_painter.drawImage(origin, tile_image); } metatile_painter.end(); diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 09cb22c7..92fa8c7f 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -21,11 +21,19 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged); connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_RequiresFlash, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onRequiresFlashChanged); + connect(ui->checkBox_ShowLocationName, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onShowLocationNameChanged); + connect(ui->checkBox_AllowRunning, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowRunningChanged); + connect(ui->checkBox_AllowBiking, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowBikingChanged); + connect(ui->checkBox_AllowEscaping, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowEscapingChanged); +#else connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged); connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged); connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); +#endif connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); @@ -207,11 +215,11 @@ void MapHeaderForm::onSongUpdated(const QString &song) { if (m_hea void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); } void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); } void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); } -void MapHeaderForm::onRequiresFlashChanged(int selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } -void MapHeaderForm::onShowLocationNameChanged(int selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } -void MapHeaderForm::onAllowRunningChanged(int selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } -void MapHeaderForm::onAllowBikingChanged(int selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } -void MapHeaderForm::onAllowEscapingChanged(int selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } +void MapHeaderForm::onRequiresFlashChanged(CheckState selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } +void MapHeaderForm::onShowLocationNameChanged(CheckState selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } +void MapHeaderForm::onAllowRunningChanged(CheckState selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } +void MapHeaderForm::onAllowBikingChanged(CheckState selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } +void MapHeaderForm::onAllowEscapingChanged(CheckState selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 39607bca..cf1339c5 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -60,6 +60,42 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_Objects, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowObjects); + connect(ui->checkBox_Warps, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowWarps); + connect(ui->checkBox_BGs, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBgs); + connect(ui->checkBox_Triggers, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowTriggers); + connect(ui->checkBox_HealLocations, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowHealLocations); + connect(ui->checkBox_AllEvents, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllEvents); + connect(ui->checkBox_ConnectionUp, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionUp); + connect(ui->checkBox_ConnectionDown, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionDown); + connect(ui->checkBox_ConnectionLeft, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionLeft); + connect(ui->checkBox_ConnectionRight, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionRight); + connect(ui->checkBox_AllConnections, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllConnections); + connect(ui->checkBox_Collision, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowCollision); + connect(ui->checkBox_Grid, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowGrid); + connect(ui->checkBox_Border, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBorder); + connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewScaling); + connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewUpdates); +#else + connect(ui->checkBox_Objects, &QCheckBox::stateChanged, this, &MapImageExporter::setShowObjects); + connect(ui->checkBox_Warps, &QCheckBox::stateChanged, this, &MapImageExporter::setShowWarps); + connect(ui->checkBox_BGs, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBgs); + connect(ui->checkBox_Triggers, &QCheckBox::stateChanged, this, &MapImageExporter::setShowTriggers); + connect(ui->checkBox_HealLocations, &QCheckBox::stateChanged, this, &MapImageExporter::setShowHealLocations); + connect(ui->checkBox_AllEvents, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllEvents); + connect(ui->checkBox_ConnectionUp, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionUp); + connect(ui->checkBox_ConnectionDown, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionDown); + connect(ui->checkBox_ConnectionLeft, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionLeft); + connect(ui->checkBox_ConnectionRight, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionRight); + connect(ui->checkBox_AllConnections, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllConnections); + connect(ui->checkBox_Collision, &QCheckBox::stateChanged, this, &MapImageExporter::setShowCollision); + connect(ui->checkBox_Grid, &QCheckBox::stateChanged, this, &MapImageExporter::setShowGrid); + connect(ui->checkBox_Border, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBorder); + connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewScaling); + connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewUpdates); +#endif + ui->graphicsView_Preview->setFocus(); } @@ -719,48 +755,48 @@ void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool en } } -void MapImageExporter::on_checkBox_Collision_stateChanged(int state) { +void MapImageExporter::setShowCollision(CheckState state) { m_settings.showCollision = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Grid_stateChanged(int state) { +void MapImageExporter::setShowGrid(CheckState state) { m_settings.showGrid = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Border_stateChanged(int state) { +void MapImageExporter::setShowBorder(CheckState state) { m_settings.showBorder = (state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Objects_stateChanged(int state) { +void MapImageExporter::setShowObjects(CheckState state) { setEventGroupEnabled(Event::Group::Object, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Warps_stateChanged(int state) { +void MapImageExporter::setShowWarps(CheckState state) { setEventGroupEnabled(Event::Group::Warp, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_BGs_stateChanged(int state) { +void MapImageExporter::setShowBgs(CheckState state) { setEventGroupEnabled(Event::Group::Bg, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_Triggers_stateChanged(int state) { +void MapImageExporter::setShowTriggers(CheckState state) { setEventGroupEnabled(Event::Group::Coord, state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_HealLocations_stateChanged(int state) { +void MapImageExporter::setShowHealLocations(CheckState state) { setEventGroupEnabled(Event::Group::Heal, state == Qt::Checked); updatePreview(); } // Shortcut setting for enabling all events -void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { +void MapImageExporter::setShowAllEvents(CheckState state) { bool on = (state == Qt::Checked); const QSignalBlocker b_Objects(ui->checkBox_Objects); @@ -791,28 +827,28 @@ void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) { +void MapImageExporter::setShowConnectionUp(CheckState state) { setConnectionDirectionEnabled("up", state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) { +void MapImageExporter::setShowConnectionDown(CheckState state) { setConnectionDirectionEnabled("down", state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) { +void MapImageExporter::setShowConnectionLeft(CheckState state) { setConnectionDirectionEnabled("left", state == Qt::Checked); updatePreview(); } -void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) { +void MapImageExporter::setShowConnectionRight(CheckState state) { setConnectionDirectionEnabled("right", state == Qt::Checked); updatePreview(); } // Shortcut setting for enabling all connection directions -void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { +void MapImageExporter::setShowAllConnections(CheckState state) { bool on = (state == Qt::Checked); const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); @@ -838,7 +874,7 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { updatePreview(); } -void MapImageExporter::on_checkBox_DisablePreviewScaling_stateChanged(int state) { +void MapImageExporter::setDisablePreviewScaling(CheckState state) { m_settings.disablePreviewScaling = (state == Qt::Checked); if (m_settings.disablePreviewScaling) { ui->graphicsView_Preview->resetTransform(); @@ -847,7 +883,7 @@ void MapImageExporter::on_checkBox_DisablePreviewScaling_stateChanged(int state) } } -void MapImageExporter::on_checkBox_DisablePreviewUpdates_stateChanged(int state) { +void MapImageExporter::setDisablePreviewUpdates(CheckState state) { m_settings.disablePreviewUpdates = (state == Qt::Checked); if (m_settings.disablePreviewUpdates) { if (m_timelapseMovie) { diff --git a/src/ui/metatilelayersitem.cpp b/src/ui/metatilelayersitem.cpp index 3050e761..87218f52 100644 --- a/src/ui/metatilelayersitem.cpp +++ b/src/ui/metatilelayersitem.cpp @@ -1,6 +1,7 @@ #include "config.h" #include "metatilelayersitem.h" #include "imageproviders.h" +#include "utility.h" #include static const QList tilePositions = { @@ -28,7 +29,11 @@ void MetatileLayersItem::draw() { for (int i = 0; i < numTiles; i++) { Tile tile = this->metatile->tiles.at(i); QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + .flipped(Util::getOrientation(tile.xflip, tile.yflip)) +#else .mirrored(tile.xflip, tile.yflip) +#endif .scaled(16, 16); painter.drawImage(tilePositions.at(i) * 16, tileImage); } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 1c869dce..b1c61151 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -45,7 +45,11 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->comboBox_BaseGameVersion, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::promptRestoreDefaults); connect(ui->comboBox_AttributesSize, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updateAttributeLimits); connect(ui->comboBox_IconSpecies, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updatePokemonIconPath); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { +#else connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::stateChanged, [this](int state) { +#endif bool customSize = (state == Qt::Checked); // When switching between the spin boxes or line edit for border metatiles we set // the newly-shown UI using the values from the hidden UI. @@ -82,8 +86,12 @@ void ProjectSettingsEditor::connectSignals() { connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited); } for (auto checkBox : ui->centralwidget->findChildren()) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(checkBox, &QCheckBox::checkStateChanged, this, &ProjectSettingsEditor::markEdited); +#else connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited); - for (auto radioButton : ui->centralwidget->findChildren()) +#endif + for (auto radioButton : ui->centralwidget->findChildren()) connect(radioButton, &QRadioButton::toggled, this, &ProjectSettingsEditor::markEdited); for (auto lineEdit : ui->centralwidget->findChildren()) connect(lineEdit, &QLineEdit::textEdited, this, &ProjectSettingsEditor::markEdited); diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 2febccd8..9495395f 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -27,6 +27,15 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->ui->setupUi(this); this->project = project; connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_tileHFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileHFlip); + connect(ui->checkBox_tileVFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileVFlip); +#else + connect(ui->checkBox_tileHFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileHFlip); + connect(ui->checkBox_tileVFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileVFlip); +#endif + + this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); this->initShortcuts(); this->restoreWindowState(); @@ -1030,15 +1039,18 @@ void RegionMapEditor::on_pushButton_RM_Options_delete_clicked() { } void RegionMapEditor::on_spinBox_tilePalette_valueChanged(int value) { - this->mapsquare_selector_item->selectPalette(value); + if (this->mapsquare_selector_item) + this->mapsquare_selector_item->selectPalette(value); } -void RegionMapEditor::on_checkBox_tileHFlip_stateChanged(int state) { - this->mapsquare_selector_item->selectHFlip(state == Qt::Checked); +void RegionMapEditor::setTileHFlip(CheckState state) { + if (this->mapsquare_selector_item) + this->mapsquare_selector_item->selectHFlip(state == Qt::Checked); } -void RegionMapEditor::on_checkBox_tileVFlip_stateChanged(int state) { - this->mapsquare_selector_item->selectVFlip(state == Qt::Checked); +void RegionMapEditor::setTileVFlip(CheckState state) { + if (this->mapsquare_selector_item) + this->mapsquare_selector_item->selectVFlip(state == Qt::Checked); } void RegionMapEditor::on_action_RegionMap_Resize_triggered() { diff --git a/src/ui/shortcut.cpp b/src/ui/shortcut.cpp index e94b4095..6b72da09 100644 --- a/src/ui/shortcut.cpp +++ b/src/ui/shortcut.cpp @@ -123,21 +123,10 @@ bool Shortcut::autoRepeat() const { return sc_vec.first()->autoRepeat(); } -int Shortcut::id() const { - return sc_vec.first()->id(); -} - -QList Shortcut::ids() const { - QList id_list; - for (auto *sc : sc_vec) - id_list.append(sc->id()); - return id_list; -} - bool Shortcut::event(QEvent *e) { if (isEnabled() && e->type() == QEvent::Shortcut) { auto se = static_cast(e); - if (ids().contains(se->shortcutId()) && keys().contains(se->key())) { + if (keys().contains(se->key())) { if (QWhatsThis::inWhatsThisMode()) { QWhatsThis::showText(QCursor::pos(), whatsThis()); } else { diff --git a/src/ui/tilemaptileselector.cpp b/src/ui/tilemaptileselector.cpp index a4ef5719..9093b694 100644 --- a/src/ui/tilemaptileselector.cpp +++ b/src/ui/tilemaptileselector.cpp @@ -93,7 +93,11 @@ QImage TilemapTileSelector::tileImg(shared_ptr tile) { // take a tile from the tileset QImage img = tilesetImage.copy(pos.x() * 8, pos.y() * 8, 8, 8); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + img.flip(Util::getOrientation(tile->hFlip(), tile->vFlip())); +#else img = img.mirrored(tile->hFlip(), tile->vFlip()); +#endif return img; } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 351771df..9281bd28 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -27,6 +27,14 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); ui->setupUi(this); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_xFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setXFlip); + connect(ui->checkBox_yFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setYFlip); +#else + connect(ui->checkBox_xFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setXFlip); + connect(ui->checkBox_yFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setYFlip); +#endif + this->tileXFlip = ui->checkBox_xFlip->isChecked(); this->tileYFlip = ui->checkBox_yFlip->isChecked(); this->paletteId = ui->spinBox_paletteSelector->value(); @@ -388,8 +396,13 @@ void TilesetEditor::drawSelectedTiles() { int tileIndex = 0; for (int j = 0; j < dimensions.y(); j++) { for (int i = 0; i < dimensions.x(); i++) { - QImage tileImage = getPalettedTileImage(tiles.at(tileIndex).tileId, this->primaryTileset, this->secondaryTileset, tiles.at(tileIndex).palette, true) - .mirrored(tiles.at(tileIndex).xflip, tiles.at(tileIndex).yflip) + auto tile = tiles.at(tileIndex); + QImage tileImage = getPalettedTileImage(tile.tileId, this->primaryTileset, this->secondaryTileset, tile.palette, true) +#if (QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)) + .flipped(Util::getOrientation(tile.xflip, tile.yflip)) +#else + .mirrored(tile.xflip, tile.yflip) +#endif .scaled(16, 16); tileIndex++; painter.drawImage(i * 16, j * 16, tileImage); @@ -540,17 +553,17 @@ void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId) this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::on_checkBox_xFlip_stateChanged(int checked) +void TilesetEditor::setXFlip(CheckState state) { - this->tileXFlip = checked; + this->tileXFlip = (state == Qt::Checked); this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::on_checkBox_yFlip_stateChanged(int checked) +void TilesetEditor::setYFlip(CheckState state) { - this->tileYFlip = checked; + this->tileYFlip = (state == Qt::Checked); this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); diff --git a/src/ui/updatepromoter.cpp b/src/ui/updatepromoter.cpp index f9331a16..27c37b0a 100644 --- a/src/ui/updatepromoter.cpp +++ b/src/ui/updatepromoter.cpp @@ -19,7 +19,11 @@ UpdatePromoter::UpdatePromoter(QWidget *parent, NetworkAccessManager *manager) // Set up "Do not alert me" check box this->updatePreferences(); ui->checkBox_StopAlerts->setVisible(false); +#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) + connect(ui->checkBox_StopAlerts, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { +#else connect(ui->checkBox_StopAlerts, &QCheckBox::stateChanged, [this](int state) { +#endif porymapConfig.checkForUpdates = (state != Qt::Checked); emit this->changedPreferences(); }); From c6e94eb6ab58207d730b990b088227cacbfeac5a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 10 Apr 2025 14:34:04 -0400 Subject: [PATCH 18/71] Replace stateChanged/checkStateChanged with toggled --- include/core/utility.h | 6 -- include/mainwindow.h | 6 +- include/ui/mapheaderform.h | 10 +- include/ui/mapimageexporter.h | 32 +++--- include/ui/regionmapeditor.h | 4 +- include/ui/tileseteditor.h | 4 +- src/editor.cpp | 10 +- src/mainwindow.cpp | 24 ++--- src/ui/customscriptseditor.cpp | 6 +- src/ui/eventframes.cpp | 8 +- src/ui/mapheaderform.cpp | 29 ++---- src/ui/mapimageexporter.cpp | 169 +++++++++++++------------------ src/ui/projectsettingseditor.cpp | 17 +--- src/ui/regionmapeditor.cpp | 17 ++-- src/ui/tileseteditor.cpp | 18 ++-- src/ui/updatepromoter.cpp | 8 +- 16 files changed, 143 insertions(+), 225 deletions(-) diff --git a/include/core/utility.h b/include/core/utility.h index d5b7900b..6613ee71 100644 --- a/include/core/utility.h +++ b/include/core/utility.h @@ -12,10 +12,4 @@ namespace Util { Qt::Orientations getOrientation(bool xflip, bool yflip); } -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - typedef Qt::CheckState CheckState; -#else - typedef int CheckState; -#endif - #endif // UTILITY_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 9b1be78a..410bd6e3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -435,9 +435,9 @@ private: void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); - void setSmartPathsEnabled(CheckState state); - void setBorderVisibility(CheckState state); - void setMirrorConnectionsEnabled(CheckState state); + void setSmartPathsEnabled(bool enabled); + void setBorderVisibility(bool visible); + void setMirrorConnectionsEnabled(bool enabled); }; // These are namespaced in a struct to avoid colliding with e.g. class Map. diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 1b7bd66a..7f246d6d 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -73,11 +73,11 @@ private: void onWeatherChanged(const QString &weather); void onTypeChanged(const QString &type); void onBattleSceneChanged(const QString &battleScene); - void onRequiresFlashChanged(CheckState selected); - void onShowLocationNameChanged(CheckState selected); - void onAllowRunningChanged(CheckState selected); - void onAllowBikingChanged(CheckState selected); - void onAllowEscapingChanged(CheckState selected); + void onRequiresFlashChanged(bool enabled); + void onShowLocationNameChanged(bool enabled); + void onAllowRunningChanged(bool enabled); + void onAllowBikingChanged(bool enabled); + void onAllowEscapingChanged(bool enabled); void onFloorNumberChanged(int offset); }; diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 7b85beb9..51c1afe3 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -92,22 +92,22 @@ protected: virtual void resizeEvent(QResizeEvent *) override; private: - void setShowGrid(CheckState state); - void setShowBorder(CheckState state); - void setShowObjects(CheckState state); - void setShowWarps(CheckState state); - void setShowBgs(CheckState state); - void setShowTriggers(CheckState state); - void setShowHealLocations(CheckState state); - void setShowAllEvents(CheckState state); - void setShowConnectionUp(CheckState state); - void setShowConnectionDown(CheckState state); - void setShowConnectionLeft(CheckState state); - void setShowConnectionRight(CheckState state); - void setShowAllConnections(CheckState state); - void setShowCollision(CheckState state); - void setDisablePreviewScaling(CheckState state); - void setDisablePreviewUpdates(CheckState state); + void setShowGrid(bool checked); + void setShowBorder(bool checked); + void setShowObjects(bool checked); + void setShowWarps(bool checked); + void setShowBgs(bool checked); + void setShowTriggers(bool checked); + void setShowHealLocations(bool checked); + void setShowAllEvents(bool checked); + void setShowConnectionUp(bool checked); + void setShowConnectionDown(bool checked); + void setShowConnectionLeft(bool checked); + void setShowConnectionRight(bool checked); + void setShowAllConnections(bool checked); + void setShowCollision(bool checked); + void setDisablePreviewScaling(bool checked); + void setDisablePreviewUpdates(bool checked); void on_pushButton_Reset_pressed(); void on_spinBox_TimelapseDelay_editingFinished(); diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index 969b6e3a..97947872 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -121,8 +121,8 @@ private: void restoreWindowState(); void closeEvent(QCloseEvent* event); - void setTileHFlip(CheckState); - void setTileVFlip(CheckState); + void setTileHFlip(bool enabled); + void setTileVFlip(bool enabled); private slots: void on_action_RegionMap_Save_triggered(); diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index ff2999ff..fdd4751c 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -145,8 +145,8 @@ private: void commitTerrainType(); void commitLayerType(); void setRawAttributesVisible(bool visible); - void setXFlip(CheckState state); - void setYFlip(CheckState state); + void setXFlip(bool enabled); + void setYFlip(bool enabled); Ui::TilesetEditor *ui; History metatileHistory; diff --git a/src/editor.cpp b/src/editor.cpp index a50e5622..a9067a79 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -296,12 +296,8 @@ void Editor::addNewWildMonGroup(QWidget *window) { form.addRow(new QLabel(monField.name), fieldCheckbox); } // Reading from ui here so not saving to disk before user. -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(copyCheckbox, &QCheckBox::checkStateChanged, [=](Qt::CheckState state){ -#else - connect(copyCheckbox, &QCheckBox::stateChanged, [=](int state){ -#endif - if (state == Qt::Checked) { + connect(copyCheckbox, &QCheckBox::toggled, [=](bool checked){ + if (checked) { int fieldIndex = 0; MonTabWidget *monWidget = static_cast(stack->widget(stack->currentIndex())); for (EncounterField monField : project->wildMonFields) { @@ -309,7 +305,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { fieldCheckboxes[fieldIndex]->setEnabled(false); fieldIndex++; } - } else if (state == Qt::Unchecked) { + } else { int fieldIndex = 0; for (EncounterField monField : project->wildMonFields) { fieldCheckboxes[fieldIndex]->setEnabled(true); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 42c070f8..a2cb1f67 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -299,16 +299,9 @@ void MainWindow::initExtraSignals() { connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); connect(ui->actionDuplicate_Current_Map_Layout, &QAction::triggered, this, &MainWindow::openDuplicateMapOrLayoutDialog); connect(ui->comboBox_LayoutSelector->lineEdit(), &QLineEdit::editingFinished, this, &MainWindow::onLayoutSelectorEditingFinished); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_smartPaths, &QCheckBox::checkStateChanged, this, &MainWindow::setSmartPathsEnabled); - connect(ui->checkBox_ToggleBorder, &QCheckBox::checkStateChanged, this, &MainWindow::setBorderVisibility); - connect(ui->checkBox_MirrorConnections, &QCheckBox::checkStateChanged, this, &MainWindow::setMirrorConnectionsEnabled); -#else - connect(ui->checkBox_smartPaths, &QCheckBox::stateChanged, this, &MainWindow::setSmartPathsEnabled); - connect(ui->checkBox_ToggleBorder, &QCheckBox::stateChanged, this, &MainWindow::setBorderVisibility); - connect(ui->checkBox_MirrorConnections, &QCheckBox::stateChanged, this, &MainWindow::setMirrorConnectionsEnabled); -#endif + connect(ui->checkBox_smartPaths, &QCheckBox::toggled, this, &MainWindow::setSmartPathsEnabled); + connect(ui->checkBox_ToggleBorder, &QCheckBox::toggled, this, &MainWindow::setBorderVisibility); + connect(ui->checkBox_MirrorConnections, &QCheckBox::toggled, this, &MainWindow::setMirrorConnectionsEnabled); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -2721,21 +2714,20 @@ void MainWindow::on_pushButton_ChangeDimensions_clicked() { } } -void MainWindow::setSmartPathsEnabled(CheckState state) +void MainWindow::setSmartPathsEnabled(bool enabled) { - bool enabled = (state == Qt::Checked); this->editor->settings->smartPathsEnabled = enabled; this->editor->cursorMapTileRect->setSmartPathMode(enabled); } -void MainWindow::setBorderVisibility(CheckState state) +void MainWindow::setBorderVisibility(bool visible) { - editor->toggleBorderVisibility(state == Qt::Checked); + editor->toggleBorderVisibility(visible); } -void MainWindow::setMirrorConnectionsEnabled(CheckState state) +void MainWindow::setMirrorConnectionsEnabled(bool enabled) { - porymapConfig.mirrorConnectingMaps = (state == Qt::Checked); + porymapConfig.mirrorConnectingMaps = enabled; } void MainWindow::on_actionTileset_Editor_triggered() diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index 9c166d43..7cc89a14 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -105,11 +105,7 @@ void CustomScriptsEditor::displayScript(const QString &filepath, bool enabled) { connect(widget->ui->b_Choose, &QAbstractButton::clicked, [this, item](bool) { this->replaceScript(item); }); connect(widget->ui->b_Edit, &QAbstractButton::clicked, [this, item](bool) { this->openScript(item); }); connect(widget->ui->b_Delete, &QAbstractButton::clicked, [this, item](bool) { this->removeScript(item); }); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(widget->ui->checkBox_Enable, &QCheckBox::checkStateChanged, this, &CustomScriptsEditor::markEdited); -#else - connect(widget->ui->checkBox_Enable, &QCheckBox::stateChanged, this, &CustomScriptsEditor::markEdited); -#endif + connect(widget->ui->checkBox_Enable, &QCheckBox::toggled, this, &CustomScriptsEditor::markEdited); connect(widget->ui->lineEdit_filepath, &QLineEdit::textEdited, this, &CustomScriptsEditor::markEdited); // Per the Qt manual, for performance reasons QListWidget::setItemWidget shouldn't be used with non-static items. diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index e9c108d3..bce55c66 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -853,12 +853,8 @@ void HiddenItemFrame::connectSignals(MainWindow *window) { // underfoot this->check_itemfinder->disconnect(); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(this->check_itemfinder, &QCheckBox::checkStateChanged, [=](Qt::CheckState state) { -#else - connect(this->check_itemfinder, &QCheckBox::stateChanged, [=](int state) { -#endif - this->hiddenItem->setUnderfoot(state == Qt::Checked); + connect(this->check_itemfinder, &QCheckBox::toggled, [=](bool checked) { + this->hiddenItem->setUnderfoot(checked); this->hiddenItem->modify(); }); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 92fa8c7f..65fe6ead 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -20,20 +20,11 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) connect(ui->comboBox_Weather, &QComboBox::currentTextChanged, this, &MapHeaderForm::onWeatherChanged); connect(ui->comboBox_Type, &QComboBox::currentTextChanged, this, &MapHeaderForm::onTypeChanged); connect(ui->comboBox_BattleScene, &QComboBox::currentTextChanged, this, &MapHeaderForm::onBattleSceneChanged); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_RequiresFlash, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onRequiresFlashChanged); - connect(ui->checkBox_ShowLocationName, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onShowLocationNameChanged); - connect(ui->checkBox_AllowRunning, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowRunningChanged); - connect(ui->checkBox_AllowBiking, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowBikingChanged); - connect(ui->checkBox_AllowEscaping, &QCheckBox::checkStateChanged, this, &MapHeaderForm::onAllowEscapingChanged); -#else - connect(ui->checkBox_RequiresFlash, &QCheckBox::stateChanged, this, &MapHeaderForm::onRequiresFlashChanged); - connect(ui->checkBox_ShowLocationName, &QCheckBox::stateChanged, this, &MapHeaderForm::onShowLocationNameChanged); - connect(ui->checkBox_AllowRunning, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowRunningChanged); - connect(ui->checkBox_AllowBiking, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowBikingChanged); - connect(ui->checkBox_AllowEscaping, &QCheckBox::stateChanged, this, &MapHeaderForm::onAllowEscapingChanged); -#endif + connect(ui->checkBox_RequiresFlash, &QCheckBox::toggled, this, &MapHeaderForm::onRequiresFlashChanged); + connect(ui->checkBox_ShowLocationName, &QCheckBox::toggled, this, &MapHeaderForm::onShowLocationNameChanged); + connect(ui->checkBox_AllowRunning, &QCheckBox::toggled, this, &MapHeaderForm::onAllowRunningChanged); + connect(ui->checkBox_AllowBiking, &QCheckBox::toggled, this, &MapHeaderForm::onAllowBikingChanged); + connect(ui->checkBox_AllowEscaping, &QCheckBox::toggled, this, &MapHeaderForm::onAllowEscapingChanged); connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); @@ -215,11 +206,11 @@ void MapHeaderForm::onSongUpdated(const QString &song) { if (m_hea void MapHeaderForm::onWeatherChanged(const QString &weather) { if (m_header) m_header->setWeather(weather); } void MapHeaderForm::onTypeChanged(const QString &type) { if (m_header) m_header->setType(type); } void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { if (m_header) m_header->setBattleScene(battleScene); } -void MapHeaderForm::onRequiresFlashChanged(CheckState selected) { if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } -void MapHeaderForm::onShowLocationNameChanged(CheckState selected) { if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } -void MapHeaderForm::onAllowRunningChanged(CheckState selected) { if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } -void MapHeaderForm::onAllowBikingChanged(CheckState selected) { if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } -void MapHeaderForm::onAllowEscapingChanged(CheckState selected) { if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } +void MapHeaderForm::onRequiresFlashChanged(bool enabled) { if (m_header) m_header->setRequiresFlash(enabled); } +void MapHeaderForm::onShowLocationNameChanged(bool enabled) { if (m_header) m_header->setShowsLocationName(enabled); } +void MapHeaderForm::onAllowRunningChanged(bool enabled) { if (m_header) m_header->setAllowsRunning(enabled); } +void MapHeaderForm::onAllowBikingChanged(bool enabled) { if (m_header) m_header->setAllowsBiking(enabled); } +void MapHeaderForm::onAllowEscapingChanged(bool enabled) { if (m_header) m_header->setAllowsEscaping(enabled); } void MapHeaderForm::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index cf1339c5..58b63cbd 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -60,41 +60,22 @@ MapImageExporter::MapImageExporter(QWidget *parent, Project *project, Map *map, connect(ui->comboBox_MapSelection, QOverload::of(&QComboBox::currentIndexChanged), this, &MapImageExporter::updateMapSelection); connect(ui->comboBox_MapSelection->lineEdit(), &QLineEdit::editingFinished, this, &MapImageExporter::updateMapSelection); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_Objects, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowObjects); - connect(ui->checkBox_Warps, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowWarps); - connect(ui->checkBox_BGs, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBgs); - connect(ui->checkBox_Triggers, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowTriggers); - connect(ui->checkBox_HealLocations, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowHealLocations); - connect(ui->checkBox_AllEvents, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllEvents); - connect(ui->checkBox_ConnectionUp, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionUp); - connect(ui->checkBox_ConnectionDown, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionDown); - connect(ui->checkBox_ConnectionLeft, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionLeft); - connect(ui->checkBox_ConnectionRight, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowConnectionRight); - connect(ui->checkBox_AllConnections, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowAllConnections); - connect(ui->checkBox_Collision, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowCollision); - connect(ui->checkBox_Grid, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowGrid); - connect(ui->checkBox_Border, &QCheckBox::checkStateChanged, this, &MapImageExporter::setShowBorder); - connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewScaling); - connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::checkStateChanged, this, &MapImageExporter::setDisablePreviewUpdates); -#else - connect(ui->checkBox_Objects, &QCheckBox::stateChanged, this, &MapImageExporter::setShowObjects); - connect(ui->checkBox_Warps, &QCheckBox::stateChanged, this, &MapImageExporter::setShowWarps); - connect(ui->checkBox_BGs, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBgs); - connect(ui->checkBox_Triggers, &QCheckBox::stateChanged, this, &MapImageExporter::setShowTriggers); - connect(ui->checkBox_HealLocations, &QCheckBox::stateChanged, this, &MapImageExporter::setShowHealLocations); - connect(ui->checkBox_AllEvents, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllEvents); - connect(ui->checkBox_ConnectionUp, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionUp); - connect(ui->checkBox_ConnectionDown, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionDown); - connect(ui->checkBox_ConnectionLeft, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionLeft); - connect(ui->checkBox_ConnectionRight, &QCheckBox::stateChanged, this, &MapImageExporter::setShowConnectionRight); - connect(ui->checkBox_AllConnections, &QCheckBox::stateChanged, this, &MapImageExporter::setShowAllConnections); - connect(ui->checkBox_Collision, &QCheckBox::stateChanged, this, &MapImageExporter::setShowCollision); - connect(ui->checkBox_Grid, &QCheckBox::stateChanged, this, &MapImageExporter::setShowGrid); - connect(ui->checkBox_Border, &QCheckBox::stateChanged, this, &MapImageExporter::setShowBorder); - connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewScaling); - connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::stateChanged, this, &MapImageExporter::setDisablePreviewUpdates); -#endif + connect(ui->checkBox_Objects, &QCheckBox::toggled, this, &MapImageExporter::setShowObjects); + connect(ui->checkBox_Warps, &QCheckBox::toggled, this, &MapImageExporter::setShowWarps); + connect(ui->checkBox_BGs, &QCheckBox::toggled, this, &MapImageExporter::setShowBgs); + connect(ui->checkBox_Triggers, &QCheckBox::toggled, this, &MapImageExporter::setShowTriggers); + connect(ui->checkBox_HealLocations, &QCheckBox::toggled, this, &MapImageExporter::setShowHealLocations); + connect(ui->checkBox_AllEvents, &QCheckBox::toggled, this, &MapImageExporter::setShowAllEvents); + connect(ui->checkBox_ConnectionUp, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionUp); + connect(ui->checkBox_ConnectionDown, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionDown); + connect(ui->checkBox_ConnectionLeft, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionLeft); + connect(ui->checkBox_ConnectionRight, &QCheckBox::toggled, this, &MapImageExporter::setShowConnectionRight); + connect(ui->checkBox_AllConnections, &QCheckBox::toggled, this, &MapImageExporter::setShowAllConnections); + connect(ui->checkBox_Collision, &QCheckBox::toggled, this, &MapImageExporter::setShowCollision); + connect(ui->checkBox_Grid, &QCheckBox::toggled, this, &MapImageExporter::setShowGrid); + connect(ui->checkBox_Border, &QCheckBox::toggled, this, &MapImageExporter::setShowBorder); + connect(ui->checkBox_DisablePreviewScaling, &QCheckBox::toggled, this, &MapImageExporter::setDisablePreviewScaling); + connect(ui->checkBox_DisablePreviewUpdates, &QCheckBox::toggled, this, &MapImageExporter::setDisablePreviewUpdates); ui->graphicsView_Preview->setFocus(); } @@ -755,127 +736,123 @@ void MapImageExporter::setConnectionDirectionEnabled(const QString &dir, bool en } } -void MapImageExporter::setShowCollision(CheckState state) { - m_settings.showCollision = (state == Qt::Checked); +void MapImageExporter::setShowCollision(bool checked) { + m_settings.showCollision = checked; updatePreview(); } -void MapImageExporter::setShowGrid(CheckState state) { - m_settings.showGrid = (state == Qt::Checked); +void MapImageExporter::setShowGrid(bool checked) { + m_settings.showGrid = checked; updatePreview(); } -void MapImageExporter::setShowBorder(CheckState state) { - m_settings.showBorder = (state == Qt::Checked); +void MapImageExporter::setShowBorder(bool checked) { + m_settings.showBorder = checked; updatePreview(); } -void MapImageExporter::setShowObjects(CheckState state) { - setEventGroupEnabled(Event::Group::Object, state == Qt::Checked); +void MapImageExporter::setShowObjects(bool checked) { + setEventGroupEnabled(Event::Group::Object, checked); updatePreview(); } -void MapImageExporter::setShowWarps(CheckState state) { - setEventGroupEnabled(Event::Group::Warp, state == Qt::Checked); +void MapImageExporter::setShowWarps(bool checked) { + setEventGroupEnabled(Event::Group::Warp, checked); updatePreview(); } -void MapImageExporter::setShowBgs(CheckState state) { - setEventGroupEnabled(Event::Group::Bg, state == Qt::Checked); +void MapImageExporter::setShowBgs(bool checked) { + setEventGroupEnabled(Event::Group::Bg, checked); updatePreview(); } -void MapImageExporter::setShowTriggers(CheckState state) { - setEventGroupEnabled(Event::Group::Coord, state == Qt::Checked); +void MapImageExporter::setShowTriggers(bool checked) { + setEventGroupEnabled(Event::Group::Coord, checked); updatePreview(); } -void MapImageExporter::setShowHealLocations(CheckState state) { - setEventGroupEnabled(Event::Group::Heal, state == Qt::Checked); +void MapImageExporter::setShowHealLocations(bool checked) { + setEventGroupEnabled(Event::Group::Heal, checked); updatePreview(); } // Shortcut setting for enabling all events -void MapImageExporter::setShowAllEvents(CheckState state) { - bool on = (state == Qt::Checked); - +void MapImageExporter::setShowAllEvents(bool checked) { const QSignalBlocker b_Objects(ui->checkBox_Objects); - ui->checkBox_Objects->setChecked(on); - ui->checkBox_Objects->setDisabled(on); - setEventGroupEnabled(Event::Group::Object, on); + ui->checkBox_Objects->setChecked(checked); + ui->checkBox_Objects->setDisabled(checked); + setEventGroupEnabled(Event::Group::Object, checked); const QSignalBlocker b_Warps(ui->checkBox_Warps); - ui->checkBox_Warps->setChecked(on); - ui->checkBox_Warps->setDisabled(on); - setEventGroupEnabled(Event::Group::Warp, on); + ui->checkBox_Warps->setChecked(checked); + ui->checkBox_Warps->setDisabled(checked); + setEventGroupEnabled(Event::Group::Warp, checked); const QSignalBlocker b_BGs(ui->checkBox_BGs); - ui->checkBox_BGs->setChecked(on); - ui->checkBox_BGs->setDisabled(on); - setEventGroupEnabled(Event::Group::Bg, on); + ui->checkBox_BGs->setChecked(checked); + ui->checkBox_BGs->setDisabled(checked); + setEventGroupEnabled(Event::Group::Bg, checked); const QSignalBlocker b_Triggers(ui->checkBox_Triggers); - ui->checkBox_Triggers->setChecked(on); - ui->checkBox_Triggers->setDisabled(on); - setEventGroupEnabled(Event::Group::Coord, on); + ui->checkBox_Triggers->setChecked(checked); + ui->checkBox_Triggers->setDisabled(checked); + setEventGroupEnabled(Event::Group::Coord, checked); const QSignalBlocker b_HealLocations(ui->checkBox_HealLocations); - ui->checkBox_HealLocations->setChecked(on); - ui->checkBox_HealLocations->setDisabled(on); - setEventGroupEnabled(Event::Group::Heal, on); + ui->checkBox_HealLocations->setChecked(checked); + ui->checkBox_HealLocations->setDisabled(checked); + setEventGroupEnabled(Event::Group::Heal, checked); updatePreview(); } -void MapImageExporter::setShowConnectionUp(CheckState state) { - setConnectionDirectionEnabled("up", state == Qt::Checked); +void MapImageExporter::setShowConnectionUp(bool checked) { + setConnectionDirectionEnabled("up", checked); updatePreview(); } -void MapImageExporter::setShowConnectionDown(CheckState state) { - setConnectionDirectionEnabled("down", state == Qt::Checked); +void MapImageExporter::setShowConnectionDown(bool checked) { + setConnectionDirectionEnabled("down", checked); updatePreview(); } -void MapImageExporter::setShowConnectionLeft(CheckState state) { - setConnectionDirectionEnabled("left", state == Qt::Checked); +void MapImageExporter::setShowConnectionLeft(bool checked) { + setConnectionDirectionEnabled("left", checked); updatePreview(); } -void MapImageExporter::setShowConnectionRight(CheckState state) { - setConnectionDirectionEnabled("right", state == Qt::Checked); +void MapImageExporter::setShowConnectionRight(bool checked) { + setConnectionDirectionEnabled("right", checked); updatePreview(); } // Shortcut setting for enabling all connection directions -void MapImageExporter::setShowAllConnections(CheckState state) { - bool on = (state == Qt::Checked); - +void MapImageExporter::setShowAllConnections(bool checked) { const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); - ui->checkBox_ConnectionUp->setChecked(on); - ui->checkBox_ConnectionUp->setDisabled(on); - setConnectionDirectionEnabled("up", on); + ui->checkBox_ConnectionUp->setChecked(checked); + ui->checkBox_ConnectionUp->setDisabled(checked); + setConnectionDirectionEnabled("up", checked); const QSignalBlocker b_Down(ui->checkBox_ConnectionDown); - ui->checkBox_ConnectionDown->setChecked(on); - ui->checkBox_ConnectionDown->setDisabled(on); - setConnectionDirectionEnabled("down", on); + ui->checkBox_ConnectionDown->setChecked(checked); + ui->checkBox_ConnectionDown->setDisabled(checked); + setConnectionDirectionEnabled("down", checked); const QSignalBlocker b_Left(ui->checkBox_ConnectionLeft); - ui->checkBox_ConnectionLeft->setChecked(on); - ui->checkBox_ConnectionLeft->setDisabled(on); - setConnectionDirectionEnabled("left", on); + ui->checkBox_ConnectionLeft->setChecked(checked); + ui->checkBox_ConnectionLeft->setDisabled(checked); + setConnectionDirectionEnabled("left", checked); const QSignalBlocker b_Right(ui->checkBox_ConnectionRight); - ui->checkBox_ConnectionRight->setChecked(on); - ui->checkBox_ConnectionRight->setDisabled(on); - setConnectionDirectionEnabled("right", on); + ui->checkBox_ConnectionRight->setChecked(checked); + ui->checkBox_ConnectionRight->setDisabled(checked); + setConnectionDirectionEnabled("right", checked); updatePreview(); } -void MapImageExporter::setDisablePreviewScaling(CheckState state) { - m_settings.disablePreviewScaling = (state == Qt::Checked); +void MapImageExporter::setDisablePreviewScaling(bool checked) { + m_settings.disablePreviewScaling = checked; if (m_settings.disablePreviewScaling) { ui->graphicsView_Preview->resetTransform(); } else { @@ -883,8 +860,8 @@ void MapImageExporter::setDisablePreviewScaling(CheckState state) { } } -void MapImageExporter::setDisablePreviewUpdates(CheckState state) { - m_settings.disablePreviewUpdates = (state == Qt::Checked); +void MapImageExporter::setDisablePreviewUpdates(bool checked) { + m_settings.disablePreviewUpdates = checked; if (m_settings.disablePreviewUpdates) { if (m_timelapseMovie) { m_timelapseMovie->stop(); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index b1c61151..29521974 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -45,16 +45,11 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->comboBox_BaseGameVersion, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::promptRestoreDefaults); connect(ui->comboBox_AttributesSize, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updateAttributeLimits); connect(ui->comboBox_IconSpecies, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::updatePokemonIconPath); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { -#else - connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::stateChanged, [this](int state) { -#endif - bool customSize = (state == Qt::Checked); + connect(ui->checkBox_EnableCustomBorderSize, &QCheckBox::toggled, [this](bool enabled) { // When switching between the spin boxes or line edit for border metatiles we set // the newly-shown UI using the values from the hidden UI. - this->setBorderMetatileIds(customSize, this->getBorderMetatileIds(!customSize)); - this->setBorderMetatilesUi(customSize); + this->setBorderMetatileIds(enabled, this->getBorderMetatileIds(!enabled)); + this->setBorderMetatilesUi(enabled); }); connect(ui->button_AddWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(true); }); connect(ui->button_RemoveWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(false); }); @@ -86,11 +81,7 @@ void ProjectSettingsEditor::connectSignals() { connect(combo, &QComboBox::currentTextChanged, this, &ProjectSettingsEditor::markEdited); } for (auto checkBox : ui->centralwidget->findChildren()) -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(checkBox, &QCheckBox::checkStateChanged, this, &ProjectSettingsEditor::markEdited); -#else - connect(checkBox, &QCheckBox::stateChanged, this, &ProjectSettingsEditor::markEdited); -#endif + connect(checkBox, &QCheckBox::toggled, this, &ProjectSettingsEditor::markEdited); for (auto radioButton : ui->centralwidget->findChildren()) connect(radioButton, &QRadioButton::toggled, this, &ProjectSettingsEditor::markEdited); for (auto lineEdit : ui->centralwidget->findChildren()) diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index 9495395f..32d0be75 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -27,13 +27,8 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->ui->setupUi(this); this->project = project; connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_tileHFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileHFlip); - connect(ui->checkBox_tileVFlip, &QCheckBox::checkStateChanged, this, &RegionMapEditor::setTileVFlip); -#else - connect(ui->checkBox_tileHFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileHFlip); - connect(ui->checkBox_tileVFlip, &QCheckBox::stateChanged, this, &RegionMapEditor::setTileVFlip); -#endif + connect(ui->checkBox_tileHFlip, &QCheckBox::toggled, this, &RegionMapEditor::setTileHFlip); + connect(ui->checkBox_tileVFlip, &QCheckBox::toggled, this, &RegionMapEditor::setTileVFlip); this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); @@ -1043,14 +1038,14 @@ void RegionMapEditor::on_spinBox_tilePalette_valueChanged(int value) { this->mapsquare_selector_item->selectPalette(value); } -void RegionMapEditor::setTileHFlip(CheckState state) { +void RegionMapEditor::setTileHFlip(bool enabled) { if (this->mapsquare_selector_item) - this->mapsquare_selector_item->selectHFlip(state == Qt::Checked); + this->mapsquare_selector_item->selectHFlip(enabled); } -void RegionMapEditor::setTileVFlip(CheckState state) { +void RegionMapEditor::setTileVFlip(bool enabled) { if (this->mapsquare_selector_item) - this->mapsquare_selector_item->selectVFlip(state == Qt::Checked); + this->mapsquare_selector_item->selectVFlip(enabled); } void RegionMapEditor::on_action_RegionMap_Resize_triggered() { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 9281bd28..3d610fb9 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -26,14 +26,8 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) setAttribute(Qt::WA_DeleteOnClose); setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); ui->setupUi(this); - -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_xFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setXFlip); - connect(ui->checkBox_yFlip, &QCheckBox::checkStateChanged, this, &TilesetEditor::setYFlip); -#else - connect(ui->checkBox_xFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setXFlip); - connect(ui->checkBox_yFlip, &QCheckBox::stateChanged, this, &TilesetEditor::setYFlip); -#endif + connect(ui->checkBox_xFlip, &QCheckBox::toggled, this, &TilesetEditor::setXFlip); + connect(ui->checkBox_yFlip, &QCheckBox::toggled, this, &TilesetEditor::setYFlip); this->tileXFlip = ui->checkBox_xFlip->isChecked(); this->tileYFlip = ui->checkBox_yFlip->isChecked(); @@ -553,17 +547,17 @@ void TilesetEditor::on_spinBox_paletteSelector_valueChanged(int paletteId) this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::setXFlip(CheckState state) +void TilesetEditor::setXFlip(bool enabled) { - this->tileXFlip = (state == Qt::Checked); + this->tileXFlip = enabled; this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); } -void TilesetEditor::setYFlip(CheckState state) +void TilesetEditor::setYFlip(bool enabled) { - this->tileYFlip = (state == Qt::Checked); + this->tileYFlip = enabled; this->tileSelector->setTileFlips(this->tileXFlip, this->tileYFlip); this->drawSelectedTiles(); this->metatileLayersItem->clearLastModifiedCoords(); diff --git a/src/ui/updatepromoter.cpp b/src/ui/updatepromoter.cpp index 27c37b0a..8afc8d9c 100644 --- a/src/ui/updatepromoter.cpp +++ b/src/ui/updatepromoter.cpp @@ -19,12 +19,8 @@ UpdatePromoter::UpdatePromoter(QWidget *parent, NetworkAccessManager *manager) // Set up "Do not alert me" check box this->updatePreferences(); ui->checkBox_StopAlerts->setVisible(false); -#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)) - connect(ui->checkBox_StopAlerts, &QCheckBox::checkStateChanged, [this](Qt::CheckState state) { -#else - connect(ui->checkBox_StopAlerts, &QCheckBox::stateChanged, [this](int state) { -#endif - porymapConfig.checkForUpdates = (state != Qt::Checked); + connect(ui->checkBox_StopAlerts, &QCheckBox::toggled, [this](bool stopAlerts) { + porymapConfig.checkForUpdates = !stopAlerts; emit this->changedPreferences(); }); From 35d5851a8f5e6990e5d14fbe26b95d14f2ecce44 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 21:32:42 -0400 Subject: [PATCH 19/71] 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 20/71] 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 21/71] 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 c54d875d3ca5c3a7777591dd0b052c6a0b374612 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 13 Apr 2025 22:43:38 -0400 Subject: [PATCH 22/71] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d64777f..984f3879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Redesigned the new map dialog, including better error checking and a collapsible section for header data. - New maps/layouts are no longer saved automatically, and can be fully discarded by closing without saving. - Map groups and ``MAPSEC`` names specified when creating a new map will be added automatically if they don't already exist. +- Custom fields in JSON files that Porymap writes are no longer discarded. - Edits to map connections now have Undo/Redo and can be viewed in exported timelapses. - 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. From e19932b90ced2c7cc2e5fdb168a1490bc260e2cc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 14 Apr 2025 11:43:54 -0400 Subject: [PATCH 23/71] 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 24/71] 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 25/71] 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 26/71] 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 27/71] 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 28/71] 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 29/71] 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 30/71] 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 31/71] 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 32/71] 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 33/71] 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 34/71] 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 35/71] 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 d992a29e3646df0d52885351770658a5d192bd42 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 17 Apr 2025 18:00:38 -0400 Subject: [PATCH 36/71] Add input fields for LOCALID --- include/core/events.h | 8 ++- include/core/map.h | 1 + include/ui/eventframes.h | 4 ++ src/core/map.cpp | 25 +++++++++ src/mainwindow.cpp | 9 +-- src/project.cpp | 5 ++ src/ui/eventframes.cpp | 116 +++++++++++++++++++++++++++++++++------ 7 files changed, 141 insertions(+), 27 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 3963e022..4a00c501 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -79,9 +79,13 @@ public: None, }; - // all event groups except warps have IDs that start at 1 + // Normally we refer to events using their index in the list of that group's events. + // Object events often get referred to with a special "local ID", which is really just the index + 1. + // We use this local ID number in the index spinner for object events instead of the actual index. + // This distinction is only really important for object and warp events, because these are normally + // the only two groups of events that need to be explicitly referred to. static int getIndexOffset(Event::Group group) { - return (group == Event::Group::Warp) ? 0 : 1; + return (group == Event::Group::Object) ? 1 : 0; } static Event::Group typeToGroup(Event::Type type) { diff --git a/include/core/map.h b/include/core/map.h index 07fce0e5..1f8b5da5 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -77,6 +77,7 @@ public: QList getEvents(Event::Group group = Event::Group::None) const; Event* getEvent(Event::Group group, int index) const; Event* getEvent(Event::Group group, const QString &idName) const; + QStringList getEventIdNames(Event::Group group) const; int getNumEvents(Event::Group group = Event::Group::None) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilePath() const; diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index a5ae6765..0cbce712 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -58,6 +58,7 @@ protected: bool connected = false; void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); private: Event *event; @@ -78,6 +79,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_local_id; NoScrollComboBox *combo_sprite; NoScrollComboBox *combo_movement; NoScrollSpinBox *spinner_radius_x; @@ -108,6 +110,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_local_id; NoScrollComboBox *combo_sprite; NoScrollComboBox *combo_target_id; NoScrollComboBox *combo_target_map; @@ -131,6 +134,7 @@ public: virtual void populate(Project *project) override; public: + QLineEdit *line_edit_id; NoScrollComboBox *combo_dest_map; NoScrollComboBox *combo_dest_warp; QPushButton *warning; diff --git a/src/core/map.cpp b/src/core/map.cpp index c61c1e8b..caff07db 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -212,6 +212,31 @@ Event* Map::getEvent(Event::Group group, const QString &idName) const { return nullptr; } +// Returns a list of ID names for the given event group (or all events, if no group is given). +// For events with no explicit ID name, their index string is given instead. +QStringList Map::getEventIdNames(Event::Group group) const { + QList groups; + if (group == Event::Group::None) { + groups = Event::groups(); + } else { + groups.append(group); + } + + QStringList idNames; + for (const auto &group : groups) { + const auto events = m_events[group]; + int indexOffset = Event::getIndexOffset(group); + for (int i = 0; i < events.length(); i++) { + QString idName = events.at(i)->getIdName(); + if (idName.isEmpty()) { + idName = QString::number(i + indexOffset); + } + idNames.append(idName); + } + } + return idNames; +} + int Map::getNumEvents(Event::Group group) const { if (group == Event::Group::None) { // Total number of events diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b8de0b63..f72c2c89 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1112,13 +1112,8 @@ void MainWindow::openEventMap(Event *sourceEvent) { return; // Map opened successfully, now try to select the targeted event on that map. - Event* targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName); - if (targetEvent) { - this->editor->selectMapEvent(targetEvent); - } else { - // Can still warp to this map, but can't select the specified event - logWarn(QString("%1 '%2' doesn't exist on map '%3'").arg(Event::groupToString(targetEventGroup)).arg(targetEventIdName).arg(targetMapName)); - } + Event *targetEvent = this->editor->map->getEvent(targetEventGroup, targetEventIdName); + this->editor->selectMapEvent(targetEvent); } void MainWindow::displayMapProperties() { diff --git a/src/project.cpp b/src/project.cpp index e02db858..300af774 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -161,6 +161,11 @@ void Project::clearTilesetCache() { } Map* Project::loadMap(const QString &mapName) { + if (mapName == getDynamicMapName()) { + // Silently ignored, caller is expected to handle this if they want this to be an error. + return nullptr; + } + Map* map = this->maps.value(mapName); if (!map) { logError(QString("Unknown map name '%1'.").arg(mapName)); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 3b5d71fa..cba5a78d 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -173,6 +173,11 @@ void EventFrame::setActive(bool active) { this->blockSignals(!active); } +// TODO: For populateScriptDropdown and populateIdNameDropdown, it would be nice to connect them to the source of their items +// and update them automatically when the source changes, i.e. for the script dropdown, invalidating the list when the +// the script file changes, and for the ID name dropdown invalidating the list when an event ID name chnages (or perhaps +// more simply invalidating it when the target map is opened). + void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. if (!this->event->getMap()) @@ -201,10 +206,31 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } +void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) { + if (!project->mapNames.contains(mapName)) + return; + + Map *map = project->loadMap(mapName); + if (!map) + return; + + combo->clear(); + combo->addItems(map->getEventIdNames(group)); +} + void ObjectFrame::setup() { EventFrame::setup(); + // local id + QFormLayout *l_form_local_id = new QFormLayout(); + this->line_edit_local_id = new QLineEdit(this); + this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" + "If no game is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setPlaceholderText("LOCALID_MY_NPC"); + l_form_local_id->addRow("Local ID", this->line_edit_local_id); + this->layout_contents->addLayout(l_form_local_id); + // sprite combo QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); @@ -290,6 +316,13 @@ void ObjectFrame::connectSignals(MainWindow *window) { EventFrame::connectSignals(window); + // local id + this->line_edit_local_id->disconnect(); + connect(this->line_edit_local_id, &QLineEdit::textChanged, [this](const QString &text) { + this->object->setIdName(text); + this->object->modify(); + }); + // sprite update this->combo_sprite->disconnect(); connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) { @@ -361,6 +394,9 @@ void ObjectFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); + // local id + this->line_edit_local_id->setText(this->object->getIdName()); + // sprite this->combo_sprite->setTextItem(this->object->getGfx()); @@ -407,9 +443,21 @@ void CloneObjectFrame::setup() { this->spinner_z->setEnabled(false); + // local id + QFormLayout *l_form_local_id = new QFormLayout(); + this->line_edit_local_id = new QLineEdit(this); + this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" + "If no game is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setPlaceholderText("LOCALID_MY_CLONE_NPC"); + l_form_local_id->addRow("Local ID", this->line_edit_local_id); + this->layout_contents->addLayout(l_form_local_id); + // sprite combo (edits disabled) QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); + this->combo_sprite->setToolTip("The sprite graphics to use for this object. This is updated automatically\n" + "to match the target object, and so can't be edited. By default the games\n" + "will get the graphics directly from the target object, so this field is ignored."); l_form_sprite->addRow("Sprite", this->combo_sprite); this->combo_sprite->setEnabled(false); this->layout_contents->addLayout(l_form_sprite); @@ -424,8 +472,7 @@ void CloneObjectFrame::setup() { // clone local id combo QFormLayout *l_form_dest_id = new QFormLayout(); this->combo_target_id = new NoScrollComboBox(this); - // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field - this->combo_target_id->setToolTip("event_object ID of the object being cloned."); + this->combo_target_id->setToolTip("The Local ID name or number of the object being cloned."); l_form_dest_id->addRow("Target Local ID", this->combo_target_id); this->layout_contents->addLayout(l_form_dest_id); @@ -437,18 +484,26 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; + + // local id + this->line_edit_local_id->disconnect(); + connect(this->line_edit_local_id, &QLineEdit::textChanged, [this](const QString &text) { + this->clone->setIdName(text); + this->clone->modify(); + }); // update icon displayed in frame with target connect(this->clone->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); // target map this->combo_target_map->disconnect(); - connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->clone->setTargetMap(text); + connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + this->clone->setTargetMap(mapName); this->clone->getPixmapItem()->updatePixmap(); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); - // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. + populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); }); // target id @@ -467,6 +522,9 @@ void CloneObjectFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); + // local id + this->line_edit_local_id->setText(this->clone->getIdName()); + // sprite this->combo_sprite->setCurrentText(this->clone->getGfx()); @@ -484,12 +542,21 @@ void CloneObjectFrame::populate(Project *project) { EventFrame::populate(project); this->combo_target_map->addItems(project->mapNames); - // TODO: Populate combo_target_id with local IDs from target map. + populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object); } void WarpFrame::setup() { EventFrame::setup(); + // ID + QFormLayout *l_form_id = new QFormLayout(); + this->line_edit_id = new QLineEdit(this); + this->line_edit_id->setToolTip("An optional, unique name to use to refer to this warp from other warps.\n" + "If no game is given you can refer to this warp using its 'warp id' number."); + this->line_edit_id->setPlaceholderText("WARP_ID_MY_WARP"); + l_form_id->addRow("ID", this->line_edit_id); + this->layout_contents->addLayout(l_form_id); + // desination map combo QFormLayout *l_form_dest_map = new QFormLayout(); this->combo_dest_map = new NoScrollComboBox(this); @@ -524,13 +591,21 @@ void WarpFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; + + // id + this->line_edit_id->disconnect(); + connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { + this->warp->setIdName(text); + this->warp->modify(); + }); // dest map this->combo_dest_map->disconnect(); - connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->warp->setDestinationMap(text); + connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + this->warp->setDestinationMap(mapName); this->warp->modify(); - // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. + populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp); }); // dest id @@ -551,6 +626,9 @@ void WarpFrame::initialize() { const QSignalBlocker blocker(this); EventFrame::initialize(); + // id + this->line_edit_id->setText(this->warp->getIdName()); + // dest map this->combo_dest_map->setTextItem(this->warp->getDestinationMap()); @@ -565,7 +643,7 @@ void WarpFrame::populate(Project *project) { EventFrame::populate(project); this->combo_dest_map->addItems(project->mapNames); - // TODO: Populate combo_dest_warp with local IDs from target map. + populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp); } @@ -965,9 +1043,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); - // TODO: Once object events have a real local ID input field, this tool tip should be updated to reflect the name of that field - this->combo_respawn_npc->setToolTip("event_object ID of the NPC the player interacts with\n" - "upon respawning after whiteout."); + this->combo_respawn_npc->setToolTip("The Local ID name or number of the NPC the player\n" + "interacts with upon respawning after whiteout."); l_form_respawn_npc->addRow("Respawn NPC", this->combo_respawn_npc); this->layout_contents->addWidget(hideable_respawn_npc); @@ -979,6 +1056,7 @@ void HealLocationFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; this->line_edit_id->disconnect(); connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { @@ -987,10 +1065,10 @@ void HealLocationFrame::connectSignals(MainWindow *window) { }); this->combo_respawn_map->disconnect(); - connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &text) { - this->healLocation->setRespawnMapName(text); + connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + this->healLocation->setRespawnMapName(mapName); this->healLocation->modify(); - // TODO: If this field changes to the name of a valid map then the available items in the ID combo box should be refreshed. + populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object); }); this->combo_respawn_npc->disconnect(); @@ -1021,6 +1099,8 @@ void HealLocationFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_respawn_map->addItems(project->mapNames); - // TODO: We should dynamically populate combo_respawn_npc with the local IDs of the respawn_map + if (projectConfig.healLocationRespawnDataEnabled) { + this->combo_respawn_map->addItems(project->mapNames); + populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object); + } } From 0f4028ab928c3e639b4d76440fbb5084c2eca1dd Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 18 Apr 2025 12:08:30 -0400 Subject: [PATCH 37/71] Add missing event frame invalidation --- include/mainwindow.h | 3 +- include/ui/eventframes.h | 7 +++ src/mainwindow.cpp | 14 ++---- src/ui/eventframes.cpp | 99 +++++++++++++++++++++++++++------------- 4 files changed, 79 insertions(+), 44 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 832d51ee..4d33847d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -197,8 +197,7 @@ private slots: void onMapLoaded(Map *map); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); - void markMapEdited(); - void markSpecificMapEdited(Map*); + void markMapEdited(Map*); void markLayoutEdited(); void on_actionNew_Tileset_triggered(); diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 0cbce712..09eae50b 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -57,6 +57,7 @@ protected: bool initialized = false; bool connected = false; + void populateDropdown(NoScrollComboBox * combo, const QStringList &items); void populateScriptDropdown(NoScrollComboBox * combo, Project * project); void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); @@ -117,6 +118,8 @@ public: private: CloneObjectEvent *clone; + + void tryInvalidateIdDropdown(Map *map); }; @@ -141,6 +144,8 @@ public: private: WarpEvent *warp; + + void tryInvalidateIdDropdown(Map *map); }; @@ -279,6 +284,8 @@ public: private: HealLocationEvent *healLocation; + + void tryInvalidateIdDropdown(Map *map); }; #endif // EVENTRAMES_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f72c2c89..e44b967d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -343,7 +343,7 @@ void MainWindow::initEditor() { connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); connect(this->editor, &Editor::openEventMap, this, &MainWindow::openEventMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); - connect(this->editor, &Editor::wildMonTableEdited, this, &MainWindow::markMapEdited); + connect(this->editor, &Editor::wildMonTableEdited, [this] { markMapEdited(this->editor->map); }); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->newEventToolButton, &NewEventToolButton::newEventAdded, this->editor, &Editor::addNewEvent); @@ -523,11 +523,7 @@ void MainWindow::updateWindowTitle() { } } -void MainWindow::markMapEdited() { - if (editor) markSpecificMapEdited(editor->map); -} - -void MainWindow::markSpecificMapEdited(Map* map) { +void MainWindow::markMapEdited(Map* map) { if (!map) return; map->setHasUnsavedDataChanges(true); @@ -949,8 +945,6 @@ bool MainWindow::setMap(QString map_name) { updateMapList(); resetMapListFilters(); - connect(editor->map, &Map::modified, this, &MainWindow::markMapEdited, Qt::UniqueConnection); - // If the map's MAPSEC / layout changes, update the map's position in the map list. // These are doing more work than necessary, rather than rebuilding the entire list they should find and relocate the appropriate row. connect(editor->map, &Map::layoutChanged, this, &MainWindow::rebuildMapList_Layouts, Qt::UniqueConnection); @@ -1153,7 +1147,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te } this->editor->map->setLayout(layout); setMap(this->editor->map->name()); - markMapEdited(); + markMapEdited(this->editor->map); } void MainWindow::onLayoutSelectorEditingFinished() { @@ -2520,7 +2514,7 @@ void MainWindow::onOpenConnectedMap(MapConnection *connection) { } void MainWindow::onMapLoaded(Map *map) { - connect(map, &Map::modified, [this, map] { this->markSpecificMapEdited(map); }); + connect(map, &Map::modified, [this, map] { markMapEdited(map); }); } void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index cba5a78d..40b2cdf2 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -173,10 +173,16 @@ void EventFrame::setActive(bool active) { this->blockSignals(!active); } -// TODO: For populateScriptDropdown and populateIdNameDropdown, it would be nice to connect them to the source of their items -// and update them automatically when the source changes, i.e. for the script dropdown, invalidating the list when the -// the script file changes, and for the ID name dropdown invalidating the list when an event ID name chnages (or perhaps -// more simply invalidating it when the target map is opened). +void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &items) { + // Set the items in the combo box. This may be called after the frame is initialized + // if the frame needs to be repopulated, so ensure the text in the combo is preserved + // and that we don't accidentally fire 'currentTextChanged'. + const QSignalBlocker b(combo); + const QString savedText = combo->currentText(); + combo->clear(); + combo->addItems(items); + combo->setCurrentText(savedText); +} void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. @@ -184,13 +190,14 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj return; QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup()); - combo->addItems(scripts); + populateDropdown(combo, scripts); // Depending on the settings, the autocomplete may also contain all global scripts. if (porymapConfig.loadAllEventScripts) { project->insertGlobalScriptLabels(scripts); } + // Note: Because 'combo' is the parent, the old QCompleter will be deleted when a new one is set. auto completer = new QCompleter(scripts, combo); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel); @@ -203,6 +210,8 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj combo->setCompleter(completer); // If the project changes the script labels, update the EventFrame. + // TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes). + // This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes. connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } @@ -211,11 +220,7 @@ void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * proj return; Map *map = project->loadMap(mapName); - if (!map) - return; - - combo->clear(); - combo->addItems(map->getEventIdNames(group)); + if (map) populateDropdown(combo, map->getEventIdNames(group)); } @@ -428,12 +433,11 @@ void ObjectFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_sprite->addItems(project->gfxDefines.keys()); - this->combo_movement->addItems(project->movementTypes); - this->combo_flag->addItems(project->flagNames); - this->combo_trainer_type->addItems(project->trainerTypes); - - this->populateScriptDropdown(this->combo_script, project); + populateDropdown(this->combo_sprite, project->gfxDefines.keys()); + populateDropdown(this->combo_movement, project->movementTypes); + populateDropdown(this->combo_flag, project->flagNames); + populateDropdown(this->combo_trainer_type, project->trainerTypes); + populateScriptDropdown(this->combo_script, project); } @@ -505,6 +509,7 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { this->clone->modify(); populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); }); + connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); // target id this->combo_target_id->disconnect(); @@ -514,6 +519,17 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) { + // If the clone's target map is opened then the names in this frame's ID dropdown may be changed. + // Make sure we update the frame next time it's opened. + if (map && this->clone && map->name() == this->clone->getTargetMap()) { + invalidateValues(); + } } void CloneObjectFrame::initialize() { @@ -541,7 +557,7 @@ void CloneObjectFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_target_map->addItems(project->mapNames); + populateDropdown(this->combo_target_map, project->mapNames); populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object); } @@ -607,6 +623,7 @@ void WarpFrame::connectSignals(MainWindow *window) { this->warp->modify(); populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp); }); + connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); // dest id this->combo_dest_warp->disconnect(); @@ -618,6 +635,17 @@ void WarpFrame::connectSignals(MainWindow *window) { // warning this->warning->disconnect(); connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void WarpFrame::tryInvalidateIdDropdown(Map *map) { + // If the warps's target map is opened then the names in this frame's ID dropdown may be changed. + // Make sure we update the frame next time it's opened. + if (map && this->warp && map->name() == this->warp->getDestinationMap()) { + invalidateValues(); + } } void WarpFrame::initialize() { @@ -642,7 +670,7 @@ void WarpFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_dest_map->addItems(project->mapNames); + populateDropdown(this->combo_dest_map, project->mapNames); populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp); } @@ -726,10 +754,8 @@ void TriggerFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - // var combo - this->combo_var->addItems(project->varNames); - - this->populateScriptDropdown(this->combo_script, project); + populateDropdown(this->combo_var, project->varNames); + populateScriptDropdown(this->combo_script, project); } @@ -777,8 +803,7 @@ void WeatherTriggerFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - // weather - this->combo_weather->addItems(project->coordEventWeatherNames); + populateDropdown(this->combo_weather, project->coordEventWeatherNames); } @@ -844,10 +869,8 @@ void SignFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - // facing dir - this->combo_facing_dir->addItems(project->bgEventFacingDirections); - - this->populateScriptDropdown(this->combo_script, project); + populateDropdown(this->combo_facing_dir, project->bgEventFacingDirections); + populateScriptDropdown(this->combo_script, project); } @@ -958,8 +981,8 @@ void HiddenItemFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_item->addItems(project->itemNames); - this->combo_flag->addItems(project->flagNames); + populateDropdown(this->combo_item, project->itemNames); + populateDropdown(this->combo_flag, project->flagNames); } @@ -1010,7 +1033,7 @@ void SecretBaseFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - this->combo_base_id->addItems(project->secretBaseIds); + populateDropdown(this->combo_base_id, project->secretBaseIds); } @@ -1070,12 +1093,24 @@ void HealLocationFrame::connectSignals(MainWindow *window) { this->healLocation->modify(); populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object); }); + connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); this->combo_respawn_npc->disconnect(); connect(this->combo_respawn_npc, &QComboBox::currentTextChanged, [this](const QString &text) { this->healLocation->setRespawnNPC(text); this->healLocation->modify(); }); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void HealLocationFrame::tryInvalidateIdDropdown(Map *map) { + // If the heal locations's target map is opened then the names in this frame's ID dropdown may be changed. + // Make sure we update the frame next time it's opened. + if (map && this->healLocation && map->name() == this->healLocation->getRespawnMapName()) { + invalidateValues(); + } } void HealLocationFrame::initialize() { @@ -1100,7 +1135,7 @@ void HealLocationFrame::populate(Project *project) { EventFrame::populate(project); if (projectConfig.healLocationRespawnDataEnabled) { - this->combo_respawn_map->addItems(project->mapNames); + populateDropdown(this->combo_respawn_map, project->mapNames); populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object); } } From e26be84d9128a64842fa054ae21b4021e7274142 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 18 Apr 2025 13:00:17 -0400 Subject: [PATCH 38/71] Fix z value for events, separate EventPixmapItem from Editor --- include/core/events.h | 6 +- include/editor.h | 17 +++++ include/ui/eventpixmapitem.h | 43 +++++++------ src/core/events.cpp | 13 ++-- src/editor.cpp | 49 +++++++------- src/ui/connectionpixmapitem.cpp | 5 +- src/ui/eventframes.cpp | 19 +++--- src/ui/eventpixmapitem.cpp | 110 ++++++++++++++++++++------------ src/ui/mapimageexporter.cpp | 3 +- src/ui/movablerect.cpp | 1 - src/ui/resizelayoutpopup.cpp | 1 + 11 files changed, 159 insertions(+), 108 deletions(-) diff --git a/include/core/events.h b/include/core/events.h index 4a00c501..9da9b531 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -153,7 +153,7 @@ public: QJsonObject getCustomAttributes() const { return this->customAttributes; } void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } - virtual void loadPixmap(Project *project); + virtual QPixmap loadPixmap(Project *project); void setPixmap(QPixmap newPixmap) { this->pixmap = newPixmap; } QPixmap getPixmap() const { return this->pixmap; } @@ -233,7 +233,7 @@ public: virtual QSet getExpectedFields() override; - virtual void loadPixmap(Project *project) override; + virtual QPixmap loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } QString getGfx() const { return this->gfx; } @@ -300,7 +300,7 @@ public: virtual QSet getExpectedFields() override; - virtual void loadPixmap(Project *project) override; + virtual QPixmap loadPixmap(Project *project) override; void setTargetMap(QString newTargetMap) { this->targetMap = newTargetMap; } QString getTargetMap() const { return this->targetMap; } diff --git a/include/editor.h b/include/editor.h index 07a5dc68..fe01014f 100644 --- a/include/editor.h +++ b/include/editor.h @@ -117,6 +117,7 @@ public: void redrawAllEvents(); void redrawEvents(const QList &events); void redrawEventPixmapItem(EventPixmapItem *item); + void updateEventPixmapItemZValue(EventPixmapItem *item); qreal getEventOpacity(const Event *event) const; void updateCursorRectPos(int x, int y); @@ -182,6 +183,22 @@ public: static void openInTextEditor(const QString &path, int lineNum = 0); void setCollisionGraphics(); + enum ZValue { + MapBorder = -4, + MapConnectionInactive = -3, + MapConnectionActive = -2, + MapConnectionMask = -1, + + // Event pixmaps set their z value to be their y position on the map. + // Their y value is int16_t, so we have enough space to allocate the + // full range + 1 for the selected event (which should always be on top). + EventMinimum = 1, + EventMaximum = EventMinimum + 0x10000, + + Ruler, + ResizeLayoutPopup + }; + public slots: void openMapScripts() const; void openScript(const QString &scriptLabel) const; diff --git a/include/ui/eventpixmapitem.h b/include/ui/eventpixmapitem.h index c44fc6bf..a28232ba 100644 --- a/include/ui/eventpixmapitem.h +++ b/include/ui/eventpixmapitem.h @@ -10,38 +10,39 @@ #include "events.h" -class Editor; +class Project; class EventPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT public: - EventPixmapItem(QPixmap pixmap): QGraphicsPixmapItem(pixmap) {} - - EventPixmapItem(Event *event, Editor *editor) : QGraphicsPixmapItem(event->getPixmap()) { - this->event = event; - event->setPixmapItem(this); - this->editor = editor; - updatePosition(); - } + explicit EventPixmapItem(Event *event); - Event *event = nullptr; + void render(Project *project); + + bool isSelected() const { return m_selected; } + void setSelected(bool selected) { m_selected = selected; } + + Event * getEvent() const { return m_event; } - void updatePosition(); void move(int dx, int dy); + void moveTo(int x, int y); void moveTo(const QPoint &pos); - void emitPositionChanged(); - void updatePixmap(); private: - Editor *editor = nullptr; - QPoint lastPos; - bool active = false; - bool releaseSelectionQueued = false; + QPixmap m_basePixmap; + Event *const m_event = nullptr; + QPoint m_lastPos; + bool m_active = false; + bool m_selected = false; + bool m_releaseSelectionQueued = false; + + void updatePixelPosition(); signals: - void xChanged(int); - void yChanged(int); - void spriteChanged(const QPixmap &pixmap); + void xChanged(int x); + void yChanged(int y); + void posChanged(int x, int y); + void rendered(const QPixmap &pixmap); void selected(Event *event, bool toggle); void dragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition); void released(Event *event, const QPoint &position); @@ -51,7 +52,7 @@ protected: virtual void mousePressEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*) override; virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*) override; - virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(this->event); } + virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*) override { emit doubleClicked(m_event); } }; #endif // EVENTPIXMAPITEM_H diff --git a/src/core/events.cpp b/src/core/events.cpp index 694919c0..ad50a60e 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -114,9 +114,10 @@ QString Event::typeToString(Event::Type type) { return typeToStringMap.value(type); } -void Event::loadPixmap(Project *project) { +QPixmap Event::loadPixmap(Project *project) { this->pixmap = project->getEventPixmap(this->getEventGroup()); this->usesDefaultPixmap = true; + return this->pixmap; } @@ -225,13 +226,13 @@ QSet ObjectEvent::getExpectedFields() { return expectedFields; } -void ObjectEvent::loadPixmap(Project *project) { +QPixmap ObjectEvent::loadPixmap(Project *project) { this->pixmap = project->getEventPixmap(this->gfx, this->movement); if (!this->pixmap.isNull()) { this->usesDefaultPixmap = false; - } else { - Event::loadPixmap(project); + return this->pixmap; } + return Event::loadPixmap(project); } @@ -314,7 +315,7 @@ QSet CloneObjectEvent::getExpectedFields() { return expectedFields; } -void CloneObjectEvent::loadPixmap(Project *project) { +QPixmap CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone Map *clonedMap = project->loadMap(this->targetMap); Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; @@ -329,7 +330,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); } - ObjectEvent::loadPixmap(project); + return ObjectEvent::loadPixmap(project); } diff --git a/src/editor.cpp b/src/editor.cpp index 6230771a..ec87c6f3 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1484,7 +1484,7 @@ bool Editor::displayLayout() { scene->installEventFilter(filter); connect(filter, &MapSceneEventFilter::wheelZoom, this, &Editor::onWheelZoom); scene->installEventFilter(this->map_ruler); - this->map_ruler->setZValue(1000); + this->map_ruler->setZValue(ZValue::Ruler); scene->addItem(this->map_ruler); } @@ -1696,11 +1696,13 @@ void Editor::displayMapEvents() { EventPixmapItem *Editor::addEventPixmapItem(Event *event) { this->project->loadEventPixmap(event); - auto item = new EventPixmapItem(event, this); + auto item = new EventPixmapItem(event); connect(item, &EventPixmapItem::doubleClicked, this, &Editor::openEventMap); connect(item, &EventPixmapItem::dragged, this, &Editor::onEventDragged); connect(item, &EventPixmapItem::released, this, &Editor::onEventReleased); connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent); + connect(item, &EventPixmapItem::posChanged, [this, event] { updateWarpEventWarning(event); }); + connect(item, &EventPixmapItem::yChanged, [this, item] { updateEventPixmapItemZValue(item); }); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item; @@ -1781,6 +1783,7 @@ void Editor::maskNonVisibleConnectionTiles() { QBrush brush(ui->graphicsView_Map->palette().color(QPalette::Active, QPalette::Base)); connection_mask = scene->addPath(mask, pen, brush); + connection_mask->setZValue(ZValue::MapConnectionMask); } void Editor::clearMapBorder() { @@ -1806,7 +1809,7 @@ void Editor::displayMapBorder() { QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); item->setX(x * 16); item->setY(y * 16); - item->setZValue(-3); + item->setZValue(ZValue::MapBorder); scene->addItem(item); borderItems.append(item); } @@ -1977,36 +1980,36 @@ qreal Editor::getEventOpacity(const Event *event) const { } void Editor::redrawEventPixmapItem(EventPixmapItem *item) { - if (!item || !item->event) - return; + if (!item) return; + Event *event = item->getEvent(); + if (!event) return; - project->loadEventPixmap(item->event, true); - - QPixmap pixmap = item->event->getPixmap(); - if (pixmap.isNull()) - return; - - qreal zValue = item->event->getY(); if (this->editMode == EditMode::Events) { - if (this->selectedEvents.contains(item->event)) { - // Draw the selection rectangle - QPainter painter(&pixmap); - painter.setPen(Qt::magenta); - painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); - zValue++; - } item->setAcceptedMouseButtons(Qt::AllButtons); + item->setSelected(this->selectedEvents.contains(event)); } else { // Can't interact with event pixmaps outside of event editing mode. // We could do setEnabled(false), but rather than ignoring the mouse events this // would reject them, which would prevent painting on the map behind the events. item->setAcceptedMouseButtons(Qt::NoButton); + item->setSelected(false); } - item->setPixmap(pixmap); - item->setZValue(zValue); - item->setOpacity(getEventOpacity(item->event)); + updateEventPixmapItemZValue(item); + item->setOpacity(getEventOpacity(event)); item->setShapeMode(porymapConfig.eventSelectionShapeMode); - item->updatePosition(); + item->render(project); +} + +void Editor::updateEventPixmapItemZValue(EventPixmapItem *item) { + if (!item) return; + Event *event = item->getEvent(); + if (!event) return; + + if (item->isSelected()) { + item->setZValue(ZValue::EventMaximum); + } else { + item->setZValue(event->getY() + ((ZValue::EventMaximum - ZValue::EventMinimum) / 2)); + } } void Editor::onEventDragged(Event *event, const QPoint &oldPosition, const QPoint &newPosition) { diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index f1bceac5..f023aa22 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -1,6 +1,7 @@ #include "connectionpixmapitem.h" #include "editcommands.h" #include "map.h" +#include "editor.h" #include @@ -31,7 +32,7 @@ void ConnectionPixmapItem::render(bool ignoreCache) { this->basePixmap = this->connection->render(); QPixmap pixmap = this->basePixmap.copy(0, 0, this->basePixmap.width(), this->basePixmap.height()); - this->setZValue(-1); + this->setZValue(Editor::ZValue::MapConnectionActive); // When editing is inactive the current selection is ignored, all connections should appear normal. if (this->getEditable()) { @@ -43,7 +44,7 @@ void ConnectionPixmapItem::render(bool ignoreCache) { painter.end(); } else { // Darken the image - this->setZValue(-2); + this->setZValue(Editor::ZValue::MapConnectionInactive); QPainter painter(&pixmap); int alpha = static_cast(255 * 0.25); painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QColor(0, 0, 0, alpha)); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 40b2cdf2..86461b83 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -320,6 +320,7 @@ void ObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); + Project *project = window->editor->project; // local id this->line_edit_local_id->disconnect(); @@ -330,18 +331,18 @@ void ObjectFrame::connectSignals(MainWindow *window) { // sprite update this->combo_sprite->disconnect(); - connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) { + connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->object->setGfx(text); - this->object->getPixmapItem()->updatePixmap(); + this->object->getPixmapItem()->render(project); this->object->modify(); }); - connect(this->object->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap); // movement this->combo_movement->disconnect(); - connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) { + connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->object->setMovement(text); - this->object->getPixmapItem()->updatePixmap(); + this->object->getPixmapItem()->render(project); this->object->modify(); }); @@ -498,13 +499,13 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { }); // update icon displayed in frame with target - connect(this->clone->getPixmapItem(), &EventPixmapItem::spriteChanged, this->label_icon, &QLabel::setPixmap); + connect(this->clone->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap); // target map this->combo_target_map->disconnect(); connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { this->clone->setTargetMap(mapName); - this->clone->getPixmapItem()->updatePixmap(); + this->clone->getPixmapItem()->render(project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); @@ -513,9 +514,9 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { // target id this->combo_target_id->disconnect(); - connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) { + connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) { this->clone->setTargetID(text); - this->clone->getPixmapItem()->updatePixmap(); + this->clone->getPixmapItem()->render(project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); diff --git a/src/ui/eventpixmapitem.cpp b/src/ui/eventpixmapitem.cpp index 1face67c..7c0b43d3 100644 --- a/src/ui/eventpixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -1,52 +1,80 @@ #include "eventpixmapitem.h" -#include "editor.h" +#include "project.h" #include "editcommands.h" #include "mapruler.h" #include "metatile.h" +EventPixmapItem::EventPixmapItem(Event *event) + : QGraphicsPixmapItem(event->getPixmap()), + m_basePixmap(pixmap()), + m_event(event) +{ + m_event->setPixmapItem(this); + updatePixelPosition(); +} + +void EventPixmapItem::render(Project *project) { + if (!m_event) + return; + + m_basePixmap = m_event->loadPixmap(project); + + // If the base pixmap changes, the event's pixel position may change. + updatePixelPosition(); + + QPixmap pixmap = m_basePixmap; + if (m_selected) { + // Draw the selection rectangle + QPainter painter(&pixmap); + painter.setPen(Qt::magenta); + painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1); + } + setPixmap(pixmap); + emit rendered(m_basePixmap); +} + void EventPixmapItem::move(int dx, int dy) { - event->setX(event->getX() + dx); - event->setY(event->getY() + dy); - updatePosition(); - emitPositionChanged(); + moveTo(m_event->getX() + dx, + m_event->getY() + dy); } void EventPixmapItem::moveTo(const QPoint &pos) { - event->setX(pos.x()); - event->setY(pos.y()); - updatePosition(); - emitPositionChanged(); + moveTo(pos.x(), pos.y()); } -void EventPixmapItem::updatePosition() { - int x = this->event->getPixelX(); - int y = this->event->getPixelY(); - setX(x); - setY(y); - editor->updateWarpEventWarning(event); +void EventPixmapItem::moveTo(int x, int y) { + bool changed = false; + if (m_event->getX() != x) { + m_event->setX(x); + emit xChanged(x); + changed = true; + } + if (m_event->getY() != y) { + m_event->setY(y); + emit yChanged(y); + changed = true; + } + if (changed) { + updatePixelPosition(); + emit posChanged(x, y); + } } -void EventPixmapItem::emitPositionChanged() { - emit xChanged(event->getX()); - emit yChanged(event->getY()); -} - -void EventPixmapItem::updatePixmap() { - editor->redrawEventPixmapItem(this); - emit spriteChanged(event->getPixmap()); +void EventPixmapItem::updatePixelPosition() { + setPos(m_event->getPixelX(), m_event->getPixelY()); } void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (this->active) + if (m_active) return; - this->active = true; - this->lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); + m_active = true; + m_lastPos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); bool selectionToggle = mouseEvent->modifiers() & Qt::ControlModifier; - if (selectionToggle || !this->editor->selectedEvents.contains(this->event)) { + if (selectionToggle || !m_selected) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. - emit selected(this->event, selectionToggle); + emit selected(m_event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: // 1. This is the only selected event, and the selection is pointless. @@ -55,32 +83,32 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { // 4. There's a group selection, and they want to drag the group around. // 'selectMapEvent' will immediately clear the rest of the selection, which supports #1-3 but prevents #4. // To support #4 we set the flag below, and we only call 'selectMapEvent' on mouse release if no move occurred. - this->releaseSelectionQueued = true; + m_releaseSelectionQueued = true; } mouseEvent->accept(); } void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (!this->active) + if (!m_active) return; QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); - if (pos == this->lastPos) + if (pos == m_lastPos) return; - this->releaseSelectionQueued = false; - emit dragged(this->event, this->lastPos, pos); - this->lastPos = pos; + m_releaseSelectionQueued = false; + emit dragged(m_event, m_lastPos, pos); + m_lastPos = pos; } void EventPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (!this->active) + if (!m_active) return; - this->active = false; - if (this->releaseSelectionQueued) { - this->releaseSelectionQueued = false; - if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == this->lastPos) - emit selected(this->event, false); + m_active = false; + if (m_releaseSelectionQueued) { + m_releaseSelectionQueued = false; + if (Metatile::coordFromPixmapCoord(mouseEvent->scenePos()) == m_lastPos) + emit selected(m_event, false); } - emit released(this->event, this->lastPos); + emit released(m_event, m_lastPos); } diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index 3272acfe..f036d215 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -113,8 +113,7 @@ void MapImageExporter::setModeSpecificUi() { } if (m_mode == ImageExporterMode::Timelapse) { - // TODO: At the moment edit history for events (and the EventPixmapItem class) - // explicitly depend on the editor and assume their map is currently open. + // TODO: At the moment edit history for events explicitly depend on the editor and assume their map is currently open. // Other edit commands rely on this more subtly, like triggering API callbacks or // spending time rendering their layout (which can make creating timelapses very slow). // Until this is resolved, the selected map/layout must remain the same as in the editor. diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index fde7f820..d867c598 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -27,7 +27,6 @@ ResizableRect::ResizableRect(QObject *parent, bool *enabled, int width, int heig : QObject(parent), MovableRect(enabled, width * 16, height * 16, color) { - setZValue(0xFFFFFFFF); // ensure on top of view setAcceptHoverEvents(true); setFlags(this->flags() | QGraphicsItem::ItemIsMovable); } diff --git a/src/ui/resizelayoutpopup.cpp b/src/ui/resizelayoutpopup.cpp index 5629d8e9..2b4f6c0b 100644 --- a/src/ui/resizelayoutpopup.cpp +++ b/src/ui/resizelayoutpopup.cpp @@ -139,6 +139,7 @@ void ResizeLayoutPopup::setupLayoutView() { static bool layoutSizeRectVisible = true; this->outline = new ResizableRect(this, &layoutSizeRectVisible, this->editor->layout->getWidth(), this->editor->layout->getHeight(), qRgb(255, 0, 255)); + this->outline->setZValue(Editor::ZValue::ResizeLayoutPopup); // Ensure on top of view this->outline->setLimit(cover->rect().toAlignedRect()); connect(outline, &ResizableRect::rectUpdated, [=](QRect rect){ // Note: this extra limit check needs access to the project values, so it is done here and not ResizableRect::mouseMoveEvent From 1d6d0c6dc9c83af91588bd27c3daf5b444f487a5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:38:12 -0400 Subject: [PATCH 39/71] 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 40/71] 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 84882a5fadb58b0cab2cc5ec4711656a40560da5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 09:24:32 -0400 Subject: [PATCH 41/71] Prevent dragging events that aren't selected --- src/ui/eventpixmapitem.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/eventpixmapitem.cpp b/src/ui/eventpixmapitem.cpp index 7c0b43d3..bc7bb964 100644 --- a/src/ui/eventpixmapitem.cpp +++ b/src/ui/eventpixmapitem.cpp @@ -74,6 +74,7 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { if (selectionToggle || !m_selected) { // User is either toggling this selection on/off as part of a group selection, // or they're newly selecting just this item. + m_selected = (selectionToggle) ? !m_selected : true; emit selected(m_event, selectionToggle); } else { // This item is already selected and the user isn't toggling the selection, so there are 4 possibilities: @@ -89,7 +90,7 @@ void EventPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) { } void EventPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent) { - if (!m_active) + if (!m_active || !m_selected) return; QPoint pos = Metatile::coordFromPixmapCoord(mouseEvent->scenePos()); From c0df85e43bb7521e015ed92c53ede606493c7d8c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 20 Apr 2025 20:03:11 -0400 Subject: [PATCH 42/71] 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 43/71] 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 44/71] 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 45/71] 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 46/71] 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 47/71] 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 48/71] 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 { From ed273b9ca0e6f00b7a77463dff13302ede424e8a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 21 Apr 2025 20:16:59 -0400 Subject: [PATCH 49/71] Add functionality for global constants --- include/config.h | 2 ++ include/core/parseutil.h | 6 ++++- include/project.h | 3 ++- src/core/parseutil.cpp | 56 ++++++++++++++++++++++++---------------- src/mainwindow.cpp | 2 +- src/project.cpp | 19 +++++++++++--- 6 files changed, 60 insertions(+), 28 deletions(-) diff --git a/include/config.h b/include/config.h index 67a8b507..4f1aeada 100644 --- a/include/config.h +++ b/include/config.h @@ -345,6 +345,7 @@ public: this->unusedTileCovered = 0x0000; this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; + this->globalConstantsFilepaths.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -417,6 +418,7 @@ public: QMargins playerViewDistance; QList warpBehaviors; int maxEventsPerGroup; + QStringList globalConstantsFilepaths; protected: virtual QString getConfigFilepath() override; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index e02d0504..58b5d0ab 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -43,7 +43,7 @@ class ParseUtil { public: ParseUtil(); - void set_root(const QString &dir); + void setRoot(const QString &dir) { this->root = dir; } static QString readTextFile(const QString &path, QString *error = nullptr); bool cacheFile(const QString &path, QString *error = nullptr); void clearFileCache() { this->fileCache.clear(); } @@ -58,6 +58,8 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); + void loadGlobalCDefines(const QString &filename, QString *error = nullptr); + void resetGlobalCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -90,6 +92,8 @@ private: QString curDefine; QHash fileCache; QHash errorMap; + QMap globalDefineValues; + QMap globalDefineExpressions; int evaluateDefine(const QString&, const QString &, QMap*, QMap*); QList tokenizeExpression(QString, QMap*, QMap*); QList generatePostfix(const QList &tokens); diff --git a/include/project.h b/include/project.h index 1031640b..2d2b5fd1 100644 --- a/include/project.h +++ b/include/project.h @@ -76,7 +76,7 @@ public: int maxEncounterRate; bool wildEncountersLoaded; - void set_root(QString); + void setRoot(const QString&); void clearMaps(); void clearTilesetCache(); @@ -203,6 +203,7 @@ public: bool readEventGraphics(); bool readFieldmapProperties(); bool readFieldmapMasks(); + bool readGlobalConstants(); QMap> readObjEventGfxInfo(); QPixmap getEventPixmap(const QString &gfxName, const QString &movementName); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index da2a8f8e..30146284 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -15,26 +15,8 @@ const QRegularExpression ParseUtil::re_poryScriptLabel("\\b(script)(\\((global|l const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((global)\\))?\\s*\\b(?
- - - - - ... + + + + + Qt::Orientation::Horizontal - - - :/icons/help.ico:/icons/help.ico + + + 40 + 20 + - +
- + @@ -1626,7 +1628,7 @@ 0 0 544 - 437 + 338 @@ -1646,6 +1648,34 @@ + + + + <html><head/><body><p>Add additional C files containing #defines or enums. These will be used to resolve unknown symbols during project launch.</p></body></html> + + + Add Global Constants File... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + ... + + + + :/icons/help.ico:/icons/help.ico + + + + + +
@@ -1671,8 +1701,8 @@ 499
- - + + ... @@ -1683,7 +1713,7 @@ - + @@ -1715,7 +1745,7 @@ 0 0 544 - 437 + 421 @@ -1735,6 +1765,36 @@ + + + + <html><head/><body><p>Add an additional #define name and expression. This may be used to evaluate other #defines during project launch.</p></body></html> + + + Add Global Constant... + + + + :/icons/add.ico:/icons/add.ico + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + +
diff --git a/include/config.h b/include/config.h index 4f1aeada..26d2a353 100644 --- a/include/config.h +++ b/include/config.h @@ -346,6 +346,7 @@ public: this->unusedTileSplit = 0x0000; this->maxEventsPerGroup = 255; this->globalConstantsFilepaths.clear(); + this->globalConstants.clear(); this->identifiers.clear(); this->readKeys.clear(); } @@ -419,6 +420,7 @@ public: QList warpBehaviors; int maxEventsPerGroup; QStringList globalConstantsFilepaths; + QMap globalConstants; protected: virtual QString getConfigFilepath() override; diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 58b5d0ab..59d4ae49 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -58,7 +58,8 @@ public: QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); - void loadGlobalCDefines(const QString &filename, QString *error = nullptr); + void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); + void loadGlobalCDefines(const QMap &defines); void resetGlobalCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); diff --git a/include/ui/newdefinedialog.h b/include/ui/newdefinedialog.h new file mode 100644 index 00000000..2107dcec --- /dev/null +++ b/include/ui/newdefinedialog.h @@ -0,0 +1,32 @@ +#ifndef NEWDEFINEDIALOG_H +#define NEWDEFINEDIALOG_H + +#include +#include + +namespace Ui { +class NewDefineDialog; +} + +class NewDefineDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewDefineDialog(QWidget *parent = nullptr); + ~NewDefineDialog(); + + virtual void accept() override; + +signals: + void createdDefine(const QString &name, const QString &expression); + +private: + Ui::NewDefineDialog *ui; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWDEFINEDIALOG_H diff --git a/include/ui/projectsettingseditor.h b/include/ui/projectsettingseditor.h index e4a6ae94..579aec21 100644 --- a/include/ui/projectsettingseditor.h +++ b/include/ui/projectsettingseditor.h @@ -67,6 +67,12 @@ private: void setWarpBehaviorsList(QStringList list); void openFilesHelp(); void openIdentifiersHelp(); + void addNewGlobalConstantsFilepath(); + void addGlobalConstantsFilepath(const QString &filepath); + QStringList getGlobalConstantsFilepaths(); + void addNewGlobalConstant(); + void addGlobalConstant(const QString &name, const QString &expression); + QMap getGlobalConstants(); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/porymap.pro b/porymap.pro index 1cdffddf..601b2f81 100644 --- a/porymap.pro +++ b/porymap.pro @@ -104,6 +104,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ + src/ui/newdefinedialog.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ @@ -216,6 +217,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/metatileselector.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ + include/ui/newdefinedialog.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ @@ -269,6 +271,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newdefinedialog.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ forms/newlocationdialog.ui \ diff --git a/src/config.cpp b/src/config.cpp index 7e7422c7..0e22840e 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -234,7 +234,7 @@ void KeyValueConfigBase::load() { continue; } - this->parseConfigKeyValue(match.captured("key").trimmed().toLower(), match.captured("value").trimmed()); + this->parseConfigKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed()); } this->setUnreadKeys(); @@ -840,6 +840,10 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else { logWarn(QString("Invalid config key found in config file %1: '%2'").arg(this->getConfigFilepath()).arg(key)); } + } else if (key.startsWith("global_constant/")) { + this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value); + } else if (key == "global_constants_filepaths") { + this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts); } else if (key == "prefabs_filepath") { this->prefabFilepath = value; } else if (key == "prefabs_import_prompted") { @@ -863,7 +867,7 @@ void ProjectConfig::parseConfigKeyValue(QString key, QString value) { } else if (key == "event_icon_path_heal") { this->eventIconPaths[Event::Group::Heal] = value; } else if (key.startsWith("pokemon_icon_path/")) { - this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()).toUpper(), value); + this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value); } else if (key == "collision_sheet_path") { this->collisionSheetPath = value; } else if (key == "collision_sheet_width") { @@ -970,12 +974,16 @@ QMap ProjectConfig::getKeyValueMap() { map.insert("event_icon_path_coord", this->eventIconPaths[Event::Group::Coord]); map.insert("event_icon_path_bg", this->eventIconPaths[Event::Group::Bg]); map.insert("event_icon_path_heal", this->eventIconPaths[Event::Group::Heal]); - for (auto i = this->pokemonIconPaths.cbegin(), end = this->pokemonIconPaths.cend(); i != end; i++){ - const QString path = i.value(); - if (!path.isEmpty()) map.insert("pokemon_icon_path/" + i.key(), path); + for (auto it = this->pokemonIconPaths.constBegin(); it != this->pokemonIconPaths.constEnd(); it++) { + const QString path = it.value(); + if (!path.isEmpty()) map.insert("pokemon_icon_path/" + it.key(), path); } - for (auto i = this->identifiers.cbegin(), end = this->identifiers.cend(); i != end; i++) { - map.insert("ident/"+defaultIdentifiers.value(i.key()).first, i.value()); + for (auto it = this->globalConstants.constBegin(); it != this->globalConstants.constEnd(); it++) { + map.insert("global_constant/" + it.key(), it.value()); + } + map.insert("global_constants_filepaths", this->globalConstantsFilepaths.join(",")); + for (auto it = this->identifiers.constBegin(); it != this->identifiers.constEnd(); it++) { + map.insert("ident/"+defaultIdentifiers.value(it.key()).first, it.value()); } map.insert("collision_sheet_path", this->collisionSheetPath); map.insert("collision_sheet_width", QString::number(this->collisionSheetSize.width())); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 30146284..3a8adf93 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -483,7 +483,7 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS const QString expression = defines.expressions.take(name); if (expression == " ") continue; this->curDefine = name; - filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); + filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); // TODO: Unite map with global expressions? Allows users to overwrite project defines logRecordedErrors(); // Only log errors for defines that Porymap is looking for } @@ -509,8 +509,12 @@ QStringList ParseUtil::readCDefineNames(const QString &filename, const QSetglobalDefineExpressions.insert(readCDefines(filename, {}, false, error).expressions); +void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *error) { + loadGlobalCDefines(readCDefines(filename, {}, false, error).expressions); +} + +void ParseUtil::loadGlobalCDefines(const QMap &defines) { + this->globalDefineExpressions.insert(defines); } void ParseUtil::resetGlobalCDefines() { diff --git a/src/project.cpp b/src/project.cpp index 5abc2036..33cac9ce 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2747,11 +2747,12 @@ bool Project::readGlobalConstants() { this->parser.resetGlobalCDefines(); for (const auto &path : projectConfig.globalConstantsFilepaths) { QString error; - this->parser.loadGlobalCDefines(path, &error); + this->parser.loadGlobalCDefinesFromFile(path, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read global constants file '%1': %2").arg(path).arg(error)); } } + this->parser.loadGlobalCDefines(projectConfig.globalConstants); return true; } diff --git a/src/ui/newdefinedialog.cpp b/src/ui/newdefinedialog.cpp new file mode 100644 index 00000000..567e4985 --- /dev/null +++ b/src/ui/newdefinedialog.cpp @@ -0,0 +1,60 @@ +#include "newdefinedialog.h" +#include "ui_newdefinedialog.h" +#include "validator.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewDefineDialog::NewDefineDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::NewDefineDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + + ui->lineEdit_Name->setValidator(new IdentifierValidator(this)); + + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewDefineDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewDefineDialog::dialogButtonClicked); + + adjustSize(); +} + +NewDefineDialog::~NewDefineDialog() +{ + delete ui; +} + +void NewDefineDialog::onNameChanged(const QString &) { + validateName(true); +} + +bool NewDefineDialog::validateName(bool allowEmpty) { + const QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (name.isEmpty() && !allowEmpty) { + errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewDefineDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewDefineDialog::accept() { + if (!validateName()) + return; + emit createdDefine(ui->lineEdit_Name->text(), ui->lineEdit_Value->text()); + QDialog::accept(); +} diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index ccbf1ec3..13b6198f 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -3,6 +3,7 @@ #include "noscrollcombobox.h" #include "prefab.h" #include "filedialog.h" +#include "newdefinedialog.h" #include "utility.h" #include @@ -54,6 +55,9 @@ void ProjectSettingsEditor::connectSignals() { connect(ui->button_AddWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(true); }); connect(ui->button_RemoveWarpBehavior, &QAbstractButton::clicked, [this](bool) { this->updateWarpBehaviorsList(false); }); + connect(ui->button_AddGlobalConstantsFile, &QAbstractButton::clicked, this, &ProjectSettingsEditor::addNewGlobalConstantsFilepath); + connect(ui->button_AddGlobalConstant, &QAbstractButton::clicked, this, &ProjectSettingsEditor::addNewGlobalConstant); + // Connect file selection buttons connect(ui->button_ChoosePrefabs, &QAbstractButton::clicked, [this](bool) { this->choosePrefabsFile(); }); connect(ui->button_CollisionGraphics, &QAbstractButton::clicked, [this](bool) { this->chooseImageFile(ui->lineEdit_CollisionGraphics); }); @@ -501,6 +505,12 @@ void ProjectSettingsEditor::refresh() { lineEdit->setText(projectConfig.getCustomFilePath(lineEdit->objectName())); for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) lineEdit->setText(projectConfig.getCustomIdentifier(lineEdit->objectName())); + for (const auto &path : projectConfig.globalConstantsFilepaths) { + addGlobalConstantsFilepath(path); + } + for (auto it = projectConfig.globalConstants.constBegin(); it != projectConfig.globalConstants.constEnd(); it++) { + addGlobalConstant(it.key(), it.value()); + } // Set warp behaviors QStringList behaviorNames; @@ -578,6 +588,10 @@ void ProjectSettingsEditor::save() { for (auto lineEdit : ui->scrollAreaContents_Identifiers->findChildren()) projectConfig.setIdentifier(lineEdit->objectName(), lineEdit->text()); + // Save global constants + projectConfig.globalConstantsFilepaths = getGlobalConstantsFilepaths(); + projectConfig.globalConstants = getGlobalConstants(); + // Save warp behaviors projectConfig.warpBehaviors.clear(); const QStringList behaviorNames = this->getWarpBehaviorsList(); @@ -624,6 +638,100 @@ void ProjectSettingsEditor::chooseFile(QLineEdit * filepathEdit, const QString & this->hasUnsavedChanges = true; } +void ProjectSettingsEditor::addNewGlobalConstantsFilepath() { + QString filepath = stripProjectDir(FileDialog::getOpenFileName(this, "Choose Global Constants File")); + if (filepath.isEmpty() || getGlobalConstantsFilepaths().contains(filepath)) + return; + + addGlobalConstantsFilepath(filepath); + this->hasUnsavedChanges = true; +} + +void ProjectSettingsEditor::addGlobalConstantsFilepath(const QString &filepath) { + auto filepathLabel = new QLabel(filepath, this); + filepathLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); + filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + + // TODO: Tool tips + + int newRow = ui->gridLayout_GlobalConstantsFiles->rowCount(); + ui->gridLayout_GlobalConstantsFiles->addWidget(filepathLabel, newRow, 0); + + auto deleteButton = new QToolButton(); + deleteButton->setIcon(QIcon(":/icons/delete.ico")); + connect(deleteButton, &QAbstractButton::clicked, [this, filepathLabel, deleteButton](bool) { + ui->gridLayout_GlobalConstantsFiles->removeWidget(filepathLabel); + ui->gridLayout_GlobalConstantsFiles->removeWidget(deleteButton); + delete filepathLabel; + delete deleteButton; + this->hasUnsavedChanges = true; + }); + ui->gridLayout_GlobalConstantsFiles->addWidget(deleteButton, newRow, 1); +} + +QStringList ProjectSettingsEditor::getGlobalConstantsFilepaths() { + QStringList paths; + for (int row = 1; row < ui->gridLayout_GlobalConstantsFiles->rowCount(); row++) { + auto item = ui->gridLayout_GlobalConstantsFiles->itemAtPosition(row, 0); + if (!item) continue; + auto pathLabel = dynamic_cast(item->widget()); + if (!pathLabel) continue; + paths.append(pathLabel->text()); + } + return paths; +} + +void ProjectSettingsEditor::addNewGlobalConstant() { + auto dialog = new NewDefineDialog(this); + connect(dialog, &NewDefineDialog::createdDefine, [this](const QString &name, const QString &expression) { + if (!getGlobalConstants().contains(name)) { + addGlobalConstant(name, expression); + this->hasUnsavedChanges = true; + } + }); + dialog->open(); +} + +void ProjectSettingsEditor::addGlobalConstant(const QString &name, const QString &expression) { + // TODO: Tool tips + auto nameLabel = new QLabel(name, this); + nameLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); + nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + + auto expressionLineEdit = new QLineEdit(expression, this); + + int newRow = ui->gridLayout_GlobalConstants->rowCount(); + ui->gridLayout_GlobalConstants->addWidget(nameLabel, newRow, 0); + ui->gridLayout_GlobalConstants->addWidget(expressionLineEdit, newRow, 1); + + auto deleteButton = new QToolButton(); + deleteButton->setIcon(QIcon(":/icons/delete.ico")); + connect(deleteButton, &QAbstractButton::clicked, [this, nameLabel, expressionLineEdit, deleteButton](bool) { + ui->gridLayout_GlobalConstants->removeWidget(nameLabel); + ui->gridLayout_GlobalConstants->removeWidget(expressionLineEdit); + ui->gridLayout_GlobalConstants->removeWidget(deleteButton); + delete nameLabel; + delete expressionLineEdit; + delete deleteButton; + this->hasUnsavedChanges = true; + }); + ui->gridLayout_GlobalConstants->addWidget(deleteButton, newRow, 2); +} + +QMap ProjectSettingsEditor::getGlobalConstants() { + QMap constants; + for (int row = 1; row < ui->gridLayout_GlobalConstants->rowCount(); row++) { + auto nameItem = ui->gridLayout_GlobalConstants->itemAtPosition(row, 0); + auto expressionItem = ui->gridLayout_GlobalConstants->itemAtPosition(row, 1); + if (!nameItem || !expressionItem) continue; + auto nameLabel = dynamic_cast(nameItem->widget()); + auto expressionLineEdit = dynamic_cast(expressionItem->widget()); + if (!nameLabel || !expressionLineEdit) continue; + constants.insert(nameLabel->text(), expressionLineEdit->text()); + } + return constants; +} + // Display relative path if this file is in the project folder QString ProjectSettingsEditor::stripProjectDir(QString s) { if (s.startsWith(this->baseDir)) From 10aa9a623f20ddd1c13120b4ea6f941729772366 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 02:33:55 -0400 Subject: [PATCH 52/71] Update new tool tips --- src/ui/eventframes.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 07b77c0b..95a31557 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -230,8 +230,9 @@ void ObjectFrame::setup() { // local id QFormLayout *l_form_local_id = new QFormLayout(); this->line_edit_local_id = new QLineEdit(this); - this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" - "If no game is given you can refer to this object using its 'object id' number."); + static const QString line_edit_local_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this object in scripts. " + "If no name is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setToolTip(line_edit_local_id_toolTip); this->line_edit_local_id->setPlaceholderText("LOCALID_MY_NPC"); l_form_local_id->addRow("Local ID", this->line_edit_local_id); this->layout_contents->addLayout(l_form_local_id); @@ -455,8 +456,9 @@ void CloneObjectFrame::setup() { // local id QFormLayout *l_form_local_id = new QFormLayout(); this->line_edit_local_id = new QLineEdit(this); - this->line_edit_local_id->setToolTip("An optional, unique name to use to refer to this object in scripts.\n" - "If no game is given you can refer to this object using its 'object id' number."); + static const QString line_edit_local_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this object in scripts. " + "If no name is given you can refer to this object using its 'object id' number."); + this->line_edit_local_id->setToolTip(line_edit_local_id_toolTip); this->line_edit_local_id->setPlaceholderText("LOCALID_MY_CLONE_NPC"); l_form_local_id->addRow("Local ID", this->line_edit_local_id); this->layout_contents->addLayout(l_form_local_id); @@ -464,9 +466,10 @@ void CloneObjectFrame::setup() { // sprite combo (edits disabled) QFormLayout *l_form_sprite = new QFormLayout(); this->combo_sprite = new NoScrollComboBox(this); - this->combo_sprite->setToolTip("The sprite graphics to use for this object. This is updated automatically\n" - "to match the target object, and so can't be edited. By default the games\n" - "will get the graphics directly from the target object, so this field is ignored."); + static const QString combo_sprite_toolTip = Util::toHtmlParagraph("The sprite graphics to use for this object. This is updated automatically " + "to match the target object, and so can't be edited. By default the games " + "will get the graphics directly from the target object, so this field is ignored."); + this->combo_sprite->setToolTip(combo_sprite_toolTip); l_form_sprite->addRow("Sprite", this->combo_sprite); this->combo_sprite->setEnabled(false); this->layout_contents->addLayout(l_form_sprite); @@ -574,8 +577,9 @@ void WarpFrame::setup() { // ID QFormLayout *l_form_id = new QFormLayout(); this->line_edit_id = new QLineEdit(this); - this->line_edit_id->setToolTip("An optional, unique name to use to refer to this warp from other warps.\n" - "If no game is given you can refer to this warp using its 'warp id' number."); + static const QString line_edit_id_toolTip = Util::toHtmlParagraph("An optional, unique name to use to refer to this warp from other warps. " + "If no name is given you can refer to this warp using its 'warp id' number."); + this->line_edit_id->setToolTip(line_edit_id_toolTip); this->line_edit_id->setPlaceholderText("WARP_ID_MY_WARP"); l_form_id->addRow("ID", this->line_edit_id); this->layout_contents->addLayout(l_form_id); From dedf0d3e5748dc356f3965ce74d78557fed8f317 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 02:50:50 -0400 Subject: [PATCH 53/71] Fix crash on project switch --- src/core/events.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/events.cpp b/src/core/events.cpp index c902db32..22315211 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -20,8 +20,7 @@ Event* Event::create(Event::Type type) { } Event::~Event() { - if (this->eventFrame) - this->eventFrame->deleteLater(); + delete this->eventFrame; } EventFrame *Event::getEventFrame() { From 046f942f4192344953939332c7ee74e5e9c06c68 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 11:41:08 -0400 Subject: [PATCH 54/71] Fix some issues with player view rectangle visibility --- include/ui/movablerect.h | 10 ++++++++-- src/editor.cpp | 2 ++ src/mainwindow.cpp | 2 +- src/ui/movablerect.cpp | 21 +++++++++++++++------ 4 files changed, 26 insertions(+), 9 deletions(-) diff --git a/include/ui/movablerect.h b/include/ui/movablerect.h index 21edd21d..92dd43f7 100644 --- a/include/ui/movablerect.h +++ b/include/ui/movablerect.h @@ -20,7 +20,7 @@ public: } void paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override { - if (!(*enabled)) return; + if (!isVisible()) return; painter->setPen(this->color); painter->drawRect(this->rect() + QMargins(1,1,1,1)); // Fill painter->setPen(Qt::black); @@ -28,11 +28,17 @@ public: painter->drawRect(this->rect()); // Inner border } void updateLocation(int x, int y); - bool *enabled; + + void setActive(bool active); + bool getActive() const { return this->active; } protected: + bool *enabled = nullptr; + bool active = true; QRectF baseRect; QRgb color; + + void updateVisibility(); }; diff --git a/src/editor.cpp b/src/editor.cpp index 0f6991ba..2d789bb8 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -170,6 +170,7 @@ void Editor::setEditMode(EditMode editMode) { } this->cursorMapTileRect->setSingleTileMode(); this->cursorMapTileRect->setActive(editingLayout); + this->playerViewRect->setActive(editingLayout); this->editGroup.setActiveStack(editStack); setMapEditingButtonsEnabled(editingLayout); @@ -1111,6 +1112,7 @@ void Editor::scaleMapView(int s) { void Editor::setPlayerViewRect(const QRectF &rect) { delete this->playerViewRect; this->playerViewRect = new MovableRect(&this->settings->playerViewRectEnabled, rect, qRgb(255, 255, 255)); + this->playerViewRect->setActive(getEditingLayout()); if (ui->graphicsView_Map->scene()) ui->graphicsView_Map->scene()->update(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 365c0d1c..3860c456 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1961,7 +1961,7 @@ void MainWindow::on_actionPlayer_View_Rectangle_triggered() this->editor->settings->playerViewRectEnabled = enabled; if ((this->editor->map_item && this->editor->map_item->has_mouse) || (this->editor->collision_item && this->editor->collision_item->has_mouse)) { - this->editor->playerViewRect->setVisible(enabled); + this->editor->playerViewRect->setVisible(enabled && this->editor->playerViewRect->getActive()); ui->graphicsView_Map->scene()->update(); } } diff --git a/src/ui/movablerect.cpp b/src/ui/movablerect.cpp index 4290d1a7..ade48afd 100644 --- a/src/ui/movablerect.cpp +++ b/src/ui/movablerect.cpp @@ -11,16 +11,25 @@ MovableRect::MovableRect(bool *enabled, const QRectF &rect, const QRgb &color) baseRect(rect), color(color) { - this->setVisible(*enabled); + updateVisibility(); } /// Center rect on grid position (x, y) void MovableRect::updateLocation(int x, int y) { - this->setRect(this->baseRect.x() + (x * 16), - this->baseRect.y() + (y * 16), - this->baseRect.width(), - this->baseRect.height()); - this->setVisible(*this->enabled); + setRect(this->baseRect.x() + (x * 16), + this->baseRect.y() + (y * 16), + this->baseRect.width(), + this->baseRect.height()); + updateVisibility(); +} + +void MovableRect::setActive(bool active) { + this->active = active; + updateVisibility(); +} + +void MovableRect::updateVisibility() { + setVisible(*this->enabled && this->active); } /****************************************************************************** From fc0b1b1b586d289bbe445942df484dad12c29157 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 14:11:13 -0400 Subject: [PATCH 55/71] Fix invalid selections being marginally visible on the collision selector --- src/ui/selectablepixmapitem.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ui/selectablepixmapitem.cpp b/src/ui/selectablepixmapitem.cpp index 7cf17ec1..e15824ad 100644 --- a/src/ui/selectablepixmapitem.cpp +++ b/src/ui/selectablepixmapitem.cpp @@ -92,19 +92,22 @@ QPoint SelectablePixmapItem::getCellPos(QPointF pos) void SelectablePixmapItem::drawSelection() { - QPixmap pixmap = this->pixmap(); - QPainter painter(&pixmap); QPoint origin = this->getSelectionStart(); QPoint dimensions = this->getSelectionDimensions(); + QRect selectionRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, dimensions.x() * this->cellWidth, dimensions.y() * this->cellHeight); - int rectWidth = dimensions.x() * this->cellWidth; - int rectHeight = dimensions.y() * this->cellHeight; + // If a selection is fully outside the bounds of the selectable area, don't draw anything. + // This prevents the border of the selection rectangle potentially being visible on an otherwise invisible selection. + QPixmap pixmap = this->pixmap(); + if (!selectionRect.intersects(pixmap.rect())) + return; + QPainter painter(&pixmap); painter.setPen(QColor(0xff, 0xff, 0xff)); - painter.drawRect(origin.x() * this->cellWidth, origin.y() * this->cellHeight, rectWidth - 1, rectHeight -1); + painter.drawRect(selectionRect.x(), selectionRect.y(), selectionRect.width() - 1, selectionRect.height() - 1); painter.setPen(QColor(0, 0, 0)); - painter.drawRect(origin.x() * this->cellWidth - 1, origin.y() * this->cellHeight - 1, rectWidth + 1, rectHeight + 1); - painter.drawRect(origin.x() * this->cellWidth + 1, origin.y() * this->cellHeight + 1, rectWidth - 3, rectHeight - 3); + painter.drawRect(selectionRect.x() - 1, selectionRect.y() - 1, selectionRect.width() + 1, selectionRect.height() + 1); + painter.drawRect(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 3, selectionRect.height() - 3); this->setPixmap(pixmap); } From 781f965d6b7586d024961f00a8568def07471bea Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 16:51:57 -0400 Subject: [PATCH 56/71] Allow parser to remember defines, globals take precedence --- include/core/parseutil.h | 16 ++++++-- src/core/parseutil.cpp | 85 +++++++++++++++++++++++----------------- src/project.cpp | 2 +- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index 59d4ae49..a8a7939d 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -60,7 +60,7 @@ public: QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); void loadGlobalCDefines(const QMap &defines); - void resetGlobalCDefines(); + void resetCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); QStringList getLabelValues(const QList&, const QString&); @@ -93,10 +93,20 @@ private: QString curDefine; QHash fileCache; QHash errorMap; + + // The maps of define names to values/expressions that are available while parsing C defines. + // As the parser reads and evaluates more defines it will update these maps accordingly. + QMap knownDefineValues; + QMap knownDefineExpressions; + + // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. + // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. QMap globalDefineValues; QMap globalDefineExpressions; - int evaluateDefine(const QString&, const QString &, QMap*, QMap*); - QList tokenizeExpression(QString, QMap*, QMap*); + + int evaluateDefine(const QString &identifier, bool *ok = nullptr); + int evaluateExpression(const QString &expression); + QList tokenizeExpression(QString expression); QList generatePostfix(const QList &tokens); int evaluatePostfix(const QList &postfix); void recordError(const QString &message); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 3a8adf93..e9c6f1c0 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -16,7 +16,7 @@ const QRegularExpression ParseUtil::re_globalPoryScriptLabel("\\b(script)(\\((gl const QRegularExpression ParseUtil::re_poryRawSection("\\b(raw)\\s*`(?[^`]*)"); ParseUtil::ParseUtil() { - resetGlobalCDefines(); + resetCDefines(); } QString ParseUtil::pathWithRoot(const QString &path) { @@ -125,28 +125,48 @@ QList ParseUtil::parseAsm(const QString &filename) { return parsed; } -// 'identifier' is the name of the #define to evaluate, e.g. 'FOO' in '#define FOO (BAR+1)' -// 'expression' is the text of the #define to evaluate, e.g. '(BAR+1)' in '#define FOO (BAR+1)' -// 'knownValues' is a pointer to a map of identifier->values for defines that have already been evaluated. -// 'unevaluatedExpressions' is a pointer to a map of identifier->expressions for defines that have not been evaluated. If this map contains any -// identifiers found in 'expression' then this function will be called recursively to evaluate that define first. -// This function will maintain the passed maps appropriately as new #defines are evaluated. -int ParseUtil::evaluateDefine(const QString &identifier, const QString &expression, QMap *knownValues, QMap *unevaluatedExpressions) { - if (unevaluatedExpressions->contains(identifier)) - unevaluatedExpressions->remove(identifier); +// Try to evaluate the given #define/enum 'identifier' name using the information the parser has. +// If it recognizes the name as an identifier it's aware of (either from having parsed it or having been told about +// it using 'loadGlobalCDefines') it will evaluate it if necessary then return the resulting value and set 'ok' to true. +// Evaluated identifiers are cached, and will only be re-evaluated if the parser encounters a new expression for that identifier. +// If it doesn't recognize it, 'ok' will be set to false and it will return 0. +int ParseUtil::evaluateDefine(const QString &identifier, bool *ok) { + if (ok) *ok = true; - if (knownValues->contains(identifier)) - return knownValues->value(identifier); + // Global defines take precedence + if (this->globalDefineExpressions.contains(identifier)) { + int value = evaluateExpression(this->globalDefineExpressions.take(identifier)); + this->globalDefineValues.insert(identifier, value); + return value; + } + auto it = this->globalDefineValues.constFind(identifier); + if (it != this->globalDefineValues.constEnd()) { + return it.value(); + } - QList tokens = tokenizeExpression(expression, knownValues, unevaluatedExpressions); - QList postfixExpression = generatePostfix(tokens); - int value = evaluatePostfix(postfixExpression); + // Check known expressions before checking known values. + // If an identifier is redefined then we'll receive a new expression for it, and we want to make sure we re-evaluate it. + if (this->knownDefineExpressions.contains(identifier)) { + int value = evaluateExpression(this->knownDefineExpressions.take(identifier)); + this->knownDefineValues.insert(identifier, value); + return value; + } + it = this->knownDefineValues.constFind(identifier); + if (it != this->knownDefineValues.constEnd()) { + return it.value(); + } - knownValues->insert(identifier, value); - return value; + if (ok) *ok = false; + return 0; } -QList ParseUtil::tokenizeExpression(QString expression, QMap *knownValues, QMap *unevaluatedExpressions) { +int ParseUtil::evaluateExpression(const QString &expression) { + QList tokens = tokenizeExpression(expression); + QList postfixExpression = generatePostfix(tokens); + return evaluatePostfix(postfixExpression); +} + +QList ParseUtil::tokenizeExpression(QString expression) { QList tokens; static const QStringList tokenTypes = {"hex", "decimal", "identifier", "operator", "leftparen", "rightparen"}; @@ -163,18 +183,14 @@ QList ParseUtil::tokenizeExpression(QString expression, QMapcontains(token)) { - evaluateDefine(token, unevaluatedExpressions->value(token), knownValues, unevaluatedExpressions); - } else if (this->globalDefineExpressions.contains(token)) { - int value = evaluateDefine(token, this->globalDefineExpressions.value(token), &this->globalDefineValues, &this->globalDefineExpressions); - knownValues->insert(token, value); - } - - if (knownValues->contains(token)) { + bool ok; + int tokenValue = evaluateDefine(token, &ok); + if (ok) { // Any errors encountered when this identifier was evaluated should be recorded for this expression as well. recordErrors(this->errorMap.value(token)); - QString actualToken = QString("%1").arg(knownValues->value(token)); + + // Replace token with evaluated expression + QString actualToken = QString::number(tokenValue); expression = expression.replace(0, token.length(), actualToken); token = actualToken; tokenType = "decimal"; @@ -467,6 +483,7 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const result.filteredNames.append(name); } } + this->knownDefineExpressions.insert(result.expressions); return result; } @@ -476,14 +493,10 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS // Evaluate defines QMap filteredValues; - QMap allValues = this->globalDefineValues; this->errorMap.clear(); while (!defines.filteredNames.isEmpty()) { - const QString name = defines.filteredNames.takeFirst(); - const QString expression = defines.expressions.take(name); - if (expression == " ") continue; - this->curDefine = name; - filteredValues.insert(name, evaluateDefine(name, expression, &allValues, &defines.expressions)); // TODO: Unite map with global expressions? Allows users to overwrite project defines + this->curDefine = defines.filteredNames.takeFirst(); + filteredValues.insert(this->curDefine, evaluateDefine(this->curDefine)); logRecordedErrors(); // Only log errors for defines that Porymap is looking for } @@ -517,7 +530,7 @@ void ParseUtil::loadGlobalCDefines(const QMap &defines) { this->globalDefineExpressions.insert(defines); } -void ParseUtil::resetGlobalCDefines() { +void ParseUtil::resetCDefines() { static const QMap defaultDefineValues = { {"FALSE", 0}, {"TRUE", 1}, @@ -535,6 +548,8 @@ void ParseUtil::resetGlobalCDefines() { }; this->globalDefineValues = defaultDefineValues; this->globalDefineExpressions.clear(); + this->knownDefineValues.clear(); + this->knownDefineExpressions.clear(); } QStringList ParseUtil::readCArray(const QString &filename, const QString &label) { diff --git a/src/project.cpp b/src/project.cpp index 33cac9ce..b798a203 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2744,7 +2744,7 @@ bool Project::readMiscellaneousConstants() { } bool Project::readGlobalConstants() { - this->parser.resetGlobalCDefines(); + this->parser.resetCDefines(); for (const auto &path : projectConfig.globalConstantsFilepaths) { QString error; this->parser.loadGlobalCDefinesFromFile(path, &error); From 715f53731d0cb43a4d0aa1bf8e1190de5ed5e3b0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 17:08:47 -0400 Subject: [PATCH 57/71] Parser define maps to hashes --- include/core/parseutil.h | 17 ++++++------ src/core/parseutil.cpp | 17 ++++++++---- src/project.cpp | 59 ++++++++++++++++++++-------------------- 3 files changed, 50 insertions(+), 43 deletions(-) diff --git a/include/core/parseutil.h b/include/core/parseutil.h index a8a7939d..aae86a88 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -55,11 +55,12 @@ public: QString readCIncbin(const QString &text, const QString &label); QMap readCIncbinMulti(const QString &filepath); QStringList readCIncbinArray(const QString &filename, const QString &label); - QMap readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); - QMap readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); + QHash readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error = nullptr); + QHash readCDefinesByName(const QString &filename, const QSet &names, QString *error = nullptr); QStringList readCDefineNames(const QString &filename, const QSet ®exList, QString *error = nullptr); void loadGlobalCDefinesFromFile(const QString &filename, QString *error = nullptr); void loadGlobalCDefines(const QMap &defines); + void loadGlobalCDefines(const QHash &defines); void resetCDefines(); OrderedMap> readCStructs(const QString &, const QString & = "", const QHash& = {}); QList getLabelMacros(const QList&, const QString&); @@ -96,13 +97,13 @@ private: // The maps of define names to values/expressions that are available while parsing C defines. // As the parser reads and evaluates more defines it will update these maps accordingly. - QMap knownDefineValues; - QMap knownDefineExpressions; + QHash knownDefineValues; + QHash knownDefineExpressions; // Maps of special define names to values/expressions that take precedence over defines encountered while parsing. // Some (like 'TRUE'/'FALSE') are always present in these maps, others may be specified by the user with 'loadGlobalCDefines' / 'loadGlobalCDefinesFromFile'. - QMap globalDefineValues; - QMap globalDefineExpressions; + QHash globalDefineValues; + QHash globalDefineExpressions; int evaluateDefine(const QString &identifier, bool *ok = nullptr); int evaluateExpression(const QString &expression); @@ -115,11 +116,11 @@ private: QString createErrorMessage(const QString &message, const QString &expression); struct ParsedDefines { - QMap expressions; // Map of all define names encountered to their expressions + QHash expressions; // Map of all define names encountered to their expressions QStringList filteredNames; // List of define names that matched the search text, in the order that they were encountered }; ParsedDefines readCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); - QMap evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); + QHash evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error); bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; bool defineNameMatchesFilter(const QString &name, const QSet &filterList) const; QString loadTextFile(const QString &path, QString *error = nullptr); diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index e9c6f1c0..9130aea0 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -488,11 +488,11 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const } // Read all the define names and their expressions in the specified file, then evaluate the ones matching the search text (and any they depend on). -QMap ParseUtil::evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error) { +QHash ParseUtil::evaluateCDefines(const QString &filename, const QSet &filterList, bool useRegex, QString *error) { ParsedDefines defines = readCDefines(filename, filterList, useRegex, error); // Evaluate defines - QMap filteredValues; + QHash filteredValues; this->errorMap.clear(); while (!defines.filteredNames.isEmpty()) { this->curDefine = defines.filteredNames.takeFirst(); @@ -504,12 +504,12 @@ QMap ParseUtil::evaluateCDefines(const QString &filename, const QS } // Find and evaluate a specific set of defines with known names. -QMap ParseUtil::readCDefinesByName(const QString &filename, const QSet &names, QString *error) { +QHash ParseUtil::readCDefinesByName(const QString &filename, const QSet &names, QString *error) { return evaluateCDefines(filename, names, false, error); } // Find and evaluate an unknown list of defines with a known name pattern. -QMap ParseUtil::readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error) { +QHash ParseUtil::readCDefinesByRegex(const QString &filename, const QSet ®exList, QString *error) { return evaluateCDefines(filename, regexList, true, error); } @@ -526,12 +526,17 @@ void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *err loadGlobalCDefines(readCDefines(filename, {}, false, error).expressions); } -void ParseUtil::loadGlobalCDefines(const QMap &defines) { +void ParseUtil::loadGlobalCDefines(const QHash &defines) { this->globalDefineExpressions.insert(defines); } +void ParseUtil::loadGlobalCDefines(const QMap &defines) { + for (auto it = defines.constBegin(); it != defines.constEnd(); it++) + this->globalDefineExpressions.insert(it.key(), it.value()); +} + void ParseUtil::resetCDefines() { - static const QMap defaultDefineValues = { + static const QHash defaultDefineValues = { {"FALSE", 0}, {"TRUE", 1}, {"SCHAR_MIN", SCHAR_MIN}, diff --git a/src/project.cpp b/src/project.cpp index b798a203..4b038618 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1481,7 +1481,7 @@ bool Project::readTilesetMetatileLabels() { fileWatcher.addPath(root + "/" + metatileLabelsFilename); const QSet regexList = {QString("\\b%1").arg(projectConfig.getIdentifier(ProjectIdentifier::define_metatile_label_prefix))}; - const QMap defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); + const auto defines = parser.readCDefinesByRegex(metatileLabelsFilename, regexList); for (auto i = defines.constBegin(); i != defines.constEnd(); i++) { QString label = i.key(); uint32_t metatileId = i.value(); @@ -2116,16 +2116,15 @@ bool Project::readFieldmapProperties() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_fieldmap); fileWatcher.addPath(root + "/" + filename); - const QMap defines = parser.readCDefinesByName(filename, { - numTilesPrimaryName, - numTilesTotalName, - numMetatilesPrimaryName, - numPalsPrimaryName, - numPalsTotalName, - maxMapSizeName, - numTilesPerMetatileName, - mapOffsetWidthName, - mapOffsetHeightName, + const auto defines = parser.readCDefinesByName(filename, { numTilesPrimaryName, + numTilesTotalName, + numMetatilesPrimaryName, + numPalsPrimaryName, + numPalsTotalName, + maxMapSizeName, + numTilesPerMetatileName, + mapOffsetWidthName, + mapOffsetHeightName, }); auto loadDefine = [defines](const QString name, int * dest, int min, int max) { @@ -2219,16 +2218,15 @@ bool Project::readFieldmapMasks() { const QString elevationMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_elevation); const QString behaviorMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_behavior); const QString layerTypeMaskName = projectConfig.getIdentifier(ProjectIdentifier::define_mask_layer); - const QSet searchNames = { - metatileIdMaskName, - collisionMaskName, - elevationMaskName, - behaviorMaskName, - layerTypeMaskName, - }; + const QString globalFieldmap = projectConfig.getFilePath(ProjectFilePath::global_fieldmap); fileWatcher.addPath(root + "/" + globalFieldmap); - QMap defines = parser.readCDefinesByName(globalFieldmap, searchNames); + const auto defines = parser.readCDefinesByName(globalFieldmap, { metatileIdMaskName, + collisionMaskName, + elevationMaskName, + behaviorMaskName, + layerTypeMaskName, + }); // These mask values are accessible via the settings editor for users who don't have these defines. // If users do have the defines we disable them in the settings editor and direct them to their project files. @@ -2239,8 +2237,8 @@ bool Project::readFieldmapMasks() { // Read Block masks auto readBlockMask = [defines](const QString name, uint16_t *value) { - auto it = defines.find(name); - if (it == defines.end()) + auto it = defines.constFind(name); + if (it == defines.constEnd()) return false; *value = static_cast(it.value()); if (*value != it.value()){ @@ -2314,7 +2312,7 @@ bool Project::readFieldmapMasks() { // Read #defines for encounter and terrain types to populate in the Tileset Editor dropdowns (if necessary) QString error; if (projectConfig.metatileEncounterTypeMask) { - QMap defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_encounter_types)}, &error); + const auto defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_encounter_types)}, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read encounter type constants from '%1': %2").arg(globalFieldmap).arg(error)); error = QString(); @@ -2325,7 +2323,7 @@ bool Project::readFieldmapMasks() { } } if (projectConfig.metatileTerrainTypeMask) { - QMap defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_terrain_types)}, &error); + const auto defines = parser.readCDefinesByRegex(globalFieldmap, {projectConfig.getIdentifier(ProjectIdentifier::regex_terrain_types)}, &error); if (!error.isEmpty()) { logWarn(QString("Failed to read terrain type constants from '%1': %2").arg(globalFieldmap).arg(error)); error = QString(); @@ -2673,7 +2671,7 @@ bool Project::readMetatileBehaviors() { QString filename = projectConfig.getFilePath(ProjectFilePath::constants_metatile_behaviors); fileWatcher.addPath(root + "/" + filename); QString error; - QMap defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); + const auto defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_behaviors)}, &error); if (defines.isEmpty() && projectConfig.metatileBehaviorMask) { // Not having any metatile behavior names is ok (their values will be displayed instead) // but if the user's metatiles can have nonzero values then warn them, as they likely want names. @@ -2710,9 +2708,14 @@ bool Project::readObjEventGfxConstants() { QString filename = projectConfig.getFilePath(ProjectFilePath::constants_obj_events); fileWatcher.addPath(root + "/" + filename); QString error; - this->gfxDefines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); + const auto defines = parser.readCDefinesByRegex(filename, {projectConfig.getIdentifier(ProjectIdentifier::regex_obj_event_gfx)}, &error); if (!error.isEmpty()) logWarn(QString("Failed to read object event graphics constants from '%1': %2").arg(filename).arg(error)); + + this->gfxDefines.clear(); + for (auto it = defines.constBegin(); it != defines.constEnd(); it++) + this->gfxDefines.insert(it.key(), it.value()); + return true; } @@ -2720,7 +2723,7 @@ bool Project::readMiscellaneousConstants() { const QString filename = projectConfig.getFilePath(ProjectFilePath::constants_global); const QString maxObjectEventsName = projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count); fileWatcher.addPath(root + "/" + filename); - QMap defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); + const auto defines = parser.readCDefinesByName(filename, {maxObjectEventsName}); this->maxObjectEvents = 64; // Default value auto it = defines.find(maxObjectEventsName); @@ -2953,9 +2956,7 @@ QPixmap Project::getEventPixmap(const QString &gfxName, int frame, bool hFlip) { // Invalid gfx constant. If this is a number, try to use that instead. bool ok; int gfxNum = ParseUtil::gameStringToInt(gfxName, &ok); - if (ok && gfxNum < this->gfxDefines.count()) { - gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr); - } + if (ok) gfx = this->eventGraphicsMap.value(this->gfxDefines.key(gfxNum, "NULL"), nullptr); } if (gfx && !gfx->loaded) { // This is the first request for this event's sprite. We'll attempt to load it now. From 4259e652448832362ce74452bb673278948c1e6c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 17:34:15 -0400 Subject: [PATCH 58/71] Clean up settings editor changes --- forms/projectsettingseditor.ui | 2 +- src/mainwindow.cpp | 7 +++++++ src/ui/projectsettingseditor.cpp | 7 ++----- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/forms/projectsettingseditor.ui b/forms/projectsettingseditor.ui index fbcaa312..05598b8f 100644 --- a/forms/projectsettingseditor.ui +++ b/forms/projectsettingseditor.ui @@ -21,7 +21,7 @@ - 4 + 0 diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 0a88c34b..ee127f36 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1665,6 +1665,13 @@ void MainWindow::duplicate() { void MainWindow::copy() { auto focused = QApplication::focusWidget(); if (focused) { + // Allow copying text from selectable QLabels. + auto label = dynamic_cast(focused); + if (label && !label->selectedText().isEmpty()) { + setClipboardData(label->selectedText()); + return; + } + QString objectName = focused->objectName(); if (objectName == "graphicsView_currentMetatileSelection") { // copy the current metatile selection as json data diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 13b6198f..d67bcc82 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -650,9 +650,7 @@ void ProjectSettingsEditor::addNewGlobalConstantsFilepath() { void ProjectSettingsEditor::addGlobalConstantsFilepath(const QString &filepath) { auto filepathLabel = new QLabel(filepath, this); filepathLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); - filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work - - // TODO: Tool tips + filepathLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); int newRow = ui->gridLayout_GlobalConstantsFiles->rowCount(); ui->gridLayout_GlobalConstantsFiles->addWidget(filepathLabel, newRow, 0); @@ -693,10 +691,9 @@ void ProjectSettingsEditor::addNewGlobalConstant() { } void ProjectSettingsEditor::addGlobalConstant(const QString &name, const QString &expression) { - // TODO: Tool tips auto nameLabel = new QLabel(name, this); nameLabel->setFrameStyle(QFrame::Panel | QFrame::Raised); - nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); // TODO: This doesn't allow Copy shortcut from the keyboard to work + nameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); auto expressionLineEdit = new QLineEdit(expression, this); From 845f93c9e4b7a24fca5fed2b7cccddb83301c3ba Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 17:53:33 -0400 Subject: [PATCH 59/71] Fix Qt 5.14 build --- src/core/parseutil.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9130aea0..00e53e52 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -483,7 +483,14 @@ ParseUtil::ParsedDefines ParseUtil::readCDefines(const QString &filename, const result.filteredNames.append(name); } } + // QHash::insert(const QHash &other) was introduced in 5.15. +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) this->knownDefineExpressions.insert(result.expressions); +#else + for (auto it = result.expressions.constBegin(); it != result.expressions.constEnd(); it++) { + this->knownDefineExpressions.insert(it.key(), it.value()); + } +#endif return result; } @@ -527,7 +534,14 @@ void ParseUtil::loadGlobalCDefinesFromFile(const QString &filename, QString *err } void ParseUtil::loadGlobalCDefines(const QHash &defines) { + // QHash::insert(const QHash &other) was introduced in 5.15. +#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) this->globalDefineExpressions.insert(defines); +#else + for (auto it = defines.constBegin(); it != defines.constEnd(); it++) { + this->globalDefineExpressions.insert(it.key(), it.value()); + } +#endif } void ParseUtil::loadGlobalCDefines(const QMap &defines) { From d97fd5b2a6a96bf581668cf201736562ec067657 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 23 Apr 2025 21:54:42 -0400 Subject: [PATCH 60/71] Update onMapResized --- docsrc/manual/scripting-capabilities.rst | 8 +++--- include/core/maplayout.h | 4 +-- include/scripting.h | 5 +++- resources/text/script_template.txt | 2 +- src/core/maplayout.cpp | 34 +++++++++--------------- src/scriptapi/scripting.cpp | 14 +++++++--- 6 files changed, 34 insertions(+), 33 deletions(-) diff --git a/docsrc/manual/scripting-capabilities.rst b/docsrc/manual/scripting-capabilities.rst index ac1cf521..0651f7d9 100644 --- a/docsrc/manual/scripting-capabilities.rst +++ b/docsrc/manual/scripting-capabilities.rst @@ -204,7 +204,7 @@ Callbacks Called when the mouse exits the map. -.. js:function:: onMapResized(oldWidth, oldHeight, newWidth, newHeight) +.. js:function:: onMapResized(oldWidth, oldHeight, delta) Called when the dimensions of the map are changed. @@ -212,10 +212,8 @@ Callbacks :type oldWidth: number :param oldHeight: the height of the map before the change :type oldHeight: number - :param newWidth: the width of the map after the change - :type newWidth: number - :param newHeight: the height of the map after the change - :type newHeight: number + :param delta: the amount the map size changed in each direction. The object's shape is ``{left, right, top, bottom}`` + :type prevBlock: delta .. js:function:: onBorderResized(oldWidth, oldHeight, newWidth, newHeight) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 6e82604a..3e67af18 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -107,8 +107,8 @@ public: void setBlock(int x, int y, Block block, bool enableScriptCallback = false); void setBlockdata(Blockdata blockdata, bool enableScriptCallback = false); - void adjustDimensions(QMargins margins, bool setNewBlockdata = true); - void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); + void adjustDimensions(const QMargins &margins, bool setNewBlockdata = true); + void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true); void setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); void cacheBlockdata(); diff --git a/include/scripting.h b/include/scripting.h index 6870f159..b85e9e26 100644 --- a/include/scripting.h +++ b/include/scripting.h @@ -39,6 +39,7 @@ public: static void populateGlobalObject(MainWindow *mainWindow); static QJSEngine *getEngine(); static void invokeAction(int actionIndex); + static void cb_ProjectOpened(QString projectPath); static void cb_ProjectClosed(QString projectPath); static void cb_MetatileChanged(int x, int y, Block prevBlock, Block newBlock); @@ -47,18 +48,20 @@ public: static void cb_BlockHoverCleared(); static void cb_MapOpened(QString mapName); static void cb_LayoutOpened(QString layoutName); - static void cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight); + static void cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta); static void cb_BorderResized(int oldWidth, int oldHeight, int newWidth, int newHeight); static void cb_MapShifted(int xDelta, int yDelta); static void cb_TilesetUpdated(QString tilesetName); static void cb_MainTabChanged(int oldTab, int newTab); static void cb_MapViewTabChanged(int oldTab, int newTab); static void cb_BorderVisibilityToggled(bool visible); + static bool tryErrorJS(QJSValue js); static QJSValue fromBlock(Block block); static QJSValue fromTile(Tile tile); static Tile toTile(QJSValue obj); static QJSValue dimensions(int width, int height); + static QJSValue margins(const QMargins &margins); static QJSValue position(int x, int y); static const QImage * getImage(const QString &filepath, bool useCache); static QJSValue dialogInput(QJSValue input, bool selectedOk); diff --git a/resources/text/script_template.txt b/resources/text/script_template.txt index 4b5134d1..bee6e56e 100644 --- a/resources/text/script_template.txt +++ b/resources/text/script_template.txt @@ -39,7 +39,7 @@ export function onBlockHoverCleared() { } // Called when the dimensions of the map are changed. -export function onMapResized(oldWidth, oldHeight, newWidth, newHeight) { +export function onMapResized(oldWidth, oldHeight, delta) { } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index fb0f714c..c3e91ba3 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -189,46 +189,38 @@ void Layout::setBorderBlockData(Blockdata newBlockdata, bool enableScriptCallbac } } -void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { +void Layout::setDimensions(int newWidth, int newHeight, bool setNewBlockdata) { if (setNewBlockdata) { setNewDimensionsBlockdata(newWidth, newHeight); } - - int oldWidth = this->width; - int oldHeight = this->height; this->width = newWidth; this->height = newHeight; - - if (enableScriptCallback && (oldWidth != newWidth || oldHeight != newHeight)) { - Scripting::cb_MapResized(oldWidth, oldHeight, newWidth, newHeight); - } - - emit dimensionsChanged(QSize(getWidth(), getHeight())); + emit dimensionsChanged(QSize(this->width, this->height)); } -void Layout::adjustDimensions(QMargins margins, bool setNewBlockdata) { - int newWidth = this->width + margins.left() + margins.right(); - int newHeight = this->height + margins.top() + margins.bottom(); +void Layout::adjustDimensions(const QMargins &margins, bool setNewBlockdata) { + int oldWidth = this->width; + int oldHeight = this->height; + this->width = oldWidth + margins.left() + margins.right(); + this->height = oldHeight + margins.top() + margins.bottom(); if (setNewBlockdata) { // Fill new blockdata Blockdata newBlockdata; - for (int y = 0; y < newHeight; y++) - for (int x = 0; x < newWidth; x++) { - if ((x < margins.left()) || (x >= newWidth - margins.right()) || (y < margins.top()) || (y >= newHeight - margins.bottom())) { + for (int y = 0; y < this->height; y++) + for (int x = 0; x < this->width; x++) { + if ((x < margins.left()) || (x >= this->width - margins.right()) || (y < margins.top()) || (y >= this->height - margins.bottom())) { newBlockdata.append(0); } else { - int index = (y - margins.top()) * this->width + (x - margins.left()); + int index = (y - margins.top()) * oldWidth + (x - margins.left()); newBlockdata.append(this->blockdata.value(index)); } } this->blockdata = newBlockdata; } - this->width = newWidth; - this->height = newHeight; - - emit dimensionsChanged(QSize(getWidth(), getHeight())); + Scripting::cb_MapResized(oldWidth, oldHeight, margins); + emit dimensionsChanged(QSize(this->width, this->height)); } void Layout::setBorderDimensions(int newWidth, int newHeight, bool setNewBlockdata, bool enableScriptCallback) { diff --git a/src/scriptapi/scripting.cpp b/src/scriptapi/scripting.cpp index 05fd31d9..93823de2 100644 --- a/src/scriptapi/scripting.cpp +++ b/src/scriptapi/scripting.cpp @@ -268,14 +268,13 @@ void Scripting::cb_LayoutOpened(QString layoutName) { instance->invokeCallback(OnLayoutOpened, args); } -void Scripting::cb_MapResized(int oldWidth, int oldHeight, int newWidth, int newHeight) { +void Scripting::cb_MapResized(int oldWidth, int oldHeight, const QMargins &delta) { if (!instance) return; QJSValueList args { oldWidth, oldHeight, - newWidth, - newHeight, + Scripting::margins(delta), }; instance->invokeCallback(OnMapResized, args); } @@ -356,6 +355,15 @@ QJSValue Scripting::dimensions(int width, int height) { return obj; } +QJSValue Scripting::margins(const QMargins &margins) { + QJSValue obj = instance->engine->newObject(); + obj.setProperty("left", margins.left()); + obj.setProperty("right", margins.right()); + obj.setProperty("top", margins.top()); + obj.setProperty("bottom", margins.bottom()); + return obj; +} + QJSValue Scripting::position(int x, int y) { QJSValue obj = instance->engine->newObject(); obj.setProperty("x", x); From c52bc46c0fdc24b0b03271c9624dde9ee62336f9 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 24 Apr 2025 16:29:33 -0400 Subject: [PATCH 61/71] Update changelog --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50007ef5..eaf4c225 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - Add an input field to the Tileset Editor for editing the full metatile attributes value directly, including unused bits. - An alert will be displayed when attempting to open a seemingly invalid project. - Add support for defining project values with `enum` where `#define` was expected. +- Add support for referring to object events and warps with named IDs, rather than referring to them with their index number. - 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. @@ -67,9 +68,13 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - 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). -- Fix selections with multiple Events not always clearing when making a new selection. +- Fix selections with multiple events not always clearing when making a new selection. - Fix the new event button not updating correctly when selecting object events. - Fix duplicated `Hidden Item` events not copying the `Requires Itemfinder` field. +- Fix event sprites disappearing in certain areas outside the map boundaries. +- Fix deselecting an event still allowing you to drag the event around. +- Fix events rendering on top of the ruler at very high y values. +- Fix new map names not appearing in event dropdowns that have already been populated. - Fix `About porymap` opening a new window each time it's activated. - Fix the `Edit History` window not raising to the front when reactivated. - New maps are now always inserted in map dropdowns at the correct position, rather than at the bottom of the list until the project is reloaded. From 134a933d11143ed84a21f947bc07e37631b7c6b5 Mon Sep 17 00:00:00 2001 From: garak Date: Wed, 16 Apr 2025 12:42:15 -0400 Subject: [PATCH 62/71] create splash screen for loading --- forms/loadingscreen.ui | 164 ++++++++++++++++++++++++++++++++ include/mainwindow.h | 2 + include/ui/loadingscreen.h | 46 +++++++++ porymap.pro | 3 + resources/images.qrc | 1 + resources/images/porysplash.gif | Bin 0 -> 2884 bytes src/core/parseutil.cpp | 3 + src/main.cpp | 10 +- src/mainwindow.cpp | 11 ++- src/ui/loadingscreen.cpp | 51 ++++++++++ 10 files changed, 288 insertions(+), 3 deletions(-) create mode 100644 forms/loadingscreen.ui create mode 100644 include/ui/loadingscreen.h create mode 100644 resources/images/porysplash.gif create mode 100644 src/ui/loadingscreen.cpp diff --git a/forms/loadingscreen.ui b/forms/loadingscreen.ui new file mode 100644 index 00000000..799f0a9d --- /dev/null +++ b/forms/loadingscreen.ui @@ -0,0 +1,164 @@ + + + LoadingScreen + + + Qt::ApplicationModal + + + + 0 + 0 + 366 + 255 + + + + BusyCursor + + + Qt::NoContextMenu + + + Form + + + + + + + 20 + true + + + + porymap + + + Qt::AlignCenter + + + + + + + + 12 + + + + Version 6.0.0 + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 64 + 64 + + + + IMAGE + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + + + Loading..... + + + + + + + TextLabel + + + + + + + + + + + diff --git a/include/mainwindow.h b/include/mainwindow.h index 683df61f..c65cac79 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -52,6 +52,8 @@ public: MainWindow(const MainWindow &) = delete; MainWindow & operator = (const MainWindow &) = delete; + void initialize(); + // Scripting API Q_INVOKABLE QJSValue getBlock(int x, int y); void tryRedrawMapArea(bool forceRedraw); diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h new file mode 100644 index 00000000..10ea4629 --- /dev/null +++ b/include/ui/loadingscreen.h @@ -0,0 +1,46 @@ +#include "qgifimage.h" + +#include +#include +#include + +namespace Ui { +class LoadingScreen; +} + +// Loading class +// LOAD() // or Offload() OFFLOAD() WorkerThread() +// function that wraps around QFuture and QtConcurrent, does not block gui thread +// can call any other function that is not directly painting the gui on another thread + +// Execute function while playing loading screen and display text +// void executeLoadingScreen(QString text, void (*function)(void)); + +class PorymapLoadingScreen : public QWidget { + + Q_OBJECT + +public: + explicit PorymapLoadingScreen(QWidget *parent = nullptr); + ~PorymapLoadingScreen(); + + void setPixmap(QPixmap pixmap); + void showMessage(QString text); + + void start() { this->timer.start(120); } + +private: + void setupUi(); + +public slots: + void updateFrame(); + +private: + Ui::LoadingScreen *ui; + + QGifImage splashImage; + int frame = 0; + QTimer timer; +}; + +extern PorymapLoadingScreen *porysplash; diff --git a/porymap.pro b/porymap.pro index d8505e11..263bc61b 100644 --- a/porymap.pro +++ b/porymap.pro @@ -133,6 +133,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/preferenceeditor.cpp \ src/ui/regionmappropertiesdialog.cpp \ src/ui/colorpicker.cpp \ + src/ui/loadingscreen.cpp \ src/config.cpp \ src/editor.cpp \ src/main.cpp \ @@ -248,6 +249,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/preferenceeditor.h \ include/ui/regionmappropertiesdialog.h \ include/ui/colorpicker.h \ + include/ui/loadingscreen.h \ include/config.h \ include/editor.h \ include/mainwindow.h \ @@ -267,6 +269,7 @@ FORMS += forms/mainwindow.ui \ forms/connectionslistitem.ui \ forms/customattributesframe.ui \ forms/gridsettingsdialog.ui \ + forms/loadingscreen.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ forms/newlayoutdialog.ui \ diff --git a/resources/images.qrc b/resources/images.qrc index e1253139..41789a6e 100644 --- a/resources/images.qrc +++ b/resources/images.qrc @@ -94,6 +94,7 @@ images/collisions_unknown.png images/Entities_16x16.png images/pokemon_icon_placeholder.png + images/porysplash.gif icons/clipboard.ico icons/map_go.ico diff --git a/resources/images/porysplash.gif b/resources/images/porysplash.gif new file mode 100644 index 0000000000000000000000000000000000000000..900874d43389e40ca91fefcce500c075d0f3145c GIT binary patch literal 2884 zcmcK5cTiK=9>DQ?Ne@Lx2*dyyPy~bkA_%A`gceFD7DS~a1SC4W(Nd8=9J#nwbHbzw0?VIS_*aS?r)dVhoGTBr@6Th{)|m zMnUnxtRseu@KB?mBS(T5>`1_1cuCsp7UElv0S9{h#-*HUW?H43?%Gib(d(`Na84^r zNt$@Hrge_uaX~d=TZLA!`D}UX;cluT$&8c+gq{g2 zqY1$yJ1XoHN@6z`)#)n;PMhQF?CB>2Deg^}G@HYXrE9kg2(7oHVK68poRFGuOW}5x z$yND7=N!$hKTxoG6#NYUIRIFC(RwQTkDF;j?{v5szQ@Y|TD>CB^ZG;q(`mO*#8pkwc$JaS_bvX3wI?P+zo@`dcPGJAb!%#A zjuGG_$AozF3&-(u{I&OH?baLnSZ~M?BFE}HYMi4AzEbDYhNmrW{HijSZ4i=2+#IiZ+@$tA6>;V=& zLyen|s>aLY=Q7oKE_m=Hz$jj;Uw7op2mlGBpJ6m2@%SbV2)9dI}snAKjUkk%JW;dXy^Ly#X4fZC9j7B6Z_7NOdI&fwxiS z*YoWNHGaR{-5{Q9Qa#!wN5nOj)g8IlV5SCsT~&YW7C9;XBHh=xW&exYwG8&3Zq8qD z+mrGCbAyapuE3#CD{v4b0wIH(r4Cab6c6CwvvZ^=tKs`Ms}`_}&*l(N7-E}^pYkaD z?y27Aeijf=4$t)LRqqXhgixTKW0mA|)lhEz0%?w;2u62JT@f48PZ~g$GTuZJwYm!o zlnQRW&&;+dGT0E$9Xzp#%+i%_zWLoN2Eq+t()_KxM{QrQM^th|ExKf^aF^1wSl9Gf zuYwQhjt~v08#}uZg6{_RKG`k^%=6x;8CcqPc6B~~(beJDa*u2O>``O6$J~+6d%TwQc8ldokBrS$D?QGy z_DC~c;2s3(8E&Az3_9bt*F`(mM6$67QE{krTv8H_i-BZ@s^zHV0m#fObr!&jA`};t zF$iGc$&$j=FhxLY314`gSqp~SUT}>Quamep(UM$%rXCckP2&=k)u&=PT&gW{s>vO` zwLS!5SxX@Uvhwn>Fc3LOJUJ}WLw$&Sx&Zq-6j`rY83&>=<7ww(F3?gEKa$__vS_|_ zMA@Y&#$J!99`w1y`MVEVs$}3Oo}5aAInnxZ`jqL%MR7C3Hf?QZ6YABP8Rufs4i)v5 zfPhPV3F;oBM{S_S7JP@3h(6!b^{$q&(C8_alsFWvRbtC;K=W&X1IA zb?5$Xuvh2rze_I<;lIMxTgK+`0w_OI*#csa#nJ~qVI^acwmHX~8Dvv#QocFVev5o; zsW){6TL*Tr#>VKO?2HSMKj=S=Id032ib+;ZKq0em9E58OKb8PVM24#6Bas4rE(TG= zQ>S1NB1C!g8KA1DgiwnSArTz{u(7bZ5p%JvqZ0;|MBij#vvZ0y+2+U{IMQ-^91EEhJ$`%x+V;Kh>C?8Ck3rGG`Vi?mJBiu=Cq-uGxrP1W zn?^HMP%relF54Ln8QuJTDe{tQ%1t+187Xs+8TLpoK~d)sok7{i^#w`Mwv`mj4JOh^ zhQl%{84BamULVE=tfxlKCZqLx73Rly4(8g(U>CjikP)Wu9{lrz<}B;DT@&&3{+T}Q z4VXTaMm@)H>B9x#CjC;={2cLRAI%P({pA`Twd7TAXL)9^lFFz`zv!L`%gCcFI3H^W z7?Q0aLvpLn_w|)19ao@UZes!eY3R$h?rPrBKT z1D%*?dcrX(+AtZHfPiPKa8eRs?lCd>SeZn4m|6iJ28S0G5elk`l}pc5!D>iiphBeH zkWvMQNeaQ1_H%9gPRZqMT_p3PSYB?c`p9*OOc&Th5)NWUZnY>++!*h=aUCMHC56#9?(#RknSzNKV3Zp2DX@ zU;Fjd$FZ09!IR6H}AwRE?t~eJ^`20qLqlTeIn?e|bis>5=y6SH2fRsx4X@C9OY>s%ini Gjz0j%eWB0* literal 0 HcmV?d00001 diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index da2a8f8e..4e048140 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -1,5 +1,6 @@ #include "log.h" #include "parseutil.h" +#include "loadingscreen.h" #include #include @@ -74,6 +75,8 @@ QString ParseUtil::createErrorMessage(const QString &message, const QString &exp } QString ParseUtil::readTextFile(const QString &path, QString *error) { + // splash screen message + porysplash->showMessage(path); QFile file(path); if (!file.open(QIODevice::ReadOnly)) { if (error) *error = file.errorString(); diff --git a/src/main.cpp b/src/main.cpp index 0e8fa7d3..ebd591df 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,6 @@ #include "mainwindow.h" +#include "loadingscreen.h" + #include int main(int argc, char *argv[]) @@ -6,8 +8,14 @@ int main(int argc, char *argv[]) QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::Round); QApplication a(argc, argv); a.setStyle("fusion"); + + porysplash = new PorymapLoadingScreen; + porysplash->show(); + + QObject::connect(&a, &QCoreApplication::aboutToQuit, [=]() { delete porysplash; }); + MainWindow w(nullptr); - w.show(); + w.initialize(); return a.exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5a0169a8..aa296779 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -29,6 +29,7 @@ #include "newmapgroupdialog.h" #include "newlocationdialog.h" #include "message.h" +#include "loadingscreen.h" #include #include @@ -75,10 +76,14 @@ MainWindow::MainWindow(QWidget *parent) : cleanupLargeLog(); logInfo(QString("Launching Porymap v%1").arg(QCoreApplication::applicationVersion())); +} +void MainWindow::initialize() { + porysplash->start(); this->initWindow(); - if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) + if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) { on_toolButton_Paint_clicked(); + } // there is a bug affecting macOS users, where the trackpad deilveres a bad touch-release gesture // the warning is a bit annoying, so it is disabled here @@ -86,6 +91,9 @@ MainWindow::MainWindow(QWidget *parent) : if (porymapConfig.checkForUpdates) this->checkForUpdates(false); + + porysplash->close(); + this->show(); } MainWindow::~MainWindow() @@ -153,7 +161,6 @@ void MainWindow::initWindow() { #endif setWindowDisabled(true); - show(); } void MainWindow::initShortcuts() { diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp new file mode 100644 index 00000000..1e8a7f9d --- /dev/null +++ b/src/ui/loadingscreen.cpp @@ -0,0 +1,51 @@ + +#include "loadingscreen.h" +#include "ui_loadingscreen.h" +#include "qgifimage.h" + +#include + +PorymapLoadingScreen *porysplash = nullptr; + + + +PorymapLoadingScreen::~PorymapLoadingScreen() { + delete ui; +} + +PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), ui(new Ui::LoadingScreen) { + ui->setupUi(this); + this->setWindowFlags(Qt::FramelessWindowHint); + + this->splashImage.load(":/images/porysplash.gif"); + + this->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); + + connect(&this->timer, &QTimer::timeout, this, &PorymapLoadingScreen::updateFrame); +} + +void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { + if (!this->isVisible()) return; + this->ui->labelPixmap->setPixmap(pixmap); +} + +void PorymapLoadingScreen::showMessage(QString text) { + if (!this->isVisible()) return; + this->ui->labelText->setText(text.mid(text.lastIndexOf("/") + 1)); + //this->updateFrame(); + + QApplication::processEvents(); +} + +void PorymapLoadingScreen::updateFrame() { + // + this->frame = (this->frame + 1) % this->splashImage.frameCount(); + + this->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); + + QApplication::processEvents(); + + //this->showMessage("Frame Number: " + QString::number(this->frame)); + + //this->repaint(); +} From c2b1f5ab85f37f9bb2b499a3cf0ee8cf3c775c58 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 09:49:53 -0400 Subject: [PATCH 63/71] fix geometry setting before window exists --- src/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index aa296779..5fc5dfd6 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -93,6 +93,8 @@ void MainWindow::initialize() { this->checkForUpdates(false); porysplash->close(); + + this->restoreWindowState(); this->show(); } @@ -150,7 +152,6 @@ void MainWindow::initWindow() { this->initMiscHeapObjects(); this->initMapList(); this->initShortcuts(); - this->restoreWindowState(); #ifndef RELEASE_PLATFORM ui->actionCheck_for_Updates->setVisible(false); From 9a0a7865fbeaa90b03b3ee8db8b13dbf56002f06 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 15:29:13 -0400 Subject: [PATCH 64/71] show splash screen whenever project is being loaded ... including when switching projects and reloading projects --- include/ui/loadingscreen.h | 3 ++- src/main.cpp | 1 - src/mainwindow.cpp | 8 +++++--- src/ui/loadingscreen.cpp | 17 ++++++++++------- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h index 10ea4629..af3e4f6f 100644 --- a/include/ui/loadingscreen.h +++ b/include/ui/loadingscreen.h @@ -27,7 +27,8 @@ public: void setPixmap(QPixmap pixmap); void showMessage(QString text); - void start() { this->timer.start(120); } + void start(); + void stop (); private: void setupUi(); diff --git a/src/main.cpp b/src/main.cpp index ebd591df..d85c1b38 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,6 @@ int main(int argc, char *argv[]) a.setStyle("fusion"); porysplash = new PorymapLoadingScreen; - porysplash->show(); QObject::connect(&a, &QCoreApplication::aboutToQuit, [=]() { delete porysplash; }); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5fc5dfd6..7527e8c3 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -79,7 +79,6 @@ MainWindow::MainWindow(QWidget *parent) : } void MainWindow::initialize() { - porysplash->start(); this->initWindow(); if (porymapConfig.reopenOnLaunch && !porymapConfig.projectManuallyClosed && this->openProject(porymapConfig.getRecentProject(), true)) { on_toolButton_Paint_clicked(); @@ -92,8 +91,6 @@ void MainWindow::initialize() { if (porymapConfig.checkForUpdates) this->checkForUpdates(false); - porysplash->close(); - this->restoreWindowState(); this->show(); } @@ -656,6 +653,8 @@ bool MainWindow::openProject(QString dir, bool initial) { return false; } + porysplash->start(); + const QString openMessage = QString("Opening %1").arg(projectString); this->statusBar()->showMessage(openMessage); logInfo(openMessage); @@ -684,6 +683,7 @@ bool MainWindow::openProject(QString dir, bool initial) { // Make sure project looks reasonable before attempting to load it if (!checkProjectSanity()) { delete this->editor->project; + porysplash->stop(); return false; } @@ -693,6 +693,7 @@ bool MainWindow::openProject(QString dir, bool initial) { showProjectOpenFailure(); delete this->editor->project; // TODO: Allow changing project settings at this point + porysplash->stop(); return false; } @@ -713,6 +714,7 @@ bool MainWindow::openProject(QString dir, bool initial) { editor->layout); Scripting::cb_ProjectOpened(dir); setWindowDisabled(false); + porysplash->stop(); return true; } diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 1e8a7f9d..d66ccf63 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -24,6 +24,16 @@ PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), u connect(&this->timer, &QTimer::timeout, this, &PorymapLoadingScreen::updateFrame); } +void PorymapLoadingScreen::start() { + this->timer.start(120); + this->show(); +} + +void PorymapLoadingScreen::stop () { + this->timer.stop(); + this->hide(); +} + void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { if (!this->isVisible()) return; this->ui->labelPixmap->setPixmap(pixmap); @@ -32,20 +42,13 @@ void PorymapLoadingScreen::setPixmap(QPixmap pixmap) { void PorymapLoadingScreen::showMessage(QString text) { if (!this->isVisible()) return; this->ui->labelText->setText(text.mid(text.lastIndexOf("/") + 1)); - //this->updateFrame(); QApplication::processEvents(); } void PorymapLoadingScreen::updateFrame() { - // this->frame = (this->frame + 1) % this->splashImage.frameCount(); - this->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); QApplication::processEvents(); - - //this->showMessage("Frame Number: " + QString::number(this->frame)); - - //this->repaint(); } From 7fb657376d81ed26074219db956d20fd98f7fb1a Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 15:50:55 -0400 Subject: [PATCH 65/71] use common version function for splash and about screen --- forms/loadingscreen.ui | 2 +- include/ui/aboutporymap.h | 2 ++ src/ui/aboutporymap.cpp | 14 +++++++++----- src/ui/loadingscreen.cpp | 6 ++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/forms/loadingscreen.ui b/forms/loadingscreen.ui index 799f0a9d..d6f52430 100644 --- a/forms/loadingscreen.ui +++ b/forms/loadingscreen.ui @@ -47,7 +47,7 @@
- Version 6.0.0 + Version X.x.x Qt::AlignCenter diff --git a/include/ui/aboutporymap.h b/include/ui/aboutporymap.h index 6bd0ed32..3a760f44 100644 --- a/include/ui/aboutporymap.h +++ b/include/ui/aboutporymap.h @@ -13,6 +13,8 @@ class AboutPorymap : public QDialog public: explicit AboutPorymap(QWidget *parent = nullptr); ~AboutPorymap(); + + static QString getVersionString(); private: Ui::AboutPorymap *ui; }; diff --git a/src/ui/aboutporymap.cpp b/src/ui/aboutporymap.cpp index 07654e14..5aa5f0c8 100644 --- a/src/ui/aboutporymap.cpp +++ b/src/ui/aboutporymap.cpp @@ -9,15 +9,19 @@ AboutPorymap::AboutPorymap(QWidget *parent) : setAttribute(Qt::WA_DeleteOnClose); static const QString commitHash = PORYMAP_LATEST_COMMIT; - this->ui->label_Version->setText(QString("Version %1%2\nQt %3 (%4)\n%5") + this->ui->label_Version->setText(getVersionString()); + + layout()->setSizeConstraint(QLayout::SetFixedSize); +} + +QString AboutPorymap::getVersionString() { + static const QString commitHash = PORYMAP_LATEST_COMMIT; + return QString("Version %1%2\nQt %3 (%4)\n%5") .arg(QCoreApplication::applicationVersion()) .arg(commitHash.isEmpty() ? "" : QString(" (%1)").arg(commitHash)) .arg(QStringLiteral(QT_VERSION_STR)) .arg(QSysInfo::buildCpuArchitecture()) - .arg(QStringLiteral(__DATE__)) - ); - - layout()->setSizeConstraint(QLayout::SetFixedSize); + .arg(QStringLiteral(__DATE__)); } AboutPorymap::~AboutPorymap() diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index d66ccf63..9cb08be1 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -1,5 +1,6 @@ #include "loadingscreen.h" +#include "aboutporymap.h" #include "ui_loadingscreen.h" #include "qgifimage.h" @@ -25,6 +26,11 @@ PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), u } void PorymapLoadingScreen::start() { + static bool shownVersion = false; + if (!shownVersion) { + this->ui->labelVersion->setText(AboutPorymap::getVersionString()); + shownVersion = true; + } this->timer.start(120); this->show(); } From 7426f474711d903c8a559232e241589a92f5e4e1 Mon Sep 17 00:00:00 2001 From: garak Date: Tue, 29 Apr 2025 15:59:53 -0400 Subject: [PATCH 66/71] clean up --- include/ui/loadingscreen.h | 10 ++-------- src/ui/loadingscreen.cpp | 1 - 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/include/ui/loadingscreen.h b/include/ui/loadingscreen.h index af3e4f6f..df55b01c 100644 --- a/include/ui/loadingscreen.h +++ b/include/ui/loadingscreen.h @@ -4,18 +4,12 @@ #include #include + + namespace Ui { class LoadingScreen; } -// Loading class -// LOAD() // or Offload() OFFLOAD() WorkerThread() -// function that wraps around QFuture and QtConcurrent, does not block gui thread -// can call any other function that is not directly painting the gui on another thread - -// Execute function while playing loading screen and display text -// void executeLoadingScreen(QString text, void (*function)(void)); - class PorymapLoadingScreen : public QWidget { Q_OBJECT diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 9cb08be1..f21d0c86 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -1,4 +1,3 @@ - #include "loadingscreen.h" #include "aboutporymap.h" #include "ui_loadingscreen.h" From 54461991f7ab037acce935079d2e86ab6caaa092 Mon Sep 17 00:00:00 2001 From: t Date: Tue, 29 Apr 2025 16:18:15 -0400 Subject: [PATCH 67/71] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eaf4c225..5e78fe5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The **"Breaking Changes"** listed below are changes that have been made in the d - 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. +- Add a splash loading screen for project openings. ### Changed - `Change Dimensions` now has an interactive resizing rectangle. From 7b7eb221e594d5b4fbdc021f804654466e2eb749 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Apr 2025 16:43:04 -0400 Subject: [PATCH 68/71] Fix missing first frame of loading screen --- src/ui/loadingscreen.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index f21d0c86..04545ff7 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -19,8 +19,6 @@ PorymapLoadingScreen::PorymapLoadingScreen(QWidget *parent) : QWidget(parent), u this->splashImage.load(":/images/porysplash.gif"); - this->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); - connect(&this->timer, &QTimer::timeout, this, &PorymapLoadingScreen::updateFrame); } @@ -30,6 +28,7 @@ void PorymapLoadingScreen::start() { this->ui->labelVersion->setText(AboutPorymap::getVersionString()); shownVersion = true; } + this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); this->timer.start(120); this->show(); } From 184025aace9785dbcc3cf0e57afd8f2141fd10a0 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Apr 2025 16:45:09 -0400 Subject: [PATCH 69/71] Reset frames between loading screens --- src/ui/loadingscreen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 04545ff7..37fa6357 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -28,7 +28,8 @@ void PorymapLoadingScreen::start() { this->ui->labelVersion->setText(AboutPorymap::getVersionString()); shownVersion = true; } - this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(0))); + this->frame = 0; + this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); this->timer.start(120); this->show(); } From 15e2d3cf05d388e4e9ca8420635e1930c87f6ef7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 29 Apr 2025 23:40:03 -0400 Subject: [PATCH 70/71] Start fixing some of the Message UI deadlocks --- include/ui/message.h | 8 +++--- src/mainwindow.cpp | 61 +++++++++++++++++++++------------------- src/ui/loadingscreen.cpp | 4 +++ src/ui/message.cpp | 28 ++++++++++-------- 4 files changed, 56 insertions(+), 45 deletions(-) diff --git a/include/ui/message.h b/include/ui/message.h index 8e36372d..1756c48d 100644 --- a/include/ui/message.h +++ b/include/ui/message.h @@ -24,21 +24,21 @@ public: class ErrorMessage : public Message { public: ErrorMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; // Basic warning message with an 'Ok' button. class WarningMessage : public Message { public: WarningMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; // Basic informational message with a 'Close' button. class InfoMessage : public Message { public: InfoMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; // Basic question message with a 'Yes' and 'No' button. @@ -53,7 +53,7 @@ public: class RecentErrorMessage : public ErrorMessage { public: RecentErrorMessage(const QString &message, QWidget *parent); - static int show(const QString &message, QWidget *parent); + static void show(const QString &message, QWidget *parent); }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7527e8c3..f9c9c5c5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -653,8 +653,6 @@ bool MainWindow::openProject(QString dir, bool initial) { return false; } - porysplash->start(); - const QString openMessage = QString("Opening %1").arg(projectString); this->statusBar()->showMessage(openMessage); logInfo(openMessage); @@ -664,6 +662,8 @@ bool MainWindow::openProject(QString dir, bool initial) { projectConfig.projectDir = dir; projectConfig.load(); + porysplash->start(); + Scripting::init(this); // Create the project @@ -730,7 +730,7 @@ bool MainWindow::checkProjectSanity() { logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root)); - ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), this); + ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), porysplash); msgBox.setInformativeText(QString("The directory '%1' is missing key files.\n\n" "Make sure you selected the correct project directory " "(the one used to make your .gba file, e.g. 'pokeemerald').").arg(editor->project->root)); @@ -750,14 +750,15 @@ void MainWindow::showProjectOpenFailure() { // Alert the user that one or more maps have been excluded while loading the project. void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { - RecentErrorMessage msgBox("", this); + auto msgBox = new RecentErrorMessage("", this); + msgBox->setAttribute(Qt::WA_DeleteOnClose); if (excludedMapNames.length() == 1) { - msgBox.setText(QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first())); + msgBox->setText(QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first())); } else { - msgBox.setText(QStringLiteral("Failed to load the maps listed below. Saving will exclude these maps from your project.")); - msgBox.setDetailedText(excludedMapNames.join("\n")); // Overwrites error details text, user will need to check the log. + msgBox->setText(QStringLiteral("Failed to load the maps listed below. Saving will exclude these maps from your project.")); + msgBox->setDetailedText(excludedMapNames.join("\n")); // Overwrites error details text, user will need to check the log. } - msgBox.exec(); + msgBox->open(); } bool MainWindow::isProjectOpen() { @@ -867,28 +868,28 @@ void MainWindow::showFileWatcherWarning() { path.remove(root); } - QuestionMessage msgBox("", this); + QPointer msgBox = new QuestionMessage("", this); if (modifiedFiles.count() == 1) { - msgBox.setText(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(modifiedFiles.first())); + msgBox->setText(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(modifiedFiles.first())); } else { - msgBox.setText(QStringLiteral("Some project files have changed on disk. Would you like to reload the project?")); - msgBox.setDetailedText(QStringLiteral("The following files have changed:\n") + modifiedFiles.join("\n")); + msgBox->setText(QStringLiteral("Some project files have changed on disk. Would you like to reload the project?")); + msgBox->setDetailedText(QStringLiteral("The following files have changed:\n") + modifiedFiles.join("\n")); } + msgBox->setCheckBox(new QCheckBox("Do not ask again.")); - QCheckBox showAgainCheck("Do not ask again."); - msgBox.setCheckBox(&showAgainCheck); - - auto reply = msgBox.exec(); - if (reply == QMessageBox::Yes) { - on_action_Reload_Project_triggered(); - } else if (reply == QMessageBox::No) { - if (showAgainCheck.isChecked()) { - porymapConfig.monitorFiles = false; - if (this->preferenceEditor) - this->preferenceEditor->updateFields(); + connect(msgBox, &QuestionMessage::accepted, this, &MainWindow::on_action_Reload_Project_triggered); + connect(msgBox, &QuestionMessage::finished, [this, msgBox] { + if (msgBox) { + if (msgBox->checkBox() && msgBox->checkBox()->isChecked()) { + porymapConfig.monitorFiles = false; + if (this->preferenceEditor) + this->preferenceEditor->updateFields(); + } + msgBox->deleteLater(); } - } - showing = false; + showing = false; + }); + msgBox->open(); } QString MainWindow::getExistingDirectory(QString dir) { @@ -903,7 +904,8 @@ void MainWindow::on_action_Open_Project_triggered() } void MainWindow::on_action_Reload_Project_triggered() { - openProject(editor->project->root); + if (this->editor && this->editor->project) + openProject(this->editor->project->root); } void MainWindow::on_action_Close_Project_triggered() { @@ -928,9 +930,10 @@ bool MainWindow::userSetMap(QString map_name) { } if (map_name == editor->project->getDynamicMapName()) { - WarningMessage msgBox(QString("Cannot open map '%1'.").arg(map_name), this); - msgBox.setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); - msgBox.exec(); + auto msgBox = new WarningMessage(QString("Cannot open map '%1'.").arg(map_name), this); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->setInformativeText(QStringLiteral("This map name is a placeholder to indicate that the warp's map will be set programmatically.")); + msgBox->open(); return false; } diff --git a/src/ui/loadingscreen.cpp b/src/ui/loadingscreen.cpp index 37fa6357..9b60cf92 100644 --- a/src/ui/loadingscreen.cpp +++ b/src/ui/loadingscreen.cpp @@ -28,8 +28,12 @@ void PorymapLoadingScreen::start() { this->ui->labelVersion->setText(AboutPorymap::getVersionString()); shownVersion = true; } + this->frame = 0; this->ui->labelPixmap->setPixmap(QPixmap::fromImage(this->splashImage.frame(this->frame))); + + this->ui->labelText->setText(""); + this->timer.start(120); this->show(); } diff --git a/src/ui/message.cpp b/src/ui/message.cpp index d2d91f75..ce51f7c7 100644 --- a/src/ui/message.cpp +++ b/src/ui/message.cpp @@ -52,19 +52,22 @@ RecentErrorMessage::RecentErrorMessage(const QString &message, QWidget *parent) setDetailedText(getMostRecentError()); } -int RecentErrorMessage::show(const QString &message, QWidget *parent) { - RecentErrorMessage msgBox(message, parent); - return msgBox.exec(); +void RecentErrorMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new RecentErrorMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; -int ErrorMessage::show(const QString &message, QWidget *parent) { - ErrorMessage msgBox(message, parent); - return msgBox.exec(); +void ErrorMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new ErrorMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; -int WarningMessage::show(const QString &message, QWidget *parent) { - WarningMessage msgBox(message, parent); - return msgBox.exec(); +void WarningMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new WarningMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; int QuestionMessage::show(const QString &message, QWidget *parent) { @@ -72,7 +75,8 @@ int QuestionMessage::show(const QString &message, QWidget *parent) { return msgBox.exec(); }; -int InfoMessage::show(const QString &message, QWidget *parent) { - InfoMessage msgBox(message, parent); - return msgBox.exec(); +void InfoMessage::show(const QString &message, QWidget *parent) { + auto msgBox = new InfoMessage(message, parent); + msgBox->setAttribute(Qt::WA_DeleteOnClose); + msgBox->open(); }; From 8b5c2ec792537269bcf6078f2d1535394ddce91f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 30 Apr 2025 11:48:39 -0400 Subject: [PATCH 71/71] Fix missing warning for initially incorrect warps --- src/editor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/editor.cpp b/src/editor.cpp index 0ebc2cb2..174d0b07 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1753,6 +1753,7 @@ EventPixmapItem *Editor::addEventPixmapItem(Event *event) { connect(item, &EventPixmapItem::selected, this, &Editor::selectMapEvent); connect(item, &EventPixmapItem::posChanged, [this, event] { updateWarpEventWarning(event); }); connect(item, &EventPixmapItem::yChanged, [this, item] { updateEventPixmapItemZValue(item); }); + updateWarpEventWarning(event); redrawEventPixmapItem(item); this->events_group->addToGroup(item); return item;