From 4fb96669e33c9e1e3edc1cf2e472f921ddee6484 Mon Sep 17 00:00:00 2001 From: Patrick Stewart Date: Wed, 9 Nov 2022 00:30:42 +0000 Subject: QBindable: Make ordinary Q_PROPERTYs bindable Implements an adaptor from the notification signal of a Q_PROPERTY to QBindable. The Q_PROPERTY does not need to be BINDABLE, but can still be bound or used in a binding. [ChangeLog][Core][Q_PROPERTY] Q_PROPERTYs without BINDABLE can be wrapped in QBindable to make them usable in bindings Change-Id: Id0ca5444b93a371ba8720a38f3607925d393d98a Reviewed-by: Fabian Kosmale --- src/corelib/kernel/qobject.cpp | 25 +++++ src/corelib/kernel/qobject_p.h | 3 + src/corelib/kernel/qobjectdefs_impl.h | 1 + src/corelib/kernel/qproperty.cpp | 176 ++++++++++++++++++++++++++++++++++ src/corelib/kernel/qproperty.h | 63 ++++++++++++ src/corelib/kernel/qproperty_p.h | 33 +++++++ 6 files changed, 301 insertions(+) (limited to 'src/corelib/kernel') diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index fd7439f3e2..81bcf19114 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -5358,6 +5358,31 @@ inline bool QObjectPrivate::removeConnection(QObjectPrivate::Connection *c) return true; } +/*! + \internal + + Used by QPropertyAdaptorSlotObject to get an existing instance for a property, if available + */ +QtPrivate::QPropertyAdaptorSlotObject * +QObjectPrivate::getPropertyAdaptorSlotObject(const QMetaProperty &property) +{ + if (auto conns = connections.loadRelaxed()) { + Q_Q(QObject); + const QMetaObject *metaObject = q->metaObject(); + int signal_index = methodIndexToSignalIndex(&metaObject, property.notifySignalIndex()); + auto connectionList = conns->connectionsForSignal(signal_index); + for (auto c = connectionList.first.loadRelaxed(); c; + c = c->nextConnectionList.loadRelaxed()) { + if (c->isSlotObject) { + if (auto p = QtPrivate::QPropertyAdaptorSlotObject::cast(c->slotObj, + property.propertyIndex())) + return p; + } + } + } + return nullptr; +} + /*! \class QMetaObject::Connection \inmodule QtCore Represents a handle to a signal-slot (or signal-functor) connection. diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index f82dce51f3..dbb4eb2326 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -179,6 +179,9 @@ public: virtual std::string flagsForDumping() const; + QtPrivate::QPropertyAdaptorSlotObject * + getPropertyAdaptorSlotObject(const QMetaProperty &property); + public: mutable ExtraData *extraData; // extra data set by the user // This atomic requires acquire/release semantics in a few places, diff --git a/src/corelib/kernel/qobjectdefs_impl.h b/src/corelib/kernel/qobjectdefs_impl.h index e619ced4fe..10f06609ec 100644 --- a/src/corelib/kernel/qobjectdefs_impl.h +++ b/src/corelib/kernel/qobjectdefs_impl.h @@ -361,6 +361,7 @@ namespace QtPrivate { inline bool compare(void **a) { bool ret = false; m_impl(Compare, this, nullptr, a, &ret); return ret; } inline void call(QObject *r, void **a) { m_impl(Call, this, r, a, nullptr); } + bool isImpl(ImplFn f) const { return m_impl == f; } protected: ~QSlotObjectBase() {} private: diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 6b79e27e47..742d2c5f40 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -8,6 +8,9 @@ #include #include #include +#include + +#include "qobject_p.h" QT_BEGIN_NAMESPACE @@ -1135,6 +1138,31 @@ QString QPropertyBindingError::description() const QObjectComputedProperty, {Qt Bindable Properties} */ +/*! + \fn template QBindable::QBindable(QObject *obj, const char *property) + + Constructs a QBindable for the \l Q_PROPERTY \a property on \a obj. The property must + have a notify signal but does not need to have \c BINDABLE in its \c Q_PROPERTY + definition, so even binding unaware \c {Q_PROPERTY}s can be bound or used in binding + expressions. You must use \c QBindable::value() in binding expressions instead of the + normal property \c READ function (or \c MEMBER) to enable dependency tracking if the + property is not \c BINDABLE. When binding using a lambda, you may prefer to capture the + QBindable by value to avoid the cost of calling this constructor in the binding + expression. + This constructor should not be used to implement \c BINDABLE for a Q_PROPERTY, as the + resulting Q_PROPERTY will not support dependency tracking. To make a property that is + usable directly without reading through a QBindable use \l QProperty or + \l QObjectBindableProperty. + + \sa QProperty, QObjectBindableProperty, {Qt Bindable Properties} +*/ + +/*! + \fn template QBindable::QBindable(QObject *obj, const QMetaProperty &property) + + See \c \l QBindable::QBindable(QObject *obj, const char *property) +*/ + /*! \fn template QPropertyBinding QBindable::makeBinding(const QPropertyBindingSourceLocation &location) const @@ -2386,6 +2414,154 @@ void printMetaTypeMismatch(QMetaType actual, QMetaType expected) */ QBindingStatus* getBindingStatus(QtPrivate::QBindingStatusAccessToken) { return &QT_PREPEND_NAMESPACE(bindingStatus); } +namespace PropertyAdaptorSlotObjectHelpers { +void getter(const QUntypedPropertyData *d, void *value) +{ + auto adaptor = static_cast(d); + adaptor->bindingData().registerWithCurrentlyEvaluatingBinding(); + auto mt = adaptor->metaProperty().metaType(); + mt.destruct(value); + mt.construct(value, adaptor->metaProperty().read(adaptor->object()).data()); +} + +void setter(QUntypedPropertyData *d, const void *value) +{ + auto adaptor = static_cast(d); + adaptor->bindingData().removeBinding(); + adaptor->metaProperty().write(adaptor->object(), + QVariant(adaptor->metaProperty().metaType(), value)); +} + +QUntypedPropertyBinding getBinding(const QUntypedPropertyData *d) +{ + auto adaptor = static_cast(d); + return QUntypedPropertyBinding(adaptor->bindingData().binding()); +} + +bool bindingWrapper(QMetaType type, QUntypedPropertyData *d, + QtPrivate::QPropertyBindingFunction binding, QUntypedPropertyData *temp, + void *value) +{ + auto adaptor = static_cast(d); + type.destruct(value); + type.construct(value, adaptor->metaProperty().read(adaptor->object()).data()); + if (binding.vtable->call(type, temp, binding.functor)) { + adaptor->metaProperty().write(adaptor->object(), QVariant(type, value)); + return true; + } + return false; +} + +QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding, + QPropertyBindingWrapper wrapper) +{ + auto adaptor = static_cast(d); + return adaptor->bindingData().setBinding(binding, d, nullptr, wrapper); +} + +void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer) +{ + observer->setSource(static_cast(d)->bindingData()); +} +} + +QPropertyAdaptorSlotObject::QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty &p) + : QSlotObjectBase(&impl), obj(o), metaProperty_(p) +{ +} + +void QPropertyAdaptorSlotObject::impl(int which, QSlotObjectBase *this_, QObject *r, void **a, + bool *ret) +{ + auto self = static_cast(this_); + switch (which) { + case Destroy: + delete self; + break; + case Call: + if (!self->bindingData_.hasBinding()) + self->bindingData_.notifyObservers(self); + break; + case Compare: + case NumOperations: + Q_UNUSED(r); + Q_UNUSED(a); + Q_UNUSED(ret); + break; + } +} + } // namespace QtPrivate end +QUntypedBindable::QUntypedBindable(QObject *obj, const QMetaProperty &metaProperty, + const QtPrivate::QBindableInterface *i) + : iface(i) +{ + if (!obj) + return; + + if (!metaProperty.isValid()) { + qCWarning(lcQPropertyBinding) << "QUntypedBindable: Property is not valid"; + return; + } + + if (metaProperty.isBindable()) { + *this = metaProperty.bindable(obj); + return; + } + + if (!metaProperty.hasNotifySignal()) { + qCWarning(lcQPropertyBinding) + << "QUntypedBindable: Property" << metaProperty.name() << "has no notify signal"; + return; + } + + auto metatype = iface->metaType(); + if (metaProperty.metaType() != metatype) { + qCWarning(lcQPropertyBinding) << "QUntypedBindable: Property" << metaProperty.name() + << "of type" << metaProperty.metaType().name() + << "does not match requested type" << metatype.name(); + return; + } + + // Test for name pointer equality proves it's exactly the same property + if (obj->metaObject()->property(metaProperty.propertyIndex()).name() != metaProperty.name()) { + qCWarning(lcQPropertyBinding) << "QUntypedBindable: Property" << metaProperty.name() + << "does not belong to this object"; + return; + } + + // Get existing binding data if it exists + auto adaptor = QObjectPrivate::get(obj)->getPropertyAdaptorSlotObject(metaProperty); + + if (!adaptor) { + adaptor = new QPropertyAdaptorSlotObject(obj, metaProperty); + + auto c = QObjectPrivate::connect(obj, metaProperty.notifySignalIndex(), obj, adaptor, + Qt::DirectConnection); + Q_ASSERT(c); + } + + data = adaptor; +} + +QUntypedBindable::QUntypedBindable(QObject *obj, const char *property, + const QtPrivate::QBindableInterface *i) + : QUntypedBindable( + obj, + [=]() -> QMetaProperty { + if (!obj) + return {}; + auto propertyIndex = obj->metaObject()->indexOfProperty(property); + if (propertyIndex < 0) { + qCWarning(lcQPropertyBinding) + << "QUntypedBindable: No property named" << property; + return {}; + } + return obj->metaObject()->property(propertyIndex); + }(), + i) +{ +} + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index ec7ff2e0fd..cce9e9791f 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -597,6 +597,60 @@ enum Reason { InvalidInterface, NonBindableInterface, ReadOnlyInterface }; Q_CORE_EXPORT void printUnsuitableBindableWarning(QAnyStringView prefix, Reason reason); Q_CORE_EXPORT void printMetaTypeMismatch(QMetaType actual, QMetaType expected); } + +namespace PropertyAdaptorSlotObjectHelpers { +Q_CORE_EXPORT void getter(const QUntypedPropertyData *d, void *value); +Q_CORE_EXPORT void setter(QUntypedPropertyData *d, const void *value); +Q_CORE_EXPORT QUntypedPropertyBinding getBinding(const QUntypedPropertyData *d); +Q_CORE_EXPORT bool bindingWrapper(QMetaType type, QUntypedPropertyData *d, + QtPrivate::QPropertyBindingFunction binding, + QUntypedPropertyData *temp, void *value); +Q_CORE_EXPORT QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, + const QUntypedPropertyBinding &binding, + QPropertyBindingWrapper wrapper); +Q_CORE_EXPORT void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer); + +template +bool bindingWrapper(QMetaType type, QUntypedPropertyData *d, + QtPrivate::QPropertyBindingFunction binding) +{ + struct Data : QPropertyData + { + void *data() { return &this->val; } + } temp; + return bindingWrapper(type, d, binding, &temp, temp.data()); +} + +template +QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding) +{ + return setBinding(d, binding, &bindingWrapper); +} + +template +QUntypedPropertyBinding makeBinding(const QUntypedPropertyData *d, + const QPropertyBindingSourceLocation &location) +{ + return Qt::makePropertyBinding( + [d]() -> T { + T r; + getter(d, &r); + return r; + }, + location); +} + +template +inline constexpr QBindableInterface iface = { + &getter, + &setter, + &getBinding, + &setBinding, + &makeBinding, + &setObserver, + &QMetaType::fromType, +}; +} } class QUntypedBindable @@ -609,6 +663,9 @@ protected: : data(d), iface(i) {} + Q_CORE_EXPORT QUntypedBindable(QObject* obj, const QMetaProperty &property, const QtPrivate::QBindableInterface *i); + Q_CORE_EXPORT QUntypedBindable(QObject* obj, const char* property, const QtPrivate::QBindableInterface *i); + public: constexpr QUntypedBindable() = default; template @@ -745,6 +802,12 @@ public: } } + QBindable(QObject *obj, const QMetaProperty &property) + : QUntypedBindable(obj, property, &QtPrivate::PropertyAdaptorSlotObjectHelpers::iface) {} + + QBindable(QObject *obj, const char *property) + : QUntypedBindable(obj, property, &QtPrivate::PropertyAdaptorSlotObjectHelpers::iface) {} + QPropertyBinding makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) const { return static_cast &&>(QUntypedBindable::makeBinding(location)); diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index 38380f0207..a7fb9e9a1a 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -18,8 +18,10 @@ #include #include +#include #include #include +#include #include #include @@ -898,6 +900,37 @@ QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return QPropertyObserver *QBindingObserverPtr::operator->() { return d; } +namespace QtPrivate { +class QPropertyAdaptorSlotObject : public QUntypedPropertyData, public QSlotObjectBase +{ + QPropertyBindingData bindingData_; + QObject *obj; + QMetaProperty metaProperty_; + + static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret); + + QPropertyAdaptorSlotObject(QObject *o, const QMetaProperty& p); + +public: + static QPropertyAdaptorSlotObject *cast(QSlotObjectBase *ptr, int propertyIndex) + { + if (ptr->isImpl(&QPropertyAdaptorSlotObject::impl)) { + auto p = static_cast(ptr); + if (p->metaProperty_.propertyIndex() == propertyIndex) + return p; + } + return nullptr; + } + + inline const QPropertyBindingData &bindingData() const { return bindingData_; } + inline QPropertyBindingData &bindingData() { return bindingData_; } + inline QObject *object() const { return obj; } + inline const QMetaProperty &metaProperty() const { return metaProperty_; } + + friend class QT_PREPEND_NAMESPACE(QUntypedBindable); +}; +} + QT_END_NAMESPACE #endif // QPROPERTY_P_H -- cgit v1.2.3