From 18308fa9dedb0348c2471cd58e9358d54c8699f8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 11 Nov 2024 18:42:26 -0500 Subject: [PATCH 01/42] Stop writing map_groups.h --- docsrc/manual/project-files.rst | 1 - include/config.h | 1 - include/project.h | 1 - src/config.cpp | 1 - src/project.cpp | 38 --------------------------------- 5 files changed, 42 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index e4f0479b..8e0dfa33 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -45,7 +45,6 @@ The filepath that Porymap expects for each file can be overridden on the ``Files src/data/region_map/region_map_sections.json, yes, yes, ``json_region_map_entries``, src/data/region_map/porymap_config.json, yes, yes, ``json_region_porymap_cfg``, include/constants/global.h, yes, no, ``constants_global``, reads ``define_obj_event_count`` - include/constants/map_groups.h, no, yes, ``constants_map_groups``, include/constants/items.h, yes, no, ``constants_items``, for Hidden Item events include/constants/flags.h, yes, no, ``constants_flags``, for Object and Hidden Item events include/constants/vars.h, yes, no, ``constants_vars``, for Trigger events diff --git a/include/config.h b/include/config.h index 6b64b612..2a93a793 100644 --- a/include/config.h +++ b/include/config.h @@ -254,7 +254,6 @@ enum ProjectFilePath { data_pokemon_gfx, data_heal_locations, constants_global, - constants_map_groups, constants_items, constants_flags, constants_vars, diff --git a/include/project.h b/include/project.h index efb7db7c..f35fc403 100644 --- a/include/project.h +++ b/include/project.h @@ -172,7 +172,6 @@ class Project : public QObject void saveMapGroups(); void saveRegionMapSections(); void saveWildMonData(); - void saveMapConstantsHeader(); void saveHealLocations(Map*); void saveTilesets(Tileset*, Tileset*); void saveTilesetMetatileLabels(Tileset*, Tileset*); diff --git a/src/config.cpp b/src/config.cpp index 3d604da6..a1987203 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -152,7 +152,6 @@ const QMap> ProjectConfig::defaultPaths {ProjectFilePath::data_pokemon_gfx, { "data_pokemon_gfx", "src/data/graphics/pokemon.h"}}, {ProjectFilePath::data_heal_locations, { "data_heal_locations", "src/data/heal_locations.h"}}, {ProjectFilePath::constants_global, { "constants_global", "include/constants/global.h"}}, - {ProjectFilePath::constants_map_groups, { "constants_map_groups", "include/constants/map_groups.h"}}, {ProjectFilePath::constants_items, { "constants_items", "include/constants/items.h"}}, {ProjectFilePath::constants_flags, { "constants_flags", "include/constants/flags.h"}}, {ProjectFilePath::constants_vars, { "constants_vars", "include/constants/vars.h"}}, diff --git a/src/project.cpp b/src/project.cpp index 88e3d9d1..22487a13 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -840,43 +840,6 @@ void Project::saveWildMonData() { wildEncountersFile.close(); } -void Project::saveMapConstantsHeader() { - QString text = QString("#ifndef GUARD_CONSTANTS_MAP_GROUPS_H\n"); - text += QString("#define GUARD_CONSTANTS_MAP_GROUPS_H\n"); - text += QString("\n//\n// DO NOT MODIFY THIS FILE! It is auto-generated from %1\n//\n\n") - .arg(projectConfig.getFilePath(ProjectFilePath::json_map_groups)); - - int groupNum = 0; - for (QStringList mapNames : groupedMapNames) { - text += "// " + groupNames.at(groupNum) + "\n"; - int maxLength = 0; - for (QString mapName : mapNames) { - QString mapConstantName = mapNamesToMapConstants.value(mapName); - if (mapConstantName.length() > maxLength) - maxLength = mapConstantName.length(); - } - int groupIndex = 0; - for (QString mapName : mapNames) { - QString mapConstantName = mapNamesToMapConstants.value(mapName); - text += QString("#define %1%2(%3 | (%4 << 8))\n") - .arg(mapConstantName) - .arg(QString(" ").repeated(maxLength - mapConstantName.length() + 1)) - .arg(groupIndex) - .arg(groupNum); - groupIndex++; - } - text += QString("\n"); - groupNum++; - } - - text += QString("#define MAP_GROUPS_COUNT %1\n\n").arg(groupNum); - text += QString("#endif // GUARD_CONSTANTS_MAP_GROUPS_H\n"); - - QString mapGroupFilepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::constants_map_groups); - ignoreWatchedFileTemporarily(mapGroupFilepath); - saveTextFile(mapGroupFilepath, text); -} - void Project::saveHealLocations(Map *map) { this->saveHealLocationsData(map); this->saveHealLocationsConstants(); @@ -1469,7 +1432,6 @@ void Project::saveAllDataStructures() { saveMapLayouts(); saveMapGroups(); saveRegionMapSections(); - saveMapConstantsHeader(); saveWildMonData(); saveConfig(); this->hasUnsavedDataChanges = false; From b7d78b0263203eb0347b770e0e08c134fc767e05 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 11 Nov 2024 22:28:53 -0500 Subject: [PATCH 02/42] Make Map members private --- include/core/map.h | 154 ++++++++++++++++-------- include/core/maplayout.h | 8 +- include/project.h | 2 - src/core/events.cpp | 12 +- src/core/map.cpp | 191 +++++++++++++++++++++--------- src/core/mapconnection.cpp | 6 +- src/core/maplayout.cpp | 16 --- src/editor.cpp | 68 +++++------ src/mainwindow.cpp | 113 +++++++++--------- src/project.cpp | 177 +++++++++++++-------------- src/scriptapi/apimap.cpp | 24 ++-- src/ui/connectionpixmapitem.cpp | 2 +- src/ui/connectionslistitem.cpp | 8 +- src/ui/draggablepixmapitem.cpp | 2 +- src/ui/eventframes.cpp | 4 +- src/ui/mapimageexporter.cpp | 62 ++++------ src/ui/newmapconnectiondialog.cpp | 2 +- src/ui/newmappopup.cpp | 45 ++++--- 18 files changed, 496 insertions(+), 400 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index acc52d90..22a2ef29 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -37,58 +37,74 @@ class Map : public QObject ~Map(); public: - QString name; - QString constantName; - - QString song; - QString layoutId; - QString location; - bool requiresFlash; - QString weather; - QString type; - bool show_location; - bool allowRunning; - bool allowBiking; - bool allowEscaping; - int floorNumber = 0; - QString battle_scene; - - QString sharedEventsMap = ""; - QString sharedScriptsMap = ""; - - QStringList scriptsFileLabels; - QMap customHeaders; - - Layout *layout = nullptr; - void setLayout(Layout *layout); - - bool isPersistedToFile = true; - bool hasUnsavedDataChanges = false; - - bool needsLayoutDir = true; - bool needsHealLocation = false; - bool scriptsLoaded = false; - - QMap> events; - QList ownedEvents; // for memory management - - QList metatileLayerOrder; - QList metatileLayerOpacity; - void setName(QString mapName); - static QString mapConstantFromName(QString mapName, bool includePrefix = true); + QString name() const { return m_name; } + QString constantName() const { return m_constantName; } - int getWidth(); - int getHeight(); - int getBorderWidth(); - int getBorderHeight(); + static QString mapConstantFromName(QString mapName, bool includePrefix = true); - QList getAllEvents() const; + void setLayout(Layout *layout); + Layout* layout() const { return m_layout; } + + void setLayoutId(const QString &layoutId) { m_layoutId = layoutId; } + QString layoutId() const { return m_layoutId; } + + int getWidth() const; + int getHeight() const; + int getBorderWidth() const; + int getBorderHeight() const; + + // TODO: Combine these into a separate MapHeader class? + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setShowsLocation(bool showsLocation); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + void setBattleScene(const QString &battleScene); + + QString song() const { return m_song; } + QString location() const { return m_location; } + bool requiresFlash() const { return m_requiresFlash; } + QString weather() const { return m_weather; } + QString type() const { return m_type; } + bool showsLocation() const { return m_showsLocation; } + bool allowsRunning() const { return m_allowsRunning; } + bool allowsBiking() const { return m_allowsBiking; } + bool allowsEscaping() const { return m_allowsEscaping; } + int floorNumber() const { return m_floorNumber; } + QString battleScene() const { return m_battleScene; } + + void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } + void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; } + + QString sharedEventsMap() const { return m_sharedEventsMap; } + QString sharedScriptsMap() const { return m_sharedScriptsMap; } + + void setNeedsLayoutDir(bool needsLayoutDir) { m_needsLayoutDir = needsLayoutDir; } + void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } + void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } + void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } + + bool needsLayoutDir() const { return m_needsLayoutDir; } + bool needsHealLocation() const { return m_needsHealLocation; } + bool isPersistedToFile() const { return m_isPersistedToFile; } + bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } + + void resetEvents(); + QList getEvents(Event::Group group = Event::Group::None) const; + Event* getEvent(Event::Group group, int index) const; + int getNumEvents(Event::Group group = Event::Group::None) const; QStringList getScriptLabels(Event::Group group = Event::Group::None); QString getScriptsFilePath() const; void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); + int getIndexOfEvent(Event *) const; void deleteConnections(); QList getConnections() const; @@ -98,18 +114,60 @@ class Map : public QObject QRect getConnectionRect(const QString &direction, Layout *fromLayout = nullptr); QPixmap renderConnection(const QString &direction, Layout *fromLayout = nullptr); - QUndoStack editHistory; + QUndoStack* editHistory() const { return m_editHistory; } + void commit(QUndoCommand*); void modify(); - void clean(); + void setClean(); bool hasUnsavedChanges() const; void pruneEditHistory(); + void setCustomAttributes(const QMap &attributes) { m_customAttributes = attributes; } + QMap customAttributes() const { return m_customAttributes; } + private: + QString m_name; + QString m_constantName; + QString m_layoutId; // TODO: Why do we do half this->layout()->id and half this->layoutId. Should these ever be different? + + QString m_song; + QString m_location; + bool m_requiresFlash; + QString m_weather; + QString m_type; + bool m_showsLocation; + bool m_allowsRunning; + bool m_allowsBiking; + bool m_allowsEscaping; + int m_floorNumber = 0; + QString m_battleScene; + + QString m_sharedEventsMap = ""; + QString m_sharedScriptsMap = ""; + + QStringList m_scriptsFileLabels; + QMap m_customAttributes; + + Layout *m_layout = nullptr; + + bool m_isPersistedToFile = true; + bool m_hasUnsavedDataChanges = false; + bool m_needsLayoutDir = true; + bool m_needsHealLocation = false; + bool m_scriptsLoaded = false; + + QMap> m_events; + QList m_ownedEvents; // for memory management + + QList m_metatileLayerOrder; + QList m_metatileLayerOpacity; + void trackConnection(MapConnection*); // MapConnections in 'ownedConnections' but not 'connections' persist in the edit history. - QList connections; - QSet ownedConnections; + QList m_connections; + QSet m_ownedConnections; + + QUndoStack *m_editHistory = nullptr; signals: void modified(); diff --git a/include/core/maplayout.h b/include/core/maplayout.h index b617002f..0cffcefa 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -84,10 +84,10 @@ class Layout : public QObject { Layout *copy(); void copyFrom(Layout *other); - int getWidth(); - int getHeight(); - int getBorderWidth(); - int getBorderHeight(); + int getWidth() const { return width; } + int getHeight() const { return height; } + int getBorderWidth() const { return border_width; } + int getBorderHeight() const { return border_height; } bool isWithinBounds(int x, int y); bool isWithinBorderBounds(int x, int y); diff --git a/include/project.h b/include/project.h index f35fc403..0b8b9978 100644 --- a/include/project.h +++ b/include/project.h @@ -247,8 +247,6 @@ class Project : public QObject void setNewLayoutBlockdata(Layout *layout); void setNewLayoutBorder(Layout *layout); - void setNewMapEvents(Map *map); - void setNewMapConnections(Map *map); void saveHealLocationsData(Map *map); void saveHealLocationsConstants(); diff --git a/src/core/events.cpp b/src/core/events.cpp index 89416eae..6cd92589 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -44,7 +44,7 @@ void Event::setPixmapItem(DraggablePixmapItem *item) { } int Event::getEventIndex() { - return this->map->events.value(this->getEventGroup()).indexOf(this); + return this->map->getIndexOfEvent(this); } void Event::setDefaultValues(Project *) { @@ -424,7 +424,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { void CloneObjectEvent::setDefaultValues(Project *project) { this->setGfx(project->gfxDefines.keys().value(0, "0")); this->setTargetID(1); - if (this->getMap()) this->setTargetMap(this->getMap()->name); + if (this->getMap()) this->setTargetMap(this->getMap()->name()); } const QSet expectedCloneObjectFields = { @@ -445,7 +445,7 @@ void CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone int eventIndex = this->targetID - 1; Map *clonedMap = project->getMap(this->targetMap); - Event *clonedEvent = clonedMap ? clonedMap->events[Event::Group::Object].value(eventIndex, nullptr) : nullptr; + Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, eventIndex) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { // Get graphics data from cloned object @@ -534,7 +534,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { } void WarpEvent::setDefaultValues(Project *) { - if (this->getMap()) this->setDestinationMap(this->getMap()->name); + if (this->getMap()) this->setDestinationMap(this->getMap()->name()); this->setDestinationWarpID("0"); this->setElevation(0); } @@ -952,13 +952,13 @@ void HealLocationEvent::setDefaultValues(Project *) { if (!this->getMap()) return; bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString mapConstant = Map::mapConstantFromName(this->getMap()->name, false); + const QString mapConstant = Map::mapConstantFromName(this->getMap()->name(), false); const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix : ProjectIdentifier::define_heal_locations_prefix); this->setLocationName(mapConstant); this->setIdName(prefix + mapConstant); if (respawnEnabled) { - this->setRespawnMap(this->getMap()->name); + this->setRespawnMap(this->getMap()->name()); this->setRespawnNPC(1); } } diff --git a/src/core/map.cpp b/src/core/map.cpp index 2a7d96dc..496e645f 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -13,25 +13,27 @@ Map::Map(QObject *parent) : QObject(parent) { - editHistory.setClean(); + m_editHistory = new QUndoStack(this); + resetEvents(); } Map::~Map() { - qDeleteAll(ownedEvents); - ownedEvents.clear(); + qDeleteAll(m_ownedEvents); + m_ownedEvents.clear(); deleteConnections(); } void Map::setName(QString mapName) { - name = mapName; - constantName = mapConstantFromName(mapName); - scriptsLoaded = false; + m_name = mapName; + m_constantName = mapConstantFromName(mapName); + m_scriptsLoaded = false; } +// Note: Map does not take ownership of layout void Map::setLayout(Layout *layout) { - this->layout = layout; + m_layout = layout; if (layout) { - this->layoutId = layout->id; + m_layoutId = layout->id; } } @@ -51,20 +53,20 @@ QString Map::mapConstantFromName(QString mapName, bool includePrefix) { return constantName; } -int Map::getWidth() { - return layout->getWidth(); +int Map::getWidth() const { + return m_layout->getWidth(); } -int Map::getHeight() { - return layout->getHeight(); +int Map::getHeight() const { + return m_layout->getHeight(); } -int Map::getBorderWidth() { - return layout->getBorderWidth(); +int Map::getBorderWidth() const { + return m_layout->getBorderWidth(); } -int Map::getBorderHeight() { - return layout->getBorderHeight(); +int Map::getBorderHeight() const { + return m_layout->getBorderHeight(); } // Get the portion of the map that can be rendered when rendered as a map connection. @@ -106,7 +108,7 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { if (MapConnection::isDiving(direction)) fromLayout = nullptr; - QPixmap connectionPixmap = this->layout->render(true, fromLayout, bounds); + QPixmap connectionPixmap = m_layout->render(true, fromLayout, bounds); return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); } @@ -114,18 +116,10 @@ void Map::openScript(QString label) { emit openScriptRequested(label); } -QList Map::getAllEvents() const { - QList all_events; - for (const auto &event_list : events) { - all_events << event_list; - } - return all_events; -} - QStringList Map::getScriptLabels(Event::Group group) { - if (!this->scriptsLoaded) { - this->scriptsFileLabels = ParseUtil::getGlobalScriptLabels(this->getScriptsFilePath()); - this->scriptsLoaded = true; + if (!m_scriptsLoaded) { + m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath()); + m_scriptsLoaded = true; } QStringList scriptLabels; @@ -133,20 +127,20 @@ QStringList Map::getScriptLabels(Event::Group group) { // Get script labels currently in-use by the map's events if (group == Event::Group::None) { ScriptTracker scriptTracker; - for (Event *event : this->getAllEvents()) { + for (const auto &event : getEvents()) { event->accept(&scriptTracker); } scriptLabels = scriptTracker.getScripts(); } else { ScriptTracker scriptTracker; - for (Event *event : events.value(group)) { + for (const auto &event : m_events.value(group)) { event->accept(&scriptTracker); } scriptLabels = scriptTracker.getScripts(); } // Add scripts from map's scripts file, and empty names. - scriptLabels.append(this->scriptsFileLabels); + scriptLabels.append(m_scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); scriptLabels.prepend("0x0"); scriptLabels.prepend("NULL"); @@ -162,7 +156,7 @@ QString Map::getScriptsFilePath() const { auto path = QDir::cleanPath(QString("%1/%2/%3/scripts") .arg(projectConfig.projectDir) .arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)) - .arg(this->name)); + .arg(m_name)); auto extension = Project::getScriptFileExtension(usePoryscript); if (usePoryscript && !QFile::exists(path + extension)) extension = Project::getScriptFileExtension(false); @@ -170,37 +164,77 @@ QString Map::getScriptsFilePath() const { return path; } +void Map::resetEvents() { + m_events[Event::Group::Object].clear(); + m_events[Event::Group::Warp].clear(); + m_events[Event::Group::Coord].clear(); + m_events[Event::Group::Bg].clear(); + m_events[Event::Group::Heal].clear(); +} + +QList Map::getEvents(Event::Group group) const { + if (group == Event::Group::None) { + // Get all events + QList all_events; + for (const auto &event_list : m_events) { + all_events << event_list; + } + return all_events; + } + return m_events[group]; +} + +Event* Map::getEvent(Event::Group group, int index) const { + return m_events[group].value(index, nullptr); +} + +int Map::getNumEvents(Event::Group group) const { + if (group == Event::Group::None) { + // Total number of events + int numEvents = 0; + for (auto i = m_events.constBegin(); i != m_events.constEnd(); i++) { + numEvents += i.value().length(); + } + return numEvents; + } + return m_events[group].length(); +} + void Map::removeEvent(Event *event) { - for (Event::Group key : events.keys()) { - events[key].removeAll(event); + for (auto i = m_events.begin(); i != m_events.end(); i++) { + i.value().removeAll(event); } } void Map::addEvent(Event *event) { event->setMap(this); - events[event->getEventGroup()].append(event); - if (!ownedEvents.contains(event)) ownedEvents.append(event); + m_events[event->getEventGroup()].append(event); + if (!m_ownedEvents.contains(event)) m_ownedEvents.append(event); +} + +int Map::getIndexOfEvent(Event *event) const { + return m_events.value(event->getEventGroup()).indexOf(event); } void Map::deleteConnections() { - qDeleteAll(this->ownedConnections); - this->ownedConnections.clear(); - this->connections.clear(); + qDeleteAll(m_ownedConnections); + m_ownedConnections.clear(); + m_connections.clear(); } QList Map::getConnections() const { - return this->connections; + return m_connections; } void Map::addConnection(MapConnection *connection) { - if (!connection || this->connections.contains(connection)) + if (!connection || m_connections.contains(connection)) return; // Maps should only have one Dive/Emerge connection at a time. // (Users can technically have more by editing their data manually, but we will only display one at a time) // Any additional connections being added (this can happen via mirroring) are tracked for deleting but otherwise ignored. if (MapConnection::isDiving(connection->direction())) { - for (auto i : this->connections) { + for (const auto &i : m_connections) { if (i->direction() == connection->direction()) { trackConnection(connection); return; @@ -218,8 +252,8 @@ void Map::loadConnection(MapConnection *connection) { if (!connection) return; - if (!this->connections.contains(connection)) - this->connections.append(connection); + if (!m_connections.contains(connection)) + m_connections.append(connection); trackConnection(connection); } @@ -227,12 +261,12 @@ void Map::loadConnection(MapConnection *connection) { void Map::trackConnection(MapConnection *connection) { connection->setParentMap(this, false); - if (!this->ownedConnections.contains(connection)) { - this->ownedConnections.insert(connection); + if (!m_ownedConnections.contains(connection)) { + m_ownedConnections.insert(connection); connect(connection, &MapConnection::parentMapChanged, [=](Map *, Map *after) { if (after != this && after != nullptr) { // MapConnection's parent has been reassigned, it's no longer our responsibility - this->ownedConnections.remove(connection); + m_ownedConnections.remove(connection); QObject::disconnect(connection, &MapConnection::parentMapChanged, this, nullptr); } }); @@ -241,23 +275,29 @@ void Map::trackConnection(MapConnection *connection) { // We retain ownership of this MapConnection until it's assigned to a new parent map. void Map::removeConnection(MapConnection *connection) { - if (!this->connections.removeOne(connection)) + if (!m_connections.removeOne(connection)) return; connection->setParentMap(nullptr, false); modify(); emit connectionRemoved(connection); } +void Map::commit(QUndoCommand *cmd) { + m_editHistory->push(cmd); +} + void Map::modify() { emit modified(); } -void Map::clean() { - this->hasUnsavedDataChanges = false; +void Map::setClean() { + m_editHistory->setClean(); + m_hasUnsavedDataChanges = false; + m_isPersistedToFile = true; } bool Map::hasUnsavedChanges() const { - return !editHistory.isClean() || this->layout->hasUnsavedChanges() || hasUnsavedDataChanges || !isPersistedToFile; + return !m_editHistory->isClean() || m_layout->hasUnsavedChanges() || m_hasUnsavedDataChanges || !m_isPersistedToFile; } void Map::pruneEditHistory() { @@ -271,12 +311,57 @@ void Map::pruneEditHistory() { ID_MapConnectionAdd, ID_MapConnectionRemove }; - for (int i = 0; i < this->editHistory.count(); i++) { + for (int i = 0; i < m_editHistory->count(); i++) { // Qt really doesn't expect editing commands in the stack to be valid (fair). // A better future design might be to have separate edit histories per map tab, // and dumping the entire Connections tab history with QUndoStack::clear. - auto command = const_cast(this->editHistory.command(i)); + auto command = const_cast(m_editHistory->command(i)); if (mapConnectionIds.contains(command->id())) command->setObsolete(true); } } + +void Map::setSong(const QString &song) { + m_song = song; +} + +void Map::setLocation(const QString &location) { + m_location = location; +} + +void Map::setRequiresFlash(bool requiresFlash) { + m_requiresFlash = requiresFlash; +} + +void Map::setWeather(const QString &weather) { + m_weather = weather; +} + +void Map::setType(const QString &type) { + m_type = type; +} + +void Map::setShowsLocation(bool showsLocation) { + m_showsLocation = showsLocation; +} + +void Map::setAllowsRunning(bool allowsRunning) { + m_allowsRunning = allowsRunning; +} + +void Map::setAllowsBiking(bool allowsBiking) { + m_allowsBiking = allowsBiking; +} + +void Map::setAllowsEscaping(bool allowsEscaping) { + m_allowsEscaping = allowsEscaping; +} + +void Map::setFloorNumber(int floorNumber) { + m_floorNumber = floorNumber; +} + +void Map::setBattleScene(const QString &battleScene) { + m_battleScene = battleScene; +} + diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index b80b8d09..db2755e9 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -65,7 +65,7 @@ QPixmap MapConnection::getPixmap() { if (!map) return QPixmap(); - return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout : nullptr); + return map->renderConnection(m_direction, m_parentMap ? m_parentMap->layout() : nullptr); } void MapConnection::setParentMap(Map* map, bool mirror) { @@ -75,7 +75,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) { if (mirror) { auto connection = findMirror(); if (connection) - connection->setTargetMapName(map ? map->name : QString(), false); + connection->setTargetMapName(map ? map->name() : QString(), false); } if (m_parentMap) @@ -91,7 +91,7 @@ void MapConnection::setParentMap(Map* map, bool mirror) { } QString MapConnection::parentMapName() const { - return m_parentMap ? m_parentMap->name : QString(); + return m_parentMap ? m_parentMap->name() : QString(); } void MapConnection::setTargetMapName(const QString &targetMapName, bool mirror) { diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 34033ac5..11e9943d 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -45,22 +45,6 @@ QString Layout::layoutConstantFromName(QString mapName) { return constantName; } -int Layout::getWidth() { - return width; -} - -int Layout::getHeight() { - return height; -} - -int Layout::getBorderWidth() { - return border_width; -} - -int Layout::getBorderHeight() { - return border_height; -} - bool Layout::isWithinBounds(int x, int y) { return (x >= 0 && x < this->getWidth() && y >= 0 && y < this->getHeight()); } diff --git a/src/editor.cpp b/src/editor.cpp index 86cedd5b..e13f76eb 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -152,7 +152,7 @@ void Editor::setEditorView() { map_item->setEditsEnabled(false); case EditMode::Events: if (this->map) { - this->editGroup.setActiveStack(&this->map->editHistory); + this->editGroup.setActiveStack(this->map->editHistory()); } break; case EditMode::Header: @@ -240,23 +240,23 @@ void Editor::displayWildMonTables() { clearWildMonTables(); // Don't try to read encounter data if it doesn't exist on disk for this map. - if (!project->wildMonData.contains(map->constantName)) { + if (!project->wildMonData.contains(map->constantName())) { return; } QComboBox *labelCombo = ui->comboBox_EncounterGroupLabel; - for (auto groupPair : project->wildMonData[map->constantName]) + for (auto groupPair : project->wildMonData[map->constantName()]) labelCombo->addItem(groupPair.first); labelCombo->setCurrentText(labelCombo->itemText(0)); QStackedWidget *stack = ui->stackedWidget_WildMons; int labelIndex = 0; - for (auto labelPair : project->wildMonData[map->constantName]) { + for (auto labelPair : project->wildMonData[map->constantName()]) { QString label = labelPair.first; - WildPokemonHeader header = project->wildMonData[map->constantName][label]; + WildPokemonHeader header = project->wildMonData[map->constantName()][label]; MonTabWidget *tabWidget = new MonTabWidget(this); stack->insertWidget(labelIndex++, tabWidget); @@ -267,7 +267,7 @@ void Editor::displayWildMonTables() { tabWidget->clearTableAt(tabIndex); - if (project->wildMonData.contains(map->constantName) && header.wildMons[fieldName].active) { + if (project->wildMonData.contains(map->constantName()) && header.wildMons[fieldName].active) { tabWidget->populateTab(tabIndex, header.wildMons[fieldName]); } else { tabWidget->setTabActive(tabIndex, false); @@ -312,7 +312,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { } }); // Give a default value to the label. - lineEdit->setText(QString("g%1%2").arg(map->name).arg(stack->count())); + lineEdit->setText(QString("g%1%2").arg(map->name()).arg(stack->count())); // Fields [x] copy from existing QLabel *fieldsLabel = new QLabel("Fields:"); @@ -415,9 +415,9 @@ void Editor::deleteWildMonGroup() { msgBox.exec(); if (msgBox.clickedButton() == deleteButton) { - auto it = project->wildMonData.find(map->constantName); + auto it = project->wildMonData.find(map->constantName()); if (it == project->wildMonData.end()) { - logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName)); + logError(QString("Failed to find data for map %1. Unable to delete").arg(map->constantName())); return; } @@ -698,7 +698,7 @@ void Editor::saveEncounterTabData() { if (!stack->count()) return; - tsl::ordered_map &encounterMap = project->wildMonData[map->constantName]; + tsl::ordered_map &encounterMap = project->wildMonData[map->constantName()]; for (int groupIndex = 0; groupIndex < stack->count(); groupIndex++) { MonTabWidget *tabWidget = static_cast(stack->widget(groupIndex)); @@ -863,13 +863,13 @@ void Editor::addConnection(MapConnection *connection) { // It's possible this is a Dive/Emerge connection, but that's ok (no selection will occur). connection_to_select = connection; - this->map->editHistory.push(new MapConnectionAdd(this->map, connection)); + this->map->commit(new MapConnectionAdd(this->map, connection)); } void Editor::removeConnection(MapConnection *connection) { if (!connection) return; - this->map->editHistory.push(new MapConnectionRemove(this->map, connection)); + this->map->commit(new MapConnectionRemove(this->map, connection)); } void Editor::removeConnectionPixmap(MapConnection *connection) { @@ -986,7 +986,7 @@ void Editor::setDivingMapName(QString mapName, QString direction) { if (mapName.isEmpty()) { removeConnection(connection); } else { - map->editHistory.push(new MapConnectionChangeMap(connection, mapName)); + map->commit(new MapConnectionChangeMap(connection, mapName)); } } else if (!mapName.isEmpty()) { // Create new connection @@ -1268,10 +1268,10 @@ bool Editor::setMap(QString map_name) { this->map = loadedMap; - setLayout(map->layout->id); + setLayout(map->layout()->id); - editGroup.addStack(&map->editHistory); - editGroup.setActiveStack(&map->editHistory); + editGroup.addStack(map->editHistory()); + editGroup.setActiveStack(map->editHistory()); selected_events->clear(); if (!displayMap()) { @@ -1469,7 +1469,7 @@ void Editor::mouseEvent_map(QGraphicsSceneMouseEvent *event, LayoutPixmapItem *i } selection_origin = QPoint(pos.x(), pos.y()); - map->editHistory.push(new EventShift(selectedEvents, xDelta, yDelta, actionId)); + map->commit(new EventShift(selectedEvents, xDelta, yDelta, actionId)); } } } @@ -1784,8 +1784,7 @@ void Editor::displayMapEvents() { events_group = new QGraphicsItemGroup; scene->addItem(events_group); - QList events = map->getAllEvents(); - for (Event *event : events) { + for (const auto &event : map->getEvents()) { project->setEventPixmap(event); addMapEvent(event); } @@ -2032,14 +2031,14 @@ void Editor::updateBorderVisibility() { // When connecting a map to itself we don't bother to re-render the map connections in real-time, // i.e. if the user paints a new metatile on the map this isn't immediately reflected in the connection. // We're rendering them now, so we take the opportunity to do a full re-render for self-connections. - bool fullRender = (this->map && item->connection && this->map->name == item->connection->targetMapName()); + bool fullRender = (this->map && item->connection && this->map->name() == item->connection->targetMapName()); item->render(fullRender); } } void Editor::updateCustomMapHeaderValues(QTableWidget *table) { - map->customHeaders = CustomAttributesTable::getAttributes(table); + map->setCustomAttributes(CustomAttributesTable::getAttributes(table)); map->modify(); } @@ -2080,13 +2079,13 @@ void Editor::redrawObject(DraggablePixmapItem *item) { void Editor::updateWarpEventWarning(Event *event) { if (porymapConfig.warpBehaviorWarningDisabled) return; - if (!project || !map || !map->layout || !event || event->getEventType() != Event::Type::Warp) + if (!project || !map || !map->layout() || !event || event->getEventType() != Event::Type::Warp) return; Block block; Metatile * metatile = nullptr; WarpEvent * warpEvent = static_cast(event); - if (map->layout->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) { - metatile = Tileset::getMetatile(block.metatileId(), map->layout->tileset_primary, map->layout->tileset_secondary); + if (map->layout()->getBlock(warpEvent->getX(), warpEvent->getY(), &block)) { + metatile = Tileset::getMetatile(block.metatileId(), map->layout()->tileset_primary, map->layout()->tileset_secondary); } // metatile may be null if the warp is in the map border. Display the warning in this case bool validWarpBehavior = metatile && projectConfig.warpBehaviors.contains(metatile->behavior()); @@ -2144,10 +2143,7 @@ void Editor::selectMapEvent(DraggablePixmapItem *object, bool toggle) { void Editor::selectedEventIndexChanged(int index, Event::Group eventGroup) { int event_offs = Event::getIndexOffset(eventGroup); index = index - event_offs; - Event *event = nullptr; - if (index < this->map->events.value(eventGroup).length()) { - event = this->map->events.value(eventGroup).at(index); - } + Event *event = this->map->getEvent(eventGroup, index); DraggablePixmapItem *selectedEvent = nullptr; for (QGraphicsItem *child : this->events_group->childItems()) { DraggablePixmapItem *item = static_cast(child); @@ -2189,7 +2185,7 @@ void Editor::duplicateSelectedEvents() { duplicate->setY(duplicate->getY() + 1); selectedEvents.append(duplicate); } - map->editHistory.push(new EventDuplicate(this, map, selectedEvents)); + map->commit(new EventDuplicate(this, map, selectedEvents)); } DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { @@ -2209,7 +2205,7 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { ((HealLocationEvent *)event)->setIndex(project->healLocations.length()); } - map->editHistory.push(new EventCreate(this, map, event)); + map->commit(new EventCreate(this, map, event)); return event->getPixmapItem(); } @@ -2217,7 +2213,7 @@ DraggablePixmapItem *Editor::addNewEvent(Event::Type type) { bool Editor::eventLimitReached(Event::Type event_type) { if (project && map) { if (Event::typeToGroup(event_type) == Event::Group::Object) - return map->events.value(Event::Group::Object).length() >= project->getMaxObjectEvents(); + return map->getNumEvents(Event::Group::Object) >= project->getMaxObjectEvents(); } return false; } @@ -2246,14 +2242,12 @@ void Editor::deleteSelectedEvents() { // If deleting multiple events, just let editor work out next selected. if (numDeleted == 1) { Event::Group event_group = selectedEvents[0]->getEventGroup(); - int index = this->map->events.value(event_group).indexOf(selectedEvents[0]); - if (index != this->map->events.value(event_group).size() - 1) + int index = this->map->getIndexOfEvent(selectedEvents[0]); + if (index != this->map->getNumEvents(event_group) - 1) index++; else index--; - Event *event = nullptr; - if (index >= 0) - event = this->map->events.value(event_group).at(index); + Event *event = this->map->getEvent(event_group, index); for (QGraphicsItem *child : this->events_group->childItems()) { DraggablePixmapItem *event_item = static_cast(child); if (event_item->event == event) { @@ -2262,7 +2256,7 @@ void Editor::deleteSelectedEvents() { } } } - this->map->editHistory.push(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); + this->map->commit(new EventDelete(this, this->map, selectedEvents, nextSelectedEvent ? nextSelectedEvent->event : nullptr)); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d9e65eb3..c8885f62 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -442,7 +442,7 @@ void MainWindow::updateWindowTitle() { if (editor->map) { setWindowTitle(QString("%1%2 - %3") .arg(editor->map->hasUnsavedChanges() ? "* " : "") - .arg(editor->map->name) + .arg(editor->map->name()) .arg(projectName) ); } else { @@ -471,7 +471,7 @@ void MainWindow::markMapEdited() { void MainWindow::markSpecificMapEdited(Map* map) { if (!map) return; - map->hasUnsavedDataChanges = true; + map->setHasUnsavedDataChanges(true); if (editor && editor->map == map) updateWindowTitle(); @@ -827,7 +827,7 @@ void MainWindow::unsetMap() { // setMap, but with a visible error message in case of failure. // Use when the user is specifically requesting a map to open. bool MainWindow::userSetMap(QString map_name) { - if (editor->map && editor->map->name == map_name) + if (editor->map && editor->map->name() == map_name) return true; // Already set if (map_name == DYNAMIC_MAP_NAME) { @@ -988,20 +988,19 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ // Select the target event. int index = event_id - Event::getIndexOffset(event_group); - QList events = editor->map->events[event_group]; - if (index < events.length() && index >= 0) { - Event *event = events.at(index); + Event* event = editor->map->getEvent(event_group, index); + if (event) { for (DraggablePixmapItem *item : editor->getObjects()) { if (item->event == event) { editor->selected_events->clear(); editor->selected_events->append(item); editor->updateSelectedEvents(); + return; } } - } 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::eventGroupToString(event_group)).arg(event_id).arg(map_name)); } + // 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::eventGroupToString(event_group)).arg(event_id).arg(map_name)); } void MainWindow::displayMapProperties() { @@ -1033,35 +1032,37 @@ void MainWindow::displayMapProperties() { ui->frame_3->setEnabled(true); Map *map = editor->map; - ui->comboBox_PrimaryTileset->setCurrentText(map->layout->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(map->layout->tileset_secondary_label); - - ui->comboBox_Song->setCurrentText(map->song); - ui->comboBox_Location->setCurrentText(map->location); - ui->checkBox_Visibility->setChecked(map->requiresFlash); - ui->comboBox_Weather->setCurrentText(map->weather); - ui->comboBox_Type->setCurrentText(map->type); - ui->comboBox_BattleScene->setCurrentText(map->battle_scene); - ui->checkBox_ShowLocation->setChecked(map->show_location); - ui->checkBox_AllowRunning->setChecked(map->allowRunning); - ui->checkBox_AllowBiking->setChecked(map->allowBiking); - ui->checkBox_AllowEscaping->setChecked(map->allowEscaping); - ui->spinBox_FloorNumber->setValue(map->floorNumber); + ui->comboBox_PrimaryTileset->setCurrentText(map->layout()->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(map->layout()->tileset_secondary_label); + + ui->comboBox_Song->setCurrentText(map->song()); + ui->comboBox_Location->setCurrentText(map->location()); + ui->checkBox_Visibility->setChecked(map->requiresFlash()); + ui->comboBox_Weather->setCurrentText(map->weather()); + ui->comboBox_Type->setCurrentText(map->type()); + ui->comboBox_BattleScene->setCurrentText(map->battleScene()); + ui->checkBox_ShowLocation->setChecked(map->showsLocation()); + ui->checkBox_AllowRunning->setChecked(map->allowsRunning()); + ui->checkBox_AllowBiking->setChecked(map->allowsBiking()); + ui->checkBox_AllowEscaping->setChecked(map->allowsEscaping()); + ui->spinBox_FloorNumber->setValue(map->floorNumber()); // Custom fields table. +/* // TODO: Re-enable ui->tableWidget_CustomHeaderFields->blockSignals(true); ui->tableWidget_CustomHeaderFields->setRowCount(0); for (auto it = map->customHeaders.begin(); it != map->customHeaders.end(); it++) CustomAttributesTable::addAttribute(ui->tableWidget_CustomHeaderFields, it.key(), it.value()); ui->tableWidget_CustomHeaderFields->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); ui->tableWidget_CustomHeaderFields->blockSignals(false); +*/ } void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &text) { if (editor && editor->project && editor->map) { if (editor->project->mapLayouts.contains(text)) { editor->map->setLayout(editor->project->loadLayout(text)); - setMap(editor->map->name); + setMap(editor->map->name()); markMapEdited(); } } @@ -1070,7 +1071,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) { if (editor && editor->map) { - editor->map->song = song; + editor->map->setSong(song); markMapEdited(); } } @@ -1078,7 +1079,7 @@ void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) void MainWindow::on_comboBox_Location_currentTextChanged(const QString &location) { if (editor && editor->map) { - editor->map->location = location; + editor->map->setLocation(location); markMapEdited(); } } @@ -1086,7 +1087,7 @@ void MainWindow::on_comboBox_Location_currentTextChanged(const QString &location void MainWindow::on_comboBox_Weather_currentTextChanged(const QString &weather) { if (editor && editor->map) { - editor->map->weather = weather; + editor->map->setWeather(weather); markMapEdited(); } } @@ -1094,7 +1095,7 @@ void MainWindow::on_comboBox_Weather_currentTextChanged(const QString &weather) void MainWindow::on_comboBox_Type_currentTextChanged(const QString &type) { if (editor && editor->map) { - editor->map->type = type; + editor->map->setType(type); markMapEdited(); } } @@ -1102,7 +1103,7 @@ void MainWindow::on_comboBox_Type_currentTextChanged(const QString &type) void MainWindow::on_comboBox_BattleScene_currentTextChanged(const QString &battle_scene) { if (editor && editor->map) { - editor->map->battle_scene = battle_scene; + editor->map->setBattleScene(battle_scene); markMapEdited(); } } @@ -1110,7 +1111,7 @@ void MainWindow::on_comboBox_BattleScene_currentTextChanged(const QString &battl void MainWindow::on_checkBox_Visibility_stateChanged(int selected) { if (editor && editor->map) { - editor->map->requiresFlash = (selected == Qt::Checked); + editor->map->setRequiresFlash(selected == Qt::Checked); markMapEdited(); } } @@ -1118,7 +1119,7 @@ void MainWindow::on_checkBox_Visibility_stateChanged(int selected) void MainWindow::on_checkBox_ShowLocation_stateChanged(int selected) { if (editor && editor->map) { - editor->map->show_location = (selected == Qt::Checked); + editor->map->setShowsLocation(selected == Qt::Checked); markMapEdited(); } } @@ -1126,7 +1127,7 @@ void MainWindow::on_checkBox_ShowLocation_stateChanged(int selected) void MainWindow::on_checkBox_AllowRunning_stateChanged(int selected) { if (editor && editor->map) { - editor->map->allowRunning = (selected == Qt::Checked); + editor->map->setAllowsRunning(selected == Qt::Checked); markMapEdited(); } } @@ -1134,7 +1135,7 @@ void MainWindow::on_checkBox_AllowRunning_stateChanged(int selected) void MainWindow::on_checkBox_AllowBiking_stateChanged(int selected) { if (editor && editor->map) { - editor->map->allowBiking = (selected == Qt::Checked); + editor->map->setAllowsBiking(selected == Qt::Checked); markMapEdited(); } } @@ -1142,7 +1143,7 @@ void MainWindow::on_checkBox_AllowBiking_stateChanged(int selected) void MainWindow::on_checkBox_AllowEscaping_stateChanged(int selected) { if (editor && editor->map) { - editor->map->allowEscaping = (selected == Qt::Checked); + editor->map->setAllowsEscaping(selected == Qt::Checked); markMapEdited(); } } @@ -1150,7 +1151,7 @@ void MainWindow::on_checkBox_AllowEscaping_stateChanged(int selected) void MainWindow::on_spinBox_FloorNumber_valueChanged(int offset) { if (editor && editor->map) { - editor->map->floorNumber = offset; + editor->map->setFloorNumber(offset); markMapEdited(); } } @@ -1251,7 +1252,7 @@ void MainWindow::refreshLocationsComboBox() { ui->comboBox_Location->clear(); ui->comboBox_Location->addItems(locations); if (this->editor->map) - ui->comboBox_Location->setCurrentText(this->editor->map->location); + ui->comboBox_Location->setCurrentText(this->editor->map->location()); } void MainWindow::clearProjectUI() { @@ -1309,7 +1310,7 @@ void MainWindow::scrollMapList(MapTree *list, QString itemName) { void MainWindow::scrollMapListToCurrentMap(MapTree *list) { if (this->editor->map) { - scrollMapList(list, this->editor->map->name); + scrollMapList(list, this->editor->map->name()); } } @@ -1591,7 +1592,7 @@ void MainWindow::mapListAddArea() { } void MainWindow::onNewMapCreated() { - QString newMapName = this->newMapPrompt->map->name; + QString newMapName = this->newMapPrompt->map->name(); int newMapGroup = this->newMapPrompt->group; Map *newMap = this->newMapPrompt->map; bool existingLayout = this->newMapPrompt->existingLayout; @@ -1607,8 +1608,8 @@ void MainWindow::onNewMapCreated() { // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); - this->mapAreaModel->insertMapItem(newMapName, newMap->location, newMapGroup); - this->layoutTreeModel->insertMapItem(newMapName, newMap->layout->id); + this->mapAreaModel->insertMapItem(newMapName, newMap->location(), newMapGroup); + this->layoutTreeModel->insertMapItem(newMapName, newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps // (other combo boxes like for warp destinations are repopulated when the map changes). @@ -1622,16 +1623,16 @@ void MainWindow::onNewMapCreated() { // Refresh layout combo box (if a new one was created) if (!existingLayout) { - int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout->id); + int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout()->id); if (layoutIndex >= 0) { const QSignalBlocker b_Layouts(ui->comboBox_LayoutSelector); - ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout->id); + ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout()->id); } } setMap(newMapName); - if (newMap->needsHealLocation) { + if (newMap->needsHealLocation()) { addNewEvent(Event::Type::HealLocation); editor->project->saveHealLocations(newMap); editor->save(); @@ -1862,9 +1863,9 @@ void MainWindow::openMapListItem(const QModelIndex &index) { void MainWindow::updateMapList() { if (this->editor->map) { - this->mapGroupModel->setMap(this->editor->map->name); + this->mapGroupModel->setMap(this->editor->map->name()); this->groupListProxyModel->layoutChanged(); - this->mapAreaModel->setMap(this->editor->map->name); + this->mapAreaModel->setMap(this->editor->map->name()); this->areaListProxyModel->layoutChanged(); } else { this->mapGroupModel->setMap(QString()); @@ -2107,7 +2108,7 @@ void MainWindow::paste() { } if (!newEvents.empty()) { - editor->map->editHistory.push(new EventPaste(this->editor, editor->map, newEvents)); + editor->map->commit(new EventPaste(this->editor, editor->map, newEvents)); updateObjects(); } @@ -2329,7 +2330,7 @@ void MainWindow::addNewEvent(Event::Type type) { void MainWindow::tryAddEventTab(QWidget * tab) { auto group = getEventGroupFromTabWidget(tab); - if (editor->map->events.value(group).length()) + if (editor->map->getNumEvents(group)) ui->tabWidget_EventType->addTab(tab, QString("%1s").arg(Event::eventGroupToString(group))); } @@ -2363,7 +2364,7 @@ void MainWindow::updateSelectedObjects() { else { QList all_events; if (editor->map) { - all_events = editor->map->getAllEvents(); + all_events = editor->map->getEvents(); } if (all_events.length()) { DraggablePixmapItem *selectedEvent = all_events.first()->getPixmapItem(); @@ -2397,7 +2398,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_ObjectID); this->ui->spinner_ObjectID->setMinimum(event_offs); - this->ui->spinner_ObjectID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_ObjectID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_ObjectID->setValue(current->getEventIndex() + event_offs); break; } @@ -2408,7 +2409,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_WarpID); this->ui->spinner_WarpID->setMinimum(event_offs); - this->ui->spinner_WarpID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_WarpID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_WarpID->setValue(current->getEventIndex() + event_offs); break; } @@ -2419,7 +2420,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_TriggerID); this->ui->spinner_TriggerID->setMinimum(event_offs); - this->ui->spinner_TriggerID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_TriggerID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_TriggerID->setValue(current->getEventIndex() + event_offs); break; } @@ -2430,7 +2431,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_BgID); this->ui->spinner_BgID->setMinimum(event_offs); - this->ui->spinner_BgID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_BgID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_BgID->setValue(current->getEventIndex() + event_offs); break; } @@ -2441,7 +2442,7 @@ void MainWindow::updateSelectedObjects() { QSignalBlocker b(this->ui->spinner_HealID); this->ui->spinner_HealID->setMinimum(event_offs); - this->ui->spinner_HealID->setMaximum(current->getMap()->events.value(eventGroup).length() + event_offs - 1); + this->ui->spinner_HealID->setMaximum(current->getMap()->getNumEvents(eventGroup) + event_offs - 1); this->ui->spinner_HealID->setValue(current->getEventIndex() + event_offs); break; } @@ -2534,8 +2535,8 @@ void MainWindow::eventTabChanged(int index) { } if (!isProgrammaticEventTabChange) { - if (!selectedEvent && editor->map->events.value(group).count()) { - Event *event = editor->map->events.value(group).at(0); + if (!selectedEvent && editor->map->getNumEvents(group)) { + Event *event = editor->map->getEvent(group, 0); for (QGraphicsItem *child : editor->events_group->childItems()) { DraggablePixmapItem *item = static_cast(child); if (item->event == event) { @@ -3222,7 +3223,7 @@ void MainWindow::reloadScriptEngine() { // Lying to the scripts here, simulating a project reload Scripting::cb_ProjectOpened(projectConfig.projectDir); if (editor && editor->map) - Scripting::cb_MapOpened(editor->map->name); // TODO: API should have equivalent for layout + Scripting::cb_MapOpened(editor->map->name()); // TODO: API should have equivalent for layout } void MainWindow::on_pushButton_AddCustomHeaderField_clicked() diff --git a/src/project.cpp b/src/project.cpp index 22487a13..1a9abffd 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -195,11 +195,11 @@ QSet Project::getTopLevelMapFields() { } bool Project::loadMapData(Map* map) { - if (!map->isPersistedToFile) { + if (!map->isPersistedToFile()) { return true; } - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map->name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); + QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map->name()).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); QJsonDocument mapDoc; if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { logError(QString("Failed to read map data from %1").arg(mapFilepath)); @@ -208,28 +208,28 @@ bool Project::loadMapData(Map* map) { QJsonObject mapObj = mapDoc.object(); - map->song = ParseUtil::jsonToQString(mapObj["music"]); - map->layoutId = ParseUtil::jsonToQString(mapObj["layout"]); - map->location = ParseUtil::jsonToQString(mapObj["region_map_section"]); - map->requiresFlash = ParseUtil::jsonToBool(mapObj["requires_flash"]); - map->weather = ParseUtil::jsonToQString(mapObj["weather"]); - map->type = ParseUtil::jsonToQString(mapObj["map_type"]); - map->show_location = ParseUtil::jsonToBool(mapObj["show_map_name"]); - map->battle_scene = ParseUtil::jsonToQString(mapObj["battle_scene"]); + map->setSong(ParseUtil::jsonToQString(mapObj["music"])); + map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); + map->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); + map->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); + map->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); + map->setType(ParseUtil::jsonToQString(mapObj["map_type"])); + map->setShowsLocation(ParseUtil::jsonToBool(mapObj["show_map_name"])); + map->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); if (projectConfig.mapAllowFlagsEnabled) { - map->allowBiking = ParseUtil::jsonToBool(mapObj["allow_cycling"]); - map->allowEscaping = ParseUtil::jsonToBool(mapObj["allow_escaping"]); - map->allowRunning = ParseUtil::jsonToBool(mapObj["allow_running"]); + map->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); + map->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); + map->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); } if (projectConfig.floorNumberEnabled) { - map->floorNumber = ParseUtil::jsonToInt(mapObj["floor_number"]); + map->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); } - map->sharedEventsMap = ParseUtil::jsonToQString(mapObj["shared_events_map"]); - map->sharedScriptsMap = ParseUtil::jsonToQString(mapObj["shared_scripts_map"]); + map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); + map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); // Events - map->events[Event::Group::Object].clear(); + map->resetEvents(); QJsonArray objectEventsArr = mapObj["object_events"].toArray(); for (int i = 0; i < objectEventsArr.size(); i++) { QJsonObject event = objectEventsArr[i].toObject(); @@ -248,11 +248,10 @@ bool Project::loadMapData(Map* map) { delete clone; } } else { - logError(QString("Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'.").arg(map->name).arg(i).arg(type)); + logError(QString("Map %1 object_event %2 has invalid type '%3'. Must be 'object' or 'clone'.").arg(map->name()).arg(i).arg(type)); } } - map->events[Event::Group::Warp].clear(); QJsonArray warpEventsArr = mapObj["warp_events"].toArray(); for (int i = 0; i < warpEventsArr.size(); i++) { QJsonObject event = warpEventsArr[i].toObject(); @@ -265,7 +264,6 @@ bool Project::loadMapData(Map* map) { } } - map->events[Event::Group::Coord].clear(); QJsonArray coordEventsArr = mapObj["coord_events"].toArray(); for (int i = 0; i < coordEventsArr.size(); i++) { QJsonObject event = coordEventsArr[i].toObject(); @@ -279,11 +277,10 @@ bool Project::loadMapData(Map* map) { coord->loadFromJson(event, this); map->addEvent(coord); } else { - logError(QString("Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'.").arg(map->name).arg(i).arg(type)); + logError(QString("Map %1 coord_event %2 has invalid type '%3'. Must be 'trigger' or 'weather'.").arg(map->name()).arg(i).arg(type)); } } - map->events[Event::Group::Bg].clear(); QJsonArray bgEventsArr = mapObj["bg_events"].toArray(); for (int i = 0; i < bgEventsArr.size(); i++) { QJsonObject event = bgEventsArr[i].toObject(); @@ -301,12 +298,12 @@ bool Project::loadMapData(Map* map) { bg->loadFromJson(event, this); map->addEvent(bg); } else { - logError(QString("Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'.").arg(map->name).arg(i).arg(type)); + logError(QString("Map %1 bg_event %2 has invalid type '%3'. Must be 'sign', 'hidden_item', or 'secret_base'.").arg(map->name()).arg(i).arg(type)); } } - map->events[Event::Group::Heal].clear(); - + +/* TODO: Re-enable const QString mapPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); for (auto it = healLocations.begin(); it != healLocations.end(); it++) { HealLocation loc = *it; @@ -328,6 +325,7 @@ bool Project::loadMapData(Map* map) { map->ownedEvents.append(heal); } } +*/ map->deleteConnections(); QJsonArray connectionsArr = mapObj["connections"].toArray(); @@ -347,19 +345,21 @@ bool Project::loadMapData(Map* map) { } // Check for custom fields +/* // TODO: Re-enable QSet baseFields = this->getTopLevelMapFields(); for (QString key : mapObj.keys()) { if (!baseFields.contains(key)) { map->customHeaders.insert(key, mapObj[key]); } } +*/ return true; } QString Project::readMapLayoutId(QString map_name) { if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->layoutId; + return mapCache.value(map_name)->layoutId(); } QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); @@ -375,7 +375,7 @@ QString Project::readMapLayoutId(QString map_name) { QString Project::readMapLocation(QString map_name) { if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->location; + return mapCache.value(map_name)->location(); } QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); @@ -473,21 +473,21 @@ Layout *Project::loadLayout(QString layoutId) { } bool Project::loadMapLayout(Map* map) { - if (!map->isPersistedToFile) { + if (!map->isPersistedToFile()) { return true; } - if (mapLayouts.contains(map->layoutId)) { - map->layout = mapLayouts[map->layoutId]; + if (mapLayouts.contains(map->layoutId())) { + map->setLayout(mapLayouts[map->layoutId()]); } else { - logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name).arg(map->layoutId)); + logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } if (map->hasUnsavedChanges()) { return true; } else { - return loadLayout(map->layout); + return loadLayout(map->layout()); } } @@ -848,12 +848,14 @@ void Project::saveHealLocations(Map *map) { // Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h void Project::saveHealLocationsData(Map *map) { // Update heal locations from map +/* TODO: Re-enable if (map->events[Event::Group::Heal].length() > 0) { for (Event *healEvent : map->events[Event::Group::Heal]) { HealLocation hl = HealLocation::fromEvent(healEvent); this->healLocations[hl.index - 1] = hl; } } +*/ // Find any duplicate constant names QMap healLocationsDupes; @@ -1262,14 +1264,14 @@ void Project::saveAllMaps() { void Project::saveMap(Map *map) { // Create/Modify a few collateral files for brand new maps. QString basePath = projectConfig.getFilePath(ProjectFilePath::data_map_folders); - QString mapDataDir = root + "/" + basePath + map->name; - if (!map->isPersistedToFile) { + QString mapDataDir = root + "/" + basePath + map->name(); + if (!map->isPersistedToFile()) { if (!QDir::root().mkdir(mapDataDir)) { logError(QString("Error: failed to create directory for new map: '%1'").arg(mapDataDir)); } // Create file data/maps//scripts.inc - QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name); + QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name()); saveTextFile(mapDataDir + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); if (projectConfig.createMapTextFileEnabled) { @@ -1278,14 +1280,14 @@ void Project::saveMap(Map *map) { } // Simply append to data/event_scripts.s. - text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name); + text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name()); if (projectConfig.createMapTextFileEnabled) { - text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name); + text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name()); } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); - if (map->needsLayoutDir) { - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name); + if (map->needsLayoutDir()) { + QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); if (!QDir::root().mkdir(newLayoutDir)) { logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); } @@ -1302,24 +1304,24 @@ void Project::saveMap(Map *map) { OrderedJson::object mapObj; // Header values. - mapObj["id"] = map->constantName; - mapObj["name"] = map->name; - mapObj["layout"] = map->layout->id; - mapObj["music"] = map->song; - mapObj["region_map_section"] = map->location; - mapObj["requires_flash"] = map->requiresFlash; - mapObj["weather"] = map->weather; - mapObj["map_type"] = map->type; + mapObj["id"] = map->constantName(); + mapObj["name"] = map->name(); + mapObj["layout"] = map->layout()->id; + mapObj["music"] = map->song(); + mapObj["region_map_section"] = map->location(); + mapObj["requires_flash"] = map->requiresFlash(); + mapObj["weather"] = map->weather(); + mapObj["map_type"] = map->type(); if (projectConfig.mapAllowFlagsEnabled) { - mapObj["allow_cycling"] = map->allowBiking; - mapObj["allow_escaping"] = map->allowEscaping; - mapObj["allow_running"] = map->allowRunning; + mapObj["allow_cycling"] = map->allowsBiking(); + mapObj["allow_escaping"] = map->allowsEscaping(); + mapObj["allow_running"] = map->allowsRunning(); } - mapObj["show_map_name"] = map->show_location; + mapObj["show_map_name"] = map->showsLocation(); if (projectConfig.floorNumberEnabled) { - mapObj["floor_number"] = map->floorNumber; + mapObj["floor_number"] = map->floorNumber(); } - mapObj["battle_scene"] = map->battle_scene; + mapObj["battle_scene"] = map->battleScene(); // Connections auto connections = map->getConnections(); @@ -1341,67 +1343,59 @@ void Project::saveMap(Map *map) { mapObj["connections"] = QJsonValue::Null; } - if (map->sharedEventsMap.isEmpty()) { + if (map->sharedEventsMap().isEmpty()) { // Object events OrderedJson::array objectEventsArr; - for (int i = 0; i < map->events[Event::Group::Object].length(); i++) { - Event *event = map->events[Event::Group::Object].value(i); - OrderedJson::object jsonObj = event->buildEventJson(this); - objectEventsArr.push_back(jsonObj); + for (const auto &event : map->getEvents(Event::Group::Object)){ + objectEventsArr.push_back(event->buildEventJson(this)); } mapObj["object_events"] = objectEventsArr; // Warp events OrderedJson::array warpEventsArr; - for (int i = 0; i < map->events[Event::Group::Warp].length(); i++) { - Event *event = map->events[Event::Group::Warp].value(i); - OrderedJson::object warpObj = event->buildEventJson(this); - warpEventsArr.append(warpObj); + for (const auto &event : map->getEvents(Event::Group::Warp)) { + warpEventsArr.push_back(event->buildEventJson(this)); } mapObj["warp_events"] = warpEventsArr; // Coord events OrderedJson::array coordEventsArr; - for (int i = 0; i < map->events[Event::Group::Coord].length(); i++) { - Event *event = map->events[Event::Group::Coord].value(i); - OrderedJson::object triggerObj = event->buildEventJson(this); - coordEventsArr.append(triggerObj); + for (const auto &event : map->getEvents(Event::Group::Coord)) { + coordEventsArr.push_back(event->buildEventJson(this)); } mapObj["coord_events"] = coordEventsArr; // Bg Events OrderedJson::array bgEventsArr; - for (int i = 0; i < map->events[Event::Group::Bg].length(); i++) { - Event *event = map->events[Event::Group::Bg].value(i); - OrderedJson::object bgObj = event->buildEventJson(this); - bgEventsArr.append(bgObj); + for (const auto &event : map->getEvents(Event::Group::Bg)) { + bgEventsArr.push_back(event->buildEventJson(this)); } mapObj["bg_events"] = bgEventsArr; } else { - mapObj["shared_events_map"] = map->sharedEventsMap; + mapObj["shared_events_map"] = map->sharedEventsMap(); } - if (!map->sharedScriptsMap.isEmpty()) { - mapObj["shared_scripts_map"] = map->sharedScriptsMap; + if (!map->sharedScriptsMap().isEmpty()) { + mapObj["shared_scripts_map"] = map->sharedScriptsMap(); } // Custom header fields. +/* // TODO: Re-enable for (QString key : map->customHeaders.keys()) { mapObj[key] = OrderedJson::fromQJsonValue(map->customHeaders[key]); } +*/ OrderedJson mapJson(mapObj); OrderedJsonDoc jsonDoc(&mapJson); jsonDoc.dump(&mapFile); mapFile.close(); - saveLayout(map->layout); + saveLayout(map->layout()); saveHealLocations(map); - map->isPersistedToFile = true; - map->hasUnsavedDataChanges = false; - map->editHistory.setClean(); + map->setClean(); } void Project::saveLayout(Layout *layout) { @@ -1910,25 +1904,24 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool this->mapGroups.insert(mapName, groupNum); this->groupedMapNames[groupNum].append(mapName); - newMap->isPersistedToFile = false; + newMap->setIsPersistedToFile(false); newMap->setName(mapName); - this->mapConstantsToMapNames.insert(newMap->constantName, newMap->name); - this->mapNamesToMapConstants.insert(newMap->name, newMap->constantName); + this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); + this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); if (!existingLayout) { - this->mapLayouts.insert(newMap->layoutId, newMap->layout); - this->mapLayoutsTable.append(newMap->layoutId); - this->layoutIdsToNames.insert(newMap->layout->id, newMap->layout->name); + this->mapLayouts.insert(newMap->layoutId(), newMap->layout()); + this->mapLayoutsTable.append(newMap->layoutId()); + this->layoutIdsToNames.insert(newMap->layout()->id, newMap->layout()->name); if (!importedMap) { - setNewLayoutBlockdata(newMap->layout); + setNewLayoutBlockdata(newMap->layout()); } - if (newMap->layout->border.isEmpty()) { - setNewLayoutBorder(newMap->layout); + if (newMap->layout()->border.isEmpty()) { + setNewLayoutBorder(newMap->layout()); } } - loadLayoutTilesets(newMap->layout); - setNewMapEvents(newMap); + loadLayoutTilesets(newMap->layout()); return newMap; } @@ -2854,14 +2847,6 @@ bool Project::readSpeciesIconPaths() { return true; } -void Project::setNewMapEvents(Map *map) { - map->events[Event::Group::Object].clear(); - map->events[Event::Group::Warp].clear(); - map->events[Event::Group::Heal].clear(); - map->events[Event::Group::Coord].clear(); - map->events[Event::Group::Bg].clear(); -} - int Project::getNumTilesPrimary() { return Project::num_tiles_primary; diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 98478f94..d13d660b 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -811,10 +811,12 @@ QJSValue MainWindow::getTilePixels(int tileId) { // Editing map header //===================== +// TODO: Replace UI setting here with calls to appropriate set functions. Update UI with signals from Map + QString MainWindow::getSong() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->song; + return this->editor->map->song(); } void MainWindow::setSong(QString song) { @@ -830,7 +832,7 @@ void MainWindow::setSong(QString song) { QString MainWindow::getLocation() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->location; + return this->editor->map->location(); } void MainWindow::setLocation(QString location) { @@ -846,7 +848,7 @@ void MainWindow::setLocation(QString location) { bool MainWindow::getRequiresFlash() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->requiresFlash; + return this->editor->map->requiresFlash(); } void MainWindow::setRequiresFlash(bool require) { @@ -858,7 +860,7 @@ void MainWindow::setRequiresFlash(bool require) { QString MainWindow::getWeather() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->weather; + return this->editor->map->weather(); } void MainWindow::setWeather(QString weather) { @@ -874,7 +876,7 @@ void MainWindow::setWeather(QString weather) { QString MainWindow::getType() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->type; + return this->editor->map->type(); } void MainWindow::setType(QString type) { @@ -890,7 +892,7 @@ void MainWindow::setType(QString type) { QString MainWindow::getBattleScene() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->battle_scene; + return this->editor->map->battleScene(); } void MainWindow::setBattleScene(QString battleScene) { @@ -906,7 +908,7 @@ void MainWindow::setBattleScene(QString battleScene) { bool MainWindow::getShowLocationName() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->show_location; + return this->editor->map->showsLocation(); } void MainWindow::setShowLocationName(bool show) { @@ -918,7 +920,7 @@ void MainWindow::setShowLocationName(bool show) { bool MainWindow::getAllowRunning() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowRunning; + return this->editor->map->allowsRunning(); } void MainWindow::setAllowRunning(bool allow) { @@ -930,7 +932,7 @@ void MainWindow::setAllowRunning(bool allow) { bool MainWindow::getAllowBiking() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowBiking; + return this->editor->map->allowsBiking(); } void MainWindow::setAllowBiking(bool allow) { @@ -942,7 +944,7 @@ void MainWindow::setAllowBiking(bool allow) { bool MainWindow::getAllowEscaping() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowEscaping; + return this->editor->map->allowsEscaping(); } void MainWindow::setAllowEscaping(bool allow) { @@ -954,7 +956,7 @@ void MainWindow::setAllowEscaping(bool allow) { int MainWindow::getFloorNumber() { if (!this->editor || !this->editor->map) return 0; - return this->editor->map->floorNumber; + return this->editor->map->floorNumber(); } void MainWindow::setFloorNumber(int floorNumber) { diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index 35e07a15..d3ff13ae 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -66,7 +66,7 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari // This is convoluted because of how our edit history works; this would otherwise just be 'this->connection->setOffset(newOffset);' if (this->connection->parentMap() && newOffset != this->connection->offset()) - this->connection->parentMap()->editHistory.push(new MapConnectionMove(this->connection, newOffset, this->actionId)); + this->connection->parentMap()->commit(new MapConnectionMove(this->connection, newOffset, this->actionId)); return QPointF(x, y); } diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index ccdf7e6c..dcff253c 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -79,24 +79,24 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(QString direction) { this->setSelected(true); if (this->map) - this->map->editHistory.push(new MapConnectionChangeDirection(this->connection, direction)); + this->map->commit(new MapConnectionChangeDirection(this->connection, direction)); } void ConnectionsListItem::on_comboBox_Map_currentTextChanged(QString mapName) { this->setSelected(true); if (this->map && ui->comboBox_Map->findText(mapName) >= 0) - this->map->editHistory.push(new MapConnectionChangeMap(this->connection, mapName)); + this->map->commit(new MapConnectionChangeMap(this->connection, mapName)); } void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) { this->setSelected(true); if (this->map) - this->map->editHistory.push(new MapConnectionMove(this->connection, offset, this->actionId)); + this->map->commit(new MapConnectionMove(this->connection, offset, this->actionId)); } void ConnectionsListItem::on_button_Delete_clicked() { if (this->map) - this->map->editHistory.push(new MapConnectionRemove(this->map, this->connection)); + this->map->commit(new MapConnectionRemove(this->map, this->connection)); } void ConnectionsListItem::on_button_OpenMap_clicked() { diff --git a/src/ui/draggablepixmapitem.cpp b/src/ui/draggablepixmapitem.cpp index 59b069dd..78a00bb6 100644 --- a/src/ui/draggablepixmapitem.cpp +++ b/src/ui/draggablepixmapitem.cpp @@ -91,7 +91,7 @@ void DraggablePixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *mouse) { } else { selectedEvents.append(this->event); } - editor->map->editHistory.push(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId)); + editor->map->commit(new EventMove(selectedEvents, moveDistance.x(), moveDistance.y(), currentActionId)); this->releaseSelectionQueued = false; } diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index e90658a2..cb06fea1 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -108,7 +108,7 @@ void EventFrame::connectSignals(MainWindow *) { connect(this->spinner_x, QOverload::of(&QSpinBox::valueChanged), [this](int value) { int delta = value - event->getX(); if (delta) { - this->event->getMap()->editHistory.push(new EventMove(QList() << this->event, delta, 0, this->spinner_x->getActionId())); + this->event->getMap()->commit(new EventMove(QList() << this->event, delta, 0, this->spinner_x->getActionId())); } }); @@ -118,7 +118,7 @@ void EventFrame::connectSignals(MainWindow *) { connect(this->spinner_y, QOverload::of(&QSpinBox::valueChanged), [this](int value) { int delta = value - event->getY(); if (delta) { - this->event->getMap()->editHistory.push(new EventMove(QList() << this->event, 0, delta, this->spinner_y->getActionId())); + 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); diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index a7a26629..c92b7a4b 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -53,7 +53,7 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor if (this->map) { this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); - this->ui->comboBox_MapSelection->setCurrentText(map->name); + this->ui->comboBox_MapSelection->setCurrentText(map->name()); this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down } @@ -85,18 +85,19 @@ void MapImageExporter::saveImage() { if (this->preview.isNull()) return; - QString title = getTitle(this->mode); + const QString title = getTitle(this->mode); + const QString itemName = this->map ? this->map->name() : this->layout->name; QString defaultFilename; switch (this->mode) { case ImageExporterMode::Normal: - defaultFilename = this->map? this->map->name : this->layout->name; + defaultFilename = itemName; break; case ImageExporterMode::Stitch: - defaultFilename = QString("Stitch_From_%1").arg(this->map? this->map->name : this->layout->name); + defaultFilename = QString("Stitch_From_%1").arg(itemName); break; case ImageExporterMode::Timelapse: - defaultFilename = QString("Timelapse_%1").arg(this->map? this->map->name : this->layout->name); + defaultFilename = QString("Timelapse_%1").arg(itemName); break; } @@ -121,7 +122,7 @@ void MapImageExporter::saveImage() { timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); // lambda to avoid redundancy - auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack &historyStack){ + auto generateTimelapseFromHistory = [this, &timelapseImg](QString progressText, QUndoStack *historyStack){ QProgressDialog progress(progressText, "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); @@ -137,9 +138,9 @@ void MapImageExporter::saveImage() { } // Rewind to the specified start of the map edit history. int i = 0; - while (historyStack.canUndo()) { + while (historyStack->canUndo()) { progress.setValue(i); - historyStack.undo(); + historyStack->undo(); int width = this->layout->getWidth() * 16; int height = this->layout->getHeight() * 16; if (this->settings.showBorder) { @@ -161,16 +162,16 @@ void MapImageExporter::saveImage() { while (i > 0) { if (progress.wasCanceled()) { progress.close(); - while (i > 0 && historyStack.canRedo()) { + while (i > 0 && historyStack->canRedo()) { i--; - historyStack.redo(); + historyStack->redo(); } return; } - while (historyStack.canRedo() && - !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + while (historyStack->canRedo() && + !historyItemAppliesToFrame(historyStack->command(historyStack->index()))) { i--; - historyStack.redo(); + historyStack->redo(); } progress.setValue(progress.maximum() - i); QPixmap pixmap = this->getFormattedMapPixmap(this->map); @@ -186,11 +187,11 @@ void MapImageExporter::saveImage() { for (int j = 0; j < this->settings.timelapseSkipAmount; j++) { if (i > 0) { i--; - historyStack.redo(); - while (historyStack.canRedo() && - !historyItemAppliesToFrame(historyStack.command(historyStack.index()))) { + historyStack->redo(); + while (historyStack->canRedo() && + !historyItemAppliesToFrame(historyStack->command(historyStack->index()))) { i--; - historyStack.redo(); + historyStack->redo(); } } } @@ -202,10 +203,10 @@ void MapImageExporter::saveImage() { }; if (this->layout) - generateTimelapseFromHistory("Building layout timelapse...", this->layout->editHistory); + generateTimelapseFromHistory("Building layout timelapse...", &this->layout->editHistory); if (this->map) - generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory); + generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory()); timelapseImg.save(filepath); break; @@ -279,9 +280,9 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu progress->setValue(visited.size()); StitchedMap cur = unvisited.takeFirst(); - if (visited.contains(cur.map->name)) + if (visited.contains(cur.map->name())) continue; - visited.insert(cur.map->name); + visited.insert(cur.map->name()); stitchedMaps.append(cur); for (MapConnection *connection : cur.map->getConnections()) { @@ -420,22 +421,11 @@ void MapImageExporter::scalePreview() { } } -// THIS QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { - QPixmap pixmap; + Layout *layout = this->map ? this->map->layout() : this->layout; + layout->render(true); - Layout *layout; - - // draw background layer / base image - if (!this->map) { - layout = this->layout; - layout->render(true); - pixmap = layout->pixmap; - } else { - layout = map->layout; - map->layout->render(true); - pixmap = map->layout->pixmap; - } + QPixmap pixmap = layout->pixmap; if (this->settings.showCollision) { QPainter collisionPainter(&pixmap); @@ -494,7 +484,7 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { if (!ignoreBorder && this->settings.showBorder) { pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; } - const QList events = map->getAllEvents(); + const QList events = map->getEvents(); for (const auto &event : events) { Event::Group group = event->getEventGroup(); if ((this->settings.showObjects && group == Event::Group::Object) diff --git a/src/ui/newmapconnectiondialog.cpp b/src/ui/newmapconnectiondialog.cpp index af06789f..def341fd 100644 --- a/src/ui/newmapconnectiondialog.cpp +++ b/src/ui/newmapconnectiondialog.cpp @@ -33,7 +33,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() == map->name() && mapNames.length() > 1) { // Prefer not to connect the map to itself defaultMapName = mapNames.at(1); } else { diff --git a/src/ui/newmappopup.cpp b/src/ui/newmappopup.cpp index def485c2..b5ebf556 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmappopup.cpp @@ -129,11 +129,11 @@ void NewMapPopup::init(Layout *mapLayout) { useLayoutSettings(mapLayout); this->map = new Map(); - this->map->layout = new Layout(); - this->map->layout->blockdata = mapLayout->blockdata; + this->map->setLayout(new Layout()); + this->map->layout()->blockdata = mapLayout->blockdata; if (!mapLayout->border.isEmpty()) { - this->map->layout->border = mapLayout->border; + this->map->layout()->border = mapLayout->border; } init(); } @@ -299,22 +299,22 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { newMapName = project->getNewMapName(); } - newMap->name = newMapName; - newMap->type = this->ui->comboBox_NewMap_Type->currentText(); - newMap->location = this->ui->comboBox_NewMap_Location->currentText(); - newMap->song = this->ui->comboBox_NewMap_Song->currentText(); - newMap->requiresFlash = false; - newMap->weather = this->project->weatherNames.value(0, "0"); - newMap->show_location = this->ui->checkBox_NewMap_Show_Location->isChecked(); - newMap->battle_scene = this->project->mapBattleScenes.value(0, "0"); + newMap->setName(newMapName); + newMap->setType(this->ui->comboBox_NewMap_Type->currentText()); + newMap->setLocation(this->ui->comboBox_NewMap_Location->currentText()); + newMap->setSong(this->ui->comboBox_NewMap_Song->currentText()); + newMap->setRequiresFlash(false); + newMap->setWeather(this->project->weatherNames.value(0, "0")); + newMap->setShowsLocation(this->ui->checkBox_NewMap_Show_Location->isChecked()); + newMap->setBattleScene(this->project->mapBattleScenes.value(0, "0")); if (this->existingLayout) { layout = this->project->mapLayouts.value(this->layoutId); - newMap->needsLayoutDir = false; + newMap->setNeedsLayoutDir(false); } else { layout = new Layout; layout->id = Layout::layoutConstantFromName(newMapName); - layout->name = QString("%1_Layout").arg(newMap->name); + layout->name = QString("%1_Layout").arg(newMap->name()); layout->width = this->ui->spinBox_NewMap_Width->value(); layout->height = this->ui->spinBox_NewMap_Height->value(); if (projectConfig.useCustomBorderSize) { @@ -332,26 +332,25 @@ void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { } if (this->importedMap) { - layout->blockdata = map->layout->blockdata; - if (!map->layout->border.isEmpty()) - layout->border = map->layout->border; + layout->blockdata = map->layout()->blockdata; + if (!map->layout()->border.isEmpty()) + layout->border = map->layout()->border; } if (this->ui->checkBox_NewMap_Flyable->isChecked()) { - newMap->needsHealLocation = true; + newMap->setNeedsHealLocation(true); } if (projectConfig.mapAllowFlagsEnabled) { - newMap->allowRunning = this->ui->checkBox_NewMap_Allow_Running->isChecked(); - newMap->allowBiking = this->ui->checkBox_NewMap_Allow_Biking->isChecked(); - newMap->allowEscaping = this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked(); + newMap->setAllowsRunning(this->ui->checkBox_NewMap_Allow_Running->isChecked()); + newMap->setAllowsBiking(this->ui->checkBox_NewMap_Allow_Biking->isChecked()); + newMap->setAllowsEscaping(this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked()); } if (projectConfig.floorNumberEnabled) { - newMap->floorNumber = this->ui->spinBox_NewMap_Floor_Number->value(); + newMap->setFloorNumber(this->ui->spinBox_NewMap_Floor_Number->value()); } - newMap->layout = layout; - newMap->layoutId = layout->id; + newMap->setLayout(layout); if (this->existingLayout) { project->loadMapLayout(newMap); } From acaed90d65be16dbd92b28efa3722b16dd07d4f5 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:13:09 -0500 Subject: [PATCH 03/42] Read map.json constants on project open --- include/core/map.h | 4 +- include/project.h | 7 ++- src/core/events.cpp | 10 ++-- src/core/heallocation.cpp | 2 + src/core/map.cpp | 7 +-- src/project.cpp | 113 +++++++++++++++++++------------------- src/ui/maplistmodels.cpp | 4 +- 7 files changed, 75 insertions(+), 72 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 22a2ef29..eab3110e 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -37,8 +37,10 @@ class Map : public QObject ~Map(); public: - void setName(QString mapName); + void setName(const QString &mapName) { m_name = mapName; } QString name() const { return m_name; } + + void setConstantName(const QString &constantName) { m_constantName = constantName; } QString constantName() const { return m_constantName; } static QString mapConstantFromName(QString mapName, bool includePrefix = true); diff --git a/include/project.h b/include/project.h index 0b8b9978..a0105bda 100644 --- a/include/project.h +++ b/include/project.h @@ -18,6 +18,7 @@ #include #include +// TODO: Expose to config // The displayed name of the special map value used by warps with multiple potential destinations static QString DYNAMIC_MAP_NAME = "Dynamic"; @@ -43,6 +44,8 @@ class Project : public QObject QMap healLocationNameToValue; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; + QMap mapNameToLayoutId; + QMap mapNameToMapSectionName; QStringList mapLayoutsTable; QStringList mapLayoutsTableMaster; QString layoutsLabel; @@ -126,9 +129,6 @@ class Project : public QObject QString getNewMapName(); QString getProjectTitle(); - QString readMapLayoutId(QString map_name); - QString readMapLocation(QString map_name); - bool readWildMonData(); tsl::ordered_map> wildMonData; @@ -146,6 +146,7 @@ class Project : public QObject bool hasUnsavedDataChanges = false; QSet getTopLevelMapFields(); + bool readMapJson(const QString &mapName, QJsonDocument * out); bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); diff --git a/src/core/events.cpp b/src/core/events.cpp index 6cd92589..94030d03 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -949,16 +949,16 @@ OrderedJson::object HealLocationEvent::buildEventJson(Project *) { void HealLocationEvent::setDefaultValues(Project *) { this->setElevation(projectConfig.defaultElevation); - if (!this->getMap()) + if (!this->map) return; + bool respawnEnabled = projectConfig.healLocationRespawnDataEnabled; - const QString mapConstant = Map::mapConstantFromName(this->getMap()->name(), false); const QString prefix = projectConfig.getIdentifier(respawnEnabled ? ProjectIdentifier::define_spawn_prefix : ProjectIdentifier::define_heal_locations_prefix); - this->setLocationName(mapConstant); - this->setIdName(prefix + mapConstant); + this->setLocationName(this->map->constantName()); + this->setIdName(prefix + this->map->constantName()); if (respawnEnabled) { - this->setRespawnMap(this->getMap()->name()); + this->setRespawnMap(this->map->name()); this->setRespawnNPC(1); } } diff --git a/src/core/heallocation.cpp b/src/core/heallocation.cpp index 7fb424da..d4f9a2c8 100644 --- a/src/core/heallocation.cpp +++ b/src/core/heallocation.cpp @@ -3,6 +3,8 @@ #include "events.h" #include "map.h" +// TODO: Remove + HealLocation::HealLocation(QString id, QString map, int i, int16_t x, int16_t y, QString respawnMap, uint8_t respawnNPC) { diff --git a/src/core/map.cpp b/src/core/map.cpp index 496e645f..33a4a13d 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -13,6 +13,7 @@ Map::Map(QObject *parent) : QObject(parent) { + m_scriptsLoaded = false; m_editHistory = new QUndoStack(this); resetEvents(); } @@ -23,12 +24,6 @@ Map::~Map() { deleteConnections(); } -void Map::setName(QString mapName) { - m_name = mapName; - m_constantName = mapConstantFromName(mapName); - m_scriptsLoaded = false; -} - // Note: Map does not take ownership of layout void Map::setLayout(Layout *layout) { m_layout = layout; diff --git a/src/project.cpp b/src/project.cpp index 1a9abffd..4d4c37a8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -134,20 +134,21 @@ void Project::clearTilesetCache() { tilesetCache.clear(); } -Map* Project::loadMap(QString map_name) { - if (map_name == DYNAMIC_MAP_NAME) +Map* Project::loadMap(QString mapName) { + if (mapName == DYNAMIC_MAP_NAME) return nullptr; Map *map; - if (mapCache.contains(map_name)) { - map = mapCache.value(map_name); + if (mapCache.contains(mapName)) { + map = mapCache.value(mapName); // TODO: uncomment when undo/redo history is fully implemented for all actions. if (true/*map->hasUnsavedChanges()*/) { return map; } } else { map = new Map; - map->setName(map_name); + map->setName(mapName); + map->setConstantName(this->mapNamesToMapConstants.value(mapName)); // TODO: How should we handle if !mapNamesToMapConstants.contains(mapName) here } if (!(loadMapData(map) && loadMapLayout(map))){ @@ -155,7 +156,7 @@ Map* Project::loadMap(QString map_name) { return nullptr; } - mapCache.insert(map_name, map); + mapCache.insert(mapName, map); emit mapLoaded(map); return map; } @@ -194,17 +195,23 @@ QSet Project::getTopLevelMapFields() { return topLevelMapFields; } +bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { + const QString mapFilepath = QString("%1%2/map.json").arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)).arg(mapName); + if (!parser.tryParseJsonFile(out, QString("%1/%2").arg(this->root).arg(mapFilepath))) { + logError(QString("Failed to read map data from %1").arg(mapFilepath)); + return false; + } + return true; +} + bool Project::loadMapData(Map* map) { if (!map->isPersistedToFile()) { return true; } - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map->name()).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); QJsonDocument mapDoc; - if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { - logError(QString("Failed to read map data from %1").arg(mapFilepath)); + if (!readMapJson(map->name(), &mapDoc)) return false; - } QJsonObject mapObj = mapDoc.object(); @@ -333,11 +340,11 @@ bool Project::loadMapData(Map* map) { for (int i = 0; i < connectionsArr.size(); i++) { QJsonObject connectionObj = connectionsArr[i].toObject(); const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); - int offset = ParseUtil::jsonToInt(connectionObj["offset"]); + const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - if (mapConstantsToMapNames.contains(mapConstant)) { + if (this->mapConstantsToMapNames.contains(mapConstant)) { // Successully read map connection - map->loadConnection(new MapConnection(mapConstantsToMapNames.value(mapConstant), direction, offset)); + map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant), direction, offset)); } else { logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant)); } @@ -357,38 +364,6 @@ bool Project::loadMapData(Map* map) { return true; } -QString Project::readMapLayoutId(QString map_name) { - if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->layoutId(); - } - - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); - QJsonDocument mapDoc; - if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { - logError(QString("Failed to read map layout id from %1").arg(mapFilepath)); - return QString(); - } - - QJsonObject mapObj = mapDoc.object(); - return ParseUtil::jsonToQString(mapObj["layout"]); -} - -QString Project::readMapLocation(QString map_name) { - if (mapCache.contains(map_name)) { - return mapCache.value(map_name)->location(); - } - - QString mapFilepath = QString("%1/%3%2/map.json").arg(root).arg(map_name).arg(projectConfig.getFilePath(ProjectFilePath::data_map_folders)); - QJsonDocument mapDoc; - if (!parser.tryParseJsonFile(&mapDoc, mapFilepath)) { - logError(QString("Failed to read map's region map section from %1").arg(mapFilepath)); - return QString(); - } - - QJsonObject mapObj = mapDoc.object(); - return ParseUtil::jsonToQString(mapObj["region_map_section"]); -} - Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); Layout *layout; @@ -1328,7 +1303,7 @@ void Project::saveMap(Map *map) { if (connections.length() > 0) { OrderedJson::array connectionsArr; for (auto connection : connections) { - if (mapNamesToMapConstants.contains(connection->targetMapName())) { + if (this->mapNamesToMapConstants.contains(connection->targetMapName())) { OrderedJson::object connectionObj; connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName()); connectionObj["offset"] = connection->offset(); @@ -1857,24 +1832,52 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { - QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); - QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); + const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); + const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); this->groupedMapNames.append(QStringList()); this->groupNames.append(groupName); for (int j = 0; j < mapNamesJson.size(); j++) { - QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); + const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); if (mapName == DYNAMIC_MAP_NAME) { logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); continue; } - this->mapGroups.insert(mapName, groupIndex); - this->groupedMapNames[groupIndex].append(mapName); - this->mapNames.append(mapName); + if (this->mapNames.contains(mapName)) { + logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); + continue; + } - // Build the mapping and reverse mapping between map constants and map names. - QString mapConstant = Map::mapConstantFromName(mapName); + // Load the map's json file so we can get its ID constant (and two other constants we use for the map list). + QJsonDocument mapDoc; + if (!readMapJson(mapName, &mapDoc)) + continue; // Error message has already been logged + + // Read and validate the map's ID from its JSON data. + const QJsonObject mapObj = mapDoc.object(); + const QString mapConstant = ParseUtil::jsonToQString(mapObj["id"]); + if (mapConstant.isEmpty()) { + logWarn(QString("Map '%1' is missing an \"id\" value and will be ignored.").arg(mapName)); + continue; + } + const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + if (!mapConstant.startsWith(expectedPrefix)) { + logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); + continue; + } + auto it = this->mapConstantsToMapNames.constFind(mapConstant); + if (it != this->mapConstantsToMapNames.constEnd()) { + logWarn(QString("Map '%1' has the same \"id\" value '%2' as map '%3' and will be ignored.").arg(mapName).arg(it.key()).arg(it.value())); + continue; + } + + // Success, save the constants to the project + this->mapNames.append(mapName); + this->groupedMapNames[groupIndex].append(mapName); + this->mapGroups.insert(mapName, groupIndex); this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); + this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); + this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); } } @@ -1905,7 +1908,7 @@ Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool this->groupedMapNames[groupNum].append(mapName); newMap->setIsPersistedToFile(false); - newMap->setName(mapName); + newMap->setName(mapName); // TODO: Set map name and map constant before calling this function this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a270736a..a2bcd8a6 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -472,7 +472,7 @@ void MapAreaModel::initialize() { for (int j = 0; j < names.length(); j++) { QString mapName = names.value(j); QStandardItem *map = createMapItem(mapName, i, j); - QString mapsecName = this->project->readMapLocation(mapName); + QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); if (this->areaItems.contains(mapsecName)) { this->areaItems[mapsecName]->appendRow(map); } @@ -627,7 +627,7 @@ void LayoutTreeModel::initialize() { for (auto mapList : this->project->groupedMapNames) { for (auto mapName : mapList) { - QString layoutId = project->readMapLayoutId(mapName); + QString layoutId = project->mapNameToLayoutId.value(mapName); QStandardItem *map = createMapItem(mapName); this->layoutItems[layoutId]->appendRow(map); } From 9d87ece663a4df4b794bfceafebd200809de3e46 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:23:15 -0500 Subject: [PATCH 04/42] New map popup/prompt to dialog --- forms/{newmappopup.ui => newmapdialog.ui} | 4 +- include/mainwindow.h | 6 +-- include/ui/{newmappopup.h => newmapdialog.h} | 18 ++++----- porymap.pro | 6 +-- src/mainwindow.cpp | 42 ++++++++++---------- src/ui/{newmappopup.cpp => newmapdialog.cpp} | 42 ++++++++++---------- 6 files changed, 59 insertions(+), 59 deletions(-) rename forms/{newmappopup.ui => newmapdialog.ui} (99%) rename include/ui/{newmappopup.h => newmapdialog.h} (83%) rename src/ui/{newmappopup.cpp => newmapdialog.cpp} (93%) diff --git a/forms/newmappopup.ui b/forms/newmapdialog.ui similarity index 99% rename from forms/newmappopup.ui rename to forms/newmapdialog.ui index 3a83073f..2414918f 100644 --- a/forms/newmappopup.ui +++ b/forms/newmapdialog.ui @@ -1,7 +1,7 @@ - NewMapPopup - + NewMapDialog + 0 diff --git a/include/mainwindow.h b/include/mainwindow.h index 397a1d72..2efe964d 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,7 +22,7 @@ #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" #include "maplistmodels.h" -#include "newmappopup.h" +#include "newmapdialog.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" @@ -186,7 +186,7 @@ private slots: void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); - void openNewMapPopupWindow(); + void openNewMapDialog(); void onNewMapCreated(); void onMapLoaded(Map *map); void importMapFromAdvanceMap1_92(); @@ -313,7 +313,7 @@ private slots: QPointer regionMapEditor = nullptr; QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; - QPointer newMapPrompt = nullptr; + QPointer newMapDialog = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; diff --git a/include/ui/newmappopup.h b/include/ui/newmapdialog.h similarity index 83% rename from include/ui/newmappopup.h rename to include/ui/newmapdialog.h index f160876a..b87f785e 100644 --- a/include/ui/newmappopup.h +++ b/include/ui/newmapdialog.h @@ -1,22 +1,22 @@ -#ifndef NEWMAPPOPUP_H -#define NEWMAPPOPUP_H +#ifndef NEWMAPDIALOG_H +#define NEWMAPDIALOG_H -#include +#include #include #include "editor.h" #include "project.h" #include "map.h" namespace Ui { -class NewMapPopup; +class NewMapDialog; } -class NewMapPopup : public QMainWindow +class NewMapDialog : public QDialog { Q_OBJECT public: - explicit NewMapPopup(QWidget *parent = nullptr, Project *project = nullptr); - ~NewMapPopup(); + explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr); + ~NewMapDialog(); Map *map; int group; bool existingLayout; @@ -32,7 +32,7 @@ class NewMapPopup : public QMainWindow void applied(); private: - Ui::NewMapPopup *ui; + Ui::NewMapDialog *ui; Project *project; bool checkNewMapDimensions(); bool checkNewMapGroup(); @@ -67,4 +67,4 @@ private slots: void on_lineEdit_NewMap_Name_textChanged(const QString &); }; -#endif // NEWMAPPOPUP_H +#endif // NEWMAPDIALOG_H diff --git a/porymap.pro b/porymap.pro index 1b1c693e..9a2b2c75 100644 --- a/porymap.pro +++ b/porymap.pro @@ -99,7 +99,7 @@ SOURCES += src/core/block.cpp \ src/ui/tileseteditortileselector.cpp \ src/ui/tilemaptileselector.cpp \ src/ui/regionmapeditor.cpp \ - src/ui/newmappopup.cpp \ + src/ui/newmapdialog.cpp \ src/ui/mapimageexporter.cpp \ src/ui/newtilesetdialog.cpp \ src/ui/flowlayout.cpp \ @@ -202,7 +202,7 @@ HEADERS += include/core/block.h \ include/ui/tileseteditortileselector.h \ include/ui/tilemaptileselector.h \ include/ui/regionmapeditor.h \ - include/ui/newmappopup.h \ + include/ui/newmapdialog.h \ include/ui/mapimageexporter.h \ include/ui/newtilesetdialog.h \ include/ui/overlay.h \ @@ -238,7 +238,7 @@ FORMS += forms/mainwindow.ui \ forms/tileseteditor.ui \ forms/paletteeditor.ui \ forms/regionmapeditor.ui \ - forms/newmappopup.ui \ + forms/newmapdialog.ui \ forms/aboutporymap.ui \ forms/newtilesetdialog.ui \ forms/mapimageexporter.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c8885f62..5f42e9c7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1363,8 +1363,8 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (addToFolderAction) { connect(addToFolderAction, &QAction::triggered, [this, itemName] { - openNewMapPopupWindow(); - this->newMapPrompt->init(ui->mapListContainer->currentIndex(), itemName); + openNewMapDialog(); + this->newMapDialog->init(ui->mapListContainer->currentIndex(), itemName); }); } if (deleteFolderAction) { @@ -1592,11 +1592,11 @@ void MainWindow::mapListAddArea() { } void MainWindow::onNewMapCreated() { - QString newMapName = this->newMapPrompt->map->name(); - int newMapGroup = this->newMapPrompt->group; - Map *newMap = this->newMapPrompt->map; - bool existingLayout = this->newMapPrompt->existingLayout; - bool importedMap = this->newMapPrompt->importedMap; + QString newMapName = this->newMapDialog->map->name(); + int newMapGroup = this->newMapDialog->group; + Map *newMap = this->newMapDialog->map; + bool existingLayout = this->newMapDialog->existingLayout; + bool importedMap = this->newMapDialog->importedMap; newMap = editor->project->addNewMapToGroup(newMapName, newMapGroup, newMap, existingLayout, importedMap); @@ -1638,27 +1638,27 @@ void MainWindow::onNewMapCreated() { editor->save(); } - disconnect(this->newMapPrompt, &NewMapPopup::applied, this, &MainWindow::onNewMapCreated); + disconnect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); delete newMap; } -void MainWindow::openNewMapPopupWindow() { +void MainWindow::openNewMapDialog() { if (!this->newMapDefaultsSet) { - NewMapPopup::setDefaultSettings(this->editor->project); + NewMapDialog::setDefaultSettings(this->editor->project); this->newMapDefaultsSet = true; } - if (!this->newMapPrompt) { - this->newMapPrompt = new NewMapPopup(this, this->editor->project); - connect(this->newMapPrompt, &NewMapPopup::applied, this, &MainWindow::onNewMapCreated); + if (!this->newMapDialog) { + this->newMapDialog = new NewMapDialog(this, this->editor->project); + connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); } - openSubWindow(this->newMapPrompt); + openSubWindow(this->newMapDialog); } void MainWindow::on_action_NewMap_triggered() { - openNewMapPopupWindow(); - this->newMapPrompt->initUi(); - this->newMapPrompt->init(); + openNewMapDialog(); + this->newMapDialog->initUi(); + this->newMapDialog->init(); } // Insert label for newly-created tileset into sorted list of existing labels @@ -2864,8 +2864,8 @@ void MainWindow::importMapFromAdvanceMap1_92() return; } - openNewMapPopupWindow(); - this->newMapPrompt->init(mapLayout); + openNewMapDialog(); + this->newMapDialog->init(mapLayout); } void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { @@ -3379,9 +3379,9 @@ bool MainWindow::closeSupplementaryWindows() { return false; this->mapImageExporter = nullptr; - if (this->newMapPrompt && !this->newMapPrompt->close()) + if (this->newMapDialog && !this->newMapDialog->close()) return false; - this->newMapPrompt = nullptr; + this->newMapDialog = nullptr; if (this->shortcutsEditor && !this->shortcutsEditor->close()) return false; diff --git a/src/ui/newmappopup.cpp b/src/ui/newmapdialog.cpp similarity index 93% rename from src/ui/newmappopup.cpp rename to src/ui/newmapdialog.cpp index b5ebf556..ce6737a8 100644 --- a/src/ui/newmappopup.cpp +++ b/src/ui/newmapdialog.cpp @@ -1,7 +1,7 @@ -#include "newmappopup.h" +#include "newmapdialog.h" #include "maplayout.h" #include "mainwindow.h" -#include "ui_newmappopup.h" +#include "ui_newmapdialog.h" #include "config.h" #include @@ -10,11 +10,11 @@ // TODO: Convert to modal dialog (among other things, this means we wouldn't need to worry about changes to the map list while this is open) -struct NewMapPopup::Settings NewMapPopup::settings = {}; +struct NewMapDialog::Settings NewMapDialog::settings = {}; -NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : - QMainWindow(parent), - ui(new Ui::NewMapPopup) +NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : + QDialog(parent), + ui(new Ui::NewMapDialog) { this->setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); @@ -23,13 +23,13 @@ NewMapPopup::NewMapPopup(QWidget *parent, Project *project) : this->importedMap = false; } -NewMapPopup::~NewMapPopup() +NewMapDialog::~NewMapDialog() { saveSettings(); delete ui; } -void NewMapPopup::initUi() { +void NewMapDialog::initUi() { // Populate combo boxes ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels); ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels); @@ -76,7 +76,7 @@ void NewMapPopup::initUi() { this->updateGeometry(); } -void NewMapPopup::init() { +void NewMapDialog::init() { // Restore previous settings ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); ui->comboBox_NewMap_Group->setTextItem(settings.group); @@ -104,7 +104,7 @@ void NewMapPopup::init() { } // Creating new map by right-clicking in the map list -void NewMapPopup::init(int tabIndex, QString fieldName) { +void NewMapDialog::init(int tabIndex, QString fieldName) { initUi(); switch (tabIndex) { @@ -123,7 +123,7 @@ void NewMapPopup::init(int tabIndex, QString fieldName) { } // Creating new map from AdvanceMap import -void NewMapPopup::init(Layout *mapLayout) { +void NewMapDialog::init(Layout *mapLayout) { initUi(); this->importedMap = true; useLayoutSettings(mapLayout); @@ -138,7 +138,7 @@ void NewMapPopup::init(Layout *mapLayout) { init(); } -bool NewMapPopup::checkNewMapDimensions() { +bool NewMapDialog::checkNewMapDimensions() { int numMetatiles = project->getMapDataSize(ui->spinBox_NewMap_Width->value(), ui->spinBox_NewMap_Height->value()); int maxMetatiles = project->getMaxMapDataSize(); @@ -162,7 +162,7 @@ bool NewMapPopup::checkNewMapDimensions() { } } -bool NewMapPopup::checkNewMapGroup() { +bool NewMapDialog::checkNewMapGroup() { group = project->groupNames.indexOf(this->ui->comboBox_NewMap_Group->currentText()); if (group < 0) { @@ -179,7 +179,7 @@ bool NewMapPopup::checkNewMapGroup() { } } -void NewMapPopup::setDefaultSettings(Project *project) { +void NewMapDialog::setDefaultSettings(Project *project) { settings.group = project->groupNames.at(0); settings.width = project->getDefaultMapSize(); settings.height = project->getDefaultMapSize(); @@ -198,7 +198,7 @@ void NewMapPopup::setDefaultSettings(Project *project) { settings.floorNumber = 0; } -void NewMapPopup::saveSettings() { +void NewMapDialog::saveSettings() { settings.group = ui->comboBox_NewMap_Group->currentText(); settings.width = ui->spinBox_NewMap_Width->value(); settings.height = ui->spinBox_NewMap_Height->value(); @@ -217,7 +217,7 @@ void NewMapPopup::saveSettings() { settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value(); } -void NewMapPopup::useLayoutSettings(Layout *layout) { +void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; settings.width = layout->width; @@ -239,7 +239,7 @@ void NewMapPopup::useLayoutSettings(Layout *layout) { ui->comboBox_NewMap_Secondary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Secondary_Tileset->findText(layout->tileset_secondary_label)); } -void NewMapPopup::useLayout(QString layoutId) { +void NewMapDialog::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; @@ -248,7 +248,7 @@ void NewMapPopup::useLayout(QString layoutId) { useLayoutSettings(project->mapLayouts.value(this->layoutId)); } -void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) { +void NewMapDialog::on_checkBox_UseExistingLayout_stateChanged(int state) { bool layoutEditsEnabled = (state == Qt::Unchecked); this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled); @@ -267,13 +267,13 @@ void NewMapPopup::on_checkBox_UseExistingLayout_stateChanged(int state) { } } -void NewMapPopup::on_comboBox_Layout_currentTextChanged(const QString &text) { +void NewMapDialog::on_comboBox_Layout_currentTextChanged(const QString &text) { if (this->project->mapLayoutsTable.contains(text)) { useLayout(text); } } -void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { +void NewMapDialog::on_lineEdit_NewMap_Name_textChanged(const QString &text) { if (project->mapNames.contains(text)) { this->ui->lineEdit_NewMap_Name->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); } else { @@ -281,7 +281,7 @@ void NewMapPopup::on_lineEdit_NewMap_Name_textChanged(const QString &text) { } } -void NewMapPopup::on_pushButton_NewMap_Accept_clicked() { +void NewMapDialog::on_pushButton_NewMap_Accept_clicked() { if (!checkNewMapDimensions() || !checkNewMapGroup()) { // ignore when map dimensions or map group are invalid return; From d6a796e3b4c1187a317e5b7ce33f7473ca8b6ca2 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:44:04 -0500 Subject: [PATCH 05/42] Start new map dialog redesign --- forms/newmapdialog.ui | 900 +++++++++++++++++++++----------------- include/project.h | 6 +- include/ui/newmapdialog.h | 29 +- src/core/map.cpp | 8 +- src/mainwindow.cpp | 4 +- src/project.cpp | 31 +- src/ui/newmapdialog.cpp | 460 ++++++++++--------- 7 files changed, 776 insertions(+), 662 deletions(-) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 2414918f..9cf4dc18 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -6,491 +6,566 @@ 0 0 - 410 - 687 + 453 + 563 New Map Options - - - - - - false + + + + + true + + + + + 0 + 0 + 427 + 841 + - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - QLayout::SetDefaultConstraint - + - 12 + 10 - + Name - + - <html><head/><body><p>The name of the new map. If the name is invalid (red), it will be replaced with the default name of a new map.</p></body></html> + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> true - - - - Group - - - - - - - <html><head/><body><p>New map group.</p></body></html> - - - true - - - - - - - Map Width - + + + + Border Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + + + + Map Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + 0 + 0 + + + + Height + + + + - - - <html><head/><body><p>Width (in blocks) of the new map.</p></body></html> - - - 200 - - - - - - - Map Height - - - - - - - <html><head/><body><p>Height (in blocks) of the new map.</p></body></html> - - - 200 - - - - - - - Border Width - - - - - - - <html><head/><body><p>Width (in blocks) of the new map's border.</p></body></html> - - - 255 - - - - - - - Border Height - - - - - - - <html><head/><body><p>Height (in blocks) of the new map's border.</p></body></html> - - - 255 - - - - - - - Primary Tileset - - - - - - - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true - - - - - - - Secondary Tileset - - - - - - - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true - - - - - - - Type - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> - - - true - - - - - - - Location - - - - - + - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> true - - - - - - Song - - - - - - - <html><head/><body><p>The default background music for this map.</p></body></html> - - - true + + QComboBox::InsertPolicy::NoInsert - - - - Can Fly To - - - - - - - <html><head/><body><p>Whether to add a heal location to the new map.</p></body></html> - - - - - - - - - - Show Location Name + + + + false - - - - - - <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> + + color: rgb(255, 0, 0) - - - - - - Allow Running + + true - - + + - <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> - - - + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - Allow Biking + + + + false - - - - - - <html><head/><body><p>Allows the player to use a Bike</p></body></html> + + color: rgb(255, 0, 0) - - - - - - Allow Dig & Escape Rope + + true - - - - <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> - - - - + + + + Header Data + + + + + + Song + + + + + + + <html><head/><body><p>The default background music for this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Location + + + + + + + Requires Flash + + + + + + + Weather + + + + + + + Type + + + + + + + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Battle Scene + + + + + + + Show Location + + + + + + + Allow Running + + + + + + + Allow Biking + + + + + + + Allow Escaping + + + + + + + Floor Number + + + + + + + Can Fly To + + + + + + + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + 127 + + + + + + + <html><head/><body><p>This field is used to help determine what graphics to use in the background of battles on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>The default weather on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> + + + + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + + + + + + + - - + + - Floor Number - - - - - - - <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> - - - 127 - - - - - - - false + Group - - + + - Layout + ID - - - - - - Use Existing Layout - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - Accept - - - - - - - true - - - - 0 - 0 - - - - false - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - + + + false - - ! - - - - - - - - 0 - 0 - - color: rgb(255, 0, 0) + + true + + + + + + + Tilesets + + + + + + Primary + + + + + + + <html><head/><body><p>The primary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Secondary + + + + + + + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + - - - - - - - 0 - 0 - 410 - 22 - - - - + + + + + + + + Accept + + + + + + @@ -498,6 +573,11 @@ QComboBox
noscrollcombobox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
diff --git a/include/project.h b/include/project.h index a0105bda..1632d38f 100644 --- a/include/project.h +++ b/include/project.h @@ -125,7 +125,7 @@ class Project : public QObject void deleteFile(QString path); bool readMapGroups(); - Map* addNewMapToGroup(QString, int, Map*, bool, bool); + Map* addNewMapToGroup(Map*, int, bool, bool); QString getNewMapName(); QString getProjectTitle(); @@ -234,7 +234,7 @@ class Project : public QObject static int getNumPalettesPrimary(); static int getNumPalettesTotal(); static int getMaxMapDataSize(); - static int getDefaultMapSize(); + static int getDefaultMapDimension(); static int getMaxMapWidth(); static int getMaxMapHeight(); static int getMapDataSize(int width, int height); @@ -260,7 +260,7 @@ class Project : public QObject static int num_pals_primary; static int num_pals_total; static int max_map_data_size; - static int default_map_size; + static int default_map_dimension; static int max_object_events; signals: diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index b87f785e..4b17f32b 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -23,7 +23,7 @@ class NewMapDialog : public QDialog bool importedMap; QString layoutId; void init(); - void initUi(); + //void initUi(); void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -34,8 +34,13 @@ class NewMapDialog : public QDialog private: Ui::NewMapDialog *ui; Project *project; - bool checkNewMapDimensions(); - bool checkNewMapGroup(); + + bool validateMapDimensions(); + bool validateMapGroup(); + bool validateTilesets(); + bool validateID(); + bool validateName(); + void saveSettings(); void useLayout(QString layoutId); void useLayoutSettings(Layout *mapLayout); @@ -48,23 +53,27 @@ class NewMapDialog : public QDialog int borderHeight; QString primaryTilesetLabel; QString secondaryTilesetLabel; - QString type; - QString location; QString song; - bool canFlyTo; + QString location; + bool requiresFlash; + QString weather; + QString type; + QString battleScene; bool showLocationName; bool allowRunning; bool allowBiking; bool allowEscaping; int floorNumber; + bool canFlyTo; }; static struct Settings settings; private slots: - void on_checkBox_UseExistingLayout_stateChanged(int state); - void on_comboBox_Layout_currentTextChanged(const QString &text); - void on_pushButton_NewMap_Accept_clicked(); - void on_lineEdit_NewMap_Name_textChanged(const QString &); + //void on_checkBox_UseExistingLayout_stateChanged(int state); + //void on_comboBox_Layout_currentTextChanged(const QString &text); + void on_pushButton_Accept_clicked(); + void on_lineEdit_Name_textChanged(const QString &); + void on_lineEdit_ID_textChanged(const QString &); }; #endif // NEWMAPDIALOG_H diff --git a/src/core/map.cpp b/src/core/map.cpp index 33a4a13d..942a4e95 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -39,13 +39,7 @@ QString Map::mapConstantFromName(QString mapName, bool includePrefix) { const QString prefix = includePrefix ? projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix) : ""; QString withMapAndUppercase = prefix + nameWithUnderscores.toUpper(); static const QRegularExpression underscores("_+"); - QString constantName = withMapAndUppercase.replace(underscores, "_"); - - // Handle special cases. - // SSTidal needs to be SS_TIDAL, rather than SSTIDAL - constantName = constantName.replace("SSTIDAL", "SS_TIDAL"); - - return constantName; + return withMapAndUppercase.replace(underscores, "_"); } int Map::getWidth() const { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 5f42e9c7..6105a3b4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1598,7 +1598,7 @@ void MainWindow::onNewMapCreated() { bool existingLayout = this->newMapDialog->existingLayout; bool importedMap = this->newMapDialog->importedMap; - newMap = editor->project->addNewMapToGroup(newMapName, newMapGroup, newMap, existingLayout, importedMap); + newMap = editor->project->addNewMapToGroup(newMap, newMapGroup, existingLayout, importedMap); logInfo(QString("Created a new map named %1.").arg(newMapName)); @@ -1657,7 +1657,7 @@ void MainWindow::openNewMapDialog() { void MainWindow::on_action_NewMap_triggered() { openNewMapDialog(); - this->newMapDialog->initUi(); + //this->newMapDialog->initUi();//TODO this->newMapDialog->init(); } diff --git a/src/project.cpp b/src/project.cpp index 4d4c37a8..f996ff1c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -32,7 +32,7 @@ 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_size = 20; +int Project::default_map_dimension = 20; int Project::max_object_events = 64; Project::Project(QObject *parent) : @@ -821,16 +821,16 @@ void Project::saveHealLocations(Map *map) { } // Saves heal location maps/coords/respawn data in root + /src/data/heal_locations.h -void Project::saveHealLocationsData(Map *map) { +void Project::saveHealLocationsData(Map *) { +/* TODO: Will be re-implemented as part of changes to reading heal locations from map.json // Update heal locations from map -/* TODO: Re-enable if (map->events[Event::Group::Heal].length() > 0) { for (Event *healEvent : map->events[Event::Group::Heal]) { HealLocation hl = HealLocation::fromEvent(healEvent); this->healLocations[hl.index - 1] = hl; } } -*/ + // Find any duplicate constant names QMap healLocationsDupes; @@ -898,6 +898,7 @@ void Project::saveHealLocationsData(Map *map) { QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::data_heal_locations); ignoreWatchedFileTemporarily(filepath); saveTextFile(filepath, text); + */ } // Saves heal location defines in root + /include/constants/heal_locations.h @@ -1876,6 +1877,7 @@ bool Project::readMapGroups() { this->mapGroups.insert(mapName, groupIndex); this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); + // TODO: Keep these updated this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); } @@ -1898,20 +1900,19 @@ bool Project::readMapGroups() { return true; } -Map* Project::addNewMapToGroup(QString mapName, int groupNum, Map *newMap, bool existingLayout, bool importedMap) { +Map* Project::addNewMapToGroup(Map *newMap, int groupNum, bool existingLayout, bool importedMap) { int mapNamePos = 0; for (int i = 0; i <= groupNum; i++) mapNamePos += this->groupedMapNames.value(i).length(); - this->mapNames.insert(mapNamePos, mapName); - this->mapGroups.insert(mapName, groupNum); - this->groupedMapNames[groupNum].append(mapName); + this->mapNames.insert(mapNamePos, newMap->name()); + this->mapGroups.insert(newMap->name(), groupNum); + this->groupedMapNames[groupNum].append(newMap->name()); + this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); + this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); newMap->setIsPersistedToFile(false); - newMap->setName(mapName); // TODO: Set map name and map constant before calling this function - this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); - this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); if (!existingLayout) { this->mapLayouts.insert(newMap->layoutId(), newMap->layout()); this->mapLayoutsTable.append(newMap->layoutId()); @@ -2891,9 +2892,9 @@ int Project::getMapDataSize(int width, int height) return (width + 15) * (height + 14); } -int Project::getDefaultMapSize() +int Project::getDefaultMapDimension() { - return Project::default_map_size; + return Project::default_map_dimension; } int Project::getMaxMapWidth() @@ -2915,11 +2916,11 @@ bool Project::calculateDefaultMapSize(){ int max = getMaxMapDataSize(); if (max >= getMapDataSize(20, 20)) { - default_map_size = 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_size = qFloor((qSqrt(4 * getMaxMapDataSize() + 1) - 29) / 2); + 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)) diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index ce6737a8..0d6bdd77 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -8,7 +8,9 @@ #include #include -// TODO: Convert to modal dialog (among other things, this means we wouldn't need to worry about changes to the map list while this is open) +// TODO: Make ui->groupBox_HeaderData collapsible + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; struct NewMapDialog::Settings NewMapDialog::settings = {}; @@ -21,6 +23,16 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : this->project = project; this->existingLayout = false; this->importedMap = false; + + // Map names and IDs can only contain word characters, and cannot start with a digit. + // TODO: Also validate this when we read ProjectIdentifier::define_map_prefix from the config + static const QRegularExpression re("[A-Za-z_]+[\\w]*"); + auto validator = new QRegularExpressionValidator(re, this); + ui->lineEdit_Name->setValidator(validator); + ui->lineEdit_ID->setValidator(validator); + + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); + connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); } NewMapDialog::~NewMapDialog() @@ -29,93 +41,77 @@ NewMapDialog::~NewMapDialog() delete ui; } -void NewMapDialog::initUi() { +void NewMapDialog::init() { // Populate combo boxes - ui->comboBox_NewMap_Primary_Tileset->addItems(project->primaryTilesetLabels); - ui->comboBox_NewMap_Secondary_Tileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_NewMap_Group->addItems(project->groupNames); - ui->comboBox_NewMap_Song->addItems(project->songNames); - ui->comboBox_NewMap_Type->addItems(project->mapTypes); - ui->comboBox_NewMap_Location->addItems(project->mapSectionIdNames); - - const QSignalBlocker b(ui->comboBox_Layout); - ui->comboBox_Layout->addItems(project->mapLayoutsTable); - this->layoutId = project->mapLayoutsTable.first(); + ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); + ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); + ui->comboBox_Group->addItems(project->groupNames); + ui->comboBox_Song->addItems(project->songNames); + ui->comboBox_Location->addItems(project->mapSectionIdNames); + ui->comboBox_Weather->addItems(project->weatherNames); + ui->comboBox_Type->addItems(project->mapTypes); + ui->comboBox_BattleScene->addItems(project->mapBattleScenes); // Set spin box limits - ui->spinBox_NewMap_Width->setMinimum(1); - ui->spinBox_NewMap_Height->setMinimum(1); - ui->spinBox_NewMap_Width->setMaximum(project->getMaxMapWidth()); - ui->spinBox_NewMap_Height->setMaximum(project->getMaxMapHeight()); - ui->spinBox_NewMap_BorderWidth->setMinimum(1); - ui->spinBox_NewMap_BorderHeight->setMinimum(1); - ui->spinBox_NewMap_BorderWidth->setMaximum(MAX_BORDER_WIDTH); - ui->spinBox_NewMap_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); - ui->spinBox_NewMap_Floor_Number->setMinimum(-128); - ui->spinBox_NewMap_Floor_Number->setMaximum(127); + ui->spinBox_MapWidth->setMaximum(project->getMaxMapWidth()); + ui->spinBox_MapHeight->setMaximum(project->getMaxMapHeight()); + ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); + ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); // Hide config specific ui elements bool hasFlags = projectConfig.mapAllowFlagsEnabled; - ui->checkBox_NewMap_Allow_Running->setVisible(hasFlags); - ui->checkBox_NewMap_Allow_Biking->setVisible(hasFlags); - ui->checkBox_NewMap_Allow_Escape_Rope->setVisible(hasFlags); - ui->label_NewMap_Allow_Running->setVisible(hasFlags); - ui->label_NewMap_Allow_Biking->setVisible(hasFlags); - ui->label_NewMap_Allow_Escape_Rope->setVisible(hasFlags); - - bool hasCustomBorders = projectConfig.useCustomBorderSize; - ui->spinBox_NewMap_BorderWidth->setVisible(hasCustomBorders); - ui->spinBox_NewMap_BorderHeight->setVisible(hasCustomBorders); - ui->label_NewMap_BorderWidth->setVisible(hasCustomBorders); - ui->label_NewMap_BorderHeight->setVisible(hasCustomBorders); + ui->checkBox_AllowRunning->setVisible(hasFlags); + ui->checkBox_AllowBiking->setVisible(hasFlags); + ui->checkBox_AllowEscaping->setVisible(hasFlags); + ui->label_AllowRunning->setVisible(hasFlags); + ui->label_AllowBiking->setVisible(hasFlags); + ui->label_AllowEscaping->setVisible(hasFlags); - bool hasFloorNumber = projectConfig.floorNumberEnabled; - ui->spinBox_NewMap_Floor_Number->setVisible(hasFloorNumber); - ui->label_NewMap_Floor_Number->setVisible(hasFloorNumber); + ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - this->updateGeometry(); -} + bool hasFloorNumber = projectConfig.floorNumberEnabled; + ui->spinBox_FloorNumber->setVisible(hasFloorNumber); + ui->label_FloorNumber->setVisible(hasFloorNumber); -void NewMapDialog::init() { // Restore previous settings - ui->lineEdit_NewMap_Name->setText(project->getNewMapName()); - ui->comboBox_NewMap_Group->setTextItem(settings.group); - ui->spinBox_NewMap_Width->setValue(settings.width); - ui->spinBox_NewMap_Height->setValue(settings.height); - ui->spinBox_NewMap_BorderWidth->setValue(settings.borderWidth); - ui->spinBox_NewMap_BorderHeight->setValue(settings.borderHeight); - ui->comboBox_NewMap_Primary_Tileset->setTextItem(settings.primaryTilesetLabel); - ui->comboBox_NewMap_Secondary_Tileset->setTextItem(settings.secondaryTilesetLabel); - ui->comboBox_NewMap_Type->setTextItem(settings.type); - ui->comboBox_NewMap_Location->setTextItem(settings.location); - ui->comboBox_NewMap_Song->setTextItem(settings.song); - ui->checkBox_NewMap_Flyable->setChecked(settings.canFlyTo); - ui->checkBox_NewMap_Show_Location->setChecked(settings.showLocationName); - ui->checkBox_NewMap_Allow_Running->setChecked(settings.allowRunning); - ui->checkBox_NewMap_Allow_Biking->setChecked(settings.allowBiking); - ui->checkBox_NewMap_Allow_Escape_Rope->setChecked(settings.allowEscaping); - ui->spinBox_NewMap_Floor_Number->setValue(settings.floorNumber); - - // Connect signals - connect(ui->spinBox_NewMap_Width, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); - connect(ui->spinBox_NewMap_Height, QOverload::of(&QSpinBox::valueChanged), [=](int){checkNewMapDimensions();}); - - ui->frame_NewMap_Options->setEnabled(true); + ui->lineEdit_Name->setText(project->getNewMapName()); + ui->comboBox_Group->setTextItem(settings.group); + ui->spinBox_MapWidth->setValue(settings.width); + ui->spinBox_MapHeight->setValue(settings.height); + ui->spinBox_BorderWidth->setValue(settings.borderWidth); + ui->spinBox_BorderHeight->setValue(settings.borderHeight); + ui->comboBox_PrimaryTileset->setTextItem(settings.primaryTilesetLabel); + ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); + ui->comboBox_Song->setTextItem(settings.song); + ui->comboBox_Location->setTextItem(settings.location); + ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); + ui->comboBox_Weather->setTextItem(settings.weather); + ui->comboBox_Type->setTextItem(settings.type); + ui->comboBox_BattleScene->setTextItem(settings.battleScene); + ui->checkBox_ShowLocation->setChecked(settings.showLocationName); + ui->checkBox_AllowRunning->setChecked(settings.allowRunning); + ui->checkBox_AllowBiking->setChecked(settings.allowBiking); + ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); + ui->spinBox_FloorNumber->setValue(settings.floorNumber); + ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); } // Creating new map by right-clicking in the map list void NewMapDialog::init(int tabIndex, QString fieldName) { - initUi(); + //initUi(); switch (tabIndex) { case MapListTab::Groups: settings.group = fieldName; + //ui->label_Group->setDisabled(true); + //ui->comboBox_Group->setDisabled(true); break; case MapListTab::Areas: settings.location = fieldName; + //ui->label_Location->setDisabled(true); + //ui->comboBox_Location->setDisabled(true); break; case MapListTab::Layouts: - this->ui->checkBox_UseExistingLayout->setCheckState(Qt::Checked); useLayout(fieldName); break; } @@ -123,234 +119,268 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { } // Creating new map from AdvanceMap import -void NewMapDialog::init(Layout *mapLayout) { - initUi(); +void NewMapDialog::init(Layout *layout) { this->importedMap = true; - useLayoutSettings(mapLayout); + useLayoutSettings(layout); + // TODO: These are probably leaking this->map = new Map(); this->map->setLayout(new Layout()); - this->map->layout()->blockdata = mapLayout->blockdata; + this->map->layout()->blockdata = layout->blockdata; - if (!mapLayout->border.isEmpty()) { - this->map->layout()->border = mapLayout->border; + if (!layout->border.isEmpty()) { + this->map->layout()->border = layout->border; } init(); } -bool NewMapDialog::checkNewMapDimensions() { - int numMetatiles = project->getMapDataSize(ui->spinBox_NewMap_Width->value(), ui->spinBox_NewMap_Height->value()); - int maxMetatiles = project->getMaxMapDataSize(); - - if (numMetatiles > maxMetatiles) { - ui->frame_NewMap_Warning->setVisible(true); - QString errorText = QString("Error: The specified width and height are too large.\n" - "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxMetatiles) - .arg(ui->spinBox_NewMap_Width->value()) - .arg(ui->spinBox_NewMap_Height->value()) - .arg(numMetatiles); - ui->label_NewMap_WarningMessage->setText(errorText); - ui->label_NewMap_WarningMessage->setWordWrap(true); - return false; - } - else { - ui->frame_NewMap_Warning->setVisible(false); - ui->label_NewMap_WarningMessage->clear(); - return true; - } -} - -bool NewMapDialog::checkNewMapGroup() { - group = project->groupNames.indexOf(this->ui->comboBox_NewMap_Group->currentText()); - - if (group < 0) { - ui->frame_NewMap_Warning->setVisible(true); - QString errorText = QString("Error: The specified map group '%1' does not exist.") - .arg(ui->comboBox_NewMap_Group->currentText()); - ui->label_NewMap_WarningMessage->setText(errorText); - ui->label_NewMap_WarningMessage->setWordWrap(true); - return false; - } else { - ui->frame_NewMap_Warning->setVisible(false); - ui->label_NewMap_WarningMessage->clear(); - return true; - } -} - void NewMapDialog::setDefaultSettings(Project *project) { settings.group = project->groupNames.at(0); - settings.width = project->getDefaultMapSize(); - settings.height = project->getDefaultMapSize(); + settings.width = project->getDefaultMapDimension(); + settings.height = project->getDefaultMapDimension(); settings.borderWidth = DEFAULT_BORDER_WIDTH; settings.borderHeight = DEFAULT_BORDER_HEIGHT; settings.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); settings.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); - settings.type = project->mapTypes.value(0, "0"); - settings.location = project->mapSectionIdNames.value(0, "0"); settings.song = project->defaultSong; - settings.canFlyTo = false; + settings.location = project->mapSectionIdNames.value(0, "0"); + settings.requiresFlash = false; + settings.weather = project->weatherNames.value(0, "0"); + settings.type = project->mapTypes.value(0, "0"); + settings.battleScene = project->mapBattleScenes.value(0, "0"); settings.showLocationName = true; settings.allowRunning = false; settings.allowBiking = false; settings.allowEscaping = false; settings.floorNumber = 0; + settings.canFlyTo = false; } void NewMapDialog::saveSettings() { - settings.group = ui->comboBox_NewMap_Group->currentText(); - settings.width = ui->spinBox_NewMap_Width->value(); - settings.height = ui->spinBox_NewMap_Height->value(); - settings.borderWidth = ui->spinBox_NewMap_BorderWidth->value(); - settings.borderHeight = ui->spinBox_NewMap_BorderHeight->value(); - settings.primaryTilesetLabel = ui->comboBox_NewMap_Primary_Tileset->currentText(); - settings.secondaryTilesetLabel = ui->comboBox_NewMap_Secondary_Tileset->currentText(); - settings.type = ui->comboBox_NewMap_Type->currentText(); - settings.location = ui->comboBox_NewMap_Location->currentText(); - settings.song = ui->comboBox_NewMap_Song->currentText(); - settings.canFlyTo = ui->checkBox_NewMap_Flyable->isChecked(); - settings.showLocationName = ui->checkBox_NewMap_Show_Location->isChecked(); - settings.allowRunning = ui->checkBox_NewMap_Allow_Running->isChecked(); - settings.allowBiking = ui->checkBox_NewMap_Allow_Biking->isChecked(); - settings.allowEscaping = ui->checkBox_NewMap_Allow_Escape_Rope->isChecked(); - settings.floorNumber = ui->spinBox_NewMap_Floor_Number->value(); + settings.group = ui->comboBox_Group->currentText(); + settings.width = ui->spinBox_MapWidth->value(); + settings.height = ui->spinBox_MapHeight->value(); + settings.borderWidth = ui->spinBox_BorderWidth->value(); + settings.borderHeight = ui->spinBox_BorderHeight->value(); + settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); + settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); + settings.song = ui->comboBox_Song->currentText(); + settings.location = ui->comboBox_Location->currentText(); + settings.requiresFlash = ui->checkBox_RequiresFlash->isChecked(); + settings.weather = ui->comboBox_Weather->currentText(); + settings.type = ui->comboBox_Type->currentText(); + settings.battleScene = ui->comboBox_BattleScene->currentText(); + settings.showLocationName = ui->checkBox_ShowLocation->isChecked(); + settings.allowRunning = ui->checkBox_AllowRunning->isChecked(); + settings.allowBiking = ui->checkBox_AllowBiking->isChecked(); + settings.allowEscaping = ui->checkBox_AllowEscaping->isChecked(); + settings.floorNumber = ui->spinBox_FloorNumber->value(); + settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); } void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; - settings.width = layout->width; - ui->spinBox_NewMap_Width->setValue(layout->width); - settings.height = layout->height; - ui->spinBox_NewMap_Height->setValue(layout->height); - settings.borderWidth = layout->border_width; - ui->spinBox_NewMap_BorderWidth->setValue(layout->border_width); - settings.borderHeight = layout->border_height; - ui->spinBox_NewMap_BorderWidth->setValue(layout->border_height); - settings.primaryTilesetLabel = layout->tileset_primary_label; - ui->comboBox_NewMap_Primary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Primary_Tileset->findText(layout->tileset_primary_label)); - settings.secondaryTilesetLabel = layout->tileset_secondary_label; - ui->comboBox_NewMap_Secondary_Tileset->setCurrentIndex(ui->comboBox_NewMap_Secondary_Tileset->findText(layout->tileset_secondary_label)); } void NewMapDialog::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; - - this->ui->comboBox_Layout->setCurrentIndex(this->ui->comboBox_Layout->findText(layoutId)); - useLayoutSettings(project->mapLayouts.value(this->layoutId)); + + // Dimensions and tilesets can't be changed for new maps using an existing layout + ui->groupBox_MapDimensions->setDisabled(true); + ui->groupBox_BorderDimensions->setDisabled(true); + ui->groupBox_Tilesets->setDisabled(true); } -void NewMapDialog::on_checkBox_UseExistingLayout_stateChanged(int state) { - bool layoutEditsEnabled = (state == Qt::Unchecked); - - this->ui->comboBox_Layout->setEnabled(!layoutEditsEnabled); +bool NewMapDialog::validateMapDimensions() { + int size = project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); + int maxSize = project->getMaxMapDataSize(); - this->ui->spinBox_NewMap_Width->setEnabled(layoutEditsEnabled); - this->ui->spinBox_NewMap_Height->setEnabled(layoutEditsEnabled); - this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled); - this->ui->spinBox_NewMap_BorderWidth->setEnabled(layoutEditsEnabled); - this->ui->comboBox_NewMap_Primary_Tileset->setEnabled(layoutEditsEnabled); - this->ui->comboBox_NewMap_Secondary_Tileset->setEnabled(layoutEditsEnabled); + QString errorText; + if (size > maxSize) { + errorText = QString("The specified width and height are too large.\n" + "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" + "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") + .arg(maxSize) + .arg(ui->spinBox_MapWidth->value()) + .arg(ui->spinBox_MapHeight->value()) + .arg(size); + } - if (!layoutEditsEnabled) { - useLayout(this->layoutId);//this->ui->comboBox_Layout->currentText()); - } else { - this->existingLayout = false; + bool isValid = errorText.isEmpty(); + ui->label_MapDimensionsError->setText(errorText); + ui->label_MapDimensionsError->setVisible(!isValid); + return isValid; +} + +bool NewMapDialog::validateMapGroup() { + this->group = project->groupNames.indexOf(ui->comboBox_Group->currentText()); + + QString errorText; + if (this->group < 0) { + errorText = QString("The specified map group '%1' does not exist.") + .arg(ui->comboBox_Group->currentText()); } + + bool isValid = errorText.isEmpty(); + ui->label_GroupError->setText(errorText); + ui->label_GroupError->setVisible(!isValid); + return isValid; } -void NewMapDialog::on_comboBox_Layout_currentTextChanged(const QString &text) { - if (this->project->mapLayoutsTable.contains(text)) { - useLayout(text); +bool NewMapDialog::validateTilesets() { + QString primaryTileset = ui->comboBox_PrimaryTileset->currentText(); + QString secondaryTileset = ui->comboBox_SecondaryTileset->currentText(); + + QString primaryErrorText; + if (primaryTileset.isEmpty()) { + primaryErrorText = QString("The primary tileset cannot be empty."); + } else if (ui->comboBox_PrimaryTileset->findText(primaryTileset) < 0) { + primaryErrorText = QString("The specified primary tileset '%1' does not exist.").arg(primaryTileset); + } + + QString secondaryErrorText; + if (secondaryTileset.isEmpty()) { + secondaryErrorText = QString("The secondary tileset cannot be empty."); + } else if (ui->comboBox_SecondaryTileset->findText(secondaryTileset) < 0) { + secondaryErrorText = QString("The specified secondary tileset '%2' does not exist.").arg(secondaryTileset); } + + QString errorText = QString("%1%2%3") + .arg(primaryErrorText) + .arg(!primaryErrorText.isEmpty() ? "\n" : "") + .arg(secondaryErrorText); + + bool isValid = errorText.isEmpty(); + ui->label_TilesetsError->setText(errorText); + ui->label_TilesetsError->setVisible(!isValid); + return isValid; } -void NewMapDialog::on_lineEdit_NewMap_Name_textChanged(const QString &text) { - if (project->mapNames.contains(text)) { - this->ui->lineEdit_NewMap_Name->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); +bool NewMapDialog::validateID() { + QString id = ui->lineEdit_ID->text(); + + QString errorText; + QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + if (!id.startsWith(expectedPrefix)) { + errorText = QString("The specified ID name '%1' must start with '%2'.").arg(id).arg(expectedPrefix); } else { - this->ui->lineEdit_NewMap_Name->setStyleSheet(""); + for (auto i = project->mapNamesToMapConstants.constBegin(), end = project->mapNamesToMapConstants.constEnd(); i != end; i++) { + if (id == i.value()) { + errorText = QString("The specified ID name '%1' is already in use.").arg(id); + break; + } + } } + + bool isValid = errorText.isEmpty(); + ui->label_IDError->setText(errorText); + ui->label_IDError->setVisible(!isValid); + ui->lineEdit_ID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; } -void NewMapDialog::on_pushButton_NewMap_Accept_clicked() { - if (!checkNewMapDimensions() || !checkNewMapGroup()) { - // ignore when map dimensions or map group are invalid - return; +void NewMapDialog::on_lineEdit_ID_textChanged(const QString &) { + validateID(); +} + +bool NewMapDialog::validateName() { + QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (project->mapNames.contains(name)) { + errorText = QString("The specified map name '%1' is already in use.").arg(name); } - Map *newMap = new Map; - Layout *layout; - // If map name is not unique, use default value. Also use only valid characters. - // After stripping invalid characters, strip any leading digits. - static const QRegularExpression re_invalidChars("[^a-zA-Z0-9_]+"); - QString newMapName = this->ui->lineEdit_NewMap_Name->text().remove(re_invalidChars); - static const QRegularExpression re_NaN("^[0-9]*"); - newMapName.remove(re_NaN); - if (project->mapNames.contains(newMapName) || newMapName.isEmpty()) { - newMapName = project->getNewMapName(); + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { + validateName(); + ui->lineEdit_ID->setText(Map::mapConstantFromName(text)); +} + +void NewMapDialog::on_pushButton_Accept_clicked() { + // Make sure to call each validation function so that all errors are shown at once. + bool success = true; + if (!validateMapDimensions()) success = false; + if (!validateMapGroup()) success = false; + if (!validateTilesets()) success = false; + if (!validateID()) success = false; + if (!validateName()) success = false; + if (!success) + return; + + // We check if the map name is empty separately from the validation above because it's likely + // that users will clear the name text box while editing, and we don't want to flash errors at them for this. + if (ui->lineEdit_Name->text().isEmpty()) { + ui->label_NameError->setText("The specified map name cannot be empty."); + ui->label_NameError->setVisible(true); + ui->lineEdit_Name->setStyleSheet(lineEdit_ErrorStylesheet); + return; } - newMap->setName(newMapName); - newMap->setType(this->ui->comboBox_NewMap_Type->currentText()); - newMap->setLocation(this->ui->comboBox_NewMap_Location->currentText()); - newMap->setSong(this->ui->comboBox_NewMap_Song->currentText()); - newMap->setRequiresFlash(false); - newMap->setWeather(this->project->weatherNames.value(0, "0")); - newMap->setShowsLocation(this->ui->checkBox_NewMap_Show_Location->isChecked()); - newMap->setBattleScene(this->project->mapBattleScenes.value(0, "0")); + Map *newMap = new Map; + newMap->setName(ui->lineEdit_Name->text()); + newMap->setConstantName(ui->lineEdit_ID->text()); + newMap->setSong(ui->comboBox_Song->currentText()); + newMap->setLocation(ui->comboBox_Location->currentText()); + newMap->setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); + newMap->setWeather(ui->comboBox_Weather->currentText()); + newMap->setType(ui->comboBox_Type->currentText()); + newMap->setBattleScene(ui->comboBox_BattleScene->currentText()); + newMap->setShowsLocation(ui->checkBox_ShowLocation->isChecked()); + if (projectConfig.mapAllowFlagsEnabled) { + newMap->setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); + newMap->setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); + newMap->setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); + } + if (projectConfig.floorNumberEnabled) { + newMap->setFloorNumber(ui->spinBox_FloorNumber->value()); + } + newMap->setNeedsHealLocation(ui->checkBox_CanFlyTo->isChecked()); + Layout *layout; if (this->existingLayout) { layout = this->project->mapLayouts.value(this->layoutId); newMap->setNeedsLayoutDir(false); } else { layout = new Layout; - layout->id = Layout::layoutConstantFromName(newMapName); + layout->id = Layout::layoutConstantFromName(newMap->name()); layout->name = QString("%1_Layout").arg(newMap->name()); - layout->width = this->ui->spinBox_NewMap_Width->value(); - layout->height = this->ui->spinBox_NewMap_Height->value(); + layout->width = ui->spinBox_MapWidth->value(); + layout->height = ui->spinBox_MapHeight->value(); if (projectConfig.useCustomBorderSize) { - layout->border_width = this->ui->spinBox_NewMap_BorderWidth->value(); - layout->border_height = this->ui->spinBox_NewMap_BorderHeight->value(); + layout->border_width = ui->spinBox_BorderWidth->value(); + layout->border_height = ui->spinBox_BorderHeight->value(); } else { layout->border_width = DEFAULT_BORDER_WIDTH; layout->border_height = DEFAULT_BORDER_HEIGHT; } - layout->tileset_primary_label = this->ui->comboBox_NewMap_Primary_Tileset->currentText(); - layout->tileset_secondary_label = this->ui->comboBox_NewMap_Secondary_Tileset->currentText(); + layout->tileset_primary_label = ui->comboBox_PrimaryTileset->currentText(); + layout->tileset_secondary_label = ui->comboBox_SecondaryTileset->currentText(); QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, newMapName); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMapName); + layout->border_path = QString("%1%2/border.bin").arg(basePath, newMap->name()); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMap->name()); } - if (this->importedMap) { layout->blockdata = map->layout()->blockdata; if (!map->layout()->border.isEmpty()) layout->border = map->layout()->border; } - - if (this->ui->checkBox_NewMap_Flyable->isChecked()) { - newMap->setNeedsHealLocation(true); - } - - if (projectConfig.mapAllowFlagsEnabled) { - newMap->setAllowsRunning(this->ui->checkBox_NewMap_Allow_Running->isChecked()); - newMap->setAllowsBiking(this->ui->checkBox_NewMap_Allow_Biking->isChecked()); - newMap->setAllowsEscaping(this->ui->checkBox_NewMap_Allow_Escape_Rope->isChecked()); - } - if (projectConfig.floorNumberEnabled) { - newMap->setFloorNumber(this->ui->spinBox_NewMap_Floor_Number->value()); - } - newMap->setLayout(layout); + if (this->existingLayout) { project->loadMapLayout(newMap); } From 9e1ef2c741c6b7796caf5369f4c0c24764034d03 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 13:48:18 -0500 Subject: [PATCH 06/42] Import collapsible section --- include/lib/collapsiblesection.h | 62 ++++++++++++++++++ porymap.pro | 2 + src/lib/collapsiblesection.cpp | 109 +++++++++++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 include/lib/collapsiblesection.h create mode 100644 src/lib/collapsiblesection.cpp diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h new file mode 100644 index 00000000..3a1b3023 --- /dev/null +++ b/include/lib/collapsiblesection.h @@ -0,0 +1,62 @@ +/* + Elypson/qt-collapsible-section + (c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com + + This file is part of Elypson/qt-collapsible section. + + Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Elypson/qt-collapsible-section is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Elypson/qt-collapsible-section. If not, see . +*/ + +#ifndef COLLAPSIBLESECTION_H +#define COLLAPSIBLESECTION_H + +#include +#include +#include +#include +#include +#include + +class CollapsibleSection : public QWidget +{ + Q_OBJECT + +private: + QGridLayout* mainLayout; + QToolButton* toggleButton; + QFrame* headerLine; + QParallelAnimationGroup* toggleAnimation; + QScrollArea* contentArea; + int animationDuration; + int collapsedHeight; + bool isExpanded = false; + +public slots: + void toggle(bool collapsed); + +public: + // initialize section + explicit CollapsibleSection(const QString& title = "", const int animationDuration = 0, QWidget* parent = 0); + + // set layout of content + void setContentLayout(QLayout& contentLayout); + + // set title + void setTitle(QString title); + + // update animations and their heights + void updateHeights(); +}; + +#endif // COLLAPSIBLESECTION_H diff --git a/porymap.pro b/porymap.pro index 9a2b2c75..1bea489b 100644 --- a/porymap.pro +++ b/porymap.pro @@ -45,6 +45,7 @@ SOURCES += src/core/block.cpp \ src/lib/fex/lexer.cpp \ src/lib/fex/parser.cpp \ src/lib/fex/parser_util.cpp \ + src/lib/collapsiblesection.cpp \ src/lib/orderedjson.cpp \ src/core/regionmapeditcommands.cpp \ src/scriptapi/apimap.cpp \ @@ -151,6 +152,7 @@ HEADERS += include/core/block.h \ include/lib/fex/lexer.h \ include/lib/fex/parser.h \ include/lib/fex/parser_util.h \ + include/lib/collapsiblesection.h \ include/lib/orderedmap.h \ include/lib/orderedjson.h \ include/ui/aboutporymap.h \ diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp new file mode 100644 index 00000000..000e822e --- /dev/null +++ b/src/lib/collapsiblesection.cpp @@ -0,0 +1,109 @@ +/* + Elypson/qt-collapsible-section + (c) 2016 Michael A. Voelkel - michael.alexander.voelkel@gmail.com + + This file is part of Elypson/qt-collapsible section. + + Elypson/qt-collapsible-section is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Elypson/qt-collapsible-section is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Elypson/qt-collapsible-section. If not, see . +*/ + +#include + +#include "collapsiblesection.h" +CollapsibleSection::CollapsibleSection(const QString& title, const int animationDuration, QWidget* parent) + : QWidget(parent), animationDuration(animationDuration) +{ + toggleButton = new QToolButton(this); + headerLine = new QFrame(this); + toggleAnimation = new QParallelAnimationGroup(this); + contentArea = new QScrollArea(this); + mainLayout = new QGridLayout(this); + + toggleButton->setStyleSheet("QToolButton {border: none;}"); + toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + toggleButton->setArrowType(Qt::ArrowType::RightArrow); + toggleButton->setText(title); + toggleButton->setCheckable(true); + toggleButton->setChecked(false); + + headerLine->setFrameShape(QFrame::HLine); + headerLine->setFrameShadow(QFrame::Sunken); + headerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); + + contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + // start out collapsed + contentArea->setMaximumHeight(0); + contentArea->setMinimumHeight(0); + + // let the entire widget grow and shrink with its content + toggleAnimation->addAnimation(new QPropertyAnimation(this, "maximumHeight")); + toggleAnimation->addAnimation(new QPropertyAnimation(this, "minimumHeight")); + toggleAnimation->addAnimation(new QPropertyAnimation(contentArea, "maximumHeight")); + + mainLayout->setVerticalSpacing(0); + mainLayout->setContentsMargins(0, 0, 0, 0); + + int row = 0; + mainLayout->addWidget(toggleButton, row, 0, 1, 1, Qt::AlignLeft); + mainLayout->addWidget(headerLine, row++, 2, 1, 1); + mainLayout->addWidget(contentArea, row, 0, 1, 3); + setLayout(mainLayout); + + connect(toggleButton, &QToolButton::toggled, this, &CollapsibleSection::toggle); +} + +void CollapsibleSection::toggle(bool expanded) +{ + toggleButton->setArrowType(expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); + toggleAnimation->setDirection(expanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); + toggleAnimation->start(); + + this->isExpanded = expanded; +} + +void CollapsibleSection::setContentLayout(QLayout& contentLayout) +{ + delete contentArea->layout(); + contentArea->setLayout(&contentLayout); + collapsedHeight = sizeHint().height() - contentArea->maximumHeight(); + + updateHeights(); +} + +void CollapsibleSection::setTitle(QString title) +{ + toggleButton->setText(std::move(title)); +} + +void CollapsibleSection::updateHeights() +{ + int contentHeight = contentArea->layout()->sizeHint().height(); + + for (int i = 0; i < toggleAnimation->animationCount() - 1; ++i) + { + QPropertyAnimation* SectionAnimation = static_cast(toggleAnimation->animationAt(i)); + SectionAnimation->setDuration(animationDuration); + SectionAnimation->setStartValue(collapsedHeight); + SectionAnimation->setEndValue(collapsedHeight + contentHeight); + } + + QPropertyAnimation* contentAnimation = static_cast(toggleAnimation->animationAt(toggleAnimation->animationCount() - 1)); + contentAnimation->setDuration(animationDuration); + contentAnimation->setStartValue(0); + contentAnimation->setEndValue(contentHeight); + + toggleAnimation->setDirection(isExpanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); + toggleAnimation->start(); +} From 205bb48c65b35919bdbbfed3bbe1603cee97bc9f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:27:35 -0500 Subject: [PATCH 07/42] Header tab and new map dialog share UI setup --- forms/mainwindow.ui | 202 +--------------- forms/mapheaderform.ui | 235 ++++++++++++++++++ forms/newmapdialog.ui | 478 +++++++++++-------------------------- include/core/map.h | 6 +- include/mainwindow.h | 16 +- include/ui/mapheaderform.h | 56 +++++ include/ui/newmapdialog.h | 2 + porymap.pro | 3 + src/core/map.cpp | 4 +- src/mainwindow.cpp | 205 +++------------- src/project.cpp | 4 +- src/scriptapi/apimap.cpp | 53 ++-- src/ui/mapheaderform.cpp | 220 +++++++++++++++++ src/ui/newmapdialog.cpp | 88 +++---- 14 files changed, 763 insertions(+), 809 deletions(-) create mode 100644 forms/mapheaderform.ui create mode 100644 include/ui/mapheaderform.h create mode 100644 src/ui/mapheaderform.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 44d69d30..eb6c7345 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1155,7 +1155,7 @@ 2 - + Layout @@ -2220,7 +2220,7 @@ 0 - + false @@ -2236,206 +2236,10 @@ QFrame::Shadow::Raised - - - QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint - - - 12 - + 9 - - - - Song - - - - - - - <html><head/><body><p>The default background music for this map.</p></body></html> - - - true - - - - - - - Location - - - - - - - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> - - - true - - - - - - - Requires Flash - - - - - - - <html><head/><body><p>Whether or not the map is dark and requires Flash to illuminate.</p></body></html> - - - - - - - - - - Weather - - - - - - - <html><head/><body><p>The default weather for this map.</p></body></html> - - - true - - - - - - - Type - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example. it determines whether biking or running is allowed.</p></body></html> - - - true - - - - - - - Battle scene - - - - - - - <html><head/><body><p>Determines the type of battle scene graphics to use.</p></body></html> - - - true - - - - - - - Show Location Name - - - - - - - <html><head/><body><p>Whether or not to display the location name when the player enters the map.</p></body></html> - - - - - - - - - - Allow Running - - - - - - - <html><head/><body><p>Allows the player to use Running Shoes</p></body></html> - - - - - - - - - - Allow Biking - - - - - - - <html><head/><body><p>Allows the player to use a Bike</p></body></html> - - - - - - - - - - Allow Dig & Escape Rope - - - - - - - <html><head/><body><p>Allows the player to use Dig or Escape Rope</p></body></html> - - - - - - - - - - Floor Number - - - - - - - <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> - - - -128 - - - 127 - - - diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui new file mode 100644 index 00000000..06541e2c --- /dev/null +++ b/forms/mapheaderform.ui @@ -0,0 +1,235 @@ + + + MapHeaderForm + + + + 0 + 0 + 407 + 349 + + + + Form + + + + QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint + + + + + Song + + + + + + + <html><head/><body><p>The default background music for this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Location + + + + + + + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Requires Flash + + + + + + + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> + + + + + + + + + + Weather + + + + + + + <html><head/><body><p>The default weather on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Type + + + + + + + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Battle Scene + + + + + + + <html><head/><body><p>This field is used to help determine what graphics to use in the background of battles on this map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Show Location Name + + + + + + + <html><head/><body><p>If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.</p></body></html> + + + + + + + + + + Allow Running + + + + + + + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> + + + + + + + + + + Allow Biking + + + + + + + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> + + + + + + + + + + Allow Dig & Escape Rope + + + + + + + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> + + + + + + + + + + Floor Number + + + + + + + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 9cf4dc18..dcf4f052 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -7,7 +7,7 @@ 0 0 453 - 563 + 588 @@ -25,7 +25,7 @@ 0 0 427 - 841 + 526 @@ -39,7 +39,14 @@
- + + + + ID + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> @@ -49,7 +56,23 @@ - + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + Border Dimensions @@ -104,92 +127,28 @@ - - - - Map Dimensions + + + + Group - - - - - - 0 - 0 - - - - Width - - - - - - - <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> - - - 1 - - - - - - - <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> - - - 1 - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - - - 0 - 0 - - - - Height - - - - - - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> - - - true + + + + Qt::Orientation::Vertical - - QComboBox::InsertPolicy::NoInsert + + + 20 + 40 + - + - - + + false @@ -204,46 +163,30 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - - - - false - - - color: rgb(255, 0, 0) - + + - - - - true + Can Fly To - - + + - Header Data + Tilesets - + - + - Song + Primary - + - <html><head/><body><p>The default background music for this map.</p></body></html> + <html><head/><body><p>The primary tileset for the new map.</p></body></html> true @@ -254,99 +197,16 @@ - - - Location - - - - - - - Requires Flash - - - - - - - Weather - - - - - - - Type - - - - - - - <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - Battle Scene - - - - - - - Show Location - - - - - - - Allow Running - - - - - - - Allow Biking - - - - - - - Allow Escaping - - - - - - - Floor Number - - - - - + - Can Fly To + Secondary - + - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> true @@ -356,121 +216,105 @@ - - - - <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> - - - 127 - - - - - - - <html><head/><body><p>This field is used to help determine what graphics to use in the background of battles on this map.</p></body></html> - - - true + + + + false - - QComboBox::InsertPolicy::NoInsert + + color: rgb(255, 0, 0) - - - - - - <html><head/><body><p>The default weather on this map.</p></body></html> + + - + true - - QComboBox::InsertPolicy::NoInsert - - - - - <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> + + + + + + + Map Dimensions + + + + + + + 0 + 0 + - + Width - - + + - <html><head/><body><p>If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.</p></body></html> + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> - - + + 1 - - - - <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> + + + + + 0 + 0 + - + Height - - + + - <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> - - + + 1 - - - - <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> - - - + + + + false - - - - - - <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + + color: rgb(255, 0, 0) + + true + - - - - Group - - - - - - - ID + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - + + false @@ -485,69 +329,35 @@ - - + + - Tilesets + Header Data + + + + + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + + + - - - - - Primary - - - - - - - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - Secondary - - - - - - - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - diff --git a/include/core/map.h b/include/core/map.h index eab3110e..314013d7 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -62,7 +62,7 @@ class Map : public QObject void setRequiresFlash(bool requiresFlash); void setWeather(const QString &weather); void setType(const QString &type); - void setShowsLocation(bool showsLocation); + void setShowsLocationName(bool showsLocationName); void setAllowsRunning(bool allowsRunning); void setAllowsBiking(bool allowsBiking); void setAllowsEscaping(bool allowsEscaping); @@ -74,7 +74,7 @@ class Map : public QObject bool requiresFlash() const { return m_requiresFlash; } QString weather() const { return m_weather; } QString type() const { return m_type; } - bool showsLocation() const { return m_showsLocation; } + bool showsLocationName() const { return m_showsLocationName; } bool allowsRunning() const { return m_allowsRunning; } bool allowsBiking() const { return m_allowsBiking; } bool allowsEscaping() const { return m_allowsEscaping; } @@ -136,7 +136,7 @@ class Map : public QObject bool m_requiresFlash; QString m_weather; QString m_type; - bool m_showsLocation; + bool m_showsLocationName; bool m_allowsRunning; bool m_allowsBiking; bool m_allowsEscaping; diff --git a/include/mainwindow.h b/include/mainwindow.h index 2efe964d..ab4e0482 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -32,6 +32,7 @@ #include "wildmonchart.h" #include "updatepromoter.h" #include "aboutporymap.h" +#include "mapheaderform.h" @@ -199,17 +200,7 @@ private slots: void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); void on_action_Exit_triggered(); - void on_comboBox_Song_currentTextChanged(const QString &arg1); - void on_comboBox_Location_currentTextChanged(const QString &arg1); - void on_comboBox_Weather_currentTextChanged(const QString &arg1); - void on_comboBox_Type_currentTextChanged(const QString &arg1); - void on_comboBox_BattleScene_currentTextChanged(const QString &arg1); - void on_comboBox_LayoutSelector_currentTextChanged(const QString &arg1); - void on_checkBox_ShowLocation_stateChanged(int selected); - void on_checkBox_AllowRunning_stateChanged(int selected); - void on_checkBox_AllowBiking_stateChanged(int selected); - void on_checkBox_AllowEscaping_stateChanged(int selected); - void on_spinBox_FloorNumber_valueChanged(int offset); + void on_comboBox_LayoutSelector_currentTextChanged(const QString &text); void on_actionShortcuts_triggered(); void on_actionZoom_In_triggered(); @@ -254,7 +245,6 @@ private slots: void on_comboBox_SecondaryTileset_currentTextChanged(const QString &arg1); void on_pushButton_ChangeDimensions_clicked(); void on_checkBox_smartPaths_stateChanged(int selected); - void on_checkBox_Visibility_stateChanged(int selected); void on_checkBox_ToggleBorder_stateChanged(int selected); void resetMapViewScale(); @@ -337,6 +327,8 @@ private slots: QAction *copyAction = nullptr; QAction *pasteAction = nullptr; + MapHeaderForm *mapHeader = nullptr; + QMap lastSelectedEvent; bool isProgrammaticEventTabChange; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h new file mode 100644 index 00000000..afdee728 --- /dev/null +++ b/include/ui/mapheaderform.h @@ -0,0 +1,56 @@ +#ifndef MAPHEADERFORM_H +#define MAPHEADERFORM_H + +#include "project.h" +#include "map.h" +#include "ui_mapheaderform.h" + +#include + +/* + This is the UI class used to edit the fields in a map's header. + It's intended to be used anywhere the UI needs to present an editor for a map's header, + e.g. for the current map in the main editor or in the new map dialog. +*/ + +namespace Ui { +class MapHeaderForm; +} + +class MapHeaderForm : public QWidget +{ + Q_OBJECT + +public: + explicit MapHeaderForm(QWidget *parent = nullptr); + ~MapHeaderForm(); + + void setProject(Project * project); + void setMap(Map * map); + + void clearDisplay(); + void clear(); + + void refreshLocationsComboBox(); + + Ui::MapHeaderForm *ui; + +private: + QPointer map = nullptr; + QPointer project = nullptr; + +private slots: + void on_comboBox_Song_currentTextChanged(const QString &); + void on_comboBox_Location_currentTextChanged(const QString &); + void on_comboBox_Weather_currentTextChanged(const QString &); + void on_comboBox_Type_currentTextChanged(const QString &); + void on_comboBox_BattleScene_currentTextChanged(const QString &); + void on_checkBox_RequiresFlash_stateChanged(int); + void on_checkBox_ShowLocationName_stateChanged(int); + void on_checkBox_AllowRunning_stateChanged(int); + void on_checkBox_AllowBiking_stateChanged(int); + void on_checkBox_AllowEscaping_stateChanged(int); + void on_spinBox_FloorNumber_valueChanged(int); +}; + +#endif // MAPHEADERFORM_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 4b17f32b..090fcb96 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -6,6 +6,7 @@ #include "editor.h" #include "project.h" #include "map.h" +#include "mapheaderform.h" namespace Ui { class NewMapDialog; @@ -34,6 +35,7 @@ class NewMapDialog : public QDialog private: Ui::NewMapDialog *ui; Project *project; + MapHeaderForm *headerData; bool validateMapDimensions(); bool validateMapGroup(); diff --git a/porymap.pro b/porymap.pro index 1bea489b..d3d870bc 100644 --- a/porymap.pro +++ b/porymap.pro @@ -83,6 +83,7 @@ SOURCES += src/core/block.cpp \ src/ui/prefabcreationdialog.cpp \ src/ui/regionmappixmapitem.cpp \ src/ui/citymappixmapitem.cpp \ + src/ui/mapheaderform.cpp \ src/ui/metatilelayersitem.cpp \ src/ui/metatileselector.cpp \ src/ui/movablerect.cpp \ @@ -166,6 +167,7 @@ HEADERS += include/core/block.h \ include/ui/connectionpixmapitem.h \ include/ui/currentselectedmetatilespixmapitem.h \ include/ui/gridsettings.h \ + include/ui/mapheaderform.h \ include/ui/newmapconnectiondialog.h \ include/ui/prefabframe.h \ include/ui/projectsettingseditor.h \ @@ -233,6 +235,7 @@ FORMS += forms/mainwindow.ui \ forms/colorinputwidget.ui \ forms/connectionslistitem.ui \ forms/gridsettingsdialog.ui \ + forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ diff --git a/src/core/map.cpp b/src/core/map.cpp index 942a4e95..ab31ac86 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -330,8 +330,8 @@ void Map::setType(const QString &type) { m_type = type; } -void Map::setShowsLocation(bool showsLocation) { - m_showsLocation = showsLocation; +void Map::setShowsLocationName(bool showsLocationName) { + m_showsLocationName = showsLocationName; } void Map::setAllowsRunning(bool allowsRunning) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 6105a3b4..d47dfef2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -235,6 +235,10 @@ void MainWindow::initCustomUI() { ui->mainTabBar->addTab(mainTabNames.value(i)); ui->mainTabBar->setTabIcon(i, mainTabIcons.value(i)); } + + // Create map header data widget + this->mapHeader = new MapHeaderForm(); + ui->layout_HeaderData->addWidget(this->mapHeader); } void MainWindow::initExtraSignals() { @@ -597,7 +601,7 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::refreshLocationsComboBox); + connect(project, &Project::mapSectionIdNamesChanged, this->mapHeader, &MapHeaderForm::refreshLocationsComboBox); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -1005,47 +1009,22 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ void MainWindow::displayMapProperties() { // Block signals to the comboboxes while they are being modified - const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker2(ui->comboBox_Location); - const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); - const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); - const QSignalBlocker blocker5(ui->comboBox_Weather); - const QSignalBlocker blocker6(ui->comboBox_BattleScene); - const QSignalBlocker blocker7(ui->comboBox_Type); - const QSignalBlocker blocker8(ui->checkBox_Visibility); - const QSignalBlocker blocker9(ui->checkBox_ShowLocation); - const QSignalBlocker blockerA(ui->checkBox_AllowRunning); - const QSignalBlocker blockerB(ui->checkBox_AllowBiking); - const QSignalBlocker blockerC(ui->spinBox_FloorNumber); - const QSignalBlocker blockerD(ui->checkBox_AllowEscaping); - - ui->checkBox_Visibility->setChecked(false); - ui->checkBox_ShowLocation->setChecked(false); - ui->checkBox_AllowRunning->setChecked(false); - ui->checkBox_AllowBiking->setChecked(false); - ui->checkBox_AllowEscaping->setChecked(false); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + + this->mapHeader->clearDisplay(); if (!editor || !editor->map || !editor->project) { - ui->frame_3->setEnabled(false); + ui->frame_HeaderData->setEnabled(false); return; } - ui->frame_3->setEnabled(true); + ui->frame_HeaderData->setEnabled(true); Map *map = editor->map; ui->comboBox_PrimaryTileset->setCurrentText(map->layout()->tileset_primary_label); ui->comboBox_SecondaryTileset->setCurrentText(map->layout()->tileset_secondary_label); - ui->comboBox_Song->setCurrentText(map->song()); - ui->comboBox_Location->setCurrentText(map->location()); - ui->checkBox_Visibility->setChecked(map->requiresFlash()); - ui->comboBox_Weather->setCurrentText(map->weather()); - ui->comboBox_Type->setCurrentText(map->type()); - ui->comboBox_BattleScene->setCurrentText(map->battleScene()); - ui->checkBox_ShowLocation->setChecked(map->showsLocation()); - ui->checkBox_AllowRunning->setChecked(map->allowsRunning()); - ui->checkBox_AllowBiking->setChecked(map->allowsBiking()); - ui->checkBox_AllowEscaping->setChecked(map->allowsEscaping()); - ui->spinBox_FloorNumber->setValue(map->floorNumber()); + this->mapHeader->setMap(map); // Custom fields table. /* // TODO: Re-enable @@ -1068,122 +1047,24 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te } } -void MainWindow::on_comboBox_Song_currentTextChanged(const QString &song) -{ - if (editor && editor->map) { - editor->map->setSong(song); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_Location_currentTextChanged(const QString &location) -{ - if (editor && editor->map) { - editor->map->setLocation(location); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_Weather_currentTextChanged(const QString &weather) -{ - if (editor && editor->map) { - editor->map->setWeather(weather); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_Type_currentTextChanged(const QString &type) -{ - if (editor && editor->map) { - editor->map->setType(type); - markMapEdited(); - } -} - -void MainWindow::on_comboBox_BattleScene_currentTextChanged(const QString &battle_scene) -{ - if (editor && editor->map) { - editor->map->setBattleScene(battle_scene); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_Visibility_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setRequiresFlash(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_ShowLocation_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setShowsLocation(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_AllowRunning_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setAllowsRunning(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_AllowBiking_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setAllowsBiking(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_checkBox_AllowEscaping_stateChanged(int selected) -{ - if (editor && editor->map) { - editor->map->setAllowsEscaping(selected == Qt::Checked); - markMapEdited(); - } -} - -void MainWindow::on_spinBox_FloorNumber_valueChanged(int offset) -{ - if (editor && editor->map) { - editor->map->setFloorNumber(offset); - markMapEdited(); - } -} - // Update the UI using information we've read from the user's project files. bool MainWindow::setProjectUI() { Project *project = editor->project; + this->mapHeader->setProject(project); + // Block signals to the comboboxes while they are being modified - const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); - const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); - const QSignalBlocker blocker5(ui->comboBox_Weather); - const QSignalBlocker blocker6(ui->comboBox_BattleScene); - const QSignalBlocker blocker7(ui->comboBox_Type); - const QSignalBlocker blocker8(ui->comboBox_DiveMap); - const QSignalBlocker blocker9(ui->comboBox_EmergeMap); - const QSignalBlocker blocker10(ui->comboBox_LayoutSelector); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); // Set up project comboboxes - ui->comboBox_Song->clear(); - ui->comboBox_Song->addItems(project->songNames); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_SecondaryTileset->clear(); ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_Weather->clear(); - ui->comboBox_Weather->addItems(project->weatherNames); - ui->comboBox_BattleScene->clear(); - ui->comboBox_BattleScene->addItems(project->mapBattleScenes); - ui->comboBox_Type->clear(); - ui->comboBox_Type->addItems(project->mapTypes); ui->comboBox_LayoutSelector->clear(); ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); ui->comboBox_DiveMap->clear(); @@ -1194,29 +1075,16 @@ bool MainWindow::setProjectUI() { ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->setClearButtonEnabled(true); ui->comboBox_EmergeMap->setFocusedScrollingEnabled(false); - refreshLocationsComboBox(); // Show/hide parts of the UI that are dependent on the user's project settings // Wild Encounters tab ui->mainTabBar->setTabEnabled(MainTab::WildPokemon, editor->project->wildEncountersLoaded); - bool hasFlags = projectConfig.mapAllowFlagsEnabled; - ui->checkBox_AllowRunning->setVisible(hasFlags); - ui->checkBox_AllowBiking->setVisible(hasFlags); - ui->checkBox_AllowEscaping->setVisible(hasFlags); - ui->label_AllowRunning->setVisible(hasFlags); - ui->label_AllowBiking->setVisible(hasFlags); - ui->label_AllowEscaping->setVisible(hasFlags); - ui->newEventToolButton->newWeatherTriggerAction->setVisible(projectConfig.eventWeatherTriggerEnabled); ui->newEventToolButton->newSecretBaseAction->setVisible(projectConfig.eventSecretBaseEnabled); ui->newEventToolButton->newCloneObjectAction->setVisible(projectConfig.eventCloneObjectEnabled); - bool floorNumEnabled = projectConfig.floorNumberEnabled; - ui->spinBox_FloorNumber->setVisible(floorNumEnabled); - ui->label_FloorNumber->setVisible(floorNumEnabled); - Event::setIcons(); editor->setCollisionGraphics(); ui->spinBox_SelectedElevation->setMaximum(Block::getMaxElevation()); @@ -1244,41 +1112,22 @@ bool MainWindow::setProjectUI() { return true; } -void MainWindow::refreshLocationsComboBox() { - QStringList locations = this->editor->project->mapSectionIdNames; - locations.sort(); - - const QSignalBlocker b(ui->comboBox_Location); - ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(locations); - if (this->editor->map) - ui->comboBox_Location->setCurrentText(this->editor->map->location()); -} - void MainWindow::clearProjectUI() { // Block signals to the comboboxes while they are being modified - const QSignalBlocker blocker1(ui->comboBox_Song); - const QSignalBlocker blocker2(ui->comboBox_Location); - const QSignalBlocker blocker3(ui->comboBox_PrimaryTileset); - const QSignalBlocker blocker4(ui->comboBox_SecondaryTileset); - const QSignalBlocker blocker5(ui->comboBox_Weather); - const QSignalBlocker blocker6(ui->comboBox_BattleScene); - const QSignalBlocker blocker7(ui->comboBox_Type); - const QSignalBlocker blocker8(ui->comboBox_DiveMap); - const QSignalBlocker blocker9(ui->comboBox_EmergeMap); - const QSignalBlocker blockerA(ui->comboBox_LayoutSelector); - - ui->comboBox_Song->clear(); - ui->comboBox_Location->clear(); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); + ui->comboBox_PrimaryTileset->clear(); ui->comboBox_SecondaryTileset->clear(); - ui->comboBox_Weather->clear(); - ui->comboBox_BattleScene->clear(); - ui->comboBox_Type->clear(); ui->comboBox_DiveMap->clear(); ui->comboBox_EmergeMap->clear(); ui->comboBox_LayoutSelector->clear(); + this->mapHeader->clear(); + // Clear map models delete this->mapGroupModel; delete this->groupListProxyModel; diff --git a/src/project.cpp b/src/project.cpp index f996ff1c..f36f7543 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -221,7 +221,7 @@ bool Project::loadMapData(Map* map) { map->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); map->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); map->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->setShowsLocation(ParseUtil::jsonToBool(mapObj["show_map_name"])); + map->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); map->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); if (projectConfig.mapAllowFlagsEnabled) { @@ -1293,7 +1293,7 @@ void Project::saveMap(Map *map) { mapObj["allow_escaping"] = map->allowsEscaping(); mapObj["allow_running"] = map->allowsRunning(); } - mapObj["show_map_name"] = map->showsLocation(); + mapObj["show_map_name"] = map->showsLocationName(); if (projectConfig.floorNumberEnabled) { mapObj["floor_number"] = map->floorNumber(); } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index d13d660b..5a0fa9ce 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -811,7 +811,8 @@ QJSValue MainWindow::getTilePixels(int tileId) { // Editing map header //===================== -// TODO: Replace UI setting here with calls to appropriate set functions. Update UI with signals from Map +// TODO: Connect signals from new function calls to update UI +// TODO: Is the error-checking for known constant names still reasonable / needed? (you can type anything after all) QString MainWindow::getSong() { if (!this->editor || !this->editor->map) @@ -820,13 +821,13 @@ QString MainWindow::getSong() { } void MainWindow::setSong(QString song) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->songNames.contains(song)) { logError(QString("Unknown song '%1'").arg(song)); return; } - this->ui->comboBox_Song->setCurrentText(song); + this->editor->map->setSong(song); } QString MainWindow::getLocation() { @@ -836,13 +837,13 @@ QString MainWindow::getLocation() { } void MainWindow::setLocation(QString location) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->mapSectionIdNames.contains(location)) { logError(QString("Unknown location '%1'").arg(location)); return; } - this->ui->comboBox_Location->setCurrentText(location); + this->editor->map->setLocation(location); } bool MainWindow::getRequiresFlash() { @@ -852,9 +853,9 @@ bool MainWindow::getRequiresFlash() { } void MainWindow::setRequiresFlash(bool require) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_Visibility->setChecked(require); + this->editor->map->setRequiresFlash(require); } QString MainWindow::getWeather() { @@ -864,13 +865,13 @@ QString MainWindow::getWeather() { } void MainWindow::setWeather(QString weather) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->weatherNames.contains(weather)) { logError(QString("Unknown weather '%1'").arg(weather)); return; } - this->ui->comboBox_Weather->setCurrentText(weather); + this->editor->map->setWeather(weather); } QString MainWindow::getType() { @@ -880,13 +881,13 @@ QString MainWindow::getType() { } void MainWindow::setType(QString type) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->mapTypes.contains(type)) { logError(QString("Unknown map type '%1'").arg(type)); return; } - this->ui->comboBox_Type->setCurrentText(type); + this->editor->map->setType(type); } QString MainWindow::getBattleScene() { @@ -896,25 +897,25 @@ QString MainWindow::getBattleScene() { } void MainWindow::setBattleScene(QString battleScene) { - if (!this->ui || !this->editor || !this->editor->project) + if (!this->editor || !this->editor->map || !this->editor->project) return; if (!this->editor->project->mapBattleScenes.contains(battleScene)) { logError(QString("Unknown battle scene '%1'").arg(battleScene)); return; } - this->ui->comboBox_BattleScene->setCurrentText(battleScene); + this->editor->map->setBattleScene(battleScene); } bool MainWindow::getShowLocationName() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->showsLocation(); + return this->editor->map->showsLocationName(); } void MainWindow::setShowLocationName(bool show) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_ShowLocation->setChecked(show); + this->editor->map->setShowsLocationName(show); } bool MainWindow::getAllowRunning() { @@ -924,9 +925,9 @@ bool MainWindow::getAllowRunning() { } void MainWindow::setAllowRunning(bool allow) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_AllowRunning->setChecked(allow); + this->editor->map->setAllowsRunning(allow); } bool MainWindow::getAllowBiking() { @@ -936,9 +937,9 @@ bool MainWindow::getAllowBiking() { } void MainWindow::setAllowBiking(bool allow) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_AllowBiking->setChecked(allow); + this->editor->map->setAllowsBiking(allow); } bool MainWindow::getAllowEscaping() { @@ -948,9 +949,9 @@ bool MainWindow::getAllowEscaping() { } void MainWindow::setAllowEscaping(bool allow) { - if (!this->ui) + if (!this->editor || !this->editor->map) return; - this->ui->checkBox_AllowEscaping->setChecked(allow); + this->editor->map->setAllowsEscaping(allow); } int MainWindow::getFloorNumber() { @@ -960,12 +961,8 @@ int MainWindow::getFloorNumber() { } void MainWindow::setFloorNumber(int floorNumber) { - if (!this->ui) - return; - if (floorNumber < -128 || floorNumber > 127) { - logError(QString("Invalid floor number '%1'").arg(floorNumber)); + if (!this->editor || !this->editor->map) return; - } - this->ui->spinBox_FloorNumber->setValue(floorNumber); + this->editor->map->setFloorNumber(floorNumber); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp new file mode 100644 index 00000000..88d7fbf1 --- /dev/null +++ b/src/ui/mapheaderform.cpp @@ -0,0 +1,220 @@ +#include "mapheaderform.h" + +#define BLOCK_SIGNALS \ + const QSignalBlocker b_Song(ui->comboBox_Song); \ + const QSignalBlocker b_Location(ui->comboBox_Location); \ + const QSignalBlocker b_RequiresFlash(ui->checkBox_RequiresFlash); \ + const QSignalBlocker b_Weather(ui->comboBox_Weather); \ + const QSignalBlocker b_Type(ui->comboBox_Type); \ + const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); \ + const QSignalBlocker b_ShowLocationName(ui->checkBox_ShowLocationName); \ + const QSignalBlocker b_AllowRunning(ui->checkBox_AllowRunning); \ + const QSignalBlocker b_AllowBiking(ui->checkBox_AllowBiking); \ + const QSignalBlocker b_AllowEscaping(ui->checkBox_AllowEscaping); \ + const QSignalBlocker b_FloorNumber(ui->spinBox_FloorNumber); + + +MapHeaderForm::MapHeaderForm(QWidget *parent) + : QWidget(parent) + , ui(new Ui::MapHeaderForm) +{ + ui->setupUi(this); + + // This value is an s8 by default, but we don't need to unnecessarily limit users. + ui->spinBox_FloorNumber->setMinimum(INT_MIN); + ui->spinBox_FloorNumber->setMaximum(INT_MAX); +} + +MapHeaderForm::~MapHeaderForm() +{ + delete ui; +} + +void MapHeaderForm::setProject(Project * newProject) { + clear(); + + this->project = newProject; + if (!this->project) + return; + + // Populate combo boxes + BLOCK_SIGNALS + ui->comboBox_Song->addItems(this->project->songNames); + ui->comboBox_Weather->addItems(this->project->weatherNames); + ui->comboBox_Type->addItems(this->project->mapTypes); + ui->comboBox_BattleScene->addItems(this->project->mapBattleScenes); + refreshLocationsComboBox(); + + // Hide config-specific settings + + bool hasFlags = projectConfig.mapAllowFlagsEnabled; + ui->checkBox_AllowRunning->setVisible(hasFlags); + ui->checkBox_AllowBiking->setVisible(hasFlags); + ui->checkBox_AllowEscaping->setVisible(hasFlags); + ui->label_AllowRunning->setVisible(hasFlags); + ui->label_AllowBiking->setVisible(hasFlags); + ui->label_AllowEscaping->setVisible(hasFlags); + + bool floorNumEnabled = projectConfig.floorNumberEnabled; + ui->spinBox_FloorNumber->setVisible(floorNumEnabled); + ui->label_FloorNumber->setVisible(floorNumEnabled); +} + +void MapHeaderForm::setMap(Map * newMap) { + this->map = newMap; + if (!this->map) { + clearDisplay(); + return; + } + + BLOCK_SIGNALS + ui->comboBox_Song->setCurrentText(this->map->song()); + ui->comboBox_Location->setCurrentText(this->map->location()); + ui->checkBox_RequiresFlash->setChecked(this->map->requiresFlash()); + ui->comboBox_Weather->setCurrentText(this->map->weather()); + ui->comboBox_Type->setCurrentText(this->map->type()); + ui->comboBox_BattleScene->setCurrentText(this->map->battleScene()); + ui->checkBox_ShowLocationName->setChecked(this->map->showsLocationName()); + ui->checkBox_AllowRunning->setChecked(this->map->allowsRunning()); + ui->checkBox_AllowBiking->setChecked(this->map->allowsBiking()); + ui->checkBox_AllowEscaping->setChecked(this->map->allowsEscaping()); + ui->spinBox_FloorNumber->setValue(this->map->floorNumber()); +} + +void MapHeaderForm::clearDisplay() { + BLOCK_SIGNALS + ui->comboBox_Song->clearEditText(); + ui->comboBox_Location->clearEditText(); + ui->comboBox_Weather->clearEditText(); + ui->comboBox_Type->clearEditText(); + ui->comboBox_BattleScene->clearEditText(); + ui->checkBox_ShowLocationName->setChecked(false); + ui->checkBox_RequiresFlash->setChecked(false); + ui->checkBox_AllowRunning->setChecked(false); + ui->checkBox_AllowBiking->setChecked(false); + ui->checkBox_AllowEscaping->setChecked(false); + ui->spinBox_FloorNumber->setValue(0); +} + +// Clear display and depopulate combo boxes +void MapHeaderForm::clear() { + BLOCK_SIGNALS + ui->comboBox_Song->clear(); + ui->comboBox_Location->clear(); + ui->comboBox_Weather->clear(); + ui->comboBox_Type->clear(); + ui->comboBox_BattleScene->clear(); + ui->checkBox_ShowLocationName->setChecked(false); + ui->checkBox_RequiresFlash->setChecked(false); + ui->checkBox_AllowRunning->setChecked(false); + ui->checkBox_AllowBiking->setChecked(false); + ui->checkBox_AllowEscaping->setChecked(false); + ui->spinBox_FloorNumber->setValue(0); +} + +void MapHeaderForm::refreshLocationsComboBox() { + const QSignalBlocker b(ui->comboBox_Location); + ui->comboBox_Location->clear(); + + if (this->project) { + QStringList locations = this->project->mapSectionIdNames; + locations.sort(); + ui->comboBox_Location->addItems(locations); + } + if (this->map) { + ui->comboBox_Location->setCurrentText(this->map->location()); + } +} + +void MapHeaderForm::on_comboBox_Song_currentTextChanged(const QString &song) +{ + if (this->map) { + this->map->setSong(song); + this->map->modify(); + } +} + +void MapHeaderForm::on_comboBox_Location_currentTextChanged(const QString &location) +{ + if (this->map) { + this->map->setLocation(location); + this->map->modify(); + + // Update cached location name in the project + // TODO: This should be handled elsewhere now, connected to the map change signal + if (this->project) + this->project->mapNameToMapSectionName.insert(this->map->name(), this->map->location()); + } +} + +void MapHeaderForm::on_comboBox_Weather_currentTextChanged(const QString &weather) +{ + if (this->map) { + this->map->setWeather(weather); + this->map->modify(); + } +} + +void MapHeaderForm::on_comboBox_Type_currentTextChanged(const QString &type) +{ + if (this->map) { + this->map->setType(type); + this->map->modify(); + } +} + +void MapHeaderForm::on_comboBox_BattleScene_currentTextChanged(const QString &battleScene) +{ + if (this->map) { + this->map->setBattleScene(battleScene); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_RequiresFlash_stateChanged(int selected) +{ + if (this->map) { + this->map->setRequiresFlash(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_ShowLocationName_stateChanged(int selected) +{ + if (this->map) { + this->map->setShowsLocationName(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_AllowRunning_stateChanged(int selected) +{ + if (this->map) { + this->map->setAllowsRunning(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_AllowBiking_stateChanged(int selected) +{ + if (this->map) { + this->map->setAllowsBiking(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_checkBox_AllowEscaping_stateChanged(int selected) +{ + if (this->map) { + this->map->setAllowsEscaping(selected == Qt::Checked); + this->map->modify(); + } +} + +void MapHeaderForm::on_spinBox_FloorNumber_valueChanged(int offset) +{ + if (this->map) { + this->map->setFloorNumber(offset); + this->map->modify(); + } +} diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 0d6bdd77..d3096a86 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -31,6 +31,9 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->lineEdit_Name->setValidator(validator); ui->lineEdit_ID->setValidator(validator); + this->headerData = new MapHeaderForm(); + ui->layout_HeaderData->addWidget(this->headerData); + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); } @@ -46,11 +49,7 @@ void NewMapDialog::init() { ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); ui->comboBox_Group->addItems(project->groupNames); - ui->comboBox_Song->addItems(project->songNames); - ui->comboBox_Location->addItems(project->mapSectionIdNames); - ui->comboBox_Weather->addItems(project->weatherNames); - ui->comboBox_Type->addItems(project->mapTypes); - ui->comboBox_BattleScene->addItems(project->mapBattleScenes); + this->headerData->setProject(project); // Set spin box limits ui->spinBox_MapWidth->setMaximum(project->getMaxMapWidth()); @@ -58,21 +57,8 @@ void NewMapDialog::init() { ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); - // Hide config specific ui elements - bool hasFlags = projectConfig.mapAllowFlagsEnabled; - ui->checkBox_AllowRunning->setVisible(hasFlags); - ui->checkBox_AllowBiking->setVisible(hasFlags); - ui->checkBox_AllowEscaping->setVisible(hasFlags); - ui->label_AllowRunning->setVisible(hasFlags); - ui->label_AllowBiking->setVisible(hasFlags); - ui->label_AllowEscaping->setVisible(hasFlags); - ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - bool hasFloorNumber = projectConfig.floorNumberEnabled; - ui->spinBox_FloorNumber->setVisible(hasFloorNumber); - ui->label_FloorNumber->setVisible(hasFloorNumber); - // Restore previous settings ui->lineEdit_Name->setText(project->getNewMapName()); ui->comboBox_Group->setTextItem(settings.group); @@ -82,17 +68,17 @@ void NewMapDialog::init() { ui->spinBox_BorderHeight->setValue(settings.borderHeight); ui->comboBox_PrimaryTileset->setTextItem(settings.primaryTilesetLabel); ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); - ui->comboBox_Song->setTextItem(settings.song); - ui->comboBox_Location->setTextItem(settings.location); - ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); - ui->comboBox_Weather->setTextItem(settings.weather); - ui->comboBox_Type->setTextItem(settings.type); - ui->comboBox_BattleScene->setTextItem(settings.battleScene); - ui->checkBox_ShowLocation->setChecked(settings.showLocationName); - ui->checkBox_AllowRunning->setChecked(settings.allowRunning); - ui->checkBox_AllowBiking->setChecked(settings.allowBiking); - ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); - ui->spinBox_FloorNumber->setValue(settings.floorNumber); + this->headerData->ui->comboBox_Song->setTextItem(settings.song); + this->headerData->ui->comboBox_Location->setTextItem(settings.location); + this->headerData->ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); + this->headerData->ui->comboBox_Weather->setTextItem(settings.weather); + this->headerData->ui->comboBox_Type->setTextItem(settings.type); + this->headerData->ui->comboBox_BattleScene->setTextItem(settings.battleScene); + this->headerData->ui->checkBox_ShowLocationName->setChecked(settings.showLocationName); + this->headerData->ui->checkBox_AllowRunning->setChecked(settings.allowRunning); + this->headerData->ui->checkBox_AllowBiking->setChecked(settings.allowBiking); + this->headerData->ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); + this->headerData->ui->spinBox_FloorNumber->setValue(settings.floorNumber); ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); } @@ -164,17 +150,17 @@ void NewMapDialog::saveSettings() { settings.borderHeight = ui->spinBox_BorderHeight->value(); settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); - settings.song = ui->comboBox_Song->currentText(); - settings.location = ui->comboBox_Location->currentText(); - settings.requiresFlash = ui->checkBox_RequiresFlash->isChecked(); - settings.weather = ui->comboBox_Weather->currentText(); - settings.type = ui->comboBox_Type->currentText(); - settings.battleScene = ui->comboBox_BattleScene->currentText(); - settings.showLocationName = ui->checkBox_ShowLocation->isChecked(); - settings.allowRunning = ui->checkBox_AllowRunning->isChecked(); - settings.allowBiking = ui->checkBox_AllowBiking->isChecked(); - settings.allowEscaping = ui->checkBox_AllowEscaping->isChecked(); - settings.floorNumber = ui->spinBox_FloorNumber->value(); + settings.song = this->headerData->ui->comboBox_Song->currentText(); + settings.location = this->headerData->ui->comboBox_Location->currentText(); + settings.requiresFlash = this->headerData->ui->checkBox_RequiresFlash->isChecked(); + settings.weather = this->headerData->ui->comboBox_Weather->currentText(); + settings.type = this->headerData->ui->comboBox_Type->currentText(); + settings.battleScene = this->headerData->ui->comboBox_BattleScene->currentText(); + settings.showLocationName = this->headerData->ui->checkBox_ShowLocationName->isChecked(); + settings.allowRunning = this->headerData->ui->checkBox_AllowRunning->isChecked(); + settings.allowBiking = this->headerData->ui->checkBox_AllowBiking->isChecked(); + settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); + settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); } @@ -334,20 +320,20 @@ void NewMapDialog::on_pushButton_Accept_clicked() { Map *newMap = new Map; newMap->setName(ui->lineEdit_Name->text()); newMap->setConstantName(ui->lineEdit_ID->text()); - newMap->setSong(ui->comboBox_Song->currentText()); - newMap->setLocation(ui->comboBox_Location->currentText()); - newMap->setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); - newMap->setWeather(ui->comboBox_Weather->currentText()); - newMap->setType(ui->comboBox_Type->currentText()); - newMap->setBattleScene(ui->comboBox_BattleScene->currentText()); - newMap->setShowsLocation(ui->checkBox_ShowLocation->isChecked()); + newMap->setSong(this->headerData->ui->comboBox_Song->currentText()); + newMap->setLocation(this->headerData->ui->comboBox_Location->currentText()); + newMap->setRequiresFlash(this->headerData->ui->checkBox_RequiresFlash->isChecked()); + newMap->setWeather(this->headerData->ui->comboBox_Weather->currentText()); + newMap->setType(this->headerData->ui->comboBox_Type->currentText()); + newMap->setBattleScene(this->headerData->ui->comboBox_BattleScene->currentText()); + newMap->setShowsLocationName(this->headerData->ui->checkBox_ShowLocationName->isChecked()); if (projectConfig.mapAllowFlagsEnabled) { - newMap->setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); - newMap->setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); - newMap->setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); + newMap->setAllowsRunning(this->headerData->ui->checkBox_AllowRunning->isChecked()); + newMap->setAllowsBiking(this->headerData->ui->checkBox_AllowBiking->isChecked()); + newMap->setAllowsEscaping(this->headerData->ui->checkBox_AllowEscaping->isChecked()); } if (projectConfig.floorNumberEnabled) { - newMap->setFloorNumber(ui->spinBox_FloorNumber->value()); + newMap->setFloorNumber(this->headerData->ui->spinBox_FloorNumber->value()); } newMap->setNeedsHealLocation(ui->checkBox_CanFlyTo->isChecked()); From 4f8224359e5eec405f62b5c35a71807c5f96ce90 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:34:10 -0500 Subject: [PATCH 08/42] Use collapsible section for header data in new map dialog --- forms/newmapdialog.ui | 282 ++++++++++++++++--------------- include/config.h | 2 + include/lib/collapsiblesection.h | 36 ++-- include/ui/newmapdialog.h | 4 +- src/config.cpp | 3 + src/lib/collapsiblesection.cpp | 104 ++++++++---- src/mainwindow.cpp | 36 ++-- src/ui/newmapdialog.cpp | 12 +- 8 files changed, 271 insertions(+), 208 deletions(-) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index dcf4f052..3ddba1dc 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,7 +25,7 @@ 0 0 427 - 526 + 520 @@ -39,24 +39,23 @@ - - - - ID + + + + false - - - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + color: rgb(255, 0, 0) - + + + + true - + false @@ -72,7 +71,7 @@ - + Border Dimensions @@ -127,97 +126,67 @@ - - - - Group - - - - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - + + - Can Fly To + ID - - + + - Tilesets + Map Dimensions - + - + + + + 0 + 0 + + - Primary + Width - + - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> - - QComboBox::InsertPolicy::NoInsert + + 1 - - + + + + + 0 + 0 + + - Secondary + Height - - + + - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> - - QComboBox::InsertPolicy::NoInsert + + 1 - - + + false @@ -235,60 +204,84 @@ - - + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> + + + + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true + + + + + - Map Dimensions + Tilesets - + - - - - 0 - 0 - - + - Width + Primary - + - <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - 1 + + true + + + QComboBox::InsertPolicy::NoInsert - - - - - 0 - 0 - - + + - Height + Secondary - - + + - <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - 1 + + true + + + QComboBox::InsertPolicy::NoInsert - - + + false @@ -306,14 +299,7 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - + false @@ -329,28 +315,45 @@ - - - - Header Data - - - - - - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + + Qt::Orientation::Vertical - - true + + + 20 + 40 + - - QComboBox::InsertPolicy::NoInsert + + + + + + Group - + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -360,6 +363,13 @@ + + + + Can Fly To + + + diff --git a/include/config.h b/include/config.h index 2a93a793..0dcbfe66 100644 --- a/include/config.h +++ b/include/config.h @@ -70,6 +70,7 @@ class PorymapConfig: public KeyValueConfigBase this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; + this->newMapHeaderSectionExpanded = false; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; @@ -121,6 +122,7 @@ class PorymapConfig: public KeyValueConfigBase bool showTilesetEditorLayerGrid; bool monitorFiles; bool tilesetCheckerboardFill; + bool newMapHeaderSectionExpanded; QString theme; QString wildMonChartTheme; QString textEditorOpenFolder; diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 3a1b3023..584e44cb 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -16,6 +16,10 @@ You should have received a copy of the GNU General Public License along with Elypson/qt-collapsible-section. If not, see . + + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. */ #ifndef COLLAPSIBLESECTION_H @@ -24,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -31,32 +36,33 @@ class CollapsibleSection : public QWidget { Q_OBJECT + +public: + explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0); + + void setContentLayout(QLayout* contentLayout); + void setTitle(QString title); + bool isExpanded() const { return this->expanded; } + +public slots: + void toggle(bool collapsed); private: QGridLayout* mainLayout; QToolButton* toggleButton; QFrame* headerLine; QParallelAnimationGroup* toggleAnimation; + QSet sectionAnimations; + QPropertyAnimation* contentAnimation; QScrollArea* contentArea; int animationDuration; int collapsedHeight; - bool isExpanded = false; - -public slots: - void toggle(bool collapsed); + bool expanded; -public: - // initialize section - explicit CollapsibleSection(const QString& title = "", const int animationDuration = 0, QWidget* parent = 0); + void updateToggleButton(); + void updateAnimationTargets(); + int getContentHeight() const; - // set layout of content - void setContentLayout(QLayout& contentLayout); - - // set title - void setTitle(QString title); - - // update animations and their heights - void updateHeights(); }; #endif // COLLAPSIBLESECTION_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 090fcb96..72b0b5de 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -7,6 +7,7 @@ #include "project.h" #include "map.h" #include "mapheaderform.h" +#include "lib/collapsiblesection.h" namespace Ui { class NewMapDialog; @@ -24,7 +25,7 @@ class NewMapDialog : public QDialog bool importedMap; QString layoutId; void init(); - //void initUi(); + //void initUi();//TODO void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -35,6 +36,7 @@ class NewMapDialog : public QDialog private: Ui::NewMapDialog *ui; Project *project; + CollapsibleSection *headerSection; MapHeaderForm *headerData; bool validateMapDimensions(); diff --git a/src/config.cpp b/src/config.cpp index a1987203..316c9ea3 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -371,6 +371,8 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->monitorFiles = getConfigBool(key, value); } else if (key == "tileset_checkerboard_fill") { this->tilesetCheckerboardFill = getConfigBool(key, value); + } else if (key == "new_map_header_section_expanded") { + this->newMapHeaderSectionExpanded = getConfigBool(key, value); } else if (key == "theme") { this->theme = value; } else if (key == "wild_mon_chart_theme") { @@ -453,6 +455,7 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("show_tileset_editor_layer_grid", this->showTilesetEditorLayerGrid ? "1" : "0"); map.insert("monitor_files", this->monitorFiles ? "1" : "0"); map.insert("tileset_checkerboard_fill", this->tilesetCheckerboardFill ? "1" : "0"); + map.insert("new_map_header_section_expanded", this->newMapHeaderSectionExpanded ? "1" : "0"); map.insert("theme", this->theme); map.insert("wild_mon_chart_theme", this->wildMonChartTheme); map.insert("text_editor_open_directory", this->textEditorOpenFolder); diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp index 000e822e..34dfb780 100644 --- a/src/lib/collapsiblesection.cpp +++ b/src/lib/collapsiblesection.cpp @@ -16,13 +16,15 @@ You should have received a copy of the GNU General Public License along with Elypson/qt-collapsible-section. If not, see . -*/ -#include + + PORYMAP NOTE: Modified to support having the section expanded by default, to stop the contents + squashing during the collapse animation, and to add some guard rails against crashes. +*/ #include "collapsiblesection.h" -CollapsibleSection::CollapsibleSection(const QString& title, const int animationDuration, QWidget* parent) - : QWidget(parent), animationDuration(animationDuration) +CollapsibleSection::CollapsibleSection(const QString& title, const bool expanded, const int animationDuration, QWidget* parent) + : QWidget(parent), animationDuration(animationDuration), expanded(expanded) { toggleButton = new QToolButton(this); headerLine = new QFrame(this); @@ -32,25 +34,24 @@ CollapsibleSection::CollapsibleSection(const QString& title, const int animation toggleButton->setStyleSheet("QToolButton {border: none;}"); toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - toggleButton->setArrowType(Qt::ArrowType::RightArrow); toggleButton->setText(title); toggleButton->setCheckable(true); - toggleButton->setChecked(false); + updateToggleButton(); headerLine->setFrameShape(QFrame::HLine); headerLine->setFrameShadow(QFrame::Sunken); headerLine->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); contentArea->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); - - // start out collapsed contentArea->setMaximumHeight(0); contentArea->setMinimumHeight(0); - // let the entire widget grow and shrink with its content - toggleAnimation->addAnimation(new QPropertyAnimation(this, "maximumHeight")); - toggleAnimation->addAnimation(new QPropertyAnimation(this, "minimumHeight")); - toggleAnimation->addAnimation(new QPropertyAnimation(contentArea, "maximumHeight")); + sectionAnimations.insert(new QPropertyAnimation(this, "minimumHeight")); + sectionAnimations.insert(new QPropertyAnimation(this, "maximumHeight")); + for (const auto &anim : sectionAnimations) + toggleAnimation->addAnimation(anim); + contentAnimation = new QPropertyAnimation(contentArea, "maximumHeight"); + toggleAnimation->addAnimation(contentAnimation); mainLayout->setVerticalSpacing(0); mainLayout->setContentsMargins(0, 0, 0, 0); @@ -64,22 +65,58 @@ CollapsibleSection::CollapsibleSection(const QString& title, const int animation connect(toggleButton, &QToolButton::toggled, this, &CollapsibleSection::toggle); } -void CollapsibleSection::toggle(bool expanded) +void CollapsibleSection::updateToggleButton() { - toggleButton->setArrowType(expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); - toggleAnimation->setDirection(expanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); + toggleButton->setChecked(this->expanded); + toggleButton->setArrowType(this->expanded ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); +} + +void CollapsibleSection::toggle(bool expand) +{ + if (toggleAnimation->state() != QAbstractAnimation::Stopped) + return; + if (this->expanded == expand) + return; + this->expanded = expand; + + updateToggleButton(); + + if (expand) { + // Opening animation. Set the contents to their maximum size immediately, + // and they will be revealed slowly by the section animation. + int contentHeight = getContentHeight(); + contentArea->setMinimumHeight(contentHeight); + contentArea->setMaximumHeight(contentHeight); + toggleAnimation->setDirection(QAbstractAnimation::Forward); + } else { + // Closing animation. Keep the contents at their current size, allowing + // them to be hidden slowly by the section animation, then change their size + // once the animation is complete so they aren't visible just below the title. + auto ctx = new QObject(); + connect(toggleAnimation, &QAbstractAnimation::finished, ctx, [this, ctx]() { + // This is a single-shot connection. Qt6 has built-in support for this kind of thing. + contentArea->setMinimumHeight(0); + contentArea->setMaximumHeight(0); + ctx->deleteLater(); + }); + toggleAnimation->setDirection(QAbstractAnimation::Backward); + } toggleAnimation->start(); - - this->isExpanded = expanded; } -void CollapsibleSection::setContentLayout(QLayout& contentLayout) +void CollapsibleSection::setContentLayout(QLayout* contentLayout) { + if (contentArea->layout() == contentLayout) + return; delete contentArea->layout(); - contentArea->setLayout(&contentLayout); + contentArea->setLayout(contentLayout); collapsedHeight = sizeHint().height() - contentArea->maximumHeight(); - - updateHeights(); + + int contentHeight = this->expanded ? getContentHeight() : 0; + contentArea->setMinimumHeight(contentHeight); + contentArea->setMaximumHeight(contentHeight); + + updateAnimationTargets(); } void CollapsibleSection::setTitle(QString title) @@ -87,23 +124,20 @@ void CollapsibleSection::setTitle(QString title) toggleButton->setText(std::move(title)); } -void CollapsibleSection::updateHeights() +int CollapsibleSection::getContentHeight() const { - int contentHeight = contentArea->layout()->sizeHint().height(); - - for (int i = 0; i < toggleAnimation->animationCount() - 1; ++i) - { - QPropertyAnimation* SectionAnimation = static_cast(toggleAnimation->animationAt(i)); - SectionAnimation->setDuration(animationDuration); - SectionAnimation->setStartValue(collapsedHeight); - SectionAnimation->setEndValue(collapsedHeight + contentHeight); - } + return contentArea->layout() ? contentArea->layout()->sizeHint().height() : 0; +} - QPropertyAnimation* contentAnimation = static_cast(toggleAnimation->animationAt(toggleAnimation->animationCount() - 1)); +void CollapsibleSection::updateAnimationTargets() +{ + const int contentHeight = getContentHeight(); + for (auto anim : sectionAnimations) { + anim->setDuration(animationDuration); + anim->setStartValue(collapsedHeight); + anim->setEndValue(collapsedHeight + contentHeight); + } contentAnimation->setDuration(animationDuration); contentAnimation->setStartValue(0); contentAnimation->setEndValue(contentHeight); - - toggleAnimation->setDirection(isExpanded ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); - toggleAnimation->start(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index d47dfef2..50c4b4d8 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -86,11 +86,27 @@ MainWindow::MainWindow(QWidget *parent) : MainWindow::~MainWindow() { + // Some config settings are updated as subwindows are destroyed (e.g. their geometry), + // so we need to ensure that the configs are saved after this happens. + saveGlobalConfigs(); + delete label_MapRulerStatus; delete editor; delete ui; } +void MainWindow::saveGlobalConfigs() { + porymapConfig.setMainGeometry( + this->saveGeometry(), + this->saveState(), + this->ui->splitter_map->saveState(), + this->ui->splitter_main->saveState(), + this->ui->splitter_Metatiles->saveState() + ); + porymapConfig.save(); + shortcutsConfig.save(); +} + void MainWindow::setWindowDisabled(bool disabled) { for (auto action : findChildren()) action->setDisabled(disabled); @@ -1739,12 +1755,14 @@ void MainWindow::on_action_Save_Project_triggered() { editor->saveProject(); updateWindowTitle(); updateMapList(); + saveGlobalConfigs(); } void MainWindow::on_action_Save_triggered() { editor->save(); updateWindowTitle(); updateMapList(); + saveGlobalConfigs(); } void MainWindow::duplicate() { @@ -3282,24 +3300,9 @@ bool MainWindow::closeProject() { return true; } -void MainWindow::saveGlobalConfigs() { - porymapConfig.setMainGeometry( - this->saveGeometry(), - this->saveState(), - this->ui->splitter_map->saveState(), - this->ui->splitter_main->saveState(), - this->ui->splitter_Metatiles->saveState() - ); - porymapConfig.save(); - shortcutsConfig.save(); -} - void MainWindow::on_action_Exit_triggered() { if (!closeProject()) return; - - saveGlobalConfigs(); - QApplication::quit(); } @@ -3308,8 +3311,5 @@ void MainWindow::closeEvent(QCloseEvent *event) { event->ignore(); return; } - - saveGlobalConfigs(); - QMainWindow::closeEvent(event); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d3096a86..ff4cb209 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -8,8 +8,6 @@ #include #include -// TODO: Make ui->groupBox_HeaderData collapsible - const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; struct NewMapDialog::Settings NewMapDialog::settings = {}; @@ -31,8 +29,15 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->lineEdit_Name->setValidator(validator); ui->lineEdit_ID->setValidator(validator); + // Create a collapsible section that has all the map header data. this->headerData = new MapHeaderForm(); - ui->layout_HeaderData->addWidget(this->headerData); + auto sectionLayout = new QVBoxLayout(); + sectionLayout->addWidget(this->headerData); + + this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); + this->headerSection->setContentLayout(sectionLayout); + ui->layout_HeaderData->addWidget(this->headerSection); + ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); @@ -162,6 +167,7 @@ void NewMapDialog::saveSettings() { settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(Layout *layout) { From 7d7db3857d074f59ca2531693c13cf84d2c49718 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 14:50:44 -0500 Subject: [PATCH 09/42] Clean up new map dialog redesign --- docsrc/manual/project-files.rst | 1 + include/config.h | 3 +- include/project.h | 10 ++-- src/config.cpp | 23 +++++++-- src/core/events.cpp | 32 ++++-------- src/mainwindow.cpp | 25 +++------- src/project.cpp | 88 +++++++++++++++++++-------------- src/ui/newmapdialog.cpp | 1 - 8 files changed, 93 insertions(+), 90 deletions(-) diff --git a/docsrc/manual/project-files.rst b/docsrc/manual/project-files.rst index 8e0dfa33..67bba635 100644 --- a/docsrc/manual/project-files.rst +++ b/docsrc/manual/project-files.rst @@ -92,6 +92,7 @@ In addition to these files, there are some specific symbol and macro names that ``symbol_spawn_npcs``, ``u8 sWhiteoutRespawnHealerNpcIds``, the type and table name for Heal Location ``Respawn NPC`` values ``symbol_attribute_table``, ``sMetatileAttrMasks``, optionally read to get settings on ``Tilesets`` tab ``symbol_tilesets_prefix``, ``gTileset_``, for new tileset names and to extract base tileset names + ``symbol_dynamic_map_name``, ``Dynamic``, reserved map name to display for ``define_map_dynamic`` ``define_obj_event_count``, ``OBJECT_EVENT_TEMPLATES_COUNT``, to limit total Object Events ``define_min_level``, ``MIN_LEVEL``, minimum wild encounters level ``define_max_level``, ``MAX_LEVEL``, maximum wild encounters level diff --git a/include/config.h b/include/config.h index 0dcbfe66..0655c0bd 100644 --- a/include/config.h +++ b/include/config.h @@ -187,6 +187,7 @@ enum ProjectIdentifier { symbol_spawn_npcs, symbol_attribute_table, symbol_tilesets_prefix, + symbol_dynamic_map_name, define_obj_event_count, define_min_level, define_max_level, @@ -323,7 +324,7 @@ class ProjectConfig: public KeyValueConfigBase QString getCustomFilePath(ProjectFilePath pathId); QString getCustomFilePath(const QString &pathId); QString getFilePath(ProjectFilePath pathId); - void setIdentifier(ProjectIdentifier id, const QString &text); + void setIdentifier(ProjectIdentifier id, QString text); void setIdentifier(const QString &id, const QString &text); QString getCustomIdentifier(ProjectIdentifier id); QString getCustomIdentifier(const QString &id); diff --git a/include/project.h b/include/project.h index 1632d38f..c76fcd72 100644 --- a/include/project.h +++ b/include/project.h @@ -18,10 +18,6 @@ #include #include -// TODO: Expose to config -// The displayed name of the special map value used by warps with multiple potential destinations -static QString DYNAMIC_MAP_NAME = "Dynamic"; - class Project : public QObject { Q_OBJECT @@ -80,6 +76,7 @@ class Project : public QObject QMap modifiedFileTimestamps; bool usingAsmTilesets; QSet disabledSettingsNames; + QSet topLevelMapFields; int pokemonMinLevel; int pokemonMaxLevel; int maxEncounterRate; @@ -145,7 +142,7 @@ class Project : public QObject bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; - QSet getTopLevelMapFields(); + void initTopLevelMapFields(); bool readMapJson(const QString &mapName, QJsonDocument * out); bool loadMapData(Map*); bool readMapLayouts(); @@ -219,7 +216,6 @@ class Project : public QObject QString getDefaultPrimaryTilesetLabel(); QString getDefaultSecondaryTilesetLabel(); - QString getDynamicMapDefineName(); void updateTilesetMetatileLabels(Tileset *tileset); QString buildMetatileLabelsText(const QMap defines); QString findMetatileLabelsTileset(QString label); @@ -227,6 +223,8 @@ class Project : public QObject static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + static QString getDynamicMapDefineName(); + static QString getDynamicMapName(); static int getNumTilesPrimary(); static int getNumTilesTotal(); static int getNumMetatilesPrimary(); diff --git a/src/config.cpp b/src/config.cpp index 316c9ea3..234a0a33 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -81,6 +81,7 @@ const QMap> ProjectConfig::defaultIde {ProjectIdentifier::symbol_spawn_npcs, {"symbol_spawn_npcs", "u8 sWhiteoutRespawnHealerNpcIds"}}, {ProjectIdentifier::symbol_attribute_table, {"symbol_attribute_table", "sMetatileAttrMasks"}}, {ProjectIdentifier::symbol_tilesets_prefix, {"symbol_tilesets_prefix", "gTileset_"}}, + {ProjectIdentifier::symbol_dynamic_map_name, {"symbol_dynamic_map_name", "Dynamic"}}, // Defines {ProjectIdentifier::define_obj_event_count, {"define_obj_event_count", "OBJECT_EVENT_TEMPLATES_COUNT"}}, {ProjectIdentifier::define_min_level, {"define_min_level", "MIN_LEVEL"}}, @@ -941,13 +942,25 @@ QString ProjectConfig::getFilePath(ProjectFilePath pathId) { } -void ProjectConfig::setIdentifier(ProjectIdentifier id, const QString &text) { - if (!defaultIdentifiers.contains(id)) return; - QString copy(text); - if (copy.isEmpty()) { +void ProjectConfig::setIdentifier(ProjectIdentifier id, QString text) { + if (!defaultIdentifiers.contains(id)) + return; + + if (text.isEmpty()) { this->identifiers.remove(id); } else { - this->identifiers[id] = copy; + const QString idName = defaultIdentifiers.value(id).first; + if (idName.startsWith("define_") || idName.startsWith("symbol_")) { + // Validate the input for the identifier, depending on the type. + static const QRegularExpression re("[A-Za-z_]+[\\w]*"); + auto validator = QRegularExpressionValidator(re); + int temp = 0; + if (validator.validate(text, temp) != QValidator::Acceptable) { + logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName)); + return; + } + } + this->identifiers[id] = text; } } diff --git a/src/core/events.cpp b/src/core/events.cpp index 94030d03..0673a652 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -404,17 +404,11 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { this->setGfx(ParseUtil::jsonToQString(json["graphics_id"])); this->setTargetID(ParseUtil::jsonToInt(json["target_local_id"])); - // Ensure the target map constant is valid before adding it to the events. - const QString dynamicMapConstant = project->getDynamicMapDefineName(); - QString mapConstant = ParseUtil::jsonToQString(json["target_map"]); - if (project->mapConstantsToMapNames.contains(mapConstant)) { - this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant)); - } else if (mapConstant == dynamicMapConstant) { - this->setTargetMap(DYNAMIC_MAP_NAME); - } else { - logWarn(QString("Target Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant)); - this->setTargetMap(DYNAMIC_MAP_NAME); - } + // 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"]); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Target Map constant '%1' is invalid.").arg(mapConstant)); + this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); @@ -516,17 +510,11 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { this->setElevation(ParseUtil::jsonToInt(json["elevation"])); this->setDestinationWarpID(ParseUtil::jsonToQString(json["dest_warp_id"])); - // Ensure the warp destination map constant is valid before adding it to the warps. - const QString dynamicMapConstant = project->getDynamicMapDefineName(); - QString mapConstant = ParseUtil::jsonToQString(json["dest_map"]); - if (project->mapConstantsToMapNames.contains(mapConstant)) { - this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant)); - } else if (mapConstant == dynamicMapConstant) { - this->setDestinationMap(DYNAMIC_MAP_NAME); - } else { - logWarn(QString("Destination Map constant '%1' is invalid. Using default '%2'.").arg(mapConstant).arg(dynamicMapConstant)); - this->setDestinationMap(DYNAMIC_MAP_NAME); - } + // 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"]); + if (!project->mapConstantsToMapNames.contains(mapConstant)) + logWarn(QString("Destination Map constant '%1' is invalid.").arg(mapConstant)); + this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 50c4b4d8..eaa98f11 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -850,10 +850,10 @@ bool MainWindow::userSetMap(QString map_name) { if (editor->map && editor->map->name() == map_name) return true; // Already set - if (map_name == DYNAMIC_MAP_NAME) { + if (map_name == editor->project->getDynamicMapName()) { QMessageBox msgBox(this); QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name); - msgBox.critical(nullptr, "Error Opening Map", errorMsg); + msgBox.warning(nullptr, "Cannot Open Map", errorMsg); return false; } @@ -870,14 +870,13 @@ bool MainWindow::userSetMap(QString map_name) { } bool MainWindow::setMap(QString map_name) { - if (map_name.isEmpty() || map_name == DYNAMIC_MAP_NAME) { - logInfo(QString("Cannot set map to '%1'").arg(map_name)); + if (!editor || !editor->project || map_name.isEmpty() || map_name == editor->project->getDynamicMapName()) { + logWarn(QString("Ignored setting map to '%1'").arg(map_name)); return false; } logInfo(QString("Setting map to '%1'").arg(map_name)); - - if (!editor || !editor->setMap(map_name)) { + if (!editor->setMap(map_name)) { logWarn(QString("Failed to set map to '%1'").arg(map_name)); return false; } @@ -996,12 +995,6 @@ void MainWindow::refreshMapScene() { } void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_group) { - // Ensure valid destination map name. - if (!editor->project->mapNames.contains(map_name)) { - logError(QString("Invalid map name '%1'").arg(map_name)); - return; - } - // Open the destination map. if (!userSetMap(map_name)) return; @@ -2780,15 +2773,11 @@ void MainWindow::on_pushButton_ConfigureEncountersJSON_clicked() { } void MainWindow::on_button_OpenDiveMap_clicked() { - const QString mapName = ui->comboBox_DiveMap->currentText(); - if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName); + userSetMap(ui->comboBox_DiveMap->currentText()); } void MainWindow::on_button_OpenEmergeMap_clicked() { - const QString mapName = ui->comboBox_EmergeMap->currentText(); - if (editor->project->mapNames.contains(mapName)) - userSetMap(mapName); + userSetMap(ui->comboBox_EmergeMap->currentText()); } void MainWindow::on_comboBox_DiveMap_currentTextChanged(const QString &mapName) { diff --git a/src/project.cpp b/src/project.cpp index f36f7543..03f7b4df 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -135,7 +135,7 @@ void Project::clearTilesetCache() { } Map* Project::loadMap(QString mapName) { - if (mapName == DYNAMIC_MAP_NAME) + if (mapName == getDynamicMapName()) return nullptr; Map *map; @@ -148,7 +148,6 @@ Map* Project::loadMap(QString mapName) { } else { map = new Map; map->setName(mapName); - map->setConstantName(this->mapNamesToMapConstants.value(mapName)); // TODO: How should we handle if !mapNamesToMapConstants.contains(mapName) here } if (!(loadMapData(map) && loadMapLayout(map))){ @@ -161,38 +160,36 @@ Map* Project::loadMap(QString mapName) { return map; } -const QSet defaultTopLevelMapFields = { - "id", - "name", - "layout", - "music", - "region_map_section", - "requires_flash", - "weather", - "map_type", - "show_map_name", - "battle_scene", - "connections", - "object_events", - "warp_events", - "coord_events", - "bg_events", - "shared_events_map", - "shared_scripts_map", -}; - -QSet Project::getTopLevelMapFields() { - QSet topLevelMapFields = defaultTopLevelMapFields; +void Project::initTopLevelMapFields() { + static const QSet defaultTopLevelMapFields = { + "id", + "name", + "layout", + "music", + "region_map_section", + "requires_flash", + "weather", + "map_type", + "show_map_name", + "battle_scene", + "connections", + "object_events", + "warp_events", + "coord_events", + "bg_events", + "heal_locations", + "shared_events_map", + "shared_scripts_map", + }; + this->topLevelMapFields = defaultTopLevelMapFields; if (projectConfig.mapAllowFlagsEnabled) { - topLevelMapFields.insert("allow_cycling"); - topLevelMapFields.insert("allow_escaping"); - topLevelMapFields.insert("allow_running"); + this->topLevelMapFields.insert("allow_cycling"); + this->topLevelMapFields.insert("allow_escaping"); + this->topLevelMapFields.insert("allow_running"); } - if (projectConfig.floorNumberEnabled) { - topLevelMapFields.insert("floor_number"); + this->topLevelMapFields.insert("floor_number"); } - return topLevelMapFields; } bool Project::readMapJson(const QString &mapName, QJsonDocument * out) { @@ -215,6 +212,11 @@ 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"])); + this->mapNamesToMapConstants.insert(map->name(), map->constantName()); + this->mapConstantsToMapNames.insert(map->constantName(), map->name()); + map->setSong(ParseUtil::jsonToQString(mapObj["music"])); map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); map->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); @@ -353,9 +355,8 @@ bool Project::loadMapData(Map* map) { // Check for custom fields /* // TODO: Re-enable - QSet baseFields = this->getTopLevelMapFields(); for (QString key : mapObj.keys()) { - if (!baseFields.contains(key)) { + if (!this->topLevelMapFields.contains(key)) { map->customHeaders.insert(key, mapObj[key]); } } @@ -1822,6 +1823,8 @@ bool Project::readMapGroups() { this->groupedMapNames.clear(); this->mapNames.clear(); + this->initTopLevelMapFields(); + const QString filepath = root + "/" + projectConfig.getFilePath(ProjectFilePath::json_map_groups); fileWatcher.addPath(filepath); QJsonDocument mapGroupsDoc; @@ -1832,14 +1835,20 @@ bool Project::readMapGroups() { QJsonObject mapGroupsObj = mapGroupsDoc.object(); QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); + + const QString dynamicMapName = getDynamicMapName(); + + // Process the map group lists for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); this->groupedMapNames.append(QStringList()); this->groupNames.append(groupName); + + // Process the names in this map group for (int j = 0; j < mapNamesJson.size(); j++) { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); - if (mapName == DYNAMIC_MAP_NAME) { + if (mapName == dynamicMapName) { logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); continue; } @@ -1877,7 +1886,7 @@ bool Project::readMapGroups() { this->mapGroups.insert(mapName, groupIndex); this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); - // TODO: Keep these updated + // TODO: Either verify that these are known IDs, or make sure nothing breaks when they're unknown. this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); } @@ -1892,10 +1901,11 @@ bool Project::readMapGroups() { return false; } + // Save special "Dynamic" constant const QString defineName = this->getDynamicMapDefineName(); - this->mapConstantsToMapNames.insert(defineName, DYNAMIC_MAP_NAME); - this->mapNamesToMapConstants.insert(DYNAMIC_MAP_NAME, defineName); - this->mapNames.append(DYNAMIC_MAP_NAME); + this->mapConstantsToMapNames.insert(defineName, dynamicMapName); + this->mapNamesToMapConstants.insert(dynamicMapName, defineName); + this->mapNames.append(dynamicMapName); return true; } @@ -2941,6 +2951,10 @@ QString Project::getDynamicMapDefineName() { return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); } +QString Project::getDynamicMapName() { + return projectConfig.getIdentifier(ProjectIdentifier::symbol_dynamic_map_name); +} + // 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/newmapdialog.cpp b/src/ui/newmapdialog.cpp index ff4cb209..da10c26a 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -23,7 +23,6 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : this->importedMap = false; // Map names and IDs can only contain word characters, and cannot start with a digit. - // TODO: Also validate this when we read ProjectIdentifier::define_map_prefix from the config static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); From 8bb01005408235d0a20ef896316089d8ebf77055 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 12 Nov 2024 15:57:27 -0500 Subject: [PATCH 10/42] Separate layout/header features of new map dialog --- forms/newlayoutform.ui | 236 ++++++++++++++++++++++++++ forms/newmapdialog.ui | 333 +++++++++---------------------------- include/config.h | 2 +- include/core/map.h | 41 +---- include/core/mapheader.h | 83 +++++++++ include/mainwindow.h | 2 +- include/project.h | 2 +- include/ui/mapheaderform.h | 65 +++++--- include/ui/newlayoutform.h | 46 +++++ include/ui/newmapdialog.h | 29 +--- porymap.pro | 5 + src/core/map.cpp | 48 +----- src/core/mapheader.cpp | 132 +++++++++++++++ src/mainwindow.cpp | 59 +++---- src/project.cpp | 51 +++--- src/scriptapi/apimap.cpp | 44 ++--- src/ui/mapheaderform.cpp | 328 ++++++++++++++++++++---------------- src/ui/newlayoutform.cpp | 123 ++++++++++++++ src/ui/newmapdialog.cpp | 245 ++++++++------------------- 19 files changed, 1094 insertions(+), 780 deletions(-) create mode 100644 forms/newlayoutform.ui create mode 100644 include/core/mapheader.h create mode 100644 include/ui/newlayoutform.h create mode 100644 src/core/mapheader.cpp create mode 100644 src/ui/newlayoutform.cpp diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui new file mode 100644 index 00000000..65183046 --- /dev/null +++ b/forms/newlayoutform.ui @@ -0,0 +1,236 @@ + + + NewLayoutForm + + + + 0 + 0 + 304 + 344 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Map Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> + + + 1 + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + Border Dimensions + + + + + + + 0 + 0 + + + + Width + + + + + + + <html><head/><body><p>Width (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + <html><head/><body><p>Height (in metatiles) of the new map's border.</p></body></html> + + + 1 + + + + + + + + 0 + 0 + + + + Height + + + + + + + + + + Tilesets + + + + + + Primary + + + + + + + <html><head/><body><p>The primary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + Secondary + + + + + + + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
+
+ + +
diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 3ddba1dc..911050d3 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -32,14 +32,27 @@ 10 - - + + - Name + Map ID - + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + false @@ -55,8 +68,29 @@ - - + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + false @@ -71,140 +105,24 @@
- - - - Border Dimensions + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - - - - 0 - 0 - - - - Width - - - - - - - <html><head/><body><p>Width (in metatiles) of the new map's border.</p></body></html> - - - 1 - - - - - - - <html><head/><body><p>Height (in metatiles) of the new map's border.</p></body></html> - - - 1 - - - - - - - - 0 - 0 - - - - Height - - - - - - - - - - ID + + true - - - - Map Dimensions + + + + Can Fly To - - - - - - 0 - 0 - - - - Width - - - - - - - <html><head/><body><p>Width (in metatiles) of the new map.</p></body></html> - - - 1 - - - - - - - - 0 - 0 - - - - Height - - - - - - - <html><head/><body><p>Height (in metatiles) of the new map.</p></body></html> - - - 1 - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> @@ -217,90 +135,15 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - true - - - - - - - Tilesets + + + + Map Group - - - - - Primary - - - - - - - <html><head/><body><p>The primary tileset for the new map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - Secondary - - - - - - - <html><head/><body><p>The secondary tileset for the new map.</p></body></html> - - - true - - - QComboBox::InsertPolicy::NoInsert - - - - - - - false - - - color: rgb(255, 0, 0) - - - - - - true - - - - - - + + false @@ -315,45 +158,14 @@ - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - - - + + - Group + Map Name - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -363,13 +175,23 @@ - - + + - Can Fly To + Layout ID + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> + + + + + + @@ -394,9 +216,10 @@
noscrollcombobox.h
- NoScrollSpinBox - QSpinBox -
noscrollspinbox.h
+ NewLayoutForm + QWidget +
newlayoutform.h
+ 1
diff --git a/include/config.h b/include/config.h index 0655c0bd..21e2dde7 100644 --- a/include/config.h +++ b/include/config.h @@ -70,7 +70,7 @@ class PorymapConfig: public KeyValueConfigBase this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; - this->newMapHeaderSectionExpanded = false; + this->newMapHeaderSectionExpanded = true; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; diff --git a/include/core/map.h b/include/core/map.h index 314013d7..55eee303 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -7,6 +7,7 @@ #include "maplayout.h" #include "tileset.h" #include "events.h" +#include "mapheader.h" #include #include @@ -56,30 +57,8 @@ class Map : public QObject int getBorderWidth() const; int getBorderHeight() const; - // TODO: Combine these into a separate MapHeader class? - void setSong(const QString &song); - void setLocation(const QString &location); - void setRequiresFlash(bool requiresFlash); - void setWeather(const QString &weather); - void setType(const QString &type); - void setShowsLocationName(bool showsLocationName); - void setAllowsRunning(bool allowsRunning); - void setAllowsBiking(bool allowsBiking); - void setAllowsEscaping(bool allowsEscaping); - void setFloorNumber(int floorNumber); - void setBattleScene(const QString &battleScene); - - QString song() const { return m_song; } - QString location() const { return m_location; } - bool requiresFlash() const { return m_requiresFlash; } - QString weather() const { return m_weather; } - QString type() const { return m_type; } - bool showsLocationName() const { return m_showsLocationName; } - bool allowsRunning() const { return m_allowsRunning; } - bool allowsBiking() const { return m_allowsBiking; } - bool allowsEscaping() const { return m_allowsEscaping; } - int floorNumber() const { return m_floorNumber; } - QString battleScene() const { return m_battleScene; } + void setHeader(const MapHeader &header) { *m_header = header; } + MapHeader* header() const { return m_header; } void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; } @@ -130,25 +109,13 @@ class Map : public QObject QString m_name; QString m_constantName; QString m_layoutId; // TODO: Why do we do half this->layout()->id and half this->layoutId. Should these ever be different? - - QString m_song; - QString m_location; - bool m_requiresFlash; - QString m_weather; - QString m_type; - bool m_showsLocationName; - bool m_allowsRunning; - bool m_allowsBiking; - bool m_allowsEscaping; - int m_floorNumber = 0; - QString m_battleScene; - QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; QStringList m_scriptsFileLabels; QMap m_customAttributes; + MapHeader *m_header = nullptr; Layout *m_layout = nullptr; bool m_isPersistedToFile = true; diff --git a/include/core/mapheader.h b/include/core/mapheader.h new file mode 100644 index 00000000..ee2c354e --- /dev/null +++ b/include/core/mapheader.h @@ -0,0 +1,83 @@ +#ifndef MAPHEADER_H +#define MAPHEADER_H + +#include + +class MapHeader : public QObject +{ + Q_OBJECT +public: + MapHeader(QObject *parent = nullptr) : QObject(parent) {}; + ~MapHeader() {}; + MapHeader(const MapHeader& other); + MapHeader& operator=(const MapHeader& other); + bool operator==(const MapHeader& other) const { + return m_song == other.m_song + && m_location == other.m_location + && m_requiresFlash == other.m_requiresFlash + && m_weather == other.m_weather + && m_type == other.m_type + && m_showsLocationName == other.m_showsLocationName + && m_allowsRunning == other.m_allowsRunning + && m_allowsBiking == other.m_allowsBiking + && m_allowsEscaping == other.m_allowsEscaping + && m_floorNumber == other.m_floorNumber + && m_battleScene == other.m_battleScene; + } + bool operator!=(const MapHeader& other) const { + return !(operator==(other)); + } + + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setShowsLocationName(bool showsLocationName); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + void setBattleScene(const QString &battleScene); + + QString song() const { return m_song; } + QString location() const { return m_location; } + bool requiresFlash() const { return m_requiresFlash; } + QString weather() const { return m_weather; } + QString type() const { return m_type; } + bool showsLocationName() const { return m_showsLocationName; } + bool allowsRunning() const { return m_allowsRunning; } + bool allowsBiking() const { return m_allowsBiking; } + bool allowsEscaping() const { return m_allowsEscaping; } + int floorNumber() const { return m_floorNumber; } + QString battleScene() const { return m_battleScene; } + +signals: + void songChanged(QString, QString); + void locationChanged(QString, QString); + void requiresFlashChanged(bool, bool); + void weatherChanged(QString, QString); + void typeChanged(QString, QString); + void showsLocationNameChanged(bool, bool); + void allowsRunningChanged(bool, bool); + void allowsBikingChanged(bool, bool); + void allowsEscapingChanged(bool, bool); + void floorNumberChanged(int, int); + void battleSceneChanged(QString, QString); + void modified(); + +private: + QString m_song; + QString m_location; + bool m_requiresFlash = false; + QString m_weather; + QString m_type; + bool m_showsLocationName = false; + bool m_allowsRunning = false; + bool m_allowsBiking = false; + bool m_allowsEscaping = false; + int m_floorNumber = 0; + QString m_battleScene; +}; + +#endif // MAPHEADER_H diff --git a/include/mainwindow.h b/include/mainwindow.h index ab4e0482..a506f5e0 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -327,7 +327,7 @@ private slots: QAction *copyAction = nullptr; QAction *pasteAction = nullptr; - MapHeaderForm *mapHeader = nullptr; + MapHeaderForm *mapHeaderForm = nullptr; QMap lastSelectedEvent; diff --git a/include/project.h b/include/project.h index c76fcd72..f28f9259 100644 --- a/include/project.h +++ b/include/project.h @@ -263,7 +263,7 @@ class Project : public QObject signals: void fileChanged(QString filepath); - void mapSectionIdNamesChanged(); + void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); }; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index afdee728..897b8e9a 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -1,18 +1,18 @@ #ifndef MAPHEADERFORM_H #define MAPHEADERFORM_H -#include "project.h" -#include "map.h" -#include "ui_mapheaderform.h" - -#include - /* This is the UI class used to edit the fields in a map's header. It's intended to be used anywhere the UI needs to present an editor for a map's header, e.g. for the current map in the main editor or in the new map dialog. */ +#include +#include +#include "mapheader.h" + +class Project; + namespace Ui { class MapHeaderForm; } @@ -25,32 +25,43 @@ class MapHeaderForm : public QWidget explicit MapHeaderForm(QWidget *parent = nullptr); ~MapHeaderForm(); - void setProject(Project * project); - void setMap(Map * map); - - void clearDisplay(); + void init(const Project * project); void clear(); - void refreshLocationsComboBox(); + void setHeader(MapHeader *header); + MapHeader headerData() const; - Ui::MapHeaderForm *ui; + void setLocations(QStringList locations); + void setLocationsDisabled(bool disabled); private: - QPointer map = nullptr; - QPointer project = nullptr; - -private slots: - void on_comboBox_Song_currentTextChanged(const QString &); - void on_comboBox_Location_currentTextChanged(const QString &); - void on_comboBox_Weather_currentTextChanged(const QString &); - void on_comboBox_Type_currentTextChanged(const QString &); - void on_comboBox_BattleScene_currentTextChanged(const QString &); - void on_checkBox_RequiresFlash_stateChanged(int); - void on_checkBox_ShowLocationName_stateChanged(int); - void on_checkBox_AllowRunning_stateChanged(int); - void on_checkBox_AllowBiking_stateChanged(int); - void on_checkBox_AllowEscaping_stateChanged(int); - void on_spinBox_FloorNumber_valueChanged(int); + Ui::MapHeaderForm *ui; + QPointer m_header = nullptr; + + void updateUi(); + void updateSong(); + void updateLocation(); + void updateRequiresFlash(); + void updateWeather(); + void updateType(); + void updateBattleScene(); + void updateShowsLocationName(); + void updateAllowsRunning(); + void updateAllowsBiking(); + void updateAllowsEscaping(); + void updateFloorNumber(); + + void onSongUpdated(const QString &song); + void onLocationChanged(const QString &location); + 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 onFloorNumberChanged(int offset); }; #endif // MAPHEADERFORM_H diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h new file mode 100644 index 00000000..6f63466b --- /dev/null +++ b/include/ui/newlayoutform.h @@ -0,0 +1,46 @@ +#ifndef NEWLAYOUTFORM_H +#define NEWLAYOUTFORM_H + +#include + +class Project; + +namespace Ui { +class NewLayoutForm; +} + +class NewLayoutForm : public QWidget +{ + Q_OBJECT + +public: + explicit NewLayoutForm(QWidget *parent = nullptr); + ~NewLayoutForm(); + + void initUi(Project *project); + + struct Settings { + int width; + int height; + int borderWidth; + int borderHeight; + QString primaryTilesetLabel; + QString secondaryTilesetLabel; + }; + + void setSettings(const Settings &settings); + NewLayoutForm::Settings settings() const; + + void setDisabled(bool disabled); + + bool validate(); + +private: + Ui::NewLayoutForm *ui; + Project *m_project; + + bool validateMapDimensions(); + bool validateTilesets(); +}; + +#endif // NEWLAYOUTFORM_H diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 72b0b5de..e7401242 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -7,6 +7,7 @@ #include "project.h" #include "map.h" #include "mapheaderform.h" +#include "newlayoutform.h" #include "lib/collapsiblesection.h" namespace Ui { @@ -25,7 +26,6 @@ class NewMapDialog : public QDialog bool importedMap; QString layoutId; void init(); - //void initUi();//TODO void init(int tabIndex, QString data); void init(Layout *); static void setDefaultSettings(Project *project); @@ -37,11 +37,9 @@ class NewMapDialog : public QDialog Ui::NewMapDialog *ui; Project *project; CollapsibleSection *headerSection; - MapHeaderForm *headerData; + MapHeaderForm *headerForm; - bool validateMapDimensions(); bool validateMapGroup(); - bool validateTilesets(); bool validateID(); bool validateName(); @@ -51,33 +49,18 @@ class NewMapDialog : public QDialog struct Settings { QString group; - int width; - int height; - int borderWidth; - int borderHeight; - QString primaryTilesetLabel; - QString secondaryTilesetLabel; - QString song; - QString location; - bool requiresFlash; - QString weather; - QString type; - QString battleScene; - bool showLocationName; - bool allowRunning; - bool allowBiking; - bool allowEscaping; - int floorNumber; bool canFlyTo; + NewLayoutForm::Settings layout; + MapHeader header; }; static struct Settings settings; private slots: - //void on_checkBox_UseExistingLayout_stateChanged(int state); + //void on_checkBox_UseExistingLayout_stateChanged(int state); //TODO //void on_comboBox_Layout_currentTextChanged(const QString &text); void on_pushButton_Accept_clicked(); void on_lineEdit_Name_textChanged(const QString &); - void on_lineEdit_ID_textChanged(const QString &); + void on_lineEdit_MapID_textChanged(const QString &); }; #endif // NEWMAPDIALOG_H diff --git a/porymap.pro b/porymap.pro index d3d870bc..c4db35d0 100644 --- a/porymap.pro +++ b/porymap.pro @@ -30,6 +30,7 @@ SOURCES += src/core/block.cpp \ src/core/imageexport.cpp \ src/core/map.cpp \ src/core/mapconnection.cpp \ + src/core/mapheader.cpp \ src/core/maplayout.cpp \ src/core/mapparser.cpp \ src/core/metatile.cpp \ @@ -89,6 +90,7 @@ SOURCES += src/core/block.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ src/ui/neweventtoolbutton.cpp \ + src/ui/newlayoutform.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -134,6 +136,7 @@ HEADERS += include/core/block.h \ include/core/imageexport.h \ include/core/map.h \ include/core/mapconnection.h \ + include/core/mapheader.h \ include/core/maplayout.h \ include/core/mapparser.h \ include/core/metatile.h \ @@ -193,6 +196,7 @@ HEADERS += include/core/block.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ include/ui/neweventtoolbutton.h \ + include/ui/newlayoutform.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ include/ui/montabwidget.h \ @@ -237,6 +241,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newlayoutform.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ diff --git a/src/core/map.cpp b/src/core/map.cpp index ab31ac86..1d7c4555 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -16,6 +16,9 @@ Map::Map(QObject *parent) : QObject(parent) m_scriptsLoaded = false; m_editHistory = new QUndoStack(this); resetEvents(); + + m_header = new MapHeader(this); + connect(m_header, &MapHeader::modified, this, &Map::modified); } Map::~Map() { @@ -309,48 +312,3 @@ void Map::pruneEditHistory() { command->setObsolete(true); } } - -void Map::setSong(const QString &song) { - m_song = song; -} - -void Map::setLocation(const QString &location) { - m_location = location; -} - -void Map::setRequiresFlash(bool requiresFlash) { - m_requiresFlash = requiresFlash; -} - -void Map::setWeather(const QString &weather) { - m_weather = weather; -} - -void Map::setType(const QString &type) { - m_type = type; -} - -void Map::setShowsLocationName(bool showsLocationName) { - m_showsLocationName = showsLocationName; -} - -void Map::setAllowsRunning(bool allowsRunning) { - m_allowsRunning = allowsRunning; -} - -void Map::setAllowsBiking(bool allowsBiking) { - m_allowsBiking = allowsBiking; -} - -void Map::setAllowsEscaping(bool allowsEscaping) { - m_allowsEscaping = allowsEscaping; -} - -void Map::setFloorNumber(int floorNumber) { - m_floorNumber = floorNumber; -} - -void Map::setBattleScene(const QString &battleScene) { - m_battleScene = battleScene; -} - diff --git a/src/core/mapheader.cpp b/src/core/mapheader.cpp new file mode 100644 index 00000000..4c0bc06e --- /dev/null +++ b/src/core/mapheader.cpp @@ -0,0 +1,132 @@ +#include "mapheader.h" + +MapHeader::MapHeader(const MapHeader& other) : MapHeader() { + m_song = other.m_song; + m_location = other.m_location; + m_requiresFlash = other.m_requiresFlash; + m_weather = other.m_weather; + m_type = other.m_type; + m_showsLocationName = other.m_showsLocationName; + m_allowsRunning = other.m_allowsRunning; + m_allowsBiking = other.m_allowsBiking; + m_allowsEscaping = other.m_allowsEscaping; + m_floorNumber = other.m_floorNumber; + m_battleScene = other.m_battleScene; +} + +MapHeader &MapHeader::operator=(const MapHeader &other) { + // We want to call each set function here to ensure any fieldChanged signals + // are sent as necessary. This does also mean the modified signal can be sent + // repeatedly (but for now at least that's not a big issue). + setSong(other.m_song); + setLocation(other.m_location); + setRequiresFlash(other.m_requiresFlash); + setWeather(other.m_weather); + setType(other.m_type); + setShowsLocationName(other.m_showsLocationName); + setAllowsRunning(other.m_allowsRunning); + setAllowsBiking(other.m_allowsBiking); + setAllowsEscaping(other.m_allowsEscaping); + setFloorNumber(other.m_floorNumber); + setBattleScene(other.m_battleScene); + return *this; +} + +void MapHeader::setSong(const QString &song) { + if (m_song == song) + return; + auto before = m_song; + m_song = song; + emit songChanged(before, m_song); + emit modified(); +} + +void MapHeader::setLocation(const QString &location) { + if (m_location == location) + return; + auto before = m_location; + m_location = location; + emit locationChanged(before, m_location); + emit modified(); +} + +void MapHeader::setRequiresFlash(bool requiresFlash) { + if (m_requiresFlash == requiresFlash) + return; + auto before = m_requiresFlash; + m_requiresFlash = requiresFlash; + emit requiresFlashChanged(before, m_requiresFlash); + emit modified(); +} + +void MapHeader::setWeather(const QString &weather) { + if (m_weather == weather) + return; + auto before = m_weather; + m_weather = weather; + emit weatherChanged(before, m_weather); + emit modified(); +} + +void MapHeader::setType(const QString &type) { + if (m_type == type) + return; + auto before = m_type; + m_type = type; + emit typeChanged(before, m_type); + emit modified(); +} + +void MapHeader::setShowsLocationName(bool showsLocationName) { + if (m_showsLocationName == showsLocationName) + return; + auto before = m_showsLocationName; + m_showsLocationName = showsLocationName; + emit showsLocationNameChanged(before, m_showsLocationName); + emit modified(); +} + +void MapHeader::setAllowsRunning(bool allowsRunning) { + if (m_allowsRunning == allowsRunning) + return; + auto before = m_allowsRunning; + m_allowsRunning = allowsRunning; + emit allowsRunningChanged(before, m_allowsRunning); + emit modified(); +} + +void MapHeader::setAllowsBiking(bool allowsBiking) { + if (m_allowsBiking == allowsBiking) + return; + auto before = m_allowsBiking; + m_allowsBiking = allowsBiking; + emit allowsBikingChanged(before, m_allowsBiking); + emit modified(); +} + +void MapHeader::setAllowsEscaping(bool allowsEscaping) { + if (m_allowsEscaping == allowsEscaping) + return; + auto before = m_allowsEscaping; + m_allowsEscaping = allowsEscaping; + emit allowsEscapingChanged(before, m_allowsEscaping); + emit modified(); +} + +void MapHeader::setFloorNumber(int floorNumber) { + if (m_floorNumber == floorNumber) + return; + auto before = m_floorNumber; + m_floorNumber = floorNumber; + emit floorNumberChanged(before, m_floorNumber); + emit modified(); +} + +void MapHeader::setBattleScene(const QString &battleScene) { + if (m_battleScene == battleScene) + return; + auto before = m_battleScene; + m_battleScene = battleScene; + emit battleSceneChanged(before, m_battleScene); + emit modified(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index eaa98f11..f4c96393 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -253,8 +253,8 @@ void MainWindow::initCustomUI() { } // Create map header data widget - this->mapHeader = new MapHeaderForm(); - ui->layout_HeaderData->addWidget(this->mapHeader); + this->mapHeaderForm = new MapHeaderForm(); + ui->layout_HeaderData->addWidget(this->mapHeaderForm); } void MainWindow::initExtraSignals() { @@ -617,7 +617,7 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - connect(project, &Project::mapSectionIdNamesChanged, this->mapHeader, &MapHeaderForm::refreshLocationsComboBox); + connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -1017,23 +1017,19 @@ void MainWindow::openWarpMap(QString map_name, int event_id, Event::Group event_ } void MainWindow::displayMapProperties() { - // Block signals to the comboboxes while they are being modified - const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - - this->mapHeader->clearDisplay(); + this->mapHeaderForm->clear(); if (!editor || !editor->map || !editor->project) { ui->frame_HeaderData->setEnabled(false); return; } - ui->frame_HeaderData->setEnabled(true); - Map *map = editor->map; + this->mapHeaderForm->setHeader(editor->map->header()); - ui->comboBox_PrimaryTileset->setCurrentText(map->layout()->tileset_primary_label); - ui->comboBox_SecondaryTileset->setCurrentText(map->layout()->tileset_secondary_label); + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); + ui->comboBox_PrimaryTileset->setCurrentText(editor->map->layout()->tileset_primary_label); + ui->comboBox_SecondaryTileset->setCurrentText(editor->map->layout()->tileset_secondary_label); - this->mapHeader->setMap(map); // Custom fields table. /* // TODO: Re-enable @@ -1060,26 +1056,28 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te bool MainWindow::setProjectUI() { Project *project = editor->project; - this->mapHeader->setProject(project); - - // Block signals to the comboboxes while they are being modified - const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); - const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); + this->mapHeaderForm->init(project); // Set up project comboboxes + const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); ui->comboBox_PrimaryTileset->clear(); ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); + + const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); ui->comboBox_SecondaryTileset->clear(); ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); + + const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); ui->comboBox_LayoutSelector->clear(); ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); + + const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); ui->comboBox_DiveMap->addItems(project->mapNames); ui->comboBox_DiveMap->setClearButtonEnabled(true); ui->comboBox_DiveMap->setFocusedScrollingEnabled(false); + + const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); ui->comboBox_EmergeMap->clear(); ui->comboBox_EmergeMap->addItems(project->mapNames); ui->comboBox_EmergeMap->setClearButtonEnabled(true); @@ -1122,20 +1120,23 @@ bool MainWindow::setProjectUI() { } void MainWindow::clearProjectUI() { - // Block signals to the comboboxes while they are being modified + // Clear project comboboxes const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); - const QSignalBlocker b_SecondaryTileset(ui->comboBox_SecondaryTileset); - const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); - const QSignalBlocker b_EmergeMap(ui->comboBox_EmergeMap); - const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); - ui->comboBox_PrimaryTileset->clear(); + + 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); ui->comboBox_LayoutSelector->clear(); - this->mapHeader->clear(); + this->mapHeaderForm->clear(); // Clear map models delete this->mapGroupModel; @@ -1466,7 +1467,7 @@ void MainWindow::onNewMapCreated() { // Add new Map / Layout to the mapList models this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); - this->mapAreaModel->insertMapItem(newMapName, newMap->location(), newMapGroup); + this->mapAreaModel->insertMapItem(newMapName, newMap->header()->location(), newMapGroup); this->layoutTreeModel->insertMapItem(newMapName, newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps diff --git a/src/project.cpp b/src/project.cpp index 03f7b4df..f168099e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -155,6 +155,9 @@ Map* Project::loadMap(QString mapName) { return nullptr; } + // If the map's MAPSEC value in the header changes, update our global array to keep it in sync. + connect(map->header(), &MapHeader::locationChanged, [this, map] { this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); }); + mapCache.insert(mapName, map); emit mapLoaded(map); return map; @@ -217,22 +220,22 @@ bool Project::loadMapData(Map* map) { this->mapNamesToMapConstants.insert(map->name(), map->constantName()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); - map->setSong(ParseUtil::jsonToQString(mapObj["music"])); + map->header()->setSong(ParseUtil::jsonToQString(mapObj["music"])); map->setLayoutId(ParseUtil::jsonToQString(mapObj["layout"])); - map->setLocation(ParseUtil::jsonToQString(mapObj["region_map_section"])); - map->setRequiresFlash(ParseUtil::jsonToBool(mapObj["requires_flash"])); - map->setWeather(ParseUtil::jsonToQString(mapObj["weather"])); - map->setType(ParseUtil::jsonToQString(mapObj["map_type"])); - map->setShowsLocationName(ParseUtil::jsonToBool(mapObj["show_map_name"])); - map->setBattleScene(ParseUtil::jsonToQString(mapObj["battle_scene"])); + 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"])); if (projectConfig.mapAllowFlagsEnabled) { - map->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); - map->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); - map->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); + map->header()->setAllowsBiking(ParseUtil::jsonToBool(mapObj["allow_cycling"])); + map->header()->setAllowsEscaping(ParseUtil::jsonToBool(mapObj["allow_escaping"])); + map->header()->setAllowsRunning(ParseUtil::jsonToBool(mapObj["allow_running"])); } if (projectConfig.floorNumberEnabled) { - map->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); + map->header()->setFloorNumber(ParseUtil::jsonToInt(mapObj["floor_number"])); } map->setSharedEventsMap(ParseUtil::jsonToQString(mapObj["shared_events_map"])); map->setSharedScriptsMap(ParseUtil::jsonToQString(mapObj["shared_scripts_map"])); @@ -1284,21 +1287,21 @@ void Project::saveMap(Map *map) { mapObj["id"] = map->constantName(); mapObj["name"] = map->name(); mapObj["layout"] = map->layout()->id; - mapObj["music"] = map->song(); - mapObj["region_map_section"] = map->location(); - mapObj["requires_flash"] = map->requiresFlash(); - mapObj["weather"] = map->weather(); - mapObj["map_type"] = map->type(); + mapObj["music"] = map->header()->song(); + mapObj["region_map_section"] = map->header()->location(); + mapObj["requires_flash"] = map->header()->requiresFlash(); + mapObj["weather"] = map->header()->weather(); + mapObj["map_type"] = map->header()->type(); if (projectConfig.mapAllowFlagsEnabled) { - mapObj["allow_cycling"] = map->allowsBiking(); - mapObj["allow_escaping"] = map->allowsEscaping(); - mapObj["allow_running"] = map->allowsRunning(); + mapObj["allow_cycling"] = map->header()->allowsBiking(); + mapObj["allow_escaping"] = map->header()->allowsEscaping(); + mapObj["allow_running"] = map->header()->allowsRunning(); } - mapObj["show_map_name"] = map->showsLocationName(); + mapObj["show_map_name"] = map->header()->showsLocationName(); if (projectConfig.floorNumberEnabled) { - mapObj["floor_number"] = map->floorNumber(); + mapObj["floor_number"] = map->header()->floorNumber(); } - mapObj["battle_scene"] = map->battleScene(); + mapObj["battle_scene"] = map->header()->battleScene(); // Connections auto connections = map->getConnections(); @@ -2301,7 +2304,7 @@ void Project::addNewMapsec(const QString &name) { this->mapSectionIdNames.append(name); } this->hasUnsavedDataChanges = true; - emit mapSectionIdNamesChanged(); + emit mapSectionIdNamesChanged(this->mapSectionIdNames); } void Project::removeMapsec(const QString &name) { @@ -2310,7 +2313,7 @@ void Project::removeMapsec(const QString &name) { this->mapSectionIdNames.removeOne(name); this->hasUnsavedDataChanges = true; - emit mapSectionIdNamesChanged(); + emit mapSectionIdNamesChanged(this->mapSectionIdNames); } // Read the constants to preserve any "unused" heal locations when writing the file later diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 5a0fa9ce..f041b7b1 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -817,7 +817,7 @@ QJSValue MainWindow::getTilePixels(int tileId) { QString MainWindow::getSong() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->song(); + return this->editor->map->header()->song(); } void MainWindow::setSong(QString song) { @@ -827,13 +827,13 @@ void MainWindow::setSong(QString song) { logError(QString("Unknown song '%1'").arg(song)); return; } - this->editor->map->setSong(song); + this->editor->map->header()->setSong(song); } QString MainWindow::getLocation() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->location(); + return this->editor->map->header()->location(); } void MainWindow::setLocation(QString location) { @@ -843,25 +843,25 @@ void MainWindow::setLocation(QString location) { logError(QString("Unknown location '%1'").arg(location)); return; } - this->editor->map->setLocation(location); + this->editor->map->header()->setLocation(location); } bool MainWindow::getRequiresFlash() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->requiresFlash(); + return this->editor->map->header()->requiresFlash(); } void MainWindow::setRequiresFlash(bool require) { if (!this->editor || !this->editor->map) return; - this->editor->map->setRequiresFlash(require); + this->editor->map->header()->setRequiresFlash(require); } QString MainWindow::getWeather() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->weather(); + return this->editor->map->header()->weather(); } void MainWindow::setWeather(QString weather) { @@ -871,13 +871,13 @@ void MainWindow::setWeather(QString weather) { logError(QString("Unknown weather '%1'").arg(weather)); return; } - this->editor->map->setWeather(weather); + this->editor->map->header()->setWeather(weather); } QString MainWindow::getType() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->type(); + return this->editor->map->header()->type(); } void MainWindow::setType(QString type) { @@ -887,13 +887,13 @@ void MainWindow::setType(QString type) { logError(QString("Unknown map type '%1'").arg(type)); return; } - this->editor->map->setType(type); + this->editor->map->header()->setType(type); } QString MainWindow::getBattleScene() { if (!this->editor || !this->editor->map) return QString(); - return this->editor->map->battleScene(); + return this->editor->map->header()->battleScene(); } void MainWindow::setBattleScene(QString battleScene) { @@ -903,66 +903,66 @@ void MainWindow::setBattleScene(QString battleScene) { logError(QString("Unknown battle scene '%1'").arg(battleScene)); return; } - this->editor->map->setBattleScene(battleScene); + this->editor->map->header()->setBattleScene(battleScene); } bool MainWindow::getShowLocationName() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->showsLocationName(); + return this->editor->map->header()->showsLocationName(); } void MainWindow::setShowLocationName(bool show) { if (!this->editor || !this->editor->map) return; - this->editor->map->setShowsLocationName(show); + this->editor->map->header()->setShowsLocationName(show); } bool MainWindow::getAllowRunning() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowsRunning(); + return this->editor->map->header()->allowsRunning(); } void MainWindow::setAllowRunning(bool allow) { if (!this->editor || !this->editor->map) return; - this->editor->map->setAllowsRunning(allow); + this->editor->map->header()->setAllowsRunning(allow); } bool MainWindow::getAllowBiking() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowsBiking(); + return this->editor->map->header()->allowsBiking(); } void MainWindow::setAllowBiking(bool allow) { if (!this->editor || !this->editor->map) return; - this->editor->map->setAllowsBiking(allow); + this->editor->map->header()->setAllowsBiking(allow); } bool MainWindow::getAllowEscaping() { if (!this->editor || !this->editor->map) return false; - return this->editor->map->allowsEscaping(); + return this->editor->map->header()->allowsEscaping(); } void MainWindow::setAllowEscaping(bool allow) { if (!this->editor || !this->editor->map) return; - this->editor->map->setAllowsEscaping(allow); + this->editor->map->header()->setAllowsEscaping(allow); } int MainWindow::getFloorNumber() { if (!this->editor || !this->editor->map) return 0; - return this->editor->map->floorNumber(); + return this->editor->map->header()->floorNumber(); } void MainWindow::setFloorNumber(int floorNumber) { if (!this->editor || !this->editor->map) return; - this->editor->map->setFloorNumber(floorNumber); + this->editor->map->header()->setFloorNumber(floorNumber); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 88d7fbf1..1fd9084c 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -1,18 +1,6 @@ #include "mapheaderform.h" - -#define BLOCK_SIGNALS \ - const QSignalBlocker b_Song(ui->comboBox_Song); \ - const QSignalBlocker b_Location(ui->comboBox_Location); \ - const QSignalBlocker b_RequiresFlash(ui->checkBox_RequiresFlash); \ - const QSignalBlocker b_Weather(ui->comboBox_Weather); \ - const QSignalBlocker b_Type(ui->comboBox_Type); \ - const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); \ - const QSignalBlocker b_ShowLocationName(ui->checkBox_ShowLocationName); \ - const QSignalBlocker b_AllowRunning(ui->checkBox_AllowRunning); \ - const QSignalBlocker b_AllowBiking(ui->checkBox_AllowBiking); \ - const QSignalBlocker b_AllowEscaping(ui->checkBox_AllowEscaping); \ - const QSignalBlocker b_FloorNumber(ui->spinBox_FloorNumber); - +#include "ui_mapheaderform.h" +#include "project.h" MapHeaderForm::MapHeaderForm(QWidget *parent) : QWidget(parent) @@ -23,6 +11,19 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) // This value is an s8 by default, but we don't need to unnecessarily limit users. ui->spinBox_FloorNumber->setMinimum(INT_MIN); ui->spinBox_FloorNumber->setMaximum(INT_MAX); + + // When the UI is updated, sync those changes to the tracked MapHeader (if there is one) + connect(ui->comboBox_Song, &QComboBox::currentTextChanged, this, &MapHeaderForm::onSongUpdated); + connect(ui->comboBox_Location, &QComboBox::currentTextChanged, this, &MapHeaderForm::onLocationChanged); + 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); + 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); + connect(ui->spinBox_FloorNumber, &QSpinBox::valueChanged, this, &MapHeaderForm::onFloorNumberChanged); } MapHeaderForm::~MapHeaderForm() @@ -30,20 +31,31 @@ MapHeaderForm::~MapHeaderForm() delete ui; } -void MapHeaderForm::setProject(Project * newProject) { +void MapHeaderForm::init(const Project * project) { clear(); - this->project = newProject; - if (!this->project) + if (!project) return; // Populate combo boxes - BLOCK_SIGNALS - ui->comboBox_Song->addItems(this->project->songNames); - ui->comboBox_Weather->addItems(this->project->weatherNames); - ui->comboBox_Type->addItems(this->project->mapTypes); - ui->comboBox_BattleScene->addItems(this->project->mapBattleScenes); - refreshLocationsComboBox(); + + const QSignalBlocker b_Song(ui->comboBox_Song); + ui->comboBox_Song->clear(); + ui->comboBox_Song->addItems(project->songNames); + + const QSignalBlocker b_Weather(ui->comboBox_Weather); + ui->comboBox_Weather->clear(); + ui->comboBox_Weather->addItems(project->weatherNames); + + const QSignalBlocker b_Type(ui->comboBox_Type); + ui->comboBox_Type->clear(); + ui->comboBox_Type->addItems(project->mapTypes); + + const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); + ui->comboBox_BattleScene->clear(); + ui->comboBox_BattleScene->addItems(project->mapBattleScenes); + + setLocations(project->mapSectionIdNames); // Hide config-specific settings @@ -60,161 +72,199 @@ void MapHeaderForm::setProject(Project * newProject) { ui->label_FloorNumber->setVisible(floorNumEnabled); } -void MapHeaderForm::setMap(Map * newMap) { - this->map = newMap; - if (!this->map) { - clearDisplay(); +// This combo box is treated specially because (unlike the other combo boxes) +// items that should be in this drop-down can be added or removed externally. +void MapHeaderForm::setLocations(QStringList locations) { + locations.sort(); + + const QSignalBlocker b(ui->comboBox_Location); + const QString before = ui->comboBox_Location->currentText(); + ui->comboBox_Location->clear(); + ui->comboBox_Location->addItems(locations); + ui->comboBox_Location->setCurrentText(before); +} + +// Assign a MapHeader that the form will keep in sync with the UI. +void MapHeaderForm::setHeader(MapHeader *header) { + if (m_header == header) return; + + if (m_header) { + m_header->disconnect(this); } - BLOCK_SIGNALS - ui->comboBox_Song->setCurrentText(this->map->song()); - ui->comboBox_Location->setCurrentText(this->map->location()); - ui->checkBox_RequiresFlash->setChecked(this->map->requiresFlash()); - ui->comboBox_Weather->setCurrentText(this->map->weather()); - ui->comboBox_Type->setCurrentText(this->map->type()); - ui->comboBox_BattleScene->setCurrentText(this->map->battleScene()); - ui->checkBox_ShowLocationName->setChecked(this->map->showsLocationName()); - ui->checkBox_AllowRunning->setChecked(this->map->allowsRunning()); - ui->checkBox_AllowBiking->setChecked(this->map->allowsBiking()); - ui->checkBox_AllowEscaping->setChecked(this->map->allowsEscaping()); - ui->spinBox_FloorNumber->setValue(this->map->floorNumber()); -} - -void MapHeaderForm::clearDisplay() { - BLOCK_SIGNALS - ui->comboBox_Song->clearEditText(); - ui->comboBox_Location->clearEditText(); - ui->comboBox_Weather->clearEditText(); - ui->comboBox_Type->clearEditText(); - ui->comboBox_BattleScene->clearEditText(); - ui->checkBox_ShowLocationName->setChecked(false); - ui->checkBox_RequiresFlash->setChecked(false); - ui->checkBox_AllowRunning->setChecked(false); - ui->checkBox_AllowBiking->setChecked(false); - ui->checkBox_AllowEscaping->setChecked(false); - ui->spinBox_FloorNumber->setValue(0); -} - -// Clear display and depopulate combo boxes + m_header = header; + + if (m_header) { + // If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly + connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::updateSong); + connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::updateLocation); + connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::updateRequiresFlash); + connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::updateWeather); + connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::updateType); + connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::updateBattleScene); + connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::updateShowsLocationName); + connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::updateAllowsRunning); + connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::updateAllowsBiking); + connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::updateAllowsEscaping); + connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::updateFloorNumber); + } + + // Immediately update the UI to reflect the assigned MapHeader + updateUi(); +} + void MapHeaderForm::clear() { - BLOCK_SIGNALS - ui->comboBox_Song->clear(); - ui->comboBox_Location->clear(); - ui->comboBox_Weather->clear(); - ui->comboBox_Type->clear(); - ui->comboBox_BattleScene->clear(); - ui->checkBox_ShowLocationName->setChecked(false); - ui->checkBox_RequiresFlash->setChecked(false); - ui->checkBox_AllowRunning->setChecked(false); - ui->checkBox_AllowBiking->setChecked(false); - ui->checkBox_AllowEscaping->setChecked(false); - ui->spinBox_FloorNumber->setValue(0); + m_header = nullptr; + updateUi(); +} + +void MapHeaderForm::updateUi() { + updateSong(); + updateLocation(); + updateRequiresFlash(); + updateWeather(); + updateType(); + updateBattleScene(); + updateShowsLocationName(); + updateAllowsRunning(); + updateAllowsBiking(); + updateAllowsEscaping(); + updateFloorNumber(); + +} + +MapHeader MapHeaderForm::headerData() const { + if (m_header) + return *m_header; + + // Build header from UI + MapHeader header; + header.setSong(ui->comboBox_Song->currentText()); + header.setLocation(ui->comboBox_Location->currentText()); + header.setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); + header.setWeather(ui->comboBox_Weather->currentText()); + header.setType(ui->comboBox_Type->currentText()); + header.setBattleScene(ui->comboBox_BattleScene->currentText()); + header.setShowsLocationName(ui->checkBox_ShowLocationName->isChecked()); + header.setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); + header.setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); + header.setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); + header.setFloorNumber(ui->spinBox_FloorNumber->value()); + return header; } -void MapHeaderForm::refreshLocationsComboBox() { +void MapHeaderForm::setLocationsDisabled(bool disabled) { + ui->label_Location->setDisabled(disabled); + ui->comboBox_Location->setDisabled(disabled); +} + +void MapHeaderForm::updateSong() { + const QSignalBlocker b(ui->comboBox_Song); + ui->comboBox_Song->setCurrentText(m_header ? m_header->song() : QString()); +} + +void MapHeaderForm::updateLocation() { const QSignalBlocker b(ui->comboBox_Location); - ui->comboBox_Location->clear(); + ui->comboBox_Location->setCurrentText(m_header ? m_header->location() : QString()); +} - if (this->project) { - QStringList locations = this->project->mapSectionIdNames; - locations.sort(); - ui->comboBox_Location->addItems(locations); - } - if (this->map) { - ui->comboBox_Location->setCurrentText(this->map->location()); - } +void MapHeaderForm::updateRequiresFlash() { + const QSignalBlocker b(ui->checkBox_RequiresFlash); + ui->checkBox_RequiresFlash->setChecked(m_header ? m_header->requiresFlash() : false); +} + +void MapHeaderForm::updateWeather() { + const QSignalBlocker b(ui->comboBox_Weather); + ui->comboBox_Weather->setCurrentText(m_header ? m_header->weather() : QString()); +} + +void MapHeaderForm::updateType() { + const QSignalBlocker b(ui->comboBox_Type); + ui->comboBox_Type->setCurrentText(m_header ? m_header->type() : QString()); +} + +void MapHeaderForm::updateBattleScene() { + const QSignalBlocker b(ui->comboBox_BattleScene); + ui->comboBox_BattleScene->setCurrentText(m_header ? m_header->battleScene() : QString()); +} + +void MapHeaderForm::updateShowsLocationName() { + const QSignalBlocker b(ui->checkBox_ShowLocationName); + ui->checkBox_ShowLocationName->setChecked(m_header ? m_header->showsLocationName() : false); +} + +void MapHeaderForm::updateAllowsRunning() { + const QSignalBlocker b(ui->checkBox_AllowRunning); + ui->checkBox_AllowRunning->setChecked(m_header ? m_header->allowsRunning() : false); } -void MapHeaderForm::on_comboBox_Song_currentTextChanged(const QString &song) +void MapHeaderForm::updateAllowsBiking() { + const QSignalBlocker b(ui->checkBox_AllowBiking); + ui->checkBox_AllowBiking->setChecked(m_header ? m_header->allowsBiking() : false); +} + +void MapHeaderForm::updateAllowsEscaping() { + const QSignalBlocker b(ui->checkBox_AllowEscaping); + ui->checkBox_AllowEscaping->setChecked(m_header ? m_header->allowsEscaping() : false); +} + +void MapHeaderForm::updateFloorNumber() { + const QSignalBlocker b(ui->spinBox_FloorNumber); + ui->spinBox_FloorNumber->setValue(m_header ? m_header->floorNumber() : 0); +} + +void MapHeaderForm::onSongUpdated(const QString &song) { - if (this->map) { - this->map->setSong(song); - this->map->modify(); - } + if (m_header) m_header->setSong(song); } -void MapHeaderForm::on_comboBox_Location_currentTextChanged(const QString &location) +void MapHeaderForm::onLocationChanged(const QString &location) { - if (this->map) { - this->map->setLocation(location); - this->map->modify(); - - // Update cached location name in the project - // TODO: This should be handled elsewhere now, connected to the map change signal - if (this->project) - this->project->mapNameToMapSectionName.insert(this->map->name(), this->map->location()); - } + if (m_header) m_header->setLocation(location); } -void MapHeaderForm::on_comboBox_Weather_currentTextChanged(const QString &weather) +void MapHeaderForm::onWeatherChanged(const QString &weather) { - if (this->map) { - this->map->setWeather(weather); - this->map->modify(); - } + if (m_header) m_header->setWeather(weather); } -void MapHeaderForm::on_comboBox_Type_currentTextChanged(const QString &type) +void MapHeaderForm::onTypeChanged(const QString &type) { - if (this->map) { - this->map->setType(type); - this->map->modify(); - } + if (m_header) m_header->setType(type); } -void MapHeaderForm::on_comboBox_BattleScene_currentTextChanged(const QString &battleScene) +void MapHeaderForm::onBattleSceneChanged(const QString &battleScene) { - if (this->map) { - this->map->setBattleScene(battleScene); - this->map->modify(); - } + if (m_header) m_header->setBattleScene(battleScene); } -void MapHeaderForm::on_checkBox_RequiresFlash_stateChanged(int selected) +void MapHeaderForm::onRequiresFlashChanged(int selected) { - if (this->map) { - this->map->setRequiresFlash(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setRequiresFlash(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_ShowLocationName_stateChanged(int selected) +void MapHeaderForm::onShowLocationNameChanged(int selected) { - if (this->map) { - this->map->setShowsLocationName(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setShowsLocationName(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_AllowRunning_stateChanged(int selected) +void MapHeaderForm::onAllowRunningChanged(int selected) { - if (this->map) { - this->map->setAllowsRunning(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setAllowsRunning(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_AllowBiking_stateChanged(int selected) +void MapHeaderForm::onAllowBikingChanged(int selected) { - if (this->map) { - this->map->setAllowsBiking(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setAllowsBiking(selected == Qt::Checked); } -void MapHeaderForm::on_checkBox_AllowEscaping_stateChanged(int selected) +void MapHeaderForm::onAllowEscapingChanged(int selected) { - if (this->map) { - this->map->setAllowsEscaping(selected == Qt::Checked); - this->map->modify(); - } + if (m_header) m_header->setAllowsEscaping(selected == Qt::Checked); } -void MapHeaderForm::on_spinBox_FloorNumber_valueChanged(int offset) +void MapHeaderForm::onFloorNumberChanged(int offset) { - if (this->map) { - this->map->setFloorNumber(offset); - this->map->modify(); - } + if (m_header) m_header->setFloorNumber(offset); } diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp new file mode 100644 index 00000000..ed217fcb --- /dev/null +++ b/src/ui/newlayoutform.cpp @@ -0,0 +1,123 @@ +#include "newlayoutform.h" +#include "ui_newlayoutform.h" +#include "project.h" + +NewLayoutForm::NewLayoutForm(QWidget *parent) + : QWidget(parent) + , ui(new Ui::NewLayoutForm) +{ + ui->setupUi(this); + + // TODO: Read from project? + ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); + ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); + + connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); + connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); +} + +NewLayoutForm::~NewLayoutForm() +{ + delete ui; +} + +void NewLayoutForm::initUi(Project *project) { + m_project = project; + + ui->comboBox_PrimaryTileset->clear(); + ui->comboBox_SecondaryTileset->clear(); + + if (m_project) { + ui->comboBox_PrimaryTileset->addItems(m_project->primaryTilesetLabels); + ui->comboBox_SecondaryTileset->addItems(m_project->secondaryTilesetLabels); + + ui->spinBox_MapWidth->setMaximum(m_project->getMaxMapWidth()); + ui->spinBox_MapHeight->setMaximum(m_project->getMaxMapHeight()); + } + + ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); +} + +void NewLayoutForm::setDisabled(bool disabled) { + ui->groupBox_MapDimensions->setDisabled(disabled); + ui->groupBox_BorderDimensions->setDisabled(disabled); + ui->groupBox_Tilesets->setDisabled(disabled); +} + +void NewLayoutForm::setSettings(const Settings &settings) { + ui->spinBox_MapWidth->setValue(settings.width); + ui->spinBox_MapHeight->setValue(settings.height); + ui->spinBox_BorderWidth->setValue(settings.borderWidth); + ui->spinBox_BorderHeight->setValue(settings.borderHeight); + ui->comboBox_PrimaryTileset->setTextItem(settings.primaryTilesetLabel); + ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); +} + +NewLayoutForm::Settings NewLayoutForm::settings() const { + NewLayoutForm::Settings settings; + settings.width = ui->spinBox_MapWidth->value(); + settings.height = ui->spinBox_MapHeight->value(); + settings.borderWidth = ui->spinBox_BorderWidth->value(); + settings.borderHeight = ui->spinBox_BorderHeight->value(); + settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); + settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); + return settings; +} + +bool NewLayoutForm::validate() { + // Make sure to call each validation function so that all errors are shown at once. + bool valid = true; + if (!validateMapDimensions()) valid = false; + if (!validateTilesets()) valid = false; + return valid; +} + +bool NewLayoutForm::validateMapDimensions() { + int size = m_project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); + int maxSize = m_project->getMaxMapDataSize(); + + QString errorText; + if (size > maxSize) { + errorText = QString("The specified width and height are too large.\n" + "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" + "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") + .arg(maxSize) + .arg(ui->spinBox_MapWidth->value()) + .arg(ui->spinBox_MapHeight->value()) + .arg(size); + } + + bool isValid = errorText.isEmpty(); + ui->label_MapDimensionsError->setText(errorText); + ui->label_MapDimensionsError->setVisible(!isValid); + return isValid; +} + +bool NewLayoutForm::validateTilesets() { + QString primaryTileset = ui->comboBox_PrimaryTileset->currentText(); + QString secondaryTileset = ui->comboBox_SecondaryTileset->currentText(); + + QString primaryErrorText; + if (primaryTileset.isEmpty()) { + primaryErrorText = QString("The primary tileset cannot be empty."); + } else if (ui->comboBox_PrimaryTileset->findText(primaryTileset) < 0) { + primaryErrorText = QString("The specified primary tileset '%1' does not exist.").arg(primaryTileset); + } + + QString secondaryErrorText; + if (secondaryTileset.isEmpty()) { + secondaryErrorText = QString("The secondary tileset cannot be empty."); + } else if (ui->comboBox_SecondaryTileset->findText(secondaryTileset) < 0) { + secondaryErrorText = QString("The specified secondary tileset '%2' does not exist.").arg(secondaryTileset); + } + + QString errorText = QString("%1%2%3") + .arg(primaryErrorText) + .arg(!primaryErrorText.isEmpty() ? "\n" : "") + .arg(secondaryErrorText); + + bool isValid = errorText.isEmpty(); + ui->label_TilesetsError->setText(errorText); + ui->label_TilesetsError->setVisible(!isValid); + return isValid; +} diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index da10c26a..9f0a9f18 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -16,30 +16,33 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : QDialog(parent), ui(new Ui::NewMapDialog) { - this->setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); ui->setupUi(this); this->project = project; - this->existingLayout = false; + this->existingLayout = false; // TODO: Replace, we can determine this from the Layout ID combo box this->importedMap = false; + ui->newLayoutForm->initUi(project); + + ui->comboBox_Group->addItems(project->groupNames); + // Map names and IDs can only contain word characters, and cannot start with a digit. static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); - ui->lineEdit_ID->setValidator(validator); + ui->lineEdit_MapID->setValidator(validator); // Create a collapsible section that has all the map header data. - this->headerData = new MapHeaderForm(); + this->headerForm = new MapHeaderForm(); + this->headerForm->init(project); auto sectionLayout = new QVBoxLayout(); - sectionLayout->addWidget(this->headerData); + sectionLayout->addWidget(this->headerForm); this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); this->headerSection->setContentLayout(sectionLayout); ui->layout_HeaderData->addWidget(this->headerSection); ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); - - connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); - connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); } NewMapDialog::~NewMapDialog() @@ -49,57 +52,25 @@ NewMapDialog::~NewMapDialog() } void NewMapDialog::init() { - // Populate combo boxes - ui->comboBox_PrimaryTileset->addItems(project->primaryTilesetLabels); - ui->comboBox_SecondaryTileset->addItems(project->secondaryTilesetLabels); - ui->comboBox_Group->addItems(project->groupNames); - this->headerData->setProject(project); - - // Set spin box limits - ui->spinBox_MapWidth->setMaximum(project->getMaxMapWidth()); - ui->spinBox_MapHeight->setMaximum(project->getMaxMapHeight()); - ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); - ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); - - ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - - // Restore previous settings - ui->lineEdit_Name->setText(project->getNewMapName()); ui->comboBox_Group->setTextItem(settings.group); - ui->spinBox_MapWidth->setValue(settings.width); - ui->spinBox_MapHeight->setValue(settings.height); - ui->spinBox_BorderWidth->setValue(settings.borderWidth); - ui->spinBox_BorderHeight->setValue(settings.borderHeight); - ui->comboBox_PrimaryTileset->setTextItem(settings.primaryTilesetLabel); - ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); - this->headerData->ui->comboBox_Song->setTextItem(settings.song); - this->headerData->ui->comboBox_Location->setTextItem(settings.location); - this->headerData->ui->checkBox_RequiresFlash->setChecked(settings.requiresFlash); - this->headerData->ui->comboBox_Weather->setTextItem(settings.weather); - this->headerData->ui->comboBox_Type->setTextItem(settings.type); - this->headerData->ui->comboBox_BattleScene->setTextItem(settings.battleScene); - this->headerData->ui->checkBox_ShowLocationName->setChecked(settings.showLocationName); - this->headerData->ui->checkBox_AllowRunning->setChecked(settings.allowRunning); - this->headerData->ui->checkBox_AllowBiking->setChecked(settings.allowBiking); - this->headerData->ui->checkBox_AllowEscaping->setChecked(settings.allowEscaping); - this->headerData->ui->spinBox_FloorNumber->setValue(settings.floorNumber); ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); + ui->newLayoutForm->setSettings(settings.layout); + this->headerForm->setHeader(&settings.header); + ui->lineEdit_Name->setText(project->getNewMapName()); } // Creating new map by right-clicking in the map list void NewMapDialog::init(int tabIndex, QString fieldName) { - //initUi(); switch (tabIndex) { case MapListTab::Groups: settings.group = fieldName; - //ui->label_Group->setDisabled(true); - //ui->comboBox_Group->setDisabled(true); + ui->label_Group->setDisabled(true); + ui->comboBox_Group->setDisabled(true); break; case MapListTab::Areas: - settings.location = fieldName; - //ui->label_Location->setDisabled(true); - //ui->comboBox_Location->setDisabled(true); + settings.header.setLocation(fieldName); + this->headerForm->setLocationsDisabled(true); break; case MapListTab::Layouts: useLayout(fieldName); @@ -109,6 +80,7 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { } // Creating new map from AdvanceMap import +// TODO: Re-use for a "Duplicate Map/Layout" option? void NewMapDialog::init(Layout *layout) { this->importedMap = true; useLayoutSettings(layout); @@ -126,91 +98,54 @@ void NewMapDialog::init(Layout *layout) { void NewMapDialog::setDefaultSettings(Project *project) { settings.group = project->groupNames.at(0); - settings.width = project->getDefaultMapDimension(); - settings.height = project->getDefaultMapDimension(); - settings.borderWidth = DEFAULT_BORDER_WIDTH; - settings.borderHeight = DEFAULT_BORDER_HEIGHT; - settings.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); - settings.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); - settings.song = project->defaultSong; - settings.location = project->mapSectionIdNames.value(0, "0"); - settings.requiresFlash = false; - settings.weather = project->weatherNames.value(0, "0"); - settings.type = project->mapTypes.value(0, "0"); - settings.battleScene = project->mapBattleScenes.value(0, "0"); - settings.showLocationName = true; - settings.allowRunning = false; - settings.allowBiking = false; - settings.allowEscaping = false; - settings.floorNumber = 0; settings.canFlyTo = false; + settings.layout.width = project->getDefaultMapDimension(); + settings.layout.height = project->getDefaultMapDimension(); + settings.layout.borderWidth = DEFAULT_BORDER_WIDTH; + settings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; + settings.layout.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); + settings.layout.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); + settings.header.setSong(project->defaultSong); + settings.header.setLocation(project->mapSectionIdNames.value(0, "0")); + settings.header.setRequiresFlash(false); + settings.header.setWeather(project->weatherNames.value(0, "0")); + settings.header.setType(project->mapTypes.value(0, "0")); + settings.header.setBattleScene(project->mapBattleScenes.value(0, "0")); + settings.header.setShowsLocationName(true); + settings.header.setAllowsRunning(false); + settings.header.setAllowsBiking(false); + settings.header.setAllowsEscaping(false); + settings.header.setFloorNumber(0); } void NewMapDialog::saveSettings() { settings.group = ui->comboBox_Group->currentText(); - settings.width = ui->spinBox_MapWidth->value(); - settings.height = ui->spinBox_MapHeight->value(); - settings.borderWidth = ui->spinBox_BorderWidth->value(); - settings.borderHeight = ui->spinBox_BorderHeight->value(); - settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); - settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); - settings.song = this->headerData->ui->comboBox_Song->currentText(); - settings.location = this->headerData->ui->comboBox_Location->currentText(); - settings.requiresFlash = this->headerData->ui->checkBox_RequiresFlash->isChecked(); - settings.weather = this->headerData->ui->comboBox_Weather->currentText(); - settings.type = this->headerData->ui->comboBox_Type->currentText(); - settings.battleScene = this->headerData->ui->comboBox_BattleScene->currentText(); - settings.showLocationName = this->headerData->ui->checkBox_ShowLocationName->isChecked(); - settings.allowRunning = this->headerData->ui->checkBox_AllowRunning->isChecked(); - settings.allowBiking = this->headerData->ui->checkBox_AllowBiking->isChecked(); - settings.allowEscaping = this->headerData->ui->checkBox_AllowEscaping->isChecked(); - settings.floorNumber = this->headerData->ui->spinBox_FloorNumber->value(); settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + settings.layout = ui->newLayoutForm->settings(); + settings.header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; - settings.width = layout->width; - settings.height = layout->height; - settings.borderWidth = layout->border_width; - settings.borderHeight = layout->border_height; - settings.primaryTilesetLabel = layout->tileset_primary_label; - settings.secondaryTilesetLabel = layout->tileset_secondary_label; + settings.layout.width = layout->width; + settings.layout.height = layout->height; + settings.layout.borderWidth = layout->border_width; + settings.layout.borderHeight = layout->border_height; + settings.layout.primaryTilesetLabel = layout->tileset_primary_label; + settings.layout.secondaryTilesetLabel = layout->tileset_secondary_label; + + // Don't allow changes to the layout settings + ui->newLayoutForm->setDisabled(true); } void NewMapDialog::useLayout(QString layoutId) { this->existingLayout = true; this->layoutId = layoutId; - useLayoutSettings(project->mapLayouts.value(this->layoutId)); - - // Dimensions and tilesets can't be changed for new maps using an existing layout - ui->groupBox_MapDimensions->setDisabled(true); - ui->groupBox_BorderDimensions->setDisabled(true); - ui->groupBox_Tilesets->setDisabled(true); -} - -bool NewMapDialog::validateMapDimensions() { - int size = project->getMapDataSize(ui->spinBox_MapWidth->value(), ui->spinBox_MapHeight->value()); - int maxSize = project->getMaxMapDataSize(); - - QString errorText; - if (size > maxSize) { - errorText = QString("The specified width and height are too large.\n" - "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") - .arg(maxSize) - .arg(ui->spinBox_MapWidth->value()) - .arg(ui->spinBox_MapHeight->value()) - .arg(size); - } - - bool isValid = errorText.isEmpty(); - ui->label_MapDimensionsError->setText(errorText); - ui->label_MapDimensionsError->setVisible(!isValid); - return isValid; + useLayoutSettings(project->mapLayouts.value(this->layoutId)); } +// TODO: Create the map group if it doesn't exist bool NewMapDialog::validateMapGroup() { this->group = project->groupNames.indexOf(ui->comboBox_Group->currentText()); @@ -226,37 +161,8 @@ bool NewMapDialog::validateMapGroup() { return isValid; } -bool NewMapDialog::validateTilesets() { - QString primaryTileset = ui->comboBox_PrimaryTileset->currentText(); - QString secondaryTileset = ui->comboBox_SecondaryTileset->currentText(); - - QString primaryErrorText; - if (primaryTileset.isEmpty()) { - primaryErrorText = QString("The primary tileset cannot be empty."); - } else if (ui->comboBox_PrimaryTileset->findText(primaryTileset) < 0) { - primaryErrorText = QString("The specified primary tileset '%1' does not exist.").arg(primaryTileset); - } - - QString secondaryErrorText; - if (secondaryTileset.isEmpty()) { - secondaryErrorText = QString("The secondary tileset cannot be empty."); - } else if (ui->comboBox_SecondaryTileset->findText(secondaryTileset) < 0) { - secondaryErrorText = QString("The specified secondary tileset '%2' does not exist.").arg(secondaryTileset); - } - - QString errorText = QString("%1%2%3") - .arg(primaryErrorText) - .arg(!primaryErrorText.isEmpty() ? "\n" : "") - .arg(secondaryErrorText); - - bool isValid = errorText.isEmpty(); - ui->label_TilesetsError->setText(errorText); - ui->label_TilesetsError->setVisible(!isValid); - return isValid; -} - bool NewMapDialog::validateID() { - QString id = ui->lineEdit_ID->text(); + QString id = ui->lineEdit_MapID->text(); QString errorText; QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); @@ -272,13 +178,13 @@ bool NewMapDialog::validateID() { } bool isValid = errorText.isEmpty(); - ui->label_IDError->setText(errorText); - ui->label_IDError->setVisible(!isValid); - ui->lineEdit_ID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + ui->label_MapIDError->setText(errorText); + ui->label_MapIDError->setVisible(!isValid); + ui->lineEdit_MapID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); return isValid; } -void NewMapDialog::on_lineEdit_ID_textChanged(const QString &) { +void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { validateID(); } @@ -299,22 +205,23 @@ bool NewMapDialog::validateName() { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(); - ui->lineEdit_ID->setText(Map::mapConstantFromName(text)); + ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); } void NewMapDialog::on_pushButton_Accept_clicked() { + saveSettings(); + // Make sure to call each validation function so that all errors are shown at once. bool success = true; - if (!validateMapDimensions()) success = false; + if (!ui->newLayoutForm->validate()) success = false; if (!validateMapGroup()) success = false; - if (!validateTilesets()) success = false; if (!validateID()) success = false; if (!validateName()) success = false; if (!success) return; - // We check if the map name is empty separately from the validation above because it's likely - // that users will clear the name text box while editing, and we don't want to flash errors at them for this. + // We check if the map name is empty separately from validateName, because validateName is also used during editing. + // It's likely that users will clear the name text box while editing, and we don't want to flash errors at them for this. if (ui->lineEdit_Name->text().isEmpty()) { ui->label_NameError->setText("The specified map name cannot be empty."); ui->label_NameError->setVisible(true); @@ -324,23 +231,9 @@ void NewMapDialog::on_pushButton_Accept_clicked() { Map *newMap = new Map; newMap->setName(ui->lineEdit_Name->text()); - newMap->setConstantName(ui->lineEdit_ID->text()); - newMap->setSong(this->headerData->ui->comboBox_Song->currentText()); - newMap->setLocation(this->headerData->ui->comboBox_Location->currentText()); - newMap->setRequiresFlash(this->headerData->ui->checkBox_RequiresFlash->isChecked()); - newMap->setWeather(this->headerData->ui->comboBox_Weather->currentText()); - newMap->setType(this->headerData->ui->comboBox_Type->currentText()); - newMap->setBattleScene(this->headerData->ui->comboBox_BattleScene->currentText()); - newMap->setShowsLocationName(this->headerData->ui->checkBox_ShowLocationName->isChecked()); - if (projectConfig.mapAllowFlagsEnabled) { - newMap->setAllowsRunning(this->headerData->ui->checkBox_AllowRunning->isChecked()); - newMap->setAllowsBiking(this->headerData->ui->checkBox_AllowBiking->isChecked()); - newMap->setAllowsEscaping(this->headerData->ui->checkBox_AllowEscaping->isChecked()); - } - if (projectConfig.floorNumberEnabled) { - newMap->setFloorNumber(this->headerData->ui->spinBox_FloorNumber->value()); - } - newMap->setNeedsHealLocation(ui->checkBox_CanFlyTo->isChecked()); + newMap->setConstantName(ui->lineEdit_MapID->text()); + newMap->setHeader(this->headerForm->headerData()); + newMap->setNeedsHealLocation(settings.canFlyTo); Layout *layout; if (this->existingLayout) { @@ -350,17 +243,17 @@ void NewMapDialog::on_pushButton_Accept_clicked() { layout = new Layout; layout->id = Layout::layoutConstantFromName(newMap->name()); layout->name = QString("%1_Layout").arg(newMap->name()); - layout->width = ui->spinBox_MapWidth->value(); - layout->height = ui->spinBox_MapHeight->value(); + layout->width = settings.layout.width; + layout->height = settings.layout.height; if (projectConfig.useCustomBorderSize) { - layout->border_width = ui->spinBox_BorderWidth->value(); - layout->border_height = ui->spinBox_BorderHeight->value(); + layout->border_width = settings.layout.borderWidth; + layout->border_height = settings.layout.borderHeight; } else { layout->border_width = DEFAULT_BORDER_WIDTH; layout->border_height = DEFAULT_BORDER_HEIGHT; } - layout->tileset_primary_label = ui->comboBox_PrimaryTileset->currentText(); - layout->tileset_secondary_label = ui->comboBox_SecondaryTileset->currentText(); + layout->tileset_primary_label = settings.layout.primaryTilesetLabel; + layout->tileset_secondary_label = settings.layout.secondaryTilesetLabel; QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); layout->border_path = QString("%1%2/border.bin").arg(basePath, newMap->name()); layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMap->name()); From 724f42019ccb74d16785aff11ab99aa8b33ff03c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 14 Nov 2024 16:01:54 -0500 Subject: [PATCH 11/42] Automatically add new map groups --- forms/newmapdialog.ui | 22 +++--- include/config.h | 2 +- include/mainwindow.h | 4 +- include/project.h | 21 ++--- include/ui/maplistmodels.h | 11 ++- include/ui/newlayoutform.h | 1 + include/ui/newmapdialog.h | 27 ++++--- src/core/events.cpp | 10 ++- src/mainwindow.cpp | 99 ++++++++++++------------ src/project.cpp | 153 ++++++++++++++++++++++--------------- src/ui/maplistmodels.cpp | 127 ++++++++++++++---------------- src/ui/newlayoutform.cpp | 6 +- src/ui/newmapdialog.cpp | 146 ++++++++++++++++++----------------- 13 files changed, 331 insertions(+), 298 deletions(-) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 911050d3..ed52f445 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,7 +25,7 @@ 0 0 427 - 520 + 522
@@ -68,7 +68,7 @@
- + @@ -142,7 +142,7 @@
- + false @@ -175,7 +175,7 @@ - + Layout ID @@ -197,15 +197,11 @@ - - - - - Accept - - - - + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + +
diff --git a/include/config.h b/include/config.h index 21e2dde7..0655c0bd 100644 --- a/include/config.h +++ b/include/config.h @@ -70,7 +70,7 @@ class PorymapConfig: public KeyValueConfigBase this->showTilesetEditorLayerGrid = true; this->monitorFiles = true; this->tilesetCheckerboardFill = true; - this->newMapHeaderSectionExpanded = true; + this->newMapHeaderSectionExpanded = false; this->theme = "default"; this->wildMonChartTheme = ""; this->textEditorOpenFolder = ""; diff --git a/include/mainwindow.h b/include/mainwindow.h index a506f5e0..e96c4cc3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -188,7 +188,9 @@ private slots: void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); void openNewMapDialog(); - void onNewMapCreated(); + void onNewMapCreated(Map *newMap, const QString &groupName); + void onNewMapGroupCreated(const QString &groupName); + void onNewLayoutCreated(Layout *layout); void onMapLoaded(Map *map); void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); diff --git a/include/project.h b/include/project.h index f28f9259..36c8e1e3 100644 --- a/include/project.h +++ b/include/project.h @@ -32,20 +32,18 @@ class Project : public QObject public: QString root; - QStringList groupNames; - QMap mapGroups; - QList groupedMapNames; QStringList mapNames; + QStringList groupNames; + QMap groupNameToMapNames; QList healLocations; QMap healLocationNameToValue; QMap mapConstantsToMapNames; QMap mapNamesToMapConstants; QMap mapNameToLayoutId; QMap mapNameToMapSectionName; - QStringList mapLayoutsTable; - QStringList mapLayoutsTableMaster; QString layoutsLabel; - QMap layoutIdsToNames; + QStringList layoutIds; + QStringList layoutIdsMaster; QMap mapLayouts; QMap mapLayoutsMaster; QMap eventGraphicsMap; @@ -122,7 +120,9 @@ class Project : public QObject void deleteFile(QString path); bool readMapGroups(); - Map* addNewMapToGroup(Map*, int, bool, bool); + void addNewMap(Map* newMap, const QString &groupName); + void addNewMapGroup(const QString &groupName); + void addNewLayout(Layout* newLayout); QString getNewMapName(); QString getProjectTitle(); @@ -214,8 +214,8 @@ class Project : public QObject QString getScriptDefaultString(bool usePoryScript, QString mapName) const; QStringList getEventScriptsFilePaths() const; - QString getDefaultPrimaryTilesetLabel(); - QString getDefaultSecondaryTilesetLabel(); + QString getDefaultPrimaryTilesetLabel() const; + QString getDefaultSecondaryTilesetLabel() const; void updateTilesetMetatileLabels(Tileset *tileset); QString buildMetatileLabelsText(const QMap defines); QString findMetatileLabelsTileset(QString label); @@ -265,6 +265,9 @@ class Project : public QObject void fileChanged(QString filepath); void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); + void mapAdded(Map *newMap, const QString &groupName); + void mapGroupAdded(const QString &groupName); + void layoutAdded(Layout *newLayout); }; #endif // PROJECT_H diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 17810c7e..59b65e50 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -13,9 +13,8 @@ class Project; enum MapListUserRoles { - GroupRole = Qt::UserRole + 1, // Used to hold the map group number. - TypeRole, // Used to differentiate between the different layers of the map list tree view. - TypeRole2, // Used for various extra data needed. + NameRole = Qt::UserRole, // Holds the name of the item in the list + TypeRole, // Used to differentiate between the different layers of the map list tree view. }; @@ -92,7 +91,7 @@ class MapGroupModel : public MapListModel { public: void setMap(QString mapName) { this->openMap = mapName; } - QStandardItem *createGroupItem(QString groupName, int groupIndex, QStandardItem *fromItem = nullptr); + QStandardItem *createGroupItem(QString groupName, QStandardItem *fromItem = nullptr); QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); QStandardItem *insertGroupItem(QString groupName); @@ -138,10 +137,10 @@ class MapAreaModel : public MapListModel { void setMap(QString mapName) { this->openMap = mapName; } QStandardItem *createAreaItem(QString areaName); - QStandardItem *createMapItem(QString mapName, int areaIndex, int mapIndex); + QStandardItem *createMapItem(QString mapName); QStandardItem *insertAreaItem(QString areaName); - QStandardItem *insertMapItem(QString mapName, QString areaName, int groupIndex); + QStandardItem *insertMapItem(QString mapName, QString areaName); virtual QStandardItem *getItem(const QModelIndex &index) const override; virtual QModelIndex indexOf(QString mapName) const override; diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index 6f63466b..d6230f6f 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -20,6 +20,7 @@ class NewLayoutForm : public QWidget void initUi(Project *project); struct Settings { + QString id; // TODO: Support in UI (toggleable line edit) int width; int height; int borderWidth; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index e7401242..2e860cba 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -20,31 +20,30 @@ class NewMapDialog : public QDialog public: explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr); ~NewMapDialog(); - Map *map; - int group; - bool existingLayout; - bool importedMap; - QString layoutId; void init(); void init(int tabIndex, QString data); void init(Layout *); - static void setDefaultSettings(Project *project); + void accept() override; + static void setDefaultSettings(const Project *project); signals: - void applied(); + void applied(const QString &newMapName); private: Ui::NewMapDialog *ui; Project *project; CollapsibleSection *headerSection; MapHeaderForm *headerForm; + Layout *importedLayout = nullptr; - bool validateMapGroup(); - bool validateID(); - bool validateName(); + // Each of these validation functions will allow empty names up until `OK` is selected, + // because clearing the text during editing is common and we don't want to flash errors for this. + bool validateID(bool allowEmpty = false); + bool validateName(bool allowEmpty = false); + bool validateGroup(bool allowEmpty = false); void saveSettings(); - void useLayout(QString layoutId); + bool isExistingLayout() const; void useLayoutSettings(Layout *mapLayout); struct Settings { @@ -56,11 +55,11 @@ class NewMapDialog : public QDialog static struct Settings settings; private slots: - //void on_checkBox_UseExistingLayout_stateChanged(int state); //TODO - //void on_comboBox_Layout_currentTextChanged(const QString &text); - void on_pushButton_Accept_clicked(); + //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO + void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); void on_lineEdit_MapID_textChanged(const QString &); + void on_comboBox_Group_currentTextChanged(const QString &text); }; #endif // NEWMAPDIALOG_H diff --git a/src/core/events.cpp b/src/core/events.cpp index 0673a652..c0cf7b7a 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -392,7 +392,8 @@ OrderedJson::object CloneObjectEvent::buildEventJson(Project *project) { cloneJson["x"] = this->getX(); cloneJson["y"] = this->getY(); cloneJson["target_local_id"] = this->getTargetID(); - cloneJson["target_map"] = project->mapNamesToMapConstants.value(this->getTargetMap()); + const QString mapName = this->getTargetMap(); + cloneJson["target_map"] = project->mapNamesToMapConstants.value(mapName, mapName); this->addCustomValuesTo(&cloneJson); return cloneJson; @@ -407,7 +408,7 @@ bool CloneObjectEvent::loadFromJson(QJsonObject json, Project *project) { // 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"]); if (!project->mapConstantsToMapNames.contains(mapConstant)) - logWarn(QString("Target Map constant '%1' is invalid.").arg(mapConstant)); + logWarn(QString("Unknown Target Map constant '%1'.").arg(mapConstant)); this->setTargetMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); @@ -496,7 +497,8 @@ OrderedJson::object WarpEvent::buildEventJson(Project *project) { warpJson["x"] = this->getX(); warpJson["y"] = this->getY(); warpJson["elevation"] = this->getElevation(); - warpJson["dest_map"] = project->mapNamesToMapConstants.value(this->getDestinationMap()); + const QString mapName = this->getDestinationMap(); + warpJson["dest_map"] = project->mapNamesToMapConstants.value(mapName, mapName); warpJson["dest_warp_id"] = this->getDestinationWarpID(); this->addCustomValuesTo(&warpJson); @@ -513,7 +515,7 @@ bool WarpEvent::loadFromJson(QJsonObject json, Project *project) { // 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"]); if (!project->mapConstantsToMapNames.contains(mapConstant)) - logWarn(QString("Destination Map constant '%1' is invalid.").arg(mapConstant)); + logWarn(QString("Unknown Destination Map constant '%1'.").arg(mapConstant)); this->setDestinationMap(project->mapConstantsToMapNames.value(mapConstant, mapConstant)); this->readCustomValues(json); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index f4c96393..59b6057f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -617,6 +617,9 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); + connect(project, &Project::mapAdded, this, &MainWindow::onNewMapCreated); + connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); + connect(project, &Project::layoutAdded, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); @@ -702,7 +705,7 @@ bool MainWindow::setInitialMap() { // User recently had a map open that still exists. if (setMap(recent)) return true; - } else if (editor->project->mapLayoutsTable.contains(recent)) { + } else if (editor->project->layoutIds.contains(recent)) { // User recently had a layout open that still exists. if (setLayout(recent)) return true; @@ -713,7 +716,7 @@ bool MainWindow::setInitialMap() { if (name != recent && setMap(name)) return true; } - for (const auto &id : editor->project->mapLayoutsTable) { + for (const auto &id : editor->project->layoutIds) { if (id != recent && setLayout(id)) return true; } @@ -932,13 +935,13 @@ bool MainWindow::userSetLayout(QString layoutId) { bool MainWindow::setLayout(QString layoutId) { if (this->editor->map) - logInfo("Switching to a layout-only editing mode. Disabling map-related edits."); + logInfo("Switching to layout-only editing mode. Disabling map-related edits."); unsetMap(); // Prefer logging the name of the layout as displayed in the map list. - const QString layoutName = this->editor->project ? this->editor->project->layoutIdsToNames.value(layoutId, layoutId) : layoutId; - logInfo(QString("Setting layout to '%1'").arg(layoutName)); + const Layout* layout = this->editor->project ? this->editor->project->mapLayouts.value(layoutId) : nullptr; + logInfo(QString("Setting layout to '%1'").arg(layout ? layout->name : layoutId)); if (!this->editor->setLayout(layoutId)) { return false; @@ -1069,7 +1072,7 @@ bool MainWindow::setProjectUI() { const QSignalBlocker b_LayoutSelector(ui->comboBox_LayoutSelector); ui->comboBox_LayoutSelector->clear(); - ui->comboBox_LayoutSelector->addItems(project->mapLayoutsTable); + ui->comboBox_LayoutSelector->addItems(project->layoutIds); const QSignalBlocker b_DiveMap(ui->comboBox_DiveMap); ui->comboBox_DiveMap->clear(); @@ -1189,7 +1192,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { auto sourceModel = static_cast(model->sourceModel()); QStandardItem *selectedItem = sourceModel->itemFromIndex(index); const QString itemType = selectedItem->data(MapListUserRoles::TypeRole).toString(); - const QString itemName = selectedItem->data(Qt::UserRole).toString(); + const QString itemName = selectedItem->data(MapListUserRoles::NameRole).toString(); QMenu menu(this); QAction* addToFolderAction = nullptr; @@ -1278,7 +1281,7 @@ void MainWindow::mapListAddGroup() { if (dialog.exec() == QDialog::Accepted) { QString newFieldName = newNameEdit->text(); if (newFieldName.isEmpty()) return; - this->mapGroupModel->insertGroupItem(newFieldName); + this->editor->project->addNewMapGroup(newFieldName); } } @@ -1307,7 +1310,7 @@ void MainWindow::mapListAddLayout() { }); NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog); - useExistingCombo->addItems(this->editor->project->mapLayoutsTable); + useExistingCombo->addItems(this->editor->project->layoutIds); useExistingCombo->setEnabled(false); QCheckBox *useExistingCheck = new QCheckBox(&dialog); @@ -1358,13 +1361,13 @@ void MainWindow::mapListAddLayout() { errorMessage = "Name cannot be empty"; } // unique layout name & id - else if (this->editor->project->mapLayoutsTable.contains(newId->text()) + /*else if (this->editor->project->layoutIds.contains(newId->text()) || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { errorMessage = "Layout Name / ID is not unique"; - } + }*/ // TODO: Re-implement // from id is existing value else if (useExistingCheck->isChecked()) { - if (!this->editor->project->mapLayoutsTable.contains(useExistingCombo->currentText())) { + if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) { errorMessage = "Existing layout ID is not valid"; } } @@ -1395,7 +1398,6 @@ void MainWindow::mapListAddLayout() { layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); } Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); - this->layoutTreeModel->insertLayoutItem(newLayout->id); setLayout(newLayout->id); } } @@ -1407,10 +1409,9 @@ void MainWindow::mapListAddArea() { connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); - QLineEdit *newNameEdit = new QLineEdit(&dialog); - QLineEdit *newNameDisplay = new QLineEdit(&dialog); + auto newNameEdit = new QLineEdit(&dialog); + auto newNameDisplay = new QLabel(&dialog); newNameDisplay->setText(prefix); - newNameDisplay->setEnabled(false); connect(newNameEdit, &QLineEdit::textEdited, [newNameDisplay, prefix] (const QString &text) { // As the user types a name, update the label to show the name with the prefix. newNameDisplay->setText(prefix + text); @@ -1450,55 +1451,52 @@ void MainWindow::mapListAddArea() { } } -void MainWindow::onNewMapCreated() { - QString newMapName = this->newMapDialog->map->name(); - int newMapGroup = this->newMapDialog->group; - Map *newMap = this->newMapDialog->map; - bool existingLayout = this->newMapDialog->existingLayout; - bool importedMap = this->newMapDialog->importedMap; - - newMap = editor->project->addNewMapToGroup(newMap, newMapGroup, existingLayout, importedMap); - - logInfo(QString("Created a new map named %1.").arg(newMapName)); +void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { + logInfo(QString("Created a new map named %1.").arg(newMap->name())); // TODO: Creating a new map shouldn't be automatically saved editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); - // Add new Map / Layout to the mapList models - this->mapGroupModel->insertMapItem(newMapName, editor->project->groupNames[newMapGroup]); - this->mapAreaModel->insertMapItem(newMapName, newMap->header()->location(), newMapGroup); - this->layoutTreeModel->insertMapItem(newMapName, newMap->layout()->id); + // Add new map to the map lists + this->mapGroupModel->insertMapItem(newMap->name(), groupName); + this->mapAreaModel->insertMapItem(newMap->name(), newMap->header()->location()); + this->layoutTreeModel->insertMapItem(newMap->name(), newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps // (other combo boxes like for warp destinations are repopulated when the map changes). - int mapIndex = this->editor->project->mapNames.indexOf(newMapName); + 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, newMapName); - ui->comboBox_EmergeMap->insertItem(mapIndex, newMapName); - } - - // Refresh layout combo box (if a new one was created) - if (!existingLayout) { - int layoutIndex = this->editor->project->mapLayoutsTable.indexOf(newMap->layout()->id); - if (layoutIndex >= 0) { - const QSignalBlocker b_Layouts(ui->comboBox_LayoutSelector); - ui->comboBox_LayoutSelector->insertItem(layoutIndex, newMap->layout()->id); - } + ui->comboBox_DiveMap->insertItem(mapIndex, newMap->name()); + ui->comboBox_EmergeMap->insertItem(mapIndex, newMap->name()); } - setMap(newMapName); - if (newMap->needsHealLocation()) { addNewEvent(Event::Type::HealLocation); editor->project->saveHealLocations(newMap); editor->save(); } +} + +void MainWindow::onNewLayoutCreated(Layout *layout) { + logInfo(QString("Created a new layout named %1.").arg(layout->name)); + + // Refresh layout combo box + int layoutIndex = this->editor->project->layoutIds.indexOf(layout->id); + if (layoutIndex >= 0) { + const QSignalBlocker b(ui->comboBox_LayoutSelector); + ui->comboBox_LayoutSelector->insertItem(layoutIndex, layout->id); + } + + // Add new layout to the Layouts map list view + this->layoutTreeModel->insertLayoutItem(layout->id); +} - disconnect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); - delete newMap; +void MainWindow::onNewMapGroupCreated(const QString &groupName) { + // Add new map group to the Groups map list view + this->mapGroupModel->insertGroupItem(groupName); } void MainWindow::openNewMapDialog() { @@ -1508,7 +1506,7 @@ void MainWindow::openNewMapDialog() { } if (!this->newMapDialog) { this->newMapDialog = new NewMapDialog(this, this->editor->project); - connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::onNewMapCreated); + connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); } openSubWindow(this->newMapDialog); @@ -1701,9 +1699,10 @@ void MainWindow::openMapListItem(const QModelIndex &index) { if (!index.isValid()) return; - QVariant data = index.data(Qt::UserRole); + QVariant data = index.data(MapListUserRoles::NameRole); if (data.isNull()) return; + const QString name = data.toString(); // Normally when a new map/layout is opened the search filters are cleared and the lists will scroll to display that map/layout in the list. // We don't want to do this when the user interacts with a list directly, so we temporarily prevent changes to the search filter. @@ -1712,9 +1711,9 @@ void MainWindow::openMapListItem(const QModelIndex &index) { QString type = index.data(MapListUserRoles::TypeRole).toString(); if (type == "map_name") { - userSetMap(data.toString()); + userSetMap(name); } else if (type == "map_layout") { - userSetLayout(data.toString()); + userSetLayout(name); } if (toolbar) toolbar->setFilterLocked(false); diff --git a/src/project.cpp b/src/project.cpp index f168099e..ccd6c8a2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -368,6 +368,7 @@ bool Project::loadMapData(Map* map) { return true; } +// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); Layout *layout; @@ -409,15 +410,16 @@ Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { return nullptr; } - mapLayouts.insert(layout->id, layout); - mapLayoutsMaster.insert(layout->id, layout->copy()); - mapLayoutsTable.append(layout->id); - mapLayoutsTableMaster.append(layout->id); - layoutIdsToNames.insert(layout->id, layout->name); + // TODO: Redundancy here, some of this is already handled in saveLayout > updateLayout + this->mapLayouts.insert(layout->id, layout); + this->mapLayoutsMaster.insert(layout->id, layout->copy()); + this->layoutIds.append(layout->id); + this->layoutIdsMaster.append(layout->id); saveLayout(layout); - this->loadLayout(layout); + loadLayout(layout); + emit layoutAdded(layout); return layout; } @@ -471,12 +473,12 @@ bool Project::loadMapLayout(Map* map) { } void Project::clearMapLayouts() { - qDeleteAll(mapLayouts); - mapLayouts.clear(); - qDeleteAll(mapLayoutsMaster); - mapLayoutsMaster.clear(); - mapLayoutsTable.clear(); - layoutIdsToNames.clear(); + qDeleteAll(this->mapLayouts); + this->mapLayouts.clear(); + qDeleteAll(this->mapLayoutsMaster); + this->mapLayoutsMaster.clear(); + this->layoutIds.clear(); + this->layoutIdsMaster.clear(); } bool Project::readMapLayouts() { @@ -498,9 +500,9 @@ bool Project::readMapLayouts() { return false; } - layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); - if (layoutsLabel.isNull()) { - layoutsLabel = "gMapLayouts"; + this->layoutsLabel = ParseUtil::jsonToQString(layoutsObj["layouts_table_label"]); + if (this->layoutsLabel.isEmpty()) { + this->layoutsLabel = "gMapLayouts"; logWarn(QString("'layouts_table_label' value is missing from %1. Defaulting to %2") .arg(layoutsFilepath) .arg(layoutsLabel)); @@ -599,11 +601,11 @@ bool Project::readMapLayouts() { delete layout; return false; } - mapLayouts.insert(layout->id, layout); - mapLayoutsMaster.insert(layout->id, layout->copy()); - mapLayoutsTable.append(layout->id); - mapLayoutsTableMaster.append(layout->id); - layoutIdsToNames.insert(layout->id, layout->name); + + this->mapLayouts.insert(layout->id, layout); + this->mapLayoutsMaster.insert(layout->id, layout->copy()); + this->layoutIds.append(layout->id); + this->layoutIdsMaster.append(layout->id); } return true; @@ -618,11 +620,11 @@ void Project::saveMapLayouts() { } OrderedJson::object layoutsObj; - layoutsObj["layouts_table_label"] = layoutsLabel; + layoutsObj["layouts_table_label"] = this->layoutsLabel; OrderedJson::array layoutsArr; - for (QString layoutId : mapLayoutsTableMaster) { - Layout *layout = mapLayoutsMaster.value(layoutId); + for (const QString &layoutId : this->layoutIdsMaster) { + Layout *layout = this->mapLayoutsMaster.value(layoutId); OrderedJson::object layoutObj; layoutObj["id"] = layout->id; layoutObj["name"] = layout->name; @@ -669,14 +671,12 @@ void Project::saveMapGroups() { } mapGroupsObj["group_order"] = groupNamesArr; - int groupNum = 0; - for (QStringList mapNames : groupedMapNames) { + for (const auto &groupName : this->groupNames) { OrderedJson::array groupArr; - for (QString mapName : mapNames) { + for (const auto &mapName : this->groupNameToMapNames.value(groupName)) { groupArr.push_back(mapName); } - mapGroupsObj[this->groupNames.at(groupNum)] = groupArr; - groupNum++; + mapGroupsObj[groupName] = groupArr; } ignoreWatchedFileTemporarily(mapGroupsFilepath); @@ -1390,15 +1390,15 @@ void Project::saveLayout(Layout *layout) { } void Project::updateLayout(Layout *layout) { - if (!mapLayoutsTableMaster.contains(layout->id)) { - mapLayoutsTableMaster.append(layout->id); + if (!this->layoutIdsMaster.contains(layout->id)) { + this->layoutIdsMaster.append(layout->id); } - if (mapLayoutsMaster.contains(layout->id)) { - mapLayoutsMaster[layout->id]->copyFrom(layout); + if (this->mapLayoutsMaster.contains(layout->id)) { + this->mapLayoutsMaster[layout->id]->copyFrom(layout); } else { - mapLayoutsMaster.insert(layout->id, layout->copy()); + this->mapLayoutsMaster.insert(layout->id, layout->copy()); } } @@ -1821,10 +1821,9 @@ bool Project::readWildMonData() { bool Project::readMapGroups() { this->mapConstantsToMapNames.clear(); this->mapNamesToMapConstants.clear(); - this->mapGroups.clear(); - this->groupNames.clear(); - this->groupedMapNames.clear(); this->mapNames.clear(); + this->groupNames.clear(); + this->groupNameToMapNames.clear(); this->initTopLevelMapFields(); @@ -1845,7 +1844,6 @@ bool Project::readMapGroups() { for (int groupIndex = 0; groupIndex < mapGroupOrder.size(); groupIndex++) { const QString groupName = ParseUtil::jsonToQString(mapGroupOrder.at(groupIndex)); const QJsonArray mapNamesJson = mapGroupsObj.value(groupName).toArray(); - this->groupedMapNames.append(QStringList()); this->groupNames.append(groupName); // Process the names in this map group @@ -1885,8 +1883,8 @@ bool Project::readMapGroups() { // Success, save the constants to the project this->mapNames.append(mapName); - this->groupedMapNames[groupIndex].append(mapName); - this->mapGroups.insert(mapName, groupIndex); + this->groupNameToMapNames[groupName].append(mapName); + // TODO: These are not well-kept in sync (and that's probably a bad design indication. Maybe Maps should have a not-fully-loaded state, but have all their map.json data cached) this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); // TODO: Either verify that these are known IDs, or make sure nothing breaks when they're unknown. @@ -1913,34 +1911,69 @@ bool Project::readMapGroups() { return true; } -Map* Project::addNewMapToGroup(Map *newMap, int groupNum, bool existingLayout, bool importedMap) { - int mapNamePos = 0; - for (int i = 0; i <= groupNum; i++) - mapNamePos += this->groupedMapNames.value(i).length(); +void Project::addNewMap(Map *newMap, const QString &groupName) { + if (!newMap) + return; + + // Make sure we keep the order of the map names the same as in the map group order. + int mapNamePos; + if (this->groupNames.contains(groupName)) { + mapNamePos = 0; + for (const auto &name : this->groupNames) { + mapNamePos += this->groupNameToMapNames[name].length(); + if (name == groupName) + break; + } + } else { + // Adding map to a map group that doesn't exist yet. + // Create the group, and we already know the map will be last in the list. + addNewMapGroup(groupName); + mapNamePos = this->mapNames.length(); + } this->mapNames.insert(mapNamePos, newMap->name()); - this->mapGroups.insert(newMap->name(), groupNum); - this->groupedMapNames[groupNum].append(newMap->name()); + this->groupNameToMapNames[groupName].append(newMap->name()); this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); newMap->setIsPersistedToFile(false); - if (!existingLayout) { - this->mapLayouts.insert(newMap->layoutId(), newMap->layout()); - this->mapLayoutsTable.append(newMap->layoutId()); - this->layoutIdsToNames.insert(newMap->layout()->id, newMap->layout()->name); - if (!importedMap) { - setNewLayoutBlockdata(newMap->layout()); - } - if (newMap->layout()->border.isEmpty()) { - setNewLayoutBorder(newMap->layout()); - } + // If we don't recognize the layout ID (i.e., it's also new) we'll add that too. + if (!this->layoutIds.contains(newMap->layout()->id)) { + addNewLayout(newMap->layout()); + } + + emit mapAdded(newMap, groupName); +} + +void Project::addNewLayout(Layout* newLayout) { + if (!newLayout || this->layoutIds.contains(newLayout->id)) + return; + + this->mapLayouts.insert(newLayout->id, newLayout); + this->layoutIds.append(newLayout->id); + + if (newLayout->blockdata.isEmpty()) { + // Fill layout using default fill settings + setNewLayoutBlockdata(newLayout); } + if (newLayout->border.isEmpty()) { + // Fill border using default fill settings + setNewLayoutBorder(newLayout); + } + + emit layoutAdded(newLayout); +} - loadLayoutTilesets(newMap->layout()); +void Project::addNewMapGroup(const QString &groupName) { + if (this->groupNames.contains(groupName)) + return; + + this->groupNames.append(groupName); + this->groupNameToMapNames.insert(groupName, QStringList()); + this->hasUnsavedDataChanges = true; - return newMap; + emit mapGroupAdded(groupName); } QString Project::getNewMapName() { @@ -1949,7 +1982,7 @@ QString Project::getNewMapName() { QString newMapName; do { newMapName = QString("NewMap%1").arg(++i); - } while (mapNames.contains(newMapName)); + } while (this->mapNames.contains(newMapName)); return newMapName; } @@ -1966,7 +1999,7 @@ Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) return qualifiers; } -QString Project::getDefaultPrimaryTilesetLabel() { +QString Project::getDefaultPrimaryTilesetLabel() const { QString defaultLabel = projectConfig.defaultPrimaryTileset; if (!this->primaryTilesetLabels.contains(defaultLabel)) { QString firstLabel = this->primaryTilesetLabels.first(); @@ -1976,7 +2009,7 @@ QString Project::getDefaultPrimaryTilesetLabel() { return defaultLabel; } -QString Project::getDefaultSecondaryTilesetLabel() { +QString Project::getDefaultSecondaryTilesetLabel() const { QString defaultLabel = projectConfig.defaultSecondaryTileset; if (!this->secondaryTilesetLabels.contains(defaultLabel)) { QString firstLabel = this->secondaryTilesetLabels.first(); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a2bcd8a6..0fd6fea9 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -66,7 +66,7 @@ QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionView } void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { - QString groupName = index.data(Qt::UserRole).toString(); + QString groupName = index.data(MapListUserRoles::NameRole).toString(); QLineEdit *le = static_cast(editor); le->setText(groupName); } @@ -74,7 +74,7 @@ void GroupNameDelegate::setEditorData(QWidget *editor, const QModelIndex &index) void GroupNameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *le = static_cast(editor); QString groupName = le->text(); - model->setData(index, groupName, Qt::UserRole); + model->setData(index, groupName, MapListUserRoles::NameRole); } void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const { @@ -112,7 +112,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { // if dropping a selection containing a group(s) and map(s), clear all selection but first group. for (const QModelIndex &index : indexes) { if (index.isValid() && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { - QString groupName = data(index, Qt::UserRole).toString(); + QString groupName = data(index, MapListUserRoles::NameRole).toString(); stream << groupName; mimeData->setData("application/porymap.mapgroupmodel.group", encodedData); mimeData->setData("application/porymap.mapgroupmodel.source.row", QByteArray::number(index.row())); @@ -122,7 +122,7 @@ QMimeData *MapGroupModel::mimeData(const QModelIndexList &indexes) const { for (const QModelIndex &index : indexes) { if (index.isValid()) { - QString mapName = data(index, Qt::UserRole).toString(); + QString mapName = data(index, MapListUserRoles::NameRole).toString(); stream << mapName; } } @@ -168,12 +168,12 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i QStringList mapsToMove; for (int i = 0; i < this->rowCount(originIndex); ++i ) { children << this->index( i, 0, originIndex); - mapsToMove << this->index( i, 0 , originIndex).data(Qt::UserRole).toString(); + mapsToMove << this->index( i, 0 , originIndex).data(MapListUserRoles::NameRole).toString(); } QModelIndex groupIndex = index(row, 0, parentIndex); QStandardItem *groupItem = this->itemFromIndex(groupIndex); - createGroupItem(groupName, row, groupItem); + createGroupItem(groupName, groupItem); for (QString mapName : mapsToMove) { QStandardItem *mapItem = createMapItem(mapName); @@ -224,43 +224,38 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i void MapGroupModel::updateProject() { if (!this->project) return; - QStringList groupNames; - QMap mapGroups; - QList groupedMapNames; + // Temporary objects in case of failure, so we won't modify the project unless it succeeds. QStringList mapNames; + QStringList groupNames; + QMap groupNameToMapNames; for (int g = 0; g < this->root->rowCount(); g++) { - QStandardItem *groupItem = this->item(g); - QString groupName = groupItem->data(Qt::UserRole).toString(); + const QStandardItem *groupItem = this->item(g); + QString groupName = groupItem->data(MapListUserRoles::NameRole).toString(); groupNames.append(groupName); - mapGroups[groupName] = g; - QStringList mapsInGroup; for (int m = 0; m < groupItem->rowCount(); m++) { - QStandardItem *mapItem = groupItem->child(m); + const QStandardItem *mapItem = groupItem->child(m); if (!mapItem) { logError("An error occured while trying to apply updates to map group structure."); return; } - QString mapName = mapItem->data(Qt::UserRole).toString(); - mapsInGroup.append(mapName); + QString mapName = mapItem->data(MapListUserRoles::NameRole).toString(); + groupNameToMapNames[groupName].append(mapName); mapNames.append(mapName); } - groupedMapNames.append(mapsInGroup); } - this->project->groupNames = groupNames; - this->project->mapGroups = mapGroups; - this->project->groupedMapNames = groupedMapNames; this->project->mapNames = mapNames; + this->project->groupNames = groupNames; + this->project->groupNameToMapNames = groupNameToMapNames; this->project->hasUnsavedDataChanges = true; } -QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *group) { +QStandardItem *MapGroupModel::createGroupItem(QString groupName, QStandardItem *group) { if (!group) group = new QStandardItem; group->setText(groupName); - group->setData(groupName, Qt::UserRole); + group->setData(groupName, MapListUserRoles::NameRole); group->setData("map_group", MapListUserRoles::TypeRole); - group->setData(groupIndex, MapListUserRoles::GroupRole); group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); this->groupItems.insert(groupName, group); return group; @@ -268,7 +263,7 @@ QStandardItem *MapGroupModel::createGroupItem(QString groupName, int groupIndex, QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { if (!map) map = new QStandardItem; - map->setData(mapName, Qt::UserRole); + map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->mapItems[mapName] = map; @@ -276,9 +271,8 @@ QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) } QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { - QStandardItem *group = createGroupItem(groupName, this->groupItems.size()); + QStandardItem *group = createGroupItem(groupName); this->root->appendRow(group); - this->updateProject(); return group; } @@ -300,15 +294,13 @@ QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) void MapGroupModel::initialize() { this->groupItems.clear(); this->mapItems.clear(); - for (int i = 0; i < this->project->groupNames.length(); i++) { - QString group_name = this->project->groupNames.value(i); - QStandardItem *group = createGroupItem(group_name, i); + + + for (const auto &groupName : this->project->groupNames) { + QStandardItem *group = createGroupItem(groupName); root->appendRow(group); - QStringList names = this->project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString map_name = names.value(j); - QStandardItem *map = createMapItem(map_name); - group->appendRow(map); + for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { + group->appendRow(createMapItem(mapName)); } } } @@ -361,7 +353,7 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { } return mapFolderIcon; } else if (type == "map_name") { - QString mapName = item->data(Qt::UserRole).toString(); + QString mapName = item->data(MapListUserRoles::NameRole).toString(); if (mapName == this->openMap) { return mapOpenedIcon; } @@ -381,10 +373,10 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_name") { - return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(Qt::UserRole).toString(); + return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(MapListUserRoles::NameRole).toString(); } else if (type == "map_group") { - return item->data(Qt::UserRole).toString(); + return item->data(MapListUserRoles::NameRole).toString(); } } @@ -392,8 +384,9 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { } bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (role == Qt::UserRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { + if (role == MapListUserRoles::NameRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { // verify uniqueness of new group name + // TODO: Check that the name is a valid symbol name (i.e. only word characters, not starting with a number) if (this->project->groupNames.contains(value.toString())) { return false; } @@ -417,18 +410,18 @@ QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) { QStandardItem *area = new QStandardItem; area->setText(mapsecName); area->setEditable(false); - area->setData(mapsecName, Qt::UserRole); + area->setData(mapsecName, MapListUserRoles::NameRole); area->setData("map_section", MapListUserRoles::TypeRole); // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->areaItems.insert(mapsecName, area); return area; } -QStandardItem *MapAreaModel::createMapItem(QString mapName, int, int) { +QStandardItem *MapAreaModel::createMapItem(QString mapName) { QStandardItem *map = new QStandardItem; map->setText(mapName); map->setEditable(false); - map->setData(mapName, Qt::UserRole); + map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->mapItems.insert(mapName, map); @@ -443,19 +436,18 @@ QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { return item; } -QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName, int groupIndex) { +QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName) { QStandardItem *area = this->areaItems[areaName]; if (!area) { return nullptr; } - int mapIndex = area->rowCount(); - QStandardItem *map = createMapItem(mapName, groupIndex, mapIndex); + QStandardItem *map = createMapItem(mapName); area->appendRow(map); return map; } void MapAreaModel::removeItem(QStandardItem *item) { - this->project->removeMapsec(item->data(Qt::UserRole).toString()); + this->project->removeMapsec(item->data(MapListUserRoles::NameRole).toString()); this->removeRow(item->row()); } @@ -467,17 +459,12 @@ void MapAreaModel::initialize() { this->root->appendRow(createAreaItem(idName)); } - for (int i = 0; i < this->project->groupNames.length(); i++) { - QStringList names = this->project->groupedMapNames.value(i); - for (int j = 0; j < names.length(); j++) { - QString mapName = names.value(j); - QStandardItem *map = createMapItem(mapName, i, j); - QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); - if (this->areaItems.contains(mapsecName)) { - this->areaItems[mapsecName]->appendRow(map); - } - } + for (const auto &mapName : this->project->mapNames) { + const QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); + if (this->areaItems.contains(mapsecName)) + this->areaItems[mapsecName]->appendRow(createMapItem(mapName)); } + this->sort(0, Qt::AscendingOrder); } @@ -529,7 +516,7 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { } return folderIcon; } else if (type == "map_name") { - QString mapName = item->data(Qt::UserRole).toString(); + QString mapName = item->data(MapListUserRoles::NameRole).toString(); if (mapName == this->openMap) { return mapOpenedIcon; } @@ -549,7 +536,7 @@ QVariant MapAreaModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_section") { - return item->data(Qt::UserRole).toString(); + return item->data(MapListUserRoles::NameRole).toString(); } } @@ -567,9 +554,9 @@ LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListMod QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { QStandardItem *layout = new QStandardItem; - layout->setText(this->project->layoutIdsToNames[layoutId]); + layout->setText(this->project->mapLayouts[layoutId]->name); layout->setEditable(false); - layout->setData(layoutId, Qt::UserRole); + layout->setData(layoutId, MapListUserRoles::NameRole); layout->setData("map_layout", MapListUserRoles::TypeRole); // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); this->layoutItems.insert(layoutId, layout); @@ -580,7 +567,7 @@ QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { QStandardItem *map = new QStandardItem; map->setText(mapName); map->setEditable(false); - map->setData(mapName, Qt::UserRole); + map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren); this->mapItems.insert(mapName, map); @@ -619,19 +606,17 @@ void LayoutTreeModel::removeItem(QStandardItem *) { void LayoutTreeModel::initialize() { this->layoutItems.clear(); this->mapItems.clear(); - for (int i = 0; i < this->project->mapLayoutsTable.length(); i++) { - QString layoutId = project->mapLayoutsTable.value(i); - QStandardItem *layoutItem = createLayoutItem(layoutId); - this->root->appendRow(layoutItem); + + for (const auto &layoutId : this->project->layoutIds) { + this->root->appendRow(createLayoutItem(layoutId)); } - for (auto mapList : this->project->groupedMapNames) { - for (auto mapName : mapList) { - QString layoutId = project->mapNameToLayoutId.value(mapName); - QStandardItem *map = createMapItem(mapName); - this->layoutItems[layoutId]->appendRow(map); - } + for (const auto &mapName : this->project->mapNames) { + QString layoutId = project->mapNameToLayoutId.value(mapName); + if (this->layoutItems.contains(layoutId)) + this->layoutItems[layoutId]->appendRow(createMapItem(mapName)); } + this->sort(0, Qt::AscendingOrder); } @@ -667,7 +652,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { QString type = item->data(MapListUserRoles::TypeRole).toString(); if (type == "map_layout") { - QString layoutId = item->data(Qt::UserRole).toString(); + QString layoutId = item->data(MapListUserRoles::NameRole).toString(); if (layoutId == this->openLayout) { return mapOpenedIcon; } diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index ed217fcb..2f972a52 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -2,6 +2,8 @@ #include "ui_newlayoutform.h" #include "project.h" +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + NewLayoutForm::NewLayoutForm(QWidget *parent) : QWidget(parent) , ui(new Ui::NewLayoutForm) @@ -10,7 +12,7 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) // TODO: Read from project? ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); - ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); + ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); @@ -119,5 +121,7 @@ bool NewLayoutForm::validateTilesets() { bool isValid = errorText.isEmpty(); ui->label_TilesetsError->setText(errorText); ui->label_TilesetsError->setVisible(!isValid); + ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!primaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); + ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!secondaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); return isValid; } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 9f0a9f18..0a14e552 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -20,8 +20,6 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : setModal(true); ui->setupUi(this); this->project = project; - this->existingLayout = false; // TODO: Replace, we can determine this from the Layout ID combo box - this->importedMap = false; ui->newLayoutForm->initUi(project); @@ -32,6 +30,7 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_MapID->setValidator(validator); + ui->comboBox_Group->setValidator(validator); // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); @@ -43,11 +42,14 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : this->headerSection->setContentLayout(sectionLayout); ui->layout_HeaderData->addWidget(this->headerSection); ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); } NewMapDialog::~NewMapDialog() { saveSettings(); + delete this->importedLayout; delete ui; } @@ -73,7 +75,7 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { this->headerForm->setLocationsDisabled(true); break; case MapListTab::Layouts: - useLayout(fieldName); + useLayoutSettings(project->mapLayouts.value(fieldName)); break; } init(); @@ -81,24 +83,23 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { // Creating new map from AdvanceMap import // TODO: Re-use for a "Duplicate Map/Layout" option? -void NewMapDialog::init(Layout *layout) { - this->importedMap = true; - useLayoutSettings(layout); +void NewMapDialog::init(Layout *layoutToCopy) { + if (this->importedLayout) + delete this->importedLayout; - // TODO: These are probably leaking - this->map = new Map(); - this->map->setLayout(new Layout()); - this->map->layout()->blockdata = layout->blockdata; + this->importedLayout = new Layout(); + this->importedLayout->blockdata = layoutToCopy->blockdata; + if (!layoutToCopy->border.isEmpty()) + this->importedLayout->border = layoutToCopy->border; - if (!layout->border.isEmpty()) { - this->map->layout()->border = layout->border; - } + useLayoutSettings(this->importedLayout); init(); } -void NewMapDialog::setDefaultSettings(Project *project) { +void NewMapDialog::setDefaultSettings(const Project *project) { settings.group = project->groupNames.at(0); settings.canFlyTo = false; + // TODO: Layout id settings.layout.width = project->getDefaultMapDimension(); settings.layout.height = project->getDefaultMapDimension(); settings.layout.borderWidth = DEFAULT_BORDER_WIDTH; @@ -128,6 +129,7 @@ void NewMapDialog::saveSettings() { void NewMapDialog::useLayoutSettings(Layout *layout) { if (!layout) return; + settings.layout.id = layout->id; settings.layout.width = layout->width; settings.layout.height = layout->height; settings.layout.borderWidth = layout->border_width; @@ -139,39 +141,24 @@ void NewMapDialog::useLayoutSettings(Layout *layout) { ui->newLayoutForm->setDisabled(true); } -void NewMapDialog::useLayout(QString layoutId) { - this->existingLayout = true; - this->layoutId = layoutId; - useLayoutSettings(project->mapLayouts.value(this->layoutId)); -} - -// TODO: Create the map group if it doesn't exist -bool NewMapDialog::validateMapGroup() { - this->group = project->groupNames.indexOf(ui->comboBox_Group->currentText()); - - QString errorText; - if (this->group < 0) { - errorText = QString("The specified map group '%1' does not exist.") - .arg(ui->comboBox_Group->currentText()); - } - - bool isValid = errorText.isEmpty(); - ui->label_GroupError->setText(errorText); - ui->label_GroupError->setVisible(!isValid); - return isValid; +// Return true if the "layout ID" field is specifying a layout that already exists. +bool NewMapDialog::isExistingLayout() const { + return this->project->mapLayouts.contains(settings.layout.id); } -bool NewMapDialog::validateID() { +bool NewMapDialog::validateID(bool allowEmpty) { QString id = ui->lineEdit_MapID->text(); + const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); QString errorText; - QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - if (!id.startsWith(expectedPrefix)) { - errorText = QString("The specified ID name '%1' must start with '%2'.").arg(id).arg(expectedPrefix); + if (id.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text()); + } else if (!id.startsWith(expectedPrefix)) { + errorText = QString("%1 '%2' must start with '%3'.").arg(ui->label_MapID->text()).arg(id).arg(expectedPrefix); } else { - for (auto i = project->mapNamesToMapConstants.constBegin(), end = project->mapNamesToMapConstants.constEnd(); i != end; i++) { + for (auto i = this->project->mapNamesToMapConstants.constBegin(), end = this->project->mapNamesToMapConstants.constEnd(); i != end; i++) { if (id == i.value()) { - errorText = QString("The specified ID name '%1' is already in use.").arg(id); + errorText = QString("%1 '%2' is already in use.").arg(ui->label_MapID->text()).arg(id); break; } } @@ -185,15 +172,17 @@ bool NewMapDialog::validateID() { } void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { - validateID(); + validateID(true); } -bool NewMapDialog::validateName() { +bool NewMapDialog::validateName(bool allowEmpty) { QString name = ui->lineEdit_Name->text(); QString errorText; - if (project->mapNames.contains(name)) { - errorText = QString("The specified map name '%1' is already in use.").arg(name); + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } else if (project->mapNames.contains(name)) { + errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); @@ -204,31 +193,53 @@ bool NewMapDialog::validateName() { } void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { - validateName(); + validateName(true); ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); } -void NewMapDialog::on_pushButton_Accept_clicked() { +bool NewMapDialog::validateGroup(bool allowEmpty) { + QString groupName = ui->comboBox_Group->currentText(); + + QString errorText; + if (groupName.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Group->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_GroupError->setText(errorText); + ui->label_GroupError->setVisible(!isValid); + ui->comboBox_Group->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewMapDialog::on_comboBox_Group_currentTextChanged(const QString &) { + validateGroup(true); +} + +void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::ResetRole) { + setDefaultSettings(this->project); // TODO: Don't allow this to change locked settings + init(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewMapDialog::accept() { saveSettings(); // Make sure to call each validation function so that all errors are shown at once. bool success = true; if (!ui->newLayoutForm->validate()) success = false; - if (!validateMapGroup()) success = false; if (!validateID()) success = false; if (!validateName()) success = false; + if (!validateGroup()) success = false; if (!success) return; - // We check if the map name is empty separately from validateName, because validateName is also used during editing. - // It's likely that users will clear the name text box while editing, and we don't want to flash errors at them for this. - if (ui->lineEdit_Name->text().isEmpty()) { - ui->label_NameError->setText("The specified map name cannot be empty."); - ui->label_NameError->setVisible(true); - ui->lineEdit_Name->setStyleSheet(lineEdit_ErrorStylesheet); - return; - } - Map *newMap = new Map; newMap->setName(ui->lineEdit_Name->text()); newMap->setConstantName(ui->lineEdit_MapID->text()); @@ -236,8 +247,9 @@ void NewMapDialog::on_pushButton_Accept_clicked() { newMap->setNeedsHealLocation(settings.canFlyTo); Layout *layout; - if (this->existingLayout) { - layout = this->project->mapLayouts.value(this->layoutId); + const bool existingLayout = isExistingLayout(); + if (existingLayout) { + layout = this->project->mapLayouts.value(settings.layout.id); newMap->setNeedsLayoutDir(false); } else { layout = new Layout; @@ -258,17 +270,15 @@ void NewMapDialog::on_pushButton_Accept_clicked() { layout->border_path = QString("%1%2/border.bin").arg(basePath, newMap->name()); layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMap->name()); } - if (this->importedMap) { - layout->blockdata = map->layout()->blockdata; - if (!map->layout()->border.isEmpty()) - layout->border = map->layout()->border; + if (this->importedLayout) { // TODO: This seems at odds with existingLayout. Would it be possible to override an existing layout? + // Copy layout data from imported layout + layout->blockdata = this->importedLayout->blockdata; + if (!this->importedLayout->border.isEmpty()) + layout->border = this->importedLayout->border; } newMap->setLayout(layout); - if (this->existingLayout) { - project->loadMapLayout(newMap); - } - map = newMap; - emit applied(); - this->close(); + this->project->addNewMap(newMap, settings.group); + emit applied(newMap->name()); + QDialog::accept(); } From bd39bcfdd297222050bdecac425d95ebd095e51a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 19 Nov 2024 14:52:47 -0500 Subject: [PATCH 12/42] Begin new layout dialog redesign --- forms/mainwindow.ui | 20 +++-- forms/newlayoutdialog.ui | 129 +++++++++++++++++++++++++++ forms/newlayoutform.ui | 8 -- forms/newmapdialog.ui | 33 ++++--- include/core/maplayout.h | 12 ++- include/mainwindow.h | 4 +- include/project.h | 16 +++- include/ui/newlayoutdialog.h | 51 +++++++++++ include/ui/newlayoutform.h | 16 +--- include/ui/newmapdialog.h | 16 +--- porymap.pro | 3 + src/mainwindow.cpp | 45 ++++++---- src/project.cpp | 129 +++++++++++++++++++-------- src/ui/newlayoutdialog.cpp | 160 ++++++++++++++++++++++++++++++++++ src/ui/newlayoutform.cpp | 19 ++-- src/ui/newmapdialog.cpp | 163 +++++++++++++++++------------------ 16 files changed, 616 insertions(+), 208 deletions(-) create mode 100644 forms/newlayoutdialog.ui create mode 100644 include/ui/newlayoutdialog.h create mode 100644 src/ui/newlayoutdialog.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index eb6c7345..d7c4abe1 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -1740,7 +1740,7 @@ 0 0 100 - 16 + 30 @@ -1834,7 +1834,7 @@ 0 0 100 - 16 + 30 @@ -1928,7 +1928,7 @@ 0 0 100 - 16 + 30 @@ -2028,7 +2028,7 @@ 0 0 100 - 16 + 30 @@ -2122,7 +2122,7 @@ 0 0 100 - 16 + 30 @@ -2700,8 +2700,8 @@ 0 0 - 204 - 16 + 100 + 30 @@ -2926,6 +2926,7 @@ + @@ -3282,6 +3283,11 @@ Grid Settings... + + + New Layout... + +
diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui new file mode 100644 index 00000000..8e623ba0 --- /dev/null +++ b/forms/newlayoutdialog.ui @@ -0,0 +1,129 @@ + + + NewLayoutDialog + + + New Map Options + + + + + + true + + + + + 0 + 0 + 238 + 146 + + + + + 10 + + + + + Layout ID + + + + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true + + + + + + + Layout Name + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + + + + + + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset + + + + + + + + NewLayoutForm + QWidget +
newlayoutform.h
+ 1 +
+
+ + +
diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui index 65183046..f51fb742 100644 --- a/forms/newlayoutform.ui +++ b/forms/newlayoutform.ui @@ -2,14 +2,6 @@ NewLayoutForm - - - 0 - 0 - 304 - 344 - - Form diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index ed52f445..ae3ba2d9 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -2,14 +2,6 @@ NewMapDialog - - - 0 - 0 - 453 - 588 - - New Map Options @@ -24,8 +16,8 @@ 0 0 - 427 - 522 + 229 + 254 @@ -69,7 +61,14 @@ - + + + true + + + QComboBox::InsertPolicy::NoInsert + + @@ -176,7 +175,7 @@ - + Layout ID @@ -206,17 +205,17 @@ - - NoScrollComboBox - QComboBox -
noscrollcombobox.h
-
NewLayoutForm QWidget
newlayoutform.h
1
+ + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 0cffcefa..caef753a 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -14,6 +14,8 @@ class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; +// TODO: Privatize members as appropriate + class Layout : public QObject { Q_OBJECT public: @@ -70,14 +72,16 @@ class Layout : public QObject { QUndoStack editHistory; // to simplify new layout settings transfer between functions - struct SimpleSettings { + // TODO: Make this the equivalent of struct MapHeader + struct Settings { QString id; QString name; int width; int height; - QString tileset_primary_label; - QString tileset_secondary_label; - QString from_id = QString(); + int borderWidth; + int borderHeight; + QString primaryTilesetLabel; + QString secondaryTilesetLabel; }; public: diff --git a/include/mainwindow.h b/include/mainwindow.h index e96c4cc3..a8dbca1f 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -23,6 +23,7 @@ #include "filterchildrenproxymodel.h" #include "maplistmodels.h" #include "newmapdialog.h" +#include "newlayoutdialog.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" @@ -188,6 +189,7 @@ private slots: void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); void openNewMapDialog(); + void openNewLayoutDialog(); void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); void onNewLayoutCreated(Layout *layout); @@ -306,6 +308,7 @@ private slots: QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; QPointer newMapDialog = nullptr; + QPointer newLayoutDialog = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; @@ -334,7 +337,6 @@ private slots: QMap lastSelectedEvent; bool isProgrammaticEventTabChange; - bool newMapDefaultsSet = false; bool tilesetNeedsRedraw = false; diff --git a/include/project.h b/include/project.h index 36c8e1e3..19b757db 100644 --- a/include/project.h +++ b/include/project.h @@ -81,6 +81,16 @@ class Project : public QObject bool wildEncountersLoaded; bool saveEmptyMapsec; + struct NewMapSettings { + QString mapName; + QString mapId; + QString group; + bool canFlyTo; + Layout::Settings layout; + MapHeader header; + }; + NewMapSettings newMapSettings; + void set_root(QString); void clearMapCache(); @@ -124,6 +134,8 @@ class Project : public QObject void addNewMapGroup(const QString &groupName); void addNewLayout(Layout* newLayout); QString getNewMapName(); + QString getNewLayoutName(); + bool isLayoutNameUnique(const QString &name); QString getProjectTitle(); bool readWildMonData(); @@ -147,7 +159,7 @@ class Project : public QObject bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); - Layout *createNewLayout(Layout::SimpleSettings &layoutSettings); + Layout *createNewLayout(const Layout::Settings &layoutSettings); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -222,6 +234,8 @@ class Project : public QObject static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + void initNewMapSettings(); + void initNewLayoutSettings(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h new file mode 100644 index 00000000..49047cc7 --- /dev/null +++ b/include/ui/newlayoutdialog.h @@ -0,0 +1,51 @@ +#ifndef NEWLAYOUTDIALOG_H +#define NEWLAYOUTDIALOG_H + +#include +#include +#include "editor.h" +#include "project.h" +#include "map.h" +#include "mapheaderform.h" +#include "newlayoutform.h" +#include "lib/collapsiblesection.h" + +namespace Ui { +class NewLayoutDialog; +} + +class NewLayoutDialog : public QDialog +{ + Q_OBJECT +public: + explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr); + ~NewLayoutDialog(); + void init(Layout *); + void accept() override; + +signals: + void applied(const QString &newLayoutId); + +private: + Ui::NewLayoutDialog *ui; + Project *project; + Layout *importedLayout = nullptr; + Layout::Settings *settings = nullptr; + + // Each of these validation functions will allow empty names up until `OK` is selected, + // because clearing the text during editing is common and we don't want to flash errors for this. + bool validateLayoutID(bool allowEmpty = false); + bool validateName(bool allowEmpty = false); + + void saveSettings(); + bool isExistingLayout() const; + void useLayoutSettings(Layout *mapLayout); + +private slots: + //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO + void dialogButtonClicked(QAbstractButton *button); + void on_lineEdit_Name_textChanged(const QString &); + void on_lineEdit_LayoutID_textChanged(const QString &); +}; + +#endif // NEWLAYOUTDIALOG_H diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index d6230f6f..6f8d9905 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -3,6 +3,8 @@ #include +#include "maplayout.h" + class Project; namespace Ui { @@ -19,18 +21,8 @@ class NewLayoutForm : public QWidget void initUi(Project *project); - struct Settings { - QString id; // TODO: Support in UI (toggleable line edit) - int width; - int height; - int borderWidth; - int borderHeight; - QString primaryTilesetLabel; - QString secondaryTilesetLabel; - }; - - void setSettings(const Settings &settings); - NewLayoutForm::Settings settings() const; + void setSettings(const Layout::Settings &settings); + Layout::Settings settings() const; void setDisabled(bool disabled); diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 2e860cba..8a769740 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -24,7 +24,6 @@ class NewMapDialog : public QDialog void init(int tabIndex, QString data); void init(Layout *); void accept() override; - static void setDefaultSettings(const Project *project); signals: void applied(const QString &newMapName); @@ -35,27 +34,20 @@ class NewMapDialog : public QDialog CollapsibleSection *headerSection; MapHeaderForm *headerForm; Layout *importedLayout = nullptr; + Project::NewMapSettings *settings = nullptr; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. - bool validateID(bool allowEmpty = false); + bool validateMapID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); bool validateGroup(bool allowEmpty = false); void saveSettings(); bool isExistingLayout() const; - void useLayoutSettings(Layout *mapLayout); - - struct Settings { - QString group; - bool canFlyTo; - NewLayoutForm::Settings layout; - MapHeader header; - }; - static struct Settings settings; + void useLayoutSettings(const Layout *mapLayout); + void useLayoutIdSettings(const QString &layoutId); private slots: - //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); void on_lineEdit_MapID_textChanged(const QString &); diff --git a/porymap.pro b/porymap.pro index c4db35d0..1cc9497c 100644 --- a/porymap.pro +++ b/porymap.pro @@ -90,6 +90,7 @@ SOURCES += src/core/block.cpp \ src/ui/movablerect.cpp \ src/ui/movementpermissionsselector.cpp \ src/ui/neweventtoolbutton.cpp \ + src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ @@ -196,6 +197,7 @@ HEADERS += include/core/block.h \ include/ui/movablerect.h \ include/ui/movementpermissionsselector.h \ include/ui/neweventtoolbutton.h \ + include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ @@ -241,6 +243,7 @@ FORMS += forms/mainwindow.ui \ forms/gridsettingsdialog.ui \ forms/mapheaderform.ui \ forms/maplisttoolbar.ui \ + forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 59b6057f..8c483f1f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -290,6 +290,8 @@ void MainWindow::initExtraSignals() { label_MapRulerStatus->setAlignment(Qt::AlignCenter); label_MapRulerStatus->setTextFormat(Qt::PlainText); label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse); + + connect(ui->actionNew_Layout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -402,6 +404,7 @@ void MainWindow::initMapList() { layout->setContentsMargins(0, 0, 0, 0); // Create add map/layout button + // TODO: Tool tip QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::on_action_NewMap_triggered); layout->addWidget(buttonAdd); @@ -442,7 +445,7 @@ void MainWindow::initMapList() { // Connect the "add folder" button in each of the map lists connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddGroup); connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddArea); - connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddLayout); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); } @@ -608,8 +611,6 @@ bool MainWindow::openProject(QString dir, bool initial) { projectConfig.projectDir = dir; projectConfig.load(); - this->newMapDefaultsSet = false; - Scripting::init(this); // Create the project @@ -920,6 +921,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // setLayout, but with a visible error message in case of failure. // Use when the user is specifically requesting a layout to open. +// TODO: Update the various functions taking layout IDs to take layout names (to mirror the equivalent map functions, this discrepancy is confusing atm) bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { QMessageBox msgBox(this); @@ -934,11 +936,6 @@ bool MainWindow::userSetLayout(QString layoutId) { } bool MainWindow::setLayout(QString layoutId) { - if (this->editor->map) - logInfo("Switching to layout-only editing mode. Disabling map-related edits."); - - unsetMap(); - // Prefer logging the name of the layout as displayed in the map list. const Layout* layout = this->editor->project ? this->editor->project->mapLayouts.value(layoutId) : nullptr; logInfo(QString("Setting layout to '%1'").arg(layout ? layout->name : layoutId)); @@ -947,6 +944,11 @@ bool MainWindow::setLayout(QString layoutId) { return false; } + if (this->editor->map) + logInfo("Switching to layout-only editing mode. Disabling map-related edits."); + + unsetMap(); + layoutTreeModel->setLayout(layoutId); refreshMapScene(); @@ -1224,6 +1226,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } if (addToFolderAction) { + // All folders only contain maps, so adding an item to any folder is adding a new map. connect(addToFolderAction, &QAction::triggered, [this, itemName] { openNewMapDialog(); this->newMapDialog->init(ui->mapListContainer->currentIndex(), itemName); @@ -1289,7 +1292,9 @@ void MainWindow::mapListAddGroup() { // (or, re-use the new map dialog with some tweaks) // TODO: This needs to take the same default settings you would get for a new map (tilesets, dimensions, etc.) // and initialize it with the same fill settings (default metatile/collision/elevation, default border) +// TODO: Remove void MainWindow::mapListAddLayout() { + /* if (!editor || !editor->project) return; QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); @@ -1361,10 +1366,10 @@ void MainWindow::mapListAddLayout() { errorMessage = "Name cannot be empty"; } // unique layout name & id - /*else if (this->editor->project->layoutIds.contains(newId->text()) + else if (this->editor->project->layoutIds.contains(newId->text()) || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { errorMessage = "Layout Name / ID is not unique"; - }*/ // TODO: Re-implement + } // from id is existing value else if (useExistingCheck->isChecked()) { if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) { @@ -1400,6 +1405,7 @@ void MainWindow::mapListAddLayout() { Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); setLayout(newLayout->id); } + */ } void MainWindow::mapListAddArea() { @@ -1408,6 +1414,7 @@ void MainWindow::mapListAddArea() { QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + // TODO: This would be a little more seamless with a single line edit that enforces the MAPSEC prefix, rather than a separate label for the actual name. const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); auto newNameEdit = new QLineEdit(&dialog); auto newNameDisplay = new QLabel(&dialog); @@ -1436,10 +1443,8 @@ void MainWindow::mapListAddArea() { QLabel *newNameEditLabel = new QLabel("New Area Name", &dialog); QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); - newNameDisplayLabel->setEnabled(false); QFormLayout form(&dialog); - form.addRow(newNameEditLabel, newNameEdit); form.addRow(newNameDisplayLabel, newNameDisplay); form.addRow("", errorMessageLabel); @@ -1499,11 +1504,10 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { this->mapGroupModel->insertGroupItem(groupName); } +// TODO: This and the new layout dialog are modal. We shouldn't need to reference their dialogs outside these open functions, +// so we should be able to remove them as members of MainWindow. +// (plus, the opening then init() call after showing for NewMapDialog is Bad) void MainWindow::openNewMapDialog() { - if (!this->newMapDefaultsSet) { - NewMapDialog::setDefaultSettings(this->editor->project); - this->newMapDefaultsSet = true; - } if (!this->newMapDialog) { this->newMapDialog = new NewMapDialog(this, this->editor->project); connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); @@ -1518,6 +1522,15 @@ void MainWindow::on_action_NewMap_triggered() { this->newMapDialog->init(); } +void MainWindow::openNewLayoutDialog() { + if (!this->newLayoutDialog) { + this->newLayoutDialog = new NewLayoutDialog(this, this->editor->project); + connect(this->newLayoutDialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + } + + openSubWindow(this->newLayoutDialog); +} + // Insert label for newly-created tileset into sorted list of existing labels int MainWindow::insertTilesetLabel(QStringList * list, QString label) { int i = 0; diff --git a/src/project.cpp b/src/project.cpp index ccd6c8a2..825c3c48 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -107,6 +107,7 @@ bool Project::load() { && readSongNames() && readMapGroups(); applyParsedLimits(); + initNewMapSettings(); return success; } @@ -368,39 +369,53 @@ bool Project::loadMapData(Map* map) { return true; } -// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout -Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - Layout *layout; - - // Handle the case where we are copying from an existing layout first. - if (!layoutSettings.from_id.isEmpty()) { - // load from layout - loadLayout(mapLayouts[layoutSettings.from_id]); +/* +void Project::addNewLayout(Layout* newLayout) { - layout = mapLayouts[layoutSettings.from_id]->copy(); - layout->name = layoutSettings.name; - layout->id = layoutSettings.id; - layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + if (newLayout->blockdata.isEmpty()) { + // Fill layout using default fill settings + setNewLayoutBlockdata(newLayout); + } + if (newLayout->border.isEmpty()) { + // Fill border using default fill settings + setNewLayoutBorder(newLayout); } - else { - layout = new Layout; - layout->name = layoutSettings.name; - layout->id = layoutSettings.id; - layout->width = layoutSettings.width; - layout->height = layoutSettings.height; - layout->border_width = DEFAULT_BORDER_WIDTH; - layout->border_height = DEFAULT_BORDER_HEIGHT; - layout->tileset_primary_label = layoutSettings.tileset_primary_label; - layout->tileset_secondary_label = layoutSettings.tileset_secondary_label; - layout->border_path = QString("%1%2/border.bin").arg(basePath, layoutSettings.name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layoutSettings.name); + emit layoutAdded(newLayout); +} +*/ - setNewLayoutBlockdata(layout); - setNewLayoutBorder(layout); +// TODO: Fold back into createNewLayout? +/* +Layout *Project::duplicateLayout(const Layout *toDuplicate) { + //TODO + if (!settings.from_id.isEmpty()) { + // load from layout + loadLayout(mapLayouts[settings.from_id]); + layout = mapLayouts[settings.from_id]->copy(); + layout->name = settings.name; + layout->id = settings.id; + layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); } +} +*/ + +// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout +Layout *Project::createNewLayout(const Layout::Settings &settings) { + Layout *layout = new Layout; + layout->id = settings.id; + layout->name = settings.name; + layout->width = settings.width; + layout->height = settings.height; + layout->border_width = settings.borderWidth; + layout->border_height = settings.borderHeight; + layout->tileset_primary_label = settings.primaryTilesetLabel; + layout->tileset_secondary_label = settings.secondaryTilesetLabel; + + const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); + layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); + layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); // Create a new directory for the layout QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); @@ -410,16 +425,8 @@ Layout *Project::createNewLayout(Layout::SimpleSettings &layoutSettings) { return nullptr; } - // TODO: Redundancy here, some of this is already handled in saveLayout > updateLayout - this->mapLayouts.insert(layout->id, layout); - this->mapLayoutsMaster.insert(layout->id, layout->copy()); - this->layoutIds.append(layout->id); - this->layoutIdsMaster.append(layout->id); - - saveLayout(layout); - - loadLayout(layout); - emit layoutAdded(layout); + addNewLayout(layout); + saveLayout(layout); // TODO: Ideally we shouldn't automatically save new layouts return layout; } @@ -1987,6 +1994,26 @@ QString Project::getNewMapName() { return newMapName; } +QString Project::getNewLayoutName() { + // Ensure default name doesn't already exist. + int i = 0; + QString newLayoutName; + do { + newLayoutName = QString("NewLayout%1").arg(++i); + } while (!isLayoutNameUnique(newLayoutName)); + + return newLayoutName; +} + +bool Project::isLayoutNameUnique(const QString &name) { + for (const auto &layout : this->mapLayouts) { + if (layout->name == name) { + return false; + } + } + return true; +} + Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) { Project::DataQualifiers qualifiers; @@ -3028,6 +3055,32 @@ void Project::applyParsedLimits() { projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1); } +void Project::initNewMapSettings() { + this->newMapSettings.group = this->groupNames.at(0); + this->newMapSettings.canFlyTo = false; + this->newMapSettings.header.setSong(this->defaultSong); + this->newMapSettings.header.setLocation(this->mapSectionIdNames.value(0, "0")); + this->newMapSettings.header.setRequiresFlash(false); + this->newMapSettings.header.setWeather(this->weatherNames.value(0, "0")); + this->newMapSettings.header.setType(this->mapTypes.value(0, "0")); + this->newMapSettings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); + this->newMapSettings.header.setShowsLocationName(true); + this->newMapSettings.header.setAllowsRunning(false); + this->newMapSettings.header.setAllowsBiking(false); + this->newMapSettings.header.setAllowsEscaping(false); + this->newMapSettings.header.setFloorNumber(0); + initNewLayoutSettings(); +} + +void Project::initNewLayoutSettings() { + this->newMapSettings.layout.width = getDefaultMapDimension(); + this->newMapSettings.layout.height = getDefaultMapDimension(); + this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH; + this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; + this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); + this->newMapSettings.layout.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); +} + bool Project::hasUnsavedChanges() { if (this->hasUnsavedDataChanges) return true; diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp new file mode 100644 index 00000000..0f67545e --- /dev/null +++ b/src/ui/newlayoutdialog.cpp @@ -0,0 +1,160 @@ +#include "newlayoutdialog.h" +#include "maplayout.h" +#include "ui_newlayoutdialog.h" +#include "config.h" + +#include +#include +#include + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewLayoutDialog::NewLayoutDialog(QWidget *parent, Project *project) : + QDialog(parent), + ui(new Ui::NewLayoutDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + ui->setupUi(this); + this->project = project; + this->settings = &project->newMapSettings.layout; + + ui->lineEdit_Name->setText(project->getNewLayoutName()); + + ui->newLayoutForm->initUi(project); + ui->newLayoutForm->setSettings(*this->settings); + + // Names and IDs can only contain word characters, and cannot start with a digit. + static const QRegularExpression re("[A-Za-z_]+[\\w]*"); + auto validator = new QRegularExpressionValidator(re, this); + ui->lineEdit_Name->setValidator(validator); + ui->lineEdit_LayoutID->setValidator(validator); + + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked); + adjustSize(); +} + +NewLayoutDialog::~NewLayoutDialog() +{ + saveSettings(); + delete this->importedLayout; + delete ui; +} + +// Creating new map from AdvanceMap import +// TODO: Re-use for a "Duplicate Layout" option? +void NewLayoutDialog::init(Layout *layoutToCopy) { + if (this->importedLayout) + delete this->importedLayout; + + this->importedLayout = new Layout(); + this->importedLayout->blockdata = layoutToCopy->blockdata; + if (!layoutToCopy->border.isEmpty()) + this->importedLayout->border = layoutToCopy->border; + + useLayoutSettings(this->importedLayout); +} + +void NewLayoutDialog::saveSettings() { + *this->settings = ui->newLayoutForm->settings(); + this->settings->id = ui->lineEdit_LayoutID->text(); + this->settings->name = ui->lineEdit_Name->text(); +} + +void NewLayoutDialog::useLayoutSettings(Layout *layout) { + if (!layout) return; + this->settings->width = layout->width; + this->settings->height = layout->height; + this->settings->borderWidth = layout->border_width; + this->settings->borderHeight = layout->border_height; + this->settings->primaryTilesetLabel = layout->tileset_primary_label; + this->settings->secondaryTilesetLabel = layout->tileset_secondary_label; + ui->newLayoutForm->setSettings(*this->settings); + + // Don't allow changes to the layout settings + ui->newLayoutForm->setDisabled(true); +} + +bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { + QString id = ui->lineEdit_LayoutID->text(); + + QString errorText; + if (id.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); + } else if (this->project->mapLayouts.contains(id)) { + errorText = QString("%1 '%2' is already in use.").arg(ui->label_LayoutID->text()).arg(id); + } + + bool isValid = errorText.isEmpty(); + ui->label_LayoutIDError->setText(errorText); + ui->label_LayoutIDError->setVisible(!isValid); + ui->lineEdit_LayoutID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewLayoutDialog::on_lineEdit_LayoutID_textChanged(const QString &) { + validateLayoutID(true); +} + +bool NewLayoutDialog::validateName(bool allowEmpty) { + QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } else if (!this->project->isLayoutNameUnique(name)) { + errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewLayoutDialog::on_lineEdit_Name_textChanged(const QString &text) { + validateName(true); + ui->lineEdit_LayoutID->setText(Layout::layoutConstantFromName(text)); +} + +void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::ResetRole) { + this->project->initNewLayoutSettings(); // TODO: Don't allow this to change locked settings + ui->newLayoutForm->setSettings(*this->settings); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewLayoutDialog::accept() { + // Make sure to call each validation function so that all errors are shown at once. + bool success = true; + if (!ui->newLayoutForm->validate()) success = false; + if (!validateLayoutID()) success = false; + if (!validateName()) success = false; + if (!success) + return; + + // Update settings from UI + saveSettings(); + + /* + if (this->importedLayout) { + // Copy layout data from imported layout + layout->blockdata = this->importedLayout->blockdata; + if (!this->importedLayout->border.isEmpty()) + layout->border = this->importedLayout->border; + } + */ + + Layout *layout = this->project->createNewLayout(*this->settings); + if (!layout) + return; + + emit applied(layout->id); + QDialog::accept(); +} diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 2f972a52..3b77b5c7 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -10,6 +10,8 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) { ui->setupUi(this); + ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); + // TODO: Read from project? ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); @@ -36,8 +38,6 @@ void NewLayoutForm::initUi(Project *project) { ui->spinBox_MapWidth->setMaximum(m_project->getMaxMapWidth()); ui->spinBox_MapHeight->setMaximum(m_project->getMaxMapHeight()); } - - ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); } void NewLayoutForm::setDisabled(bool disabled) { @@ -46,7 +46,7 @@ void NewLayoutForm::setDisabled(bool disabled) { ui->groupBox_Tilesets->setDisabled(disabled); } -void NewLayoutForm::setSettings(const Settings &settings) { +void NewLayoutForm::setSettings(const Layout::Settings &settings) { ui->spinBox_MapWidth->setValue(settings.width); ui->spinBox_MapHeight->setValue(settings.height); ui->spinBox_BorderWidth->setValue(settings.borderWidth); @@ -55,12 +55,17 @@ void NewLayoutForm::setSettings(const Settings &settings) { ui->comboBox_SecondaryTileset->setTextItem(settings.secondaryTilesetLabel); } -NewLayoutForm::Settings NewLayoutForm::settings() const { - NewLayoutForm::Settings settings; +Layout::Settings NewLayoutForm::settings() const { + Layout::Settings settings; settings.width = ui->spinBox_MapWidth->value(); settings.height = ui->spinBox_MapHeight->value(); - settings.borderWidth = ui->spinBox_BorderWidth->value(); - settings.borderHeight = ui->spinBox_BorderHeight->value(); + if (ui->groupBox_BorderDimensions->isVisible()) { + settings.borderWidth = ui->spinBox_BorderWidth->value(); + settings.borderHeight = ui->spinBox_BorderHeight->value(); + } else { + settings.borderWidth = DEFAULT_BORDER_WIDTH; + settings.borderHeight = DEFAULT_BORDER_HEIGHT; + } settings.primaryTilesetLabel = ui->comboBox_PrimaryTileset->currentText(); settings.secondaryTilesetLabel = ui->comboBox_SecondaryTileset->currentText(); return settings; diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 0a14e552..ef31a7b1 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -10,8 +10,6 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -struct NewMapDialog::Settings NewMapDialog::settings = {}; - NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : QDialog(parent), ui(new Ui::NewMapDialog) @@ -20,21 +18,26 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : setModal(true); ui->setupUi(this); this->project = project; + this->settings = &project->newMapSettings; + // Populate UI using data from project + this->settings->mapName = project->getNewMapName(); ui->newLayoutForm->initUi(project); - ui->comboBox_Group->addItems(project->groupNames); + ui->comboBox_LayoutID->addItems(project->layoutIds); - // Map names and IDs can only contain word characters, and cannot start with a digit. + // Names and IDs can only contain word characters, and cannot start with a digit. static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_MapID->setValidator(validator); ui->comboBox_Group->setValidator(validator); + ui->comboBox_LayoutID->setValidator(validator); // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); this->headerForm->init(project); + this->headerForm->setHeader(&this->settings->header); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -44,6 +47,9 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); + connect(ui->comboBox_LayoutID, &QComboBox::currentTextChanged, this, &NewMapDialog::useLayoutIdSettings); + + adjustSize(); // TODO: Save geometry? } NewMapDialog::~NewMapDialog() @@ -54,11 +60,13 @@ NewMapDialog::~NewMapDialog() } void NewMapDialog::init() { - ui->comboBox_Group->setTextItem(settings.group); - ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); - ui->newLayoutForm->setSettings(settings.layout); - this->headerForm->setHeader(&settings.header); - ui->lineEdit_Name->setText(project->getNewMapName()); + const QSignalBlocker b_LayoutId(ui->comboBox_LayoutID); + ui->comboBox_LayoutID->setCurrentText(this->settings->layout.id); + + ui->lineEdit_Name->setText(this->settings->mapName); + ui->comboBox_Group->setTextItem(this->settings->group); + ui->checkBox_CanFlyTo->setChecked(this->settings->canFlyTo); + ui->newLayoutForm->setSettings(this->settings->layout); } // Creating new map by right-clicking in the map list @@ -66,16 +74,18 @@ void NewMapDialog::init(int tabIndex, QString fieldName) { switch (tabIndex) { case MapListTab::Groups: - settings.group = fieldName; + this->settings->group = fieldName; ui->label_Group->setDisabled(true); ui->comboBox_Group->setDisabled(true); break; case MapListTab::Areas: - settings.header.setLocation(fieldName); + this->settings->header.setLocation(fieldName); this->headerForm->setLocationsDisabled(true); break; case MapListTab::Layouts: - useLayoutSettings(project->mapLayouts.value(fieldName)); + ui->label_LayoutID->setDisabled(true); + ui->comboBox_LayoutID->setDisabled(true); + useLayoutIdSettings(fieldName); break; } init(); @@ -96,57 +106,47 @@ void NewMapDialog::init(Layout *layoutToCopy) { init(); } -void NewMapDialog::setDefaultSettings(const Project *project) { - settings.group = project->groupNames.at(0); - settings.canFlyTo = false; - // TODO: Layout id - settings.layout.width = project->getDefaultMapDimension(); - settings.layout.height = project->getDefaultMapDimension(); - settings.layout.borderWidth = DEFAULT_BORDER_WIDTH; - settings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; - settings.layout.primaryTilesetLabel = project->getDefaultPrimaryTilesetLabel(); - settings.layout.secondaryTilesetLabel = project->getDefaultSecondaryTilesetLabel(); - settings.header.setSong(project->defaultSong); - settings.header.setLocation(project->mapSectionIdNames.value(0, "0")); - settings.header.setRequiresFlash(false); - settings.header.setWeather(project->weatherNames.value(0, "0")); - settings.header.setType(project->mapTypes.value(0, "0")); - settings.header.setBattleScene(project->mapBattleScenes.value(0, "0")); - settings.header.setShowsLocationName(true); - settings.header.setAllowsRunning(false); - settings.header.setAllowsBiking(false); - settings.header.setAllowsEscaping(false); - settings.header.setFloorNumber(0); -} - void NewMapDialog::saveSettings() { - settings.group = ui->comboBox_Group->currentText(); - settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); - settings.layout = ui->newLayoutForm->settings(); - settings.header = this->headerForm->headerData(); + this->settings->mapName = ui->lineEdit_Name->text(); + this->settings->mapId = ui->lineEdit_MapID->text(); + this->settings->group = ui->comboBox_Group->currentText(); + this->settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + this->settings->layout = ui->newLayoutForm->settings(); + this->settings->layout.id = ui->comboBox_LayoutID->currentText(); + this->settings->layout.name = QString("%1_Layout").arg(this->settings->mapName); + this->settings->header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } -void NewMapDialog::useLayoutSettings(Layout *layout) { - if (!layout) return; - settings.layout.id = layout->id; - settings.layout.width = layout->width; - settings.layout.height = layout->height; - settings.layout.borderWidth = layout->border_width; - settings.layout.borderHeight = layout->border_height; - settings.layout.primaryTilesetLabel = layout->tileset_primary_label; - settings.layout.secondaryTilesetLabel = layout->tileset_secondary_label; +void NewMapDialog::useLayoutSettings(const Layout *layout) { + if (!layout) { + ui->newLayoutForm->setDisabled(false); + return; + } + + this->settings->layout.width = layout->width; + this->settings->layout.height = layout->height; + this->settings->layout.borderWidth = layout->border_width; + this->settings->layout.borderHeight = layout->border_height; + this->settings->layout.primaryTilesetLabel = layout->tileset_primary_label; + this->settings->layout.secondaryTilesetLabel = layout->tileset_secondary_label; // Don't allow changes to the layout settings + ui->newLayoutForm->setSettings(this->settings->layout); ui->newLayoutForm->setDisabled(true); } +void NewMapDialog::useLayoutIdSettings(const QString &layoutId) { + this->settings->layout.id = layoutId; + useLayoutSettings(this->project->mapLayouts.value(layoutId)); +} + // Return true if the "layout ID" field is specifying a layout that already exists. bool NewMapDialog::isExistingLayout() const { - return this->project->mapLayouts.contains(settings.layout.id); + return this->project->mapLayouts.contains(this->settings->layout.id); } -bool NewMapDialog::validateID(bool allowEmpty) { +bool NewMapDialog::validateMapID(bool allowEmpty) { QString id = ui->lineEdit_MapID->text(); const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); @@ -172,7 +172,7 @@ bool NewMapDialog::validateID(bool allowEmpty) { } void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { - validateID(true); + validateMapID(true); } bool NewMapDialog::validateName(bool allowEmpty) { @@ -195,6 +195,9 @@ bool NewMapDialog::validateName(bool allowEmpty) { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); + if (ui->comboBox_LayoutID->isEnabled()) { + ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); + } } bool NewMapDialog::validateGroup(bool allowEmpty) { @@ -221,7 +224,7 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - setDefaultSettings(this->project); // TODO: Don't allow this to change locked settings + this->project->initNewMapSettings(); // TODO: Don't allow this to change locked settings init(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); @@ -229,56 +232,46 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { } void NewMapDialog::accept() { - saveSettings(); - // Make sure to call each validation function so that all errors are shown at once. bool success = true; if (!ui->newLayoutForm->validate()) success = false; - if (!validateID()) success = false; + if (!validateMapID()) success = false; if (!validateName()) success = false; if (!validateGroup()) success = false; if (!success) return; + // Update settings from UI + saveSettings(); + Map *newMap = new Map; - newMap->setName(ui->lineEdit_Name->text()); - newMap->setConstantName(ui->lineEdit_MapID->text()); - newMap->setHeader(this->headerForm->headerData()); - newMap->setNeedsHealLocation(settings.canFlyTo); + newMap->setName(this->settings->mapName); + newMap->setConstantName(this->settings->mapId); + newMap->setHeader(this->settings->header); + newMap->setNeedsHealLocation(this->settings->canFlyTo); - Layout *layout; + Layout *layout = nullptr; const bool existingLayout = isExistingLayout(); if (existingLayout) { - layout = this->project->mapLayouts.value(settings.layout.id); - newMap->setNeedsLayoutDir(false); + layout = this->project->mapLayouts.value(this->settings->layout.id); + newMap->setNeedsLayoutDir(false); // TODO: Remove this member } else { - layout = new Layout; - layout->id = Layout::layoutConstantFromName(newMap->name()); - layout->name = QString("%1_Layout").arg(newMap->name()); - layout->width = settings.layout.width; - layout->height = settings.layout.height; - if (projectConfig.useCustomBorderSize) { - layout->border_width = settings.layout.borderWidth; - layout->border_height = settings.layout.borderHeight; - } else { - layout->border_width = DEFAULT_BORDER_WIDTH; - layout->border_height = DEFAULT_BORDER_HEIGHT; + /* TODO: Re-implement (make sure this won't ever override an existing layout) + if (this->importedLayout) { + // Copy layout data from imported layout + layout->blockdata = this->importedLayout->blockdata; + if (!this->importedLayout->border.isEmpty()) + layout->border = this->importedLayout->border; } - layout->tileset_primary_label = settings.layout.primaryTilesetLabel; - layout->tileset_secondary_label = settings.layout.secondaryTilesetLabel; - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, newMap->name()); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, newMap->name()); - } - if (this->importedLayout) { // TODO: This seems at odds with existingLayout. Would it be possible to override an existing layout? - // Copy layout data from imported layout - layout->blockdata = this->importedLayout->blockdata; - if (!this->importedLayout->border.isEmpty()) - layout->border = this->importedLayout->border; + */ + layout = this->project->createNewLayout(this->settings->layout); } + if (!layout) + return; + newMap->setLayout(layout); - this->project->addNewMap(newMap, settings.group); + this->project->addNewMap(newMap, this->settings->group); emit applied(newMap->name()); QDialog::accept(); } From e7df8298434f0fc4198291e96c53547d177eb05c Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 21 Nov 2024 13:24:12 -0500 Subject: [PATCH 13/42] Group AdvanceMap parsing together, fix its tileset defaults --- forms/mainwindow.ui | 6 +- include/core/advancemapparser.h | 18 +++ include/core/mapparser.h | 16 --- include/core/metatileparser.h | 12 -- include/core/parseutil.h | 3 +- include/lib/fex/parser.h | 5 +- include/mainwindow.h | 3 +- include/project.h | 2 +- include/ui/newlayoutdialog.h | 3 +- porymap.pro | 10 +- src/core/advancemapparser.cpp | 217 ++++++++++++++++++++++++++++++++ src/core/mapparser.cpp | 97 -------------- src/core/metatileparser.cpp | 99 --------------- src/core/paletteutil.cpp | 36 +----- src/core/parseutil.cpp | 12 +- src/lib/fex/parser.cpp | 8 +- src/lib/fex/parser_util.cpp | 4 +- src/mainwindow.cpp | 22 ++-- src/project.cpp | 27 ++-- src/ui/newlayoutdialog.cpp | 35 +++--- src/ui/tileseteditor.cpp | 4 +- 21 files changed, 304 insertions(+), 335 deletions(-) create mode 100644 include/core/advancemapparser.h delete mode 100644 include/core/mapparser.h delete mode 100644 include/core/metatileparser.h create mode 100644 src/core/advancemapparser.cpp delete mode 100644 src/core/mapparser.cpp delete mode 100644 src/core/metatileparser.cpp diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index d7c4abe1..50e03e88 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2931,7 +2931,7 @@ - +
@@ -3222,9 +3222,9 @@ Open Config Folder - + - Import Map from Advance Map 1.92... + Import Layout from Advance Map 1.92... diff --git a/include/core/advancemapparser.h b/include/core/advancemapparser.h new file mode 100644 index 00000000..c3cb76cb --- /dev/null +++ b/include/core/advancemapparser.h @@ -0,0 +1,18 @@ +#ifndef ADVANCEMAPPARSER_H +#define ADVANCEMAPPARSER_H + +#include +#include +#include + +class Project; +class Layout; +class Metatile; + +namespace AdvanceMapParser { + Layout *parseLayout(const QString &filepath, bool *error, const Project *project); + QList parseMetatiles(const QString &filepath, bool *error, bool primaryTileset); + QList parsePalette(const QString &filepath, bool *error); +}; + +#endif // ADVANCEMAPPARSER_H diff --git a/include/core/mapparser.h b/include/core/mapparser.h deleted file mode 100644 index 4032154a..00000000 --- a/include/core/mapparser.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef MAPPARSER_H -#define MAPPARSER_H - -#include "maplayout.h" -#include "project.h" -#include -#include - -class MapParser -{ -public: - MapParser(); - Layout *parse(QString filepath, bool *error, Project *project); -}; - -#endif // MAPPARSER_H diff --git a/include/core/metatileparser.h b/include/core/metatileparser.h deleted file mode 100644 index b85e5b36..00000000 --- a/include/core/metatileparser.h +++ /dev/null @@ -1,12 +0,0 @@ -#pragma once -#ifndef METATILEPARSER_H -#define METATILEPARSER_H - -#include "metatile.h" -#include - -namespace MetatileParser { - QList parse(QString filepath, bool *error, bool primaryTileset); -} - -#endif // METATILEPARSER_H diff --git a/include/core/parseutil.h b/include/core/parseutil.h index e3df8375..d184646b 100644 --- a/include/core/parseutil.h +++ b/include/core/parseutil.h @@ -5,6 +5,7 @@ #include "heallocation.h" #include "log.h" #include "orderedjson.h" +#include "orderedmap.h" #include #include @@ -57,7 +58,7 @@ class ParseUtil QMap readCDefinesByRegex(const QString &filename, const QStringList ®exList); QMap readCDefinesByName(const QString &filename, const QStringList &names); QStringList readCDefineNames(const QString &filename, const QStringList ®exList); - QMap> readCStructs(const QString &, const QString & = "", const QHash = { }); + tsl::ordered_map> 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); diff --git a/include/lib/fex/parser.h b/include/lib/fex/parser.h index 6a6b9e43..c79b34a2 100644 --- a/include/lib/fex/parser.h +++ b/include/lib/fex/parser.h @@ -9,6 +9,7 @@ #include "array_value.h" #include "define_statement.h" #include "lexer.h" +#include "orderedmap.h" namespace fex { @@ -19,9 +20,9 @@ namespace fex std::vector Parse(std::vector tokens); std::vector ParseTopLevelArrays(std::vector tokens); - std::map ParseTopLevelObjects(std::vector tokens); + tsl::ordered_map ParseTopLevelObjects(std::vector tokens); - std::map ReadDefines(const std::string &filename, std::vector matching); + tsl::ordered_map ReadDefines(const std::string &filename, std::vector matching); private: int EvaluateExpression(std::vector tokens); diff --git a/include/mainwindow.h b/include/mainwindow.h index a8dbca1f..53889dff 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -194,7 +194,6 @@ private slots: void onNewMapGroupCreated(const QString &groupName); void onNewLayoutCreated(Layout *layout); void onMapLoaded(Map *map); - void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); void markMapEdited(); @@ -238,7 +237,7 @@ private slots: void on_action_Export_Map_Image_triggered(); void on_actionExport_Stitched_Map_Image_triggered(); void on_actionExport_Map_Timelapse_Image_triggered(); - void on_actionImport_Map_from_Advance_Map_1_92_triggered(); + void on_actionImport_Layout_from_Advance_Map_1_92_triggered(); void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); diff --git a/include/project.h b/include/project.h index 19b757db..c1a36a80 100644 --- a/include/project.h +++ b/include/project.h @@ -189,7 +189,7 @@ class Project : public QObject void saveTilesetMetatiles(Tileset*); void saveTilesetTilesImage(Tileset*); void saveTilesetPalettes(Tileset*); - void appendTilesetLabel(QString label, QString isSecondaryStr); + void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); bool readRegionMapSections(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index 49047cc7..c90a408b 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -20,7 +20,7 @@ class NewLayoutDialog : public QDialog public: explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr); ~NewLayoutDialog(); - void init(Layout *); + void copyFrom(const Layout &); void accept() override; signals: @@ -39,7 +39,6 @@ class NewLayoutDialog : public QDialog void saveSettings(); bool isExistingLayout() const; - void useLayoutSettings(Layout *mapLayout); private slots: //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO diff --git a/porymap.pro b/porymap.pro index 1cc9497c..1dfaf4b1 100644 --- a/porymap.pro +++ b/porymap.pro @@ -21,7 +21,8 @@ QMAKE_TARGET_BUNDLE_PREFIX = com.pret VERSION = 5.4.1 DEFINES += PORYMAP_VERSION=\\\"$$VERSION\\\" -SOURCES += src/core/block.cpp \ +SOURCES += src/core/advancemapparser.cpp \ + src/core/block.cpp \ src/core/bitpacker.cpp \ src/core/blockdata.cpp \ src/core/events.cpp \ @@ -32,9 +33,7 @@ SOURCES += src/core/block.cpp \ src/core/mapconnection.cpp \ src/core/mapheader.cpp \ src/core/maplayout.cpp \ - src/core/mapparser.cpp \ src/core/metatile.cpp \ - src/core/metatileparser.cpp \ src/core/network.cpp \ src/core/paletteutil.cpp \ src/core/parseutil.cpp \ @@ -127,7 +126,8 @@ SOURCES += src/core/block.cpp \ src/ui/updatepromoter.cpp \ src/ui/wildmonchart.cpp -HEADERS += include/core/block.h \ +HEADERS += include/core/advancemapparser.h \ + include/core/block.h \ include/core/bitpacker.h \ include/core/blockdata.h \ include/core/events.h \ @@ -139,9 +139,7 @@ HEADERS += include/core/block.h \ include/core/mapconnection.h \ include/core/mapheader.h \ include/core/maplayout.h \ - include/core/mapparser.h \ include/core/metatile.h \ - include/core/metatileparser.h \ include/core/network.h \ include/core/paletteutil.h \ include/core/parseutil.h \ diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp new file mode 100644 index 00000000..6af7476e --- /dev/null +++ b/src/core/advancemapparser.cpp @@ -0,0 +1,217 @@ +#include "advancemapparser.h" +#include "log.h" +#include "project.h" +#include "maplayout.h" + +Layout *AdvanceMapParser::parseLayout(const QString &filepath, bool *error, const Project *project) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); + return nullptr; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 20 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); + return nullptr; + } + + int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files + int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files + int numBorderTiles = borderWidth * borderHeight; // 0 if RSE + + int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header + int mapWidth = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + int mapHeight = static_cast(in.at(4)) | + (static_cast(in.at(5)) << 8) | + (static_cast(in.at(6)) << 16) | + (static_cast(in.at(7)) << 24); + int mapPrimaryTilesetNum = static_cast(in.at(8)) | + (static_cast(in.at(9)) << 8) | + (static_cast(in.at(10)) << 16) | + (static_cast(in.at(11)) << 24); + int mapSecondaryTilesetNum = static_cast(in.at(12)) | + (static_cast(in.at(13)) << 8) | + (static_cast(in.at(14)) << 16) | + (static_cast(in.at(15)) << 24); + + int numMetatiles = mapWidth * mapHeight; + int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return nullptr; + } + + Blockdata blockdata; + for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + blockdata.append(word); + } + + Blockdata border; + if (numBorderTiles != 0) { + for (int i = 20; (i + 1) < mapDataOffset; i += 2) { + uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); + border.append(word); + } + } + + Layout *mapLayout = new Layout(); + mapLayout->width = mapWidth; + mapLayout->height = mapHeight; + mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; + mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight; + + const QList tilesets = project->tilesetLabelsOrdered; + + if (mapPrimaryTilesetNum > tilesets.size()) + mapLayout->tileset_primary_label = project->getDefaultPrimaryTilesetLabel(); + else + mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); + + if (mapSecondaryTilesetNum > tilesets.size()) + mapLayout->tileset_secondary_label = project->getDefaultSecondaryTilesetLabel(); + else + mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); + + mapLayout->blockdata = blockdata; + + if (!border.isEmpty()) { + mapLayout->border = border; + } + + return mapLayout; +} + +QList AdvanceMapParser::parseMetatiles(const QString &filepath, bool *error, bool primaryTileset) +{ + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString()); + return { }; + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() < 9 || in.length() % 2 != 0) { + *error = true; + logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath)); + return { }; + } + + int projIdOffset = in.length() - 4; + int metatileSize = 16; + BaseGameVersion version; + if (in.at(projIdOffset + 0) == 'R' + && in.at(projIdOffset + 1) == 'S' + && in.at(projIdOffset + 2) == 'E' + && in.at(projIdOffset + 3) == ' ') { + // ruby and emerald are handled equally here. + version = BaseGameVersion::pokeemerald; + } else if (in.at(projIdOffset + 0) == 'F' + && in.at(projIdOffset + 1) == 'R' + && in.at(projIdOffset + 2) == 'L' + && in.at(projIdOffset + 3) == 'G') { + version = BaseGameVersion::pokefirered; + } else { + *error = true; + logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); + return { }; + } + + int attrSize = Metatile::getDefaultAttributesSize(version); + int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); + int numMetatiles = static_cast(in.at(0)) | + (static_cast(in.at(1)) << 8) | + (static_cast(in.at(2)) << 16) | + (static_cast(in.at(3)) << 24); + if (numMetatiles > maxMetatiles) { + *error = true; + logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles)); + return { }; + } + if (numMetatiles < 1) { + *error = true; + logError(QString(".bvd file contains no data for metatiles.")); + return { }; + } + + int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4; + if (in.length() != expectedFileSize) { + *error = true; + logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); + return { }; + } + + QList metatiles; + for (int i = 0; i < numMetatiles; i++) { + Metatile *metatile = new Metatile(); + QList tiles; + for (int j = 0; j < 8; j++) { + int metatileOffset = 4 + i * metatileSize + j * 2; + Tile tile(static_cast( + static_cast(in.at(metatileOffset)) | + (static_cast(in.at(metatileOffset + 1)) << 8))); + tiles.append(tile); + } + + // AdvanceMap .bvd files only contain 8 tiles of data per metatile. + // If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves. + if (projectConfig.tripleLayerMetatilesEnabled) { + Tile tile = Tile(); + for (int j = 0; j < 4; j++) + tiles.append(tile); + } + + int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); + uint32_t attributes = 0; + for (int j = 0; j < attrSize; j++) + attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); + metatile->setAttributes(attributes, version); + metatile->tiles = tiles; + metatiles.append(metatile); + } + + return metatiles; +} + +QList AdvanceMapParser::parsePalette(const QString &filepath, bool *error) { + QFile file(filepath); + if (!file.open(QIODevice::ReadOnly)) { + *error = true; + logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); + return QList(); + } + + QByteArray in = file.readAll(); + file.close(); + + if (in.length() % 4 != 0) { + *error = true; + logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); + return QList(); + } + + QList palette; + int i = 0; + while (i < in.length()) { + unsigned char red = qMin(qMax(static_cast(in.at(i + 0)), 0u), 255u); + unsigned char green = qMin(qMax(static_cast(in.at(i + 1)), 0u), 255u); + unsigned char blue = qMin(qMax(static_cast(in.at(i + 2)), 0u), 255u); + palette.append(qRgb(red, green, blue)); + i += 4; + } + + return palette; +} diff --git a/src/core/mapparser.cpp b/src/core/mapparser.cpp deleted file mode 100644 index 3d4258bd..00000000 --- a/src/core/mapparser.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "mapparser.h" -#include "config.h" -#include "log.h" -#include "project.h" - -MapParser::MapParser() -{ -} - -Layout *MapParser::parse(QString filepath, bool *error, Project *project) -{ - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 Map .map file '%1': ").arg(filepath) + file.errorString()); - return nullptr; - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() < 20 || in.length() % 2 != 0) { - *error = true; - logError(QString("Advance Map 1.92 Map .map file '%1' is an unexpected size.").arg(filepath)); - return nullptr; - } - - int borderWidth = static_cast(in.at(16)); // 0 in RSE .map files - int borderHeight = static_cast(in.at(17)); // 0 in RSE .map files - int numBorderTiles = borderWidth * borderHeight; // 0 if RSE - - int mapDataOffset = 20 + (numBorderTiles * 2); // FRLG .map files store border metatile data after the header - int mapWidth = static_cast(in.at(0)) | - (static_cast(in.at(1)) << 8) | - (static_cast(in.at(2)) << 16) | - (static_cast(in.at(3)) << 24); - int mapHeight = static_cast(in.at(4)) | - (static_cast(in.at(5)) << 8) | - (static_cast(in.at(6)) << 16) | - (static_cast(in.at(7)) << 24); - int mapPrimaryTilesetNum = static_cast(in.at(8)) | - (static_cast(in.at(9)) << 8) | - (static_cast(in.at(10)) << 16) | - (static_cast(in.at(11)) << 24); - int mapSecondaryTilesetNum = static_cast(in.at(12)) | - (static_cast(in.at(13)) << 8) | - (static_cast(in.at(14)) << 16) | - (static_cast(in.at(15)) << 24); - - int numMetatiles = mapWidth * mapHeight; - int expectedFileSize = 20 + (numBorderTiles * 2) + (numMetatiles * 2); - if (in.length() != expectedFileSize) { - *error = true; - logError(QString(".map file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); - return nullptr; - } - - Blockdata blockdata; - for (int i = mapDataOffset; (i + 1) < in.length(); i += 2) { - uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); - blockdata.append(word); - } - - Blockdata border; - if (numBorderTiles != 0) { - for (int i = 20; (i + 1) < mapDataOffset; i += 2) { - uint16_t word = static_cast((in[i] & 0xff) + ((in[i + 1] & 0xff) << 8)); - border.append(word); - } - } - - Layout *mapLayout = new Layout(); - mapLayout->width = mapWidth; - mapLayout->height = mapHeight; - mapLayout->border_width = (borderWidth == 0) ? DEFAULT_BORDER_WIDTH : borderWidth; - mapLayout->border_height = (borderHeight == 0) ? DEFAULT_BORDER_HEIGHT : borderHeight; - - QList tilesets = project->tilesetLabelsOrdered; - - if (mapPrimaryTilesetNum > tilesets.size()) - mapLayout->tileset_primary_label = tilesets.at(0); - else - mapLayout->tileset_primary_label = tilesets.at(mapPrimaryTilesetNum); - - if (mapSecondaryTilesetNum > tilesets.size()) - mapLayout->tileset_secondary_label = tilesets.at(1); - else - mapLayout->tileset_secondary_label = tilesets.at(mapSecondaryTilesetNum); - - mapLayout->blockdata = blockdata; - - if (!border.isEmpty()) { - mapLayout->border = border; - } - - return mapLayout; -} diff --git a/src/core/metatileparser.cpp b/src/core/metatileparser.cpp deleted file mode 100644 index 104b757a..00000000 --- a/src/core/metatileparser.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "metatileparser.h" -#include "config.h" -#include "log.h" -#include "project.h" -#include - -QList MetatileParser::parse(QString filepath, bool *error, bool primaryTileset) -{ - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 Metatile .bvd file '%1': ").arg(filepath) + file.errorString()); - return { }; - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() < 9 || in.length() % 2 != 0) { - *error = true; - logError(QString("Advance Map 1.92 Metatile .bvd file '%1' is an unexpected size.").arg(filepath)); - return { }; - } - - int projIdOffset = in.length() - 4; - int metatileSize = 16; - BaseGameVersion version; - if (in.at(projIdOffset + 0) == 'R' - && in.at(projIdOffset + 1) == 'S' - && in.at(projIdOffset + 2) == 'E' - && in.at(projIdOffset + 3) == ' ') { - // ruby and emerald are handled equally here. - version = BaseGameVersion::pokeemerald; - } else if (in.at(projIdOffset + 0) == 'F' - && in.at(projIdOffset + 1) == 'R' - && in.at(projIdOffset + 2) == 'L' - && in.at(projIdOffset + 3) == 'G') { - version = BaseGameVersion::pokefirered; - } else { - *error = true; - logError(QString("Detected unsupported game type from .bvd file. Last 4 bytes of file must be 'RSE ' or 'FRLG'.")); - return { }; - } - - int attrSize = Metatile::getDefaultAttributesSize(version); - int maxMetatiles = primaryTileset ? Project::getNumMetatilesPrimary() : Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary(); - int numMetatiles = static_cast(in.at(0)) | - (static_cast(in.at(1)) << 8) | - (static_cast(in.at(2)) << 16) | - (static_cast(in.at(3)) << 24); - if (numMetatiles > maxMetatiles) { - *error = true; - logError(QString(".bvd file contains data for %1 metatiles, but the maximum number of metatiles is %2.").arg(numMetatiles).arg(maxMetatiles)); - return { }; - } - if (numMetatiles < 1) { - *error = true; - logError(QString(".bvd file contains no data for metatiles.")); - return { }; - } - - int expectedFileSize = 4 + (metatileSize * numMetatiles) + (attrSize * numMetatiles) + 4; - if (in.length() != expectedFileSize) { - *error = true; - logError(QString(".bvd file is an unexpected size. Expected %1 bytes, but it has %2 bytes.").arg(expectedFileSize).arg(in.length())); - return { }; - } - - QList metatiles; - for (int i = 0; i < numMetatiles; i++) { - Metatile *metatile = new Metatile(); - QList tiles; - for (int j = 0; j < 8; j++) { - int metatileOffset = 4 + i * metatileSize + j * 2; - Tile tile(static_cast( - static_cast(in.at(metatileOffset)) | - (static_cast(in.at(metatileOffset + 1)) << 8))); - tiles.append(tile); - } - - // AdvanceMap .bvd files only contain 8 tiles of data per metatile. - // If the user has triple-layer metatiles enabled we need to fill the remaining 4 tiles ourselves. - if (projectConfig.tripleLayerMetatilesEnabled) { - Tile tile = Tile(); - for (int j = 0; j < 4; j++) - tiles.append(tile); - } - - int attrOffset = 4 + (numMetatiles * metatileSize) + (i * attrSize); - uint32_t attributes = 0; - for (int j = 0; j < attrSize; j++) - attributes |= static_cast(in.at(attrOffset + j)) << (8 * j); - metatile->setAttributes(attributes, version); - metatile->tiles = tiles; - metatiles.append(metatile); - } - - return metatiles; -} diff --git a/src/core/paletteutil.cpp b/src/core/paletteutil.cpp index da281ce6..929336b2 100644 --- a/src/core/paletteutil.cpp +++ b/src/core/paletteutil.cpp @@ -1,4 +1,5 @@ #include "paletteutil.h" +#include "advancemapparser.h" #include "log.h" #include #include @@ -6,7 +7,6 @@ QList parsePal(QString filepath, bool *error); QList parseJASC(QString filepath, bool *error); -QList parseAdvanceMapPal(QString filepath, bool *error); QList parseAdobeColorTable(QString filepath, bool *error); QList parseTileLayerPro(QString filepath, bool *error); QList parseAdvancePaletteEditor(QString filepath, bool *error); @@ -81,7 +81,7 @@ QList parsePal(QString filepath, bool *error) { return parseJASC(filepath, error); } else { file.close(); - return parseAdvanceMapPal(filepath, error); + return AdvanceMapParser::parsePalette(filepath, error); } } @@ -152,38 +152,6 @@ QList parseJASC(QString filepath, bool *error) { return palette; } -QList parseAdvanceMapPal(QString filepath, bool *error) { - QFile file(filepath); - if (!file.open(QIODevice::ReadOnly)) { - *error = true; - logError(QString("Could not open Advance Map 1.92 palette file '%1': ").arg(filepath) + file.errorString()); - return QList(); - } - - QByteArray in = file.readAll(); - file.close(); - - if (in.length() % 4 != 0) { - *error = true; - logError(QString("Advance Map 1.92 palette file '%1' had an unexpected format. File's length must be a multiple of 4, but the length is %2.").arg(filepath).arg(in.length())); - return QList(); - } - - QList palette; - int i = 0; - while (i < in.length()) { - unsigned char red = static_cast(in.at(i)); - unsigned char green = static_cast(in.at(i + 1)); - unsigned char blue = static_cast(in.at(i + 2)); - palette.append(qRgb(clampColorValue(red), - clampColorValue(green), - clampColorValue(blue))); - i += 4; - } - - return palette; -} - QList parseAdobeColorTable(QString filepath, bool *error) { QFile file(filepath); if (!file.open(QIODevice::ReadOnly)) { diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp index 9664fdc7..1aff9cd5 100644 --- a/src/core/parseutil.cpp +++ b/src/core/parseutil.cpp @@ -593,13 +593,13 @@ bool ParseUtil::gameStringToBool(QString gameString, bool * ok) { return gameStringToInt(gameString, ok) != 0; } -QMap> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash memberMap) { +tsl::ordered_map> ParseUtil::readCStructs(const QString &filename, const QString &label, const QHash &memberMap) { QString filePath = this->root + "/" + filename; auto cParser = fex::Parser(); auto tokens = fex::Lexer().LexFile(filePath.toStdString()); - auto structs = cParser.ParseTopLevelObjects(tokens); - QMap> structMaps; - for (auto it = structs.begin(); it != structs.end(); it++) { + auto topLevelObjects = cParser.ParseTopLevelObjects(tokens); + tsl::ordered_map> structs; + for (auto it = topLevelObjects.begin(); it != topLevelObjects.end(); it++) { QString structLabel = QString::fromStdString(it->first); if (structLabel.isEmpty()) continue; if (!label.isEmpty() && label != structLabel) continue; // Speed up parsing if only looking for a particular symbol @@ -617,9 +617,9 @@ QMap> ParseUtil::readCStructs(const QString &fi } i++; } - structMaps.insert(structLabel, values); + structs[structLabel] = values; } - return structMaps; + return structs; } QList ParseUtil::getLabelMacros(const QList &list, const QString &label) { diff --git a/src/lib/fex/parser.cpp b/src/lib/fex/parser.cpp index bb5c90a8..1c010528 100644 --- a/src/lib/fex/parser.cpp +++ b/src/lib/fex/parser.cpp @@ -337,9 +337,9 @@ namespace fex return DefineStatement(identifer, value); } - std::map Parser::ReadDefines(const std::string &filename, std::vector matching) + tsl::ordered_map Parser::ReadDefines(const std::string &filename, std::vector matching) { - std::map out; + tsl::ordered_map out; Lexer lexer; auto tokens = lexer.LexFile(filename); @@ -488,12 +488,12 @@ namespace fex return items; } - std::map Parser::ParseTopLevelObjects(std::vector tokens) + tsl::ordered_map Parser::ParseTopLevelObjects(std::vector tokens) { index_ = 0; tokens_ = std::move(tokens); - std::map items; + tsl::ordered_map items; while (index_ < tokens_.size()) { diff --git a/src/lib/fex/parser_util.cpp b/src/lib/fex/parser_util.cpp index 0f375b81..3a5d47f9 100644 --- a/src/lib/fex/parser_util.cpp +++ b/src/lib/fex/parser_util.cpp @@ -17,7 +17,7 @@ QStringList ParserUtil::ReadDefines(QString filename, QString prefix) fex::Parser parser; std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); + tsl::ordered_map defines = parser.ReadDefines(filepath.toStdString(), match_list); QStringList out; for(auto const& define : defines) { @@ -39,7 +39,7 @@ QStringList ParserUtil::ReadDefinesValueSort(QString filename, QString prefix) fex::Parser parser; std::vector match_list = { prefix.toStdString() + ".*" }; - std::map defines = parser.ReadDefines(filepath.toStdString(), match_list); + tsl::ordered_map defines = parser.ReadDefines(filepath.toStdString(), match_list); QMultiMap defines_keyed_by_value; for (const auto& pair : defines) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 8c483f1f..7af3d100 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -14,7 +14,7 @@ #include "editcommands.h" #include "flowlayout.h" #include "shortcut.h" -#include "mapparser.h" +#include "advancemapparser.h" #include "prefab.h" #include "montabwidget.h" #include "imageexport.h" @@ -1634,7 +1634,7 @@ void MainWindow::on_actionNew_Tileset_triggered() { int index = insertTilesetLabel(&editor->project->secondaryTilesetLabels, createTilesetDialog->fullSymbolName); this->ui->comboBox_SecondaryTileset->insertItem(index, createTilesetDialog->fullSymbolName); } - insertTilesetLabel(&editor->project->tilesetLabelsOrdered, createTilesetDialog->fullSymbolName); + editor->project->tilesetLabelsOrdered.append(createTilesetDialog->fullSymbolName); QMessageBox msgBox(this); msgBox.setText("Successfully created tileset."); @@ -2712,20 +2712,14 @@ void MainWindow::on_actionExport_Map_Timelapse_Image_triggered() { showExportMapImageWindow(ImageExporterMode::Timelapse); } -void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered(){ - importMapFromAdvanceMap1_92(); -} - -void MainWindow::importMapFromAdvanceMap1_92() -{ - QString filepath = FileDialog::getOpenFileName(this, "Import Map from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); +void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { + QString filepath = FileDialog::getOpenFileName(this, "Import Layout from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); if (filepath.isEmpty()) { return; } - MapParser parser; bool error = false; - Layout *mapLayout = parser.parse(filepath, &error, editor->project); + Layout *mapLayout = AdvanceMapParser::parseLayout(filepath, &error, editor->project); if (error) { QMessageBox msgBox(this); msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); @@ -2734,11 +2728,13 @@ void MainWindow::importMapFromAdvanceMap1_92() msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Icon::Critical); msgBox.exec(); + delete mapLayout; return; } - openNewMapDialog(); - this->newMapDialog->init(mapLayout); + openNewLayoutDialog(); + this->newLayoutDialog->copyFrom(*mapLayout); + delete mapLayout; } void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { diff --git a/src/project.cpp b/src/project.cpp index 825c3c48..2f37e24a 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1115,6 +1115,9 @@ bool Project::loadLayoutTilesets(Layout *layout) { return true; } +// TODO: We are parsing the tileset headers file whenever we load a tileset for the first time. +// At a minimum this means we're parsing the file three times per session (twice here for the first map's tilesets, once on launch in Project::readTilesetLabels). +// We can cache the header data instead and only parse it once on launch. Tileset* Project::loadTileset(QString label, Tileset *tileset) { auto memberMap = Tileset::getHeaderMemberMap(this->usingAsmTilesets); if (this->usingAsmTilesets) { @@ -1134,14 +1137,14 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { tileset->metatile_attrs_label = values.value(memberMap.key("metatileAttributes")); } else { // Read C tileset header - const auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap); + auto structs = parser.readCStructs(projectConfig.getFilePath(ProjectFilePath::tilesets_headers), label, memberMap); if (!structs.contains(label)) { return nullptr; } if (tileset == nullptr) { tileset = new Tileset; } - const auto tilesetAttributes = structs[label]; + auto tilesetAttributes = structs[label]; tileset->name = label; tileset->is_secondary = ParseUtil::gameStringToBool(tilesetAttributes.value("isSecondary")); tileset->tiles_label = tilesetAttributes.value("tiles"); @@ -1579,7 +1582,7 @@ void Project::loadTilesetMetatiles(Tileset* tileset) { } QString Project::findMetatileLabelsTileset(QString label) { - for (QString tilesetName : this->tilesetLabelsOrdered) { + for (const QString &tilesetName : this->tilesetLabelsOrdered) { QString metatileLabelPrefix = Tileset::getMetatileLabelPrefix(tilesetName); if (label.startsWith(metatileLabelPrefix)) return tilesetName; @@ -2046,7 +2049,7 @@ QString Project::getDefaultSecondaryTilesetLabel() const { return defaultLabel; } -void Project::appendTilesetLabel(QString label, QString isSecondaryStr) { +void Project::appendTilesetLabel(const QString &label, const QString &isSecondaryStr) { bool ok; bool isSecondary = ParseUtil::gameStringToBool(isSecondaryStr, &ok); if (!ok) { @@ -2080,20 +2083,18 @@ bool Project::readTilesetLabels() { QRegularExpressionMatch match = iter.next(); appendTilesetLabel(match.captured("label"), match.captured("isSecondary")); } - this->primaryTilesetLabels.sort(); - this->secondaryTilesetLabels.sort(); - this->tilesetLabelsOrdered.sort(); filename = asm_filename; // For error reporting further down } else { this->usingAsmTilesets = false; const auto structs = parser.readCStructs(filename, "", Tileset::getHeaderMemberMap(this->usingAsmTilesets)); - const QStringList labels = structs.keys(); - // TODO: This is alphabetical, AdvanceMap import wants the vanilla order in tilesetLabelsOrdered - for (const auto &tilesetLabel : labels){ - appendTilesetLabel(tilesetLabel, structs[tilesetLabel].value("isSecondary")); + for (auto i = structs.cbegin(); i != structs.cend(); i++){ + appendTilesetLabel(i.key(), i.value().value("isSecondary")); } } + this->primaryTilesetLabels.sort(); + this->secondaryTilesetLabels.sort(); + bool success = true; if (this->secondaryTilesetLabels.isEmpty()) { logError(QString("Failed to find any secondary tilesets in %1").arg(filename)); @@ -2784,7 +2785,7 @@ bool Project::readEventGraphics() { }; QString filepath = projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx_info); - const auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap); + auto gfxInfos = parser.readCStructs(filepath, "", gfxInfoMemberMap); QMap picTables = parser.readCArrayMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_pic_tables)); QMap graphicIncbins = parser.readCIncbinMulti(projectConfig.getFilePath(ProjectFilePath::data_obj_event_gfx)); @@ -2794,7 +2795,7 @@ bool Project::readEventGraphics() { if (!gfxInfos.contains(info_label)) continue; - const auto gfxInfoAttributes = gfxInfos[info_label]; + auto gfxInfoAttributes = gfxInfos[info_label]; auto eventGraphics = new EventGraphics; eventGraphics->inanimate = ParseUtil::gameStringToBool(gfxInfoAttributes.value("inanimate")); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 0f67545e..73822ba8 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -41,18 +41,27 @@ NewLayoutDialog::~NewLayoutDialog() delete ui; } -// Creating new map from AdvanceMap import +// Creating new layout from AdvanceMap import // TODO: Re-use for a "Duplicate Layout" option? -void NewLayoutDialog::init(Layout *layoutToCopy) { +void NewLayoutDialog::copyFrom(const Layout &layoutToCopy) { if (this->importedLayout) delete this->importedLayout; this->importedLayout = new Layout(); - this->importedLayout->blockdata = layoutToCopy->blockdata; - if (!layoutToCopy->border.isEmpty()) - this->importedLayout->border = layoutToCopy->border; + this->importedLayout->blockdata = layoutToCopy.blockdata; + if (!layoutToCopy.border.isEmpty()) + this->importedLayout->border = layoutToCopy.border; - useLayoutSettings(this->importedLayout); + this->settings->width = layoutToCopy.width; + this->settings->height = layoutToCopy.height; + this->settings->borderWidth = layoutToCopy.border_width; + this->settings->borderHeight = layoutToCopy.border_height; + this->settings->primaryTilesetLabel = layoutToCopy.tileset_primary_label; + this->settings->secondaryTilesetLabel = layoutToCopy.tileset_secondary_label; + + // Don't allow changes to the layout settings + ui->newLayoutForm->setSettings(*this->settings); + ui->newLayoutForm->setDisabled(true); } void NewLayoutDialog::saveSettings() { @@ -61,20 +70,6 @@ void NewLayoutDialog::saveSettings() { this->settings->name = ui->lineEdit_Name->text(); } -void NewLayoutDialog::useLayoutSettings(Layout *layout) { - if (!layout) return; - this->settings->width = layout->width; - this->settings->height = layout->height; - this->settings->borderWidth = layout->border_width; - this->settings->borderHeight = layout->border_height; - this->settings->primaryTilesetLabel = layout->tileset_primary_label; - this->settings->secondaryTilesetLabel = layout->tileset_secondary_label; - ui->newLayoutForm->setSettings(*this->settings); - - // Don't allow changes to the layout settings - ui->newLayoutForm->setDisabled(true); -} - bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { QString id = ui->lineEdit_LayoutID->text(); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index e709bed1..b6bf1735 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -2,7 +2,7 @@ #include "ui_tileseteditor.h" #include "log.h" #include "imageproviders.h" -#include "metatileparser.h" +#include "advancemapparser.h" #include "paletteutil.h" #include "imageexport.h" #include "config.h" @@ -978,7 +978,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) } bool error = false; - QList metatiles = MetatileParser::parse(filepath, &error, primary); + QList metatiles = AdvanceMapParser::parseMetatiles(filepath, &error, primary); if (error) { QMessageBox msgBox(this); msgBox.setText("Failed to import metatiles from Advance Map 1.92 .bvd file."); From d0101d807e985615771274e1eb7666ebdbf3dd5f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 21 Nov 2024 15:04:42 -0500 Subject: [PATCH 14/42] Finish new layout dialog redesign --- forms/mainwindow.ui | 4 +- forms/newlayoutdialog.ui | 87 ++++++++----- forms/newmapdialog.ui | 172 +++++++++++++++---------- include/core/maplayout.h | 7 +- include/mainwindow.h | 10 +- include/project.h | 14 +-- include/ui/mapheaderform.h | 4 +- include/ui/newlayoutdialog.h | 14 ++- include/ui/newmapdialog.h | 22 ++-- src/core/maplayout.cpp | 27 ++-- src/mainwindow.cpp | 183 ++++----------------------- src/project.cpp | 177 +++++++++++++------------- src/ui/mapheaderform.cpp | 7 +- src/ui/newlayoutdialog.cpp | 106 +++++++++------- src/ui/newmapdialog.cpp | 235 +++++++++++++++++++---------------- src/ui/newtilesetdialog.cpp | 3 +- 16 files changed, 520 insertions(+), 552 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index 50e03e88..e9a3c440 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2926,7 +2926,7 @@ - + @@ -3283,7 +3283,7 @@ Grid Settings... - + New Layout... diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index 8e623ba0..53d950fe 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -2,8 +2,16 @@ NewLayoutDialog + + + 0 + 0 + 264 + 173 + + - New Map Options + New Layout Options @@ -17,27 +25,36 @@ 0 0 238 - 146 + 106 10 - - + + + + false + + + color: rgb(255, 0, 0) + - Layout ID + + + + true - - + + + + + - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - true + <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> @@ -48,24 +65,25 @@ - - - - false - - - color: rgb(255, 0, 0) - + + - + Layout ID - + + + + + + <html><head/><body><p>The name of the new layout. The name cannot be the same as any other existing layout.</p></body></html> + + true - - + + false @@ -80,16 +98,6 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - - - @@ -107,6 +115,19 @@ + + + + color: rgb(255, 0, 0) + + + + + + true + + + diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index ae3ba2d9..e6aece18 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -2,6 +2,14 @@ NewMapDialog + + + 0 + 0 + 255 + 320 + + New Map Options @@ -17,32 +25,51 @@ 0 0 229 - 254 + 306 10 - - + + + + false + + + color: rgb(255, 0, 0) + - Map ID + + + + true - - - - Qt::Orientation::Vertical + + + + true - - - 20 - 40 - + + QComboBox::InsertPolicy::NoInsert - + + + + + + color: rgb(255, 0, 0) + + + + + + true + + @@ -60,17 +87,21 @@ - - - - true + + + + <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - QComboBox::InsertPolicy::NoInsert + + + + + + Map ID - + @@ -88,36 +119,17 @@ - - - - false - - - color: rgb(255, 0, 0) - + + - - - - true - - - - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - true + Can Fly To - - + + - Can Fly To + Map Name @@ -134,10 +146,23 @@ - - + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true + + + + + + + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> + - Map Group + @@ -157,20 +182,13 @@ - - - - Map Name - - + + - - - - <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> - + + - + Map Group @@ -181,20 +199,36 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> + + + + Qt::Orientation::Vertical - - - - + + + 20 + 40 + + + + + + + color: rgb(255, 0, 0) + + + + + + true + + + diff --git a/include/core/maplayout.h b/include/core/maplayout.h index caef753a..8dea0795 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -21,6 +21,7 @@ class Layout : public QObject { public: Layout() {} + static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); bool loaded = false; @@ -83,10 +84,10 @@ class Layout : public QObject { QString primaryTilesetLabel; QString secondaryTilesetLabel; }; + Settings settings() const; -public: - Layout *copy(); - void copyFrom(Layout *other); + Layout *copy() const; + void copyFrom(const Layout *other); int getWidth() const { return width; } int getHeight() const { return height; } diff --git a/include/mainwindow.h b/include/mainwindow.h index 53889dff..775d364e 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,8 +22,6 @@ #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" #include "maplistmodels.h" -#include "newmapdialog.h" -#include "newlayoutdialog.h" #include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" @@ -188,8 +186,6 @@ private slots: void onLayoutChanged(Layout *layout); void onOpenConnectedMap(MapConnection*); void onTilesetsSaved(QString, QString); - void openNewMapDialog(); - void openNewLayoutDialog(); void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); void onNewLayoutCreated(Layout *layout); @@ -199,7 +195,6 @@ private slots: void markMapEdited(); void markSpecificMapEdited(Map*); - void on_action_NewMap_triggered(); void on_actionNew_Tileset_triggered(); void on_action_Save_triggered(); void on_action_Exit_triggered(); @@ -306,8 +301,6 @@ private slots: QPointer regionMapEditor = nullptr; QPointer shortcutsEditor = nullptr; QPointer mapImageExporter = nullptr; - QPointer newMapDialog = nullptr; - QPointer newLayoutDialog = nullptr; QPointer preferenceEditor = nullptr; QPointer projectSettingsEditor = nullptr; QPointer gridSettingsDialog = nullptr; @@ -353,6 +346,8 @@ private slots: bool setProjectUI(); void clearProjectUI(); + void openNewMapDialog(); + void openNewLayoutDialog(); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, QString itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -371,7 +366,6 @@ private slots: void updateMapList(); void mapListAddGroup(); - void mapListAddLayout(); void mapListAddArea(); void openMapListItem(const QModelIndex &index); void saveMapListTab(int index); diff --git a/include/project.h b/include/project.h index c1a36a80..41593350 100644 --- a/include/project.h +++ b/include/project.h @@ -82,8 +82,8 @@ class Project : public QObject bool saveEmptyMapsec; struct NewMapSettings { - QString mapName; - QString mapId; + QString name; + QString id; QString group; bool canFlyTo; Layout::Settings layout; @@ -133,9 +133,9 @@ class Project : public QObject void addNewMap(Map* newMap, const QString &groupName); void addNewMapGroup(const QString &groupName); void addNewLayout(Layout* newLayout); - QString getNewMapName(); - QString getNewLayoutName(); - bool isLayoutNameUnique(const QString &name); + NewMapSettings getNewMapSettings() const; + Layout::Settings getNewLayoutSettings() const; + bool isIdentifierUnique(const QString &identifier) const; QString getProjectTitle(); bool readWildMonData(); @@ -159,7 +159,7 @@ class Project : public QObject bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); - Layout *createNewLayout(const Layout::Settings &layoutSettings); + Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -234,8 +234,6 @@ class Project : public QObject static QString getExistingFilepath(QString filepath); void applyParsedLimits(); - void initNewMapSettings(); - void initNewLayoutSettings(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 897b8e9a..136499e5 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -32,11 +32,13 @@ class MapHeaderForm : public QWidget MapHeader headerData() const; void setLocations(QStringList locations); - void setLocationsDisabled(bool disabled); + void setLocationDisabled(bool disabled); + bool isLocationDisabled() const { return m_locationDisabled; } private: Ui::MapHeaderForm *ui; QPointer m_header = nullptr; + bool m_locationDisabled = false; void updateUi(); void updateSong(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index c90a408b..ba4396b9 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -18,10 +18,11 @@ class NewLayoutDialog : public QDialog { Q_OBJECT public: - explicit NewLayoutDialog(QWidget *parent = nullptr, Project *project = nullptr); + explicit NewLayoutDialog(Project *project, QWidget *parent = nullptr); + explicit NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent = nullptr); ~NewLayoutDialog(); - void copyFrom(const Layout &); - void accept() override; + + virtual void accept() override; signals: void applied(const QString &newLayoutId); @@ -30,18 +31,21 @@ class NewLayoutDialog : public QDialog Ui::NewLayoutDialog *ui; Project *project; Layout *importedLayout = nullptr; - Layout::Settings *settings = nullptr; + + static Layout::Settings settings; + static bool initializedSettings; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. bool validateLayoutID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); + void refresh(); + void saveSettings(); bool isExistingLayout() const; private slots: - //void on_comboBox_Layout_currentTextChanged(const QString &text);//TODO void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); void on_lineEdit_LayoutID_textChanged(const QString &); diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 8a769740..cdc324e2 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -18,12 +18,12 @@ class NewMapDialog : public QDialog { Q_OBJECT public: - explicit NewMapDialog(QWidget *parent = nullptr, Project *project = nullptr); + explicit NewMapDialog(Project *project, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent = nullptr); ~NewMapDialog(); - void init(); - void init(int tabIndex, QString data); - void init(Layout *); - void accept() override; + + virtual void accept() override; signals: void applied(const QString &newMapName); @@ -33,25 +33,29 @@ class NewMapDialog : public QDialog Project *project; CollapsibleSection *headerSection; MapHeaderForm *headerForm; - Layout *importedLayout = nullptr; - Project::NewMapSettings *settings = nullptr; + Map *importedMap = nullptr; + + static Project::NewMapSettings settings; + static bool initializedSettings; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. bool validateMapID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); bool validateGroup(bool allowEmpty = false); + bool validateLayoutID(bool allowEmpty = false); + + void refresh(); void saveSettings(); - bool isExistingLayout() const; void useLayoutSettings(const Layout *mapLayout); - void useLayoutIdSettings(const QString &layoutId); private slots: void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); void on_lineEdit_MapID_textChanged(const QString &); void on_comboBox_Group_currentTextChanged(const QString &text); + void on_comboBox_LayoutID_currentTextChanged(const QString &text); }; #endif // NEWMAPDIALOG_H diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 11e9943d..921c0894 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -7,13 +7,13 @@ -Layout *Layout::copy() { +Layout *Layout::copy() const { Layout *layout = new Layout; layout->copyFrom(this); return layout; } -void Layout::copyFrom(Layout *other) { +void Layout::copyFrom(const Layout *other) { this->id = other->id; this->name = other->name; this->width = other->width; @@ -30,19 +30,30 @@ void Layout::copyFrom(Layout *other) { this->border = other->border; } +QString Layout::layoutNameFromMapName(const QString &mapName) { + return QString("%1_Layout").arg(mapName); +} + QString Layout::layoutConstantFromName(QString mapName) { // Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'. static const QRegularExpression caseChange("([a-z])([A-Z])"); QString nameWithUnderscores = mapName.replace(caseChange, "\\1_\\2"); QString withMapAndUppercase = "LAYOUT_" + nameWithUnderscores.toUpper(); static const QRegularExpression underscores("_+"); - QString constantName = withMapAndUppercase.replace(underscores, "_"); - - // Handle special cases. - // SSTidal should be SS_TIDAL, rather than SSTIDAL - constantName = constantName.replace("SSTIDAL", "SS_TIDAL"); + return withMapAndUppercase.replace(underscores, "_"); +} - return constantName; +Layout::Settings Layout::settings() const { + Layout::Settings settings; + settings.id = this->id; + settings.name = this->name; + settings.width = this->width; + settings.height = this->height; + settings.borderWidth = this->border_width; + settings.borderHeight = this->border_height; + settings.primaryTilesetLabel = this->tileset_primary_label; + settings.secondaryTilesetLabel = this->tileset_secondary_label; + return settings; } bool Layout::isWithinBounds(int x, int y) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 7af3d100..41f85724 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -23,6 +23,8 @@ #include "newmapconnectiondialog.h" #include "config.h" #include "filedialog.h" +#include "newmapdialog.h" +#include "newlayoutdialog.h" #include #include @@ -291,7 +293,8 @@ void MainWindow::initExtraSignals() { label_MapRulerStatus->setTextFormat(Qt::PlainText); label_MapRulerStatus->setTextInteractionFlags(Qt::TextSelectableByMouse); - connect(ui->actionNew_Layout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); + connect(ui->action_NewMap, &QAction::triggered, this, &MainWindow::openNewMapDialog); + connect(ui->action_NewLayout, &QAction::triggered, this, &MainWindow::openNewLayoutDialog); } void MainWindow::on_actionCheck_for_Updates_triggered() { @@ -406,7 +409,7 @@ void MainWindow::initMapList() { // Create add map/layout button // TODO: Tool tip QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); - connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::on_action_NewMap_triggered); + connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::openNewMapDialog); layout->addWidget(buttonAdd); /* TODO: Remove button disabled, no current support for deleting maps/layouts @@ -932,6 +935,10 @@ bool MainWindow::userSetLayout(QString layoutId) { msgBox.critical(nullptr, "Error Opening Layout", errorMsg); return false; } + + // Only the Layouts tab of the map list shows Layouts, so if we're not already on that tab we'll open it now. + ui->mapListContainer->setCurrentIndex(MapListTab::Layouts); + return true; } @@ -1228,8 +1235,9 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (addToFolderAction) { // All folders only contain maps, so adding an item to any folder is adding a new map. connect(addToFolderAction, &QAction::triggered, [this, itemName] { - openNewMapDialog(); - this->newMapDialog->init(ui->mapListContainer->currentIndex(), itemName); + auto dialog = new NewMapDialog(this->editor->project, ui->mapListContainer->currentIndex(), itemName, this); + connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); + dialog->open(); }); } if (deleteFolderAction) { @@ -1267,8 +1275,8 @@ void MainWindow::mapListAddGroup() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ const QString mapGroupName = newNameEdit->text(); - if (this->editor->project->groupNames.contains(mapGroupName)) { - errorMessageLabel->setText(QString("A map group with the name '%1' already exists").arg(mapGroupName)); + if (!this->editor->project->isIdentifierUnique(mapGroupName)) { + errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(mapGroupName)); errorMessageLabel->setVisible(true); } else { dialog.accept(); @@ -1288,126 +1296,6 @@ void MainWindow::mapListAddGroup() { } } -// TODO: Pull this all out into a custom window. Connect that to an action in the main menu as well. -// (or, re-use the new map dialog with some tweaks) -// TODO: This needs to take the same default settings you would get for a new map (tilesets, dimensions, etc.) -// and initialize it with the same fill settings (default metatile/collision/elevation, default border) -// TODO: Remove -void MainWindow::mapListAddLayout() { - /* - if (!editor || !editor->project) return; - - QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::ApplicationModal); - QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - - QLineEdit *newNameEdit = new QLineEdit(&dialog); - newNameEdit->setClearButtonEnabled(true); - - static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); - newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); - - // TODO: Support arbitrary LAYOUT_ ID names (Note from GriffinR: This is already handled in an unopened PR) - QLabel *newId = new QLabel("LAYOUT_", &dialog); - connect(newNameEdit, &QLineEdit::textChanged, [&](QString text){ - newId->setText(Layout::layoutConstantFromName(text.remove("_Layout"))); - }); - - NoScrollComboBox *useExistingCombo = new NoScrollComboBox(&dialog); - useExistingCombo->addItems(this->editor->project->layoutIds); - useExistingCombo->setEnabled(false); - - QCheckBox *useExistingCheck = new QCheckBox(&dialog); - - QLabel *errorMessageLabel = new QLabel(&dialog); - errorMessageLabel->setVisible(false); - errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - - QComboBox *primaryCombo = new QComboBox(&dialog); - primaryCombo->addItems(this->editor->project->primaryTilesetLabels); - QComboBox *secondaryCombo = new QComboBox(&dialog); - secondaryCombo->addItems(this->editor->project->secondaryTilesetLabels); - - QSpinBox *widthSpin = new QSpinBox(&dialog); - QSpinBox *heightSpin = new QSpinBox(&dialog); - - widthSpin->setMinimum(1); - heightSpin->setMinimum(1); - widthSpin->setMaximum(this->editor->project->getMaxMapWidth()); - heightSpin->setMaximum(this->editor->project->getMaxMapHeight()); - - connect(useExistingCheck, &QCheckBox::stateChanged, [&](int state){ - bool useExisting = (state == Qt::Checked); - useExistingCombo->setEnabled(useExisting); - primaryCombo->setEnabled(!useExisting); - secondaryCombo->setEnabled(!useExisting); - widthSpin->setEnabled(!useExisting); - heightSpin->setEnabled(!useExisting); - }); - - QFormLayout form(&dialog); - form.addRow("New Layout Name", newNameEdit); - form.addRow("New Layout ID", newId); - form.addRow("Copy Existing Layout", useExistingCheck); - form.addRow("", useExistingCombo); - form.addRow("Primary Tileset", primaryCombo); - form.addRow("Secondary Tileset", secondaryCombo); - form.addRow("Layout Width", widthSpin); - form.addRow("Layout Height", heightSpin); - form.addRow("", errorMessageLabel); - - connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - // verify some things - QString errorMessage; - QString tryLayoutName = newNameEdit->text(); - // name not empty - if (tryLayoutName.isEmpty()) { - errorMessage = "Name cannot be empty"; - } - // unique layout name & id - else if (this->editor->project->layoutIds.contains(newId->text()) - || this->editor->project->layoutIdsToNames.find(tryLayoutName) != this->editor->project->layoutIdsToNames.end()) { - errorMessage = "Layout Name / ID is not unique"; - } - // from id is existing value - else if (useExistingCheck->isChecked()) { - if (!this->editor->project->layoutIds.contains(useExistingCombo->currentText())) { - errorMessage = "Existing layout ID is not valid"; - } - } - - if (!errorMessage.isEmpty()) { - // show error - errorMessageLabel->setText(errorMessage); - errorMessageLabel->setVisible(true); - } - else { - dialog.accept(); - } - }); - - form.addRow(&newItemButtonBox); - - if (dialog.exec() == QDialog::Accepted) { - Layout::SimpleSettings layoutSettings; - QString layoutName = newNameEdit->text(); - layoutSettings.name = layoutName; - layoutSettings.id = Layout::layoutConstantFromName(layoutName.remove("_Layout")); - if (useExistingCheck->isChecked()) { - layoutSettings.from_id = useExistingCombo->currentText(); - } else { - layoutSettings.width = widthSpin->value(); - layoutSettings.height = heightSpin->value(); - layoutSettings.tileset_primary_label = primaryCombo->currentText(); - layoutSettings.tileset_secondary_label = secondaryCombo->currentText(); - } - Layout *newLayout = this->editor->project->createNewLayout(layoutSettings); - setLayout(newLayout->id); - } - */ -} - void MainWindow::mapListAddArea() { QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); dialog.setWindowModality(Qt::ApplicationModal); @@ -1433,8 +1321,8 @@ void MainWindow::mapListAddArea() { connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ const QString newAreaName = newNameDisplay->text(); - if (this->editor->project->mapSectionIdNames.contains(newAreaName)){ - errorMessageLabel->setText(QString("An area with the name '%1' already exists").arg(newAreaName)); + if (!this->editor->project->isIdentifierUnique(newAreaName)) { + errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(newAreaName)); errorMessageLabel->setVisible(true); } else { dialog.accept(); @@ -1485,6 +1373,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { } } +// Called any time a new layout is created (including as a byproduct of creating a new map) void MainWindow::onNewLayoutCreated(Layout *layout) { logInfo(QString("Created a new layout named %1.").arg(layout->name)); @@ -1504,31 +1393,16 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { this->mapGroupModel->insertGroupItem(groupName); } -// TODO: This and the new layout dialog are modal. We shouldn't need to reference their dialogs outside these open functions, -// so we should be able to remove them as members of MainWindow. -// (plus, the opening then init() call after showing for NewMapDialog is Bad) void MainWindow::openNewMapDialog() { - if (!this->newMapDialog) { - this->newMapDialog = new NewMapDialog(this, this->editor->project); - connect(this->newMapDialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); - } - - openSubWindow(this->newMapDialog); -} - -void MainWindow::on_action_NewMap_triggered() { - openNewMapDialog(); - //this->newMapDialog->initUi();//TODO - this->newMapDialog->init(); + auto dialog = new NewMapDialog(this->editor->project, this); + connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); + dialog->open(); } void MainWindow::openNewLayoutDialog() { - if (!this->newLayoutDialog) { - this->newLayoutDialog = new NewLayoutDialog(this, this->editor->project); - connect(this->newLayoutDialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); - } - - openSubWindow(this->newLayoutDialog); + auto dialog = new NewLayoutDialog(this->editor->project, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); } // Insert label for newly-created tileset into sorted list of existing labels @@ -2732,8 +2606,9 @@ void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { return; } - openNewLayoutDialog(); - this->newLayoutDialog->copyFrom(*mapLayout); + auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); delete mapLayout; } @@ -2756,7 +2631,7 @@ void MainWindow::on_pushButton_AddConnection_clicked() { auto dialog = new NewMapConnectionDialog(this, this->editor->map, this->editor->project->mapNames); connect(dialog, &NewMapConnectionDialog::accepted, this->editor, &Editor::addConnection); - dialog->exec(); + dialog->open(); } void MainWindow::on_pushButton_NewWildMonGroup_clicked() { @@ -3244,10 +3119,6 @@ bool MainWindow::closeSupplementaryWindows() { return false; this->mapImageExporter = nullptr; - if (this->newMapDialog && !this->newMapDialog->close()) - return false; - this->newMapDialog = nullptr; - if (this->shortcutsEditor && !this->shortcutsEditor->close()) return false; this->shortcutsEditor = nullptr; diff --git a/src/project.cpp b/src/project.cpp index 2f37e24a..9976c549 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -107,7 +107,6 @@ bool Project::load() { && readSongNames() && readMapGroups(); applyParsedLimits(); - initNewMapSettings(); return success; } @@ -348,12 +347,7 @@ bool Project::loadMapData(Map* map) { const QString direction = ParseUtil::jsonToQString(connectionObj["direction"]); const int offset = ParseUtil::jsonToInt(connectionObj["offset"]); const QString mapConstant = ParseUtil::jsonToQString(connectionObj["map"]); - if (this->mapConstantsToMapNames.contains(mapConstant)) { - // Successully read map connection - map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant), direction, offset)); - } else { - logError(QString("Failed to find connected map for map constant '%1'").arg(mapConstant)); - } + map->loadConnection(new MapConnection(this->mapConstantsToMapNames.value(mapConstant, mapConstant), direction, offset)); } } @@ -369,40 +363,7 @@ bool Project::loadMapData(Map* map) { return true; } -/* -void Project::addNewLayout(Layout* newLayout) { - - if (newLayout->blockdata.isEmpty()) { - // Fill layout using default fill settings - setNewLayoutBlockdata(newLayout); - } - if (newLayout->border.isEmpty()) { - // Fill border using default fill settings - setNewLayoutBorder(newLayout); - } - - emit layoutAdded(newLayout); -} -*/ - -// TODO: Fold back into createNewLayout? -/* -Layout *Project::duplicateLayout(const Layout *toDuplicate) { - //TODO - if (!settings.from_id.isEmpty()) { - // load from layout - loadLayout(mapLayouts[settings.from_id]); - layout = mapLayouts[settings.from_id]->copy(); - layout->name = settings.name; - layout->id = settings.id; - layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); - } -} -*/ - -// TODO: Refactor, we're duplicating logic between here, the new map dialog, and addNewLayout -Layout *Project::createNewLayout(const Layout::Settings &settings) { +Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout *toDuplicate) { Layout *layout = new Layout; layout->id = settings.id; layout->name = settings.name; @@ -413,6 +374,13 @@ Layout *Project::createNewLayout(const Layout::Settings &settings) { layout->tileset_primary_label = settings.primaryTilesetLabel; layout->tileset_secondary_label = settings.secondaryTilesetLabel; + if (toDuplicate) { + // If we're duplicating an existing layout we'll copy over the blockdata. + // Otherwise addNewLayout will fill our new layout using the default settings. + layout->blockdata = toDuplicate->blockdata; + layout->border = toDuplicate->border; + } + const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); @@ -1276,6 +1244,7 @@ void Project::saveMap(Map *map) { } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); + // TODO: Either simplify this redundancy or explain why we need it (to create folders without the _Layout suffix) if (map->needsLayoutDir()) { QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); if (!QDir::root().mkdir(newLayoutDir)) { @@ -1314,19 +1283,15 @@ void Project::saveMap(Map *map) { mapObj["battle_scene"] = map->header()->battleScene(); // Connections - auto connections = map->getConnections(); + const auto connections = map->getConnections(); if (connections.length() > 0) { OrderedJson::array connectionsArr; - for (auto connection : connections) { - if (this->mapNamesToMapConstants.contains(connection->targetMapName())) { - OrderedJson::object connectionObj; - connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName()); - connectionObj["offset"] = connection->offset(); - connectionObj["direction"] = connection->direction(); - connectionsArr.append(connectionObj); - } else { - logError(QString("Failed to write map connection. '%1' is not a valid map name").arg(connection->targetMapName())); - } + for (const auto &connection : connections) { + OrderedJson::object connectionObj; + connectionObj["map"] = this->mapNamesToMapConstants.value(connection->targetMapName(), connection->targetMapName()); + connectionObj["offset"] = connection->offset(); + connectionObj["direction"] = connection->direction(); + connectionsArr.append(connectionObj); } mapObj["connections"] = connectionsArr; } else { @@ -1986,31 +1951,81 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } -QString Project::getNewMapName() { - // Ensure default name doesn't already exist. +Project::NewMapSettings Project::getNewMapSettings() const { + // Ensure default name/ID doesn't already exist. int i = 0; QString newMapName; + QString newMapId; do { newMapName = QString("NewMap%1").arg(++i); - } while (this->mapNames.contains(newMapName)); - - return newMapName; -} - -QString Project::getNewLayoutName() { - // Ensure default name doesn't already exist. + newMapId = Map::mapConstantFromName(newMapName); + } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(newMapId)); + + NewMapSettings settings; + settings.name = newMapName; + settings.id = newMapId; + settings.group = this->groupNames.at(0); + settings.canFlyTo = false; + settings.layout = getNewLayoutSettings(); + settings.layout.id = Layout::layoutConstantFromName(newMapName); + settings.layout.name = Layout::layoutNameFromMapName(newMapName); + settings.header.setSong(this->defaultSong); + settings.header.setLocation(this->mapSectionIdNames.value(0, "0")); + settings.header.setRequiresFlash(false); + settings.header.setWeather(this->weatherNames.value(0, "0")); + settings.header.setType(this->mapTypes.value(0, "0")); + settings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); + settings.header.setShowsLocationName(true); + settings.header.setAllowsRunning(false); + settings.header.setAllowsBiking(false); + settings.header.setAllowsEscaping(false); + settings.header.setFloorNumber(0); + return settings; +} + +Layout::Settings Project::getNewLayoutSettings() const { + // Ensure default name/ID doesn't already exist. int i = 0; QString newLayoutName; + QString newLayoutId; do { newLayoutName = QString("NewLayout%1").arg(++i); - } while (!isLayoutNameUnique(newLayoutName)); - - return newLayoutName; -} - -bool Project::isLayoutNameUnique(const QString &name) { + newLayoutId = Layout::layoutConstantFromName(newLayoutName); + } while (!isIdentifierUnique(newLayoutId) || !isIdentifierUnique(newLayoutName)); + + Layout::Settings settings; + settings.name = newLayoutName; + settings.id = newLayoutId; + settings.width = getDefaultMapDimension(); + settings.height = getDefaultMapDimension(); + settings.borderWidth = DEFAULT_BORDER_WIDTH; + settings.borderHeight = DEFAULT_BORDER_HEIGHT; + settings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); + settings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); + return settings; +} + +// When we ask the user to provide a new identifier for something (like a map/layout name or ID) +// we use this to make sure that it doesn't collide with any known identifiers first. +// Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap. +// In general this only matters to Porymap if the identifier will be added to the group it collides with, +// but name collisions are likely undesirable in the project. +// TODO: Use elsewhere +bool Project::isIdentifierUnique(const QString &identifier) const { + if (this->mapNames.contains(identifier)) + return false; + if (this->mapConstantsToMapNames.contains(identifier)) + return false; + if (this->groupNames.contains(identifier)) + return false; + if (this->mapSectionIdNames.contains(identifier)) + return false; + if (this->tilesetLabelsOrdered.contains(identifier)) + return false; + if (this->layoutIds.contains(identifier)) + return false; for (const auto &layout : this->mapLayouts) { - if (layout->name == name) { + if (layout->name == identifier) { return false; } } @@ -3056,32 +3071,6 @@ void Project::applyParsedLimits() { projectConfig.collisionSheetWidth = qMin(projectConfig.collisionSheetWidth, Block::getMaxCollision() + 1); } -void Project::initNewMapSettings() { - this->newMapSettings.group = this->groupNames.at(0); - this->newMapSettings.canFlyTo = false; - this->newMapSettings.header.setSong(this->defaultSong); - this->newMapSettings.header.setLocation(this->mapSectionIdNames.value(0, "0")); - this->newMapSettings.header.setRequiresFlash(false); - this->newMapSettings.header.setWeather(this->weatherNames.value(0, "0")); - this->newMapSettings.header.setType(this->mapTypes.value(0, "0")); - this->newMapSettings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); - this->newMapSettings.header.setShowsLocationName(true); - this->newMapSettings.header.setAllowsRunning(false); - this->newMapSettings.header.setAllowsBiking(false); - this->newMapSettings.header.setAllowsEscaping(false); - this->newMapSettings.header.setFloorNumber(0); - initNewLayoutSettings(); -} - -void Project::initNewLayoutSettings() { - this->newMapSettings.layout.width = getDefaultMapDimension(); - this->newMapSettings.layout.height = getDefaultMapDimension(); - this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH; - this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; - this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); - this->newMapSettings.layout.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); -} - bool Project::hasUnsavedChanges() { if (this->hasUnsavedDataChanges) return true; diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 1fd9084c..fa0e2c1c 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -154,9 +154,10 @@ MapHeader MapHeaderForm::headerData() const { return header; } -void MapHeaderForm::setLocationsDisabled(bool disabled) { - ui->label_Location->setDisabled(disabled); - ui->comboBox_Location->setDisabled(disabled); +void MapHeaderForm::setLocationDisabled(bool disabled) { + m_locationDisabled = disabled; + ui->label_Location->setDisabled(m_locationDisabled); + ui->comboBox_Location->setDisabled(m_locationDisabled); } void MapHeaderForm::updateSong() { diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 73822ba8..3cf7b9a9 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -9,31 +9,55 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewLayoutDialog::NewLayoutDialog(QWidget *parent, Project *project) : +Layout::Settings NewLayoutDialog::settings = {}; +bool NewLayoutDialog::initializedSettings = false; + +NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : QDialog(parent), ui(new Ui::NewLayoutDialog) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); + ui->label_GenericError->setVisible(false); this->project = project; - this->settings = &project->newMapSettings.layout; - - ui->lineEdit_Name->setText(project->getNewLayoutName()); + Layout::Settings newSettings = project->getNewLayoutSettings(); + if (!initializedSettings) { + // The first time this dialog is opened we initialize all the default settings. + settings = newSettings; + initializedSettings = true; + } else { + // On subsequent openings we only initialize the settings that should be unique, + // preserving all other settings from the last time the dialog was open. + settings.name = newSettings.name; + settings.id = newSettings.id; + } ui->newLayoutForm->initUi(project); - ui->newLayoutForm->setSettings(*this->settings); - // Names and IDs can only contain word characters, and cannot start with a digit. + // Identifiers can only contain word characters, and cannot start with a digit. static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_LayoutID->setValidator(validator); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked); + + refresh(); adjustSize(); } +// Creating new layout from AdvanceMap import +// TODO: Re-use for a "Duplicate Layout" option +NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layout, QWidget *parent) : + NewLayoutDialog(project, parent) +{ + if (layout) { + this->importedLayout = layout->copy(); + refresh(); + } +} + NewLayoutDialog::~NewLayoutDialog() { saveSettings(); @@ -41,33 +65,24 @@ NewLayoutDialog::~NewLayoutDialog() delete ui; } -// Creating new layout from AdvanceMap import -// TODO: Re-use for a "Duplicate Layout" option? -void NewLayoutDialog::copyFrom(const Layout &layoutToCopy) { - if (this->importedLayout) - delete this->importedLayout; - - this->importedLayout = new Layout(); - this->importedLayout->blockdata = layoutToCopy.blockdata; - if (!layoutToCopy.border.isEmpty()) - this->importedLayout->border = layoutToCopy.border; - - this->settings->width = layoutToCopy.width; - this->settings->height = layoutToCopy.height; - this->settings->borderWidth = layoutToCopy.border_width; - this->settings->borderHeight = layoutToCopy.border_height; - this->settings->primaryTilesetLabel = layoutToCopy.tileset_primary_label; - this->settings->secondaryTilesetLabel = layoutToCopy.tileset_secondary_label; - - // Don't allow changes to the layout settings - ui->newLayoutForm->setSettings(*this->settings); - ui->newLayoutForm->setDisabled(true); +void NewLayoutDialog::refresh() { + if (this->importedLayout) { + // If we're importing a layout then some settings will be enforced. + ui->newLayoutForm->setSettings(this->importedLayout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { + ui->newLayoutForm->setSettings(settings); + ui->newLayoutForm->setDisabled(false); + } + + ui->lineEdit_Name->setText(settings.name); + ui->lineEdit_LayoutID->setText(settings.id); } void NewLayoutDialog::saveSettings() { - *this->settings = ui->newLayoutForm->settings(); - this->settings->id = ui->lineEdit_LayoutID->text(); - this->settings->name = ui->lineEdit_Name->text(); + settings = ui->newLayoutForm->settings(); + settings.id = ui->lineEdit_LayoutID->text(); + settings.name = ui->lineEdit_Name->text(); } bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { @@ -76,8 +91,8 @@ bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { QString errorText; if (id.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); - } else if (this->project->mapLayouts.contains(id)) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_LayoutID->text()).arg(id); + } else if (!this->project->isIdentifierUnique(id)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_LayoutID->text()).arg(id); } bool isValid = errorText.isEmpty(); @@ -97,8 +112,8 @@ bool NewLayoutDialog::validateName(bool allowEmpty) { QString errorText; if (name.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (!this->project->isLayoutNameUnique(name)) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); @@ -110,6 +125,8 @@ bool NewLayoutDialog::validateName(bool allowEmpty) { void NewLayoutDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); + + // Changing the layout name updates the layout ID field to match. ui->lineEdit_LayoutID->setText(Layout::layoutConstantFromName(text)); } @@ -118,8 +135,8 @@ void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - this->project->initNewLayoutSettings(); // TODO: Don't allow this to change locked settings - ui->newLayoutForm->setSettings(*this->settings); + settings = this->project->getNewLayoutSettings(); + refresh(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -137,18 +154,13 @@ void NewLayoutDialog::accept() { // Update settings from UI saveSettings(); - /* - if (this->importedLayout) { - // Copy layout data from imported layout - layout->blockdata = this->importedLayout->blockdata; - if (!this->importedLayout->border.isEmpty()) - layout->border = this->importedLayout->border; - } - */ - - Layout *layout = this->project->createNewLayout(*this->settings); - if (!layout) + Layout *layout = this->project->createNewLayout(settings, this->importedLayout); + if (!layout) { + ui->label_GenericError->setText(QString("Failed to create layout. See %1 for details.").arg(getLogPath())); + ui->label_GenericError->setVisible(true); return; + } + ui->label_GenericError->setVisible(false); emit applied(layout->id); QDialog::accept(); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index ef31a7b1..a97fd2ab 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -10,23 +10,36 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : +Project::NewMapSettings NewMapDialog::settings = {}; +bool NewMapDialog::initializedSettings = false; + +NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : QDialog(parent), ui(new Ui::NewMapDialog) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); + ui->label_GenericError->setVisible(false); this->project = project; - this->settings = &project->newMapSettings; - // Populate UI using data from project - this->settings->mapName = project->getNewMapName(); + Project::NewMapSettings newSettings = project->getNewMapSettings(); + if (!initializedSettings) { + // The first time this dialog is opened we initialize all the default settings. + settings = newSettings; + initializedSettings = true; + } else { + // On subsequent openings we only initialize the settings that should be unique, + // preserving all other settings from the last time the dialog was open. + settings.name = newSettings.name; + settings.id = newSettings.id; + } ui->newLayoutForm->initUi(project); + ui->comboBox_Group->addItems(project->groupNames); ui->comboBox_LayoutID->addItems(project->layoutIds); - // Names and IDs can only contain word characters, and cannot start with a digit. + // Identifiers can only contain word characters, and cannot start with a digit. static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); @@ -37,7 +50,7 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); this->headerForm->init(project); - this->headerForm->setHeader(&this->settings->header); + this->headerForm->setHeader(&settings.header); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -47,103 +60,91 @@ NewMapDialog::NewMapDialog(QWidget *parent, Project *project) : ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); - connect(ui->comboBox_LayoutID, &QComboBox::currentTextChanged, this, &NewMapDialog::useLayoutIdSettings); + refresh(); adjustSize(); // TODO: Save geometry? } -NewMapDialog::~NewMapDialog() +// Adding new map to existing map list folder. +NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) : + NewMapDialog(project, parent) { - saveSettings(); - delete this->importedLayout; - delete ui; -} - -void NewMapDialog::init() { - const QSignalBlocker b_LayoutId(ui->comboBox_LayoutID); - ui->comboBox_LayoutID->setCurrentText(this->settings->layout.id); - - ui->lineEdit_Name->setText(this->settings->mapName); - ui->comboBox_Group->setTextItem(this->settings->group); - ui->checkBox_CanFlyTo->setChecked(this->settings->canFlyTo); - ui->newLayoutForm->setSettings(this->settings->layout); -} - -// Creating new map by right-clicking in the map list -void NewMapDialog::init(int tabIndex, QString fieldName) { - switch (tabIndex) + switch (mapListTab) { case MapListTab::Groups: - this->settings->group = fieldName; + settings.group = mapListItem; ui->label_Group->setDisabled(true); ui->comboBox_Group->setDisabled(true); + ui->comboBox_Group->setTextItem(settings.group); break; case MapListTab::Areas: - this->settings->header.setLocation(fieldName); - this->headerForm->setLocationsDisabled(true); + settings.header.setLocation(mapListItem); + this->headerForm->setLocationDisabled(true); + // Header UI is kept in sync automatically by MapHeaderForm break; case MapListTab::Layouts: + settings.layout.id = mapListItem; ui->label_LayoutID->setDisabled(true); ui->comboBox_LayoutID->setDisabled(true); - useLayoutIdSettings(fieldName); + ui->comboBox_LayoutID->setTextItem(settings.layout.id); break; } - init(); } -// Creating new map from AdvanceMap import -// TODO: Re-use for a "Duplicate Map/Layout" option? -void NewMapDialog::init(Layout *layoutToCopy) { - if (this->importedLayout) - delete this->importedLayout; +// TODO: Use for a "Duplicate Map" option +NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : + NewMapDialog(project, parent) +{ + /* + if (this->importedMap) + delete this->importedMap; - this->importedLayout = new Layout(); - this->importedLayout->blockdata = layoutToCopy->blockdata; - if (!layoutToCopy->border.isEmpty()) - this->importedLayout->border = layoutToCopy->border; + this->importedMap = new Map(mapToCopy); + useLayoutSettings(this->importedMap->layout()); + */ +} - useLayoutSettings(this->importedLayout); - init(); +NewMapDialog::~NewMapDialog() +{ + saveSettings(); + delete this->importedMap; + delete ui; +} + +// Sync UI with settings. If any UI elements are disabled (because their settings are being enforced) +// then we don't update them using the settings here. +void NewMapDialog::refresh() { + ui->lineEdit_Name->setText(settings.name); + ui->lineEdit_MapID->setText(settings.id); + + ui->comboBox_Group->setTextItem(settings.group); + ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); + ui->comboBox_LayoutID->setTextItem(settings.layout.id); + ui->newLayoutForm->setSettings(settings.layout); + // Header UI is kept in sync automatically by MapHeaderForm } void NewMapDialog::saveSettings() { - this->settings->mapName = ui->lineEdit_Name->text(); - this->settings->mapId = ui->lineEdit_MapID->text(); - this->settings->group = ui->comboBox_Group->currentText(); - this->settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); - this->settings->layout = ui->newLayoutForm->settings(); - this->settings->layout.id = ui->comboBox_LayoutID->currentText(); - this->settings->layout.name = QString("%1_Layout").arg(this->settings->mapName); - this->settings->header = this->headerForm->headerData(); + settings.name = ui->lineEdit_Name->text(); + settings.id = ui->lineEdit_MapID->text(); + settings.group = ui->comboBox_Group->currentText(); + settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + settings.layout = ui->newLayoutForm->settings(); + settings.layout.id = ui->comboBox_LayoutID->currentText(); + // We don't provide full control for naming new layouts here (just via the ID). + // If a user wants to explicitly name a layout they can create it individually before creating the map. + settings.layout.name = Layout::layoutNameFromMapName(settings.name); + settings.header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } void NewMapDialog::useLayoutSettings(const Layout *layout) { - if (!layout) { + if (layout) { + ui->newLayoutForm->setSettings(layout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { ui->newLayoutForm->setDisabled(false); - return; } - - this->settings->layout.width = layout->width; - this->settings->layout.height = layout->height; - this->settings->layout.borderWidth = layout->border_width; - this->settings->layout.borderHeight = layout->border_height; - this->settings->layout.primaryTilesetLabel = layout->tileset_primary_label; - this->settings->layout.secondaryTilesetLabel = layout->tileset_secondary_label; - - // Don't allow changes to the layout settings - ui->newLayoutForm->setSettings(this->settings->layout); - ui->newLayoutForm->setDisabled(true); -} - -void NewMapDialog::useLayoutIdSettings(const QString &layoutId) { - this->settings->layout.id = layoutId; - useLayoutSettings(this->project->mapLayouts.value(layoutId)); -} - -// Return true if the "layout ID" field is specifying a layout that already exists. -bool NewMapDialog::isExistingLayout() const { - return this->project->mapLayouts.contains(this->settings->layout.id); } bool NewMapDialog::validateMapID(bool allowEmpty) { @@ -155,13 +156,8 @@ bool NewMapDialog::validateMapID(bool allowEmpty) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text()); } else if (!id.startsWith(expectedPrefix)) { errorText = QString("%1 '%2' must start with '%3'.").arg(ui->label_MapID->text()).arg(id).arg(expectedPrefix); - } else { - for (auto i = this->project->mapNamesToMapConstants.constBegin(), end = this->project->mapNamesToMapConstants.constEnd(); i != end; i++) { - if (id == i.value()) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_MapID->text()).arg(id); - break; - } - } + } else if (!this->project->isIdentifierUnique(id)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id); } bool isValid = errorText.isEmpty(); @@ -181,8 +177,8 @@ bool NewMapDialog::validateName(bool allowEmpty) { QString errorText; if (name.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (project->mapNames.contains(name)) { - errorText = QString("%1 '%2' is already in use.").arg(ui->label_Name->text()).arg(name); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); @@ -206,6 +202,8 @@ bool NewMapDialog::validateGroup(bool allowEmpty) { QString errorText; if (groupName.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Group->text()); + } else if (!this->project->groupNames.contains(groupName) && !this->project->isIdentifierUnique(groupName)) { + errorText = QString("%1 must either be the name of an existing map group, or a unique identifier for a new map group.").arg(ui->label_Group->text()); } bool isValid = errorText.isEmpty(); @@ -219,13 +217,43 @@ void NewMapDialog::on_comboBox_Group_currentTextChanged(const QString &) { validateGroup(true); } +bool NewMapDialog::validateLayoutID(bool allowEmpty) { + QString layoutId = ui->comboBox_LayoutID->currentText(); + + QString errorText; + if (layoutId.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); + } else if (!this->project->layoutIds.contains(layoutId) && !this->project->isIdentifierUnique(layoutId)) { + errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text()); + } + + bool isValid = errorText.isEmpty(); + ui->label_LayoutIDError->setText(errorText); + ui->label_LayoutIDError->setVisible(!isValid); + ui->comboBox_LayoutID->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { + validateLayoutID(true); + useLayoutSettings(this->project->mapLayouts.value(text)); +} + void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { auto role = ui->buttonBox->buttonRole(button); if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - this->project->initNewMapSettings(); // TODO: Don't allow this to change locked settings - init(); + auto newSettings = this->project->getNewMapSettings(); + + // If the location setting is disabled we need to enforce that setting on the new header. + if (this->headerForm->isLocationDisabled()) + newSettings.header.setLocation(settings.header.location()); + + settings = newSettings; + this->headerForm->setHeader(&settings.header); // TODO: Unnecessary? + refresh(); + } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -238,6 +266,7 @@ void NewMapDialog::accept() { if (!validateMapID()) success = false; if (!validateName()) success = false; if (!validateGroup()) success = false; + if (!validateLayoutID()) success = false; if (!success) return; @@ -245,33 +274,29 @@ void NewMapDialog::accept() { saveSettings(); Map *newMap = new Map; - newMap->setName(this->settings->mapName); - newMap->setConstantName(this->settings->mapId); - newMap->setHeader(this->settings->header); - newMap->setNeedsHealLocation(this->settings->canFlyTo); - - Layout *layout = nullptr; - const bool existingLayout = isExistingLayout(); - if (existingLayout) { - layout = this->project->mapLayouts.value(this->settings->layout.id); - newMap->setNeedsLayoutDir(false); // TODO: Remove this member + newMap->setName(settings.name); + newMap->setConstantName(settings.id); + newMap->setHeader(settings.header); + newMap->setNeedsHealLocation(settings.canFlyTo); + + Layout *layout = this->project->mapLayouts.value(settings.layout.id); + if (layout) { + // Layout already exists + newMap->setNeedsLayoutDir(false); // TODO: Remove this member? } else { - /* TODO: Re-implement (make sure this won't ever override an existing layout) - if (this->importedLayout) { - // Copy layout data from imported layout - layout->blockdata = this->importedLayout->blockdata; - if (!this->importedLayout->border.isEmpty()) - layout->border = this->importedLayout->border; - } - */ - layout = this->project->createNewLayout(this->settings->layout); + layout = this->project->createNewLayout(settings.layout); } - if (!layout) + if (!layout) { + ui->label_GenericError->setText(QString("Failed to create layout for map. See %1 for details.").arg(getLogPath())); + ui->label_GenericError->setVisible(true); + delete newMap; return; + } + ui->label_GenericError->setVisible(false); newMap->setLayout(layout); - this->project->addNewMap(newMap, this->settings->group); + this->project->addNewMap(newMap, settings.group); emit applied(newMap->name()); QDialog::accept(); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index e9eee946..8cb4c5f9 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -10,7 +10,7 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : this->setFixedSize(this->width(), this->height()); this->project = project; //only allow characters valid for a symbol - static const QRegularExpression expression("[_A-Za-z0-9]+$"); + static const QRegularExpression expression("[_A-Za-z0-9]+$"); // TODO: Incorrect, allows digits at beginning QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); this->ui->nameLineEdit->setValidator(validator); @@ -35,6 +35,7 @@ void NewTilesetDialog::SecondaryChanged(){ NameOrSecondaryChanged(); } +// TODO: No validation void NewTilesetDialog::NameOrSecondaryChanged() { this->friendlyName = this->ui->nameLineEdit->text(); this->fullSymbolName = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + this->friendlyName; From 4671321690f008eafbe719675e924e30df07235b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 22 Nov 2024 15:17:25 -0500 Subject: [PATCH 15/42] Add item to duplicate map/layouts from list --- include/core/map.h | 3 +- include/core/mapheader.h | 22 ++-- include/core/maplayout.h | 1 + include/project.h | 8 +- include/ui/mapheaderform.h | 41 ++++--- include/ui/newmapdialog.h | 8 +- src/core/map.cpp | 30 ++++- src/core/mapheader.cpp | 33 ++---- src/core/maplayout.cpp | 4 +- src/mainwindow.cpp | 20 +++- src/project.cpp | 132 ++++++++++----------- src/ui/mapheaderform.cpp | 234 +++++++++++++------------------------ src/ui/newlayoutdialog.cpp | 25 +++- src/ui/newmapdialog.cpp | 106 ++++++----------- 14 files changed, 306 insertions(+), 361 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 55eee303..00e7e63c 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -35,6 +35,7 @@ class Map : public QObject Q_OBJECT public: explicit Map(QObject *parent = nullptr); + explicit Map(const Map &other, QObject *parent = nullptr); ~Map(); public: @@ -125,7 +126,7 @@ class Map : public QObject bool m_scriptsLoaded = false; QMap> m_events; - QList m_ownedEvents; // for memory management + QSet m_ownedEvents; // for memory management QList m_metatileLayerOrder; QList m_metatileLayerOpacity; diff --git a/include/core/mapheader.h b/include/core/mapheader.h index ee2c354e..2058280a 100644 --- a/include/core/mapheader.h +++ b/include/core/mapheader.h @@ -53,17 +53,17 @@ class MapHeader : public QObject QString battleScene() const { return m_battleScene; } signals: - void songChanged(QString, QString); - void locationChanged(QString, QString); - void requiresFlashChanged(bool, bool); - void weatherChanged(QString, QString); - void typeChanged(QString, QString); - void showsLocationNameChanged(bool, bool); - void allowsRunningChanged(bool, bool); - void allowsBikingChanged(bool, bool); - void allowsEscapingChanged(bool, bool); - void floorNumberChanged(int, int); - void battleSceneChanged(QString, QString); + void songChanged(QString); + void locationChanged(QString); + void requiresFlashChanged(bool); + void weatherChanged(QString); + void typeChanged(QString); + void showsLocationNameChanged(bool); + void allowsRunningChanged(bool); + void allowsBikingChanged(bool); + void allowsEscapingChanged(bool); + void floorNumberChanged(int); + void battleSceneChanged(QString); void modified(); private: diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 8dea0795..cfd093ef 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -20,6 +20,7 @@ class Layout : public QObject { Q_OBJECT public: Layout() {} + Layout(const Layout &other); static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); diff --git a/include/project.h b/include/project.h index 41593350..e65b755f 100644 --- a/include/project.h +++ b/include/project.h @@ -132,7 +132,8 @@ class Project : public QObject bool readMapGroups(); void addNewMap(Map* newMap, const QString &groupName); void addNewMapGroup(const QString &groupName); - void addNewLayout(Layout* newLayout); + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); + Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); NewMapSettings getNewMapSettings() const; Layout::Settings getNewLayoutSettings() const; bool isIdentifierUnique(const QString &identifier) const; @@ -159,7 +160,6 @@ class Project : public QObject bool loadMapData(Map*); bool readMapLayouts(); Layout *loadLayout(QString layoutId); - Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); bool loadLayout(Layout *); bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); @@ -277,9 +277,9 @@ class Project : public QObject void fileChanged(QString filepath); void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); - void mapAdded(Map *newMap, const QString &groupName); + void mapCreated(Map *newMap, const QString &groupName); void mapGroupAdded(const QString &groupName); - void layoutAdded(Layout *newLayout); + void layoutCreated(Layout *newLayout); }; #endif // PROJECT_H diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 136499e5..13e1b67d 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -29,29 +29,38 @@ class MapHeaderForm : public QWidget void clear(); void setHeader(MapHeader *header); + void setHeaderData(const MapHeader &header); MapHeader headerData() const; + void setSong(const QString &song); + void setLocation(const QString &location); + void setRequiresFlash(bool requiresFlash); + void setWeather(const QString &weather); + void setType(const QString &type); + void setBattleScene(const QString &battleScene); + void setShowsLocationName(bool showsLocationName); + void setAllowsRunning(bool allowsRunning); + void setAllowsBiking(bool allowsBiking); + void setAllowsEscaping(bool allowsEscaping); + void setFloorNumber(int floorNumber); + + QString song() const; + QString location() const; + bool requiresFlash() const; + QString weather() const; + QString type() const; + QString battleScene() const; + bool showsLocationName() const; + bool allowsRunning() const; + bool allowsBiking() const; + bool allowsEscaping() const; + int floorNumber() const; + void setLocations(QStringList locations); - void setLocationDisabled(bool disabled); - bool isLocationDisabled() const { return m_locationDisabled; } private: Ui::MapHeaderForm *ui; QPointer m_header = nullptr; - bool m_locationDisabled = false; - - void updateUi(); - void updateSong(); - void updateLocation(); - void updateRequiresFlash(); - void updateWeather(); - void updateType(); - void updateBattleScene(); - void updateShowsLocationName(); - void updateAllowsRunning(); - void updateAllowsBiking(); - void updateAllowsEscaping(); - void updateFloorNumber(); void onSongUpdated(const QString &song); void onLocationChanged(const QString &location); diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index cdc324e2..15dd3f6c 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -25,9 +25,6 @@ class NewMapDialog : public QDialog virtual void accept() override; -signals: - void applied(const QString &newMapName); - private: Ui::NewMapDialog *ui; Project *project; @@ -45,10 +42,9 @@ class NewMapDialog : public QDialog bool validateGroup(bool allowEmpty = false); bool validateLayoutID(bool allowEmpty = false); - void refresh(); - + void setUI(const Project::NewMapSettings &settings); void saveSettings(); - void useLayoutSettings(const Layout *mapLayout); + void setLayout(const Layout *mapLayout); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/core/map.cpp b/src/core/map.cpp index 1d7c4555..a42faa27 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -13,7 +13,6 @@ Map::Map(QObject *parent) : QObject(parent) { - m_scriptsLoaded = false; m_editHistory = new QUndoStack(this); resetEvents(); @@ -21,6 +20,33 @@ Map::Map(QObject *parent) : QObject(parent) connect(m_header, &MapHeader::modified, this, &Map::modified); } +Map::Map(const Map &other, QObject *parent) : Map(parent) { + m_name = other.m_name; + m_constantName = other.m_constantName; + m_layoutId = other.m_layoutId; + m_sharedEventsMap = other.m_sharedEventsMap; + m_sharedScriptsMap = other.m_sharedScriptsMap; + m_customAttributes = other.m_customAttributes; + *m_header = *other.m_header; + m_layout = other.m_layout; + m_isPersistedToFile = false; + m_metatileLayerOrder = other.m_metatileLayerOrder; + m_metatileLayerOpacity = other.m_metatileLayerOpacity; + + // Copy events + for (auto i = other.m_events.constBegin(); i != other.m_events.constEnd(); i++) { + QList newEvents; + for (const auto &event : i.value()) { + auto newEvent = event->duplicate(); + m_ownedEvents.insert(newEvent); + newEvents.append(newEvent); + } + m_events[i.key()] = newEvents; + } + + // Duplicating the map connections is probably not desirable, so we skip them. +} + Map::~Map() { qDeleteAll(m_ownedEvents); m_ownedEvents.clear(); @@ -201,7 +227,7 @@ void Map::removeEvent(Event *event) { void Map::addEvent(Event *event) { event->setMap(this); m_events[event->getEventGroup()].append(event); - if (!m_ownedEvents.contains(event)) m_ownedEvents.append(event); + if (!m_ownedEvents.contains(event)) m_ownedEvents.insert(event); } int Map::getIndexOfEvent(Event *event) const { diff --git a/src/core/mapheader.cpp b/src/core/mapheader.cpp index 4c0bc06e..689a52dc 100644 --- a/src/core/mapheader.cpp +++ b/src/core/mapheader.cpp @@ -35,98 +35,87 @@ MapHeader &MapHeader::operator=(const MapHeader &other) { void MapHeader::setSong(const QString &song) { if (m_song == song) return; - auto before = m_song; m_song = song; - emit songChanged(before, m_song); + emit songChanged(m_song); emit modified(); } void MapHeader::setLocation(const QString &location) { if (m_location == location) return; - auto before = m_location; m_location = location; - emit locationChanged(before, m_location); + emit locationChanged(m_location); emit modified(); } void MapHeader::setRequiresFlash(bool requiresFlash) { if (m_requiresFlash == requiresFlash) return; - auto before = m_requiresFlash; m_requiresFlash = requiresFlash; - emit requiresFlashChanged(before, m_requiresFlash); + emit requiresFlashChanged(m_requiresFlash); emit modified(); } void MapHeader::setWeather(const QString &weather) { if (m_weather == weather) return; - auto before = m_weather; m_weather = weather; - emit weatherChanged(before, m_weather); + emit weatherChanged(m_weather); emit modified(); } void MapHeader::setType(const QString &type) { if (m_type == type) return; - auto before = m_type; m_type = type; - emit typeChanged(before, m_type); + emit typeChanged(m_type); emit modified(); } void MapHeader::setShowsLocationName(bool showsLocationName) { if (m_showsLocationName == showsLocationName) return; - auto before = m_showsLocationName; m_showsLocationName = showsLocationName; - emit showsLocationNameChanged(before, m_showsLocationName); + emit showsLocationNameChanged(m_showsLocationName); emit modified(); } void MapHeader::setAllowsRunning(bool allowsRunning) { if (m_allowsRunning == allowsRunning) return; - auto before = m_allowsRunning; m_allowsRunning = allowsRunning; - emit allowsRunningChanged(before, m_allowsRunning); + emit allowsRunningChanged(m_allowsRunning); emit modified(); } void MapHeader::setAllowsBiking(bool allowsBiking) { if (m_allowsBiking == allowsBiking) return; - auto before = m_allowsBiking; m_allowsBiking = allowsBiking; - emit allowsBikingChanged(before, m_allowsBiking); + emit allowsBikingChanged(m_allowsBiking); emit modified(); } void MapHeader::setAllowsEscaping(bool allowsEscaping) { if (m_allowsEscaping == allowsEscaping) return; - auto before = m_allowsEscaping; m_allowsEscaping = allowsEscaping; - emit allowsEscapingChanged(before, m_allowsEscaping); + emit allowsEscapingChanged(m_allowsEscaping); emit modified(); } void MapHeader::setFloorNumber(int floorNumber) { if (m_floorNumber == floorNumber) return; - auto before = m_floorNumber; m_floorNumber = floorNumber; - emit floorNumberChanged(before, m_floorNumber); + emit floorNumberChanged(m_floorNumber); emit modified(); } void MapHeader::setBattleScene(const QString &battleScene) { if (m_battleScene == battleScene) return; - auto before = m_battleScene; m_battleScene = battleScene; - emit battleSceneChanged(before, m_battleScene); + emit battleSceneChanged(m_battleScene); emit modified(); } diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 921c0894..c30ae798 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -5,7 +5,9 @@ #include "scripting.h" #include "imageproviders.h" - +Layout::Layout(const Layout &other) : Layout() { + copyFrom(&other); +} Layout *Layout::copy() const { Layout *layout = new Layout; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 41f85724..17f05726 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -621,9 +621,9 @@ bool MainWindow::openProject(QString dir, bool initial) { project->set_root(dir); connect(project, &Project::fileChanged, this, &MainWindow::showFileWatcherWarning); connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); - connect(project, &Project::mapAdded, this, &MainWindow::onNewMapCreated); + connect(project, &Project::mapCreated, this, &MainWindow::onNewMapCreated); + connect(project, &Project::layoutCreated, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); - connect(project, &Project::layoutAdded, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); @@ -1210,6 +1210,10 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); + connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { + auto dialog = new NewMapDialog(this->editor->project, this->editor->project->getMap(itemName), this); + dialog->open(); + }); //menu.addSeparator(); //connect(menu.addAction("Delete Map"), &QAction::triggered, [this, index] { deleteMapListItem(index); }); // TODO: No support for deleting maps } else if (itemType == "map_group") { @@ -1227,6 +1231,14 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } else if (itemType == "map_layout") { // Right-clicking on a map layout openItemAction = menu.addAction("Open Layout"); + connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { + auto layout = this->editor->project->loadLayout(itemName); + if (layout) { + auto dialog = new NewLayoutDialog(this->editor->project, layout, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); + } + }); addToFolderAction = menu.addAction("Add New Map with Layout"); //menu.addSeparator(); //deleteFolderAction = menu.addAction("Delete Layout"); // TODO: No support for deleting layouts @@ -1236,7 +1248,6 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { // All folders only contain maps, so adding an item to any folder is adding a new map. connect(addToFolderAction, &QAction::triggered, [this, itemName] { auto dialog = new NewMapDialog(this->editor->project, ui->mapListContainer->currentIndex(), itemName, this); - connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); dialog->open(); }); } @@ -1371,6 +1382,8 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { editor->project->saveHealLocations(newMap); editor->save(); } + + userSetMap(newMap->name()); } // Called any time a new layout is created (including as a byproduct of creating a new map) @@ -1395,7 +1408,6 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); - connect(dialog, &NewMapDialog::applied, this, &MainWindow::userSetMap); dialog->open(); } diff --git a/src/project.cpp b/src/project.cpp index 9976c549..cf80369e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -363,8 +363,59 @@ bool Project::loadMapData(Map* map) { return true; } +Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* toDuplicate) { + Map *map = toDuplicate ? new Map(*toDuplicate) : new Map; + map->setName(settings.name); + map->setConstantName(settings.id); + map->setHeader(settings.header); + map->setNeedsHealLocation(settings.canFlyTo); + + Layout *layout = this->mapLayouts.value(settings.layout.id); + if (layout) { + // Layout already exists + map->setNeedsLayoutDir(false); // TODO: Remove this member? + } else { + layout = createNewLayout(settings.layout); + } + if (!layout) { + delete map; + return nullptr; + } + map->setLayout(layout); + + // Make sure we keep the order of the map names the same as in the map group order. + int mapNamePos; + if (this->groupNames.contains(settings.group)) { + mapNamePos = 0; + for (const auto &name : this->groupNames) { + mapNamePos += this->groupNameToMapNames[name].length(); + if (name == settings.group) + break; + } + } else { + // Adding map to a map group that doesn't exist yet. + // Create the group, and we already know the map will be last in the list. + addNewMapGroup(settings.group); + mapNamePos = this->mapNames.length(); + } + + this->mapNames.insert(mapNamePos, map->name()); + this->groupNameToMapNames[settings.group].append(map->name()); + this->mapConstantsToMapNames.insert(map->constantName(), map->name()); + this->mapNamesToMapConstants.insert(map->name(), map->constantName()); + + map->setIsPersistedToFile(false); + + emit mapCreated(map, settings.group); + + return map; +} + Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout *toDuplicate) { - Layout *layout = new Layout; + if (this->layoutIds.contains(settings.id)) + return nullptr; + + Layout *layout = toDuplicate ? new Layout(*toDuplicate) : new Layout(); layout->id = settings.id; layout->name = settings.name; layout->width = settings.width; @@ -374,13 +425,6 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout layout->tileset_primary_label = settings.primaryTilesetLabel; layout->tileset_secondary_label = settings.secondaryTilesetLabel; - if (toDuplicate) { - // If we're duplicating an existing layout we'll copy over the blockdata. - // Otherwise addNewLayout will fill our new layout using the default settings. - layout->blockdata = toDuplicate->blockdata; - layout->border = toDuplicate->border; - } - const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); @@ -393,9 +437,22 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout return nullptr; } - addNewLayout(layout); + this->mapLayouts.insert(layout->id, layout); + this->layoutIds.append(layout->id); + + if (layout->blockdata.isEmpty()) { + // Fill layout using default fill settings + setNewLayoutBlockdata(layout); + } + if (layout->border.isEmpty()) { + // Fill border using default fill settings + setNewLayoutBorder(layout); + } + saveLayout(layout); // TODO: Ideally we shouldn't automatically save new layouts + emit layoutCreated(layout); + return layout; } @@ -1886,60 +1943,6 @@ bool Project::readMapGroups() { return true; } -void Project::addNewMap(Map *newMap, const QString &groupName) { - if (!newMap) - return; - - // Make sure we keep the order of the map names the same as in the map group order. - int mapNamePos; - if (this->groupNames.contains(groupName)) { - mapNamePos = 0; - for (const auto &name : this->groupNames) { - mapNamePos += this->groupNameToMapNames[name].length(); - if (name == groupName) - break; - } - } else { - // Adding map to a map group that doesn't exist yet. - // Create the group, and we already know the map will be last in the list. - addNewMapGroup(groupName); - mapNamePos = this->mapNames.length(); - } - - this->mapNames.insert(mapNamePos, newMap->name()); - this->groupNameToMapNames[groupName].append(newMap->name()); - this->mapConstantsToMapNames.insert(newMap->constantName(), newMap->name()); - this->mapNamesToMapConstants.insert(newMap->name(), newMap->constantName()); - - newMap->setIsPersistedToFile(false); - - // If we don't recognize the layout ID (i.e., it's also new) we'll add that too. - if (!this->layoutIds.contains(newMap->layout()->id)) { - addNewLayout(newMap->layout()); - } - - emit mapAdded(newMap, groupName); -} - -void Project::addNewLayout(Layout* newLayout) { - if (!newLayout || this->layoutIds.contains(newLayout->id)) - return; - - this->mapLayouts.insert(newLayout->id, newLayout); - this->layoutIds.append(newLayout->id); - - if (newLayout->blockdata.isEmpty()) { - // Fill layout using default fill settings - setNewLayoutBlockdata(newLayout); - } - if (newLayout->border.isEmpty()) { - // Fill border using default fill settings - setNewLayoutBorder(newLayout); - } - - emit layoutAdded(newLayout); -} - void Project::addNewMapGroup(const QString &groupName) { if (this->groupNames.contains(groupName)) return; @@ -2005,12 +2008,11 @@ Layout::Settings Project::getNewLayoutSettings() const { return settings; } -// When we ask the user to provide a new identifier for something (like a map/layout name or ID) +// When we ask the user to provide a new identifier for something (like a map name or MAPSEC id) // we use this to make sure that it doesn't collide with any known identifiers first. // Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap. // In general this only matters to Porymap if the identifier will be added to the group it collides with, // but name collisions are likely undesirable in the project. -// TODO: Use elsewhere bool Project::isIdentifierUnique(const QString &identifier) const { if (this->mapNames.contains(identifier)) return false; diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index fa0e2c1c..6f6e4dec 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -92,46 +92,47 @@ void MapHeaderForm::setHeader(MapHeader *header) { if (m_header) { m_header->disconnect(this); } - m_header = header; - if (m_header) { - // If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly - connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::updateSong); - connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::updateLocation); - connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::updateRequiresFlash); - connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::updateWeather); - connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::updateType); - connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::updateBattleScene); - connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::updateShowsLocationName); - connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::updateAllowsRunning); - connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::updateAllowsBiking); - connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::updateAllowsEscaping); - connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::updateFloorNumber); + if (!m_header) { + clear(); + return; } + // If the MapHeader is changed externally (for example, with the scripting API) update the UI accordingly + connect(m_header, &MapHeader::songChanged, this, &MapHeaderForm::setSong); + connect(m_header, &MapHeader::locationChanged, this, &MapHeaderForm::setLocation); + connect(m_header, &MapHeader::requiresFlashChanged, this, &MapHeaderForm::setRequiresFlash); + connect(m_header, &MapHeader::weatherChanged, this, &MapHeaderForm::setWeather); + connect(m_header, &MapHeader::typeChanged, this, &MapHeaderForm::setType); + connect(m_header, &MapHeader::battleSceneChanged, this, &MapHeaderForm::setBattleScene); + connect(m_header, &MapHeader::showsLocationNameChanged, this, &MapHeaderForm::setShowsLocationName); + connect(m_header, &MapHeader::allowsRunningChanged, this, &MapHeaderForm::setAllowsRunning); + connect(m_header, &MapHeader::allowsBikingChanged, this, &MapHeaderForm::setAllowsBiking); + connect(m_header, &MapHeader::allowsEscapingChanged, this, &MapHeaderForm::setAllowsEscaping); + connect(m_header, &MapHeader::floorNumberChanged, this, &MapHeaderForm::setFloorNumber); + // Immediately update the UI to reflect the assigned MapHeader - updateUi(); + setHeaderData(*m_header); } void MapHeaderForm::clear() { m_header = nullptr; - updateUi(); + setHeaderData(MapHeader()); } -void MapHeaderForm::updateUi() { - updateSong(); - updateLocation(); - updateRequiresFlash(); - updateWeather(); - updateType(); - updateBattleScene(); - updateShowsLocationName(); - updateAllowsRunning(); - updateAllowsBiking(); - updateAllowsEscaping(); - updateFloorNumber(); - +void MapHeaderForm::setHeaderData(const MapHeader &header) { + setSong(header.song()); + setLocation(header.location()); + setRequiresFlash(header.requiresFlash()); + setWeather(header.weather()); + setType(header.type()); + setBattleScene(header.battleScene()); + setShowsLocationName(header.showsLocationName()); + setAllowsRunning(header.allowsRunning()); + setAllowsBiking(header.allowsBiking()); + setAllowsEscaping(header.allowsEscaping()); + setFloorNumber(header.floorNumber()); } MapHeader MapHeaderForm::headerData() const { @@ -140,132 +141,55 @@ MapHeader MapHeaderForm::headerData() const { // Build header from UI MapHeader header; - header.setSong(ui->comboBox_Song->currentText()); - header.setLocation(ui->comboBox_Location->currentText()); - header.setRequiresFlash(ui->checkBox_RequiresFlash->isChecked()); - header.setWeather(ui->comboBox_Weather->currentText()); - header.setType(ui->comboBox_Type->currentText()); - header.setBattleScene(ui->comboBox_BattleScene->currentText()); - header.setShowsLocationName(ui->checkBox_ShowLocationName->isChecked()); - header.setAllowsRunning(ui->checkBox_AllowRunning->isChecked()); - header.setAllowsBiking(ui->checkBox_AllowBiking->isChecked()); - header.setAllowsEscaping(ui->checkBox_AllowEscaping->isChecked()); - header.setFloorNumber(ui->spinBox_FloorNumber->value()); + header.setSong(song()); + header.setLocation(location()); + header.setRequiresFlash(requiresFlash()); + header.setWeather(weather()); + header.setType(type()); + header.setBattleScene(battleScene()); + header.setShowsLocationName(showsLocationName()); + header.setAllowsRunning(allowsRunning()); + header.setAllowsBiking(allowsBiking()); + header.setAllowsEscaping(allowsEscaping()); + header.setFloorNumber(floorNumber()); return header; } -void MapHeaderForm::setLocationDisabled(bool disabled) { - m_locationDisabled = disabled; - ui->label_Location->setDisabled(m_locationDisabled); - ui->comboBox_Location->setDisabled(m_locationDisabled); -} - -void MapHeaderForm::updateSong() { - const QSignalBlocker b(ui->comboBox_Song); - ui->comboBox_Song->setCurrentText(m_header ? m_header->song() : QString()); -} - -void MapHeaderForm::updateLocation() { - const QSignalBlocker b(ui->comboBox_Location); - ui->comboBox_Location->setCurrentText(m_header ? m_header->location() : QString()); -} - -void MapHeaderForm::updateRequiresFlash() { - const QSignalBlocker b(ui->checkBox_RequiresFlash); - ui->checkBox_RequiresFlash->setChecked(m_header ? m_header->requiresFlash() : false); -} - -void MapHeaderForm::updateWeather() { - const QSignalBlocker b(ui->comboBox_Weather); - ui->comboBox_Weather->setCurrentText(m_header ? m_header->weather() : QString()); -} - -void MapHeaderForm::updateType() { - const QSignalBlocker b(ui->comboBox_Type); - ui->comboBox_Type->setCurrentText(m_header ? m_header->type() : QString()); -} - -void MapHeaderForm::updateBattleScene() { - const QSignalBlocker b(ui->comboBox_BattleScene); - ui->comboBox_BattleScene->setCurrentText(m_header ? m_header->battleScene() : QString()); -} - -void MapHeaderForm::updateShowsLocationName() { - const QSignalBlocker b(ui->checkBox_ShowLocationName); - ui->checkBox_ShowLocationName->setChecked(m_header ? m_header->showsLocationName() : false); -} - -void MapHeaderForm::updateAllowsRunning() { - const QSignalBlocker b(ui->checkBox_AllowRunning); - ui->checkBox_AllowRunning->setChecked(m_header ? m_header->allowsRunning() : false); -} - -void MapHeaderForm::updateAllowsBiking() { - const QSignalBlocker b(ui->checkBox_AllowBiking); - ui->checkBox_AllowBiking->setChecked(m_header ? m_header->allowsBiking() : false); -} - -void MapHeaderForm::updateAllowsEscaping() { - const QSignalBlocker b(ui->checkBox_AllowEscaping); - ui->checkBox_AllowEscaping->setChecked(m_header ? m_header->allowsEscaping() : false); -} - -void MapHeaderForm::updateFloorNumber() { - const QSignalBlocker b(ui->spinBox_FloorNumber); - ui->spinBox_FloorNumber->setValue(m_header ? m_header->floorNumber() : 0); -} - -void MapHeaderForm::onSongUpdated(const QString &song) -{ - if (m_header) m_header->setSong(song); -} - -void MapHeaderForm::onLocationChanged(const QString &location) -{ - if (m_header) m_header->setLocation(location); -} - -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::onFloorNumberChanged(int offset) -{ - if (m_header) m_header->setFloorNumber(offset); -} +// 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::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::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); } + +// Read data from UI +QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); } +QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); } +bool MapHeaderForm::requiresFlash() const { return ui->checkBox_RequiresFlash->isChecked(); } +QString MapHeaderForm::weather() const { return ui->comboBox_Weather->currentText(); } +QString MapHeaderForm::type() const { return ui->comboBox_Type->currentText(); } +QString MapHeaderForm::battleScene() const { return ui->comboBox_BattleScene->currentText(); } +bool MapHeaderForm::showsLocationName() const { return ui->checkBox_ShowLocationName->isChecked(); } +bool MapHeaderForm::allowsRunning() const { return ui->checkBox_AllowRunning->isChecked(); } +bool MapHeaderForm::allowsBiking() const { return ui->checkBox_AllowBiking->isChecked(); } +bool MapHeaderForm::allowsEscaping() const { return ui->checkBox_AllowEscaping->isChecked(); } +int MapHeaderForm::floorNumber() const { return ui->spinBox_FloorNumber->value(); } + +// Send changes in UI to tracked MapHeader (if there is one) +void MapHeaderForm::onSongUpdated(const QString &song) { if (m_header) m_header->setSong(song); } +void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); } +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::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 3cf7b9a9..de41a3d6 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -47,15 +47,26 @@ NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : adjustSize(); } -// Creating new layout from AdvanceMap import -// TODO: Re-use for a "Duplicate Layout" option -NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layout, QWidget *parent) : +// Creating new layout from an existing layout (e.g. via AdvanceMap import, or duplicating from map list). +NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) : NewLayoutDialog(project, parent) { - if (layout) { - this->importedLayout = layout->copy(); - refresh(); + if (!layoutToCopy) + return; + + this->importedLayout = layoutToCopy->copy(); + if (!this->importedLayout->name.isEmpty()) { + // If the layout we're duplicating has a name and ID we'll initialize the name/ID fields + // using that name and add a suffix to make it unique. + // Layouts imported with AdvanceMap won't have a name/ID. + int i = 2; + do { + settings.name = QString("%1_%2").arg(this->importedLayout->name).arg(i); + settings.id = QString("%1_%2").arg(this->importedLayout->id).arg(i); + i++; + } while (!this->project->isIdentifierUnique(settings.name) || !this->project->isIdentifierUnique(settings.id)); } + refresh(); } NewLayoutDialog::~NewLayoutDialog() @@ -162,6 +173,8 @@ void NewLayoutDialog::accept() { } ui->label_GenericError->setVisible(false); + // TODO: See if we can get away with emitting this from Project so that we don't need to connect + // to this signal every time we create the dialog. emit applied(layout->id); QDialog::accept(); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index a97fd2ab..59fe0a16 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -50,7 +50,6 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); this->headerForm->init(project); - this->headerForm->setHeader(&settings.header); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -61,47 +60,36 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); - refresh(); + setUI(settings); adjustSize(); // TODO: Save geometry? } -// Adding new map to existing map list folder. +// Adding new map to existing map list folder. Initialize settings accordingly. +// Even if we initialize settings like this we'll allow users to change them afterwards, +// because nothing is expecting them to stay at these values. NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) : NewMapDialog(project, parent) { switch (mapListTab) { case MapListTab::Groups: - settings.group = mapListItem; - ui->label_Group->setDisabled(true); - ui->comboBox_Group->setDisabled(true); - ui->comboBox_Group->setTextItem(settings.group); + ui->comboBox_Group->setTextItem(mapListItem); break; case MapListTab::Areas: - settings.header.setLocation(mapListItem); - this->headerForm->setLocationDisabled(true); - // Header UI is kept in sync automatically by MapHeaderForm + this->headerForm->setLocation(mapListItem); break; case MapListTab::Layouts: - settings.layout.id = mapListItem; - ui->label_LayoutID->setDisabled(true); - ui->comboBox_LayoutID->setDisabled(true); - ui->comboBox_LayoutID->setTextItem(settings.layout.id); + ui->comboBox_LayoutID->setTextItem(mapListItem); break; } } -// TODO: Use for a "Duplicate Map" option NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : NewMapDialog(project, parent) { - /* - if (this->importedMap) - delete this->importedMap; - - this->importedMap = new Map(mapToCopy); - useLayoutSettings(this->importedMap->layout()); - */ + if (!mapToCopy) + return; + // TODO } NewMapDialog::~NewMapDialog() @@ -111,35 +99,38 @@ NewMapDialog::~NewMapDialog() delete ui; } -// Sync UI with settings. If any UI elements are disabled (because their settings are being enforced) -// then we don't update them using the settings here. -void NewMapDialog::refresh() { +void NewMapDialog::setUI(const Project::NewMapSettings &settings) { ui->lineEdit_Name->setText(settings.name); ui->lineEdit_MapID->setText(settings.id); - ui->comboBox_Group->setTextItem(settings.group); - ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); ui->comboBox_LayoutID->setTextItem(settings.layout.id); - ui->newLayoutForm->setSettings(settings.layout); - // Header UI is kept in sync automatically by MapHeaderForm + if (this->importedMap && this->importedMap->layout()) { + // When importing a layout these settings shouldn't be changed. + ui->newLayoutForm->setSettings(this->importedMap->layout()->settings()); + } else { + ui->newLayoutForm->setSettings(settings.layout); + } + ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); + this->headerForm->setHeaderData(settings.header); } void NewMapDialog::saveSettings() { settings.name = ui->lineEdit_Name->text(); settings.id = ui->lineEdit_MapID->text(); settings.group = ui->comboBox_Group->currentText(); - settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings.layout = ui->newLayoutForm->settings(); settings.layout.id = ui->comboBox_LayoutID->currentText(); // We don't provide full control for naming new layouts here (just via the ID). // If a user wants to explicitly name a layout they can create it individually before creating the map. - settings.layout.name = Layout::layoutNameFromMapName(settings.name); + settings.layout.name = Layout::layoutNameFromMapName(settings.name); // TODO: Verify uniqueness + settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings.header = this->headerForm->headerData(); porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } -void NewMapDialog::useLayoutSettings(const Layout *layout) { +void NewMapDialog::setLayout(const Layout *layout) { if (layout) { + ui->comboBox_LayoutID->setTextItem(layout->id); ui->newLayoutForm->setSettings(layout->settings()); ui->newLayoutForm->setDisabled(true); } else { @@ -152,10 +143,10 @@ bool NewMapDialog::validateMapID(bool allowEmpty) { const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); QString errorText; - if (id.isEmpty()) { + if (id.isEmpty() || id == expectedPrefix) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text()); } else if (!id.startsWith(expectedPrefix)) { - errorText = QString("%1 '%2' must start with '%3'.").arg(ui->label_MapID->text()).arg(id).arg(expectedPrefix); + errorText = QString("%1 must start with '%2'.").arg(ui->label_MapID->text()).arg(expectedPrefix); } else if (!this->project->isIdentifierUnique(id)) { errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id); } @@ -223,8 +214,14 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { QString errorText; if (layoutId.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); - } else if (!this->project->layoutIds.contains(layoutId) && !this->project->isIdentifierUnique(layoutId)) { - errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text()); + } else if (!this->project->isIdentifierUnique(layoutId)) { + // Layout name is already in use by something. If we're duplicating a map this isn't allowed. + if (this->importedMap) { + errorText = QString("%1 is not unique.").arg(ui->label_LayoutID->text()); + // If we're not duplicating a map this is ok as long as it's the name of an existing layout. + } else if (!this->project->layoutIds.contains(layoutId)) { + errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text()); + } } bool isValid = errorText.isEmpty(); @@ -236,7 +233,7 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { validateLayoutID(true); - useLayoutSettings(this->project->mapLayouts.value(text)); + setLayout(this->project->mapLayouts.value(text)); } void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { @@ -244,16 +241,7 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - auto newSettings = this->project->getNewMapSettings(); - - // If the location setting is disabled we need to enforce that setting on the new header. - if (this->headerForm->isLocationDisabled()) - newSettings.header.setLocation(settings.header.location()); - - settings = newSettings; - this->headerForm->setHeader(&settings.header); // TODO: Unnecessary? - refresh(); - + setUI(this->project->getNewMapSettings()); } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -273,30 +261,12 @@ void NewMapDialog::accept() { // Update settings from UI saveSettings(); - Map *newMap = new Map; - newMap->setName(settings.name); - newMap->setConstantName(settings.id); - newMap->setHeader(settings.header); - newMap->setNeedsHealLocation(settings.canFlyTo); - - Layout *layout = this->project->mapLayouts.value(settings.layout.id); - if (layout) { - // Layout already exists - newMap->setNeedsLayoutDir(false); // TODO: Remove this member? - } else { - layout = this->project->createNewLayout(settings.layout); - } - if (!layout) { - ui->label_GenericError->setText(QString("Failed to create layout for map. See %1 for details.").arg(getLogPath())); + Map *map = this->project->createNewMap(settings, this->importedMap); + if (!map) { + ui->label_GenericError->setText(QString("Failed to create map. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); - delete newMap; return; } ui->label_GenericError->setVisible(false); - - newMap->setLayout(layout); - - this->project->addNewMap(newMap, settings.group); - emit applied(newMap->name()); QDialog::accept(); } From b230f21e8dfe83fa8e12f402799985e7772d8df8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 23 Nov 2024 23:17:57 -0500 Subject: [PATCH 16/42] Automatically add new MAPSEC values from New Map dialog --- include/mainwindow.h | 1 + include/project.h | 7 ++++--- src/mainwindow.cpp | 10 +++++++++- src/project.cpp | 31 +++++++++++++++++++++---------- src/ui/maplistmodels.cpp | 1 - 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index 775d364e..a144d6e3 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -188,6 +188,7 @@ private slots: void onTilesetsSaved(QString, QString); void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); + void onNewMapSectionCreated(const QString &idName); void onNewLayoutCreated(Layout *layout); void onMapLoaded(Map *map); void onMapRulerStatusChanged(const QString &); diff --git a/include/project.h b/include/project.h index e65b755f..58e2d5a3 100644 --- a/include/project.h +++ b/include/project.h @@ -130,8 +130,8 @@ class Project : public QObject void deleteFile(QString path); bool readMapGroups(); - void addNewMap(Map* newMap, const QString &groupName); void addNewMapGroup(const QString &groupName); + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); NewMapSettings getNewMapSettings() const; @@ -275,11 +275,12 @@ class Project : public QObject signals: void fileChanged(QString filepath); - void mapSectionIdNamesChanged(const QStringList &idNames); void mapLoaded(Map *map); void mapCreated(Map *newMap, const QString &groupName); - void mapGroupAdded(const QString &groupName); void layoutCreated(Layout *newLayout); + void mapGroupAdded(const QString &groupName); + void mapSectionAdded(const QString &idName); + void mapSectionIdNamesChanged(const QStringList &idNames); }; #endif // PROJECT_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 17f05726..615fd40d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -624,6 +624,7 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::mapCreated, this, &MainWindow::onNewMapCreated); connect(project, &Project::layoutCreated, this, &MainWindow::onNewLayoutCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); + connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); this->editor->setProject(project); @@ -1351,7 +1352,7 @@ void MainWindow::mapListAddArea() { if (dialog.exec() == QDialog::Accepted) { if (newNameEdit->text().isEmpty()) return; - this->mapAreaModel->insertAreaItem(newNameDisplay->text()); + this->editor->project->addNewMapsec(newNameDisplay->text()); } } @@ -1406,6 +1407,13 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { this->mapGroupModel->insertGroupItem(groupName); } +void MainWindow::onNewMapSectionCreated(const QString &idName) { + // Add new map section to the Areas map list view + this->mapAreaModel->insertAreaItem(idName); + + // TODO: Refresh Region Map Editor's map section dropdown, if it's open +} + void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); dialog->open(); diff --git a/src/project.cpp b/src/project.cpp index cf80369e..3d8d3f9c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -399,10 +399,18 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t mapNamePos = this->mapNames.length(); } + if (!this->mapSectionIdNames.contains(map->header()->location())) { + // Unrecognized MAPSEC value. Add it. + // TODO: Validate location before adding + addNewMapsec(map->header()->location()); + } + this->mapNames.insert(mapNamePos, map->name()); this->groupNameToMapNames[settings.group].append(map->name()); this->mapConstantsToMapNames.insert(map->constantName(), map->name()); this->mapNamesToMapConstants.insert(map->name(), map->constantName()); + this->mapNameToLayoutId.insert(map->name(), map->layoutId()); + this->mapNameToMapSectionName.insert(map->name(), map->header()->location()); map->setIsPersistedToFile(false); @@ -730,8 +738,6 @@ void Project::saveRegionMapSections() { const QString emptyMapsecName = getEmptyMapsecName(); OrderedJson::array mapSectionArray; for (const auto &idName : this->mapSectionIdNames) { - // The 'empty' map section (MAPSEC_NONE) isn't normally present in the region map sections data file. - // We append this name to mapSectionIdNames ourselves if it isn't present, in which case we don't want to output data for it here. if (!this->saveEmptyMapsec && idName == emptyMapsecName) continue; @@ -2308,9 +2314,10 @@ bool Project::readRegionMapSections() { const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); QJsonDocument doc; - const QString filepath = QString("%1/%2").arg(this->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_map_entries)); + const QString baseFilepath = projectConfig.getFilePath(ProjectFilePath::json_region_map_entries); + const QString filepath = QString("%1/%2").arg(this->root).arg(baseFilepath); if (!parser.tryParseJsonFile(&doc, filepath)) { - logError(QString("Failed to read region map sections from '%1'").arg(filepath)); + logError(QString("Failed to read region map sections from '%1'").arg(baseFilepath)); return false; } fileWatcher.addPath(filepath); @@ -2319,22 +2326,23 @@ bool Project::readRegionMapSections() { for (int i = 0; i < mapSections.size(); i++) { QJsonObject mapSectionObj = mapSections.at(i).toObject(); - // For each map section, "id" is the only required field. This is the field we use to display the location names in various drop-downs. + // For each map section, "id" is the only required field. This is the field we use to display the location names in the map list, and in various drop-downs. const QString idField = "id"; if (!mapSectionObj.contains(idField)) { - logWarn(QString("Ignoring data for map section %1. Missing required field \"%2\"").arg(i).arg(idField)); + logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField)); continue; } const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); if (!idName.startsWith(requiredPrefix)) { - logWarn(QString("Ignoring data for map section '%1'. IDs must start with the prefix '%2'").arg(idName).arg(requiredPrefix)); + logWarn(QString("Ignoring data for map section '%1' in '%2'. IDs must start with the prefix '%3'").arg(idName).arg(baseFilepath).arg(requiredPrefix)); continue; } this->mapSectionIdNames.append(idName); if (idName == defaultName) { - // If the user has data for the 'empty' MAPSEC we need to know to output it later, - // because we will otherwise add a dummy entry for this value. + // The default map section (MAPSEC_NONE) isn't normally present in the region map sections data file. + // We append this name to mapSectionIdNames ourselves if it isn't present. + // We need to record whether we found it in the data file, so that we can preserve the data when we save the file later. this->saveEmptyMapsec = true; } @@ -2375,13 +2383,16 @@ QString Project::getEmptyMapsecName() { // This function assumes a valid and unique name void Project::addNewMapsec(const QString &name) { - if (!this->mapSectionIdNames.isEmpty() && this->mapSectionIdNames.last() == getEmptyMapsecName()) { + if (this->mapSectionIdNames.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); } else { this->mapSectionIdNames.append(name); } this->hasUnsavedDataChanges = true; + + // TODO: Simplify into a single signal that updates the map list only if necessary + emit mapSectionAdded(name); emit mapSectionIdNamesChanged(this->mapSectionIdNames); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 0fd6fea9..be5b0932 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -429,7 +429,6 @@ QStandardItem *MapAreaModel::createMapItem(QString mapName) { } QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { - this->project->addNewMapsec(areaName); QStandardItem *item = createAreaItem(areaName); this->root->appendRow(item); this->sort(0, Qt::AscendingOrder); From ff04a41db25e012ed42a254cd5fe79b6056891fa Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 26 Nov 2024 11:36:11 -0500 Subject: [PATCH 17/42] Add map list tool tips / copy actions, simplify MapListModel --- include/mainwindow.h | 3 +- include/ui/maplistmodels.h | 125 +++------ src/mainwindow.cpp | 80 ++++-- src/ui/maplistmodels.cpp | 538 ++++++++++++------------------------- 4 files changed, 263 insertions(+), 483 deletions(-) diff --git a/include/mainwindow.h b/include/mainwindow.h index a144d6e3..17f19623 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -180,6 +180,7 @@ private slots: void duplicate(); void setClipboardData(poryjson::Json::object); void setClipboardData(QImage); + void setClipboardData(const QString &text); void copy(); void paste(); @@ -350,7 +351,7 @@ private slots: void openNewMapDialog(); void openNewLayoutDialog(); void openSubWindow(QWidget * window); - void scrollMapList(MapTree *list, QString itemName); + void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); void scrollMapListToCurrentLayout(MapTree *list); void resetMapListFilters(); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 59b65e50..1962ad40 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -61,15 +61,42 @@ class MapListModel : public QStandardItemModel { Q_OBJECT public: - MapListModel(QObject *parent = nullptr) : QStandardItemModel(parent) {}; + MapListModel(Project *project, QObject *parent = nullptr); ~MapListModel() { } - virtual QModelIndex indexOf(QString id) const = 0; + void setActiveItem(const QString &itemName) { this->activeItemName = itemName; } + + virtual QStandardItem *insertMapItem(const QString &mapName, const QString &folderName); + virtual QStandardItem *insertMapFolderItem(const QString &folderName); + + virtual QModelIndex indexOf(const QString &itemName) const; virtual void removeItemAt(const QModelIndex &index); - virtual QStandardItem *getItem(const QModelIndex &index) const = 0; + virtual QStandardItem *getItem(const QModelIndex &index) const; + + virtual QVariant data(const QModelIndex &index, int role) const override; protected: - virtual void removeItem(QStandardItem *item) = 0; + Project *project; + QStandardItem *root = nullptr; + + QString activeItemName; + QString folderTypeName; + bool sortingEnabled = false; + bool editable = false; + + QIcon mapGrayIcon; + QIcon mapIcon; + QIcon mapEditedIcon; + QIcon mapOpenedIcon; + QIcon mapFolderIcon; + QIcon emptyMapFolderIcon; + + QMap mapFolderItems; + QMap mapItems; + + virtual QStandardItem *createMapItem(const QString &mapName, QStandardItem *map = nullptr); + virtual QStandardItem *createMapFolderItem(const QString &groupName, QStandardItem *fromItem = nullptr); + virtual void removeItem(QStandardItem *item) = 0; }; class MapGroupModel : public MapListModel { @@ -80,44 +107,20 @@ class MapGroupModel : public MapListModel { ~MapGroupModel() { } QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::DropActions supportedDropActions() const override; QStringList mimeTypes() const override; - virtual QMimeData *mimeData(const QModelIndexList &indexes) const override; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; - - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - -public: - void setMap(QString mapName) { this->openMap = mapName; } - - QStandardItem *createGroupItem(QString groupName, QStandardItem *fromItem = nullptr); - QStandardItem *createMapItem(QString mapName, QStandardItem *fromItem = nullptr); - - QStandardItem *insertGroupItem(QString groupName); - QStandardItem *insertMapItem(QString mapName, QString groupName); - - virtual QStandardItem *getItem(const QModelIndex &index) const override; - virtual QModelIndex indexOf(QString mapName) const override; - - void initialize(); + QMimeData *mimeData(const QModelIndexList &indexes) const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; protected: - virtual void removeItem(QStandardItem *item) override; + void removeItem(QStandardItem *item) override; private: friend class MapTree; void updateProject(); -private: - Project *project; - QStandardItem *root = nullptr; - - QMap groupItems; - QMap mapItems; - - QString openMap; - signals: void dragMoveCompleted(); }; @@ -131,36 +134,8 @@ class MapAreaModel : public MapListModel { MapAreaModel(Project *project, QObject *parent = nullptr); ~MapAreaModel() {} - QVariant data(const QModelIndex &index, int role) const override; - -public: - void setMap(QString mapName) { this->openMap = mapName; } - - QStandardItem *createAreaItem(QString areaName); - QStandardItem *createMapItem(QString mapName); - - QStandardItem *insertAreaItem(QString areaName); - QStandardItem *insertMapItem(QString mapName, QString areaName); - - virtual QStandardItem *getItem(const QModelIndex &index) const override; - virtual QModelIndex indexOf(QString mapName) const override; - - void initialize(); - protected: - virtual void removeItem(QStandardItem *item) override; - -private: - Project *project; - QStandardItem *root = nullptr; - - QMap areaItems; - QMap mapItems; - - QString openMap; - -signals: - void edited(); + void removeItem(QStandardItem *item) override; }; @@ -174,34 +149,8 @@ class LayoutTreeModel : public MapListModel { QVariant data(const QModelIndex &index, int role) const override; -public: - void setLayout(QString layoutId) { this->openLayout = layoutId; } - - QStandardItem *createLayoutItem(QString layoutId); - QStandardItem *createMapItem(QString mapName); - - QStandardItem *insertLayoutItem(QString layoutId); - QStandardItem *insertMapItem(QString mapName, QString layoutId); - - virtual QStandardItem *getItem(const QModelIndex &index) const override; - virtual QModelIndex indexOf(QString layoutName) const override; - - void initialize(); - protected: - virtual void removeItem(QStandardItem *item) override; - -private: - Project *project; - QStandardItem *root = nullptr; - - QMap layoutItems; - QMap mapItems; - - QString openLayout; - -signals: - void edited(); + void removeItem(QStandardItem *item) override; }; #endif // MAPLISTMODELS_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 615fd40d..bb01d5c5 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -956,9 +956,6 @@ bool MainWindow::setLayout(QString layoutId) { logInfo("Switching to layout-only editing mode. Disabling map-related edits."); unsetMap(); - - layoutTreeModel->setLayout(layoutId); - refreshMapScene(); updateWindowTitle(); updateMapList(); @@ -1163,7 +1160,7 @@ void MainWindow::clearProjectUI() { Event::clearIcons(); } -void MainWindow::scrollMapList(MapTree *list, QString itemName) { +void MainWindow::scrollMapList(MapTree *list, const QString &itemName) { if (!list || itemName.isEmpty()) return; auto model = static_cast(list->model()); @@ -1208,9 +1205,16 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QAction* addToFolderAction = nullptr; QAction* deleteFolderAction = nullptr; QAction* openItemAction = nullptr; + QAction* copyDisplayNameAction = nullptr; + QAction* copyToolTipAction = nullptr; + if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); + menu.addSeparator(); + copyDisplayNameAction = menu.addAction("Copy Map Name"); + copyToolTipAction = menu.addAction("Copy Map ID"); + menu.addSeparator(); connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { auto dialog = new NewMapDialog(this->editor->project, this->editor->project->getMap(itemName), this); dialog->open(); @@ -1226,12 +1230,18 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { // Right-clicking on a MAPSEC folder addToFolderAction = menu.addAction("Add New Map to Area"); menu.addSeparator(); + copyDisplayNameAction = menu.addAction("Copy Area Name"); + menu.addSeparator(); deleteFolderAction = menu.addAction("Delete Area"); if (itemName == this->editor->project->getEmptyMapsecName()) deleteFolderAction->setEnabled(false); // Disallow deleting the default name } else if (itemType == "map_layout") { // Right-clicking on a map layout openItemAction = menu.addAction("Open Layout"); + menu.addSeparator(); + copyDisplayNameAction = menu.addAction("Copy Layout Name"); + copyToolTipAction = menu.addAction("Copy Layout ID"); + menu.addSeparator(); connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { auto layout = this->editor->project->loadLayout(itemName); if (layout) { @@ -1261,8 +1271,21 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { deleteFolderAction->setEnabled(false); } } + if (openItemAction) { - connect(openItemAction, &QAction::triggered, [this, index] { openMapListItem(index); }); + connect(openItemAction, &QAction::triggered, [this, index] { + openMapListItem(index); + }); + } + if (copyDisplayNameAction) { + connect(copyDisplayNameAction, &QAction::triggered, [this, sourceModel, index] { + setClipboardData(sourceModel->data(index, Qt::DisplayRole).toString()); + }); + } + if (copyToolTipAction) { + connect(copyToolTipAction, &QAction::triggered, [this, sourceModel, index] { + setClipboardData(sourceModel->data(index, Qt::ToolTipRole).toString()); + }); } if (menu.actions().length() != 0) @@ -1399,17 +1422,17 @@ void MainWindow::onNewLayoutCreated(Layout *layout) { } // Add new layout to the Layouts map list view - this->layoutTreeModel->insertLayoutItem(layout->id); + this->layoutTreeModel->insertMapFolderItem(layout->id); } void MainWindow::onNewMapGroupCreated(const QString &groupName) { // Add new map group to the Groups map list view - this->mapGroupModel->insertGroupItem(groupName); + this->mapGroupModel->insertMapFolderItem(groupName); } void MainWindow::onNewMapSectionCreated(const QString &idName) { // Add new map section to the Areas map list view - this->mapAreaModel->insertAreaItem(idName); + this->mapAreaModel->insertMapFolderItem(idName); // TODO: Refresh Region Map Editor's map section dropdown, if it's open } @@ -1627,28 +1650,28 @@ void MainWindow::openMapListItem(const QModelIndex &index) { } void MainWindow::updateMapList() { + // Get the name of the open map/layout (or clear the relevant selection if there is none). + QString activeItemName; if (this->editor->map) { - this->mapGroupModel->setMap(this->editor->map->name()); - this->groupListProxyModel->layoutChanged(); - this->mapAreaModel->setMap(this->editor->map->name()); - this->areaListProxyModel->layoutChanged(); + activeItemName = this->editor->map->name(); } else { - this->mapGroupModel->setMap(QString()); - this->groupListProxyModel->layoutChanged(); - this->ui->mapList->clearSelection(); - this->mapAreaModel->setMap(QString()); - this->areaListProxyModel->layoutChanged(); - this->ui->areaList->clearSelection(); - } + ui->mapList->clearSelection(); + ui->areaList->clearSelection(); - if (this->editor->layout) { - this->layoutTreeModel->setLayout(this->editor->layout->id); - this->layoutListProxyModel->layoutChanged(); - } else { - this->layoutTreeModel->setLayout(QString()); - this->layoutListProxyModel->layoutChanged(); - this->ui->layoutList->clearSelection(); + if (this->editor->layout) { + activeItemName = this->editor->layout->id; + } else { + ui->layoutList->clearSelection(); + } } + + this->mapGroupModel->setActiveItem(activeItemName); + this->mapAreaModel->setActiveItem(activeItemName); + this->layoutTreeModel->setActiveItem(activeItemName); + + this->groupListProxyModel->layoutChanged(); + this->areaListProxyModel->layoutChanged(); + this->layoutListProxyModel->layoutChanged(); } void MainWindow::on_action_Save_Project_triggered() { @@ -1782,6 +1805,11 @@ void MainWindow::setClipboardData(OrderedJson::object object) { clipboard->setText(newText); } +void MainWindow::setClipboardData(const QString &text) { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(text); +} + void MainWindow::setClipboardData(QImage image) { QClipboard *clipboard = QGuiApplication::clipboard(); clipboard->setImage(image); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index be5b0932..6d5dd45d 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -38,6 +38,43 @@ void MapTree::keyPressEvent(QKeyEvent *event) { } } + + +MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemModel(parent) { + this->project = project; + this->root = invisibleRootItem(); + + this->mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); + this->mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); + this->mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); + this->mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + + this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); + this->mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); + + this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); + this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); +} + +QStandardItem *MapListModel::getItem(const QModelIndex &index) const { + if (index.isValid()) { + QStandardItem *item = static_cast(index.internalPointer()); + if (item) + return item; + } + return this->root; +} + +QModelIndex MapListModel::indexOf(const QString &itemName) const { + if (this->mapItems.contains(itemName)) + return this->mapItems.value(itemName)->index(); + + if (this->mapFolderItems.contains(itemName)) + return this->mapFolderItems.value(itemName)->index(); + + return QModelIndex(); +} + void MapListModel::removeItemAt(const QModelIndex &index) { QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); if (!item) @@ -49,11 +86,89 @@ void MapListModel::removeItemAt(const QModelIndex &index) { } else { // TODO: Because there's no support for deleting maps we can only delete empty folders if (!item->hasChildren()) { - this->removeItem(item); + removeItem(item); } } } +QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem *map) { + if (!map) map = new QStandardItem; + map->setText(mapName); + map->setData(mapName, MapListUserRoles::NameRole); + map->setData("map_name", MapListUserRoles::TypeRole); + map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); + map->setEditable(this->editable); // Will override flags if necessary + this->mapItems.insert(mapName, map); + return map; +} + +QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) { + if (!folder) folder = new QStandardItem; + folder->setText(folderName); + folder->setData(folderName, MapListUserRoles::NameRole); + folder->setData(this->folderTypeName, MapListUserRoles::TypeRole); + folder->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); + folder->setEditable(this->editable); // Will override flags if necessary + this->mapFolderItems.insert(folderName, folder); + return folder; +} + +QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) { + // Disallow adding MAP_DYNAMIC to the map list. + if (mapName == this->project->getDynamicMapName()) + return nullptr; + + QStandardItem *folder = this->mapFolderItems[folderName]; + if (!folder) folder = insertMapFolderItem(folderName); + + QStandardItem *map = createMapItem(mapName); + folder->appendRow(map); + if (this->sortingEnabled) + this->sort(0, Qt::AscendingOrder); + return map; +} + +QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) { + QStandardItem *item = createMapFolderItem(folderName); + this->root->appendRow(item); + if (this->sortingEnabled) + this->sort(0, Qt::AscendingOrder); + return item; +} + +QVariant MapListModel::data(const QModelIndex &index, int role) const { + if (!index.isValid()) + return QVariant(); + + int row = index.row(); + int col = index.column(); + + const QStandardItem *item = this->getItem(index)->child(row, col); + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QString name = item->data(MapListUserRoles::NameRole).toString(); + + if (type == "map_name") { + // Data for maps in the map list + if (role == Qt::DecorationRole) { + if (name == this->activeItemName) + return this->mapOpenedIcon; + + const Map* map = this->project->mapCache.value(name); + if (!map) + return this->mapGrayIcon; + return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; + } else if (role == Qt::ToolTipRole) { + return this->project->mapNamesToMapConstants.value(name); + } + } else if (type == this->folderTypeName) { + // Data for map folders in the map list + if (role == Qt::DecorationRole) { + return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon; + } + } + return QStandardItemModel::data(index, role); +} + QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { @@ -83,11 +198,15 @@ void GroupNameDelegate::updateEditorGeometry(QWidget *editor, const QStyleOption -MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(parent) { - this->project = project; - this->root = this->invisibleRootItem(); +MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(project, parent) { + this->folderTypeName = "map_group"; + this->editable = true; - initialize(); + for (const auto &groupName : this->project->groupNames) { + for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { + insertMapItem(mapName, groupName); + } + } } Qt::DropActions MapGroupModel::supportedDropActions() const { @@ -173,7 +292,7 @@ bool MapGroupModel::dropMimeData(const QMimeData *data, Qt::DropAction action, i QModelIndex groupIndex = index(row, 0, parentIndex); QStandardItem *groupItem = this->itemFromIndex(groupIndex); - createGroupItem(groupName, groupItem); + createMapFolderItem(groupName, groupItem); for (QString mapName : mapsToMove) { QStandardItem *mapItem = createMapItem(mapName); @@ -251,143 +370,36 @@ void MapGroupModel::updateProject() { this->project->hasUnsavedDataChanges = true; } -QStandardItem *MapGroupModel::createGroupItem(QString groupName, QStandardItem *group) { - if (!group) group = new QStandardItem; - group->setText(groupName); - group->setData(groupName, MapListUserRoles::NameRole); - group->setData("map_group", MapListUserRoles::TypeRole); - group->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsSelectable); - this->groupItems.insert(groupName, group); - return group; -} - -QStandardItem *MapGroupModel::createMapItem(QString mapName, QStandardItem *map) { - if (!map) map = new QStandardItem; - map->setData(mapName, MapListUserRoles::NameRole); - map->setData("map_name", MapListUserRoles::TypeRole); - map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->mapItems[mapName] = map; - return map; -} - -QStandardItem *MapGroupModel::insertGroupItem(QString groupName) { - QStandardItem *group = createGroupItem(groupName); - this->root->appendRow(group); - return group; -} - void MapGroupModel::removeItem(QStandardItem *item) { this->removeRow(item->row()); this->updateProject(); } -QStandardItem *MapGroupModel::insertMapItem(QString mapName, QString groupName) { - QStandardItem *group = this->groupItems[groupName]; - if (!group) { - group = insertGroupItem(groupName); - } - QStandardItem *map = createMapItem(mapName); - group->appendRow(map); - return map; -} - -void MapGroupModel::initialize() { - this->groupItems.clear(); - this->mapItems.clear(); - - - for (const auto &groupName : this->project->groupNames) { - QStandardItem *group = createGroupItem(groupName); - root->appendRow(group); - for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { - group->appendRow(createMapItem(mapName)); - } - } -} - -QStandardItem *MapGroupModel::getItem(const QModelIndex &index) const { - if (index.isValid()) { - QStandardItem *item = static_cast(index.internalPointer()); - if (item) - return item; - } - return this->root; -} - -QModelIndex MapGroupModel::indexOf(QString mapName) const { - if (this->mapItems.contains(mapName)) { - return this->mapItems[mapName]->index(); - } - return QModelIndex(); -} - QVariant MapGroupModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) return QVariant(); + if (!index.isValid()) + return QVariant(); int row = index.row(); int col = index.column(); - if (role == Qt::DecorationRole) { - static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); - static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); - static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); - - static QIcon mapFolderIcon; - static QIcon folderIcon; - static bool loaded = false; - if (!loaded) { - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - loaded = true; - } - - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); - - if (type == "map_group") { - if (!item->hasChildren()) { - return folderIcon; - } - return mapFolderIcon; - } else if (type == "map_name") { - QString mapName = item->data(MapListUserRoles::NameRole).toString(); - if (mapName == this->openMap) { - return mapOpenedIcon; - } - else if (this->project->mapCache.contains(mapName)) { - if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { - return mapEditedIcon; - } - else { - return mapIcon; - } - } - return mapGrayIcon; - } - } - else if (role == Qt::DisplayRole) { - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QStandardItem *item = this->getItem(index)->child(row, col); + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QString name = item->data(MapListUserRoles::NameRole).toString(); + if (role == Qt::DisplayRole) { if (type == "map_name") { - return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + item->data(MapListUserRoles::NameRole).toString(); + return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name; } - else if (type == "map_group") { - return item->data(MapListUserRoles::NameRole).toString(); + else if (type == this->folderTypeName) { + return name; } } - - return QStandardItemModel::data(index, role); + return MapListModel::data(index, role); } bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == MapListUserRoles::NameRole && data(index, MapListUserRoles::TypeRole).toString() == "map_group") { - // verify uniqueness of new group name - // TODO: Check that the name is a valid symbol name (i.e. only word characters, not starting with a number) - if (this->project->groupNames.contains(value.toString())) { + if (!this->project->isIdentifierUnique(value.toString())) { return false; } } @@ -399,50 +411,15 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int -MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(parent) { - this->project = project; - this->root = this->invisibleRootItem(); +MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(project, parent) { + this->folderTypeName = "map_section"; - initialize(); -} - -QStandardItem *MapAreaModel::createAreaItem(QString mapsecName) { - QStandardItem *area = new QStandardItem; - area->setText(mapsecName); - area->setEditable(false); - area->setData(mapsecName, MapListUserRoles::NameRole); - area->setData("map_section", MapListUserRoles::TypeRole); - // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->areaItems.insert(mapsecName, area); - return area; -} - -QStandardItem *MapAreaModel::createMapItem(QString mapName) { - QStandardItem *map = new QStandardItem; - map->setText(mapName); - map->setEditable(false); - map->setData(mapName, MapListUserRoles::NameRole); - map->setData("map_name", MapListUserRoles::TypeRole); - // map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->mapItems.insert(mapName, map); - return map; -} - -QStandardItem *MapAreaModel::insertAreaItem(QString areaName) { - QStandardItem *item = createAreaItem(areaName); - this->root->appendRow(item); - this->sort(0, Qt::AscendingOrder); - return item; -} - -QStandardItem *MapAreaModel::insertMapItem(QString mapName, QString areaName) { - QStandardItem *area = this->areaItems[areaName]; - if (!area) { - return nullptr; + for (const auto &mapName : this->project->mapNames) { + insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); } - QStandardItem *map = createMapItem(mapName); - area->appendRow(map); - return map; + + this->sortingEnabled = true; + sort(0, Qt::AscendingOrder); } void MapAreaModel::removeItem(QStandardItem *item) { @@ -450,227 +427,52 @@ void MapAreaModel::removeItem(QStandardItem *item) { this->removeRow(item->row()); } -void MapAreaModel::initialize() { - this->areaItems.clear(); - this->mapItems.clear(); - - for (const auto &idName : this->project->mapSectionIdNames) { - this->root->appendRow(createAreaItem(idName)); - } - - for (const auto &mapName : this->project->mapNames) { - const QString mapsecName = this->project->mapNameToMapSectionName.value(mapName); - if (this->areaItems.contains(mapsecName)) - this->areaItems[mapsecName]->appendRow(createMapItem(mapName)); - } - - this->sort(0, Qt::AscendingOrder); -} - -QStandardItem *MapAreaModel::getItem(const QModelIndex &index) const { - if (index.isValid()) { - QStandardItem *item = static_cast(index.internalPointer()); - if (item) - return item; - } - return this->root; -} - -QModelIndex MapAreaModel::indexOf(QString mapName) const { - if (this->mapItems.contains(mapName)) { - return this->mapItems[mapName]->index(); - } - return QModelIndex(); -} -QVariant MapAreaModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) return QVariant(); - int row = index.row(); - int col = index.column(); +LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) { + this->folderTypeName = "map_layout"; - if (role == Qt::DecorationRole) { - static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); - static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); - static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); - - static QIcon mapFolderIcon; - static QIcon folderIcon; - static bool loaded = false; - if (!loaded) { - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_closed_map.ico"), QSize(), QIcon::Normal, QIcon::Off); - mapFolderIcon.addFile(QStringLiteral(":/icons/folder_map.ico"), QSize(), QIcon::Normal, QIcon::On); - folderIcon.addFile(QStringLiteral(":/icons/folder_closed.ico"), QSize(), QIcon::Normal, QIcon::Off); - folderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); - loaded = true; - } - - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); - - if (type == "map_section") { - if (item->hasChildren()) { - return mapFolderIcon; - } - return folderIcon; - } else if (type == "map_name") { - QString mapName = item->data(MapListUserRoles::NameRole).toString(); - if (mapName == this->openMap) { - return mapOpenedIcon; - } - else if (this->project->mapCache.contains(mapName)) { - if (this->project->mapCache.value(mapName)->hasUnsavedChanges()) { - return mapEditedIcon; - } - else { - return mapIcon; - } - } - return mapGrayIcon; - } - } - else if (role == Qt::DisplayRole) { - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); - - if (type == "map_section") { - return item->data(MapListUserRoles::NameRole).toString(); - } + for (const auto &mapName : this->project->mapNames) { + insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); } - return QStandardItemModel::data(index, role); -} - - - -LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(parent) { - this->project = project; - this->root = this->invisibleRootItem(); - - initialize(); -} - -QStandardItem *LayoutTreeModel::createLayoutItem(QString layoutId) { - QStandardItem *layout = new QStandardItem; - layout->setText(this->project->mapLayouts[layoutId]->name); - layout->setEditable(false); - layout->setData(layoutId, MapListUserRoles::NameRole); - layout->setData("map_layout", MapListUserRoles::TypeRole); - // // group->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled); - this->layoutItems.insert(layoutId, layout); - return layout; -} - -QStandardItem *LayoutTreeModel::createMapItem(QString mapName) { - QStandardItem *map = new QStandardItem; - map->setText(mapName); - map->setEditable(false); - map->setData(mapName, MapListUserRoles::NameRole); - map->setData("map_name", MapListUserRoles::TypeRole); - map->setFlags(Qt::NoItemFlags | Qt::ItemNeverHasChildren); - this->mapItems.insert(mapName, map); - return map; -} - -QStandardItem *LayoutTreeModel::insertLayoutItem(QString layoutId) { - QStandardItem *layoutItem = this->createLayoutItem(layoutId); - this->root->appendRow(layoutItem); - this->sort(0, Qt::AscendingOrder); - return layoutItem; -} - -QStandardItem *LayoutTreeModel::insertMapItem(QString mapName, QString layoutId) { - QStandardItem *layout = nullptr; - if (this->layoutItems.contains(layoutId)) { - layout = this->layoutItems[layoutId]; - } - else { - layout = createLayoutItem(layoutId); - this->root->appendRow(layout); - } - if (!layout) { - return nullptr; - } - QStandardItem *map = createMapItem(mapName); - layout->appendRow(map); - return map; + this->sortingEnabled = true; + sort(0, Qt::AscendingOrder); } void LayoutTreeModel::removeItem(QStandardItem *) { // TODO: Deleting layouts not supported } - -void LayoutTreeModel::initialize() { - this->layoutItems.clear(); - this->mapItems.clear(); - - for (const auto &layoutId : this->project->layoutIds) { - this->root->appendRow(createLayoutItem(layoutId)); - } - - for (const auto &mapName : this->project->mapNames) { - QString layoutId = project->mapNameToLayoutId.value(mapName); - if (this->layoutItems.contains(layoutId)) - this->layoutItems[layoutId]->appendRow(createMapItem(mapName)); - } - - this->sort(0, Qt::AscendingOrder); -} - -QStandardItem *LayoutTreeModel::getItem(const QModelIndex &index) const { - if (index.isValid()) { - QStandardItem *item = static_cast(index.internalPointer()); - if (item) - return item; - } - return this->root; -} - -QModelIndex LayoutTreeModel::indexOf(QString layoutName) const { - if (this->layoutItems.contains(layoutName)) { - return this->layoutItems[layoutName]->index(); - } - return QModelIndex(); -} - QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) return QVariant(); + if (!index.isValid()) + return QVariant(); int row = index.row(); int col = index.column(); - if (role == Qt::DecorationRole) { - static QIcon mapGrayIcon = QIcon(QStringLiteral(":/icons/map_grayed.ico")); - static QIcon mapIcon = QIcon(QStringLiteral(":/icons/map.ico")); - static QIcon mapEditedIcon = QIcon(QStringLiteral(":/icons/map_edited.ico")); - static QIcon mapOpenedIcon = QIcon(QStringLiteral(":/icons/map_opened.ico")); + const QStandardItem *item = this->getItem(index)->child(row, col); + const QString type = item->data(MapListUserRoles::TypeRole).toString(); + const QString name = item->data(MapListUserRoles::NameRole).toString(); - QStandardItem *item = this->getItem(index)->child(row, col); - QString type = item->data(MapListUserRoles::TypeRole).toString(); + if (type == this->folderTypeName) { + const Layout* layout = this->project->mapLayouts.value(name); - if (type == "map_layout") { - QString layoutId = item->data(MapListUserRoles::NameRole).toString(); - if (layoutId == this->openLayout) { - return mapOpenedIcon; - } - else if (this->project->mapLayouts.contains(layoutId)) { - if (this->project->mapLayouts.value(layoutId)->hasUnsavedChanges()) { - return mapEditedIcon; - } - else if (!this->project->mapLayouts[layoutId]->loaded) { - return mapGrayIcon; - } - } - return mapIcon; + if (role == Qt::DecorationRole) { + // Map layouts are used as folders, but we display them with the same icons as maps. + if (name == this->activeItemName) + return this->mapOpenedIcon; + + if (!layout || !layout->loaded) + return this->mapGrayIcon; + return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } - else if (type == "map_name") { - return QVariant(); + else if (role == Qt::DisplayRole) { + // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. + if (layout) return layout->name; + } else if (role == Qt::ToolTipRole) { + if (layout) return layout->id; } - - return QVariant(); } - - return QStandardItemModel::data(index, role); + return MapListModel::data(index, role); } From f1a4b78ca9177b29a02fde8992ef81488eb4ecfc Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 26 Nov 2024 15:33:56 -0500 Subject: [PATCH 18/42] Fix map duplication --- forms/newlayoutdialog.ui | 3 + forms/newmapdialog.ui | 150 +++++++++++++++-------------------- include/core/maplayout.h | 1 - include/project.h | 27 ++++--- include/ui/newlayoutdialog.h | 6 +- include/ui/newmapdialog.h | 11 +-- src/project.cpp | 127 ++++++++++++++++------------- src/ui/newlayoutdialog.cpp | 101 ++++++++++++----------- src/ui/newmapdialog.cpp | 134 ++++++++++++++----------------- 9 files changed, 270 insertions(+), 290 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index 53d950fe..d285bdb1 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -117,6 +117,9 @@ + + false + color: rgb(255, 0, 0) diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index e6aece18..3e133dfd 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -25,41 +25,43 @@ 0 0 229 - 306 + 228 10 - - - - false - - - color: rgb(255, 0, 0) - + + - - - - true + Map Name - - - - true - - - QComboBox::InsertPolicy::NoInsert - + + + + + 0 + + + 0 + + + 0 + + + 0 + + - - + + + + false + color: rgb(255, 0, 0) @@ -87,57 +89,22 @@ - - - - <html><head/><body><p>The constant that will be used to refer to this map. It cannot be the same as any other existing map, and it must start with the specified prefix.</p></body></html> - - - - - - - Map ID - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - + + - Can Fly To + Layout ID - - + + - Map Name + Map Group - - - <html><head/><body><p>The name of the group this map will be added to.</p></body></html> - + true @@ -146,17 +113,7 @@ - - - - <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> - - - true - - - - + <html><head/><body><p>If checked, a Heal Location will be added to this map automatically.</p></body></html> @@ -166,8 +123,8 @@ - - + + false @@ -182,24 +139,40 @@ - + - - - - Map Group + + + + <html><head/><body><p>The name of the group this map will be added to.</p></body></html> + + + true + + + QComboBox::InsertPolicy::NoInsert - - + + - Layout ID + Can Fly To + + + + + + + <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + + true - + Qt::Orientation::Vertical @@ -218,6 +191,9 @@ + + false + color: rgb(255, 0, 0) diff --git a/include/core/maplayout.h b/include/core/maplayout.h index cfd093ef..5d717a43 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -74,7 +74,6 @@ class Layout : public QObject { QUndoStack editHistory; // to simplify new layout settings transfer between functions - // TODO: Make this the equivalent of struct MapHeader struct Settings { QString id; QString name; diff --git a/include/project.h b/include/project.h index 58e2d5a3..9b6f64bf 100644 --- a/include/project.h +++ b/include/project.h @@ -81,16 +81,6 @@ class Project : public QObject bool wildEncountersLoaded; bool saveEmptyMapsec; - struct NewMapSettings { - QString name; - QString id; - QString group; - bool canFlyTo; - Layout::Settings layout; - MapHeader header; - }; - NewMapSettings newMapSettings; - void set_root(QString); void clearMapCache(); @@ -132,10 +122,23 @@ class Project : public QObject bool readMapGroups(); void addNewMapGroup(const QString &groupName); + struct NewMapSettings { + QString name; + QString group; + bool canFlyTo; + Layout::Settings layout; + MapHeader header; + }; + NewMapSettings newMapSettings; + Layout::Settings newLayoutSettings; + + QString getNewMapName() const; + QString getNewLayoutName() const; + void initNewMapSettings(); + void initNewLayoutSettings(); + Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); - NewMapSettings getNewMapSettings() const; - Layout::Settings getNewLayoutSettings() const; bool isIdentifierUnique(const QString &identifier) const; QString getProjectTitle(); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index ba4396b9..5fdb780f 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -30,10 +30,7 @@ class NewLayoutDialog : public QDialog private: Ui::NewLayoutDialog *ui; Project *project; - Layout *importedLayout = nullptr; - - static Layout::Settings settings; - static bool initializedSettings; + const Layout *layoutToCopy; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. @@ -41,7 +38,6 @@ class NewLayoutDialog : public QDialog bool validateName(bool allowEmpty = false); void refresh(); - void saveSettings(); bool isExistingLayout() const; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 15dd3f6c..1d0f316f 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -19,8 +19,8 @@ class NewMapDialog : public QDialog Q_OBJECT public: explicit NewMapDialog(Project *project, QWidget *parent = nullptr); + explicit NewMapDialog(Project *project, const Map *mapToCopy = nullptr, QWidget *parent = nullptr); explicit NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent = nullptr); - explicit NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent = nullptr); ~NewMapDialog(); virtual void accept() override; @@ -30,26 +30,21 @@ class NewMapDialog : public QDialog Project *project; CollapsibleSection *headerSection; MapHeaderForm *headerForm; - Map *importedMap = nullptr; - - static Project::NewMapSettings settings; - static bool initializedSettings; + const Map *mapToCopy; // Each of these validation functions will allow empty names up until `OK` is selected, // because clearing the text during editing is common and we don't want to flash errors for this. - bool validateMapID(bool allowEmpty = false); bool validateName(bool allowEmpty = false); bool validateGroup(bool allowEmpty = false); bool validateLayoutID(bool allowEmpty = false); - void setUI(const Project::NewMapSettings &settings); + void refresh(); void saveSettings(); void setLayout(const Layout *mapLayout); private slots: void dialogButtonClicked(QAbstractButton *button); void on_lineEdit_Name_textChanged(const QString &); - void on_lineEdit_MapID_textChanged(const QString &); void on_comboBox_Group_currentTextChanged(const QString &text); void on_comboBox_LayoutID_currentTextChanged(const QString &text); }; diff --git a/src/project.cpp b/src/project.cpp index 3d8d3f9c..f6cddfec 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -106,6 +106,9 @@ bool Project::load() { && readEventGraphics() && readSongNames() && readMapGroups(); + + initNewLayoutSettings(); + initNewMapSettings(); applyParsedLimits(); return success; } @@ -366,16 +369,24 @@ bool Project::loadMapData(Map* map) { Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* toDuplicate) { Map *map = toDuplicate ? new Map(*toDuplicate) : new Map; map->setName(settings.name); - map->setConstantName(settings.id); map->setHeader(settings.header); map->setNeedsHealLocation(settings.canFlyTo); + // Generate a unique MAP constant. + int suffix = 2; + const QString baseMapConstant = Map::mapConstantFromName(map->name()); + QString mapConstant = baseMapConstant; + while (!isIdentifierUnique(mapConstant)) { + mapConstant = QString("%1_%2").arg(baseMapConstant).arg(suffix++); + } + map->setConstantName(mapConstant); + Layout *layout = this->mapLayouts.value(settings.layout.id); if (layout) { // Layout already exists map->setNeedsLayoutDir(false); // TODO: Remove this member? } else { - layout = createNewLayout(settings.layout); + layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); } if (!layout) { delete map; @@ -1960,60 +1971,6 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } -Project::NewMapSettings Project::getNewMapSettings() const { - // Ensure default name/ID doesn't already exist. - int i = 0; - QString newMapName; - QString newMapId; - do { - newMapName = QString("NewMap%1").arg(++i); - newMapId = Map::mapConstantFromName(newMapName); - } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(newMapId)); - - NewMapSettings settings; - settings.name = newMapName; - settings.id = newMapId; - settings.group = this->groupNames.at(0); - settings.canFlyTo = false; - settings.layout = getNewLayoutSettings(); - settings.layout.id = Layout::layoutConstantFromName(newMapName); - settings.layout.name = Layout::layoutNameFromMapName(newMapName); - settings.header.setSong(this->defaultSong); - settings.header.setLocation(this->mapSectionIdNames.value(0, "0")); - settings.header.setRequiresFlash(false); - settings.header.setWeather(this->weatherNames.value(0, "0")); - settings.header.setType(this->mapTypes.value(0, "0")); - settings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); - settings.header.setShowsLocationName(true); - settings.header.setAllowsRunning(false); - settings.header.setAllowsBiking(false); - settings.header.setAllowsEscaping(false); - settings.header.setFloorNumber(0); - return settings; -} - -Layout::Settings Project::getNewLayoutSettings() const { - // Ensure default name/ID doesn't already exist. - int i = 0; - QString newLayoutName; - QString newLayoutId; - do { - newLayoutName = QString("NewLayout%1").arg(++i); - newLayoutId = Layout::layoutConstantFromName(newLayoutName); - } while (!isIdentifierUnique(newLayoutId) || !isIdentifierUnique(newLayoutName)); - - Layout::Settings settings; - settings.name = newLayoutName; - settings.id = newLayoutId; - settings.width = getDefaultMapDimension(); - settings.height = getDefaultMapDimension(); - settings.borderWidth = DEFAULT_BORDER_WIDTH; - settings.borderHeight = DEFAULT_BORDER_HEIGHT; - settings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); - settings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); - return settings; -} - // When we ask the user to provide a new identifier for something (like a map name or MAPSEC id) // we use this to make sure that it doesn't collide with any known identifiers first. // Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap. @@ -2040,6 +1997,64 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return true; } +QString Project::getNewMapName() const { + // Ensure default name/ID doesn't already exist. + int i = 0; + QString newMapName; + do { + newMapName = QString("NewMap%1").arg(++i); + } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(Map::mapConstantFromName(newMapName))); + return newMapName; +} + +QString Project::getNewLayoutName() const { + // Ensure default name/ID doesn't already exist. + int i = 0; + QString newLayoutName; + do { + newLayoutName = QString("NewLayout%1").arg(++i); + } while (!isIdentifierUnique(newLayoutName) || !isIdentifierUnique(Layout::layoutConstantFromName(newLayoutName))); + return newLayoutName; +} + +void Project::initNewMapSettings() { + this->newMapSettings.name = getNewMapName(); + this->newMapSettings.group = this->groupNames.at(0); + this->newMapSettings.canFlyTo = false; + + this->newMapSettings.layout.name = Layout::layoutNameFromMapName(this->newMapSettings.name); + this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); + this->newMapSettings.layout.width = getDefaultMapDimension(); + this->newMapSettings.layout.height = getDefaultMapDimension(); + this->newMapSettings.layout.borderWidth = DEFAULT_BORDER_WIDTH; + this->newMapSettings.layout.borderHeight = DEFAULT_BORDER_HEIGHT; + this->newMapSettings.layout.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); + this->newMapSettings.layout.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); + + this->newMapSettings.header.setSong(this->defaultSong); + this->newMapSettings.header.setLocation(this->mapSectionIdNames.value(0, "0")); + this->newMapSettings.header.setRequiresFlash(false); + this->newMapSettings.header.setWeather(this->weatherNames.value(0, "0")); + this->newMapSettings.header.setType(this->mapTypes.value(0, "0")); + this->newMapSettings.header.setBattleScene(this->mapBattleScenes.value(0, "0")); + this->newMapSettings.header.setShowsLocationName(true); + this->newMapSettings.header.setAllowsRunning(false); + this->newMapSettings.header.setAllowsBiking(false); + this->newMapSettings.header.setAllowsEscaping(false); + this->newMapSettings.header.setFloorNumber(0); +} + +void Project::initNewLayoutSettings() { + this->newLayoutSettings.name = getNewLayoutName(); + this->newLayoutSettings.id = Layout::layoutConstantFromName(this->newLayoutSettings.name); + this->newLayoutSettings.width = getDefaultMapDimension(); + this->newLayoutSettings.height = getDefaultMapDimension(); + this->newLayoutSettings.borderWidth = DEFAULT_BORDER_WIDTH; + this->newLayoutSettings.borderHeight = DEFAULT_BORDER_HEIGHT; + this->newLayoutSettings.primaryTilesetLabel = getDefaultPrimaryTilesetLabel(); + this->newLayoutSettings.secondaryTilesetLabel = getDefaultSecondaryTilesetLabel(); +} + Project::DataQualifiers Project::getDataQualifiers(QString text, QString label) { Project::DataQualifiers qualifiers; diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index de41a3d6..7b9d347f 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -9,30 +9,56 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -Layout::Settings NewLayoutDialog::settings = {}; -bool NewLayoutDialog::initializedSettings = false; - NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : + NewLayoutDialog(project, nullptr, parent) +{} + +NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) : QDialog(parent), - ui(new Ui::NewLayoutDialog) + ui(new Ui::NewLayoutDialog), + layoutToCopy(layoutToCopy) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); - ui->label_GenericError->setVisible(false); this->project = project; - Layout::Settings newSettings = project->getNewLayoutSettings(); - if (!initializedSettings) { - // The first time this dialog is opened we initialize all the default settings. - settings = newSettings; - initializedSettings = true; + QString newName; + QString newId; + if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { + // Duplicating a layout, the initial name will be the base layout's name + // with a numbered suffix to make it unique. + // Note: Layouts imported with AdvanceMap have no name, so they'll use the default new layout name instead. + + // If the layout name ends with the default '_Layout' suffix we'll ignore it. + // This is because (normally) the ID for these layouts will not have this suffix, + // so you can end up in a situation where you might have Map_Layout and Map_2_Layout, + // and if you try to duplicate Map_Layout the next available name (because of ID collisions) + // would be Map_Layout_3 instead of Map_3_Layout. + QString baseName = this->layoutToCopy->name; + QString suffix = "_Layout"; + if (baseName.length() > suffix.length() && baseName.endsWith(suffix)) { + baseName.truncate(baseName.length() - suffix.length()); + } else { + suffix = ""; + } + + int i = 2; + do { + newName = QString("%1_%2%3").arg(baseName).arg(i).arg(suffix); + newId = QString("%1_%2").arg(this->layoutToCopy->id).arg(i); + i++; + } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); } else { - // On subsequent openings we only initialize the settings that should be unique, - // preserving all other settings from the last time the dialog was open. - settings.name = newSettings.name; - settings.id = newSettings.id; + newName = project->getNewLayoutName(); + newId = Layout::layoutConstantFromName(newName); } + + // We reset these settings for every session with the new layout dialog. + // The rest of the settings are preserved in the project between sessions. + project->newLayoutSettings.name = newName; + project->newLayoutSettings.id = newId; + ui->newLayoutForm->initUi(project); // Identifiers can only contain word characters, and cannot start with a digit. @@ -47,53 +73,34 @@ NewLayoutDialog::NewLayoutDialog(Project *project, QWidget *parent) : adjustSize(); } -// Creating new layout from an existing layout (e.g. via AdvanceMap import, or duplicating from map list). -NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, QWidget *parent) : - NewLayoutDialog(project, parent) -{ - if (!layoutToCopy) - return; - - this->importedLayout = layoutToCopy->copy(); - if (!this->importedLayout->name.isEmpty()) { - // If the layout we're duplicating has a name and ID we'll initialize the name/ID fields - // using that name and add a suffix to make it unique. - // Layouts imported with AdvanceMap won't have a name/ID. - int i = 2; - do { - settings.name = QString("%1_%2").arg(this->importedLayout->name).arg(i); - settings.id = QString("%1_%2").arg(this->importedLayout->id).arg(i); - i++; - } while (!this->project->isIdentifierUnique(settings.name) || !this->project->isIdentifierUnique(settings.id)); - } - refresh(); -} - NewLayoutDialog::~NewLayoutDialog() { saveSettings(); - delete this->importedLayout; delete ui; } void NewLayoutDialog::refresh() { - if (this->importedLayout) { + const Layout::Settings *settings = &this->project->newLayoutSettings; + + if (this->layoutToCopy) { // If we're importing a layout then some settings will be enforced. - ui->newLayoutForm->setSettings(this->importedLayout->settings()); + ui->newLayoutForm->setSettings(this->layoutToCopy->settings()); ui->newLayoutForm->setDisabled(true); } else { - ui->newLayoutForm->setSettings(settings); + ui->newLayoutForm->setSettings(*settings); ui->newLayoutForm->setDisabled(false); } - ui->lineEdit_Name->setText(settings.name); - ui->lineEdit_LayoutID->setText(settings.id); + ui->lineEdit_Name->setText(settings->name); + ui->lineEdit_LayoutID->setText(settings->id); } void NewLayoutDialog::saveSettings() { - settings = ui->newLayoutForm->settings(); - settings.id = ui->lineEdit_LayoutID->text(); - settings.name = ui->lineEdit_Name->text(); + Layout::Settings *settings = &this->project->newLayoutSettings; + + *settings = ui->newLayoutForm->settings(); + settings->id = ui->lineEdit_LayoutID->text(); + settings->name = ui->lineEdit_Name->text(); } bool NewLayoutDialog::validateLayoutID(bool allowEmpty) { @@ -146,7 +153,7 @@ void NewLayoutDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - settings = this->project->getNewLayoutSettings(); + this->project->initNewLayoutSettings(); refresh(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); @@ -165,7 +172,7 @@ void NewLayoutDialog::accept() { // Update settings from UI saveSettings(); - Layout *layout = this->project->createNewLayout(settings, this->importedLayout); + Layout *layout = this->project->createNewLayout(this->project->newLayoutSettings, this->layoutToCopy); if (!layout) { ui->label_GenericError->setText(QString("Failed to create layout. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 59fe0a16..e6d3101b 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -10,30 +10,41 @@ const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -Project::NewMapSettings NewMapDialog::settings = {}; -bool NewMapDialog::initializedSettings = false; - NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : + NewMapDialog(project, nullptr, parent) +{} + +NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : QDialog(parent), - ui(new Ui::NewMapDialog) + ui(new Ui::NewMapDialog), + mapToCopy(mapToCopy) { setAttribute(Qt::WA_DeleteOnClose); setModal(true); ui->setupUi(this); - ui->label_GenericError->setVisible(false); this->project = project; - Project::NewMapSettings newSettings = project->getNewMapSettings(); - if (!initializedSettings) { - // The first time this dialog is opened we initialize all the default settings. - settings = newSettings; - initializedSettings = true; + QString newMapName; + QString newLayoutId; + if (this->mapToCopy) { + // Duplicating a map, the initial name will be the base map's name + // with a numbered suffix to make it unique. + int i = 2; + do { + newMapName = QString("%1_%2").arg(this->mapToCopy->name()).arg(i++); + newLayoutId = Layout::layoutConstantFromName(newMapName); + } while (!project->isIdentifierUnique(newMapName) || !project->isIdentifierUnique(newLayoutId)); } else { - // On subsequent openings we only initialize the settings that should be unique, - // preserving all other settings from the last time the dialog was open. - settings.name = newSettings.name; - settings.id = newSettings.id; + // Not duplicating a map, get a generic new map name. + newMapName = project->getNewMapName(); + newLayoutId = Layout::layoutConstantFromName(newMapName); } + + // We reset these settings for every session with the new map dialog. + // The rest of the settings are preserved in the project between sessions. + project->newMapSettings.name = newMapName; + project->newMapSettings.layout.id = newLayoutId; + ui->newLayoutForm->initUi(project); ui->comboBox_Group->addItems(project->groupNames); @@ -43,7 +54,6 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : static const QRegularExpression re("[A-Za-z_]+[\\w]*"); auto validator = new QRegularExpressionValidator(re, this); ui->lineEdit_Name->setValidator(validator); - ui->lineEdit_MapID->setValidator(validator); ui->comboBox_Group->setValidator(validator); ui->comboBox_LayoutID->setValidator(validator); @@ -60,7 +70,7 @@ NewMapDialog::NewMapDialog(Project *project, QWidget *parent) : connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); - setUI(settings); + refresh(); adjustSize(); // TODO: Save geometry? } @@ -79,52 +89,53 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL this->headerForm->setLocation(mapListItem); break; case MapListTab::Layouts: + // We specifically lock the layout ID because otherwise the setting would be overwritten when + // the user changes the map name (which will normally automatically update the layout ID to match). + // For the Group/Area settings above we don't care if the user changes them afterwards. ui->comboBox_LayoutID->setTextItem(mapListItem); + ui->comboBox_LayoutID->setDisabled(true); break; } } -NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *parent) : - NewMapDialog(project, parent) -{ - if (!mapToCopy) - return; - // TODO -} - NewMapDialog::~NewMapDialog() { saveSettings(); - delete this->importedMap; delete ui; } -void NewMapDialog::setUI(const Project::NewMapSettings &settings) { - ui->lineEdit_Name->setText(settings.name); - ui->lineEdit_MapID->setText(settings.id); - ui->comboBox_Group->setTextItem(settings.group); - ui->comboBox_LayoutID->setTextItem(settings.layout.id); - if (this->importedMap && this->importedMap->layout()) { +// Reload the UI from the last-saved settings. +void NewMapDialog::refresh() { + const Project::NewMapSettings *settings = &this->project->newMapSettings; + + ui->lineEdit_Name->setText(settings->name); + ui->comboBox_Group->setTextItem(settings->group); + + // If the layout combo box is disabled, it's because we're enforcing the setting. Leave it unchanged. + if (ui->comboBox_LayoutID->isEnabled()) + ui->comboBox_LayoutID->setTextItem(settings->layout.id); + + if (this->mapToCopy && this->mapToCopy->layout()) { // When importing a layout these settings shouldn't be changed. - ui->newLayoutForm->setSettings(this->importedMap->layout()->settings()); + ui->newLayoutForm->setSettings(this->mapToCopy->layout()->settings()); } else { - ui->newLayoutForm->setSettings(settings.layout); + ui->newLayoutForm->setSettings(settings->layout); } - ui->checkBox_CanFlyTo->setChecked(settings.canFlyTo); - this->headerForm->setHeaderData(settings.header); + ui->checkBox_CanFlyTo->setChecked(settings->canFlyTo); + this->headerForm->setHeaderData(settings->header); } void NewMapDialog::saveSettings() { - settings.name = ui->lineEdit_Name->text(); - settings.id = ui->lineEdit_MapID->text(); - settings.group = ui->comboBox_Group->currentText(); - settings.layout = ui->newLayoutForm->settings(); - settings.layout.id = ui->comboBox_LayoutID->currentText(); - // We don't provide full control for naming new layouts here (just via the ID). - // If a user wants to explicitly name a layout they can create it individually before creating the map. - settings.layout.name = Layout::layoutNameFromMapName(settings.name); // TODO: Verify uniqueness - settings.canFlyTo = ui->checkBox_CanFlyTo->isChecked(); - settings.header = this->headerForm->headerData(); + Project::NewMapSettings *settings = &this->project->newMapSettings; + + settings->name = ui->lineEdit_Name->text(); + settings->group = ui->comboBox_Group->currentText(); + settings->layout = ui->newLayoutForm->settings(); + settings->layout.id = ui->comboBox_LayoutID->currentText(); + settings->layout.name = Layout::layoutNameFromMapName(settings->name); // TODO: Verify uniqueness + settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); + settings->header = this->headerForm->headerData(); + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } @@ -138,30 +149,6 @@ void NewMapDialog::setLayout(const Layout *layout) { } } -bool NewMapDialog::validateMapID(bool allowEmpty) { - QString id = ui->lineEdit_MapID->text(); - const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); - - QString errorText; - if (id.isEmpty() || id == expectedPrefix) { - if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_MapID->text()); - } else if (!id.startsWith(expectedPrefix)) { - errorText = QString("%1 must start with '%2'.").arg(ui->label_MapID->text()).arg(expectedPrefix); - } else if (!this->project->isIdentifierUnique(id)) { - errorText = QString("%1 '%2' is not unique.").arg(ui->label_MapID->text()).arg(id); - } - - bool isValid = errorText.isEmpty(); - ui->label_MapIDError->setText(errorText); - ui->label_MapIDError->setVisible(!isValid); - ui->lineEdit_MapID->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); - return isValid; -} - -void NewMapDialog::on_lineEdit_MapID_textChanged(const QString &) { - validateMapID(true); -} - bool NewMapDialog::validateName(bool allowEmpty) { QString name = ui->lineEdit_Name->text(); @@ -181,7 +168,6 @@ bool NewMapDialog::validateName(bool allowEmpty) { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); - ui->lineEdit_MapID->setText(Map::mapConstantFromName(text)); if (ui->comboBox_LayoutID->isEnabled()) { ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); } @@ -216,7 +202,7 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); } else if (!this->project->isIdentifierUnique(layoutId)) { // Layout name is already in use by something. If we're duplicating a map this isn't allowed. - if (this->importedMap) { + if (this->mapToCopy) { errorText = QString("%1 is not unique.").arg(ui->label_LayoutID->text()); // If we're not duplicating a map this is ok as long as it's the name of an existing layout. } else if (!this->project->layoutIds.contains(layoutId)) { @@ -241,7 +227,8 @@ void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { if (role == QDialogButtonBox::RejectRole){ reject(); } else if (role == QDialogButtonBox::ResetRole) { - setUI(this->project->getNewMapSettings()); + this->project->initNewMapSettings(); + refresh(); } else if (role == QDialogButtonBox::AcceptRole) { accept(); } @@ -251,7 +238,6 @@ void NewMapDialog::accept() { // Make sure to call each validation function so that all errors are shown at once. bool success = true; if (!ui->newLayoutForm->validate()) success = false; - if (!validateMapID()) success = false; if (!validateName()) success = false; if (!validateGroup()) success = false; if (!validateLayoutID()) success = false; @@ -261,7 +247,7 @@ void NewMapDialog::accept() { // Update settings from UI saveSettings(); - Map *map = this->project->createNewMap(settings, this->importedMap); + Map *map = this->project->createNewMap(this->project->newMapSettings, this->mapToCopy); if (!map) { ui->label_GenericError->setText(QString("Failed to create map. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); From 6aa88023336013a53230eb536202c0da7ded337b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 27 Nov 2024 00:15:21 -0500 Subject: [PATCH 19/42] Fix map list empty folder regression --- src/ui/maplistmodels.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 6d5dd45d..1890684b 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -203,6 +203,7 @@ MapGroupModel::MapGroupModel(Project *project, QObject *parent) : MapListModel(p this->editable = true; for (const auto &groupName : this->project->groupNames) { + insertMapFolderItem(groupName); for (const auto &mapName : this->project->groupNameToMapNames.value(groupName)) { insertMapItem(mapName, groupName); } @@ -414,6 +415,9 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(project, parent) { this->folderTypeName = "map_section"; + for (const auto &idName : this->project->mapSectionIdNames) { + insertMapFolderItem(idName); + } for (const auto &mapName : this->project->mapNames) { insertMapItem(mapName, this->project->mapNameToMapSectionName.value(mapName)); } @@ -432,6 +436,9 @@ void MapAreaModel::removeItem(QStandardItem *item) { LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) { this->folderTypeName = "map_layout"; + for (const auto &layoutId : this->project->layoutIds) { + insertMapFolderItem(layoutId); + } for (const auto &mapName : this->project->mapNames) { insertMapItem(mapName, this->project->mapNameToLayoutId.value(mapName)); } From 06a263c6895370cc968be49c7fa7251eb1a30474 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 27 Nov 2024 02:41:57 -0500 Subject: [PATCH 20/42] Fix regression to map stitch images from layout split --- include/ui/mapimageexporter.h | 17 ++- src/ui/mapimageexporter.cpp | 279 ++++++++++++++++++---------------- 2 files changed, 161 insertions(+), 135 deletions(-) diff --git a/include/ui/mapimageexporter.h b/include/ui/mapimageexporter.h index 37df3038..88a00447 100644 --- a/include/ui/mapimageexporter.h +++ b/include/ui/mapimageexporter.h @@ -45,22 +45,25 @@ class MapImageExporter : public QDialog private: Ui::MapImageExporter *ui; - Layout *layout = nullptr; - Map *map = nullptr; - Editor *editor = nullptr; - QGraphicsScene *scene = nullptr; + Layout *m_layout = nullptr; + Map *m_map = nullptr; + Editor *m_editor = nullptr; + QGraphicsScene *m_scene = nullptr; - QPixmap preview; + QPixmap m_preview; - ImageExporterSettings settings; - ImageExporterMode mode = ImageExporterMode::Normal; + ImageExporterSettings m_settings; + ImageExporterMode m_mode = ImageExporterMode::Normal; void updatePreview(); void scalePreview(); void updateShowBorderState(); void saveImage(); QPixmap getStitchedImage(QProgressDialog *progress, bool includeBorder); + QPixmap getFormattedMapPixmap(); QPixmap getFormattedMapPixmap(Map *map, bool ignoreBorder = false); + QPixmap getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder = false, bool ignoreGrid = false); + void paintGrid(QPixmap *pixmap, bool ignoreBorder = false); bool historyItemAppliesToFrame(const QUndoCommand *command); protected: diff --git a/src/ui/mapimageexporter.cpp b/src/ui/mapimageexporter.cpp index c92b7a4b..4596ba36 100644 --- a/src/ui/mapimageexporter.cpp +++ b/src/ui/mapimageexporter.cpp @@ -36,25 +36,25 @@ QString getDescription(ImageExporterMode mode) { return ""; } -MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExporterMode mode) : - QDialog(parent_), +MapImageExporter::MapImageExporter(QWidget *parent, Editor *editor, ImageExporterMode mode) : + QDialog(parent), ui(new Ui::MapImageExporter) { - this->setAttribute(Qt::WA_DeleteOnClose); + setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); - this->map = editor_->map; - this->layout = editor_->layout; - this->editor = editor_; - this->mode = mode; - this->setWindowTitle(getTitle(this->mode)); - this->ui->label_Description->setText(getDescription(this->mode)); - this->ui->groupBox_Connections->setVisible(this->mode != ImageExporterMode::Stitch); - this->ui->groupBox_Timelapse->setVisible(this->mode == ImageExporterMode::Timelapse); - - if (this->map) { - this->ui->comboBox_MapSelection->addItems(editor->project->mapNames); - this->ui->comboBox_MapSelection->setCurrentText(map->name()); - this->ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down + m_map = editor->map; + m_layout = editor->layout; + m_editor = editor; + m_mode = mode; + setWindowTitle(getTitle(m_mode)); + ui->label_Description->setText(getDescription(m_mode)); + ui->groupBox_Connections->setVisible(m_mode != ImageExporterMode::Stitch); + ui->groupBox_Timelapse->setVisible(m_mode == ImageExporterMode::Timelapse); + + if (m_map) { + ui->comboBox_MapSelection->addItems(editor->project->mapNames); + ui->comboBox_MapSelection->setCurrentText(m_map->name()); + ui->comboBox_MapSelection->setEnabled(false);// TODO: allow selecting map from drop-down } connect(ui->pushButton_Save, &QPushButton::pressed, this, &MapImageExporter::saveImage); @@ -62,7 +62,7 @@ MapImageExporter::MapImageExporter(QWidget *parent_, Editor *editor_, ImageExpor } MapImageExporter::~MapImageExporter() { - delete scene; + delete m_scene; delete ui; } @@ -80,15 +80,15 @@ void MapImageExporter::resizeEvent(QResizeEvent *event) { void MapImageExporter::saveImage() { // Make sure preview is up-to-date before we save. - if (this->preview.isNull()) + if (m_preview.isNull()) updatePreview(); - if (this->preview.isNull()) + if (m_preview.isNull()) return; - const QString title = getTitle(this->mode); - const QString itemName = this->map ? this->map->name() : this->layout->name; + const QString title = getTitle(m_mode); + const QString itemName = m_map ? m_map->name() : m_layout->name; QString defaultFilename; - switch (this->mode) + switch (m_mode) { case ImageExporterMode::Normal: defaultFilename = itemName; @@ -104,21 +104,21 @@ void MapImageExporter::saveImage() { QString defaultFilepath = QString("%1/%2.%3") .arg(FileDialog::getDirectory()) .arg(defaultFilename) - .arg(this->mode == ImageExporterMode::Timelapse ? "gif" : "png"); - QString filter = this->mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)"; + .arg(m_mode == ImageExporterMode::Timelapse ? "gif" : "png"); + QString filter = m_mode == ImageExporterMode::Timelapse ? "Image Files (*.gif)" : "Image Files (*.png *.jpg *.bmp)"; QString filepath = FileDialog::getSaveFileName(this, title, defaultFilepath, filter); if (!filepath.isEmpty()) { - switch (this->mode) { + switch (m_mode) { case ImageExporterMode::Normal: case ImageExporterMode::Stitch: // Normal and Stitch modes already have the image ready to go in the preview. - this->preview.save(filepath); + m_preview.save(filepath); break; case ImageExporterMode::Timelapse: // Timelapse will play in order of layout changes then map changes (events) // TODO: potentially update in the future? QGifImage timelapseImg; - timelapseImg.setDefaultDelay(this->settings.timelapseDelayMs); + timelapseImg.setDefaultDelay(m_settings.timelapseDelayMs); timelapseImg.setDefaultTransparentColor(QColor(0, 0, 0)); // lambda to avoid redundancy @@ -130,9 +130,9 @@ void MapImageExporter::saveImage() { progress.setMaximum(1); progress.setValue(0); - int maxWidth = this->layout->getWidth() * 16; - int maxHeight = this->layout->getHeight() * 16; - if (this->settings.showBorder) { + int maxWidth = m_layout->getWidth() * 16; + int maxHeight = m_layout->getHeight() * 16; + if (m_settings.showBorder) { maxWidth += 2 * STITCH_MODE_BORDER_DISTANCE * 16; maxHeight += 2 * STITCH_MODE_BORDER_DISTANCE * 16; } @@ -141,9 +141,9 @@ void MapImageExporter::saveImage() { while (historyStack->canUndo()) { progress.setValue(i); historyStack->undo(); - int width = this->layout->getWidth() * 16; - int height = this->layout->getHeight() * 16; - if (this->settings.showBorder) { + int width = m_layout->getWidth() * 16; + int height = m_layout->getHeight() * 16; + if (m_settings.showBorder) { width += 2 * STITCH_MODE_BORDER_DISTANCE * 16; height += 2 * STITCH_MODE_BORDER_DISTANCE * 16; } @@ -174,7 +174,7 @@ void MapImageExporter::saveImage() { historyStack->redo(); } progress.setValue(progress.maximum() - i); - QPixmap pixmap = this->getFormattedMapPixmap(this->map); + QPixmap pixmap = getFormattedMapPixmap(); if (pixmap.width() < maxWidth || pixmap.height() < maxHeight) { QPixmap pixmap2 = QPixmap(maxWidth, maxHeight); QPainter painter(&pixmap2); @@ -184,7 +184,7 @@ void MapImageExporter::saveImage() { pixmap = pixmap2; } timelapseImg.addFrame(pixmap.toImage()); - for (int j = 0; j < this->settings.timelapseSkipAmount; j++) { + for (int j = 0; j < m_settings.timelapseSkipAmount; j++) { if (i > 0) { i--; historyStack->redo(); @@ -197,21 +197,21 @@ void MapImageExporter::saveImage() { } } // The latest map state is the last animated frame. - QPixmap pixmap = this->getFormattedMapPixmap(this->map); + QPixmap pixmap = getFormattedMapPixmap(); timelapseImg.addFrame(pixmap.toImage()); progress.close(); }; - if (this->layout) - generateTimelapseFromHistory("Building layout timelapse...", &this->layout->editHistory); + if (m_layout) + generateTimelapseFromHistory("Building layout timelapse...", &m_layout->editHistory); - if (this->map) - generateTimelapseFromHistory("Building map timelapse...", this->map->editHistory()); + if (m_map) + generateTimelapseFromHistory("Building map timelapse...", m_map->editHistory()); timelapseImg.save(filepath); break; } - this->close(); + close(); } } @@ -230,26 +230,26 @@ bool MapImageExporter::historyItemAppliesToFrame(const QUndoCommand *command) { case CommandId::ID_PaintCollision: case CommandId::ID_BucketFillCollision: case CommandId::ID_MagicFillCollision: - return this->settings.showCollision; + return m_settings.showCollision; case CommandId::ID_PaintBorder: - return this->settings.showBorder; + return m_settings.showBorder; case CommandId::ID_MapConnectionMove: case CommandId::ID_MapConnectionChangeDirection: case CommandId::ID_MapConnectionChangeMap: case CommandId::ID_MapConnectionAdd: case CommandId::ID_MapConnectionRemove: - return this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections; + return m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections; case CommandId::ID_EventMove: case CommandId::ID_EventShift: case CommandId::ID_EventCreate: case CommandId::ID_EventDelete: case CommandId::ID_EventDuplicate: { bool eventTypeIsApplicable = - (this->settings.showObjects && (command->id() & IDMask_EventType_Object) != 0) - || (this->settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0) - || (this->settings.showBGs && (command->id() & IDMask_EventType_BG) != 0) - || (this->settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0) - || (this->settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0); + (m_settings.showObjects && (command->id() & IDMask_EventType_Object) != 0) + || (m_settings.showWarps && (command->id() & IDMask_EventType_Warp) != 0) + || (m_settings.showBGs && (command->id() & IDMask_EventType_BG) != 0) + || (m_settings.showTriggers && (command->id() & IDMask_EventType_Trigger) != 0) + || (m_settings.showHealLocations && (command->id() & IDMask_EventType_Heal) != 0); return eventTypeIsApplicable; } default: @@ -269,7 +269,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu QSet visited; QList stitchedMaps; QList unvisited; - unvisited.append(StitchedMap{0, 0, this->editor->map}); + unvisited.append(StitchedMap{0, 0, m_editor->map}); progress->setLabelText("Gathering stitched maps..."); while (!unvisited.isEmpty()) { @@ -362,7 +362,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu pixelX -= STITCH_MODE_BORDER_DISTANCE * 16; pixelY -= STITCH_MODE_BORDER_DISTANCE * 16; } - QPixmap pixmap = this->getFormattedMapPixmap(map.map); + QPixmap pixmap = getFormattedMapPixmap(map.map); painter.drawPixmap(pixelX, pixelY, pixmap); } @@ -383,7 +383,7 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu int pixelX = (map.x - minX) * 16; int pixelY = (map.y - minY) * 16; - QPixmap pixmapWithoutBorders = this->getFormattedMapPixmap(map.map, true); + QPixmap pixmapWithoutBorders = getFormattedMapPixmap(map.map, true); painter.drawPixmap(pixelX, pixelY, pixmapWithoutBorders); } } @@ -392,45 +392,50 @@ QPixmap MapImageExporter::getStitchedImage(QProgressDialog *progress, bool inclu } void MapImageExporter::updatePreview() { - if (this->scene) { - delete this->scene; - this->scene = nullptr; + if (m_scene) { + delete m_scene; + m_scene = nullptr; } - this->scene = new QGraphicsScene; + m_scene = new QGraphicsScene; - if (this->mode == ImageExporterMode::Stitch) { + if (m_mode == ImageExporterMode::Stitch) { QProgressDialog progress("Building map stitch...", "Cancel", 0, 1, this); progress.setAutoClose(true); progress.setWindowModality(Qt::WindowModal); progress.setModal(true); progress.setMinimumDuration(1000); - this->preview = getStitchedImage(&progress, this->settings.showBorder); + m_preview = getStitchedImage(&progress, m_settings.showBorder); progress.close(); } else { // Timelapse mode doesn't currently have a real preview. It just displays the current map as in Normal mode. - this->preview = getFormattedMapPixmap(this->map); + m_preview = getFormattedMapPixmap(); } - this->scene->addPixmap(this->preview); - ui->graphicsView_Preview->setScene(scene); + m_scene->addPixmap(m_preview); + ui->graphicsView_Preview->setScene(m_scene); scalePreview(); } void MapImageExporter::scalePreview() { - if (this->scene && !this->settings.previewActualSize){ - ui->graphicsView_Preview->fitInView(this->scene->sceneRect(), Qt::KeepAspectRatioByExpanding); + if (m_scene && !m_settings.previewActualSize){ + ui->graphicsView_Preview->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatioByExpanding); } } -QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { - Layout *layout = this->map ? this->map->layout() : this->layout; - layout->render(true); +QPixmap MapImageExporter::getFormattedMapPixmap() { + return m_map ? getFormattedMapPixmap(m_map) : getFormattedLayoutPixmap(m_layout); +} +QPixmap MapImageExporter::getFormattedLayoutPixmap(Layout *layout, bool ignoreBorder, bool ignoreGrid) { + if (!layout) + return QPixmap(); + + layout->render(true); QPixmap pixmap = layout->pixmap; - if (this->settings.showCollision) { + if (m_settings.showCollision) { QPainter collisionPainter(&pixmap); layout->renderCollision(true); - collisionPainter.setOpacity(editor->collisionOpacity); + collisionPainter.setOpacity(m_editor->collisionOpacity); collisionPainter.drawPixmap(0, 0, layout->collision_pixmap); collisionPainter.end(); } @@ -438,11 +443,11 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { // draw map border // note: this will break when allowing map to be selected from drop down maybe int borderHeight = 0, borderWidth = 0; - if (!ignoreBorder && this->settings.showBorder) { - int borderDistance = this->mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; + if (!ignoreBorder && m_settings.showBorder) { + int borderDistance = m_mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; layout->renderBorder(); - int borderHorzDist = editor->getBorderDrawDistance(layout->getBorderWidth()); - int borderVertDist = editor->getBorderDrawDistance(layout->getBorderHeight()); + int borderHorzDist = m_editor->getBorderDrawDistance(layout->getBorderWidth()); + int borderVertDist = m_editor->getBorderDrawDistance(layout->getBorderHeight()); borderWidth = borderDistance * 16; borderHeight = borderDistance * 16; QPixmap newPixmap = QPixmap(layout->pixmap.width() + borderWidth * 2, layout->pixmap.height() + borderHeight * 2); @@ -457,20 +462,34 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { pixmap = newPixmap; } - if (!this->map) { - return pixmap; - } + // The grid should be painted last, so if this layout pixmap is being painted + // as part of a map (which has more to paint after this) then don't paint the grid yet. + if (!ignoreGrid) + paintGrid(&pixmap, ignoreBorder); + + return pixmap; +} - if (!ignoreBorder && (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections)) { +QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { + if (!map) + return QPixmap(); + + QPixmap pixmap = getFormattedLayoutPixmap(map->layout(), ignoreBorder, true); + + if (!ignoreBorder && (m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections)) { // if showing connections, draw on outside of image QPainter connectionPainter(&pixmap); + + int borderDistance = m_mode ? STITCH_MODE_BORDER_DISTANCE : BORDER_DISTANCE; + int borderWidth = borderDistance * 16; + int borderHeight = borderDistance * 16; // TODO: Reading the connections from the editor and not 'map' is incorrect. - for (auto connectionItem : editor->connection_items) { + for (auto connectionItem : m_editor->connection_items) { const QString direction = connectionItem->connection->direction(); - if ((this->settings.showUpConnections && direction == "up") - || (this->settings.showDownConnections && direction == "down") - || (this->settings.showLeftConnections && direction == "left") - || (this->settings.showRightConnections && direction == "right")) + if ((m_settings.showUpConnections && direction == "up") + || (m_settings.showDownConnections && direction == "down") + || (m_settings.showLeftConnections && direction == "left") + || (m_settings.showRightConnections && direction == "right")) connectionPainter.drawImage(connectionItem->x() + borderWidth, connectionItem->y() + borderHeight, connectionItem->connection->getPixmap().toImage()); } @@ -478,37 +497,43 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { } // draw events - if (this->settings.showObjects || this->settings.showWarps || this->settings.showBGs || this->settings.showTriggers || this->settings.showHealLocations) { + if (m_settings.showObjects || m_settings.showWarps || m_settings.showBGs || m_settings.showTriggers || m_settings.showHealLocations) { QPainter eventPainter(&pixmap); int pixelOffset = 0; - if (!ignoreBorder && this->settings.showBorder) { - pixelOffset = this->mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; + if (!ignoreBorder && m_settings.showBorder) { + pixelOffset = m_mode == ImageExporterMode::Normal ? BORDER_DISTANCE * 16 : STITCH_MODE_BORDER_DISTANCE * 16; } const QList events = map->getEvents(); for (const auto &event : events) { Event::Group group = event->getEventGroup(); - if ((this->settings.showObjects && group == Event::Group::Object) - || (this->settings.showWarps && group == Event::Group::Warp) - || (this->settings.showBGs && group == Event::Group::Bg) - || (this->settings.showTriggers && group == Event::Group::Coord) - || (this->settings.showHealLocations && group == Event::Group::Heal)) { - editor->project->setEventPixmap(event); + if ((m_settings.showObjects && group == Event::Group::Object) + || (m_settings.showWarps && group == Event::Group::Warp) + || (m_settings.showBGs && group == Event::Group::Bg) + || (m_settings.showTriggers && group == Event::Group::Coord) + || (m_settings.showHealLocations && group == Event::Group::Heal)) { + m_editor->project->setEventPixmap(event); eventPainter.drawImage(QPoint(event->getPixelX() + pixelOffset, event->getPixelY() + pixelOffset), event->getPixmap().toImage()); } } eventPainter.end(); } + paintGrid(&pixmap, ignoreBorder); + return pixmap; +} + +void MapImageExporter::paintGrid(QPixmap *pixmap, bool ignoreBorder) { // draw grid directly onto the pixmap // since the last grid lines are outside of the pixmap, add a pixel to the bottom and right - if (this->settings.showGrid) { + if (m_settings.showGrid) { + bool hasBorder = !ignoreBorder && m_settings.showBorder; int addX = 1, addY = 1; - if (borderHeight) addY = 0; - if (borderWidth) addX = 0; + if (hasBorder) addY = 0; + if (hasBorder) addX = 0; - QPixmap newPixmap= QPixmap(pixmap.width() + addX, pixmap.height() + addY); + QPixmap newPixmap= QPixmap(pixmap->width() + addX, pixmap->height() + addY); QPainter gridPainter(&newPixmap); - gridPainter.drawImage(QPoint(0, 0), pixmap.toImage()); + gridPainter.drawImage(QPoint(0, 0), pixmap->toImage()); for (int x = 0; x < newPixmap.width(); x += 16) { gridPainter.drawLine(x, 0, x, newPixmap.height()); } @@ -516,58 +541,56 @@ QPixmap MapImageExporter::getFormattedMapPixmap(Map *map, bool ignoreBorder) { gridPainter.drawLine(0, y, newPixmap.width(), y); } gridPainter.end(); - pixmap = newPixmap; + *pixmap = newPixmap; } - - return pixmap; } void MapImageExporter::updateShowBorderState() { // If any of the Connections settings are enabled then this setting is locked (it's implicitly enabled) - bool on = (this->settings.showUpConnections || this->settings.showDownConnections || this->settings.showLeftConnections || this->settings.showRightConnections); + bool on = (m_settings.showUpConnections || m_settings.showDownConnections || m_settings.showLeftConnections || m_settings.showRightConnections); const QSignalBlocker blocker(ui->checkBox_Border); ui->checkBox_Border->setChecked(on); ui->checkBox_Border->setDisabled(on); - this->settings.showBorder = on; + m_settings.showBorder = on; } void MapImageExporter::on_checkBox_Elevation_stateChanged(int state) { - this->settings.showCollision = (state == Qt::Checked); + m_settings.showCollision = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Grid_stateChanged(int state) { - this->settings.showGrid = (state == Qt::Checked); + m_settings.showGrid = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Border_stateChanged(int state) { - this->settings.showBorder = (state == Qt::Checked); + m_settings.showBorder = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Objects_stateChanged(int state) { - this->settings.showObjects = (state == Qt::Checked); + m_settings.showObjects = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Warps_stateChanged(int state) { - this->settings.showWarps = (state == Qt::Checked); + m_settings.showWarps = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_BGs_stateChanged(int state) { - this->settings.showBGs = (state == Qt::Checked); + m_settings.showBGs = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_Triggers_stateChanged(int state) { - this->settings.showTriggers = (state == Qt::Checked); + m_settings.showTriggers = (state == Qt::Checked); updatePreview(); } void MapImageExporter::on_checkBox_HealLocations_stateChanged(int state) { - this->settings.showHealLocations = (state == Qt::Checked); + m_settings.showHealLocations = (state == Qt::Checked); updatePreview(); } @@ -578,51 +601,51 @@ void MapImageExporter::on_checkBox_AllEvents_stateChanged(int state) { const QSignalBlocker b_Objects(ui->checkBox_Objects); ui->checkBox_Objects->setChecked(on); ui->checkBox_Objects->setDisabled(on); - this->settings.showObjects = on; + m_settings.showObjects = on; const QSignalBlocker b_Warps(ui->checkBox_Warps); ui->checkBox_Warps->setChecked(on); ui->checkBox_Warps->setDisabled(on); - this->settings.showWarps = on; + m_settings.showWarps = on; const QSignalBlocker b_BGs(ui->checkBox_BGs); ui->checkBox_BGs->setChecked(on); ui->checkBox_BGs->setDisabled(on); - this->settings.showBGs = on; + m_settings.showBGs = on; const QSignalBlocker b_Triggers(ui->checkBox_Triggers); ui->checkBox_Triggers->setChecked(on); ui->checkBox_Triggers->setDisabled(on); - this->settings.showTriggers = on; + m_settings.showTriggers = on; const QSignalBlocker b_HealLocations(ui->checkBox_HealLocations); ui->checkBox_HealLocations->setChecked(on); ui->checkBox_HealLocations->setDisabled(on); - this->settings.showHealLocations = on; + m_settings.showHealLocations = on; updatePreview(); } void MapImageExporter::on_checkBox_ConnectionUp_stateChanged(int state) { - this->settings.showUpConnections = (state == Qt::Checked); + m_settings.showUpConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionDown_stateChanged(int state) { - this->settings.showDownConnections = (state == Qt::Checked); + m_settings.showDownConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionLeft_stateChanged(int state) { - this->settings.showLeftConnections = (state == Qt::Checked); + m_settings.showLeftConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ConnectionRight_stateChanged(int state) { - this->settings.showRightConnections = (state == Qt::Checked); + m_settings.showRightConnections = (state == Qt::Checked); updateShowBorderState(); updatePreview(); } @@ -634,30 +657,30 @@ void MapImageExporter::on_checkBox_AllConnections_stateChanged(int state) { const QSignalBlocker b_Up(ui->checkBox_ConnectionUp); ui->checkBox_ConnectionUp->setChecked(on); ui->checkBox_ConnectionUp->setDisabled(on); - this->settings.showUpConnections = on; + m_settings.showUpConnections = on; const QSignalBlocker b_Down(ui->checkBox_ConnectionDown); ui->checkBox_ConnectionDown->setChecked(on); ui->checkBox_ConnectionDown->setDisabled(on); - this->settings.showDownConnections = on; + m_settings.showDownConnections = on; const QSignalBlocker b_Left(ui->checkBox_ConnectionLeft); ui->checkBox_ConnectionLeft->setChecked(on); ui->checkBox_ConnectionLeft->setDisabled(on); - this->settings.showLeftConnections = on; + m_settings.showLeftConnections = on; const QSignalBlocker b_Right(ui->checkBox_ConnectionRight); ui->checkBox_ConnectionRight->setChecked(on); ui->checkBox_ConnectionRight->setDisabled(on); - this->settings.showRightConnections = on; + m_settings.showRightConnections = on; updateShowBorderState(); updatePreview(); } void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) { - this->settings.previewActualSize = (state == Qt::Checked); - if (this->settings.previewActualSize) { + m_settings.previewActualSize = (state == Qt::Checked); + if (m_settings.previewActualSize) { ui->graphicsView_Preview->resetTransform(); } else { scalePreview(); @@ -665,20 +688,20 @@ void MapImageExporter::on_checkBox_ActualSize_stateChanged(int state) { } void MapImageExporter::on_pushButton_Reset_pressed() { - this->settings = {}; + m_settings = {}; for (auto widget : this->findChildren()) { const QSignalBlocker b(widget); // Prevent calls to updatePreview widget->setChecked(false); } - ui->spinBox_TimelapseDelay->setValue(this->settings.timelapseDelayMs); - ui->spinBox_FrameSkip->setValue(this->settings.timelapseSkipAmount); + ui->spinBox_TimelapseDelay->setValue(m_settings.timelapseDelayMs); + ui->spinBox_FrameSkip->setValue(m_settings.timelapseSkipAmount); updatePreview(); } void MapImageExporter::on_spinBox_TimelapseDelay_valueChanged(int delayMs) { - this->settings.timelapseDelayMs = delayMs; + m_settings.timelapseDelayMs = delayMs; } void MapImageExporter::on_spinBox_FrameSkip_valueChanged(int skip) { - this->settings.timelapseSkipAmount = skip; + m_settings.timelapseSkipAmount = skip; } From 83ef14a2420e5418f92f866dcdc69b2f5fef77f8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 27 Nov 2024 02:42:14 -0500 Subject: [PATCH 21/42] Fix some problems with layout directory creation --- include/core/map.h | 3 -- include/core/maplayout.h | 6 ++- include/ui/newmapdialog.h | 1 - src/core/maplayout.cpp | 4 +- src/mainwindow.cpp | 4 +- src/project.cpp | 83 ++++++++++++++++++-------------------- src/ui/newlayoutdialog.cpp | 22 ++-------- src/ui/newlayoutform.cpp | 1 + src/ui/newmapdialog.cpp | 37 ++++++++++------- 9 files changed, 77 insertions(+), 84 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index 00e7e63c..6e953877 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -67,12 +67,10 @@ class Map : public QObject QString sharedEventsMap() const { return m_sharedEventsMap; } QString sharedScriptsMap() const { return m_sharedScriptsMap; } - void setNeedsLayoutDir(bool needsLayoutDir) { m_needsLayoutDir = needsLayoutDir; } void setNeedsHealLocation(bool needsHealLocation) { m_needsHealLocation = needsHealLocation; } void setIsPersistedToFile(bool persistedToFile) { m_isPersistedToFile = persistedToFile; } void setHasUnsavedDataChanges(bool unsavedDataChanges) { m_hasUnsavedDataChanges = unsavedDataChanges; } - bool needsLayoutDir() const { return m_needsLayoutDir; } bool needsHealLocation() const { return m_needsHealLocation; } bool isPersistedToFile() const { return m_isPersistedToFile; } bool hasUnsavedDataChanges() const { return m_hasUnsavedDataChanges; } @@ -121,7 +119,6 @@ class Map : public QObject bool m_isPersistedToFile = true; bool m_hasUnsavedDataChanges = false; - bool m_needsLayoutDir = true; bool m_needsHealLocation = false; bool m_scriptsLoaded = false; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 5d717a43..7761d7fc 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -22,8 +22,9 @@ class Layout : public QObject { Layout() {} Layout(const Layout &other); - static QString layoutNameFromMapName(const QString &mapName); static QString layoutConstantFromName(QString mapName); + static QString defaultSuffix(); + bool loaded = false; @@ -77,6 +78,9 @@ class Layout : public QObject { struct Settings { QString id; QString name; + // The name of a new layout's folder in `data/layouts/` is not always the same as the layout's name + // (e.g. the majority of the default layouts use the name of their associated map). + QString folderName; int width; int height; int borderWidth; diff --git a/include/ui/newmapdialog.h b/include/ui/newmapdialog.h index 1d0f316f..4b560bf1 100644 --- a/include/ui/newmapdialog.h +++ b/include/ui/newmapdialog.h @@ -40,7 +40,6 @@ class NewMapDialog : public QDialog void refresh(); void saveSettings(); - void setLayout(const Layout *mapLayout); private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index c30ae798..26d4cb89 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,8 +32,8 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -QString Layout::layoutNameFromMapName(const QString &mapName) { - return QString("%1_Layout").arg(mapName); +QString Layout::defaultSuffix() { + return "_Layout"; } QString Layout::layoutConstantFromName(QString mapName) { diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index bb01d5c5..9623963a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1382,7 +1382,9 @@ void MainWindow::mapListAddArea() { void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); - // TODO: Creating a new map shouldn't be automatically saved + // TODO: Creating a new map shouldn't be automatically saved. + // For one, it takes away the option to discard the new map. + // For two, if the new map uses an existing layout, any unsaved changes to that layout will also be saved. editor->project->saveMap(newMap); editor->project->saveAllDataStructures(); diff --git a/src/project.cpp b/src/project.cpp index f6cddfec..7772ac8c 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -382,15 +382,17 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setConstantName(mapConstant); Layout *layout = this->mapLayouts.value(settings.layout.id); - if (layout) { - // Layout already exists - map->setNeedsLayoutDir(false); // TODO: Remove this member? - } else { - layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); - } if (!layout) { - delete map; - return nullptr; + // Layout doesn't already exist, create it. + layout = createNewLayout(settings.layout, toDuplicate ? toDuplicate->layout() : nullptr); + if (!layout) { + // Layout creation failed. + delete map; + return nullptr; + } + } else { + // This layout already exists. Make sure it's loaded. + loadLayout(layout); } map->setLayout(layout); @@ -444,14 +446,17 @@ Layout *Project::createNewLayout(const Layout::Settings &settings, const Layout layout->tileset_primary_label = settings.primaryTilesetLabel; layout->tileset_secondary_label = settings.secondaryTilesetLabel; - const QString basePath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders); - layout->border_path = QString("%1%2/border.bin").arg(basePath, layout->name); - layout->blockdata_path = QString("%1%2/map.bin").arg(basePath, layout->name); - - // Create a new directory for the layout - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), layout->name); - if (!QDir::root().mkdir(newLayoutDir)) { - logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); + // If a special folder name was specified (as in the case when we're creating a layout for a new map) then use that name. + // Otherwise the new layout's folder name will just be the layout's name. + const QString folderName = !settings.folderName.isEmpty() ? settings.folderName : layout->name; + const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_layouts_folders) + folderName; + layout->border_path = folderPath + "/border.bin"; + layout->blockdata_path = folderPath + "/map.bin"; + + // Create a new directory for the layout, if it doesn't already exist. + const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new layout: '%1'").arg(fullPath)); delete layout; return nullptr; } @@ -493,14 +498,14 @@ bool Project::loadLayout(Layout *layout) { } Layout *Project::loadLayout(QString layoutId) { - if (mapLayouts.contains(layoutId)) { - Layout *layout = mapLayouts[layoutId]; + if (this->mapLayouts.contains(layoutId)) { + Layout *layout = this->mapLayouts[layoutId]; if (loadLayout(layout)) { return layout; } } - logError(QString("Error: Failed to load layout '%1'").arg(layoutId)); + logError(QString("Failed to load layout '%1'").arg(layoutId)); return nullptr; } @@ -509,10 +514,10 @@ bool Project::loadMapLayout(Map* map) { return true; } - if (mapLayouts.contains(map->layoutId())) { - map->setLayout(mapLayouts[map->layoutId()]); + if (this->mapLayouts.contains(map->layoutId())) { + map->setLayout(this->mapLayouts[map->layoutId()]); } else { - logError(QString("Error: Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); + logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } @@ -535,8 +540,8 @@ void Project::clearMapLayouts() { bool Project::readMapLayouts() { clearMapLayouts(); - QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); - QString fullFilepath = QString("%1/%2").arg(root).arg(layoutsFilepath); + const QString layoutsFilepath = projectConfig.getFilePath(ProjectFilePath::json_layouts); + const QString fullFilepath = QString("%1/%2").arg(this->root).arg(layoutsFilepath); fileWatcher.addPath(fullFilepath); QJsonDocument layoutsDoc; if (!parser.tryParseJsonFile(&layoutsDoc, fullFilepath)) { @@ -1295,40 +1300,32 @@ void Project::saveAllMaps() { void Project::saveMap(Map *map) { // Create/Modify a few collateral files for brand new maps. - QString basePath = projectConfig.getFilePath(ProjectFilePath::data_map_folders); - QString mapDataDir = root + "/" + basePath + map->name(); + const QString folderPath = projectConfig.getFilePath(ProjectFilePath::data_map_folders) + map->name(); + const QString fullPath = QString("%1/%2").arg(this->root).arg(folderPath); if (!map->isPersistedToFile()) { - if (!QDir::root().mkdir(mapDataDir)) { - logError(QString("Error: failed to create directory for new map: '%1'").arg(mapDataDir)); + if (!QDir::root().mkpath(fullPath)) { + logError(QString("Failed to create directory for new map: '%1'").arg(fullPath)); } // Create file data/maps//scripts.inc QString text = this->getScriptDefaultString(projectConfig.usePoryScript, map->name()); - saveTextFile(mapDataDir + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); + saveTextFile(fullPath + "/scripts" + this->getScriptFileExtension(projectConfig.usePoryScript), text); if (projectConfig.createMapTextFileEnabled) { // Create file data/maps//text.inc - saveTextFile(mapDataDir + "/text" + this->getScriptFileExtension(projectConfig.usePoryScript), "\n"); + saveTextFile(fullPath + "/text" + this->getScriptFileExtension(projectConfig.usePoryScript), "\n"); } // Simply append to data/event_scripts.s. - text = QString("\n\t.include \"%1%2/scripts.inc\"\n").arg(basePath, map->name()); + text = QString("\n\t.include \"%1/scripts.inc\"\n").arg(folderPath); if (projectConfig.createMapTextFileEnabled) { - text += QString("\t.include \"%1%2/text.inc\"\n").arg(basePath, map->name()); + text += QString("\t.include \"%1/text.inc\"\n").arg(folderPath); } appendTextFile(root + "/" + projectConfig.getFilePath(ProjectFilePath::data_event_scripts), text); - - // TODO: Either simplify this redundancy or explain why we need it (to create folders without the _Layout suffix) - if (map->needsLayoutDir()) { - QString newLayoutDir = QString(root + "/%1%2").arg(projectConfig.getFilePath(ProjectFilePath::data_layouts_folders), map->name()); - if (!QDir::root().mkdir(newLayoutDir)) { - logError(QString("Error: failed to create directory for new layout: '%1'").arg(newLayoutDir)); - } - } } // Create map.json for map data. - QString mapFilepath = QString("%1/map.json").arg(mapDataDir); + QString mapFilepath = fullPath + "/map.json"; QFile mapFile(mapFilepath); if (!mapFile.open(QIODevice::WriteOnly)) { logError(QString("Error: Could not open %1 for writing").arg(mapFilepath)); @@ -1428,7 +1425,6 @@ void Project::saveMap(Map *map) { } void Project::saveLayout(Layout *layout) { - // saveLayoutBorder(layout); saveLayoutBlockdata(layout); @@ -2022,7 +2018,8 @@ void Project::initNewMapSettings() { this->newMapSettings.group = this->groupNames.at(0); this->newMapSettings.canFlyTo = false; - this->newMapSettings.layout.name = Layout::layoutNameFromMapName(this->newMapSettings.name); + this->newMapSettings.layout.folderName = this->newMapSettings.name; + this->newMapSettings.layout.name = QString("%1%2").arg(this->newMapSettings.name).arg(Layout::defaultSuffix()); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); this->newMapSettings.layout.width = getDefaultMapDimension(); this->newMapSettings.layout.height = getDefaultMapDimension(); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 7b9d347f..79af74e5 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -28,26 +28,12 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { // Duplicating a layout, the initial name will be the base layout's name // with a numbered suffix to make it unique. - // Note: Layouts imported with AdvanceMap have no name, so they'll use the default new layout name instead. - - // If the layout name ends with the default '_Layout' suffix we'll ignore it. - // This is because (normally) the ID for these layouts will not have this suffix, - // so you can end up in a situation where you might have Map_Layout and Map_2_Layout, - // and if you try to duplicate Map_Layout the next available name (because of ID collisions) - // would be Map_Layout_3 instead of Map_3_Layout. - QString baseName = this->layoutToCopy->name; - QString suffix = "_Layout"; - if (baseName.length() > suffix.length() && baseName.endsWith(suffix)) { - baseName.truncate(baseName.length() - suffix.length()); - } else { - suffix = ""; - } - + // Note: If 'layoutToCopy' is an imported AdvanceMap layout it won't have + // a name, so it uses the default new layout name instead. int i = 2; do { - newName = QString("%1_%2%3").arg(baseName).arg(i).arg(suffix); - newId = QString("%1_%2").arg(this->layoutToCopy->id).arg(i); - i++; + newName = QString("%1_%2").arg(this->layoutToCopy->name).arg(i++); + newId = Layout::layoutConstantFromName(newName); } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); } else { newName = project->getNewLayoutName(); diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 3b77b5c7..64c2fdfb 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -71,6 +71,7 @@ Layout::Settings NewLayoutForm::settings() const { return settings; } +// TODO: Validate while typing bool NewLayoutForm::validate() { // Make sure to call each validation function so that all errors are shown at once. bool valid = true; diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index e6d3101b..d362d2dc 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -71,12 +71,12 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); - adjustSize(); // TODO: Save geometry? + adjustSize(); } -// Adding new map to existing map list folder. Initialize settings accordingly. +// Adding new map to an existing map list folder. Initialize settings accordingly. // Even if we initialize settings like this we'll allow users to change them afterwards, -// because nothing is expecting them to stay at these values. +// because nothing is expecting them to stay at these values (with exception to layouts). NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapListItem, QWidget *parent) : NewMapDialog(project, parent) { @@ -132,21 +132,18 @@ void NewMapDialog::saveSettings() { settings->group = ui->comboBox_Group->currentText(); settings->layout = ui->newLayoutForm->settings(); settings->layout.id = ui->comboBox_LayoutID->currentText(); - settings->layout.name = Layout::layoutNameFromMapName(settings->name); // TODO: Verify uniqueness settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); -} + // TODO: Verify uniqueness. If the layout ID belongs to an existing layout we don't need to do this at all. + settings->layout.name = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); -void NewMapDialog::setLayout(const Layout *layout) { - if (layout) { - ui->comboBox_LayoutID->setTextItem(layout->id); - ui->newLayoutForm->setSettings(layout->settings()); - ui->newLayoutForm->setDisabled(true); - } else { - ui->newLayoutForm->setDisabled(false); - } + // Folders for new layouts created for new maps use the map name, rather than the layout name. + // There's no real reason for this, aside from maintaining consistency with the default layout + // folder names that do this (which would otherwise all have a '_Layout' suffix in the name). + settings->layout.folderName = settings->name; + + porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); } bool NewMapDialog::validateName(bool allowEmpty) { @@ -168,6 +165,8 @@ bool NewMapDialog::validateName(bool allowEmpty) { void NewMapDialog::on_lineEdit_Name_textChanged(const QString &text) { validateName(true); + + // Changing the map name updates the layout ID field to match. if (ui->comboBox_LayoutID->isEnabled()) { ui->comboBox_LayoutID->setCurrentText(Layout::layoutConstantFromName(text)); } @@ -219,7 +218,15 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) { validateLayoutID(true); - setLayout(this->project->mapLayouts.value(text)); + + // Changing the layout ID to an existing layout updates the layout settings to match. + const Layout *layout = this->project->mapLayouts.value(text); + if (layout) { + ui->newLayoutForm->setSettings(layout->settings()); + ui->newLayoutForm->setDisabled(true); + } else { + ui->newLayoutForm->setDisabled(false); + } } void NewMapDialog::dialogButtonClicked(QAbstractButton *button) { From ba4a43d5957d237292bba227cc09e35c4c6a1c06 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 3 Dec 2024 15:46:25 -0500 Subject: [PATCH 22/42] Reserve MAP_UNDEFINED --- include/project.h | 1 + src/project.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/project.h b/include/project.h index 9b6f64bf..2b84d8d4 100644 --- a/include/project.h +++ b/include/project.h @@ -238,6 +238,7 @@ class Project : public QObject static QString getExistingFilepath(QString filepath); void applyParsedLimits(); + static QString getEmptyMapDefineName(); static QString getDynamicMapDefineName(); static QString getDynamicMapName(); static int getNumTilesPrimary(); diff --git a/src/project.cpp b/src/project.cpp index 7772ac8c..22e1924e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1990,6 +1990,8 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return false; } } + if (identifier == getEmptyMapDefineName()) + return false; return true; } @@ -3050,6 +3052,11 @@ int Project::getMaxObjectEvents() return Project::max_object_events; } +QString Project::getEmptyMapDefineName() { + const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); + return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_empty); +} + QString Project::getDynamicMapDefineName() { const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); return prefix + projectConfig.getIdentifier(ProjectIdentifier::define_map_dynamic); From b7c34a67e5faeabe3215f7eea5c2e34a1feab4e7 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 3 Dec 2024 16:08:53 -0500 Subject: [PATCH 23/42] Fix AdvanceMap import memory leaks, revert name change --- forms/mainwindow.ui | 6 +++--- include/mainwindow.h | 2 +- src/mainwindow.cpp | 6 +++--- src/ui/tileseteditor.cpp | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index e9a3c440..c431d469 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2931,7 +2931,7 @@ - + @@ -3222,9 +3222,9 @@ Open Config Folder - + - Import Layout from Advance Map 1.92... + Import Map from Advance Map 1.92... diff --git a/include/mainwindow.h b/include/mainwindow.h index 17f19623..22f64a81 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -234,7 +234,7 @@ private slots: void on_action_Export_Map_Image_triggered(); void on_actionExport_Stitched_Map_Image_triggered(); void on_actionExport_Map_Timelapse_Image_triggered(); - void on_actionImport_Layout_from_Advance_Map_1_92_triggered(); + void on_actionImport_Map_from_Advance_Map_1_92_triggered(); void on_pushButton_AddConnection_clicked(); void on_button_OpenDiveMap_clicked(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 9623963a..ff39fef7 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2636,8 +2636,8 @@ void MainWindow::on_actionExport_Map_Timelapse_Image_triggered() { showExportMapImageWindow(ImageExporterMode::Timelapse); } -void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { - QString filepath = FileDialog::getOpenFileName(this, "Import Layout from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); +void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() { + QString filepath = FileDialog::getOpenFileName(this, "Import Map from Advance Map 1.92", "", "Advance Map 1.92 Map Files (*.map)"); if (filepath.isEmpty()) { return; } @@ -2658,8 +2658,8 @@ void MainWindow::on_actionImport_Layout_from_Advance_Map_1_92_triggered() { auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this); connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + connect(dialog, &NewLayoutDialog::finished, [mapLayout] { mapLayout->deleteLater(); }); dialog->open(); - delete mapLayout; } void MainWindow::showExportMapImageWindow(ImageExporterMode mode) { diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index b6bf1735..940761ad 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -987,6 +987,7 @@ void TilesetEditor::importTilesetMetatiles(Tileset *tileset, bool primary) msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setIcon(QMessageBox::Icon::Critical); msgBox.exec(); + qDeleteAll(metatiles); return; } From 9c40b04ad541d4a78dcbe98e8ce738708489b01a Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 3 Dec 2024 22:27:29 -0500 Subject: [PATCH 24/42] Redesign new tileset dialog --- forms/mainwindow.ui | 1 + forms/newtilesetdialog.ui | 356 +++++++++++++--------------------- include/core/tileset.h | 18 +- include/mainwindow.h | 3 +- include/project.h | 9 +- include/ui/newlayoutdialog.h | 1 - include/ui/newtilesetdialog.h | 20 +- src/core/tileset.cpp | 184 +++++++++++++++++- src/mainwindow.cpp | 134 ++----------- src/project.cpp | 247 +++++++++-------------- src/scriptapi/apimap.cpp | 29 +-- src/ui/newtilesetdialog.cpp | 93 ++++++--- src/ui/tileseteditor.cpp | 15 +- 13 files changed, 529 insertions(+), 581 deletions(-) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index c431d469..602ff620 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -2928,6 +2928,7 @@ + diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index c582eba0..1fed2558 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -6,238 +6,152 @@ 0 0 - 400 - 216 + 450 + 209 - - - 0 - 0 - - Add new Tileset - - - - 0 - 0 - 400 - 216 - - - - - 0 - 0 - - - - - 10 - - - 10 - - - 10 - - - 10 - - - 6 - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - 0 - 0 - - - - - 380 - 161 - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - - 0 - 0 - 380 - 155 - - - - - 6 + + + + + QFrame::Shape::StyledPanel + + + QFrame::Shadow::Raised + + + + + + Name - - 10 + + + + + + true - - 10 + + + + + + false - - 10 + + color: rgb(255, 0, 0) - - - - Name - - - - - - - true - - - - - - - Type - - - - - - - - Primary - - - - - Secondary - - - - - - - - Path - - - - - - - false - - - true - - - - - - - Symbol Name - - - - - - - false - - - true - - - - - - - Checkerboard Fill - - + + + + + + + + + Symbol Name + + + + + + + + + + + + + + Type + + + + + + + false + + + + Primary + - - - + + + Secondary + - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - +
+ + + + + Checkerboard Fill + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 1 + + + + + +
+ + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + NoScrollComboBox + QComboBox +
noscrollcombobox.h
+
+
- - - buttonBox - accepted() - NewTilesetDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - NewTilesetDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - +
diff --git a/include/core/tileset.h b/include/core/tileset.h index bb7631d7..999e731d 100644 --- a/include/core/tileset.h +++ b/include/core/tileset.h @@ -38,8 +38,6 @@ class Tileset QList> palettes; QList> palettePreviews; - bool hasUnsavedTilesImage; - static Tileset* getMetatileTileset(int, Tileset*, Tileset*); static Tileset* getTileTileset(int, Tileset*, Tileset*); static Metatile* getMetatile(int, Tileset*, Tileset*); @@ -56,10 +54,25 @@ class Tileset static QHash getHeaderMemberMap(bool usingAsm); static QString getExpectedDir(QString tilesetName, bool isSecondary); QString getExpectedDir(); + + void load(); + void loadMetatiles(); + void loadMetatileAttributes(); + void loadTilesImage(QImage *importedImage = nullptr); + void loadPalettes(); + + void save(); + void saveMetatileAttributes(); + void saveMetatiles(); + void saveTilesImage(); + void savePalettes(); + bool appendToHeaders(QString root, QString friendlyName, bool usingAsm); bool appendToGraphics(QString root, QString friendlyName, bool usingAsm); bool appendToMetatiles(QString root, QString friendlyName, bool usingAsm); + void setTilesImage(const QImage &image); + void setMetatiles(const QList &metatiles); void addMetatile(Metatile* metatile); @@ -72,6 +85,7 @@ class Tileset private: QList m_metatiles; + bool m_hasUnsavedTilesImage = false; }; #endif // TILESET_H diff --git a/include/mainwindow.h b/include/mainwindow.h index 22f64a81..c51b10cf 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -22,7 +22,6 @@ #include "mapimageexporter.h" #include "filterchildrenproxymodel.h" #include "maplistmodels.h" -#include "newtilesetdialog.h" #include "shortcutseditor.h" #include "preferenceeditor.h" #include "projectsettingseditor.h" @@ -191,6 +190,7 @@ private slots: void onNewMapGroupCreated(const QString &groupName); void onNewMapSectionCreated(const QString &idName); void onNewLayoutCreated(Layout *layout); + void onNewTilesetCreated(Tileset *tileset); void onMapLoaded(Map *map); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); @@ -412,7 +412,6 @@ private slots: QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); - int insertTilesetLabel(QStringList * list, QString label); void checkForUpdates(bool requestedByUser); void setDivingMapsVisible(bool visible); diff --git a/include/project.h b/include/project.h index 2b84d8d4..8a08cef6 100644 --- a/include/project.h +++ b/include/project.h @@ -139,6 +139,7 @@ class Project : public QObject Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); + Tileset *createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; QString getProjectTitle(); @@ -167,10 +168,7 @@ class Project : public QObject bool loadMapLayout(Map*); bool loadLayoutTilesets(Layout *); void loadTilesetAssets(Tileset*); - void loadTilesetTiles(Tileset*, QImage); - void loadTilesetMetatiles(Tileset*); void loadTilesetMetatileLabels(Tileset*); - void loadTilesetPalettes(Tileset*); void readTilesetPaths(Tileset* tileset); void saveLayout(Layout *); @@ -188,10 +186,6 @@ class Project : public QObject void saveHealLocations(Map*); void saveTilesets(Tileset*, Tileset*); void saveTilesetMetatileLabels(Tileset*, Tileset*); - void saveTilesetMetatileAttributes(Tileset*); - void saveTilesetMetatiles(Tileset*); - void saveTilesetTilesImage(Tileset*); - void saveTilesetPalettes(Tileset*); void appendTilesetLabel(const QString &label, const QString &isSecondaryStr); bool readTilesetLabels(); bool readTilesetMetatileLabels(); @@ -282,6 +276,7 @@ class Project : public QObject void mapLoaded(Map *map); void mapCreated(Map *newMap, const QString &groupName); void layoutCreated(Layout *newLayout); + void tilesetCreated(Tileset *newTileset); void mapGroupAdded(const QString &groupName); void mapSectionAdded(const QString &idName); void mapSectionIdNamesChanged(const QStringList &idNames); diff --git a/include/ui/newlayoutdialog.h b/include/ui/newlayoutdialog.h index 5fdb780f..b1a2c47b 100644 --- a/include/ui/newlayoutdialog.h +++ b/include/ui/newlayoutdialog.h @@ -39,7 +39,6 @@ class NewLayoutDialog : public QDialog void refresh(); void saveSettings(); - bool isExistingLayout() const; private slots: void dialogButtonClicked(QAbstractButton *button); diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index c2563f21..06012708 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -2,7 +2,10 @@ #define NEWTILESETDIALOG_H #include -#include "project.h" +#include + +class Project; +class Tileset; namespace Ui { class NewTilesetDialog; @@ -15,20 +18,17 @@ class NewTilesetDialog : public QDialog public: explicit NewTilesetDialog(Project *project, QWidget *parent = nullptr); ~NewTilesetDialog(); - QString path; - QString fullSymbolName; - QString friendlyName; - bool isSecondary; - bool checkerboardFill; -private slots: - void NameOrSecondaryChanged(); - void SecondaryChanged(); - void FillChanged(); + virtual void accept() override; private: Ui::NewTilesetDialog *ui; Project *project = nullptr; + const QString symbolPrefix; + + bool validateName(bool allowEmpty = false); + void onFriendlyNameChanged(const QString &friendlyName); + void dialogButtonClicked(QAbstractButton *button); }; #endif // NEWTILESETDIALOG_H diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index be3a04d4..cf21a675 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -3,6 +3,7 @@ #include "project.h" #include "log.h" #include "config.h" +#include "imageproviders.h" #include #include @@ -23,7 +24,7 @@ Tileset::Tileset(const Tileset &other) metatileLabels(other.metatileLabels), palettes(other.palettes), palettePreviews(other.palettePreviews), - hasUnsavedTilesImage(false) + m_hasUnsavedTilesImage(other.m_hasUnsavedTilesImage) { for (auto tile : other.tiles) { tiles.append(tile.copy()); @@ -397,3 +398,184 @@ QHash Tileset::getHeaderMemberMap(bool usingAsm) map.insert(metatileAttrPosition, "metatileAttributes"); return map; } + +void 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; + } + + QByteArray data = metatiles_file.readAll(); + int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); + int bytesPerMetatile = 2 * tilesPerMetatile; + int num_metatiles = data.length() / bytesPerMetatile; + for (int i = 0; i < num_metatiles; i++) { + auto metatile = new Metatile; + int index = i * bytesPerMetatile; + for (int j = 0; j < tilesPerMetatile; j++) { + uint16_t tileRaw = static_cast(data[index++]); + tileRaw |= static_cast(data[index++]) << 8; + metatile->tiles.append(Tile(tileRaw)); + } + m_metatiles.append(metatile); + } +} + +void 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; + } + + QByteArray data; + int numTiles = projectConfig.getNumTilesInMetatile(); + for (const auto &metatile : m_metatiles) { + for (int i = 0; i < numTiles; i++) { + uint16_t tile = metatile->tiles.at(i).rawValue(); + data.append(static_cast(tile)); + data.append(static_cast(tile >> 8)); + } + } + metatiles_file.write(data); +} + +void 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; + } + + QByteArray data = attrs_file.readAll(); + int attrSize = projectConfig.metatileAttributesSize; + int numMetatiles = m_metatiles.length(); + int numMetatileAttrs = data.length() / attrSize; + if (numMetatiles != numMetatileAttrs) { + logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(numMetatiles).arg(numMetatileAttrs).arg(this->name)); + } + + for (int i = 0; i < qMin(numMetatiles, numMetatileAttrs); i++) { + uint32_t attributes = 0; + for (int j = 0; j < attrSize; j++) + attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); + m_metatiles.at(i)->setAttributes(attributes); + } +} + +void 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; + } + + QByteArray data; + for (const auto &metatile : m_metatiles) { + uint32_t attributes = metatile->getAttributes(); + for (int i = 0; i < projectConfig.metatileAttributesSize; i++) + data.append(static_cast(attributes >> (8 * i))); + } + attrs_file.write(data); +} + +void Tileset::loadTilesImage(QImage *importedImage) { + QImage image; + if (importedImage) { + image = *importedImage; + m_hasUnsavedTilesImage = true; + } else if (QFile::exists(this->tilesImagePath)) { + // No image provided, load from file path. + image = QImage(this->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); + } else { + // Use default image + image = QImage(8, 8, QImage::Format_Indexed8); + } + + // Validate image contains 16 colors. + int colorCount = image.colorCount(); + if (colorCount > 16) { + flattenTo4bppImage(&image); + } else if (colorCount < 16) { + QVector colorTable = image.colorTable(); + for (int i = colorTable.length(); i < 16; i++) { + colorTable.append(Qt::black); + } + image.setColorTable(colorTable); + } + + QList tiles; + int w = 8; + int h = 8; + for (int y = 0; y < image.height(); y += h) + for (int x = 0; x < image.width(); x += w) { + QImage tile = image.copy(x, y, w, h); + tiles.append(tile); + } + this->tilesImage = image; + this->tiles = tiles; +} + +void 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; + + if (!this->tilesImage.save(this->tilesImagePath, "PNG")) { + logError(QString("Failed to save tiles image '%1'").arg(this->tilesImagePath)); + return; + } + + m_hasUnsavedTilesImage = false; +} + +void Tileset::loadPalettes() { + this->palettes.clear(); + this->palettePreviews.clear(); + + for (int i = 0; i < Project::getNumPalettesTotal(); i++) { + QList palette; + QString path = this->palettePaths.value(i); + if (!path.isEmpty()) { + bool error = false; + palette = PaletteUtil::parse(path, &error); + if (error) palette.clear(); + } + if (palette.isEmpty()) { + // Either the palette failed to load, or no palette exists. + // We expect tilesets to have a certain number of palettes, + // so fill this palette with dummy colors. + for (int j = 0; j < 16; j++) { + palette.append(qRgb(j * 16, j * 16, j * 16)); + } + } + this->palettes.append(palette); + this->palettePreviews.append(palette); + } +} + +void Tileset::savePalettes() { + 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); + } +} + +void Tileset::load() { + loadMetatiles(); + loadMetatileAttributes(); + loadTilesImage(); + loadPalettes(); +} + +// Because metatile labels are global (and handled by the project) we don't save them here. +void Tileset::save() { + saveMetatiles(); + saveMetatileAttributes(); + saveTilesImage(); + savePalettes(); +} diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ff39fef7..3c16b0f4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -25,6 +25,7 @@ #include "filedialog.h" #include "newmapdialog.h" #include "newlayoutdialog.h" +#include "newtilesetdialog.h" #include #include @@ -407,8 +408,8 @@ void MainWindow::initMapList() { layout->setContentsMargins(0, 0, 0, 0); // Create add map/layout button - // TODO: Tool tip QPushButton *buttonAdd = new QPushButton(QIcon(":/icons/add.ico"), ""); + buttonAdd->setToolTip("Create New Map"); connect(buttonAdd, &QPushButton::clicked, this, &MainWindow::openNewMapDialog); layout->addWidget(buttonAdd); @@ -623,6 +624,7 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); connect(project, &Project::mapCreated, this, &MainWindow::onNewMapCreated); connect(project, &Project::layoutCreated, this, &MainWindow::onNewLayoutCreated); + connect(project, &Project::tilesetCreated, this, &MainWindow::onNewTilesetCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); @@ -925,7 +927,6 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // setLayout, but with a visible error message in case of failure. // Use when the user is specifically requesting a layout to open. -// TODO: Update the various functions taking layout IDs to take layout names (to mirror the equivalent map functions, this discrepancy is confusing atm) bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { QMessageBox msgBox(this); @@ -1439,6 +1440,21 @@ void MainWindow::onNewMapSectionCreated(const QString &idName) { // TODO: Refresh Region Map Editor's map section dropdown, if it's open } +void MainWindow::onNewTilesetCreated(Tileset *tileset) { + QString message = QString("Created a new tileset named %1.").arg(tileset->name); + logInfo(message); + statusBar()->showMessage(message); + + // Refresh tileset combo boxes + if (!tileset->is_secondary) { + int index = this->editor->project->primaryTilesetLabels.indexOf(tileset->name); + ui->comboBox_PrimaryTileset->insertItem(index, tileset->name); + } else { + int index = this->editor->project->secondaryTilesetLabels.indexOf(tileset->name); + ui->comboBox_SecondaryTileset->insertItem(index, tileset->name); + } +} + void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); dialog->open(); @@ -1450,119 +1466,9 @@ void MainWindow::openNewLayoutDialog() { dialog->open(); } -// Insert label for newly-created tileset into sorted list of existing labels -int MainWindow::insertTilesetLabel(QStringList * list, QString label) { - int i = 0; - for (; i < list->length(); i++) - if (list->at(i) > label) break; - list->insert(i, label); - return i; -} - void MainWindow::on_actionNew_Tileset_triggered() { - NewTilesetDialog *createTilesetDialog = new NewTilesetDialog(editor->project, this); - if(createTilesetDialog->exec() == QDialog::Accepted){ - if(createTilesetDialog->friendlyName.isEmpty()) { - logError(QString("Tried to create a directory with an empty name.")); - QMessageBox msgBox(this); - msgBox.setText("Failed to add new tileset."); - QString message = QString("The given name was empty."); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; - } - QString fullDirectoryPath = editor->project->root + "/" + createTilesetDialog->path; - QDir directory; - if(directory.exists(fullDirectoryPath)) { - logError(QString("Could not create tileset \"%1\", the folder \"%2\" already exists.").arg(createTilesetDialog->friendlyName, fullDirectoryPath)); - QMessageBox msgBox(this); - msgBox.setText("Failed to add new tileset."); - QString message = QString("The folder for tileset \"%1\" already exists. View porymap.log for specific errors.").arg(createTilesetDialog->friendlyName); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; - } - if (editor->project->tilesetLabelsOrdered.contains(createTilesetDialog->fullSymbolName)) { - logError(QString("Could not create tileset \"%1\", the symbol \"%2\" already exists.").arg(createTilesetDialog->friendlyName, createTilesetDialog->fullSymbolName)); - QMessageBox msgBox(this); - msgBox.setText("Failed to add new tileset."); - QString message = QString("The symbol for tileset \"%1\" (\"%2\") already exists.").arg(createTilesetDialog->friendlyName, createTilesetDialog->fullSymbolName); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); - return; - } - directory.mkdir(fullDirectoryPath); - directory.mkdir(fullDirectoryPath + "/palettes"); - Tileset newSet; - newSet.name = createTilesetDialog->fullSymbolName; - newSet.tilesImagePath = fullDirectoryPath + "/tiles.png"; - newSet.metatiles_path = fullDirectoryPath + "/metatiles.bin"; - newSet.metatile_attrs_path = fullDirectoryPath + "/metatile_attributes.bin"; - newSet.is_secondary = createTilesetDialog->isSecondary; - int numMetatiles = createTilesetDialog->isSecondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); - QImage tilesImage(":/images/blank_tileset.png"); - editor->project->loadTilesetTiles(&newSet, tilesImage); - int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); - for(int i = 0; i < numMetatiles; ++i) { - Metatile *mt = new Metatile(); - for(int j = 0; j < tilesPerMetatile; ++j){ - Tile tile = Tile(); - if (createTilesetDialog->checkerboardFill) { - // Create a checkerboard-style dummy tileset - if (((i / 8) % 2) == 0) - tile.tileId = ((i % 2) == 0) ? 1 : 2; - else - tile.tileId = ((i % 2) == 1) ? 1 : 2; - } - mt->tiles.append(tile); - } - newSet.addMetatile(mt); - } - for(int i = 0; i < 16; ++i) { - QList currentPal; - for(int i = 0; i < 16;++i) { - currentPal.append(qRgb(0,0,0)); - } - newSet.palettes.append(currentPal); - newSet.palettePreviews.append(currentPal); - QString fileName = QString("%1.pal").arg(i, 2, 10, QLatin1Char('0')); - newSet.palettePaths.append(fullDirectoryPath+"/palettes/" + fileName); - } - newSet.palettes[0][1] = qRgb(255,0,255); - newSet.palettePreviews[0][1] = qRgb(255,0,255); - exportIndexed4BPPPng(newSet.tilesImage, newSet.tilesImagePath); - editor->project->saveTilesetMetatiles(&newSet); - editor->project->saveTilesetMetatileAttributes(&newSet); - editor->project->saveTilesetPalettes(&newSet); - - //append to tileset specific files - newSet.appendToHeaders(editor->project->root, createTilesetDialog->friendlyName, editor->project->usingAsmTilesets); - newSet.appendToGraphics(editor->project->root, createTilesetDialog->friendlyName, editor->project->usingAsmTilesets); - newSet.appendToMetatiles(editor->project->root, createTilesetDialog->friendlyName, editor->project->usingAsmTilesets); - - if (!createTilesetDialog->isSecondary) { - int index = insertTilesetLabel(&editor->project->primaryTilesetLabels, createTilesetDialog->fullSymbolName); - this->ui->comboBox_PrimaryTileset->insertItem(index, createTilesetDialog->fullSymbolName); - } else { - int index = insertTilesetLabel(&editor->project->secondaryTilesetLabels, createTilesetDialog->fullSymbolName); - this->ui->comboBox_SecondaryTileset->insertItem(index, createTilesetDialog->fullSymbolName); - } - editor->project->tilesetLabelsOrdered.append(createTilesetDialog->fullSymbolName); - - QMessageBox msgBox(this); - msgBox.setText("Successfully created tileset."); - QString message = QString("Tileset \"%1\" was created successfully.").arg(createTilesetDialog->friendlyName); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Information); - msgBox.exec(); - } + auto dialog = new NewTilesetDialog(editor->project, this); + dialog->open(); } void MainWindow::updateTilesetEditor() { diff --git a/src/project.cpp b/src/project.cpp index 22e1924e..e7fc3ab2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -122,19 +122,13 @@ QString Project::getProjectTitle() { } void Project::clearMapCache() { - for (auto *map : mapCache.values()) { - if (map) - delete map; - } - mapCache.clear(); + qDeleteAll(this->mapCache); + this->mapCache.clear(); } void Project::clearTilesetCache() { - for (auto *tileset : tilesetCache.values()) { - if (tileset) - delete tileset; - } - tilesetCache.clear(); + qDeleteAll(this->tilesetCache); + this->tilesetCache.clear(); } Map* Project::loadMap(QString mapName) { @@ -1005,25 +999,21 @@ void Project::saveHealLocationsConstants() { void Project::saveTilesets(Tileset *primaryTileset, Tileset *secondaryTileset) { saveTilesetMetatileLabels(primaryTileset, secondaryTileset); - saveTilesetMetatileAttributes(primaryTileset); - saveTilesetMetatileAttributes(secondaryTileset); - saveTilesetMetatiles(primaryTileset); - saveTilesetMetatiles(secondaryTileset); - saveTilesetTilesImage(primaryTileset); - saveTilesetTilesImage(secondaryTileset); - saveTilesetPalettes(primaryTileset); - saveTilesetPalettes(secondaryTileset); + if (primaryTileset) + primaryTileset->save(); + if (secondaryTileset) + secondaryTileset->save(); } void Project::updateTilesetMetatileLabels(Tileset *tileset) { // Erase old labels, then repopulate with new labels const QString prefix = tileset->getMetatileLabelPrefix(); - metatileLabelsMap[tileset->name].clear(); + this->metatileLabelsMap[tileset->name].clear(); for (int metatileId : tileset->metatileLabels.keys()) { if (tileset->metatileLabels[metatileId].isEmpty()) continue; QString label = prefix + tileset->metatileLabels[metatileId]; - metatileLabelsMap[tileset->name][label] = metatileId; + this->metatileLabelsMap[tileset->name][label] = metatileId; } } @@ -1082,59 +1072,6 @@ void Project::saveTilesetMetatileLabels(Tileset *primaryTileset, Tileset *second saveTextFile(root + "/" + filename, outputText); } -void Project::saveTilesetMetatileAttributes(Tileset *tileset) { - QFile attrs_file(tileset->metatile_attrs_path); - if (attrs_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QByteArray data; - for (const auto &metatile : tileset->metatiles()) { - uint32_t attributes = metatile->getAttributes(); - for (int i = 0; i < projectConfig.metatileAttributesSize; i++) - data.append(static_cast(attributes >> (8 * i))); - } - attrs_file.write(data); - } else { - logError(QString("Could not save tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); - } -} - -void Project::saveTilesetMetatiles(Tileset *tileset) { - QFile metatiles_file(tileset->metatiles_path); - if (metatiles_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - QByteArray data; - int numTiles = projectConfig.getNumTilesInMetatile(); - for (const auto &metatile : tileset->metatiles()) { - for (int i = 0; i < numTiles; i++) { - uint16_t tile = metatile->tiles.at(i).rawValue(); - data.append(static_cast(tile)); - data.append(static_cast(tile >> 8)); - } - } - metatiles_file.write(data); - } else { - tileset->clearMetatiles(); - logError(QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path)); - } -} - -void Project::saveTilesetTilesImage(Tileset *tileset) { - // Only write the tiles image if it was changed. - // Porymap will only ever change an existing tiles image by importing a new one. - if (tileset->hasUnsavedTilesImage) { - if (!tileset->tilesImage.save(tileset->tilesImagePath, "PNG")) { - logError(QString("Failed to save tiles image '%1'").arg(tileset->tilesImagePath)); - return; - } - tileset->hasUnsavedTilesImage = false; - } -} - -void Project::saveTilesetPalettes(Tileset *tileset) { - for (int i = 0; i < Project::getNumPalettesTotal(); i++) { - QString filepath = tileset->palettePaths.at(i); - PaletteUtil::writeJASC(filepath, tileset->palettes.at(i).toVector(), 0, 16); - } -} - bool Project::loadLayoutTilesets(Layout *layout) { layout->tileset_primary = getTileset(layout->tileset_primary_label); if (!layout->tileset_primary) { @@ -1465,18 +1402,9 @@ void Project::loadTilesetAssets(Tileset* tileset) { if (tileset->name.isNull()) { return; } - this->readTilesetPaths(tileset); - QImage image; - if (QFile::exists(tileset->tilesImagePath)) { - image = QImage(tileset->tilesImagePath).convertToFormat(QImage::Format_Indexed8, Qt::ThresholdDither); - flattenTo4bppImage(&image); - } else { - image = QImage(8, 8, QImage::Format_Indexed8); - } - this->loadTilesetTiles(tileset, image); - this->loadTilesetMetatiles(tileset); - this->loadTilesetMetatileLabels(tileset); - this->loadTilesetPalettes(tileset); + readTilesetPaths(tileset); + loadTilesetMetatileLabels(tileset); + tileset->load(); } void Project::readTilesetPaths(Tileset* tileset) { @@ -1536,84 +1464,89 @@ void Project::readTilesetPaths(Tileset* tileset) { } } -void Project::loadTilesetPalettes(Tileset* tileset) { - QList> palettes; - QList> palettePreviews; - for (int i = 0; i < tileset->palettePaths.length(); i++) { - QString path = tileset->palettePaths.value(i); - bool error = false; - QList palette = PaletteUtil::parse(path, &error); - if (error) { - for (int j = 0; j < 16; j++) { - palette.append(qRgb(j * 16, j * 16, j * 16)); - } - } +Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill) { + auto tileset = new Tileset(); + tileset->name = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + friendlyName; + tileset->is_secondary = secondary; - palettes.append(palette); - palettePreviews.append(palette); - } - tileset->palettes = palettes; - tileset->palettePreviews = palettePreviews; -} - -void Project::loadTilesetTiles(Tileset *tileset, QImage image) { - QList tiles; - int w = 8; - int h = 8; - for (int y = 0; y < image.height(); y += h) - for (int x = 0; x < image.width(); x += w) { - QImage tile = image.copy(x, y, w, h); - tiles.append(tile); - } - tileset->tilesImage = image; - tileset->tiles = tiles; -} - -void Project::loadTilesetMetatiles(Tileset* tileset) { - QFile metatiles_file(tileset->metatiles_path); - if (metatiles_file.open(QIODevice::ReadOnly)) { - QByteArray data = metatiles_file.readAll(); - int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); - int bytesPerMetatile = 2 * tilesPerMetatile; - int num_metatiles = data.length() / bytesPerMetatile; - QList metatiles; - for (int i = 0; i < num_metatiles; i++) { - Metatile *metatile = new Metatile; - int index = i * bytesPerMetatile; - for (int j = 0; j < tilesPerMetatile; j++) { - uint16_t tileRaw = static_cast(data[index++]); - tileRaw |= static_cast(data[index++]) << 8; - metatile->tiles.append(Tile(tileRaw)); + // Create tileset directories + const QString fullDirectoryPath = QString("%1/%2").arg(this->root).arg(tileset->getExpectedDir()); + QDir directory; + if (!directory.mkpath(fullDirectoryPath)) { + logError(QString("Failed to create directory '%1' for new tileset '%2'").arg(fullDirectoryPath).arg(tileset->name)); + delete tileset; + return nullptr; + } + const QString palettesPath = fullDirectoryPath + "/palettes"; + if (!directory.mkpath(palettesPath)) { + logError(QString("Failed to create palettes directory '%1' for new tileset '%2'").arg(palettesPath).arg(tileset->name)); + delete tileset; + return nullptr; + } + + tileset->tilesImagePath = fullDirectoryPath + "/tiles.png"; + tileset->metatiles_path = fullDirectoryPath + "/metatiles.bin"; + tileset->metatile_attrs_path = fullDirectoryPath + "/metatile_attributes.bin"; + + // Set default tiles image + QImage tilesImage(":/images/blank_tileset.png"); + tileset->loadTilesImage(&tilesImage); + //exportIndexed4BPPPng(tileset->tilesImage, tileset->tilesImagePath); // TODO: Make sure we can now properly handle the 8bpp images that get written without this. + + // Create default metatiles + const int numMetatiles = tileset->is_secondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); + const int tilesPerMetatile = projectConfig.getNumTilesInMetatile(); + for (int i = 0; i < numMetatiles; ++i) { + auto metatile = new Metatile(); + for(int j = 0; j < tilesPerMetatile; ++j){ + Tile tile = Tile(); + if (checkerboardFill) { + // Create a checkerboard-style dummy tileset + if (((i / 8) % 2) == 0) + tile.tileId = ((i % 2) == 0) ? 1 : 2; + else + tile.tileId = ((i % 2) == 1) ? 1 : 2; } - metatiles.append(metatile); + metatile->tiles.append(tile); } - tileset->setMetatiles(metatiles); - } else { - tileset->clearMetatiles(); - logError(QString("Could not open tileset metatiles file '%1'").arg(tileset->metatiles_path)); + tileset->addMetatile(metatile); } - QFile attrs_file(tileset->metatile_attrs_path); - if (attrs_file.open(QIODevice::ReadOnly)) { - QByteArray data = attrs_file.readAll(); - int num_metatiles = tileset->numMetatiles(); - int attrSize = projectConfig.metatileAttributesSize; - int num_metatileAttrs = data.length() / attrSize; - if (num_metatiles != num_metatileAttrs) { - logWarn(QString("Metatile count %1 does not match metatile attribute count %2 in %3").arg(num_metatiles).arg(num_metatileAttrs).arg(tileset->name)); - if (num_metatileAttrs > num_metatiles) - num_metatileAttrs = num_metatiles; + // Create default palettes + for(int i = 0; i < 16; ++i) { + QList currentPal; + for(int i = 0; i < 16;++i) { + currentPal.append(qRgb(0,0,0)); } + tileset->palettes.append(currentPal); + tileset->palettePreviews.append(currentPal); + tileset->palettePaths.append(QString("%1/%2.pal").arg(palettesPath).arg(i, 2, 10, QLatin1Char('0'))); + } + tileset->palettes[0][1] = qRgb(255,0,255); + tileset->palettePreviews[0][1] = qRgb(255,0,255); - for (int i = 0; i < num_metatileAttrs; i++) { - uint32_t attributes = 0; - for (int j = 0; j < attrSize; j++) - attributes |= static_cast(data.at(i * attrSize + j)) << (8 * j); - tileset->metatileAt(i)->setAttributes(attributes); + // Update tileset label arrays + QStringList *labelList = tileset->is_secondary ? &this->secondaryTilesetLabels : &this->primaryTilesetLabels; + for (int i = 0; i < labelList->length(); i++) { + if (labelList->at(i) > tileset->name) { + labelList->insert(i, tileset->name); + break; } - } else { - logError(QString("Could not open tileset metatile attributes file '%1'").arg(tileset->metatile_attrs_path)); } + this->tilesetLabelsOrdered.append(tileset->name); + + // TODO: Ideally we wouldn't save new Tilesets immediately + // Append to tileset specific files + tileset->appendToHeaders(this->root, friendlyName, this->usingAsmTilesets); + tileset->appendToGraphics(this->root, friendlyName, this->usingAsmTilesets); + tileset->appendToMetatiles(this->root, friendlyName, this->usingAsmTilesets); + + tileset->save(); + + this->tilesetCache.insert(tileset->name, tileset); + + emit tilesetCreated(tileset); + return tileset; } QString Project::findMetatileLabelsTileset(QString label) { @@ -1658,9 +1591,9 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) { QString metatileLabelPrefix = tileset->getMetatileLabelPrefix(); // Reverse map for faster lookup by metatile id - for (QString labelName : metatileLabelsMap[tileset->name].keys()) { - auto metatileId = metatileLabelsMap[tileset->name][labelName]; - tileset->metatileLabels[metatileId] = labelName.replace(metatileLabelPrefix, ""); + for (auto it = this->metatileLabelsMap[tileset->name].constBegin(); it != this->metatileLabelsMap[tileset->name].constEnd(); it++) { + QString labelName = it.key(); + tileset->metatileLabels[it.value()] = labelName.replace(metatileLabelPrefix, ""); } } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index f041b7b1..91cc4e67 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -337,7 +337,7 @@ void MainWindow::refreshAfterPaletteChange(Tileset *tileset) { this->editor->map_item->draw(true); this->editor->updateMapBorder(); this->editor->updateMapConnections(); - this->editor->project->saveTilesetPalettes(tileset); + tileset->savePalettes(); } void MainWindow::setTilesetPalette(Tileset *tileset, int paletteIndex, QList> colors) { @@ -576,14 +576,14 @@ void MainWindow::setSecondaryTileset(QString tileset) { void MainWindow::saveMetatilesByMetatileId(int metatileId) { Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); - if (this->editor->project && tileset) - this->editor->project->saveTilesetMetatiles(tileset); + if (tileset) + tileset->saveMetatiles(); } void MainWindow::saveMetatileAttributesByMetatileId(int metatileId) { Tileset * tileset = Tileset::getMetatileTileset(metatileId, this->editor->layout->tileset_primary, this->editor->layout->tileset_secondary); - if (this->editor->project && tileset) - this->editor->project->saveTilesetMetatileAttributes(tileset); + if (tileset) + tileset->saveMetatileAttributes(); // If the tileset editor is open it needs to be refreshed with the new changes. // Rather than do a full refresh (which is costly) we tell the editor it will need @@ -811,9 +811,6 @@ QJSValue MainWindow::getTilePixels(int tileId) { // Editing map header //===================== -// TODO: Connect signals from new function calls to update UI -// TODO: Is the error-checking for known constant names still reasonable / needed? (you can type anything after all) - QString MainWindow::getSong() { if (!this->editor || !this->editor->map) return QString(); @@ -823,10 +820,6 @@ QString MainWindow::getSong() { void MainWindow::setSong(QString song) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->songNames.contains(song)) { - logError(QString("Unknown song '%1'").arg(song)); - return; - } this->editor->map->header()->setSong(song); } @@ -867,10 +860,6 @@ QString MainWindow::getWeather() { void MainWindow::setWeather(QString weather) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->weatherNames.contains(weather)) { - logError(QString("Unknown weather '%1'").arg(weather)); - return; - } this->editor->map->header()->setWeather(weather); } @@ -883,10 +872,6 @@ QString MainWindow::getType() { void MainWindow::setType(QString type) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->mapTypes.contains(type)) { - logError(QString("Unknown map type '%1'").arg(type)); - return; - } this->editor->map->header()->setType(type); } @@ -899,10 +884,6 @@ QString MainWindow::getBattleScene() { void MainWindow::setBattleScene(QString battleScene) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->mapBattleScenes.contains(battleScene)) { - logError(QString("Unknown battle scene '%1'").arg(battleScene)); - return; - } this->editor->map->header()->setBattleScene(battleScene); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 8cb4c5f9..1ddd975d 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -1,50 +1,87 @@ #include "newtilesetdialog.h" #include "ui_newtilesetdialog.h" #include "project.h" +#include "imageexport.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : QDialog(parent), - ui(new Ui::NewTilesetDialog) + ui(new Ui::NewTilesetDialog), + symbolPrefix(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix)) { + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); ui->setupUi(this); - this->setFixedSize(this->width(), this->height()); this->project = project; + + ui->checkBox_CheckerboardFill->setChecked(porymapConfig.tilesetCheckerboardFill); + ui->label_SymbolNameDisplay->setText(this->symbolPrefix); + ui->comboBox_Type->setMinimumContentsLength(12); + //only allow characters valid for a symbol - static const QRegularExpression expression("[_A-Za-z0-9]+$"); // TODO: Incorrect, allows digits at beginning - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); - this->ui->nameLineEdit->setValidator(validator); - - bool checkerboard = porymapConfig.tilesetCheckerboardFill; - this->ui->fillCheckBox->setChecked(checkerboard); - this->checkerboardFill = checkerboard; - - connect(this->ui->nameLineEdit, &QLineEdit::textChanged, this, &NewTilesetDialog::NameOrSecondaryChanged); - connect(this->ui->typeComboBox, &QComboBox::currentTextChanged, this, &NewTilesetDialog::SecondaryChanged); - connect(this->ui->fillCheckBox, &QCheckBox::stateChanged, this, &NewTilesetDialog::FillChanged); - //connect(this->ui->toolButton, &QToolButton::clicked, this, &NewTilesetDialog::ChangeFilePath); - this->SecondaryChanged(); + static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); + ui->lineEdit_FriendlyName->setValidator(validator); + + connect(ui->lineEdit_FriendlyName, &QLineEdit::textChanged, this, &NewTilesetDialog::onFriendlyNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewTilesetDialog::dialogButtonClicked); + + adjustSize(); } NewTilesetDialog::~NewTilesetDialog() { + porymapConfig.tilesetCheckerboardFill = ui->checkBox_CheckerboardFill->isChecked(); delete ui; } -void NewTilesetDialog::SecondaryChanged(){ - this->isSecondary = (this->ui->typeComboBox->currentIndex() == 1); - NameOrSecondaryChanged(); +void NewTilesetDialog::onFriendlyNameChanged(const QString &friendlyName) { + // When the tileset name is changed, update this label to display the full symbol name. + ui->label_SymbolNameDisplay->setText(this->symbolPrefix + friendlyName); + + validateName(true); +} + +bool NewTilesetDialog::validateName(bool allowEmpty) { + const QString friendlyName = ui->lineEdit_FriendlyName->text(); + const QString symbolName = ui->label_SymbolNameDisplay->text(); + + QString errorText; + if (friendlyName.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_FriendlyName->text()); + } else if (!this->project->isIdentifierUnique(symbolName)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_SymbolName->text()).arg(symbolName); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_FriendlyName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; } -// TODO: No validation -void NewTilesetDialog::NameOrSecondaryChanged() { - this->friendlyName = this->ui->nameLineEdit->text(); - this->fullSymbolName = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + this->friendlyName; - this->ui->symbolNameLineEdit->setText(this->fullSymbolName); - this->path = Tileset::getExpectedDir(this->fullSymbolName, this->isSecondary); - this->ui->pathLineEdit->setText(this->path); +void NewTilesetDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } } -void NewTilesetDialog::FillChanged() { - this->checkerboardFill = this->ui->fillCheckBox->isChecked(); - porymapConfig.tilesetCheckerboardFill = this->checkerboardFill; +void NewTilesetDialog::accept() { + if (!validateName()) + return; + + bool secondary = ui->comboBox_Type->currentIndex() == 1; + Tileset *tileset = this->project->createNewTileset(ui->lineEdit_FriendlyName->text(), secondary, ui->checkBox_CheckerboardFill->isChecked()); + if (!tileset) { + ui->label_GenericError->setText(QString("Failed to create tileset. See %1 for details.").arg(getLogPath())); + ui->label_GenericError->setVisible(true); + return; + } + ui->label_GenericError->setVisible(false); + + QDialog::accept(); } diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 940761ad..f3c4a1c9 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -716,22 +716,9 @@ void TilesetEditor::importTilesetTiles(Tileset *tileset, bool primary) { image = image.convertToFormat(QImage::Format::Format_Indexed8, colorTable); } - // Validate image is properly indexed to 16 colors. - int colorCount = image.colorCount(); - if (colorCount > 16) { - flattenTo4bppImage(&image); - } else if (colorCount < 16) { - QVector colorTable = image.colorTable(); - for (int i = colorTable.length(); i < 16; i++) { - colorTable.append(Qt::black); - } - image.setColorTable(colorTable); - } - - this->project->loadTilesetTiles(tileset, image); + tileset->loadTilesImage(&image); this->refresh(); this->hasUnsavedChanges = true; - tileset->hasUnsavedTilesImage = true; } void TilesetEditor::closeEvent(QCloseEvent *event) From 435d22c63a16aaaea9bd4f00b1b702fffb505840 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 6 Dec 2024 11:02:38 -0500 Subject: [PATCH 25/42] Fix layouts list sorting by ID rather than name --- include/ui/maplistmodels.h | 1 + src/mainwindow.cpp | 8 ++++---- src/ui/maplistmodels.cpp | 37 ++++++++++++++++++++----------------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 1962ad40..920bf9ba 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -148,6 +148,7 @@ class LayoutTreeModel : public MapListModel { ~LayoutTreeModel() {} QVariant data(const QModelIndex &index, int role) const override; + QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override; protected: void removeItem(QStandardItem *item) override; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 3c16b0f4..71484f2f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1279,13 +1279,13 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { }); } if (copyDisplayNameAction) { - connect(copyDisplayNameAction, &QAction::triggered, [this, sourceModel, index] { - setClipboardData(sourceModel->data(index, Qt::DisplayRole).toString()); + connect(copyDisplayNameAction, &QAction::triggered, [this, selectedItem] { + setClipboardData(selectedItem->text()); }); } if (copyToolTipAction) { - connect(copyToolTipAction, &QAction::triggered, [this, sourceModel, index] { - setClipboardData(sourceModel->data(index, Qt::ToolTipRole).toString()); + connect(copyToolTipAction, &QAction::triggered, [this, selectedItem] { + setClipboardData(selectedItem->toolTip()); }); } diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 1890684b..d6b20af9 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -98,6 +98,7 @@ QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); map->setEditable(this->editable); // Will override flags if necessary + map->setToolTip(this->project->mapNamesToMapConstants.value(mapName)); this->mapItems.insert(mapName, map); return map; } @@ -147,9 +148,9 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); - if (type == "map_name") { - // Data for maps in the map list - if (role == Qt::DecorationRole) { + if (role == Qt::DecorationRole) { + if (type == "map_name") { + // Decorating map in the map list if (name == this->activeItemName) return this->mapOpenedIcon; @@ -157,12 +158,8 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { if (!map) return this->mapGrayIcon; return map->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; - } else if (role == Qt::ToolTipRole) { - return this->project->mapNamesToMapConstants.value(name); - } - } else if (type == this->folderTypeName) { - // Data for map folders in the map list - if (role == Qt::DecorationRole) { + } else if (type == this->folderTypeName) { + // Decorating map folder in the map list return item->hasChildren() ? this->mapFolderIcon : this->emptyMapFolderIcon; } } @@ -451,6 +448,19 @@ void LayoutTreeModel::removeItem(QStandardItem *) { // TODO: Deleting layouts not supported } +QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) { + folder = MapListModel::createMapFolderItem(folderName, folder); + + // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. + // We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name. + const Layout* layout = this->project->mapLayouts.value(folderName); + if (layout) { + folder->setText(layout->name); + folder->setToolTip(layout->id); + } + return folder; +} + QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); @@ -463,23 +473,16 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { const QString name = item->data(MapListUserRoles::NameRole).toString(); if (type == this->folderTypeName) { - const Layout* layout = this->project->mapLayouts.value(name); - if (role == Qt::DecorationRole) { // Map layouts are used as folders, but we display them with the same icons as maps. if (name == this->activeItemName) return this->mapOpenedIcon; + const Layout* layout = this->project->mapLayouts.value(name); if (!layout || !layout->loaded) return this->mapGrayIcon; return layout->hasUnsavedChanges() ? this->mapEditedIcon : this->mapIcon; } - else if (role == Qt::DisplayRole) { - // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. - if (layout) return layout->name; - } else if (role == Qt::ToolTipRole) { - if (layout) return layout->id; - } } return MapListModel::data(index, role); } From 391f7b16855d0c32bd099c8ac75eb8ad59847cc8 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 6 Dec 2024 12:29:40 -0500 Subject: [PATCH 26/42] Reserve MAP_DYNAMIC, fix some MAPSEC displays not updating, new error messages/handling --- forms/newlayoutdialog.ui | 5 +- include/mainwindow.h | 5 +- include/project.h | 1 + include/ui/regionmapeditor.h | 2 + src/core/regionmap.cpp | 7 ++- src/mainwindow.cpp | 91 +++++++++++++++++++++-------- src/project.cpp | 110 +++++++++++++++++++++-------------- src/scriptapi/apimap.cpp | 4 -- src/ui/mapheaderform.cpp | 9 ++- src/ui/maplistmodels.cpp | 9 +-- src/ui/regionmapeditor.cpp | 27 ++++++--- 11 files changed, 179 insertions(+), 91 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index d285bdb1..bfbc9b02 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -25,7 +25,7 @@ 0 0 238 - 106 + 107 @@ -56,6 +56,9 @@ <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> + + true + diff --git a/include/mainwindow.h b/include/mainwindow.h index c51b10cf..1e8c53bb 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -349,7 +349,9 @@ private slots: void clearProjectUI(); void openNewMapDialog(); + void openDuplicateMapDialog(const QString &mapName); void openNewLayoutDialog(); + void openDuplicateLayoutDialog(const QString &layoutId); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -360,6 +362,7 @@ private slots: bool openProject(QString dir, bool initial = false); bool closeProject(); void showProjectOpenFailure(); + void showMapsExcludedAlert(const QStringList &excludedMapNames); bool setInitialMap(); void saveGlobalConfigs(); @@ -408,7 +411,7 @@ private slots: void scrollMetatileSelectorToSelection(); MapListToolBar* getCurrentMapListToolBar(); MapTree* getCurrentMapList(); - void refreshLocationsComboBox(); + void setLocationComboBoxes(const QStringList &locations); QObjectList shortcutableObjects() const; void addCustomHeaderValue(QString key, QJsonValue value, bool isNew = false); diff --git a/include/project.h b/include/project.h index 8a08cef6..895f9979 100644 --- a/include/project.h +++ b/include/project.h @@ -280,6 +280,7 @@ class Project : public QObject void mapGroupAdded(const QString &groupName); void mapSectionAdded(const QString &idName); void mapSectionIdNamesChanged(const QStringList &idNames); + void mapsExcluded(const QStringList &excludedMapNames); }; #endif // PROJECT_H diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index c1941651..d5269548 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -44,6 +44,8 @@ class RegionMapEditor : public QMainWindow bool reconfigure(); + void setLocations(const QStringList &locations); + QObjectList shortcutableObjects() const; public slots: diff --git a/src/core/regionmap.cpp b/src/core/regionmap.cpp index 94650ccd..587be33c 100644 --- a/src/core/regionmap.cpp +++ b/src/core/regionmap.cpp @@ -397,10 +397,15 @@ void RegionMap::saveLayout() { case LayoutFormat::Binary: { QByteArray data; + int defaultValue = this->project->mapSectionIdNames.indexOf(this->default_map_section); for (int m = 0; m < this->layout_height; m++) { for (int n = 0; n < this->layout_width; n++) { int i = n + this->layout_width * m; - data.append(this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section)); + int mapSectionValue = this->project->mapSectionIdNames.indexOf(this->layouts["main"][i].map_section); + if (mapSectionValue < 0){ + mapSectionValue = defaultValue; + } + data.append(mapSectionValue); } } QFile bfile(fullPath(this->layout_path)); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 71484f2f..e006ed9a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -627,7 +627,8 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::tilesetCreated, this, &MainWindow::onNewTilesetCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); - connect(project, &Project::mapSectionIdNamesChanged, this->mapHeaderForm, &MapHeaderForm::setLocations); + connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::setLocationComboBoxes); + connect(project, &Project::mapsExcluded, this, &MainWindow::showMapsExcludedAlert); this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it @@ -702,6 +703,22 @@ void MainWindow::showProjectOpenFailure() { error.exec(); } +// Alert the user that one or more maps have been excluded while loading the project. +void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { + QMessageBox msgBox(QMessageBox::Icon::Warning, "porymap", "", QMessageBox::Ok, this); + + QString errorMsg; + if (excludedMapNames.length() == 1) { + errorMsg = QString("Failed to load map '%1'. Saving will exclude this map from your project.").arg(excludedMapNames.first()); + } else { + errorMsg = QString("Failed to load the maps listed below. Saving will exclude these maps from your project."); + msgBox.setDetailedText(excludedMapNames.join("\n")); + } + errorMsg.append(QString("\n\nPlease see %1 for full error details.").arg(getLogPath())); + msgBox.setText(errorMsg); + msgBox.exec(); +} + bool MainWindow::isProjectOpen() { return editor && editor->project; } @@ -861,19 +878,23 @@ bool MainWindow::userSetMap(QString map_name) { return true; // Already set if (map_name == editor->project->getDynamicMapName()) { - QMessageBox msgBox(this); - QString errorMsg = QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name); - msgBox.warning(nullptr, "Cannot Open Map", errorMsg); + QMessageBox msgBox(QMessageBox::Icon::Warning, + "Cannot Open Map", + QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name), + QMessageBox::Ok, + this); + msgBox.exec(); return false; } if (!setMap(map_name)) { - QMessageBox msgBox(this); - QString errorMsg = QString("There was an error opening map %1. Please see %2 for full error details.\n\n%3") - .arg(map_name) - .arg(getLogPath()) - .arg(getMostRecentError()); - msgBox.critical(nullptr, "Error Opening Map", errorMsg); + QMessageBox msgBox(QMessageBox::Icon::Critical, + "Error Opening Map", + QString("There was an error opening map %1.\n\nPlease see %2 for full error details.").arg(map_name).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); return false; } return true; @@ -929,12 +950,13 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // Use when the user is specifically requesting a layout to open. bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { - QMessageBox msgBox(this); - QString errorMsg = QString("There was an error opening layout %1. Please see %2 for full error details.\n\n%3") - .arg(layoutId) - .arg(getLogPath()) - .arg(getMostRecentError()); - msgBox.critical(nullptr, "Error Opening Layout", errorMsg); + QMessageBox msgBox(QMessageBox::Icon::Critical, + "Error Opening Layout", + QString("There was an error opening layout %1.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); return false; } @@ -1217,8 +1239,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { copyToolTipAction = menu.addAction("Copy Map ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { - auto dialog = new NewMapDialog(this->editor->project, this->editor->project->getMap(itemName), this); - dialog->open(); + openDuplicateMapDialog(itemName); }); //menu.addSeparator(); //connect(menu.addAction("Delete Map"), &QAction::triggered, [this, index] { deleteMapListItem(index); }); // TODO: No support for deleting maps @@ -1244,12 +1265,7 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { copyToolTipAction = menu.addAction("Copy Layout ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { - auto layout = this->editor->project->loadLayout(itemName); - if (layout) { - auto dialog = new NewLayoutDialog(this->editor->project, layout, this); - connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); - dialog->open(); - } + openDuplicateLayoutDialog(itemName); }); addToFolderAction = menu.addAction("Add New Map with Layout"); //menu.addSeparator(); @@ -1436,8 +1452,12 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { void MainWindow::onNewMapSectionCreated(const QString &idName) { // Add new map section to the Areas map list view this->mapAreaModel->insertMapFolderItem(idName); +} - // TODO: Refresh Region Map Editor's map section dropdown, if it's open +void MainWindow::setLocationComboBoxes(const QStringList &locations) { + this->mapHeaderForm->setLocations(locations); + if (this->regionMapEditor) + this->regionMapEditor->setLocations(locations); } void MainWindow::onNewTilesetCreated(Tileset *tileset) { @@ -1460,12 +1480,33 @@ void MainWindow::openNewMapDialog() { dialog->open(); } +void MainWindow::openDuplicateMapDialog(const QString &mapName) { + const Map *map = this->editor->project->getMap(mapName); + if (map) { + auto dialog = new NewMapDialog(this->editor->project, map, this); + dialog->open(); + } else { + //TODO + } +} + void MainWindow::openNewLayoutDialog() { auto dialog = new NewLayoutDialog(this->editor->project, this); connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); dialog->open(); } +void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { + auto layout = this->editor->project->loadLayout(layoutId); + if (layout) { + auto dialog = new NewLayoutDialog(this->editor->project, layout, this); + connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + dialog->open(); + } else { + //TODO + } +} + void MainWindow::on_actionNew_Tileset_triggered() { auto dialog = new NewTilesetDialog(editor->project, this); dialog->open(); diff --git a/src/project.cpp b/src/project.cpp index e7fc3ab2..578d20d2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -406,10 +406,10 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t mapNamePos = this->mapNames.length(); } - if (!this->mapSectionIdNames.contains(map->header()->location())) { + const QString location = map->header()->location(); + if (!this->mapSectionIdNames.contains(location) && isIdentifierUnique(location)) { // Unrecognized MAPSEC value. Add it. - // TODO: Validate location before adding - addNewMapsec(map->header()->location()); + addNewMapsec(location); } this->mapNames.insert(mapNamePos, map->name()); @@ -492,15 +492,12 @@ bool Project::loadLayout(Layout *layout) { } Layout *Project::loadLayout(QString layoutId) { - if (this->mapLayouts.contains(layoutId)) { - Layout *layout = this->mapLayouts[layoutId]; - if (loadLayout(layout)) { - return layout; - } + Layout *layout = this->mapLayouts.value(layoutId); + if (!layout || !loadLayout(layout)) { + logError(QString("Failed to load layout '%1'").arg(layoutId)); + return nullptr; } - - logError(QString("Failed to load layout '%1'").arg(layoutId)); - return nullptr; + return layout; } bool Project::loadMapLayout(Map* map) { @@ -508,12 +505,12 @@ bool Project::loadMapLayout(Map* map) { return true; } - if (this->mapLayouts.contains(map->layoutId())) { - map->setLayout(this->mapLayouts[map->layoutId()]); - } else { + Layout *layout = this->mapLayouts.value(map->layoutId()); + if (!layout) { logError(QString("Map '%1' has an unknown layout '%2'").arg(map->name()).arg(map->layoutId())); return false; } + map->setLayout(layout); if (map->hasUnsavedChanges()) { return true; @@ -558,24 +555,11 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - static const QList requiredFields = QList{ - "id", - "name", - "width", - "height", - "primary_tileset", - "secondary_tileset", - "border_filepath", - "blockdata_filepath", - }; + QStringList failedLayoutNames; // TODO: Populate for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) continue; - if (!parser.ensureFieldsExist(layoutObj, requiredFields)) { - logError(QString("Layout %1 is missing field(s) in %2.").arg(i).arg(layoutsFilepath)); - return false; - } Layout *layout = new Layout(); layout->id = ParseUtil::jsonToQString(layoutObj["id"]); if (layout->id.isEmpty()) { @@ -611,15 +595,11 @@ bool Project::readMapLayouts() { if (projectConfig.useCustomBorderSize) { int bwidth = ParseUtil::jsonToInt(layoutObj["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 - logWarn(QString("Invalid 'border_width' value '%1' for %2 in %3. Must be greater than 0. Using default (%4) instead.") - .arg(bwidth).arg(layout->id).arg(layoutsFilepath).arg(DEFAULT_BORDER_WIDTH)); bwidth = DEFAULT_BORDER_WIDTH; } layout->border_width = bwidth; int bheight = ParseUtil::jsonToInt(layoutObj["border_height"]); if (bheight <= 0) { - logWarn(QString("Invalid 'border_height' value '%1' for %2 in %3. Must be greater than 0. Using default (%4) instead.") - .arg(bheight).arg(layout->id).arg(layoutsFilepath).arg(DEFAULT_BORDER_HEIGHT)); bheight = DEFAULT_BORDER_HEIGHT; } layout->border_height = bheight; @@ -1817,8 +1797,10 @@ bool Project::readMapGroups() { QJsonArray mapGroupOrder = mapGroupsObj["group_order"].toArray(); const QString dynamicMapName = getDynamicMapName(); + const QString dynamicMapConstant = getDynamicMapDefineName(); // Process the map group lists + 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(); @@ -1829,45 +1811,73 @@ bool Project::readMapGroups() { const QString mapName = ParseUtil::jsonToQString(mapNamesJson.at(j)); if (mapName == dynamicMapName) { logWarn(QString("Ignoring map with reserved name '%1'.").arg(mapName)); + failedMapNames.append(mapName); continue; } if (this->mapNames.contains(mapName)) { logWarn(QString("Ignoring repeated map name '%1'.").arg(mapName)); + failedMapNames.append(mapName); continue; } // Load the map's json file so we can get its ID constant (and two other constants we use for the map list). QJsonDocument mapDoc; - if (!readMapJson(mapName, &mapDoc)) + if (!readMapJson(mapName, &mapDoc)) { + failedMapNames.append(mapName); continue; // Error message has already been logged + } // Read and validate the map's ID from its JSON data. const QJsonObject mapObj = mapDoc.object(); const QString mapConstant = ParseUtil::jsonToQString(mapObj["id"]); if (mapConstant.isEmpty()) { logWarn(QString("Map '%1' is missing an \"id\" value and will be ignored.").arg(mapName)); + failedMapNames.append(mapName); + continue; + } + if (mapConstant == dynamicMapConstant) { + logWarn(QString("Ignoring map with reserved \"id\" value '%1'.").arg(mapName)); + failedMapNames.append(mapName); continue; } const QString expectedPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_prefix); if (!mapConstant.startsWith(expectedPrefix)) { logWarn(QString("Map '%1' has invalid \"id\" value '%2' and will be ignored. Value must begin with '%3'.").arg(mapName).arg(mapConstant).arg(expectedPrefix)); + failedMapNames.append(mapName); continue; } auto it = this->mapConstantsToMapNames.constFind(mapConstant); if (it != this->mapConstantsToMapNames.constEnd()) { logWarn(QString("Map '%1' has the same \"id\" value '%2' as map '%3' and will be ignored.").arg(mapName).arg(it.key()).arg(it.value())); + failedMapNames.append(mapName); + continue; + } + + // Read layout ID for map list + const QString layoutId = ParseUtil::jsonToQString(mapObj["layout"]); + if (!this->layoutIds.contains(layoutId)) { + // If a map has an unknown layout ID it won't be able to load it at all anyway, so skip it. + // Skipping these will let us assume all the map layout IDs are valid, which simplies some handling elsewhere. + logWarn(QString("Map '%1' has unknown \"layout\" value '%2' and will be ignored.").arg(mapName).arg(layoutId)); + failedMapNames.append(mapName); continue; } + // Read MAPSEC name for map list + const QString mapSectionName = ParseUtil::jsonToQString(mapObj["region_map_section"]); + if (!this->mapSectionIdNames.contains(mapSectionName)) { + // An unknown location is OK. Aside from that name not appearing in the dropdowns this shouldn't cause problems. + // We'll log a warning, but allow this map to be displayed. + logWarn(QString("Map '%1' has unknown \"region_map_section\" value '%2'.").arg(mapName).arg(mapSectionName)); + } + // Success, save the constants to the project this->mapNames.append(mapName); this->groupNameToMapNames[groupName].append(mapName); - // TODO: These are not well-kept in sync (and that's probably a bad design indication. Maybe Maps should have a not-fully-loaded state, but have all their map.json data cached) this->mapConstantsToMapNames.insert(mapConstant, mapName); this->mapNamesToMapConstants.insert(mapName, mapConstant); - // TODO: Either verify that these are known IDs, or make sure nothing breaks when they're unknown. - this->mapNameToLayoutId.insert(mapName, ParseUtil::jsonToQString(mapObj["layout"])); - this->mapNameToMapSectionName.insert(mapName, ParseUtil::jsonToQString(mapObj["region_map_section"])); + this->mapNameToLayoutId.insert(mapName, layoutId); + this->mapNameToMapSectionName.insert(mapName, mapSectionName); } } @@ -1880,10 +1890,15 @@ bool Project::readMapGroups() { return false; } + if (!failedMapNames.isEmpty()) { + // At least 1 map was excluded due to an error. + // User should be alerted of this, rather than just silently logging the details. + emit mapsExcluded(failedMapNames); + } + // Save special "Dynamic" constant - const QString defineName = this->getDynamicMapDefineName(); - this->mapConstantsToMapNames.insert(defineName, dynamicMapName); - this->mapNamesToMapConstants.insert(dynamicMapName, defineName); + this->mapConstantsToMapNames.insert(dynamicMapConstant, dynamicMapName); + this->mapNamesToMapConstants.insert(dynamicMapName, dynamicMapConstant); this->mapNames.append(dynamicMapName); return true; @@ -2274,10 +2289,18 @@ bool Project::readRegionMapSections() { QJsonObject mapSectionObj = mapSections.at(i).toObject(); // For each map section, "id" is the only required field. This is the field we use to display the location names in the map list, and in various drop-downs. - const QString idField = "id"; + QString idField = "id"; if (!mapSectionObj.contains(idField)) { - logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField)); - continue; + const QString oldIdField = "map_section"; + if (mapSectionObj.contains(oldIdField)) { + // User has the old name for this field. Parse using this name, then save with the new name. + // This will presumably stop the user's project from compiling, but that's preferable to + // ignoring everything here and then wiping the file's data when we save later. + idField = oldIdField; + } else { + logWarn(QString("Ignoring data for map section %1 in '%2'. Missing required field \"%3\"").arg(i).arg(baseFilepath).arg(idField)); + continue; + } } const QString idName = ParseUtil::jsonToQString(mapSectionObj[idField]); if (!idName.startsWith(requiredPrefix)) { @@ -2338,7 +2361,6 @@ void Project::addNewMapsec(const QString &name) { } this->hasUnsavedDataChanges = true; - // TODO: Simplify into a single signal that updates the map list only if necessary emit mapSectionAdded(name); emit mapSectionIdNamesChanged(this->mapSectionIdNames); } diff --git a/src/scriptapi/apimap.cpp b/src/scriptapi/apimap.cpp index 91cc4e67..2532f132 100644 --- a/src/scriptapi/apimap.cpp +++ b/src/scriptapi/apimap.cpp @@ -832,10 +832,6 @@ QString MainWindow::getLocation() { void MainWindow::setLocation(QString location) { if (!this->editor || !this->editor->map || !this->editor->project) return; - if (!this->editor->project->mapSectionIdNames.contains(location)) { - logError(QString("Unknown location '%1'").arg(location)); - return; - } this->editor->map->header()->setLocation(location); } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 6f6e4dec..7aa5a628 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -55,7 +55,11 @@ void MapHeaderForm::init(const Project * project) { ui->comboBox_BattleScene->clear(); ui->comboBox_BattleScene->addItems(project->mapBattleScenes); - setLocations(project->mapSectionIdNames); + QStringList locations = project->mapSectionIdNames; + locations.sort(); + const QSignalBlocker b_Locations(ui->comboBox_Location); + ui->comboBox_Location->clear(); + ui->comboBox_Location->addItems(locations); // Hide config-specific settings @@ -72,8 +76,7 @@ void MapHeaderForm::init(const Project * project) { ui->label_FloorNumber->setVisible(floorNumEnabled); } -// This combo box is treated specially because (unlike the other combo boxes) -// items that should be in this drop-down can be added or removed externally. +// Unlike other combo boxes in the map header form, locations can be added or removed externally. void MapHeaderForm::setLocations(QStringList locations) { locations.sort(); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index d6b20af9..0f352f4f 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -454,10 +454,11 @@ QStandardItem *LayoutTreeModel::createMapFolderItem(const QString &folderName, Q // Despite using layout IDs internally, the Layouts map list shows layouts using their file path name. // We could handle this with Qt::DisplayRole in LayoutTreeModel::data, but then it would be sorted using the ID instead of the name. const Layout* layout = this->project->mapLayouts.value(folderName); - if (layout) { - folder->setText(layout->name); - folder->setToolTip(layout->id); - } + if (layout) folder->setText(layout->name); + + // The layout ID will instead be shown as a tool tip. + folder->setToolTip(folderName); + return folder; } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index c6485d51..cb0f6d44 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -651,10 +651,9 @@ void RegionMapEditor::displayRegionMapLayout() { void RegionMapEditor::displayRegionMapLayoutOptions() { if (!this->region_map->layoutEnabled()) return; - this->ui->comboBox_RM_ConnectedMap->blockSignals(true); + const QSignalBlocker b(ui->comboBox_RM_ConnectedMap); this->ui->comboBox_RM_ConnectedMap->clear(); this->ui->comboBox_RM_ConnectedMap->addItems(this->project->mapSectionIdNames); - this->ui->comboBox_RM_ConnectedMap->blockSignals(false); this->ui->frame_RM_Options->setEnabled(true); @@ -662,22 +661,19 @@ void RegionMapEditor::displayRegionMapLayoutOptions() { } void RegionMapEditor::updateRegionMapLayoutOptions(int index) { - this->ui->comboBox_RM_ConnectedMap->blockSignals(true); + const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap); this->ui->comboBox_RM_ConnectedMap->setCurrentText(this->region_map->squareMapSection(index)); - this->ui->comboBox_RM_ConnectedMap->blockSignals(false); this->ui->pushButton_RM_Options_delete->setEnabled(this->region_map->squareHasMap(index)); - this->ui->spinBox_RM_LayoutWidth->blockSignals(true); - this->ui->spinBox_RM_LayoutHeight->blockSignals(true); + const QSignalBlocker b_LayoutWidth(ui->spinBox_RM_LayoutWidth); + const QSignalBlocker b_LayoutHeight(ui->spinBox_RM_LayoutHeight); this->ui->spinBox_RM_LayoutWidth->setMinimum(1); this->ui->spinBox_RM_LayoutWidth->setMaximum(this->region_map->tilemapWidth() - this->region_map->padLeft()); this->ui->spinBox_RM_LayoutHeight->setMinimum(1); this->ui->spinBox_RM_LayoutHeight->setMaximum(this->region_map->tilemapHeight() - this->region_map->padTop()); this->ui->spinBox_RM_LayoutWidth->setValue(this->region_map->layoutWidth()); this->ui->spinBox_RM_LayoutHeight->setValue(this->region_map->layoutHeight()); - this->ui->spinBox_RM_LayoutWidth->blockSignals(false); - this->ui->spinBox_RM_LayoutHeight->blockSignals(false); } void RegionMapEditor::displayRegionMapEntriesImage() { @@ -1323,3 +1319,18 @@ void RegionMapEditor::on_verticalSlider_Zoom_Image_Tiles_valueChanged(int val) { ui->graphicsView_RegionMap_Tiles->setTransform(transform); ui->graphicsView_RegionMap_Tiles->setFixedSize(width + 2, height + 2); } + +// Repopulate the combo boxes that display MAPSEC names. +void RegionMapEditor::setLocations(const QStringList &locations) { + const QSignalBlocker b_ConnectedMap(ui->comboBox_RM_ConnectedMap); + auto before = ui->comboBox_RM_ConnectedMap->currentText(); + ui->comboBox_RM_ConnectedMap->clear(); + ui->comboBox_RM_ConnectedMap->addItems(locations); + ui->comboBox_RM_ConnectedMap->setCurrentText(before); + + const QSignalBlocker b_MapSection(ui->comboBox_RM_Entry_MapSection); + before = ui->comboBox_RM_Entry_MapSection->currentText(); + ui->comboBox_RM_Entry_MapSection->clear(); + ui->comboBox_RM_Entry_MapSection->addItems(locations); + ui->comboBox_RM_Entry_MapSection->setCurrentText(before); +} From 52a7cd4f56181007f9e5ad3ee20349937cc6a310 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Tue, 10 Dec 2024 18:22:22 -0500 Subject: [PATCH 27/42] Combine minor creation dialogs --- forms/newlayoutform.ui | 24 ++++++++-- forms/newnamedialog.ui | 82 +++++++++++++++++++++++++++++++++++ include/ui/newlayoutform.h | 3 +- include/ui/newnamedialog.h | 42 ++++++++++++++++++ porymap.pro | 3 ++ src/mainwindow.cpp | 89 ++++---------------------------------- src/ui/newlayoutform.cpp | 69 ++++++++++++++++------------- src/ui/newnamedialog.cpp | 76 ++++++++++++++++++++++++++++++++ 8 files changed, 273 insertions(+), 115 deletions(-) create mode 100644 forms/newnamedialog.ui create mode 100644 include/ui/newnamedialog.h create mode 100644 src/ui/newnamedialog.cpp diff --git a/forms/newlayoutform.ui b/forms/newlayoutform.ui index f51fb742..bc3e7502 100644 --- a/forms/newlayoutform.ui +++ b/forms/newlayoutform.ui @@ -170,14 +170,30 @@ - + + + + false + + + color: rgb(255, 0, 0) + + + + + + true + + + + Secondary - + <html><head/><body><p>The secondary tileset for the new map.</p></body></html> @@ -190,8 +206,8 @@ - - + + false diff --git a/forms/newnamedialog.ui b/forms/newnamedialog.ui new file mode 100644 index 00000000..6fa6b56d --- /dev/null +++ b/forms/newnamedialog.ui @@ -0,0 +1,82 @@ + + + NewNameDialog + + + + 0 + 0 + 252 + 87 + + + + + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Name + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/include/ui/newlayoutform.h b/include/ui/newlayoutform.h index 6f8d9905..3e77af18 100644 --- a/include/ui/newlayoutform.h +++ b/include/ui/newlayoutform.h @@ -33,7 +33,8 @@ class NewLayoutForm : public QWidget Project *m_project; bool validateMapDimensions(); - bool validateTilesets(); + bool validatePrimaryTileset(bool allowEmpty = false); + bool validateSecondaryTileset(bool allowEmpty = false); }; #endif // NEWLAYOUTFORM_H diff --git a/include/ui/newnamedialog.h b/include/ui/newnamedialog.h new file mode 100644 index 00000000..23979fc5 --- /dev/null +++ b/include/ui/newnamedialog.h @@ -0,0 +1,42 @@ +#ifndef NEWNAMEDIALOG_H +#define NEWNAMEDIALOG_H + +/* + This is a generic dialog for requesting a new unique name from the user. +*/ + +#include +#include + +class Project; + +namespace Ui { +class NewNameDialog; +} + +class NewNameDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewNameDialog(const QString &label, Project *project, QWidget *parent = nullptr); + ~NewNameDialog(); + + void setNamePrefix(const QString &prefix); + + virtual void accept() override; + +signals: + void applied(const QString &newName); + +private: + Ui::NewNameDialog *ui; + Project *project = nullptr; + const QString symbolPrefix; + + bool validateName(bool allowEmpty = false); + void onNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWNAMEDIALOG_H diff --git a/porymap.pro b/porymap.pro index 1dfaf4b1..171ea48d 100644 --- a/porymap.pro +++ b/porymap.pro @@ -91,6 +91,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ + src/ui/newnamedialog.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -197,6 +198,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ + include/ui/newnamedialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ include/ui/montabwidget.h \ @@ -243,6 +245,7 @@ FORMS += forms/mainwindow.ui \ forms/maplisttoolbar.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ + forms/newnamedialog.ui \ forms/newmapconnectiondialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e006ed9a..629a582f 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,6 +26,7 @@ #include "newmapdialog.h" #include "newlayoutdialog.h" #include "newtilesetdialog.h" +#include "newnamedialog.h" #include #include @@ -1310,90 +1311,16 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } void MainWindow::mapListAddGroup() { - QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::ApplicationModal); - QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - - QLineEdit *newNameEdit = new QLineEdit(&dialog); - newNameEdit->setClearButtonEnabled(true); - - static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); - newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); - - QLabel *errorMessageLabel = new QLabel(&dialog); - errorMessageLabel->setVisible(false); - errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - - connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - const QString mapGroupName = newNameEdit->text(); - if (!this->editor->project->isIdentifierUnique(mapGroupName)) { - errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(mapGroupName)); - errorMessageLabel->setVisible(true); - } else { - dialog.accept(); - } - }); - - QFormLayout form(&dialog); - - form.addRow("New Group Name", newNameEdit); - form.addRow("", errorMessageLabel); - form.addRow(&newItemButtonBox); - - if (dialog.exec() == QDialog::Accepted) { - QString newFieldName = newNameEdit->text(); - if (newFieldName.isEmpty()) return; - this->editor->project->addNewMapGroup(newFieldName); - } + auto dialog = new NewNameDialog("New Group Name", this->editor->project, this); + connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); + dialog->open(); } void MainWindow::mapListAddArea() { - QDialog dialog(this, Qt::WindowTitleHint | Qt::WindowCloseButtonHint); - dialog.setWindowModality(Qt::ApplicationModal); - QDialogButtonBox newItemButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &dialog); - connect(&newItemButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); - - // TODO: This would be a little more seamless with a single line edit that enforces the MAPSEC prefix, rather than a separate label for the actual name. - const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); - auto newNameEdit = new QLineEdit(&dialog); - auto newNameDisplay = new QLabel(&dialog); - newNameDisplay->setText(prefix); - connect(newNameEdit, &QLineEdit::textEdited, [newNameDisplay, prefix] (const QString &text) { - // As the user types a name, update the label to show the name with the prefix. - newNameDisplay->setText(prefix + text); - }); - - QLabel *errorMessageLabel = new QLabel(&dialog); - errorMessageLabel->setVisible(false); - errorMessageLabel->setStyleSheet("QLabel { background-color: rgba(255, 0, 0, 25%) }"); - - static const QRegularExpression re_validChars("[A-Za-z_]+[\\w]*"); - newNameEdit->setValidator(new QRegularExpressionValidator(re_validChars, newNameEdit)); - - connect(&newItemButtonBox, &QDialogButtonBox::accepted, [&](){ - const QString newAreaName = newNameDisplay->text(); - if (!this->editor->project->isIdentifierUnique(newAreaName)) { - errorMessageLabel->setText(QString("The name '%1' is not unique.").arg(newAreaName)); - errorMessageLabel->setVisible(true); - } else { - dialog.accept(); - } - }); - - QLabel *newNameEditLabel = new QLabel("New Area Name", &dialog); - QLabel *newNameDisplayLabel = new QLabel("Constant Name", &dialog); - - QFormLayout form(&dialog); - form.addRow(newNameEditLabel, newNameEdit); - form.addRow(newNameDisplayLabel, newNameDisplay); - form.addRow("", errorMessageLabel); - form.addRow(&newItemButtonBox); - - if (dialog.exec() == QDialog::Accepted) { - if (newNameEdit->text().isEmpty()) return; - this->editor->project->addNewMapsec(newNameDisplay->text()); - } + auto dialog = new NewNameDialog("New Area Name", this->editor->project, this); + dialog->setNamePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); + connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); + dialog->open(); } void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { diff --git a/src/ui/newlayoutform.cpp b/src/ui/newlayoutform.cpp index 64c2fdfb..aa8178ce 100644 --- a/src/ui/newlayoutform.cpp +++ b/src/ui/newlayoutform.cpp @@ -12,12 +12,14 @@ NewLayoutForm::NewLayoutForm(QWidget *parent) ui->groupBox_BorderDimensions->setVisible(projectConfig.useCustomBorderSize); - // TODO: Read from project? ui->spinBox_BorderWidth->setMaximum(MAX_BORDER_WIDTH); ui->spinBox_BorderHeight->setMaximum(MAX_BORDER_HEIGHT); - connect(ui->spinBox_MapWidth, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); - connect(ui->spinBox_MapHeight, QOverload::of(&QSpinBox::valueChanged), [=](int){validateMapDimensions();}); + 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); }); } NewLayoutForm::~NewLayoutForm() @@ -71,12 +73,12 @@ Layout::Settings NewLayoutForm::settings() const { return settings; } -// TODO: Validate while typing bool NewLayoutForm::validate() { // Make sure to call each validation function so that all errors are shown at once. bool valid = true; if (!validateMapDimensions()) valid = false; - if (!validateTilesets()) valid = false; + if (!validatePrimaryTileset()) valid = false; + if (!validateSecondaryTileset()) valid = false; return valid; } @@ -84,11 +86,17 @@ 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) { errorText = QString("The specified width and height are too large.\n" - "The maximum map width and height is the following: (width + 15) * (height + 14) <= %1\n" - "The specified map width and height was: (%2 + 15) * (%3 + 14) = %4") + "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(maxSize) .arg(ui->spinBox_MapWidth->value()) .arg(ui->spinBox_MapHeight->value()) @@ -101,33 +109,36 @@ bool NewLayoutForm::validateMapDimensions() { return isValid; } -bool NewLayoutForm::validateTilesets() { - QString primaryTileset = ui->comboBox_PrimaryTileset->currentText(); - QString secondaryTileset = ui->comboBox_SecondaryTileset->currentText(); +bool NewLayoutForm::validatePrimaryTileset(bool allowEmpty) { + const QString name = ui->comboBox_PrimaryTileset->currentText(); - QString primaryErrorText; - if (primaryTileset.isEmpty()) { - primaryErrorText = QString("The primary tileset cannot be empty."); - } else if (ui->comboBox_PrimaryTileset->findText(primaryTileset) < 0) { - primaryErrorText = QString("The specified primary tileset '%1' does not exist.").arg(primaryTileset); + QString errorText; + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("The Primary Tileset cannot be empty."); + } else if (ui->comboBox_PrimaryTileset->findText(name) < 0) { + errorText = QString("The Primary Tileset '%1' does not exist.").arg(ui->label_PrimaryTileset->text()).arg(name); } - QString secondaryErrorText; - if (secondaryTileset.isEmpty()) { - secondaryErrorText = QString("The secondary tileset cannot be empty."); - } else if (ui->comboBox_SecondaryTileset->findText(secondaryTileset) < 0) { - secondaryErrorText = QString("The specified secondary tileset '%2' does not exist.").arg(secondaryTileset); - } + bool isValid = errorText.isEmpty(); + ui->label_PrimaryTilesetError->setText(errorText); + ui->label_PrimaryTilesetError->setVisible(!isValid); + ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +bool NewLayoutForm::validateSecondaryTileset(bool allowEmpty) { + const QString name = ui->comboBox_SecondaryTileset->currentText(); - QString errorText = QString("%1%2%3") - .arg(primaryErrorText) - .arg(!primaryErrorText.isEmpty() ? "\n" : "") - .arg(secondaryErrorText); + QString errorText; + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("The Secondary Tileset cannot be empty."); + } else if (ui->comboBox_SecondaryTileset->findText(name) < 0) { + errorText = QString("The Secondary Tileset '%1' does not exist.").arg(name); + } bool isValid = errorText.isEmpty(); - ui->label_TilesetsError->setText(errorText); - ui->label_TilesetsError->setVisible(!isValid); - ui->comboBox_PrimaryTileset->lineEdit()->setStyleSheet(!primaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); - ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!secondaryErrorText.isEmpty() ? lineEdit_ErrorStylesheet : ""); + ui->label_SecondaryTilesetError->setText(errorText); + ui->label_SecondaryTilesetError->setVisible(!isValid); + ui->comboBox_SecondaryTileset->lineEdit()->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); return isValid; } diff --git a/src/ui/newnamedialog.cpp b/src/ui/newnamedialog.cpp new file mode 100644 index 00000000..d5800daa --- /dev/null +++ b/src/ui/newnamedialog.cpp @@ -0,0 +1,76 @@ +#include "newnamedialog.h" +#include "ui_newnamedialog.h" +#include "project.h" +#include "imageexport.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *parent) : + QDialog(parent), + ui(new Ui::NewNameDialog) +{ + setAttribute(Qt::WA_DeleteOnClose); + setModal(true); + ui->setupUi(this); + this->project = project; + + if (!label.isEmpty()) + ui->label_Name->setText(label); + + // Identifiers must only contain word characters, and cannot start with a digit. + static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); + QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); + ui->lineEdit_Name->setValidator(validator); + + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewNameDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewNameDialog::dialogButtonClicked); + + adjustSize(); +} + +NewNameDialog::~NewNameDialog() +{ + delete ui; +} + +void NewNameDialog::setNamePrefix(const QString &) { + //TODO +} + +void NewNameDialog::onNameChanged(const QString &) { + validateName(true); +} + +bool NewNameDialog::validateName(bool allowEmpty) { + const QString name = ui->lineEdit_Name->text(); + + QString errorText; + if (name.isEmpty()) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); + } + + bool isValid = errorText.isEmpty(); + ui->label_NameError->setText(errorText); + ui->label_NameError->setVisible(!isValid); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewNameDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewNameDialog::accept() { + if (!validateName()) + return; + + emit applied(ui->lineEdit_Name->text()); + QDialog::accept(); +} From a6233e97c247bd5162bfe882f4f251f2206b7688 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 12 Dec 2024 16:13:12 -0500 Subject: [PATCH 28/42] Ensure automatic new layout names are unique --- src/ui/newmapdialog.cpp | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d362d2dc..5d9c7413 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -135,12 +135,29 @@ void NewMapDialog::saveSettings() { settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - // TODO: Verify uniqueness. If the layout ID belongs to an existing layout we don't need to do this at all. - settings->layout.name = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + // This dialog doesn't give users the option to give new layouts a name. + // If a new layout is being created we'll generate a unique layout name using the map name and a suffix. + // (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for + // something the majority of users creating a map won't need. If they want to give a specific name to a layout + // they can create the layout first, then create a new map that uses that layout.) + const Layout *layout = this->project->mapLayouts.value(settings->layout.id); + if (!layout) { + const QString baseLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + QString newLayoutName = baseLayoutName; + int i = 2; + while (!this->project->isIdentifierUnique(newLayoutName)) { + newLayoutName = QString("%1_%2").arg(baseLayoutName).arg(i++); + } + settings->layout.name = newLayoutName; + } else { + // Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway. + settings->layout.name = layout->name; + } // Folders for new layouts created for new maps use the map name, rather than the layout name. // There's no real reason for this, aside from maintaining consistency with the default layout - // folder names that do this (which would otherwise all have a '_Layout' suffix in the name). + // folder names that do this (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', + // rather than 'data/layouts/MyMap_Layout/'). settings->layout.folderName = settings->name; porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); From 4209c3e3f809a99295cf1546f0c7b520b19a9e1d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Thu, 12 Dec 2024 16:38:51 -0500 Subject: [PATCH 29/42] Fix checkerboard pattern for secondary tilesets --- src/project.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/project.cpp b/src/project.cpp index 578d20d2..0ad2114e 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1486,6 +1486,9 @@ Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, tile.tileId = ((i % 2) == 0) ? 1 : 2; else tile.tileId = ((i % 2) == 1) ? 1 : 2; + + if (tileset->is_secondary) + tile.tileId += Project::getNumTilesPrimary(); } metatile->tiles.append(tile); } From bdd64a6c6b2a26d1986050595d10e2fbab2bfe2d Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 14 Dec 2024 16:22:28 -0500 Subject: [PATCH 30/42] Use applicationName() for window titles, clean up some remaining TODO items --- forms/newlayoutdialog.ui | 16 +++++- forms/newmapdialog.ui | 26 ++++++++-- forms/newnamedialog.ui | 9 ++-- forms/newtilesetdialog.ui | 21 +++++--- include/core/map.h | 2 +- include/core/maplayout.h | 2 - include/lib/collapsiblesection.h | 2 +- include/mainwindow.h | 6 ++- include/project.h | 5 +- src/lib/collapsiblesection.cpp | 4 +- src/mainwindow.cpp | 68 ++++++++++++++++---------- src/project.cpp | 83 +++++++++++++++++++++++--------- src/ui/customscriptseditor.cpp | 4 +- src/ui/maplistmodels.cpp | 19 ++++++-- src/ui/newlayoutdialog.cpp | 27 +++-------- src/ui/newmapdialog.cpp | 69 +++++++++----------------- src/ui/newnamedialog.cpp | 1 - src/ui/newtilesetdialog.cpp | 1 - src/ui/prefab.cpp | 2 +- src/ui/projectsettingseditor.cpp | 2 +- src/ui/regionmapeditor.cpp | 2 +- src/ui/shortcutseditor.cpp | 2 +- src/ui/tileseteditor.cpp | 4 +- src/ui/wildmonchart.cpp | 2 +- 24 files changed, 220 insertions(+), 159 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index bfbc9b02..42c7733c 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -13,9 +13,21 @@ New Layout Options + + true + + + true + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + true @@ -24,8 +36,8 @@ 0 0 - 238 - 107 + 240 + 109 diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 3e133dfd..6b32810a 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -6,16 +6,31 @@ 0 0 - 255 - 320 + 559 + 614 New Map Options + + true + + + true + + + QFrame::Shape::NoFrame + + + QFrame::Shadow::Plain + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents + true @@ -24,8 +39,8 @@ 0 0 - 229 - 228 + 535 + 550 @@ -42,6 +57,9 @@ + + QLayout::SizeConstraint::SetMinAndMaxSize + 0 diff --git a/forms/newnamedialog.ui b/forms/newnamedialog.ui index 6fa6b56d..fa6cb5ae 100644 --- a/forms/newnamedialog.ui +++ b/forms/newnamedialog.ui @@ -10,15 +10,12 @@ 87 + + true + - - QFrame::Shape::NoFrame - - - QFrame::Shadow::Plain - 0 diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index 1fed2558..3b898761 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -13,16 +13,25 @@ Add new Tileset + + true + - - QFrame::Shape::StyledPanel - - - QFrame::Shadow::Raised - + + 0 + + + 0 + + + 0 + + + 0 + diff --git a/include/core/map.h b/include/core/map.h index 6e953877..9cc2a69b 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -107,7 +107,7 @@ class Map : public QObject private: QString m_name; QString m_constantName; - QString m_layoutId; // TODO: Why do we do half this->layout()->id and half this->layoutId. Should these ever be different? + QString m_layoutId; QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; diff --git a/include/core/maplayout.h b/include/core/maplayout.h index 7761d7fc..1579b4ad 100644 --- a/include/core/maplayout.h +++ b/include/core/maplayout.h @@ -14,8 +14,6 @@ class LayoutPixmapItem; class CollisionPixmapItem; class BorderMetatilesPixmapItem; -// TODO: Privatize members as appropriate - class Layout : public QObject { Q_OBJECT public: diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 584e44cb..73169303 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -41,7 +41,7 @@ class CollapsibleSection : public QWidget explicit CollapsibleSection(const QString& title = "", const bool expanded = false, const int animationDuration = 0, QWidget* parent = 0); void setContentLayout(QLayout* contentLayout); - void setTitle(QString title); + void setTitle(const QString &title); bool isExpanded() const { return this->expanded; } public slots: diff --git a/include/mainwindow.h b/include/mainwindow.h index 1e8c53bb..0eac8947 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -31,6 +31,7 @@ #include "updatepromoter.h" #include "aboutporymap.h" #include "mapheaderform.h" +#include "newlayoutdialog.h" @@ -350,8 +351,11 @@ private slots: void openNewMapDialog(); void openDuplicateMapDialog(const QString &mapName); + NewLayoutDialog* createNewLayoutDialog(const Layout *layoutToCopy = nullptr); void openNewLayoutDialog(); void openDuplicateLayoutDialog(const QString &layoutId); + void openNewMapGroupDialog(); + void openNewAreaDialog(); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -370,8 +374,6 @@ private slots: void refreshRecentProjectsMenu(); void updateMapList(); - void mapListAddGroup(); - void mapListAddArea(); void openMapListItem(const QModelIndex &index); void saveMapListTab(int index); diff --git a/include/project.h b/include/project.h index 895f9979..9c9fdf33 100644 --- a/include/project.h +++ b/include/project.h @@ -111,7 +111,7 @@ class Project : public QObject QStringList secondaryTilesetLabels; QStringList tilesetLabelsOrdered; - Blockdata readBlockdata(QString); + Blockdata readBlockdata(QString, bool *ok = nullptr); bool loadBlockdata(Layout *); bool loadLayoutBorder(Layout *); @@ -121,6 +121,7 @@ class Project : public QObject bool readMapGroups(); void addNewMapGroup(const QString &groupName); + QString mapNameToMapGroup(const QString &mapName); struct NewMapSettings { QString name; @@ -141,6 +142,8 @@ class Project : public QObject Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); Tileset *createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; + bool isValidNewIdentifier(const QString &identifier) const; + QString toUniqueIdentifier(const QString &identifier) const; QString getProjectTitle(); bool readWildMonData(); diff --git a/src/lib/collapsiblesection.cpp b/src/lib/collapsiblesection.cpp index 34dfb780..8ad8560a 100644 --- a/src/lib/collapsiblesection.cpp +++ b/src/lib/collapsiblesection.cpp @@ -119,9 +119,9 @@ void CollapsibleSection::setContentLayout(QLayout* contentLayout) updateAnimationTargets(); } -void CollapsibleSection::setTitle(QString title) +void CollapsibleSection::setTitle(const QString &title) { - toggleButton->setText(std::move(title)); + toggleButton->setText(title); } int CollapsibleSection::getContentHeight() const diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 629a582f..65a323e2 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -24,7 +24,6 @@ #include "config.h" #include "filedialog.h" #include "newmapdialog.h" -#include "newlayoutdialog.h" #include "newtilesetdialog.h" #include "newnamedialog.h" @@ -69,7 +68,7 @@ MainWindow::MainWindow(QWidget *parent) : QCoreApplication::setOrganizationName("pret"); QCoreApplication::setApplicationName("porymap"); QCoreApplication::setApplicationVersion(PORYMAP_VERSION); - QApplication::setApplicationDisplayName("porymap"); + QApplication::setApplicationDisplayName(QApplication::applicationName()); QApplication::setWindowIcon(QIcon(":/icons/porymap-icon-2.ico")); ui->setupUi(this); @@ -448,8 +447,8 @@ void MainWindow::initMapList() { connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); // Connect the "add folder" button in each of the map lists - connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddGroup); - connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::mapListAddArea); + connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewMapGroupDialog); + connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewAreaDialog); connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); @@ -699,14 +698,14 @@ bool MainWindow::checkProjectSanity() { void MainWindow::showProjectOpenFailure() { QString errorMsg = QString("There was an error opening the project. Please see %1 for full error details.").arg(getLogPath()); - QMessageBox error(QMessageBox::Critical, "porymap", errorMsg, QMessageBox::Ok, this); + QMessageBox error(QMessageBox::Critical, QApplication::applicationName(), errorMsg, QMessageBox::Ok, this); error.setDetailedText(getMostRecentError()); error.exec(); } // Alert the user that one or more maps have been excluded while loading the project. void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { - QMessageBox msgBox(QMessageBox::Icon::Warning, "porymap", "", QMessageBox::Ok, this); + QMessageBox msgBox(QMessageBox::Icon::Warning, QApplication::applicationName(), "", QMessageBox::Ok, this); QString errorMsg; if (excludedMapNames.length() == 1) { @@ -880,7 +879,7 @@ bool MainWindow::userSetMap(QString map_name) { if (map_name == editor->project->getDynamicMapName()) { QMessageBox msgBox(QMessageBox::Icon::Warning, - "Cannot Open Map", + QApplication::applicationName(), QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name), QMessageBox::Ok, this); @@ -890,7 +889,7 @@ bool MainWindow::userSetMap(QString map_name) { if (!setMap(map_name)) { QMessageBox msgBox(QMessageBox::Icon::Critical, - "Error Opening Map", + QApplication::applicationName(), QString("There was an error opening map %1.\n\nPlease see %2 for full error details.").arg(map_name).arg(getLogPath()), QMessageBox::Ok, this); @@ -952,7 +951,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { QMessageBox msgBox(QMessageBox::Icon::Critical, - "Error Opening Layout", + QApplication::applicationName(), QString("There was an error opening layout %1.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), QMessageBox::Ok, this); @@ -1310,13 +1309,13 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { menu.exec(QCursor::pos()); } -void MainWindow::mapListAddGroup() { +void MainWindow::openNewMapGroupDialog() { auto dialog = new NewNameDialog("New Group Name", this->editor->project, this); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); dialog->open(); } -void MainWindow::mapListAddArea() { +void MainWindow::openNewAreaDialog() { auto dialog = new NewNameDialog("New Area Name", this->editor->project, this); dialog->setNamePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); @@ -1388,9 +1387,11 @@ void MainWindow::setLocationComboBoxes(const QStringList &locations) { } void MainWindow::onNewTilesetCreated(Tileset *tileset) { - QString message = QString("Created a new tileset named %1.").arg(tileset->name); - logInfo(message); - statusBar()->showMessage(message); + logInfo(QString("Created a new tileset named %1.").arg(tileset->name)); + + // Unlike creating a new map or layout (which immediately opens the new item) + // creating a new tileset has no visual feedback that it succeeded, so we show a message. + QMessageBox::information(this, QApplication::applicationName(), QString( "New tileset created at '%1'!").arg(tileset->getExpectedDir())); // Refresh tileset combo boxes if (!tileset->is_secondary) { @@ -1413,24 +1414,40 @@ void MainWindow::openDuplicateMapDialog(const QString &mapName) { auto dialog = new NewMapDialog(this->editor->project, map, this); dialog->open(); } else { - //TODO + QMessageBox msgBox(QMessageBox::Icon::Critical, + QApplication::applicationName(), + QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(mapName).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); } } -void MainWindow::openNewLayoutDialog() { - auto dialog = new NewLayoutDialog(this->editor->project, this); +NewLayoutDialog* MainWindow::createNewLayoutDialog(const Layout *layoutToCopy) { + auto dialog = new NewLayoutDialog(this->editor->project, layoutToCopy, this); connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + return dialog; +} + +void MainWindow::openNewLayoutDialog() { + auto dialog = createNewLayoutDialog(); dialog->open(); } void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { auto layout = this->editor->project->loadLayout(layoutId); if (layout) { - auto dialog = new NewLayoutDialog(this->editor->project, layout, this); - connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + auto dialog = createNewLayoutDialog(layout); dialog->open(); } else { - //TODO + QMessageBox msgBox(QMessageBox::Icon::Critical, + QApplication::applicationName(), + QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), + QMessageBox::Ok, + this); + msgBox.setDetailedText(getMostRecentError()); + msgBox.exec(); } } @@ -1675,7 +1692,7 @@ void MainWindow::setClipboardData(OrderedJson::object object) { QClipboard *clipboard = QGuiApplication::clipboard(); QString newText; int indent = 0; - object["application"] = "porymap"; + object["application"] = QApplication::applicationName(); OrderedJson data(object); data.dump(newText, &indent); clipboard->setText(newText); @@ -1714,7 +1731,7 @@ void MainWindow::paste() { QJsonObject pasteObject = pasteJsonDoc.object(); //OrderedJson::object pasteObject = pasteJson.object_items(); - if (pasteObject["application"].toString() != "porymap") { + if (pasteObject["application"].toString() != QApplication::applicationName()) { return; } @@ -2530,8 +2547,7 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() { return; } - auto dialog = new NewLayoutDialog(this->editor->project, mapLayout, this); - connect(dialog, &NewLayoutDialog::applied, this, &MainWindow::userSetLayout); + auto dialog = createNewLayoutDialog(mapLayout); connect(dialog, &NewLayoutDialog::finished, [mapLayout] { mapLayout->deleteLater(); }); dialog->open(); } @@ -2858,7 +2874,7 @@ void MainWindow::onWarpBehaviorWarningClicked() { "You can disable this warning or edit the list of behaviors that silence this warning under Options -> Project Settings..." "

" ); - QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this); + QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this); QPushButton *settings = msgBox.addButton("Open Settings...", QMessageBox::ActionRole); msgBox.setDefaultButton(QMessageBox::Close); msgBox.setTextFormat(Qt::RichText); @@ -3074,7 +3090,7 @@ bool MainWindow::closeProject() { if (this->editor->project->hasUnsavedChanges()) { QMessageBox::StandardButton result = QMessageBox::question( - this, "porymap", "The project has been modified, save changes?", + this, QApplication::applicationName(), "The project has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (result == QMessageBox::Yes) { diff --git a/src/project.cpp b/src/project.cpp index 0ad2114e..0a66c0e8 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -367,13 +367,7 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t map->setNeedsHealLocation(settings.canFlyTo); // Generate a unique MAP constant. - int suffix = 2; - const QString baseMapConstant = Map::mapConstantFromName(map->name()); - QString mapConstant = baseMapConstant; - while (!isIdentifierUnique(mapConstant)) { - mapConstant = QString("%1_%2").arg(baseMapConstant).arg(suffix++); - } - map->setConstantName(mapConstant); + map->setConstantName(toUniqueIdentifier(Map::mapConstantFromName(map->name()))); Layout *layout = this->mapLayouts.value(settings.layout.id); if (!layout) { @@ -402,13 +396,15 @@ Map *Project::createNewMap(const Project::NewMapSettings &settings, const Map* t } else { // Adding map to a map group that doesn't exist yet. // Create the group, and we already know the map will be last in the list. - addNewMapGroup(settings.group); + if (isValidNewIdentifier(settings.group)) { + addNewMapGroup(settings.group); + } mapNamePos = this->mapNames.length(); } const QString location = map->header()->location(); - if (!this->mapSectionIdNames.contains(location) && isIdentifierUnique(location)) { - // Unrecognized MAPSEC value. Add it. + if (!this->mapSectionIdNames.contains(location) && isValidNewIdentifier(location)) { + // Unrecognized MAPSEC name, we can automatically add a new MAPSEC for it. addNewMapsec(location); } @@ -555,7 +551,6 @@ bool Project::readMapLayouts() { .arg(layoutsLabel)); } - QStringList failedLayoutNames; // TODO: Populate for (int i = 0; i < layouts.size(); i++) { QJsonObject layoutObj = layouts[i].toObject(); if (layoutObj.isEmpty()) @@ -1124,9 +1119,16 @@ Tileset* Project::loadTileset(QString label, Tileset *tileset) { } bool Project::loadBlockdata(Layout *layout) { + bool ok = true; QString path = QString("%1/%2").arg(root).arg(layout->blockdata_path); - layout->blockdata = readBlockdata(path); - layout->lastCommitBlocks.blocks = layout->blockdata; + auto blockdata = readBlockdata(path, &ok); + if (!ok) { + logError(QString("Failed to load layout blockdata from '%1'").arg(path)); + return false; + } + + layout->blockdata = blockdata; + layout->lastCommitBlocks.blocks = blockdata; layout->lastCommitBlocks.layoutDimensions = QSize(layout->getWidth(), layout->getHeight()); if (layout->blockdata.count() != layout->getWidth() * layout->getHeight()) { @@ -1153,9 +1155,16 @@ void Project::setNewLayoutBlockdata(Layout *layout) { } bool Project::loadLayoutBorder(Layout *layout) { + bool ok = true; QString path = QString("%1/%2").arg(root).arg(layout->border_path); - layout->border = readBlockdata(path); - layout->lastCommitBlocks.border = layout->border; + auto blockdata = readBlockdata(path, &ok); + if (!ok) { + logError(QString("Failed to load layout border from '%1'").arg(path)); + return false; + } + + layout->border = blockdata; + layout->lastCommitBlocks.border = blockdata; layout->lastCommitBlocks.borderDimensions = QSize(layout->getBorderWidth(), layout->getBorderHeight()); int borderLength = layout->getBorderWidth() * layout->getBorderHeight(); @@ -1471,7 +1480,6 @@ Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, // Set default tiles image QImage tilesImage(":/images/blank_tileset.png"); tileset->loadTilesImage(&tilesImage); - //exportIndexed4BPPPng(tileset->tilesImage, tileset->tilesImagePath); // TODO: Make sure we can now properly handle the 8bpp images that get written without this. // Create default metatiles const int numMetatiles = tileset->is_secondary ? (Project::getNumMetatilesTotal() - Project::getNumMetatilesPrimary()) : Project::getNumMetatilesPrimary(); @@ -1580,7 +1588,7 @@ void Project::loadTilesetMetatileLabels(Tileset* tileset) { } } -Blockdata Project::readBlockdata(QString path) { +Blockdata Project::readBlockdata(QString path, bool *ok) { Blockdata blockdata; QFile file(path); if (file.open(QIODevice::ReadOnly)) { @@ -1589,8 +1597,10 @@ Blockdata Project::readBlockdata(QString path) { uint16_t word = static_cast((data[i] & 0xff) + ((data[i + 1] & 0xff) << 8)); blockdata.append(word); } + if (ok) *ok = true; } else { - logError(QString("Failed to open blockdata path '%1'").arg(path)); + // Failed + if (ok) *ok = false; } return blockdata; @@ -1918,6 +1928,16 @@ void Project::addNewMapGroup(const QString &groupName) { emit mapGroupAdded(groupName); } +QString Project::mapNameToMapGroup(const QString &mapName) { + for (auto it = this->groupNameToMapNames.constBegin(); it != this->groupNameToMapNames.constEnd(); it++) { + const QStringList mapNames = it.value(); + if (mapNames.contains(mapName)) { + return it.key(); + } + } + return QString(); +} + // When we ask the user to provide a new identifier for something (like a map name or MAPSEC id) // we use this to make sure that it doesn't collide with any known identifiers first. // Porymap knows of many more identifiers than this, but for simplicity we only check the lists that users can add to via Porymap. @@ -1946,22 +1966,41 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return true; } +// For some arbitrary string, return true if it's both a valid identifier name +// and not one that's already in-use. +bool Project::isValidNewIdentifier(const QString &identifier) const { + static const QRegularExpression re_identifier("[A-Za-z_]+[\\w]*"); + QRegularExpressionMatch match = re_identifier.match(identifier); + return match.hasMatch() && isIdentifierUnique(identifier); +} + +// Assumes 'identifier' is a valid name. If 'identifier' is unique, returns 'identifier'. +// Otherwise returns the identifier with a numbered suffix added to make it unique. +QString Project::toUniqueIdentifier(const QString &identifier) const { + int suffix = 2; + QString uniqueIdentifier = identifier; + while (!isIdentifierUnique(uniqueIdentifier)) { + uniqueIdentifier = QString("%1_%2").arg(identifier).arg(suffix++); + } + return uniqueIdentifier; +} + QString Project::getNewMapName() const { // Ensure default name/ID doesn't already exist. - int i = 0; + int suffix = 1; QString newMapName; do { - newMapName = QString("NewMap%1").arg(++i); + newMapName = QString("NewMap%1").arg(suffix++); } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(Map::mapConstantFromName(newMapName))); return newMapName; } QString Project::getNewLayoutName() const { // Ensure default name/ID doesn't already exist. - int i = 0; + int suffix = 1; QString newLayoutName; do { - newLayoutName = QString("NewLayout%1").arg(++i); + newLayoutName = QString("NewLayout%1").arg(suffix++); } while (!isIdentifierUnique(newLayoutName) || !isIdentifierUnique(Layout::layoutConstantFromName(newLayoutName))); return newLayoutName; } diff --git a/src/ui/customscriptseditor.cpp b/src/ui/customscriptseditor.cpp index 284ea333..c3c412c2 100644 --- a/src/ui/customscriptseditor.cpp +++ b/src/ui/customscriptseditor.cpp @@ -185,7 +185,7 @@ void CustomScriptsEditor::displayNewScript(QString filepath) { // Verify new script path is not already in list for (int i = 0; i < ui->list->count(); i++) { if (filepath == this->getScriptFilepath(ui->list->item(i), false)) { - QMessageBox::information(this, "", QString("The script '%1' is already loaded").arg(filepath)); + QMessageBox::information(this, QApplication::applicationName(), QString("The script '%1' is already loaded").arg(filepath)); return; } } @@ -219,7 +219,7 @@ void CustomScriptsEditor::openScript(QListWidgetItem * item) { const QString path = this->getScriptFilepath(item); QFileInfo fileInfo(path); if (!fileInfo.exists() || !fileInfo.isFile()){ - QMessageBox::warning(this, "", QString("Failed to open script '%1'").arg(path)); + QMessageBox::warning(this, QApplication::applicationName(), QString("Failed to open script '%1'").arg(path)); return; } Editor::openInTextEditor(path); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index 0f352f4f..dc1e3d7e 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -115,21 +115,30 @@ QStandardItem *MapListModel::createMapFolderItem(const QString &folderName, QSta } QStandardItem *MapListModel::insertMapItem(const QString &mapName, const QString &folderName) { - // Disallow adding MAP_DYNAMIC to the map list. - if (mapName == this->project->getDynamicMapName()) + if (mapName.isEmpty() || mapName == this->project->getDynamicMapName()) // Disallow adding MAP_DYNAMIC to the map list. return nullptr; + QStandardItem *map = createMapItem(mapName); + QStandardItem *folder = this->mapFolderItems[folderName]; - if (!folder) folder = insertMapFolderItem(folderName); + if (!folder) { + // Folder doesn't exist yet, add it. + folder = insertMapFolderItem(folderName); + } + // If folder is still nullptr here it's because we failed to create it. + if (folder) { + folder->appendRow(map); + } - QStandardItem *map = createMapItem(mapName); - folder->appendRow(map); if (this->sortingEnabled) this->sort(0, Qt::AscendingOrder); return map; } QStandardItem *MapListModel::insertMapFolderItem(const QString &folderName) { + if (folderName.isEmpty()) + return nullptr; + QStandardItem *item = createMapFolderItem(folderName); this->root->appendRow(item); if (this->sortingEnabled) diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 79af74e5..086bc346 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -19,31 +19,18 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q layoutToCopy(layoutToCopy) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; + Layout::Settings *settings = &project->newLayoutSettings; - QString newName; - QString newId; + // Note: 'layoutToCopy' will have an empty name if it's an import from AdvanceMap if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { - // Duplicating a layout, the initial name will be the base layout's name - // with a numbered suffix to make it unique. - // Note: If 'layoutToCopy' is an imported AdvanceMap layout it won't have - // a name, so it uses the default new layout name instead. - int i = 2; - do { - newName = QString("%1_%2").arg(this->layoutToCopy->name).arg(i++); - newId = Layout::layoutConstantFromName(newName); - } while (!project->isIdentifierUnique(newName) || !project->isIdentifierUnique(newId)); + settings->name = project->toUniqueIdentifier(this->layoutToCopy->name); } else { - newName = project->getNewLayoutName(); - newId = Layout::layoutConstantFromName(newName); + settings->name = project->getNewLayoutName(); } - - // We reset these settings for every session with the new layout dialog. - // The rest of the settings are preserved in the project between sessions. - project->newLayoutSettings.name = newName; - project->newLayoutSettings.id = newId; + // Generate a unique Layout constant + settings->id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); ui->newLayoutForm->initUi(project); @@ -166,8 +153,6 @@ void NewLayoutDialog::accept() { } ui->label_GenericError->setVisible(false); - // TODO: See if we can get away with emitting this from Project so that we don't need to connect - // to this signal every time we create the dialog. emit applied(layout->id); QDialog::accept(); } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 5d9c7413..caf98060 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -20,33 +20,28 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare mapToCopy(mapToCopy) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; + Project::NewMapSettings *settings = &project->newMapSettings; - QString newMapName; - QString newLayoutId; if (this->mapToCopy) { - // Duplicating a map, the initial name will be the base map's name - // with a numbered suffix to make it unique. - int i = 2; - do { - newMapName = QString("%1_%2").arg(this->mapToCopy->name()).arg(i++); - newLayoutId = Layout::layoutConstantFromName(newMapName); - } while (!project->isIdentifierUnique(newMapName) || !project->isIdentifierUnique(newLayoutId)); + // Copy settings from the map we're duplicating + if (this->mapToCopy->layout()){ + settings->layout = this->mapToCopy->layout()->settings(); + } + settings->header = *this->mapToCopy->header(); + settings->group = project->mapNameToMapGroup(this->mapToCopy->name()); + settings->name = project->toUniqueIdentifier(this->mapToCopy->name()); + } else { // Not duplicating a map, get a generic new map name. - newMapName = project->getNewMapName(); - newLayoutId = Layout::layoutConstantFromName(newMapName); + // The rest of the settings are preserved in the project between sessions. + settings->name = project->getNewMapName(); } - - // We reset these settings for every session with the new map dialog. - // The rest of the settings are preserved in the project between sessions. - project->newMapSettings.name = newMapName; - project->newMapSettings.layout.id = newLayoutId; + // Generate a unique Layout constant + settings->layout.id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); ui->newLayoutForm->initUi(project); - ui->comboBox_Group->addItems(project->groupNames); ui->comboBox_LayoutID->addItems(project->layoutIds); @@ -66,12 +61,10 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare this->headerSection = new CollapsibleSection("Header Data", porymapConfig.newMapHeaderSectionExpanded, 150, this); this->headerSection->setContentLayout(sectionLayout); ui->layout_HeaderData->addWidget(this->headerSection); - ui->layout_HeaderData->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); - adjustSize(); } // Adding new map to an existing map list folder. Initialize settings accordingly. @@ -115,12 +108,7 @@ void NewMapDialog::refresh() { if (ui->comboBox_LayoutID->isEnabled()) ui->comboBox_LayoutID->setTextItem(settings->layout.id); - if (this->mapToCopy && this->mapToCopy->layout()) { - // When importing a layout these settings shouldn't be changed. - ui->newLayoutForm->setSettings(this->mapToCopy->layout()->settings()); - } else { - ui->newLayoutForm->setSettings(settings->layout); - } + ui->newLayoutForm->setSettings(settings->layout); ui->checkBox_CanFlyTo->setChecked(settings->canFlyTo); this->headerForm->setHeaderData(settings->header); } @@ -135,29 +123,22 @@ void NewMapDialog::saveSettings() { settings->canFlyTo = ui->checkBox_CanFlyTo->isChecked(); settings->header = this->headerForm->headerData(); - // This dialog doesn't give users the option to give new layouts a name. - // If a new layout is being created we'll generate a unique layout name using the map name and a suffix. + // This dialog doesn't give users the option to give new layouts a name, we generate one using the map name. // (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for // something the majority of users creating a map won't need. If they want to give a specific name to a layout // they can create the layout first, then create a new map that uses that layout.) const Layout *layout = this->project->mapLayouts.value(settings->layout.id); if (!layout) { - const QString baseLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); - QString newLayoutName = baseLayoutName; - int i = 2; - while (!this->project->isIdentifierUnique(newLayoutName)) { - newLayoutName = QString("%1_%2").arg(baseLayoutName).arg(i++); - } - settings->layout.name = newLayoutName; + const QString newLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + settings->layout.name = this->project->toUniqueIdentifier(newLayoutName); } else { // Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway. settings->layout.name = layout->name; } - // Folders for new layouts created for new maps use the map name, rather than the layout name. - // There's no real reason for this, aside from maintaining consistency with the default layout - // folder names that do this (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', - // rather than 'data/layouts/MyMap_Layout/'). + // Folders for new layouts created for new maps use the map name, rather than the layout name + // (i.e., if you create "MyMap", you'll get a 'data/layouts/MyMap/', rather than 'data/layouts/MyMap_Layout/'). + // There's no real reason for this, aside from maintaining consistency with the default layout folder names that do this. settings->layout.folderName = settings->name; porymapConfig.newMapHeaderSectionExpanded = this->headerSection->isExpanded(); @@ -216,14 +197,8 @@ bool NewMapDialog::validateLayoutID(bool allowEmpty) { QString errorText; if (layoutId.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_LayoutID->text()); - } else if (!this->project->isIdentifierUnique(layoutId)) { - // Layout name is already in use by something. If we're duplicating a map this isn't allowed. - if (this->mapToCopy) { - errorText = QString("%1 is not unique.").arg(ui->label_LayoutID->text()); - // If we're not duplicating a map this is ok as long as it's the name of an existing layout. - } else if (!this->project->layoutIds.contains(layoutId)) { - errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text()); - } + } else if (!this->project->layoutIds.contains(layoutId) && !this->project->isIdentifierUnique(layoutId)) { + errorText = QString("%1 must either be the ID for an existing layout, or a unique identifier for a new layout.").arg(ui->label_LayoutID->text()); } bool isValid = errorText.isEmpty(); diff --git a/src/ui/newnamedialog.cpp b/src/ui/newnamedialog.cpp index d5800daa..8f9cdc42 100644 --- a/src/ui/newnamedialog.cpp +++ b/src/ui/newnamedialog.cpp @@ -10,7 +10,6 @@ NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *pa ui(new Ui::NewNameDialog) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 1ddd975d..1e8c569f 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -11,7 +11,6 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : symbolPrefix(projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix)) { setAttribute(Qt::WA_DeleteOnClose); - setModal(true); ui->setupUi(this); this->project = project; diff --git a/src/ui/prefab.cpp b/src/ui/prefab.cpp index 10642178..e1fc83e9 100644 --- a/src/ui/prefab.cpp +++ b/src/ui/prefab.cpp @@ -303,7 +303,7 @@ bool Prefab::tryImportDefaultPrefabs(QWidget * parent, BaseGameVersion version, // into their project. QMessageBox::StandardButton prompt = QMessageBox::question(parent, - "Import Default Prefabs", + QApplication::applicationName(), QString("Would you like to import the default prefabs for %1? %2.") .arg(projectConfig.getBaseGameVersionString(version)) .arg(fileWarning), diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 346591f4..58d808ca 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -395,7 +395,7 @@ QString ProjectSettingsEditor::chooseProjectFile(const QString &defaultFilepath) if (!path.startsWith(this->baseDir)){ // Most of Porymap's file-parsing code for project files will assume that filepaths // are relative to the root project folder, so we enforce that here. - QMessageBox::warning(this, "Failed to set custom filepath", + QMessageBox::warning(this, QApplication::applicationName(), QString("Custom filepaths must be inside the root project folder '%1'").arg(this->baseDir)); return QString(); } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index cb0f6d44..f0415726 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -1265,7 +1265,7 @@ void RegionMapEditor::closeEvent(QCloseEvent *event) if (this->modified()) { QMessageBox::StandardButton result = QMessageBox::question( this, - "porymap", + QApplication::applicationName(), "The region map has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); diff --git a/src/ui/shortcutseditor.cpp b/src/ui/shortcutseditor.cpp index 30f9d62a..36bc30d0 100644 --- a/src/ui/shortcutseditor.cpp +++ b/src/ui/shortcutseditor.cpp @@ -158,7 +158,7 @@ void ShortcutsEditor::promptUserOnDuplicateFound(MultiKeyEdit *sender, MultiKeyE .arg(duplicateKeySequence.toString()).arg(siblingLabel); const auto result = QMessageBox::question( - this, "porymap", message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + this, QApplication::applicationName(), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::No); if (result == QMessageBox::Yes) removeKeySequence(duplicateKeySequence, sibling); diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index f3c4a1c9..0603e8cb 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -57,7 +57,7 @@ void TilesetEditor::updateTilesets(QString primaryTilesetLabel, QString secondar if (this->hasUnsavedChanges) { QMessageBox::StandardButton result = QMessageBox::question( this, - "porymap", + QApplication::applicationName(), "Tileset has been modified, save changes?", QMessageBox::No | QMessageBox::Yes, QMessageBox::Yes); @@ -726,7 +726,7 @@ void TilesetEditor::closeEvent(QCloseEvent *event) if (this->hasUnsavedChanges) { QMessageBox::StandardButton result = QMessageBox::question( this, - "porymap", + QApplication::applicationName(), "Tileset has been modified, save changes?", QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); diff --git a/src/ui/wildmonchart.cpp b/src/ui/wildmonchart.cpp index 32ff9bc9..80c41f81 100644 --- a/src/ui/wildmonchart.cpp +++ b/src/ui/wildmonchart.cpp @@ -465,7 +465,7 @@ void WildMonChart::showHelpDialog() { informativeText = levelTabInfo; } - QMessageBox msgBox(QMessageBox::Information, "porymap", text, QMessageBox::Close, this); + QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this); msgBox.setTextFormat(Qt::RichText); msgBox.setInformativeText(informativeText); msgBox.exec(); From bf3820745a1870d6c0395c14de5febc608b315a6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 14 Dec 2024 16:25:11 -0500 Subject: [PATCH 31/42] Add new QValidator classes --- forms/newtilesetdialog.ui | 26 +++---------- include/core/validator.h | 66 ++++++++++++++++++++++++++++++++ include/project.h | 4 +- include/ui/aboutporymap.h | 1 - include/ui/colorinputwidget.h | 1 - include/ui/maplistmodels.h | 3 -- include/ui/newnamedialog.h | 6 +-- include/ui/newtilesetdialog.h | 2 +- include/ui/tileseteditor.h | 2 - porymap.pro | 2 + src/config.cpp | 7 ++-- src/core/tileset.cpp | 7 ++-- src/core/validator.cpp | 30 +++++++++++++++ src/editor.cpp | 5 +-- src/mainwindow.cpp | 5 +-- src/project.cpp | 31 ++++++++------- src/ui/colorinputwidget.cpp | 12 ++---- src/ui/maplistmodels.cpp | 9 ++--- src/ui/newlayoutdialog.cpp | 5 +-- src/ui/newmapdialog.cpp | 5 +-- src/ui/newnamedialog.cpp | 20 ++++------ src/ui/newtilesetdialog.cpp | 33 +++++++--------- src/ui/projectsettingseditor.cpp | 5 --- src/ui/tileseteditor.cpp | 50 ++++++++++-------------- 24 files changed, 189 insertions(+), 148 deletions(-) create mode 100644 include/core/validator.h create mode 100644 src/core/validator.cpp diff --git a/forms/newtilesetdialog.ui b/forms/newtilesetdialog.ui index 3b898761..13996c73 100644 --- a/forms/newtilesetdialog.ui +++ b/forms/newtilesetdialog.ui @@ -33,14 +33,14 @@ 0
- + Name - + true @@ -60,27 +60,13 @@ - - - Symbol Name - - - - - - - - - - - Type - + false @@ -97,17 +83,17 @@
- + Checkerboard Fill - + - + Qt::Orientation::Vertical diff --git a/include/core/validator.h b/include/core/validator.h new file mode 100644 index 00000000..f5f2ef91 --- /dev/null +++ b/include/core/validator.h @@ -0,0 +1,66 @@ +#ifndef VALIDATOR_H +#define VALIDATOR_H + +/* + This file contains our subclasses of QValidator. + + - PrefixValidator is for input widgets that want to enforce a particular prefix. + It differs from a QRegularExpressionValidator with a prefix in the regex because + it will automatically enforce the prefix in fixup() if it isn't present. + It's preferable to QLineEdit's input mask because it won't affect cursor behavior. + + - IdentifierValidator is for validating that input text can be used for the name of an identifier in the project. + (i.e., starts with a letter or underscore, then may continue with letters, numbers, or underscores). + Unless a prefix is specified this is a normal QRegularExpressionValidator, we only have a subclass because we use it so often. + + - UppercaseValidator is just a validator that uppercases input text. +*/ + +#include + +class PrefixValidator : public QRegularExpressionValidator { + Q_OBJECT + +public: + explicit PrefixValidator(const QString &prefix, QObject *parent = nullptr) + : QRegularExpressionValidator(parent), m_prefix(prefix) {}; + explicit PrefixValidator(const QString &prefix, const QRegularExpression &re, QObject *parent = nullptr) + : QRegularExpressionValidator(re, parent), m_prefix(prefix) {}; + ~PrefixValidator() {}; + + virtual QValidator::State validate(QString &input, int &) const override; + virtual void fixup(QString &input) const override; + + QString prefix() const { return m_prefix; } + void setPrefix(const QString &prefix); + + bool isValid(QString &input) const; + +private: + QString m_prefix; + + bool missingPrefix(const QString &input) const; +}; + +class IdentifierValidator : public PrefixValidator { + Q_OBJECT + +public: + explicit IdentifierValidator(QObject *parent = nullptr) + : PrefixValidator("", re_identifier, parent) {}; + explicit IdentifierValidator(const QString &prefix, QObject *parent = nullptr) + : PrefixValidator(prefix, re_identifier, parent) {}; + ~IdentifierValidator() {}; + +private: + static const QRegularExpression re_identifier; +}; + +class UppercaseValidator : public QValidator { + virtual QValidator::State validate(QString &input, int &) const override { + input = input.toUpper(); + return QValidator::Acceptable; + } +}; + +#endif // VALIDATOR_H diff --git a/include/project.h b/include/project.h index 9c9fdf33..fefa1f34 100644 --- a/include/project.h +++ b/include/project.h @@ -140,9 +140,9 @@ class Project : public QObject Map *createNewMap(const Project::NewMapSettings &mapSettings, const Map* toDuplicate = nullptr); Layout *createNewLayout(const Layout::Settings &layoutSettings, const Layout* toDuplicate = nullptr); - Tileset *createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill); + Tileset *createNewTileset(QString name, bool secondary, bool checkerboardFill); bool isIdentifierUnique(const QString &identifier) const; - bool isValidNewIdentifier(const QString &identifier) const; + bool isValidNewIdentifier(QString identifier) const; QString toUniqueIdentifier(const QString &identifier) const; QString getProjectTitle(); diff --git a/include/ui/aboutporymap.h b/include/ui/aboutporymap.h index 28b06249..f960ab8c 100644 --- a/include/ui/aboutporymap.h +++ b/include/ui/aboutporymap.h @@ -2,7 +2,6 @@ #define ABOUTPORYMAP_H #include -#include #include namespace Ui { diff --git a/include/ui/colorinputwidget.h b/include/ui/colorinputwidget.h index cd871e0b..527794d9 100644 --- a/include/ui/colorinputwidget.h +++ b/include/ui/colorinputwidget.h @@ -2,7 +2,6 @@ #define COLORINPUTWIDGET_H #include -#include namespace Ui { class ColorInputWidget; diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 920bf9ba..53a79f4d 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -54,9 +54,6 @@ class GroupNameDelegate : public QStyledItemDelegate { }; - -class QRegularExpressionValidator; - class MapListModel : public QStandardItemModel { Q_OBJECT diff --git a/include/ui/newnamedialog.h b/include/ui/newnamedialog.h index 23979fc5..4abe02df 100644 --- a/include/ui/newnamedialog.h +++ b/include/ui/newnamedialog.h @@ -19,11 +19,9 @@ class NewNameDialog : public QDialog Q_OBJECT public: - explicit NewNameDialog(const QString &label, Project *project, QWidget *parent = nullptr); + explicit NewNameDialog(const QString &label, const QString &prefix = "", Project *project = nullptr, QWidget *parent = nullptr); ~NewNameDialog(); - void setNamePrefix(const QString &prefix); - virtual void accept() override; signals: @@ -32,7 +30,7 @@ class NewNameDialog : public QDialog private: Ui::NewNameDialog *ui; Project *project = nullptr; - const QString symbolPrefix; + const QString namePrefix; bool validateName(bool allowEmpty = false); void onNameChanged(const QString &name); diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index 06012708..374c85a7 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -27,7 +27,7 @@ class NewTilesetDialog : public QDialog const QString symbolPrefix; bool validateName(bool allowEmpty = false); - void onFriendlyNameChanged(const QString &friendlyName); + void onNameChanged(const QString &name); void dialogButtonClicked(QAbstractButton *button); }; diff --git a/include/ui/tileseteditor.h b/include/ui/tileseteditor.h index 32bdc97f..5cd6205e 100644 --- a/include/ui/tileseteditor.h +++ b/include/ui/tileseteditor.h @@ -120,9 +120,7 @@ private slots: void on_horizontalSlider_TilesZoom_valueChanged(int value); private: - void initUi(); void setAttributesUi(); - void setMetatileLabelValidator(); void initMetatileSelector(); void initTileSelector(); void initSelectedTileItem(); diff --git a/porymap.pro b/porymap.pro index 171ea48d..5ba1e6e0 100644 --- a/porymap.pro +++ b/porymap.pro @@ -39,6 +39,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/core/parseutil.cpp \ src/core/tile.cpp \ src/core/tileset.cpp \ + src/core/validator.cpp \ src/core/regionmap.cpp \ src/core/wildmoninfo.cpp \ src/core/editcommands.cpp \ @@ -146,6 +147,7 @@ HEADERS += include/core/advancemapparser.h \ include/core/parseutil.h \ include/core/tile.h \ include/core/tileset.h \ + include/core/validator.h \ include/core/regionmap.h \ include/core/wildmoninfo.h \ include/core/editcommands.h \ diff --git a/src/config.cpp b/src/config.cpp index 234a0a33..bcfa3489 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -2,6 +2,7 @@ #include "log.h" #include "shortcut.h" #include "map.h" +#include "validator.h" #include #include #include @@ -952,10 +953,8 @@ void ProjectConfig::setIdentifier(ProjectIdentifier id, QString text) { const QString idName = defaultIdentifiers.value(id).first; if (idName.startsWith("define_") || idName.startsWith("symbol_")) { // Validate the input for the identifier, depending on the type. - static const QRegularExpression re("[A-Za-z_]+[\\w]*"); - auto validator = QRegularExpressionValidator(re); - int temp = 0; - if (validator.validate(text, temp) != QValidator::Acceptable) { + IdentifierValidator validator; + if (!validator.isValid(text)) { logError(QString("The name '%1' for project identifier '%2' is invalid. It must only contain word characters, and cannot start with a digit.").arg(text).arg(idName)); return; } diff --git a/src/core/tileset.cpp b/src/core/tileset.cpp index cf21a675..e79e7706 100644 --- a/src/core/tileset.cpp +++ b/src/core/tileset.cpp @@ -4,6 +4,7 @@ #include "log.h" #include "config.h" #include "imageproviders.h" +#include "validator.h" #include #include @@ -180,10 +181,8 @@ bool Tileset::setMetatileLabel(int metatileId, QString label, Tileset *primaryTi if (!tileset) return false; - static const QRegularExpression expression("[_A-Za-z0-9]*$"); - QRegularExpressionValidator validator(expression); - int pos = 0; - if (validator.validate(label, pos) != QValidator::Acceptable) + IdentifierValidator validator; + if (!validator.isValid(label)) return false; tileset->metatileLabels[metatileId] = label; diff --git a/src/core/validator.cpp b/src/core/validator.cpp new file mode 100644 index 00000000..b3b36589 --- /dev/null +++ b/src/core/validator.cpp @@ -0,0 +1,30 @@ +#include "validator.h" + +// Identifiers must only contain word characters, and cannot start with a digit. +const QRegularExpression IdentifierValidator::re_identifier = QRegularExpression("[A-Za-z_]+[\\w]*"); + + +bool PrefixValidator::missingPrefix(const QString &input) const { + return !m_prefix.isEmpty() && !input.startsWith(m_prefix); +} + +QValidator::State PrefixValidator::validate(QString &input, int &pos) const { + auto state = QRegularExpressionValidator::validate(input, pos); + if (state == QValidator::Acceptable) { + // This input could be valid. If there's a prefix we should require it now. + if (missingPrefix(input)) + state = QValidator::Intermediate; + } + return state; +} + +void PrefixValidator::fixup(QString &input) const { + QRegularExpressionValidator::fixup(input); + if (missingPrefix(input)) + input.prepend(m_prefix); +} + +bool PrefixValidator::isValid(QString &input) const { + int pos = 0; + return validate(input, pos) == QValidator::Acceptable; +} diff --git a/src/editor.cpp b/src/editor.cpp index e13f76eb..56914c30 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -11,6 +11,7 @@ #include "config.h" #include "scripting.h" #include "customattributestable.h" +#include "validator.h" #include #include #include @@ -299,9 +300,7 @@ void Editor::addNewWildMonGroup(QWidget *window) { QLineEdit *lineEdit = new QLineEdit(); lineEdit->setClearButtonEnabled(true); form.addRow(new QLabel("Group Base Label:"), lineEdit); - static const QRegularExpression re_validChars("[_A-Za-z0-9]*"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(re_validChars); - lineEdit->setValidator(validator); + lineEdit->setValidator(new IdentifierValidator(lineEdit)); connect(lineEdit, &QLineEdit::textChanged, [this, &lineEdit, &buttonBox](QString text){ if (this->project->encounterGroupLabels.contains(text)) { lineEdit->setStyleSheet("QLineEdit { background-color: rgba(255, 0, 0, 25%) }"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 65a323e2..50c8beae 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1310,14 +1310,13 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { } void MainWindow::openNewMapGroupDialog() { - auto dialog = new NewNameDialog("New Group Name", this->editor->project, this); + auto dialog = new NewNameDialog("New Group Name", "", this->editor->project, this); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); dialog->open(); } void MainWindow::openNewAreaDialog() { - auto dialog = new NewNameDialog("New Area Name", this->editor->project, this); - dialog->setNamePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)); + auto dialog = new NewNameDialog("New Area Name", projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix), this->editor->project, this); connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); dialog->open(); } diff --git a/src/project.cpp b/src/project.cpp index 0a66c0e8..efc591e4 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -8,7 +8,7 @@ #include "tileset.h" #include "map.h" #include "filedialog.h" - +#include "validator.h" #include "orderedjson.h" #include @@ -1453,9 +1453,15 @@ void Project::readTilesetPaths(Tileset* tileset) { } } -Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, bool checkerboardFill) { +Tileset *Project::createNewTileset(QString name, bool secondary, bool checkerboardFill) { + const QString prefix = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix); + if (!name.startsWith(prefix)) { + logError(QString("Tileset name '%1' doesn't begin with the prefix '%2'.").arg(name).arg(prefix)); + return nullptr; + } + auto tileset = new Tileset(); - tileset->name = projectConfig.getIdentifier(ProjectIdentifier::symbol_tilesets_prefix) + friendlyName; + tileset->name = name; tileset->is_secondary = secondary; // Create tileset directories @@ -1527,10 +1533,11 @@ Tileset *Project::createNewTileset(const QString &friendlyName, bool secondary, this->tilesetLabelsOrdered.append(tileset->name); // TODO: Ideally we wouldn't save new Tilesets immediately - // Append to tileset specific files - tileset->appendToHeaders(this->root, friendlyName, this->usingAsmTilesets); - tileset->appendToGraphics(this->root, friendlyName, this->usingAsmTilesets); - tileset->appendToMetatiles(this->root, friendlyName, this->usingAsmTilesets); + // Append to tileset specific files. Strip prefix from name to get base name for use in other symbols. + name.remove(0, prefix.length()); + tileset->appendToHeaders(this->root, name, this->usingAsmTilesets); + tileset->appendToGraphics(this->root, name, this->usingAsmTilesets); + tileset->appendToMetatiles(this->root, name, this->usingAsmTilesets); tileset->save(); @@ -1966,12 +1973,10 @@ bool Project::isIdentifierUnique(const QString &identifier) const { return true; } -// For some arbitrary string, return true if it's both a valid identifier name -// and not one that's already in-use. -bool Project::isValidNewIdentifier(const QString &identifier) const { - static const QRegularExpression re_identifier("[A-Za-z_]+[\\w]*"); - QRegularExpressionMatch match = re_identifier.match(identifier); - return match.hasMatch() && isIdentifierUnique(identifier); +// For some arbitrary string, return true if it's both a valid identifier name and not one that's already in-use. +bool Project::isValidNewIdentifier(QString identifier) const { + IdentifierValidator validator; + return validator.isValid(identifier) && isIdentifierUnique(identifier); } // Assumes 'identifier' is a valid name. If 'identifier' is unique, returns 'identifier'. diff --git a/src/ui/colorinputwidget.cpp b/src/ui/colorinputwidget.cpp index 8b40be27..6ed587aa 100644 --- a/src/ui/colorinputwidget.cpp +++ b/src/ui/colorinputwidget.cpp @@ -1,16 +1,10 @@ #include "colorinputwidget.h" #include "ui_colorinputwidget.h" #include "colorpicker.h" +#include "validator.h" #include -class HexCodeValidator : public QValidator { - virtual QValidator::State validate(QString &input, int &) const override { - input = input.toUpper(); - return QValidator::Acceptable; - } -}; - static inline int rgb5(int rgb) { return round(static_cast(rgb * 31) / 255.0); } static inline int rgb8(int rgb) { return round(rgb * 255. / 31.); } static inline int gbaRed(int rgb) { return rgb & 0x1f; } @@ -43,8 +37,8 @@ void ColorInputWidget::init() { connect(ui->spinBox_Green, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); connect(ui->spinBox_Blue, QOverload::of(&QSpinBox::valueChanged), this, &ColorInputWidget::setRgbFromSpinners); - static const HexCodeValidator hexValidator; - ui->lineEdit_Hex->setValidator(&hexValidator); + static const UppercaseValidator uppercaseValidator; + ui->lineEdit_Hex->setValidator(&uppercaseValidator); connect(ui->lineEdit_Hex, &QLineEdit::textEdited, this, &ColorInputWidget::setRgbFromHexString); // We have separate signals for when color input editing finishes. diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index dc1e3d7e..a46dfc58 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -1,11 +1,11 @@ #include "maplistmodels.h" +#include "validator.h" +#include "project.h" +#include "filterchildrenproxymodel.h" #include #include -#include "project.h" -#include "filterchildrenproxymodel.h" - void MapTree::removeSelected() { @@ -179,9 +179,8 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); - static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); editor->setPlaceholderText("gMapGroup_"); - editor->setValidator(new QRegularExpressionValidator(expression, parent)); + editor->setValidator(new IdentifierValidator(parent)); editor->setFrame(false); return editor; } diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 086bc346..226df897 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -2,6 +2,7 @@ #include "maplayout.h" #include "ui_newlayoutdialog.h" #include "config.h" +#include "validator.h" #include #include @@ -34,9 +35,7 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q ui->newLayoutForm->initUi(project); - // Identifiers can only contain word characters, and cannot start with a digit. - static const QRegularExpression re("[A-Za-z_]+[\\w]*"); - auto validator = new QRegularExpressionValidator(re, this); + auto validator = new IdentifierValidator(this); ui->lineEdit_Name->setValidator(validator); ui->lineEdit_LayoutID->setValidator(validator); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index caf98060..2d1fa46f 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -3,6 +3,7 @@ #include "mainwindow.h" #include "ui_newmapdialog.h" #include "config.h" +#include "validator.h" #include #include @@ -45,9 +46,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare ui->comboBox_Group->addItems(project->groupNames); ui->comboBox_LayoutID->addItems(project->layoutIds); - // Identifiers can only contain word characters, and cannot start with a digit. - static const QRegularExpression re("[A-Za-z_]+[\\w]*"); - auto validator = new QRegularExpressionValidator(re, this); + auto validator = new IdentifierValidator(this); ui->lineEdit_Name->setValidator(validator); ui->comboBox_Group->setValidator(validator); ui->comboBox_LayoutID->setValidator(validator); diff --git a/src/ui/newnamedialog.cpp b/src/ui/newnamedialog.cpp index 8f9cdc42..842bf4b7 100644 --- a/src/ui/newnamedialog.cpp +++ b/src/ui/newnamedialog.cpp @@ -2,12 +2,14 @@ #include "ui_newnamedialog.h" #include "project.h" #include "imageexport.h" +#include "validator.h" const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *parent) : +NewNameDialog::NewNameDialog(const QString &label, const QString &prefix, Project* project, QWidget *parent) : QDialog(parent), - ui(new Ui::NewNameDialog) + ui(new Ui::NewNameDialog), + namePrefix(prefix) { setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); @@ -16,10 +18,8 @@ NewNameDialog::NewNameDialog(const QString &label, Project* project, QWidget *pa if (!label.isEmpty()) ui->label_Name->setText(label); - // Identifiers must only contain word characters, and cannot start with a digit. - static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); - ui->lineEdit_Name->setValidator(validator); + ui->lineEdit_Name->setValidator(new IdentifierValidator(namePrefix, this)); + ui->lineEdit_Name->setText(namePrefix); connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewNameDialog::onNameChanged); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewNameDialog::dialogButtonClicked); @@ -32,10 +32,6 @@ NewNameDialog::~NewNameDialog() delete ui; } -void NewNameDialog::setNamePrefix(const QString &) { - //TODO -} - void NewNameDialog::onNameChanged(const QString &) { validateName(true); } @@ -44,9 +40,9 @@ bool NewNameDialog::validateName(bool allowEmpty) { const QString name = ui->lineEdit_Name->text(); QString errorText; - if (name.isEmpty()) { + if (name.isEmpty() || name == namePrefix) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (!this->project->isIdentifierUnique(name)) { + } else if (this->project && !this->project->isIdentifierUnique(name)) { errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 1e8c569f..049b317d 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -2,6 +2,7 @@ #include "ui_newtilesetdialog.h" #include "project.h" #include "imageexport.h" +#include "validator.h" const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; @@ -15,15 +16,11 @@ NewTilesetDialog::NewTilesetDialog(Project* project, QWidget *parent) : this->project = project; ui->checkBox_CheckerboardFill->setChecked(porymapConfig.tilesetCheckerboardFill); - ui->label_SymbolNameDisplay->setText(this->symbolPrefix); - ui->comboBox_Type->setMinimumContentsLength(12); + ui->comboBox_Type->setMinimumContentsLength(12 + this->symbolPrefix.length()); + ui->lineEdit_Name->setValidator(new IdentifierValidator(this->symbolPrefix, this)); + ui->lineEdit_Name->setText(this->symbolPrefix); - //only allow characters valid for a symbol - static const QRegularExpression expression("[A-Za-z_]+[\\w]*"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression, this); - ui->lineEdit_FriendlyName->setValidator(validator); - - connect(ui->lineEdit_FriendlyName, &QLineEdit::textChanged, this, &NewTilesetDialog::onFriendlyNameChanged); + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewTilesetDialog::onNameChanged); connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewTilesetDialog::dialogButtonClicked); adjustSize(); @@ -35,28 +32,24 @@ NewTilesetDialog::~NewTilesetDialog() delete ui; } -void NewTilesetDialog::onFriendlyNameChanged(const QString &friendlyName) { - // When the tileset name is changed, update this label to display the full symbol name. - ui->label_SymbolNameDisplay->setText(this->symbolPrefix + friendlyName); - +void NewTilesetDialog::onNameChanged(const QString &) { validateName(true); } bool NewTilesetDialog::validateName(bool allowEmpty) { - const QString friendlyName = ui->lineEdit_FriendlyName->text(); - const QString symbolName = ui->label_SymbolNameDisplay->text(); + const QString name = ui->lineEdit_Name->text(); QString errorText; - if (friendlyName.isEmpty()) { - if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_FriendlyName->text()); - } else if (!this->project->isIdentifierUnique(symbolName)) { - errorText = QString("%1 '%2' is not unique.").arg(ui->label_SymbolName->text()).arg(symbolName); + if (name.isEmpty() || name == symbolPrefix) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } bool isValid = errorText.isEmpty(); ui->label_NameError->setText(errorText); ui->label_NameError->setVisible(!isValid); - ui->lineEdit_FriendlyName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + ui->lineEdit_Name->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); return isValid; } @@ -74,7 +67,7 @@ void NewTilesetDialog::accept() { return; bool secondary = ui->comboBox_Type->currentIndex() == 1; - Tileset *tileset = this->project->createNewTileset(ui->lineEdit_FriendlyName->text(), secondary, ui->checkBox_CheckerboardFill->isChecked()); + Tileset *tileset = this->project->createNewTileset(ui->lineEdit_Name->text(), secondary, ui->checkBox_CheckerboardFill->isChecked()); if (!tileset) { ui->label_GenericError->setText(QString("Failed to create tileset. See %1 for details.").arg(getLogPath())); ui->label_GenericError->setVisible(true); diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 58d808ca..d01a3351 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -114,11 +114,6 @@ void ProjectSettingsEditor::initUi() { ui->lineEdit_BorderMetatiles->setValidator(validator_HexList); this->setBorderMetatilesUi(projectConfig.useCustomBorderSize); - // Validate that the text added to the warp behavior list could be a valid define - // (we don't care whether it actually is a metatile behavior define) - static const QRegularExpression expression_Word("^[A-Za-z0-9_]*$"); - QRegularExpressionValidator *validator_Word = new QRegularExpressionValidator(expression_Word); - ui->comboBox_WarpBehaviors->setValidator(validator_Word); ui->textEdit_WarpBehaviors->setTextColor(Qt::gray); // Set spin box limits diff --git a/src/ui/tileseteditor.cpp b/src/ui/tileseteditor.cpp index 0603e8cb..fab59928 100644 --- a/src/ui/tileseteditor.cpp +++ b/src/ui/tileseteditor.cpp @@ -8,6 +8,7 @@ #include "config.h" #include "shortcut.h" #include "filedialog.h" +#include "validator.h" #include #include #include @@ -20,9 +21,25 @@ TilesetEditor::TilesetEditor(Project *project, Layout *layout, QWidget *parent) layout(layout), hasUnsavedChanges(false) { - this->setAttribute(Qt::WA_DeleteOnClose); - this->setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); - this->initUi(); + setAttribute(Qt::WA_DeleteOnClose); + setTilesets(this->layout->tileset_primary_label, this->layout->tileset_secondary_label); + ui->setupUi(this); + + this->tileXFlip = ui->checkBox_xFlip->isChecked(); + this->tileYFlip = ui->checkBox_yFlip->isChecked(); + this->paletteId = ui->spinBox_paletteSelector->value(); + ui->spinBox_paletteSelector->setMinimum(0); + ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); + ui->lineEdit_metatileLabel->setValidator(new IdentifierValidator(this)); + + setAttributesUi(); + initMetatileSelector(); + initMetatileLayersItem(); + initTileSelector(); + initSelectedTileItem(); + initShortcuts(); + this->metatileSelector->select(0); + restoreWindowState(); } TilesetEditor::~TilesetEditor() @@ -92,26 +109,6 @@ void TilesetEditor::setTilesets(QString primaryTilesetLabel, QString secondaryTi this->initMetatileHistory(); } -void TilesetEditor::initUi() { - ui->setupUi(this); - this->tileXFlip = ui->checkBox_xFlip->isChecked(); - this->tileYFlip = ui->checkBox_yFlip->isChecked(); - this->paletteId = ui->spinBox_paletteSelector->value(); - this->ui->spinBox_paletteSelector->setMinimum(0); - this->ui->spinBox_paletteSelector->setMaximum(Project::getNumPalettesTotal() - 1); - - this->setAttributesUi(); - this->setMetatileLabelValidator(); - - this->initMetatileSelector(); - this->initMetatileLayersItem(); - this->initTileSelector(); - this->initSelectedTileItem(); - this->initShortcuts(); - this->metatileSelector->select(0); - this->restoreWindowState(); -} - void TilesetEditor::setAttributesUi() { // Behavior if (projectConfig.metatileBehaviorMask) { @@ -171,13 +168,6 @@ void TilesetEditor::setAttributesUi() { this->ui->frame_Properties->adjustSize(); } -void TilesetEditor::setMetatileLabelValidator() { - //only allow characters valid for a symbol - static const QRegularExpression expression("[_A-Za-z0-9]*$"); - QRegularExpressionValidator *validator = new QRegularExpressionValidator(expression); - this->ui->lineEdit_metatileLabel->setValidator(validator); -} - void TilesetEditor::initMetatileSelector() { this->metatileSelector = new TilesetEditorMetatileSelector(this->primaryTileset, this->secondaryTileset, this->layout); From 59464aa89cebc50375f62d7345261eb0fcb5db89 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Dec 2024 14:39:23 -0500 Subject: [PATCH 32/42] Fix possible crash when layout fails to open --- src/editor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index 56914c30..d0a122ec 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1166,7 +1166,7 @@ void Editor::setCursorRectVisible(bool visible) { void Editor::onHoveredMapMetatileChanged(const QPoint &pos) { int x = pos.x(); int y = pos.y(); - if (!layout->isWithinBounds(x, y)) + if (!layout || !layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); @@ -1198,7 +1198,7 @@ void Editor::onHoveredMapMetatileCleared() { } void Editor::onHoveredMapMovementPermissionChanged(int x, int y) { - if (!layout->isWithinBounds(x, y)) + if (!layout || !layout->isWithinBounds(x, y)) return; this->updateCursorRectPos(x, y); From d9be7d594e4abe755ad2d35ea1390d479c6ff2b3 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 16 Dec 2024 15:21:41 -0500 Subject: [PATCH 33/42] Fix Qt5 build --- include/lib/collapsiblesection.h | 1 + src/core/advancemapparser.cpp | 6 +++--- src/ui/mapheaderform.cpp | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/include/lib/collapsiblesection.h b/include/lib/collapsiblesection.h index 73169303..b74835f4 100644 --- a/include/lib/collapsiblesection.h +++ b/include/lib/collapsiblesection.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/core/advancemapparser.cpp b/src/core/advancemapparser.cpp index 6af7476e..6a8428ec 100644 --- a/src/core/advancemapparser.cpp +++ b/src/core/advancemapparser.cpp @@ -206,9 +206,9 @@ QList AdvanceMapParser::parsePalette(const QString &filepath, bool *error) QList palette; int i = 0; while (i < in.length()) { - unsigned char red = qMin(qMax(static_cast(in.at(i + 0)), 0u), 255u); - unsigned char green = qMin(qMax(static_cast(in.at(i + 1)), 0u), 255u); - unsigned char blue = qMin(qMax(static_cast(in.at(i + 2)), 0u), 255u); + unsigned char red = static_cast(in.at(i + 0)); + unsigned char green = static_cast(in.at(i + 1)); + unsigned char blue = static_cast(in.at(i + 2)); palette.append(qRgb(red, green, blue)); i += 4; } diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 7aa5a628..4bea0bef 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -23,7 +23,7 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) 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); - connect(ui->spinBox_FloorNumber, &QSpinBox::valueChanged, this, &MapHeaderForm::onFloorNumberChanged); + connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); } MapHeaderForm::~MapHeaderForm() From 0d939772bf451412b76dff113fe5f000a24fe3fb Mon Sep 17 00:00:00 2001 From: GriffinR Date: Fri, 20 Dec 2024 14:49:40 -0500 Subject: [PATCH 34/42] Add QMessageBox convenience classes --- include/mainwindow.h | 1 + include/ui/message.h | 60 +++++++++++++++++ porymap.pro | 2 + src/mainwindow.cpp | 149 ++++++++++++------------------------------- src/project.cpp | 11 +++- src/ui/message.cpp | 78 ++++++++++++++++++++++ 6 files changed, 189 insertions(+), 112 deletions(-) create mode 100644 include/ui/message.h create mode 100644 src/ui/message.cpp diff --git a/include/mainwindow.h b/include/mainwindow.h index 0eac8947..5ffc82a7 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -365,6 +365,7 @@ private slots: QString getExistingDirectory(QString); bool openProject(QString dir, bool initial = false); bool closeProject(); + void showRecentError(const QString &baseMessage); void showProjectOpenFailure(); void showMapsExcludedAlert(const QStringList &excludedMapNames); diff --git a/include/ui/message.h b/include/ui/message.h new file mode 100644 index 00000000..8e36372d --- /dev/null +++ b/include/ui/message.h @@ -0,0 +1,60 @@ +#pragma once +#ifndef MESSAGE_H +#define MESSAGE_H + +/* + These classes are thin wrappers around QMessageBox for convenience. + The base Message class is a regular window-modal QMessageBox with "porymap" as the window title. + + QMessageBox's static functions enforce application modality (among other things), which changes the style of the message boxes on macOS. + With these equivalent static functions we have more control over the appearance and behavior of the window, + and we keep the convenience of not needing to provide all the arguments. + + If more control is needed (like adding custom buttons to the window) use the constructors as you would for a normal QMessageBox. +*/ + +#include + +class Message : public QMessageBox { +public: + Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent); +}; + +// Basic error message with an 'Ok' button. +class ErrorMessage : public Message { +public: + ErrorMessage(const QString &message, QWidget *parent); + static int 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); +}; + +// 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); +}; + +// Basic question message with a 'Yes' and 'No' button. +class QuestionMessage : public Message { +public: + QuestionMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + +// Error message directing users to their log file. +// Shows the most recent error as detailed text. +class RecentErrorMessage : public ErrorMessage { +public: + RecentErrorMessage(const QString &message, QWidget *parent); + static int show(const QString &message, QWidget *parent); +}; + + +#endif // MESSAGE_H diff --git a/porymap.pro b/porymap.pro index 99489758..940c0a7f 100644 --- a/porymap.pro +++ b/porymap.pro @@ -77,6 +77,7 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/filterchildrenproxymodel.cpp \ src/ui/maplistmodels.cpp \ src/ui/maplisttoolbar.cpp \ + src/ui/message.cpp \ src/ui/graphicsview.cpp \ src/ui/imageproviders.cpp \ src/ui/layoutpixmapitem.cpp \ @@ -183,6 +184,7 @@ HEADERS += include/core/advancemapparser.h \ include/ui/filterchildrenproxymodel.h \ include/ui/maplistmodels.h \ include/ui/maplisttoolbar.h \ + include/ui/message.h \ include/ui/graphicsview.h \ include/ui/imageproviders.h \ include/ui/layoutpixmapitem.h \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 50c8beae..717ca085 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,6 +26,7 @@ #include "newmapdialog.h" #include "newtilesetdialog.h" #include "newnamedialog.h" +#include "message.h" #include #include @@ -36,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -151,6 +151,7 @@ void MainWindow::initWindow() { #endif setWindowDisabled(true); + show(); } void MainWindow::initShortcuts() { @@ -678,14 +679,10 @@ bool MainWindow::checkProjectSanity() { logWarn(QString("The directory '%1' failed the project sanity check.").arg(editor->project->root)); - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setText(QString("The selected directory appears to be invalid.")); + ErrorMessage msgBox(QStringLiteral("The selected directory appears to be invalid."), this); 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)); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setDefaultButton(QMessageBox::Ok); auto tryAnyway = msgBox.addButton("Try Anyway", QMessageBox::ActionRole); msgBox.exec(); if (msgBox.clickedButton() == tryAnyway) { @@ -697,25 +694,18 @@ bool MainWindow::checkProjectSanity() { } void MainWindow::showProjectOpenFailure() { - QString errorMsg = QString("There was an error opening the project. Please see %1 for full error details.").arg(getLogPath()); - QMessageBox error(QMessageBox::Critical, QApplication::applicationName(), errorMsg, QMessageBox::Ok, this); - error.setDetailedText(getMostRecentError()); - error.exec(); + RecentErrorMessage::show(QStringLiteral("There was an error opening the project."), this); } // Alert the user that one or more maps have been excluded while loading the project. void MainWindow::showMapsExcludedAlert(const QStringList &excludedMapNames) { - QMessageBox msgBox(QMessageBox::Icon::Warning, QApplication::applicationName(), "", QMessageBox::Ok, this); - - QString errorMsg; + RecentErrorMessage msgBox("", this); if (excludedMapNames.length() == 1) { - errorMsg = 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 { - errorMsg = QString("Failed to load the maps listed below. Saving will exclude these maps from your project."); - msgBox.setDetailedText(excludedMapNames.join("\n")); + 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. } - errorMsg.append(QString("\n\nPlease see %1 for full error details.").arg(getLogPath())); - msgBox.setText(errorMsg); msgBox.exec(); } @@ -812,22 +802,15 @@ void MainWindow::showFileWatcherWarning(QString filepath) { static bool showing = false; if (showing) return; - QMessageBox notice(this); - notice.setText("File Changed"); - notice.setInformativeText(QString("The file %1 has changed on disk. Would you like to reload the project?") - .arg(filepath.remove(project->root + "/"))); - notice.setStandardButtons(QMessageBox::No | QMessageBox::Yes); - notice.setDefaultButton(QMessageBox::No); - notice.setIcon(QMessageBox::Question); - + QuestionMessage msgBox(QString("The file %1 has changed on disk. Would you like to reload the project?").arg(filepath.remove(project->root + "/")), this); QCheckBox showAgainCheck("Do not ask again."); - notice.setCheckBox(&showAgainCheck); + msgBox.setCheckBox(&showAgainCheck); showing = true; - int choice = notice.exec(); - if (choice == QMessageBox::Yes) { + auto reply = msgBox.exec(); + if (reply == QMessageBox::Yes) { on_action_Reload_Project_triggered(); - } else if (choice == QMessageBox::No) { + } else if (reply == QMessageBox::No) { if (showAgainCheck.isChecked()) { porymapConfig.monitorFiles = false; if (this->preferenceEditor) @@ -850,14 +833,10 @@ void MainWindow::on_action_Open_Project_triggered() void MainWindow::on_action_Reload_Project_triggered() { // TODO: when undo history is complete show only if has unsaved changes - QMessageBox warning(this); - warning.setText("WARNING"); - warning.setInformativeText("Reloading this project will discard any unsaved changes."); - warning.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - warning.setDefaultButton(QMessageBox::Cancel); - warning.setIcon(QMessageBox::Warning); - - if (warning.exec() == QMessageBox::Ok) + WarningMessage msgBox(QStringLiteral("Reloading this project will discard any unsaved changes."), this); + msgBox.addButton(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + if (msgBox.exec() == QMessageBox::Ok) openProject(editor->project->root); } @@ -878,23 +857,14 @@ bool MainWindow::userSetMap(QString map_name) { return true; // Already set if (map_name == editor->project->getDynamicMapName()) { - QMessageBox msgBox(QMessageBox::Icon::Warning, - QApplication::applicationName(), - QString("The map '%1' can't be opened, it's a placeholder to indicate the specified map will be set programmatically.").arg(map_name), - QMessageBox::Ok, - this); + 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(); return false; } if (!setMap(map_name)) { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("There was an error opening map %1.\n\nPlease see %2 for full error details.").arg(map_name).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("There was an error opening map '%1'.").arg(map_name), this); return false; } return true; @@ -950,13 +920,7 @@ void MainWindow::setLayoutOnlyMode(bool layoutOnly) { // Use when the user is specifically requesting a layout to open. bool MainWindow::userSetLayout(QString layoutId) { if (!setLayout(layoutId)) { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("There was an error opening layout %1.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("There was an error opening layout '%1'.").arg(layoutId), this); return false; } @@ -1390,7 +1354,7 @@ void MainWindow::onNewTilesetCreated(Tileset *tileset) { // Unlike creating a new map or layout (which immediately opens the new item) // creating a new tileset has no visual feedback that it succeeded, so we show a message. - QMessageBox::information(this, QApplication::applicationName(), QString( "New tileset created at '%1'!").arg(tileset->getExpectedDir())); + InfoMessage::show(QString("New tileset created at '%1'!").arg(tileset->getExpectedDir()), this); // Refresh tileset combo boxes if (!tileset->is_secondary) { @@ -1413,13 +1377,7 @@ void MainWindow::openDuplicateMapDialog(const QString &mapName) { auto dialog = new NewMapDialog(this->editor->project, map, this); dialog->open(); } else { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(mapName).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("Unable to duplicate '%1'.").arg(mapName), this); } } @@ -1440,13 +1398,7 @@ void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { auto dialog = createNewLayoutDialog(layout); dialog->open(); } else { - QMessageBox msgBox(QMessageBox::Icon::Critical, - QApplication::applicationName(), - QString("Unable to duplicate '%1'.\n\nPlease see %2 for full error details.").arg(layoutId).arg(getLogPath()), - QMessageBox::Ok, - this); - msgBox.setDetailedText(getMostRecentError()); - msgBox.exec(); + RecentErrorMessage::show(QString("Unable to duplicate '%1'.").arg(layoutId), this); } } @@ -1999,8 +1951,7 @@ void MainWindow::addNewEvent(Event::Type type) { updateObjects(); editor->selectMapEvent(object); } else { - QMessageBox msgBox(this); - msgBox.setText("Failed to add new event"); + WarningMessage msgBox(QStringLiteral("Failed to add new event."), this); if (Event::typeToGroup(type) == Event::Group::Object) { msgBox.setInformativeText(QString("The limit for object events (%1) has been reached.\n\n" "This limit can be adjusted with %2 in '%3'.") @@ -2008,8 +1959,6 @@ void MainWindow::addNewEvent(Event::Type type) { .arg(projectConfig.getIdentifier(ProjectIdentifier::define_obj_event_count)) .arg(projectConfig.getFilePath(ProjectFilePath::constants_global))); } - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Warning); msgBox.exec(); } } @@ -2509,14 +2458,7 @@ void MainWindow::on_action_Export_Map_Image_triggered() { void MainWindow::on_actionExport_Stitched_Map_Image_triggered() { if (!this->editor->map) { - QMessageBox warning(this); - warning.setText("Notice"); - warning.setInformativeText("Map stitch images are not possible without a map selected."); - warning.setStandardButtons(QMessageBox::Ok); - warning.setDefaultButton(QMessageBox::Cancel); - warning.setIcon(QMessageBox::Warning); - - warning.exec(); + WarningMessage::show(QStringLiteral("Map stitch images are not possible without a map selected."), this); return; } showExportMapImageWindow(ImageExporterMode::Stitch); @@ -2535,13 +2477,7 @@ void MainWindow::on_actionImport_Map_from_Advance_Map_1_92_triggered() { bool error = false; Layout *mapLayout = AdvanceMapParser::parseLayout(filepath, &error, editor->project); if (error) { - QMessageBox msgBox(this); - msgBox.setText("Failed to import map from Advance Map 1.92 .map file."); - QString message = QString("The .map file could not be processed. View porymap.log for specific errors."); - msgBox.setInformativeText(message); - msgBox.setDefaultButton(QMessageBox::Ok); - msgBox.setIcon(QMessageBox::Icon::Critical); - msgBox.exec(); + RecentErrorMessage::show(QStringLiteral("Failed to import map from Advance Map 1.92 .map file."), this); delete mapLayout; return; } @@ -2858,8 +2794,7 @@ void MainWindow::on_actionProject_Settings_triggered() { } void MainWindow::onWarpBehaviorWarningClicked() { - static const QString text = QString("Warp Events only function as exits on certain metatiles"); - static const QString informative = QString( + static const QString informative = QStringLiteral( "

" "For instance, most floor metatiles in a cave have the metatile behavior MB_CAVE, but the floor space in front of an exit " "will have MB_SOUTH_ARROW_WARP, which is treated specially in your project's code to allow a Warp Event to warp the player. " @@ -2873,13 +2808,13 @@ void MainWindow::onWarpBehaviorWarningClicked() { "You can disable this warning or edit the list of behaviors that silence this warning under Options -> Project Settings..." "

" ); - QMessageBox msgBox(QMessageBox::Information, QApplication::applicationName(), text, QMessageBox::Close, this); - QPushButton *settings = msgBox.addButton("Open Settings...", QMessageBox::ActionRole); - msgBox.setDefaultButton(QMessageBox::Close); + + InfoMessage msgBox(QStringLiteral("Warp Events only function as exits on certain metatiles"), this); + auto settingsButton = msgBox.addButton("Open Settings...", QMessageBox::ActionRole); msgBox.setTextFormat(Qt::RichText); msgBox.setInformativeText(informative); msgBox.exec(); - if (msgBox.clickedButton() == settings) + if (msgBox.clickedButton() == settingsButton) this->openProjectSettingsEditor(ProjectSettingsEditor::eventsTab); } @@ -3013,12 +2948,7 @@ bool MainWindow::initRegionMapEditor(bool silent) { } bool MainWindow::askToFixRegionMapEditor() { - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Critical); - msgBox.setText(QString("There was an error opening the region map data. Please see %1 for full error details.").arg(getLogPath())); - msgBox.setDetailedText(getMostRecentError()); - msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setDefaultButton(QMessageBox::Ok); + RecentErrorMessage msgBox(QStringLiteral("There was an error opening the region map data."), this); auto reconfigButton = msgBox.addButton("Reconfigure", QMessageBox::ActionRole); msgBox.exec(); if (msgBox.clickedButton() == reconfigButton) { @@ -3088,15 +3018,16 @@ bool MainWindow::closeProject() { return true; if (this->editor->project->hasUnsavedChanges()) { - QMessageBox::StandardButton result = QMessageBox::question( - this, QApplication::applicationName(), "The project has been modified, save changes?", - QMessageBox::No | QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); + QuestionMessage msgBox(QStringLiteral("The project has been modified, save changes?"), this); + msgBox.addButton(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Yes); - if (result == QMessageBox::Yes) { + auto reply = msgBox.exec(); + if (reply == QMessageBox::Yes) { editor->saveProject(); - } else if (result == QMessageBox::No) { + } else if (reply == QMessageBox::No) { logWarn("Closing project with unsaved changes."); - } else if (result == QMessageBox::Cancel) { + } else if (reply == QMessageBox::Cancel) { return false; } } diff --git a/src/project.cpp b/src/project.cpp index efc591e4..e0717b36 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -107,9 +107,14 @@ bool Project::load() { && readSongNames() && readMapGroups(); - initNewLayoutSettings(); - initNewMapSettings(); - applyParsedLimits(); + if (success) { + // No need to do this if something failed to load. + // (and in fact we shouldn't, because they contain + // assumptions that some things have loaded correctly). + initNewLayoutSettings(); + initNewMapSettings(); + applyParsedLimits(); + } return success; } diff --git a/src/ui/message.cpp b/src/ui/message.cpp new file mode 100644 index 00000000..d2d91f75 --- /dev/null +++ b/src/ui/message.cpp @@ -0,0 +1,78 @@ +#include "message.h" +#include "log.h" + +#include + +Message::Message(QMessageBox::Icon icon, const QString &text, QMessageBox::StandardButtons buttons, QWidget *parent) : + QMessageBox(icon, QApplication::applicationName(), text, buttons, parent) +{ + setWindowModality(Qt::WindowModal); +} + +ErrorMessage::ErrorMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Critical, + message, + QMessageBox::Ok, + parent) +{ + setDefaultButton(QMessageBox::Ok); +} + +WarningMessage::WarningMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Warning, + message, + QMessageBox::Ok, + parent) +{ + setDefaultButton(QMessageBox::Ok); +} + +InfoMessage::InfoMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Information, + message, + QMessageBox::Close, + parent) +{ + setDefaultButton(QMessageBox::Close); +} + +QuestionMessage::QuestionMessage(const QString &message, QWidget *parent) : + Message(QMessageBox::Question, + message, + QMessageBox::No | QMessageBox::Yes, + parent) +{ + setDefaultButton(QMessageBox::No); +} + +RecentErrorMessage::RecentErrorMessage(const QString &message, QWidget *parent) : + ErrorMessage(message, parent) +{ + setInformativeText(QString("Please see %1 for full error details.").arg(getLogPath())); + setDetailedText(getMostRecentError()); +} + +int RecentErrorMessage::show(const QString &message, QWidget *parent) { + RecentErrorMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int ErrorMessage::show(const QString &message, QWidget *parent) { + ErrorMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int WarningMessage::show(const QString &message, QWidget *parent) { + WarningMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int QuestionMessage::show(const QString &message, QWidget *parent) { + QuestionMessage msgBox(message, parent); + return msgBox.exec(); +}; + +int InfoMessage::show(const QString &message, QWidget *parent) { + InfoMessage msgBox(message, parent); + return msgBox.exec(); +}; From 40adedef346e9f818016b26c5a71e3bebde28021 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Mon, 23 Dec 2024 11:42:57 -0500 Subject: [PATCH 35/42] Fix editor's map/layout clearing if a map/layout fails to load --- src/editor.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/editor.cpp b/src/editor.cpp index d0a122ec..c22068d2 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -1242,10 +1242,10 @@ QString Editor::getMovementPermissionText(uint16_t collision, uint16_t elevation void Editor::unsetMap() { // disconnect previous map's signals so they are not firing // multiple times if set again in the future - if (map) { - map->pruneEditHistory(); - map->disconnect(this); - for (auto connection : map->getConnections()) + if (this->map) { + this->map->pruneEditHistory(); + this->map->disconnect(this); + for (const auto &connection : this->map->getConnections()) disconnectMapConnection(connection); } clearMapConnections(); @@ -1258,13 +1258,12 @@ bool Editor::setMap(QString map_name) { return false; } - unsetMap(); - Map *loadedMap = project->loadMap(map_name); if (!loadedMap) { return false; } + unsetMap(); this->map = loadedMap; setLayout(map->layout()->id); @@ -1291,13 +1290,17 @@ bool Editor::setLayout(QString layoutId) { return false; } - this->layout = this->project->loadLayout(layoutId); + Layout *loadedLayout = this->project->loadLayout(layoutId); + if (!loadedLayout) { + return false; + } + this->layout = loadedLayout; if (!displayLayout()) { return false; } - editGroup.addStack(&layout->editHistory); + editGroup.addStack(&this->layout->editHistory); map_ruler->setMapDimensions(QSize(this->layout->getWidth(), this->layout->getHeight())); connect(this->layout, &Layout::layoutDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); From 90c904ecb92032f3c45ab1c3c9f27b086b47ab1f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sat, 28 Dec 2024 01:51:40 -0500 Subject: [PATCH 36/42] Make it easier to edit MAPSEC names, Area -> Location --- forms/mainwindow.ui | 14 +- forms/mapheaderform.ui | 46 +++-- forms/newlocationdialog.ui | 89 +++++++++ ...{newnamedialog.ui => newmapgroupdialog.ui} | 6 +- forms/regionmapeditor.ui | 179 +++++++++--------- include/core/regionmap.h | 1 - include/mainwindow.h | 9 +- include/project.h | 14 +- include/ui/mapheaderform.h | 12 +- include/ui/maplistmodels.h | 11 +- include/ui/newlocationdialog.h | 34 ++++ .../{newnamedialog.h => newmapgroupdialog.h} | 20 +- include/ui/regionmapeditor.h | 6 +- porymap.pro | 9 +- src/mainwindow.cpp | 115 +++++------ src/project.cpp | 78 +++++--- src/ui/mapheaderform.cpp | 58 ++++-- src/ui/maplistmodels.cpp | 31 ++- src/ui/newlocationdialog.cpp | 79 ++++++++ src/ui/newmapdialog.cpp | 10 +- ...ewnamedialog.cpp => newmapgroupdialog.cpp} | 38 ++-- src/ui/projectsettingseditor.cpp | 2 +- src/ui/regionmapeditor.cpp | 61 ++---- src/ui/regionmapentriespixmapitem.cpp | 2 +- 24 files changed, 593 insertions(+), 331 deletions(-) create mode 100644 forms/newlocationdialog.ui rename forms/{newnamedialog.ui => newmapgroupdialog.ui} (94%) create mode 100644 include/ui/newlocationdialog.h rename include/ui/{newnamedialog.h => newmapgroupdialog.h} (51%) create mode 100644 src/ui/newlocationdialog.cpp rename src/ui/{newnamedialog.cpp => newmapgroupdialog.cpp} (57%) diff --git a/forms/mainwindow.ui b/forms/mainwindow.ui index f403f198..39f59836 100644 --- a/forms/mainwindow.ui +++ b/forms/mainwindow.ui @@ -95,11 +95,11 @@
- + - Areas + Locations - + 0 @@ -116,10 +116,10 @@ 0
- + - + 0 @@ -3101,7 +3101,7 @@ Ctrl+T - + true @@ -3110,7 +3110,7 @@ :/icons/sort_alphabet.ico:/icons/sort_alphabet.ico - Sort by &Area + Sort by &Location diff --git a/forms/mapheaderform.ui b/forms/mapheaderform.ui index 06541e2c..8faba290 100644 --- a/forms/mapheaderform.ui +++ b/forms/mapheaderform.ui @@ -57,14 +57,14 @@ - + Requires Flash - + <html><head/><body><p>If checked, the player will need to use Flash to see fully on this map.</p></body></html> @@ -74,14 +74,14 @@ - + Weather - + <html><head/><body><p>The default weather on this map.</p></body></html> @@ -94,14 +94,14 @@ - + Type - + <html><head/><body><p>The map type is a general attribute, which is used for many different things. For example, underground type maps will have a special transition effect when the player enters/exits the map.</p></body></html> @@ -114,14 +114,14 @@ - + Battle Scene - + <html><head/><body><p>This field is used to help determine what graphics to use in the background of battles on this map.</p></body></html> @@ -134,14 +134,14 @@ - + Show Location Name - + <html><head/><body><p>If checked, a map name popup will appear when the player enters this map. The name that appears on this popup depends on the Location field.</p></body></html> @@ -151,14 +151,14 @@ - + Allow Running - + <html><head/><body><p>If checked, the player will be allowed to run on this map.</p></body></html> @@ -168,14 +168,14 @@ - + Allow Biking - + <html><head/><body><p>If checked, the player will be allowed to get on their bike on this map.</p></body></html> @@ -185,14 +185,14 @@ - + Allow Dig & Escape Rope - + <html><head/><body><p>If checked, the player will be allowed to use Dig or Escape Rope on this map.</p></body></html> @@ -202,20 +202,30 @@ - + Floor Number - + <html><head/><body><p>Floor number to be used for maps with elevators.</p></body></html> + + + + Location Name + + + + + +
diff --git a/forms/newlocationdialog.ui b/forms/newlocationdialog.ui new file mode 100644 index 00000000..285e2704 --- /dev/null +++ b/forms/newlocationdialog.ui @@ -0,0 +1,89 @@ + + + NewLocationDialog + + + + 0 + 0 + 252 + 124 + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Location ID + + + + + + + true + + + + + + + false + + + color: rgb(255, 0, 0) + + + + + + + + + + + + + Location Name + + + + + + + + + + Qt::Orientation::Horizontal + + + QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok + + + false + + + + + + + + diff --git a/forms/newnamedialog.ui b/forms/newmapgroupdialog.ui similarity index 94% rename from forms/newnamedialog.ui rename to forms/newmapgroupdialog.ui index fa6cb5ae..d475b665 100644 --- a/forms/newnamedialog.ui +++ b/forms/newmapgroupdialog.ui @@ -1,7 +1,7 @@ - NewNameDialog - + NewMapGroupDialog + 0 @@ -32,7 +32,7 @@ - Name + Map Group Name diff --git a/forms/regionmapeditor.ui b/forms/regionmapeditor.ui index 17cff63e..66787b7b 100644 --- a/forms/regionmapeditor.ui +++ b/forms/regionmapeditor.ui @@ -18,10 +18,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -46,14 +46,14 @@ - QComboBox::AdjustToContents + QComboBox::SizeAdjustPolicy::AdjustToContents - Qt::Horizontal + Qt::Orientation::Horizontal @@ -83,7 +83,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus 10 @@ -95,10 +95,10 @@ 30 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::NoTicks + QSlider::TickPosition::NoTicks 1 @@ -108,7 +108,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -123,7 +123,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal false @@ -160,7 +160,7 @@ 0 0 466 - 351 + 336 @@ -182,7 +182,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -207,17 +207,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag
- Qt::Vertical + Qt::Orientation::Vertical @@ -230,7 +230,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -243,7 +243,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -281,7 +281,7 @@ 0 0 466 - 351 + 336 @@ -315,17 +315,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag
- Qt::Horizontal + Qt::Orientation::Horizontal @@ -338,7 +338,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -351,7 +351,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -364,7 +364,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -402,7 +402,7 @@ 0 0 466 - 351 + 336 @@ -436,17 +436,17 @@ false - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - QGraphicsView::NoDrag + QGraphicsView::DragMode::NoDrag
- Qt::Horizontal + Qt::Orientation::Horizontal @@ -459,7 +459,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -472,7 +472,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -485,7 +485,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -529,19 +529,19 @@ - Qt::ScrollBarAlwaysOn + Qt::ScrollBarPolicy::ScrollBarAlwaysOn - Qt::ScrollBarAsNeeded + Qt::ScrollBarPolicy::ScrollBarAsNeeded - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored true - Qt::AlignHCenter|Qt::AlignTop + Qt::AlignmentFlag::AlignHCenter|Qt::AlignmentFlag::AlignTop @@ -552,7 +552,7 @@ 8 0 278 - 342 + 327 @@ -563,7 +563,7 @@ - QLayout::SetDefaultConstraint + QLayout::SizeConstraint::SetDefaultConstraint 0 @@ -583,7 +583,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -596,7 +596,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -618,20 +618,20 @@ - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - Qt::ScrollBarAlwaysOff + Qt::ScrollBarPolicy::ScrollBarAlwaysOff - QAbstractScrollArea::AdjustIgnored + QAbstractScrollArea::SizeAdjustPolicy::AdjustIgnored - Qt::Horizontal + Qt::Orientation::Horizontal @@ -648,10 +648,10 @@ - QFrame::Panel + QFrame::Shape::Panel - QFrame::Sunken + QFrame::Shadow::Sunken @@ -681,7 +681,7 @@ - + 15 @@ -719,10 +719,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -747,7 +747,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -760,10 +760,10 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint - + 0 @@ -771,10 +771,10 @@ - <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> + <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is displayed when the player enters it.</p></body></html> - - true + + false @@ -803,7 +803,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -816,10 +816,10 @@ - + - + @@ -862,10 +862,10 @@ - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised 1 @@ -874,15 +874,15 @@ - QLayout::SetNoConstraint + QLayout::SizeConstraint::SetNoConstraint - + - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -893,10 +893,10 @@ - + - + @@ -909,7 +909,7 @@ - + true @@ -922,25 +922,25 @@ <html><head/><body><p>The section of the region map which the map is grouped under. This also determines the name of the map that is display when the player enters it.</p></body></html> - - true + + false - - + + Dimensions - + - QFrame::StyledPanel + QFrame::Shape::StyledPanel - QFrame::Raised + QFrame::Shadow::Raised @@ -951,10 +951,10 @@ - + - + @@ -966,7 +966,7 @@
- + Location @@ -980,16 +980,6 @@ - - - - Map Name - - - - - -
@@ -1002,7 +992,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1037,7 +1027,7 @@ - Qt::StrongFocus + Qt::FocusPolicy::StrongFocus 10 @@ -1049,10 +1039,10 @@ 30 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::NoTicks + QSlider::TickPosition::NoTicks 1 @@ -1062,7 +1052,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1082,7 +1072,7 @@ 0 0 957 - 22 + 37 @@ -1180,9 +1170,14 @@ NoScrollComboBox - QWidget + QComboBox
noscrollcombobox.h
+ + NoScrollSpinBox + QSpinBox +
noscrollspinbox.h
+
diff --git a/include/core/regionmap.h b/include/core/regionmap.h index 822df79b..18362663 100644 --- a/include/core/regionmap.h +++ b/include/core/regionmap.h @@ -33,7 +33,6 @@ struct LayoutSquare struct MapSectionEntry { - QString name = ""; int x = 0; int y = 0; int width = 1; diff --git a/include/mainwindow.h b/include/mainwindow.h index 20789781..1ee612bc 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -190,6 +190,7 @@ private slots: void onNewMapCreated(Map *newMap, const QString &groupName); void onNewMapGroupCreated(const QString &groupName); void onNewMapSectionCreated(const QString &idName); + void onMapSectionDisplayNameChanged(const QString &idName, const QString &displayName); void onNewLayoutCreated(Layout *layout); void onNewTilesetCreated(Tileset *tileset); void onMapLoaded(Map *map); @@ -311,8 +312,8 @@ private slots: QPointer groupListProxyModel = nullptr; QPointer mapGroupModel = nullptr; - QPointer areaListProxyModel = nullptr; - QPointer mapAreaModel = nullptr; + QPointer locationListProxyModel = nullptr; + QPointer mapLocationModel = nullptr; QPointer layoutListProxyModel = nullptr; QPointer layoutTreeModel = nullptr; @@ -357,7 +358,7 @@ private slots: void openNewLayoutDialog(); void openDuplicateLayoutDialog(const QString &layoutId); void openNewMapGroupDialog(); - void openNewAreaDialog(); + void openNewLocationDialog(); void openSubWindow(QWidget * window); void scrollMapList(MapTree *list, const QString &itemName); void scrollMapListToCurrentMap(MapTree *list); @@ -446,7 +447,7 @@ struct MapViewTab { struct MapListTab { enum { - Groups = 0, Areas, Layouts + Groups = 0, Locations, Layouts }; }; diff --git a/include/project.h b/include/project.h index fefa1f34..c29a11b4 100644 --- a/include/project.h +++ b/include/project.h @@ -62,6 +62,7 @@ class Project : public QObject QStringList bgEventFacingDirections; QStringList trainerTypes; QStringList globalScriptLabels; + QStringList mapSectionIdNamesSaveOrder; QStringList mapSectionIdNames; QMap regionMapEntries; QMap> metatileLabelsMap; @@ -79,7 +80,6 @@ class Project : public QObject int pokemonMaxLevel; int maxEncounterRate; bool wildEncountersLoaded; - bool saveEmptyMapsec; void set_root(QString); @@ -156,8 +156,10 @@ class Project : public QObject bool readSpeciesIconPaths(); QMap speciesToIconPath; - void addNewMapsec(const QString &name); - void removeMapsec(const QString &name); + void addNewMapsec(const QString &idName); + void removeMapsec(const QString &idName); + QString getMapsecDisplayName(const QString &idName) const { return this->mapSectionDisplayNames.value(idName); } + void setMapsecDisplayName(const QString &idName, const QString &displayName); bool hasUnsavedChanges(); bool hasUnsavedDataChanges = false; @@ -253,8 +255,13 @@ class Project : public QObject bool calculateDefaultMapSize(); static int getMaxObjectEvents(); static QString getEmptyMapsecName(); + static QString getMapGroupPrefix(); + + static void numericalModeSort(QStringList &list); private: + QMap mapSectionDisplayNames; + void updateLayout(Layout *); void setNewLayoutBlockdata(Layout *layout); @@ -282,6 +289,7 @@ class Project : public QObject void tilesetCreated(Tileset *newTileset); void mapGroupAdded(const QString &groupName); void mapSectionAdded(const QString &idName); + void mapSectionDisplayNameChanged(const QString &idName, const QString &displayName); void mapSectionIdNamesChanged(const QStringList &idNames); void mapsExcluded(const QStringList &excludedMapNames); }; diff --git a/include/ui/mapheaderform.h b/include/ui/mapheaderform.h index 13e1b67d..8f9adda8 100644 --- a/include/ui/mapheaderform.h +++ b/include/ui/mapheaderform.h @@ -25,7 +25,7 @@ class MapHeaderForm : public QWidget explicit MapHeaderForm(QWidget *parent = nullptr); ~MapHeaderForm(); - void init(const Project * project); + void setProject(Project * project, bool allowProjectChanges = true); void clear(); void setHeader(MapHeader *header); @@ -34,6 +34,7 @@ class MapHeaderForm : public QWidget void setSong(const QString &song); void setLocation(const QString &location); + void setLocationName(const QString &locationName); void setRequiresFlash(bool requiresFlash); void setWeather(const QString &weather); void setType(const QString &type); @@ -46,6 +47,7 @@ class MapHeaderForm : public QWidget QString song() const; QString location() const; + QString locationName() const; bool requiresFlash() const; QString weather() const; QString type() const; @@ -56,14 +58,18 @@ class MapHeaderForm : public QWidget bool allowsEscaping() const; int floorNumber() const; - void setLocations(QStringList locations); - private: Ui::MapHeaderForm *ui; QPointer m_header = nullptr; + QPointer m_project = nullptr; + bool m_allowProjectChanges = true; + + void setLocations(const QStringList &locations); + void updateLocationName(); void onSongUpdated(const QString &song); void onLocationChanged(const QString &location); + void onLocationNameChanged(const QString &locationName); void onWeatherChanged(const QString &weather); void onTypeChanged(const QString &type); void onBattleSceneChanged(const QString &battleScene); diff --git a/include/ui/maplistmodels.h b/include/ui/maplistmodels.h index 53a79f4d..b973f438 100644 --- a/include/ui/maplistmodels.h +++ b/include/ui/maplistmodels.h @@ -68,7 +68,8 @@ class MapListModel : public QStandardItemModel { virtual QModelIndex indexOf(const QString &itemName) const; virtual void removeItemAt(const QModelIndex &index); - virtual QStandardItem *getItem(const QModelIndex &index) const; + virtual QStandardItem *itemAt(const QModelIndex &index) const; + virtual QStandardItem *itemAt(const QString &itemName) const; virtual QVariant data(const QModelIndex &index, int role) const override; @@ -124,12 +125,14 @@ class MapGroupModel : public MapListModel { -class MapAreaModel : public MapListModel { +class MapLocationModel : public MapListModel { Q_OBJECT public: - MapAreaModel(Project *project, QObject *parent = nullptr); - ~MapAreaModel() {} + MapLocationModel(Project *project, QObject *parent = nullptr); + ~MapLocationModel() {} + + QStandardItem *createMapFolderItem(const QString &folderName, QStandardItem *folder) override; protected: void removeItem(QStandardItem *item) override; diff --git a/include/ui/newlocationdialog.h b/include/ui/newlocationdialog.h new file mode 100644 index 00000000..33af5c9c --- /dev/null +++ b/include/ui/newlocationdialog.h @@ -0,0 +1,34 @@ +#ifndef NEWLOCATIONDIALOG_H +#define NEWLOCATIONDIALOG_H + +#include +#include +#include + +class Project; + +namespace Ui { +class NewLocationDialog; +} + +class NewLocationDialog : public QDialog +{ + Q_OBJECT + +public: + explicit NewLocationDialog(Project *project = nullptr, QWidget *parent = nullptr); + ~NewLocationDialog(); + + virtual void accept() override; + +private: + Ui::NewLocationDialog *ui; + QPointer project = nullptr; + const QString namePrefix; + + bool validateIdName(bool allowEmpty = false); + void onIdNameChanged(const QString &name); + void dialogButtonClicked(QAbstractButton *button); +}; + +#endif // NEWLOCATIONDIALOG_H diff --git a/include/ui/newnamedialog.h b/include/ui/newmapgroupdialog.h similarity index 51% rename from include/ui/newnamedialog.h rename to include/ui/newmapgroupdialog.h index 4abe02df..cbf8aa34 100644 --- a/include/ui/newnamedialog.h +++ b/include/ui/newmapgroupdialog.h @@ -1,5 +1,5 @@ -#ifndef NEWNAMEDIALOG_H -#define NEWNAMEDIALOG_H +#ifndef NEWMAPGROUPDIALOG_H +#define NEWMAPGROUPDIALOG_H /* This is a generic dialog for requesting a new unique name from the user. @@ -11,30 +11,26 @@ class Project; namespace Ui { -class NewNameDialog; +class NewMapGroupDialog; } -class NewNameDialog : public QDialog +class NewMapGroupDialog : public QDialog { Q_OBJECT public: - explicit NewNameDialog(const QString &label, const QString &prefix = "", Project *project = nullptr, QWidget *parent = nullptr); - ~NewNameDialog(); + explicit NewMapGroupDialog(Project *project = nullptr, QWidget *parent = nullptr); + ~NewMapGroupDialog(); virtual void accept() override; -signals: - void applied(const QString &newName); - private: - Ui::NewNameDialog *ui; + Ui::NewMapGroupDialog *ui; Project *project = nullptr; - const QString namePrefix; bool validateName(bool allowEmpty = false); void onNameChanged(const QString &name); void dialogButtonClicked(QAbstractButton *button); }; -#endif // NEWNAMEDIALOG_H +#endif // NEWMAPGROUPDIALOG_H diff --git a/include/ui/regionmapeditor.h b/include/ui/regionmapeditor.h index d5269548..9a838827 100644 --- a/include/ui/regionmapeditor.h +++ b/include/ui/regionmapeditor.h @@ -44,8 +44,6 @@ class RegionMapEditor : public QMainWindow bool reconfigure(); - void setLocations(const QStringList &locations); - QObjectList shortcutableObjects() const; public slots: @@ -118,6 +116,7 @@ public slots: void displayRegionMapEntryOptions(); void updateRegionMapEntryOptions(QString); void setRegionMap(RegionMap *map); + void setLocations(const QStringList &locations); void restoreWindowState(); void closeEvent(QCloseEvent* event); @@ -135,7 +134,7 @@ private slots: void on_tabWidget_Region_Map_currentChanged(int); void on_pushButton_RM_Options_delete_clicked(); void on_comboBox_RM_ConnectedMap_textActivated(const QString &); - void on_comboBox_RM_Entry_MapSection_textActivated(const QString &); + void on_comboBox_RM_Entry_MapSection_currentTextChanged(const QString &); void on_comboBox_regionSelector_textActivated(const QString &); void on_comboBox_layoutLayer_textActivated(const QString &); void on_spinBox_RM_Entry_x_valueChanged(int); @@ -150,7 +149,6 @@ private slots: void on_checkBox_tileVFlip_stateChanged(int); void on_verticalSlider_Zoom_Map_Image_valueChanged(int); void on_verticalSlider_Zoom_Image_Tiles_valueChanged(int); - void on_lineEdit_RM_MapName_textEdited(const QString &); void onHoveredRegionMapTileChanged(int x, int y); void onHoveredRegionMapTileCleared(); void mouseEvent_region_map(QGraphicsSceneMouseEvent *event, RegionMapPixmapItem *item); diff --git a/porymap.pro b/porymap.pro index f97cd56a..8987b5cc 100644 --- a/porymap.pro +++ b/porymap.pro @@ -104,7 +104,8 @@ SOURCES += src/core/advancemapparser.cpp \ src/ui/neweventtoolbutton.cpp \ src/ui/newlayoutdialog.cpp \ src/ui/newlayoutform.cpp \ - src/ui/newnamedialog.cpp \ + src/ui/newlocationdialog.cpp \ + src/ui/newmapgroupdialog.cpp \ src/ui/noscrollcombobox.cpp \ src/ui/noscrollspinbox.cpp \ src/ui/montabwidget.cpp \ @@ -212,7 +213,8 @@ HEADERS += include/core/advancemapparser.h \ include/ui/neweventtoolbutton.h \ include/ui/newlayoutdialog.h \ include/ui/newlayoutform.h \ - include/ui/newnamedialog.h \ + include/ui/newlocationdialog.h \ + include/ui/newmapgroupdialog.h \ include/ui/noscrollcombobox.h \ include/ui/noscrollspinbox.h \ include/ui/montabwidget.h \ @@ -259,8 +261,9 @@ FORMS += forms/mainwindow.ui \ forms/maplisttoolbar.ui \ forms/newlayoutdialog.ui \ forms/newlayoutform.ui \ - forms/newnamedialog.ui \ + forms/newlocationdialog.ui \ forms/newmapconnectiondialog.ui \ + forms/newmapgroupdialog.ui \ forms/prefabcreationdialog.ui \ forms/prefabframe.ui \ forms/tileseteditor.ui \ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 086f114e..fbd7e409 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -25,7 +25,8 @@ #include "filedialog.h" #include "newmapdialog.h" #include "newtilesetdialog.h" -#include "newnamedialog.h" +#include "newmapgroupdialog.h" +#include "newlocationdialog.h" #include "message.h" #include @@ -425,32 +426,32 @@ void MainWindow::initMapList() { // Connect tool bars to lists ui->mapListToolBar_Groups->setList(ui->mapList); - ui->mapListToolBar_Areas->setList(ui->areaList); + ui->mapListToolBar_Locations->setList(ui->locationList); ui->mapListToolBar_Layouts->setList(ui->layoutList); // Left-clicking on items in the map list opens the corresponding map/layout. - connect(ui->mapList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); - connect(ui->areaList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); - connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->mapList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->locationList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); + connect(ui->layoutList, &QAbstractItemView::activated, this, &MainWindow::openMapListItem); // Right-clicking on items in the map list brings up a context menu. - connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); - connect(ui->areaList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); - connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->mapList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->locationList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); + connect(ui->layoutList, &QTreeView::customContextMenuRequested, this, &MainWindow::onOpenMapListContextMenu); // Only the groups list allows reorganizing folder contents, editing folder names, etc. - ui->mapListToolBar_Areas->setEditsAllowedButtonVisible(false); + ui->mapListToolBar_Locations->setEditsAllowedButtonVisible(false); ui->mapListToolBar_Layouts->setEditsAllowedButtonVisible(false); // When map list search filter is cleared we want the current map/layout in the editor to be visible in the list. - connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); - connect(ui->mapListToolBar_Areas, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); - connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); + connect(ui->mapListToolBar_Groups, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Locations, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentMap); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::filterCleared, this, &MainWindow::scrollMapListToCurrentLayout); // Connect the "add folder" button in each of the map lists - connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewMapGroupDialog); - connect(ui->mapListToolBar_Areas, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewAreaDialog); - connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); + connect(ui->mapListToolBar_Groups, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewMapGroupDialog); + connect(ui->mapListToolBar_Locations, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLocationDialog); + connect(ui->mapListToolBar_Layouts, &MapListToolBar::addFolderClicked, this, &MainWindow::openNewLayoutDialog); connect(ui->mapListContainer, &QTabWidget::currentChanged, this, &MainWindow::saveMapListTab); } @@ -628,7 +629,7 @@ bool MainWindow::openProject(QString dir, bool initial) { connect(project, &Project::tilesetCreated, this, &MainWindow::onNewTilesetCreated); connect(project, &Project::mapGroupAdded, this, &MainWindow::onNewMapGroupCreated); connect(project, &Project::mapSectionAdded, this, &MainWindow::onNewMapSectionCreated); - connect(project, &Project::mapSectionIdNamesChanged, this, &MainWindow::setLocationComboBoxes); + connect(project, &Project::mapSectionDisplayNameChanged, this, &MainWindow::onMapSectionDisplayNameChanged); connect(project, &Project::mapsExcluded, this, &MainWindow::showMapsExcludedAlert); this->editor->setProject(project); @@ -1058,7 +1059,7 @@ void MainWindow::on_comboBox_LayoutSelector_currentTextChanged(const QString &te bool MainWindow::setProjectUI() { Project *project = editor->project; - this->mapHeaderForm->init(project); + this->mapHeaderForm->setProject(project); // Set up project comboboxes const QSignalBlocker b_PrimaryTileset(ui->comboBox_PrimaryTileset); @@ -1108,10 +1109,10 @@ bool MainWindow::setProjectUI() { this->ui->mapList->setItemDelegateForColumn(0, new GroupNameDelegate(this->editor->project, this)); connect(this->mapGroupModel, &MapGroupModel::dragMoveCompleted, this->ui->mapList, &MapTree::removeSelected); - this->mapAreaModel = new MapAreaModel(editor->project); - this->areaListProxyModel = new FilterChildrenProxyModel(); - areaListProxyModel->setSourceModel(this->mapAreaModel); - ui->areaList->setModel(areaListProxyModel); + this->mapLocationModel = new MapLocationModel(editor->project); + this->locationListProxyModel = new FilterChildrenProxyModel(); + locationListProxyModel->setSourceModel(this->mapLocationModel); + ui->locationList->setModel(locationListProxyModel); this->layoutTreeModel = new LayoutTreeModel(editor->project); this->layoutListProxyModel = new FilterChildrenProxyModel(); @@ -1143,8 +1144,8 @@ void MainWindow::clearProjectUI() { // Clear map models delete this->mapGroupModel; delete this->groupListProxyModel; - delete this->mapAreaModel; - delete this->areaListProxyModel; + delete this->mapLocationModel; + delete this->locationListProxyModel; delete this->layoutTreeModel; delete this->layoutListProxyModel; resetMapListFilters(); @@ -1197,14 +1198,14 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { QAction* addToFolderAction = nullptr; QAction* deleteFolderAction = nullptr; QAction* openItemAction = nullptr; - QAction* copyDisplayNameAction = nullptr; + QAction* copyListNameAction = nullptr; QAction* copyToolTipAction = nullptr; if (itemType == "map_name") { // Right-clicking on a map. openItemAction = menu.addAction("Open Map"); menu.addSeparator(); - copyDisplayNameAction = menu.addAction("Copy Map Name"); + copyListNameAction = menu.addAction("Copy Map Name"); copyToolTipAction = menu.addAction("Copy Map ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Map"), &QAction::triggered, [this, itemName] { @@ -1219,18 +1220,19 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { deleteFolderAction = menu.addAction("Delete Map Group"); } else if (itemType == "map_section") { // Right-clicking on a MAPSEC folder - addToFolderAction = menu.addAction("Add New Map to Area"); + addToFolderAction = menu.addAction("Add New Map to Location"); menu.addSeparator(); - copyDisplayNameAction = menu.addAction("Copy Area Name"); + copyListNameAction = menu.addAction("Copy Location ID Name"); + copyToolTipAction = menu.addAction("Copy Location In-Game Name"); menu.addSeparator(); - deleteFolderAction = menu.addAction("Delete Area"); + deleteFolderAction = menu.addAction("Delete Location"); if (itemName == this->editor->project->getEmptyMapsecName()) deleteFolderAction->setEnabled(false); // Disallow deleting the default name } else if (itemType == "map_layout") { // Right-clicking on a map layout openItemAction = menu.addAction("Open Layout"); menu.addSeparator(); - copyDisplayNameAction = menu.addAction("Copy Layout Name"); + copyListNameAction = menu.addAction("Copy Layout Name"); copyToolTipAction = menu.addAction("Copy Layout ID"); menu.addSeparator(); connect(menu.addAction("Duplicate Layout"), &QAction::triggered, [this, itemName] { @@ -1263,8 +1265,8 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { openMapListItem(index); }); } - if (copyDisplayNameAction) { - connect(copyDisplayNameAction, &QAction::triggered, [this, selectedItem] { + if (copyListNameAction) { + connect(copyListNameAction, &QAction::triggered, [this, selectedItem] { setClipboardData(selectedItem->text()); }); } @@ -1278,18 +1280,6 @@ void MainWindow::onOpenMapListContextMenu(const QPoint &point) { menu.exec(QCursor::pos()); } -void MainWindow::openNewMapGroupDialog() { - auto dialog = new NewNameDialog("New Group Name", "", this->editor->project, this); - connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapGroup); - dialog->open(); -} - -void MainWindow::openNewAreaDialog() { - auto dialog = new NewNameDialog("New Area Name", projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix), this->editor->project, this); - connect(dialog, &NewNameDialog::applied, this->editor->project, &Project::addNewMapsec); - dialog->open(); -} - void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { logInfo(QString("Created a new map named %1.").arg(newMap->name())); @@ -1301,7 +1291,7 @@ void MainWindow::onNewMapCreated(Map *newMap, const QString &groupName) { // Add new map to the map lists this->mapGroupModel->insertMapItem(newMap->name(), groupName); - this->mapAreaModel->insertMapItem(newMap->name(), newMap->header()->location()); + this->mapLocationModel->insertMapItem(newMap->name(), newMap->header()->location()); this->layoutTreeModel->insertMapItem(newMap->name(), newMap->layout()->id); // Refresh any combo box that displays map names and persists between maps @@ -1344,14 +1334,14 @@ void MainWindow::onNewMapGroupCreated(const QString &groupName) { } void MainWindow::onNewMapSectionCreated(const QString &idName) { - // Add new map section to the Areas map list view - this->mapAreaModel->insertMapFolderItem(idName); + // Add new map section to the Locations map list view + this->mapLocationModel->insertMapFolderItem(idName); } -void MainWindow::setLocationComboBoxes(const QStringList &locations) { - this->mapHeaderForm->setLocations(locations); - if (this->regionMapEditor) - this->regionMapEditor->setLocations(locations); +void MainWindow::onMapSectionDisplayNameChanged(const QString &idName, const QString &displayName) { + // Update the tool tip in the map list that shows the MAPSEC's in-game name. + QStandardItem *item = this->mapLocationModel->itemAt(idName); + if (item) item->setToolTip(displayName); } void MainWindow::onNewTilesetCreated(Tileset *tileset) { @@ -1371,6 +1361,16 @@ void MainWindow::onNewTilesetCreated(Tileset *tileset) { } } +void MainWindow::openNewMapGroupDialog() { + auto dialog = new NewMapGroupDialog(this->editor->project, this); + dialog->open(); +} + +void MainWindow::openNewLocationDialog() { + auto dialog = new NewLocationDialog(this->editor->project, this); + dialog->open(); +} + void MainWindow::openNewMapDialog() { auto dialog = new NewMapDialog(this->editor->project, this); dialog->open(); @@ -1505,7 +1505,7 @@ void MainWindow::updateMapList() { activeItemName = this->editor->map->name(); } else { ui->mapList->clearSelection(); - ui->areaList->clearSelection(); + ui->locationList->clearSelection(); if (this->editor->layout) { activeItemName = this->editor->layout->id; @@ -1515,11 +1515,11 @@ void MainWindow::updateMapList() { } this->mapGroupModel->setActiveItem(activeItemName); - this->mapAreaModel->setActiveItem(activeItemName); + this->mapLocationModel->setActiveItem(activeItemName); this->layoutTreeModel->setActiveItem(activeItemName); this->groupListProxyModel->layoutChanged(); - this->areaListProxyModel->layoutChanged(); + this->locationListProxyModel->layoutChanged(); this->layoutListProxyModel->layoutChanged(); } @@ -1813,6 +1813,7 @@ void MainWindow::on_mainTabBar_tabBarClicked(int index) clickToolButtonFromEditAction(editor->objectEditAction); } else if (index == MainTab::Connections) { editor->setEditingConnections(); + ui->graphicsView_Connections->setFocus(); // Avoid opening tab with focus on something editable } else if (index == MainTab::WildPokemon) { editor->setEditingEncounters(); } @@ -2706,9 +2707,9 @@ void MainWindow::initTilesetEditor() { MapListToolBar* MainWindow::getCurrentMapListToolBar() { switch (ui->mapListContainer->currentIndex()) { - case MapListTab::Groups: return ui->mapListToolBar_Groups; - case MapListTab::Areas: return ui->mapListToolBar_Areas; - case MapListTab::Layouts: return ui->mapListToolBar_Layouts; + case MapListTab::Groups: return ui->mapListToolBar_Groups; + case MapListTab::Locations: return ui->mapListToolBar_Locations; + case MapListTab::Layouts: return ui->mapListToolBar_Layouts; default: return nullptr; } } @@ -2724,7 +2725,7 @@ MapTree* MainWindow::getCurrentMapList() { // When the search filter is cleared the map lists will (if possible) display the currently-selected map/layout. void MainWindow::resetMapListFilters() { ui->mapListToolBar_Groups->clearFilter(); - ui->mapListToolBar_Areas->clearFilter(); + ui->mapListToolBar_Locations->clearFilter(); ui->mapListToolBar_Layouts->clearFilter(); } diff --git a/src/project.cpp b/src/project.cpp index 83fb39c8..9fff8710 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -727,16 +727,16 @@ void Project::saveRegionMapSections() { const QString emptyMapsecName = getEmptyMapsecName(); OrderedJson::array mapSectionArray; - for (const auto &idName : this->mapSectionIdNames) { - if (!this->saveEmptyMapsec && idName == emptyMapsecName) - continue; - + for (const auto &idName : this->mapSectionIdNamesSaveOrder) { OrderedJson::object mapSectionObj; mapSectionObj["id"] = idName; + if (this->mapSectionDisplayNames.contains(idName)) { + mapSectionObj["name"] = this->mapSectionDisplayNames.value(idName); + } + if (this->regionMapEntries.contains(idName)) { MapSectionEntry entry = this->regionMapEntries.value(idName); - mapSectionObj["name"] = entry.name; mapSectionObj["x"] = entry.x; mapSectionObj["y"] = entry.y; mapSectionObj["width"] = entry.width; @@ -2129,8 +2129,8 @@ bool Project::readTilesetLabels() { } } - this->primaryTilesetLabels.sort(); - this->secondaryTilesetLabels.sort(); + numericalModeSort(this->primaryTilesetLabels); + numericalModeSort(this->secondaryTilesetLabels); bool success = true; if (this->secondaryTilesetLabels.isEmpty()) { @@ -2332,8 +2332,9 @@ bool Project::readFieldmapMasks() { bool Project::readRegionMapSections() { this->mapSectionIdNames.clear(); + this->mapSectionIdNamesSaveOrder.clear(); + this->mapSectionDisplayNames.clear(); this->regionMapEntries.clear(); - this->saveEmptyMapsec = false; const QString defaultName = getEmptyMapsecName(); const QString requiredPrefix = projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix); @@ -2371,17 +2372,15 @@ bool Project::readRegionMapSections() { } this->mapSectionIdNames.append(idName); - if (idName == defaultName) { - // The default map section (MAPSEC_NONE) isn't normally present in the region map sections data file. - // We append this name to mapSectionIdNames ourselves if it isn't present. - // We need to record whether we found it in the data file, so that we can preserve the data when we save the file later. - this->saveEmptyMapsec = true; - } + this->mapSectionIdNamesSaveOrder.append(idName); + + if (mapSectionObj.contains("name")) + this->mapSectionDisplayNames.insert(idName, ParseUtil::jsonToQString(mapSectionObj["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. bool hasRegionMapData = true; - static const QSet regionMapFieldNames = { "name", "x", "y", "width", "height" }; + static const QSet regionMapFieldNames = { "x", "y", "width", "height" }; for (auto fieldName : regionMapFieldNames) { if (!mapSectionObj.contains(fieldName)) { hasRegionMapData = false; @@ -2392,7 +2391,6 @@ bool Project::readRegionMapSections() { continue; MapSectionEntry entry; - entry.name = ParseUtil::jsonToQString(mapSectionObj["name"]); entry.x = ParseUtil::jsonToInt(mapSectionObj["x"]); entry.y = ParseUtil::jsonToInt(mapSectionObj["y"]); entry.width = ParseUtil::jsonToInt(mapSectionObj["width"]); @@ -2405,6 +2403,7 @@ bool Project::readRegionMapSections() { if (!this->mapSectionIdNames.contains(defaultName)) { this->mapSectionIdNames.append(defaultName); } + numericalModeSort(this->mapSectionIdNames); return true; } @@ -2413,29 +2412,47 @@ QString Project::getEmptyMapsecName() { return projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix) + projectConfig.getIdentifier(ProjectIdentifier::define_map_section_empty); } +QString Project::getMapGroupPrefix() { + // We could expose this to users, but it's never enforced so it probably won't affect anyone. + return QStringLiteral("gMapGroup_"); +} + // This function assumes a valid and unique name -void Project::addNewMapsec(const QString &name) { - if (this->mapSectionIdNames.last() == getEmptyMapsecName()) { +void Project::addNewMapsec(const QString &idName) { + if (this->mapSectionIdNamesSaveOrder.last() == getEmptyMapsecName()) { // If the default map section name (MAPSEC_NONE) is last in the list we'll keep it last in the list. - this->mapSectionIdNames.insert(this->mapSectionIdNames.length() - 1, name); + this->mapSectionIdNamesSaveOrder.insert(this->mapSectionIdNames.length() - 1, idName); } else { - this->mapSectionIdNames.append(name); + this->mapSectionIdNamesSaveOrder.append(idName); } + + this->mapSectionIdNames.append(idName); + numericalModeSort(this->mapSectionIdNames); + this->hasUnsavedDataChanges = true; - emit mapSectionAdded(name); + emit mapSectionAdded(idName); emit mapSectionIdNamesChanged(this->mapSectionIdNames); } -void Project::removeMapsec(const QString &name) { - if (!this->mapSectionIdNames.contains(name) || name == getEmptyMapsecName()) +void Project::removeMapsec(const QString &idName) { + if (!this->mapSectionIdNames.contains(idName) || idName == getEmptyMapsecName()) return; - this->mapSectionIdNames.removeOne(name); + this->mapSectionIdNames.removeOne(idName); + this->mapSectionIdNamesSaveOrder.removeOne(idName); this->hasUnsavedDataChanges = true; emit mapSectionIdNamesChanged(this->mapSectionIdNames); } +void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { + if (this->mapSectionDisplayNames[idName] == displayName) + return; + this->mapSectionDisplayNames[idName] = displayName; + this->hasUnsavedDataChanges = true; + emit mapSectionDisplayNameChanged(idName, displayName); +} + // Read the constants to preserve any "unused" heal locations when writing the file later bool Project::readHealLocationConstants() { this->healLocationNameToValue.clear(); @@ -2704,7 +2721,7 @@ bool Project::readSongNames() { // Song names don't have a very useful order (esp. if we include SE_* values), so sort them alphabetically. // The default song should be the first in the list, not the first alphabetically, so save that before sorting. this->defaultSong = this->songNames.value(0, "0"); - this->songNames.sort(); + numericalModeSort(this->songNames); return true; } @@ -3139,3 +3156,14 @@ bool Project::hasUnsavedChanges() { } return false; } + +// TODO: This belongs in a more general utility file, once we have one. +// Sometimes we want to sort names alphabetically to make them easier to find in large combo box lists. +// QStringList::sort (as of writing) can only sort numbers in lexical order, which has an undesirable +// effect (e.g. MAPSEC_ROUTE_10 comes after MAPSEC_ROUTE_1, rather than MAPSEC_ROUTE_9). +// We can use QCollator to sort these lists with better handling for numbers. +void Project::numericalModeSort(QStringList &list) { + QCollator collator; + collator.setNumericMode(true); + std::sort(list.begin(), list.end(), collator); +} diff --git a/src/ui/mapheaderform.cpp b/src/ui/mapheaderform.cpp index 4bea0bef..09cb22c7 100644 --- a/src/ui/mapheaderform.cpp +++ b/src/ui/mapheaderform.cpp @@ -12,18 +12,24 @@ MapHeaderForm::MapHeaderForm(QWidget *parent) ui->spinBox_FloorNumber->setMinimum(INT_MIN); ui->spinBox_FloorNumber->setMaximum(INT_MAX); - // When the UI is updated, sync those changes to the tracked MapHeader (if there is one) + // The layout for this UI keeps fields at their size hint, which is a little short for the line edit. + ui->lineEdit_LocationName->setMinimumWidth(ui->comboBox_Location->sizeHint().width()); + connect(ui->comboBox_Song, &QComboBox::currentTextChanged, this, &MapHeaderForm::onSongUpdated); connect(ui->comboBox_Location, &QComboBox::currentTextChanged, this, &MapHeaderForm::onLocationChanged); 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); + 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); + connect(ui->spinBox_FloorNumber, QOverload::of(&QSpinBox::valueChanged), this, &MapHeaderForm::onFloorNumberChanged); + + connect(ui->lineEdit_LocationName, &QLineEdit::textChanged, this, &MapHeaderForm::onLocationNameChanged); } MapHeaderForm::~MapHeaderForm() @@ -31,35 +37,39 @@ MapHeaderForm::~MapHeaderForm() delete ui; } -void MapHeaderForm::init(const Project * project) { +void MapHeaderForm::setProject(Project * project, bool allowChanges) { clear(); - if (!project) + if (m_project) { + m_project->disconnect(this); + } + m_project = project; + m_allowProjectChanges = allowChanges; + + if (!m_project) return; // Populate combo boxes const QSignalBlocker b_Song(ui->comboBox_Song); ui->comboBox_Song->clear(); - ui->comboBox_Song->addItems(project->songNames); + ui->comboBox_Song->addItems(m_project->songNames); const QSignalBlocker b_Weather(ui->comboBox_Weather); ui->comboBox_Weather->clear(); - ui->comboBox_Weather->addItems(project->weatherNames); + ui->comboBox_Weather->addItems(m_project->weatherNames); const QSignalBlocker b_Type(ui->comboBox_Type); ui->comboBox_Type->clear(); - ui->comboBox_Type->addItems(project->mapTypes); + ui->comboBox_Type->addItems(m_project->mapTypes); const QSignalBlocker b_BattleScene(ui->comboBox_BattleScene); ui->comboBox_BattleScene->clear(); - ui->comboBox_BattleScene->addItems(project->mapBattleScenes); + ui->comboBox_BattleScene->addItems(m_project->mapBattleScenes); - QStringList locations = project->mapSectionIdNames; - locations.sort(); const QSignalBlocker b_Locations(ui->comboBox_Location); ui->comboBox_Location->clear(); - ui->comboBox_Location->addItems(locations); + ui->comboBox_Location->addItems(m_project->mapSectionIdNames); // Hide config-specific settings @@ -74,12 +84,13 @@ void MapHeaderForm::init(const Project * project) { bool floorNumEnabled = projectConfig.floorNumberEnabled; ui->spinBox_FloorNumber->setVisible(floorNumEnabled); ui->label_FloorNumber->setVisible(floorNumEnabled); -} -// Unlike other combo boxes in the map header form, locations can be added or removed externally. -void MapHeaderForm::setLocations(QStringList locations) { - locations.sort(); + // If the project changes any of the displayed data, update it accordingly. + connect(m_project, &Project::mapSectionIdNamesChanged, this, &MapHeaderForm::setLocations); + connect(m_project, &Project::mapSectionDisplayNameChanged, this, &MapHeaderForm::updateLocationName); +} +void MapHeaderForm::setLocations(const QStringList &locations) { const QSignalBlocker b(ui->comboBox_Location); const QString before = ui->comboBox_Location->currentText(); ui->comboBox_Location->clear(); @@ -136,6 +147,7 @@ void MapHeaderForm::setHeaderData(const MapHeader &header) { setAllowsBiking(header.allowsBiking()); setAllowsEscaping(header.allowsEscaping()); setFloorNumber(header.floorNumber()); + updateLocationName(); } MapHeader MapHeaderForm::headerData() const { @@ -158,9 +170,14 @@ MapHeader MapHeaderForm::headerData() const { return header; } +void MapHeaderForm::updateLocationName() { + setLocationName(m_project ? m_project->getMapsecDisplayName(location()) : QString()); +} + // 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::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); } @@ -174,6 +191,7 @@ void MapHeaderForm::setFloorNumber(int floorNumber) { ui->spinBox_F // Read data from UI QString MapHeaderForm::song() const { return ui->comboBox_Song->currentText(); } QString MapHeaderForm::location() const { return ui->comboBox_Location->currentText(); } +QString MapHeaderForm::locationName() const { return ui->lineEdit_LocationName->text(); } bool MapHeaderForm::requiresFlash() const { return ui->checkBox_RequiresFlash->isChecked(); } QString MapHeaderForm::weather() const { return ui->comboBox_Weather->currentText(); } QString MapHeaderForm::type() const { return ui->comboBox_Type->currentText(); } @@ -186,7 +204,6 @@ int MapHeaderForm::floorNumber() const { return ui->spinBox_FloorNumber-> // Send changes in UI to tracked MapHeader (if there is one) void MapHeaderForm::onSongUpdated(const QString &song) { if (m_header) m_header->setSong(song); } -void MapHeaderForm::onLocationChanged(const QString &location) { if (m_header) m_header->setLocation(location); } 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); } @@ -196,3 +213,14 @@ void MapHeaderForm::onAllowRunningChanged(int selected) { if (m_hea 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::onFloorNumberChanged(int offset) { if (m_header) m_header->setFloorNumber(offset); } +void MapHeaderForm::onLocationChanged(const QString &location) { + if (m_header) m_header->setLocation(location); + updateLocationName(); +} +void MapHeaderForm::onLocationNameChanged(const QString &locationName) { + if (m_project && m_allowProjectChanges) { + // The location name is actually part of the project, not the map header. + // If the field is changed in the UI we can push these changes to the project. + m_project->setMapsecDisplayName(location(), locationName); + } +} diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index a46dfc58..e74d0a93 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -56,7 +56,7 @@ MapListModel::MapListModel(Project *project, QObject *parent) : QStandardItemMod this->emptyMapFolderIcon.addFile(QStringLiteral(":/icons/folder.ico"), QSize(), QIcon::Normal, QIcon::On); } -QStandardItem *MapListModel::getItem(const QModelIndex &index) const { +QStandardItem *MapListModel::itemAt(const QModelIndex &index) const { if (index.isValid()) { QStandardItem *item = static_cast(index.internalPointer()); if (item) @@ -65,6 +65,13 @@ QStandardItem *MapListModel::getItem(const QModelIndex &index) const { return this->root; } +QStandardItem *MapListModel::itemAt(const QString &itemName) const { + QModelIndex index = this->indexOf(itemName); + if (!index.isValid()) + return nullptr; + return this->itemAt(index)->child(index.row(), index.column()); +} + QModelIndex MapListModel::indexOf(const QString &itemName) const { if (this->mapItems.contains(itemName)) return this->mapItems.value(itemName)->index(); @@ -76,7 +83,7 @@ QModelIndex MapListModel::indexOf(const QString &itemName) const { } void MapListModel::removeItemAt(const QModelIndex &index) { - QStandardItem *item = this->getItem(index)->child(index.row(), index.column()); + QStandardItem *item = this->itemAt(index)->child(index.row(), index.column()); if (!item) return; @@ -153,7 +160,7 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); - const QStandardItem *item = this->getItem(index)->child(row, col); + const QStandardItem *item = this->itemAt(index)->child(row, col); const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); @@ -179,7 +186,7 @@ QVariant MapListModel::data(const QModelIndex &index, int role) const { QWidget *GroupNameDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const { QLineEdit *editor = new QLineEdit(parent); - editor->setPlaceholderText("gMapGroup_"); + editor->setPlaceholderText(Project::getMapGroupPrefix()); editor->setValidator(new IdentifierValidator(parent)); editor->setFrame(false); return editor; @@ -388,13 +395,13 @@ QVariant MapGroupModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); - const QStandardItem *item = this->getItem(index)->child(row, col); + const QStandardItem *item = this->itemAt(index)->child(row, col); const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); if (role == Qt::DisplayRole) { if (type == "map_name") { - return QString("[%1.%2] ").arg(this->getItem(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name; + return QString("[%1.%2] ").arg(this->itemAt(index)->row()).arg(row, 2, 10, QLatin1Char('0')) + name; } else if (type == this->folderTypeName) { return name; @@ -417,7 +424,7 @@ bool MapGroupModel::setData(const QModelIndex &index, const QVariant &value, int -MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(project, parent) { +MapLocationModel::MapLocationModel(Project *project, QObject *parent) : MapListModel(project, parent) { this->folderTypeName = "map_section"; for (const auto &idName : this->project->mapSectionIdNames) { @@ -431,11 +438,17 @@ MapAreaModel::MapAreaModel(Project *project, QObject *parent) : MapListModel(pro sort(0, Qt::AscendingOrder); } -void MapAreaModel::removeItem(QStandardItem *item) { +void MapLocationModel::removeItem(QStandardItem *item) { this->project->removeMapsec(item->data(MapListUserRoles::NameRole).toString()); this->removeRow(item->row()); } +QStandardItem *MapLocationModel::createMapFolderItem(const QString &folderName, QStandardItem *folder) { + folder = MapListModel::createMapFolderItem(folderName, folder); + folder->setToolTip(this->project->getMapsecDisplayName(folderName)); + return folder; +} + LayoutTreeModel::LayoutTreeModel(Project *project, QObject *parent) : MapListModel(project, parent) { @@ -477,7 +490,7 @@ QVariant LayoutTreeModel::data(const QModelIndex &index, int role) const { int row = index.row(); int col = index.column(); - const QStandardItem *item = this->getItem(index)->child(row, col); + const QStandardItem *item = this->itemAt(index)->child(row, col); const QString type = item->data(MapListUserRoles::TypeRole).toString(); const QString name = item->data(MapListUserRoles::NameRole).toString(); diff --git a/src/ui/newlocationdialog.cpp b/src/ui/newlocationdialog.cpp new file mode 100644 index 00000000..d0586d99 --- /dev/null +++ b/src/ui/newlocationdialog.cpp @@ -0,0 +1,79 @@ +#include "newlocationdialog.h" +#include "ui_newlocationdialog.h" +#include "project.h" +#include "validator.h" + +const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; + +NewLocationDialog::NewLocationDialog(Project* project, QWidget *parent) : + QDialog(parent), + ui(new Ui::NewLocationDialog), + namePrefix(projectConfig.getIdentifier(ProjectIdentifier::define_map_section_prefix)) +{ + setAttribute(Qt::WA_DeleteOnClose); + ui->setupUi(this); + this->project = project; + + ui->lineEdit_IdName->setValidator(new IdentifierValidator(namePrefix, this)); + ui->lineEdit_IdName->setText(namePrefix); + + connect(ui->lineEdit_IdName, &QLineEdit::textChanged, this, &NewLocationDialog::onIdNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLocationDialog::dialogButtonClicked); + + adjustSize(); +} + +NewLocationDialog::~NewLocationDialog() +{ + delete ui; +} + +void NewLocationDialog::onIdNameChanged(const QString &idName) { + validateIdName(true); + + // Extract a presumed display name from the ID name + QString displayName = idName; + if (displayName.startsWith(namePrefix)) + displayName.remove(0, namePrefix.length()); + displayName.replace("_", " "); + ui->lineEdit_DisplayName->setText(displayName); +} + +bool NewLocationDialog::validateIdName(bool allowEmpty) { + const QString name = ui->lineEdit_IdName->text(); + + QString errorText; + if (name.isEmpty() || name == namePrefix) { + if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_IdName->text()); + } else if (!this->project->isIdentifierUnique(name)) { + errorText = QString("%1 '%2' is not unique.").arg(ui->label_IdName->text()).arg(name); + } + + bool isValid = errorText.isEmpty(); + ui->label_IdNameError->setText(errorText); + ui->label_IdNameError->setVisible(!isValid); + ui->lineEdit_IdName->setStyleSheet(!isValid ? lineEdit_ErrorStylesheet : ""); + return isValid; +} + +void NewLocationDialog::dialogButtonClicked(QAbstractButton *button) { + auto role = ui->buttonBox->buttonRole(button); + if (role == QDialogButtonBox::RejectRole){ + reject(); + } else if (role == QDialogButtonBox::AcceptRole) { + accept(); + } +} + +void NewLocationDialog::accept() { + if (!validateIdName()) + return; + + const QString idName = ui->lineEdit_IdName->text(); + const QString displayName = ui->lineEdit_DisplayName->text(); + + this->project->addNewMapsec(idName); + this->project->setMapsecDisplayName(idName, displayName); + + QDialog::accept(); +} diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 2d1fa46f..fda3d59a 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -53,7 +53,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare // Create a collapsible section that has all the map header data. this->headerForm = new MapHeaderForm(); - this->headerForm->init(project); + this->headerForm->setProject(project, false); auto sectionLayout = new QVBoxLayout(); sectionLayout->addWidget(this->headerForm); @@ -77,13 +77,13 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL case MapListTab::Groups: ui->comboBox_Group->setTextItem(mapListItem); break; - case MapListTab::Areas: + case MapListTab::Locations: this->headerForm->setLocation(mapListItem); break; case MapListTab::Layouts: // We specifically lock the layout ID because otherwise the setting would be overwritten when // the user changes the map name (which will normally automatically update the layout ID to match). - // For the Group/Area settings above we don't care if the user changes them afterwards. + // For the Group/Location settings above we don't care if the user changes them afterwards. ui->comboBox_LayoutID->setTextItem(mapListItem); ui->comboBox_LayoutID->setDisabled(true); break; @@ -252,5 +252,9 @@ void NewMapDialog::accept() { return; } ui->label_GenericError->setVisible(false); + + // If the location name field was changed, update the project for that too. + this->project->setMapsecDisplayName(this->headerForm->location(), this->headerForm->locationName()); + QDialog::accept(); } diff --git a/src/ui/newnamedialog.cpp b/src/ui/newmapgroupdialog.cpp similarity index 57% rename from src/ui/newnamedialog.cpp rename to src/ui/newmapgroupdialog.cpp index 842bf4b7..13461b68 100644 --- a/src/ui/newnamedialog.cpp +++ b/src/ui/newmapgroupdialog.cpp @@ -1,48 +1,43 @@ -#include "newnamedialog.h" -#include "ui_newnamedialog.h" +#include "newmapgroupdialog.h" +#include "ui_newmapgroupdialog.h" #include "project.h" -#include "imageexport.h" #include "validator.h" const QString lineEdit_ErrorStylesheet = "QLineEdit { background-color: rgba(255, 0, 0, 25%) }"; -NewNameDialog::NewNameDialog(const QString &label, const QString &prefix, Project* project, QWidget *parent) : +NewMapGroupDialog::NewMapGroupDialog(Project* project, QWidget *parent) : QDialog(parent), - ui(new Ui::NewNameDialog), - namePrefix(prefix) + ui(new Ui::NewMapGroupDialog) { setAttribute(Qt::WA_DeleteOnClose); ui->setupUi(this); this->project = project; - if (!label.isEmpty()) - ui->label_Name->setText(label); + ui->lineEdit_Name->setValidator(new IdentifierValidator(this)); + ui->lineEdit_Name->setText(Project::getMapGroupPrefix()); - ui->lineEdit_Name->setValidator(new IdentifierValidator(namePrefix, this)); - ui->lineEdit_Name->setText(namePrefix); - - connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewNameDialog::onNameChanged); - connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewNameDialog::dialogButtonClicked); + connect(ui->lineEdit_Name, &QLineEdit::textChanged, this, &NewMapGroupDialog::onNameChanged); + connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapGroupDialog::dialogButtonClicked); adjustSize(); } -NewNameDialog::~NewNameDialog() +NewMapGroupDialog::~NewMapGroupDialog() { delete ui; } -void NewNameDialog::onNameChanged(const QString &) { +void NewMapGroupDialog::onNameChanged(const QString &) { validateName(true); } -bool NewNameDialog::validateName(bool allowEmpty) { +bool NewMapGroupDialog::validateName(bool allowEmpty) { const QString name = ui->lineEdit_Name->text(); QString errorText; - if (name.isEmpty() || name == namePrefix) { + if (name.isEmpty()) { if (!allowEmpty) errorText = QString("%1 cannot be empty.").arg(ui->label_Name->text()); - } else if (this->project && !this->project->isIdentifierUnique(name)) { + } else if (!this->project->isIdentifierUnique(name)) { errorText = QString("%1 '%2' is not unique.").arg(ui->label_Name->text()).arg(name); } @@ -53,7 +48,7 @@ bool NewNameDialog::validateName(bool allowEmpty) { return isValid; } -void NewNameDialog::dialogButtonClicked(QAbstractButton *button) { +void NewMapGroupDialog::dialogButtonClicked(QAbstractButton *button) { auto role = ui->buttonBox->buttonRole(button); if (role == QDialogButtonBox::RejectRole){ reject(); @@ -62,10 +57,11 @@ void NewNameDialog::dialogButtonClicked(QAbstractButton *button) { } } -void NewNameDialog::accept() { +void NewMapGroupDialog::accept() { if (!validateName()) return; - emit applied(ui->lineEdit_Name->text()); + this->project->addNewMapGroup(ui->lineEdit_Name->text()); + QDialog::accept(); } diff --git a/src/ui/projectsettingseditor.cpp b/src/ui/projectsettingseditor.cpp index 9324c123..016480a7 100644 --- a/src/ui/projectsettingseditor.cpp +++ b/src/ui/projectsettingseditor.cpp @@ -293,7 +293,7 @@ QStringList ProjectSettingsEditor::getWarpBehaviorsList() { void ProjectSettingsEditor::setWarpBehaviorsList(QStringList list) { list.removeDuplicates(); - list.sort(); + Project::numericalModeSort(list); ui->textEdit_WarpBehaviors->setText(list.join("\n")); } diff --git a/src/ui/regionmapeditor.cpp b/src/ui/regionmapeditor.cpp index f0415726..af0e220b 100644 --- a/src/ui/regionmapeditor.cpp +++ b/src/ui/regionmapeditor.cpp @@ -28,6 +28,7 @@ RegionMapEditor::RegionMapEditor(QWidget *parent, Project *project) : this->setAttribute(Qt::WA_DeleteOnClose); this->ui->setupUi(this); this->project = project; + connect(this->project, &Project::mapSectionIdNamesChanged, this, &RegionMapEditor::setLocations); this->configFilepath = QString("%1/%2").arg(this->project->root).arg(projectConfig.getFilePath(ProjectFilePath::json_region_porymap_cfg)); this->initShortcuts(); this->restoreWindowState(); @@ -728,8 +729,12 @@ void RegionMapEditor::displayRegionMapEntryOptions() { void RegionMapEditor::updateRegionMapEntryOptions(QString section) { if (!this->region_map->layoutEnabled()) return; + const QSignalBlocker b_X(this->ui->spinBox_RM_Entry_x); + const QSignalBlocker b_Y(this->ui->spinBox_RM_Entry_y); + const QSignalBlocker b_W(this->ui->spinBox_RM_Entry_width); + const QSignalBlocker b_H(this->ui->spinBox_RM_Entry_height); + bool enabled = (section != this->region_map->default_map_section) && this->region_map_entries.contains(section); - this->ui->lineEdit_RM_MapName->setEnabled(enabled); this->ui->spinBox_RM_Entry_x->setEnabled(enabled); this->ui->spinBox_RM_Entry_y->setEnabled(enabled); this->ui->spinBox_RM_Entry_width->setEnabled(enabled); @@ -737,56 +742,31 @@ void RegionMapEditor::updateRegionMapEntryOptions(QString section) { this->ui->pushButton_entryActivate->setEnabled(section != this->region_map->default_map_section); this->ui->pushButton_entryActivate->setText(enabled ? "Remove" : "Add"); - this->ui->lineEdit_RM_MapName->blockSignals(true); - this->ui->spinBox_RM_Entry_x->blockSignals(true); - this->ui->spinBox_RM_Entry_y->blockSignals(true); - this->ui->spinBox_RM_Entry_width->blockSignals(true); - this->ui->spinBox_RM_Entry_height->blockSignals(true); - this->ui->comboBox_RM_Entry_MapSection->setCurrentText(section); this->activeEntry = section; this->region_map_entries_item->currentSection = section; MapSectionEntry entry = enabled ? this->region_map_entries[section] : MapSectionEntry(); - this->ui->lineEdit_RM_MapName->setText(entry.name); this->ui->spinBox_RM_Entry_x->setValue(entry.x); this->ui->spinBox_RM_Entry_y->setValue(entry.y); this->ui->spinBox_RM_Entry_width->setValue(entry.width); this->ui->spinBox_RM_Entry_height->setValue(entry.height); - - this->ui->lineEdit_RM_MapName->blockSignals(false); - this->ui->spinBox_RM_Entry_x->blockSignals(false); - this->ui->spinBox_RM_Entry_y->blockSignals(false); - this->ui->spinBox_RM_Entry_width->blockSignals(false); - this->ui->spinBox_RM_Entry_height->blockSignals(false); } void RegionMapEditor::on_pushButton_entryActivate_clicked() { QString section = this->ui->comboBox_RM_Entry_MapSection->currentText(); if (section == this->region_map->default_map_section) return; + MapSectionEntry oldEntry = this->region_map->getEntry(section); if (this->region_map_entries.contains(section)) { // disable - MapSectionEntry oldEntry = this->region_map->getEntry(section); - this->region_map->removeEntry(section); - MapSectionEntry newEntry = this->region_map->getEntry(section); - RemoveEntry *commit = new RemoveEntry(this->region_map, section, oldEntry, newEntry); - this->region_map->editHistory.push(commit); - updateRegionMapEntryOptions(section); - - this->ui->pushButton_entryActivate->setText("Add"); + this->region_map->editHistory.push(new RemoveEntry(this->region_map, section, oldEntry, MapSectionEntry())); } else { // enable - MapSectionEntry oldEntry = this->region_map->getEntry(section); - MapSectionEntry entry = MapSectionEntry(); - entry.valid = true; - this->region_map->setEntry(section, entry); - MapSectionEntry newEntry = this->region_map->getEntry(section); - AddEntry *commit = new AddEntry(this->region_map, section, oldEntry, newEntry); - this->region_map->editHistory.push(commit); - updateRegionMapEntryOptions(section); - - this->ui->pushButton_entryActivate->setText("Remove"); + MapSectionEntry newEntry = MapSectionEntry(); + newEntry.valid = true; + this->region_map->editHistory.push(new AddEntry(this->region_map, section, oldEntry, newEntry)); } + updateRegionMapEntryOptions(section); } void RegionMapEditor::displayRegionMapTileSelector() { @@ -932,7 +912,7 @@ void RegionMapEditor::on_tabWidget_Region_Map_currentChanged(int index) { break; case 2: this->ui->verticalSlider_Zoom_Image_Tiles->setVisible(false); - on_comboBox_RM_Entry_MapSection_textActivated(ui->comboBox_RM_Entry_MapSection->currentText()); + on_comboBox_RM_Entry_MapSection_currentTextChanged(ui->comboBox_RM_Entry_MapSection->currentText()); break; } } @@ -943,7 +923,7 @@ void RegionMapEditor::on_comboBox_RM_ConnectedMap_textActivated(const QString &m onRegionMapLayoutSelectedTileChanged(this->currIndex);// re-draw layout image } -void RegionMapEditor::on_comboBox_RM_Entry_MapSection_textActivated(const QString &text) { +void RegionMapEditor::on_comboBox_RM_Entry_MapSection_currentTextChanged(const QString &text) { this->activeEntry = text; this->region_map_entries_item->currentSection = text; updateRegionMapEntryOptions(text); @@ -1046,15 +1026,6 @@ void RegionMapEditor::on_spinBox_RM_LayoutHeight_valueChanged(int value) { } } -void RegionMapEditor::on_lineEdit_RM_MapName_textEdited(const QString &text) { - if (!this->region_map_entries.contains(activeEntry)) return; - MapSectionEntry oldEntry = this->region_map_entries[activeEntry]; - this->region_map_entries[activeEntry].name = text; - MapSectionEntry newEntry = this->region_map_entries[activeEntry]; - EditEntry *commit = new EditEntry(this->region_map, activeEntry, oldEntry, newEntry); - this->region_map->editHistory.push(commit); -} - void RegionMapEditor::on_pushButton_RM_Options_delete_clicked() { int index = this->region_map->tilemapToLayoutIndex(this->currIndex); QList oldLayout = this->region_map->getLayout(this->region_map->getLayer()); @@ -1147,7 +1118,7 @@ void RegionMapEditor::on_action_Swap_triggered() { connect(&buttonBox, &QDialogButtonBox::accepted, [&popup, &oldSecBox, &newSecBox, &beforeSection, &afterSection](){ beforeSection = oldSecBox->currentText(); afterSection = newSecBox->currentText(); - if (!beforeSection.isEmpty() && !afterSection.isEmpty()) { + if (!beforeSection.isEmpty() && !afterSection.isEmpty() && beforeSection != afterSection) { popup.accept(); } }); @@ -1186,7 +1157,7 @@ void RegionMapEditor::on_action_Replace_triggered() { connect(&buttonBox, &QDialogButtonBox::accepted, [&popup, &oldSecBox, &newSecBox, &beforeSection, &afterSection](){ beforeSection = oldSecBox->currentText(); afterSection = newSecBox->currentText(); - if (!beforeSection.isEmpty() && !afterSection.isEmpty()) { + if (!beforeSection.isEmpty() && !afterSection.isEmpty() && beforeSection != afterSection) { popup.accept(); } }); diff --git a/src/ui/regionmapentriespixmapitem.cpp b/src/ui/regionmapentriespixmapitem.cpp index ace3c38d..8ac888a0 100644 --- a/src/ui/regionmapentriespixmapitem.cpp +++ b/src/ui/regionmapentriespixmapitem.cpp @@ -8,7 +8,7 @@ void RegionMapEntriesPixmapItem::draw() { int entry_x, entry_y, entry_w, entry_h; - if (!entry.valid || entry.name == region_map->default_map_section) { + if (!entry.valid || currentSection == region_map->default_map_section) { entry_x = entry_y = 0; entry_w = entry_h = 1; } else { From f4c0cb2d2dacd4c2aed36a2b18b2768a6dc94568 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 13:24:09 -0500 Subject: [PATCH 37/42] Use placeholder text for new map/layout names --- forms/newlayoutdialog.ui | 6 ++++++ forms/newmapdialog.ui | 3 +++ include/project.h | 2 -- src/core/maplayout.cpp | 1 + src/project.cpp | 24 ++---------------------- src/ui/newlayoutdialog.cpp | 2 +- src/ui/newmapdialog.cpp | 5 ++--- 7 files changed, 15 insertions(+), 28 deletions(-) diff --git a/forms/newlayoutdialog.ui b/forms/newlayoutdialog.ui index 42c7733c..980b5351 100644 --- a/forms/newlayoutdialog.ui +++ b/forms/newlayoutdialog.ui @@ -68,6 +68,9 @@ <html><head/><body><p>The constant that will be used to refer to this layout. It cannot be the same as any other existing layout.</p></body></html> + + LAYOUT_MY_NEW_LAYOUT + true @@ -92,6 +95,9 @@ <html><head/><body><p>The name of the new layout. The name cannot be the same as any other existing layout.</p></body></html> + + MyNewLayout + true diff --git a/forms/newmapdialog.ui b/forms/newmapdialog.ui index 6b32810a..604800f8 100644 --- a/forms/newmapdialog.ui +++ b/forms/newmapdialog.ui @@ -185,6 +185,9 @@ <html><head/><body><p>The name of the new map. The name cannot be the same as any other existing map.</p></body></html> + + MyNewMap + true diff --git a/include/project.h b/include/project.h index c29a11b4..9fb4e2ed 100644 --- a/include/project.h +++ b/include/project.h @@ -133,8 +133,6 @@ class Project : public QObject NewMapSettings newMapSettings; Layout::Settings newLayoutSettings; - QString getNewMapName() const; - QString getNewLayoutName() const; void initNewMapSettings(); void initNewLayoutSettings(); diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index 26d4cb89..e2549897 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,6 +32,7 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } +// When we create a layout automatically for a new map we add this suffix to differentiate the layout name from the map name. QString Layout::defaultSuffix() { return "_Layout"; } diff --git a/src/project.cpp b/src/project.cpp index 9fff8710..f8ccd768 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -1995,28 +1995,8 @@ QString Project::toUniqueIdentifier(const QString &identifier) const { return uniqueIdentifier; } -QString Project::getNewMapName() const { - // Ensure default name/ID doesn't already exist. - int suffix = 1; - QString newMapName; - do { - newMapName = QString("NewMap%1").arg(suffix++); - } while (!isIdentifierUnique(newMapName) || !isIdentifierUnique(Map::mapConstantFromName(newMapName))); - return newMapName; -} - -QString Project::getNewLayoutName() const { - // Ensure default name/ID doesn't already exist. - int suffix = 1; - QString newLayoutName; - do { - newLayoutName = QString("NewLayout%1").arg(suffix++); - } while (!isIdentifierUnique(newLayoutName) || !isIdentifierUnique(Layout::layoutConstantFromName(newLayoutName))); - return newLayoutName; -} - void Project::initNewMapSettings() { - this->newMapSettings.name = getNewMapName(); + this->newMapSettings.name = QString(); this->newMapSettings.group = this->groupNames.at(0); this->newMapSettings.canFlyTo = false; @@ -2044,7 +2024,7 @@ void Project::initNewMapSettings() { } void Project::initNewLayoutSettings() { - this->newLayoutSettings.name = getNewLayoutName(); + this->newLayoutSettings.name = QString(); this->newLayoutSettings.id = Layout::layoutConstantFromName(this->newLayoutSettings.name); this->newLayoutSettings.width = getDefaultMapDimension(); this->newLayoutSettings.height = getDefaultMapDimension(); diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 226df897..537a3092 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -28,7 +28,7 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q if (this->layoutToCopy && !this->layoutToCopy->name.isEmpty()) { settings->name = project->toUniqueIdentifier(this->layoutToCopy->name); } else { - settings->name = project->getNewLayoutName(); + settings->name = QString(); } // Generate a unique Layout constant settings->id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index fda3d59a..3ce36869 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -35,9 +35,8 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare settings->name = project->toUniqueIdentifier(this->mapToCopy->name()); } else { - // Not duplicating a map, get a generic new map name. - // The rest of the settings are preserved in the project between sessions. - settings->name = project->getNewMapName(); + // Clear the previously-used map name. The rest of the settings are preserved between sessions. + settings->name = QString(); } // Generate a unique Layout constant settings->layout.id = project->toUniqueIdentifier(Layout::layoutConstantFromName(settings->name)); From cfb6f70580b5f4ca743a46394852347c0a44967b Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 14:38:49 -0500 Subject: [PATCH 38/42] Fix freeze when creating a new tileset --- include/ui/newtilesetdialog.h | 3 +++ src/mainwindow.cpp | 10 ++++++---- src/ui/newtilesetdialog.cpp | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/include/ui/newtilesetdialog.h b/include/ui/newtilesetdialog.h index 374c85a7..2a5ad14e 100644 --- a/include/ui/newtilesetdialog.h +++ b/include/ui/newtilesetdialog.h @@ -21,6 +21,9 @@ class NewTilesetDialog : public QDialog virtual void accept() override; +signals: + void applied(Tileset *tileset); + private: Ui::NewTilesetDialog *ui; Project *project = nullptr; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fbd7e409..16d55c2d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1347,10 +1347,6 @@ void MainWindow::onMapSectionDisplayNameChanged(const QString &idName, const QSt void MainWindow::onNewTilesetCreated(Tileset *tileset) { logInfo(QString("Created a new tileset named %1.").arg(tileset->name)); - // Unlike creating a new map or layout (which immediately opens the new item) - // creating a new tileset has no visual feedback that it succeeded, so we show a message. - InfoMessage::show(QString("New tileset created at '%1'!").arg(tileset->getExpectedDir()), this); - // Refresh tileset combo boxes if (!tileset->is_secondary) { int index = this->editor->project->primaryTilesetLabels.indexOf(tileset->name); @@ -1409,6 +1405,12 @@ void MainWindow::openDuplicateLayoutDialog(const QString &layoutId) { void MainWindow::on_actionNew_Tileset_triggered() { auto dialog = new NewTilesetDialog(editor->project, this); + connect(dialog, &NewTilesetDialog::applied, [this](Tileset *tileset) { + // Unlike creating a new map or layout (which immediately opens the new item) + // creating a new tileset has no visual feedback that it succeeded, so we show a message. + // It's important that we do this after the dialog has closed (sheet modal dialogs on macOS don't seem to play nice together). + InfoMessage::show(QString("New tileset created at '%1'!").arg(tileset->getExpectedDir()), this); + }); dialog->open(); } diff --git a/src/ui/newtilesetdialog.cpp b/src/ui/newtilesetdialog.cpp index 049b317d..968b3b88 100644 --- a/src/ui/newtilesetdialog.cpp +++ b/src/ui/newtilesetdialog.cpp @@ -76,4 +76,5 @@ void NewTilesetDialog::accept() { ui->label_GenericError->setVisible(false); QDialog::accept(); + emit applied(tileset); } From fe8f978a6bdce960794c99baefbaaca0961ceb5f Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 14:46:49 -0500 Subject: [PATCH 39/42] Fix map symbol editing regression, save new map/layout dialog geometry --- include/config.h | 2 ++ src/config.cpp | 6 ++++++ src/ui/maplistmodels.cpp | 1 - src/ui/newlayoutdialog.cpp | 9 ++++++++- src/ui/newmapdialog.cpp | 2 ++ 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/include/config.h b/include/config.h index 083fea78..926e93c6 100644 --- a/include/config.h +++ b/include/config.h @@ -137,6 +137,8 @@ class PorymapConfig: public KeyValueConfigBase QVersionNumber lastUpdateCheckVersion; QMap rateLimitTimes; QByteArray wildMonChartGeometry; + QByteArray newMapDialogGeometry; + QByteArray newLayoutDialogGeometry; protected: virtual QString getConfigFilepath() override; diff --git a/src/config.cpp b/src/config.cpp index c25eb239..ce78d8b9 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -349,6 +349,10 @@ void PorymapConfig::parseConfigKeyValue(QString key, QString value) { this->customScriptsEditorState = bytesFromString(value); } else if (key == "wild_mon_chart_geometry") { this->wildMonChartGeometry = bytesFromString(value); + } else if (key == "new_map_dialog_geometry") { + this->newMapDialogGeometry = bytesFromString(value); + } else if (key == "new_layout_dialog_geometry") { + this->newLayoutDialogGeometry = bytesFromString(value); } else if (key == "metatiles_zoom") { this->metatilesZoom = getConfigInteger(key, value, 10, 100, 30); } else if (key == "collision_zoom") { @@ -441,6 +445,8 @@ QMap PorymapConfig::getKeyValueMap() { map.insert("custom_scripts_editor_geometry", stringFromByteArray(this->customScriptsEditorGeometry)); map.insert("custom_scripts_editor_state", stringFromByteArray(this->customScriptsEditorState)); map.insert("wild_mon_chart_geometry", stringFromByteArray(this->wildMonChartGeometry)); + map.insert("new_map_dialog_geometry", stringFromByteArray(this->newMapDialogGeometry)); + map.insert("new_layout_dialog_geometry", stringFromByteArray(this->newLayoutDialogGeometry)); map.insert("mirror_connecting_maps", this->mirrorConnectingMaps ? "1" : "0"); map.insert("show_dive_emerge_maps", this->showDiveEmergeMaps ? "1" : "0"); map.insert("dive_emerge_map_opacity", QString::number(this->diveEmergeMapOpacity)); diff --git a/src/ui/maplistmodels.cpp b/src/ui/maplistmodels.cpp index e74d0a93..d08bb2cf 100644 --- a/src/ui/maplistmodels.cpp +++ b/src/ui/maplistmodels.cpp @@ -104,7 +104,6 @@ QStandardItem *MapListModel::createMapItem(const QString &mapName, QStandardItem map->setData(mapName, MapListUserRoles::NameRole); map->setData("map_name", MapListUserRoles::TypeRole); map->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemNeverHasChildren); - map->setEditable(this->editable); // Will override flags if necessary map->setToolTip(this->project->mapNamesToMapConstants.value(mapName)); this->mapItems.insert(mapName, map); return map; diff --git a/src/ui/newlayoutdialog.cpp b/src/ui/newlayoutdialog.cpp index 537a3092..779a4694 100644 --- a/src/ui/newlayoutdialog.cpp +++ b/src/ui/newlayoutdialog.cpp @@ -42,11 +42,18 @@ NewLayoutDialog::NewLayoutDialog(Project *project, const Layout *layoutToCopy, Q connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewLayoutDialog::dialogButtonClicked); refresh(); - adjustSize(); + + if (porymapConfig.newLayoutDialogGeometry.isEmpty()){ + // On first display resize to fit contents a little better + adjustSize(); + } else { + restoreGeometry(porymapConfig.newLayoutDialogGeometry); + } } NewLayoutDialog::~NewLayoutDialog() { + porymapConfig.newLayoutDialogGeometry = saveGeometry(); saveSettings(); delete ui; } diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 3ce36869..521878c5 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -63,6 +63,7 @@ NewMapDialog::NewMapDialog(Project *project, const Map *mapToCopy, QWidget *pare connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &NewMapDialog::dialogButtonClicked); refresh(); + restoreGeometry(porymapConfig.newMapDialogGeometry); } // Adding new map to an existing map list folder. Initialize settings accordingly. @@ -91,6 +92,7 @@ NewMapDialog::NewMapDialog(Project *project, int mapListTab, const QString &mapL NewMapDialog::~NewMapDialog() { + porymapConfig.newMapDialogGeometry = saveGeometry(); saveSettings(); delete ui; } From 07e4d24b988174de16965d26bf0fe3641963ac80 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 15:19:21 -0500 Subject: [PATCH 40/42] Enforce layout settings for duplicate maps --- src/ui/newmapdialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index 521878c5..d54e85be 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -213,6 +213,12 @@ void NewMapDialog::on_comboBox_LayoutID_currentTextChanged(const QString &text) // Changing the layout ID to an existing layout updates the layout settings to match. const Layout *layout = this->project->mapLayouts.value(text); + if (!layout && this->mapToCopy) { + // When duplicating a map, if a new layout ID is specified the settings will be updated + // to match the layout of the map we're duplicating. + layout = this->mapToCopy->layout(); + } + if (layout) { ui->newLayoutForm->setSettings(layout->settings()); ui->newLayoutForm->setDisabled(true); From a00636260c823f1558f5c261b3bc25df005da185 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 15:29:11 -0500 Subject: [PATCH 41/42] Remove old layout suffix function --- src/core/maplayout.cpp | 5 ----- src/project.cpp | 2 +- src/ui/newmapdialog.cpp | 3 ++- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/core/maplayout.cpp b/src/core/maplayout.cpp index e2549897..94e6c291 100644 --- a/src/core/maplayout.cpp +++ b/src/core/maplayout.cpp @@ -32,11 +32,6 @@ void Layout::copyFrom(const Layout *other) { this->border = other->border; } -// When we create a layout automatically for a new map we add this suffix to differentiate the layout name from the map name. -QString Layout::defaultSuffix() { - return "_Layout"; -} - QString Layout::layoutConstantFromName(QString mapName) { // Transform map names of the form 'GraniteCave_B1F` into layout constants like 'LAYOUT_GRANITE_CAVE_B1F'. static const QRegularExpression caseChange("([a-z])([A-Z])"); diff --git a/src/project.cpp b/src/project.cpp index f8ccd768..9b8025b2 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2001,7 +2001,7 @@ void Project::initNewMapSettings() { this->newMapSettings.canFlyTo = false; this->newMapSettings.layout.folderName = this->newMapSettings.name; - this->newMapSettings.layout.name = QString("%1%2").arg(this->newMapSettings.name).arg(Layout::defaultSuffix()); + this->newMapSettings.layout.name = QString(); this->newMapSettings.layout.id = Layout::layoutConstantFromName(this->newMapSettings.name); this->newMapSettings.layout.width = getDefaultMapDimension(); this->newMapSettings.layout.height = getDefaultMapDimension(); diff --git a/src/ui/newmapdialog.cpp b/src/ui/newmapdialog.cpp index d54e85be..caa1e839 100644 --- a/src/ui/newmapdialog.cpp +++ b/src/ui/newmapdialog.cpp @@ -124,12 +124,13 @@ void NewMapDialog::saveSettings() { settings->header = this->headerForm->headerData(); // This dialog doesn't give users the option to give new layouts a name, we generate one using the map name. + // We instead add a "_Layout" suffix to differentiate the layout name from the map name. // (an older iteration of this dialog gave users an option to name new layouts, but it's extra clutter for // something the majority of users creating a map won't need. If they want to give a specific name to a layout // they can create the layout first, then create a new map that uses that layout.) const Layout *layout = this->project->mapLayouts.value(settings->layout.id); if (!layout) { - const QString newLayoutName = QString("%1%2").arg(settings->name).arg(Layout::defaultSuffix()); + const QString newLayoutName = settings->name + QStringLiteral("_Layout"); settings->layout.name = this->project->toUniqueIdentifier(newLayoutName); } else { // Pre-existing layout. The layout name won't be read, but we'll make sure it's correct anyway. From 0cf7a45890c1e814b4099105a9f5b71cd6bcf169 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Wed, 22 Jan 2025 15:49:16 -0500 Subject: [PATCH 42/42] Don't automatically create empty MAPSEC display names --- src/project.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project.cpp b/src/project.cpp index 9b8025b2..3abfcfbf 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -2426,7 +2426,7 @@ void Project::removeMapsec(const QString &idName) { } void Project::setMapsecDisplayName(const QString &idName, const QString &displayName) { - if (this->mapSectionDisplayNames[idName] == displayName) + if (this->mapSectionDisplayNames.value(idName) == displayName) return; this->mapSectionDisplayNames[idName] = displayName; this->hasUnsavedDataChanges = true;