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