Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New properties framework #4045

Draft
wants to merge 78 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
2d2a3f1
Started a rewrite of the property editor
bjorn Aug 15, 2024
5179a8e
Introduced a responsive editor for QSize
bjorn Aug 21, 2024
1637962
First fully functional "Map Orientation" property
bjorn Aug 26, 2024
8113c2b
Implemented "Map Size" and "Tile Size" properties
bjorn Aug 30, 2024
e40c924
Implemented "Infinite" and "Hex Side Length" properties
bjorn Sep 2, 2024
21259d3
Implemented "Stagger Axis/Index" properties
bjorn Sep 2, 2024
0ee9d10
Implemented remaining Map properties
bjorn Sep 3, 2024
5e9cb22
Implemented custom widget for Class property
bjorn Sep 4, 2024
ad0a4dd
Made most Tileset properties functional
bjorn Sep 4, 2024
754fd17
Implemented Layer properties
bjorn Sep 4, 2024
ba5c31d
Added Image Layer and Group Layer properties
bjorn Sep 4, 2024
2f0e76a
Added properties for map objects
bjorn Sep 5, 2024
7a6de73
Implemented Tile, WangSet and WangColor properties
bjorn Sep 6, 2024
d89f757
Refactor that gets rid of most editor factories
bjorn Sep 6, 2024
ddbf1d6
Renamed ValueTypeEditorFactory to PropertyFactory
bjorn Sep 6, 2024
3211a46
Simplifying or over-complicating things
bjorn Sep 9, 2024
dc083df
Addressed various property todo items
bjorn Sep 9, 2024
c52c920
Use tool buttons for text styling in FontProperty
bjorn Sep 9, 2024
2cb3d25
Progress on various property editor features
bjorn Sep 12, 2024
09f16a6
Made a special case for the image layer repeat setting
bjorn Sep 16, 2024
e1416bd
Added support for "no label" properties, used for bool values
bjorn Sep 16, 2024
6bbae7f
Used Slider for opacity property, replacing the one in Layers view
bjorn Sep 16, 2024
3daeedc
Made the property label indentation shrink when there is very little …
bjorn Sep 16, 2024
057edb4
Implemented custom widget for allowed transformations
bjorn Sep 17, 2024
0bc17ed
Fixed an issue with VariantEditor::clear
bjorn Sep 17, 2024
82da15c
Added suffixes, support for custom enums and other progress
bjorn Sep 17, 2024
fed14c8
Added support for custom enum types that are used as flags
bjorn Sep 18, 2024
d40ab86
Allow expanding the child properties for any GroupProperty
bjorn Sep 18, 2024
ba50ef9
Added support for custom class properties
bjorn Sep 24, 2024
382e4fe
Show inherited class name as placeholder text
bjorn Sep 26, 2024
74666e1
Implemented Remove and Reset buttons for custom properties
bjorn Sep 27, 2024
c99a1e7
Added ability to add (override) an inherited property
bjorn Oct 1, 2024
75e24c3
Optimized updating of the list of custom properties
bjorn Oct 10, 2024
02eda6e
Optimized PropertyLabel to not need a QLineEdit instance
bjorn Oct 10, 2024
deb0a09
Don't display label on checkbox for boolean properties
bjorn Oct 11, 2024
b301505
Create child property widgets on-demand as they are expanded
bjorn Oct 11, 2024
82ab2cb
Handle changes in the custom property types
bjorn Oct 11, 2024
8cc0374
Collect property values from all selected objects
bjorn Oct 11, 2024
629b53c
Made layer properties apply to all selected layers
bjorn Oct 11, 2024
9e0c001
Added a custom widget for the object flipping property
bjorn Oct 11, 2024
ed94a00
Apply changes to map objects to all selected objects
bjorn Oct 11, 2024
da05ecf
Show the type of custom properties in their tool tip
bjorn Oct 14, 2024
58d7a35
Fixed the visibility of custom properties on the project
bjorn Oct 15, 2024
bd54309
Remember the expanded state of custom class properties
bjorn Oct 17, 2024
c982c20
Added context menu to custom properties
bjorn Oct 17, 2024
35a7ca1
Moved CSV layer data format up for consistency with New Map dialog
bjorn Oct 24, 2024
fae849c
Added rename action to property context menu
bjorn Oct 24, 2024
56b4deb
Spin boxes and combo boxes ignore wheel events when not focused
bjorn Oct 24, 2024
6fc6c5d
Fixed changing the compression level property
bjorn Oct 25, 2024
dd0784a
Addressed a few usability issues
bjorn Oct 25, 2024
2177fac
Made consecutive edits to map properties merge together
bjorn Oct 29, 2024
82d7b49
Used ChangeMapProperty template to reduce code duplication
bjorn Oct 29, 2024
2cabfad
Remember the expanded state for built-in property groups
bjorn Oct 31, 2024
13fe21e
Fixed initialization of SizeProperty::m_minimum
bjorn Nov 5, 2024
921bce2
Fixed flickering when rewrapping widget pairs
bjorn Nov 6, 2024
9711234
Fixed opacity slider to also ignore wheel events when not focused
bjorn Nov 6, 2024
023ba4c
Group object "Position" and "Size" properties as "Geometry"
bjorn Nov 6, 2024
a7777eb
Used the new properties widget in the Custom Types Editor
bjorn Nov 8, 2024
2b710d4
Removed now unused PropertyBrowser and CustomPropertiesHelper
bjorn Nov 8, 2024
d294ece
Used the new properties framework in the Project Properties dialog
bjorn Nov 8, 2024
e500940
Removed now unused VariantEditorFactory and VariantPropertyManager
bjorn Nov 8, 2024
d0d1ff5
Removed the QtPropertyBrowser solution
bjorn Nov 8, 2024
66ec4aa
Allow using Tab to change focus in the Properties view
bjorn Nov 11, 2024
8c767d0
Fixed some issues when changing map properties
bjorn Nov 11, 2024
ccf704d
Focus new custom properties or class members when adding them
bjorn Nov 11, 2024
5fefeba
Made Add Property action use inline widgets
bjorn Nov 13, 2024
b6e7510
Fixed updating of layer names in Properties view
bjorn Nov 15, 2024
9380587
Added clear button to the editor for color properties
bjorn Nov 15, 2024
d1ddc01
Use VariantEditor directly for ChildrenOnly properties
bjorn Nov 21, 2024
9b00402
Disable custom property editing for tileset objects in map editor
bjorn Nov 21, 2024
eb3eb10
Made custom properties selectable
bjorn Nov 21, 2024
681d4ea
Renamed varianteditor.{h,cpp} to propertiesview.{h,cpp}
bjorn Nov 29, 2024
edc9dac
Fixed color widget and reset button alignment
bjorn Dec 2, 2024
b7c230a
Don't copy an empty array when no properties are selected
bjorn Dec 3, 2024
8a36db5
Select pasted custom properties and expand the group
bjorn Dec 4, 2024
73e6373
Add custom property when pressing Enter with type combo focused
bjorn Dec 5, 2024
8eb447e
Moved the context menu implementation back to PropertiesWidget
bjorn Dec 11, 2024
754b2a5
Make sure the checkbox label remains readable when selected
bjorn Dec 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Simplifying or over-complicating things
* Removed EditorFactory, EnumEditorFactory, AbstractProperty,
  ValueProperty and GetSetProperty.

