From 4af1c4d463b846efe9bfe6e72269adf15b90bba6 Mon Sep 17 00:00:00 2001 From: GriffinR Date: Sun, 4 Aug 2024 19:11:29 -0400 Subject: [PATCH] Take advantage of MapConnection as QObject --- include/core/map.h | 3 + include/core/mapconnection.h | 18 +- include/editor.h | 24 +- include/mainwindow.h | 3 +- include/project.h | 1 + include/ui/connectionpixmapitem.h | 9 +- include/ui/connectionslistitem.h | 10 +- src/core/map.cpp | 19 ++ src/core/mapconnection.cpp | 181 +++++++++--- src/editor.cpp | 442 +++++++++++------------------- src/mainwindow.cpp | 22 +- src/project.cpp | 5 +- src/ui/connectionpixmapitem.cpp | 5 +- src/ui/connectionslistitem.cpp | 14 +- 14 files changed, 374 insertions(+), 382 deletions(-) diff --git a/include/core/map.h b/include/core/map.h index eabe0a8a..1dfc881e 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -98,6 +98,8 @@ public: QStringList getScriptLabels(Event::Group group = Event::Group::None); void removeEvent(Event *); void addEvent(Event *); + bool removeConnection(MapConnection *); + void addConnection(MapConnection *); QPixmap renderConnection(const QString &, MapLayout *); QPixmap renderBorder(bool ignoreCache = false); void setDimensions(int newWidth, int newHeight, bool setNewBlockdata = true, bool enableScriptCallback = false); @@ -132,6 +134,7 @@ signals: void mapDimensionsChanged(const QSize &size); void mapNeedsRedrawing(); void openScriptRequested(QString label); + void connectionAdded(MapConnection*); }; #endif // MAP_H diff --git a/include/core/mapconnection.h b/include/core/mapconnection.h index 1da62efa..d934b388 100644 --- a/include/core/mapconnection.h +++ b/include/core/mapconnection.h @@ -4,6 +4,9 @@ #include #include +#include + +class Project; class MapConnection : public QObject { @@ -23,19 +26,32 @@ public: int offset() const { return m_offset; } void setOffset(int offset); - MapConnection * createMirror(); bool isMirror(const MapConnection*); + MapConnection* findMirror(); + MapConnection* createMirror(); + QPixmap getPixmap(); + + static QPointer project; + static const QMap oppositeDirections; static const QStringList cardinalDirections; static bool isCardinal(const QString &direction); static bool isHorizontal(const QString &direction); static bool isVertical(const QString &direction); + static QString oppositeDirection(const QString &direction) { return oppositeDirections.value(direction, direction); } private: QString m_direction; QString m_hostMapName; QString m_targetMapName; int m_offset; + bool m_ignoreMirror; + + void mirrorDirection(const QString &direction); + void mirrorHostMapName(const QString &hostMapName); + void mirrorTargetMapName(const QString &targetMapName); + void mirrorOffset(int offset); + void markMapEdited(); signals: void directionChanged(const QString &before, const QString &after); diff --git a/include/editor.h b/include/editor.h index 982e8f77..e1a24de6 100644 --- a/include/editor.h +++ b/include/editor.h @@ -46,6 +46,7 @@ public: QPointer project = nullptr; Map *map = nullptr; Settings *settings; + void setProject(Project * project); void saveProject(); void save(); void closeProject(); @@ -78,8 +79,8 @@ public: void updateDiveEmergeVisibility(); void addNewConnection(); void addConnection(MapConnection* connection, bool addMirror = true); - void removeConnection(MapConnection* connection, bool removeMirror = true); - void removeConnectionItem(ConnectionPixmapItem* connectionItem); + void removeConnection(MapConnection* connection, bool addMirror = true); + void removeConnectionPixmap(ConnectionPixmapItem* connectionItem); void removeSelectedConnection(); void addNewWildMonGroup(QWidget *window); void deleteWildMonGroup(); @@ -186,15 +187,11 @@ private: void clearWildMonTables(); void updateBorderVisibility(); QPoint calculateConnectionPosition(MapConnection *connection, const QPixmap &pixmap); - QPixmap getConnectionPixmap(MapConnection *connection); - void updateConnectionItem(ConnectionPixmapItem* connectionItem); - void updateConnectionItemPos(ConnectionPixmapItem* connectionItem); - void createConnectionItem(MapConnection* connection); - void createDiveEmergeConnection(MapConnection* connection); + void updateConnectionPixmap(ConnectionPixmapItem* connectionItem); + void updateConnectionPixmapPos(ConnectionPixmapItem* connectionItem); + void displayConnection(MapConnection* connection); + void displayDiveEmergeConnection(MapConnection* connection); void setDiveEmergeMapName(QString mapName, QString direction); - MapConnection* getMirroredConnection(MapConnection*); - void addMirroredConnection(MapConnection*); - void removeMirroredConnection(MapConnection*); void updateEncounterFields(EncounterFields newFields); QString getMovementPermissionText(uint16_t collision, uint16_t elevation); QString getMetatileDisplayMessage(uint16_t metatileId); @@ -210,9 +207,6 @@ private slots: void setStraightPathCursorMode(QGraphicsSceneMouseEvent *event); void mouseEvent_map(QGraphicsSceneMouseEvent *event, MapPixmapItem *item); void mouseEvent_collision(QGraphicsSceneMouseEvent *event, CollisionPixmapItem *item); - void setConnectionOffset(MapConnection *connection, int offset); - void setConnectionMap(MapConnection *connection, const QString &mapName); - void setConnectionDirection(MapConnection *connection, const QString &direction); void setSelectedConnection(ConnectionPixmapItem* connectionItem); void onHoveredMovementPermissionChanged(uint16_t, uint16_t); void onHoveredMovementPermissionCleared(); @@ -225,15 +219,15 @@ private slots: void onSelectedMetatilesChanged(); void onWheelZoom(int); void onToggleGridClicked(bool); + void onMapConnectionDoubleClicked(MapConnection*); signals: void objectsChanged(); - void connectionItemDoubleClicked(QString, QString); + void openConnectedMap(QString, QString); void wildMonDataChanged(); void warpEventDoubleClicked(QString, int, Event::Group); void currentMetatilesSelectionChanged(); void mapRulerStatusChanged(const QString &); - void editedMapData(Map*); void tilesetUpdated(QString); }; diff --git a/include/mainwindow.h b/include/mainwindow.h index e37c2c56..619ee544 100644 --- a/include/mainwindow.h +++ b/include/mainwindow.h @@ -178,13 +178,14 @@ private slots: void copy(); void paste(); - void onConnectionItemDoubleClicked(QString, QString); + void onOpenConnectedMap(QString, QString); void onMapNeedsRedrawing(); void onTilesetsSaved(QString, QString); void onWildMonDataChanged(); void openNewMapPopupWindow(); void onNewMapCreated(); void onMapCacheCleared(); + void onMapLoaded(Map *map); void importMapFromAdvanceMap1_92(); void onMapRulerStatusChanged(const QString &); void applyUserShortcuts(); diff --git a/include/project.h b/include/project.h index c9a1a0c0..8c08e508 100644 --- a/include/project.h +++ b/include/project.h @@ -257,6 +257,7 @@ signals: void reloadProject(); void uncheckMonitorFilesAction(); void mapCacheCleared(); + void mapLoaded(Map *map); }; #endif // PROJECT_H diff --git a/include/ui/connectionpixmapitem.h b/include/ui/connectionpixmapitem.h index cd1d6622..969fa6c1 100644 --- a/include/ui/connectionpixmapitem.h +++ b/include/ui/connectionpixmapitem.h @@ -4,6 +4,7 @@ #include "mapconnection.h" #include #include +#include class ConnectionPixmapItem : public QObject, public QGraphicsPixmapItem { Q_OBJECT @@ -18,11 +19,10 @@ public: this->initialX = x; this->initialY = y; this->initialOffset = connection->offset(); - this->setX(x); - this->setY(y); + this->setPos(x, y); } QPixmap basePixmap; - MapConnection* connection; + QPointer connection; int initialX; int initialY; int initialOffset; @@ -41,8 +41,7 @@ protected: void mouseDoubleClickEvent(QGraphicsSceneMouseEvent*); signals: - void connectionItemDoubleClicked(ConnectionPixmapItem* connectionItem); - void connectionMoved(MapConnection *, int newOffset); + void connectionItemDoubleClicked(MapConnection*); void selectionChanged(bool selected); }; diff --git a/include/ui/connectionslistitem.h b/include/ui/connectionslistitem.h index a2ce2c16..9cac5689 100644 --- a/include/ui/connectionslistitem.h +++ b/include/ui/connectionslistitem.h @@ -5,6 +5,7 @@ #include #include +#include namespace Ui { class ConnectionsListItem; @@ -25,7 +26,7 @@ public: void updateUI(); void setSelected(bool selected); - MapConnection * connection; + QPointer connection; private: Ui::ConnectionsListItem *ui; @@ -36,12 +37,9 @@ protected: void mouseDoubleClickEvent(QMouseEvent *); signals: - void editedOffset(MapConnection *connection, int newOffset); - void editedDirection(MapConnection *connection, const QString &direction); - void editedMapName(MapConnection *connection, const QString &mapName); - void removed(); + void removed(MapConnection*); void selected(); - void doubleClicked(const QString &mapName); + void doubleClicked(MapConnection*); private slots: void on_comboBox_Direction_currentTextChanged(const QString &direction); diff --git a/src/core/map.cpp b/src/core/map.cpp index 494ff273..75191a2d 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -218,6 +218,9 @@ QPixmap Map::renderBorder(bool ignoreCache) { } QPixmap Map::renderConnection(const QString &direction, MapLayout * fromLayout) { + if (direction == "dive" || direction == "emerge") + return render(); + if (!MapConnection::isCardinal(direction)) return QPixmap(); @@ -510,6 +513,22 @@ void Map::addEvent(Event *event) { if (!ownedEvents.contains(event)) ownedEvents.append(event); } +bool Map::removeConnection(MapConnection *connection) { + if (connections.removeOne(connection)) { + modify(); + return true; + } + return false; +} + +void Map::addConnection(MapConnection *connection) { + if (!connection || connections.contains(connection)) + return; + connections.append(connection); + modify(); + emit connectionAdded(connection); +} + void Map::modify() { emit modified(); } diff --git a/src/core/mapconnection.cpp b/src/core/mapconnection.cpp index 4e4434ba..5139d810 100644 --- a/src/core/mapconnection.cpp +++ b/src/core/mapconnection.cpp @@ -1,7 +1,9 @@ -#include #include "mapconnection.h" +#include "project.h" -const QMap oppositeDirections = { +QPointer MapConnection::project = nullptr; + +const QMap MapConnection::oppositeDirections = { {"up", "down"}, {"down", "up"}, {"right", "left"}, {"left", "right"}, {"dive", "emerge"}, {"emerge", "dive"} @@ -12,47 +14,7 @@ MapConnection::MapConnection(const QString &direction, const QString &hostMapNam m_hostMapName = hostMapName; m_targetMapName = targetMapName; m_offset = offset; -} - -void MapConnection::setDirection(const QString &direction) { - if (direction == m_direction) - return; - - auto before = m_direction; - m_direction = direction; - emit directionChanged(before, m_direction); -} - -void MapConnection::setHostMapName(const QString &hostMapName) { - if (hostMapName == m_hostMapName) - return; - - auto before = m_hostMapName; - m_hostMapName = hostMapName; - emit hostMapNameChanged(before, m_hostMapName); - -} - -void MapConnection::setTargetMapName(const QString &targetMapName) { - if (targetMapName == m_targetMapName) - return; - - auto before = m_targetMapName; - m_targetMapName = targetMapName; - emit targetMapNameChanged(before, m_targetMapName); -} - -void MapConnection::setOffset(int offset) { - if (offset == m_offset) - return; - - auto before = m_offset; - m_offset = offset; - emit offsetChanged(before, m_offset); -} - -MapConnection * MapConnection::createMirror() { - return new MapConnection(oppositeDirections.value(m_direction, m_direction), m_targetMapName, m_hostMapName, -m_offset); + m_ignoreMirror = false; } bool MapConnection::isMirror(const MapConnection* other) { @@ -62,7 +24,138 @@ bool MapConnection::isMirror(const MapConnection* other) { return m_hostMapName == other->m_targetMapName && m_targetMapName == other->m_hostMapName && m_offset == -other->m_offset - && m_direction == oppositeDirections.value(other->m_direction, other->m_direction); + && m_direction == oppositeDirection(other->m_direction); +} + +MapConnection* MapConnection::findMirror() { + if (!porymapConfig.mirrorConnectingMaps || !project || m_ignoreMirror) + return nullptr; + + Map *connectedMap = project->getMap(m_targetMapName); + if (!connectedMap) + return nullptr; + + // Find the matching connection in the connected map. + // Note: There is no strict source -> mirror pairing, i.e. we are not guaranteed + // to always get the same MapConnection if there are multiple identical copies. + for (auto connection : connectedMap->connections) { + if (this != connection && this->isMirror(connection)) + return connection; + } + return nullptr; +} + +MapConnection * MapConnection::createMirror() { + if (!porymapConfig.mirrorConnectingMaps) + return nullptr; + return new MapConnection(oppositeDirection(m_direction), m_targetMapName, m_hostMapName, -m_offset); +} + +void MapConnection::markMapEdited() { + if (project) { + Map * map = project->getMap(m_hostMapName); + if (map) map->modify(); + } +} + +QPixmap MapConnection::getPixmap() { + if (!project) + return QPixmap(); + + Map *hostMap = project->getMap(m_hostMapName); + Map *targetMap = project->getMap(m_targetMapName); + if (!hostMap || !targetMap) + return QPixmap(); + + return targetMap->renderConnection(m_direction, hostMap->layout); +} + +void MapConnection::setDirection(const QString &direction) { + if (direction == m_direction) + return; + + mirrorDirection(direction); + auto before = m_direction; + m_direction = direction; + emit directionChanged(before, m_direction); + markMapEdited(); +} + +void MapConnection::mirrorDirection(const QString &direction) { + MapConnection * mirror = findMirror(); + if (!mirror) + return; + mirror->m_ignoreMirror = true; + mirror->setDirection(oppositeDirection(direction)); + mirror->m_ignoreMirror = false; +} + +void MapConnection::setHostMapName(const QString &hostMapName) { + if (hostMapName == m_hostMapName) + return; + + mirrorHostMapName(hostMapName); + auto before = m_hostMapName; + m_hostMapName = hostMapName; + + if (project) { + // TODO: This is probably unexpected design, esp because creating a MapConnection doesn't insert to host map + // Reassign connection to new host map + Map * oldMap = project->getMap(before); + Map * newMap = project->getMap(m_hostMapName); + if (oldMap) oldMap->removeConnection(this); + if (newMap) newMap->addConnection(this); + } + emit hostMapNameChanged(before, m_hostMapName); +} + +void MapConnection::mirrorHostMapName(const QString &hostMapName) { + MapConnection * mirror = findMirror(); + if (!mirror) + return; + mirror->m_ignoreMirror = true; + mirror->setTargetMapName(hostMapName); + mirror->m_ignoreMirror = false; +} + +void MapConnection::setTargetMapName(const QString &targetMapName) { + if (targetMapName == m_targetMapName) + return; + + mirrorTargetMapName(targetMapName); + auto before = m_targetMapName; + m_targetMapName = targetMapName; + emit targetMapNameChanged(before, m_targetMapName); + markMapEdited(); +} + +void MapConnection::mirrorTargetMapName(const QString &targetMapName) { + MapConnection * mirror = findMirror(); + if (!mirror) + return; + mirror->m_ignoreMirror = true; + mirror->setHostMapName(targetMapName); + mirror->m_ignoreMirror = false; +} + +void MapConnection::setOffset(int offset) { + if (offset == m_offset) + return; + + mirrorOffset(offset); + auto before = m_offset; + m_offset = offset; + emit offsetChanged(before, m_offset); + markMapEdited(); +} + +void MapConnection::mirrorOffset(int offset) { + MapConnection * mirror = findMirror(); + if (!mirror) + return; + mirror->m_ignoreMirror = true; + mirror->setOffset(-offset); + mirror->m_ignoreMirror = false; } const QStringList MapConnection::cardinalDirections = { diff --git a/src/editor.cpp b/src/editor.cpp index 8fc3b842..c9d1f196 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -78,6 +78,14 @@ void Editor::saveUiFields() { saveEncounterTabData(); } +void Editor::setProject(Project * project) { + if (this->project) { + closeProject(); + } + this->project = project; + MapConnection::project = project; +} + void Editor::closeProject() { if (!this->project) return; @@ -725,70 +733,83 @@ void Editor::updateEncounterFields(EncounterFields newFields) { project->wildMonFields = newFields; } -void Editor::createConnectionItem(MapConnection* connection) { +void Editor::displayConnection(MapConnection* connection) { if (!connection) return; + // It's possible for a map connection to be displayed repeatedly if it's being + // updated by mirroring and the target map is changing to/from the current map. + connection->disconnect(); + + if (connection->direction() == "dive" || connection->direction() == "emerge") { + displayDiveEmergeConnection(connection); + return; + } + // Create connection image - QPixmap pixmap = getConnectionPixmap(connection); + QPixmap pixmap = connection->getPixmap(); QPoint pos = calculateConnectionPosition(connection, pixmap); - ConnectionPixmapItem *connectionItem = new ConnectionPixmapItem(pixmap, connection, pos.x(), pos.y()); - connectionItem->render(); - scene->addItem(connectionItem); + ConnectionPixmapItem *pixmapItem = new ConnectionPixmapItem(pixmap, connection, pos.x(), pos.y()); + pixmapItem->render(); + scene->addItem(pixmapItem); // Create item for the list panel - ConnectionsListItem *listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, connectionItem->connection, project->mapNames); + ConnectionsListItem *listItem = new ConnectionsListItem(ui->scrollAreaContents_ConnectionsList, pixmapItem->connection, project->mapNames); ui->layout_ConnectionsList->insertWidget(ui->layout_ConnectionsList->count() - 1, listItem); // Insert above the vertical spacer - connect(connectionItem, &ConnectionPixmapItem::selectionChanged, [this, connectionItem](bool selected) { - if (selected) setSelectedConnection(connectionItem); + // Double clicking the list item or pixmap opens the connected map + connect(listItem, &ConnectionsListItem::doubleClicked, this, &Editor::onMapConnectionDoubleClicked); + connect(pixmapItem, &ConnectionPixmapItem::connectionItemDoubleClicked, this, &Editor::onMapConnectionDoubleClicked); + + // Sync the selection highlight between the list UI and the pixmap + connect(pixmapItem, &ConnectionPixmapItem::selectionChanged, [this, listItem, pixmapItem](bool selected) { + listItem->setSelected(selected); + if (selected) setSelectedConnection(pixmapItem); }); - connect(connectionItem, &ConnectionPixmapItem::connectionItemDoubleClicked, [this, connectionItem] { - emit this->connectionItemDoubleClicked(connectionItem->connection->targetMapName(), map->name); + connect(listItem, &ConnectionsListItem::selected, [this, pixmapItem] { + setSelectedConnection(pixmapItem); }); - // Sync the selection highlight between the list UI and the graphical map connection - connect(connectionItem, &ConnectionPixmapItem::selectionChanged, listItem, &ConnectionsListItem::setSelected); - connect(listItem, &ConnectionsListItem::selected, [this, connectionItem] { - setSelectedConnection(connectionItem); - }); - - // Sync edits to 'offset' between the list UI and the graphical map connection - connect(connectionItem, &ConnectionPixmapItem::connectionMoved, [this, listItem](MapConnection* connection, int offset) { - setConnectionOffset(connection, offset); + // Sync edits to 'offset' between the list UI and the pixmap + connect(connection, &MapConnection::offsetChanged, [this, listItem, pixmapItem](int, int) { listItem->updateUI(); - }); - connect (listItem, &ConnectionsListItem::editedOffset, [this, connectionItem](MapConnection* connection, int offset) { - setConnectionOffset(connection, offset); - updateConnectionItemPos(connectionItem); + updateConnectionPixmapPos(pixmapItem); }); - // Sync edits to 'direction' or 'map' between the list UI and the graphical map connection. - // These are 1-way because the direction and map cannot be edited graphically, only via the list UI. - connect(listItem, &ConnectionsListItem::editedDirection, [this, connectionItem, listItem](MapConnection* connection, QString direction) { - setConnectionDirection(connection, direction); - updateConnectionItem(connectionItem); // TODO: Simplify - listItem->updateUI(); // Changing direction may have changed our offset too - }); - connect(listItem, &ConnectionsListItem::editedMapName, [this, connectionItem](MapConnection* connection, QString mapName) { - setConnectionMap(connection, mapName); - updateConnectionItem(connectionItem); + // Sync edits to 'direction' between the list UI and the pixmap + connect(connection, &MapConnection::directionChanged, [this, listItem, pixmapItem](QString before, QString after) { + if (MapConnection::isHorizontal(before) != MapConnection::isHorizontal(after) + || MapConnection::isVertical(before) != MapConnection::isVertical(after)) { + // If the direction has changed between vertical/horizontal then the old offset may not make sense, so we reset it + pixmapItem->connection->setOffset(0); + } + listItem->updateUI(); + updateConnectionPixmap(pixmapItem); }); - // Sync deleting the map connection - connect(connectionItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater); - connect(listItem, &ConnectionsListItem::removed, [this, connectionItem] { - // 'Remove' button has been clicked. Delete the pixmap. - // The list item will be deleted via the above connection to the pixmap's 'destroyed' signal. - removeConnectionItem(connectionItem); + // Sync edits to 'map' between the list UI and the pixmap + connect(connection, &MapConnection::targetMapNameChanged, [this, listItem, pixmapItem](QString, QString) { + listItem->updateUI(); + updateConnectionPixmap(pixmapItem); }); - // Double clicking the list item opens the connected map - connect(listItem, &ConnectionsListItem::doubleClicked, [this](QString connectedMapName) { - emit connectionItemDoubleClicked(connectedMapName, map->name); + connect(connection, &MapConnection::hostMapNameChanged, [this, pixmapItem](QString before, QString) { + // A connection was removed from the current map by reassignment. We're deleting the UI elements, + // but not the associated connection (it still exists, but now it belongs to a different map). + // At the moment this is only possible when updating the mirror for a map connected to itself, + // users have no other way (or need) to relocate a map connection like this. + if (before == this->map->name) + removeConnectionPixmap(pixmapItem); }); - connection_items.append(connectionItem); + // User manually deleting connection via the remove button + connect(listItem, &ConnectionsListItem::removed, [this](MapConnection* connection) { removeConnection(connection); }); + + // Sync removing the UI elements. They may be removed when deleting connections or when displaying a new map. + connect(connection, &MapConnection::destroyed, [this, pixmapItem] { removeConnectionPixmap(pixmapItem); }); + connect(pixmapItem, &ConnectionPixmapItem::destroyed, listItem, &ConnectionsListItem::deleteLater); + + connection_items.append(pixmapItem); } @@ -821,33 +842,38 @@ void Editor::addConnection(MapConnection * connection, bool addMirror) { if (!connection) return; - if (addMirror) - addMirroredConnection(connection); + if (addMirror) addConnection(connection->createMirror(), false); Map *map = project->getMap(connection->hostMapName()); - if (!map) - return; - - map->connections.append(connection); - if (map == this->map) { - // Adding a connection to the current map, we need to display it visually. - if (connection->direction() == "dive" || connection->direction() == "emerge") { - createDiveEmergeConnection(connection); - } else { - createConnectionItem(connection); - } - } - emit editedMapData(map); + if (map) map->addConnection(connection); + // TODO: Edit history } -void Editor::removeConnectionItem(ConnectionPixmapItem* connectionItem) { - if (!connectionItem) +void Editor::removeConnection(MapConnection* connection, bool removeMirror) { + if (!connection) return; - int index = connection_items.indexOf(connectionItem); + if (removeMirror) removeConnection(connection->findMirror(), false); + + Map* map = project->getMap(connection->hostMapName()); + if (map) map->removeConnection(connection); + delete connection; + // TODO: Edit history +} + +void Editor::removeSelectedConnection() { + if (selected_connection_item) + removeConnection(selected_connection_item->connection); +} + +void Editor::removeConnectionPixmap(ConnectionPixmapItem* pixmapItem) { + if (!pixmapItem) + return; + + int index = connection_items.indexOf(pixmapItem); if (index >= 0) { connection_items.removeAt(index); - if (connectionItem == selected_connection_item) { + if (pixmapItem == selected_connection_item) { // This was the selected connection, select the next one up in the list. selected_connection_item = nullptr; if (index != 0) index--; @@ -856,117 +882,53 @@ void Editor::removeConnectionItem(ConnectionPixmapItem* connectionItem) { } } - if (connectionItem->scene()) - connectionItem->scene()->removeItem(connectionItem); + if (pixmapItem->scene()) + pixmapItem->scene()->removeItem(pixmapItem); - removeConnection(connectionItem->connection); - - delete connectionItem; -} - -void Editor::removeSelectedConnection() { - removeConnectionItem(selected_connection_item); -} - -void Editor::removeConnection(MapConnection* connection, bool removeMirror) { - if (!connection) - return; - - if (removeMirror) - removeMirroredConnection(connection); - - Map* map = project->getMap(connection->hostMapName()); - if (!map) - return; - - if (map == this->map) { - // The connection to delete is displayed on the currently-opened map, we need to delete it visually as well. - if (connection->direction() == "dive") { - clearDiveMap(); - } else if (connection->direction() == "emerge") { - clearEmergeMap(); - } else { - // Find and delete the matching connection graphics. - // If this was called via removeConnectionItem we rely on - // the item having already been removed from this list. - for (auto item :connection_items) { - if (item->connection == connection) { - item->connection = nullptr; - removeConnectionItem(item); - break; - } - } - } - } - - map->connections.removeOne(connection); - delete connection; - emit editedMapData(map); -} - -MapConnection* Editor::getMirroredConnection(MapConnection* source) { - if (!source || !porymapConfig.mirrorConnectingMaps) - return nullptr; - - // Note: It's possible (and ok) for connectedMap == this->map - Map *connectedMap = project->getMap(source->targetMapName()); - if (!connectedMap) - return nullptr; - - // Find the matching connection in the connected map. - // Note: There is no strict source -> mirror pairing, i.e. we are not guaranteed - // to always get the same MapConnection if there are multiple identical copies. - for (auto connection : connectedMap->connections) { - if (connection != source && connection->isMirror(source)) - return connection; - } - - return nullptr; -} - -void Editor::addMirroredConnection(MapConnection* source) { - if (!source || !porymapConfig.mirrorConnectingMaps) - return; - addConnection(source->createMirror(), false); -} - -void Editor::removeMirroredConnection(MapConnection* source) { - removeConnection(getMirroredConnection(source), false); + delete pixmapItem; } // TODO: Self-connecting a Dive/Emerge map connection will not actually replace any existing Dive/Emerge connection, if there is one. -void Editor::createDiveEmergeConnection(MapConnection* connection) { +void Editor::displayDiveEmergeConnection(MapConnection* connection) { if (!connection) return; - - // Create image of Dive/Emerge map - QPixmap pixmap; - Map *connectedMap = project->getMap(connection->targetMapName()); - if (!connectedMap || connectedMap == this->map) { - // There's no point in rendering a map on top of itself. - // We create an empty image anyway to allow for changes later. - pixmap = QPixmap(); + + // Note: We only support editing 1 Dive and Emerge connection per map. + // In a vanilla game only the first Dive/Emerge connection is considered, so allowing + // users to have multiple is likely to lead to confusion. In case users have changed + // this we won't delete extra diving connections, but we'll only display the first one. + if (connection->direction() == "dive") { + if (dive_map_overlay) return; + } else if (connection->direction() == "emerge") { + if (emerge_map_overlay) return; } else { - pixmap = connectedMap->render(); + // Invalid direction + return; } + + // Create image of Dive/Emerge map (unless we'd be rendering the current map on top of itself) + QPixmap pixmap = (connection->targetMapName() == this->map->name) ? QPixmap() : connection->getPixmap(); QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap); scene->addItem(item); if (connection->direction() == "dive") { - clearDiveMap(); dive_map_overlay = item; const QSignalBlocker blocker(ui->comboBox_DiveMap); ui->comboBox_DiveMap->setCurrentText(connection->targetMapName()); - } else if (connection->direction() == "emerge") { - clearEmergeMap(); + connect(connection, &MapConnection::destroyed, this, &Editor::clearDiveMap); + connect(connection, &MapConnection::hostMapNameChanged, this, &Editor::clearDiveMap); + } else { emerge_map_overlay = item; const QSignalBlocker blocker(ui->comboBox_EmergeMap); ui->comboBox_EmergeMap->setCurrentText(connection->targetMapName()); - } else { - // Shouldn't happen - scene->removeItem(item); - delete item; + connect(connection, &MapConnection::destroyed, this, &Editor::clearEmergeMap); + connect(connection, &MapConnection::hostMapNameChanged, this, &Editor::clearEmergeMap); } + + // Update pixmap if the connected map is swapped + connect(connection, &MapConnection::targetMapNameChanged, [this, item, connection] { + item->setPixmap((connection->targetMapName() == this->map->name) ? QPixmap() : connection->getPixmap()); + }); } void Editor::updateDiveMap(QString mapName) { @@ -978,11 +940,6 @@ void Editor::updateEmergeMap(QString mapName) { } void Editor::setDiveEmergeMapName(QString mapName, QString direction) { - if (!mapName.isEmpty() && !project->mapNamesToMapConstants.contains(mapName)) { - logError(QString("Invalid %1 connection map name: '%2'").arg(direction).arg(mapName)); - return; - } - // Only the first Dive/Emerge map (if present) is considered, as in-game. MapConnection* connection = nullptr; for (MapConnection* conn : map->connections) { @@ -996,13 +953,13 @@ void Editor::setDiveEmergeMapName(QString mapName, QString direction) { // Update existing connection if (mapName.isEmpty()) { removeConnection(connection); + // TODO: Queue up another Dive/Emerge map if there is one } else { - setConnectionMap(connection, mapName); + connection->setTargetMapName(mapName); } } else if (!mapName.isEmpty()) { // Create new connection - connection = new MapConnection(direction, map->name, mapName); - addConnection(connection); + addConnection(new MapConnection(direction, map->name, mapName)); } updateDiveEmergeVisibility(); } @@ -1024,6 +981,7 @@ void Editor::updateDiveEmergeVisibility() { } } +// TODO: Fold into PixmapItem QPoint Editor::calculateConnectionPosition(MapConnection *connection, const QPixmap &pixmap) { const QString direction = connection->direction(); int offset = connection->offset(); @@ -1045,132 +1003,46 @@ QPoint Editor::calculateConnectionPosition(MapConnection *connection, const QPix return QPoint(x, y); } -QPixmap Editor::getConnectionPixmap(MapConnection *connection) { - Map *connectedMap = project->getMap(connection->targetMapName()); - - // connectedMap will be null for MAP_DYNAMIC and any map that fails to load. - // The connection will be editable from the list, but no image will be displayed on the map. - if (!connectedMap) - return QPixmap(); - - return connectedMap->renderConnection(connection->direction(), this->map->layout); -} - -void Editor::updateConnectionItem(ConnectionPixmapItem* connectionItem) { - if (!connectionItem || !connectionItem->connection) +void Editor::updateConnectionPixmap(ConnectionPixmapItem* pixmapItem) { + if (!pixmapItem || !pixmapItem->connection) return; - const QString mapName = connectionItem->connection->targetMapName(); - if (mapName.isEmpty()) - return; + pixmapItem->initialOffset = pixmapItem->connection->offset(); + pixmapItem->basePixmap = pixmapItem->connection->getPixmap(); + QPoint pos = calculateConnectionPosition(pixmapItem->connection, pixmapItem->basePixmap); - if (!project->mapNames.contains(mapName)) { - logError(QString("Invalid map name '%1' specified for connection.").arg(mapName)); - return; - } - - connectionItem->initialOffset = connectionItem->connection->offset(); - connectionItem->basePixmap = getConnectionPixmap(connectionItem->connection); - QPoint pos = calculateConnectionPosition(connectionItem->connection, connectionItem->basePixmap); - - const QSignalBlocker blocker(connectionItem); - connectionItem->setPixmap(connectionItem->basePixmap); - connectionItem->initialX = pos.x(); - connectionItem->initialY = pos.y(); - connectionItem->setX(pos.x()); - connectionItem->setY(pos.y()); - connectionItem->render(); + const QSignalBlocker blocker(pixmapItem); + pixmapItem->setPixmap(pixmapItem->basePixmap); + pixmapItem->initialX = pos.x(); + pixmapItem->initialY = pos.y(); + pixmapItem->setPos(pos); + pixmapItem->render(); maskNonVisibleConnectionTiles(); } -void Editor::updateConnectionItemPos(ConnectionPixmapItem* connectionItem) { - if (!connectionItem || !connectionItem->connection) +void Editor::updateConnectionPixmapPos(ConnectionPixmapItem* pixmapItem) { + if (!pixmapItem || !pixmapItem->connection) return; - const QSignalBlocker blocker(connectionItem); - MapConnection *connection = connectionItem->connection; + const QSignalBlocker blocker(pixmapItem); + MapConnection *connection = pixmapItem->connection; if (MapConnection::isVertical(connection->direction())) { - connectionItem->setX(connectionItem->initialX + (connection->offset() - connectionItem->initialOffset) * 16); + qreal x = pixmapItem->initialX + (connection->offset() - pixmapItem->initialOffset) * 16; + if (x != pixmapItem->x()) pixmapItem->setX(x); } else if (MapConnection::isHorizontal(connection->direction())) { - connectionItem->setY(connectionItem->initialY + (connection->offset() - connectionItem->initialOffset) * 16); + qreal y = pixmapItem->initialY + (connection->offset() - pixmapItem->initialOffset) * 16; + if (y != pixmapItem->y()) pixmapItem->setY(y); } maskNonVisibleConnectionTiles(); } -void Editor::setConnectionOffset(MapConnection *connection, int offset) { - if (!connection || !this->map || connection->offset() == offset || !MapConnection::isCardinal(connection->direction())) - return; - - MapConnection *mirror = getMirroredConnection(connection); - if (mirror) { - mirror->setOffset(-offset); - - Map *connectedMap = project->getMap(mirror->hostMapName()); - if (connectedMap != this->map) { - emit editedMapData(connectedMap); - } else { - // TODO: Remove, this will be handled by connecting to the MapConnection - - // The mirror is displayed on the current map, update its graphics - for (auto item :connection_items) { - if (item->connection == mirror) { - updateConnectionItemPos(item); - break; - } - } - for (auto listItem : ui->scrollAreaContents_ConnectionsList->findChildren()) { - if (listItem->connection == mirror){ - listItem->updateUI(); - break; - } - } - } - } - - connection->setOffset(offset); - emit editedMapData(this->map); - - // TODO: This is likely the source of the visual masking bug while dragging (this happens after the move) - maskNonVisibleConnectionTiles(); -} - -void Editor::setConnectionMap(MapConnection *connection, const QString &mapName) { - if (!connection || !map || connection->targetMapName() == mapName) - return; - - removeMirroredConnection(connection); - connection->setTargetMapName(mapName); - addMirroredConnection(connection); - - emit editedMapData(map); -} - -void Editor::setConnectionDirection(MapConnection *connection, const QString &direction) { - if (!connection || !map || connection->direction() == direction) - return; - - // TODO: Lazy - removeMirroredConnection(connection); - - if (MapConnection::isHorizontal(connection->direction()) != MapConnection::isHorizontal(direction) - || MapConnection::isVertical(connection->direction()) != MapConnection::isVertical(direction)) { - // If the direction has changed between vertical/horizontal then the old offset may not make sense, so we reset it - setConnectionOffset(connection, 0); - } - connection->setDirection(direction); - - addMirroredConnection(connection); - - emit editedMapData(map); -} - -void Editor::setSelectedConnection(ConnectionPixmapItem* connectionItem) { - if (!connectionItem || connectionItem == selected_connection_item) +void Editor::setSelectedConnection(ConnectionPixmapItem* pixmapItem) { + if (!pixmapItem || pixmapItem == selected_connection_item) return; if (selected_connection_item) selected_connection_item->setSelected(false); - selected_connection_item = connectionItem; + selected_connection_item = pixmapItem; selected_connection_item->setSelected(true); } @@ -1185,6 +1057,10 @@ void Editor::setSelectedConnectionFromMap(QString mapName) { } } +void Editor::onMapConnectionDoubleClicked(MapConnection* connection) { + emit openConnectedMap(connection->targetMapName(), connection->hostMapName()); +} + void Editor::onBorderMetatilesChanged() { displayMapBorder(); updateBorderVisibility(); // TODO: Why do we need to call this here @@ -1368,6 +1244,8 @@ bool Editor::setMap(QString map_name) { // multiple times if set again in the future if (map) { map->disconnect(this); + for (auto connection : map->connections) + connection->disconnect(); } if (project) { @@ -1387,6 +1265,7 @@ bool Editor::setMap(QString map_name) { map_ruler->setMapDimensions(QSize(map->getWidth(), map->getHeight())); connect(map, &Map::mapDimensionsChanged, map_ruler, &MapRuler::setMapDimensions); connect(map, &Map::openScriptRequested, this, &Editor::openScript); + connect(map, &Map::connectionAdded, this, &Editor::displayConnection); updateSelectedEvents(); } @@ -1907,23 +1786,8 @@ void Editor::clearEmergeMap() { void Editor::displayMapConnections() { clearMapConnections(); - // Note: We only support editing 1 Dive and Emerge connection per map. - // In a vanilla game only the first Dive/Emerge connection is considered, so allowing - // users to have multiple is likely to lead to confusion. In case users have changed - // this we won't delete extra diving connections, but we'll only display the first one. - // TODO: Move text check to inside createDiveEmergeConnection? - for (MapConnection *connection : map->connections) { - if (connection->direction() == "dive") { - if (ui->comboBox_DiveMap->currentText().isEmpty()) - createDiveEmergeConnection(connection); - } else if (connection->direction() == "emerge") { - if (ui->comboBox_EmergeMap->currentText().isEmpty()) - createDiveEmergeConnection(connection); - } else { - // We allow any unknown direction names here. They'll be editable from the list menu. - createConnectionItem(connection); - } - } + for (MapConnection *connection : map->connections) + displayConnection(connection); if (!connection_items.empty()) setSelectedConnection(connection_items.first()); @@ -2001,7 +1865,7 @@ void Editor::updateMapConnections() { for (auto item : connection_items) { if (!item->connection) continue; - item->basePixmap = getConnectionPixmap(item->connection); + item->basePixmap = item->connection->getPixmap(); item->render(); } @@ -2107,7 +1971,7 @@ void Editor::updateBorderVisibility() { void Editor::updateCustomMapHeaderValues(QTableWidget *table) { map->customHeaders = CustomAttributesTable::getAttributes(table); - emit editedMapData(map); + map->modify(); } Tileset* Editor::getCurrentMapPrimaryTileset() diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 588547f2..e4728465 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -306,12 +306,11 @@ void MainWindow::checkForUpdates(bool) {} void MainWindow::initEditor() { this->editor = new Editor(ui); connect(this->editor, &Editor::objectsChanged, this, &MainWindow::updateObjects); - connect(this->editor, &Editor::connectionItemDoubleClicked, this, &MainWindow::onConnectionItemDoubleClicked); + connect(this->editor, &Editor::openConnectedMap, this, &MainWindow::onOpenConnectedMap); connect(this->editor, &Editor::warpEventDoubleClicked, this, &MainWindow::openWarpMap); connect(this->editor, &Editor::currentMetatilesSelectionChanged, this, &MainWindow::currentMetatilesSelectionChanged); connect(this->editor, &Editor::wildMonDataChanged, this, &MainWindow::onWildMonDataChanged); connect(this->editor, &Editor::mapRulerStatusChanged, this, &MainWindow::onMapRulerStatusChanged); - connect(this->editor, &Editor::editedMapData, [this](Map* map) { this->markMapEdited(map); }); connect(this->editor, &Editor::tilesetUpdated, this, &Scripting::cb_TilesetUpdated); connect(ui->toolButton_Open_Scripts, &QToolButton::pressed, this->editor, &Editor::openMapScripts); connect(ui->actionOpen_Project_in_Text_Editor, &QAction::triggered, this->editor, &Editor::openProjectInTextEditor); @@ -569,15 +568,17 @@ bool MainWindow::openProject(QString dir, bool initial) { Scripting::init(this); // Create the project - this->editor->project = new Project(this); - QObject::connect(this->editor->project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered); - QObject::connect(this->editor->project, &Project::mapCacheCleared, this, &MainWindow::onMapCacheCleared); - QObject::connect(this->editor->project, &Project::uncheckMonitorFilesAction, [this]() { + auto project = new Project(this); + project->set_root(dir); + QObject::connect(project, &Project::reloadProject, this, &MainWindow::on_action_Reload_Project_triggered); + QObject::connect(project, &Project::mapCacheCleared, this, &MainWindow::onMapCacheCleared); + QObject::connect(project, &Project::mapLoaded, this, &MainWindow::onMapLoaded); + QObject::connect(project, &Project::uncheckMonitorFilesAction, [this]() { porymapConfig.monitorFiles = false; if (this->preferenceEditor) this->preferenceEditor->updateFields(); }); - this->editor->project->set_root(dir); + this->editor->setProject(project); // Make sure project looks reasonable before attempting to load it if (!checkProjectSanity()) { @@ -805,7 +806,6 @@ bool MainWindow::setMap(QString map_name, bool scrollTreeView) { showWindowTitle(); connect(editor->map, &Map::mapNeedsRedrawing, this, &MainWindow::onMapNeedsRedrawing); - connect(editor->map, &Map::modified, [this](){ this->markMapEdited(); }); userConfig.recentMap = map_name; updateMapList(); @@ -2526,7 +2526,7 @@ void MainWindow::clickToolButtonFromEditMode(QString editMode) { } } -void MainWindow::onConnectionItemDoubleClicked(QString mapName, QString fromMapName) { +void MainWindow::onOpenConnectedMap(QString mapName, QString fromMapName) { if (mapName != fromMapName && userSetMap(mapName, true)) editor->setSelectedConnectionFromMap(fromMapName); } @@ -2539,6 +2539,10 @@ void MainWindow::onMapCacheCleared() { editor->map = nullptr; } +void MainWindow::onMapLoaded(Map *map) { + connect(map, &Map::modified, [this, map] { this->markMapEdited(map); }); +} + void MainWindow::onTilesetsSaved(QString primaryTilesetLabel, QString secondaryTilesetLabel) { // If saved tilesets are currently in-use, update them and redraw // Otherwise overwrite the cache for the saved tileset diff --git a/src/project.cpp b/src/project.cpp index 60d8ef87..d60c8a09 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -186,10 +186,13 @@ Map* Project::loadMap(QString map_name) { map->setName(map_name); } - if (!(loadMapData(map) && loadMapLayout(map))) + if (!(loadMapData(map) && loadMapLayout(map))){ + delete map; return nullptr; + } mapCache.insert(map_name, map); + emit mapLoaded(map); return map; } diff --git a/src/ui/connectionpixmapitem.cpp b/src/ui/connectionpixmapitem.cpp index 14a46358..c98d3ba7 100644 --- a/src/ui/connectionpixmapitem.cpp +++ b/src/ui/connectionpixmapitem.cpp @@ -51,8 +51,7 @@ QVariant ConnectionPixmapItem::itemChange(GraphicsItemChange change, const QVari y = this->initialY; } - if (this->connection->offset() != newOffset) - emit connectionMoved(this->connection, newOffset); + this->connection->setOffset(newOffset); return QPointF(x, y); } else { @@ -84,5 +83,5 @@ void ConnectionPixmapItem::mousePressEvent(QGraphicsSceneMouseEvent *) { } void ConnectionPixmapItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *) { - emit connectionItemDoubleClicked(this); + emit connectionItemDoubleClicked(this->connection); } diff --git a/src/ui/connectionslistitem.cpp b/src/ui/connectionslistitem.cpp index b735559a..fd2532bf 100644 --- a/src/ui/connectionslistitem.cpp +++ b/src/ui/connectionslistitem.cpp @@ -56,27 +56,25 @@ void ConnectionsListItem::mousePressEvent(QMouseEvent *) { } void ConnectionsListItem::mouseDoubleClickEvent(QMouseEvent *) { - emit doubleClicked(this->connection->targetMapName()); + emit doubleClicked(this->connection); } void ConnectionsListItem::on_comboBox_Direction_currentTextChanged(const QString &direction) { this->setSelected(true); - if (this->connection->direction() != direction) - emit this->editedDirection(this->connection, direction); + this->connection->setDirection(direction); } void ConnectionsListItem::on_comboBox_Map_currentTextChanged(const QString &mapName) { this->setSelected(true); - if (ui->comboBox_Map->findText(mapName) >= 0 && this->connection->targetMapName() != mapName) - emit this->editedMapName(this->connection, mapName); + if (ui->comboBox_Map->findText(mapName) >= 0) + this->connection->setTargetMapName(mapName); } void ConnectionsListItem::on_spinBox_Offset_valueChanged(int offset) { this->setSelected(true); - if (this->connection->offset() != offset) - emit editedOffset(this->connection, offset); + this->connection->setOffset(offset); } void ConnectionsListItem::on_button_Delete_clicked() { - emit this->removed(); + emit this->removed(this->connection); }