diff options
-rw-r--r-- | src/corelib/kernel/qproperty.cpp | 43 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty.h | 22 | ||||
-rw-r--r-- | src/corelib/kernel/qproperty_p.h | 9 | ||||
-rw-r--r-- | src/corelib/kernel/qpropertyprivate.h | 2 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp | 77 |
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" |