diff options
author | Fabian Kosmale <fabian.kosmale@qt.io> | 2020-06-19 13:58:53 +0200 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2020-06-25 14:11:56 +0200 |
commit | e18a060c034beec6c7f4a22044255d9fb55d091a (patch) | |
tree | 068400f49e62abc8c0c85e31ae7c1c73afee5647 /src/corelib/kernel/qproperty.h | |
parent | 6a24ac7c4e0a7737b0cd0738c4e6fe8b9ac589ca (diff) |
QNotifiedProperty: Add guard callback
A guard callback is a predicate which takes the new value set by
setValue or computed as the result of a binding expression. If it
returns false, the value is discarded and the old value is kept.
Note that due to lazyness, when setting a binding, we still notify
everyone as the binding is only evaluated on demand, and the guard can
thus only run when someone actually queries the value.
Note further that a guard is allowed to modify the value that is passed
to it (e.g. to clamp it to a certain range).
Task-number: QTBUG-85032
Change-Id: I3551e4357fe5780fb75da80bf8be208ec152dc2a
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'src/corelib/kernel/qproperty.h')
-rw-r--r-- | src/corelib/kernel/qproperty.h | 141 |
1 files changed, 88 insertions, 53 deletions
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"); |