diff --git a/include/core/events.h b/include/core/events.h index 9da9b531..76a58dcf 100644 --- a/include/core/events.h +++ b/include/core/events.h @@ -34,15 +34,6 @@ class HiddenItemEvent; class SecretBaseEvent; class HealLocationEvent; -class EventVisitor { -public: - virtual void nothing() { } - virtual void visitObject(ObjectEvent *) = 0; - virtual void visitTrigger(TriggerEvent *) = 0; - virtual void visitSign(SignEvent *) = 0; -}; - - /// /// Event base class -- purely virtual /// @@ -121,8 +112,6 @@ public: void modify(); - virtual void accept(EventVisitor *) { } - void setX(int newX) { this->x = newX; } void setY(int newY) { this->y = newY; } void setZ(int newZ) { this->elevation = newZ; } @@ -150,6 +139,8 @@ public: virtual QSet getExpectedFields() = 0; + virtual QStringList getScripts() const { return QStringList(); } + QJsonObject getCustomAttributes() const { return this->customAttributes; } void setCustomAttributes(const QJsonObject &newCustomAttributes) { this->customAttributes = newCustomAttributes; } @@ -222,8 +213,6 @@ public: virtual Event *duplicate() const override; - virtual void accept(EventVisitor *visitor) override { visitor->visitObject(this); } - virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; @@ -233,6 +222,8 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScript()}; } + virtual QPixmap loadPixmap(Project *project) override; void setGfx(QString newGfx) { this->gfx = newGfx; } @@ -392,8 +383,6 @@ public: virtual Event *duplicate() const override; - virtual void accept(EventVisitor *visitor) override { visitor->visitTrigger(this); } - virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; @@ -403,6 +392,8 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScriptLabel()}; } + void setScriptVar(QString newScriptVar) { this->scriptVar = newScriptVar; } QString getScriptVar() const { return this->scriptVar; } @@ -490,8 +481,6 @@ public: virtual Event *duplicate() const override; - virtual void accept(EventVisitor *visitor) override { visitor->visitSign(this); } - virtual EventFrame *createEventFrame() override; virtual OrderedJson::object buildEventJson(Project *project) override; @@ -501,6 +490,8 @@ public: virtual QSet getExpectedFields() override; + virtual QStringList getScripts() const override { return {getScriptLabel()}; } + void setFacingDirection(QString newFacingDirection) { this->facingDirection = newFacingDirection; } QString getFacingDirection() const { return this->facingDirection; } @@ -633,21 +624,4 @@ inline uint qHash(const Event::Group &key, uint seed = 0) { return qHash(static_cast(key), seed); } - -/// -/// Keeps track of scripts -/// -class ScriptTracker : public EventVisitor { -public: - virtual void visitObject(ObjectEvent *object) override { this->scripts << object->getScript(); }; - virtual void visitTrigger(TriggerEvent *trigger) override { this->scripts << trigger->getScriptLabel(); }; - virtual void visitSign(SignEvent *sign) override { this->scripts << sign->getScriptLabel(); }; - - QStringList getScripts() const { return this->scripts; } - -private: - QStringList scripts; -}; - - #endif // EVENTS_H diff --git a/include/core/map.h b/include/core/map.h index 84791fa3..2ba172ce 100644 --- a/include/core/map.h +++ b/include/core/map.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #define DEFAULT_BORDER_WIDTH 2 @@ -56,7 +57,7 @@ public: MapHeader* header() const { return m_header; } void setSharedEventsMap(const QString &sharedEventsMap) { m_sharedEventsMap = sharedEventsMap; } - void setSharedScriptsMap(const QString &sharedScriptsMap) { m_sharedScriptsMap = sharedScriptsMap; } + void setSharedScriptsMap(const QString &sharedScriptsMap); QString sharedEventsMap() const { return m_sharedEventsMap; } QString sharedScriptsMap() const { return m_sharedScriptsMap; } @@ -75,14 +76,15 @@ public: Event* getEvent(Event::Group group, const QString &idName) const; QStringList getEventIdNames(Event::Group group) const; int getNumEvents(Event::Group group = Event::Group::None) const; - QStringList getScriptLabels(Event::Group group = Event::Group::None); - QString getScriptsFilePath() const; - void openScript(QString label); void removeEvent(Event *); void addEvent(Event *); int getIndexOfEvent(Event *) const; bool hasEvent(Event *) const; + QStringList getScriptLabels(Event::Group group = Event::Group::None); + QString getScriptsFilePath() const; + void openScript(const QString &label); + void deleteConnections(); QList getConnections() const { return m_connections; } MapConnection* getConnection(const QString &direction) const; @@ -108,7 +110,7 @@ private: QString m_sharedEventsMap = ""; QString m_sharedScriptsMap = ""; - QStringList m_scriptsFileLabels; + QStringList m_scriptLabels; QJsonObject m_customAttributes; MapHeader *m_header = nullptr; @@ -131,10 +133,14 @@ private: QList m_connections; QSet m_ownedConnections; - QUndoStack *m_editHistory = nullptr; + QPointer m_editHistory; + QPointer m_scriptFileWatcher; + + void invalidateScripts(); signals: void modified(); + void scriptsModified(); void mapDimensionsChanged(const QSize &size); void openScriptRequested(QString label); void connectionAdded(MapConnection*); diff --git a/include/ui/eventframes.h b/include/ui/eventframes.h index 09eae50b..3858e54e 100644 --- a/include/ui/eventframes.h +++ b/include/ui/eventframes.h @@ -56,9 +56,11 @@ protected: bool populated = false; bool initialized = false; bool connected = false; + QPointer project; void populateDropdown(NoScrollComboBox * combo, const QStringList &items); void populateScriptDropdown(NoScrollComboBox * combo, Project * project); + void populateMapNameDropdown(NoScrollComboBox * combo, Project * project); void populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group); private: diff --git a/src/core/events.cpp b/src/core/events.cpp index 22315211..ce376c44 100644 --- a/src/core/events.cpp +++ b/src/core/events.cpp @@ -114,7 +114,7 @@ QString Event::typeToString(Event::Type type) { } QPixmap Event::loadPixmap(Project *project) { - this->pixmap = project->getEventPixmap(this->getEventGroup()); + this->pixmap = project ? project->getEventPixmap(this->getEventGroup()) : QPixmap(); this->usesDefaultPixmap = true; return this->pixmap; } @@ -226,7 +226,7 @@ QSet ObjectEvent::getExpectedFields() { } QPixmap ObjectEvent::loadPixmap(Project *project) { - this->pixmap = project->getEventPixmap(this->gfx, this->movement); + this->pixmap = project ? project->getEventPixmap(this->gfx, this->movement) : QPixmap(); if (!this->pixmap.isNull()) { this->usesDefaultPixmap = false; return this->pixmap; @@ -316,7 +316,7 @@ QSet CloneObjectEvent::getExpectedFields() { QPixmap CloneObjectEvent::loadPixmap(Project *project) { // Try to get the targeted object to clone - Map *clonedMap = project->loadMap(this->targetMap); + Map *clonedMap = project ? project->loadMap(this->targetMap) : nullptr; Event *clonedEvent = clonedMap ? clonedMap->getEvent(Event::Group::Object, this->targetID) : nullptr; if (clonedEvent && clonedEvent->getEventType() == Event::Type::Object) { @@ -324,10 +324,13 @@ QPixmap CloneObjectEvent::loadPixmap(Project *project) { ObjectEvent *clonedObject = dynamic_cast(clonedEvent); this->gfx = clonedObject->getGfx(); this->movement = clonedObject->getMovement(); - } else { + } else if (project) { // Invalid object specified, use default graphics data (as would be shown in-game) this->gfx = project->gfxDefines.key(0, "0"); this->movement = project->movementTypes.value(0, "0"); + } else { + this->gfx = "0"; + this->movement = "0"; } return ObjectEvent::loadPixmap(project); } diff --git a/src/core/map.cpp b/src/core/map.cpp index 3cd087e1..c1905f0a 100644 --- a/src/core/map.cpp +++ b/src/core/map.cpp @@ -14,6 +14,10 @@ Map::Map(QObject *parent) : QObject(parent) { m_editHistory = new QUndoStack(this); + + m_scriptFileWatcher = new QFileSystemWatcher(this); + connect(m_scriptFileWatcher, &QFileSystemWatcher::fileChanged, this, &Map::invalidateScripts); + resetEvents(); m_header = new MapHeader(this); @@ -120,35 +124,40 @@ QPixmap Map::renderConnection(const QString &direction, Layout * fromLayout) { return connectionPixmap.copy(bounds.x() * 16, bounds.y() * 16, bounds.width() * 16, bounds.height() * 16); } -void Map::openScript(QString label) { +void Map::openScript(const QString &label) { emit openScriptRequested(label); } +void Map::setSharedScriptsMap(const QString &sharedScriptsMap) { + if (m_sharedScriptsMap == sharedScriptsMap) + return; + m_sharedScriptsMap = sharedScriptsMap; + invalidateScripts(); +} + +void Map::invalidateScripts() { + m_scriptsLoaded = false; + emit scriptsModified(); +} + QStringList Map::getScriptLabels(Event::Group group) { if (!m_scriptsLoaded) { - m_scriptsFileLabels = ParseUtil::getGlobalScriptLabels(getScriptsFilePath()); + const QString scriptsFilePath = getScriptsFilePath(); + m_scriptLabels = ParseUtil::getGlobalScriptLabels(scriptsFilePath); m_scriptsLoaded = true; + + // Track the scripts file for changes. Path may have changed, so stop tracking old files. + m_scriptFileWatcher->removePaths(m_scriptFileWatcher->files()); + m_scriptFileWatcher->addPath(scriptsFilePath); } - QStringList scriptLabels; + QStringList scriptLabels = m_scriptLabels; - // Get script labels currently in-use by the map's events - if (group == Event::Group::None) { - ScriptTracker scriptTracker; - for (const auto &event : getEvents()) { - event->accept(&scriptTracker); - } - scriptLabels = scriptTracker.getScripts(); - } else { - ScriptTracker scriptTracker; - for (const auto &event : m_events.value(group)) { - event->accept(&scriptTracker); - } - scriptLabels = scriptTracker.getScripts(); + // Add script labels currently in-use by the map's events + for (const auto &event : getEvents(group)) { + scriptLabels.append(event->getScripts()); } - // Add labels from the map's scripts file - scriptLabels.append(m_scriptsFileLabels); scriptLabels.sort(Qt::CaseInsensitive); scriptLabels.removeAll(""); scriptLabels.removeAll("0"); diff --git a/src/ui/eventframes.cpp b/src/ui/eventframes.cpp index 95a31557..b9cdab22 100644 --- a/src/ui/eventframes.cpp +++ b/src/ui/eventframes.cpp @@ -152,8 +152,12 @@ void EventFrame::initialize() { this->label_icon->setPixmap(this->event->getPixmap()); } -void EventFrame::populate(Project *) { +void EventFrame::populate(Project *project) { this->populated = true; + if (this->project && this->project != project) { + this->project->disconnect(this); + } + this->project = project; } void EventFrame::invalidateConnections() { @@ -166,6 +170,10 @@ void EventFrame::invalidateUi() { void EventFrame::invalidateValues() { this->populated = false; + if (this->isVisible()) { + // Repopulate immediately + this->populate(this->project); + } } void EventFrame::setActive(bool active) { @@ -186,14 +194,15 @@ void EventFrame::populateDropdown(NoScrollComboBox * combo, const QStringList &i void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * project) { // The script dropdown and autocomplete are populated with scripts used by the map's events and from its scripts file. - if (!this->event->getMap()) + Map *map = this->event ? this->event->getMap() : nullptr; + if (!map) return; - QStringList scripts = this->event->getMap()->getScriptLabels(this->event->getEventGroup()); + QStringList scripts = map->getScriptLabels(this->event->getEventGroup()); populateDropdown(combo, scripts); // Depending on the settings, the autocomplete may also contain all global scripts. - if (porymapConfig.loadAllEventScripts) { + if (project && porymapConfig.loadAllEventScripts) { project->insertGlobalScriptLabels(scripts); } @@ -209,14 +218,23 @@ void EventFrame::populateScriptDropdown(NoScrollComboBox * combo, Project * proj combo->setCompleter(completer); - // If the project changes the script labels, update the EventFrame. - // TODO: At the moment this only happens when the user changes script settings (i.e. when 'porymapConfig.loadAllEventScripts' changes). - // This should ultimately be connected to a file watcher so that we can also update the dropdown when the scripts file changes. - connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); + // If the script labels change then we need to update the EventFrame. + if (project) connect(project, &Project::eventScriptLabelsRead, this, &EventFrame::invalidateValues, Qt::UniqueConnection); + connect(map, &Map::scriptsModified, this, &EventFrame::invalidateValues, Qt::UniqueConnection); +} + +void EventFrame::populateMapNameDropdown(NoScrollComboBox * combo, Project * project) { + if (!project) + return; + + populateDropdown(combo, project->mapNames); + + // This frame type displays map names, so when a new map is created we need to repopulate it. + connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void EventFrame::populateIdNameDropdown(NoScrollComboBox * combo, Project * project, const QString &mapName, Event::Group group) { - if (!project->mapNames.contains(mapName)) + if (!project || !project->mapNames.contains(mapName)) return; Map *map = project->loadMap(mapName); @@ -325,7 +343,6 @@ void ObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; // local id this->line_edit_local_id->disconnect(); @@ -336,18 +353,18 @@ void ObjectFrame::connectSignals(MainWindow *window) { // sprite update this->combo_sprite->disconnect(); - connect(this->combo_sprite, &QComboBox::currentTextChanged, [this, project](const QString &text) { + connect(this->combo_sprite, &QComboBox::currentTextChanged, [this](const QString &text) { this->object->setGfx(text); - this->object->getPixmapItem()->render(project); + this->object->getPixmapItem()->render(this->project); this->object->modify(); }); connect(this->object->getPixmapItem(), &EventPixmapItem::rendered, this->label_icon, &QLabel::setPixmap); // movement this->combo_movement->disconnect(); - connect(this->combo_movement, &QComboBox::currentTextChanged, [this, project](const QString &text) { + connect(this->combo_movement, &QComboBox::currentTextChanged, [this](const QString &text) { this->object->setMovement(text); - this->object->getPixmapItem()->render(project); + this->object->getPixmapItem()->render(this->project); this->object->modify(); }); @@ -498,7 +515,6 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; // local id this->line_edit_local_id->disconnect(); @@ -512,26 +528,23 @@ void CloneObjectFrame::connectSignals(MainWindow *window) { // target map this->combo_target_map->disconnect(); - connect(this->combo_target_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + connect(this->combo_target_map, &QComboBox::currentTextChanged, [this](const QString &mapName) { this->clone->setTargetMap(mapName); - this->clone->getPixmapItem()->render(project); + this->clone->getPixmapItem()->render(this->project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); - populateIdNameDropdown(this->combo_target_id, project, mapName, Event::Group::Object); + populateIdNameDropdown(this->combo_target_id, this->project, mapName, Event::Group::Object); }); connect(window, &MainWindow::mapOpened, this, &CloneObjectFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); // target id this->combo_target_id->disconnect(); - connect(this->combo_target_id, &QComboBox::currentTextChanged, [this, project](const QString &text) { + connect(this->combo_target_id, &QComboBox::currentTextChanged, [this](const QString &text) { this->clone->setTargetID(text); - this->clone->getPixmapItem()->render(project); + this->clone->getPixmapItem()->render(this->project); this->combo_sprite->setCurrentText(this->clone->getGfx()); this->clone->modify(); }); - - // This frame type displays map names, so when a new map is created we need to repopulate it. - connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void CloneObjectFrame::tryInvalidateIdDropdown(Map *map) { @@ -567,7 +580,7 @@ void CloneObjectFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - populateDropdown(this->combo_target_map, project->mapNames); + populateMapNameDropdown(this->combo_target_map, project); populateIdNameDropdown(this->combo_target_id, project, this->clone->getTargetMap(), Event::Group::Object); } @@ -618,7 +631,6 @@ void WarpFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; // id this->line_edit_id->disconnect(); @@ -629,10 +641,10 @@ void WarpFrame::connectSignals(MainWindow *window) { // dest map this->combo_dest_map->disconnect(); - connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + connect(this->combo_dest_map, &QComboBox::currentTextChanged, [this](const QString &mapName) { this->warp->setDestinationMap(mapName); this->warp->modify(); - populateIdNameDropdown(this->combo_dest_warp, project, mapName, Event::Group::Warp); + populateIdNameDropdown(this->combo_dest_warp, this->project, mapName, Event::Group::Warp); }); connect(window, &MainWindow::mapOpened, this, &WarpFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); @@ -646,9 +658,6 @@ void WarpFrame::connectSignals(MainWindow *window) { // warning this->warning->disconnect(); connect(this->warning, &QPushButton::clicked, window, &MainWindow::onWarpBehaviorWarningClicked); - - // This frame type displays map names, so when a new map is created we need to repopulate it. - connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void WarpFrame::tryInvalidateIdDropdown(Map *map) { @@ -681,7 +690,7 @@ void WarpFrame::populate(Project *project) { const QSignalBlocker blocker(this); EventFrame::populate(project); - populateDropdown(this->combo_dest_map, project->mapNames); + populateMapNameDropdown(this->combo_dest_map, project); populateIdNameDropdown(this->combo_dest_warp, project, this->warp->getDestinationMap(), Event::Group::Warp); } @@ -1102,7 +1111,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) { if (this->connected) return; EventFrame::connectSignals(window); - Project *project = window->editor->project; this->line_edit_id->disconnect(); connect(this->line_edit_id, &QLineEdit::textChanged, [this](const QString &text) { @@ -1111,10 +1119,10 @@ void HealLocationFrame::connectSignals(MainWindow *window) { }); this->combo_respawn_map->disconnect(); - connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this, project](const QString &mapName) { + connect(this->combo_respawn_map, &QComboBox::currentTextChanged, [this](const QString &mapName) { this->healLocation->setRespawnMapName(mapName); this->healLocation->modify(); - populateIdNameDropdown(this->combo_respawn_npc, project, mapName, Event::Group::Object); + populateIdNameDropdown(this->combo_respawn_npc, this->project, mapName, Event::Group::Object); }); connect(window, &MainWindow::mapOpened, this, &HealLocationFrame::tryInvalidateIdDropdown, Qt::UniqueConnection); @@ -1123,9 +1131,6 @@ void HealLocationFrame::connectSignals(MainWindow *window) { this->healLocation->setRespawnNPC(text); this->healLocation->modify(); }); - - // This frame type displays map names, so when a new map is created we need to repopulate it. - connect(project, &Project::mapCreated, this, &EventFrame::invalidateValues, Qt::UniqueConnection); } void HealLocationFrame::tryInvalidateIdDropdown(Map *map) { @@ -1158,7 +1163,7 @@ void HealLocationFrame::populate(Project *project) { EventFrame::populate(project); if (projectConfig.healLocationRespawnDataEnabled) { - populateDropdown(this->combo_respawn_map, project->mapNames); + populateMapNameDropdown(this->combo_respawn_map, project); populateIdNameDropdown(this->combo_respawn_npc, project, this->healLocation->getRespawnMapName(), Event::Group::Object); } }