diff options
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 249 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty.h | 159 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty_p.h | 1 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp | 47 |
4 files changed, 446 insertions, 10 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp index 4ed4af4c8e..c3e7711f94 100644 --- a/src/corelib/kernel/qproperty.cpp +++ b/src/corelib/kernel/qproperty.cpp @@ -254,6 +254,12 @@ QPropertyObserver::QPropertyObserver(void (*callback)(QPropertyObserver *, void d.setChangeHandler(callback); } +QPropertyObserver::QPropertyObserver(void *aliasedPropertyPtr) +{ + QPropertyObserverPointer d{this}; + d.setAliasedProperty(aliasedPropertyPtr); +} + void QPropertyObserver::setSource(QPropertyBase &property) { QPropertyObserverPointer d{this}; @@ -303,6 +309,8 @@ QPropertyObserver &QPropertyObserver::operator=(QPropertyObserver &&other) void QPropertyObserverPointer::unlink() { + if (ptr->next.tag() & QPropertyObserver::ObserverNotifiesAlias) + ptr->aliasedPropertyPtr = 0; if (ptr->next) ptr->next->prev = ptr->prev; if (ptr->prev) @@ -317,6 +325,12 @@ void QPropertyObserverPointer::setChangeHandler(void (*changeHandler)(QPropertyO ptr->next.setTag(QPropertyObserver::ObserverNotifiesChangeHandler); } +void QPropertyObserverPointer::setAliasedProperty(void *propertyPtr) +{ + ptr->aliasedPropertyPtr = quintptr(propertyPtr); + ptr->next.setTag(QPropertyObserver::ObserverNotifiesAlias); +} + void QPropertyObserverPointer::setBindingToMarkDirty(QPropertyBindingPrivate *binding) { ptr->bindingToMarkDirty = binding; @@ -331,7 +345,8 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding auto observer = const_cast<QPropertyObserver*>(ptr); while (observer) { auto * const next = observer->next.data(); - if (observer->next.tag() == QPropertyObserver::ObserverNotifiesChangeHandler) { + switch (observer->next.tag()) { + case QPropertyObserver::ObserverNotifiesChangeHandler: if (!knownIfPropertyChanged && triggeringBinding) { knownIfPropertyChanged = true; @@ -344,9 +359,13 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding handlerToCall(observer, propertyDataPtr); observer->changeHandler = handlerToCall; } - } else { + break; + case QPropertyObserver::ObserverNotifiesBinding: if (observer->bindingToMarkDirty) observer->bindingToMarkDirty->markDirtyAndNotifyObservers(); + break; + case QPropertyObserver::ObserverNotifiesAlias: + break; } observer = next; } @@ -354,7 +373,8 @@ void QPropertyObserverPointer::notify(QPropertyBindingPrivate *triggeringBinding void QPropertyObserverPointer::observeProperty(QPropertyBasePointer property) { - unlink(); + if (ptr->prev) + unlink(); property.addObserver(ptr); } @@ -697,4 +717,227 @@ QPropertyBindingSourceLocation QPropertyBindingError::location() const A handler instance can be transferred between C++ scopes using move semantics. */ +/*! + \class QPropertyAlias + \inmodule QtCore + \brief The QPropertyAlias class is a safe alias for a QProperty with same template parameter. + + \ingroup tools + + QPropertyAlias\<T\> wraps a pointer to a QProperty\<T\> and automatically + invalidates itself when the QProperty\<T\> is destroyed. It forwards all + method invocations to the wrapped property. For example: + + \code + QProperty<QString> *name = new QProperty<QString>("John"); + QProperty<int> age(41); + + QPropertyAlias<QString> nameAlias(name); + QPropertyAlias<int> ageAlias(&age); + + QPropertyAlias<QString> fullname; + fullname.setBinding([&]() { return nameAlias.value() + " age:" + QString::number(ageAlias.value()); }); + + qDebug() << fullname.value(); // Prints "Smith age: 41" + + *name = "Emma"; // Marks binding expression as dirty + + qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma age: 41" + + // Birthday is coming up + ageAlias.setValue(age.value() + 1); // Writes the age property through the alias + + qDebug() << fullname.value(); // Re-evaluates the binding expression and prints "Emma age: 42" + + delete name; // Leaves the alias in an invalid, but accessible state + nameAlias.setValue("Eve"); // Ignored: nameAlias carries a default-constructed QString now + + ageAlias.setValue(92); + qDebug() << fullname.value(); // Re-evaluates the binding expression and prints " age: 92" + \endcode +*/ + +/*! + \fn template <typename T> QPropertyAlias<T>::QPropertyAlias(QProperty<T> *property) + + Constructs a property alias for the given \a property. +*/ + +/*! + \fn template <typename T> explicit QPropertyAlias<T>::QPropertyAlias(QPropertyAlias<T> *alias) + + Constructs a property alias for the property aliased by \a alias. +*/ + +/*! + \fn template <typename T> T QPropertyAlias<T>::value() const + + Returns the value of the aliased property. This may evaluate a binding + expression that is tied to the property, before returning the value. +*/ + +/*! + \fn template <typename T> QPropertyAlias<T>::operator T() const + + Returns the value of the aliased property. This may evaluate a binding + expression that is tied to the property, before returning the value. +*/ + +/*! + \fn template <typename T> void QPropertyAlias<T>::setValue(const T &newValue) + + Assigns \a newValue to the aliased property and removes the property's + associated binding, if present. +*/ + +/*! + \fn template <typename T> void QPropertyAlias<T>::setValue(T &&newValue) + \overload + + Assigns \a newValue to the aliased property and removes the property's + associated binding, if present. +*/ + +/*! + \fn template <typename T> QPropertyAlias<T> &QPropertyAlias<T>::operator=(const T &newValue) + + Assigns \a newValue to the aliased property and returns a reference to this + QPropertyAlias. +*/ + +/*! + \fn template <typename T> QPropertyAlias<T> &QPropertyAlias<T>::operator=(T &&newValue) + \overload + + Assigns \a newValue to the aliased property and returns a reference to this + QPropertyAlias. +*/ + +/*! + \fn template <typename T> QPropertyAlias<T> &QPropertyAlias<T>::operator=(const QPropertyBinding<T> &newBinding) + \overload + + Associates the value of the aliased property with the provided \a newBinding + expression and returns a reference to this alias. The first time the + property value is read, either from the property itself or from any alias, the + binding is evaluated. Whenever a dependency of the binding changes, the + binding will be re-evaluated the next time the value of this property is read. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QPropertyAlias<T>::setBinding(const QPropertyBinding<T> &newBinding) + + Associates the value of the aliased property with the provided \a newBinding + expression and returns any previous binding the associated with the aliased + property. The first time the property value is read, either from the property + itself or from any alias, the binding is evaluated. Whenever a dependency of + the binding changes, the binding will be re-evaluated the next time the value + of this property is read. + + Returns any previous binding associated with the property, or a + default-constructed QPropertyBinding<T>. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QPropertyAlias<T>::setBinding(QPropertyBinding<T> &&newBinding) + \overload + + Associates the value of the aliased property with the provided \a newBinding + expression and returns any previous binding the associated with the aliased + property. The first time the property value is read, either from the property + itself or from any alias, the binding is evaluated. Whenever a dependency of + the binding changes, the binding will be re-evaluated the next time the value + of this property is read. + + Returns any previous binding associated with the property, or a + default-constructed QPropertyBinding<T>. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> bool QPropertyAlias<T>::setBinding(const QUntypedPropertyBinding &newBinding) + \overload + + Associates the value of the aliased property with the provided \a newBinding + expression. The first time the property value is read, either from the + property itself or from any alias, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. + + Returns true if the type of this property is the same as the type the binding + function returns; false otherwise. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyBinding<T> setBinding(Functor f) + \overload + + Associates the value of the aliased property with the provided functor \a f + expression. The first time the property value is read, either from the + property itself or from any alias, the binding is evaluated. Whenever a + dependency of the binding changes, the binding will be re-evaluated the next + time the value of this property is read. + + Returns any previous binding associated with the property, or a + default-constructed QPropertyBinding<T>. +*/ + +/*! + \fn template <typename T> bool QPropertyAlias<T>::hasBinding() const + + Returns true if the aliased property is associated with a binding; false + otherwise. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QPropertyAlias<T>::binding() const + + Returns the binding expression that is associated with the aliased property. A + default constructed QPropertyBinding<T> will be returned if no such + association exists. +*/ + +/*! + \fn template <typename T> QPropertyBinding<T> QPropertyAlias<T>::takeBinding() + + Disassociates the binding expression from the aliased property and returns it. + After calling this function, the value of the property will only change if + you assign a new value to it, or when a new binding is set. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QPropertyAlias<T>::onValueChanged(Functor f) + + Registers the given functor \a f as a callback that shall be called whenever + the value of the aliased property changes. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, an std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the registration. When it + goes out of scope, the callback is de-registered. +*/ + +/*! + \fn template <typename T> template <typename Functor> QPropertyChangeHandler<T, Functor> QPropertyAlias<T>::subscribe(Functor f) + + Subscribes the given functor \a f as a callback that is called immediately and whenever + the value of the aliased property changes in the future. + + The callback \a f is expected to be a type that has a plain call operator () without any + parameters. This means that you can provide a C++ lambda expression, an std::function + or even a custom struct with a call operator. + + The returned property change handler object keeps track of the subscription. When it + goes out of scope, the callback is unsubscribed. +*/ + +/*! + \fn template <typename T> bool QPropertyAlias<T>::isValid() const + + Returns true if the aliased property still exists; false otherwise. + + If the aliased property doesn't exist, all other method calls are ignored. +*/ + QT_END_NAMESPACE diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h index 938de61fd5..76c41f356c 100644 --- a/src/corelib/kernel/qproperty.h +++ b/src/corelib/kernel/qproperty.h @@ -378,10 +378,10 @@ class Q_CORE_EXPORT QPropertyObserver public: // Internal enum ObserverTag { - ObserverNotifiesBinding = 0x0, - ObserverNotifiesChangeHandler = 0x1, + ObserverNotifiesBinding, + ObserverNotifiesChangeHandler, + ObserverNotifiesAlias, }; - Q_DECLARE_FLAGS(ObserverTags, ObserverTag) QPropertyObserver(); QPropertyObserver(QPropertyObserver &&other); @@ -394,18 +394,26 @@ public: protected: QPropertyObserver(void (*callback)(QPropertyObserver*, void *)); + QPropertyObserver(void *aliasedPropertyPtr); + + template<typename PropertyType> + QProperty<PropertyType> *aliasedProperty() const + { + return reinterpret_cast<QProperty<PropertyType> *>(aliasedPropertyPtr); + } private: void setSource(QtPrivate::QPropertyBase &property); - QTaggedPointer<QPropertyObserver, ObserverTags> next; + QTaggedPointer<QPropertyObserver, ObserverTag> next; // prev is a pointer to the "next" element within the previous node, or to the "firstObserverPtr" if it is the // first node. - QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTags> prev; + QtPrivate::QTagPreservingPointerToPointer<QPropertyObserver, ObserverTag> prev; union { QPropertyBindingPrivate *bindingToMarkDirty = nullptr; void (*changeHandler)(QPropertyObserver*, void *); + quintptr aliasedPropertyPtr; }; QPropertyObserver(const QPropertyObserver &) = delete; @@ -416,8 +424,6 @@ private: friend class QPropertyBindingPrivate; }; -Q_DECLARE_OPERATORS_FOR_FLAGS(QPropertyObserver::ObserverTags) - template <typename Functor> class QPropertyChangeHandler : public QPropertyObserver { @@ -487,6 +493,145 @@ struct QPropertyMemberChangeHandler<PropertyMember, Callback> : public QProperty } }; +template<typename T> +class QPropertyAlias : public QPropertyObserver +{ + Q_DISABLE_COPY_MOVE(QPropertyAlias) +public: + QPropertyAlias(QProperty<T> *property) + : QPropertyObserver(property) + { + if (property) + setSource(*property); + } + + QPropertyAlias(QPropertyAlias<T> *alias) + : QPropertyAlias(alias->aliasedProperty<T>()) + {} + + T value() const + { + if (auto *p = aliasedProperty<T>()) + return p->value(); + return T(); + } + + operator T() const { return value(); } + + void setValue(T &&newValue) + { + if (auto *p = aliasedProperty<T>()) + p->setValue(std::move(newValue)); + } + + void setValue(const T &newValue) + { + if (auto *p = aliasedProperty<T>()) + p->setValue(newValue); + } + + QPropertyAlias<T> &operator=(T &&newValue) + { + if (auto *p = aliasedProperty<T>()) + *p = std::move(newValue); + return *this; + } + + QPropertyAlias<T> &operator=(const T &newValue) + { + if (auto *p = aliasedProperty<T>()) + *p = newValue; + return *this; + } + + QPropertyAlias<T> &operator=(const QPropertyBinding<T> &newBinding) + { + setBinding(newBinding); + return *this; + } + + QPropertyAlias<T> &operator=(QPropertyBinding<T> &&newBinding) + { + setBinding(std::move(newBinding)); + return *this; + } + + QPropertyBinding<T> setBinding(const QPropertyBinding<T> &newBinding) + { + if (auto *p = aliasedProperty<T>()) + return p->setBinding(newBinding); + return QPropertyBinding<T>(); + } + + QPropertyBinding<T> setBinding(QPropertyBinding<T> &&newBinding) + { + if (auto *p = aliasedProperty<T>()) + return p->setBinding(std::move(newBinding)); + return QPropertyBinding<T>(); + } + + bool setBinding(const QUntypedPropertyBinding &newBinding) + { + if (auto *p = aliasedProperty<T>()) + return p->setBinding(newBinding); + return false; + } + +#ifndef Q_CLANG_QDOC + template <typename Functor> + QPropertyBinding<T> setBinding(Functor &&f, + const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION, + std::enable_if_t<std::is_invocable_v<Functor>> * = nullptr) + { + return setBinding(Qt::makePropertyBinding(std::forward<Functor>(f), location)); + } +#else + template <typename Functor> + QPropertyBinding<T> setBinding(Functor f); +#endif + + bool hasBinding() const + { + if (auto *p = aliasedProperty<T>()) + return p->hasBinding(); + return false; + } + + QPropertyBinding<T> binding() const + { + if (auto *p = aliasedProperty<T>()) + return p->binding(); + return QPropertyBinding<T>(); + } + + QPropertyBinding<T> takeBinding() + { + if (auto *p = aliasedProperty<T>()) + return p->takeBinding(); + return QPropertyBinding<T>(); + } + + template<typename Functor> + QPropertyChangeHandler<Functor> onValueChanged(Functor f) + { + if (auto *p = aliasedProperty<T>()) + return p->onValueChanged(f); + return QPropertyChangeHandler<Functor>(f); + } + + template<typename Functor> + QPropertyChangeHandler<Functor> subscribe(Functor f) + { + if (auto *p = aliasedProperty<T>()) + return p->subscribe(f); + return QPropertyChangeHandler<Functor>(f); + } + + bool isValid() const + { + return aliasedProperty<T>() != nullptr; + } +}; QT_END_NAMESPACE diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h index 6638785ccf..e5365f36fc 100644 --- a/src/corelib/kernel/qproperty_p.h +++ b/src/corelib/kernel/qproperty_p.h @@ -88,6 +88,7 @@ struct QPropertyObserverPointer void setBindingToMarkDirty(QPropertyBindingPrivate *binding); void setChangeHandler(void (*changeHandler)(QPropertyObserver *, void *)); + void setAliasedProperty(void *propertyPtr); void notify(QPropertyBindingPrivate *triggeringBinding, void *propertyDataPtr); void observeProperty(QPropertyBasePointer property); diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp index a8846e0987..15adcdffd6 100644 --- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp +++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp @@ -70,6 +70,7 @@ private slots: void staticChangeHandler(); void setBindingFunctor(); void multipleObservers(); + void propertyAlias(); }; void tst_QProperty::functorBinding() @@ -722,6 +723,52 @@ void tst_QProperty::multipleObservers() QCOMPARE(property.value(), 22); } +void tst_QProperty::propertyAlias() +{ + QScopedPointer<QProperty<int>> property(new QProperty<int>); + property->setValue(5); + QPropertyAlias alias(property.get()); + QVERIFY(alias.isValid()); + QCOMPARE(alias.value(), 5); + + int value1 = 1; + auto changeHandler = alias.onValueChanged([&]() { value1 = alias.value(); }); + QCOMPARE(value1, 1); + + int value2 = 2; + auto subscribeHandler = alias.subscribe([&]() { value2 = alias.value(); }); + QCOMPARE(value2, 5); + + alias.setValue(6); + QVERIFY(alias.isValid()); + QCOMPARE(alias.value(), 6); + QCOMPARE(value1, 6); + QCOMPARE(value2, 6); + + alias.setBinding([]() { return 12; }); + QCOMPARE(value1, 12); + QCOMPARE(value2, 12); + QCOMPARE(alias.value(), 12); + + alias.setValue(22); + QCOMPARE(value1, 22); + QCOMPARE(value2, 22); + QCOMPARE(alias.value(), 22); + + property.reset(); + + QVERIFY(!alias.isValid()); + QCOMPARE(alias.value(), int()); + QCOMPARE(value1, 22); + QCOMPARE(value2, 22); + + // Does not crash + alias.setValue(25); + QCOMPARE(alias.value(), int()); + QCOMPARE(value1, 22); + QCOMPARE(value2, 22); +} + QTEST_MAIN(tst_QProperty); #include "tst_qproperty.moc" |