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