// 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