1 /*
2  * tilesetmanager.cpp
3  * Copyright 2008-2014, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright 2009, Edward Hutchins <eah1@yahoo.com>
5  *
6  * This file is part of libtiled.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions are met:
10  *
11  *    1. Redistributions of source code must retain the above copyright notice,
12  *       this list of conditions and the following disclaimer.
13  *
14  *    2. Redistributions in binary form must reproduce the above copyright
15  *       notice, this list of conditions and the following disclaimer in the
16  *       documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
20  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
21  * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
23  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
24  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
25  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
27  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 #include "tilesetmanager.h"
31 
32 #include "filesystemwatcher.h"
33 #include "imagecache.h"
34 #include "tile.h"
35 #include "tileanimationdriver.h"
36 #include "tilesetformat.h"
37 
38 #include "qtcompat_p.h"
39 
40 namespace Tiled {
41 
42 TilesetManager *TilesetManager::mInstance;
43 
44 /**
45  * Constructor. Only used by the tileset manager itself.
46  */
TilesetManager()47 TilesetManager::TilesetManager():
48     mWatcher(new FileSystemWatcher(this)),
49     mAnimationDriver(new TileAnimationDriver(this)),
50     mReloadTilesetsOnChange(false)
51 {
52     connect(mWatcher, &FileSystemWatcher::pathsChanged,
53             this, &TilesetManager::filesChanged);
54 
55     connect(mAnimationDriver, &TileAnimationDriver::update,
56             this, &TilesetManager::advanceTileAnimations);
57 }
58 
~TilesetManager()59 TilesetManager::~TilesetManager()
60 {
61     // Assert that there are no remaining tileset instances
62     Q_ASSERT(mTilesets.isEmpty());
63 }
64 
65 /**
66  * Requests the tileset manager. When the manager doesn't exist yet, it
67  * will be created.
68  */
instance()69 TilesetManager *TilesetManager::instance()
70 {
71     if (!mInstance)
72         mInstance = new TilesetManager;
73 
74     return mInstance;
75 }
76 
77 /**
78  * Deletes the tileset manager instance, when it exists.
79  */
deleteInstance()80 void TilesetManager::deleteInstance()
81 {
82     delete mInstance;
83     mInstance = nullptr;
84 }
85 
86 /**
87  * Loads the tileset with the given \a fileName. If the tileset is already
88  * loaded, returns that instance.
89  *
90  * When an error occurs during loading it is assigned to the optional \a error
91  * parameter.
92  */
loadTileset(const QString & fileName,QString * error)93 SharedTileset TilesetManager::loadTileset(const QString &fileName, QString *error)
94 {
95     SharedTileset tileset = findTileset(fileName);
96     if (!tileset)
97         tileset = readTileset(fileName, error);
98 
99     return tileset;
100 }
101 
102 /**
103  * Searches for a tileset matching the given file name.
104  * @return a tileset matching the given file name, or 0 if none exists
105  */
findTileset(const QString & fileName) const106 SharedTileset TilesetManager::findTileset(const QString &fileName) const
107 {
108     for (Tileset *tileset : mTilesets)
109         if (tileset->fileName() == fileName)
110             return tileset->sharedPointer();
111 
112     return SharedTileset();
113 }
114 
115 /**
116  * Adds a tileset reference. This will make sure the tileset is watched for
117  * changes and can be found using findTileset().
118  */
addTileset(Tileset * tileset)119 void TilesetManager::addTileset(Tileset *tileset)
120 {
121     Q_ASSERT(!mTilesets.contains(tileset));
122     mTilesets.append(tileset);
123 }
124 
125 /**
126  * Removes a tileset reference. When the last reference has been removed,
127  * the tileset is no longer watched for changes.
128  */
removeTileset(Tileset * tileset)129 void TilesetManager::removeTileset(Tileset *tileset)
130 {
131     Q_ASSERT(mTilesets.contains(tileset));
132     mTilesets.removeOne(tileset);
133 
134     if (tileset->imageSource().isLocalFile())
135         mWatcher->removePath(tileset->imageSource().toLocalFile());
136 }
137 
138 /**
139  * Forces a tileset to reload.
140  */
reloadImages(Tileset * tileset)141 void TilesetManager::reloadImages(Tileset *tileset)
142 {
143     if (!mTilesets.contains(tileset))
144         return;
145 
146     if (tileset->isCollection()) {
147         for (Tile *tile : tileset->tiles()) {
148             // todo: trigger reload of remote files
149             if (tile->imageSource().isLocalFile()) {
150                 const QString localFile = tile->imageSource().toLocalFile();
151                 ImageCache::remove(localFile);
152                 tile->setImage(ImageCache::loadPixmap(localFile));
153             }
154         }
155         emit tilesetImagesChanged(tileset);
156     } else {
157         ImageCache::remove(tileset->imageSource().toLocalFile());
158         if (tileset->loadImage())
159             emit tilesetImagesChanged(tileset);
160     }
161 }
162 
163 /**
164  * Sets whether tilesets are automatically reloaded when their tileset
165  * image changes.
166  */
setReloadTilesetsOnChange(bool enabled)167 void TilesetManager::setReloadTilesetsOnChange(bool enabled)
168 {
169     mReloadTilesetsOnChange = enabled;
170     // TODO: Clear the file system watcher when disabled
171 }
172 
173 /**
174  * Sets whether tile animations are running.
175  */
setAnimateTiles(bool enabled)176 void TilesetManager::setAnimateTiles(bool enabled)
177 {
178     // TODO: Avoid running the driver when there are no animated tiles
179     if (enabled)
180         mAnimationDriver->start();
181     else
182         mAnimationDriver->stop();
183 }
184 
animateTiles() const185 bool TilesetManager::animateTiles() const
186 {
187     return mAnimationDriver->state() == QAbstractAnimation::Running;
188 }
189 
tilesetImageSourceChanged(const Tileset & tileset,const QUrl & oldImageSource)190 void TilesetManager::tilesetImageSourceChanged(const Tileset &tileset,
191                                                const QUrl &oldImageSource)
192 {
193     Q_ASSERT(mTilesets.contains(const_cast<Tileset*>(&tileset)));
194 
195     if (oldImageSource.isLocalFile())
196         mWatcher->removePath(oldImageSource.toLocalFile());
197 
198     if (tileset.imageSource().isLocalFile())
199         mWatcher->addPath(tileset.imageSource().toLocalFile());
200 }
201 
filesChanged(const QStringList & fileNames)202 void TilesetManager::filesChanged(const QStringList &fileNames)
203 {
204     if (!mReloadTilesetsOnChange)
205         return;
206 
207     for (const QString &fileName : fileNames)
208         ImageCache::remove(fileName);
209 
210     for (Tileset *tileset : qAsConst(mTilesets)) {
211         const QString fileName = tileset->imageSource().toLocalFile();
212         if (fileNames.contains(fileName))
213             if (tileset->loadImage())
214                 emit tilesetImagesChanged(tileset);
215     }
216 }
217 
218 /**
219  * Resets all tile animations. Used to keep animations synchronized when they
220  * are edited.
221  */
resetTileAnimations()222 void TilesetManager::resetTileAnimations()
223 {
224     // TODO: This could be more optimal by keeping track of the list of
225     // actually animated tiles
226 
227     for (Tileset *tileset : qAsConst(mTilesets)) {
228         bool imageChanged = false;
229 
230         for (Tile *tile : tileset->tiles())
231             imageChanged |= tile->resetAnimation();
232 
233         if (imageChanged)
234             emit repaintTileset(tileset);
235     }
236 }
237 
advanceTileAnimations(int ms)238 void TilesetManager::advanceTileAnimations(int ms)
239 {
240     // TODO: This could be more optimal by keeping track of the list of
241     // actually animated tiles
242 
243     for (Tileset *tileset : qAsConst(mTilesets)) {
244         bool imageChanged = false;
245 
246         for (Tile *tile : tileset->tiles())
247             imageChanged |= tile->advanceAnimation(ms);
248 
249         if (imageChanged)
250             emit repaintTileset(tileset);
251     }
252 }
253 
254 } // namespace Tiled
255 
256 #include "moc_tilesetmanager.cpp"
257