Skip to content

Commit

Permalink
Worlds: Report errors and refresh on load/unload
Browse files Browse the repository at this point in the history
Added error reporting when loading a world fails.

Also added a worldsChanged signal, which is used to immediately update
the map scenes. While refreshing a scene, existing MapItem instances are
now reused when applicable.

Issue #1669
  • Loading branch information
bjorn committed May 30, 2018
1 parent 8570d89 commit aa85acf
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 76 deletions.
39 changes: 29 additions & 10 deletions src/libtiled/worldmanager.cpp
Expand Up @@ -34,16 +34,15 @@
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QScopedPointer>

#include <QDebug>

namespace Tiled {

WorldManager *WorldManager::mInstance;

WorldManager::WorldManager()
{
}
WorldManager::WorldManager() = default;

WorldManager::~WorldManager()
{
Expand All @@ -64,18 +63,30 @@ void WorldManager::deleteInstance()
mInstance = nullptr;
}

void WorldManager::loadWorld(const QString &fileName)
/**
* Loads the world with the given \a fileName.
*
* \returns whether the world was loaded succesfully, optionally setting
* \a errorString when not.
*/
bool WorldManager::loadWorld(const QString &fileName, QString *errorString)
{
unloadWorld(fileName); // unload possibly existing world

QFile file(fileName);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
return;
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
if (errorString)
*errorString = tr("Could not open file for reading.");
return false;
}

QJsonParseError error;
const QJsonObject object = QJsonDocument::fromJson(file.readAll(), &error).object();
if (error.error != QJsonParseError::NoError)
return;
if (error.error != QJsonParseError::NoError) {
if (errorString)
*errorString = tr("JSON parse error at offset %1:\n%2.").arg(error.offset).arg(error.errorString());
return false;
}

QDir dir = QFileInfo(fileName).dir();
QScopedPointer<World> world(new World);
Expand Down Expand Up @@ -126,16 +137,24 @@ void WorldManager::loadWorld(const QString &fileName)
world->onlyShowAdjacentMaps = object.value(QLatin1String("onlyShowAdjacentMaps")).toBool();

mWorlds.insert(fileName, world.take());
emit worldsChanged();

return true;
}

/**
* Unloads the world with the given \a fileName.
*/
void WorldManager::unloadWorld(const QString &fileName)
{
delete mWorlds.take(fileName);
QScopedPointer<World> world(mWorlds.take(fileName));
if (world)
emit worldsChanged();
}

const World *WorldManager::worldForMap(const QString &fileName) const
{
for (const World *world : mWorlds)
for (auto world : mWorlds)
if (world->containsMap(fileName))
return world;

Expand Down
18 changes: 12 additions & 6 deletions src/libtiled/worldmanager.h
Expand Up @@ -31,7 +31,9 @@

#include "tiled_global.h"

#include <QHash>
#include <QCoreApplication>
#include <QMap>
#include <QObject>
#include <QPoint>
#include <QRect>
#include <QRegularExpression>
Expand Down Expand Up @@ -69,26 +71,30 @@ struct TILEDSHARED_EXPORT World
QVector<MapEntry> contextMaps(const QString &fileName) const;
};

class TILEDSHARED_EXPORT WorldManager
class TILEDSHARED_EXPORT WorldManager : public QObject
{
Q_DISABLE_COPY(WorldManager)
Q_OBJECT

public:
static WorldManager &instance();
static void deleteInstance();

void loadWorld(const QString &fileName);
bool loadWorld(const QString &fileName, QString *errorString = nullptr);
void unloadWorld(const QString &fileName);

const QHash<QString, World*> &worlds() const { return mWorlds; }
const QMap<QString, World*> &worlds() const { return mWorlds; }
QStringList loadedWorldFiles() const { return mWorlds.keys(); }

const World *worldForMap(const QString &fileName) const;

signals:
void worldsChanged();

private:
WorldManager();
~WorldManager();

QHash<QString, World*> mWorlds;
QMap<QString, World*> mWorlds;

static WorldManager *mInstance;
};
Expand Down
22 changes: 14 additions & 8 deletions src/tiled/mainwindow.cpp
Expand Up @@ -418,20 +418,26 @@ MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags)
return;

