summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFabian Kosmale <fabian.kosmale@qt.io>2020-08-20 11:01:18 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2020-12-18 13:10:46 +0100
commit10bf3ae90cadbec44c6007190bf16e3c9c7652d8 (patch)
treec23e1aa6ccde0d1878759f7ad955e688b6ae77b9
parentabd2cbc12a28f2e962827ac9608d9e0bef6f0489 (diff)
QProperty add markdirty
This adds functionality for marking properties (QProperty and related classes) manually as dirty. This facilliates the integration of bindable properties with non-binable properties and makes it possible for bindable properties to change due to external events. Fixes: QTBUG-89167 Change-Id: I256cf154d914149dacb6cadaba92b13c88c9d027 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
-rw-r--r--src/corelib/kernel/qproperty.cpp43
-rw-r--r--src/corelib/kernel/qproperty.h22
-rw-r--r--src/corelib/kernel/qproperty_p.h9
-rw-r--r--src/corelib/kernel/qpropertyprivate.h2
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp77
5 files changed, 153 insertions, 0 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp
index 20efbaa1aa..5dd6507e26 100644
--- a/src/corelib/kernel/qproperty.cpp
+++ b/src/corelib/kernel/qproperty.cpp
@@ -381,6 +381,13 @@ void QPropertyBindingData::notifyObservers(QUntypedPropertyData *propertyDataPtr
observer.notify(d.bindingPtr(), propertyDataPtr);
}
+void QPropertyBindingData::markDirty()
+{
+ QPropertyBindingDataPointer d{this};
+ if (auto *binding = d.bindingPtr())
+ binding->setDirty(true);
+}
+
int QPropertyBindingDataPointer::observerCount() const
{
int count = 0;
@@ -837,6 +844,31 @@ QString QPropertyBindingError::description() const
*/
/*!
+ \fn template <typename T> void QProperty<T>::markDirty()
+
+ Programatically sets the property dirty. Any binding which depends on it will
+ be notified.
+ This can be useful for properties which do not only depend on bindable properties,
+ but also on non-bindable properties or some other state.
+
+ For example, assume we have a \c Circle class, with a non-bindable \c radius property
+ and a corresponding \c radiusChanged signal. We now want to create a property for a
+ cylinders volume, based on a height \c QProperty and an instance of Circle. To ensure
+ that the volume changes, we can call setDirty in a slot connected to radiusChanged.
+ \code
+ Circle circle;
+ QProperty<double> height;
+
+ QProperty<double> volume;
+ volume.setBinding([&]() {return height * std::pi_v<double> * circle.radius() * circle.radius()};
+ QOBject::connect(&circle, &Circle::radiusChanged, [&](){volume.markDirty();});
+ \endcode
+
+ \note Binding to a QObjectBindableProperty's signal does not make sense in general. Bindings
+ across bindable properties get marked dirty automatically.
+*/
+
+/*!
\fn template <typename T> QPropertyBinding<T> QProperty<T>::setBinding(const QPropertyBinding<T> &newBinding)
Associates the value of this property with the provided \a newBinding
@@ -1036,6 +1068,17 @@ QString QPropertyBindingError::description() const
*/
/*!
+ \fn template <typename Class, typename T, auto offset, auto Callback> void QObjectBindableProperty<Class, T, offset, Callback>::markDirty()
+
+ Programatically sets the property dirty. Any binding which depend on it will
+ be notified.
+ This can be useful for properties which do not only depend on bindable properties,
+ but also on non-bindable properties or some other state.
+
+ \sa QProperty<T>::markDirty
+*/
+
+/*!
\fn template <typename Class, typename T, auto offset, auto Callback> QPropertyBinding<T> QObjectBindableProperty<Class, T, offset, Callback>::setBinding(const QPropertyBinding<T> &newBinding)
Associates the value of this property with the provided \a newBinding
diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h
index 20e7daf811..102b627d76 100644
--- a/src/corelib/kernel/qproperty.h
+++ b/src/corelib/kernel/qproperty.h
@@ -406,6 +406,11 @@ public:
return true;
}
+ void markDirty() {
+ d.markDirty();
+ notify();
+ }
+
#ifndef Q_CLANG_QDOC
template <typename Functor>
QPropertyBinding<T> setBinding(Functor &&f,
@@ -972,6 +977,15 @@ public:
return bd && bd->binding() != nullptr;
}
+ void markDirty() {
+ QBindingStorage *storage = qGetBindingStorage(owner());
+ auto bd = storage->bindingData(this, /*create=*/false);
+ if (bd) { // if we have no BindingData, nobody can listen anyway
+ bd->markDirty();
+ notify(bd);
+ }
+ }
+
QPropertyBinding<T> binding() const
{
auto *bd = qGetBindingStorage(owner())->bindingData(this);
@@ -1121,6 +1135,14 @@ public:
return *storage->bindingData(const_cast<QObjectComputedProperty *>(this), true);
}
+ void markDirty() {
+ // computed property can't store a binding, so there's nothing to mark
+ auto *storage = const_cast<QBindingStorage *>(qGetBindingStorage(owner()));
+ auto bd = storage->bindingData(const_cast<QObjectComputedProperty *>(this), false);
+ if (bd)
+ bindingData().notifyObservers(this);
+ }
+
private:
};
diff --git a/src/corelib/kernel/qproperty_p.h b/src/corelib/kernel/qproperty_p.h
index f7792c1b5a..3b874ec27b 100644
--- a/src/corelib/kernel/qproperty_p.h
+++ b/src/corelib/kernel/qproperty_p.h
@@ -498,6 +498,15 @@ public:
return bd && bd->binding() != nullptr;
}
+ void markDirty() {
+ QBindingStorage *storage = qGetBindingStorage(owner());
+ auto *bd = storage->bindingData(this, false);
+ if (bd) {
+ bd->markDirty();
+ notify(bd);
+ }
+ }
+
QPropertyBinding<T> binding() const
{
auto *bd = qGetBindingStorage(owner())->bindingData(this);
diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h
index f4b81ea40b..c53e15c2ec 100644
--- a/src/corelib/kernel/qpropertyprivate.h
+++ b/src/corelib/kernel/qpropertyprivate.h
@@ -248,6 +248,8 @@ public:
}
void evaluateIfDirty(const QUntypedPropertyData *property) const;
+ void markDirty();
+
void removeBinding()
{
if (hasBinding())
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
index 2ff32d0d1a..4559587afd 100644
--- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
+++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
@@ -91,6 +91,7 @@ private slots:
void noFakeDependencies();
void bindablePropertyWithInitialization();
+ void markDirty();
};
void tst_QProperty::functorBinding()
@@ -1480,6 +1481,82 @@ void tst_QProperty::bindablePropertyWithInitialization()
QCOMPARE(tester.prop3().anotherValue, 20);
}
+class MarkDirtyTester : public QObject
+{
+ Q_OBJECT
+public:
+ Q_PROPERTY(int value1 READ value1 WRITE setValue1 BINDABLE bindableValue1)
+ Q_PROPERTY(int value2 READ value2 WRITE setValue1 BINDABLE bindableValue2)
+ Q_PROPERTY(int computed READ computed BINDABLE bindableComputed)
+
+ inline static int staticValue = 0;
+
+ int value1() const {return m_value1;}
+ void setValue1(int val) {m_value1 = val;}
+ QBindable<int> bindableValue1() {return { &m_value1 };}
+
+ int value2() const {return m_value2;}
+ void setValue2(int val) {m_value2 = val;}
+ QBindable<int> bindableValue2() {return { &m_value2 };}
+
+ int computed() const { return staticValue + m_value1; }
+ QBindable<int> bindableComputed() {return {&m_computed};}
+
+ void incrementStaticValue() {
+ ++staticValue;
+ m_computed.markDirty();
+ }
+
+ void markValue1Dirty() {
+ m_value1.markDirty();
+ }
+
+ void markValue2Dirty() {
+ m_value2.markDirty();
+ }
+private:
+ Q_OBJECT_BINDABLE_PROPERTY(MarkDirtyTester, int, m_value1, nullptr)
+ Q_OBJECT_COMPAT_PROPERTY(MarkDirtyTester, int, m_value2, &MarkDirtyTester::setValue2)
+ Q_OBJECT_COMPUTED_PROPERTY(MarkDirtyTester, int, m_computed, &MarkDirtyTester::computed)
+};
+
+void tst_QProperty::markDirty()
+{
+ {
+ QProperty<int> testProperty;
+ int changeCounter = 0;
+ auto handler = testProperty.onValueChanged([&](){++changeCounter;});
+ testProperty.markDirty();
+ QCOMPARE(changeCounter, 1);
+ }
+ {
+ MarkDirtyTester dirtyTester;
+ int computedChangeCounter = 0;
+ int value1ChangeCounter = 0;
+ auto handler = dirtyTester.bindableComputed().onValueChanged([&](){
+ computedChangeCounter++;
+ });
+ auto handler2 = dirtyTester.bindableValue1().onValueChanged([&](){
+ value1ChangeCounter++;
+ });
+ dirtyTester.incrementStaticValue();
+ QCOMPARE(computedChangeCounter, 1);
+ QCOMPARE(dirtyTester.computed(), 1);
+ dirtyTester.markValue1Dirty();
+ QCOMPARE(value1ChangeCounter, 1);
+ QCOMPARE(computedChangeCounter, 1);
+ }
+ {
+ MarkDirtyTester dirtyTester;
+ int changeCounter = 0;
+ auto handler = dirtyTester.bindableValue2().onValueChanged([&](){
+ changeCounter++;
+ });
+ dirtyTester.markValue2Dirty();
+ QCOMPARE(changeCounter, 1);
+ }
+}
+
QTEST_MAIN(tst_QProperty);
#include "tst_qproperty.moc"