1 /*
2  * tilecollisiondock.cpp
3  * Copyright 2013-2017, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
4  *
5  * This file is part of Tiled.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the Free
9  * Software Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "tilecollisiondock.h"
22 
23 #include "actionmanager.h"
24 #include "addremovemapobject.h"
25 #include "changetileobjectgroup.h"
26 #include "createellipseobjecttool.h"
27 #include "createobjecttool.h"
28 #include "createpointobjecttool.h"
29 #include "createpolygonobjecttool.h"
30 #include "createrectangleobjecttool.h"
31 #include "createtemplatetool.h"
32 #include "editablemanager.h"
33 #include "editablemapobject.h"
34 #include "editpolygontool.h"
35 #include "layermodel.h"
36 #include "map.h"
37 #include "mapdocument.h"
38 #include "mapdocumentactionhandler.h"
39 #include "mapobject.h"
40 #include "mapscene.h"
41 #include "mapview.h"
42 #include "objectgroup.h"
43 #include "objectselectiontool.h"
44 #include "objectsview.h"
45 #include "preferences.h"
46 #include "scriptmanager.h"
47 #include "tile.h"
48 #include "tilelayer.h"
49 #include "tileset.h"
50 #include "tilesetdocument.h"
51 #include "toolmanager.h"
52 #include "utils.h"
53 #include "zoomable.h"
54 
55 #include <QActionGroup>
56 #include <QBitmap>
57 #include <QCloseEvent>
58 #include <QComboBox>
59 #include <QCoreApplication>
60 #include <QMenu>
61 #include <QShortcut>
62 #include <QSplitter>
63 #include <QStatusBar>
64 #include <QToolBar>
65 #include <QToolButton>
66 #include <QUndoStack>
67 #include <QVBoxLayout>
68 
69 namespace Tiled {
70 
71 namespace preferences {
72 static Preference<QVariant> objectsViewVisibility { "TileCollisionDock/ObjectsViewVisibility", TileCollisionDock::Hidden };
73 static Preference<QByteArray> splitterState { "TileCollisionDock/SplitterState" };
74 } // namespace preferences
75 
TileCollisionDock(QWidget * parent)76 TileCollisionDock::TileCollisionDock(QWidget *parent)
77     : QDockWidget(parent)
78     , mMapScene(new MapScene(this))
79     , mMapView(new MapView(this, MapView::NoStaticContents))
80     , mObjectsView(new ObjectsView(this))
81     , mToolManager(new ToolManager(this))
82 {
83     setObjectName(QLatin1String("tileCollisionDock"));
84 
85     mMapView->setScene(mMapScene);
86 
87     mMapView->setResizeAnchor(QGraphicsView::AnchorViewCenter);
88     mMapView->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
89     mMapView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
90 
91     mObjectsView->setRootIsDecorated(false);
92 
93     CreateObjectTool *rectangleObjectsTool = new CreateRectangleObjectTool(this);
94     CreateObjectTool *pointObjectsTool = new CreatePointObjectTool(this);
95     CreateObjectTool *ellipseObjectsTool = new CreateEllipseObjectTool(this);
96     CreateObjectTool *polygonObjectsTool = new CreatePolygonObjectTool(this);
97     CreateObjectTool *templatesTool = new CreateTemplateTool(this);
98 
99     // Autodetection of tile extents
100     QIcon autoDetectMaskIcon(QLatin1String("://images/24/detect-bounding-box.png"));
101     autoDetectMaskIcon.addFile(QLatin1String("://images/48/detect-bounding-box.png"));
102     mActionAutoDetectMask = new QAction(this);
103     mActionAutoDetectMask->setEnabled(false);
104     mActionAutoDetectMask->setIcon(autoDetectMaskIcon);
105     connect(mActionAutoDetectMask, &QAction::triggered, this, &TileCollisionDock::autoDetectMask);
106 
107     QToolBar *toolsToolBar = new QToolBar(this);
108     toolsToolBar->setObjectName(QLatin1String("TileCollisionDockToolBar"));
109     toolsToolBar->setMovable(false);
110     toolsToolBar->setFloatable(false);
111     toolsToolBar->setContextMenuPolicy(Qt::ActionsContextMenu);
112 
113     mToolManager = new ToolManager(this);
114     toolsToolBar->addAction(mToolManager->registerTool(new ObjectSelectionTool(this)));
115     toolsToolBar->addAction(mToolManager->registerTool(new EditPolygonTool(this)));
116     toolsToolBar->addAction(mToolManager->registerTool(rectangleObjectsTool));
117     toolsToolBar->addAction(mToolManager->registerTool(pointObjectsTool));
118     toolsToolBar->addAction(mToolManager->registerTool(ellipseObjectsTool));
119     toolsToolBar->addAction(mToolManager->registerTool(polygonObjectsTool));
120     toolsToolBar->addAction(mToolManager->registerTool(templatesTool));
121     toolsToolBar->addSeparator();
122     toolsToolBar->addAction(mActionAutoDetectMask);
123 
124     mActionDuplicateObjects = new QAction(this);
125     mActionDuplicateObjects->setIcon(QIcon(QLatin1String(":/images/16/stock-duplicate-16.png")));
126     mActionRemoveObjects = new QAction(this);
127     mActionRemoveObjects->setIcon(QIcon(QLatin1String(":/images/16/edit-delete.png")));
128     mActionMoveUp = new QAction(this);
129     mActionMoveUp->setIcon(QIcon(QLatin1String(":/images/16/go-up.png")));
130     mActionMoveDown = new QAction(this);
131     mActionMoveDown->setIcon(QIcon(QLatin1String(":/images/16/go-down.png")));
132     mActionObjectProperties = new QAction(this);
133     mActionObjectProperties->setIcon(QIcon(QLatin1String(":/images/16/document-properties.png")));
134 
135     Utils::setThemeIcon(mActionRemoveObjects, "edit-delete");
136     Utils::setThemeIcon(mActionMoveUp, "go-up");
137     Utils::setThemeIcon(mActionMoveDown, "go-down");
138     Utils::setThemeIcon(mActionObjectProperties, "document-properties");
139 
140     QToolBar *objectsToolBar = new QToolBar(this);
141     objectsToolBar->setMovable(false);
142     objectsToolBar->setFloatable(false);
143     objectsToolBar->setIconSize(Utils::smallIconSize());
144     objectsToolBar->addAction(mActionDuplicateObjects);
145     objectsToolBar->addAction(mActionRemoveObjects);
146     objectsToolBar->addAction(mActionMoveUp);
147     objectsToolBar->addAction(mActionMoveDown);
148     objectsToolBar->addAction(mActionObjectProperties);
149 
150     mObjectsWidget = new QWidget;
151     mObjectsWidget->setVisible(false);
152     auto objectsVertical = new QVBoxLayout(mObjectsWidget);
153     objectsVertical->setSpacing(0);
154     objectsVertical->setContentsMargins(0, 0, 0, 0);
155     objectsVertical->addWidget(mObjectsView);
156     objectsVertical->addWidget(objectsToolBar);
157 
158     mObjectsViewSplitter = new QSplitter;
159     mObjectsViewSplitter->addWidget(mMapView);
160     mObjectsViewSplitter->addWidget(mObjectsWidget);
161 
162     connect(mToolManager, &ToolManager::selectedToolChanged,
163             mMapScene, &MapScene::setSelectedTool);
164     connect(mToolManager, &ToolManager::statusInfoChanged,
165             this, &TileCollisionDock::statusInfoChanged);
166 
167     auto objectsViewActionGroup = new QActionGroup(this);
168 
169     mObjectsViewHiddenAction = new QAction(tr("Hidden"), objectsViewActionGroup);
170     mObjectsViewHiddenAction->setData(QVariant::fromValue(Hidden));
171     mObjectsViewHiddenAction->setCheckable(true);
172     mObjectsViewHiddenAction->setChecked(true);
173 
174     mObjectsViewShowRightAction = new QAction(tr("Show Right"), objectsViewActionGroup);
175     mObjectsViewShowRightAction->setData(QVariant::fromValue(ShowRight));
176     mObjectsViewShowRightAction->setCheckable(true);
177 
178     mObjectsViewShowBottomAction = new QAction(tr("Show Bottom"), objectsViewActionGroup);
179     mObjectsViewShowBottomAction->setData(QVariant::fromValue(ShowBottom));
180     mObjectsViewShowBottomAction->setCheckable(true);
181 
182     connect(objectsViewActionGroup, &QActionGroup::triggered, this, [this] (QAction *action) {
183         setObjectsViewVisibility(action->data().value<ObjectsViewVisibility>());
184     });
185 
186     auto objectsViewMenu = new QMenu(this);
187     objectsViewMenu->addActions(objectsViewActionGroup->actions());
188 
189     QIcon objectsViewIcon(QLatin1String("://images/16/layer-object.png"));
190     objectsViewIcon.addFile(QLatin1String("://images/32/layer-object.png"));
191 
192     auto objectsViewButton = new QToolButton;
193     objectsViewButton->setMenu(objectsViewMenu);
194     objectsViewButton->setPopupMode(QToolButton::InstantPopup);
195     objectsViewButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
196     objectsViewButton->setAutoRaise(true);
197     objectsViewButton->setIcon(objectsViewIcon);
198     objectsViewButton->setToolTip(tr("Objects list"));
199 
200     QComboBox *zoomComboBox = new QComboBox;
201     mMapView->zoomable()->setComboBox(zoomComboBox);
202 
203     auto rightToolBar = new QToolBar;
204     rightToolBar->setIconSize(Utils::smallIconSize());
205     rightToolBar->addWidget(objectsViewButton);
206     rightToolBar->addSeparator();
207     rightToolBar->addWidget(zoomComboBox);
208 
209     auto horizontal = new QHBoxLayout;
210     horizontal->setSpacing(Utils::dpiScaled(5));
211     horizontal->addWidget(toolsToolBar, 1);
212     horizontal->addWidget(rightToolBar);
213 
214     auto widget = new QWidget(this);
215     auto vertical = new QVBoxLayout(widget);
216     vertical->setSpacing(0);
217     vertical->setContentsMargins(0, 0, 0, 0);
218     vertical->addLayout(horizontal);
219     vertical->addWidget(mObjectsViewSplitter);
220 
221     auto selectAllShortcut = new QShortcut(Qt::CTRL + Qt::Key_A, mMapView, nullptr, nullptr, Qt::WidgetShortcut);
222     connect(selectAllShortcut, &QShortcut::activated, this, &TileCollisionDock::selectAll);
223 
224     connect(mActionDuplicateObjects, &QAction::triggered, this, &TileCollisionDock::duplicateObjects);
225     connect(mActionRemoveObjects, &QAction::triggered, this, &TileCollisionDock::removeObjects);
226     connect(mActionMoveUp, &QAction::triggered, this, &TileCollisionDock::moveObjectsUp);
227     connect(mActionMoveDown, &QAction::triggered, this, &TileCollisionDock::moveObjectsDown);
228     connect(mActionObjectProperties, &QAction::triggered, this, &TileCollisionDock::objectProperties);
229 
230     retranslateUi();
231     selectedObjectsChanged();   // disables actions
232 
233     ActionManager::registerAction(mActionDuplicateObjects, "DuplicateObjects");
234     ActionManager::registerAction(mActionRemoveObjects, "RemoveObjects");
235     ActionManager::registerAction(mActionMoveUp, "MoveObjectsUp");
236     ActionManager::registerAction(mActionMoveDown, "MoveObjectsDown");
237 
238     setWidget(widget);
239 }
240 
~TileCollisionDock()241 TileCollisionDock::~TileCollisionDock()
242 {
243     setTile(nullptr);
244 }
245 
246 /**
247  * Automatically detect the extents of the tile and append a simple
248  * rectangular collision mask.
249  */
autoDetectMask()250 void TileCollisionDock::autoDetectMask()
251 {
252     if (!mDummyMapDocument)
253         return;
254 
255     const QPixmap &pixmap = mTile->image();
256     const QRect content = pixmap.hasAlphaChannel() ? QRegion(pixmap.mask()).boundingRect()
257                                                    : pixmap.rect();
258 
259     // Create the rectangular collision shape
260     MapObject *newObject = new MapObject(QString(), QString(),
261                                          content.topLeft(),
262                                          content.size());
263 
264     ObjectGroup *objectGroup = static_cast<ObjectGroup*>(mDummyMapDocument->map()->layerAt(1));
265     mDummyMapDocument->undoStack()->push(new AddMapObjects(mDummyMapDocument.data(), objectGroup, newObject));
266     mDummyMapDocument->setSelectedObjects({ newObject });
267 }
268 
saveState()269 void TileCollisionDock::saveState()
270 {
271     preferences::objectsViewVisibility = QVariant::fromValue(mObjectsViewVisibility).toString();
272     preferences::splitterState = mObjectsViewSplitter->saveState();
273 }
274 
restoreState()275 void TileCollisionDock::restoreState()
276 {
277     setObjectsViewVisibility(preferences::objectsViewVisibility.get().value<ObjectsViewVisibility>());
278     mObjectsViewSplitter->restoreState(preferences::splitterState);
279 }
280 
setTilesetDocument(TilesetDocument * tilesetDocument)281 void TileCollisionDock::setTilesetDocument(TilesetDocument *tilesetDocument)
282 {
283     if (mTilesetDocument)
284         mTilesetDocument->disconnect(this);
285 
286     mTilesetDocument = tilesetDocument;
287 
288     if (mTilesetDocument) {
289         connect(mTilesetDocument, &Document::changed,
290                 this, &TileCollisionDock::documentChanged);
291         connect(mTilesetDocument, &TilesetDocument::tileObjectGroupChanged,
292                 this, &TileCollisionDock::tileObjectGroupChanged);
293         connect(mTilesetDocument, &TilesetDocument::tilesetTileOffsetChanged,
294                 this, &TileCollisionDock::tilesetTileOffsetChanged);
295     }
296 }
297 
selectedObjectsForScript() const298 QList<QObject *> TileCollisionDock::selectedObjectsForScript() const
299 {
300     QList<QObject*> objects;
301 
302     if (!mDummyMapDocument)
303         return objects;
304 
305     auto &editableManager = EditableManager::instance();
306     auto editableTileset = mTilesetDocument->editable();
307     const auto &originalObjects = mTile->objectGroup()->objects();
308 
309     for (MapObject *mapObject : mDummyMapDocument->selectedObjects()) {
310         const int id = mapObject->id();
311         auto it = std::find_if(originalObjects.begin(), originalObjects.end(),
312                                [id] (MapObject *o) { return o->id() == id; });
313 
314         if (it != originalObjects.end()) {
315             MapObject *oo = *it;
316             objects.append(editableManager.editableMapObject(editableTileset, oo));
317         }
318     }
319 
320     return objects;
321 }
322 
setSelectedObjectsFromScript(const QList<QObject * > & selectedObjects)323 void TileCollisionDock::setSelectedObjectsFromScript(const QList<QObject *> &selectedObjects)
324 {
325     if (!mDummyMapDocument)
326         return;
327 
328     QList<MapObject*> objectsToSelect;
329 
330     for (QObject *object : selectedObjects) {
331         if (auto clonedObject = clonedObjectForScriptObject(qobject_cast<EditableMapObject*>(object)))
332             objectsToSelect.append(clonedObject);
333         else
334             return;
335     }
336 
337     mDummyMapDocument->setSelectedObjects(objectsToSelect);
338 }
339 
focusObject(EditableMapObject * object)340 void TileCollisionDock::focusObject(EditableMapObject *object)
341 {
342     if (!mDummyMapDocument)
343         return;
344 
345     if (auto clonedObject = clonedObjectForScriptObject(object)) {
346         emit mDummyMapDocument->focusMapObjectRequested(clonedObject);
347         mObjectsView->ensureVisible(clonedObject);
348     }
349 }
350 
clonedObjectForScriptObject(EditableMapObject * scriptObject)351 MapObject *TileCollisionDock::clonedObjectForScriptObject(EditableMapObject *scriptObject)
352 {
353     if (!scriptObject) {
354         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Not an object"));
355         return nullptr;
356     }
357     if (scriptObject->asset() != mTilesetDocument->editable()) {
358         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Object not from this asset"));
359         return nullptr;
360     }
361 
362     const auto objectGroup = static_cast<ObjectGroup*>(mDummyMapDocument->map()->layerAt(1));
363     const auto &clonedObjects = objectGroup->objects();
364     const int id = scriptObject->id();
365 
366     const auto it = std::find_if(clonedObjects.begin(), clonedObjects.end(),
367                                  [id] (MapObject *o) { return o->id() == id; });
368 
369     if (it == clonedObjects.end()) {
370         ScriptManager::instance().throwError(QCoreApplication::translate("Script Errors", "Object not found"));
371         return nullptr;
372     }
373 
374     return *it;
375 }
376 
setTile(Tile * tile)377 void TileCollisionDock::setTile(Tile *tile)
378 {
379     if (mTile == tile)
380         return;
381 
382     mTile = tile;
383 
384     mMapScene->setSelectedTool(nullptr);
385     auto previousDocument = mDummyMapDocument;
386 
387     mMapView->setEnabled(tile);
388     mActionAutoDetectMask->setEnabled(tile);
389 
390     if (tile) {
391         Map::Parameters mapParameters;
392         mapParameters.width = 1;
393         mapParameters.height = 1;
394 
395         if (tile->tileset()->orientation() == Tileset::Isometric) {
396             mapParameters.orientation = Map::Isometric;
397             mapParameters.tileWidth = tile->tileset()->gridSize().width();
398             mapParameters.tileHeight = tile->tileset()->gridSize().height();
399         } else {
400             mapParameters.tileWidth = tile->width();
401             mapParameters.tileHeight = tile->height();
402         }
403 
404         auto map = std::make_unique<Map>(mapParameters);
405         map->addTileset(tile->sharedTileset());
406 
407         TileLayer *tileLayer = new TileLayer(QString(), 0, 0, 1, 1);
408         tileLayer->setCell(0, 0, Cell(tile));
409         tileLayer->setOffset(-tile->offset());  // undo tile offset
410         map->addLayer(tileLayer);
411 
412         ObjectGroup *objectGroup;
413         if (tile->objectGroup())
414             objectGroup = tile->objectGroup()->clone();
415         else
416             objectGroup = new ObjectGroup;
417 
418         objectGroup->setDrawOrder(ObjectGroup::IndexOrder);
419         map->setNextObjectId(objectGroup->highestObjectId() + 1);
420         map->addLayer(objectGroup);
421 
422         mDummyMapDocument = MapDocumentPtr::create(std::move(map));
423         mDummyMapDocument->setAllowTileObjects(false);
424         mDummyMapDocument->switchCurrentLayer(objectGroup);
425 
426         mMapScene->setMapDocument(mDummyMapDocument.data());
427         mObjectsView->setMapDocument(mDummyMapDocument.data());
428         mObjectsView->setRootIndex(mObjectsView->layerViewIndex(objectGroup));
429         mToolManager->setMapDocument(mDummyMapDocument.data());
430 
431         mMapScene->setSelectedTool(mToolManager->selectedTool());
432 
433         connect(mDummyMapDocument->undoStack(), &QUndoStack::indexChanged,
434                 this, &TileCollisionDock::applyChanges);
435 
436         connect(mDummyMapDocument.data(), &MapDocument::selectedObjectsChanged,
437                 this, &TileCollisionDock::selectedObjectsChanged);
438 
439     } else {
440         mDummyMapDocument.clear();
441         mMapScene->setMapDocument(nullptr);
442         mObjectsView->setMapDocument(nullptr);
443         mToolManager->setMapDocument(nullptr);
444     }
445 
446     emit dummyMapDocumentChanged(mDummyMapDocument.data());
447 
448     setHasSelectedObjects(false);
449 
450     if (previousDocument) {
451         // Explicitly disconnect early from this signal, since it can get fired
452         // from the QUndoStack destructor.
453         disconnect(previousDocument->undoStack(), &QUndoStack::indexChanged,
454                    this, &TileCollisionDock::applyChanges);
455     }
456 }
457 
applyChanges()458 void TileCollisionDock::applyChanges()
459 {
460     if (mSynchronizing)
461         return;
462 
463     ObjectGroup *objectGroup = static_cast<ObjectGroup*>(mDummyMapDocument->map()->layerAt(1));
464     std::unique_ptr<ObjectGroup> clonedGroup;
465     if (!objectGroup->isEmpty())
466         clonedGroup.reset(objectGroup->clone());
467 
468     QUndoStack *undoStack = mTilesetDocument->undoStack();
469     mApplyingChanges = true;
470     undoStack->push(new ChangeTileObjectGroup(mTilesetDocument, mTile, std::move(clonedGroup)));
471     mApplyingChanges = false;
472 }
473 
documentChanged(const ChangeEvent & change)474 void TileCollisionDock::documentChanged(const ChangeEvent &change)
475 {
476     if (!mTile || !mTile->objectGroup() || mApplyingChanges)
477         return;
478 
479     switch (change.type) {
480     case ChangeEvent::MapObjectsAdded:
481     case ChangeEvent::MapObjectsChanged:
482     case ChangeEvent::MapObjectsRemoved: {
483         auto &mapObjects = static_cast<const MapObjectsEvent&>(change).mapObjects;
484         bool affectsTile = std::any_of(mapObjects.begin(),
485                                        mapObjects.end(),
486                                        [og = mTile->objectGroup()] (MapObject *mapObject) {
487             return mapObject->objectGroup() == og;
488         });
489 
490         if (affectsTile)
491             tileObjectGroupChanged(mTile);
492 
493         break;
494     }
495     default:
496         break;
497     }
498 }
499 
tileObjectGroupChanged(Tile * tile)500 void TileCollisionDock::tileObjectGroupChanged(Tile *tile)
501 {
502     if (mTile != tile)
503         return;
504     if (mApplyingChanges)
505         return;
506 
507     mSynchronizing = true;
508 
509     mDummyMapDocument->undoStack()->clear();
510     auto selectedTool = mToolManager->selectedTool();
511 
512     LayerModel *layerModel = mDummyMapDocument->layerModel();
513     delete layerModel->takeLayerAt(nullptr, 1);
514 
515     ObjectGroup *objectGroup;
516     if (tile->objectGroup())
517         objectGroup = tile->objectGroup()->clone();
518     else
519         objectGroup = new ObjectGroup;
520 
521     objectGroup->setDrawOrder(ObjectGroup::IndexOrder);
522 
523     layerModel->insertLayer(nullptr, 1, objectGroup);
524     mDummyMapDocument->switchCurrentLayer(objectGroup);
525     mObjectsView->setRootIndex(mObjectsView->layerViewIndex(objectGroup));
526 
527     mToolManager->selectTool(selectedTool);
528 
529     mSynchronizing = false;
530 }
531 
tilesetTileOffsetChanged(Tileset * tileset)532 void TileCollisionDock::tilesetTileOffsetChanged(Tileset *tileset)
533 {
534     if (!mDummyMapDocument)
535         return;
536 
537     auto tileLayer = mDummyMapDocument->map()->layerAt(0);
538     auto tileOffset = tileset->tileOffset();
539     tileLayer->setOffset(-tileOffset);
540 
541     emit mDummyMapDocument->changed(LayerChangeEvent(tileLayer, LayerChangeEvent::OffsetProperty));
542 }
543 
cut()544 void TileCollisionDock::cut()
545 {
546     if (!mTile)
547         return;
548 
549     copy();
550     delete_(Cut);
551 }
552 
copy()553 void TileCollisionDock::copy()
554 {
555     if (!mDummyMapDocument)
556         return;
557 
558     ClipboardManager::instance()->copySelection(*mDummyMapDocument);
559 }
560 
paste()561 void TileCollisionDock::paste()
562 {
563     paste(ClipboardManager::PasteDefault);
564 }
565 
pasteInPlace()566 void TileCollisionDock::pasteInPlace()
567 {
568     paste(ClipboardManager::PasteInPlace);
569 }
570 
paste(ClipboardManager::PasteFlags flags)571 void TileCollisionDock::paste(ClipboardManager::PasteFlags flags)
572 {
573     if (!mTile)
574         return;
575 
576     ClipboardManager *clipboardManager = ClipboardManager::instance();
577     const std::unique_ptr<Map> map(clipboardManager->map());
578     if (!map)
579         return;
580 
581     // We can currently only handle maps with a single layer
582     if (map->layerCount() != 1)
583         return;
584 
585     Layer *layer = map->layerAt(0);
586 
587     if (ObjectGroup *objectGroup = layer->asObjectGroup()) {
588         MapDocument *dummyDocument = mMapScene->mapDocument();
589         clipboardManager->pasteObjectGroup(objectGroup,
590                                            dummyDocument, mMapView,
591                                            flags | ClipboardManager::PasteNoTileObjects);
592     }
593 }
594 
delete_(Operation operation)595 void TileCollisionDock::delete_(Operation operation)
596 {
597     if (!mDummyMapDocument)
598         return;
599 
600     const QList<MapObject*> &selectedObjects = mDummyMapDocument->selectedObjects();
601     if (selectedObjects.isEmpty())
602         return;
603 
604     auto command = new RemoveMapObjects(mDummyMapDocument.data(), selectedObjects);
605     command->setText(operation == Delete ? tr("Delete") : tr("Cut"));
606 
607     mDummyMapDocument->undoStack()->push(command);
608 }
609 
selectedObjectsChanged()610 void TileCollisionDock::selectedObjectsChanged()
611 {
612     setHasSelectedObjects(mDummyMapDocument ? !mDummyMapDocument->selectedObjects().isEmpty() : false);
613 
614     mActionDuplicateObjects->setEnabled(hasSelectedObjects());
615     mActionRemoveObjects->setEnabled(hasSelectedObjects());
616     mActionMoveUp->setEnabled(hasSelectedObjects());
617     mActionMoveDown->setEnabled(hasSelectedObjects());
618     mActionObjectProperties->setEnabled(hasSelectedObjects());
619 }
620 
setHasSelectedObjects(bool hasSelectedObjects)621 void TileCollisionDock::setHasSelectedObjects(bool hasSelectedObjects)
622 {
623     if (mHasSelectedObjects != hasSelectedObjects) {
624         mHasSelectedObjects = hasSelectedObjects;
625         emit hasSelectedObjectsChanged();
626     }
627 }
628 
selectAll()629 void TileCollisionDock::selectAll()
630 {
631     if (!mDummyMapDocument)
632         return;
633 
634     ObjectGroup *objectGroup = static_cast<ObjectGroup*>(mDummyMapDocument->map()->layerAt(1));
635     mDummyMapDocument->setSelectedObjects(objectGroup->objects());
636 }
637 
duplicateObjects()638 void TileCollisionDock::duplicateObjects()
639 {
640     if (mDummyMapDocument)
641         mDummyMapDocument->duplicateObjects(mDummyMapDocument->selectedObjects());
642 }
643 
removeObjects()644 void TileCollisionDock::removeObjects()
645 {
646     if (mDummyMapDocument)
647         mDummyMapDocument->removeObjects(mDummyMapDocument->selectedObjects());
648 }
649 
moveObjectsUp()650 void TileCollisionDock::moveObjectsUp()
651 {
652     if (mDummyMapDocument)
653         mDummyMapDocument->moveObjectsUp(mDummyMapDocument->selectedObjects());
654 }
655 
moveObjectsDown()656 void TileCollisionDock::moveObjectsDown()
657 {
658     if (mDummyMapDocument)
659         mDummyMapDocument->moveObjectsDown(mDummyMapDocument->selectedObjects());
660 }
661 
objectProperties()662 void TileCollisionDock::objectProperties()
663 {
664     if (!mDummyMapDocument)
665         return;
666 
667     const auto &selectedObjects = mDummyMapDocument->selectedObjects();
668     mDummyMapDocument->setCurrentObject(selectedObjects.first());
669     emit mDummyMapDocument->editCurrentObject();
670 }
671 
setObjectsViewVisibility(ObjectsViewVisibility visibility)672 void TileCollisionDock::setObjectsViewVisibility(ObjectsViewVisibility visibility)
673 {
674     if (mObjectsViewVisibility == visibility)
675         return;
676 
677     mObjectsViewVisibility = visibility;
678 
679     switch (visibility) {
680     case Hidden:
681         mObjectsWidget->setVisible(false);
682         mObjectsViewHiddenAction->setChecked(true);
683         break;
684     case ShowRight:
685         mObjectsWidget->setVisible(true);
686         mObjectsViewSplitter->setOrientation(Qt::Horizontal);
687         mObjectsViewShowRightAction->setChecked(true);
688         break;
689     case ShowBottom:
690         mObjectsWidget->setVisible(true);
691         mObjectsViewSplitter->setOrientation(Qt::Vertical);
692         mObjectsViewShowBottomAction->setChecked(true);
693         break;
694     }
695 }
696 
changeEvent(QEvent * e)697 void TileCollisionDock::changeEvent(QEvent *e)
698 {
699     QDockWidget::changeEvent(e);
700     switch (e->type()) {
701     case QEvent::LanguageChange:
702         retranslateUi();
703         break;
704     default:
705         break;
706     }
707 }
708 
retranslateUi()709 void TileCollisionDock::retranslateUi()
710 {
711     setWindowTitle(QCoreApplication::translate("Tiled::MainWindow", "Tile Collision Editor"));
712 
713     mActionAutoDetectMask->setText(tr("Detect Bounding Box"));
714 
715     mActionDuplicateObjects->setText(tr("Duplicate Objects"));
716     mActionRemoveObjects->setText(tr("Remove Objects"));
717     mActionMoveUp->setText(tr("Move Objects Up"));
718     mActionMoveDown->setText(tr("Move Objects Down"));
719     mActionObjectProperties->setText(tr("Object Properties"));
720 }
721 
722 } // namespace Tiled
723 
724 #include "moc_tilecollisiondock.cpp"
725