* EnumProperty now derives from IntProperty and retrieves the enum
  meta-data based on its template argument.

* Use the typed properties to avoid QVariant when synchronizing between
  the created editor.

For the built-in properties, this definitely simplifies things. But it
remains to be seen how custom properties are best handled.

Also due to the lack of registering editor factories, QObjectProperty
is now broken for enums (but it's unused).
bjorn committed Oct 24, 2024
commit 3211a461fd91ef60049bc9768d44353cc71c8e52
767 changes: 377 additions & 390 deletions src/tiled/propertieswidget.cpp

Large diffs are not rendered by default.

8 changes: 0 additions & 8 deletions src/tiled/propertieswidget.h
Original file line number Diff line number Diff line change
@@ -22,16 +22,12 @@

#include <QWidget>

#include <memory>

namespace Tiled {

class Object;

class Document;
class EditorFactory;
class ObjectProperties;
class PropertyFactory;
class VariantEditor;

/**
@@ -63,9 +59,6 @@ public slots:
void keyPressEvent(QKeyEvent *event) override;

private:
void registerEditorFactories();
void registerEditorFactory(int type, std::unique_ptr<EditorFactory> factory);

void currentObjectChanged(Object *object);
void updateActions();

@@ -84,7 +77,6 @@ public slots:
Document *mDocument = nullptr;
ObjectProperties *mPropertiesObject = nullptr;
VariantEditor *mPropertyBrowser;
std::unique_ptr<PropertyFactory> mPropertyFactory;
QAction *mActionAddProperty;
QAction *mActionRemoveProperty;
QAction *mActionRenameProperty;
194 changes: 48 additions & 146 deletions src/tiled/varianteditor.cpp
Original file line number Diff line number Diff line change
@@ -36,45 +36,19 @@
#include <QResizeEvent>
#include <QSpacerItem>
#include <QSpinBox>
#include <QStringListModel>

namespace Tiled {

AbstractProperty::AbstractProperty(const QString &name,
EditorFactory *editorFactory,
QObject *parent)
: Property(name, parent)
, m_editorFactory(editorFactory)
{}

QWidget *AbstractProperty::createEditor(QWidget *parent)
{
return m_editorFactory ? m_editorFactory->createEditor(this, parent)
: nullptr;
}


GetSetProperty::GetSetProperty(const QString &name,
std::function<QVariant ()> get,
std::function<void (const QVariant &)> set,
EditorFactory *editorFactory,
QObject *parent)
: AbstractProperty(name, editorFactory, parent)
, m_get(std::move(get))
, m_set(std::move(set))
{}


QWidget *StringProperty::createEditor(QWidget *parent)
{
auto editor = new QLineEdit(parent);
auto syncEditor = [=] {
editor->setText(m_get());
editor->setText(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &QLineEdit::textEdited, this, m_set);
QObject::connect(editor, &QLineEdit::textEdited, this, &StringProperty::setValue);

return editor;
}
@@ -85,12 +59,12 @@ QWidget *UrlProperty::createEditor(QWidget *parent)
editor->setFilter(m_filter);

auto syncEditor = [=] {
editor->setFileUrl(m_get());
editor->setFileUrl(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &FileEdit::fileUrlChanged, this, m_set);
QObject::connect(editor, &FileEdit::fileUrlChanged, this, &UrlProperty::setValue);

return editor;
}
@@ -100,13 +74,13 @@ QWidget *IntProperty::createEditor(QWidget *parent)
auto editor = new SpinBox(parent);
auto syncEditor = [=] {
const QSignalBlocker blocker(editor);
editor->setValue(m_get());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, qOverload<int>(&SpinBox::valueChanged),
this, m_set);
this, &IntProperty::setValue);

return editor;
}
@@ -118,13 +92,13 @@ QWidget *FloatProperty::createEditor(QWidget *parent)

auto syncEditor = [=] {
const QSignalBlocker blocker(editor);
editor->setValue(m_get());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, qOverload<double>(&DoubleSpinBox::valueChanged),
this, m_set);
this, &FloatProperty::setValue);

return editor;
}
@@ -134,7 +108,7 @@ QWidget *BoolProperty::createEditor(QWidget *parent)
auto editor = new QCheckBox(parent);
auto syncEditor = [=] {
const QSignalBlocker blocker(editor);
bool checked = m_get();
bool checked = value();
editor->setChecked(checked);
editor->setText(checked ? tr("On") : tr("Off"));
};
@@ -143,7 +117,7 @@ QWidget *BoolProperty::createEditor(QWidget *parent)
QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &QCheckBox::toggled, this, [=](bool checked) {
editor->setText(checked ? QObject::tr("On") : QObject::tr("Off"));
m_set(checked);
setValue(checked);
});

return editor;
@@ -154,14 +128,14 @@ QWidget *PointProperty::createEditor(QWidget *parent)
auto editor = new PointEdit(parent);
auto syncEditor = [this, editor] {
const QSignalBlocker blocker(editor);
editor->setValue(m_get());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &PointEdit::valueChanged, this,
[this, editor] {
m_set(editor->value());
setValue(editor->value());
});

return editor;
@@ -172,14 +146,14 @@ QWidget *PointFProperty::createEditor(QWidget *parent)
auto editor = new PointFEdit(parent);
auto syncEditor = [this, editor] {
const QSignalBlocker blocker(editor);
editor->setValue(this->value().toPointF());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &PointFEdit::valueChanged, this,
[this, editor] {
this->setValue(editor->value());
this->setVariantValue(editor->value());
});

return editor;
@@ -190,14 +164,14 @@ QWidget *SizeProperty::createEditor(QWidget *parent)
auto editor = new SizeEdit(parent);
auto syncEditor = [this, editor] {
const QSignalBlocker blocker(editor);
editor->setValue(m_get());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &SizeEdit::valueChanged, this,
[this, editor] {
m_set(editor->value());
setValue(editor->value());
});

return editor;
@@ -208,14 +182,14 @@ QWidget *SizeFProperty::createEditor(QWidget *parent)
auto editor = new SizeFEdit(parent);
auto syncEditor = [this, editor] {
const QSignalBlocker blocker(editor);
editor->setValue(this->value().toSizeF());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &SizeFEdit::valueChanged, this,
[this, editor] {
this->setValue(editor->value());
setValue(editor->value());
});

return editor;
@@ -226,15 +200,15 @@ QWidget *RectProperty::createEditor(QWidget *parent)
auto editor = new RectEdit(parent);
auto syncEditor = [this, editor] {
const QSignalBlocker blocker(editor);
editor->setValue(this->value().toRect());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &RectEdit::valueChanged, this,
[this, editor] {
this->setValue(editor->value());
});
setValue(editor->value());
});

return editor;
}
@@ -244,14 +218,14 @@ QWidget *RectFProperty::createEditor(QWidget *parent)
auto editor = new RectFEdit(parent);
auto syncEditor = [this, editor] {
const QSignalBlocker blocker(editor);
editor->setValue(this->value().toRectF());
editor->setValue(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &RectFEdit::valueChanged, this,
[this, editor] {
this->setValue(editor->value());
setValue(editor->value());
});

return editor;
@@ -263,14 +237,14 @@ QWidget *ColorProperty::createEditor(QWidget *parent)
auto editor = new ColorButton(parent);
auto syncEditor = [=] {
const QSignalBlocker blocker(editor);
editor->setColor(this->value().value<QColor>());
editor->setColor(value());
};
syncEditor();

QObject::connect(this, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, &ColorButton::colorChanged, this,
[this, editor] {
this->setValue(editor->color());
setValue(editor->color());
});

return editor;
@@ -301,7 +275,7 @@ QWidget *FontProperty::createEditor(QWidget *parent)
layout->addWidget(kerningCheckBox);

auto syncEditor = [=] {
const auto font = this->value().value<QFont>();
const auto font = value();
const QSignalBlocker fontBlocker(fontComboBox);
const QSignalBlocker sizeBlocker(sizeSpinBox);
const QSignalBlocker boldBlocker(boldCheckBox);
@@ -326,7 +300,7 @@ QWidget *FontProperty::createEditor(QWidget *parent)
font.setUnderline(underlineCheckBox->isChecked());
font.setStrikeOut(strikeoutCheckBox->isChecked());
font.setKerning(kerningCheckBox->isChecked());
this->setValue(font);
setValue(font);
};

syncEditor();
@@ -343,7 +317,7 @@ QWidget *FontProperty::createEditor(QWidget *parent)
return editor;
}

QWidget *AlignmentProperty::createEditor(QWidget *parent)
QWidget *QtAlignmentProperty::createEditor(QWidget *parent)
{
auto editor = new QWidget(parent);
auto layout = new QGridLayout(editor);
@@ -374,15 +348,14 @@ QWidget *AlignmentProperty::createEditor(QWidget *parent)
auto syncEditor = [=] {
const QSignalBlocker horizontalBlocker(horizontalComboBox);
const QSignalBlocker verticalBlocker(verticalComboBox);
const auto alignment = this->value().value<Qt::Alignment>();
const auto alignment = value();
horizontalComboBox->setCurrentIndex(horizontalComboBox->findData(static_cast<int>(alignment & Qt::AlignHorizontal_Mask)));
verticalComboBox->setCurrentIndex(verticalComboBox->findData(static_cast<int>(alignment & Qt::AlignVertical_Mask)));
};

auto syncProperty = [=] {
const Qt::Alignment alignment(horizontalComboBox->currentData().toInt() |
verticalComboBox->currentData().toInt());
this->setValue(QVariant::fromValue(alignment));
setValue(Qt::Alignment(horizontalComboBox->currentData().toInt() |
verticalComboBox->currentData().toInt()));
};

syncEditor();
@@ -395,39 +368,6 @@ QWidget *AlignmentProperty::createEditor(QWidget *parent)
}


ValueProperty::ValueProperty(const QString &name,
const QVariant &value,
EditorFactory *editorFactory,
QObject *parent)
: AbstractProperty(name, editorFactory, parent)
, m_value(value)
{}

void ValueProperty::setValue(const QVariant &value)
{
if (m_value != value) {
m_value = value;
emit valueChanged();
}
}


EnumProperty::EnumProperty(const QString &name,
QObject *parent)
: AbstractProperty(name, &m_editorFactory, parent)
{}

void EnumProperty::setEnumNames(const QStringList &enumNames)
{
m_editorFactory.setEnumNames(enumNames);
}

void EnumProperty::setEnumValues(const QList<int> &enumValues)
{
m_editorFactory.setEnumValues(enumValues);
}


VariantEditor::VariantEditor(QWidget *parent)
: QScrollArea(parent)
{
@@ -567,70 +507,45 @@ QWidget *VariantEditor::createEditor(Property *property)
}


EnumEditorFactory::EnumEditorFactory(const QStringList &enumNames,
const QList<int> &enumValues)
: m_enumNamesModel(enumNames)
, m_enumValues(enumValues)
{}

void EnumEditorFactory::setEnumNames(const QStringList &enumNames)
{
m_enumNamesModel.setStringList(enumNames);
}

void EnumEditorFactory::setEnumIcons(const QMap<int, QIcon> &enumIcons)
{
// todo: add support for showing these icons in the QComboBox
m_enumIcons = enumIcons;
}

void EnumEditorFactory::setEnumValues(const QList<int> &enumValues)
{
m_enumValues = enumValues;
}

QWidget *EnumEditorFactory::createEditor(Property *property, QWidget *parent)
QWidget *createEnumEditor(IntProperty *property, const EnumData &enumData, QWidget *parent)
{
auto editor = new QComboBox(parent);
// This allows the combo box to shrink horizontally.
editor->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
editor->setModel(&m_enumNamesModel);

auto syncEditor = [property, editor, this] {
for (qsizetype i = 0; i < enumData.names.size(); ++i) {
auto value = enumData.values.isEmpty() ? i : enumData.values.value(i);
editor->addItem(enumData.icons[value],
enumData.names[i],
value);
}

auto syncEditor = [property, editor] {
const QSignalBlocker blocker(editor);
if (m_enumValues.isEmpty())
editor->setCurrentIndex(property->value().toInt());
else
editor->setCurrentIndex(m_enumValues.indexOf(property->value().toInt()));
editor->setCurrentIndex(editor->findData(property->value()));
};
syncEditor();

QObject::connect(property, &Property::valueChanged, editor, syncEditor);
QObject::connect(editor, qOverload<int>(&QComboBox::currentIndexChanged), property,
[property, this](int index) {
property->setValue(m_enumValues.isEmpty() ? index : m_enumValues.at(index));
[editor, property] {
property->setValue(editor->currentData().toInt());
});

return editor;
}


void PropertyFactory::registerEditorFactory(int type, std::unique_ptr<EditorFactory> factory)
{
m_factories[type] = std::move(factory);
}

Property *PropertyFactory::createQObjectProperty(QObject *qObject,
const char *name,
const char *propertyName,
const QString &displayName)
{
auto metaObject = qObject->metaObject();
auto propertyIndex = metaObject->indexOfProperty(name);
auto propertyIndex = metaObject->indexOfProperty(propertyName);
if (propertyIndex < 0)
return nullptr;

auto metaProperty = metaObject->property(propertyIndex);
auto property = createProperty(displayName.isEmpty() ? QString::fromUtf8(name)
auto property = createProperty(displayName.isEmpty() ? QString::fromUtf8(propertyName)
: displayName,
[=] {
return metaProperty.read(qObject);
@@ -655,15 +570,6 @@ Property *PropertyFactory::createQObjectProperty(QObject *qObject,
return property;
}

ValueProperty *PropertyFactory::createProperty(const QString &name,
const QVariant &value)
{
auto f = m_factories.find(value.userType());
return new ValueProperty(name, value,
f != m_factories.end() ? f->second.get()
: nullptr);
}

template<typename PropertyClass>
Property *createTypedProperty(const QString &name,
std::function<QVariant ()> get,
@@ -708,14 +614,10 @@ Property *PropertyFactory::createProperty(const QString &name,
return createTypedProperty<SizeFProperty>(name, get, set);
default:
if (type == qMetaTypeId<Qt::Alignment>())
return createTypedProperty<AlignmentProperty>(name, get, set);
return createTypedProperty<QtAlignmentProperty>(name, get, set);
}

// Fall back to registered factories approach (still used for enums)
auto f = m_factories.find(get().userType());
return new GetSetProperty(name, get, set,
f != m_factories.end() ? f->second.get()
: nullptr);
return nullptr;
}

} // namespace Tiled
203 changes: 62 additions & 141 deletions src/tiled/varianteditor.h
Original file line number Diff line number Diff line change
@@ -28,9 +28,6 @@
#include <QVariant>
#include <QWidget>

#include <memory>
#include <unordered_map>

class QGridLayout;

namespace Tiled {
@@ -43,7 +40,7 @@ class Property : public QObject
Q_OBJECT
Q_PROPERTY(QString name READ name CONSTANT)
Q_PROPERTY(QString toolTip READ toolTip WRITE setToolTip NOTIFY toolTipChanged)
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)
Q_PROPERTY(QVariant value READ variantValue WRITE setVariantValue NOTIFY valueChanged)
Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY enabledChanged)

public:
@@ -72,8 +69,8 @@ class Property : public QObject
}
}

virtual QVariant value() const = 0;
virtual void setValue(const QVariant &value) = 0;
virtual QVariant variantValue() const = 0;
virtual void setVariantValue(const QVariant &value) = 0;

virtual QWidget *createEditor(QWidget *parent) = 0;

@@ -106,18 +103,21 @@ class PropertyTemplate : public Property
, m_set(std::move(set))
{}

QVariant value() const override
Type value() const { return m_get(); }
void setValue(const Type &value) { m_set(value); }

QVariant variantValue() const override
{
return QVariant::fromValue(m_get());
}

void setValue(const QVariant &value) override
void setVariantValue(const QVariant &value) override
{
if (m_set)
m_set(value.value<Type>());
}

protected:
private:
std::function<Type()> m_get;
std::function<void(const Type&)> m_set;
};
@@ -207,115 +207,12 @@ struct FontProperty : PropertyTemplate<QFont>
QWidget *createEditor(QWidget *parent) override;
};

struct AlignmentProperty : PropertyTemplate<Qt::Alignment>
struct QtAlignmentProperty : PropertyTemplate<Qt::Alignment>
{
using PropertyTemplate::PropertyTemplate;
QWidget *createEditor(QWidget *parent) override;
};

/**
* An editor factory is responsible for creating an editor widget for a given
* property. It can be used to share the configuration of editor widgets
* between different properties.
*/
class EditorFactory
{
Q_DECLARE_TR_FUNCTIONS(EditorFactory)

public:
virtual QWidget *createEditor(Property *property, QWidget *parent) = 0;
};

/**
* An editor factory that creates a combo box for enum properties.
*/
class EnumEditorFactory : public EditorFactory
{
public:
EnumEditorFactory(const QStringList &enumNames = {},
const QList<int> &enumValues = {});

void setEnumNames(const QStringList &enumNames);
void setEnumIcons(const QMap<int, QIcon> &enumIcons);
void setEnumValues(const QList<int> &enumValues);

QWidget *createEditor(Property *property, QWidget *parent) override;

private:
QStringListModel m_enumNamesModel;
QMap<int, QIcon> m_enumIcons;
QList<int> m_enumValues;
};

/**
* A property that uses an editor factory to create its editor, but does not
* store a value itself.
*
* The property does not take ownership of the editor factory.
*/
class AbstractProperty : public Property
{
Q_OBJECT

public:
AbstractProperty(const QString &name,
EditorFactory *editorFactory,
QObject *parent = nullptr);

QWidget *createEditor(QWidget *parent) override;

private:
EditorFactory *m_editorFactory;
};

/**
* A property that uses the given functions to get and set the value and uses
* an editor factory to create its editor.
*
* The property does not take ownership of the editor factory.
*/
class GetSetProperty : public AbstractProperty
{
Q_OBJECT

public:
GetSetProperty(const QString &name,
std::function<QVariant()> get,
std::function<void(const QVariant&)> set,
EditorFactory *editorFactory,
QObject *parent = nullptr);

QVariant value() const override { return m_get(); }
void setValue(const QVariant &value) override { m_set(value); }

private:
std::function<QVariant()> m_get;
std::function<void(const QVariant&)> m_set;
};

/**
* A property that stores a value of a given type and uses an editor factory to
* create its editor.
*
* The property does not take ownership of the editor factory.
*/
class ValueProperty : public AbstractProperty
{
Q_OBJECT

public:
ValueProperty(const QString &name,
const QVariant &value,
EditorFactory *editorFactory,
QObject *parent = nullptr);

QVariant value() const override { return m_value; }
void setValue(const QVariant &value) override;

private:
QVariant m_value;
};


/**
* A property factory that instantiates the appropriate property type based on
@@ -326,57 +223,81 @@ class PropertyFactory
public:
PropertyFactory() = default;

/**
* Register an editor factory for a given type.
*
* When there is already an editor factory registered for the given type,
* it will be replaced.
*/
void registerEditorFactory(int type, std::unique_ptr<EditorFactory> factory);

/**
* Creates a property that wraps a QObject property.
*/
Property *createQObjectProperty(QObject *qObject,
const char *name,
const char *propertyName,
const QString &displayName = {});

/**
* Creates a property with the given name and value. The property will use
* the editor factory registered for the type of the value.
*/
ValueProperty *createProperty(const QString &name, const QVariant &value);

/**
* Creates a property with the given name and get/set functions. The
* property will use the editor factory registered for the type of the
* value.
* value type determines the kind of property that will be created.
*/
Property *createProperty(const QString &name,
std::function<QVariant()> get,
std::function<void(const QVariant&)> set);
};

private:
std::unordered_map<int, std::unique_ptr<EditorFactory>> m_factories;
struct EnumData
{
EnumData(const QStringList &names,
const QList<int> &values = {},
const QMap<int, QIcon> &icons = {})
: names(names)
, values(values)
, icons(icons)
{}

QStringList names;
QList<int> values; // optional
QMap<int, QIcon> icons; // optional
};

template<typename>
EnumData enumData()
{
return {{}};
}

QWidget *createEnumEditor(IntProperty *property,
const EnumData &enumData,
QWidget *parent);

/**
* A property that wraps an enum value and uses an editor factory to create
* its editor.
* A property that wraps an enum value and creates a combo box based on the
* given EnumData.
*/
class EnumProperty : public AbstractProperty
template <typename Enum>
class EnumProperty : public IntProperty
{
Q_OBJECT

public:
EnumProperty(const QString &name,
QObject *parent = nullptr);
std::function<Enum()> get,
std::function<void(Enum)> set,
QObject *parent = nullptr)
: IntProperty(name,
[get] {
return static_cast<int>(get());
},
set ? [set](const int &value){ set(static_cast<Enum>(value)); }
: std::function<void(const int&)>(),
parent)
, m_enumData(enumData<Enum>())
{}

void setEnumNames(const QStringList &enumNames);
void setEnumValues(const QList<int> &enumValues);
void setEnumData(const EnumData &enumData)
{
m_enumData = enumData;
}

QWidget *createEditor(QWidget *parent) override
{
return createEnumEditor(this, m_enumData, parent);
}

private:
EnumEditorFactory m_editorFactory;
EnumData m_enumData;
};