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