summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/kernel/qproperty.cpp5
-rw-r--r--src/corelib/kernel/qproperty.h141
-rw-r--r--src/corelib/kernel/qpropertybinding.cpp10
-rw-r--r--src/corelib/kernel/qpropertybinding_p.h4
-rw-r--r--src/corelib/kernel/qpropertyprivate.h3
-rw-r--r--tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp131
6 files changed, 235 insertions, 59 deletions
diff --git a/src/corelib/kernel/qproperty.cpp b/src/corelib/kernel/qproperty.cpp
index 7ee035030f..b362319f22 100644
--- a/src/corelib/kernel/qproperty.cpp
+++ b/src/corelib/kernel/qproperty.cpp
@@ -95,7 +95,8 @@ QPropertyBase::~QPropertyBase()
QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding &binding,
void *propertyDataPtr,
void *staticObserver,
- void (*staticObserverCallback)(void*, void*))
+ void (*staticObserverCallback)(void*, void*),
+ bool (*guardCallback)(void *, void*))
{
QPropertyBindingPrivatePtr oldBinding;
QPropertyBindingPrivatePtr newBinding = binding.d;
@@ -122,7 +123,7 @@ QUntypedPropertyBinding QPropertyBase::setBinding(const QUntypedPropertyBinding
newBinding->setProperty(propertyDataPtr);
if (observer)
newBinding->prependObserver(observer);
- newBinding->setStaticObserver(staticObserver, staticObserverCallback);
+ newBinding->setStaticObserver(staticObserver, staticObserverCallback, guardCallback);
} else if (observer) {
d.setObservers(observer.ptr);
} else {
diff --git a/src/corelib/kernel/qproperty.h b/src/corelib/kernel/qproperty.h
index b1b4dbeae1..5ba4d2eda7 100644
--- a/src/corelib/kernel/qproperty.h
+++ b/src/corelib/kernel/qproperty.h
@@ -85,7 +85,7 @@ struct Q_CORE_EXPORT QPropertyBindingSourceLocation
template <typename Functor> class QPropertyChangeHandler;
template <typename T> class QProperty;
-template <typename T, auto callbackMember> class QNotifiedProperty;
+template <typename T, auto callbackMember, auto guardCallback> class QNotifiedProperty;
class QPropertyBindingErrorPrivate;
@@ -179,8 +179,8 @@ public:
: QUntypedPropertyBinding(property.d.priv.binding())
{}
- template<auto notifier>
- QPropertyBinding(const QNotifiedProperty<PropertyType, notifier> &property)
+ template<auto notifier, auto guard>
+ QPropertyBinding(const QNotifiedProperty<PropertyType, notifier, guard> &property)
: QUntypedPropertyBinding(property.d.priv.binding())
{}
@@ -382,13 +382,36 @@ namespace detail {
struct ExtractClassFromFunctionPointer<T C::*> { using Class = C; };
}
-template <typename T, auto Callback>
+template <typename T, auto Callback, auto ValueGuard=nullptr>
class QNotifiedProperty
{
public:
using value_type = T;
using Class = typename detail::ExtractClassFromFunctionPointer<decltype(Callback)>::Class;
- static_assert(std::is_invocable_v<decltype(Callback), Class, T> || std::is_invocable_v<decltype(Callback), Class>);
+private:
+ static bool constexpr ValueGuardModifiesArgument = std::is_invocable_r_v<bool, decltype(ValueGuard), Class, T&>;
+ static bool constexpr CallbackAcceptsOldValue = std::is_invocable_v<decltype(Callback), Class, T>;
+ static bool constexpr HasValueGuard = !std::is_same_v<decltype(ValueGuard), std::nullptr_t>;
+public:
+ static_assert(CallbackAcceptsOldValue || std::is_invocable_v<decltype(Callback), Class>);
+ static_assert(
+ std::is_invocable_r_v<bool, decltype(ValueGuard), Class, T> ||
+ ValueGuardModifiesArgument ||
+ !HasValueGuard,
+ "Guard has wrong signature");
+private:
+ // type erased guard functions, casts its arguments to the correct types
+ static constexpr bool (*GuardTEHelper)(void *, void*) = [](void *o, void *newValue){
+ if constexpr (HasValueGuard) { // Guard->* is invalid if Guard == nullptr
+ return (reinterpret_cast<Class *>(o)->*(ValueGuard))(*static_cast<T *>(newValue));
+ } else {
+ Q_UNUSED(o); // some compilers complain about unused variables
+ Q_UNUSED(newValue);
+ return true;
+ }
+ };
+ static constexpr bool(*GuardTE)(void *, void*) = HasValueGuard ? GuardTEHelper : nullptr;
+public:
QNotifiedProperty() = default;
@@ -428,66 +451,76 @@ public:
return value();
}
- void setValue(Class *owner, T &&newValue)
+ template<typename S>
+ auto setValue(Class *owner, S &&newValue) -> std::enable_if_t<!ValueGuardModifiesArgument && std::is_same_v<S, T>, void>
{
- if constexpr(std::is_invocable_v<decltype(Callback), Class>) {
- if (d.setValueAndReturnTrueIfChanged(std::move(newValue)))
- notify(owner);
- } else {
+ if constexpr (HasValueGuard) {
+ if (!(owner->*ValueGuard)(newValue))
+ return;
+ }
+ if constexpr (CallbackAcceptsOldValue) {
T oldValue = value(); // TODO: kind of pointless if there was no change
if (d.setValueAndReturnTrueIfChanged(std::move(newValue)))
notify(owner, &oldValue);
+ } else {
+ if (d.setValueAndReturnTrueIfChanged(std::move(newValue)))
+ notify(owner);
}
d.priv.removeBinding();
}
- void setValue(Class *owner, const T &newValue)
+ void setValue(Class *owner, std::conditional_t<ValueGuardModifiesArgument, T, const T &> newValue)
{
- if constexpr(std::is_invocable_v<decltype(Callback), Class>) {
- if (d.setValueAndReturnTrueIfChanged(newValue))
- notify(owner);
- } else {
+ if constexpr (HasValueGuard) {
+ if (!(owner->*ValueGuard)(newValue))
+ return;
+ }
+ if constexpr (CallbackAcceptsOldValue) {
+ // When newValue is T, we move it, if it's const T& it stays const T& and won't get moved
T oldValue = value();
- if (d.setValueAndReturnTrueIfChanged(newValue))
+ if (d.setValueAndReturnTrueIfChanged(std::move(newValue)))
notify(owner, &oldValue);
+ } else {
+ if (d.setValueAndReturnTrueIfChanged(std::move(newValue)))
+ notify(owner);
}
d.priv.removeBinding();
}
QPropertyBinding<T> setBinding(Class *owner, const QPropertyBinding<T> &newBinding)
{
- if constexpr(std::is_invocable_v<decltype(Callback), Class>) {
- QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o) {
- (reinterpret_cast<Class *>(o)->*Callback)();
- }));
- notify(owner);
- return oldBinding;
- } else {
+ if constexpr (CallbackAcceptsOldValue) {
T oldValue = value();
- QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o, void *oldValue) {
- (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldValue));
- }));
+ QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o, void *oldVal) {
+ (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldVal));
+ }, GuardTE));
notify(owner, &oldValue);
return oldBinding;
+ } else {
+ QPropertyBinding<T> oldBinding(d.priv.setBinding(newBinding, &d, owner, [](void *o, void *) {
+ (reinterpret_cast<Class *>(o)->*Callback)();
+ }, GuardTE));
+ notify(owner);
+ return oldBinding;
}
}
QPropertyBinding<T> setBinding(Class *owner, QPropertyBinding<T> &&newBinding)
{
QPropertyBinding<T> b(std::move(newBinding));
- if constexpr(std::is_invocable_v<decltype(Callback), Class>) {
+ if constexpr (CallbackAcceptsOldValue) {
+ T oldValue = value();
+ QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d, owner, [](void *o, void *oldVal) {
+ (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldVal));
+ }, GuardTE));
+ notify(owner, &oldValue);
+ return oldBinding;
+ } else {
QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d, owner, [](void *o, void *) {
(reinterpret_cast<Class *>(o)->*Callback)();
- }));
+ }, GuardTE));
notify(owner);
return oldBinding;
- } else {
- T oldValue = value();
- QPropertyBinding<T> oldBinding(d.priv.setBinding(b, &d, owner, [](void *o, void *oldValue) {
- (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldValue));
- }));
- notify(owner, &oldValue);
- return oldBinding;
}
}
@@ -495,17 +528,17 @@ public:
{
if (newBinding.valueMetaType().id() != qMetaTypeId<T>())
return false;
- if constexpr(std::is_invocable_v<decltype(Callback), Class>) {
+ if constexpr (CallbackAcceptsOldValue) {
+ T oldValue = value();
+ d.priv.setBinding(newBinding, &d, owner, [](void *o, void *oldVal) {
+ (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldVal));
+ }, GuardTE);
+ notify(owner, &oldValue);
+ } else {
d.priv.setBinding(newBinding, &d, owner, [](void *o, void *) {
(reinterpret_cast<Class *>(o)->*Callback)();
- });
+ }, GuardTE);
notify(owner);
- } else {
- T oldValue = value();
- d.priv.setBinding(newBinding, &d, owner, [](void *o, void *oldValue) {
- (reinterpret_cast<Class *>(o)->*Callback)(*reinterpret_cast<T *>(oldValue));
- });
- notify(owner, &oldValue);
}
return true;
}
@@ -544,10 +577,12 @@ private:
void notify(Class *owner, T *oldValue=nullptr)
{
d.priv.notifyObservers(&d);
- if constexpr(std::is_invocable_v<decltype(Callback), Class>)
+ if constexpr (std::is_invocable_v<decltype(Callback), Class>) {
+ Q_UNUSED(oldValue);
(owner->*Callback)();
- else
+ } else {
(owner->*Callback)(*oldValue);
+ }
}
Q_DISABLE_COPY_MOVE(QNotifiedProperty)
@@ -581,8 +616,8 @@ public:
void setSource(const QProperty<PropertyType> &property)
{ setSource(property.d.priv); }
- template <typename PropertyType, auto notifier>
- void setSource(const QNotifiedProperty<PropertyType, notifier> &property)
+ template <typename PropertyType, auto notifier, auto guard>
+ void setSource(const QNotifiedProperty<PropertyType, notifier, guard> &property)
{ setSource(property.d.priv); }
protected:
@@ -642,8 +677,8 @@ public:
setSource(property);
}
- template <typename PropertyType, typename Class, void(Class::*Callback)()>
- QPropertyChangeHandler(const QNotifiedProperty<PropertyType, Callback> &property, Functor handler)
+ template <typename PropertyType, auto Callback, auto Guard>
+ QPropertyChangeHandler(const QNotifiedProperty<PropertyType, Callback, Guard> &property, Functor handler)
: QPropertyObserver([](QPropertyObserver *self, void *) {
auto This = static_cast<QPropertyChangeHandler<Functor>*>(self);
This->m_handler();
@@ -675,9 +710,9 @@ QPropertyChangeHandler<Functor> QProperty<T>::subscribe(Functor f)
return onValueChanged(f);
}
-template <typename T, auto Callback>
+template <typename T, auto Callback, auto ValueGuard>
template<typename Functor>
-QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback>::onValueChanged(Functor f)
+QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback, ValueGuard>::onValueChanged(Functor f)
{
#if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L)
static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
@@ -685,9 +720,9 @@ QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback>::onValueChanged(F
return QPropertyChangeHandler<Functor>(*this, f);
}
-template <typename T, auto Callback>
+template <typename T, auto Callback, auto ValueGuard>
template<typename Functor>
-QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback>::subscribe(Functor f)
+QPropertyChangeHandler<Functor> QNotifiedProperty<T, Callback, ValueGuard>::subscribe(Functor f)
{
#if defined(__cpp_lib_is_invocable) && (__cpp_lib_is_invocable >= 201703L)
static_assert(std::is_invocable_v<Functor>, "Functor callback must be callable without any parameters");
diff --git a/src/corelib/kernel/qpropertybinding.cpp b/src/corelib/kernel/qpropertybinding.cpp
index 3a0f54b44a..8602ef957f 100644
--- a/src/corelib/kernel/qpropertybinding.cpp
+++ b/src/corelib/kernel/qpropertybinding.cpp
@@ -111,7 +111,10 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged()
bool newValue = false;
evalError = evaluationFunction(metaType, &newValue);
if (evalError.type() == QPropertyBindingError::NoError) {
- if (propertyPtr->extraBit() != newValue) {
+ bool updateAllowed = true;
+ if (hasStaticObserver && staticGuardCallback)
+ updateAllowed = staticGuardCallback(staticObserver, &newValue);
+ if (updateAllowed && propertyPtr->extraBit() != newValue) {
propertyPtr->setExtraBit(newValue);
changed = true;
}
@@ -121,7 +124,10 @@ bool QPropertyBindingPrivate::evaluateIfDirtyAndReturnTrueIfValueChanged()
evalError = evaluationFunction(metaType, resultVariant.data());
if (evalError.type() == QPropertyBindingError::NoError) {
int compareResult = 0;
- if (!QMetaType::compare(propertyDataPtr, resultVariant.constData(), metaType.id(), &compareResult) || compareResult != 0) {
+ bool updateAllowed = true;
+ if (hasStaticObserver && staticGuardCallback)
+ updateAllowed = staticGuardCallback(staticObserver, resultVariant.data());
+ if (updateAllowed && (!QMetaType::compare(propertyDataPtr, resultVariant.constData(), metaType.id(), &compareResult) || compareResult != 0)) {
changed = true;
metaType.destruct(propertyDataPtr);
metaType.construct(propertyDataPtr, resultVariant.constData());
diff --git a/src/corelib/kernel/qpropertybinding_p.h b/src/corelib/kernel/qpropertybinding_p.h
index 99a6b22b4b..151f5543d5 100644
--- a/src/corelib/kernel/qpropertybinding_p.h
+++ b/src/corelib/kernel/qpropertybinding_p.h
@@ -81,6 +81,7 @@ private:
struct {
void *staticObserver;
void (*staticObserverCallback)(void*, void*);
+ bool (*staticGuardCallback)(void*, void*);
};
};
QScopedPointer<std::vector<QPropertyObserver>> heapObservers;
@@ -107,7 +108,7 @@ public:
void setDirty(bool d) { dirty = d; }
void setProperty(void *propertyPtr) { propertyDataPtr = propertyPtr; }
- void setStaticObserver(void *observer, void (*callback)(void*, void*))
+ void setStaticObserver(void *observer, void (*callback)(void*, void*), bool (*guardCallback)(void *, void*))
{
if (observer) {
if (!hasStaticObserver) {
@@ -123,6 +124,7 @@ public:
hasStaticObserver = true;
staticObserver = observer;
staticObserverCallback = callback;
+ staticGuardCallback = guardCallback;
} else if (hasStaticObserver) {
hasStaticObserver = false;
new (&inlineDependencyObservers) ObserverArray();
diff --git a/src/corelib/kernel/qpropertyprivate.h b/src/corelib/kernel/qpropertyprivate.h
index 43318f25c0..145f2c66b9 100644
--- a/src/corelib/kernel/qpropertyprivate.h
+++ b/src/corelib/kernel/qpropertyprivate.h
@@ -84,7 +84,8 @@ public:
QUntypedPropertyBinding setBinding(const QUntypedPropertyBinding &newBinding,
void *propertyDataPtr, void *staticObserver = nullptr,
- void (*staticObserverCallback)(void *, void *) = nullptr);
+ void (*staticObserverCallback)(void *, void *) = nullptr,
+ bool (*guardCallback)(void *, void *) = nullptr);
QPropertyBindingPrivate *binding();
void evaluateIfDirty();
diff --git a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
index 7200bf176e..53d361ca26 100644
--- a/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
+++ b/tests/auto/corelib/kernel/qproperty/tst_qproperty.cpp
@@ -73,6 +73,7 @@ private slots:
void propertyAlias();
void notifiedProperty();
void notifiedPropertyWithOldValueCallback();
+ void notifiedPropertyWithGuard();
};
void tst_QProperty::functorBinding()
@@ -876,6 +877,136 @@ void tst_QProperty::notifiedPropertyWithOldValueCallback()
QCOMPARE(instance.property.value(), 4);
}
+struct ClassWithNotifiedPropertyWithGuard
+{
+ using This = ClassWithNotifiedPropertyWithGuard;
+ QVector<int> recordedValues;
+
+ void callback() { recordedValues << property.value(); }
+ void callback2() { recordedValues << property2.value(); }
+ void callback3() {}
+ bool trivialGuard(int ) {return true;}
+ bool reject42(int newValue) {return newValue != 42;}
+ bool bound(int &newValue) { newValue = qBound<int>(0, newValue, 100); return true; }
+
+ QNotifiedProperty<int, &This::callback, &This::trivialGuard> property;
+ QNotifiedProperty<int, &This::callback2, &This::reject42> property2;
+ QNotifiedProperty<int, &This::callback3, &This::bound> property3;
+};
+
+void tst_QProperty::notifiedPropertyWithGuard()
+{
+ {
+ // property with guard that returns always true is the same as using no guard
+ ClassWithNotifiedPropertyWithGuard instance;
+
+ std::array<QProperty<int>, 5> otherProperties = {
+ QProperty<int>([&]() { return instance.property + 1; }),
+ QProperty<int>([&]() { return instance.property + 2; }),
+ QProperty<int>([&]() { return instance.property + 3; }),
+ QProperty<int>([&]() { return instance.property + 4; }),
+ QProperty<int>([&]() { return instance.property + 5; }),
+ };
+
+ auto check = [&] {
+ const int val = instance.property.value();
+ for (int i = 0; i < int(otherProperties.size()); ++i)
+ QCOMPARE(otherProperties[i].value(), val + i + 1);
+ };
+
+ QVERIFY(instance.recordedValues.isEmpty());
+ check();
+
+ instance.property.setValue(&instance, 42);
+ QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.at(0), 42);
+ instance.recordedValues.clear();
+ check();
+
+ instance.property.setValue(&instance, 42);
+ QVERIFY(instance.recordedValues.isEmpty());
+ check();
+
+ int subscribedCount = 0;
+ QProperty<int> injectedValue(100);
+ instance.property.setBinding(&instance, [&injectedValue]() { return injectedValue.value(); });
+ auto subscriber = [&] { ++subscribedCount; };
+ std::array<QPropertyChangeHandler<decltype (subscriber)>, 10> subscribers = {
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber),
+ instance.property.subscribe(subscriber)
+ };
+
+ QCOMPARE(subscribedCount, 10);
+ subscribedCount = 0;
+
+ QCOMPARE(instance.property.value(), 100);
+ QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.at(0), 100);
+ instance.recordedValues.clear();
+ check();
+ QCOMPARE(subscribedCount, 0);
+
+ injectedValue = 200;
+ QCOMPARE(instance.property.value(), 200);
+ QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.at(0), 200);
+ instance.recordedValues.clear();
+ check();
+ QCOMPARE(subscribedCount, 10);
+ subscribedCount = 0;
+
+ injectedValue = 400;
+ QCOMPARE(instance.property.value(), 400);
+ QCOMPARE(instance.recordedValues.count(), 1);
+ QCOMPARE(instance.recordedValues.at(0), 400);
+ instance.recordedValues.clear();
+ check();
+ QCOMPARE(subscribedCount, 10);
+ }
+
+ {
+ // Values can be rejected
+ ClassWithNotifiedPropertyWithGuard instance2;
+
+ instance2.property2.setValue(&instance2, 1);
+ instance2.property2.setBinding(&instance2, [](){return 42;});
+ instance2.property2.setValue(&instance2, 2);
+ instance2.property2.setBinding(&instance2, [](){return 3;});
+ instance2.property2.setValue(&instance2, 42);
+ // Note that we get 1 twice
+ // This is an unfortunate result of the lazyness used for bindings
+ // When we call setBinding, the binding does not get evaluated, but we
+ // call the callback in notify; the callback will in our case query the
+ // properties' value. At that point we then evaluate the binding, and
+ // notice that the value is in fact disallowed. Thus we return the old
+ // value.
+ QVector<int> expected {1, 1, 2, 3};
+ QCOMPARE(instance2.recordedValues, expected);
+ }
+
+ {
+ // guard can modify incoming values
+ ClassWithNotifiedPropertyWithGuard instance3;
+ instance3.property3.setValue(&instance3, 5);
+ int i1 = 5;
+ QCOMPARE(instance3.property3.value(), i1);
+ instance3.property3.setBinding(&instance3, [](){return 255;});
+ QCOMPARE(instance3.property3.value(), 100);
+ const int i2 = -1;
+ instance3.property3.setValue(&instance3, i2);
+ QCOMPARE(instance3.property3.value(), 0);
+ }
+}
+
+
QTEST_MAIN(tst_QProperty);
#include "tst_qproperty.moc"