preferences->setLastPath(Preferences::WorldFile, QFileInfo(worldFile).path());
WorldManager::instance().loadWorld(worldFile);
mSettings.setValue(QLatin1String("LoadedWorlds"),
QVariant(WorldManager::instance().worlds().keys()));
mUi->menuUnloadWorld->setEnabled(!WorldManager::instance().worlds().isEmpty());
QString errorString;
if (!WorldManager::instance().loadWorld(worldFile, &errorString)) {
QMessageBox::critical(this, tr("Error Loading World"), errorString);
} else {
const auto worldFiles = WorldManager::instance().loadedWorldFiles();
mSettings.setValue(QLatin1String("LoadedWorlds"), QVariant(worldFiles));
mUi->menuUnloadWorld->setEnabled(!worldFiles.isEmpty());
}
});
connect(mUi->menuUnloadWorld, &QMenu::aboutToShow, this, [this] {
mUi->menuUnloadWorld->clear();
for (const QString &fileName : WorldManager::instance().worlds().keys()) {

const auto worldFiles = WorldManager::instance().loadedWorldFiles();
for (const QString &fileName : worldFiles) {
QAction *unloadAction = mUi->menuUnloadWorld->addAction(fileName);
connect(unloadAction, &QAction::triggered, this, [this,fileName] {
WorldManager::instance().unloadWorld(fileName);
mSettings.setValue(QLatin1String("LoadedWorlds"),
QVariant(WorldManager::instance().worlds().keys()));
mUi->menuUnloadWorld->setEnabled(!WorldManager::instance().worlds().isEmpty());
const auto worldFiles = WorldManager::instance().loadedWorldFiles();
mSettings.setValue(QLatin1String("LoadedWorlds"), QVariant(worldFiles));
mUi->menuUnloadWorld->setEnabled(!worldFiles.isEmpty());
});
}
});
Expand Down
95 changes: 64 additions & 31 deletions src/tiled/mapitem.cpp
Expand Up @@ -20,6 +20,7 @@

#include "mapitem.h"

#include "documentmanager.h"
#include "grouplayer.h"
#include "grouplayeritem.h"
#include "imagelayeritem.h"
Expand All @@ -40,36 +41,26 @@
#include <QPen>
#include <QWidget>

#include "qtcompat_p.h"

namespace Tiled {
namespace Internal {

static const qreal darkeningFactor = 0.6;
static const qreal opacityFactor = 0.4;

MapItem::MapItem(MapDocument *mapDocument, DisplayMode displayMode,
MapItem::MapItem(const MapDocumentPtr &mapDocument, DisplayMode displayMode,
QGraphicsItem *parent)
: QGraphicsObject(parent)
, mMapDocument(mapDocument->sharedFromThis())
, mMapDocument(mapDocument)
, mDarkRectangle(new QGraphicsRectItem(this))
, mDisplayMode(displayMode)
, mDisplayMode(Editable)
{
// Since we don't do any painting, we can spare us the call to paint()
setFlag(QGraphicsItem::ItemHasNoContents);

// In read-only display mode, we are a link to the editable view for our map
if (displayMode == ReadOnly)
setCursor(Qt::PointingHandCursor);

createLayerItems(mapDocument->map()->layers());

if (displayMode == Editable) {
auto tileSelectionItem = new TileSelectionItem(mapDocument, this);
tileSelectionItem->setZValue(10000 - 2);

auto objectSelectionItem = new ObjectSelectionItem(mapDocument, this);
objectSelectionItem->setZValue(10000 - 1);
}

Preferences *prefs = Preferences::instance();

MapRenderer *renderer = mapDocument->renderer();
Expand All @@ -81,22 +72,22 @@ MapItem::MapItem(MapDocument *mapDocument, DisplayMode displayMode,
connect(prefs, &Preferences::highlightCurrentLayerChanged, this, &MapItem::updateCurrentLayerHighlight);
connect(prefs, &Preferences::objectTypesChanged, this, &MapItem::syncAllObjectItems);

connect(mapDocument, &MapDocument::mapChanged, this, &MapItem::mapChanged);
connect(mapDocument, &MapDocument::regionChanged, this, &MapItem::repaintRegion);
connect(mapDocument, &MapDocument::tileLayerChanged, this, &MapItem::tileLayerChanged);
connect(mapDocument, &MapDocument::layerAdded, this, &MapItem::layerAdded);
connect(mapDocument, &MapDocument::layerRemoved, this, &MapItem::layerRemoved);
connect(mapDocument, &MapDocument::layerChanged, this, &MapItem::layerChanged);
connect(mapDocument, &MapDocument::objectGroupChanged, this, &MapItem::objectGroupChanged);
connect(mapDocument, &MapDocument::imageLayerChanged, this, &MapItem::imageLayerChanged);
connect(mapDocument, &MapDocument::selectedLayersChanged, this, &MapItem::updateCurrentLayerHighlight);
connect(mapDocument, &MapDocument::tilesetTileOffsetChanged, this, &MapItem::adaptToTilesetTileSizeChanges);
connect(mapDocument, &MapDocument::tileImageSourceChanged, this, &MapItem::adaptToTileSizeChanges);
connect(mapDocument, &MapDocument::tilesetReplaced, this, &MapItem::tilesetReplaced);
connect(mapDocument, &MapDocument::objectsInserted, this, &MapItem::objectsInserted);
connect(mapDocument, &MapDocument::objectsRemoved, this, &MapItem::objectsRemoved);
connect(mapDocument, &MapDocument::objectsChanged, this, &MapItem::objectsChanged);
connect(mapDocument, &MapDocument::objectsIndexChanged, this, &MapItem::objectsIndexChanged);
connect(mapDocument.data(), &MapDocument::mapChanged, this, &MapItem::mapChanged);
connect(mapDocument.data(), &MapDocument::regionChanged, this, &MapItem::repaintRegion);
connect(mapDocument.data(), &MapDocument::tileLayerChanged, this, &MapItem::tileLayerChanged);
connect(mapDocument.data(), &MapDocument::layerAdded, this, &MapItem::layerAdded);
connect(mapDocument.data(), &MapDocument::layerRemoved, this, &MapItem::layerRemoved);
connect(mapDocument.data(), &MapDocument::layerChanged, this, &MapItem::layerChanged);
connect(mapDocument.data(), &MapDocument::objectGroupChanged, this, &MapItem::objectGroupChanged);
connect(mapDocument.data(), &MapDocument::imageLayerChanged, this, &MapItem::imageLayerChanged);
connect(mapDocument.data(), &MapDocument::selectedLayersChanged, this, &MapItem::updateCurrentLayerHighlight);
connect(mapDocument.data(), &MapDocument::tilesetTileOffsetChanged, this, &MapItem::adaptToTilesetTileSizeChanges);
connect(mapDocument.data(), &MapDocument::tileImageSourceChanged, this, &MapItem::adaptToTileSizeChanges);
connect(mapDocument.data(), &MapDocument::tilesetReplaced, this, &MapItem::tilesetReplaced);
connect(mapDocument.data(), &MapDocument::objectsInserted, this, &MapItem::objectsInserted);
connect(mapDocument.data(), &MapDocument::objectsRemoved, this, &MapItem::objectsRemoved);
connect(mapDocument.data(), &MapDocument::objectsChanged, this, &MapItem::objectsChanged);
connect(mapDocument.data(), &MapDocument::objectsIndexChanged, this, &MapItem::objectsIndexChanged);

updateBoundingRect();

Expand All @@ -105,6 +96,48 @@ MapItem::MapItem(MapDocument *mapDocument, DisplayMode displayMode,
mDarkRectangle->setOpacity(darkeningFactor);
mDarkRectangle->setRect(QRect(INT_MIN / 512, INT_MIN / 512,
INT_MAX / 256, INT_MAX / 256));

if (displayMode == ReadOnly)
setDisplayMode(displayMode);
else
updateCurrentLayerHighlight();
}

MapItem::~MapItem() = default;

void MapItem::setDisplayMode(DisplayMode displayMode)
{
if (mDisplayMode == displayMode)
return;

mDisplayMode = displayMode;

// Enabled state is checked by selection tools
for (LayerItem *layerItem : qAsConst(mLayerItems))
layerItem->setEnabled(displayMode == Editable);

if (displayMode == ReadOnly) {
// In read-only display mode, we are a link to the editable view for our map
setCursor(Qt::PointingHandCursor);

setOpacity(0.5);
setZValue(-1);

mTileSelectionItem.reset(new TileSelectionItem(mapDocument(), this));
mTileSelectionItem->setZValue(10000 - 2);

mObjectSelectionItem.reset(new ObjectSelectionItem(mapDocument(), this));
mObjectSelectionItem->setZValue(10000 - 1);
} else {
unsetCursor();

setOpacity(1.0);
setZValue(0);

mTileSelectionItem.reset();
mObjectSelectionItem.reset();
}

updateCurrentLayerHighlight();
}

Expand Down
12 changes: 9 additions & 3 deletions src/tiled/mapitem.h
Expand Up @@ -20,12 +20,11 @@

#pragma once

#include "documentmanager.h"
#include "mapdocument.h"

#include <QGraphicsObject>
#include <QMap>
#include <QSet>
#include <QScopedPointer>

namespace Tiled {

Expand All @@ -41,6 +40,8 @@ namespace Internal {

class LayerItem;
class MapObjectItem;
class ObjectSelectionItem;
class TileSelectionItem;

/**
* A graphics item that represents the contents of a map.
Expand All @@ -58,11 +59,14 @@ class MapItem : public QGraphicsObject
Editable
};

MapItem(MapDocument *mapDocument, DisplayMode displayMode,
MapItem(const MapDocumentPtr &mapDocument, DisplayMode displayMode,
QGraphicsItem *parent = nullptr);
~MapItem() override;

MapDocument *mapDocument() const;

void setDisplayMode(DisplayMode displayMode);

// QGraphicsItem
QRectF boundingRect() const override;
void paint(QPainter *, const QStyleOptionGraphicsItem *,
Expand Down Expand Up @@ -115,6 +119,8 @@ class MapItem : public QGraphicsObject

MapDocumentPtr mMapDocument;
QGraphicsRectItem *mDarkRectangle;
QScopedPointer<TileSelectionItem> mTileSelectionItem;
QScopedPointer<ObjectSelectionItem> mObjectSelectionItem;
QMap<Layer*, LayerItem*> mLayerItems;
QMap<MapObject*, MapObjectItem*> mObjectItems;
DisplayMode mDisplayMode;
Expand Down

0 comments on commit aa85acf

Please sign in to comment.