Skip to content

Commit

Permalink
Reimplemented map reload to happen in-place
Browse files Browse the repository at this point in the history
Maps are now reloaded the same way as tilesets, by swapping the internal
data of the MapDocument rather than replacing the entire document. This
also means undo history is kept, since the reload is added as an
undoable action.

This fixes map reload to also affect instances that are open as part of
a world. Previously, the old version would stay open in the world.
However, maps that are only open as part of a world are not yet watched
for changes.

This is a somewhat risky change since the code was never prepared for
the map and its internal structures to be replaced entirely without any
specific change events. New "DocumentAboutToReload" and
"DocumentReloaded" events were added, which are hopefully handled in all
the right places.

Selected layers and objects, as well as expanded group layers, are
restored after reload by ID.

This change also addresses several issues with reloading tilesets.

Added tiled.assetReloaded signal to the scripting API. Most scripts will
not have to do anything,, but some might.

Fixed enabled state of Reload action, which shouldn't be always enabled
for maps (since we can only reload maps when they have a file name and
we know the file format).
  • Loading branch information
bjorn committed May 24, 2024
1 parent f6d6d4e commit 6d75839
Show file tree
Hide file tree
Showing 40 changed files with 431 additions and 85 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
* Automapping: Added per-input-layer properties for ignoring flip flags (#3803)
* AutoMapping: Always apply output sets with empty index
* Windows: Fixed the support for WebP images (updated to Qt 6.6.1, #3661)
* Fixed issues related to map and tileset reloading
* Fixed possible crash after assigning to tiled.activeAsset
* Fixed the option to resolve properties on export to also resolve class members (#3411, #3315)
* Fixed terrain tool behavior and terrain overlays after changing terrain set type (#3204, #3260)
Expand Down
7 changes: 7 additions & 0 deletions docs/scripting-doc/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4607,6 +4607,13 @@ declare namespace tiled {
*/
export const assetOpened: Signal<Asset>;

/**
* An asset has been reloaded.
*
* @since 1.11
*/
export const assetReloaded: Signal<Asset>;

/**
* An asset is about to be saved. Can be used to make last-minute
* changes.
Expand Down
2 changes: 1 addition & 1 deletion src/libtiled/layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ bool LayerIterator::operator==(const LayerIterator &other) const
* Returns the global layer index for the given \a layer. Obtained by iterating
* the layer's map while incrementing the index until layer is found.
*/
int globalIndex(Layer *layer)
int globalIndex(const Layer *layer)
{
if (!layer)
return -1;
Expand Down
2 changes: 1 addition & 1 deletion src/libtiled/layer.h
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ inline Layer *LayerIterator::operator->() const
}


TILEDSHARED_EXPORT int globalIndex(Layer *layer);
TILEDSHARED_EXPORT int globalIndex(const Layer *layer);
TILEDSHARED_EXPORT Layer *layerAtGlobalIndex(const Map *map, int index);

} // namespace Tiled
Expand Down
6 changes: 6 additions & 0 deletions src/libtiled/wangset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,12 @@ WangSet::WangSet(Tileset *tileset,
setType(type);
}

WangSet::~WangSet()
{
for (auto &color : std::as_const(mColors))
color->mWangSet = nullptr;
}

/**
* Changes the type of this Wang set.
*
Expand Down
1 change: 1 addition & 0 deletions src/libtiled/wangset.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ class TILEDSHARED_EXPORT WangSet : public Object
const QString &name,
Type type,
int imageTileId = -1);
~WangSet();

Tileset *tileset() const;
void setTileset(Tileset *tileset);
Expand Down
34 changes: 29 additions & 5 deletions src/tiled/brokenlinks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "brokenlinks.h"

#include "changeevents.h"
#include "changetileimagesource.h"
#include "documentmanager.h"
#include "fileformat.h"
Expand Down Expand Up @@ -117,6 +118,9 @@ void BrokenLinksModel::setDocument(Document *document)

if (mDocument) {
if (auto mapDocument = qobject_cast<MapDocument*>(mDocument)) {
connect(mDocument, &Document::changed,
this, &BrokenLinksModel::documentChanged);

connect(mapDocument, &MapDocument::tilesetAdded,
this, &BrokenLinksModel::tilesetAdded);
connect(mapDocument, &MapDocument::tilesetRemoved,
Expand All @@ -127,8 +131,6 @@ void BrokenLinksModel::setDocument(Document *document)
for (const SharedTileset &tileset : mapDocument->map()->tilesets())
connectToTileset(tileset);

connect(DocumentManager::instance(), &DocumentManager::templateTilesetReplaced,
this, &BrokenLinksModel::refresh);
} else if (auto tilesetDocument = qobject_cast<TilesetDocument*>(mDocument)) {
connectToTileset(tilesetDocument->tileset());
}
Expand Down Expand Up @@ -304,15 +306,37 @@ QVariant BrokenLinksModel::headerData(int section, Qt::Orientation orientation,
return QVariant();
}

void BrokenLinksModel::documentChanged(const ChangeEvent &event)
{
switch (event.type) {
case ChangeEvent::DocumentAboutToReload:
if (auto mapDocument = qobject_cast<MapDocument*>(mDocument)) {
for (const SharedTileset &tileset : mapDocument->map()->tilesets())
disconnectFromTileset(tileset);
}
break;
case ChangeEvent::DocumentReloaded:
refresh();

if (auto mapDocument = qobject_cast<MapDocument*>(mDocument)) {
for (const SharedTileset &tileset : mapDocument->map()->tilesets())
connectToTileset(tileset);
}
break;
default:
break;
}
}

void BrokenLinksModel::tileImageSourceChanged(Tile *tile)
{
auto matchesTile = [tile](const BrokenLink &link) {
return link.type == TilesetTileImageSource && link._tile == tile;
};

QVector<BrokenLink>::iterator it = std::find_if(mBrokenLinks.begin(),
mBrokenLinks.end(),
matchesTile);
auto it = std::find_if(mBrokenLinks.begin(),
mBrokenLinks.end(),
matchesTile);

if (!tile->imageSource().isEmpty() && tile->imageStatus() == LoadingError) {
if (it != mBrokenLinks.end()) {
Expand Down
2 changes: 2 additions & 0 deletions src/tiled/brokenlinks.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class BrokenLinksModel : public QAbstractListModel
void hasBrokenLinksChanged(bool hasBrokenLinks);

private:
void documentChanged(const ChangeEvent &event);

void tileImageSourceChanged(Tile *tile);
void tilesetChanged(Tileset *tileset);

Expand Down
18 changes: 18 additions & 0 deletions src/tiled/changeevents.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class ChangeEvent
{
public:
enum Type {
DocumentAboutToReload,
DocumentReloaded,
ObjectsChanged,
MapChanged,
LayerChanged,
Expand Down Expand Up @@ -71,6 +73,22 @@ class ChangeEvent
{}
};

class AboutToReloadEvent : public ChangeEvent
{
public:
AboutToReloadEvent()
: ChangeEvent(DocumentAboutToReload)
{}
};

class ReloadEvent : public ChangeEvent
{
public:
ReloadEvent()
: ChangeEvent(DocumentReloaded)
{}
};

class ObjectsChangeEvent : public ChangeEvent
{
public:
Expand Down
4 changes: 4 additions & 0 deletions src/tiled/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ void Document::setCurrentObject(Object *object, Document *owningDocument)
void Document::currentObjectDocumentChanged(const ChangeEvent &change)
{
switch (change.type) {
case ChangeEvent::DocumentAboutToReload:
setCurrentObject(nullptr);
break;

case ChangeEvent::TilesAboutToBeRemoved: {
auto tilesEvent = static_cast<const TilesEvent&>(change);

Expand Down
2 changes: 2 additions & 0 deletions src/tiled/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class Document : public QObject,
*/
virtual bool save(const QString &fileName, QString *error = nullptr) = 0;

virtual bool canReload() const { return false; }

virtual FileFormat *writerFormat() const = 0;

QDateTime lastSaved() const { return mLastSaved; }
Expand Down
71 changes: 42 additions & 29 deletions src/tiled/documentmanager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ DocumentManager::DocumentManager(QObject *parent)
connect(mFileChangedWarning, &FileChangedWarning::reload, this, &DocumentManager::reloadCurrentDocument);
connect(mFileChangedWarning, &FileChangedWarning::ignore, this, &DocumentManager::hideChangedWarning);

connect(this, &DocumentManager::templateTilesetReplaced,
mBrokenLinksModel, &BrokenLinksModel::refresh);

QVBoxLayout *vertical = new QVBoxLayout(mWidget);
vertical->addWidget(mTabBar);
vertical->addWidget(mFileChangedWarning);
Expand Down Expand Up @@ -573,14 +576,18 @@ int DocumentManager::insertDocument(int index, const DocumentPtr &document)
if (Editor *editor = mEditorForType.value(document->type()))
editor->addDocument(documentPtr);

mTabBar->insertTab(index, QString());
updateDocumentTab(documentPtr);

// Connect before adding the tab, so that we handle the 'changed' signal
// first, since we may be creating TilesetDocument instances for tilesets
// used by a reloaded map (design not ideal...).
connect(documentPtr, &Document::fileNameChanged, this, &DocumentManager::fileNameChanged);
connect(documentPtr, &Document::modifiedChanged, this, [=] { updateDocumentTab(documentPtr); });
connect(documentPtr, &Document::isReadOnlyChanged, this, [=] { updateDocumentTab(documentPtr); });
connect(documentPtr, &Document::changed, this, &DocumentManager::onDocumentChanged);
connect(documentPtr, &Document::saved, this, &DocumentManager::onDocumentSaved);

mTabBar->insertTab(index, QString());
updateDocumentTab(documentPtr);

if (auto *mapDocument = qobject_cast<MapDocument*>(documentPtr)) {
connect(mapDocument, &MapDocument::tilesetAdded, this, &DocumentManager::tilesetAdded);
connect(mapDocument, &MapDocument::tilesetRemoved, this, &DocumentManager::tilesetRemoved);
Expand Down Expand Up @@ -904,55 +911,38 @@ bool DocumentManager::reloadCurrentDocument()
}

/**
* Reloads the document at the given \a index. It will lose any undo
* history and current selections. Will not ask the user whether to save
* any changes!
* Reloads the document at the given \a index. Will not ask the user whether to
* save any changes!
*
* Returns whether the document loaded successfully.
*/
bool DocumentManager::reloadDocumentAt(int index)
{
const auto oldDocument = mDocuments.at(index);
const auto document = mDocuments.at(index);
QString error;

if (auto mapDocument = oldDocument.objectCast<MapDocument>()) {
auto readerFormat = mapDocument->readerFormat();
if (!readerFormat)
return false;

// TODO: Consider fixing the reload to avoid recreating the MapDocument
auto newDocument = MapDocument::load(oldDocument->fileName(),
readerFormat,
&error);
if (!newDocument) {
emit reloadError(tr("%1:\n\n%2").arg(oldDocument->fileName(), error));
if (auto mapDocument = document.objectCast<MapDocument>()) {
if (!mapDocument->reload(&error)) {
emit reloadError(tr("%1:\n\n%2").arg(document->fileName(), error));
return false;
}

// Save the document state, to ensure the new document will match it
static_cast<MapEditor*>(editor(Document::MapDocumentType))->saveDocumentState(mapDocument.data());

// Replace old tab
const bool isCurrent = index == mTabBar->currentIndex();
insertDocument(index, newDocument);
if (isCurrent) {
switchToDocument(index);

if (mBrokenLinksModel->hasBrokenLinks())
mBrokenLinksWidget->show();
}
closeDocumentAt(index + 1);

checkTilesetColumns(newDocument.data());
checkTilesetColumns(mapDocument.data());

} else if (auto tilesetDocument = qobject_cast<TilesetDocument*>(oldDocument)) {
} else if (auto tilesetDocument = qobject_cast<TilesetDocument*>(document)) {
if (tilesetDocument->isEmbedded()) {
// For embedded tilesets, we need to reload the map
index = findDocument(tilesetDocument->mapDocuments().first());
if (!reloadDocumentAt(index))
return false;
} else if (!tilesetDocument->reload(&error)) {
emit reloadError(tr("%1:\n\n%2").arg(oldDocument->fileName(), error));
emit reloadError(tr("%1:\n\n%2").arg(document->fileName(), error));
return false;
}

Expand All @@ -962,6 +952,8 @@ bool DocumentManager::reloadDocumentAt(int index)
if (!isDocumentChangedOnDisk(currentDocument()))
mFileChangedWarning->setVisible(false);

emit documentReloaded(document.data());

return true;
}

Expand Down Expand Up @@ -1035,6 +1027,27 @@ void DocumentManager::updateDocumentTab(Document *document)
mTabBar->setTabToolTip(index, tabToolTip);
}

void DocumentManager::onDocumentChanged(const ChangeEvent &event)
{
auto mapDocument = qobject_cast<MapDocument*>(sender());
if (!mapDocument)
return;

// In case a map is reloaded, the set of used tilesets might have changed
switch (event.type) {
case ChangeEvent::DocumentAboutToReload:
for (const SharedTileset &tileset : mapDocument->map()->tilesets())
removeFromTilesetDocument(tileset, mapDocument);
break;
case ChangeEvent::DocumentReloaded:
for (const SharedTileset &tileset : mapDocument->map()->tilesets())
addToTilesetDocument(tileset, mapDocument);
break;
default:
break;
}
}

void DocumentManager::onDocumentSaved()
{
Document *document = static_cast<Document*>(sender());
Expand Down
2 changes: 2 additions & 0 deletions src/tiled/documentmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ class DocumentManager : public QObject
signals:
void documentCreated(Document *document);
void documentOpened(Document *document);
void documentReloaded(Document *document);
void documentAboutToBeSaved(Document *document);
void documentSaved(Document *document);

Expand Down Expand Up @@ -198,6 +199,7 @@ public slots:
void fileNameChanged(const QString &fileName,
const QString &oldFileName);
void updateDocumentTab(Document *document);
void onDocumentChanged(const ChangeEvent &event);
void onDocumentSaved();
void documentTabMoved(int from, int to);
void tabContextMenuRequested(const QPoint &pos);
Expand Down
10 changes: 10 additions & 0 deletions src/tiled/editablemap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,16 @@ void EditableMap::setDocument(Document *document)
void EditableMap::documentChanged(const ChangeEvent &change)
{
switch (change.type) {
case ChangeEvent::DocumentAboutToReload:
for (Layer *layer : map()->layers())
detachLayer(layer);

mRenderer.reset();
setObject(nullptr);
break;
case ChangeEvent::DocumentReloaded:
setObject(mapDocument()->map());
break;
case ChangeEvent::MapChanged:
if (static_cast<const MapChangeEvent&>(change).property == Map::OrientationProperty)
mRenderer.reset();
Expand Down
19 changes: 19 additions & 0 deletions src/tiled/editabletileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "addremovetiles.h"
#include "addremovewangset.h"
#include "changeevents.h"
#include "editabletile.h"
#include "editablewangset.h"
#include "scriptimage.h"
Expand Down Expand Up @@ -402,6 +403,7 @@ void EditableTileset::setDocument(Document *document)

if (auto doc = tilesetDocument()) {
connect(doc, &Document::fileNameChanged, this, &EditableAsset::fileNameChanged);
connect(doc, &Document::changed, this, &EditableTileset::documentChanged);
connect(doc, &TilesetDocument::tilesAdded, this, &EditableTileset::attachTiles);
connect(doc, &TilesetDocument::tilesRemoved, this, &EditableTileset::detachTiles);
connect(doc, &TilesetDocument::tileObjectGroupChanged, this, &EditableTileset::tileObjectGroupChanged);
Expand Down Expand Up @@ -429,6 +431,23 @@ bool EditableTileset::tilesFromEditables(const QList<QObject *> &editableTiles,
return true;
}

void EditableTileset::documentChanged(const ChangeEvent &event)
{
switch (event.type) {
case ChangeEvent::DocumentAboutToReload:
detachTiles(tileset()->tiles());
detachWangSets(tileset()->wangSets());

setObject(nullptr);
break;
case ChangeEvent::DocumentReloaded:
setObject(tilesetDocument()->tileset().data());
break;
default:
break;
}
}

void EditableTileset::attachTiles(const QList<Tile *> &tiles)
{
for (Tile *tile : tiles) {
Expand Down
Loading

0 comments on commit 6d75839

Please sign in to comment.