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
|
||||
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;
|
||||
}
|
||||
|
||||
/*!
|
||||
\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.
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -8,6 +8,9 @@
|
||||
#include <QScopeGuard>
|
||||
#include <QtCore/qloggingcategory.h>
|
||||
#include <QThread>
|
||||
#include <QtCore/qmetaobject.h>
|
||||
|
||||
#include "qobject_p.h"
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
@ -1135,6 +1138,31 @@ QString QPropertyBindingError::description() const
|
||||
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
|
||||
|
||||
@ -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<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
|
||||
|
||||
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
|
||||
|
@ -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<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
|
||||
@ -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<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
|
||||
{
|
||||
return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::makeBinding(location));
|
||||
|
@ -18,8 +18,10 @@
|
||||
#include <private/qglobal_p.h>
|
||||
#include <qproperty.h>
|
||||
|
||||
#include <qmetaobject.h>
|
||||
#include <qscopedpointer.h>
|
||||
#include <qscopedvaluerollback.h>
|
||||
#include <qvariant.h>
|
||||
#include <vector>
|
||||
#include <QtCore/QVarLengthArray>
|
||||
|
||||
@ -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<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
|
||||
|
||||
#endif // QPROPERTY_P_H
|
||||
|
@ -99,6 +99,8 @@ private slots:
|
||||
void scheduleNotify();
|
||||
|
||||
void notifyAfterAllDepsGone();
|
||||
|
||||
void propertyAdaptorBinding();
|
||||
};
|
||||
|
||||
void tst_QProperty::functorBinding()
|
||||
@ -1663,6 +1665,190 @@ void tst_QProperty::noFakeDependencies()
|
||||
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)
|
||||
struct ThreadSafetyTester : public QObject
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user