Enable generic property bindings to QProperty<T>

A generic binding allows implementing the binding function in a way that
enables the QML engine to run binding scripts and convert the V4::Value
into a QVariant and then assign the value to the property with the help
of QMetaType::construct.

Change-Id: Id4807be92eee7e3501908e6c5e4c861cfcb7772a
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Simon Hausmann 2020-01-03 10:15:18 +01:00
parent 3c2aa878dc
commit 1fcce51053
5 changed files with 118 additions and 13 deletions

View File

@ -610,6 +610,19 @@ QPropertyBindingSourceLocation QPropertyBindingError::location() const
this property is read.
*/
/*!
\fn template <typename T> QPropertyBinding<T> bool QProperty<T>::setBinding(const QUntypedPropertyBinding &newBinding)
\overload
Associates the value of this property with the provided \a newBinding
expression. The first time the property value is read, the binding is evaluated.
Whenever a dependency of the binding changes, the binding will be re-evaluated
the next time the value of this property is read.
Returns true if the type of this property is the same as the type the binding
function returns; false otherwise.
*/
/*!
\fn template <typename T> QPropertyBinding<T> QProperty<T>::binding() const

View File

@ -43,6 +43,7 @@
#include <QtCore/qglobal.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QString>
#include <QtCore/qmetatype.h>
#include <functional>
#include <type_traits>
#include <variant>
@ -120,10 +121,10 @@ public:
using BindingEvaluationResult = std::variant<bool, QPropertyBindingError>;
// returns true if value changed, false if the binding evaluation lead to the same value as the property
// already has.
using BindingEvaluationFunction = std::function<BindingEvaluationResult(int version, void *propertyStoragePtr)>;
using BindingEvaluationFunction = std::function<BindingEvaluationResult(const QMetaType &metaType, void *dataPtr)>;
QUntypedPropertyBinding();
QUntypedPropertyBinding(BindingEvaluationFunction function, const QPropertyBindingSourceLocation &location);
QUntypedPropertyBinding(const QMetaType &metaType, BindingEvaluationFunction function, const QPropertyBindingSourceLocation &location);
QUntypedPropertyBinding(QUntypedPropertyBinding &&other);
QUntypedPropertyBinding(const QUntypedPropertyBinding &other);
QUntypedPropertyBinding &operator=(const QUntypedPropertyBinding &other);
@ -134,6 +135,8 @@ public:
QPropertyBindingError error() const;
QMetaType valueMetaType() const;
private:
explicit QUntypedPropertyBinding(const QPropertyBindingPrivatePtr &priv);
friend class QtPrivate::QPropertyBase;
@ -149,15 +152,18 @@ class QPropertyBinding : public QUntypedPropertyBinding
struct BindingAdaptor
{
Functor impl;
QUntypedPropertyBinding::BindingEvaluationResult operator()(int /*version*/, void *propertyStoragePtr)
QUntypedPropertyBinding::BindingEvaluationResult operator()(const QMetaType &/*metaType*/, void *dataPtr)
{
std::variant<PropertyType, QPropertyBindingError> result(impl());
if (auto errorPtr = std::get_if<QPropertyBindingError>(&result))
return *errorPtr;
if (auto valuePtr = std::get_if<PropertyType>(&result)) {
auto storagePtr = reinterpret_cast<QtPrivate::QPropertyValueStorage<PropertyType>*>(propertyStoragePtr);
return storagePtr->setValueAndReturnTrueIfChanged(std::move(*valuePtr));
PropertyType *propertyPtr = reinterpret_cast<PropertyType *>(dataPtr);
if (*propertyPtr == *valuePtr)
return false;
*propertyPtr = std::move(*valuePtr);
return true;
}
return false;
@ -169,7 +175,7 @@ public:
template<typename Functor>
QPropertyBinding(Functor &&f, const QPropertyBindingSourceLocation &location)
: QUntypedPropertyBinding(BindingAdaptor<Functor>{std::forward<Functor>(f)}, location)
: QUntypedPropertyBinding(QMetaType::fromType<PropertyType>(), BindingAdaptor<Functor>{std::forward<Functor>(f)}, location)
{}
QPropertyBinding(const QProperty<PropertyType> &property)
@ -212,6 +218,8 @@ template <typename T>
class QProperty
{
public:
using value_type = T;
QProperty() = default;
explicit QProperty(const T &initialValue) : d(initialValue) {}
explicit QProperty(T &&initialValue) : d(std::move(initialValue)) {}
@ -299,6 +307,15 @@ public:
return oldBinding;
}
bool setBinding(const QUntypedPropertyBinding &newBinding)
{
if (newBinding.valueMetaType().id() != qMetaTypeId<T>())
return false;
d.priv.setBinding(newBinding, &d);
notify();
return true;
}
#ifndef Q_CLANG_QDOC
template <typename Functor>
QPropertyBinding<T> setBinding(Functor f,

View File

@ -79,22 +79,37 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged()
QScopedValueRollback<bool> updateGuard(updating, true);
BindingEvaluationState evaluationFrame(this);
QUntypedPropertyBinding::BindingEvaluationResult result;
bool changed = false;
auto result = evaluationFunction(/*version*/1, propertyDataPtr);
if (auto changedPtr = std::get_if<bool>(&result))
changed = *changedPtr;
else if (auto errorPtr = std::get_if<QPropertyBindingError>(&result))
if (metaType.id() == QMetaType::Bool) {
auto propertyPtr = reinterpret_cast<QPropertyBase *>(propertyDataPtr);
bool temp = propertyPtr->extraBit();
result = evaluationFunction(metaType, &temp);
if (auto changedPtr = std::get_if<bool>(&result)) {
changed = *changedPtr;
if (changed)
propertyPtr->setExtraBit(temp);
}
} else {
result = evaluationFunction(metaType, propertyDataPtr);
if (auto changedPtr = std::get_if<bool>(&result))
changed = *changedPtr;
}
if (auto errorPtr = std::get_if<QPropertyBindingError>(&result))
error = std::move(*errorPtr);
dirty = false;
return changed;
}
QUntypedPropertyBinding::QUntypedPropertyBinding() = default;
QUntypedPropertyBinding::QUntypedPropertyBinding(QUntypedPropertyBinding::BindingEvaluationFunction function,
QUntypedPropertyBinding::QUntypedPropertyBinding(const QMetaType &metaType, QUntypedPropertyBinding::BindingEvaluationFunction function,
const QPropertyBindingSourceLocation &location)
{
d = new QPropertyBindingPrivate(std::move(function), std::move(location));
d = new QPropertyBindingPrivate(metaType, std::move(function), std::move(location));
}
QUntypedPropertyBinding::QUntypedPropertyBinding(QUntypedPropertyBinding &&other)
@ -140,4 +155,11 @@ QPropertyBindingError QUntypedPropertyBinding::error() const
return d->error;
}
QMetaType QUntypedPropertyBinding::valueMetaType() const
{
if (!d)
return QMetaType();
return d->metaType;
}
QT_END_NAMESPACE

View File

@ -74,13 +74,16 @@ struct QPropertyBindingPrivate : public QSharedData
QPropertyBindingSourceLocation location;
QPropertyBindingError error;
QMetaType metaType;
bool dirty = false;
bool updating = false;
QPropertyBindingPrivate(QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction,
QPropertyBindingPrivate(const QMetaType &metaType, QUntypedPropertyBinding::BindingEvaluationFunction evaluationFunction,
const QPropertyBindingSourceLocation &location)
: evaluationFunction(std::move(evaluationFunction))
, location(location)
, metaType(metaType)
{}
virtual ~QPropertyBindingPrivate();

View File

@ -65,6 +65,8 @@ private slots:
void changePropertyFromWithinChangeHandlerThroughDependency();
void changePropertyFromWithinChangeHandler2();
void settingPropertyValueDoesRemoveBinding();
void genericPropertyBinding();
void genericPropertyBindingBool();
};
void tst_QProperty::functorBinding()
@ -610,6 +612,54 @@ void tst_QProperty::settingPropertyValueDoesRemoveBinding()
QVERIFY(property.binding().isNull());
}
void tst_QProperty::genericPropertyBinding()
{
QProperty<int> property;
{
QUntypedPropertyBinding doubleBinding(QMetaType::fromType<double>(),
[](const QMetaType &, void *) -> bool {
Q_ASSERT(false);
return false;
}, QPropertyBindingSourceLocation());
QVERIFY(!property.setBinding(doubleBinding));
}
QUntypedPropertyBinding intBinding(QMetaType::fromType<int>(),
[](const QMetaType &metaType, void *dataPtr) -> bool {
Q_ASSERT(metaType.id() == qMetaTypeId<int>());
int *intPtr = reinterpret_cast<int*>(dataPtr);
if (*intPtr == 100)
return false;
*intPtr = 100;
return true;
}, QPropertyBindingSourceLocation());
QVERIFY(property.setBinding(intBinding));
QCOMPARE(property.value(), 100);
}
void tst_QProperty::genericPropertyBindingBool()
{
QProperty<bool> property;
QVERIFY(!property.value());
QUntypedPropertyBinding boolBinding(QMetaType::fromType<bool>(),
[](const QMetaType &, void *dataPtr) -> bool {
auto boolPtr = reinterpret_cast<bool *>(dataPtr);
if (*boolPtr)
return false;
*boolPtr = true;
return true;
}, QPropertyBindingSourceLocation());
QVERIFY(property.setBinding(boolBinding));
QVERIFY(property.value());
}
QTEST_MAIN(tst_QProperty);
#include "tst_qproperty.moc"