Skip to content

Commit

Permalink
Added support for custom class properties
Browse files Browse the repository at this point in the history
Including changing of nested values. However, several things remain to be
done, including:

* When the custom properties are refreshed, all properties (and their
  widgets) are re-created. There should be a way to update their value
  instead.

* There is no way to see yet whether a property is set, nor can its value
  be reset.
  • Loading branch information
bjorn committed Sep 24, 2024
1 parent 68d84af commit 2d18a43
Show file tree
Hide file tree
Showing 9 changed files with 363 additions and 148 deletions.
1 change: 1 addition & 0 deletions src/tiled/document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ void Document::setCurrentObject(Object *object, Document *owningDocument)

emit currentObjectSet(object);
emit currentObjectChanged(object);
emit currentObjectsChanged();
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/tiled/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ class Document : public QObject,

void currentObjectSet(Object *object);
void currentObjectChanged(Object *object);
void currentObjectsChanged();

/**
* Makes the Properties window visible and take focus.
Expand Down
12 changes: 10 additions & 2 deletions src/tiled/mapdocument.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,9 @@ void MapDocument::setSelectedLayers(const QList<Layer *> &layers)

mSelectedLayers = layers;
emit selectedLayersChanged();

if (currentObject() && currentObject()->typeId() == Object::LayerType)
emit currentObjectsChanged();
}

void MapDocument::switchCurrentLayer(Layer *layer)
Expand Down Expand Up @@ -1277,8 +1280,10 @@ void MapDocument::setSelectedObjects(const QList<MapObject *> &selectedObjects)
// Make sure the current object is one of the selected ones
if (!selectedObjects.isEmpty()) {
if (currentObject() && currentObject()->typeId() == Object::MapObjectType) {
if (selectedObjects.contains(static_cast<MapObject*>(currentObject())))
if (selectedObjects.contains(static_cast<MapObject*>(currentObject()))) {
emit currentObjectsChanged();
return;
}
}

setCurrentObject(selectedObjects.first());
Expand Down Expand Up @@ -1730,8 +1735,11 @@ void MapDocument::deselectObjects(const QList<MapObject *> &objects)
removedAboutToBeSelectedObjects += mAboutToBeSelectedObjects.removeAll(object);
}

if (removedSelectedObjects > 0)
if (removedSelectedObjects > 0) {
emit selectedObjectsChanged();
if (mCurrentObject && mCurrentObject->typeId() == Object::MapObjectType)
emit currentObjectsChanged();
}
if (removedAboutToBeSelectedObjects > 0)
emit aboutToBeSelectedObjectsChanged(mAboutToBeSelectedObjects);
}
Expand Down
273 changes: 197 additions & 76 deletions src/tiled/propertieswidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
#include <QKeyEvent>
#include <QMenu>
#include <QPushButton>
#include <QScopedValueRollback>
#include <QToolBar>
#include <QToolButton>
#include <QUndoStack>
Expand Down Expand Up @@ -374,8 +375,48 @@ class TilesetImageProperty : public GroupProperty
};


class CustomProperties : public GroupProperty
{
Q_OBJECT

public:
CustomProperties(QObject *parent = nullptr)
: GroupProperty(tr("Custom Properties"), parent)
{}

void setDocument(Document *document);

private:
// todo: optimize
void propertyAdded(Object *, const QString &) { refresh(); }
void propertyRemoved(Object *, const QString &) { refresh(); }
void propertyChanged(Object *, const QString &) {
if (!mUpdating)
refresh();
}
void propertiesChanged(Object *) { refresh(); }

void refresh();

Property *createProperty(const QStringList &path,
std::function<QVariant ()> get,
std::function<void (const QVariant &)> set);

void createClassMembers(const QStringList &path, GroupProperty *groupProperty,
const ClassPropertyType &classType,
std::function<QVariant ()> get);

void setPropertyValue(const QStringList &path, const QVariant &value);

bool mUpdating = false;
Document *mDocument = nullptr;
Properties mProperties;
};


PropertiesWidget::PropertiesWidget(QWidget *parent)
: QWidget{parent}
, mCustomProperties(new CustomProperties)
, mScrollArea(new QScrollArea(this))
{
auto scrollWidget = new QWidget(mScrollArea);
Expand Down Expand Up @@ -453,6 +494,7 @@ void PropertiesWidget::setDocument(Document *document)

mDocument = document;
// mPropertyBrowser->setDocument(document);
mCustomProperties->setDocument(document);

if (document) {
connect(document, &Document::currentObjectChanged,
Expand Down Expand Up @@ -496,12 +538,12 @@ static QStringList classNamesFor(const Object &object)
}

// todo: add support for changing multiple objects
class ClassProperty : public StringProperty
class ClassNameProperty : public StringProperty
{
Q_OBJECT

public:
ClassProperty(Document *document, Object *object, QObject *parent = nullptr)
ClassNameProperty(Document *document, Object *object, QObject *parent = nullptr)
: StringProperty(tr("Class"),
[this] { return mObject->className(); },
[this] (const QString &value) {
Expand All @@ -515,7 +557,7 @@ class ClassProperty : public StringProperty
, mObject(object)
{
connect(mDocument, &Document::changed,
this, &ClassProperty::onChanged);
this, &ClassNameProperty::onChanged);
}

QWidget *createEditor(QWidget *parent) override
Expand Down Expand Up @@ -606,7 +648,7 @@ class ObjectProperties : public QObject
, mDocument(document)
, mObject(object)
{
mClassProperty = new ClassProperty(document, object, this);
mClassProperty = new ClassNameProperty(document, object, this);
}

virtual void populateEditor(VariantEditor *)
Expand Down Expand Up @@ -2071,94 +2113,173 @@ void PropertiesWidget::currentObjectChanged(Object *object)
}
}

if (mPropertiesObject)
if (mPropertiesObject) {
mPropertiesObject->populateEditor(mPropertyBrowser);
mPropertyBrowser->addProperty(mCustomProperties);
}

bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType;
bool isTileset = object && object->isPartOfTileset();
bool enabled = object && (!isTileset || editingTileset);

GroupProperty *customProperties = new GroupProperty(tr("Custom Properties"));
mPropertyBrowser->setEnabled(object);
mActionAddProperty->setEnabled(enabled);
}


void CustomProperties::setDocument(Document *document)
{
if (mDocument == document)
return;

if (mDocument)
mDocument->disconnect(this);

QMapIterator<QString, QVariant> it(object ? object->properties() : Properties());
PropertyFactory factory;
mDocument = document;

if (document) {
connect(document, &Document::currentObjectsChanged, this, &CustomProperties::refresh);

connect(document, &Document::propertyAdded, this, &CustomProperties::propertyAdded);
connect(document, &Document::propertyRemoved, this, &CustomProperties::propertyRemoved);
connect(document, &Document::propertyChanged, this, &CustomProperties::propertyChanged);
connect(document, &Document::propertiesChanged, this, &CustomProperties::propertiesChanged);
}

refresh();
}

void CustomProperties::refresh()
{
clear();

if (!mDocument || !mDocument->currentObject())
return;

mProperties = mDocument->currentObject()->properties();

QMapIterator<QString, QVariant> it(mProperties);
while (it.hasNext()) {
it.next();
const QString &name = it.key();

auto get = [=] { return mProperties.value(name); };
auto set = [=] (const QVariant &value) {
setPropertyValue({ name }, value);
};

if (auto property = createProperty({ name }, std::move(get), std::move(set)))
addProperty(property);
}
}

const auto &name = it.key();
const auto &value = it.value();

Property *property = nullptr;
auto userType = value.userType();

switch (userType) {
case QMetaType::Bool:
case QMetaType::QColor:
case QMetaType::Double:
case QMetaType::Int:
case QMetaType::QString: {
auto get = [object, name] { return object->property(name); };
auto set = [this, object, name] (const QVariant &value) {
mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, value));
Property *CustomProperties::createProperty(const QStringList &path,
std::function<QVariant ()> get,
std::function<void (const QVariant &)> set)
{
const auto value = get();
const auto type = value.userType();
const auto &name = path.last();

switch (type) {
case QMetaType::Bool:
case QMetaType::QColor:
case QMetaType::Double:
case QMetaType::Int:
case QMetaType::QString:
return PropertyFactory::createProperty(name, std::move(get), std::move(set));

default:
if (type == filePathTypeId()) {
auto getUrl = [get = std::move(get)] { return get().value<FilePath>().url; };
auto setUrl = [set = std::move(set)] (const QUrl &value) {
set(QVariant::fromValue(FilePath { value }));
};
property = factory.createProperty(name, std::move(get), std::move(set));
break;
return new UrlProperty(name, std::move(getUrl), std::move(setUrl));
}
default:
if (userType == filePathTypeId()) {
auto get = [object, name] { return object->property(name).value<FilePath>().url; };
auto set = [this, object, name](const QUrl &value) {
mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(FilePath { value })));
};
property = new UrlProperty(name, get, set);
} else if (userType == objectRefTypeId()) {
auto get = [this, object, name] {
return DisplayObjectRef(object->property(name).value<ObjectRef>(),
static_cast<MapDocument*>(mDocument));
};
auto set = [this, object, name](const DisplayObjectRef &value) {
mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, QVariant::fromValue(value.ref)));
};
property = new ObjectRefProperty(name, get, set);
} else if (userType == propertyValueId()) {
auto propertyValue = value.value<PropertyValue>();
if (auto propertyType = propertyValue.type()) {
switch (propertyType->type) {
case PropertyType::PT_Invalid:
break;
case PropertyType::PT_Class:
// todo: class values
break;
case PropertyType::PT_Enum: {
auto enumProperty = new BaseEnumProperty(
name,
[object, name] { return object->property(name).value<PropertyValue>().value.toInt(); },
[=](int value) {
mDocument->undoStack()->push(new SetProperty(mDocument, { object }, name, propertyType->wrap(value)));
});

auto enumType = static_cast<const EnumPropertyType&>(*propertyType);
enumProperty->setEnumData(enumType.values);
enumProperty->setFlags(enumType.valuesAsFlags);

property = enumProperty;
break;
}
}

if (type == objectRefTypeId()) {
auto getObjectRef = [get = std::move(get), this] {
return DisplayObjectRef(get().value<ObjectRef>(),
static_cast<MapDocument*>(mDocument));
};
auto setObjectRef = [set = std::move(set)](const DisplayObjectRef &value) {
set(QVariant::fromValue(value.ref));
};
return new ObjectRefProperty(name, std::move(getObjectRef), std::move(setObjectRef));
}

if (type == propertyValueId()) {
if (auto propertyType = value.value<PropertyValue>().type()) {
switch (propertyType->type) {
case PropertyType::PT_Invalid:
break;
case PropertyType::PT_Class: {
auto classType = static_cast<const ClassPropertyType&>(*propertyType);

auto groupProperty = new GroupProperty(name);
groupProperty->setHeader(false);

createClassMembers(path, groupProperty, classType, std::move(get));

return groupProperty;
}
case PropertyType::PT_Enum: {
auto enumProperty = new BaseEnumProperty(
name,
[get = std::move(get)] { return get().value<PropertyValue>().value.toInt(); },
[set = std::move(set), propertyType](int value) {
set(propertyType->wrap(value));
});

auto enumType = static_cast<const EnumPropertyType&>(*propertyType);
enumProperty->setEnumData(enumType.values);
enumProperty->setFlags(enumType.valuesAsFlags);

return enumProperty;
}
}
}
break;
}

if (property)
customProperties->addProperty(property);
}

mPropertyBrowser->addProperty(customProperties);
return nullptr;
}

bool editingTileset = mDocument && mDocument->type() == Document::TilesetDocumentType;
bool isTileset = object && object->isPartOfTileset();
bool enabled = object && (!isTileset || editingTileset);
void CustomProperties::createClassMembers(const QStringList &path,
GroupProperty *groupProperty,
const ClassPropertyType &classType,
std::function<QVariant ()> get)
{
// Create a sub-property for each member
QMapIterator<QString, QVariant> it(classType.members);
while (it.hasNext()) {
it.next();
const QString &name = it.key();

mPropertyBrowser->setEnabled(object);
mActionAddProperty->setEnabled(enabled);
auto childPath = path;
childPath.append(name);

auto getMember = [=] {
auto def = classType.members.value(name);
return get().value<PropertyValue>().value.toMap().value(name, def);
};
auto setMember = [=] (const QVariant &value) {
setPropertyValue(childPath, value);
};

if (auto childProperty = createProperty(childPath, std::move(getMember), std::move(setMember)))
groupProperty->addProperty(childProperty);
}
}

void CustomProperties::setPropertyValue(const QStringList &path, const QVariant &value)
{
const auto objects = mDocument->currentObjects();
if (!objects.isEmpty()) {
QScopedValueRollback<bool> updating(mUpdating, true);
mDocument->undoStack()->push(new SetProperty(mDocument, objects, path, value));
}
}

void PropertiesWidget::updateActions()
Expand Down
Loading

0 comments on commit 2d18a43

Please sign in to comment.