1 /*
2 * createobjecttool.cpp
3 * Copyright 2010-2011, Thorbjørn Lindeijer <thorbjorn@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 "createobjecttool.h"
22
23 #include "addremovemapobject.h"
24 #include "addremovetileset.h"
25 #include "map.h"
26 #include "mapdocument.h"
27 #include "mapobject.h"
28 #include "mapobjectitem.h"
29 #include "maprenderer.h"
30 #include "mapscene.h"
31 #include "objectgroup.h"
32 #include "objectgroupitem.h"
33 #include "objectselectiontool.h"
34 #include "snaphelper.h"
35 #include "tile.h"
36 #include "toolmanager.h"
37 #include "utils.h"
38
39 #include <QApplication>
40 #include <QKeyEvent>
41 #include <QPalette>
42
43 using namespace Tiled;
44
CreateObjectTool(Id id,QObject * parent)45 CreateObjectTool::CreateObjectTool(Id id, QObject *parent)
46 : AbstractObjectTool(id,
47 QString(),
48 QIcon(),
49 QKeySequence(),
50 parent)
51 , mNewMapObjectItem(nullptr)
52 , mNewMapObjectGroup(new ObjectGroup)
53 , mObjectGroupItem(new ObjectGroupItem(mNewMapObjectGroup.get()))
54 {
55 mObjectGroupItem->setZValue(10000); // same as the BrushItem
56 }
57
~CreateObjectTool()58 CreateObjectTool::~CreateObjectTool()
59 {
60 }
61
activate(MapScene * scene)62 void CreateObjectTool::activate(MapScene *scene)
63 {
64 AbstractObjectTool::activate(scene);
65 scene->addItem(mObjectGroupItem.get());
66
67 connect(scene, &MapScene::parallaxParametersChanged, this, &CreateObjectTool::updateNewObjectGroupItemPos);
68 }
69
deactivate(MapScene * scene)70 void CreateObjectTool::deactivate(MapScene *scene)
71 {
72 disconnect(scene, &MapScene::parallaxParametersChanged, this, &CreateObjectTool::updateNewObjectGroupItemPos);
73
74 if (mNewMapObjectItem)
75 cancelNewMapObject();
76
77 scene->removeItem(mObjectGroupItem.get());
78 AbstractObjectTool::deactivate(scene);
79 }
80
keyPressed(QKeyEvent * event)81 void CreateObjectTool::keyPressed(QKeyEvent *event)
82 {
83 switch (event->key()) {
84 case Qt::Key_Enter:
85 case Qt::Key_Return:
86 if (mState == Preview || mState == CreatingObject) {
87 finishNewMapObject();
88 return;
89 }
90 break;
91 case Qt::Key_Escape:
92 if (mState == CreatingObject) {
93 cancelNewMapObject();
94 } else {
95 // If we're not currently creating a new object, switch to object selection tool
96 toolManager()->selectTool(toolManager()->findTool<ObjectSelectionTool>());
97 }
98 return;
99 }
100
101 AbstractObjectTool::keyPressed(event);
102 }
103
mouseEntered()104 void CreateObjectTool::mouseEntered()
105 {
106 }
107
mouseLeft()108 void CreateObjectTool::mouseLeft()
109 {
110 AbstractObjectTool::mouseLeft();
111
112 if (mState == Preview)
113 cancelNewMapObject();
114 }
115
mouseMoved(const QPointF & pos,Qt::KeyboardModifiers modifiers)116 void CreateObjectTool::mouseMoved(const QPointF &pos,
117 Qt::KeyboardModifiers modifiers)
118 {
119 AbstractObjectTool::mouseMoved(pos, modifiers);
120
121 mLastScenePos = pos;
122 mLastModifiers = modifiers;
123
124 if (mState == Idle)
125 tryCreatePreview(pos, modifiers);
126
127 if (mState == Preview || mState == CreatingObject) {
128 QPointF offset = mapScene()->absolutePositionForLayer(*mNewMapObjectItem->mapObject()->objectGroup());
129 mouseMovedWhileCreatingObject(pos - offset, modifiers);
130 }
131 }
132
133 /**
134 * Default implementation starts a new object on left mouse button, and cancels
135 * object creation on right mouse button.
136 */
mousePressed(QGraphicsSceneMouseEvent * event)137 void CreateObjectTool::mousePressed(QGraphicsSceneMouseEvent *event)
138 {
139 if (event->button() == Qt::RightButton) {
140 if (mState == CreatingObject)
141 cancelNewMapObject();
142 return;
143 }
144
145 if (event->button() != Qt::LeftButton) {
146 AbstractObjectTool::mousePressed(event);
147 return;
148 }
149
150 if (mState == Idle)
151 tryCreatePreview(event->scenePos(), event->modifiers());
152
153 if (mState == Preview) {
154 mState = CreatingObject;
155 mNewMapObjectItem->setOpacity(1.0);
156 }
157 }
158
159 /**
160 * Default implementation finishes object placement upon release.
161 */
mouseReleased(QGraphicsSceneMouseEvent * event)162 void CreateObjectTool::mouseReleased(QGraphicsSceneMouseEvent *event)
163 {
164 if (event->button() != Qt::LeftButton)
165 return;
166
167 if (mState == CreatingObject)
168 finishNewMapObject();
169 else if (mState == Idle)
170 tryCreatePreview(event->scenePos(), event->modifiers());
171 }
172
modifiersChanged(Qt::KeyboardModifiers modifiers)173 void CreateObjectTool::modifiersChanged(Qt::KeyboardModifiers modifiers)
174 {
175 AbstractObjectTool::modifiersChanged(modifiers);
176
177 mLastModifiers = modifiers;
178
179 if (mState == Preview || mState == CreatingObject) {
180 // The mouse didn't actually move, but the modifiers do affect the snapping
181 QPointF offset = mapScene()->absolutePositionForLayer(*mNewMapObjectItem->mapObject()->objectGroup());
182 mouseMovedWhileCreatingObject(mLastScenePos - offset, modifiers);
183 }
184 }
185
changeEvent(const ChangeEvent & event)186 void CreateObjectTool::changeEvent(const ChangeEvent &event)
187 {
188 AbstractObjectTool::changeEvent(event);
189
190 switch (event.type) {
191 case ChangeEvent::LayerChanged:
192 if (static_cast<const LayerChangeEvent&>(event).properties & LayerChangeEvent::PositionProperties)
193 updateNewObjectGroupItemPos();
194 break;
195 case ChangeEvent::ObjectGroupChanged:
196 objectGroupChanged(static_cast<const ObjectGroupChangeEvent&>(event));
197 break;
198 default:
199 break;
200 }
201 }
202
updateEnabledState()203 void CreateObjectTool::updateEnabledState()
204 {
205 AbstractObjectTool::updateEnabledState();
206
207 if (!isEnabled())
208 return;
209
210 ObjectGroup *objectGroup = currentObjectGroup();
211 bool canCreate = objectGroup && objectGroup->isVisible() && objectGroup->isUnlocked();
212
213 if (mState == Preview || mState == CreatingObject) {
214 if (!canCreate) {
215 // Make sure we disable the preview when conditions changed
216 cancelNewMapObject();
217 } else {
218 // Synchronize possibly changed object group properties
219 if (mNewMapObjectGroup->color() != objectGroup->color()) {
220 mNewMapObjectGroup->setColor(objectGroup->color());
221 mNewMapObjectItem->syncWithMapObject();
222 }
223
224 const auto offset = objectGroup->totalOffset();
225 const auto parallaxFactor = objectGroup->effectiveParallaxFactor();
226 if (mNewMapObjectGroup->offset() != offset || mNewMapObjectGroup->parallaxFactor() != parallaxFactor) {
227 mNewMapObjectGroup->setOffset(offset);
228 mNewMapObjectGroup->setParallaxFactor(parallaxFactor);
229
230 updateNewObjectGroupItemPos();
231
232 // The mouse didn't actually move, but the offset affects the position
233 mouseMovedWhileCreatingObject(mLastScenePos - mObjectGroupItem->pos(), mLastModifiers);
234 }
235 }
236 }
237 }
238
startNewMapObject(const QPointF & pos,ObjectGroup * objectGroup)239 bool CreateObjectTool::startNewMapObject(const QPointF &pos,
240 ObjectGroup *objectGroup)
241 {
242 Q_ASSERT(!mNewMapObjectItem);
243
244 MapObject *newMapObject = createNewMapObject();
245 if (!newMapObject)
246 return false;
247
248 newMapObject->setPosition(pos);
249
250 mNewMapObjectGroup->addObject(newMapObject);
251
252 mNewMapObjectGroup->setColor(objectGroup->color());
253 mNewMapObjectGroup->setOffset(objectGroup->totalOffset());
254 mNewMapObjectGroup->setParallaxFactor(objectGroup->parallaxFactor());
255
256 updateNewObjectGroupItemPos();
257
258 mNewMapObjectItem = new MapObjectItem(newMapObject, mapDocument(), mObjectGroupItem.get());
259 mNewMapObjectItem->setOpacity(0.5);
260
261 mState = Preview;
262
263 return true;
264 }
265
266 /**
267 * Deletes the new map object item, and returns its map object.
268 */
clearNewMapObjectItem()269 std::unique_ptr<MapObject> CreateObjectTool::clearNewMapObjectItem()
270 {
271 Q_ASSERT(mNewMapObjectItem);
272
273 std::unique_ptr<MapObject> newMapObject { mNewMapObjectItem->mapObject() };
274
275 mNewMapObjectGroup->removeObject(newMapObject.get());
276
277 delete mNewMapObjectItem;
278 mNewMapObjectItem = nullptr;
279
280 mState = Idle;
281
282 return newMapObject;
283 }
284
objectGroupChanged(const ObjectGroupChangeEvent & event)285 void CreateObjectTool::objectGroupChanged(const ObjectGroupChangeEvent &event)
286 {
287 if (event.objectGroup != currentObjectGroup())
288 return;
289
290 if (event.properties & ObjectGroupChangeEvent::ColorProperty) {
291 mNewMapObjectGroup->setColor(event.objectGroup->color());
292
293 if (mNewMapObjectItem)
294 mNewMapObjectItem->syncWithMapObject();
295 }
296 }
297
updateNewObjectGroupItemPos()298 void CreateObjectTool::updateNewObjectGroupItemPos()
299 {
300 if (mObjectGroupItem && mapScene())
301 mObjectGroupItem->setPos(mapScene()->absolutePositionForLayer(*mNewMapObjectGroup));
302 }
303
tryCreatePreview(const QPointF & scenePos,Qt::KeyboardModifiers modifiers)304 void CreateObjectTool::tryCreatePreview(const QPointF &scenePos,
305 Qt::KeyboardModifiers modifiers)
306 {
307 ObjectGroup *objectGroup = currentObjectGroup();
308 if (!objectGroup || !objectGroup->isVisible() || !objectGroup->isUnlocked())
309 return;
310
311 const MapRenderer *renderer = mapDocument()->renderer();
312 const QPointF offsetPos = scenePos - mapScene()->absolutePositionForLayer(*objectGroup);
313
314 QPointF pixelCoords = renderer->screenToPixelCoords(offsetPos);
315 SnapHelper(renderer, modifiers).snap(pixelCoords);
316
317 if (startNewMapObject(pixelCoords, objectGroup))
318 mouseMovedWhileCreatingObject(offsetPos, modifiers);
319 }
320
cancelNewMapObject()321 void CreateObjectTool::cancelNewMapObject()
322 {
323 clearNewMapObjectItem();
324 }
325
finishNewMapObject()326 void CreateObjectTool::finishNewMapObject()
327 {
328 Q_ASSERT(mNewMapObjectItem);
329
330 ObjectGroup *objectGroup = currentObjectGroup();
331 if (!objectGroup) {
332 cancelNewMapObject();
333 return;
334 }
335
336 auto newMapObject = clearNewMapObjectItem();
337
338 auto addObjectCommand = new AddMapObjects(mapDocument(),
339 objectGroup,
340 newMapObject.get());
341
342 if (Tileset *tileset = newMapObject.get()->cell().tileset()) {
343 SharedTileset sharedTileset = tileset->sharedPointer();
344
345 // Make sure this tileset is part of the map
346 if (!mapDocument()->map()->tilesets().contains(sharedTileset))
347 new AddTileset(mapDocument(), sharedTileset, addObjectCommand);
348 }
349
350 mapDocument()->undoStack()->push(addObjectCommand);
351
352 mapDocument()->setSelectedObjects({newMapObject.get()});
353 newMapObject.release(); // now owned by its object group
354
355 mState = Idle;
356 }
357
358 /**
359 * Default implementation simply synchronizes the position of the new object
360 * with the mouse position.
361 */
mouseMovedWhileCreatingObject(const QPointF & pos,Qt::KeyboardModifiers modifiers)362 void CreateObjectTool::mouseMovedWhileCreatingObject(const QPointF &pos, Qt::KeyboardModifiers modifiers)
363 {
364 const MapRenderer *renderer = mapDocument()->renderer();
365
366 QPointF pixelCoords = renderer->screenToPixelCoords(pos);
367 SnapHelper(renderer, modifiers).snap(pixelCoords);
368
369 mNewMapObjectItem->mapObject()->setPosition(pixelCoords);
370 mNewMapObjectItem->syncWithMapObject();
371 }
372
373 #include "moc_createobjecttool.cpp"
374