From 22920966221c71a2e1bb8e4264e2cb6a72ec42ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Tue, 16 Jan 2024 16:18:09 +0100 Subject: [PATCH] Perform changes to the world through the undo commands This also makes sure the UI adjusts immediately to the changes, since the WorldManager::worldsChanged signal is emitted. Also added some checks on the function parameters. --- src/tiled/abstractworldtool.cpp | 68 +-------------------- src/tiled/changeworld.cpp | 102 ++++++++++++++++++++++++++++++++ src/tiled/changeworld.h | 71 ++++++++++++++++++++++ src/tiled/editableworld.cpp | 75 ++++++++++++----------- src/tiled/editableworld.h | 25 +++++--- src/tiled/libtilededitor.qbs | 2 + src/tiled/worldmovemaptool.cpp | 38 +----------- src/tiled/worldmovemaptool.h | 2 - 8 files changed, 236 insertions(+), 147 deletions(-) create mode 100644 src/tiled/changeworld.cpp create mode 100644 src/tiled/changeworld.h diff --git a/src/tiled/abstractworldtool.cpp b/src/tiled/abstractworldtool.cpp index 2c1fcc1fbb..39a9fb0181 100644 --- a/src/tiled/abstractworldtool.cpp +++ b/src/tiled/abstractworldtool.cpp @@ -20,6 +20,7 @@ #include "abstractworldtool.h" #include "actionmanager.h" +#include "changeworld.h" #include "documentmanager.h" #include "mainwindow.h" #include "map.h" @@ -43,73 +44,6 @@ namespace Tiled { -class AddMapCommand : public QUndoCommand -{ -public: - AddMapCommand(const QString &worldName, const QString &mapName, const QRect &rect) - : QUndoCommand(QCoreApplication::translate("Undo Commands", "Add Map to World")) - , mWorldName(worldName) - , mMapName(mapName) - , mRect(rect) - { - } - - void undo() override - { - WorldManager::instance().removeMap(mMapName); - } - - void redo() override - { - WorldManager::instance().addMap(mWorldName, mMapName, mRect); - } - -private: - QString mWorldName; - QString mMapName; - QRect mRect; -}; - -class RemoveMapCommand : public QUndoCommand -{ -public: - RemoveMapCommand(const QString &mapName) - : QUndoCommand(QCoreApplication::translate("Undo Commands", "Remove Map from World")) - , mMapName(mapName) - { - const WorldManager &manager = WorldManager::instance(); - const World *world = manager.worldForMap(mMapName); - mPreviousRect = world->mapRect(mMapName); - mWorldName = world->fileName; - } - - void undo() override - { - WorldManager::instance().addMap(mWorldName, mMapName, mPreviousRect); - } - - void redo() override - { - // ensure we're switching to a different map in case the current map is removed - DocumentManager *manager = DocumentManager::instance(); - if (manager->currentDocument() && manager->currentDocument()->fileName() == mMapName) { - const World *world = WorldManager::instance().worldForMap(mMapName); - for (World::MapEntry &entry : world->allMaps()) - if (entry.fileName != mMapName) { - manager->switchToDocument(entry.fileName); - break; - } - } - WorldManager::instance().removeMap(mMapName); - } - -private: - QString mWorldName; - QString mMapName; - QRect mPreviousRect; -}; - - AbstractWorldTool::AbstractWorldTool(Id id, const QString &name, const QIcon &icon, diff --git a/src/tiled/changeworld.cpp b/src/tiled/changeworld.cpp new file mode 100644 index 0000000000..2af453b235 --- /dev/null +++ b/src/tiled/changeworld.cpp @@ -0,0 +1,102 @@ +/* + * changeworld.cpp + * Copyright 2019, Nils Kuebler + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "changeworld.h" + +#include "documentmanager.h" +#include "worldmanager.h" + +#include + +namespace Tiled { + +AddMapCommand::AddMapCommand(const QString &worldName, const QString &mapName, const QRect &rect) + : QUndoCommand(QCoreApplication::translate("Undo Commands", "Add Map to World")) + , mWorldName(worldName) + , mMapName(mapName) + , mRect(rect) +{ +} + +void AddMapCommand::undo() +{ + WorldManager::instance().removeMap(mMapName); +} + +void AddMapCommand::redo() +{ + qDebug() << Q_FUNC_INFO; + WorldManager::instance().addMap(mWorldName, mMapName, mRect); +} + + +RemoveMapCommand::RemoveMapCommand(const QString &mapName) + : QUndoCommand(QCoreApplication::translate("Undo Commands", "Remove Map from World")) + , mMapName(mapName) +{ + const WorldManager &manager = WorldManager::instance(); + const World *world = manager.worldForMap(mMapName); + mPreviousRect = world->mapRect(mMapName); + mWorldName = world->fileName; +} + +void RemoveMapCommand::undo() +{ + WorldManager::instance().addMap(mWorldName, mMapName, mPreviousRect); +} + +void RemoveMapCommand::redo() +{ + // ensure we're switching to a different map in case the current map is removed + DocumentManager *manager = DocumentManager::instance(); + if (manager->currentDocument() && manager->currentDocument()->fileName() == mMapName) { + const World *world = WorldManager::instance().worldForMap(mMapName); + for (const World::MapEntry &entry : world->allMaps()) { + if (entry.fileName != mMapName) { + manager->switchToDocument(entry.fileName); + break; + } + } + } + WorldManager::instance().removeMap(mMapName); +} + + +SetMapRectCommand::SetMapRectCommand(const QString &mapName, QRect rect) + : QUndoCommand(QCoreApplication::translate("Undo Commands", "Move Map")) + , mMapName(mapName) + , mRect(rect) +{ + const WorldManager &manager = WorldManager::instance(); + mPreviousRect = manager.worldForMap(mMapName)->mapRect(mMapName); +} + +void SetMapRectCommand::undo() +{ + WorldManager::instance().setMapRect(mMapName, mPreviousRect); +} + +void SetMapRectCommand::redo() +{ + WorldManager::instance().setMapRect(mMapName, mRect); +} + +} // namespace Tiled diff --git a/src/tiled/changeworld.h b/src/tiled/changeworld.h new file mode 100644 index 0000000000..9dfe0ae24f --- /dev/null +++ b/src/tiled/changeworld.h @@ -0,0 +1,71 @@ +/* + * changeworld.h + * Copyright 2019, Nils Kuebler + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program 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 General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include +#include + +namespace Tiled { + +class AddMapCommand : public QUndoCommand +{ +public: + AddMapCommand(const QString &worldName, const QString &mapName, const QRect &rect); + + void undo() override; + void redo() override; + +private: + QString mWorldName; + QString mMapName; + QRect mRect; +}; + +class RemoveMapCommand : public QUndoCommand +{ +public: + RemoveMapCommand(const QString &mapName); + + void undo() override; + void redo() override; + +private: + QString mWorldName; + QString mMapName; + QRect mPreviousRect; +}; + +class SetMapRectCommand : public QUndoCommand +{ +public: + SetMapRectCommand(const QString &mapName, QRect rect); + + void undo() override; + void redo() override; + +private: + QString mMapName; + QRect mRect; + QRect mPreviousRect; +}; + +} // namespace Tiled diff --git a/src/tiled/editableworld.cpp b/src/tiled/editableworld.cpp index 2d062ab437..904dae8ef4 100644 --- a/src/tiled/editableworld.cpp +++ b/src/tiled/editableworld.cpp @@ -20,16 +20,13 @@ */ #include "editableworld.h" -#include "worlddocument.h" -#include "worldmanager.h" -namespace Tiled { +#include "changeworld.h" +#include "scriptmanager.h" -ScriptWorld::ScriptWorld(World *world) - : Object(WorldType) - , world(world) -{ -} +#include + +namespace Tiled { EditableWorld::EditableWorld(WorldDocument *worldDocument, QObject *parent) : EditableAsset(worldDocument, nullptr, parent) @@ -38,22 +35,6 @@ EditableWorld::EditableWorld(WorldDocument *worldDocument, QObject *parent) setObject(&mWorldObject); } - -ScriptWorldMapEntry::ScriptWorldMapEntry(World::MapEntry mapEntry) -{ - mMapEntry = mapEntry; -} - -QString ScriptWorldMapEntry::fileName() const -{ - return mMapEntry.fileName; -} - -QRect ScriptWorldMapEntry::rect() const -{ - return mMapEntry.rect; -} - QString EditableWorld::displayName() const { return world()->displayName(); @@ -64,18 +45,18 @@ bool EditableWorld::containsMap(const QString &fileName) return world()->containsMap(fileName); } -QVector EditableWorld::allMaps() const +QList EditableWorld::maps() const { - QVector maps; - for (auto &entry : world()->allMaps()) + QList maps; + for (const auto &entry : std::as_const(world()->maps)) maps.append(new ScriptWorldMapEntry(entry)); return maps; } -QVector EditableWorld::mapsInRect(const QRect &rect) +QList EditableWorld::mapsInRect(const QRect &rect) { - QVector maps; - for (auto &entry : world()->mapsInRect(rect)) + QList maps; + for (const auto &entry : world()->mapsInRect(rect)) maps.append(new ScriptWorldMapEntry(entry)); return maps; } @@ -89,23 +70,49 @@ int EditableWorld::mapIndex(const QString &fileName) const { return mWorldObject.world->mapIndex(fileName); } + void EditableWorld::setMapRect(int mapIndex, const QRect &rect) { - mWorldObject.world->setMapRect(mapIndex, rect); + if (mapIndex < 0 || mapIndex >= mWorldObject.world->maps.size()) { + ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Index out of range")); + return; + } + + const QString &fileName = mWorldObject.world->maps.at(mapIndex).fileName; + document()->undoStack()->push(new SetMapRectCommand(fileName, rect)); } -void EditableWorld::addMap(const QString &fileName, const QRect &rect) + +void EditableWorld::addMap(const QString &mapFileName, const QRect &rect) { - mWorldObject.world->addMap(fileName, rect); + if (mapFileName.isEmpty()) { + ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Invalid argument")); + return; + } + + if (WorldManager::instance().worldForMap(mapFileName)) { + ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Map is already part of a loaded world")); + return; + } + + document()->undoStack()->push(new AddMapCommand(fileName(), mapFileName, rect)); } + void EditableWorld::removeMap(int mapIndex) { - mWorldObject.world->removeMap(mapIndex); + if (mapIndex < 0 || mapIndex >= mWorldObject.world->maps.size()) { + ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Index out of range")); + return; + } + + const QString &fileName = mWorldObject.world->maps.at(mapIndex).fileName; + document()->undoStack()->push(new RemoveMapCommand(fileName)); } bool EditableWorld::save() { return WorldManager::instance().saveWorld(mWorldObject.world->fileName); } + QSharedPointer EditableWorld::createDocument() { // We don't currently support opening a world in its own tab, which this diff --git a/src/tiled/editableworld.h b/src/tiled/editableworld.h index f9a809eef6..6794f2359d 100644 --- a/src/tiled/editableworld.h +++ b/src/tiled/editableworld.h @@ -39,9 +39,12 @@ class ScriptWorldMapEntry : public QObject Q_PROPERTY(QRect rect READ rect CONSTANT) public: - ScriptWorldMapEntry(World::MapEntry mapEntry); - QString fileName() const; - QRect rect() const; + ScriptWorldMapEntry(World::MapEntry mapEntry) + : mMapEntry(mapEntry) + {} + + const QString &fileName() const { return mMapEntry.fileName; } + QRect rect() const { return mMapEntry.rect; } private: World::MapEntry mMapEntry; @@ -54,7 +57,10 @@ class ScriptWorldMapEntry : public QObject class ScriptWorld : public Object { public: - ScriptWorld(World *world); + ScriptWorld(World *world) + : Object(WorldType) + , world(world) + {} World *world; }; @@ -66,7 +72,7 @@ class EditableWorld final : public EditableAsset { Q_OBJECT Q_PROPERTY(QString displayName READ displayName) - Q_PROPERTY(QVector allMaps READ allMaps) + Q_PROPERTY(QList maps READ maps) public: EditableWorld(WorldDocument *worldDocument, QObject *parent = nullptr); @@ -76,16 +82,17 @@ class EditableWorld final : public EditableAsset World *world() const; QString displayName() const; - Q_INVOKABLE QVector allMaps() const; - Q_INVOKABLE QVector mapsInRect(const QRect &rect); + + Q_INVOKABLE QList maps() const; + Q_INVOKABLE QList mapsInRect(const QRect &rect); Q_INVOKABLE bool containsMap(const QString &fileName); Q_INVOKABLE int mapIndex(const QString &fileName) const; Q_INVOKABLE void setMapRect(int mapIndex, const QRect &rect); - Q_INVOKABLE void addMap(const QString &fileName, const QRect &rect); + Q_INVOKABLE void addMap(const QString &mapFileName, const QRect &rect); Q_INVOKABLE void removeMap(int mapIndex); Q_INVOKABLE bool save(); - QSharedPointer createDocument() override; + QSharedPointer createDocument() override; private: ScriptWorld mWorldObject; diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index 34f0af061a..16c1efefbe 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -160,6 +160,8 @@ DynamicLibrary { "changewangcolordata.h", "changewangsetdata.cpp", "changewangsetdata.h", + "changeworld.cpp", + "changeworld.h", "clipboardmanager.cpp", "clipboardmanager.h", "colorbutton.cpp", diff --git a/src/tiled/worldmovemaptool.cpp b/src/tiled/worldmovemaptool.cpp index 7f91bb5d9e..7f96b9cf29 100644 --- a/src/tiled/worldmovemaptool.cpp +++ b/src/tiled/worldmovemaptool.cpp @@ -18,10 +18,10 @@ * this program. If not, see . */ -#include "actionmanager.h" -#include "changeevents.h" +#include "worldmovemaptool.h" + +#include "changeworld.h" #include "documentmanager.h" -#include "geometry.h" #include "layer.h" #include "map.h" #include "mapdocument.h" @@ -29,10 +29,8 @@ #include "mapscene.h" #include "mapview.h" #include "preferences.h" -#include "snaphelper.h" #include "toolmanager.h" #include "utils.h" -#include "worldmovemaptool.h" #include "worldmanager.h" #include "zoomable.h" @@ -43,42 +41,12 @@ #include #include -#include #include using namespace Tiled; namespace Tiled { -class SetMapRectCommand : public QUndoCommand -{ -public: - SetMapRectCommand(const QString &mapName, QRect rect) - : QUndoCommand(QCoreApplication::translate("Undo Commands", "Move Map")) - , mMapName(mapName) - , mRect(rect) - { - const WorldManager &manager = WorldManager::instance(); - mPreviousRect = manager.worldForMap(mMapName)->mapRect(mMapName); - } - - void undo() override - { - WorldManager::instance().setMapRect(mMapName, mPreviousRect); - } - - void redo() override - { - WorldManager::instance().setMapRect(mMapName, mRect); - } - -private: - QString mMapName; - QRect mRect; - QRect mPreviousRect; -}; - - WorldMoveMapTool::WorldMoveMapTool(QObject *parent) : AbstractWorldTool("WorldMoveMapTool", tr("World Tool"), diff --git a/src/tiled/worldmovemaptool.h b/src/tiled/worldmovemaptool.h index d1a72fcbc9..05c444b472 100644 --- a/src/tiled/worldmovemaptool.h +++ b/src/tiled/worldmovemaptool.h @@ -22,8 +22,6 @@ #include "abstractworldtool.h" -#include - namespace Tiled { class MapItem;