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