1 /*
2  * mapscene.cpp
3  * Copyright 2008-2017, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
5  * Copyright 2009, Edward Hutchins <eah1@yahoo.com>
6  * Copyright 2010, Jeff Bland <jksb@member.fsf.org>
7  *
8  * This file is part of Tiled.
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU General Public License as published by the Free
12  * Software Foundation; either version 2 of the License, or (at your option)
13  * any later version.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
17  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
18  * more details.
19  *
20  * You should have received a copy of the GNU General Public License along with
21  * this program. If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 #include "mapscene.h"
25 
26 #include "abstracttool.h"
27 #include "addremovemapobject.h"
28 #include "containerhelpers.h"
29 #include "debugdrawitem.h"
30 #include "documentmanager.h"
31 #include "map.h"
32 #include "mapobject.h"
33 #include "maprenderer.h"
34 #include "objectgroup.h"
35 #include "objecttemplate.h"
36 #include "preferences.h"
37 #include "stylehelper.h"
38 #include "templatemanager.h"
39 #include "tilesetmanager.h"
40 #include "toolmanager.h"
41 #include "worldmanager.h"
42 
43 #include <QApplication>
44 #include <QFileInfo>
45 #include <QGraphicsSceneMouseEvent>
46 #include <QKeyEvent>
47 #include <QMimeData>
48 #include <QPalette>
49 
50 #include "qtcompat_p.h"
51 
52 using namespace Tiled;
53 
54 SessionOption<bool> MapScene::enableWorlds { "mapScene.enableWorlds", true };
55 
MapScene(QObject * parent)56 MapScene::MapScene(QObject *parent)
57     : QGraphicsScene(parent)
58     , mWorldsEnabled(enableWorlds)
59 {
60     updateDefaultBackgroundColor();
61 
62     connect(StyleHelper::instance(), &StyleHelper::styleApplied,
63             this, &MapScene::updateDefaultBackgroundColor);
64 
65     TilesetManager *tilesetManager = TilesetManager::instance();
66     connect(tilesetManager, &TilesetManager::tilesetImagesChanged,
67             this, &MapScene::repaintTileset);
68     connect(tilesetManager, &TilesetManager::repaintTileset,
69             this, &MapScene::repaintTileset);
70 
71     WorldManager &worldManager = WorldManager::instance();
72     connect(&worldManager, &WorldManager::worldsChanged, this, &MapScene::refreshScene);
73 
74     // Install an event filter so that we can get key events on behalf of the
75     // active tool without having to have the current focus.
76     qApp->installEventFilter(this);
77 
78     mEnableWorldsCallback = enableWorlds.onChange([this] { setWorldsEnabled(enableWorlds); });
79 
80 #ifdef QT_DEBUG
81     mDebugDrawItem = new DebugDrawItem;
82     addItem(mDebugDrawItem);
83 #endif
84 }
85 
~MapScene()86 MapScene::~MapScene()
87 {
88     enableWorlds.unregister(mEnableWorldsCallback);
89 
90     qApp->removeEventFilter(this);
91 }
92 
93 /**
94  * Sets the map this scene displays.
95  */
setMapDocument(MapDocument * mapDocument)96 void MapScene::setMapDocument(MapDocument *mapDocument)
97 {
98     if (mMapDocument)
99         mMapDocument->disconnect(this);
100 
101     mMapDocument = mapDocument;
102 
103     if (mMapDocument) {
104         connect(mMapDocument, &MapDocument::mapChanged,
105                 this, &MapScene::mapChanged);
106         connect(mMapDocument, &MapDocument::tilesetTilePositioningChanged,
107                 this, [this] { update(); });
108         connect(mMapDocument, &MapDocument::tileImageSourceChanged,
109                 this, [this] { update(); });
110         connect(mMapDocument, &MapDocument::tilesetReplaced,
111                 this, &MapScene::tilesetReplaced);
112     }
113 
114     refreshScene();
115     emit mapDocumentChanged(mMapDocument);
116 }
117 
setShowTileCollisionShapes(bool enabled)118 void MapScene::setShowTileCollisionShapes(bool enabled)
119 {
120     if (mShowTileCollisionShapes == enabled)
121         return;
122 
123     mShowTileCollisionShapes = enabled;
124     for (auto mapItem : qAsConst(mMapItems))
125         mapItem->setShowTileCollisionShapes(enabled);
126 }
127 
setParallaxEnabled(bool enabled)128 void MapScene::setParallaxEnabled(bool enabled)
129 {
130     if (mParallaxEnabled == enabled)
131         return;
132 
133     mParallaxEnabled = enabled;
134     emit parallaxParametersChanged();
135 }
136 
137 /**
138  * Returns the bounding rect of the map. This can be different from the
139  * sceneRect() when multiple maps are displayed.
140  */
mapBoundingRect() const141 QRectF MapScene::mapBoundingRect() const
142 {
143     if (auto mapItem = mMapItems.value(mMapDocument))
144         return mapItem->boundingRect();
145     return QRectF();
146 }
147 
148 /**
149  * Sets the currently selected tool.
150  */
setSelectedTool(AbstractTool * tool)151 void MapScene::setSelectedTool(AbstractTool *tool)
152 {
153     if (mSelectedTool == tool)
154         return;
155 
156     if (mSelectedTool) {
157         if (mUnderMouse)
158             mSelectedTool->mouseLeft();
159         mSelectedTool->deactivate(this);
160         mSelectedTool = nullptr;
161     }
162 
163     if (tool && mMapDocument) {
164         mSelectedTool = tool;
165         mSelectedTool->activate(this);
166 
167         mCurrentModifiers = QApplication::keyboardModifiers();
168         mSelectedTool->modifiersChanged(mCurrentModifiers);
169 
170         if (mUnderMouse) {
171             mSelectedTool->mouseEntered();
172             mSelectedTool->mouseMoved(mLastMousePos, mCurrentModifiers);
173         }
174     }
175 }
176 
177 /**
178  * Sets the area of the scene that is currently visible in the MapView.
179  */
setViewRect(const QRectF & rect)180 void MapScene::setViewRect(const QRectF &rect)
181 {
182     if (mViewRect == rect)
183         return;
184 
185     mViewRect = rect;
186 
187     if (mParallaxEnabled)
188         emit parallaxParametersChanged();
189 }
190 
191 /**
192  * Returns the position the given layer is supposed to have, taking into
193  * account its offset and the parallax factor along with the current view rect.
194  */
absolutePositionForLayer(const Layer & layer) const195 QPointF MapScene::absolutePositionForLayer(const Layer &layer) const
196 {
197     return layer.totalOffset() + parallaxOffset(layer);
198 }
199 
200 /**
201  * Returns the parallax offset of the given layer, taking into account its
202  * parallax factor in combination with the current view rect.
203  */
parallaxOffset(const Layer & layer) const204 QPointF MapScene::parallaxOffset(const Layer &layer) const
205 {
206     if (!mParallaxEnabled)
207         return {};
208 
209     const QPointF parallaxFactor = layer.effectiveParallaxFactor();
210     const QPointF viewCenter = mViewRect.center();
211     return QPointF((1.0 - parallaxFactor.x()) * viewCenter.x(),
212                    (1.0 - parallaxFactor.y()) * viewCenter.y());
213 }
214 
215 /**
216  * Refreshes the map scene.
217  */
refreshScene()218 void MapScene::refreshScene()
219 {
220     QHash<MapDocument*, MapItem*> mapItems;
221 
222     if (!mMapDocument) {
223         mMapItems.swap(mapItems);
224         qDeleteAll(mapItems);
225         updateSceneRect();
226         return;
227     }
228 
229     const WorldManager &worldManager = WorldManager::instance();
230     const QString currentMapFile = mMapDocument->canonicalFilePath();
231 
232     if (const World *world = worldManager.worldForMap(currentMapFile)) {
233         const QPoint currentMapPosition = world->mapRect(currentMapFile).topLeft();
234         auto const contextMaps = world->contextMaps(currentMapFile);
235 
236         for (const World::MapEntry &mapEntry : contextMaps) {
237             MapDocumentPtr mapDocument;
238 
239             if (mapEntry.fileName == currentMapFile) {
240                 mapDocument = mMapDocument->sharedFromThis();
241             } else {
242                 auto doc = DocumentManager::instance()->loadDocument(mapEntry.fileName);
243                 mapDocument = doc.objectCast<MapDocument>();
244             }
245 
246             if (mapDocument) {
247                 MapItem::DisplayMode displayMode = MapItem::ReadOnly;
248                 if (mapDocument == mMapDocument)
249                     displayMode = MapItem::Editable;
250 
251                 auto mapItem = takeOrCreateMapItem(mapDocument, displayMode);
252                 mapItem->setPos(mapEntry.rect.topLeft() - currentMapPosition);
253                 mapItem->setVisible(mWorldsEnabled || mapDocument == mMapDocument);
254                 mapItems.insert(mapDocument.data(), mapItem);
255             }
256         }
257     } else {
258         auto mapItem = takeOrCreateMapItem(mMapDocument->sharedFromThis(), MapItem::Editable);
259         mapItems.insert(mMapDocument, mapItem);
260     }
261 
262     mMapItems.swap(mapItems);
263     qDeleteAll(mapItems);       // delete all map items that didn't get reused
264 
265     updateSceneRect();
266 
267     const Map *map = mMapDocument->map();
268 
269     if (map->backgroundColor().isValid())
270         setBackgroundBrush(map->backgroundColor());
271     else
272         setBackgroundBrush(mDefaultBackgroundColor);
273 
274     emit sceneRefreshed();
275 }
276 
updateDefaultBackgroundColor()277 void MapScene::updateDefaultBackgroundColor()
278 {
279     mDefaultBackgroundColor = QGuiApplication::palette().dark().color();
280 
281     if (!mMapDocument || !mMapDocument->map()->backgroundColor().isValid())
282         setBackgroundBrush(mDefaultBackgroundColor);
283 }
284 
updateSceneRect()285 void MapScene::updateSceneRect()
286 {
287     QRectF sceneRect;
288 
289     for (MapItem *mapItem : qAsConst(mMapItems))
290         sceneRect |= mapItem->boundingRect().translated(mapItem->pos());
291 
292     setSceneRect(sceneRect);
293 }
294 
setWorldsEnabled(bool enabled)295 void MapScene::setWorldsEnabled(bool enabled)
296 {
297     if (mWorldsEnabled == enabled)
298         return;
299 
300     mWorldsEnabled = enabled;
301 
302     for (MapItem *mapItem : qAsConst(mMapItems))
303         mapItem->setVisible(mWorldsEnabled || mapItem->mapDocument() == mMapDocument);
304 }
305 
takeOrCreateMapItem(const MapDocumentPtr & mapDocument,MapItem::DisplayMode displayMode)306 MapItem *MapScene::takeOrCreateMapItem(const MapDocumentPtr &mapDocument, MapItem::DisplayMode displayMode)
307 {
308     // Try to reuse an existing map item
309     auto mapItem = mMapItems.take(mapDocument.data());
310     if (!mapItem) {
311         mapItem = new MapItem(mapDocument, displayMode);
312         mapItem->setShowTileCollisionShapes(mShowTileCollisionShapes);
313         connect(mapItem, &MapItem::boundingRectChanged, this, &MapScene::updateSceneRect);
314         connect(this, &MapScene::parallaxParametersChanged, mapItem, &MapItem::updateLayerPositions);
315         addItem(mapItem);
316         mapItem->updateLayerPositions();
317     } else {
318         mapItem->setDisplayMode(displayMode);
319     }
320     return mapItem;
321 }
322 
323 /**
324  * Updates the possibly changed background color.
325  */
mapChanged()326 void MapScene::mapChanged()
327 {
328     const Map *map = mMapDocument->map();
329     if (map->backgroundColor().isValid())
330         setBackgroundBrush(map->backgroundColor());
331     else
332         setBackgroundBrush(mDefaultBackgroundColor);
333 }
334 
repaintTileset(Tileset * tileset)335 void MapScene::repaintTileset(Tileset *tileset)
336 {
337     for (MapItem *mapItem : qAsConst(mMapItems)) {
338         if (contains(mapItem->mapDocument()->map()->tilesets(), tileset)) {
339             update();
340             return;
341         }
342     }
343 }
344 
tilesetReplaced(int index,Tileset * tileset,Tileset * oldTileset)345 void MapScene::tilesetReplaced(int index, Tileset *tileset, Tileset *oldTileset)
346 {
347     Q_UNUSED(index)
348     Q_UNUSED(oldTileset)
349 
350     repaintTileset(tileset);
351 }
352 
353 /**
354  * Override for handling enter and leave events.
355  */
event(QEvent * event)356 bool MapScene::event(QEvent *event)
357 {
358     switch (event->type()) {
359     case QEvent::Enter:
360         mUnderMouse = true;
361         if (mSelectedTool)
362             mSelectedTool->mouseEntered();
363         break;
364     case QEvent::Leave:
365         mUnderMouse = false;
366         if (mSelectedTool)
367             mSelectedTool->mouseLeft();
368         break;
369     default:
370         break;
371     }
372 
373     return QGraphicsScene::event(event);
374 }
375 
keyPressEvent(QKeyEvent * event)376 void MapScene::keyPressEvent(QKeyEvent *event)
377 {
378     if (mSelectedTool)
379         mSelectedTool->keyPressed(event);
380 
381     if (!(mSelectedTool && event->isAccepted()))
382         QGraphicsScene::keyPressEvent(event);
383 }
384 
mouseMoveEvent(QGraphicsSceneMouseEvent * mouseEvent)385 void MapScene::mouseMoveEvent(QGraphicsSceneMouseEvent *mouseEvent)
386 {
387     mLastMousePos = mouseEvent->scenePos();
388 
389     if (!mMapDocument)
390         return;
391 
392     QGraphicsScene::mouseMoveEvent(mouseEvent);
393 
394     // Currently we always want to inform the active tool about mouse move
395     // events, regardless of whether this event was delived to a graphics item
396     // as a hover event. This is due to the behavior of MapItem, which needs
397     // to accept hover events but should not block them here.
398 //    if (mouseEvent->isAccepted())
399 //        return;
400 
401     if (mSelectedTool) {
402         mSelectedTool->mouseMoved(mouseEvent->scenePos(),
403                                   mouseEvent->modifiers());
404         mouseEvent->accept();
405     }
406 }
407 
mousePressEvent(QGraphicsSceneMouseEvent * mouseEvent)408 void MapScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent)
409 {
410     QGraphicsScene::mousePressEvent(mouseEvent);
411     if (mouseEvent->isAccepted())
412         return;
413 
414     if (mSelectedTool) {
415         mouseEvent->accept();
416         mSelectedTool->mousePressed(mouseEvent);
417     }
418 }
419 
mouseReleaseEvent(QGraphicsSceneMouseEvent * mouseEvent)420 void MapScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent)
421 {
422     QGraphicsScene::mouseReleaseEvent(mouseEvent);
423     if (mouseEvent->isAccepted())
424         return;
425 
426     if (mSelectedTool) {
427         mouseEvent->accept();
428         mSelectedTool->mouseReleased(mouseEvent);
429     }
430 }
431 
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * mouseEvent)432 void MapScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *mouseEvent)
433 {
434     QGraphicsScene::mouseDoubleClickEvent(mouseEvent);
435     if (mouseEvent->isAccepted())
436         return;
437 
438     if (mSelectedTool) {
439         mouseEvent->accept();
440         mSelectedTool->mouseDoubleClicked(mouseEvent);
441     }
442 }
443 
readObjectTemplate(const QMimeData * mimeData)444 static const ObjectTemplate *readObjectTemplate(const QMimeData *mimeData)
445 {
446     const auto urls = mimeData->urls();
447     if (urls.size() != 1)
448         return nullptr;
449 
450     const QString fileName = urls.first().toLocalFile();
451     if (fileName.isEmpty())
452         return nullptr;
453 
454     const QFileInfo info(fileName);
455     if (info.isDir())
456         return nullptr;
457 
458     auto objectTemplate = TemplateManager::instance()->loadObjectTemplate(info.absoluteFilePath());
459     return objectTemplate->object() ? objectTemplate : nullptr;
460 }
461 
462 /**
463  * Override to ignore drag enter events except for templates.
464  */
dragEnterEvent(QGraphicsSceneDragDropEvent * event)465 void MapScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
466 {
467     event->ignore();    // ignore, because events start out accepted
468 
469     if (!mapDocument())
470         return;
471 
472     ObjectGroup *objectGroup = dynamic_cast<ObjectGroup*>(mapDocument()->currentLayer());
473     if (!objectGroup)
474         return;
475 
476     const ObjectTemplate *objectTemplate = readObjectTemplate(event->mimeData());
477     if (!objectTemplate || !mapDocument()->templateAllowed(objectTemplate))
478         return;
479 
480     QGraphicsScene::dragEnterEvent(event);  // accepts the event
481 }
482 
483 /**
484  * Accepts dropping a single template into an object group
485  */
dropEvent(QGraphicsSceneDragDropEvent * event)486 void MapScene::dropEvent(QGraphicsSceneDragDropEvent *event)
487 {
488     if (!mapDocument())
489         return;
490 
491     ObjectGroup *objectGroup = dynamic_cast<ObjectGroup*>(mapDocument()->currentLayer());
492     if (!objectGroup)
493         return;
494 
495     const ObjectTemplate *objectTemplate = readObjectTemplate(event->mimeData());
496     if (!objectTemplate || !mapDocument()->templateAllowed(objectTemplate))
497         return;
498 
499     MapObject *newMapObject = new MapObject;
500     newMapObject->setObjectTemplate(objectTemplate);
501     newMapObject->syncWithTemplate();
502     newMapObject->setPosition(event->scenePos());
503 
504     auto addObjectCommand = new AddMapObjects(mapDocument(),
505                                               objectGroup,
506                                               newMapObject);
507 
508     mapDocument()->undoStack()->push(addObjectCommand);
509 
510     mapDocument()->setSelectedObjects({newMapObject});
511 }
512 
dragLeaveEvent(QGraphicsSceneDragDropEvent * event)513 void MapScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *event)
514 {
515     Q_UNUSED(event)
516 }
517 
dragMoveEvent(QGraphicsSceneDragDropEvent * event)518 void MapScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
519 {
520     Q_UNUSED(event)
521 }
522 
eventFilter(QObject *,QEvent * event)523 bool MapScene::eventFilter(QObject *, QEvent *event)
524 {
525     switch (event->type()) {
526     case QEvent::KeyPress:
527     case QEvent::KeyRelease: {
528             QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
529             Qt::KeyboardModifiers newModifiers = keyEvent->modifiers();
530 
531             if (mSelectedTool && newModifiers != mCurrentModifiers) {
532                 mSelectedTool->modifiersChanged(newModifiers);
533                 mCurrentModifiers = newModifiers;
534             }
535         }
536         break;
537     default:
538         break;
539     }
540 
541     return false;
542 }
543 
544 #include "moc_mapscene.cpp"
545