Files
KDDockWidgets/src/3rdparty/kdbindings/property.h
2023-01-06 12:41:10 -05:00

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