From 78bacd99d0b59bbd7b83690805377c4bad183f88 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Mon, 25 Aug 2025 23:49:22 +0200 Subject: [PATCH] App: Add helpers for guarding property overrides. Within code often it is required to temporarly override value of some object properties. To properly restore old values they must be stored somewhere and the developer must remember to manualy restore them. This commit introduces utilities that can be used to make it easier using RAII idiom to guard the overrides. --- src/App/CMakeLists.txt | 1 + src/App/PropertyOverrides.h | 294 ++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 src/App/PropertyOverrides.h diff --git a/src/App/CMakeLists.txt b/src/App/CMakeLists.txt index f274bb674b..943c57fcae 100644 --- a/src/App/CMakeLists.txt +++ b/src/App/CMakeLists.txt @@ -262,6 +262,7 @@ SET(Properties_HPP_SRCS PropertyStandard.h PropertyUnits.h PropertyExpressionEngine.h + PropertyOverrides.h ) SET(Properties_SRCS ${Properties_CPP_SRCS} diff --git a/src/App/PropertyOverrides.h b/src/App/PropertyOverrides.h new file mode 100644 index 0000000000..91530fffa0 --- /dev/null +++ b/src/App/PropertyOverrides.h @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2025 Kacper Donat * + * * + * This file is part of FreeCAD. * + * * + * FreeCAD is free software: you can redistribute it and/or modify it * + * under the terms of the GNU Lesser General Public License as * + * published by the Free Software Foundation, either version 2.1 of the * + * License, or (at your option) any later version. * + * * + * FreeCAD is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with FreeCAD. If not, see * + * . * + * * + ***************************************************************************/ + +#ifndef FREECAD_PROPERTYOVERRIDES_H +#define FREECAD_PROPERTYOVERRIDES_H + +#include +#include +#include +#include +#include + +#include + +namespace App +{ + +/** + * @brief Default accessor for property-like classes. + * + * The `DefaultAccessor` provides a generic implementation of `get()` and `set()` methods + * that call the `getValue()` and `setValue()` members of a property. This works for the + * majority of `App::Property` types in FreeCAD. + * + * ### Usage + * Normally you do not use `DefaultAccessor` directly. Instead, use `PropAccessor

`, + * which inherits from `DefaultAccessor` unless specialized. + * + * @tparam P The property type, e.g. `App::PropertyBool`, `App::PropertyFloat`. + */ +template +struct DefaultAccessor +{ + /// Value type of the property, deduced from `getValue()`. + using Value = std::decay_t().getValue())>; + + /// Retrieves the property's current value. + static Value get(const P& p) + { + return p.getValue(); + } + + /// Assigns a new value to the property. + static void set(P& p, const Value& v) + { + p.setValue(v); + } +}; + +/** + * @brief Customizable accessor hook for properties. + * + * `PropAccessor` defaults to `DefaultAccessor`. Specialize `PropAccessor

` if you + * need custom behavior for a specific property type (e.g. suppressing recomputes, + * triggering extra updates). + * + * @tparam P The property type to customize. + */ +template +struct PropAccessor: DefaultAccessor

+{ +}; + +template +concept HasGetValue = requires(const P& cp) { cp.getValue(); }; +template +concept HasSetValue = requires(P& p, typename DefaultAccessor

::Value v) { p.setValue(v); }; +template +concept PropertyLike = HasGetValue

&& HasSetValue

; + +/** + * @brief RAII guard that overrides a property for the duration of a scope. + * + * `ScopedPropertyOverride` temporarily changes the value of a property, and restores + * the old value when the guard is destroyed. This ensures properties are always restored + * even if exceptions occur. + * + * ### Example + * @code{.cpp} + * App::PropertyBool& vis = ...; + * { + * App::ScopedPropertyOverride guard(vis, false); + * // object is hidden here + * } + * // old visibility automatically restored + * @endcode + * + * @tparam P The property type, e.g. `App::PropertyBool`. + */ +template +class ScopedPropertyOverride +{ +public: + using Value = PropAccessor

::Value; + + + /** + * @brief Constructs an override guard. + * + * Saves the old value and sets the property to a new one. + * + * @param prop Reference to the property being overridden. + * @param newValue The temporary value to apply. + */ + ScopedPropertyOverride(P& prop, const Value& newValue) + : _prop(&prop) + , _old(PropAccessor

::get(prop)) + , _active(true) + { + PropAccessor

::set(*_prop, newValue); + } + + FC_DISABLE_COPY(ScopedPropertyOverride); + + ScopedPropertyOverride(ScopedPropertyOverride&& other) noexcept + : _prop(other._prop) + , _old(std::move(other._old)) + , _active(other._active) + { + other._prop = nullptr; + other._active = false; + } + + ScopedPropertyOverride& operator=(ScopedPropertyOverride&& other) noexcept + { + if (this != &other) { + restore(); + _prop = other._prop; + _old = std::move(other._old); + _active = other._active; + other._prop = nullptr; + other._active = false; + } + return *this; + } + + /** + * @brief Destructor. + * + * Restores the original value of the property. + */ + ~ScopedPropertyOverride() + { + restore(); + } + + /** + * @brief Checks if the guard is currently active. + * + * @return True if the override is in effect. + */ + bool active() const + { + return _active; + } + +private: + void restore() noexcept + { + if (_prop && _active) { + PropAccessor

::set(*_prop, _old); + _active = false; + } + } + + P* _prop {nullptr}; ///< Pointer to the property being overridden. + Value _old {}; ///< Saved old value. + bool _active {false}; ///< Whether this guard is currently active. +}; + +/** + * @brief Helper function to create a `ScopedPropertyOverride` with type deduction. + * + * @tparam P The property type. + * @tparam V The value type (deduced). + * @param prop Property reference. + * @param v New value to assign temporarily. + * + * @return A `ScopedPropertyOverride` guard. + */ +template +auto makeOverride(P& prop, V&& v) +{ + using Guard = ScopedPropertyOverride

; + return Guard(prop, static_cast(std::forward(v))); +} + +/** + * @brief RAII context for managing multiple property overrides. + * + * `PropertyOverrideContext` stores multiple property overrides at once. When the + * context is destroyed, all properties are restored in reverse (LIFO) order. + * + * ### Example + * @code{.cpp} + * App::PropertyBool& vis = ...; + * App::PropertyString& label = ...; + * + * App::PropertyOverrideContext overrides; + * overrides.override(vis, false); + * overrides.override(label, "Temp"); + * // both props overridden + * // ... + * // restored automatically when ctx is destroyed + * @endcode + */ +class PropertyOverrideContext +{ +public: + PropertyOverrideContext() = default; + + FC_DISABLE_COPY(PropertyOverrideContext); + FC_DEFAULT_MOVE(PropertyOverrideContext); + + /** + * @brief Destructor. + * + * Restores all overrides. + */ + ~PropertyOverrideContext() + { + clear(); + } + + /** + * @brief Override a property within this context. + * + * Saves the old value and assigns a new one. The value is restored automatically + * when the context is destroyed or `clear()` is called. + * + * @tparam P Property type. + * @param prop Reference to property. + * @param newValue Temporary value to assign. + */ + template + void override(P& prop, PropAccessor

::Value newValue) + { + using Access = PropAccessor

; + + _guards.emplace_back([&prop, old = PropAccessor

::get(prop)]() { + Access::set(prop, std::move(old)); + }); + + Access::set(prop, std::move(newValue)); + } + + /** + * @brief Restore all properties immediately. + * + * Calls the restore handlers in reverse order of creation and clears the context. + */ + void clear() + { + for (auto& _guard : std::views::reverse(_guards)) { + _guard(); + } + _guards.clear(); + } + + /** + * @brief Returns the number of active overrides in the context. + * + * @return Count of overrides. + */ + std::size_t size() const + { + return _guards.size(); + } + +private: + std::vector> _guards; +}; +} // namespace App +#endif // FREECAD_PROPERTYOVERRIDES_H