summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/kernel/qproperty.cpp249
-rw-r--r--src/corelib/kernel/qproperty.h159
-rw-r--r--src/corelib/kernel/qproperty_p.h1
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp47
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"