447 lines
14 KiB
C++
447 lines
14 KiB
C++
/*
|
|
This file is part of KDBindings.
|
|
|
|
SPDX-FileCopyrightText: 2021-2023 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
|
|
Author: Sean Harmer <sean.harmer@kdab.com>
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
Contact KDAB at <info@kdab.com> for commercial licensing options.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <kdbindings/property_updater.h>
|
|
#include <kdbindings/signal.h>
|
|
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <type_traits>
|
|
|
|
namespace KDBindings {
|
|
|
|
/**
|
|
* @brief A namespace containing parts of KDBindings that are not part of the public API.
|
|
*
|
|
* The contents of this namespace may only be accessed by the implementation of KDBindings, they
|
|
* are not part of KDBindings public API and may be altered at any time and provide no guarantees
|
|
* of any kind when used directly.
|
|
**/
|
|
namespace Private {
|
|
|
|
template<typename X, typename Y, typename = void>
|
|
struct are_equality_comparable : std::false_type {
|
|
};
|
|
|
|
template<typename X, typename Y>
|
|
struct are_equality_comparable<X, Y,
|
|
std::enable_if_t<
|
|
std::is_same<
|
|
std::decay_t<
|
|
decltype(std::equal_to<>{}(std::declval<X>(), std::declval<Y>()))>,
|
|
bool>::value>> : std::true_type {
|
|
};
|
|
|
|
template<typename X, typename Y>
|
|
constexpr bool are_equality_comparable_v = are_equality_comparable<X, Y>::value;
|
|
|
|
} // namespace Private
|
|
|
|
/**
|
|
* A ReadOnlyProperty is thrown when trying to set the value of a Property
|
|
* that has a PropertyUpdater associated with it.
|
|
*
|
|
* Most commonly because the property holds the result of a binding expression.
|
|
*/
|
|
struct ReadOnlyProperty : std::runtime_error {
|
|
ReadOnlyProperty() = delete;
|
|
|
|
using std::runtime_error::runtime_error;
|
|
};
|
|
|
|
/**
|
|
* @brief An instance of the KDBindings::equal_to struct is used to decide whether
|
|
* two values of type T are equal in the context of data binding.
|
|
*
|
|
* If a new value is assigned to a Property and the existing value is equal_to
|
|
* the existing value, the Property will not emit the Property::valueChanged or
|
|
* Property::valueAboutToChange signals and not change the stored value.
|
|
*
|
|
* By default, all classes T that are equality comparable using std::equal_to
|
|
* delegate to std::equal_to for equality comparison. All other instances are
|
|
* assumed to never be equal.
|
|
* Therefore, to change the equality behavior of a Property<T>, either:
|
|
* - Implement operator== for T (std::equal_to uses operator== for equality comparison)
|
|
* - Provide a template spezialization of KDBindings::equal_to and implement operator()()
|
|
*/
|
|
template<typename T>
|
|
struct equal_to {
|
|
/**
|
|
* This implementation of operator()() is only enabled if std::equal_to can be
|
|
* used to compare values of type T.
|
|
* In this case, std::equal_to is used to decide whether values of type T are equal.
|
|
*
|
|
* @return bool - Whether the values are equal.
|
|
*/
|
|
auto operator()(const T &x, const T &y) const noexcept
|
|
-> std::enable_if_t<Private::are_equality_comparable_v<T, T>, bool>
|
|
{
|
|
return std::equal_to<>{}(x, y);
|
|
}
|
|
|
|
/**
|
|
* The fallback implementation of operator()() if the types are not equality comparable
|
|
* using std::equal_to (i.e. no operator== implementation exists for this type).
|
|
* In this case, two values of type T are assumed to never be equal.
|
|
*
|
|
* @return bool - Whether the values are equal - always false for this default implementation
|
|
*/
|
|
template<typename X, typename Y>
|
|
auto operator()(const X &, const Y &) const noexcept
|
|
-> std::enable_if_t<!Private::are_equality_comparable_v<X, Y>, bool>
|
|
{
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @brief A property represents a value that can be part of or the result of data binding.
|
|
*
|
|
* Properties are at the basis of data binding.
|
|
* They can contain a value of any type T.
|
|
* The value can either represent the result of a data binding or a value that is used
|
|
* in the calculation of a binding expression.
|
|
*
|
|
* If the value of a property is changed, either manually or because it is the result of a
|
|
* binding expression, the Property will emit the valueAboutToChange(), and valueChanged() Signal.
|
|
* If it is used as part of a binding expression, the expression will be marked
|
|
* as dirty and (unless a custom BindingEvaluator is used) updated immediately.
|
|
*
|
|
* To create a property from a data binding expression, use the @ref makeBoundProperty or @ref makeBinding
|
|
* functions in the @ref KDBindings namespace.
|
|
*
|
|
* Examples:
|
|
* - @ref 04-simple-property/main.cpp
|
|
* - @ref 05-property-bindings/main.cpp
|
|
* - @ref 06-lazy-property-bindings/main.cpp
|
|
*/
|
|
template<typename T>
|
|
class Property
|
|
{
|
|
public:
|
|
typedef T valuetype;
|
|
|
|
/**
|
|
* Properties are default constructable.
|
|
*
|
|
* The value of a default constructed property is then also default constructed.
|
|
*/
|
|
Property() = default;
|
|
|
|
/**
|
|
* If a Property is destroyed, it emits the destroyed() Signal.
|
|
*/
|
|
~Property()
|
|
{
|
|
m_destroyed.emit();
|
|
}
|
|
|
|
/**
|
|
* Constructs a Property from the provided value.
|
|
*/
|
|
explicit Property(T value) noexcept(std::is_nothrow_move_constructible<T>::value)
|
|
: m_value{ std::move(value) }
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Properties are not copyable.
|
|
*/
|
|
Property(Property<T> const &other) = delete;
|
|
Property &operator=(Property<T> const &other) = delete;
|
|
|
|
/**
|
|
* @brief Properties are movable.
|
|
*
|
|
* This will emit the moved() Signal of the property that is moving as well as
|
|
* the property being moved into.
|
|
* All data bindings that depend on this Property will update their references
|
|
* to the newly move-constructed Property using this Signal.
|
|
*/
|
|
Property(Property<T> &&other) noexcept(std::is_nothrow_move_constructible<T>::value)
|
|
: m_value(std::move(other.m_value))
|
|
, m_valueAboutToChange(std::move(other.m_valueAboutToChange))
|
|
, m_valueChanged(std::move(other.m_valueChanged))
|
|
, m_destroyed(std::move(other.m_destroyed))
|
|
, m_updater(std::move(other.m_updater))
|
|
{
|
|
// We do not move the m_moved signal so that objects interested in the moved-into
|
|
// property can recreate any connections they need.
|
|
|
|
// If we have an updater, let it know how to update our internal value
|
|
if (m_updater) {
|
|
using namespace std::placeholders;
|
|
m_updater->setUpdateFunction(
|
|
std::bind(&Property<T>::setHelper, this, _1));
|
|
}
|
|
|
|
// Emit the moved signals for the moved from and moved to properties
|
|
m_moved.emit(*this);
|
|
other.m_moved.emit(*this);
|
|
m_moved = std::move(other.m_moved);
|
|
}
|
|
|
|
/**
|
|
* See: Property(Property<T> &&other)
|
|
*/
|
|
Property &operator=(Property<T> &&other) noexcept(std::is_nothrow_move_assignable<T>::value)
|
|
{
|
|
// We do not move the m_moved signal so that objects interested in the moved-into
|
|
// property can recreate any connections they need.
|
|
m_value = std::move(other.m_value);
|
|
m_valueAboutToChange = std::move(other.m_valueAboutToChange);
|
|
m_valueChanged = std::move(other.m_valueChanged);
|
|
m_destroyed = std::move(other.m_destroyed);
|
|
m_updater = std::move(other.m_updater);
|
|
|
|
// If we have an updater, let it know how to update our internal value
|
|
if (m_updater) {
|
|
using namespace std::placeholders;
|
|
m_updater->setUpdateFunction(
|
|
std::bind(&Property<T>::setHelper, this, _1));
|
|
}
|
|
|
|
// Emit the moved signals for the moved from and moved to properties
|
|
m_moved.emit(*this);
|
|
other.m_moved.emit(*this);
|
|
m_moved = std::move(other.m_moved);
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Construct a property that will be updated by the specified PropertyUpdater.
|
|
*
|
|
* This constructor is usually called by the creation of a data binding
|
|
* and usually doesn't need to be called manually.
|
|
*/
|
|
template<typename UpdaterT>
|
|
explicit Property(std::unique_ptr<UpdaterT> &&updater)
|
|
{
|
|
*this = std::move(updater);
|
|
}
|
|
|
|
/**
|
|
* Assigns a Binding or other Updater to this Property.
|
|
*
|
|
* In comparison to the move assignment operator, this does NOT change any
|
|
* of the existing Signal connections. They are all kept as-is.
|
|
* Only the source of the update is changed.
|
|
*
|
|
* This will immediately set the value of this Property to the
|
|
* result of the updater and will call the valueAboutToChange or valueChanged
|
|
* Signals respectively if necessary.
|
|
*/
|
|
template<typename UpdaterT>
|
|
Property &operator=(std::unique_ptr<UpdaterT> &&updater)
|
|
{
|
|
m_updater = std::move(updater);
|
|
|
|
// Let the updater know how to update our internal value
|
|
using namespace std::placeholders;
|
|
m_updater->setUpdateFunction(
|
|
std::bind(&Property<T>::setHelper, this, _1));
|
|
|
|
// Now synchronise our value with whatever the updator has right now.
|
|
setHelper(m_updater->get());
|
|
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* @brief Disconnects the binding from this Property
|
|
*
|
|
* If this Property has a binding, it will no longer update it.
|
|
* Otherwise, this function does nothing.
|
|
*
|
|
* The value of the property does not change when it is reset.
|
|
*/
|
|
void reset()
|
|
{
|
|
m_updater.reset();
|
|
}
|
|
|
|
/**
|
|
* Returns a Signal that will be emitted before the value is changed.
|
|
*
|
|
* The first emitted value is the current value of the Property.<br>
|
|
* The second emitted value is the new value of the Property.
|
|
*/
|
|
Signal<const T &, const T &> &valueAboutToChange() const { return m_valueAboutToChange; }
|
|
|
|
/**
|
|
* Returns a Signal that will be emitted after the value of the property changed.
|
|
*
|
|
* The emitted value is the current (new) value of the Property.
|
|
*/
|
|
Signal<const T &> &valueChanged() const { return m_valueChanged; }
|
|
|
|
/**
|
|
* Returns a Signal that will be emitted when the Property is moved.
|
|
*
|
|
* The emitted value is a reference to the newly constructed Property that
|
|
* this Property was moved into.
|
|
*
|
|
* The Signal will also be emitted if another Property is moved into
|
|
* this property.
|
|
*/
|
|
Signal<Property<T> &> &moved() { return m_moved; }
|
|
Signal<Property<T> &> const &moved() const { return m_moved; }
|
|
|
|
/**
|
|
* Returns a Signal that will be emitted when this Property is destructed.
|
|
*/
|
|
Signal<> &destroyed() const { return m_destroyed; }
|
|
|
|
/**
|
|
* Assign a new value to this Property.
|
|
*
|
|
* If the new value is equal_to the existing value, the value will not be
|
|
* changed and no Signal will be emitted.
|
|
*
|
|
* Otherwise, the valueAboutToChange() Signal will be emitted before the value
|
|
* of the Property is changed.
|
|
* Then, the provided value will be assigned, and the valueChanged() Signal
|
|
* will be emitted.
|
|
*
|
|
* @throw ReadOnlyProperty If the Property has a PropertyUpdater associated with it (i.e. it is
|
|
* the result of a binding expression).
|
|
*/
|
|
void set(T value)
|
|
{
|
|
if (m_updater) {
|
|
throw ReadOnlyProperty{
|
|
"Cannot set value on a read-only property. This property likely holds the result of a binding expression."
|
|
};
|
|
}
|
|
setHelper(std::move(value));
|
|
}
|
|
|
|
/**
|
|
* Returns the value represented by this Property.
|
|
*/
|
|
T const &get() const
|
|
{
|
|
return m_value;
|
|
}
|
|
|
|
/**
|
|
* Assigns a new value to this Property.
|
|
*
|
|
* See: set().
|
|
*/
|
|
Property<T> &operator=(T const &rhs)
|
|
{
|
|
set(std::move(rhs));
|
|
return *this;
|
|
}
|
|
|
|
/**
|
|
* Returns the value represented by this Property.
|
|
*
|
|
* See: get().
|
|
*/
|
|
T const &operator()() const
|
|
{
|
|
return Property<T>::get();
|
|
}
|
|
|
|
private:
|
|
void setHelper(T value)
|
|
{
|
|
if (equal_to<T>{}(value, m_value))
|
|
return;
|
|
|
|
m_valueAboutToChange.emit(m_value, value);
|
|
m_value = std::move(value);
|
|
m_valueChanged.emit(m_value);
|
|
}
|
|
|
|
T m_value;
|
|
// the signals in a property are mutable, as a property
|
|
// being "const" should mean that it's value or binding does
|
|
// not change, not that nobody can listen to it anymore.
|
|
mutable Signal<const T &, const T &> m_valueAboutToChange;
|
|
mutable Signal<const T &> m_valueChanged; // By const ref so we can emit the signal for move-only types of T e.g. std::unique_ptr<int>
|
|
Signal<Property<T> &> m_moved;
|
|
mutable Signal<> m_destroyed;
|
|
std::unique_ptr<PropertyUpdater<T>> m_updater;
|
|
};
|
|
|
|
/**
|
|
* Outputs the value of the Property onto an output stream.
|
|
*/
|
|
template<typename T>
|
|
std::ostream &operator<<(std::ostream &stream, Property<T> const &property)
|
|
{
|
|
stream << property.get();
|
|
return stream;
|
|
}
|
|
|
|
/**
|
|
* Reads a value of type T from the input stream and assigns it to
|
|
* the Property using set().
|
|
*/
|
|
template<typename T>
|
|
std::istream &operator>>(std::istream &stream, Property<T> &prop)
|
|
{
|
|
T temp;
|
|
stream >> temp;
|
|
prop.set(std::move(temp));
|
|
return stream;
|
|
}
|
|
|
|
namespace Private {
|
|
|
|
template<typename T>
|
|
struct is_property_helper : std::false_type {
|
|
};
|
|
|
|
template<typename T>
|
|
struct is_property_helper<Property<T>> : std::true_type {
|
|
};
|
|
|
|
template<typename T>
|
|
struct is_property : is_property_helper<std::decay_t<T>> {
|
|
};
|
|
|
|
} // namespace Private
|
|
|
|
/**
|
|
* @example 04-simple-property/main.cpp
|
|
*
|
|
* An example of how to create a KDBindings::Property and use its valueChanged() KDBindings::Signal to receive notifications whenever the value of the KDBindigns::Property changes.
|
|
*
|
|
* The output of this example is:
|
|
* ```
|
|
* The new value is 42
|
|
* The new value is 69
|
|
* Property value is 69
|
|
* ```
|
|
*/
|
|
|
|
/**
|
|
* @example 05-property-bindings/main.cpp
|
|
*
|
|
* An example of how to use makeBoundProperty() to create a KDBindings::Property that is automatically updated once any of its inputs change.
|
|
*
|
|
* The output of this example is:
|
|
* ```
|
|
* The initial size of the image = 1920000 bytes
|
|
* The new size of the image = 4608000 bytes
|
|
* The new size of the image = 8294400 bytes
|
|
* ```
|
|
*/
|
|
|
|
} // namespace KDBindings
|