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