From 7ca8cc95f3b0bdbe9e0476e8ef9a2e63487349c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 10 Jul 2024 12:46:50 +0200 Subject: [PATCH 1/8] Initial work on supporting list property types * Added 'list' as option to the Add Property dialog, for adding a QVariantList. * Adjusted the CustomPropertiesHelper to create sub-properties that display the values in the list (maybe should rather be done by the VariantPropertyManager). * Added support for saving and loading list properties to TMX and JSON formats. --- src/libtiled/mapreader.cpp | 17 ++++- src/libtiled/maptovariantconverter.cpp | 22 ++++++- src/libtiled/mapwriter.cpp | 30 ++++++--- src/libtiled/properties.cpp | 21 ++----- src/libtiled/properties.h | 6 +- src/libtiled/varianttomapconverter.cpp | 21 ++++++- src/tiled/addpropertydialog.cpp | 1 + src/tiled/custompropertieshelper.cpp | 87 +++++++++++++++++++++++++- src/tiled/custompropertieshelper.h | 3 + 9 files changed, 178 insertions(+), 30 deletions(-) diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 820eed8b43..8a5a33afbc 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -137,6 +137,7 @@ class MapReaderPrivate Properties readProperties(); void readProperty(Properties *properties, const ExportContext &context); + QVariant readPropertyValue(const ExportContext &context); MapReader *p; @@ -1448,6 +1449,13 @@ void MapReaderPrivate::readProperty(Properties *properties, const ExportContext const QXmlStreamAttributes atts = xml.attributes(); QString propertyName = atts.value(QLatin1String("name")).toString(); + properties->insert(propertyName, readPropertyValue(context)); +} + +QVariant MapReaderPrivate::readPropertyValue(const ExportContext &context) +{ + const QXmlStreamAttributes atts = xml.attributes(); + ExportValue exportValue; exportValue.typeName = atts.value(QLatin1String("type")).toString(); exportValue.propertyTypeName = atts.value(QLatin1String("propertytype")).toString(); @@ -1455,6 +1463,8 @@ void MapReaderPrivate::readProperty(Properties *properties, const ExportContext const QString propertyValue = atts.value(QLatin1String("value")).toString(); exportValue.value = propertyValue; + QVariantList values; + while (xml.readNext() != QXmlStreamReader::Invalid) { if (xml.isEndElement()) { break; @@ -1464,12 +1474,17 @@ void MapReaderPrivate::readProperty(Properties *properties, const ExportContext } else if (xml.isStartElement()) { if (xml.name() == QLatin1String("properties")) exportValue.value = readProperties(); + else if (xml.name() == QLatin1String("value")) + values.append(readPropertyValue(context)); else readUnknownElement(); } } - properties->insert(propertyName, context.toPropertyValue(exportValue)); + if (exportValue.typeName == QLatin1String("list")) + exportValue.value = values; + + return context.toPropertyValue(exportValue); } diff --git a/src/libtiled/maptovariantconverter.cpp b/src/libtiled/maptovariantconverter.cpp index 93e7fb882f..c3b8943644 100644 --- a/src/libtiled/maptovariantconverter.cpp +++ b/src/libtiled/maptovariantconverter.cpp @@ -819,12 +819,30 @@ void MapToVariantConverter::addProperties(QVariantMap &variantMap, QVariantMap propertyVariantMap; propertyVariantMap[QStringLiteral("name")] = it.key(); - propertyVariantMap[QStringLiteral("value")] = exportValue.value; propertyVariantMap[QStringLiteral("type")] = exportValue.typeName; - if (!exportValue.propertyTypeName.isEmpty()) propertyVariantMap[QStringLiteral("propertytype")] = exportValue.propertyTypeName; + if (exportValue.typeName == QLatin1String("list")) { + const QVariantList values = it.value().toList(); + QVariantList valuesVariantList; + + // todo: this doesn't support lists of lists + for (const QVariant &value : values) { + QVariantMap valueVariantMap; + const auto exportValue = context.toExportValue(value); + valueVariantMap[QStringLiteral("value")] = exportValue.value; + valueVariantMap[QStringLiteral("type")] = exportValue.typeName; + if (!exportValue.propertyTypeName.isEmpty()) + valueVariantMap[QStringLiteral("propertytype")] = exportValue.propertyTypeName; + valuesVariantList << valueVariantMap; + } + + propertyVariantMap[QStringLiteral("value")] = valuesVariantList; + } else { + propertyVariantMap[QStringLiteral("value")] = exportValue.value; + } + propertiesVariantList << propertyVariantMap; } diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index d6c4f3265d..ce806b13cf 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -905,24 +905,40 @@ void MapWriterPrivate::writeProperties(QXmlStreamWriter &w, if (!exportValue.propertyTypeName.isEmpty()) w.writeAttribute(QStringLiteral("propertytype"), exportValue.propertyTypeName); - // For class property values, write out the original value, so that the - // propertytype attribute can also be written for their members where - // applicable. - if (exportValue.value.userType() == QMetaType::QVariantMap) { + switch (exportValue.value.userType()) { + case QMetaType::QVariantList: { + const auto values = exportValue.value.toList(); + for (const QVariant &v : values) { + const auto exportValue = context.toExportValue(v); + w.writeStartElement(QStringLiteral("value")); + if (exportValue.typeName != QLatin1String("string")) + w.writeAttribute(QStringLiteral("type"), exportValue.typeName); + if (!exportValue.propertyTypeName.isEmpty()) + w.writeAttribute(QStringLiteral("propertytype"), exportValue.propertyTypeName); + w.writeCharacters(exportValue.value.toString()); + w.writeEndElement(); + } + break; + } + case QMetaType::QVariantMap: + // Write out the original value, so that the propertytype attribute + // can also be written for their members where applicable. writeProperties(w, it.value().value().value.toMap()); - } else { + break; + default: const QString value = exportValue.value.toString(); if (value.contains(QLatin1Char('\n'))) w.writeCharacters(value); else w.writeAttribute(QStringLiteral("value"), value); + break; } - w.writeEndElement(); + w.writeEndElement(); // } - w.writeEndElement(); + w.writeEndElement(); // } void MapWriterPrivate::writeImage(QXmlStreamWriter &w, diff --git a/src/libtiled/properties.cpp b/src/libtiled/properties.cpp index cd05bfacca..3a4975c318 100644 --- a/src/libtiled/properties.cpp +++ b/src/libtiled/properties.cpp @@ -196,21 +196,6 @@ void aggregateProperties(AggregatedProperties &aggregated, const Properties &pro } } -int propertyValueId() -{ - return qMetaTypeId(); -} - -int filePathTypeId() -{ - return qMetaTypeId(); -} - -int objectRefTypeId() -{ - return qMetaTypeId(); -} - QString typeToName(int type) { // We can't handle the PropertyValue purely by its type ID, since we need to @@ -226,6 +211,8 @@ QString typeToName(int type) return QStringLiteral("color"); case QMetaType::QVariantMap: return QStringLiteral("class"); + case QMetaType::QVariantList: + return QStringLiteral("list"); default: if (type == filePathTypeId()) @@ -309,6 +296,7 @@ ExportValue ExportContext::toExportValue(const QVariant &value) const } else if (metaType == objectRefTypeId()) { exportValue.value = ObjectRef::toInt(value.value()); } else { + // Other values, including lists, do not need special handling here exportValue.value = value; } @@ -343,6 +331,9 @@ QVariant ExportContext::toPropertyValue(const QVariant &value, int metaType) con if (metaType == QMetaType::QVariantMap || metaType == propertyValueId()) return value; // should be covered by property type + if (metaType == QMetaType::QVariantList) + return value; // list elements should be converted individually + if (metaType == filePathTypeId()) { const QUrl url = toUrl(value.toString(), mPath); return QVariant::fromValue(FilePath { url }); diff --git a/src/libtiled/properties.h b/src/libtiled/properties.h index 404e7361f8..2604fff75e 100644 --- a/src/libtiled/properties.h +++ b/src/libtiled/properties.h @@ -185,9 +185,9 @@ TILEDSHARED_EXPORT void mergeProperties(Properties &target, const Properties &so TILEDSHARED_EXPORT QJsonArray propertiesToJson(const Properties &properties, const ExportContext &context = ExportContext()); TILEDSHARED_EXPORT Properties propertiesFromJson(const QJsonArray &json, const ExportContext &context = ExportContext()); -TILEDSHARED_EXPORT int propertyValueId(); -TILEDSHARED_EXPORT int filePathTypeId(); -TILEDSHARED_EXPORT int objectRefTypeId(); +constexpr int propertyValueId() { return qMetaTypeId(); } +constexpr int filePathTypeId() { return qMetaTypeId(); } +constexpr int objectRefTypeId() { return qMetaTypeId(); } TILEDSHARED_EXPORT QString typeToName(int type); TILEDSHARED_EXPORT QString typeName(const QVariant &value); diff --git a/src/libtiled/varianttomapconverter.cpp b/src/libtiled/varianttomapconverter.cpp index 2c2b9bf3b5..f81ccd9b2c 100644 --- a/src/libtiled/varianttomapconverter.cpp +++ b/src/libtiled/varianttomapconverter.cpp @@ -190,7 +190,26 @@ Properties VariantToMapConverter::toProperties(const QVariant &propertiesVariant exportValue.typeName = propertyVariantMap[QStringLiteral("type")].toString(); exportValue.propertyTypeName = propertyVariantMap[QStringLiteral("propertytype")].toString(); - properties[propertyName] = context.toPropertyValue(exportValue); + auto &value = properties[propertyName]; + + if (exportValue.typeName == QLatin1String("list")) { + const QVariantList values = exportValue.value.toList(); + QVariantList convertedList; + convertedList.reserve(values.size()); + for (const QVariant &value : values) { + const QVariantMap valueVariantMap = value.toMap(); + ExportValue itemValue; + itemValue.value = valueVariantMap[QStringLiteral("value")]; + itemValue.typeName = valueVariantMap[QStringLiteral("type")].toString(); + itemValue.propertyTypeName = valueVariantMap[QStringLiteral("propertytype")].toString(); + + // todo: this doesn't support lists of lists + convertedList.append(context.toPropertyValue(itemValue)); + } + value = convertedList; + } else { + value = context.toPropertyValue(exportValue); + } } return properties; diff --git a/src/tiled/addpropertydialog.cpp b/src/tiled/addpropertydialog.cpp index 230ba545ad..1e00ac3621 100644 --- a/src/tiled/addpropertydialog.cpp +++ b/src/tiled/addpropertydialog.cpp @@ -67,6 +67,7 @@ void AddPropertyDialog::initialize(const Tiled::ClassPropertyType *parentClassTy mUi->typeBox->addItem(plain, typeToName(QMetaType::Int), 0); mUi->typeBox->addItem(plain, typeToName(objectRefTypeId()), QVariant::fromValue(ObjectRef())); mUi->typeBox->addItem(plain, typeToName(QMetaType::QString), QString()); + mUi->typeBox->addItem(plain, typeToName(QMetaType::QVariantList), QVariantList()); for (const auto propertyType : Object::propertyTypes()) { // Avoid suggesting the creation of circular dependencies between types diff --git a/src/tiled/custompropertieshelper.cpp b/src/tiled/custompropertieshelper.cpp index a64dd7ed3a..6aa24f03fe 100644 --- a/src/tiled/custompropertieshelper.cpp +++ b/src/tiled/custompropertieshelper.cpp @@ -31,6 +31,13 @@ namespace Tiled { +/** + * Constructs a custom properties helper that manages properties in the given + * \a propertyBrowser. + * + * Uses its own VariantPropertyManager to create the properties and + * instantiates a VariantEditorFactory which creates the editor widgets. + */ CustomPropertiesHelper::CustomPropertiesHelper(QtAbstractPropertyBrowser *propertyBrowser, QObject *parent) : QObject(parent) @@ -56,6 +63,14 @@ CustomPropertiesHelper::~CustomPropertiesHelper() mPropertyBrowser->unsetFactoryForManager(mPropertyManager); } +/** + * Creates a top-level property with the given \a name and \a value. + * + * The property is not added to the property browser. It should be added either + * directly or to a suitable group property. + * + * The name of the property needs to be unique. + */ QtVariantProperty *CustomPropertiesHelper::createProperty(const QString &name, const QVariant &value) { @@ -71,6 +86,17 @@ QtVariantProperty *CustomPropertiesHelper::createProperty(const QString &name, return property; } +/** + * Implementation of property creation. Used by createProperty for creating + * top-level properties and by setPropertyAttributes for creating nested + * properties. + * + * The \a value is only used for determining the type of the property, it is + * not set on the property. + * + * This function works recursively, creating nested properties for class + * properties. + */ QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString &name, const QVariant &value) { @@ -99,6 +125,14 @@ QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString } } } + } else if (type == QMetaType::QVariantList) { + // In case of list values, we need an expandable property with the list + // values as subproperties (though creation of such properties will + // only be done once the value is set) + + // todo: lists probably need their own type here, such that a widget + // can be created that allows for adding elements + type = VariantPropertyManager::unstyledGroupTypeId(); } if (type == objectRefTypeId()) @@ -127,6 +161,11 @@ QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString return property; } +/** + * Deletes the given top-level property. + * + * Should only be used for properties created with createProperty. + */ void CustomPropertiesHelper::deleteProperty(QtProperty *property) { Q_ASSERT(hasProperty(property)); @@ -135,6 +174,13 @@ void CustomPropertiesHelper::deleteProperty(QtProperty *property) deletePropertyInternal(property); } +/** + * Implementation of property deletion. Used by deleteProperty for deleting + * top-level properties and by deleteSubProperties for deleting nested + * properties. + * + * This function works recursively, also deleting all nested properties. + */ void CustomPropertiesHelper::deletePropertyInternal(QtProperty *property) { Q_ASSERT(mPropertyTypeIds.contains(property)); @@ -143,6 +189,12 @@ void CustomPropertiesHelper::deletePropertyInternal(QtProperty *property) delete property; } +/** + * Deletes all sub-properties of the given \a property. + * + * Used when a property is being deleted or before refreshing the nested + * properties that represent class members. + */ void CustomPropertiesHelper::deleteSubProperties(QtProperty *property) { const auto subProperties = property->subProperties(); @@ -154,6 +206,9 @@ void CustomPropertiesHelper::deleteSubProperties(QtProperty *property) } } +/** + * Removes all properties. + */ void CustomPropertiesHelper::clear() { QHashIterator it(mPropertyTypeIds); @@ -171,7 +226,10 @@ QVariant CustomPropertiesHelper::toDisplayValue(QVariant value) const value = value.value().value; if (value.userType() == objectRefTypeId()) - value = QVariant::fromValue(DisplayObjectRef { value.value(), mMapDocument }); + value = QVariant::fromValue(DisplayObjectRef { + value.value(), + mMapDocument + }); return value; } @@ -226,6 +284,24 @@ void CustomPropertiesHelper::onValueChanged(QtProperty *property, const QVariant static_cast(subProperty)->setValue(toDisplayValue(value)); } } + + if (value.userType() == QMetaType::QVariantList) { + QScopedValueRollback updating(mUpdating, true); + + // Delete any existing sub-properties + deleteSubProperties(property); + + // Create a sub-property for each list value + const auto values = value.toList(); + for (int i = 0; i < values.size(); ++i) { + const auto &value = values.at(i); + + auto subProperty = createPropertyInternal(QString::number(i), value); + subProperty->setValue(toDisplayValue(value)); + property->addSubProperty(subProperty); + mPropertyParents.insert(subProperty, property); + } + } } void CustomPropertiesHelper::resetProperty(QtProperty *property) @@ -278,6 +354,15 @@ void CustomPropertiesHelper::propertyTypesChanged() } } +/** + * When the given \a propertyType is an EnumPropertyType, sets the appropriate + * attributes on the given \a property. + * + * Also creates sub-properties for members when the given \a propertyType is a + * ClassPropertyType. + * + * Called after property creation, as well as when the property types changed. + */ void CustomPropertiesHelper::setPropertyAttributes(QtVariantProperty *property, const PropertyType &propertyType) { diff --git a/src/tiled/custompropertieshelper.h b/src/tiled/custompropertieshelper.h index 1006b4a071..8492ef82bb 100644 --- a/src/tiled/custompropertieshelper.h +++ b/src/tiled/custompropertieshelper.h @@ -34,6 +34,9 @@ class MapDocument; class PropertyType; class VariantEditorFactory; +/** + * Helper class for managing custom properties in a QtAbstractPropertyBrowser. + */ class CustomPropertiesHelper : public QObject { Q_OBJECT From b93cb06c23b4088fe0cf7dffdc3d38ac9bb481c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 10 Jul 2024 18:32:08 +0200 Subject: [PATCH 2/8] Introduced ListEdit widget for the Properties view * VariantPropertyManager now displays the number of list items. * VariantEditorFactory now instantiates a ListEdit widget when a list value is selected, which features a button to add an item to the list (currently just "0" integer values). --- src/tiled/custompropertieshelper.cpp | 8 -- src/tiled/libtilededitor.qbs | 2 + src/tiled/listedit.cpp | 88 +++++++++++++++++++++ src/tiled/listedit.h | 56 +++++++++++++ src/tiled/objectrefedit.h | 1 - src/tiled/varianteditorfactory.cpp | 114 +++++++++++++-------------- src/tiled/varianteditorfactory.h | 5 ++ src/tiled/variantpropertymanager.cpp | 20 +++-- 8 files changed, 220 insertions(+), 74 deletions(-) create mode 100644 src/tiled/listedit.cpp create mode 100644 src/tiled/listedit.h diff --git a/src/tiled/custompropertieshelper.cpp b/src/tiled/custompropertieshelper.cpp index 6aa24f03fe..69ebb6dcd6 100644 --- a/src/tiled/custompropertieshelper.cpp +++ b/src/tiled/custompropertieshelper.cpp @@ -125,14 +125,6 @@ QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString } } } - } else if (type == QMetaType::QVariantList) { - // In case of list values, we need an expandable property with the list - // values as subproperties (though creation of such properties will - // only be done once the value is set) - - // todo: lists probably need their own type here, such that a widget - // can be created that allows for adding elements - type = VariantPropertyManager::unstyledGroupTypeId(); } if (type == objectRefTypeId()) diff --git a/src/tiled/libtilededitor.qbs b/src/tiled/libtilededitor.qbs index 9e576776b4..939a8f2011 100644 --- a/src/tiled/libtilededitor.qbs +++ b/src/tiled/libtilededitor.qbs @@ -294,6 +294,8 @@ DynamicLibrary { "layermodel.h", "layeroffsettool.cpp", "layeroffsettool.h", + "listedit.cpp", + "listedit.h", "locatorwidget.cpp", "locatorwidget.h", "magicwandtool.h", diff --git a/src/tiled/listedit.cpp b/src/tiled/listedit.cpp new file mode 100644 index 0000000000..f6379806ab --- /dev/null +++ b/src/tiled/listedit.cpp @@ -0,0 +1,88 @@ +/* + * listedit.h + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include "listedit.h" + +#include "utils.h" + +#include +#include +#include + +namespace Tiled { + +ListEdit::ListEdit(QWidget *parent) + : QWidget{parent} +{ + QHBoxLayout *layout = new QHBoxLayout{this}; + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + mLabel = new QLabel{this}; + mLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); + + QToolButton *addButton = new QToolButton{this}; + addButton->setIcon(QIcon(QStringLiteral(":/images/22/add.png"))); + Utils::setThemeIcon(addButton, "add"); + + QToolButton *editButton = new QToolButton{this}; + editButton->setSizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Preferred}); + editButton->setText(tr("Edit...")); + layout->addWidget(mLabel); + layout->addWidget(addButton); + layout->addWidget(editButton); + + setFocusProxy(editButton); + setFocusPolicy(Qt::StrongFocus); + + connect(addButton, &QToolButton::clicked, + this, &ListEdit::addButtonClicked); + connect(editButton, &QToolButton::clicked, + this, &ListEdit::editButtonClicked); +} + +void ListEdit::setValue(const QVariantList &value) +{ + mValue = value; + mLabel->setText(valueText(value)); +} + +QString ListEdit::valueText(const QVariantList &value) +{ + return value.isEmpty() ? tr("") + : tr("%1 items").arg(value.count()); +} + +void ListEdit::addButtonClicked() +{ + // todo: spawn a kind of "add property" dialog, but without a name field? + // or maybe add button is a dropdown with the available types? + mValue.append(0); + emit valueChanged(mValue); +} + +void ListEdit::editButtonClicked() +{ + // todo: spawn list edit dialog +} + +} // namespace Tiled + +#include "moc_listedit.cpp" diff --git a/src/tiled/listedit.h b/src/tiled/listedit.h new file mode 100644 index 0000000000..19e6a60902 --- /dev/null +++ b/src/tiled/listedit.h @@ -0,0 +1,56 @@ +/* + * listedit.h + * Copyright 2024, Thorbjørn Lindeijer + * + * This file is part of Tiled. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#pragma once + +#include + +class QLabel; + +namespace Tiled { + +/** + * The widget that enables the user to edit a list property. + */ +class ListEdit final : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QVariantList value READ value WRITE setValue NOTIFY valueChanged FINAL) + +public: + explicit ListEdit(QWidget *parent = nullptr); + + const QVariantList &value() const { return mValue; } + void setValue(const QVariantList &value); + + static QString valueText(const QVariantList &value); + +signals: + void valueChanged(const QVariantList &value); + +private: + void addButtonClicked(); + void editButtonClicked(); + + QLabel *mLabel; + QVariantList mValue; +}; + +} // namespace Tiled diff --git a/src/tiled/objectrefedit.h b/src/tiled/objectrefedit.h index bb518f6577..ebcf94bdfb 100644 --- a/src/tiled/objectrefedit.h +++ b/src/tiled/objectrefedit.h @@ -20,7 +20,6 @@ #pragma once -#include "properties.h" #include "variantpropertymanager.h" #include diff --git a/src/tiled/varianteditorfactory.cpp b/src/tiled/varianteditorfactory.cpp index e63040dedb..fbc696152c 100644 --- a/src/tiled/varianteditorfactory.cpp +++ b/src/tiled/varianteditorfactory.cpp @@ -22,6 +22,7 @@ #include "varianteditorfactory.h" #include "fileedit.h" +#include "listedit.h" #include "objectrefedit.h" #include "textpropertyedit.h" #include "tilesetdocument.h" @@ -89,6 +90,7 @@ VariantEditorFactory::~VariantEditorFactory() qDeleteAll(mTextPropertyEditToProperty.keys()); qDeleteAll(mObjectRefEditToProperty.keys()); qDeleteAll(mComboBoxToProperty.keys()); + qDeleteAll(mListEditToProperty.keys()); } void VariantEditorFactory::connectPropertyManager(QtVariantPropertyManager *manager) @@ -145,6 +147,18 @@ QWidget *VariantEditorFactory::createEditor(QtVariantPropertyManager *manager, this, &VariantEditorFactory::slotEditorDestroyed); editor = tilesetEdit; + } else if (type == QMetaType::QVariantList) { + auto listEdit = new ListEdit(parent); + listEdit->setValue(manager->value(property).toList()); + mCreatedListEdits[property].append(listEdit); + mListEditToProperty[listEdit] = property; + + connect(listEdit, &ListEdit::valueChanged, + this, &VariantEditorFactory::listEditValueChanged); + connect(listEdit, &QObject::destroyed, + this, &VariantEditorFactory::slotEditorDestroyed); + + editor = listEdit; } else if (type == QMetaType::QString) { bool multiline = manager->attributeValue(property, QLatin1String("multiline")).toBool(); QStringList suggestions = manager->attributeValue(property, QLatin1String("suggestions")).toStringList(); @@ -227,6 +241,10 @@ void VariantEditorFactory::slotPropertyChanged(QtProperty *property, for (ObjectRefEdit *objectRefEdit : std::as_const(mCreatedObjectRefEdits)[property]) objectRefEdit->setValue(value.value()); } + else if (mCreatedListEdits.contains(property)) { + for (ListEdit *listEdit : std::as_const(mCreatedListEdits)[property]) + listEdit->setValue(value.toList()); + } } void VariantEditorFactory::slotPropertyAttributeChanged(QtProperty *property, @@ -304,72 +322,50 @@ void VariantEditorFactory::objectRefEditValueChanged(const DisplayObjectRef &val } } -void VariantEditorFactory::slotEditorDestroyed(QObject *object) +void VariantEditorFactory::listEditValueChanged(const QVariantList &value) { - // Check if it was an ObjectRefEdit - { - auto objectRefEdit = static_cast(object); - - if (QtProperty *property = mObjectRefEditToProperty.value(objectRefEdit)) { - mObjectRefEditToProperty.remove(objectRefEdit); - mCreatedObjectRefEdits[property].removeAll(objectRefEdit); - if (mCreatedObjectRefEdits[property].isEmpty()) - mCreatedObjectRefEdits.remove(property); - return; - } - } - - // Check if it was a FileEdit - { - auto fileEdit = static_cast(object); - - if (QtProperty *property = mFileEditToProperty.value(fileEdit)) { - mFileEditToProperty.remove(fileEdit); - mCreatedFileEdits[property].removeAll(fileEdit); - if (mCreatedFileEdits[property].isEmpty()) - mCreatedFileEdits.remove(property); - return; - } - } - - // Check if it was a TilesetParametersEdit - { - auto tilesetEdit = static_cast(object); - - if (QtProperty *property = mTilesetEditToProperty.value(tilesetEdit)) { - mTilesetEditToProperty.remove(tilesetEdit); - mCreatedTilesetEdits[property].removeAll(tilesetEdit); - if (mCreatedTilesetEdits[property].isEmpty()) - mCreatedTilesetEdits.remove(property); + auto listEdit = qobject_cast(sender()); + Q_ASSERT(listEdit); + if (QtProperty *property = mListEditToProperty.value(listEdit)) { + QtVariantPropertyManager *manager = propertyManager(property); + if (!manager) return; - } + manager->setValue(property, value); } +} - // Check if it was a TextPropertyEdit - { - auto textPropertyEdit = static_cast(object); - - if (QtProperty *property = mTextPropertyEditToProperty.value(textPropertyEdit)) { - mTextPropertyEditToProperty.remove(textPropertyEdit); - mCreatedTextPropertyEdits[property].removeAll(textPropertyEdit); - if (mCreatedTextPropertyEdits[property].isEmpty()) - mCreatedTextPropertyEdits.remove(property); - return; - } +template +static bool removeEditor(QMap > &map, + QMap &reverseMap, + QObject *object) +{ + T *editor = static_cast(object); + + if (QtProperty *property = reverseMap.value(editor)) { + map[property].removeAll(editor); + if (map[property].isEmpty()) + map.remove(property); + reverseMap.remove(editor); + return true; } - // Check if it was a QComboBox - { - auto comboBox = static_cast(object); + return false; +} - if (QtProperty *property = mComboBoxToProperty.value(comboBox)) { - mComboBoxToProperty.remove(comboBox); - mCreatedComboBoxes[property].removeAll(comboBox); - if (mCreatedComboBoxes[property].isEmpty()) - mCreatedComboBoxes.remove(property); - return; - } - } +void VariantEditorFactory::slotEditorDestroyed(QObject *object) +{ + if (removeEditor(mCreatedObjectRefEdits, mObjectRefEditToProperty, object)) + return; + if (removeEditor(mCreatedFileEdits, mFileEditToProperty, object)) + return; + if (removeEditor(mCreatedTilesetEdits, mTilesetEditToProperty, object)) + return; + if (removeEditor(mCreatedTextPropertyEdits, mTextPropertyEditToProperty, object)) + return; + if (removeEditor(mCreatedComboBoxes, mComboBoxToProperty, object)) + return; + if (removeEditor(mCreatedListEdits, mListEditToProperty, object)) + return; } } // namespace Tiled diff --git a/src/tiled/varianteditorfactory.h b/src/tiled/varianteditorfactory.h index 630a83f013..9c4b486f1d 100644 --- a/src/tiled/varianteditorfactory.h +++ b/src/tiled/varianteditorfactory.h @@ -29,6 +29,7 @@ namespace Tiled { class DisplayObjectRef; class FileEdit; +class ListEdit; class ObjectRefEdit; class TextPropertyEdit; class TilesetParametersEdit; @@ -71,6 +72,7 @@ class VariantEditorFactory : public QtVariantEditorFactory void textPropertyEditTextChanged(const QString &value); void comboBoxPropertyEditTextChanged(const QString &value); void objectRefEditValueChanged(const DisplayObjectRef &value); + void listEditValueChanged(const QVariantList &value); void slotEditorDestroyed(QObject *object); QMap > mCreatedFileEdits; @@ -87,6 +89,9 @@ class VariantEditorFactory : public QtVariantEditorFactory QMap > mCreatedObjectRefEdits; QMap mObjectRefEditToProperty; + + QMap > mCreatedListEdits; + QMap mListEditToProperty; }; } // namespace Tiled diff --git a/src/tiled/variantpropertymanager.cpp b/src/tiled/variantpropertymanager.cpp index d54fd3c4cb..1b9ae413c1 100644 --- a/src/tiled/variantpropertymanager.cpp +++ b/src/tiled/variantpropertymanager.cpp @@ -22,6 +22,7 @@ #include "variantpropertymanager.h" #include "documentmanager.h" +#include "listedit.h" #include "mapdocument.h" #include "mapobject.h" #include "mapobjectmodel.h" @@ -91,7 +92,8 @@ bool VariantPropertyManager::isPropertyTypeSupported(int propertyType) const || propertyType == displayObjectRefTypeId() || propertyType == tilesetParametersTypeId() || propertyType == alignmentTypeId() - || propertyType == unstyledGroupTypeId()) + || propertyType == unstyledGroupTypeId() + || propertyType == QMetaType::QVariantList) return true; return QtVariantPropertyManager::isPropertyTypeSupported(propertyType); } @@ -108,6 +110,8 @@ int VariantPropertyManager::valueType(int propertyType) const return propertyType; if (propertyType == unstyledGroupTypeId()) return QMetaType::QVariantMap; // allows storing any child values + if (propertyType == QMetaType::QVariantList) + return QMetaType::QVariantList; return QtVariantPropertyManager::valueType(propertyType); } @@ -195,8 +199,8 @@ QString VariantPropertyManager::objectRefLabel(const MapObject &object) QString VariantPropertyManager::valueText(const QtProperty *property) const { if (mValues.contains(property)) { - QVariant value = mValues[property]; - int typeId = propertyType(property); + const QVariant &value = mValues[property]; + const int typeId = propertyType(property); if (typeId == displayObjectRefTypeId()) { const auto ref = value.value(); @@ -227,6 +231,9 @@ QString VariantPropertyManager::valueText(const QtProperty *property) const return tilesetDocument->tileset()->imageSource().fileName(); } + if (typeId == QMetaType::QVariantList) + return ListEdit::valueText(value.toList()); + return value.toString(); } @@ -248,7 +255,7 @@ QString VariantPropertyManager::valueText(const QtProperty *property) const QIcon VariantPropertyManager::valueIcon(const QtProperty *property) const { if (mValues.contains(property)) { - const QVariant value = mValues[property]; + const QVariant &value = mValues[property]; const int typeId = propertyType(property); if (typeId == displayObjectRefTypeId()) { @@ -257,7 +264,7 @@ QIcon VariantPropertyManager::valueIcon(const QtProperty *property) const return ObjectIconManager::instance().iconForObject(*object); } - if (typeId == unstyledGroupTypeId()) + if (typeId == unstyledGroupTypeId() || typeId == QMetaType::QVariantList) return QIcon(); QString filePath; @@ -371,7 +378,8 @@ void VariantPropertyManager::initializeProperty(QtProperty *property) if (type == filePathTypeId() || type == displayObjectRefTypeId() || type == tilesetParametersTypeId() - || type == unstyledGroupTypeId()) { // for storing hash map + || type == unstyledGroupTypeId() // for storing hash map + || type == QMetaType::QVariantList) { // for storing list of values mValues[property] = QVariant(); if (type == filePathTypeId()) mFilePathAttributes[property] = FilePathAttributes(); From f13393da14cb4c194e4b15a67438b1c2a65ffd40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Sun, 14 Jul 2024 18:09:37 +0200 Subject: [PATCH 3/8] Various progress on list properties * There is now menu alongside the menu to add new values: - Choosing from the menu adds a value of the selected type - Clicking the button now adds the same type as the last item, or spawns the menu (for empty lists) * Saving in JSON format now supports lists of lists (not yet supported for loading) * When list values change, it no longer deletes and re-creates all sub-properties, but instead reuses the existing items. This should be faster, but also avoids affecting the scroll position of the view. * Some code moved around to share code between Add Property dialog and the new menu for adding list items. --- src/libtiled/maptovariantconverter.cpp | 57 +++++++++++++------------- src/libtiled/maptovariantconverter.h | 1 + src/libtiled/object.h | 2 + src/libtiled/properties.cpp | 39 ++++++++++++++++++ src/libtiled/properties.h | 3 ++ src/tiled/addpropertydialog.cpp | 32 ++------------- src/tiled/custompropertieshelper.cpp | 29 +++++++++---- src/tiled/listedit.cpp | 52 +++++++++++++++-------- src/tiled/listedit.h | 5 ++- src/tiled/propertytypesmodel.cpp | 14 ++++++- src/tiled/propertytypesmodel.h | 2 +- 11 files changed, 150 insertions(+), 86 deletions(-) diff --git a/src/libtiled/maptovariantconverter.cpp b/src/libtiled/maptovariantconverter.cpp index c3b8943644..0214ef92ac 100644 --- a/src/libtiled/maptovariantconverter.cpp +++ b/src/libtiled/maptovariantconverter.cpp @@ -807,45 +807,44 @@ void MapToVariantConverter::addProperties(QVariantMap &variantMap, // TODO: Support custom property types in json1? Maybe with a customPropertyTypesMap... } - variantMap[QStringLiteral("properties")] = propertiesMap; - variantMap[QStringLiteral("propertytypes")] = propertyTypesMap; + variantMap[QStringLiteral("properties")] = std::move(propertiesMap); + variantMap[QStringLiteral("propertytypes")] = std::move(propertyTypesMap); } else { QVariantList propertiesVariantList; Properties::const_iterator it = properties.constBegin(); Properties::const_iterator it_end = properties.constEnd(); for (; it != it_end; ++it) { - const auto exportValue = context.toExportValue(it.value()); - - QVariantMap propertyVariantMap; + QVariantMap propertyVariantMap = toVariantMap(it.value(), context); propertyVariantMap[QStringLiteral("name")] = it.key(); - propertyVariantMap[QStringLiteral("type")] = exportValue.typeName; - if (!exportValue.propertyTypeName.isEmpty()) - propertyVariantMap[QStringLiteral("propertytype")] = exportValue.propertyTypeName; - - if (exportValue.typeName == QLatin1String("list")) { - const QVariantList values = it.value().toList(); - QVariantList valuesVariantList; - - // todo: this doesn't support lists of lists - for (const QVariant &value : values) { - QVariantMap valueVariantMap; - const auto exportValue = context.toExportValue(value); - valueVariantMap[QStringLiteral("value")] = exportValue.value; - valueVariantMap[QStringLiteral("type")] = exportValue.typeName; - if (!exportValue.propertyTypeName.isEmpty()) - valueVariantMap[QStringLiteral("propertytype")] = exportValue.propertyTypeName; - valuesVariantList << valueVariantMap; - } - - propertyVariantMap[QStringLiteral("value")] = valuesVariantList; - } else { - propertyVariantMap[QStringLiteral("value")] = exportValue.value; - } - propertiesVariantList << propertyVariantMap; + propertiesVariantList << std::move(propertyVariantMap); } variantMap[QStringLiteral("properties")] = propertiesVariantList; } } + +QVariantMap MapToVariantConverter::toVariantMap(const QVariant &propertyValue, const ExportContext &context) const +{ + const auto exportValue = context.toExportValue(propertyValue); + QVariantMap propertyVariantMap; + + propertyVariantMap[QStringLiteral("type")] = exportValue.typeName; + if (!exportValue.propertyTypeName.isEmpty()) + propertyVariantMap[QStringLiteral("propertytype")] = exportValue.propertyTypeName; + + if (exportValue.typeName == QLatin1String("list")) { + QVariantList valuesVariantList; + + const QVariantList values = propertyValue.toList(); + for (const QVariant &value : values) + valuesVariantList << toVariantMap(value, context); + + propertyVariantMap[QStringLiteral("value")] = std::move(valuesVariantList); + } else { + propertyVariantMap[QStringLiteral("value")] = exportValue.value; + } + + return propertyVariantMap; +} diff --git a/src/libtiled/maptovariantconverter.h b/src/libtiled/maptovariantconverter.h index 8f3bfd5f03..fb6ed38bd3 100644 --- a/src/libtiled/maptovariantconverter.h +++ b/src/libtiled/maptovariantconverter.h @@ -86,6 +86,7 @@ class TILEDSHARED_EXPORT MapToVariantConverter void addProperties(QVariantMap &variantMap, const Properties &properties) const; + QVariantMap toVariantMap(const QVariant &propertyValue, const ExportContext &context) const; int mVersion; QDir mDir; diff --git a/src/libtiled/object.h b/src/libtiled/object.h index 589fcbcd47..7664be6fe6 100644 --- a/src/libtiled/object.h +++ b/src/libtiled/object.h @@ -123,6 +123,8 @@ class TILEDSHARED_EXPORT Object /** * Returns the type of the object's \a name property, as a string. + * + * This function exists only for the Python plugin. */ QString propertyType(const QString &name) const { return typeName(mProperties.value(name)); } diff --git a/src/libtiled/properties.cpp b/src/libtiled/properties.cpp index 3a4975c318..e537befeb5 100644 --- a/src/libtiled/properties.cpp +++ b/src/libtiled/properties.cpp @@ -237,6 +237,8 @@ static int nameToType(const QString &name) return objectRefTypeId(); if (name == QLatin1String("class")) return QMetaType::QVariantMap; + if (name == QLatin1String("list")) + return QMetaType::QVariantList; return QVariant::nameToType(name.toLatin1().constData()); } @@ -249,6 +251,14 @@ QString typeName(const QVariant &value) return typeToName(value.userType()); } +QString userTypeName(const QVariant &value) +{ + if (value.userType() == propertyValueId()) + return value.value().typeName(); + + return typeToName(value.userType()); +} + const PropertyType *PropertyValue::type() const { return Object::propertyTypes().findTypeById(typeId); @@ -356,6 +366,35 @@ void initializeMetatypes() QMetaType::registerConverter(&FilePath::fromString); } +QVariantList possiblePropertyValues(const ClassPropertyType *parentClassType) +{ + QVariantList values; + + values.append(false); // bool + values.append(QColor()); // color + values.append(0.0); // float + values.append(QVariant::fromValue(FilePath())); // file + values.append(0); // int + values.append(QVariant::fromValue(ObjectRef())); // object + values.append(QString()); // string + values.append(QVariant(QVariantList())); // list + + for (const auto propertyType : Object::propertyTypes()) { + // Avoid suggesting the creation of circular dependencies between types + if (parentClassType && !parentClassType->canAddMemberOfType(propertyType)) + continue; + + // Avoid suggesting classes not meant to be used as property value + if (propertyType->isClass()) + if (!static_cast(propertyType)->isPropertyValueType()) + continue; + + values.append(propertyType->wrap(propertyType->defaultValue())); + } + + return values; +} + } // namespace Tiled #include "moc_properties.cpp" diff --git a/src/libtiled/properties.h b/src/libtiled/properties.h index 2604fff75e..7bb964c242 100644 --- a/src/libtiled/properties.h +++ b/src/libtiled/properties.h @@ -191,7 +191,10 @@ constexpr int objectRefTypeId() { return qMetaTypeId(); } TILEDSHARED_EXPORT QString typeToName(int type); TILEDSHARED_EXPORT QString typeName(const QVariant &value); +TILEDSHARED_EXPORT QString userTypeName(const QVariant &value); TILEDSHARED_EXPORT void initializeMetatypes(); +TILEDSHARED_EXPORT QVariantList possiblePropertyValues(const ClassPropertyType *parentClassType = nullptr); + } // namespace Tiled diff --git a/src/tiled/addpropertydialog.cpp b/src/tiled/addpropertydialog.cpp index 1e00ac3621..4ed2b1aac4 100644 --- a/src/tiled/addpropertydialog.cpp +++ b/src/tiled/addpropertydialog.cpp @@ -22,9 +22,6 @@ #include "addpropertydialog.h" #include "ui_addpropertydialog.h" -#include "documentmanager.h" -#include "object.h" -#include "preferences.h" #include "properties.h" #include "propertytypesmodel.h" #include "session.h" @@ -57,31 +54,10 @@ void AddPropertyDialog::initialize(const Tiled::ClassPropertyType *parentClassTy mUi->setupUi(this); resize(Utils::dpiScaled(size())); - const QIcon plain(QStringLiteral("://images/scalable/property-type-plain.svg")); - - // Add possible types from QVariant - mUi->typeBox->addItem(plain, typeToName(QMetaType::Bool), false); - mUi->typeBox->addItem(plain, typeToName(QMetaType::QColor), QColor()); - mUi->typeBox->addItem(plain, typeToName(QMetaType::Double), 0.0); - mUi->typeBox->addItem(plain, typeToName(filePathTypeId()), QVariant::fromValue(FilePath())); - mUi->typeBox->addItem(plain, typeToName(QMetaType::Int), 0); - mUi->typeBox->addItem(plain, typeToName(objectRefTypeId()), QVariant::fromValue(ObjectRef())); - mUi->typeBox->addItem(plain, typeToName(QMetaType::QString), QString()); - mUi->typeBox->addItem(plain, typeToName(QMetaType::QVariantList), QVariantList()); - - for (const auto propertyType : Object::propertyTypes()) { - // Avoid suggesting the creation of circular dependencies between types - if (parentClassType && !parentClassType->canAddMemberOfType(propertyType)) - continue; - - // Avoid suggesting classes not meant to be used as property value - if (propertyType->isClass()) - if (!static_cast(propertyType)->isPropertyValueType()) - continue; - - const QVariant var = propertyType->wrap(propertyType->defaultValue()); - const QIcon icon = PropertyTypesModel::iconForPropertyType(propertyType->type); - mUi->typeBox->addItem(icon, propertyType->name, var); + const QVariantList values = possiblePropertyValues(parentClassType); + for (const auto &value : values) { + const QIcon icon = PropertyTypesModel::iconForProperty(value); + mUi->typeBox->addItem(icon, userTypeName(value), value); } mUi->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); diff --git a/src/tiled/custompropertieshelper.cpp b/src/tiled/custompropertieshelper.cpp index 69ebb6dcd6..b3494d2370 100644 --- a/src/tiled/custompropertieshelper.cpp +++ b/src/tiled/custompropertieshelper.cpp @@ -278,20 +278,33 @@ void CustomPropertiesHelper::onValueChanged(QtProperty *property, const QVariant } if (value.userType() == QMetaType::QVariantList) { + const auto values = value.toList(); + QScopedValueRollback updating(mUpdating, true); - // Delete any existing sub-properties - deleteSubProperties(property); + // Delete any superfluous sub-properties + auto subProperties = property->subProperties(); + while (subProperties.size() > values.size()) { + auto subProperty = subProperties.takeLast(); + if (mPropertyParents.value(subProperty) == property) { + deletePropertyInternal(subProperty); + mPropertyParents.remove(subProperty); + } + } - // Create a sub-property for each list value - const auto values = value.toList(); + subProperties.resize(values.size(), nullptr); + + // Set sub-property values, creating them if necessary for (int i = 0; i < values.size(); ++i) { const auto &value = values.at(i); - auto subProperty = createPropertyInternal(QString::number(i), value); - subProperty->setValue(toDisplayValue(value)); - property->addSubProperty(subProperty); - mPropertyParents.insert(subProperty, property); + if (!subProperties[i]) { + subProperties[i] = createPropertyInternal(QString::number(i), value); + property->addSubProperty(subProperties[i]); + mPropertyParents.insert(subProperties[i], property); + } + + static_cast(subProperties[i])->setValue(toDisplayValue(value)); } } } diff --git a/src/tiled/listedit.cpp b/src/tiled/listedit.cpp index f6379806ab..fe355faf74 100644 --- a/src/tiled/listedit.cpp +++ b/src/tiled/listedit.cpp @@ -20,10 +20,13 @@ #include "listedit.h" +#include "properties.h" +#include "propertytypesmodel.h" #include "utils.h" #include #include +#include #include namespace Tiled { @@ -38,24 +41,31 @@ ListEdit::ListEdit(QWidget *parent) mLabel = new QLabel{this}; mLabel->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); - QToolButton *addButton = new QToolButton{this}; - addButton->setIcon(QIcon(QStringLiteral(":/images/22/add.png"))); - Utils::setThemeIcon(addButton, "add"); + mAddMenu = new QMenu{this}; + + mAddButton = new QToolButton{this}; + mAddButton->setIcon(QIcon(QStringLiteral(":/images/22/add.png"))); + mAddButton->setText(tr("Add")); + mAddButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + mAddButton->setMenu(mAddMenu); + mAddButton->setPopupMode(QToolButton::MenuButtonPopup); + Utils::setThemeIcon(mAddButton, "add"); - QToolButton *editButton = new QToolButton{this}; - editButton->setSizePolicy(QSizePolicy{QSizePolicy::Fixed, QSizePolicy::Preferred}); - editButton->setText(tr("Edit...")); layout->addWidget(mLabel); - layout->addWidget(addButton); - layout->addWidget(editButton); + layout->addWidget(mAddButton); - setFocusProxy(editButton); + setFocusProxy(mAddButton); setFocusPolicy(Qt::StrongFocus); - connect(addButton, &QToolButton::clicked, + connect(mAddButton, &QToolButton::clicked, this, &ListEdit::addButtonClicked); - connect(editButton, &QToolButton::clicked, - this, &ListEdit::editButtonClicked); + connect(mAddMenu, &QMenu::aboutToShow, + this, &ListEdit::populateAddMenu); + + connect(mAddMenu, &QMenu::triggered, this, [this](QAction *action) { + mValue.append(action->data()); + emit valueChanged(mValue); + }); } void ListEdit::setValue(const QVariantList &value) @@ -72,15 +82,23 @@ QString ListEdit::valueText(const QVariantList &value) void ListEdit::addButtonClicked() { - // todo: spawn a kind of "add property" dialog, but without a name field? - // or maybe add button is a dropdown with the available types? - mValue.append(0); + if (mValue.isEmpty()) + return mAddButton->showMenu(); + + mValue.append(QVariant(mValue.last().metaType())); emit valueChanged(mValue); } -void ListEdit::editButtonClicked() +void ListEdit::populateAddMenu() { - // todo: spawn list edit dialog + mAddMenu->clear(); + + const QVariantList values = possiblePropertyValues(nullptr); + for (const auto &value : values) { + const QIcon icon = PropertyTypesModel::iconForProperty(value); + auto action = mAddMenu->addAction(icon, userTypeName(value)); + action->setData(value); + } } } // namespace Tiled diff --git a/src/tiled/listedit.h b/src/tiled/listedit.h index 19e6a60902..ec0d37810e 100644 --- a/src/tiled/listedit.h +++ b/src/tiled/listedit.h @@ -23,6 +23,7 @@ #include class QLabel; +class QToolButton; namespace Tiled { @@ -47,9 +48,11 @@ class ListEdit final : public QWidget private: void addButtonClicked(); - void editButtonClicked(); + void populateAddMenu(); QLabel *mLabel; + QToolButton *mAddButton; + QMenu *mAddMenu; QVariantList mValue; }; diff --git a/src/tiled/propertytypesmodel.cpp b/src/tiled/propertytypesmodel.cpp index e5b426cc7b..b881a7b00b 100644 --- a/src/tiled/propertytypesmodel.cpp +++ b/src/tiled/propertytypesmodel.cpp @@ -198,17 +198,27 @@ void PropertyTypesModel::importObjectTypes(const QVector &objectType endResetModel(); } +QIcon PropertyTypesModel::iconForProperty(const QVariant &value) +{ + if (value.userType() == propertyValueId()) + if (auto type = value.value().type()) + return iconForPropertyType(type->type); + + static const QIcon plain(QStringLiteral("://images/scalable/property-type-plain.svg")); + return plain; +} + QIcon PropertyTypesModel::iconForPropertyType(PropertyType::Type type) { switch (type) { case PropertyType::PT_Invalid: break; case PropertyType::PT_Class: { - static QIcon classIcon(QStringLiteral("://images/scalable/property-type-class.svg")); + static const QIcon classIcon(QStringLiteral("://images/scalable/property-type-class.svg")); return classIcon; } case PropertyType::PT_Enum: { - static QIcon enumIcon(QStringLiteral("://images/scalable/property-type-enum.svg")); + static const QIcon enumIcon(QStringLiteral("://images/scalable/property-type-enum.svg")); return enumIcon; } } diff --git a/src/tiled/propertytypesmodel.h b/src/tiled/propertytypesmodel.h index 50719c47ea..48ebe37f43 100644 --- a/src/tiled/propertytypesmodel.h +++ b/src/tiled/propertytypesmodel.h @@ -20,7 +20,6 @@ #pragma once -#include "object.h" #include "properties.h" #include "propertytype.h" @@ -57,6 +56,7 @@ class PropertyTypesModel : public QAbstractListModel void importPropertyTypes(PropertyTypes typesToImport); void importObjectTypes(const QVector &objectTypes); + static QIcon iconForProperty(const QVariant &value); static QIcon iconForPropertyType(PropertyType::Type type); signals: From 1cb35836e5688e6d81a6ac41f5747ecfce42ff0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Sun, 14 Jul 2024 19:02:39 +0200 Subject: [PATCH 4/8] Fixed compilation against Qt 5 --- src/libtiled/properties.h | 2 +- src/tiled/custompropertieshelper.cpp | 5 +++++ src/tiled/listedit.cpp | 4 ++++ src/tiled/listedit.h | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libtiled/properties.h b/src/libtiled/properties.h index 7bb964c242..de16c313fb 100644 --- a/src/libtiled/properties.h +++ b/src/libtiled/properties.h @@ -91,7 +91,7 @@ class TILEDSHARED_EXPORT ObjectRef Q_PROPERTY(int id MEMBER id) public: - int id; + int id = 0; bool operator==(const ObjectRef &o) const { return id == o.id; } diff --git a/src/tiled/custompropertieshelper.cpp b/src/tiled/custompropertieshelper.cpp index b3494d2370..71dee23101 100644 --- a/src/tiled/custompropertieshelper.cpp +++ b/src/tiled/custompropertieshelper.cpp @@ -292,7 +292,12 @@ void CustomPropertiesHelper::onValueChanged(QtProperty *property, const QVariant } } +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) subProperties.resize(values.size(), nullptr); +#else + while (subProperties.size() < values.size()) + subProperties.append(nullptr); +#endif // Set sub-property values, creating them if necessary for (int i = 0; i < values.size(); ++i) { diff --git a/src/tiled/listedit.cpp b/src/tiled/listedit.cpp index fe355faf74..90e31be692 100644 --- a/src/tiled/listedit.cpp +++ b/src/tiled/listedit.cpp @@ -85,7 +85,11 @@ void ListEdit::addButtonClicked() if (mValue.isEmpty()) return mAddButton->showMenu(); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) mValue.append(QVariant(mValue.last().metaType())); +#else + mValue.append(QVariant(mValue.last().userType(), nullptr)); +#endif emit valueChanged(mValue); } diff --git a/src/tiled/listedit.h b/src/tiled/listedit.h index ec0d37810e..62edb0cb1a 100644 --- a/src/tiled/listedit.h +++ b/src/tiled/listedit.h @@ -23,6 +23,7 @@ #include class QLabel; +class QMenu; class QToolButton; namespace Tiled { From 46e9483f60d0b9271b405237ddb7dbc9431c8616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 17 Jul 2024 14:59:01 +0200 Subject: [PATCH 5/8] Support nested list properties in TMX format Also switched from "value" to "item" elements. --- src/libtiled/mapreader.cpp | 2 +- src/libtiled/mapwriter.cpp | 74 ++++++++++++++++++++------------------ 2 files changed, 40 insertions(+), 36 deletions(-) diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 8a5a33afbc..483dde34b8 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -1474,7 +1474,7 @@ QVariant MapReaderPrivate::readPropertyValue(const ExportContext &context) } else if (xml.isStartElement()) { if (xml.name() == QLatin1String("properties")) exportValue.value = readProperties(); - else if (xml.name() == QLatin1String("value")) + else if (xml.name() == QLatin1String("item")) values.append(readPropertyValue(context)); else readUnknownElement(); diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index ce806b13cf..70f06f65b5 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -94,6 +94,8 @@ class MapWriterPrivate void writeGroupLayer(QXmlStreamWriter &w, const GroupLayer &groupLayer); void writeProperties(QXmlStreamWriter &w, const Properties &properties); + void writeExportValue(QXmlStreamWriter &w, + const QVariant &value, const ExportContext &context); void writeImage(QXmlStreamWriter &w, const QUrl &source, const QPixmap &image, @@ -899,41 +901,7 @@ void MapWriterPrivate::writeProperties(QXmlStreamWriter &w, w.writeStartElement(QStringLiteral("property")); w.writeAttribute(QStringLiteral("name"), it.key()); - const auto exportValue = context.toExportValue(it.value()); - if (exportValue.typeName != QLatin1String("string")) - w.writeAttribute(QStringLiteral("type"), exportValue.typeName); - if (!exportValue.propertyTypeName.isEmpty()) - w.writeAttribute(QStringLiteral("propertytype"), exportValue.propertyTypeName); - - switch (exportValue.value.userType()) { - case QMetaType::QVariantList: { - const auto values = exportValue.value.toList(); - for (const QVariant &v : values) { - const auto exportValue = context.toExportValue(v); - w.writeStartElement(QStringLiteral("value")); - if (exportValue.typeName != QLatin1String("string")) - w.writeAttribute(QStringLiteral("type"), exportValue.typeName); - if (!exportValue.propertyTypeName.isEmpty()) - w.writeAttribute(QStringLiteral("propertytype"), exportValue.propertyTypeName); - w.writeCharacters(exportValue.value.toString()); - w.writeEndElement(); - } - break; - } - case QMetaType::QVariantMap: - // Write out the original value, so that the propertytype attribute - // can also be written for their members where applicable. - writeProperties(w, it.value().value().value.toMap()); - break; - default: - const QString value = exportValue.value.toString(); - - if (value.contains(QLatin1Char('\n'))) - w.writeCharacters(value); - else - w.writeAttribute(QStringLiteral("value"), value); - break; - } + writeExportValue(w, it.value(), context); w.writeEndElement(); // } @@ -941,6 +909,42 @@ void MapWriterPrivate::writeProperties(QXmlStreamWriter &w, w.writeEndElement(); // } +void MapWriterPrivate::writeExportValue(QXmlStreamWriter &w, + const QVariant &value, + const ExportContext &context) +{ + const auto exportValue = context.toExportValue(value); + if (exportValue.typeName != QLatin1String("string")) + w.writeAttribute(QStringLiteral("type"), exportValue.typeName); + if (!exportValue.propertyTypeName.isEmpty()) + w.writeAttribute(QStringLiteral("propertytype"), exportValue.propertyTypeName); + + switch (exportValue.value.userType()) { + case QMetaType::QVariantList: { + const auto values = exportValue.value.toList(); + for (const QVariant &value : values) { + w.writeStartElement(QStringLiteral("item")); + writeExportValue(w, value, context); + w.writeEndElement(); // + } + break; + } + case QMetaType::QVariantMap: + // Write out the original value, so that the propertytype attribute + // can also be written for their members where applicable. + writeProperties(w, value.value().value.toMap()); + break; + default: + const QString value = exportValue.value.toString(); + + if (value.contains(QLatin1Char('\n'))) + w.writeCharacters(value); + else + w.writeAttribute(QStringLiteral("value"), value); + break; + } +} + void MapWriterPrivate::writeImage(QXmlStreamWriter &w, const QUrl &source, const QPixmap &image, From dd465b9f740141098288a2ac4d28adbee748226c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 17 Jul 2024 15:00:15 +0200 Subject: [PATCH 6/8] Fixed alignment for Reset property button In case there is no editor widget. Also avoids a warning about trying to add nullptr as widget. --- src/tiled/varianteditorfactory.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tiled/varianteditorfactory.cpp b/src/tiled/varianteditorfactory.cpp index fbc696152c..85111b568d 100644 --- a/src/tiled/varianteditorfactory.cpp +++ b/src/tiled/varianteditorfactory.cpp @@ -67,10 +67,11 @@ ResetWidget::ResetWidget(QtProperty *property, QWidget *editor, QWidget *parent) layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); - layout->addWidget(editor); - layout->addWidget(resetButton); - - setFocusProxy(editor); + if (editor) { + layout->addWidget(editor, 1); + setFocusProxy(editor); + } + layout->addWidget(resetButton, 0, Qt::AlignRight); connect(resetButton, &QToolButton::clicked, this, &ResetWidget::buttonClicked); } From c957f8e2691254253a83b08b0f067ef63fc63449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 17 Jul 2024 15:09:31 +0200 Subject: [PATCH 7/8] Addressed various issues with editing list values * When changing a class member of a class that is either directly or indirectly part of a list, it is now emitted as a change of the top-most list property. This is because there is currently no more specific way to apply such changes. * The above is now also taken into account by the Reset action. * List properties are now re-created as necessary when their type changes. * Class members are now correctly marked as modified when the class is a list value. --- src/tiled/custompropertieshelper.cpp | 202 ++++++++++++++++++++------- src/tiled/custompropertieshelper.h | 9 ++ 2 files changed, 159 insertions(+), 52 deletions(-) diff --git a/src/tiled/custompropertieshelper.cpp b/src/tiled/custompropertieshelper.cpp index 71dee23101..0b0fa5c091 100644 --- a/src/tiled/custompropertieshelper.cpp +++ b/src/tiled/custompropertieshelper.cpp @@ -99,10 +99,41 @@ QtVariantProperty *CustomPropertiesHelper::createProperty(const QString &name, */ QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString &name, const QVariant &value) +{ + const PropertyType *propertyType = nullptr; + const int type = propertyTypeForValue(value, propertyType); + return createPropertyInternal(name, type, propertyType); +} + +QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString &name, + int type, + const PropertyType *propertyType) +{ + QtVariantProperty *property = mPropertyManager->addProperty(type, name); + + if (type == QMetaType::Bool) + property->setAttribute(QLatin1String("textVisible"), false); + if (type == QMetaType::QString) + property->setAttribute(QLatin1String("multiline"), true); + if (type == QMetaType::Double) + property->setAttribute(QLatin1String("decimals"), 9); + + if (propertyType) { + mPropertyTypeIds.insert(property, propertyType->id); + setPropertyAttributes(property, *propertyType); + } else { + mPropertyTypeIds.insert(property, 0); + } + + return property; +} + +int CustomPropertiesHelper::propertyTypeForValue(const QVariant &value, + const PropertyType *&propertyType) const { int type = value.userType(); - const PropertyType *propertyType = nullptr; + propertyType = nullptr; if (type == propertyValueId()) { const PropertyValue propertyValue = value.value(); @@ -130,27 +161,11 @@ QtVariantProperty *CustomPropertiesHelper::createPropertyInternal(const QString if (type == objectRefTypeId()) type = VariantPropertyManager::displayObjectRefTypeId(); - QtVariantProperty *property = mPropertyManager->addProperty(type, name); - if (!property) { - // fall back to string property for unsupported property types - property = mPropertyManager->addProperty(QMetaType::QString, name); - } - - if (type == QMetaType::Bool) - property->setAttribute(QLatin1String("textVisible"), false); - if (type == QMetaType::QString) - property->setAttribute(QLatin1String("multiline"), true); - if (type == QMetaType::Double) - property->setAttribute(QLatin1String("decimals"), 9); - - if (propertyType) { - mPropertyTypeIds.insert(property, propertyType->id); - setPropertyAttributes(property, *propertyType); - } else { - mPropertyTypeIds.insert(property, 0); - } + // fall back to string property for unsupported property types + if (!mPropertyManager->isPropertyTypeSupported(type)) + type = QMetaType::QString; - return property; + return type; } /** @@ -238,47 +253,80 @@ QVariant CustomPropertiesHelper::fromDisplayValue(QtProperty *property, return value; } -void CustomPropertiesHelper::onValueChanged(QtProperty *property, const QVariant &value) +void CustomPropertiesHelper::applyValueChange(QtProperty *property, const QVariant &displayValue) { - if (!mPropertyTypeIds.contains(property)) - return; + const auto propertyValue = fromDisplayValue(property, displayValue); + + if (auto parent = static_cast(mPropertyParents.value(property))) { + // If this is a list value, we need to apply the new value to the list. + if (parent->propertyType() == QMetaType::QVariantList) { + auto variantList = parent->value().toList(); + const int index = parent->subProperties().indexOf(property); + if (index != -1) { + variantList[index] = propertyValue; + + QScopedValueRollback applyToParent(mNoApplyToChildren, true); + parent->setValue(variantList); + } + return; + } + + // If this is a class member that is part of a list, we need to bubble + // up the value change to the parent class first. + if (isPartOfList(parent)) { + auto variantMap = parent->value().toMap(); + variantMap.insert(property->propertyName(), propertyValue); - if (!mUpdating) { - const auto propertyValue = fromDisplayValue(property, value); - const auto path = propertyPath(property); + QScopedValueRollback applyToParent(mNoApplyToChildren, true); + parent->setValue(variantMap); - QScopedValueRollback emittingValueChanged(mEmittingValueChanged, true); - emit propertyMemberValueChanged(path, propertyValue); + property->setModified(!variantMap.isEmpty()); + return; + } } + // In all other cases, we emit the value change and let the listener handle + // it. This allows to apply the change to possibly multiple selected + // objects slightly differently. + QScopedValueRollback emittingValueChanged(mEmittingValueChanged, true); + emit propertyMemberValueChanged(propertyPath(property), propertyValue); +} + +void CustomPropertiesHelper::applyChangeToChildren(QtProperty *property, const QVariant &displayValue) +{ if (auto type = propertyType(property); type && type->isClass()) { // Apply the change to the children + auto parent = static_cast(mPropertyParents.value(property)); auto &members = static_cast(*type).members; const auto subProperties = property->subProperties(); - const auto map = value.toMap(); + const auto map = displayValue.toMap(); QScopedValueRollback updating(mUpdating, true); for (QtProperty *subProperty : subProperties) { const auto name = subProperty->propertyName(); const bool modified = map.contains(name); - const auto value = modified ? map.value(name) - : members.value(name); + const auto value = toDisplayValue(modified ? map.value(name) + : members.value(name)); // Avoid setting child class members as modified, just because // the class definition sets different defaults on them. - const bool isParentTopLevel = !mPropertyParents.contains(property); + const bool isParentTopLevel = !parent; + const bool isParentInList = parent && parent->propertyType() == QMetaType::QVariantList; const bool isParentModified = property->isModified(); - subProperty->setModified(modified && (isParentTopLevel || isParentModified)); + subProperty->setModified(modified && (isParentTopLevel || + isParentInList || + isParentModified)); - static_cast(subProperty)->setValue(toDisplayValue(value)); + static_cast(subProperty)->setValue(value); + applyChangeToChildren(subProperty, value); } } - if (value.userType() == QMetaType::QVariantList) { - const auto values = value.toList(); + if (displayValue.userType() == QMetaType::QVariantList) { + const auto values = displayValue.toList(); QScopedValueRollback updating(mUpdating, true); @@ -292,35 +340,77 @@ void CustomPropertiesHelper::onValueChanged(QtProperty *property, const QVariant } } -#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - subProperties.resize(values.size(), nullptr); -#else - while (subProperties.size() < values.size()) - subProperties.append(nullptr); -#endif + QtProperty *previousProperty = nullptr; - // Set sub-property values, creating them if necessary + // Set sub-property values, (re)creating them if necessary for (int i = 0; i < values.size(); ++i) { const auto &value = values.at(i); + auto subProperty = static_cast(subProperties.value(i)); - if (!subProperties[i]) { - subProperties[i] = createPropertyInternal(QString::number(i), value); - property->addSubProperty(subProperties[i]); - mPropertyParents.insert(subProperties[i], property); + const PropertyType *propertyType = nullptr; + const int type = propertyTypeForValue(value, propertyType); + + // If the value is of a different type, delete the property + if (subProperty && subProperty->propertyType() != type) { + deletePropertyInternal(subProperty); + subProperty = nullptr; } - static_cast(subProperties[i])->setValue(toDisplayValue(value)); + if (!subProperty) { + // Create a new property + subProperty = createPropertyInternal(QString::number(i), type, propertyType); + property->insertSubProperty(subProperty, previousProperty); + mPropertyParents.insert(subProperty, property); + } else if (propertyType && mPropertyTypeIds.value(subProperty) != propertyType->id) { + // Adjust property in case its property type changed + mPropertyTypeIds.insert(subProperty, propertyType->id); + setPropertyAttributes(subProperty, *propertyType); + } + + const auto displayValue = toDisplayValue(value); + static_cast(subProperty)->setValue(displayValue); + applyChangeToChildren(subProperty, displayValue); + + previousProperty = subProperty; } } } +void CustomPropertiesHelper::onValueChanged(QtProperty *property, const QVariant &displayValue) +{ + if (!mPropertyTypeIds.contains(property)) + return; + + if (!mUpdating) + applyValueChange(property, displayValue); + + if (!mNoApplyToChildren) { + QScopedValueRollback applyingToChildren(mNoApplyToChildren, true); + applyChangeToChildren(property, displayValue); + } +} + void CustomPropertiesHelper::resetProperty(QtProperty *property) { // Reset class property value by removing it if (property->isModified()) { - // Only nested properties are currently marked as "modified", so in - // this case we rely on the handling of this signal - emit propertyMemberValueChanged(propertyPath(property), QVariant()); + // Only class members are currently marked as "modified", but if there + // is a list involved, we need to bubble up the value change to the + // parent. + if (isPartOfList(property)) { + auto parent = static_cast(mPropertyParents.value(property)); + if (parent) { + auto variantMap = parent->value().toMap(); + variantMap.remove(property->propertyName()); + + // Not setting mApplyingToParent here since we need this change + // to be applied to the children as well. + parent->setValue(variantMap); + } + } else { + // No lists involved, so we can rely on the handling of this signal + emit propertyMemberValueChanged(propertyPath(property), QVariant()); + } return; } @@ -441,6 +531,14 @@ QStringList CustomPropertiesHelper::propertyPath(QtProperty *property) const return path; } +bool CustomPropertiesHelper::isPartOfList(QtProperty *property) const +{ + if (auto parent = static_cast(mPropertyParents.value(property))) + return parent->propertyType() == QMetaType::QVariantList || isPartOfList(parent); + + return false; +} + } // namespace Tiled #include "moc_custompropertieshelper.cpp" diff --git a/src/tiled/custompropertieshelper.h b/src/tiled/custompropertieshelper.h index 8492ef82bb..aa3e668964 100644 --- a/src/tiled/custompropertieshelper.h +++ b/src/tiled/custompropertieshelper.h @@ -65,9 +65,16 @@ class CustomPropertiesHelper : public QObject private: QtVariantProperty *createPropertyInternal(const QString &name, const QVariant &value); + QtVariantProperty *createPropertyInternal(const QString &name, + int type, + const PropertyType *propertyType); + int propertyTypeForValue(const QVariant &value, const PropertyType* &propertyType) const; void deletePropertyInternal(QtProperty *property); void deleteSubProperties(QtProperty *property); + void applyValueChange(QtProperty *property, const QVariant &displayValue); + void applyChangeToChildren(QtProperty *property, const QVariant &displayValue); + void onValueChanged(QtProperty *property, const QVariant &value); void resetProperty(QtProperty *property); void propertyTypesChanged(); @@ -77,6 +84,7 @@ class CustomPropertiesHelper : public QObject const PropertyType *propertyType(QtProperty *property) const; QStringList propertyPath(QtProperty *property) const; + bool isPartOfList(QtProperty *property) const; QtAbstractPropertyBrowser *mPropertyBrowser; QtVariantPropertyManager *mPropertyManager; @@ -86,6 +94,7 @@ class CustomPropertiesHelper : public QObject QHash mPropertyParents; bool mUpdating = false; bool mEmittingValueChanged = false; + bool mNoApplyToChildren = false; }; inline bool CustomPropertiesHelper::hasProperty(QtProperty *property) const From 6f0a9009039802aae6e760f248910599a1290ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Thu, 18 Jul 2024 18:01:27 +0200 Subject: [PATCH 8/8] WIP: Added "delete" button to remove list items Unfortunately, VariantEditorFactory::createEditor is currently unable to check whether the value is part of a list or not. --- src/tiled/custompropertieshelper.cpp | 26 +++++++++++++++++++++++-- src/tiled/custompropertieshelper.h | 1 + src/tiled/varianteditorfactory.cpp | 29 ++++++++++++++++++---------- src/tiled/varianteditorfactory.h | 1 + 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/tiled/custompropertieshelper.cpp b/src/tiled/custompropertieshelper.cpp index 0b0fa5c091..6c96b01ada 100644 --- a/src/tiled/custompropertieshelper.cpp +++ b/src/tiled/custompropertieshelper.cpp @@ -53,6 +53,8 @@ CustomPropertiesHelper::CustomPropertiesHelper(QtAbstractPropertyBrowser *proper connect(variantEditorFactory, &VariantEditorFactory::resetProperty, this, &CustomPropertiesHelper::resetProperty); + connect(variantEditorFactory, &VariantEditorFactory::removeProperty, + this, &CustomPropertiesHelper::removeProperty); connect(Preferences::instance(), &Preferences::propertyTypesChanged, this, &CustomPropertiesHelper::propertyTypesChanged); @@ -403,8 +405,8 @@ void CustomPropertiesHelper::resetProperty(QtProperty *property) auto variantMap = parent->value().toMap(); variantMap.remove(property->propertyName()); - // Not setting mApplyingToParent here since we need this change - // to be applied to the children as well. + // Not setting mNoApplyToChildren here since we need this + // change to be applied to the children as well. parent->setValue(variantMap); } } else { @@ -426,6 +428,26 @@ void CustomPropertiesHelper::resetProperty(QtProperty *property) } } +void CustomPropertiesHelper::removeProperty(QtProperty *property) +{ + // Removing is only supported for list items for now + auto parent = static_cast(mPropertyParents.value(property)); + if (!parent) + return; + if (parent->propertyType() != QMetaType::QVariantList) + return; + + auto variantList = parent->value().toList(); + const int index = parent->subProperties().indexOf(property); + if (index != -1) { + variantList.removeAt(index); + + // Not setting mNoApplyToChildren here since we need this + // change to be applied to the children as well. + parent->setValue(variantList); + } +} + void CustomPropertiesHelper::propertyTypesChanged() { // When this happens in response to emitting propertyValueChanged, it means diff --git a/src/tiled/custompropertieshelper.h b/src/tiled/custompropertieshelper.h index aa3e668964..342c5ba8c3 100644 --- a/src/tiled/custompropertieshelper.h +++ b/src/tiled/custompropertieshelper.h @@ -77,6 +77,7 @@ class CustomPropertiesHelper : public QObject void onValueChanged(QtProperty *property, const QVariant &value); void resetProperty(QtProperty *property); + void removeProperty(QtProperty *property); void propertyTypesChanged(); void setPropertyAttributes(QtVariantProperty *property, diff --git a/src/tiled/varianteditorfactory.cpp b/src/tiled/varianteditorfactory.cpp index 85111b568d..779baeba2f 100644 --- a/src/tiled/varianteditorfactory.cpp +++ b/src/tiled/varianteditorfactory.cpp @@ -45,10 +45,9 @@ class ResetWidget : public QWidget signals: void resetProperty(QtProperty *property); + void removeProperty(QtProperty *property); private: - void buttonClicked(); - QtProperty *mProperty; }; @@ -65,6 +64,13 @@ ResetWidget::ResetWidget(QtProperty *property, QWidget *editor, QWidget *parent) resetButton->setToolTip(tr("Reset")); Utils::setThemeIcon(resetButton, "edit-clear"); + auto removeButton = new QToolButton(this); + removeButton->setIcon(QIcon(QLatin1String(":/images/16/edit-delete.png"))); + removeButton->setIconSize(Utils::smallIconSize()); + removeButton->setAutoRaise(true); + removeButton->setToolTip(tr("Remove")); + Utils::setThemeIcon(removeButton, "edit-delete"); + layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); if (editor) { @@ -72,13 +78,14 @@ ResetWidget::ResetWidget(QtProperty *property, QWidget *editor, QWidget *parent) setFocusProxy(editor); } layout->addWidget(resetButton, 0, Qt::AlignRight); - - connect(resetButton, &QToolButton::clicked, this, &ResetWidget::buttonClicked); -} - -void ResetWidget::buttonClicked() -{ - emit resetProperty(mProperty); + layout->addWidget(removeButton, 0, Qt::AlignRight); + + connect(resetButton, &QToolButton::clicked, this, [this] { + emit resetProperty(mProperty); + }); + connect(removeButton, &QToolButton::clicked, this, [this] { + emit removeProperty(mProperty); + }); } @@ -196,12 +203,14 @@ QWidget *VariantEditorFactory::createEditor(QtVariantPropertyManager *manager, if (!editor) editor = QtVariantEditorFactory::createEditor(manager, property, parent); - if (type == QMetaType::QColor || type == VariantPropertyManager::displayObjectRefTypeId() || property->isModified()) { + if (true || type == QMetaType::QColor || type == VariantPropertyManager::displayObjectRefTypeId() || property->isModified()) { // Allow resetting color and object reference properties, or allow // unsetting a class member (todo: resolve conflict...). auto resetWidget = new ResetWidget(property, editor, parent); connect(resetWidget, &ResetWidget::resetProperty, this, &VariantEditorFactory::resetProperty); + connect(resetWidget, &ResetWidget::removeProperty, + this, &VariantEditorFactory::removeProperty); editor = resetWidget; } diff --git a/src/tiled/varianteditorfactory.h b/src/tiled/varianteditorfactory.h index 9c4b486f1d..f05450e8ac 100644 --- a/src/tiled/varianteditorfactory.h +++ b/src/tiled/varianteditorfactory.h @@ -55,6 +55,7 @@ class VariantEditorFactory : public QtVariantEditorFactory signals: void resetProperty(QtProperty *property); + void removeProperty(QtProperty *property); protected: void connectPropertyManager(QtVariantPropertyManager *manager) override;