1 /*
2  * mapdocument.cpp
3  * Copyright 2008-2017, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright 2009, Jeff Bland <jeff@teamphobic.com>
5  *
6  * This file is part of Tiled.
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the Free
10  * Software Foundation; either version 2 of the License, or (at your option)
11  * any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  * more details.
17  *
18  * You should have received a copy of the GNU General Public License along with
19  * this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include "mapdocument.h"
23 
24 #include "addremovelayer.h"
25 #include "addremovemapobject.h"
26 #include "addremovetileset.h"
27 #include "brokenlinks.h"
28 #include "changelayer.h"
29 #include "changemapobject.h"
30 #include "changemapobjectsorder.h"
31 #include "changeproperties.h"
32 #include "changeselectedarea.h"
33 #include "containerhelpers.h"
34 #include "editablemap.h"
35 #include "flipmapobjects.h"
36 #include "grouplayer.h"
37 #include "imagelayer.h"
38 #include "issuesmodel.h"
39 #include "layermodel.h"
40 #include "logginginterface.h"
41 #include "mapobject.h"
42 #include "mapobjectmodel.h"
43 #include "maprenderer.h"
44 #include "movelayer.h"
45 #include "movemapobject.h"
46 #include "movemapobjecttogroup.h"
47 #include "objectgroup.h"
48 #include "objecttemplate.h"
49 #include "offsetlayer.h"
50 #include "painttilelayer.h"
51 #include "rangeset.h"
52 #include "reparentlayers.h"
53 #include "resizemap.h"
54 #include "resizetilelayer.h"
55 #include "rotatemapobject.h"
56 #include "templatemanager.h"
57 #include "tile.h"
58 #include "tilelayer.h"
59 #include "tilesetdocument.h"
60 
61 #include <QFileInfo>
62 #include <QRect>
63 #include <QUndoStack>
64 
65 #include "changeevents.h"
66 #include "qtcompat_p.h"
67 
68 using namespace Tiled;
69 
MapDocument(std::unique_ptr<Map> map)70 MapDocument::MapDocument(std::unique_ptr<Map> map)
71     : Document(MapDocumentType, map->fileName)
72     , mMap(std::move(map))
73     , mLayerModel(new LayerModel(this))
74     , mHoveredMapObject(nullptr)
75     , mMapObjectModel(new MapObjectModel(this))
76 {
77     mCurrentObject = mMap.get();
78 
79     createRenderer();
80 
81     if (mMap->layerCount() > 0) {
82         mCurrentLayer = mMap->layerAt(0);
83         mSelectedLayers.append(mCurrentLayer);
84     }
85 
86     mLayerModel->setMapDocument(this);
87 
88     // Forward signals emitted from the layer model
89     connect(mLayerModel, &LayerModel::layerAdded,
90             this, &MapDocument::onLayerAdded);
91     connect(mLayerModel, &LayerModel::layerAboutToBeRemoved,
92             this, &MapDocument::onLayerAboutToBeRemoved);
93     connect(mLayerModel, &LayerModel::layerRemoved,
94             this, &MapDocument::onLayerRemoved);
95 
96     // Forward signals emitted from the map object model
97     mMapObjectModel->setMapDocument(this);
98     connect(this, &Document::changed,
99             this, &MapDocument::onChanged);
100 
101     connect(mMapObjectModel, &QAbstractItemModel::rowsInserted,
102             this, &MapDocument::onMapObjectModelRowsInserted);
103     connect(mMapObjectModel, &QAbstractItemModel::rowsRemoved,
104             this, &MapDocument::onMapObjectModelRowsInsertedOrRemoved);
105     connect(mMapObjectModel, &QAbstractItemModel::rowsMoved,
106             this, &MapDocument::onObjectsMoved);
107 
108     connect(TemplateManager::instance(), &TemplateManager::objectTemplateChanged,
109             this, &MapDocument::updateTemplateInstances);
110 }
111 
~MapDocument()112 MapDocument::~MapDocument()
113 {
114     // Clear any previously found issues in this document
115     IssuesModel::instance().removeIssuesWithContext(this);
116 
117     // Needs to be deleted before the Map instance is deleted, because it may
118     // cause script values to detach from the map, in which case they'll need
119     // to be able to copy the data.
120     mEditable.reset();
121 }
122 
save(const QString & fileName,QString * error)123 bool MapDocument::save(const QString &fileName, QString *error)
124 {
125     MapFormat *mapFormat = writerFormat();
126     if (!mapFormat) {
127         if (error)
128             *error = tr("Map format '%s' not found").arg(mWriterFormat);
129         return false;
130     }
131 
132     if (!mapFormat->write(map(), fileName)) {
133         if (error)
134             *error = mapFormat->errorString();
135         return false;
136     }
137 
138     undoStack()->setClean();
139 
140     if (mMap->fileName != fileName) {
141         mMap->fileName = fileName;
142         mMap->exportFileName.clear();
143     }
144 
145     setFileName(fileName);
146     mLastSaved = QFileInfo(fileName).lastModified();
147 
148     // Mark TilesetDocuments for embedded tilesets as saved
149     for (const SharedTileset &tileset : mMap->tilesets()) {
150         if (TilesetDocument *tilesetDocument = TilesetDocument::findDocumentForTileset(tileset))
151             if (tilesetDocument->isEmbedded())
152                 tilesetDocument->setClean();
153     }
154 
155     emit saved();
156     return true;
157 }
158 
load(const QString & fileName,MapFormat * format,QString * error)159 MapDocumentPtr MapDocument::load(const QString &fileName,
160                                  MapFormat *format,
161                                  QString *error)
162 {
163     auto map = format->read(fileName);
164 
165     if (!map) {
166         if (error)
167             *error = format->errorString();
168         return MapDocumentPtr();
169     }
170 
171     map->fileName = fileName;
172 
173     MapDocumentPtr document = MapDocumentPtr::create(std::move(map));
174     document->setReaderFormat(format);
175     if (format->hasCapabilities(MapFormat::Write))
176         document->setWriterFormat(format);
177 
178     return document;
179 }
180 
readerFormat() const181 MapFormat *MapDocument::readerFormat() const
182 {
183     return findFileFormat<MapFormat>(mReaderFormat, FileFormat::Read);
184 }
185 
setReaderFormat(MapFormat * format)186 void MapDocument::setReaderFormat(MapFormat *format)
187 {
188     Q_ASSERT(format->hasCapabilities(FileFormat::Read));
189     mReaderFormat = format->shortName();
190 }
191 
writerFormat() const192 MapFormat *MapDocument::writerFormat() const
193 {
194     return findFileFormat<MapFormat>(mWriterFormat, FileFormat::Write);
195 }
196 
setWriterFormat(MapFormat * format)197 void MapDocument::setWriterFormat(MapFormat *format)
198 {
199     Q_ASSERT(format->hasCapabilities(FileFormat::Write));
200     mWriterFormat = format->shortName();
201 }
202 
lastExportFileName() const203 QString MapDocument::lastExportFileName() const
204 {
205     return map()->exportFileName;
206 }
207 
setLastExportFileName(const QString & fileName)208 void MapDocument::setLastExportFileName(const QString &fileName)
209 {
210     map()->exportFileName = fileName;
211 }
212 
exportFormat() const213 MapFormat *MapDocument::exportFormat() const
214 {
215     return findFileFormat<MapFormat>(map()->exportFormat);
216 }
217 
setExportFormat(FileFormat * format)218 void MapDocument::setExportFormat(FileFormat *format)
219 {
220     Q_ASSERT(qobject_cast<MapFormat*>(format));
221     map()->exportFormat = format->shortName();
222 }
223 
224 /**
225  * Returns the name with which to display this map. It is the file name without
226  * its path, or 'untitled.tmx' when the map has no file name.
227  */
displayName() const228 QString MapDocument::displayName() const
229 {
230     QString displayName = QFileInfo(fileName()).fileName();
231     if (displayName.isEmpty())
232         displayName = tr("untitled.tmx");
233 
234     return displayName;
235 }
236 
editable()237 EditableAsset *MapDocument::editable()
238 {
239     if (!mEditable)
240         mEditable.reset(new EditableMap(this, this));
241 
242     return mEditable.get();
243 }
244 
245 /**
246   * Returns the sibling index of the given \a layer, or -1 if no layer is given.
247   */
layerIndex(const Layer * layer) const248 int MapDocument::layerIndex(const Layer *layer) const
249 {
250     if (!layer)
251         return -1;
252     return layer->siblingIndex();
253 }
254 
setCurrentLayer(Layer * layer)255 void MapDocument::setCurrentLayer(Layer *layer)
256 {
257     if (mCurrentLayer == layer)
258         return;
259 
260     mCurrentLayer = layer;
261     emit currentLayerChanged(layer);
262 
263     if (layer)
264         if (!mCurrentObject || mCurrentObject->typeId() == Object::LayerType)
265             setCurrentObject(layer);
266 }
267 
setSelectedLayers(const QList<Layer * > & layers)268 void MapDocument::setSelectedLayers(const QList<Layer *> &layers)
269 {
270     if (mSelectedLayers == layers)
271         return;
272 
273     mSelectedLayers = layers;
274     emit selectedLayersChanged();
275 }
276 
switchCurrentLayer(Layer * layer)277 void MapDocument::switchCurrentLayer(Layer *layer)
278 {
279     setCurrentLayer(layer);
280 
281     // Automatically select the layer if it isn't already
282     if (layer && !mSelectedLayers.contains(layer))
283         setSelectedLayers({ layer });
284 }
285 
switchSelectedLayers(const QList<Layer * > & layers)286 void MapDocument::switchSelectedLayers(const QList<Layer *> &layers)
287 {
288     setSelectedLayers(layers);
289 
290     // Automatically make sure the current layer is one of the selected ones
291     if (!layers.contains(mCurrentLayer))
292         setCurrentLayer(layers.isEmpty() ? nullptr : layers.first());
293 }
294 
295 /**
296  * Custom intersects check necessary because QRectF::intersects wants a
297  * non-empty area of overlap, but we should also consider overlap with empty
298  * area as intersection.
299  *
300  * Results for rectangles with negative size are undefined.
301  */
intersects(const QRectF & a,const QRectF & b)302 static bool intersects(const QRectF &a, const QRectF &b)
303 {
304     return a.right() >= b.left() &&
305             a.bottom() >= b.top() &&
306             a.left() <= b.right() &&
307             a.top() <= b.bottom();
308 }
309 
visibleIn(const QRectF & area,MapObject * object,const MapRenderer & renderer)310 static bool visibleIn(const QRectF &area, MapObject *object,
311                       const MapRenderer &renderer)
312 {
313     QRectF boundingRect = renderer.boundingRect(object);
314 
315     if (object->rotation() != 0) {
316         // Rotate around object position
317         QPointF pos = renderer.pixelToScreenCoords(object->position());
318         boundingRect.translate(-pos);
319 
320         QTransform transform;
321         transform.rotate(object->rotation());
322         boundingRect = transform.mapRect(boundingRect);
323 
324         boundingRect.translate(pos);
325     }
326 
327     return intersects(area, boundingRect);
328 }
329 
resizeMap(QSize size,QPoint offset,bool removeObjects)330 void MapDocument::resizeMap(QSize size, QPoint offset, bool removeObjects)
331 {
332     const QRegion movedSelection = selectedArea().translated(offset);
333     const QRect newArea = QRect(-offset, size);
334     const QRectF visibleArea = renderer()->boundingRect(newArea);
335 
336     const QPointF origin = renderer()->tileToPixelCoords(QPointF());
337     const QPointF newOrigin = renderer()->tileToPixelCoords(-offset);
338     const QPointF pixelOffset = origin - newOrigin;
339 
340     // Resize the map and each layer
341     QUndoCommand *command = new QUndoCommand(tr("Resize Map"));
342 
343     QList<MapObject *> objectsToRemove;
344 
345     LayerIterator iterator(map());
346     while (Layer *layer = iterator.next()) {
347         switch (layer->layerType()) {
348         case Layer::TileLayerType: {
349             TileLayer *tileLayer = static_cast<TileLayer*>(layer);
350             new ResizeTileLayer(this, tileLayer, size, offset, command);
351             break;
352         }
353         case Layer::ObjectGroupType: {
354             ObjectGroup *objectGroup = static_cast<ObjectGroup*>(layer);
355 
356             for (MapObject *o : objectGroup->objects()) {
357                 if (removeObjects && !visibleIn(visibleArea, o, *renderer())) {
358                     // Remove objects that will fall outside of the map
359                     objectsToRemove.append(o);
360                 } else {
361                     QPointF oldPos = o->position();
362                     QPointF newPos = oldPos + pixelOffset;
363                     new MoveMapObject(this, o, newPos, oldPos, command);
364                 }
365             }
366             break;
367         }
368         case Layer::ImageLayerType: {
369             // Adjust image layer by changing its offset
370             auto imageLayer = static_cast<ImageLayer*>(layer);
371             new SetLayerOffset(this, layer,
372                                imageLayer->offset() + pixelOffset,
373                                command);
374             break;
375         }
376         case Layer::GroupLayerType: {
377             // Recursion handled by LayerIterator
378             break;
379         }
380         }
381     }
382 
383     if (!objectsToRemove.isEmpty())
384         new RemoveMapObjects(this, objectsToRemove, command);
385 
386     new ResizeMap(this, size, command);
387     new ChangeSelectedArea(this, movedSelection, command);
388 
389     undoStack()->push(command);
390 
391     // TODO: Handle layers that don't match the map size correctly
392 }
393 
autocropMap()394 void MapDocument::autocropMap()
395 {
396     if (!mCurrentLayer || !mCurrentLayer->isTileLayer())
397         return;
398 
399     TileLayer *tileLayer = static_cast<TileLayer*>(mCurrentLayer);
400 
401     const QRect bounds = tileLayer->region().boundingRect();
402     if (bounds.isNull())
403         return;
404 
405     resizeMap(bounds.size(), -bounds.topLeft(), true);
406 }
407 
offsetMap(const QList<Layer * > & layers,const QPoint offset,const QRect & bounds,bool wrapX,bool wrapY)408 void MapDocument::offsetMap(const QList<Layer*> &layers,
409                             const QPoint offset,
410                             const QRect &bounds,
411                             bool wrapX, bool wrapY)
412 {
413     if (layers.empty())
414         return;
415 
416     undoStack()->beginMacro(tr("Offset Map"));
417     for (auto layer : layers) {
418         undoStack()->push(new OffsetLayer(this, layer, offset,
419                                           bounds, wrapX, wrapY));
420     }
421     undoStack()->endMacro();
422 }
423 
424 /**
425  * Flips the selected objects in the given \a direction.
426  */
flipSelectedObjects(FlipDirection direction)427 void MapDocument::flipSelectedObjects(FlipDirection direction)
428 {
429     if (mSelectedObjects.isEmpty())
430         return;
431 
432     undoStack()->push(new FlipMapObjects(this, mSelectedObjects, direction));
433 }
434 
435 /**
436  * Rotates the selected objects.
437  */
rotateSelectedObjects(RotateDirection direction)438 void MapDocument::rotateSelectedObjects(RotateDirection direction)
439 {
440     if (mSelectedObjects.isEmpty())
441         return;
442 
443     undoStack()->beginMacro(tr("Rotate %n Object(s)", "",
444                                mSelectedObjects.size()));
445 
446     // TODO: Rotate them properly as a group
447     const auto &selectedObjects = mSelectedObjects;
448     for (MapObject *mapObject : selectedObjects) {
449         const qreal oldRotation = mapObject->rotation();
450         qreal newRotation = oldRotation;
451 
452         if (direction == RotateLeft) {
453             newRotation -= 90;
454             if (newRotation < -180)
455                 newRotation += 360;
456         } else {
457             newRotation += 90;
458             if (newRotation > 180)
459                 newRotation -= 360;
460         }
461 
462         undoStack()->push(new RotateMapObject(this, mapObject,
463                                               newRotation, oldRotation));
464     }
465     undoStack()->endMacro();
466 }
467 
468 /**
469  * Adds a layer of the given type to the top of the layer stack. After adding
470  * the new layer, emits editLayerNameRequested().
471  */
addLayer(Layer::TypeFlag layerType)472 Layer *MapDocument::addLayer(Layer::TypeFlag layerType)
473 {
474     Layer *layer = nullptr;
475     QString name;
476 
477     switch (layerType) {
478     case Layer::TileLayerType:
479         name = tr("Tile Layer %1").arg(mMap->tileLayerCount() + 1);
480         layer = new TileLayer(name, 0, 0, mMap->width(), mMap->height());
481         break;
482     case Layer::ObjectGroupType:
483         name = tr("Object Layer %1").arg(mMap->objectGroupCount() + 1);
484         layer = new ObjectGroup(name, 0, 0);
485         break;
486     case Layer::ImageLayerType:
487         name = tr("Image Layer %1").arg(mMap->imageLayerCount() + 1);
488         layer = new ImageLayer(name, 0, 0);
489         break;
490     case Layer::GroupLayerType:
491         name = tr("Group %1").arg(mMap->groupLayerCount() + 1);
492         layer = new GroupLayer(name, 0, 0);
493         break;
494     }
495     Q_ASSERT(layer);
496 
497     auto parentLayer = mCurrentLayer ? mCurrentLayer->parentLayer() : nullptr;
498     const int index = layerIndex(mCurrentLayer) + 1;
499     undoStack()->push(new AddLayer(this, index, layer, parentLayer));
500     switchSelectedLayers({layer});
501 
502     emit editLayerNameRequested();
503 
504     return layer;
505 }
506 
groupLayers(const QList<Layer * > & layers)507 void MapDocument::groupLayers(const QList<Layer *> &layers)
508 {
509     if (layers.isEmpty())
510         return;
511 
512     const auto parentLayer = layers.first()->parentLayer();
513     const int index = layers.first()->siblingIndex() + 1;
514 
515     for (Layer *layer : layers) {
516         Q_ASSERT(layer->map() == mMap.get());
517 
518         // If any of the layers to be grouped is a GroupLayer, make sure we
519         // do not try to move it into one of its own children.
520         if (layer->isGroupLayer() && parentLayer && parentLayer->isParentOrSelf(layer))
521             return;
522     }
523 
524     const QString name = tr("Group %1").arg(mMap->groupLayerCount() + 1);
525     auto groupLayer = new GroupLayer(name, 0, 0);
526     undoStack()->beginMacro(tr("Group %n Layer(s)", "", layers.size()));
527     undoStack()->push(new AddLayer(this, index, groupLayer, parentLayer));
528     undoStack()->push(new ReparentLayers(this, layers, groupLayer, 0));
529     undoStack()->endMacro();
530 }
531 
532 /**
533  * Ungroups the given list of \a layers. If the layer itself is a group layer,
534  * then this group is ungrouped. Otherwise, if the layer is part of a group
535  * layer, then it is removed from the group.
536  */
ungroupLayers(const QList<Layer * > & layers)537 void MapDocument::ungroupLayers(const QList<Layer *> &layers)
538 {
539     if (layers.isEmpty())
540         return;
541 
542     undoStack()->beginMacro(tr("Ungroup %n Layer(s)", "", layers.size()));
543 
544     // Copy needed because while ungrouping the original list may get modified.
545     // Also, we may need to remove group layers from this list if they get
546     // removed due to becoming empty.
547     auto layersToUngroup = layers;
548 
549     while (!layersToUngroup.isEmpty()) {
550         Layer *layer = layersToUngroup.takeFirst();
551 
552         GroupLayer *groupLayer = layer->asGroupLayer();
553         QList<Layer *> layersToReparent;
554 
555         if (groupLayer) {
556             layersToReparent = groupLayer->layers();
557         } else if (layer->parentLayer()) {
558             layersToReparent.append(layer);
559             groupLayer = layer->parentLayer();
560         } else {
561             // No ungrouping possible
562             continue;
563         }
564 
565         GroupLayer *targetParent = groupLayer->parentLayer();
566         int groupIndex = groupLayer->siblingIndex();
567 
568         if (!layersToReparent.isEmpty())
569             undoStack()->push(new ReparentLayers(this, layersToReparent, targetParent, groupIndex + 1));
570 
571         if (groupLayer->layerCount() == 0) {
572             undoStack()->push(new RemoveLayer(this, groupIndex, targetParent));
573             layersToUngroup.removeOne(groupLayer);
574         }
575     }
576 
577     undoStack()->endMacro();
578 }
579 
580 /**
581  * Duplicates the currently selected layers.
582  */
duplicateLayers(const QList<Layer * > & layers)583 void MapDocument::duplicateLayers(const QList<Layer *> &layers)
584 {
585     if (layers.isEmpty())
586         return;
587 
588     undoStack()->beginMacro(tr("Duplicate %n Layer(s)", "", layers.size()));
589 
590     QList<Layer *> layersToDuplicate;
591 
592     // Duplicate layers in the right order (groups before their children)
593     LayerIterator iterator(mMap.get());
594     iterator.toBack();
595     while (Layer *layer = iterator.previous())
596         if (layers.contains(layer))
597             layersToDuplicate.append(layer);
598 
599     QList<Layer *> newLayers;
600     GroupLayer *previousParentLayer = nullptr;
601     int previousIndex = 0;
602 
603     while (!layersToDuplicate.isEmpty()) {
604         Layer *layer = layersToDuplicate.takeFirst();
605 
606         // If a group layer gets duplicated, make sure any children are removed
607         // from the remaining list of layers to duplicate
608         if (layer->isGroupLayer()) {
609             for (int i = layersToDuplicate.size() - 1; i >= 0; --i)
610                 if (layersToDuplicate.at(i)->isParentOrSelf(layer))
611                     layersToDuplicate.removeAt(i);
612         }
613 
614         Layer *duplicate = layer->clone();
615         duplicate->resetIds();
616         duplicate->setName(tr("Copy of %1").arg(duplicate->name()));
617 
618         auto parentLayer = layer->parentLayer();
619 
620         int index = previousIndex;
621         if (newLayers.isEmpty() || previousParentLayer != parentLayer)
622             index = layer->siblingIndex() + 1;
623 
624         undoStack()->push(new AddLayer(this, index, duplicate, parentLayer));
625 
626         previousParentLayer = parentLayer;
627         previousIndex = index;
628 
629         newLayers.append(duplicate);
630     }
631 
632     undoStack()->endMacro();
633 
634     switchSelectedLayers(newLayers);
635 }
636 
637 /**
638  * Merges the given \a layers down, each to the layer directly below them.
639  * Layers that can't be merged down are skipped.
640  *
641  * \see Layer::canMergeDown
642  */
mergeLayersDown(const QList<Layer * > & layers)643 void MapDocument::mergeLayersDown(const QList<Layer *> &layers)
644 {
645     QList<Layer *> layersToMerge;
646 
647     for (Layer *layer : layers)
648         if (layer->canMergeDown())
649             layersToMerge.append(layer);
650 
651     if (layersToMerge.isEmpty())
652         return;
653 
654     undoStack()->beginMacro(tr("Merge Layer Down")); // todo: support plural after string-freeze
655 
656     Layer *lastMergedLayer = nullptr;
657 
658     while (!layersToMerge.isEmpty()) {
659         Layer *layer = layersToMerge.takeFirst();
660 
661         const int index = layer->siblingIndex();
662         Q_ASSERT(index >= 1);
663 
664         Layer *lowerLayer = layer->siblings().at(index - 1);
665         Layer *merged = lowerLayer->mergedWith(layer);
666         GroupLayer *parentLayer = layer->parentLayer();
667 
668         undoStack()->push(new AddLayer(this, index - 1, merged, parentLayer));
669         undoStack()->push(new RemoveLayer(this, index, parentLayer));
670         undoStack()->push(new RemoveLayer(this, index, parentLayer));
671 
672         // If the layer we've merged with was also scheduled to get merged down,
673         // we need to update the pointer to the new layer.
674         int lowerLayerIndex = layersToMerge.indexOf(lowerLayer);
675         if (lowerLayerIndex != -1)
676             layersToMerge[lowerLayerIndex] = merged;
677 
678         lastMergedLayer = merged;
679     }
680 
681     undoStack()->endMacro();
682 
683     switchSelectedLayers({ lastMergedLayer });
684 }
685 
686 /**
687  * Moves the given \a layers up, when it is not already at the top of the map.
688  */
moveLayersUp(const QList<Layer * > & layers)689 void MapDocument::moveLayersUp(const QList<Layer *> &layers)
690 {
691     QList<Layer *> layersToMove;
692     layersToMove.reserve(layers.size());
693 
694     // Move layers in the right order, and abort if one of the layers can't be
695     // moved (iterating backwards because when moving layers up we need to
696     // start moving the top-most layer first)
697     LayerIterator iterator(mMap.get());
698     iterator.toBack();
699     while (Layer *layer = iterator.previous()) {
700         if (layers.contains(layer)) {
701             if (!MoveLayer::canMoveUp(*layer))
702                 return;
703 
704             layersToMove.append(layer);
705         }
706     }
707 
708     if (layersToMove.isEmpty())
709         return;
710 
711     undoStack()->beginMacro(QCoreApplication::translate("Undo Commands", "Raise %n Layer(s)", "", layersToMove.size()));
712     for (Layer *layer : qAsConst(layersToMove))
713         undoStack()->push(new MoveLayer(this, layer, MoveLayer::Up));
714     undoStack()->endMacro();
715 }
716 
717 /**
718  * Moves the given \a layers up, when it is not already at the bottom of the map.
719  */
moveLayersDown(const QList<Layer * > & layers)720 void MapDocument::moveLayersDown(const QList<Layer *> &layers)
721 {
722     QList<Layer *> layersToMove;
723     layersToMove.reserve(layers.size());
724 
725     // Move layers in the right order, and abort if one of the layers can't be moved
726     for (Layer *layer : mMap->allLayers()) {
727         if (layers.contains(layer)) {
728             if (!MoveLayer::canMoveDown(*layer))
729                 return;
730 
731             layersToMove.append(layer);
732         }
733     }
734 
735     if (layersToMove.isEmpty())
736         return;
737 
738     undoStack()->beginMacro(QCoreApplication::translate("Undo Commands", "Lower %n Layer(s)", "", layersToMove.size()));
739     for (Layer *layer : qAsConst(layersToMove))
740         undoStack()->push(new MoveLayer(this, layer, MoveLayer::Down));
741     undoStack()->endMacro();
742 }
743 
744 /**
745  * Removes the given \a layers.
746  */
removeLayers(const QList<Layer * > & layers)747 void MapDocument::removeLayers(const QList<Layer *> &layers)
748 {
749     if (layers.isEmpty())
750         return;
751 
752     undoStack()->beginMacro(tr("Remove %n Layer(s)", "", layers.size()));
753 
754     // Copy needed because while removing the original list may get modified
755     auto layersToRemove = layers;
756 
757     while (!layersToRemove.isEmpty()) {
758         Layer *layer = layersToRemove.takeFirst();
759         Q_ASSERT(layer->map() == mMap.get());
760 
761         undoStack()->push(new RemoveLayer(this,
762                                           layer->siblingIndex(),
763                                           layer->parentLayer()));
764 
765         // If a group layer gets removed, make sure any children are removed
766         // from the remaining list of layers to remove
767         if (layer->isGroupLayer()) {
768             for (int i = layersToRemove.size() - 1; i >= 0; --i)
769                 if (layersToRemove.at(i)->isParentOrSelf(layer))
770                     layersToRemove.removeAt(i);
771         }
772     }
773 
774     undoStack()->endMacro();
775 }
776 
777 /**
778  * \see LayerModel::toggleLayers
779  */
toggleLayers(const QList<Layer * > & layers)780 void MapDocument::toggleLayers(const QList<Layer *> &layers)
781 {
782     mLayerModel->toggleLayers(layers);
783 }
784 
785 /**
786  * \see LayerModel::toggleLockLayers
787  */
toggleLockLayers(const QList<Layer * > & layers)788 void MapDocument::toggleLockLayers(const QList<Layer *> &layers)
789 {
790     mLayerModel->toggleLockLayers(layers);
791 }
792 
793 /**
794  * \see LayerModel::toggleOtherLayers
795  */
toggleOtherLayers(const QList<Layer * > & layers)796 void MapDocument::toggleOtherLayers(const QList<Layer *> &layers)
797 {
798     mLayerModel->toggleOtherLayers(layers);
799 }
800 
801 /**
802  * \see LayerModel::toggleLockOtherLayers
803  */
toggleLockOtherLayers(const QList<Layer * > & layers)804 void MapDocument::toggleLockOtherLayers(const QList<Layer *> &layers)
805 {
806     mLayerModel->toggleLockOtherLayers(layers);
807 }
808 
809 
810 /**
811  * Adds a tileset to this map at the given \a index. Emits the appropriate
812  * signal.
813  */
insertTileset(int index,const SharedTileset & tileset)814 void MapDocument::insertTileset(int index, const SharedTileset &tileset)
815 {
816     emit tilesetAboutToBeAdded(index);
817     mMap->insertTileset(index, tileset);
818     emit tilesetAdded(index, tileset.data());
819 }
820 
821 /**
822  * Removes the tileset at the given \a index from this map. Emits the
823  * appropriate signal.
824  *
825  * \warning Does not make sure that any references to tiles in the removed
826  *          tileset are cleared.
827  */
removeTilesetAt(int index)828 void MapDocument::removeTilesetAt(int index)
829 {
830     emit tilesetAboutToBeRemoved(index);
831 
832     SharedTileset tileset = mMap->tilesets().at(index);
833     mMap->removeTilesetAt(index);
834     emit tilesetRemoved(tileset.data());
835 }
836 
837 /**
838  * Replaces the tileset at the given \a index with the new \a tileset. Replaces
839  * all tiles from the replaced tileset with tiles from the new tileset.
840  *
841  * @return The replaced tileset.
842  */
replaceTileset(int index,const SharedTileset & tileset)843 SharedTileset MapDocument::replaceTileset(int index, const SharedTileset &tileset)
844 {
845     emit tilesetAboutToBeRemoved(index);
846 
847     const SharedTileset oldTileset = mMap->tilesetAt(index);
848     bool added = mMap->replaceTileset(oldTileset, tileset);
849 
850     emit tilesetReplaced(index, tileset.data(), oldTileset.data());
851     emit tilesetRemoved(oldTileset.data());
852     if (added)
853         emit tilesetAdded(index, tileset.data());
854 
855     return oldTileset;
856 }
857 
858 /**
859  * Paints the tile layers present in the given \a map onto this map. Matches
860  * layers by name and creates new layers when they could not be found.
861  *
862  * In case the \a map only contains a single tile layer, it is always painted
863  * into the current tile layer. This happens also for unnamed layers. In these
864  * cases, the layers are skipped when the current layer isn't a tile layer.
865  *
866  * If the matched target layer is locked it is also skipped.
867  *
868  * \a mergeable indicates whether the paint operations performed by this
869  * function are mergeable with previous compatible paint operations.
870  *
871  * If \a missingTilesets is given, the listed tilesets will be added to the map
872  * on the first paint operation. The list will then be cleared.
873  *
874  * If \a paintedRegions is given, then no regionEdited signal is emitted.
875  * In this case it is the responsibility of the caller to emit this signal for
876  * each affected tile layer.
877  */
paintTileLayers(const Map * map,bool mergeable,QVector<SharedTileset> * missingTilesets,QHash<TileLayer *,QRegion> * paintedRegions)878 void MapDocument::paintTileLayers(const Map *map, bool mergeable,
879                                   QVector<SharedTileset> *missingTilesets,
880                                   QHash<TileLayer*, QRegion> *paintedRegions)
881 {
882     TileLayer *currentTileLayer = mCurrentLayer ? mCurrentLayer->asTileLayer() : nullptr;
883 
884     LayerIterator it(map, Layer::TileLayerType);
885     const bool isMultiLayer = it.next() && it.next();
886 
887     it.toFront();
888     while (auto tileLayer = static_cast<TileLayer*>(it.next())) {
889         TileLayer *targetLayer = currentTileLayer;
890         bool addLayer = false;
891 
892         // When the map contains only a single layer, always paint it into
893         // the current layer. This makes sure you can still take pieces from
894         // one layer and draw them into another.
895         if (isMultiLayer && !tileLayer->name().isEmpty()) {
896             targetLayer = static_cast<TileLayer*>(mMap->findLayer(tileLayer->name(), Layer::TileLayerType));
897             if (!targetLayer) {
898                 // Create a layer with this name
899                 targetLayer = new TileLayer(tileLayer->name(), 0, 0,
900                                             mMap->width(),
901                                             mMap->height());
902                 addLayer = true;
903             }
904         }
905 
906         if (!targetLayer)
907             continue;
908         if (!targetLayer->isUnlocked())
909             continue;
910         if (!mMap->infinite() && !targetLayer->rect().intersects(tileLayer->bounds()))
911             continue;
912 
913         PaintTileLayer *paint = new PaintTileLayer(this,
914                                                    targetLayer,
915                                                    tileLayer->x(),
916                                                    tileLayer->y(),
917                                                    tileLayer);
918 
919         if (missingTilesets && !missingTilesets->isEmpty()) {
920             for (const SharedTileset &tileset : qAsConst(*missingTilesets)) {
921                 if (!mMap->tilesets().contains(tileset))
922                     new AddTileset(this, tileset, paint);
923             }
924 
925             missingTilesets->clear();
926         }
927 
928         if (addLayer) {
929             new AddLayer(this,
930                          mMap->layerCount(), targetLayer, nullptr,
931                          paint);
932         }
933 
934         paint->setMergeable(mergeable);
935         undoStack()->push(paint);
936 
937         const QRegion editedRegion = tileLayer->region();
938         if (paintedRegions)
939             (*paintedRegions)[targetLayer] |= editedRegion;
940         else
941             emit regionEdited(editedRegion, targetLayer);
942 
943         mergeable = true; // further paints are always mergeable
944     }
945 }
946 
replaceObjectTemplate(const ObjectTemplate * oldObjectTemplate,const ObjectTemplate * newObjectTemplate)947 void MapDocument::replaceObjectTemplate(const ObjectTemplate *oldObjectTemplate,
948                                         const ObjectTemplate *newObjectTemplate)
949 {
950     auto changedObjects = mMap->replaceObjectTemplate(oldObjectTemplate, newObjectTemplate);
951 
952     // Update the objects in the map scene
953     emit changed(MapObjectsChangeEvent(std::move(changedObjects)));
954     emit objectTemplateReplaced(newObjectTemplate, oldObjectTemplate);
955 }
956 
setSelectedArea(const QRegion & selection)957 void MapDocument::setSelectedArea(const QRegion &selection)
958 {
959     if (mSelectedArea != selection) {
960         const QRegion oldSelectedArea = mSelectedArea;
961         mSelectedArea = selection;
962         emit selectedAreaChanged(mSelectedArea, oldSelectedArea);
963     }
964 }
965 
sortObjects(const Map & map,const QList<MapObject * > & objects)966 static QList<MapObject *> sortObjects(const Map &map, const QList<MapObject *> &objects)
967 {
968     QList<MapObject *> sorted;
969     sorted.reserve(objects.size());
970 
971     LayerIterator iterator(&map);
972     while (Layer *layer = iterator.next()) {
973         if (layer->layerType() != Layer::ObjectGroupType)
974             continue;
975 
976         for (MapObject *mapObject : static_cast<ObjectGroup*>(layer)->objects()) {
977             if (objects.contains(mapObject))
978                 sorted.append(mapObject);
979         }
980     }
981 
982     return sorted;
983 }
984 
985 /**
986  * Returns the list of selected objects, in their display order (when
987  * ObjectGroup::IndexOrder is used).
988  */
selectedObjectsOrdered() const989 QList<MapObject *> MapDocument::selectedObjectsOrdered() const
990 {
991     return sortObjects(*mMap, mSelectedObjects);
992 }
993 
setSelectedObjects(const QList<MapObject * > & selectedObjects)994 void MapDocument::setSelectedObjects(const QList<MapObject *> &selectedObjects)
995 {
996     mSelectedObjects = selectedObjects;
997     emit selectedObjectsChanged();
998 
999     ObjectGroup *singleObjectGroup = nullptr;
1000     for (MapObject *object : selectedObjects) {
1001         ObjectGroup *currentObjectGroup = object->objectGroup();
1002 
1003         if (!singleObjectGroup) {
1004             singleObjectGroup = currentObjectGroup;
1005         } else if (singleObjectGroup != currentObjectGroup) {
1006             singleObjectGroup = nullptr;
1007             break;
1008         }
1009     }
1010 
1011     // Switch the current object layer if only one object layer (and/or its objects)
1012     // are included in the current selection.
1013     if (singleObjectGroup)
1014         switchCurrentLayer(singleObjectGroup);
1015 
1016     // Make sure the current object is one of the selected ones
1017     if (!selectedObjects.isEmpty()) {
1018         if (currentObject() && currentObject()->typeId() == Object::MapObjectType) {
1019             if (selectedObjects.contains(static_cast<MapObject*>(currentObject())))
1020                 return;
1021         }
1022 
1023         setCurrentObject(selectedObjects.first());
1024     }
1025 }
1026 
1027 /**
1028  * Sets the list of objects that are about to be selected, for highlighting
1029  * purposes.
1030  */
setAboutToBeSelectedObjects(const QList<MapObject * > & objects)1031 void MapDocument::setAboutToBeSelectedObjects(const QList<MapObject *> &objects)
1032 {
1033     if (mAboutToBeSelectedObjects == objects)
1034         return;
1035 
1036     mAboutToBeSelectedObjects = objects;
1037     emit aboutToBeSelectedObjectsChanged(objects);
1038 }
1039 
currentObjects() const1040 QList<Object*> MapDocument::currentObjects() const
1041 {
1042     if (mCurrentObject) {
1043         switch (mCurrentObject->typeId()) {
1044         case Object::MapObjectType:
1045             if (!mSelectedObjects.isEmpty()) {
1046                 QList<Object*> objects;
1047                 for (MapObject *mapObj : mSelectedObjects)
1048                     objects.append(mapObj);
1049                 return objects;
1050             }
1051             break;
1052         case Object::LayerType:
1053             if (!mSelectedLayers.isEmpty()) {
1054                 QList<Object*> objects;
1055                 for (Layer *layer : mSelectedLayers)
1056                     objects.append(layer);
1057                 return objects;
1058             }
1059             break;
1060         default:
1061             break;
1062         }
1063     }
1064 
1065     return Document::currentObjects();
1066 }
1067 
setHoveredMapObject(MapObject * object)1068 void MapDocument::setHoveredMapObject(MapObject *object)
1069 {
1070     if (mHoveredMapObject == object)
1071         return;
1072 
1073     MapObject *previous = mHoveredMapObject;
1074     mHoveredMapObject = object;
1075     emit hoveredMapObjectChanged(object, previous);
1076 }
1077 
1078 /**
1079  * Makes sure the all tilesets which are used at the given \a map will be
1080  * present in the map document.
1081  *
1082  * To reach the aim, all similar tilesets will be replaced by the version
1083  * in the current map document and all missing tilesets will be added to
1084  * the current map document.
1085  */
unifyTilesets(Map * map)1086 void MapDocument::unifyTilesets(Map *map)
1087 {
1088     QList<QUndoCommand*> undoCommands;
1089     QVector<SharedTileset> availableTilesets = mMap->tilesets();
1090 
1091     // Iterate over a copy because map->replaceTileset may invalidate iterator
1092     const QVector<SharedTileset> tilesets = map->tilesets();
1093     for (const SharedTileset &tileset : tilesets) {
1094         if (availableTilesets.contains(tileset))
1095             continue;
1096 
1097         SharedTileset replacement = tileset->findSimilarTileset(availableTilesets);
1098         if (!replacement) {
1099             undoCommands.append(new AddTileset(this, tileset));
1100             availableTilesets.append(tileset);
1101             continue;
1102         }
1103 
1104         // Merge the tile properties
1105         for (Tile *replacementTile : replacement->tiles()) {
1106             if (Tile *originalTile = tileset->findTile(replacementTile->id())) {
1107                 Properties properties = replacementTile->properties();
1108                 mergeProperties(properties, originalTile->properties());
1109                 undoCommands.append(new ChangeProperties(this,
1110                                                          tr("Tile"),
1111                                                          replacementTile,
1112                                                          properties));
1113             }
1114         }
1115 
1116         map->replaceTileset(tileset, replacement);
1117     }
1118 
1119     if (!undoCommands.isEmpty()) {
1120         undoStack()->beginMacro(tr("Tileset Changes"));
1121         const auto &commands = undoCommands;
1122         for (QUndoCommand *command : commands)
1123             undoStack()->push(command);
1124         undoStack()->endMacro();
1125     }
1126 }
1127 
1128 /**
1129  * Replaces tilesets in \a map by similar tilesets in this map when possible,
1130  * and adds tilesets to \a missingTilesets whenever there is a tileset without
1131  * replacement in this map.
1132  */
unifyTilesets(Map * map,QVector<SharedTileset> & missingTilesets)1133 void MapDocument::unifyTilesets(Map *map, QVector<SharedTileset> &missingTilesets)
1134 {
1135     QVector<SharedTileset> availableTilesets = mMap->tilesets();
1136     for (const SharedTileset &tileset : qAsConst(missingTilesets))
1137         if (!availableTilesets.contains(tileset))
1138             availableTilesets.append(tileset);
1139 
1140     // Iterate over a copy because map->replaceTileset may invalidate iterator
1141     const QVector<SharedTileset> tilesets = map->tilesets();
1142     for (const SharedTileset &tileset : tilesets) {
1143         // tileset already added
1144         if (availableTilesets.contains(tileset))
1145             continue;
1146 
1147         SharedTileset replacement = tileset->findSimilarTileset(availableTilesets);
1148 
1149         // tileset not present and no replacement tileset found
1150         if (!replacement) {
1151             missingTilesets.append(tileset);
1152             availableTilesets.append(tileset);
1153             continue;
1154         }
1155 
1156         // replacement tileset found, change given map
1157         map->replaceTileset(tileset, replacement);
1158     }
1159 }
1160 
templateAllowed(const ObjectTemplate * objectTemplate) const1161 bool MapDocument::templateAllowed(const ObjectTemplate *objectTemplate) const
1162 {
1163     if (!objectTemplate->object())
1164         return false;
1165     if (objectTemplate->object()->isTileObject() && !mAllowTileObjects)
1166         return false;
1167 
1168     return true;
1169 }
1170 
onChanged(const ChangeEvent & change)1171 void MapDocument::onChanged(const ChangeEvent &change)
1172 {
1173     switch (change.type) {
1174     case ChangeEvent::MapChanged: {
1175         const auto property = static_cast<const MapChangeEvent&>(change).property;
1176         if (property == Map::OrientationProperty)
1177             createRenderer();
1178         break;
1179     }
1180     case ChangeEvent::MapObjectsAboutToBeRemoved: {
1181         const auto &mapObjects = static_cast<const MapObjectsEvent&>(change).mapObjects;
1182 
1183         if (mHoveredMapObject && mapObjects.contains(mHoveredMapObject))
1184             setHoveredMapObject(nullptr);
1185 
1186         // Deselecting all objects to be removed here avoids causing a selection
1187         // change for each individual object.
1188         deselectObjects(mapObjects);
1189 
1190         break;
1191     }
1192     default:
1193         break;
1194     }
1195 }
1196 
onMapObjectModelRowsInserted(const QModelIndex & parent,int first,int last)1197 void MapDocument::onMapObjectModelRowsInserted(const QModelIndex &parent,
1198                                                int first, int last)
1199 {
1200     ObjectGroup *objectGroup = mMapObjectModel->toObjectGroup(parent);
1201     if (!objectGroup) // we're not dealing with insertion of objects
1202         return;
1203 
1204     emit objectsInserted(objectGroup, first, last);
1205     onMapObjectModelRowsInsertedOrRemoved(parent, first, last);
1206 }
1207 
onMapObjectModelRowsInsertedOrRemoved(const QModelIndex & parent,int first,int last)1208 void MapDocument::onMapObjectModelRowsInsertedOrRemoved(const QModelIndex &parent,
1209                                                         int first, int last)
1210 {
1211     Q_UNUSED(first)
1212 
1213     ObjectGroup *objectGroup = mMapObjectModel->toObjectGroup(parent);
1214     if (!objectGroup)
1215         return;
1216 
1217     // Inserting or removing objects changes the index of any that come after
1218     const int lastIndex = objectGroup->objectCount() - 1;
1219     if (last < lastIndex)
1220         emit objectsIndexChanged(objectGroup, last + 1, lastIndex);
1221 }
1222 
onObjectsMoved(const QModelIndex & parent,int start,int end,const QModelIndex & destination,int row)1223 void MapDocument::onObjectsMoved(const QModelIndex &parent, int start, int end,
1224                                  const QModelIndex &destination, int row)
1225 {
1226     if (parent != destination)
1227         return;
1228 
1229     ObjectGroup *objectGroup = mMapObjectModel->toObjectGroup(parent);
1230 
1231     // Determine the full range over which object indexes changed
1232     const int first = qMin(start, row);
1233     const int last = qMax(end, row - 1);
1234 
1235     emit objectsIndexChanged(objectGroup, first, last);
1236 }
1237 
onLayerAdded(Layer * layer)1238 void MapDocument::onLayerAdded(Layer *layer)
1239 {
1240     emit layerAdded(layer);
1241 
1242     // Select the first layer that gets added to the map
1243     if (mMap->layerCount() == 1 && mMap->layerAt(0) == layer)
1244         switchCurrentLayer(layer);
1245 }
1246 
collectObjects(Layer * layer,QList<MapObject * > & objects)1247 static void collectObjects(Layer *layer, QList<MapObject*> &objects)
1248 {
1249     switch (layer->layerType()) {
1250     case Layer::ObjectGroupType:
1251         objects.append(static_cast<ObjectGroup*>(layer)->objects());
1252         break;
1253     case Layer::GroupLayerType:
1254         for (auto childLayer : *static_cast<GroupLayer*>(layer))
1255             collectObjects(childLayer, objects);
1256         break;
1257     case Layer::ImageLayerType:
1258     case Layer::TileLayerType:
1259         break;
1260     }
1261 }
1262 
onLayerAboutToBeRemoved(GroupLayer * groupLayer,int index)1263 void MapDocument::onLayerAboutToBeRemoved(GroupLayer *groupLayer, int index)
1264 {
1265     Layer *layer = groupLayer ? groupLayer->layerAt(index) : mMap->layerAt(index);
1266 
1267     // Deselect any objects on this layer when necessary
1268     if (layer->isObjectGroup() || layer->isGroupLayer()) {
1269         QList<MapObject*> objects;
1270         collectObjects(layer, objects);
1271         deselectObjects(objects);
1272 
1273         if (mHoveredMapObject && objects.contains(mHoveredMapObject))
1274             setHoveredMapObject(nullptr);
1275     }
1276 
1277     emit layerAboutToBeRemoved(groupLayer, index);
1278 }
1279 
onLayerRemoved(Layer * layer)1280 void MapDocument::onLayerRemoved(Layer *layer)
1281 {
1282     if (mCurrentLayer && mCurrentLayer->isParentOrSelf(layer)) {
1283         // Assumption: the current object is either not a layer, or it is the current layer.
1284         if (mCurrentObject == mCurrentLayer)
1285             setCurrentObject(nullptr);
1286     }
1287 
1288     // Make sure affected layers are removed from the selection
1289     auto selectedLayers = mSelectedLayers;
1290     for (int i = selectedLayers.size() - 1; i >= 0; --i)
1291         if (selectedLayers.at(i)->isParentOrSelf(layer))
1292             selectedLayers.removeAt(i);
1293     switchSelectedLayers(selectedLayers);
1294 
1295     emit layerRemoved(layer);
1296 }
1297 
checkIssues()1298 void MapDocument::checkIssues()
1299 {
1300     // Clear any previously found issues in this document
1301     IssuesModel::instance().removeIssuesWithContext(this);
1302 
1303     for (const SharedTileset &tileset : map()->tilesets()) {
1304         if (tileset->isExternal() && tileset->status() == LoadingError) {
1305             ERROR(tr("Failed to load tileset '%1'").arg(tileset->fileName()),
1306                   LocateTileset { tileset, sharedFromThis() },
1307                   this);
1308         }
1309     }
1310 
1311     QSet<const ObjectTemplate*> brokenTemplates;
1312 
1313     LayerIterator it(map());
1314     for (Layer *layer : map()->objectGroups()) {
1315         ObjectGroup *objectGroup = static_cast<ObjectGroup*>(layer->asObjectGroup());
1316         for (MapObject *mapObject : *objectGroup) {
1317             if (const ObjectTemplate *objectTemplate = mapObject->objectTemplate())
1318                 if (!objectTemplate->object())
1319                     brokenTemplates.insert(objectTemplate);
1320         }
1321     }
1322 
1323     for (auto objectTemplate : brokenTemplates) {
1324         ERROR(tr("Failed to load template '%1'").arg(objectTemplate->fileName()),
1325               LocateObjectTemplate { objectTemplate, sharedFromThis() },
1326               this);
1327     }
1328 
1329     checkFilePathProperties(map());
1330 
1331     for (Layer *layer : map()->allLayers()) {
1332         checkFilePathProperties(layer);
1333 
1334         if (layer->isObjectGroup()) {
1335             for (MapObject *mapObject : static_cast<ObjectGroup*>(layer)->objects())
1336                 checkFilePathProperties(mapObject);
1337         }
1338     }
1339 }
1340 
updateTemplateInstances(const ObjectTemplate * objectTemplate)1341 void MapDocument::updateTemplateInstances(const ObjectTemplate *objectTemplate)
1342 {
1343     QList<MapObject*> objectList;
1344     for (Layer *layer : mMap->objectGroups()) {
1345         for (auto object : static_cast<ObjectGroup*>(layer)->objects()) {
1346             if (object->objectTemplate() == objectTemplate) {
1347                 object->syncWithTemplate();
1348                 objectList.append(object);
1349             }
1350         }
1351     }
1352     emit changed(MapObjectsChangeEvent(std::move(objectList)));
1353 }
1354 
selectAllInstances(const ObjectTemplate * objectTemplate)1355 void MapDocument::selectAllInstances(const ObjectTemplate *objectTemplate)
1356 {
1357     QList<MapObject*> objectList;
1358     for (Layer *layer : mMap->objectGroups()) {
1359         for (auto object : static_cast<ObjectGroup*>(layer)->objects())
1360             if (object->objectTemplate() == objectTemplate)
1361                 objectList.append(object);
1362     }
1363     setSelectedObjects(objectList);
1364 }
1365 
1366 /**
1367  * Deselects the given list of \a objects.
1368  *
1369  * If any of the given objects is the "current" object, the current object
1370  * is reset as well.
1371  */
deselectObjects(const QList<MapObject * > & objects)1372 void MapDocument::deselectObjects(const QList<MapObject *> &objects)
1373 {
1374     // Unset the current object when it was part of this list of objects
1375     if (mCurrentObject && mCurrentObject->typeId() == Object::MapObjectType)
1376         if (objects.contains(static_cast<MapObject*>(mCurrentObject)))
1377             setCurrentObject(nullptr);
1378 
1379     int removedSelectedObjects = 0;
1380     int removedAboutToBeSelectedObjects = 0;
1381 
1382     for (MapObject *object : objects) {
1383         removedSelectedObjects += mSelectedObjects.removeAll(object);
1384         removedAboutToBeSelectedObjects += mAboutToBeSelectedObjects.removeAll(object);
1385     }
1386 
1387     if (removedSelectedObjects > 0)
1388         emit selectedObjectsChanged();
1389     if (removedAboutToBeSelectedObjects > 0)
1390         emit aboutToBeSelectedObjectsChanged(mAboutToBeSelectedObjects);
1391 }
1392 
duplicateObjects(const QList<MapObject * > & objects)1393 void MapDocument::duplicateObjects(const QList<MapObject *> &objects)
1394 {
1395     if (objects.isEmpty())
1396         return;
1397 
1398     QVector<AddMapObjects::Entry> objectsToAdd;
1399     objectsToAdd.reserve(objects.size());
1400 
1401     for (MapObject *mapObject : objects) {
1402         MapObject *clone = mapObject->clone();
1403         clone->resetId();
1404         objectsToAdd.append(AddMapObjects::Entry { clone, mapObject->objectGroup() });
1405         objectsToAdd.last().index = mapObject->objectGroup()->objects().indexOf(mapObject) + 1;
1406     }
1407 
1408     auto command = new AddMapObjects(this, objectsToAdd);
1409     command->setText(tr("Duplicate %n Object(s)", "", objects.size()));
1410 
1411     undoStack()->push(command);
1412 
1413     setSelectedObjects(AddMapObjects::objects(objectsToAdd));
1414 }
1415 
removeObjects(const QList<MapObject * > & objects)1416 void MapDocument::removeObjects(const QList<MapObject *> &objects)
1417 {
1418     if (objects.isEmpty())
1419         return;
1420 
1421     auto command = new RemoveMapObjects(this, objects);
1422     command->setText(tr("Remove %n Object(s)", "", objects.size()));
1423 
1424     undoStack()->push(command);
1425 }
1426 
moveObjectsToGroup(const QList<MapObject * > & objects,ObjectGroup * objectGroup)1427 void MapDocument::moveObjectsToGroup(const QList<MapObject *> &objects,
1428                                      ObjectGroup *objectGroup)
1429 {
1430     if (objects.isEmpty())
1431         return;
1432 
1433     undoStack()->beginMacro(tr("Move %n Object(s) to Layer", "",
1434                                objects.size()));
1435 
1436     const auto objectsToMove = sortObjects(*mMap, objects);
1437     for (MapObject *mapObject : objectsToMove) {
1438         if (mapObject->objectGroup() == objectGroup)
1439             continue;
1440 
1441         undoStack()->push(new MoveMapObjectToGroup(this,
1442                                                    mapObject,
1443                                                    objectGroup));
1444     }
1445     undoStack()->endMacro();
1446 }
1447 
1448 typedef QHash<ObjectGroup*, RangeSet<int>>           Ranges;
1449 typedef QHashIterator<ObjectGroup*, RangeSet<int>>   RangesIterator;
1450 
computeRanges(const QList<MapObject * > & objects)1451 static Ranges computeRanges(const QList<MapObject *> &objects)
1452 {
1453     Ranges ranges;
1454 
1455     for (MapObject *object : objects) {
1456         ObjectGroup *group = object->objectGroup();
1457         auto &set = ranges[group];
1458         set.insert(group->objects().indexOf(object));
1459     }
1460 
1461     return ranges;
1462 }
1463 
moveObjectsUp(const QList<MapObject * > & objects)1464 void MapDocument::moveObjectsUp(const QList<MapObject *> &objects)
1465 {
1466     if (objects.isEmpty())
1467         return;
1468 
1469     const auto ranges = computeRanges(objects);
1470 
1471     std::unique_ptr<QUndoCommand> command(new QUndoCommand(tr("Move %n Object(s) Up",
1472                                                               "", objects.size())));
1473 
1474     RangesIterator rangesIterator(ranges);
1475     while (rangesIterator.hasNext()) {
1476         rangesIterator.next();
1477 
1478         ObjectGroup *group = rangesIterator.key();
1479         const RangeSet<int> &rangeSet = rangesIterator.value();
1480 
1481         const RangeSet<int>::Range it_begin = rangeSet.begin();
1482         RangeSet<int>::Range it = rangeSet.end();
1483         Q_ASSERT(it != it_begin);
1484 
1485         do {
1486             --it;
1487 
1488             int from = it.first();
1489             int count = it.length();
1490             int to = from + count + 1;
1491 
1492             if (to <= group->objectCount())
1493                 new ChangeMapObjectsOrder(this, group, from, to, count, command.get());
1494 
1495         } while (it != it_begin);
1496     }
1497 
1498     if (command->childCount() > 0)
1499         undoStack()->push(command.release());
1500 }
1501 
moveObjectsDown(const QList<MapObject * > & objects)1502 void MapDocument::moveObjectsDown(const QList<MapObject *> &objects)
1503 {
1504     if (objects.isEmpty())
1505         return;
1506 
1507     std::unique_ptr<QUndoCommand> command(new QUndoCommand(tr("Move %n Object(s) Down",
1508                                                               "", objects.size())));
1509 
1510     RangesIterator rangesIterator(computeRanges(objects));
1511     while (rangesIterator.hasNext()) {
1512         rangesIterator.next();
1513 
1514         ObjectGroup *group = rangesIterator.key();
1515         const RangeSet<int> &rangeSet = rangesIterator.value();
1516 
1517         RangeSet<int>::Range it = rangeSet.begin();
1518         const RangeSet<int>::Range it_end = rangeSet.end();
1519 
1520         for (; it != it_end; ++it) {
1521             int from = it.first();
1522 
1523             if (from > 0) {
1524                 int to = from - 1;
1525                 int count = it.length();
1526 
1527                 new ChangeMapObjectsOrder(this, group, from, to, count, command.get());
1528             }
1529         }
1530     }
1531 
1532     if (command->childCount() > 0)
1533         undoStack()->push(command.release());
1534 }
1535 
detachObjects(const QList<MapObject * > & objects)1536 void MapDocument::detachObjects(const QList<MapObject *> &objects)
1537 {
1538     if (objects.isEmpty())
1539         return;
1540 
1541     undoStack()->push(new DetachObjects(this, objects));
1542 }
1543 
createRenderer()1544 void MapDocument::createRenderer()
1545 {
1546     mRenderer = MapRenderer::create(mMap.get());
1547 }
1548 
1549 #include "moc_mapdocument.cpp"
1550