Make bindings introspectable through moc
Add a new BINDABLE declaration to the Q_PROPERTY() macro that tells moc where to find the QBindable for the property. Add a QUntypedBindable base class to QBindable<T> that gives access to generic functionality and checks argument compatibility at runtime. QBindable<T> will still do static checking at compile time. Add QMetaProperty::isBindable() and QMetaProperty::bindable() to be able to dynamically access the binding functionality. Change-Id: Ic7b08ae2cde83fd43e627d813a886e1de01fa3dc Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
parent
3e6c092793
commit
ad32ac5b4f
@ -3251,6 +3251,23 @@ bool QMetaProperty::reset(QObject *object) const
|
||||
QMetaObject::metacall(object, QMetaObject::ResetProperty, data.index(mobj) + mobj->propertyOffset(), argv);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*!
|
||||
\since 6.0
|
||||
Returns the bindable interface for the property on a given \a object.
|
||||
|
||||
If the property doesn't support bindings, the returned interface will be
|
||||
invalid.
|
||||
|
||||
\sa QUntypedBindable, QProperty, isBindable()
|
||||
*/
|
||||
QUntypedBindable QMetaProperty::bindable(QObject *object) const
|
||||
{
|
||||
QUntypedBindable bindable;
|
||||
void * argv[1] { &bindable };
|
||||
mobj->metacall(object, QMetaObject::BindableProperty, data.index(mobj) + mobj->propertyOffset(), argv);
|
||||
return bindable;
|
||||
}
|
||||
/*!
|
||||
\since 5.5
|
||||
|
||||
@ -3509,16 +3526,18 @@ bool QMetaProperty::isRequired() const
|
||||
|
||||
/*!
|
||||
\since 6.0
|
||||
Returns \c true if the property is implemented using a QProperty member; otherwise returns \c false.
|
||||
Returns \c true if the \c{Q_PROPERTY()} exposes binding functionality; otherwise returns false.
|
||||
|
||||
This can be used to detect the availability of QProperty related meta-call types ahead of
|
||||
performing the call itself.
|
||||
This implies that you can create bindings that use this property as a dependency or install QPropertyObserver
|
||||
objects on this property. Unless the property is readonly, you can also set a binding on this property.
|
||||
|
||||
\sa QProperty, isReadOnly(), bindable()
|
||||
*/
|
||||
bool QMetaProperty::isQProperty() const
|
||||
bool QMetaProperty::isBindable() const
|
||||
{
|
||||
if (!mobj)
|
||||
return false;
|
||||
return data.flags() & IsQProperty;
|
||||
return (data.flags() & Bindable);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -46,6 +46,8 @@
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QUntypedBindable;
|
||||
|
||||
#define Q_METAMETHOD_INVOKE_MAX_ARGS 10
|
||||
|
||||
class Q_CORE_EXPORT QMetaMethod
|
||||
@ -294,7 +296,7 @@ public:
|
||||
bool isConstant() const;
|
||||
bool isFinal() const;
|
||||
bool isRequired() const;
|
||||
bool isQProperty() const;
|
||||
bool isBindable() const;
|
||||
|
||||
bool isFlagType() const;
|
||||
bool isEnumType() const;
|
||||
@ -310,6 +312,8 @@ public:
|
||||
bool write(QObject *obj, const QVariant &value) const;
|
||||
bool reset(QObject *obj) const;
|
||||
|
||||
QUntypedBindable bindable(QObject *object) const;
|
||||
|
||||
QVariant readOnGadget(const void *gadget) const;
|
||||
bool writeOnGadget(void *gadget, const QVariant &value) const;
|
||||
bool resetOnGadget(void *gadget) const;
|
||||
|
@ -81,7 +81,7 @@ enum PropertyFlags {
|
||||
Stored = 0x00010000,
|
||||
User = 0x00100000,
|
||||
Required = 0x01000000,
|
||||
IsQProperty = 0x02000000
|
||||
Bindable = 0x02000000
|
||||
};
|
||||
|
||||
enum MethodFlags {
|
||||
|
@ -386,8 +386,7 @@ struct Q_CORE_EXPORT QMetaObject
|
||||
IndexOfMethod,
|
||||
RegisterPropertyMetaType,
|
||||
RegisterMethodArgumentMetaType,
|
||||
RegisterQPropertyObserver,
|
||||
SetQPropertyBinding
|
||||
BindableProperty
|
||||
};
|
||||
|
||||
int static_metacall(Call, int, void **) const;
|
||||
|
@ -615,10 +615,12 @@ struct QBindableInterface
|
||||
using BindingSetter = QUntypedPropertyBinding (*)(QUntypedPropertyData *d, const QUntypedPropertyBinding &binding);
|
||||
using MakeBinding = QUntypedPropertyBinding (*)(const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location);
|
||||
using SetObserver = void (*)(const QUntypedPropertyData *d, QPropertyObserver *observer);
|
||||
using GetMetaType = QMetaType (*)();
|
||||
BindingGetter getBinding;
|
||||
BindingSetter setBinding;
|
||||
MakeBinding makeBinding;
|
||||
SetObserver setObserver;
|
||||
GetMetaType metaType;
|
||||
};
|
||||
|
||||
template<typename Property, typename = void>
|
||||
@ -634,7 +636,8 @@ public:
|
||||
[](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding
|
||||
{ return Qt::makePropertyBinding([d]() -> T { return static_cast<const Property *>(d)->value(); }, location); },
|
||||
[](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
|
||||
{ observer->setSource(static_cast<const Property *>(d)->bindingData()); }
|
||||
{ observer->setSource(static_cast<const Property *>(d)->bindingData()); },
|
||||
[]() { return QMetaType::fromType<T>(); }
|
||||
};
|
||||
};
|
||||
|
||||
@ -651,36 +654,45 @@ public:
|
||||
[](const QUntypedPropertyData *d, const QPropertyBindingSourceLocation &location) -> QUntypedPropertyBinding
|
||||
{ return Qt::makePropertyBinding([d]() -> T { return static_cast<const Property *>(d)->value(); }, location); },
|
||||
[](const QUntypedPropertyData *d, QPropertyObserver *observer) -> void
|
||||
{ observer->setSource(static_cast<const Property *>(d)->bindingData()); }
|
||||
{ observer->setSource(static_cast<const Property *>(d)->bindingData()); },
|
||||
[]() { return QMetaType::fromType<T>(); }
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
class QBindable
|
||||
class QUntypedBindable
|
||||
{
|
||||
protected:
|
||||
QUntypedPropertyData *data;
|
||||
const QtPrivate::QBindableInterface *iface;
|
||||
QUntypedPropertyData *data = nullptr;
|
||||
const QtPrivate::QBindableInterface *iface = nullptr;
|
||||
|
||||
public:
|
||||
constexpr QUntypedBindable() = default;
|
||||
template<typename Property>
|
||||
QBindable(Property *p)
|
||||
QUntypedBindable(Property *p)
|
||||
: data(const_cast<std::remove_cv_t<Property> *>(p)),
|
||||
iface(&QtPrivate::QBindableInterfaceForProperty<Property>::iface)
|
||||
{}
|
||||
{ Q_ASSERT(data && iface); }
|
||||
|
||||
QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION)
|
||||
bool isValid() const { return data != nullptr; }
|
||||
bool isBindable() const { return iface && iface->getBinding; }
|
||||
|
||||
QUntypedPropertyBinding makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION)
|
||||
{
|
||||
return static_cast<QPropertyBinding<T> &&>(iface->makeBinding(data, location));
|
||||
return iface ? iface->makeBinding(data, location) : QUntypedPropertyBinding();
|
||||
}
|
||||
void observe(QPropertyObserver *observer)
|
||||
{
|
||||
if (iface)
|
||||
iface->setObserver(data, observer);
|
||||
}
|
||||
|
||||
template<typename Functor>
|
||||
QPropertyChangeHandler<Functor> onValueChanged(Functor f)
|
||||
{
|
||||
QPropertyChangeHandler<Functor> handler(f);
|
||||
iface->setObserver(data, &handler);
|
||||
observe(&handler);
|
||||
return handler;
|
||||
}
|
||||
|
||||
@ -691,17 +703,54 @@ public:
|
||||
return onValueChanged(f);
|
||||
}
|
||||
|
||||
QPropertyBinding<T> binding() const
|
||||
QUntypedPropertyBinding binding() const
|
||||
{
|
||||
if (!iface->getBinding)
|
||||
return QPropertyBinding<T>();
|
||||
return static_cast<QPropertyBinding<T> &&>(iface->getBinding(data));
|
||||
return QUntypedPropertyBinding();
|
||||
return iface->getBinding(data);
|
||||
}
|
||||
QPropertyBinding<T> setBinding(const QPropertyBinding<T> &binding)
|
||||
bool setBinding(const QUntypedPropertyBinding &binding)
|
||||
{
|
||||
if (!iface->setBinding)
|
||||
return QPropertyBinding<T>();
|
||||
return static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding));
|
||||
return false;
|
||||
if (!binding.isNull() && binding.valueMetaType() != iface->metaType())
|
||||
return false;
|
||||
iface->setBinding(data, binding);
|
||||
return true;
|
||||
}
|
||||
bool hasBinding() const
|
||||
{
|
||||
return !binding().isNull();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class QBindable : public QUntypedBindable
|
||||
{
|
||||
public:
|
||||
using QUntypedBindable::QUntypedBindable;
|
||||
explicit QBindable(const QUntypedBindable &b) : QUntypedBindable(b)
|
||||
{
|
||||
if (iface && iface->metaType() != QMetaType::fromType<T>()) {
|
||||
data = nullptr;
|
||||
iface = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QPropertyBinding<T> makeBinding(const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION)
|
||||
{
|
||||
return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::makeBinding(location));
|
||||
}
|
||||
QPropertyBinding<T> binding() const
|
||||
{
|
||||
return static_cast<QPropertyBinding<T> &&>(QUntypedBindable::binding());
|
||||
}
|
||||
using QUntypedBindable::setBinding;
|
||||
QPropertyBinding<T> setBinding(const QPropertyBinding<T> &binding)
|
||||
{
|
||||
Q_ASSERT(!iface || binding.isNull() || binding.valueMetaType() == iface->metaType());
|
||||
return iface ? static_cast<QPropertyBinding<T> &&>(iface->setBinding(data, binding)) : QPropertyBinding<T>();
|
||||
}
|
||||
#ifndef Q_CLANG_QDOC
|
||||
template <typename Functor>
|
||||
@ -715,10 +764,6 @@ public:
|
||||
template <typename Functor>
|
||||
QPropertyBinding<T> setBinding(Functor f);
|
||||
#endif
|
||||
bool hasBinding() const
|
||||
{
|
||||
return !binding().isNull();
|
||||
}
|
||||
};
|
||||
|
||||
struct QBindingStatus;
|
||||
|
@ -906,8 +906,8 @@ void Generator::generateProperties()
|
||||
if (p.required)
|
||||
flags |= Required;
|
||||
|
||||
if (p.isQProperty)
|
||||
flags |= IsQProperty;
|
||||
if (!p.bind.isEmpty())
|
||||
flags |= Bindable;
|
||||
|
||||
fprintf(out, " %4d, ", stridx(p.name));
|
||||
generateTypeInfo(p.type);
|
||||
@ -1025,9 +1025,8 @@ void Generator::generateMetacall()
|
||||
fprintf(out, "else ");
|
||||
fprintf(out,
|
||||
"if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty\n"
|
||||
" || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType\n"
|
||||
" || _c == QMetaObject::RegisterQPropertyObserver\n"
|
||||
" || _c == QMetaObject::SetQPropertyBinding) {\n"
|
||||
" || _c == QMetaObject::ResetProperty || _c == QMetaObject::BindableProperty\n"
|
||||
" || _c == QMetaObject::RegisterPropertyMetaType) {\n"
|
||||
" qt_static_metacall(this, _c, _id, _a);\n"
|
||||
" _id -= %d;\n }", int(cdef->propertyList.count()));
|
||||
fprintf(out, "\n#endif // QT_NO_PROPERTIES");
|
||||
@ -1268,7 +1267,7 @@ void Generator::generateStaticMetacall()
|
||||
bool needTempVarForGet = false;
|
||||
bool needSet = false;
|
||||
bool needReset = false;
|
||||
bool haveQProperties = false;
|
||||
bool hasBindableProperties = false;
|
||||
for (int i = 0; i < cdef->propertyList.size(); ++i) {
|
||||
const PropertyDef &p = cdef->propertyList.at(i);
|
||||
needGet |= !p.read.isEmpty() || !p.member.isEmpty();
|
||||
@ -1278,7 +1277,7 @@ void Generator::generateStaticMetacall()
|
||||
|
||||
needSet |= !p.write.isEmpty() || (!p.member.isEmpty() && !p.constant);
|
||||
needReset |= !p.reset.isEmpty();
|
||||
haveQProperties |= p.isQProperty;
|
||||
hasBindableProperties |= !p.bind.isEmpty();
|
||||
}
|
||||
fprintf(out, "\n#ifndef QT_NO_PROPERTIES\n ");
|
||||
|
||||
@ -1404,59 +1403,21 @@ void Generator::generateStaticMetacall()
|
||||
}
|
||||
fprintf(out, " }");
|
||||
|
||||
#if 0
|
||||
fprintf(out, " else ");
|
||||
fprintf(out, "if (_c == QMetaObject::RegisterQPropertyObserver) {\n");
|
||||
if (haveQProperties) {
|
||||
fprintf(out, "if (_c == QMetaObject::BindableProperty) {\n");
|
||||
if (hasBindableProperties) {
|
||||
setupMemberAccess();
|
||||
fprintf(out, " QPropertyObserver *observer = reinterpret_cast<QPropertyObserver *>(_a[0]);\n");
|
||||
fprintf(out, " switch (_id) {\n");
|
||||
for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) {
|
||||
const PropertyDef &p = cdef->propertyList.at(propindex);
|
||||
if (!p.isQProperty)
|
||||
if (p.bind.isEmpty())
|
||||
continue;
|
||||
QByteArray prefix = "_t->";
|
||||
if (p.qpropertyname.isEmpty() || p.stored == "true") {
|
||||
fprintf(out, " case %d: observer->setSource(%s%s); break;\n",
|
||||
propindex, prefix.constData(), p.bindingAccessor.constData());
|
||||
} else {
|
||||
fprintf(out, " case %d: if (auto *source = %s%s) observer->setSource(*source); break; \n",
|
||||
propindex, prefix.constData(), p.bindingAccessor.constData());
|
||||
}
|
||||
fprintf(out, " case %d: *static_cast<QUntypedBindable *>(_a[0]) = _t->%s(); break;\n", propindex, p.bind.constData());
|
||||
}
|
||||
fprintf(out, " default: break;\n");
|
||||
fprintf(out, " }\n");
|
||||
}
|
||||
fprintf(out, " }");
|
||||
|
||||
fprintf(out, " else ");
|
||||
fprintf(out, "if (_c == QMetaObject::SetQPropertyBinding) {\n");
|
||||
if (haveQProperties) {
|
||||
setupMemberAccess();
|
||||
fprintf(out, " switch (_id) {\n");
|
||||
for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) {
|
||||
const PropertyDef &p = cdef->propertyList.at(propindex);
|
||||
if (!p.isQProperty)
|
||||
continue;
|
||||
QByteArray prefix = "_t->";
|
||||
|
||||
if (p.qpropertyname.isEmpty() || p.stored == "true") {
|
||||
fprintf(out, " case %d: %s%s.setBinding(*reinterpret_cast<QPropertyBinding<%s> *>(_a[0])); break;\n",
|
||||
propindex, prefix.constData(),
|
||||
p.bindingAccessor.constData(),
|
||||
p.type.constData());
|
||||
} else {
|
||||
fprintf(out, " case %d: if (auto *source = %s%s) source->setBinding(*reinterpret_cast<QPropertyBinding<%s> *>(_a[0])); break;\n",
|
||||
propindex, prefix.constData(), p.bindingAccessor.constData(),
|
||||
p.type.constData());
|
||||
}
|
||||
|
||||
}
|
||||
fprintf(out, " default: break;\n");
|
||||
fprintf(out, " }\n");
|
||||
}
|
||||
fprintf(out, " }");
|
||||
#endif
|
||||
fprintf(out, "\n#endif // QT_NO_PROPERTIES");
|
||||
needElse = true;
|
||||
}
|
||||
|
@ -1022,7 +1022,7 @@ static QByteArrayList requiredQtContainers(const QList<ClassDef> &classes)
|
||||
|
||||
for (const auto &c : classes) {
|
||||
for (const auto &p : c.propertyList)
|
||||
needsQProperty |= p.isQProperty;
|
||||
needsQProperty |= !p.bind.isEmpty();
|
||||
if (any_type_contains(c.propertyList, pattern) ||
|
||||
any_arg_contains(c.slotList, pattern) ||
|
||||
any_arg_contains(c.signalList, pattern) ||
|
||||
@ -1219,23 +1219,9 @@ void Moc::createPropertyDef(PropertyDef &propDef)
|
||||
{
|
||||
propDef.location = index;
|
||||
|
||||
const bool isPrivateProperty = !propDef.inPrivateClass.isEmpty();
|
||||
bool typeWrappedInQProperty = false;
|
||||
if (isPrivateProperty) {
|
||||
const int rewind = index;
|
||||
if (test(IDENTIFIER) && lexem() == "QProperty" && test(LANGLE)) {
|
||||
typeWrappedInQProperty = true;
|
||||
propDef.isQProperty = true;
|
||||
} else {
|
||||
index = rewind;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray type = parseType().name;
|
||||
if (type.isEmpty())
|
||||
error();
|
||||
if (typeWrappedInQProperty)
|
||||
next(RANGLE);
|
||||
propDef.designable = propDef.scriptable = propDef.stored = "true";
|
||||
propDef.user = "false";
|
||||
/*
|
||||
@ -1346,6 +1332,9 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef)
|
||||
case 'W': if (l != "WRITE") error(2);
|
||||
propDef.write = v;
|
||||
break;
|
||||
case 'B': if (l != "BINDABLE") error(2);
|
||||
propDef.bind = v;
|
||||
break;
|
||||
case 'D': if (l != "DESIGNABLE") error(2);
|
||||
propDef.designable = v + v2;
|
||||
checkIsFunction(propDef.designable, "DESIGNABLE");
|
||||
@ -1373,6 +1362,12 @@ void Moc::parsePropertyAttributes(PropertyDef &propDef)
|
||||
propDef.constant = false;
|
||||
warning(msg.constData());
|
||||
}
|
||||
if (propDef.constant && !propDef.bind.isNull()) {
|
||||
const QByteArray msg = "Property declaration " + propDef.name
|
||||
+ " is both BINDable and CONSTANT. CONSTANT will be ignored.";
|
||||
propDef.constant = false;
|
||||
warning(msg.constData());
|
||||
}
|
||||
}
|
||||
|
||||
void Moc::parseProperty(ClassDef *def)
|
||||
@ -1808,7 +1803,7 @@ void Moc::checkProperties(ClassDef *cdef)
|
||||
warning(msg.constData());
|
||||
}
|
||||
|
||||
if (p.read.isEmpty() && p.member.isEmpty() && !p.isQProperty) {
|
||||
if (p.read.isEmpty() && p.member.isEmpty() && p.bind.isEmpty()) {
|
||||
const int rewind = index;
|
||||
if (p.location >= 0)
|
||||
index = p.location;
|
||||
@ -2012,6 +2007,7 @@ QJsonObject PropertyDef::toJson() const
|
||||
jsonify("member", member);
|
||||
jsonify("read", read);
|
||||
jsonify("write", write);
|
||||
jsonify("bindable", bind);
|
||||
jsonify("reset", reset);
|
||||
jsonify("notify", notify);
|
||||
jsonify("privateClass", inPrivateClass);
|
||||
@ -2035,7 +2031,6 @@ QJsonObject PropertyDef::toJson() const
|
||||
prop[QLatin1String("constant")] = constant;
|
||||
prop[QLatin1String("final")] = final;
|
||||
prop[QLatin1String("required")] = required;
|
||||
prop[QLatin1String("isQProperty")] = isQProperty;
|
||||
|
||||
if (revision > 0)
|
||||
prop[QLatin1String("revision")] = revision;
|
||||
|
@ -132,7 +132,7 @@ struct PropertyDef
|
||||
return (s == write);
|
||||
}
|
||||
|
||||
QByteArray name, type, member, read, write, reset, designable, scriptable, stored, user, notify, inPrivateClass;
|
||||
QByteArray name, type, member, read, write, bind, reset, designable, scriptable, stored, user, notify, inPrivateClass;
|
||||
int notifyId = -1; // -1 means no notifyId, >= 0 means signal defined in this class, < -1 means signal not defined in this class
|
||||
enum Specification { ValueSpec, ReferenceSpec, PointerSpec };
|
||||
Specification gspec = ValueSpec;
|
||||
@ -140,7 +140,6 @@ struct PropertyDef
|
||||
bool constant = false;
|
||||
bool final = false;
|
||||
bool required = false;
|
||||
bool isQProperty = false;
|
||||
|
||||
int location = -1; // token index, used for error reporting
|
||||
|
||||
|
@ -77,6 +77,7 @@ private slots:
|
||||
void testNewStuff();
|
||||
void qobjectObservers();
|
||||
void compatBindings();
|
||||
void metaProperty();
|
||||
};
|
||||
|
||||
void tst_QProperty::functorBinding()
|
||||
@ -966,8 +967,8 @@ void tst_QProperty::bindingValueReplacement()
|
||||
class MyQObject : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int foo READ foo WRITE setFoo NOTIFY fooChanged) // Use Q_BINDABLE_PROPERTY and generate iface API
|
||||
Q_PROPERTY(int bar READ bar WRITE setBar NOTIFY barChanged)
|
||||
Q_PROPERTY(int foo READ foo WRITE setFoo BINDABLE bindableFoo NOTIFY fooChanged)
|
||||
Q_PROPERTY(int bar READ bar WRITE setBar BINDABLE bindableBar NOTIFY barChanged)
|
||||
Q_PROPERTY(int read READ read NOTIFY readChanged)
|
||||
Q_PROPERTY(int computed READ computed STORED false)
|
||||
Q_PROPERTY(int compat READ compat WRITE setCompat NOTIFY compatChanged)
|
||||
@ -1162,6 +1163,53 @@ void tst_QProperty::compatBindings()
|
||||
QCOMPARE(object.setCompatCalled, 4);
|
||||
}
|
||||
|
||||
void tst_QProperty::metaProperty()
|
||||
{
|
||||
MyQObject object;
|
||||
QObject::connect(&object, &MyQObject::fooChanged, &object, &MyQObject::fooHasChanged);
|
||||
QObject::connect(&object, &MyQObject::barChanged, &object, &MyQObject::barHasChanged);
|
||||
QObject::connect(&object, &MyQObject::compatChanged, &object, &MyQObject::compatHasChanged);
|
||||
|
||||
QCOMPARE(object.fooChangedCount, 0);
|
||||
object.setFoo(10);
|
||||
QCOMPARE(object.fooChangedCount, 1);
|
||||
QCOMPARE(object.foo(), 10);
|
||||
|
||||
auto f = [&object]() -> int {
|
||||
return object.barData;
|
||||
};
|
||||
QCOMPARE(object.barChangedCount, 0);
|
||||
object.setBar(42);
|
||||
QCOMPARE(object.barChangedCount, 1);
|
||||
QCOMPARE(object.fooChangedCount, 1);
|
||||
int fooIndex = object.metaObject()->indexOfProperty("foo");
|
||||
QVERIFY(fooIndex >= 0);
|
||||
QMetaProperty fooProp = object.metaObject()->property(fooIndex);
|
||||
QVERIFY(fooProp.isValid());
|
||||
auto fooBindable = fooProp.bindable(&object);
|
||||
QVERIFY(fooBindable.isValid());
|
||||
QVERIFY(fooBindable.isBindable());
|
||||
QVERIFY(!fooBindable.hasBinding());
|
||||
fooBindable.setBinding(Qt::makePropertyBinding(f));
|
||||
QVERIFY(fooBindable.hasBinding());
|
||||
QCOMPARE(object.fooChangedCount, 2);
|
||||
QCOMPARE(object.fooData.value(), 42);
|
||||
object.setBar(666);
|
||||
QCOMPARE(object.fooChangedCount, 3);
|
||||
QCOMPARE(object.barChangedCount, 2);
|
||||
QCOMPARE(object.fooData.value(), 666);
|
||||
QCOMPARE(object.fooChangedCount, 3);
|
||||
|
||||
fooBindable.setBinding(QUntypedPropertyBinding());
|
||||
QVERIFY(!fooBindable.hasBinding());
|
||||
QCOMPARE(object.fooData.value(), 666);
|
||||
|
||||
object.setBar(0);
|
||||
QCOMPARE(object.fooData.value(), 666);
|
||||
object.setFoo(1);
|
||||
QCOMPARE(object.fooData.value(), 1);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QProperty);
|
||||
|
||||
#include "tst_qproperty.moc"
|
||||
|
@ -1063,7 +1063,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "prop1",
|
||||
"read": "getProp1",
|
||||
"required": false,
|
||||
@ -1077,7 +1076,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "prop2",
|
||||
"read": "getProp2",
|
||||
"required": false,
|
||||
@ -1091,7 +1089,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "prop3",
|
||||
"read": "getProp3",
|
||||
"required": false,
|
||||
@ -1204,7 +1201,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "flags",
|
||||
"read": "flags",
|
||||
"required": false,
|
||||
@ -1231,7 +1227,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "flags",
|
||||
"read": "flags",
|
||||
"required": false,
|
||||
@ -1245,7 +1240,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "flagsList",
|
||||
"read": "flagsList",
|
||||
"required": false,
|
||||
@ -1593,8 +1587,8 @@
|
||||
"outputRevision": 68
|
||||
},
|
||||
{
|
||||
"classes": [
|
||||
{
|
||||
"classes": [
|
||||
{
|
||||
"className": "TestPointeeCanBeIncomplete",
|
||||
"object": true,
|
||||
"qualifiedClassName": "TestPointeeCanBeIncomplete",
|
||||
@ -1787,7 +1781,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "blah",
|
||||
"read": "blah",
|
||||
"required": false,
|
||||
@ -1840,7 +1833,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "blah",
|
||||
"read": "blah",
|
||||
"required": false,
|
||||
@ -2016,7 +2008,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "gadgetPoperty",
|
||||
"read": "gadgetPoperty",
|
||||
"required": false,
|
||||
@ -2029,7 +2020,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "objectPoperty",
|
||||
"read": "objectPoperty",
|
||||
"required": false,
|
||||
@ -2055,7 +2045,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "nestedGadgetPoperty",
|
||||
"read": "nestedGadgetPoperty",
|
||||
"required": false,
|
||||
@ -2081,7 +2070,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "nestedObjectPoperty",
|
||||
"read": "nestedObjectPoperty",
|
||||
"required": false,
|
||||
@ -2215,7 +2203,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "gadgetPoperty",
|
||||
"read": "gadgetPoperty",
|
||||
"required": false,
|
||||
@ -2228,7 +2215,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "objectPoperty",
|
||||
"read": "objectPoperty",
|
||||
"required": false,
|
||||
@ -2254,7 +2240,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "nestedGadgetPoperty",
|
||||
"read": "nestedGadgetPoperty",
|
||||
"required": false,
|
||||
@ -2280,7 +2265,6 @@
|
||||
"constant": false,
|
||||
"designable": true,
|
||||
"final": false,
|
||||
"isQProperty": false,
|
||||
"name": "nestedObjectPoperty",
|
||||
"read": "nestedObjectPoperty",
|
||||
"required": false,
|
||||
|
@ -4071,15 +4071,16 @@ void tst_Moc::requiredProperties()
|
||||
class ClassWithQPropertyMembers : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int publicProperty NOTIFY publicPropertyChanged)
|
||||
Q_PROPERTY(int privateExposedProperty)
|
||||
Q_PROPERTY(int publicProperty MEMBER publicProperty BINDABLE bindablePublicProperty NOTIFY publicPropertyChanged)
|
||||
Q_PROPERTY(int privateExposedProperty MEMBER privateExposedProperty)
|
||||
public:
|
||||
|
||||
signals:
|
||||
void publicPropertyChanged();
|
||||
|
||||
public:
|
||||
// QNotifiedProperty<int, &ClassWithQPropertyMembers::publicPropertyChanged> publicProperty;
|
||||
QBindable<int> bindablePublicProperty() { return QBindable<int>(&publicProperty); }
|
||||
Q_OBJECT_BINDABLE_PROPERTY(ClassWithQPropertyMembers, int, publicProperty, &ClassWithQPropertyMembers::publicPropertyChanged);
|
||||
QProperty<int> notExposed;
|
||||
|
||||
|
||||
@ -4093,188 +4094,163 @@ private:
|
||||
|
||||
void tst_Moc::qpropertyMembers()
|
||||
{
|
||||
// const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
|
||||
const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
|
||||
|
||||
// QCOMPARE(metaObject->propertyCount() - metaObject->superClass()->propertyCount(), 2);
|
||||
QCOMPARE(metaObject->propertyCount() - metaObject->superClass()->propertyCount(), 2);
|
||||
|
||||
// QCOMPARE(metaObject->indexOfProperty("notExposed"), -1);
|
||||
QCOMPARE(metaObject->indexOfProperty("notExposed"), -1);
|
||||
|
||||
// QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
|
||||
// QVERIFY(prop.isValid());
|
||||
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
|
||||
QVERIFY(prop.isValid());
|
||||
|
||||
// QVERIFY(metaObject->property(metaObject->indexOfProperty("privateExposedProperty")).isValid());
|
||||
QVERIFY(metaObject->property(metaObject->indexOfProperty("privateExposedProperty")).isValid());
|
||||
|
||||
// ClassWithQPropertyMembers instance;
|
||||
ClassWithQPropertyMembers instance;
|
||||
|
||||
// prop.write(&instance, 42);
|
||||
// QCOMPARE(instance.publicProperty.value(), 42);
|
||||
prop.write(&instance, 42);
|
||||
QCOMPARE(instance.publicProperty.value(), 42);
|
||||
|
||||
// QSignalSpy publicPropertySpy(&instance, SIGNAL(publicPropertyChanged()));
|
||||
QSignalSpy publicPropertySpy(&instance, SIGNAL(publicPropertyChanged()));
|
||||
|
||||
// instance.publicProperty.setValue(&instance, 100);
|
||||
// QCOMPARE(prop.read(&instance).toInt(), 100);
|
||||
// QCOMPARE(publicPropertySpy.count(), 1);
|
||||
instance.publicProperty.setValue(100);
|
||||
QCOMPARE(prop.read(&instance).toInt(), 100);
|
||||
QCOMPARE(publicPropertySpy.count(), 1);
|
||||
|
||||
// QCOMPARE(prop.metaType(), QMetaType(QMetaType::Int));
|
||||
QCOMPARE(prop.metaType(), QMetaType(QMetaType::Int));
|
||||
|
||||
// QVERIFY(prop.notifySignal().isValid());
|
||||
QVERIFY(prop.notifySignal().isValid());
|
||||
}
|
||||
|
||||
|
||||
|
||||
void tst_Moc::observerMetaCall()
|
||||
{
|
||||
// const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
|
||||
// QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
|
||||
// QVERIFY(prop.isValid());
|
||||
const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
|
||||
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
|
||||
QVERIFY(prop.isValid());
|
||||
|
||||
// ClassWithQPropertyMembers instance;
|
||||
ClassWithQPropertyMembers instance;
|
||||
|
||||
// int observerCallCount = 0;
|
||||
int observerCallCount = 0;
|
||||
|
||||
|
||||
// auto handler = QPropertyChangeHandler([&observerCallCount]() {
|
||||
// ++observerCallCount;
|
||||
// });
|
||||
auto observer = [&observerCallCount]() {
|
||||
++observerCallCount;
|
||||
};
|
||||
|
||||
// {
|
||||
// void *argv[] = { &handler };
|
||||
// instance.qt_metacall(QMetaObject::RegisterQPropertyObserver, prop.propertyIndex(), argv);
|
||||
// }
|
||||
auto bindable = prop.bindable(&instance);
|
||||
QVERIFY(bindable.isBindable());
|
||||
|
||||
// QCOMPARE(observerCallCount, 0);
|
||||
// instance.publicProperty.setValue(100);
|
||||
// QCOMPARE(observerCallCount, 1);
|
||||
// instance.publicProperty.setValue(&instance, 101);
|
||||
// QCOMPARE(observerCallCount, 2);
|
||||
auto handler = bindable.onValueChanged(observer);
|
||||
|
||||
QCOMPARE(observerCallCount, 0);
|
||||
instance.publicProperty.setValue(100);
|
||||
QCOMPARE(observerCallCount, 1);
|
||||
instance.publicProperty.setValue(101);
|
||||
QCOMPARE(observerCallCount, 2);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void tst_Moc::setQPRopertyBinding()
|
||||
{
|
||||
// const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
|
||||
// QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
|
||||
// QVERIFY(prop.isValid());
|
||||
const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
|
||||
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
|
||||
QVERIFY(prop.isValid());
|
||||
|
||||
// ClassWithQPropertyMembers instance;
|
||||
ClassWithQPropertyMembers instance;
|
||||
|
||||
// bool bindingCalled = false;
|
||||
// auto binding = Qt::makePropertyBinding([&bindingCalled]() {
|
||||
// bindingCalled = true;
|
||||
// return 42;
|
||||
// });
|
||||
bool bindingCalled = false;
|
||||
auto binding = Qt::makePropertyBinding([&bindingCalled]() {
|
||||
bindingCalled = true;
|
||||
return 42;
|
||||
});
|
||||
|
||||
// {
|
||||
// void *argv[] = { &binding };
|
||||
// instance.qt_metacall(QMetaObject::SetQPropertyBinding, prop.propertyIndex(), argv);
|
||||
// }
|
||||
auto bindable = prop.bindable(&instance);
|
||||
QVERIFY(bindable.isBindable());
|
||||
bindable.setBinding(binding);
|
||||
|
||||
// QCOMPARE(instance.publicProperty.value(), 42);
|
||||
// QVERIFY(bindingCalled); // but now it should've been called :)
|
||||
QCOMPARE(instance.publicProperty.value(), 42);
|
||||
QVERIFY(bindingCalled); // but now it should've been called :)
|
||||
}
|
||||
|
||||
#if 0
|
||||
class ClassWithPrivateQPropertyShim :public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_PRIVATE_QPROPERTY(d_func(), int, testProperty, setTestProperty, NOTIFY testPropertyChanged)
|
||||
Q_PRIVATE_QPROPERTY(d_func(), int, testProperty2, setTestProperty2, NOTIFY false)
|
||||
Q_PRIVATE_QPROPERTY(d_func(), int, lazyTestProperty, setLazyTestProperty,
|
||||
NOTIFY lazyTestPropertyChanged STORED false)
|
||||
|
||||
Q_PRIVATE_QPROPERTIES_BEGIN
|
||||
Q_PRIVATE_QPROPERTY_IMPL(testProperty)
|
||||
Q_PRIVATE_QPROPERTY_IMPL(testProperty2)
|
||||
Q_PRIVATE_QPROPERTY_IMPL(lazyTestProperty)
|
||||
Q_PRIVATE_QPROPERTIES_END
|
||||
Q_PROPERTY(int testProperty READ testProperty WRITE setTestProperty BINDABLE bindableTestProperty NOTIFY testPropertyChanged)
|
||||
Q_PROPERTY(int testProperty2 READ testProperty2 WRITE setTestProperty2 BINDABLE bindableTestProperty2)
|
||||
//Q_PROPERTY(d_func(), int, lazyTestProperty, setLazyTestProperty, NOTIFY lazyTestPropertyChanged)
|
||||
|
||||
signals:
|
||||
void testPropertyChanged();
|
||||
void lazyTestPropertyChanged();
|
||||
public:
|
||||
|
||||
int testProperty() const { return priv.testProperty; }
|
||||
void setTestProperty(int val) { priv.testProperty = val; }
|
||||
int testProperty2() const { return priv.testProperty2; }
|
||||
void setTestProperty2(int val) { priv.testProperty2 = val; }
|
||||
|
||||
QBindable<int> bindableTestProperty() { return QBindable<int>(&priv.testProperty); }
|
||||
QBindable<int> bindableTestProperty2() { return QBindable<int>(&priv.testProperty2); }
|
||||
|
||||
struct Private {
|
||||
Private(ClassWithPrivateQPropertyShim *pub)
|
||||
: q(pub)
|
||||
{}
|
||||
|
||||
QBindingStorage bindingStorage;
|
||||
|
||||
ClassWithPrivateQPropertyShim *q = nullptr;
|
||||
|
||||
void onTestPropertyChanged() { q->testPropertyChanged(); }
|
||||
QNotifiedProperty<int, &Private::onTestPropertyChanged> testProperty;
|
||||
Q_OBJECT_BINDABLE_PROPERTY(Private, int, testProperty, &Private::onTestPropertyChanged);
|
||||
QProperty<int> testProperty2;
|
||||
|
||||
void onLazyTestPropertyChanged() { q->lazyTestPropertyChanged(); }
|
||||
|
||||
const QNotifiedProperty<int, &Private::onLazyTestPropertyChanged> *lazyTestProperty() const {
|
||||
// Mind that this prevents the property read from being recorded.
|
||||
// For real-world use cases some more logic is necessary here.
|
||||
return lazyTestPropertyStorage.data();
|
||||
}
|
||||
|
||||
QNotifiedProperty<int, &Private::onLazyTestPropertyChanged> *lazyTestProperty() {
|
||||
if (!lazyTestPropertyStorage)
|
||||
lazyTestPropertyStorage.reset(new QNotifiedProperty<int, &Private::onLazyTestPropertyChanged>);
|
||||
return lazyTestPropertyStorage.data();
|
||||
}
|
||||
|
||||
QScopedPointer<QNotifiedProperty<int, &Private::onLazyTestPropertyChanged>> lazyTestPropertyStorage;
|
||||
};
|
||||
Private priv{this};
|
||||
|
||||
Private *d_func() { return &priv; }
|
||||
const Private *d_func() const { return &priv; }
|
||||
};
|
||||
#endif
|
||||
|
||||
inline const QBindingStorage *qGetBindingStorage(const ClassWithPrivateQPropertyShim::Private *o)
|
||||
{
|
||||
return &o->bindingStorage;
|
||||
}
|
||||
inline QBindingStorage *qGetBindingStorage(ClassWithPrivateQPropertyShim::Private *o)
|
||||
{
|
||||
return &o->bindingStorage;
|
||||
}
|
||||
|
||||
void tst_Moc::privateQPropertyShim()
|
||||
{
|
||||
// ClassWithPrivateQPropertyShim testObject;
|
||||
ClassWithPrivateQPropertyShim testObject;
|
||||
|
||||
// {
|
||||
// auto metaObject = &ClassWithPrivateQPropertyShim::staticMetaObject;
|
||||
// QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("testProperty"));
|
||||
// QVERIFY(prop.isValid());
|
||||
// QVERIFY(prop.notifySignal().isValid());
|
||||
// }
|
||||
{
|
||||
auto metaObject = &ClassWithPrivateQPropertyShim::staticMetaObject;
|
||||
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("testProperty"));
|
||||
QVERIFY(prop.isValid());
|
||||
QVERIFY(prop.notifySignal().isValid());
|
||||
}
|
||||
|
||||
// testObject.priv.testProperty.setValue(&testObject.priv, 42);
|
||||
// QCOMPARE(testObject.property("testProperty").toInt(), 42);
|
||||
testObject.priv.testProperty.setValue(42);
|
||||
QCOMPARE(testObject.property("testProperty").toInt(), 42);
|
||||
|
||||
// // Behave like a QProperty
|
||||
// QVERIFY(!testObject.testProperty.hasBinding());
|
||||
// testObject.testProperty.setBinding([]() { return 100; });
|
||||
// QCOMPARE(testObject.testProperty.value(), 100);
|
||||
// QVERIFY(testObject.testProperty.hasBinding());
|
||||
// Behave like a QProperty
|
||||
QVERIFY(!testObject.bindableTestProperty().hasBinding());
|
||||
testObject.bindableTestProperty().setBinding([]() { return 100; });
|
||||
QCOMPARE(testObject.testProperty(), 100);
|
||||
QVERIFY(testObject.bindableTestProperty().hasBinding());
|
||||
|
||||
// // Old style setter getters
|
||||
// testObject.setTestProperty(400);
|
||||
// QVERIFY(!testObject.testProperty.hasBinding());
|
||||
// QCOMPARE(testObject.testProperty(), 400);
|
||||
// Old style setter getters
|
||||
testObject.setTestProperty(400);
|
||||
QVERIFY(!testObject.bindableTestProperty().hasBinding());
|
||||
QCOMPARE(testObject.testProperty(), 400);
|
||||
|
||||
// // Created and default-initialized, without nullptr access
|
||||
// QCOMPARE(testObject.lazyTestProperty(), 0);
|
||||
|
||||
// // Explicitly set to something
|
||||
// testObject.priv.lazyTestProperty()->setValue(&testObject.priv, 42);
|
||||
// QCOMPARE(testObject.property("lazyTestProperty").toInt(), 42);
|
||||
|
||||
// // Behave like a QProperty
|
||||
// QVERIFY(!testObject.lazyTestProperty.hasBinding());
|
||||
// testObject.lazyTestProperty.setBinding([]() { return 100; });
|
||||
// QCOMPARE(testObject.lazyTestProperty.value(), 100);
|
||||
// QVERIFY(testObject.lazyTestProperty.hasBinding());
|
||||
|
||||
// // Old style setter getters
|
||||
// testObject.setLazyTestProperty(400);
|
||||
// QVERIFY(!testObject.lazyTestProperty.hasBinding());
|
||||
// QCOMPARE(testObject.lazyTestProperty(), 400);
|
||||
|
||||
// // mo generates correct code for plain QProperty in PIMPL
|
||||
// testObject.testProperty2.setValue(42);
|
||||
// QCOMPARE(testObject.testProperty2.value(), 42);
|
||||
// moc generates correct code for plain QProperty in PIMPL
|
||||
testObject.setTestProperty2(42);
|
||||
QCOMPARE(testObject.priv.testProperty2.value(), 42);
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_Moc)
|
||||
|
Loading…
Reference in New Issue
Block a user