1 /*
2  * propertybrowser.cpp
3  * Copyright 2013, Thorbjørn Lindeijer <thorbjorn@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 "propertybrowser.h"
22 
23 #include "changeimagelayerproperties.h"
24 #include "changelayer.h"
25 #include "changemapobject.h"
26 #include "changemapproperty.h"
27 #include "changeobjectgroupproperties.h"
28 #include "changeproperties.h"
29 #include "changetile.h"
30 #include "changetileimagesource.h"
31 #include "changetileprobability.h"
32 #include "changewangcolordata.h"
33 #include "changewangsetdata.h"
34 #include "documentmanager.h"
35 #include "flipmapobjects.h"
36 #include "grouplayer.h"
37 #include "imagelayer.h"
38 #include "map.h"
39 #include "mapdocument.h"
40 #include "mapobject.h"
41 #include "movemapobject.h"
42 #include "objectgroup.h"
43 #include "objecttemplate.h"
44 #include "preferences.h"
45 #include "properties.h"
46 #include "replacetileset.h"
47 #include "resizemapobject.h"
48 #include "rotatemapobject.h"
49 #include "tile.h"
50 #include "tilelayer.h"
51 #include "tilesetchanges.h"
52 #include "tilesetdocument.h"
53 #include "tilesetformat.h"
54 #include "tilesetmanager.h"
55 #include "tilesetwangsetmodel.h"
56 #include "utils.h"
57 #include "varianteditorfactory.h"
58 #include "variantpropertymanager.h"
59 #include "wangcolormodel.h"
60 #include "wangoverlay.h"
61 #include "wangset.h"
62 
63 #include <QtGroupPropertyManager>
64 
65 #include <QCoreApplication>
66 #include <QDebug>
67 #include <QKeyEvent>
68 #include <QMessageBox>
69 #include <QScopedValueRollback>
70 
71 #include <algorithm>
72 
73 namespace Tiled {
74 
75 namespace {
76 
77 /**
78  * Makes sure the resize mode is set to Fixed during its lifetime. Used to work
79  * around performance issues caused by the view continuously making sure its
80  * name column is adjusted to the contents.
81  */
82 class SetFixedResizeMode
83 {
84 public:
SetFixedResizeMode(QtTreePropertyBrowser * browser)85     SetFixedResizeMode(QtTreePropertyBrowser *browser)
86         : mBrowser(browser)
87         , mPreviousResizeMode(browser->resizeMode())
88     {
89         mBrowser->setResizeMode(QtTreePropertyBrowser::Fixed);
90     }
91 
~SetFixedResizeMode()92     ~SetFixedResizeMode()
93     {
94         mBrowser->setResizeMode(mPreviousResizeMode);
95     }
96 
97 private:
98     QtTreePropertyBrowser * const mBrowser;
99     QtTreePropertyBrowser::ResizeMode const mPreviousResizeMode;
100 };
101 
102 }
103 
PropertyBrowser(QWidget * parent)104 PropertyBrowser::PropertyBrowser(QWidget *parent)
105     : QtTreePropertyBrowser(parent)
106     , mVariantManager(new VariantPropertyManager(this))
107     , mGroupManager(new QtGroupPropertyManager(this))
108     , mCustomPropertiesGroup(nullptr)
109 {
110     VariantEditorFactory *variantEditorFactory = new VariantEditorFactory(this);
111 
112     setFactoryForManager(mVariantManager, variantEditorFactory);
113     setResizeMode(ResizeToContents);
114     setRootIsDecorated(false);
115     setPropertiesWithoutValueMarked(true);
116     setAllowMultiSelection(true);
117 
118     retranslateUi();
119 
120     mWangSetIcons.insert(WangSet::Corner, wangSetIcon(WangSet::Corner));
121     mWangSetIcons.insert(WangSet::Edge, wangSetIcon(WangSet::Edge));
122     mWangSetIcons.insert(WangSet::Mixed, wangSetIcon(WangSet::Mixed));
123 
124     connect(mVariantManager, &QtVariantPropertyManager::valueChanged,
125             this, &PropertyBrowser::valueChanged);
126 
127     connect(variantEditorFactory, &VariantEditorFactory::resetProperty,
128             this, &PropertyBrowser::resetProperty);
129 
130     connect(Preferences::instance(), &Preferences::objectTypesChanged,
131             this, &PropertyBrowser::objectTypesChanged);
132 }
133 
134 /**
135  * Sets the \a object for which to display the properties.
136  */
setObject(Object * object)137 void PropertyBrowser::setObject(Object *object)
138 {
139     if (mObject == object)
140         return;
141 
142     removeProperties();
143     mObject = object;
144     addProperties();
145 }
146 
147 /**
148  * Sets the \a document, used for keeping track of changes and for
149  * undo/redo support.
150  */
setDocument(Document * document)151 void PropertyBrowser::setDocument(Document *document)
152 {
153     MapDocument *mapDocument = qobject_cast<MapDocument*>(document);
154     TilesetDocument *tilesetDocument = qobject_cast<TilesetDocument*>(document);
155 
156     if (mDocument == document)
157         return;
158 
159     if (mDocument) {
160         mDocument->disconnect(this);
161         if (mTilesetDocument) {
162             mTilesetDocument->wangSetModel()->disconnect(this);
163         }
164     }
165 
166     mDocument = document;
167     mMapDocument = mapDocument;
168     mTilesetDocument = tilesetDocument;
169 
170     if (mapDocument) {
171         connect(mapDocument, &MapDocument::mapChanged,
172                 this, &PropertyBrowser::mapChanged);
173         connect(mapDocument, &MapDocument::imageLayerChanged,
174                 this, &PropertyBrowser::imageLayerChanged);
175         connect(mapDocument, &MapDocument::tileTypeChanged,
176                 this, &PropertyBrowser::tileTypeChanged);
177 
178         connect(mapDocument, &MapDocument::selectedObjectsChanged,
179                 this, &PropertyBrowser::selectedObjectsChanged);
180         connect(mapDocument, &MapDocument::selectedLayersChanged,
181                 this, &PropertyBrowser::selectedLayersChanged);
182     }
183 
184     if (tilesetDocument) {
185         connect(tilesetDocument, &TilesetDocument::tilesetNameChanged,
186                 this, &PropertyBrowser::tilesetChanged);
187         connect(tilesetDocument, &TilesetDocument::tilesetTileOffsetChanged,
188                 this, &PropertyBrowser::tilesetChanged);
189         connect(tilesetDocument, &TilesetDocument::tilesetObjectAlignmentChanged,
190                 this, &PropertyBrowser::tilesetChanged);
191         connect(tilesetDocument, &TilesetDocument::tilesetChanged,
192                 this, &PropertyBrowser::tilesetChanged);
193 
194         connect(tilesetDocument, &TilesetDocument::tileProbabilityChanged,
195                 this, &PropertyBrowser::tileChanged);
196         connect(tilesetDocument, &TilesetDocument::tileImageSourceChanged,
197                 this, &PropertyBrowser::tileChanged);
198         connect(tilesetDocument, &TilesetDocument::tileTypeChanged,
199                 this, &PropertyBrowser::tileTypeChanged);
200 
201         connect(tilesetDocument, &TilesetDocument::selectedTilesChanged,
202                 this, &PropertyBrowser::selectedTilesChanged);
203 
204         TilesetWangSetModel *wangSetModel = tilesetDocument->wangSetModel();
205         connect(wangSetModel, &TilesetWangSetModel::wangSetChanged,
206                 this, &PropertyBrowser::wangSetChanged);
207     }
208 
209     if (document) {
210         connect(document, &Document::changed,
211                 this, &PropertyBrowser::documentChanged);
212 
213         // For custom properties:
214         connect(document, &Document::propertyAdded,
215                 this, &PropertyBrowser::propertyAdded);
216         connect(document, &Document::propertyRemoved,
217                 this, &PropertyBrowser::propertyRemoved);
218         connect(document, &Document::propertyChanged,
219                 this, &PropertyBrowser::propertyChanged);
220         connect(document, &Document::propertiesChanged,
221                 this, &PropertyBrowser::propertiesChanged);
222     }
223 }
224 
225 /**
226  * Returns whether the given \a item displays a custom property.
227  */
isCustomPropertyItem(const QtBrowserItem * item) const228 bool PropertyBrowser::isCustomPropertyItem(const QtBrowserItem *item) const
229 {
230     return item && mPropertyToId[item->property()] == CustomProperty;
231 }
232 
233 /**
234  * Returns whether the given list of \a items are all custom properties.
235  */
allCustomPropertyItems(const QList<QtBrowserItem * > & items) const236 bool PropertyBrowser::allCustomPropertyItems(const QList<QtBrowserItem *> &items) const
237 {
238     return std::all_of(items.begin(), items.end(), [this] (QtBrowserItem *item) {
239         return mPropertyToId[item->property()] == CustomProperty;
240     });
241 }
242 
243 /**
244  * Selects the custom property with the given \a name, if it exists.
245  */
selectCustomProperty(const QString & name)246 void PropertyBrowser::selectCustomProperty(const QString &name)
247 {
248     QtVariantProperty *property = mNameToProperty.value(name);
249     if (!property)
250         return;
251 
252     const QList<QtBrowserItem*> propertyItems = items(property);
253     if (!propertyItems.isEmpty())
254         setCurrentItem(propertyItems.first());
255 }
256 
257 /**
258  * Makes the custom property with the \a name the currently edited one,
259  * if it exists.
260  */
editCustomProperty(const QString & name)261 void PropertyBrowser::editCustomProperty(const QString &name)
262 {
263     QtVariantProperty *property = mNameToProperty.value(name);
264     if (!property)
265         return;
266 
267     const QList<QtBrowserItem*> propertyItems = items(property);
268     if (!propertyItems.isEmpty())
269         editItem(propertyItems.first());
270 }
271 
sizeHint() const272 QSize PropertyBrowser::sizeHint() const
273 {
274     return Utils::dpiScaled(QSize(260, 100));
275 }
276 
event(QEvent * event)277 bool PropertyBrowser::event(QEvent *event)
278 {
279     if (event->type() == QEvent::LanguageChange)
280         retranslateUi();
281 
282     if (event->type() == QEvent::ShortcutOverride) {
283         if (static_cast<QKeyEvent *>(event)->key() == Qt::Key_Tab) {
284             if (editedItem()) {
285                 event->accept();
286                 return true;
287             }
288         }
289     }
290 
291     return QtTreePropertyBrowser::event(event);
292 }
293 
documentChanged(const ChangeEvent & change)294 void PropertyBrowser::documentChanged(const ChangeEvent &change)
295 {
296     switch (change.type) {
297     case ChangeEvent::LayerChanged:
298     case ChangeEvent::TileLayerChanged:
299         if (mObject == static_cast<const LayerChangeEvent&>(change).layer)
300             updateProperties();
301         break;
302     case ChangeEvent::MapObjectsChanged:
303         objectsChanged(static_cast<const MapObjectsChangeEvent&>(change));
304         break;
305     case ChangeEvent::ObjectGroupChanged:
306         if (mObject == static_cast<const ObjectGroupChangeEvent&>(change).objectGroup)
307             updateProperties();
308         break;
309     case ChangeEvent::WangSetChanged:
310         if (mObject == static_cast<const WangSetChangeEvent&>(change).wangSet)
311             updateProperties();
312         break;
313     default:
314         break;
315     }
316 }
317 
mapChanged()318 void PropertyBrowser::mapChanged()
319 {
320     if (mObject == mMapDocument->map())
321         updateProperties();
322 }
323 
objectsChanged(const MapObjectsChangeEvent & mapObjectsChange)324 void PropertyBrowser::objectsChanged(const MapObjectsChangeEvent &mapObjectsChange)
325 {
326     if (!mObject || mObject->typeId() != Object::MapObjectType)
327         return;
328     if (!mapObjectsChange.mapObjects.contains(static_cast<MapObject*>(mObject)))
329         return;
330 
331     updateProperties();
332 
333     if (mapObjectsChange.properties & (MapObject::CustomProperties | MapObject::TypeProperty))
334         updateCustomProperties();
335 }
336 
imageLayerChanged(ImageLayer * imageLayer)337 void PropertyBrowser::imageLayerChanged(ImageLayer *imageLayer)
338 {
339     if (mObject == imageLayer)
340         updateProperties();
341 }
342 
tilesetChanged(Tileset * tileset)343 void PropertyBrowser::tilesetChanged(Tileset *tileset)
344 {
345     if (mObject == tileset) {
346         updateProperties();
347         updateCustomProperties();   // Tileset may have been swapped
348     }
349 }
350 
tileChanged(Tile * tile)351 void PropertyBrowser::tileChanged(Tile *tile)
352 {
353     if (mObject == tile)
354         updateProperties();
355 }
356 
tileTypeChanged(Tile * tile)357 void PropertyBrowser::tileTypeChanged(Tile *tile)
358 {
359     if (mObject == tile) {
360         updateProperties();
361         updateCustomProperties();
362     } else if (mObject && mObject->typeId() == Object::MapObjectType) {
363         auto mapObject = static_cast<MapObject*>(mObject);
364         if (mapObject->cell().tile() == tile && mapObject->type().isEmpty())
365             updateProperties();
366     }
367 }
368 
wangSetChanged(Tileset * tileset,int index)369 void PropertyBrowser::wangSetChanged(Tileset *tileset, int index)
370 {
371     if (mObject == tileset->wangSet(index))
372         updateProperties();
373 }
374 
predefinedPropertyValue(Object * object,const QString & name)375 static QVariant predefinedPropertyValue(Object *object, const QString &name)
376 {
377     QString objectType;
378 
379     switch (object->typeId()) {
380     case Object::TileType:
381         objectType = static_cast<Tile*>(object)->type();
382         break;
383     case Object::MapObjectType: {
384         auto mapObject = static_cast<MapObject*>(object);
385         objectType = mapObject->type();
386 
387         if (Tile *tile = mapObject->cell().tile()) {
388             if (tile->hasProperty(name))
389                 return tile->property(name);
390 
391             if (objectType.isEmpty())
392                 objectType = tile->type();
393         }
394         break;
395     }
396     case Object::LayerType:
397     case Object::MapType:
398     case Object::TilesetType:
399     case Object::WangSetType:
400     case Object::WangColorType:
401     case Object::ObjectTemplateType:
402         break;
403     }
404 
405     if (objectType.isEmpty())
406         return QVariant();
407 
408     for (const ObjectType &type : Object::objectTypes()) {
409         if (type.name == objectType)
410             if (type.defaultProperties.contains(name))
411                 return type.defaultProperties.value(name);
412     }
413 
414     return QVariant();
415 }
416 
anyObjectHasProperty(const QList<Object * > & objects,const QString & name)417 static bool anyObjectHasProperty(const QList<Object*> &objects, const QString &name)
418 {
419     for (Object *obj : objects) {
420         if (obj->hasProperty(name))
421             return true;
422     }
423     return false;
424 }
425 
propertyValueAffected(Object * currentObject,Object * changedObject,const QString & propertyName)426 static bool propertyValueAffected(Object *currentObject,
427                                   Object *changedObject,
428                                   const QString &propertyName)
429 {
430     if (currentObject == changedObject)
431         return true;
432 
433     // Changed property may be inherited
434     if (currentObject && currentObject->typeId() == Object::MapObjectType && changedObject->typeId() == Object::TileType) {
435         auto tile = static_cast<MapObject*>(currentObject)->cell().tile();
436         if (tile == changedObject && !currentObject->hasProperty(propertyName))
437             return true;
438     }
439 
440     return false;
441 }
442 
objectPropertiesRelevant(Document * document,Object * object)443 static bool objectPropertiesRelevant(Document *document, Object *object)
444 {
445     auto currentObject = document->currentObject();
446     if (!currentObject)
447         return false;
448 
449     if (currentObject == object)
450         return true;
451 
452     if (currentObject->typeId() == Object::MapObjectType)
453         if (static_cast<MapObject*>(currentObject)->cell().tile() == object)
454             return true;
455 
456     if (document->currentObjects().contains(object))
457         return true;
458 
459     return false;
460 }
461 
propertyAdded(Object * object,const QString & name)462 void PropertyBrowser::propertyAdded(Object *object, const QString &name)
463 {
464     if (!objectPropertiesRelevant(mDocument, object))
465         return;
466     if (QtVariantProperty *property = mNameToProperty.value(name)) {
467         if (propertyValueAffected(mObject, object, name))
468             setCustomPropertyValue(property, object->property(name));
469     } else {
470         QVariant value;
471         if (mObject->hasProperty(name))
472             value = mObject->property(name);
473         else
474             value = predefinedPropertyValue(mObject, name);
475 
476         createCustomProperty(name, toDisplayValue(value));
477     }
478     updateCustomPropertyColor(name);
479 }
480 
propertyRemoved(Object * object,const QString & name)481 void PropertyBrowser::propertyRemoved(Object *object, const QString &name)
482 {
483     auto property = mNameToProperty.value(name);
484     if (!property)
485         return;
486     if (!objectPropertiesRelevant(mDocument, object))
487         return;
488 
489     QVariant predefinedValue = predefinedPropertyValue(mObject, name);
490 
491     if (!predefinedValue.isValid() &&
492             !anyObjectHasProperty(mDocument->currentObjects(), name)) {
493         // It's not a predefined property and no selected object has this
494         // property, so delete it.
495 
496         // First move up or down the currently selected item
497         QtBrowserItem *item = currentItem();
498         if (item && item->property() == property) {
499             const QList<QtBrowserItem *> siblings = item->parent()->children();
500             if (siblings.count() > 1) {
501                 int currentItemIndex = siblings.indexOf(item);
502                 if (item == siblings.last()) {
503                     setCurrentItem(siblings.at(currentItemIndex - 1));
504                 } else {
505                     setCurrentItem(siblings.at(currentItemIndex + 1));
506                 }
507             }
508         }
509 
510         deleteCustomProperty(property);
511         return;
512     }
513 
514     if (propertyValueAffected(mObject, object, name)) {
515         // Property deleted from the current object, so reset the value.
516         setCustomPropertyValue(property, predefinedValue);
517     }
518 
519     updateCustomPropertyColor(name);
520 }
521 
propertyChanged(Object * object,const QString & name)522 void PropertyBrowser::propertyChanged(Object *object, const QString &name)
523 {
524     auto property = mNameToProperty.value(name);
525     if (!property)
526         return;
527 
528     if (propertyValueAffected(mObject, object, name))
529         setCustomPropertyValue(property, object->property(name));
530 
531     if (mDocument->currentObjects().contains(object))
532         updateCustomPropertyColor(name);
533 }
534 
propertiesChanged(Object * object)535 void PropertyBrowser::propertiesChanged(Object *object)
536 {
537     if (objectPropertiesRelevant(mDocument, object))
538         updateCustomProperties();
539 }
540 
selectedObjectsChanged()541 void PropertyBrowser::selectedObjectsChanged()
542 {
543     updateCustomProperties();
544 }
545 
selectedLayersChanged()546 void PropertyBrowser::selectedLayersChanged()
547 {
548     updateCustomProperties();
549 }
550 
selectedTilesChanged()551 void PropertyBrowser::selectedTilesChanged()
552 {
553     updateCustomProperties();
554 }
555 
objectTypesChanged()556 void PropertyBrowser::objectTypesChanged()
557 {
558     if (mObject && mObject->typeId() == Object::MapObjectType)
559         updateCustomProperties();
560 }
561 
valueChanged(QtProperty * property,const QVariant & val)562 void PropertyBrowser::valueChanged(QtProperty *property, const QVariant &val)
563 {
564     if (mUpdating)
565         return;
566     if (!mObject || !mDocument)
567         return;
568     if (!mPropertyToId.contains(property))
569         return;
570 
571     const PropertyId id = mPropertyToId.value(property);
572 
573     if (id == CustomProperty) {
574         QUndoStack *undoStack = mDocument->undoStack();
575         undoStack->push(new SetProperty(mDocument,
576                                         mDocument->currentObjects(),
577                                         property->propertyName(),
578                                         fromDisplayValue(val)));
579         return;
580     }
581 
582     switch (mObject->typeId()) {
583     case Object::MapType:               applyMapValue(id, val); break;
584     case Object::MapObjectType:         applyMapObjectValue(id, val); break;
585     case Object::LayerType:             applyLayerValue(id, val); break;
586     case Object::TilesetType:           applyTilesetValue(id, val); break;
587     case Object::TileType:              applyTileValue(id, val); break;
588     case Object::WangSetType:           applyWangSetValue(id, val); break;
589     case Object::WangColorType:         applyWangColorValue(id, val); break;
590     case Object::ObjectTemplateType:    break;
591     }
592 }
593 
resetProperty(QtProperty * property)594 void PropertyBrowser::resetProperty(QtProperty *property)
595 {
596     auto typeId = mVariantManager->propertyType(property);
597     if (typeId == QMetaType::QColor)
598         mVariantManager->setValue(property, QColor());
599     else if (typeId == VariantPropertyManager::displayObjectRefTypeId()) {
600         mVariantManager->setValue(property, toDisplayValue(QVariant::fromValue(ObjectRef())));
601     } else
602         qWarning() << "Resetting of property type not supported right now";
603 }
604 
addMapProperties()605 void PropertyBrowser::addMapProperties()
606 {
607     QtProperty *groupProperty = mGroupManager->addProperty(tr("Map"));
608 
609     QtVariantProperty *orientationProperty =
610             addProperty(OrientationProperty,
611                         QtVariantPropertyManager::enumTypeId(),
612                         tr("Orientation"),
613                         groupProperty);
614 
615     orientationProperty->setAttribute(QLatin1String("enumNames"), mOrientationNames);
616 
617     addProperty(WidthProperty, QMetaType::Int, tr("Width"), groupProperty)->setEnabled(false);
618     addProperty(HeightProperty, QMetaType::Int, tr("Height"), groupProperty)->setEnabled(false);
619     auto tileWidthProperty = addProperty(TileWidthProperty, QMetaType::Int, tr("Tile Width"), groupProperty);
620     auto tileHeightProperty = addProperty(TileHeightProperty, QMetaType::Int, tr("Tile Height"), groupProperty);
621     addProperty(InfiniteProperty, QMetaType::Bool, tr("Infinite"), groupProperty);
622 
623     tileWidthProperty->setAttribute(QStringLiteral("minimum"), 1);
624     tileHeightProperty->setAttribute(QStringLiteral("minimum"), 1);
625 
626     addProperty(HexSideLengthProperty, QMetaType::Int, tr("Tile Side Length (Hex)"), groupProperty);
627 
628     QtVariantProperty *staggerAxisProperty =
629             addProperty(StaggerAxisProperty,
630                         QtVariantPropertyManager::enumTypeId(),
631                         tr("Stagger Axis"),
632                         groupProperty);
633 
634     staggerAxisProperty->setAttribute(QLatin1String("enumNames"), mStaggerAxisNames);
635 
636     QtVariantProperty *staggerIndexProperty =
637             addProperty(StaggerIndexProperty,
638                         QtVariantPropertyManager::enumTypeId(),
639                         tr("Stagger Index"),
640                         groupProperty);
641 
642     staggerIndexProperty->setAttribute(QLatin1String("enumNames"), mStaggerIndexNames);
643 
644     QtVariantProperty *layerFormatProperty =
645             addProperty(LayerFormatProperty,
646                         QtVariantPropertyManager::enumTypeId(),
647                         tr("Tile Layer Format"),
648                         groupProperty);
649 
650     layerFormatProperty->setAttribute(QLatin1String("enumNames"), mLayerFormatNames);
651 
652     QtVariantProperty *chunkWidthProperty = addProperty(ChunkWidthProperty, QMetaType::Int, tr("Output Chunk Width"), groupProperty);
653     QtVariantProperty *chunkHeightProperty = addProperty(ChunkHeightProperty, QMetaType::Int, tr("Output Chunk Height"), groupProperty);
654 
655     chunkWidthProperty->setAttribute(QLatin1String("minimum"), CHUNK_SIZE_MIN);
656     chunkHeightProperty->setAttribute(QLatin1String("minimum"), CHUNK_SIZE_MIN);
657 
658     QtVariantProperty *renderOrderProperty =
659             addProperty(RenderOrderProperty,
660                         QtVariantPropertyManager::enumTypeId(),
661                         tr("Tile Render Order"),
662                         groupProperty);
663 
664     addProperty(CompressionLevelProperty, QMetaType::Int, tr("Compression Level"), groupProperty);
665 
666     renderOrderProperty->setAttribute(QLatin1String("enumNames"), mRenderOrderNames);
667 
668     addProperty(BackgroundColorProperty, QMetaType::QColor, tr("Background Color"), groupProperty);
669     addProperty(groupProperty);
670 }
671 
objectTypeNames()672 static QStringList objectTypeNames()
673 {
674     QStringList names;
675     for (const ObjectType &type : Object::objectTypes())
676         names.append(type.name);
677     return names;
678 }
679 
680 enum MapObjectFlags {
681     ObjectHasDimensions = 0x1,
682     ObjectHasTile = 0x2,
683     ObjectIsText = 0x4
684 };
685 
mapObjectFlags(const MapObject * mapObject)686 static int mapObjectFlags(const MapObject *mapObject)
687 {
688     int flags = 0;
689     if (mapObject->hasDimensions())
690         flags |= ObjectHasDimensions;
691     if (!mapObject->cell().isEmpty())
692         flags |= ObjectHasTile;
693     if (mapObject->shape() == MapObject::Text)
694         flags |= ObjectIsText;
695     return flags;
696 }
697 
addMapObjectProperties()698 void PropertyBrowser::addMapObjectProperties()
699 {
700     QtProperty *groupProperty = mGroupManager->addProperty(tr("Object"));
701 
702     addProperty(IdProperty, QMetaType::Int, tr("ID"), groupProperty)->setEnabled(false);
703     addProperty(TemplateProperty, filePathTypeId(), tr("Template"), groupProperty)->setEnabled(false);
704     addProperty(NameProperty, QMetaType::QString, tr("Name"), groupProperty);
705 
706     QtVariantProperty *typeProperty =
707             addProperty(TypeProperty, QMetaType::QString, tr("Type"), groupProperty);
708     typeProperty->setAttribute(QLatin1String("suggestions"), objectTypeNames());
709 
710     if (mMapDocument->allowHidingObjects())
711         addProperty(VisibleProperty, QMetaType::Bool, tr("Visible"), groupProperty);
712 
713     addProperty(XProperty, QMetaType::Double, tr("X"), groupProperty);
714     addProperty(YProperty, QMetaType::Double, tr("Y"), groupProperty);
715 
716     auto mapObject = static_cast<const MapObject*>(mObject);
717     mMapObjectFlags = mapObjectFlags(mapObject);
718 
719     if (mMapObjectFlags & ObjectHasDimensions) {
720         addProperty(WidthProperty, QMetaType::Double, tr("Width"), groupProperty);
721         addProperty(HeightProperty, QMetaType::Double, tr("Height"), groupProperty);
722     }
723 
724     bool isPoint = mapObject->shape() == MapObject::Point;
725     addProperty(RotationProperty, QMetaType::Double, tr("Rotation"), groupProperty)->setEnabled(!isPoint);
726 
727     if (mMapObjectFlags & ObjectHasTile) {
728         QtVariantProperty *flippingProperty =
729                 addProperty(FlippingProperty, VariantPropertyManager::flagTypeId(),
730                                tr("Flipping"), groupProperty);
731 
732         flippingProperty->setAttribute(QLatin1String("flagNames"), mFlippingFlagNames);
733     }
734 
735     if (mMapObjectFlags & ObjectIsText) {
736         addProperty(TextProperty, QMetaType::QString, tr("Text"), groupProperty)->setAttribute(QLatin1String("multiline"), true);
737         addProperty(TextAlignmentProperty, VariantPropertyManager::alignmentTypeId(), tr("Alignment"), groupProperty);
738         addProperty(FontProperty, QMetaType::QFont, tr("Font"), groupProperty);
739         addProperty(WordWrapProperty, QMetaType::Bool, tr("Word Wrap"), groupProperty);
740         addProperty(ColorProperty, QMetaType::QColor, tr("Color"), groupProperty);
741     }
742 
743     addProperty(groupProperty);
744 }
745 
addLayerProperties(QtProperty * parent)746 void PropertyBrowser::addLayerProperties(QtProperty *parent)
747 {
748     addProperty(IdProperty, QMetaType::Int, tr("ID"), parent)->setEnabled(false);
749     addProperty(NameProperty, QMetaType::QString, tr("Name"), parent);
750     addProperty(VisibleProperty, QMetaType::Bool, tr("Visible"), parent);
751     addProperty(LockedProperty, QMetaType::Bool, tr("Locked"), parent);
752 
753     QtVariantProperty *opacityProperty =
754             addProperty(OpacityProperty, QMetaType::Double, tr("Opacity"), parent);
755     opacityProperty->setAttribute(QLatin1String("minimum"), 0.0);
756     opacityProperty->setAttribute(QLatin1String("maximum"), 1.0);
757     opacityProperty->setAttribute(QLatin1String("singleStep"), 0.1);
758     addProperty(TintColorProperty, QMetaType::QColor, tr("Tint Color"), parent);
759 
760     addProperty(OffsetXProperty, QMetaType::Double, tr("Horizontal Offset"), parent);
761     addProperty(OffsetYProperty, QMetaType::Double, tr("Vertical Offset"), parent);
762 
763     addProperty(ParallaxFactorProperty, QMetaType::QPointF, tr("Parallax Factor"), parent);
764 }
765 
addTileLayerProperties()766 void PropertyBrowser::addTileLayerProperties()
767 {
768     QtProperty *groupProperty = mGroupManager->addProperty(tr("Tile Layer"));
769     addLayerProperties(groupProperty);
770     addProperty(groupProperty);
771 }
772 
addObjectGroupProperties()773 void PropertyBrowser::addObjectGroupProperties()
774 {
775     QtProperty *groupProperty = mGroupManager->addProperty(tr("Object Layer"));
776     addLayerProperties(groupProperty);
777 
778     addProperty(ColorProperty, QMetaType::QColor, tr("Color"), groupProperty);
779 
780     QtVariantProperty *drawOrderProperty =
781             addProperty(DrawOrderProperty,
782                         QtVariantPropertyManager::enumTypeId(),
783                         tr("Drawing Order"),
784                         groupProperty);
785 
786     drawOrderProperty->setAttribute(QLatin1String("enumNames"), mDrawOrderNames);
787 
788     addProperty(groupProperty);
789 }
790 
addImageLayerProperties()791 void PropertyBrowser::addImageLayerProperties()
792 {
793     QtProperty *groupProperty = mGroupManager->addProperty(tr("Image Layer"));
794     addLayerProperties(groupProperty);
795 
796     QtVariantProperty *imageSourceProperty = addProperty(ImageSourceProperty,
797                                                          filePathTypeId(),
798                                                          tr("Image"), groupProperty);
799 
800     imageSourceProperty->setAttribute(QLatin1String("filter"),
801                                       Utils::readableImageFormatsFilter());
802 
803     addProperty(ColorProperty, QMetaType::QColor, tr("Transparent Color"), groupProperty);
804 
805     addProperty(groupProperty);
806 }
807 
addGroupLayerProperties()808 void PropertyBrowser::addGroupLayerProperties()
809 {
810     QtProperty *groupProperty = mGroupManager->addProperty(tr("Group Layer"));
811     addLayerProperties(groupProperty);
812     addProperty(groupProperty);
813 }
814 
addTilesetProperties()815 void PropertyBrowser::addTilesetProperties()
816 {
817     const Tileset *tileset = static_cast<const Tileset*>(mObject);
818 
819     QtProperty *groupProperty = mGroupManager->addProperty(tr("Tileset"));
820 
821     if (mMapDocument) {
822         auto property = addProperty(FileNameProperty, filePathTypeId(), tr("Filename"), groupProperty);
823 
824         QString filter = QCoreApplication::translate("MainWindow", "All Files (*)");
825         FormatHelper<TilesetFormat> helper(FileFormat::Read, filter);
826 
827         property->setAttribute(QStringLiteral("filter"), helper.filter());
828     }
829 
830     QtVariantProperty *nameProperty = addProperty(NameProperty, QMetaType::QString, tr("Name"), groupProperty);
831     nameProperty->setEnabled(mTilesetDocument);
832 
833     QtVariantProperty *alignmentProperty =
834             addProperty(ObjectAlignmentProperty,
835                         QtVariantPropertyManager::enumTypeId(),
836                         tr("Object Alignment"),
837                         groupProperty);
838 
839     alignmentProperty->setAttribute(QLatin1String("enumNames"), mAlignmentNames);
840 
841     QtVariantProperty *tileOffsetProperty = addProperty(TileOffsetProperty, QMetaType::QPoint, tr("Drawing Offset"), groupProperty);
842     tileOffsetProperty->setEnabled(mTilesetDocument);
843 
844     QtVariantProperty *backgroundProperty = addProperty(BackgroundColorProperty, QMetaType::QColor, tr("Background Color"), groupProperty);
845     backgroundProperty->setEnabled(mTilesetDocument);
846 
847     QtVariantProperty *orientationProperty =
848             addProperty(OrientationProperty,
849                         QtVariantPropertyManager::enumTypeId(),
850                         tr("Orientation"),
851                         groupProperty);
852 
853     orientationProperty->setAttribute(QLatin1String("enumNames"), mTilesetOrientationNames);
854 
855     QtVariantProperty *gridWidthProperty = addProperty(GridWidthProperty, QMetaType::Int, tr("Grid Width"), groupProperty);
856     gridWidthProperty->setEnabled(mTilesetDocument);
857     gridWidthProperty->setAttribute(QLatin1String("minimum"), 1);
858     QtVariantProperty *gridHeightProperty = addProperty(GridHeightProperty, QMetaType::Int, tr("Grid Height"), groupProperty);
859     gridHeightProperty->setEnabled(mTilesetDocument);
860     gridHeightProperty->setAttribute(QLatin1String("minimum"), 1);
861 
862     QtVariantProperty *columnsProperty = addProperty(ColumnCountProperty, QMetaType::Int, tr("Columns"), groupProperty);
863     columnsProperty->setAttribute(QLatin1String("minimum"), 1);
864 
865     QtVariantProperty *transformationsGroupProperty = mVariantManager->addProperty(VariantPropertyManager::unstyledGroupTypeId(), tr("Allowed Transformations"));
866 
867     QtVariantProperty *flipHorizontallyProperty = addProperty(AllowFlipHorizontallyProperty, QMetaType::Bool, tr("Flip Horizontally"), transformationsGroupProperty);
868     QtVariantProperty *flipVerticallyProperty = addProperty(AllowFlipVerticallyProperty, QMetaType::Bool, tr("Flip Vertically"), transformationsGroupProperty);
869     QtVariantProperty *rotateProperty = addProperty(AllowRotateProperty, QMetaType::Bool, tr("Rotate"), transformationsGroupProperty);
870     QtVariantProperty *randomProperty = addProperty(PreferUntransformedProperty, QMetaType::Bool, tr("Prefer Untransformed Tiles"), transformationsGroupProperty);
871     flipHorizontallyProperty->setEnabled(mTilesetDocument);
872     flipVerticallyProperty->setEnabled(mTilesetDocument);
873     rotateProperty->setEnabled(mTilesetDocument);
874     randomProperty->setEnabled(mTilesetDocument);
875 
876     groupProperty->addSubProperty(transformationsGroupProperty);
877 
878     // Next properties we should add only for non 'Collection of Images' tilesets
879     if (!tileset->isCollection()) {
880         QtVariantProperty *parametersProperty =
881                 addProperty(TilesetImageParametersProperty, VariantPropertyManager::tilesetParametersTypeId(), tr("Image"), groupProperty);
882 
883         parametersProperty->setEnabled(mTilesetDocument);
884 
885         QtVariantProperty *imageSourceProperty = addProperty(ImageSourceProperty, QMetaType::QString, tr("Source"), parametersProperty);
886         QtVariantProperty *tileWidthProperty = addProperty(TileWidthProperty, QMetaType::Int, tr("Tile Width"), parametersProperty);
887         QtVariantProperty *tileHeightProperty = addProperty(TileHeightProperty, QMetaType::Int, tr("Tile Height"), parametersProperty);
888         QtVariantProperty *marginProperty = addProperty(MarginProperty, QMetaType::Int, tr("Margin"), parametersProperty);
889         QtVariantProperty *spacingProperty = addProperty(SpacingProperty, QMetaType::Int, tr("Spacing"), parametersProperty);
890         QtVariantProperty *colorProperty = addProperty(ColorProperty, QMetaType::QColor, tr("Transparent Color"), parametersProperty);
891 
892         // These properties can't be directly edited. To change the parameters,
893         // the TilesetParametersEdit is used.
894         imageSourceProperty->setEnabled(false);
895         tileWidthProperty->setEnabled(false);
896         tileHeightProperty->setEnabled(false);
897         marginProperty->setEnabled(false);
898         spacingProperty->setEnabled(false);
899         colorProperty->setEnabled(false);
900     }
901     addProperty(groupProperty);
902 }
903 
addTileProperties()904 void PropertyBrowser::addTileProperties()
905 {
906     QtProperty *groupProperty = mGroupManager->addProperty(tr("Tile"));
907     addProperty(IdProperty, QMetaType::Int, tr("ID"), groupProperty)->setEnabled(false);
908 
909     QtVariantProperty *typeProperty =
910             addProperty(TypeProperty, QMetaType::QString, tr("Type"), groupProperty);
911     typeProperty->setAttribute(QLatin1String("suggestions"), objectTypeNames());
912     typeProperty->setEnabled(mTilesetDocument);
913 
914     addProperty(WidthProperty, QMetaType::Int, tr("Width"), groupProperty)->setEnabled(false);
915     addProperty(HeightProperty, QMetaType::Int, tr("Height"), groupProperty)->setEnabled(false);
916 
917     QtVariantProperty *probabilityProperty = addProperty(TileProbabilityProperty,
918                                                          QMetaType::Double,
919                                                          tr("Probability"),
920                                                          groupProperty);
921     probabilityProperty->setAttribute(QLatin1String("decimals"), 3);
922     probabilityProperty->setToolTip(tr("Relative chance this tile will be picked"));
923     probabilityProperty->setEnabled(mTilesetDocument);
924 
925     const Tile *tile = static_cast<const Tile*>(mObject);
926     if (!tile->imageSource().isEmpty()) {
927         QtVariantProperty *imageSourceProperty = addProperty(ImageSourceProperty,
928                                                              filePathTypeId(),
929                                                              tr("Image"), groupProperty);
930 
931         imageSourceProperty->setAttribute(QLatin1String("filter"),
932                                           Utils::readableImageFormatsFilter());
933         imageSourceProperty->setEnabled(mTilesetDocument);
934     }
935 
936     addProperty(groupProperty);
937 }
938 
addWangSetProperties()939 void PropertyBrowser::addWangSetProperties()
940 {
941     QtProperty *groupProperty = mGroupManager->addProperty(tr("Terrain Set"));
942     QtVariantProperty *nameProperty = addProperty(NameProperty, QMetaType::QString, tr("Name"), groupProperty);
943     QtVariantProperty *typeProperty = addProperty(WangSetTypeProperty,
944                                                   QtVariantPropertyManager::enumTypeId(),
945                                                   tr("Type"),
946                                                   groupProperty);
947     QtVariantProperty *colorCountProperty = addProperty(ColorCountProperty, QMetaType::Int, tr("Terrain Count"), groupProperty);
948 
949     typeProperty->setAttribute(QLatin1String("enumNames"), mWangSetTypeNames);
950     typeProperty->setAttribute(QLatin1String("enumIcons"), QVariant::fromValue(mWangSetIcons));
951 
952     colorCountProperty->setAttribute(QLatin1String("minimum"), 0);
953     colorCountProperty->setAttribute(QLatin1String("maximum"), WangId::MAX_COLOR_COUNT);
954 
955     nameProperty->setEnabled(mTilesetDocument);
956     colorCountProperty->setEnabled(mTilesetDocument);
957 
958     addProperty(groupProperty);
959 }
960 
addWangColorProperties()961 void PropertyBrowser::addWangColorProperties()
962 {
963     QtProperty *groupProperty = mGroupManager->addProperty(tr("Terrain"));
964     QtVariantProperty *nameProperty = addProperty(NameProperty,
965                                                   QMetaType::QString,
966                                                   tr("Name"),
967                                                   groupProperty);
968     QtVariantProperty *colorProperty = addProperty(ColorProperty,
969                                                    QMetaType::QColor,
970                                                    tr("Color"),
971                                                    groupProperty);
972     QtVariantProperty *probabilityProperty = addProperty(WangColorProbabilityProperty,
973                                                          QMetaType::Double,
974                                                          tr("Probability"),
975                                                          groupProperty);
976 
977     probabilityProperty->setAttribute(QLatin1String("minimum"), 0.01);
978 
979     nameProperty->setEnabled(mTilesetDocument);
980     colorProperty->setEnabled(mTilesetDocument);
981     probabilityProperty->setEnabled(mTilesetDocument);
982 
983     addProperty(groupProperty);
984 }
985 
applyMapValue(PropertyId id,const QVariant & val)986 void PropertyBrowser::applyMapValue(PropertyId id, const QVariant &val)
987 {
988     QUndoCommand *command = nullptr;
989 
990     switch (id) {
991     case TileWidthProperty:
992         command = new ChangeMapProperty(mMapDocument, Map::TileWidthProperty,
993                                         val.toInt());
994         break;
995     case TileHeightProperty:
996         command = new ChangeMapProperty(mMapDocument, Map::TileHeightProperty,
997                                         val.toInt());
998         break;
999     case InfiniteProperty: {
1000         bool infinite = val.toInt();
1001 
1002         QUndoStack *undoStack = mDocument->undoStack();
1003         undoStack->beginMacro(tr("Change Infinite Property"));
1004 
1005         if (!infinite) {
1006             QRect mapBounds(QPoint(0, 0), mMapDocument->map()->size());
1007 
1008             LayerIterator iterator(mMapDocument->map());
1009             while (Layer *layer = iterator.next()) {
1010                 if (TileLayer *tileLayer = dynamic_cast<TileLayer*>(layer))
1011                     mapBounds = mapBounds.united(tileLayer->region().boundingRect());
1012             }
1013 
1014             if (mapBounds.size() == QSize(0, 0))
1015                 mapBounds.setSize(QSize(1, 1));
1016 
1017             mMapDocument->resizeMap(mapBounds.size(), -mapBounds.topLeft(), false);
1018         }
1019 
1020         undoStack->push(new ChangeMapProperty(mMapDocument, Map::InfiniteProperty,
1021                                               val.toInt()));
1022         undoStack->endMacro();
1023         break;
1024     }
1025     case OrientationProperty: {
1026         Map::Orientation orientation = static_cast<Map::Orientation>(val.toInt() + 1);
1027         command = new ChangeMapProperty(mMapDocument, orientation);
1028         break;
1029     }
1030     case HexSideLengthProperty: {
1031         command = new ChangeMapProperty(mMapDocument, Map::HexSideLengthProperty,
1032                                         val.toInt());
1033         break;
1034     }
1035     case StaggerAxisProperty: {
1036         Map::StaggerAxis staggerAxis = static_cast<Map::StaggerAxis>(val.toInt());
1037         command = new ChangeMapProperty(mMapDocument, staggerAxis);
1038         break;
1039     }
1040     case StaggerIndexProperty: {
1041         Map::StaggerIndex staggerIndex = static_cast<Map::StaggerIndex>(val.toInt());
1042         command = new ChangeMapProperty(mMapDocument, staggerIndex);
1043         break;
1044     }
1045     case LayerFormatProperty: {
1046         Map::LayerDataFormat format = mLayerFormatValues.at(val.toInt());
1047         command = new ChangeMapProperty(mMapDocument, format);
1048         break;
1049     }
1050     case RenderOrderProperty: {
1051         Map::RenderOrder renderOrder = static_cast<Map::RenderOrder>(val.toInt());
1052         command = new ChangeMapProperty(mMapDocument, renderOrder);
1053         break;
1054     }
1055     case BackgroundColorProperty:
1056         command = new ChangeMapProperty(mMapDocument, val.value<QColor>());
1057         break;
1058     case CompressionLevelProperty:
1059         command = new ChangeMapProperty(mMapDocument, Map::CompressionLevelProperty, val.toInt());
1060         break;
1061     case ChunkWidthProperty: {
1062         QSize chunkSize = mMapDocument->map()->chunkSize();
1063         chunkSize.setWidth(val.toInt());
1064         command = new ChangeMapProperty(mMapDocument, chunkSize);
1065         break;
1066     }
1067     case ChunkHeightProperty: {
1068         QSize chunkSize = mMapDocument->map()->chunkSize();
1069         chunkSize.setHeight(val.toInt());
1070         command = new ChangeMapProperty(mMapDocument, chunkSize);
1071         break;
1072     }
1073     default:
1074         break;
1075     }
1076 
1077     if (command)
1078         mDocument->undoStack()->push(command);
1079 }
1080 
applyMapObjectValueTo(PropertyId id,const QVariant & val,MapObject * mapObject)1081 QUndoCommand *PropertyBrowser::applyMapObjectValueTo(PropertyId id, const QVariant &val, MapObject *mapObject)
1082 {
1083     QUndoCommand *command = nullptr;
1084 
1085     switch (id) {
1086     default: {
1087         MapObject::Property property;
1088 
1089         switch (id) {
1090         case NameProperty:          property = MapObject::NameProperty; break;
1091         case TypeProperty:          property = MapObject::TypeProperty; break;
1092         case VisibleProperty:       property = MapObject::VisibleProperty; break;
1093         case TextProperty:          property = MapObject::TextProperty; break;
1094         case FontProperty:          property = MapObject::TextFontProperty; break;
1095         case TextAlignmentProperty: property = MapObject::TextAlignmentProperty; break;
1096         case WordWrapProperty:      property = MapObject::TextWordWrapProperty; break;
1097         case ColorProperty:         property = MapObject::TextColorProperty; break;
1098         default:
1099             return nullptr; // unrecognized property
1100         }
1101 
1102         command = new ChangeMapObject(mDocument, mapObject, property, val);
1103         break;
1104     }
1105     case XProperty: {
1106         const QPointF oldPos = mapObject->position();
1107         const QPointF newPos(val.toReal(), oldPos.y());
1108         command = new MoveMapObject(mDocument, mapObject, newPos, oldPos);
1109         break;
1110     }
1111     case YProperty: {
1112         const QPointF oldPos = mapObject->position();
1113         const QPointF newPos(oldPos.x(), val.toReal());
1114         command = new MoveMapObject(mDocument, mapObject, newPos, oldPos);
1115         break;
1116     }
1117     case WidthProperty: {
1118         const QSizeF oldSize = mapObject->size();
1119         const QSizeF newSize(val.toReal(), oldSize.height());
1120         command = new ResizeMapObject(mDocument, mapObject, newSize, oldSize);
1121         break;
1122     }
1123     case HeightProperty: {
1124         const QSizeF oldSize = mapObject->size();
1125         const QSizeF newSize(oldSize.width(), val.toReal());
1126         command = new ResizeMapObject(mDocument, mapObject, newSize, oldSize);
1127         break;
1128     }
1129     case RotationProperty:
1130         if (mapObject->canRotate()) {
1131             const qreal newRotation = val.toDouble();
1132             const qreal oldRotation = mapObject->rotation();
1133             command = new RotateMapObject(mDocument, mapObject, newRotation, oldRotation);
1134         }
1135         break;
1136     case FlippingProperty: {
1137         const int flippingFlags = val.toInt();
1138 
1139         MapObjectCell mapObjectCell;
1140         mapObjectCell.object = mapObject;
1141         mapObjectCell.cell = mapObject->cell();
1142         mapObjectCell.cell.setFlippedHorizontally(flippingFlags & 1);
1143         mapObjectCell.cell.setFlippedVertically(flippingFlags & 2);
1144 
1145         command = new ChangeMapObjectCells(mDocument, { mapObjectCell });
1146 
1147         command->setText(QCoreApplication::translate("Undo Commands",
1148                                                      "Flip %n Object(s)",
1149                                                      nullptr,
1150                                                      mMapDocument->selectedObjects().size()));
1151         break;
1152     }
1153     }
1154 
1155     return command;
1156 }
1157 
applyMapObjectValue(PropertyId id,const QVariant & val)1158 void PropertyBrowser::applyMapObjectValue(PropertyId id, const QVariant &val)
1159 {
1160     MapObject *mapObject = static_cast<MapObject*>(mObject);
1161 
1162     QUndoCommand *command = applyMapObjectValueTo(id, val, mapObject);
1163     if (!command)
1164         return;
1165 
1166     mDocument->undoStack()->beginMacro(command->text());
1167     mDocument->undoStack()->push(command);
1168 
1169     for (MapObject *obj : mMapDocument->selectedObjects()) {
1170         if (obj != mapObject) {
1171             if (QUndoCommand *cmd = applyMapObjectValueTo(id, val, obj))
1172                 mDocument->undoStack()->push(cmd);
1173         }
1174     }
1175 
1176     mDocument->undoStack()->endMacro();
1177 }
1178 
applyLayerValue(PropertyId id,const QVariant & val)1179 void PropertyBrowser::applyLayerValue(PropertyId id, const QVariant &val)
1180 {
1181     Layer *currentLayer = static_cast<Layer*>(mObject);
1182 
1183     QUndoCommand *command = applyLayerValueTo(id, val, currentLayer);
1184     if (!command)
1185         return;
1186 
1187     mDocument->undoStack()->beginMacro(command->text());
1188     mDocument->undoStack()->push(command);
1189 
1190     for (Layer *layer : mMapDocument->selectedLayers()) {
1191         if (layer != currentLayer) {
1192             if (QUndoCommand *cmd = applyLayerValueTo(id, val, layer))
1193                 mDocument->undoStack()->push(cmd);
1194         }
1195     }
1196 
1197     mDocument->undoStack()->endMacro();
1198 }
1199 
applyLayerValueTo(PropertyBrowser::PropertyId id,const QVariant & val,Layer * layer)1200 QUndoCommand *PropertyBrowser::applyLayerValueTo(PropertyBrowser::PropertyId id, const QVariant &val, Layer *layer)
1201 {
1202     QUndoCommand *command = nullptr;
1203 
1204     switch (id) {
1205     case NameProperty:
1206         command = new SetLayerName(mMapDocument, layer, val.toString());
1207         break;
1208     case VisibleProperty:
1209         command = new SetLayerVisible(mMapDocument, layer, val.toBool());
1210         break;
1211     case LockedProperty:
1212         command = new SetLayerLocked(mMapDocument, layer, val.toBool());
1213         break;
1214     case OpacityProperty:
1215         command = new SetLayerOpacity(mMapDocument, layer, val.toDouble());
1216         break;
1217     case TintColorProperty:
1218         command = new SetLayerTintColor(mMapDocument, layer, val.value<QColor>());
1219         break;
1220     case OffsetXProperty:
1221     case OffsetYProperty: {
1222         QPointF offset = layer->offset();
1223 
1224         if (id == OffsetXProperty)
1225             offset.setX(val.toDouble());
1226         else
1227             offset.setY(val.toDouble());
1228 
1229         command = new SetLayerOffset(mMapDocument, layer, offset);
1230         break;
1231     }
1232     case ParallaxFactorProperty:
1233         command = new SetLayerParallaxFactor(mMapDocument, layer, val.toPointF());
1234         break;
1235     default:
1236         switch (layer->layerType()) {
1237         case Layer::TileLayerType:
1238             command = applyTileLayerValueTo(id, val, static_cast<TileLayer*>(layer));
1239             break;
1240         case Layer::ObjectGroupType:
1241             command = applyObjectGroupValueTo(id, val, static_cast<ObjectGroup*>(layer));
1242             break;
1243         case Layer::ImageLayerType:
1244             command = applyImageLayerValueTo(id, val, static_cast<ImageLayer*>(layer));
1245             break;
1246         case Layer::GroupLayerType:
1247             command = applyGroupLayerValueTo(id, val, static_cast<GroupLayer*>(layer));
1248             break;
1249         }
1250         break;
1251     }
1252 
1253     return command;
1254 }
1255 
applyTileLayerValueTo(PropertyId id,const QVariant & val,TileLayer * tileLayer)1256 QUndoCommand *PropertyBrowser::applyTileLayerValueTo(PropertyId id, const QVariant &val, TileLayer *tileLayer)
1257 {
1258     Q_UNUSED(id)
1259     Q_UNUSED(val)
1260     Q_UNUSED(tileLayer)
1261 
1262     return nullptr;
1263 }
1264 
applyObjectGroupValueTo(PropertyId id,const QVariant & val,ObjectGroup * objectGroup)1265 QUndoCommand *PropertyBrowser::applyObjectGroupValueTo(PropertyId id, const QVariant &val, ObjectGroup *objectGroup)
1266 {
1267     QUndoCommand *command = nullptr;
1268 
1269     switch (id) {
1270     case ColorProperty: {
1271         const QColor color = val.value<QColor>();
1272         command = new ChangeObjectGroupProperties(mMapDocument,
1273                                                   objectGroup,
1274                                                   color,
1275                                                   objectGroup->drawOrder());
1276         break;
1277     }
1278     case DrawOrderProperty: {
1279         ObjectGroup::DrawOrder drawOrder = static_cast<ObjectGroup::DrawOrder>(val.toInt());
1280         command = new ChangeObjectGroupProperties(mMapDocument,
1281                                                   objectGroup,
1282                                                   objectGroup->color(),
1283                                                   drawOrder);
1284         break;
1285     }
1286     default:
1287         break;
1288     }
1289 
1290     return command;
1291 }
1292 
applyImageLayerValueTo(PropertyId id,const QVariant & val,ImageLayer * imageLayer)1293 QUndoCommand *PropertyBrowser::applyImageLayerValueTo(PropertyId id, const QVariant &val, ImageLayer *imageLayer)
1294 {
1295     QUndoCommand *command = nullptr;
1296 
1297     switch (id) {
1298     case ImageSourceProperty: {
1299         const FilePath imageSource = val.value<FilePath>();
1300         const QColor &color = imageLayer->transparentColor();
1301         command = new ChangeImageLayerProperties(mMapDocument,
1302                                                  imageLayer,
1303                                                  color,
1304                                                  imageSource.url);
1305         break;
1306     }
1307     case ColorProperty: {
1308         const QColor color = val.value<QColor>();
1309         const QUrl &imageSource = imageLayer->imageSource();
1310         command = new ChangeImageLayerProperties(mMapDocument,
1311                                                  imageLayer,
1312                                                  color,
1313                                                  imageSource);
1314         break;
1315     }
1316     default:
1317         break;
1318     }
1319 
1320     return command;
1321 }
1322 
applyGroupLayerValueTo(PropertyId id,const QVariant & val,GroupLayer * groupLayer)1323 QUndoCommand *PropertyBrowser::applyGroupLayerValueTo(PropertyId id, const QVariant &val, GroupLayer *groupLayer)
1324 {
1325     Q_UNUSED(id)
1326     Q_UNUSED(val)
1327     Q_UNUSED(groupLayer)
1328 
1329     return nullptr;
1330 }
1331 
applyTilesetValue(PropertyId id,const QVariant & val)1332 void PropertyBrowser::applyTilesetValue(PropertyId id, const QVariant &val)
1333 {
1334     Tileset *tileset = static_cast<Tileset*>(mObject);
1335     QUndoStack *undoStack = mDocument->undoStack();
1336 
1337     switch (id) {
1338     case FileNameProperty: {
1339         FilePath filePath = val.value<FilePath>();
1340         QString error;
1341         SharedTileset newTileset = TilesetManager::instance()->loadTileset(filePath.url.toLocalFile(), &error);
1342         if (!newTileset) {
1343             QMessageBox::critical(window(), tr("Error Reading Tileset"), error);
1344             return;
1345         }
1346 
1347         int index = mMapDocument->map()->tilesets().indexOf(tileset->sharedPointer());
1348         if (index != -1)
1349             undoStack->push(new ReplaceTileset(mMapDocument, index, newTileset));
1350 
1351         break;
1352     }
1353     case NameProperty:
1354         Q_ASSERT(mTilesetDocument);
1355         undoStack->push(new RenameTileset(mTilesetDocument, val.toString()));
1356         break;
1357     case ObjectAlignmentProperty: {
1358         Q_ASSERT(mTilesetDocument);
1359         auto objectAlignment = static_cast<Alignment>(val.toInt());
1360         undoStack->push(new ChangeTilesetObjectAlignment(mTilesetDocument,
1361                                                          objectAlignment));
1362         break;
1363     }
1364     case TileOffsetProperty:
1365         Q_ASSERT(mTilesetDocument);
1366         undoStack->push(new ChangeTilesetTileOffset(mTilesetDocument,
1367                                                     val.toPoint()));
1368         break;
1369     case OrientationProperty: {
1370         Q_ASSERT(mTilesetDocument);
1371         auto orientation = static_cast<Tileset::Orientation>(val.toInt());
1372         undoStack->push(new ChangeTilesetOrientation(mTilesetDocument,
1373                                                      orientation));
1374         break;
1375     }
1376     case GridWidthProperty: {
1377         Q_ASSERT(mTilesetDocument);
1378         QSize gridSize = tileset->gridSize();
1379         gridSize.setWidth(val.toInt());
1380         undoStack->push(new ChangeTilesetGridSize(mTilesetDocument,
1381                                                   gridSize));
1382         break;
1383     }
1384     case GridHeightProperty: {
1385         Q_ASSERT(mTilesetDocument);
1386         QSize gridSize = tileset->gridSize();
1387         gridSize.setHeight(val.toInt());
1388         undoStack->push(new ChangeTilesetGridSize(mTilesetDocument,
1389                                                   gridSize));
1390         break;
1391     }
1392     case ColumnCountProperty:
1393         Q_ASSERT(mTilesetDocument);
1394         undoStack->push(new ChangeTilesetColumnCount(mTilesetDocument,
1395                                                      val.toInt()));
1396         break;
1397     case BackgroundColorProperty:
1398         Q_ASSERT(mTilesetDocument);
1399         undoStack->push(new ChangeTilesetBackgroundColor(mTilesetDocument,
1400                                                          val.value<QColor>()));
1401         break;
1402     case AllowFlipHorizontallyProperty:
1403     case AllowFlipVerticallyProperty:
1404     case AllowRotateProperty:
1405     case PreferUntransformedProperty: {
1406         Q_ASSERT(mTilesetDocument);
1407 
1408         Tileset::TransformationFlag flag = Tileset::NoTransformation;
1409         switch (id) {
1410         case AllowFlipHorizontallyProperty:
1411             flag = Tileset::AllowFlipHorizontally;
1412             break;
1413         case AllowFlipVerticallyProperty:
1414             flag = Tileset::AllowFlipVertically;
1415             break;
1416         case AllowRotateProperty:
1417             flag = Tileset::AllowRotate;
1418             break;
1419         case PreferUntransformedProperty:
1420             flag = Tileset::PreferUntransformed;
1421             break;
1422         default:
1423             return;
1424         }
1425 
1426         auto flags = tileset->transformationFlags();
1427 
1428 #if QT_VERSION >= 0x050700
1429         flags.setFlag(flag, val.toBool());
1430 #else
1431         if (val.toBool())
1432             flags |= flag;
1433         else
1434             flags &= ~flag;
1435 #endif
1436 
1437         undoStack->push(new ChangeTilesetTransformationFlags(mTilesetDocument, flags));
1438         break;
1439     }
1440     default:
1441         break;
1442     }
1443 }
1444 
applyTileValue(PropertyId id,const QVariant & val)1445 void PropertyBrowser::applyTileValue(PropertyId id, const QVariant &val)
1446 {
1447     Q_ASSERT(mTilesetDocument);
1448 
1449     Tile *tile = static_cast<Tile*>(mObject);
1450     QUndoStack *undoStack = mDocument->undoStack();
1451 
1452     switch (id) {
1453     case TypeProperty:
1454         undoStack->push(new ChangeTileType(mTilesetDocument,
1455                                            mTilesetDocument->selectedTiles(),
1456                                            val.toString()));
1457         break;
1458     case TileProbabilityProperty:
1459         undoStack->push(new ChangeTileProbability(mTilesetDocument,
1460                                                   mTilesetDocument->selectedTiles(),
1461                                                   val.toFloat()));
1462         break;
1463     case ImageSourceProperty: {
1464         const FilePath filePath = val.value<FilePath>();
1465         undoStack->push(new ChangeTileImageSource(mTilesetDocument,
1466                                                   tile, filePath.url));
1467         break;
1468     }
1469     default:
1470         break;
1471     }
1472 }
1473 
applyWangSetValue(PropertyId id,const QVariant & val)1474 void PropertyBrowser::applyWangSetValue(PropertyId id, const QVariant &val)
1475 {
1476     Q_ASSERT(mTilesetDocument);
1477 
1478     WangSet *wangSet = static_cast<WangSet*>(mObject);
1479 
1480     switch (id) {
1481     case NameProperty:
1482         mDocument->undoStack()->push(new RenameWangSet(mTilesetDocument,
1483                                                        wangSet,
1484                                                        val.toString()));
1485         break;
1486     case WangSetTypeProperty: {
1487         auto type = static_cast<WangSet::Type>(val.toInt());
1488         mDocument->undoStack()->push(new ChangeWangSetType(mTilesetDocument,
1489                                                            wangSet,
1490                                                            type));
1491         break;
1492     }
1493     case ColorCountProperty:
1494         mDocument->undoStack()->push(new ChangeWangSetColorCount(mTilesetDocument,
1495                                                                  wangSet,
1496                                                                  val.toInt()));
1497         break;
1498     default:
1499         break;
1500     }
1501 }
1502 
applyWangColorValue(PropertyId id,const QVariant & val)1503 void PropertyBrowser::applyWangColorValue(PropertyId id, const QVariant &val)
1504 {
1505     Q_ASSERT(mTilesetDocument);
1506 
1507     WangColor *wangColor = static_cast<WangColor*>(mObject);
1508 
1509     switch (id) {
1510     case NameProperty:
1511         mDocument->undoStack()->push(new ChangeWangColorName(mTilesetDocument,
1512                                                              wangColor,
1513                                                              val.toString()));
1514         break;
1515     case ColorProperty:
1516         mDocument->undoStack()->push(new ChangeWangColorColor(mTilesetDocument,
1517                                                               wangColor,
1518                                                               val.value<QColor>()));
1519         break;
1520     case WangColorProbabilityProperty:
1521         mDocument->undoStack()->push(new ChangeWangColorProbability(mTilesetDocument,
1522                                                                     wangColor,
1523                                                                     val.toDouble()));
1524         break;
1525     default:
1526         break;
1527     }
1528 }
1529 
1530 /**
1531  * @warning This function does not add the property to the view.
1532  */
createProperty(PropertyId id,int type,const QString & name)1533 QtVariantProperty *PropertyBrowser::createProperty(PropertyId id, int type,
1534                                                    const QString &name)
1535 {
1536     QtVariantProperty *property = mVariantManager->addProperty(type, name);
1537     if (!property) {
1538         // fall back to string property for unsupported property types
1539         property = mVariantManager->addProperty(QMetaType::QString, name);
1540     }
1541 
1542     if (type == QMetaType::Bool)
1543         property->setAttribute(QLatin1String("textVisible"), false);
1544     if (type == QMetaType::QString && id == CustomProperty)
1545         property->setAttribute(QLatin1String("multiline"), true);
1546     if (type == QMetaType::Double && id == CustomProperty)
1547         property->setAttribute(QLatin1String("decimals"), 9);
1548 
1549     mPropertyToId.insert(property, id);
1550 
1551     if (id != CustomProperty) {
1552         Q_ASSERT(!mIdToProperty.contains(id));
1553         mIdToProperty.insert(id, property);
1554     } else {
1555         Q_ASSERT(!mNameToProperty.contains(name));
1556         mNameToProperty.insert(name, property);
1557     }
1558 
1559     return property;
1560 }
1561 
addProperty(PropertyId id,int type,const QString & name,QtProperty * parent)1562 QtVariantProperty *PropertyBrowser::addProperty(PropertyId id, int type,
1563                                                 const QString &name,
1564                                                 QtProperty *parent)
1565 {
1566     QtVariantProperty *property = createProperty(id, type, name);
1567 
1568     parent->addSubProperty(property);
1569 
1570     if (id == CustomProperty) {
1571         // Collapse custom color properties, to save space
1572         if (type == QMetaType::QColor)
1573             setExpanded(items(property).constFirst(), false);
1574 
1575         if (mObject->isPartOfTileset())
1576             property->setEnabled(mTilesetDocument);
1577     }
1578 
1579     return property;
1580 }
1581 
createCustomProperty(const QString & name,const QVariant & value)1582 QtVariantProperty *PropertyBrowser::createCustomProperty(const QString &name, const QVariant &value)
1583 {
1584     // Determine the property preceding the new property, if any
1585     const QList<QtProperty *> properties = mCustomPropertiesGroup->subProperties();
1586     QtProperty *precedingProperty = nullptr;
1587     for (int i = 0; i < properties.size(); ++i) {
1588         if (properties.at(i)->propertyName() < name)
1589             precedingProperty = properties.at(i);
1590         else
1591             break;
1592     }
1593 
1594     QScopedValueRollback<bool> updating(mUpdating, true);
1595     QtVariantProperty *property = createProperty(CustomProperty, value.userType(), name);
1596     property->setValue(value);
1597     mCustomPropertiesGroup->insertSubProperty(property, precedingProperty);
1598 
1599     // Collapse custom color properties, to save space
1600     if (value.userType() == QMetaType::QColor)
1601         setExpanded(items(property).constFirst(), false);
1602 
1603     return property;
1604 }
1605 
deleteCustomProperty(QtVariantProperty * property)1606 void PropertyBrowser::deleteCustomProperty(QtVariantProperty *property)
1607 {
1608     Q_ASSERT(mNameToProperty.contains(property->propertyName()));
1609     mNameToProperty.remove(property->propertyName());
1610     delete property;
1611 }
1612 
setCustomPropertyValue(QtVariantProperty * property,const QVariant & value)1613 void PropertyBrowser::setCustomPropertyValue(QtVariantProperty *property,
1614                                              const QVariant &value)
1615 {
1616     const QVariant displayValue = toDisplayValue(value);
1617 
1618     if (displayValue.userType() != property->valueType()) {
1619         // Re-creating the property is necessary to change its type
1620         const QString name = property->propertyName();
1621         const bool wasCurrent = currentItem() && currentItem()->property() == property;
1622 
1623         deleteCustomProperty(property);
1624         property = createCustomProperty(name, displayValue);
1625         updateCustomPropertyColor(name);
1626 
1627         if (wasCurrent)
1628             setCurrentItem(items(property).constFirst());
1629     } else {
1630         QScopedValueRollback<bool> updating(mUpdating, true);
1631         property->setValue(displayValue);
1632     }
1633 }
1634 
addProperties()1635 void PropertyBrowser::addProperties()
1636 {
1637     if (!mObject)
1638         return;
1639 
1640     QScopedValueRollback<bool> updating(mUpdating, true);
1641     SetFixedResizeMode resizeMode(this);
1642 
1643     // Add the built-in properties for each object type
1644     switch (mObject->typeId()) {
1645     case Object::MapType:               addMapProperties(); break;
1646     case Object::MapObjectType:         addMapObjectProperties(); break;
1647     case Object::LayerType:
1648         switch (static_cast<Layer*>(mObject)->layerType()) {
1649         case Layer::TileLayerType:      addTileLayerProperties();   break;
1650         case Layer::ObjectGroupType:    addObjectGroupProperties(); break;
1651         case Layer::ImageLayerType:     addImageLayerProperties();  break;
1652         case Layer::GroupLayerType:     addGroupLayerProperties();  break;
1653         }
1654         break;
1655     case Object::TilesetType:           addTilesetProperties(); break;
1656     case Object::TileType:              addTileProperties(); break;
1657     case Object::WangSetType:           addWangSetProperties(); break;
1658     case Object::WangColorType:         addWangColorProperties(); break;
1659     case Object::ObjectTemplateType:    break;
1660     }
1661 
1662     // Make sure the color and font properties are collapsed, to save space
1663     if (QtProperty *colorProperty = mIdToProperty.value(ColorProperty))
1664         setExpanded(items(colorProperty).constFirst(), false);
1665     if (QtProperty *colorProperty = mIdToProperty.value(BackgroundColorProperty))
1666         setExpanded(items(colorProperty).constFirst(), false);
1667     if (QtProperty *fontProperty = mIdToProperty.value(FontProperty))
1668         setExpanded(items(fontProperty).constFirst(), false);
1669     if (QtProperty *tintColorProperty = mIdToProperty.value(TintColorProperty))
1670         setExpanded(items(tintColorProperty).constFirst(), false);
1671 
1672     // Add a node for the custom properties
1673     mCustomPropertiesGroup = mGroupManager->addProperty(tr("Custom Properties"));
1674     addProperty(mCustomPropertiesGroup);
1675 
1676     updateProperties();
1677     updateCustomProperties();
1678 }
1679 
removeProperties()1680 void PropertyBrowser::removeProperties()
1681 {
1682     SetFixedResizeMode resizeMode(this);
1683 
1684     mVariantManager->clear();
1685     mGroupManager->clear();
1686     mPropertyToId.clear();
1687     mIdToProperty.clear();
1688     mNameToProperty.clear();
1689     mCustomPropertiesGroup = nullptr;
1690 }
1691 
updateProperties()1692 void PropertyBrowser::updateProperties()
1693 {
1694     Q_ASSERT(mObject);
1695 
1696     QScopedValueRollback<bool> updating(mUpdating, true);
1697 
1698     switch (mObject->typeId()) {
1699     case Object::MapType: {
1700         const Map *map = static_cast<const Map*>(mObject);
1701         mIdToProperty[WidthProperty]->setValue(map->width());
1702         mIdToProperty[HeightProperty]->setValue(map->height());
1703         mIdToProperty[TileWidthProperty]->setValue(map->tileWidth());
1704         mIdToProperty[TileHeightProperty]->setValue(map->tileHeight());
1705         mIdToProperty[InfiniteProperty]->setValue(map->infinite());
1706         mIdToProperty[OrientationProperty]->setValue(map->orientation() - 1);
1707         mIdToProperty[HexSideLengthProperty]->setValue(map->hexSideLength());
1708         mIdToProperty[StaggerAxisProperty]->setValue(map->staggerAxis());
1709         mIdToProperty[StaggerIndexProperty]->setValue(map->staggerIndex());
1710         mIdToProperty[LayerFormatProperty]->setValue(mLayerFormatValues.indexOf(map->layerDataFormat()));
1711         mIdToProperty[CompressionLevelProperty]->setValue(map->compressionLevel());
1712         mIdToProperty[RenderOrderProperty]->setValue(map->renderOrder());
1713         mIdToProperty[BackgroundColorProperty]->setValue(map->backgroundColor());
1714         mIdToProperty[ChunkWidthProperty]->setValue(map->chunkSize().width());
1715         mIdToProperty[ChunkHeightProperty]->setValue(map->chunkSize().height());
1716         break;
1717     }
1718     case Object::MapObjectType: {
1719         const MapObject *mapObject = static_cast<const MapObject*>(mObject);
1720         const int flags = mapObjectFlags(mapObject);
1721 
1722         if (mMapObjectFlags != flags) {
1723             removeProperties();
1724             addProperties();
1725             return;
1726         }
1727 
1728         const QString &type = mapObject->effectiveType();
1729         const auto typeColorGroup = mapObject->type().isEmpty() ? QPalette::Disabled
1730                                                                 : QPalette::Active;
1731 
1732         FilePath templateFilePath;
1733         if (auto objectTemplate = mapObject->objectTemplate())
1734             templateFilePath.url = QUrl::fromLocalFile(objectTemplate->fileName());
1735 
1736         mIdToProperty[IdProperty]->setValue(mapObject->id());
1737         mIdToProperty[TemplateProperty]->setValue(QVariant::fromValue(templateFilePath));
1738         mIdToProperty[NameProperty]->setValue(mapObject->name());
1739         mIdToProperty[TypeProperty]->setValue(type);
1740         mIdToProperty[TypeProperty]->setValueColor(palette().color(typeColorGroup, QPalette::WindowText));
1741         if (auto visibleProperty = mIdToProperty[VisibleProperty])
1742             visibleProperty->setValue(mapObject->isVisible());
1743         mIdToProperty[XProperty]->setValue(mapObject->x());
1744         mIdToProperty[YProperty]->setValue(mapObject->y());
1745 
1746         if (flags & ObjectHasDimensions) {
1747             mIdToProperty[WidthProperty]->setValue(mapObject->width());
1748             mIdToProperty[HeightProperty]->setValue(mapObject->height());
1749         }
1750 
1751         mIdToProperty[RotationProperty]->setValue(mapObject->rotation());
1752 
1753         if (flags & ObjectHasTile) {
1754             int flippingFlags = 0;
1755             if (mapObject->cell().flippedHorizontally())
1756                 flippingFlags |= 1;
1757             if (mapObject->cell().flippedVertically())
1758                 flippingFlags |= 2;
1759             mIdToProperty[FlippingProperty]->setValue(flippingFlags);
1760         }
1761 
1762         if (flags & ObjectIsText) {
1763             const auto& textData = mapObject->textData();
1764             mIdToProperty[TextProperty]->setValue(textData.text);
1765             mIdToProperty[FontProperty]->setValue(textData.font);
1766             mIdToProperty[TextAlignmentProperty]->setValue(QVariant::fromValue(textData.alignment));
1767             mIdToProperty[WordWrapProperty]->setValue(textData.wordWrap);
1768             mIdToProperty[ColorProperty]->setValue(textData.color);
1769         }
1770         break;
1771     }
1772     case Object::LayerType: {
1773         const Layer *layer = static_cast<const Layer*>(mObject);
1774 
1775         mIdToProperty[IdProperty]->setValue(layer->id());
1776         mIdToProperty[NameProperty]->setValue(layer->name());
1777         mIdToProperty[VisibleProperty]->setValue(layer->isVisible());
1778         mIdToProperty[LockedProperty]->setValue(layer->isLocked());
1779         mIdToProperty[OpacityProperty]->setValue(layer->opacity());
1780         mIdToProperty[TintColorProperty]->setValue(layer->tintColor());
1781         mIdToProperty[OffsetXProperty]->setValue(layer->offset().x());
1782         mIdToProperty[OffsetYProperty]->setValue(layer->offset().y());
1783         mIdToProperty[ParallaxFactorProperty]->setValue(layer->parallaxFactor());
1784 
1785         switch (layer->layerType()) {
1786         case Layer::TileLayerType:
1787             break;
1788         case Layer::ObjectGroupType: {
1789             const ObjectGroup *objectGroup = static_cast<const ObjectGroup*>(layer);
1790             const QColor color = objectGroup->color();
1791             mIdToProperty[ColorProperty]->setValue(color);
1792             mIdToProperty[DrawOrderProperty]->setValue(objectGroup->drawOrder());
1793             break;
1794         }
1795         case Layer::ImageLayerType: {
1796             const ImageLayer *imageLayer = static_cast<const ImageLayer*>(layer);
1797             mIdToProperty[ImageSourceProperty]->setValue(QVariant::fromValue(FilePath { imageLayer->imageSource() }));
1798             mIdToProperty[ColorProperty]->setValue(imageLayer->transparentColor());
1799             break;
1800         }
1801         case Layer::GroupLayerType:
1802             break;
1803         }
1804         break;
1805     }
1806     case Object::TilesetType: {
1807         Tileset *tileset = static_cast<Tileset*>(mObject);
1808 
1809         if (QtVariantProperty *fileNameProperty = mIdToProperty.value(FileNameProperty))
1810             fileNameProperty->setValue(QVariant::fromValue(FilePath { QUrl::fromLocalFile(tileset->fileName()) }));
1811 
1812         mIdToProperty[BackgroundColorProperty]->setValue(tileset->backgroundColor());
1813 
1814         mIdToProperty[NameProperty]->setValue(tileset->name());
1815         mIdToProperty[ObjectAlignmentProperty]->setValue(tileset->objectAlignment());
1816         mIdToProperty[TileOffsetProperty]->setValue(tileset->tileOffset());
1817         mIdToProperty[OrientationProperty]->setValue(tileset->orientation());
1818         mIdToProperty[GridWidthProperty]->setValue(tileset->gridSize().width());
1819         mIdToProperty[GridHeightProperty]->setValue(tileset->gridSize().height());
1820         mIdToProperty[ColumnCountProperty]->setValue(tileset->columnCount());
1821         mIdToProperty[ColumnCountProperty]->setEnabled(mTilesetDocument && tileset->isCollection());
1822 
1823         if (!tileset->isCollection()) {
1824             mIdToProperty[TilesetImageParametersProperty]->setValue(QVariant::fromValue(mTilesetDocument));
1825             mIdToProperty[ImageSourceProperty]->setValue(tileset->imageSource().toString(QUrl::PreferLocalFile));
1826             mIdToProperty[TileWidthProperty]->setValue(tileset->tileWidth());
1827             mIdToProperty[TileHeightProperty]->setValue(tileset->tileHeight());
1828             mIdToProperty[MarginProperty]->setValue(tileset->margin());
1829             mIdToProperty[SpacingProperty]->setValue(tileset->tileSpacing());
1830             mIdToProperty[ColorProperty]->setValue(tileset->transparentColor());
1831         }
1832 
1833         const auto flags = tileset->transformationFlags();
1834         mIdToProperty[AllowFlipHorizontallyProperty]->setValue(flags.testFlag(Tileset::AllowFlipHorizontally));
1835         mIdToProperty[AllowFlipVerticallyProperty]->setValue(flags.testFlag(Tileset::AllowFlipVertically));
1836         mIdToProperty[AllowRotateProperty]->setValue(flags.testFlag(Tileset::AllowRotate));
1837         mIdToProperty[PreferUntransformedProperty]->setValue(flags.testFlag(Tileset::PreferUntransformed));
1838         break;
1839     }
1840     case Object::TileType: {
1841         const Tile *tile = static_cast<const Tile*>(mObject);
1842         const QSize tileSize = tile->size();
1843         mIdToProperty[IdProperty]->setValue(tile->id());
1844         mIdToProperty[TypeProperty]->setValue(tile->type());
1845         mIdToProperty[WidthProperty]->setValue(tileSize.width());
1846         mIdToProperty[HeightProperty]->setValue(tileSize.height());
1847         mIdToProperty[TileProbabilityProperty]->setValue(tile->probability());
1848         if (QtVariantProperty *imageSourceProperty = mIdToProperty.value(ImageSourceProperty))
1849             imageSourceProperty->setValue(QVariant::fromValue(FilePath { tile->imageSource() }));
1850         break;
1851     }
1852     case Object::WangSetType: {
1853         const WangSet *wangSet = static_cast<const WangSet*>(mObject);
1854         mIdToProperty[NameProperty]->setValue(wangSet->name());
1855         mIdToProperty[WangSetTypeProperty]->setValue(wangSet->type());
1856         mIdToProperty[ColorCountProperty]->setValue(wangSet->colorCount());
1857         break;
1858     }
1859     case Object::WangColorType: {
1860         const WangColor *wangColor = static_cast<const WangColor*>(mObject);
1861         mIdToProperty[NameProperty]->setValue(wangColor->name());
1862         mIdToProperty[ColorProperty]->setValue(wangColor->color());
1863         mIdToProperty[WangColorProbabilityProperty]->setValue(wangColor->probability());
1864         break;
1865     }
1866     case Object::ObjectTemplateType:
1867         break;
1868     }
1869 }
1870 
updateCustomProperties()1871 void PropertyBrowser::updateCustomProperties()
1872 {
1873     if (!mObject)
1874         return;
1875 
1876     QScopedValueRollback<bool> updating(mUpdating, true);
1877     SetFixedResizeMode resizeMode(this);
1878 
1879     qDeleteAll(mNameToProperty);
1880     mNameToProperty.clear();
1881 
1882     mCombinedProperties = mObject->properties();
1883     // Add properties from selected objects which mObject does not contain to mCombinedProperties.
1884     const auto currentObjects = mDocument->currentObjects();
1885     for (Object *obj : currentObjects) {
1886         if (obj == mObject)
1887             continue;
1888 
1889         QMapIterator<QString,QVariant> it(obj->properties());
1890 
1891         while (it.hasNext()) {
1892             it.next();
1893             if (!mCombinedProperties.contains(it.key()))
1894                 mCombinedProperties.insert(it.key(), QString());
1895         }
1896     }
1897 
1898     QString objectType;
1899 
1900     switch (mObject->typeId()) {
1901     case Object::TileType:
1902         objectType = static_cast<Tile*>(mObject)->type();
1903         break;
1904     case Object::MapObjectType: {
1905         auto mapObject = static_cast<MapObject*>(mObject);
1906         objectType = mapObject->type();
1907 
1908         // Inherit properties from the template
1909         if (const MapObject *templateObject = mapObject->templateObject()) {
1910             QMapIterator<QString,QVariant> it(templateObject->properties());
1911             while (it.hasNext()) {
1912                 it.next();
1913                 if (!mCombinedProperties.contains(it.key()))
1914                     mCombinedProperties.insert(it.key(), it.value());
1915             }
1916         }
1917 
1918         if (Tile *tile = mapObject->cell().tile()) {
1919             if (objectType.isEmpty())
1920                 objectType = tile->type();
1921 
1922             // Inherit properties from the tile
1923             QMapIterator<QString,QVariant> it(tile->properties());
1924             while (it.hasNext()) {
1925                 it.next();
1926                 if (!mCombinedProperties.contains(it.key()))
1927                     mCombinedProperties.insert(it.key(), it.value());
1928             }
1929         }
1930         break;
1931     }
1932     case Object::LayerType:
1933     case Object::MapType:
1934     case Object::TilesetType:
1935     case Object::WangSetType:
1936     case Object::WangColorType:
1937     case Object::ObjectTemplateType:
1938         break;
1939     }
1940 
1941     if (!objectType.isEmpty()) {
1942         // Inherit properties from the object type
1943         for (const ObjectType &type : Object::objectTypes()) {
1944             if (type.name == objectType) {
1945                 QMapIterator<QString,QVariant> it(type.defaultProperties);
1946                 while (it.hasNext()) {
1947                     it.next();
1948                     if (!mCombinedProperties.contains(it.key()))
1949                         mCombinedProperties.insert(it.key(), it.value());
1950                 }
1951             }
1952         }
1953     }
1954 
1955     QMapIterator<QString,QVariant> it(mCombinedProperties);
1956 
1957     while (it.hasNext()) {
1958         it.next();
1959 
1960         const QVariant displayValue = toDisplayValue(it.value());
1961 
1962         QtVariantProperty *property = addProperty(CustomProperty,
1963                                                   displayValue.userType(),
1964                                                   it.key(),
1965                                                   mCustomPropertiesGroup);
1966         property->setValue(displayValue);
1967         updateCustomPropertyColor(it.key());
1968     }
1969 }
1970 
1971 // If there are other objects selected check if their properties are equal. If not give them a gray color.
updateCustomPropertyColor(const QString & name)1972 void PropertyBrowser::updateCustomPropertyColor(const QString &name)
1973 {
1974     QtVariantProperty *property = mNameToProperty.value(name);
1975     if (!property)
1976         return;
1977     if (!property->isEnabled())
1978         return;
1979 
1980     QString propertyName = property->propertyName();
1981     QString propertyValue = property->valueText();
1982 
1983     const auto &objects = mDocument->currentObjects();
1984 
1985     QColor textColor = palette().color(QPalette::Active, QPalette::WindowText);
1986     QColor disabledTextColor = palette().color(QPalette::Disabled, QPalette::WindowText);
1987 
1988     // If one of the objects doesn't have this property then gray out the name and value.
1989     for (Object *obj : objects) {
1990         if (!obj->hasProperty(propertyName)) {
1991             property->setNameColor(disabledTextColor);
1992             property->setValueColor(disabledTextColor);
1993             return;
1994         }
1995     }
1996 
1997     // If one of the objects doesn't have the same property value then gray out the value.
1998     for (Object *obj : objects) {
1999         if (obj == mObject)
2000             continue;
2001         if (obj->property(propertyName) != propertyValue) {
2002             property->setNameColor(textColor);
2003             property->setValueColor(disabledTextColor);
2004             return;
2005         }
2006     }
2007 
2008     property->setNameColor(textColor);
2009     property->setValueColor(textColor);
2010 }
2011 
toDisplayValue(const QVariant & value) const2012 QVariant PropertyBrowser::toDisplayValue(const QVariant &value) const
2013 {
2014     if (value.userType() == objectRefTypeId())
2015         return QVariant::fromValue(DisplayObjectRef { value.value<ObjectRef>(), mMapDocument });
2016 
2017     return value;
2018 }
2019 
fromDisplayValue(const QVariant & value) const2020 QVariant PropertyBrowser::fromDisplayValue(const QVariant &value) const
2021 {
2022     if (value.userType() == VariantPropertyManager::displayObjectRefTypeId())
2023         return QVariant::fromValue(value.value<DisplayObjectRef>().ref);
2024 
2025     return value;
2026 }
2027 
retranslateUi()2028 void PropertyBrowser::retranslateUi()
2029 {
2030     mStaggerAxisNames.clear();
2031     mStaggerAxisNames.append(tr("X"));
2032     mStaggerAxisNames.append(tr("Y"));
2033 
2034     mStaggerIndexNames.clear();
2035     mStaggerIndexNames.append(tr("Odd"));
2036     mStaggerIndexNames.append(tr("Even"));
2037 
2038     mOrientationNames.clear();
2039     mOrientationNames.append(QCoreApplication::translate("Tiled::NewMapDialog", "Orthogonal"));
2040     mOrientationNames.append(QCoreApplication::translate("Tiled::NewMapDialog", "Isometric"));
2041     mOrientationNames.append(QCoreApplication::translate("Tiled::NewMapDialog", "Isometric (Staggered)"));
2042     mOrientationNames.append(QCoreApplication::translate("Tiled::NewMapDialog", "Hexagonal (Staggered)"));
2043 
2044     mTilesetOrientationNames.clear();
2045     mTilesetOrientationNames.append(mOrientationNames.at(0));
2046     mTilesetOrientationNames.append(mOrientationNames.at(1));
2047 
2048     mLayerFormatNames.clear();
2049     mLayerFormatValues.clear();
2050 
2051     mLayerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "XML (deprecated)"));
2052     mLayerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (uncompressed)"));
2053     mLayerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (gzip compressed)"));
2054     mLayerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (zlib compressed)"));
2055 #ifdef TILED_ZSTD_SUPPORT
2056     mLayerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "Base64 (Zstandard compressed)"));
2057 #endif
2058     mLayerFormatNames.append(QCoreApplication::translate("PreferencesDialog", "CSV"));
2059 
2060     mLayerFormatValues.append(Map::XML);
2061     mLayerFormatValues.append(Map::Base64);
2062     mLayerFormatValues.append(Map::Base64Gzip);
2063     mLayerFormatValues.append(Map::Base64Zlib);
2064 #ifdef TILED_ZSTD_SUPPORT
2065     mLayerFormatValues.append(Map::Base64Zstandard);
2066 #endif
2067     mLayerFormatValues.append(Map::CSV);
2068 
2069     mRenderOrderNames.clear();
2070     mRenderOrderNames.append(QCoreApplication::translate("PreferencesDialog", "Right Down"));
2071     mRenderOrderNames.append(QCoreApplication::translate("PreferencesDialog", "Right Up"));
2072     mRenderOrderNames.append(QCoreApplication::translate("PreferencesDialog", "Left Down"));
2073     mRenderOrderNames.append(QCoreApplication::translate("PreferencesDialog", "Left Up"));
2074 
2075     mAlignmentNames.clear();
2076     mAlignmentNames.append(tr("Unspecified"));
2077     mAlignmentNames.append(tr("Top Left"));
2078     mAlignmentNames.append(tr("Top"));
2079     mAlignmentNames.append(tr("Top Right"));
2080     mAlignmentNames.append(tr("Left"));
2081     mAlignmentNames.append(tr("Center"));
2082     mAlignmentNames.append(tr("Right"));
2083     mAlignmentNames.append(tr("Bottom Left"));
2084     mAlignmentNames.append(tr("Bottom"));
2085     mAlignmentNames.append(tr("Bottom Right"));
2086 
2087     mFlippingFlagNames.clear();
2088     mFlippingFlagNames.append(tr("Horizontal"));
2089     mFlippingFlagNames.append(tr("Vertical"));
2090 
2091     mDrawOrderNames.clear();
2092     mDrawOrderNames.append(tr("Top Down"));
2093     mDrawOrderNames.append(tr("Manual"));
2094 
2095     mWangSetTypeNames.clear();
2096     mWangSetTypeNames.append(tr("Corner"));
2097     mWangSetTypeNames.append(tr("Edge"));
2098     mWangSetTypeNames.append(tr("Mixed"));
2099 
2100     removeProperties();
2101     addProperties();
2102 }
2103 
2104 } // namespace Tiled
2105 
2106 #include "moc_propertybrowser.cpp"
2107