1 /*
2 * editablemap.cpp
3 * Copyright 2018, 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 "editablemap.h"
22
23 #include "addremovelayer.h"
24 #include "addremovetileset.h"
25 #include "automappingmanager.h"
26 #include "changeevents.h"
27 #include "changemapproperty.h"
28 #include "changeselectedarea.h"
29 #include "editablegrouplayer.h"
30 #include "editableimagelayer.h"
31 #include "editablelayer.h"
32 #include "editablemanager.h"
33 #include "editablemapobject.h"
34 #include "editableobjectgroup.h"
35 #include "editableselectedarea.h"
36 #include "editabletilelayer.h"
37 #include "grouplayer.h"
38 #include "imagelayer.h"
39 #include "mapobject.h"
40 #include "maprenderer.h"
41 #include "objectgroup.h"
42 #include "replacetileset.h"
43 #include "resizemap.h"
44 #include "scriptmanager.h"
45 #include "tilelayer.h"
46 #include "tileset.h"
47 #include "tilesetdocument.h"
48
49 #include <QCoreApplication>
50 #include <QUndoStack>
51
52 #include "qtcompat_p.h"
53
54 namespace Tiled {
55
EditableMap(QObject * parent)56 EditableMap::EditableMap(QObject *parent)
57 : EditableAsset(nullptr, new Map(), parent)
58 , mReadOnly(false)
59 , mSelectedArea(nullptr)
60 {
61 mDetachedMap.reset(map());
62 }
63
EditableMap(MapDocument * mapDocument,QObject * parent)64 EditableMap::EditableMap(MapDocument *mapDocument, QObject *parent)
65 : EditableAsset(mapDocument, mapDocument->map(), parent)
66 , mReadOnly(false)
67 , mSelectedArea(new EditableSelectedArea(mapDocument, this))
68 {
69 connect(mapDocument, &Document::fileNameChanged, this, &EditableAsset::fileNameChanged);
70 connect(mapDocument, &Document::changed, this, &EditableMap::documentChanged);
71 connect(mapDocument, &MapDocument::layerAdded, this, &EditableMap::attachLayer);
72 connect(mapDocument, &MapDocument::layerRemoved, this, &EditableMap::detachLayer);
73
74 connect(mapDocument, &MapDocument::currentLayerChanged, this, &EditableMap::onCurrentLayerChanged);
75 connect(mapDocument, &MapDocument::selectedLayersChanged, this, &EditableMap::selectedLayersChanged);
76 connect(mapDocument, &MapDocument::selectedObjectsChanged, this, &EditableMap::selectedObjectsChanged);
77 }
78
79 /**
80 * Creates a read-only instance of EditableMap that works on the given \a map.
81 *
82 * The map's lifetime must exceed that of the EditableMap instance.
83 */
EditableMap(const Map * map,QObject * parent)84 EditableMap::EditableMap(const Map *map, QObject *parent)
85 : EditableAsset(nullptr, const_cast<Map*>(map), parent)
86 , mReadOnly(true)
87 , mSelectedArea(nullptr)
88 {
89 }
90
EditableMap(std::unique_ptr<Map> map,QObject * parent)91 EditableMap::EditableMap(std::unique_ptr<Map> map, QObject *parent)
92 : EditableAsset(nullptr, map.get(), parent)
93 , mDetachedMap(std::move(map))
94 , mReadOnly(false)
95 , mSelectedArea(nullptr)
96 {
97 }
98
~EditableMap()99 EditableMap::~EditableMap()
100 {
101 for (Layer *layer : map()->layers())
102 detachLayer(layer);
103 }
104
tilesets() const105 QList<QObject *> EditableMap::tilesets() const
106 {
107 QList<QObject *> editableTilesets;
108 auto &editableManager = EditableManager::instance();
109
110 for (const SharedTileset &tileset : map()->tilesets()) {
111 if (auto document = TilesetDocument::findDocumentForTileset(tileset))
112 editableTilesets.append(document->editable());
113 else
114 editableTilesets.append(editableManager.editableTileset(tileset.data()));
115 }
116 return editableTilesets;
117 }
118
currentLayer()119 EditableLayer *EditableMap::currentLayer()
120 {
121 if (auto document = mapDocument())
122 return EditableManager::instance().editableLayer(this, document->currentLayer());
123 return nullptr;
124 }
125
selectedLayers()126 QList<QObject *> EditableMap::selectedLayers()
127 {
128 if (!mapDocument())
129 return QList<QObject*>();
130
131 QList<QObject*> selectedLayers;
132
133 auto &editableManager = EditableManager::instance();
134 for (Layer *layer : mapDocument()->selectedLayers())
135 selectedLayers.append(editableManager.editableLayer(this, layer));
136
137 return selectedLayers;
138 }
139
selectedObjects()140 QList<QObject *> EditableMap::selectedObjects()
141 {
142 if (!mapDocument())
143 return QList<QObject*>();
144
145 QList<QObject*> selectedObjects;
146
147 auto &editableManager = EditableManager::instance();
148 for (MapObject *object : mapDocument()->selectedObjects())
149 selectedObjects.append(editableManager.editableMapObject(this, object));
150
151 return selectedObjects;
152 }
153
layerAt(int index)154 EditableLayer *EditableMap::layerAt(int index)
155 {
156 if (index < 0 || index >= layerCount()) {
157 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Index out of range"));
158 return nullptr;
159 }
160
161 Layer *layer = map()->layerAt(index);
162 return EditableManager::instance().editableLayer(this, layer);
163 }
164
removeLayerAt(int index)165 void EditableMap::removeLayerAt(int index)
166 {
167 if (index < 0 || index >= layerCount()) {
168 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Index out of range"));
169 return;
170 }
171
172 if (auto doc = mapDocument()) {
173 push(new RemoveLayer(doc, index, nullptr));
174 } else if (!checkReadOnly()) {
175 auto layer = map()->takeLayerAt(index);
176 EditableManager::instance().release(layer);
177 }
178 }
179
removeLayer(EditableLayer * editableLayer)180 void EditableMap::removeLayer(EditableLayer *editableLayer)
181 {
182 if (!editableLayer) {
183 ScriptManager::instance().throwNullArgError(0);
184 return;
185 }
186
187 int index = map()->layers().indexOf(editableLayer->layer());
188 if (index == -1) {
189 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Layer not found"));
190 return;
191 }
192
193 removeLayerAt(index);
194 }
195
insertLayerAt(int index,EditableLayer * editableLayer)196 void EditableMap::insertLayerAt(int index, EditableLayer *editableLayer)
197 {
198 if (index < 0 || index > layerCount()) {
199 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Index out of range"));
200 return;
201 }
202
203 if (!editableLayer) {
204 ScriptManager::instance().throwNullArgError(0);
205 return;
206 }
207
208 if (editableLayer->map()) {
209 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Layer already part of a map"));
210 return;
211 }
212
213 // If this map has a valid size but the tile layer that's getting added
214 // doesn't, default the layer's size to the map size.
215 if (editableLayer->isTileLayer()) {
216 auto editableTileLayer = static_cast<EditableTileLayer*>(editableLayer);
217 if (editableTileLayer->size().isNull() && !size().isNull())
218 editableTileLayer->setSize(size());
219 }
220
221 if (auto doc = mapDocument()) {
222 push(new AddLayer(doc, index, editableLayer->layer(), nullptr));
223 } else if (!checkReadOnly()) {
224 // ownership moves to the map
225 map()->insertLayer(index, editableLayer->release());
226 }
227 }
228
addLayer(EditableLayer * editableLayer)229 void EditableMap::addLayer(EditableLayer *editableLayer)
230 {
231 insertLayerAt(layerCount(), editableLayer);
232 }
233
addTileset(EditableTileset * editableTileset)234 bool EditableMap::addTileset(EditableTileset *editableTileset)
235 {
236 if (!editableTileset) {
237 ScriptManager::instance().throwNullArgError(0);
238 return false;
239 }
240 const auto &tileset = editableTileset->tileset()->sharedPointer();
241 if (map()->indexOfTileset(tileset) != -1)
242 return false; // can't add existing tileset
243
244 if (auto doc = mapDocument())
245 push(new AddTileset(doc, tileset));
246 else if (!checkReadOnly())
247 map()->addTileset(tileset);
248
249 return true;
250 }
251
replaceTileset(EditableTileset * oldEditableTileset,EditableTileset * newEditableTileset)252 bool EditableMap::replaceTileset(EditableTileset *oldEditableTileset,
253 EditableTileset *newEditableTileset)
254 {
255 if (!oldEditableTileset) {
256 ScriptManager::instance().throwNullArgError(0);
257 return false;
258 }
259 if (!newEditableTileset) {
260 ScriptManager::instance().throwNullArgError(1);
261 return false;
262 }
263 if (oldEditableTileset == newEditableTileset) {
264 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Invalid argument"));
265 return false;
266 }
267
268 SharedTileset oldTileset = oldEditableTileset->tileset()->sharedPointer();
269 int indexOfOldTileset = map()->indexOfTileset(oldTileset);
270 if (indexOfOldTileset == -1)
271 return false; // can't replace non-existing tileset
272
273 SharedTileset newTileset = newEditableTileset->tileset()->sharedPointer();
274 int indexOfNewTileset = map()->indexOfTileset(newTileset);
275 if (indexOfNewTileset != -1)
276 return false; // can't replace with tileset that is already part of the map (undo broken)
277
278 if (auto doc = mapDocument())
279 push(new ReplaceTileset(doc, indexOfOldTileset, newTileset));
280 else if (!checkReadOnly())
281 map()->replaceTileset(oldTileset, newTileset);
282
283 return true;
284 }
285
removeTileset(EditableTileset * editableTileset)286 bool EditableMap::removeTileset(EditableTileset *editableTileset)
287 {
288 if (!editableTileset) {
289 ScriptManager::instance().throwNullArgError(0);
290 return false;
291 }
292 Tileset *tileset = editableTileset->tileset();
293 int index = map()->indexOfTileset(tileset->sharedPointer());
294 if (index == -1)
295 return false; // can't remove non-existing tileset
296
297 if (map()->isTilesetUsed(tileset))
298 return false; // not allowed to remove a tileset that's in use
299
300 if (auto doc = mapDocument())
301 push(new RemoveTileset(doc, index));
302 else if (!checkReadOnly())
303 map()->removeTilesetAt(index);
304
305 return true;
306 }
307
usedTilesets() const308 QList<QObject *> EditableMap::usedTilesets() const
309 {
310 const auto tilesets = map()->usedTilesets();
311
312 QList<QObject *> editableTilesets;
313 for (const SharedTileset &tileset : tilesets)
314 if (auto document = TilesetDocument::findDocumentForTileset(tileset))
315 editableTilesets.append(document->editable());
316 return editableTilesets;
317 }
318
319 /**
320 * Merges the given map with this map. Automatically adds any tilesets that are
321 * used by the merged map which are not yet part of this map.
322 *
323 * Might replace tilesets in the given \a editableMap, if it is detached.
324 *
325 * Pass \a canJoin as 'true' if the operation is allowed to join with the
326 * previous one on the undo stack.
327 *
328 * @warning Currently only supports tile layers!
329 */
merge(EditableMap * editableMap,bool canJoin)330 void EditableMap::merge(EditableMap *editableMap, bool canJoin)
331 {
332 if (!editableMap) {
333 ScriptManager::instance().throwNullArgError(0);
334 return;
335 }
336 if (!mapDocument()) { // todo: support this outside of the undo stack
337 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Merge is currently not supported for detached maps"));
338 return;
339 }
340
341 // unifyTilesets might modify the given map, so need to clone if it has a document.
342 Map *map = editableMap->map();
343 std::unique_ptr<Map> copy; // manages lifetime
344 if (editableMap->document()) {
345 copy = map->clone();
346 map = copy.get();
347 }
348
349 QVector<SharedTileset> missingTilesets;
350 mapDocument()->unifyTilesets(map, missingTilesets);
351 mapDocument()->paintTileLayers(map, canJoin, &missingTilesets);
352 }
353
354 /**
355 * Resize this map to the given \a size, while at the same time shifting
356 * the contents by \a offset. If \a removeObjects is true then all objects
357 * which are outside the map will be removed.
358 */
resize(QSize size,QPoint offset,bool removeObjects)359 void EditableMap::resize(QSize size, QPoint offset, bool removeObjects)
360 {
361 if (checkReadOnly())
362 return;
363 if (!mapDocument()) { // todo: should be able to resize still
364 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Resize is currently not supported for detached maps"));
365 return;
366 }
367 if (size.isEmpty()) {
368 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Invalid size"));
369 return;
370 }
371
372 mapDocument()->resizeMap(size, offset, removeObjects);
373 }
374
autoMap(const RegionValueType & region,const QString & rulesFile)375 void EditableMap::autoMap(const RegionValueType ®ion, const QString &rulesFile)
376 {
377 if (checkReadOnly())
378 return;
379 if (!mapDocument()) {
380 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "AutoMapping is currently not supported for detached maps"));
381 return;
382 }
383
384 if (!mAutomappingManager)
385 mAutomappingManager = new AutomappingManager(this);
386
387 AutomappingManager &manager = *mAutomappingManager;
388 manager.setMapDocument(mapDocument(), rulesFile);
389
390 if (region.region().isEmpty())
391 manager.autoMap();
392 else
393 manager.autoMapRegion(region.region());
394 }
395
screenToTile(qreal x,qreal y) const396 QPointF EditableMap::screenToTile(qreal x, qreal y) const
397 {
398 return renderer()->screenToTileCoords(x, y);
399 }
400
tileToScreen(qreal x,qreal y) const401 QPointF EditableMap::tileToScreen(qreal x, qreal y) const
402 {
403 return renderer()->tileToScreenCoords(x, y);
404 }
405
screenToPixel(qreal x,qreal y) const406 QPointF EditableMap::screenToPixel(qreal x, qreal y) const
407 {
408 return renderer()->screenToPixelCoords(x, y);
409 }
410
pixelToScreen(qreal x,qreal y) const411 QPointF EditableMap::pixelToScreen(qreal x, qreal y) const
412 {
413 return renderer()->pixelToScreenCoords(x, y);
414 }
415
pixelToTile(qreal x,qreal y) const416 QPointF EditableMap::pixelToTile(qreal x, qreal y) const
417 {
418 return renderer()->pixelToTileCoords(x, y);
419 }
420
tileToPixel(qreal x,qreal y) const421 QPointF EditableMap::tileToPixel(qreal x, qreal y) const
422 {
423 return renderer()->tileToPixelCoords(x, y);
424 }
425
setSize(int width,int height)426 void EditableMap::setSize(int width, int height)
427 {
428 if (auto doc = mapDocument()) {
429 push(new ResizeMap(doc, QSize(width, height)));
430 } else if (!checkReadOnly()) {
431 map()->setWidth(width);
432 map()->setHeight(height);
433 }
434 }
435
setTileWidth(int value)436 void EditableMap::setTileWidth(int value)
437 {
438 if (auto doc = mapDocument())
439 push(new ChangeMapProperty(doc, Map::TileWidthProperty, value));
440 else if (!checkReadOnly())
441 map()->setTileWidth(value);
442 }
443
setTileHeight(int value)444 void EditableMap::setTileHeight(int value)
445 {
446 if (auto doc = mapDocument())
447 push(new ChangeMapProperty(doc, Map::TileHeightProperty, value));
448 else if (!checkReadOnly())
449 map()->setTileHeight(value);
450 }
451
setTileSize(int width,int height)452 void EditableMap::setTileSize(int width, int height)
453 {
454 if (checkReadOnly())
455 return;
456
457 if (auto doc = mapDocument()) {
458 doc->undoStack()->beginMacro(QCoreApplication::translate("Undo Commands",
459 "Change Tile Size"));
460 setTileWidth(width);
461 setTileHeight(height);
462 doc->undoStack()->endMacro();
463 } else {
464 map()->setTileWidth(width);
465 map()->setTileHeight(height);
466 }
467 }
468
setInfinite(bool value)469 void EditableMap::setInfinite(bool value)
470 {
471 if (auto doc = mapDocument())
472 push(new ChangeMapProperty(doc, Map::InfiniteProperty, value));
473 else if (!checkReadOnly())
474 map()->setInfinite(value);
475 }
476
setHexSideLength(int value)477 void EditableMap::setHexSideLength(int value)
478 {
479 if (auto doc = mapDocument())
480 push(new ChangeMapProperty(doc, Map::HexSideLengthProperty, value));
481 else if (!checkReadOnly())
482 map()->setHexSideLength(value);
483 }
484
setStaggerAxis(StaggerAxis value)485 void EditableMap::setStaggerAxis(StaggerAxis value)
486 {
487 if (auto doc = mapDocument())
488 push(new ChangeMapProperty(doc, static_cast<Map::StaggerAxis>(value)));
489 else if (!checkReadOnly())
490 map()->setStaggerAxis(static_cast<Map::StaggerAxis>(value));
491 }
492
setStaggerIndex(StaggerIndex value)493 void EditableMap::setStaggerIndex(StaggerIndex value)
494 {
495 if (auto doc = mapDocument())
496 push(new ChangeMapProperty(doc, static_cast<Map::StaggerIndex>(value)));
497 else if (!checkReadOnly())
498 map()->setStaggerIndex(static_cast<Map::StaggerIndex>(value));
499 }
500
setOrientation(Orientation value)501 void EditableMap::setOrientation(Orientation value)
502 {
503 if (auto doc = mapDocument()) {
504 push(new ChangeMapProperty(doc, static_cast<Map::Orientation>(value)));
505 } else if (!checkReadOnly()) {
506 map()->setOrientation(static_cast<Map::Orientation>(value));
507 mRenderer.reset();
508 }
509 }
510
setRenderOrder(RenderOrder value)511 void EditableMap::setRenderOrder(RenderOrder value)
512 {
513 if (auto doc = mapDocument())
514 push(new ChangeMapProperty(doc, static_cast<Map::RenderOrder>(value)));
515 else if (!checkReadOnly())
516 map()->setRenderOrder(static_cast<Map::RenderOrder>(value));
517 }
518
setBackgroundColor(const QColor & value)519 void EditableMap::setBackgroundColor(const QColor &value)
520 {
521 if (auto doc = mapDocument())
522 push(new ChangeMapProperty(doc, value));
523 else if (!checkReadOnly())
524 map()->setBackgroundColor(value);
525 }
526
setLayerDataFormat(LayerDataFormat value)527 void EditableMap::setLayerDataFormat(LayerDataFormat value)
528 {
529 if (auto doc = mapDocument())
530 push(new ChangeMapProperty(doc, static_cast<Map::LayerDataFormat>(value)));
531 else if (!checkReadOnly())
532 map()->setLayerDataFormat(static_cast<Map::LayerDataFormat>(value));
533 }
534
setCurrentLayer(EditableLayer * layer)535 void EditableMap::setCurrentLayer(EditableLayer *layer)
536 {
537 QList<QObject*> layers;
538 if (layer)
539 layers.append(layer);
540
541 setSelectedLayers(layers);
542 }
543
setSelectedLayers(const QList<QObject * > & layers)544 void EditableMap::setSelectedLayers(const QList<QObject *> &layers)
545 {
546 auto document = mapDocument();
547 if (!document)
548 return;
549
550 QList<Layer*> plainLayers;
551
552 for (QObject *layerObject : layers) {
553 auto editableLayer = qobject_cast<EditableLayer*>(layerObject);
554 if (!editableLayer) {
555 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Not a layer"));
556 return;
557 }
558 if (editableLayer->map() != this) {
559 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Layer not from this map"));
560 return;
561 }
562
563 plainLayers.append(editableLayer->layer());
564 }
565
566 document->switchSelectedLayers(plainLayers);
567 }
568
setSelectedObjects(const QList<QObject * > & objects)569 void EditableMap::setSelectedObjects(const QList<QObject *> &objects)
570 {
571 auto document = mapDocument();
572 if (!document)
573 return;
574
575 QList<MapObject*> plainObjects;
576
577 for (QObject *objectObject : objects) {
578 auto editableMapObject = qobject_cast<EditableMapObject*>(objectObject);
579 if (!editableMapObject) {
580 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Not an object"));
581 return;
582 }
583 if (editableMapObject->map() != this) {
584 ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Object not from this map"));
585 return;
586 }
587
588 plainObjects.append(editableMapObject->mapObject());
589 }
590
591 document->setSelectedObjects(plainObjects);
592 }
593
documentChanged(const ChangeEvent & change)594 void EditableMap::documentChanged(const ChangeEvent &change)
595 {
596 switch (change.type) {
597 case ChangeEvent::MapChanged:
598 if (static_cast<const MapChangeEvent&>(change).property == Map::OrientationProperty)
599 mRenderer.reset();
600 break;
601 case ChangeEvent::MapObjectsAdded:
602 attachMapObjects(static_cast<const MapObjectsEvent&>(change).mapObjects);
603 break;
604 case ChangeEvent::MapObjectsAboutToBeRemoved:
605 detachMapObjects(static_cast<const MapObjectsEvent&>(change).mapObjects);
606 break;
607 default:
608 break;
609 }
610 }
611
attachLayer(Layer * layer)612 void EditableMap::attachLayer(Layer *layer)
613 {
614 if (EditableLayer *editable = EditableManager::instance().find(layer))
615 editable->attach(this);
616
617 if (GroupLayer *groupLayer = layer->asGroupLayer()) {
618 for (Layer *childLayer : groupLayer->layers())
619 attachLayer(childLayer);
620 } else if (ObjectGroup *objectGroup = layer->asObjectGroup()) {
621 attachMapObjects(objectGroup->objects());
622 }
623 }
624
detachLayer(Layer * layer)625 void EditableMap::detachLayer(Layer *layer)
626 {
627 auto editableLayer = EditableManager::instance().find(layer);
628 if (editableLayer && editableLayer->map() == this)
629 editableLayer->detach();
630
631 if (GroupLayer *groupLayer = layer->asGroupLayer()) {
632 for (Layer *childLayer : groupLayer->layers())
633 detachLayer(childLayer);
634 } else if (ObjectGroup *objectGroup = layer->asObjectGroup()) {
635 detachMapObjects(objectGroup->objects());
636 }
637 }
638
attachMapObjects(const QList<MapObject * > & mapObjects)639 void EditableMap::attachMapObjects(const QList<MapObject *> &mapObjects)
640 {
641 const auto &editableManager = EditableManager::instance();
642 for (MapObject *mapObject : mapObjects) {
643 if (EditableMapObject *editable = editableManager.find(mapObject))
644 editable->attach(this);
645 }
646 }
647
detachMapObjects(const QList<MapObject * > & mapObjects)648 void EditableMap::detachMapObjects(const QList<MapObject *> &mapObjects)
649 {
650 const auto &editableManager = EditableManager::instance();
651 for (MapObject *mapObject : mapObjects) {
652 if (EditableMapObject *editable = editableManager.find(mapObject)) {
653 Q_ASSERT(editable->map() == this);
654 editable->detach();
655 }
656 }
657 }
658
onCurrentLayerChanged(Layer *)659 void EditableMap::onCurrentLayerChanged(Layer *)
660 {
661 emit currentLayerChanged();
662 }
663
renderer() const664 MapRenderer *EditableMap::renderer() const
665 {
666 if (!mRenderer)
667 mRenderer = MapRenderer::create(map());
668
669 return mRenderer.get();
670 }
671
672 } // namespace Tiled
673
674 #include "moc_editablemap.cpp"
675