1 /*
2  * editabletile.cpp
3  * Copyright 2019, 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 "editabletile.h"
22 
23 #include "changetile.h"
24 #include "changetileanimation.h"
25 #include "changetileimagesource.h"
26 #include "changetileobjectgroup.h"
27 #include "changetileprobability.h"
28 #include "editablemanager.h"
29 #include "editableobjectgroup.h"
30 #include "editabletileset.h"
31 #include "imagecache.h"
32 #include "objectgroup.h"
33 #include "scriptimage.h"
34 #include "scriptmanager.h"
35 
36 #include <QCoreApplication>
37 #include <QJSEngine>
38 
39 namespace Tiled {
40 
EditableTile(EditableTileset * tileset,Tile * tile,QObject * parent)41 EditableTile::EditableTile(EditableTileset *tileset, Tile *tile, QObject *parent)
42     : EditableObject(tileset, tile, parent)
43 {
44 }
45 
~EditableTile()46 EditableTile::~EditableTile()
47 {
48     EditableManager::instance().mEditableTiles.remove(tile());
49 }
50 
objectGroup() const51 EditableObjectGroup *EditableTile::objectGroup() const
52 {
53     if (!mAttachedObjectGroup) {
54         mAttachedObjectGroup = tile()->objectGroup();
55     } else {
56         Q_ASSERT(mAttachedObjectGroup == tile()->objectGroup());
57     }
58 
59     return EditableManager::instance().editableObjectGroup(asset(), mAttachedObjectGroup);
60 }
61 
frames() const62 QJSValue EditableTile::frames() const
63 {
64     QJSEngine *engine = ScriptManager::instance().engine();
65 
66     const auto &frames = tile()->frames();
67     QJSValue array = engine->newArray(frames.size());
68 
69     for (int i = 0; i < frames.size(); ++i) {
70         QJSValue frameObject = engine->newObject();
71         frameObject.setProperty(QStringLiteral("tileId"), frames.at(i).tileId);
72         frameObject.setProperty(QStringLiteral("duration"), frames.at(i).duration);
73         array.setProperty(i, frameObject);
74     }
75 
76     return array;
77 }
78 
tileset() const79 EditableTileset *EditableTile::tileset() const
80 {
81     return static_cast<EditableTileset*>(asset());
82 }
83 
setImage(ScriptImage * image)84 void EditableTile::setImage(ScriptImage *image)
85 {
86     // WARNING: This function has no undo!
87     tile()->setImage(QPixmap::fromImage(image->image()));
88 }
89 
detach()90 void EditableTile::detach()
91 {
92     Q_ASSERT(tileset());
93 
94     auto &editableManager = EditableManager::instance();
95 
96     editableManager.mEditableTiles.remove(tile());
97     setAsset(nullptr);
98 
99     mDetachedTile.reset(tile()->clone(nullptr));
100     setObject(mDetachedTile.get());
101     editableManager.mEditableTiles.insert(tile(), this);
102 
103     // Move over any attached editable object group
104     if (auto editable = editableManager.find(mAttachedObjectGroup)) {
105         editableManager.mEditableLayers.remove(mAttachedObjectGroup);
106         editable->setAsset(nullptr);
107         editable->setObject(tile()->objectGroup());
108         editableManager.mEditableLayers.insert(tile()->objectGroup(), editable);
109         mAttachedObjectGroup = tile()->objectGroup();
110     } else {
111         mAttachedObjectGroup = nullptr;
112     }
113 }
114 
attach(EditableTileset * tileset)115 void EditableTile::attach(EditableTileset *tileset)
116 {
117     Q_ASSERT(!asset() && tileset);
118 
119     setAsset(tileset);
120     mDetachedTile.release();
121 }
122 
detachObjectGroup()123 void EditableTile::detachObjectGroup()
124 {
125     if (auto editable = EditableManager::instance().find(mAttachedObjectGroup))
126         editable->detach();
127     mAttachedObjectGroup = nullptr;
128 }
129 
setType(const QString & type)130 void EditableTile::setType(const QString &type)
131 {
132     if (TilesetDocument *doc = tilesetDocument())
133         asset()->push(new ChangeTileType(doc, { tile() }, type));
134     else if (!checkReadOnly())
135         tile()->setType(type);
136 }
137 
setImageFileName(const QString & fileName)138 void EditableTile::setImageFileName(const QString &fileName)
139 {
140     if (TilesetDocument *doc = tilesetDocument()) {
141         if (!tileset()->tileset()->isCollection()) {
142             ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Tileset needs to be an image collection"));
143             return;
144         }
145 
146         asset()->push(new ChangeTileImageSource(doc, tile(),
147                                                 QUrl::fromLocalFile(fileName)));
148     } else if (!checkReadOnly()) {
149         tile()->setImage(ImageCache::loadPixmap(fileName));
150         tile()->setImageSource(QUrl::fromLocalFile(fileName));
151     }
152 }
153 
setProbability(qreal probability)154 void EditableTile::setProbability(qreal probability)
155 {
156     if (TilesetDocument *doc = tilesetDocument())
157         asset()->push(new ChangeTileProbability(doc, { tile() }, probability));
158     else if (!checkReadOnly())
159         tile()->setProbability(probability);
160 }
161 
setObjectGroup(EditableObjectGroup * editableObjectGroup)162 void EditableTile::setObjectGroup(EditableObjectGroup *editableObjectGroup)
163 {
164     if (!editableObjectGroup) {
165         ScriptManager::instance().throwNullArgError(0);
166         return;
167     }
168 
169     if (!editableObjectGroup->isOwning()) {
170         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "ObjectGroup is in use"));
171         return;
172     }
173 
174     if (checkReadOnly())
175         return;
176 
177     std::unique_ptr<ObjectGroup> og(static_cast<ObjectGroup*>(editableObjectGroup->release()));
178 
179     if (TilesetDocument *doc = tilesetDocument()) {
180         asset()->push(new ChangeTileObjectGroup(doc, tile(), std::move(og)));
181     } else {
182         detachObjectGroup();
183         tile()->setObjectGroup(std::move(og));
184     }
185 
186     Q_ASSERT(editableObjectGroup->objectGroup() == tile()->objectGroup());
187     Q_ASSERT(!editableObjectGroup->isOwning());
188 }
189 
setFrames(QJSValue value)190 void EditableTile::setFrames(QJSValue value)
191 {
192     if (!value.isArray()) {
193         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Array expected"));
194         return;
195     }
196 
197     if (checkReadOnly())
198         return;
199 
200     QVector<Frame> frames;
201     const int length = value.property(QStringLiteral("length")).toInt();
202 
203     for (int i = 0; i < length; ++i) {
204         const auto frameValue = value.property(i);
205         const Frame frame {
206             frameValue.property(QStringLiteral("tileId")).toInt(),
207             frameValue.property(QStringLiteral("duration")).toInt()
208         };
209 
210         if (frame.tileId < 0 || frame.duration < 0) {
211             ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Invalid value (negative)"));
212             return;
213         }
214 
215         frames.append(frame);
216     }
217 
218     if (TilesetDocument *doc = tilesetDocument())
219         asset()->push(new ChangeTileAnimation(doc, tile(), frames));
220     else
221         tile()->setFrames(frames);
222 }
223 
tilesetDocument() const224 TilesetDocument *EditableTile::tilesetDocument() const
225 {
226     return tileset() ? tileset()->tilesetDocument() : nullptr;
227 }
228 
229 } // namespace Tiled
230 
231 #include "moc_editabletile.cpp"
232