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>
This commit is contained in:
Fabian Kosmale 2020-08-20 11:01:18 +02:00
parent abd2cbc12a
commit 10bf3ae90c
5 changed files with 153 additions and 0 deletions

View File

@ -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;
@ -836,6 +843,31 @@ QString QPropertyBindingError::description() const
this property is read.
*/
/*!
\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)
@ -1035,6 +1067,17 @@ QString QPropertyBindingError::description() const
Callback function on \a owner.
*/
/*!
\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)

View File

@ -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:
};

View File

@ -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);

View File

@ -248,6 +248,8 @@ public:
}
void evaluateIfDirty(const QUntypedPropertyData *property) const;
void markDirty();
void removeBinding()
{
if (hasBinding())

View File

@ -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"