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