1 /*
2  * editabletileset.cpp
3  * Copyright 2018, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "editabletileset.h"
22 
23 #include "addremovetiles.h"
24 #include "addremovewangset.h"
25 #include "editablemanager.h"
26 #include "editabletile.h"
27 #include "editablewangset.h"
28 #include "scriptimage.h"
29 #include "scriptmanager.h"
30 #include "tilesetchanges.h"
31 #include "tilesetdocument.h"
32 #include "tilesetwangsetmodel.h"
33 
34 #include <QCoreApplication>
35 
36 namespace Tiled {
37 
EditableTileset(const QString & name,QObject * parent)38 EditableTileset::EditableTileset(const QString &name,
39                                  QObject *parent)
40     : EditableAsset(nullptr, nullptr, parent)
41     , mTileset(Tileset::create(name, 0, 0))
42 {
43     setObject(mTileset.data());
44 }
45 
EditableTileset(const Tileset * tileset,QObject * parent)46 EditableTileset::EditableTileset(const Tileset *tileset, QObject *parent)
47     : EditableAsset(nullptr, const_cast<Tileset*>(tileset), parent)
48     , mReadOnly(true)
49     , mTileset(tileset->sharedPointer())    // keep alive
50 {
51 }
52 
EditableTileset(TilesetDocument * tilesetDocument,QObject * parent)53 EditableTileset::EditableTileset(TilesetDocument *tilesetDocument,
54                                  QObject *parent)
55     : EditableAsset(tilesetDocument, tilesetDocument->tileset().data(), parent)
56 {
57     connect(tilesetDocument, &Document::fileNameChanged, this, &EditableAsset::fileNameChanged);
58     connect(tilesetDocument, &TilesetDocument::tilesAdded, this, &EditableTileset::attachTiles);
59     connect(tilesetDocument, &TilesetDocument::tilesRemoved, this, &EditableTileset::detachTiles);
60     connect(tilesetDocument, &TilesetDocument::tileObjectGroupChanged, this, &EditableTileset::tileObjectGroupChanged);
61     connect(tilesetDocument->wangSetModel(), &TilesetWangSetModel::wangSetAdded, this, &EditableTileset::wangSetAdded);
62     connect(tilesetDocument->wangSetModel(), &TilesetWangSetModel::wangSetRemoved, this, &EditableTileset::wangSetRemoved);
63 }
64 
~EditableTileset()65 EditableTileset::~EditableTileset()
66 {
67     detachTiles(tileset()->tiles());
68     detachWangSets(tileset()->wangSets());
69 
70     EditableManager::instance().mEditableTilesets.remove(tileset());
71 }
72 
loadFromImage(ScriptImage * image,const QString & source)73 void EditableTileset::loadFromImage(ScriptImage *image, const QString &source)
74 {
75     // WARNING: This function has no undo!
76     tileset()->loadFromImage(image->image(), source);
77 }
78 
tile(int id)79 EditableTile *EditableTileset::tile(int id)
80 {
81     Tile *tile = tileset()->findTile(id);
82 
83     if (!tile) {
84         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Invalid tile ID"));
85         return nullptr;
86     }
87 
88     return EditableManager::instance().editableTile(this, tile);
89 }
90 
tiles()91 QList<QObject*> EditableTileset::tiles()
92 {
93     auto &editableManager = EditableManager::instance();
94     QList<QObject*> tiles;
95     for (Tile *tile : tileset()->tiles())
96         tiles.append(editableManager.editableTile(this, tile));
97     return tiles;
98 }
99 
wangSets()100 QList<QObject *> EditableTileset::wangSets()
101 {
102     auto &editableManager = EditableManager::instance();
103     QList<QObject*> wangSets;
104     for (WangSet *wangSet : tileset()->wangSets())
105         wangSets.append(editableManager.editableWangSet(this, wangSet));
106     return wangSets;
107 }
108 
selectedTiles()109 QList<QObject *> EditableTileset::selectedTiles()
110 {
111     if (!tilesetDocument())
112         return QList<QObject*>();
113 
114     QList<QObject*> selectedTiles;
115 
116     auto &editableManager = EditableManager::instance();
117     for (Tile *tile : tilesetDocument()->selectedTiles())
118         selectedTiles.append(editableManager.editableTile(this, tile));
119 
120     return selectedTiles;
121 }
122 
setSelectedTiles(const QList<QObject * > & tiles)123 void EditableTileset::setSelectedTiles(const QList<QObject *> &tiles)
124 {
125     auto document = tilesetDocument();
126     if (!document)
127         return;
128 
129     QList<Tile*> plainTiles;
130     if (!tilesFromEditables(tiles, plainTiles))
131         return;
132 
133     document->setSelectedTiles(plainTiles);
134 }
135 
addTile()136 Tiled::EditableTile *EditableTileset::addTile()
137 {
138     if (!isCollection()) {
139         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Can only add tiles to an image collection tileset"));
140         return nullptr;
141     }
142     if (checkReadOnly())
143         return nullptr;
144 
145     Tile *tile = new Tile(tileset()->takeNextTileId(), tileset());
146 
147     if (auto doc = tilesetDocument())
148         push(new AddTiles(doc, { tile }));
149     else
150         tileset()->addTiles({ tile });
151 
152     return EditableManager::instance().editableTile(this, tile);
153 }
154 
removeTiles(const QList<QObject * > & tiles)155 void EditableTileset::removeTiles(const QList<QObject *> &tiles)
156 {
157     if (!isCollection()) {
158         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Can only remove tiles from an image collection tileset"));
159         return;
160     }
161 
162     QList<Tile*> plainTiles;
163     if (!tilesFromEditables(tiles, plainTiles))
164         return;
165 
166     if (auto doc = tilesetDocument()) {
167         push(new RemoveTiles(doc, plainTiles));
168     } else if (!checkReadOnly()) {
169         tileset()->removeTiles(plainTiles);
170         detachTiles(plainTiles);
171     }
172 }
173 
addWangSet(const QString & name,int type)174 EditableWangSet *EditableTileset::addWangSet(const QString &name, int type)
175 {
176     auto wangSet = std::make_unique<WangSet>(tileset(), name, static_cast<WangSet::Type>(type));
177     EditableWangSet *editable = nullptr;
178 
179     if (auto doc = tilesetDocument()) {
180         push(new AddWangSet(doc, wangSet.release()));
181         editable = EditableManager::instance().editableWangSet(this, tileset()->wangSets().last());
182     } else if (!checkReadOnly()) {
183         tileset()->addWangSet(std::move(wangSet));
184         editable = EditableManager::instance().editableWangSet(this, tileset()->wangSets().last());
185     }
186 
187     return editable;
188 }
189 
removeWangSet(EditableWangSet * editableWangSet)190 void EditableTileset::removeWangSet(EditableWangSet *editableWangSet)
191 {
192     if (!editableWangSet) {
193         ScriptManager::instance().throwNullArgError(0);
194         return;
195     }
196 
197     if (auto doc = tilesetDocument()) {
198         push(new RemoveWangSet(doc, editableWangSet->wangSet()));
199     } else if (!checkReadOnly()) {
200         const int index = tileset()->wangSets().indexOf(editableWangSet->wangSet());
201         auto wangSet = tileset()->takeWangSetAt(index);
202         EditableManager::instance().release(std::move(wangSet));
203     }
204 }
205 
tilesetDocument() const206 TilesetDocument *EditableTileset::tilesetDocument() const
207 {
208     return static_cast<TilesetDocument*>(document());
209 }
210 
setName(const QString & name)211 void EditableTileset::setName(const QString &name)
212 {
213     if (auto doc = tilesetDocument())
214         push(new RenameTileset(doc, name));
215     else if (!checkReadOnly())
216         tileset()->setName(name);
217 }
218 
setImage(const QString & imageFilePath)219 void EditableTileset::setImage(const QString &imageFilePath)
220 {
221     if (isCollection() && tileCount() > 0) {
222         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Can't set the image of an image collection tileset"));
223         return;
224     }
225 
226     if (auto doc = tilesetDocument()) {
227         TilesetParameters parameters(*tileset());
228         parameters.imageSource = QUrl::fromLocalFile(imageFilePath);
229 
230         push(new ChangeTilesetParameters(doc, parameters));
231     } else if (!checkReadOnly()) {
232         tileset()->setImageSource(imageFilePath);
233 
234         if (!tileSize().isEmpty() && !image().isEmpty())
235             tileset()->loadImage();
236     }
237 }
238 
setTileSize(QSize size)239 void EditableTileset::setTileSize(QSize size)
240 {
241     if (isCollection() && tileCount() > 0) {
242         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Can't set tile size on an image collection tileset"));
243         return;
244     }
245 
246     if (auto doc = tilesetDocument()) {
247         TilesetParameters parameters(*tileset());
248         parameters.tileSize = size;
249 
250         push(new ChangeTilesetParameters(doc, parameters));
251     } else if (!checkReadOnly()) {
252         tileset()->setTileSize(size);
253 
254         if (!tileSize().isEmpty() && !image().isEmpty())
255             tileset()->loadImage();
256     }
257 }
258 
setObjectAlignment(Alignment alignment)259 void EditableTileset::setObjectAlignment(Alignment alignment)
260 {
261     if (auto doc = tilesetDocument())
262         push(new ChangeTilesetObjectAlignment(doc, static_cast<Tiled::Alignment>(alignment)));
263     else if (!checkReadOnly())
264         tileset()->setObjectAlignment(static_cast<Tiled::Alignment>(alignment));
265 }
266 
setTileOffset(QPoint tileOffset)267 void EditableTileset::setTileOffset(QPoint tileOffset)
268 {
269     if (auto doc = tilesetDocument())
270         push(new ChangeTilesetTileOffset(doc, tileOffset));
271     else if (!checkReadOnly())
272         tileset()->setTileOffset(tileOffset);
273 }
274 
setOrientation(Orientation orientation)275 void EditableTileset::setOrientation(Orientation orientation)
276 {
277     if (auto doc = tilesetDocument())
278         push(new ChangeTilesetOrientation(doc, static_cast<Tileset::Orientation>(orientation)));
279     else if (!checkReadOnly())
280         tileset()->setOrientation(static_cast<Tileset::Orientation>(orientation));
281 }
282 
setTransparentColor(const QColor & color)283 void EditableTileset::setTransparentColor(const QColor &color)
284 {
285     if (auto doc = tilesetDocument()) {
286         TilesetParameters parameters(*tileset());
287         parameters.transparentColor = color;
288 
289         push(new ChangeTilesetParameters(doc, parameters));
290     } else if (!checkReadOnly()) {
291         tileset()->setTransparentColor(color);
292 
293         if (!tileSize().isEmpty() && !image().isEmpty())
294             tileset()->loadImage();
295     }
296 }
297 
setBackgroundColor(const QColor & color)298 void EditableTileset::setBackgroundColor(const QColor &color)
299 {
300     if (auto doc = tilesetDocument())
301         push(new ChangeTilesetBackgroundColor(doc, color));
302     else if (!checkReadOnly())
303         tileset()->setBackgroundColor(color);
304 }
305 
tilesFromEditables(const QList<QObject * > & editableTiles,QList<Tile * > & tiles)306 bool EditableTileset::tilesFromEditables(const QList<QObject *> &editableTiles, QList<Tile*> &tiles)
307 {
308     for (QObject *tileObject : editableTiles) {
309         auto editableTile = qobject_cast<EditableTile*>(tileObject);
310         if (!editableTile) {
311             ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Not a tile"));
312             return false;
313         }
314         if (editableTile->tileset() != this) {
315             ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Tile not from this tileset"));
316             return false;
317         }
318 
319         tiles.append(editableTile->tile());
320     }
321 
322     return true;
323 }
324 
attachTiles(const QList<Tile * > & tiles)325 void EditableTileset::attachTiles(const QList<Tile *> &tiles)
326 {
327     const auto &editableManager = EditableManager::instance();
328     for (Tile *tile : tiles) {
329         if (EditableTile *editable = editableManager.find(tile))
330             editable->attach(this);
331     }
332 }
333 
detachTiles(const QList<Tile * > & tiles)334 void EditableTileset::detachTiles(const QList<Tile *> &tiles)
335 {
336     const auto &editableManager = EditableManager::instance();
337     for (Tile *tile : tiles) {
338         if (auto editable = editableManager.find(tile)) {
339             Q_ASSERT(editable->tileset() == this);
340             editable->detach();
341         }
342     }
343 }
344 
detachWangSets(const QList<WangSet * > & wangSets)345 void EditableTileset::detachWangSets(const QList<WangSet *> &wangSets)
346 {
347     const auto &editableManager = EditableManager::instance();
348     for (WangSet *wangSet : wangSets) {
349         if (auto editable = editableManager.find(wangSet)) {
350             Q_ASSERT(editable->tileset() == this);
351             editable->detach();
352         }
353     }
354 }
355 
tileObjectGroupChanged(Tile * tile)356 void EditableTileset::tileObjectGroupChanged(Tile *tile)
357 {
358     Q_ASSERT(tile->tileset() == tileset());
359 
360     if (auto editable = EditableManager::instance().find(tile))
361         if (editable->attachedObjectGroup() != tile->objectGroup())
362             editable->detachObjectGroup();
363 }
364 
wangSetAdded(Tileset * tileset,int index)365 void EditableTileset::wangSetAdded(Tileset *tileset, int index)
366 {
367     Q_ASSERT(this->tileset() == tileset);
368 
369     WangSet *wangSet = tileset->wangSet(index);
370 
371     if (auto editable = EditableManager::instance().find(wangSet))
372         editable->attach(this);
373 }
374 
wangSetRemoved(WangSet * wangSet)375 void EditableTileset::wangSetRemoved(WangSet *wangSet)
376 {
377     detachWangSets({ wangSet });
378 }
379 
380 } // namespace Tiled
381 
382 #include "moc_editabletileset.cpp"
383