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