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 <fabian.kosmale@qt.io>
This commit is contained in:
parent
84d4b21f69
commit
4fb96669e3
@ -247,4 +247,18 @@
|
|||||||
be called for the current value of the property, register your callback using
|
be called for the current value of the property, register your callback using
|
||||||
subscribe() instead.
|
subscribe() instead.
|
||||||
|
|
||||||
|
\section1 Interaction with Q_PROPERTYs
|
||||||
|
|
||||||
|
A \l {The Property System}{Q_PROPERTY} that defines \c BINDABLE can be bound and
|
||||||
|
used in binding expressions. You can implement such properties using \l {QProperty},
|
||||||
|
\l {QObjectBindableProperty}, or \l {QObjectComputedProperty}.
|
||||||
|
|
||||||
|
Q_PROPERTYs without \c BINDABLE can also be bound and be used in binding expressions,
|
||||||
|
as long as they define a \c NOTIFY signal. You must wrap the property in a \l QBindable
|
||||||
|
using the \c {QBindable(QObject* obj, const char* property)} constructor. Then, the
|
||||||
|
property can be bound using \c \l QBindable::setBinding() or used in a binding
|
||||||
|
expression via \c \l QBindable::value(). 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.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
@ -5358,6 +5358,31 @@ inline bool QObjectPrivate::removeConnection(QObjectPrivate::Connection *c)
|
|||||||
return true;
|
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
|
/*! \class QMetaObject::Connection
|
||||||
\inmodule QtCore
|
\inmodule QtCore
|
||||||
Represents a handle to a signal-slot (or signal-functor) connection.
|
Represents a handle to a signal-slot (or signal-functor) connection.
|
||||||
|
@ -179,6 +179,9 @@ public:
|
|||||||
|
|
||||||
virtual std::string flagsForDumping() const;
|
virtual std::string flagsForDumping() const;
|
||||||
|
|
||||||
|
QtPrivate::QPropertyAdaptorSlotObject *
|
||||||
|
getPropertyAdaptorSlotObject(const QMetaProperty &property);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
mutable ExtraData *extraData; // extra data set by the user
|
mutable ExtraData *extraData; // extra data set by the user
|
||||||
// This atomic requires acquire/release semantics in a few places,
|
// This atomic requires acquire/release semantics in a few places,
|
||||||
|
@ -361,6 +361,7 @@ namespace QtPrivate {
|
|||||||
|
|
||||||
inline bool compare(void **a) { bool ret = false; m_impl(Compare, this, nullptr, a, &ret); return ret; }
|
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); }
|
inline void call(QObject *r, void **a) { m_impl(Call, this, r, a, nullptr); }
|
||||||
|
bool isImpl(ImplFn f) const { return m_impl == f; }
|
||||||
protected:
|
protected:
|
||||||
~QSlotObjectBase() {}
|
~QSlotObjectBase() {}
|
||||||
private:
|
private:
|
||||||
|
@ -8,6 +8,9 @@
|
|||||||
#include <QScopeGuard>
|
#include <QScopeGuard>
|
||||||
#include <QtCore/qloggingcategory.h>
|
#include <QtCore/qloggingcategory.h>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QtCore/qmetaobject.h>
|
||||||
|
|
||||||
|
#include "qobject_p.h"
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
@ -1135,6 +1138,31 @@ QString QPropertyBindingError::description() const
|
|||||||
QObjectComputedProperty, {Qt Bindable Properties}
|
QObjectComputedProperty, {Qt Bindable Properties}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*!
|
||||||
|
\fn template<typename T> QBindable<T>::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<typename T> QBindable<T>::QBindable(QObject *obj, const QMetaProperty &property)
|
||||||
|
|
||||||
|
See \c \l QBindable::QBindable(QObject *obj, const char *property)
|
||||||
|
*/
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
\fn template<typename T> QPropertyBinding<T> QBindable<T>::makeBinding(const QPropertyBindingSourceLocation &location) const
|
\fn template<typename T> QPropertyBinding<T> QBindable<T>::makeBinding(const QPropertyBindingSourceLocation &location) const
|
||||||
|
|
||||||
@ -2386,6 +2414,154 @@ void printMetaTypeMismatch(QMetaType actual, QMetaType expected)
|
|||||||
*/
|
*/
|
||||||
QBindingStatus* getBindingStatus(QtPrivate::QBindingStatusAccessToken) { return &QT_PREPEND_NAMESPACE(bindingStatus); }
|
QBindingStatus* getBindingStatus(QtPrivate::QBindingStatusAccessToken) { return &QT_PREPEND_NAMESPACE(bindingStatus); }
|
||||||
|
|
||||||
|
namespace PropertyAdaptorSlotObjectHelpers {
|
||||||
|
void getter(const QUntypedPropertyData *d, void *value)
|
||||||
|
{
|
||||||
|
auto adaptor = static_cast<const QtPrivate::QPropertyAdaptorSlotObject *>(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<QtPrivate::QPropertyAdaptorSlotObject *>(d);
|
||||||
|
adaptor->bindingData().removeBinding();
|
||||||
|
adaptor->metaProperty().write(adaptor->object(),
|
||||||
|
QVariant(adaptor->metaProperty().metaType(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
QUntypedPropertyBinding getBinding(const QUntypedPropertyData *d)
|
||||||
|
{
|
||||||
|
auto adaptor = static_cast<const QtPrivate::QPropertyAdaptorSlotObject *>(d);
|
||||||
|
return QUntypedPropertyBinding(adaptor->bindingData().binding());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bindingWrapper(QMetaType type, QUntypedPropertyData *d,
|
||||||
|
QtPrivate::QPropertyBindingFunction binding, QUntypedPropertyData *temp,
|
||||||
|
void *value)
|
||||||
|
{
|
||||||
|
auto adaptor = static_cast<const QtPrivate::QPropertyAdaptorSlotObject *>(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<QPropertyAdaptorSlotObject *>(d);
|
||||||
|
return adaptor->bindingData().setBinding(binding, d, nullptr, wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setObserver(const QUntypedPropertyData *d, QPropertyObserver *observer)
|
||||||
|
{
|
||||||
|
observer->setSource(static_cast<const QPropertyAdaptorSlotObject *>(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<QPropertyAdaptorSlotObject *>(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
|
} // 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
|
QT_END_NAMESPACE
|
||||||
|
@ -597,6 +597,60 @@ enum Reason { InvalidInterface, NonBindableInterface, ReadOnlyInterface };
|
|||||||
Q_CORE_EXPORT void printUnsuitableBindableWarning(QAnyStringView prefix, Reason reason);
|
Q_CORE_EXPORT void printUnsuitableBindableWarning(QAnyStringView prefix, Reason reason);
|
||||||
Q_CORE_EXPORT void printMetaTypeMismatch(QMetaType actual, QMetaType expected);
|
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<typename T>
|
||||||
|
bool bindingWrapper(QMetaType type, QUntypedPropertyData *d,
|
||||||
|
QtPrivate::QPropertyBindingFunction binding)
|
||||||
|
{
|
||||||
|
struct Data : QPropertyData<T>
|
||||||
|
{
|
||||||
|
void *data() { return &this->val; }
|
||||||
|
} temp;
|
||||||
|
return bindingWrapper(type, d, binding, &temp, temp.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
QUntypedPropertyBinding setBinding(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding)
|
||||||
|
{
|
||||||
|
return setBinding(d, binding, &bindingWrapper<T>);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
QUntypedPropertyBinding makeBinding(const QUntypedPropertyData *d,
|
||||||
|
const QPropertyBindingSourceLocation &location)
|
||||||
|
{
|
||||||
|
return Qt::makePropertyBinding(
|
||||||
|
[d]() -> T {
|
||||||
|
T r;
|
||||||
|
getter(d, &r);
|
||||||
|
return r;
|
||||||
|
},
|
||||||
|
location);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline constexpr QBindableInterface iface = {
|
||||||
|
&getter,
|
||||||
|
&setter,
|
||||||
|
&getBinding,
|
||||||
|
&setBinding<T>,
|
||||||
|
&makeBinding<T>,
|
||||||
|
&setObserver,
|
||||||
|
&QMetaType::fromType<T>,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class QUntypedBindable
|
class QUntypedBindable
|
||||||
@ -609,6 +663,9 @@ protected:
|
|||||||
: data(d), iface(i)
|
: 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:
|
public:
|
||||||
constexpr QUntypedBindable() = default;
|
constexpr QUntypedBindable() = default;
|
||||||
template<typename Property>
|
template<typename Property>
|
||||||
@ -745,6 +802,12 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QBindable(QObject *obj, const QMetaProperty &property)
|
||||||
|
: QUntypedBindable(obj, property, &QtPrivate::PropertyAdaptorSlotObjectHelpers::iface<T>) {}
|
||||||
|
|
||||||
|
QBindable(QObject *obj, const char *property)
|
||||||
|
: QUntypedBindable(obj, property, &QtPrivate::PropertyAdaptorSlotObjectHelpers::iface<T>) {}
|
||||||
|
|
||||||
QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) const
|
QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) const
|
||||||
{
|
{
|
||||||
return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::makeBinding(location));
|
return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::makeBinding(location));
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
#include <private/qglobal_p.h>
|
#include <private/qglobal_p.h>
|
||||||
#include <qproperty.h>
|
#include <qproperty.h>
|
||||||
|
|
||||||
|
#include <qmetaobject.h>
|
||||||
#include <qscopedpointer.h>
|
#include <qscopedpointer.h>
|
||||||
#include <qscopedvaluerollback.h>
|
#include <qscopedvaluerollback.h>
|
||||||
|
#include <qvariant.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <QtCore/QVarLengthArray>
|
#include <QtCore/QVarLengthArray>
|
||||||
|
|
||||||
@ -898,6 +900,37 @@ QPropertyBindingPrivate *QBindingObserverPtr::binding() const noexcept { return
|
|||||||
|
|
||||||
QPropertyObserver *QBindingObserverPtr::operator->() { return d; }
|
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<QPropertyAdaptorSlotObject *>(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
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // QPROPERTY_P_H
|
#endif // QPROPERTY_P_H
|
||||||
|
@ -99,6 +99,8 @@ private slots:
|
|||||||
void scheduleNotify();
|
void scheduleNotify();
|
||||||
|
|
||||||
void notifyAfterAllDepsGone();
|
void notifyAfterAllDepsGone();
|
||||||
|
|
||||||
|
void propertyAdaptorBinding();
|
||||||
};
|
};
|
||||||
|
|
||||||
void tst_QProperty::functorBinding()
|
void tst_QProperty::functorBinding()
|
||||||
@ -1663,6 +1665,190 @@ void tst_QProperty::noFakeDependencies()
|
|||||||
QCOMPARE(old, bindingFunctionCalled);
|
QCOMPARE(old, bindingFunctionCalled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PropertyAdaptorTester : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged)
|
||||||
|
Q_PROPERTY(int foo1 READ foo WRITE setFoo)
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void fooChanged(int newFoo);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void fooHasChanged() { fooChangedCount++; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
int foo() const { return fooData; }
|
||||||
|
void setFoo(int i)
|
||||||
|
{
|
||||||
|
if (i != fooData) {
|
||||||
|
fooData = i;
|
||||||
|
fooChanged(fooData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
int fooData = 0;
|
||||||
|
int fooChangedCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void tst_QProperty::propertyAdaptorBinding()
|
||||||
|
{
|
||||||
|
QProperty<int> source { 5 };
|
||||||
|
QProperty<int> dest1 { 99 };
|
||||||
|
QProperty<int> dest2 { 98 };
|
||||||
|
|
||||||
|
// Check binding of non BINDABLE property
|
||||||
|
PropertyAdaptorTester object;
|
||||||
|
QObject::connect(&object, &PropertyAdaptorTester::fooChanged, &object,
|
||||||
|
&PropertyAdaptorTester::fooHasChanged);
|
||||||
|
QBindable<int> binding(&object, "foo");
|
||||||
|
binding.setBinding([&]() { return source + 1; });
|
||||||
|
QCOMPARE(object.foo(), 6);
|
||||||
|
QCOMPARE(object.fooChangedCount, 1);
|
||||||
|
|
||||||
|
struct MyBindable : QBindable<int> {
|
||||||
|
using QBindable<int>::QBindable;
|
||||||
|
QtPrivate::QPropertyAdaptorSlotObject* data() {
|
||||||
|
return static_cast<QtPrivate::QPropertyAdaptorSlotObject*>(QUntypedBindable::data);
|
||||||
|
}
|
||||||
|
} dataBinding(&object, "foo");
|
||||||
|
QPropertyBindingDataPointer data{&dataBinding.data()->bindingData()};
|
||||||
|
|
||||||
|
QCOMPARE(data.observerCount(), 0);
|
||||||
|
dest1.setBinding(binding.makeBinding());
|
||||||
|
QCOMPARE(data.observerCount(), 1);
|
||||||
|
dest2.setBinding([=]() { return binding.value() + 1; });
|
||||||
|
binding = {};
|
||||||
|
QCOMPARE(data.observerCount(), 2);
|
||||||
|
|
||||||
|
// Check addNotifer
|
||||||
|
{
|
||||||
|
int local_foo = 0;
|
||||||
|
auto notifier = QBindable<int>(&object, "foo").addNotifier([&]() { local_foo++; });
|
||||||
|
QCOMPARE(data.observerCount(), 3);
|
||||||
|
QCOMPARE(object.foo(), 6);
|
||||||
|
QCOMPARE(dest1.value(), 6);
|
||||||
|
QCOMPARE(dest2.value(), 7);
|
||||||
|
QCOMPARE(local_foo, 0);
|
||||||
|
QCOMPARE(object.fooChangedCount, 1);
|
||||||
|
|
||||||
|
source = 7;
|
||||||
|
QCOMPARE(object.foo(), 8);
|
||||||
|
QCOMPARE(dest1.value(), 8);
|
||||||
|
QCOMPARE(dest2.value(), 9);
|
||||||
|
QCOMPARE(local_foo, 1);
|
||||||
|
QCOMPARE(object.fooChangedCount, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
QCOMPARE(data.observerCount(), 2);
|
||||||
|
|
||||||
|
// Check a new QBindable object can override the existing binding
|
||||||
|
QBindable<int>(&object, "foo").setValue(10);
|
||||||
|
QCOMPARE(object.foo(), 10);
|
||||||
|
QCOMPARE(dest1.value(), 10);
|
||||||
|
QCOMPARE(dest2.value(), 11);
|
||||||
|
QCOMPARE(object.fooChangedCount, 3);
|
||||||
|
source.setValue(99);
|
||||||
|
QCOMPARE(object.foo(), 10);
|
||||||
|
QCOMPARE(dest1.value(), 10);
|
||||||
|
QCOMPARE(dest2.value(), 11);
|
||||||
|
QCOMPARE(object.fooChangedCount, 3);
|
||||||
|
object.setFoo(12);
|
||||||
|
QCOMPARE(object.foo(), 12);
|
||||||
|
QCOMPARE(dest1.value(), 12);
|
||||||
|
QCOMPARE(dest2.value(), 13);
|
||||||
|
QCOMPARE(object.fooChangedCount, 4);
|
||||||
|
|
||||||
|
// Check binding multiple notifiers
|
||||||
|
QProperty<int> source2 { 20 };
|
||||||
|
source.setValue(21);
|
||||||
|
binding = QBindable<int>(&object, "foo");
|
||||||
|
binding.setBinding([&]() { return source + source2; });
|
||||||
|
QCOMPARE(object.foo(), 41);
|
||||||
|
QCOMPARE(dest1.value(), 41);
|
||||||
|
QCOMPARE(object.fooChangedCount, 5);
|
||||||
|
source.setValue(22);
|
||||||
|
QCOMPARE(object.foo(), 42);
|
||||||
|
QCOMPARE(dest1.value(), 42);
|
||||||
|
QCOMPARE(object.fooChangedCount, 6);
|
||||||
|
source2.setValue(21);
|
||||||
|
QCOMPARE(object.foo(), 43);
|
||||||
|
QCOMPARE(dest1.value(), 43);
|
||||||
|
QCOMPARE(object.fooChangedCount, 7);
|
||||||
|
|
||||||
|
// Check update group
|
||||||
|
Qt::beginPropertyUpdateGroup();
|
||||||
|
source.setValue(23);
|
||||||
|
source2.setValue(22);
|
||||||
|
QCOMPARE(object.foo(), 43);
|
||||||
|
QCOMPARE(dest1.value(), 43);
|
||||||
|
QCOMPARE(object.fooChangedCount, 7);
|
||||||
|
Qt::endPropertyUpdateGroup();
|
||||||
|
QCOMPARE(object.foo(), 45);
|
||||||
|
QCOMPARE(dest1.value(), 45);
|
||||||
|
QCOMPARE(object.fooChangedCount, 8);
|
||||||
|
|
||||||
|
PropertyAdaptorTester object2;
|
||||||
|
PropertyAdaptorTester object3;
|
||||||
|
|
||||||
|
// Check multiple observers
|
||||||
|
QBindable<int> binding2(&object2, "foo");
|
||||||
|
QBindable<int> binding3(&object3, "foo");
|
||||||
|
binding.setBinding([=]() { return binding2.value(); });
|
||||||
|
binding3.setBinding([=]() { return binding.value(); });
|
||||||
|
QCOMPARE(object.foo(), 0);
|
||||||
|
QCOMPARE(object2.foo(), 0);
|
||||||
|
QCOMPARE(object3.foo(), 0);
|
||||||
|
QCOMPARE(dest1.value(), 0);
|
||||||
|
object2.setFoo(1);
|
||||||
|
QCOMPARE(object.foo(), 1);
|
||||||
|
QCOMPARE(object2.foo(), 1);
|
||||||
|
QCOMPARE(object3.foo(), 1);
|
||||||
|
QCOMPARE(dest1.value(), 1);
|
||||||
|
|
||||||
|
// Check interoperation with BINDABLE properties
|
||||||
|
MyQObject bindableObject;
|
||||||
|
bindableObject.fooData.setBinding([]() { return 5; });
|
||||||
|
QVERIFY(bindableObject.fooData.hasBinding());
|
||||||
|
QVERIFY(!bindableObject.barData.hasBinding());
|
||||||
|
QVERIFY(QBindable<int>(&bindableObject, "foo").hasBinding());
|
||||||
|
QBindable<int> bindableBar(&bindableObject, "bar");
|
||||||
|
QVERIFY(!bindableBar.hasBinding());
|
||||||
|
bindableBar.setBinding([]() { return 6; });
|
||||||
|
QVERIFY(bindableBar.hasBinding());
|
||||||
|
QVERIFY(bindableObject.barData.hasBinding());
|
||||||
|
|
||||||
|
// Check bad arguments
|
||||||
|
#ifndef QT_NO_DEBUG
|
||||||
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid");
|
||||||
|
#endif
|
||||||
|
QVERIFY(!QBindable<int>(&object, QMetaProperty{}).isValid());
|
||||||
|
#ifndef QT_NO_DEBUG
|
||||||
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo1 has no notify signal");
|
||||||
|
#endif
|
||||||
|
QVERIFY(!QBindable<int>(&object, "foo1").isValid());
|
||||||
|
#ifndef QT_NO_DEBUG
|
||||||
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property foo of type int does not match requested type bool");
|
||||||
|
#endif
|
||||||
|
QVERIFY(!QBindable<bool>(&object, "foo").isValid());
|
||||||
|
#ifndef QT_NO_DEBUG
|
||||||
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg,
|
||||||
|
"QUntypedBindable: Property foo does not belong to this object");
|
||||||
|
#endif
|
||||||
|
QObject qobj;
|
||||||
|
QVERIFY(!QBindable<int>(
|
||||||
|
&qobj,
|
||||||
|
object.metaObject()->property(object.metaObject()->indexOfProperty("foo")))
|
||||||
|
.isValid());
|
||||||
|
#ifndef QT_NO_DEBUG
|
||||||
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: No property named fizz");
|
||||||
|
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "QUntypedBindable: Property is not valid");
|
||||||
|
#endif
|
||||||
|
QVERIFY(!QBindable<int>(&object, "fizz").isValid());
|
||||||
|
}
|
||||||
|
|
||||||
#if QT_CONFIG(thread)
|
#if QT_CONFIG(thread)
|
||||||
struct ThreadSafetyTester : public QObject
|
struct ThreadSafetyTester : public QObject
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user