File size: 19,861 Bytes
985c397 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 | // SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2025 Kacper Donat <kacper@kadet.net> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef STYLEPARAMETERS_PARAMETERMANAGER_H
#define STYLEPARAMETERS_PARAMETERMANAGER_H
#include <list>
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include <QColor>
#include <App/Application.h>
#include <Base/Bitmask.h>
#include <Base/Parameter.h>
// That macro uses inline const because some older compilers to not properly support constexpr
// for std::string. It should be changed into static constepxr once we migrate to newer compiler.
#define DEFINE_STYLE_PARAMETER(_name_, _defaultValue_) \
static inline const Gui::StyleParameters::ParameterDefinition _name_ \
{ \
.name = #_name_, .defaultValue = (_defaultValue_), \
}
namespace Gui::StyleParameters
{
// Forward declaration for Parser
class Parser;
/**
* @brief Represents a length in a specified unit.
*
* This struct is a very simplified representation of lengths that can be used as parameters for
* styling purposes. The length basically consists of value and unit. Unit is optional, empty unit
* represents a dimensionless length that can be used as a scalar. This struct does not care about
* unit conversions as its uses do not require it.
*/
struct Numeric
{
/// Numeric value of the length.
double value;
/// Unit of the length, empty if the value is dimensionless.
std::string unit = "";
/**
* @name Operators
*
* This struct supports basic operations on Length. Each operation requires for operands to be
* the same unit. Multiplication and division additionally allow one operand to be dimensionless
* and hence act as a scalar.
*
* @code{c++}
* Numeric a { 10, "px" };
* Numeric b { 5, "px" };
*
* Numeric differentUnit { 3, "rem" }
* Numeric scalar { 2, "" };
*
* // basic operations with the same unit are allowed
* auto sum = a + b; // 15 px
* auto difference = a - 5; // 10 px
*
* // basic operations with mixed units are NOT allowed
* auto sumOfIncompatibleUnits = a + differentUnit; // will throw
* auto productOfIncompatibleUnits = a * differentUnit; // will throw
*
* // exception is that for multiplication and division dimensionless units are allowed
* auto productWithScalar = a * scalar; // 20 px
* @endcode
* @{
*/
Numeric operator+(const Numeric& rhs) const;
Numeric operator-(const Numeric& rhs) const;
Numeric operator-() const;
Numeric operator/(const Numeric& rhs) const;
Numeric operator*(const Numeric& rhs) const;
/// @}
private:
void ensureEqualUnits(const Numeric& rhs) const;
};
/**
* @brief This struct represents any valid value that can be used as the parameter value.
*
* The value can be one of three basic types:
* - Numbers / Lengths (so any length with optional unit) (Length)
* - Colors (QColor)
* - Any other generic expression. (std::string)
*
* As a rule, operations can be only performed over values of the same type.
*/
struct Value: std::variant<Numeric, Base::Color, std::string>
{
using std::variant<Numeric, Base::Color, std::string>::variant;
/**
* Converts the object into its string representation.
*
* @return A string representation of the object that can later be used in QSS.
*/
std::string toString() const;
};
/**
* @brief A structure to define parameters which can be referenced in the code.
*
* @tparam T The type of the parameter.
*
* This structure allows defining parameters which encapsulate a name and a corresponding
* default value. Parameters defined this way can be reused across the codebase for consistency.
* The supported value types include:
* - Numeric types.
* - Colors using `Base::Color`.
* - Strings.
*
* Example Usage:
* @code{.cpp}
* DEFINE_STYLE_PARAMETER(SomeParameter, Numeric { 10 });
* DEFINE_STYLE_PARAMETER(TextColor, Base::Color(0.5F, 0.2F, 0.8F));
* @endcode
*/
template<typename T>
struct ParameterDefinition
{
/// The name of the parameter, must be unique.
const char* name;
/// The default value of the parameter.
T defaultValue;
};
/**
* @struct Parameter
*
* @brief Represents a named, dynamic expression-based parameter.
*
* The Parameter structure is used to define reusable named variables in styling or layout systems.
* Each parameter consists of a `name` and a `value` string, where the value is a CSS-like
* expression that supports numbers, units, arithmetic, colors, functions, and parameter references.
*
* ### Naming Convention
* Parameter names must be unique and follow **CamelCase**.
*/
struct Parameter
{
/// Comparator that assumes that parameters are equal as long as name is the same
struct NameComparator
{
bool operator()(const Parameter& lhs, const Parameter& rhs) const
{
return lhs.name < rhs.name;
}
};
/// Name of the parameter, name should follow CamelCase
std::string name;
/// Expression associated with the parameter
std::string value;
};
enum class ParameterSourceOption
{
// clang-format off
/// Parameters are read-only and the source does not allow editing
ReadOnly = 1 << 0,
/// Parameters are expected to be edited by the user, not only theme developers
UserEditable = 1 << 1,
// clang-format on
};
using ParameterSourceOptions = Base::Flags<ParameterSourceOption>;
/**
* @brief Abstract base class representing a source of style parameters.
*
* A `ParameterSource` is responsible for managing a collection of named parameters. Each source
* has metadata describing its type, characteristics, and behavior.
*
* ### Key Responsibilities
* - Define, update, and remove parameters within the source.
* - Provide access to all parameters or specific ones by name.
* - Act as a backend for parameter management, feeding the `ParameterManager` with available
* parameter data.
*
* ### Metadata
* Each parameter source includes metadata consisting of:
* - `name`: Name of the source, for identification purposes.
* - `options`: Flags specifying optional behavior (e.g., `ReadOnly`, `UserEditable`).
*
* ### Notes on Usage
* - Subclasses of `ParameterSource` (e.g., `BuiltInParameterSource`, `UserParameterSource`)
* implement different storage mechanisms and behaviors based on whether parameters are
* pre-defined, user-defined, or loaded in memory.
* - Parameters can be retrieved and manipulated globally through the `ParameterManager`, which
* aggregates multiple `ParameterSource` instances.
*
* #### Example
* @code{.cpp}
* // Create an in-memory parameter source
* InMemoryParameterSource source({
* Parameter{ "BasePadding", "16px" },
* Parameter{ "DefaultColor", "#ff00ff" },
* });
*
* source.define(Parameter{ "Margin", "4px" }); // Adds a new parameter
*
* auto padding = source.get("BasePadding"); // Retrieves parameter named "BasePadding"
* auto parametersList = source.all(); // Retrieve all parameters
* @endcode
*
* ### Subclass Requirements
* Derived classes must implement:
* - `all()` - Retrieve all parameters in the source.
* - `get()` - Retrieve a specific parameter.
* - `define()` - Add or update a parameter, can be left empty for readonly sources.
* - `remove()` - Remove a parameter by name, can be left empty for readonly sources.
*/
class GuiExport ParameterSource
{
public:
using enum ParameterSourceOption;
/**
* @brief Contains metadata information about a `ParameterSource`.
*
* The `Metadata` struct provides a way to describe the characteristics and identity
* of a `ParameterSource`. It includes a name for identification and a set of options
* that define the source's behavior and restrictions.
*/
struct Metadata
{
/// The name of the parameter source. Should be marked for translation using QT_TR_NOOP
std::string name;
/// Flags defining the behavior and properties of the parameter source.
ParameterSourceOptions options {};
};
/// Metadata of the parameter source
Metadata metadata;
FC_DEFAULT_MOVE(ParameterSource);
FC_DISABLE_COPY(ParameterSource);
explicit ParameterSource(const Metadata& metadata);
virtual ~ParameterSource() = default;
/**
* @brief Retrieves a list of all parameters available in the source.
*
* This method returns every parameter defined within this source, enabling iteration and bulk
* access to all stored values.
*
* @return A list containing all `Parameter` objects stored in the source.
*/
virtual std::list<Parameter> all() const = 0;
/**
* @brief Retrieves a specific parameter by its name.
*
* @param[in] name The name of the parameter to retrieve.
* @return An optional containing the requested parameter if it exists, or empty if not.
*/
virtual std::optional<Parameter> get(const std::string& name) const = 0;
/**
* @brief Defines or updates a parameter in the source.
*
* Adds a new parameter to the source if it doesn't already exist or updates the value of an
* existing parameter with the same name.
*
* @param[in] parameter The `Parameter` object to define or update in the source.
*/
virtual void define([[maybe_unused]] const Parameter& parameter)
{}
/**
* @brief Removes a parameter from the source by its name.
*
* Deletes the specific parameter from the source if it exists. If no parameter with the given
* name is found, the method does nothing.
*
* @param[in] name The name of the parameter to remove.
*/
virtual void remove([[maybe_unused]] const std::string& name)
{}
/**
* @brief Flushes buffered changes into more persistent storage.
*/
virtual void flush()
{}
};
/**
* @brief In-memory parameter source that stores parameters in a map.
*
* This source is useful for temporary parameter storage or when you need to
* define parameters programmatically without persisting them to disk.
*/
class GuiExport InMemoryParameterSource: public ParameterSource
{
std::map<std::string, Parameter> parameters;
public:
InMemoryParameterSource(const std::list<Parameter>& parameters, const Metadata& metadata);
std::list<Parameter> all() const override;
std::optional<Parameter> get(const std::string& name) const override;
void define(const Parameter& parameter) override;
void remove(const std::string& name) override;
};
/**
* @brief Built-in parameter source that reads from FreeCAD's parameter system.
*
* This source provides access to predefined parameters that are stored in
* FreeCAD's global parameter system. These parameters are typically defined
* by the application and are read-only.
*/
class GuiExport BuiltInParameterSource: public ParameterSource
{
public:
explicit BuiltInParameterSource(const Metadata& metadata = {});
std::list<Parameter> all() const override;
std::optional<Parameter> get(const std::string& name) const override;
private:
ParameterGrp::handle hGrpThemes = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Themes"
);
ParameterGrp::handle hGrpView = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/View"
);
std::map<std::string, ParameterGrp::handle> params = {
{"ThemeAccentColor1", hGrpThemes},
{"ThemeAccentColor2", hGrpThemes},
{"ThemeAccentColor3", hGrpThemes},
{"BackgroundColor", hGrpView},
};
};
/**
* @brief User-defined parameter source that reads from user preferences.
*
* This source provides access to user-defined parameters that are stored
* in the user's preference file. These parameters can be modified by the
* user and persist across application sessions.
*/
class GuiExport UserParameterSource: public ParameterSource
{
ParameterGrp::handle hGrp;
public:
UserParameterSource(ParameterGrp::handle hGrp, const Metadata& metadata);
std::list<Parameter> all() const override;
std::optional<Parameter> get(const std::string& name) const override;
void define(const Parameter& parameter) override;
void remove(const std::string& name) override;
};
/**
* @class YamlParameterSource
* @brief A ParameterSource implementation that loads and saves parameters
* from a YAML file using yaml-cpp.
*
* This class maintains an in-memory map of parameters loaded from a YAML file.
* Any changes through define() or remove() will also update the file.
*/
class GuiExport YamlParameterSource: public ParameterSource
{
public:
/**
* @brief Constructs a YamlParameterSource that reads parameters from the given YAML file.
*
* If the file exists, all key-value pairs are loaded into memory.
* If the file does not exist, an empty parameter set is initialized.
*
* @param filePath Path to the YAML file used for persistence.
* @param metadata Optional metadata describing this source.
*/
explicit YamlParameterSource(const std::string& filePath, const Metadata& metadata = {});
void changeFilePath(const std::string& path);
void reload();
std::list<Parameter> all() const override;
std::optional<Parameter> get(const std::string& name) const override;
void define(const Parameter& param) override;
void remove(const std::string& name) override;
void flush() override;
private:
std::string filePath;
std::map<std::string, Parameter> parameters;
};
/**
* @brief Central manager for style parameters that aggregates multiple sources.
*
* The ParameterManager is responsible for:
* - Managing multiple parameter sources
* - Resolving parameter references and expressions
* - Caching resolved values for performance
* - Handling circular references
*/
class GuiExport ParameterManager
{
std::list<ParameterSource*> _sources;
mutable std::map<std::string, Value> _resolved;
public:
struct ResolveContext
{
/// Names of parameters currently being resolved.
std::set<std::string> visited;
};
ParameterManager();
/**
* @brief Clears the internal cache of resolved values.
*
* Call this method when parameters have been modified to ensure
* that the changes are reflected in subsequent resolutions.
*/
void reload();
/**
* @brief Replaces parameter placeholders in a string with their resolved values.
*
* This method performs simple string substitution of @parameter references
* with their actual values. It does not evaluate expressions, only performs
* direct substitution.
*
* @param expression The string containing parameter placeholders
* @param context Resolution context for handling circular references
* @return The string with all placeholders replaced
*/
std::string replacePlaceholders(const std::string& expression, ResolveContext context = {}) const;
/**
* @brief Returns all available parameters from all sources.
*
* Parameters are returned in order of source priority, with later sources
* taking precedence over earlier ones.
*
* @return List of all available parameters
*/
std::list<Parameter> parameters() const;
/**
* @brief Gets the raw expression string for a parameter.
*
* @param name The name of the parameter
* @return The expression string if the parameter exists, empty otherwise
*/
std::optional<std::string> expression(const std::string& name) const;
/**
* @brief Resolves a parameter to its final value.
*
* This method evaluates the parameter's expression and returns the computed
* value. The result is cached for subsequent calls.
*
* @param name The name of the parameter to resolve
* @param context Resolution context for handling circular references
* @return The resolved value
*/
std::optional<Value> resolve(const std::string& name, ResolveContext context = {}) const;
/**
* @brief Resolves a parameter to its final value, based on definition.
*
* This method evaluates the parameter's expression and returns the computed
* value or default one from definition if the parameter is not available.
*
* @param definition Definition of the parameter to resolve
* @param context Resolution context for handling circular references
* @return The resolved value
*/
template<typename T>
T resolve(const ParameterDefinition<T>& definition, ResolveContext context = {}) const
{
auto value = resolve(definition.name, std::move(context));
if (!value || !std::holds_alternative<T>(*value)) {
return definition.defaultValue;
}
return std::get<T>(*value);
}
/**
* @brief Evaluates an expression string and returns the result.
*
* @param expression The expression to evaluate
* @param context Resolution context for handling circular references
* @return The evaluated value
*/
Value evaluate(const std::string& expression, ResolveContext context = {}) const;
/**
* @brief Gets a parameter by name from any source.
*
* @param name The name of the parameter
* @return The parameter if found, empty otherwise
*/
std::optional<Parameter> parameter(const std::string& name) const;
/**
* @brief Adds a parameter source to the manager.
*
* Sources are evaluated in the order they are added, with later sources
* taking precedence over earlier ones.
*
* @param source The parameter source to add
*/
void addSource(ParameterSource* source);
/**
* @brief Returns all registered parameter sources.
*
* @return List of parameter sources in order of registration
*/
std::list<ParameterSource*> sources() const;
};
} // namespace Gui::StyleParameters
ENABLE_BITMASK_OPERATORS(Gui::StyleParameters::ParameterSourceOption);
#endif // STYLEPARAMETERS_PARAMETERMANAGER_H
|