From db3e96ebf00fb0f3d1a6fb725e3f72ce6cc69371 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sat, 16 Aug 2025 12:20:01 +0200 Subject: [PATCH 1/2] Gui: Add YamlParameterSource for StyleParameters This adds ability to read and write Style Parameters from YAML files. This will allow to move certain tokens out of the User Parameter system to ensure that they can be update without user needing to reapply the theme. --- src/Gui/CMakeLists.txt | 14 ++++ src/Gui/Dialogs/DlgThemeEditor.cpp | 4 + src/Gui/StyleParameters/ParameterManager.cpp | 81 ++++++++++++++++++++ src/Gui/StyleParameters/ParameterManager.h | 39 ++++++++++ 4 files changed, 138 insertions(+) diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 3bf63d9514..0bb152ee76 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -146,7 +146,9 @@ target_include_directories( ${QtNetwork_INCLUDE_DIRS} ${QtUiTools_INCLUDE_DIRS} ${QtXml_INCLUDE_DIRS} + ${YAML_CPP_INCLUDE_DIRS} ) + list(APPEND FreeCADGui_LIBS ${QtCore_LIBRARIES} ${QtWidgets_LIBRARIES} @@ -159,6 +161,18 @@ list(APPEND FreeCADGui_LIBS ${QtUiTools_LIBRARIES} ) +link_directories(${YAML_CPP_LIBRARY_DIR}) + +if(yaml-cpp_VERSION VERSION_LESS 0.8.0) + list(APPEND FreeCADGui_LIBS + ${YAML_CPP_LIBRARIES} + ) +else() + list(APPEND FreeCADGui_LIBS + yaml-cpp::yaml-cpp + ) +endif() + if(${Qt5WinExtras_FOUND}) target_include_directories( FreeCADGui diff --git a/src/Gui/Dialogs/DlgThemeEditor.cpp b/src/Gui/Dialogs/DlgThemeEditor.cpp index efe66aa1c2..a590661027 100644 --- a/src/Gui/Dialogs/DlgThemeEditor.cpp +++ b/src/Gui/Dialogs/DlgThemeEditor.cpp @@ -465,6 +465,10 @@ void StyleParametersModel::flush() } }); + for (auto* source : sources) { + source->flush(); + } + reset(); } diff --git a/src/Gui/StyleParameters/ParameterManager.cpp b/src/Gui/StyleParameters/ParameterManager.cpp index 3e2e24ec8f..8fa1727609 100644 --- a/src/Gui/StyleParameters/ParameterManager.cpp +++ b/src/Gui/StyleParameters/ParameterManager.cpp @@ -26,6 +26,10 @@ #include "ParameterManager.h" #include "Parser.h" +#include +#include +#include + #ifndef _PreComp_ #include #include @@ -35,6 +39,8 @@ #include #endif +FC_LOG_LEVEL_INIT("Gui", true, true) + namespace Gui::StyleParameters { @@ -215,6 +221,81 @@ void UserParameterSource::remove(const std::string& name) hGrp->RemoveASCII(name.c_str()); } +YamlParameterSource::YamlParameterSource(const std::string& filePath, const Metadata& metadata) + : ParameterSource(metadata) + , filePath(filePath) +{ + QFile file(QString::fromStdString(filePath)); + + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + FC_TRACE("StyleParameters: Unable to open file " << filePath); + return; + } + + if (filePath.starts_with(":/")) { + this->metadata.options |= ReadOnly; + } + + QTextStream in(&file); + std::string content = in.readAll().toStdString(); + + YAML::Node root = YAML::Load(content); + for (auto it = root.begin(); it != root.end(); ++it) { + auto key = it->first.as(); + auto value = it->second.as(); + + parameters[key] = Parameter { + .name = key, + .value = value, + }; + } +} + +std::list YamlParameterSource::all() const +{ + std::list result; + for (const auto& param : parameters | std::views::values) { + result.push_back(param); + } + return result; +} + +std::optional YamlParameterSource::get(const std::string& name) const +{ + if (auto it = parameters.find(name); it != parameters.end()) { + return it->second; + } + + return std::nullopt; +} + +void YamlParameterSource::define(const Parameter& param) +{ + parameters[param.name] = param; +} + +void YamlParameterSource::remove(const std::string& name) +{ + parameters.erase(name); +} + +void YamlParameterSource::flush() +{ + YAML::Node root; + for (const auto& [name, param] : parameters) { + root[name] = param.value; + } + + QFile file(QString::fromStdString(filePath)); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + FC_TRACE("StyleParameters: Unable to open file " << filePath); + return; + } + + QTextStream out(&file); + out << QString::fromStdString(YAML::Dump(root)); +} + ParameterManager::ParameterManager() = default; void ParameterManager::reload() diff --git a/src/Gui/StyleParameters/ParameterManager.h b/src/Gui/StyleParameters/ParameterManager.h index 53d3ed3703..a2832a9030 100644 --- a/src/Gui/StyleParameters/ParameterManager.h +++ b/src/Gui/StyleParameters/ParameterManager.h @@ -306,6 +306,11 @@ public: * @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() {} }; /** @@ -376,6 +381,40 @@ public: 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 = {}); + + std::list all() const override; + std::optional 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 parameters; +}; + /** * @brief Central manager for style parameters that aggregates multiple sources. * From fd502ce94f3a8cae55de259ffe9afce8388375cd Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sat, 16 Aug 2025 12:51:16 +0200 Subject: [PATCH 2/2] Gui: Try to load Theme Parameters from file This creates logic that tries to load user parameters from file as the priority and pushes back older mechanism to lower layer. Older way of loading theme parameters should be preserved until we release first 1.1 RC. --- src/Gui/Application.cpp | 43 ++++++++++++++++---- src/Gui/StyleParameters/ParameterManager.cpp | 15 ++++++- src/Gui/StyleParameters/ParameterManager.h | 3 ++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 7acd57933f..8f46494588 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -141,6 +141,7 @@ #include "QtWidgets.h" #include +#include #include #ifdef BUILD_TRACY_FRAME_PROFILER @@ -381,20 +382,48 @@ struct PyMethodDef FreeCADGui_methods[] = { void Application::initStyleParameterManager() { + static ParamHandlers handlers; + + const auto deduceParametersFilePath = []() -> std::string { + const auto hMainWindowGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow"); + + if (const std::string& path = hMainWindowGrp->GetASCII("ThemeStyleParametersFile"); + !path.empty()) { + return path; + } + + return fmt::format("qss:parameters/{}.yaml", hMainWindowGrp->GetASCII("Theme", "Classic")); + }; + + auto themeParametersSource = new StyleParameters::YamlParameterSource( + deduceParametersFilePath(), + {.name = QT_TR_NOOP("Theme Parameters"), + .options = StyleParameters::ParameterSourceOption::UserEditable}); + + handlers.addDelayedHandler( + "BaseApp/Preferences/MainWindow", + {"ThemeStyleParametersFiles", "Theme"}, + [themeParametersSource, deduceParametersFilePath](ParameterGrp::handle) { + themeParametersSource->changeFilePath(deduceParametersFilePath()); + }); + Base::registerServiceImplementation( new StyleParameters::BuiltInParameterSource({.name = QT_TR_NOOP("Built-in Parameters")})); - Base::registerServiceImplementation( - new StyleParameters::UserParameterSource( - App::GetApplication().GetParameterGroupByPath( - "User parameter:BaseApp/Preferences/Themes/Tokens"), - {.name = QT_TR_NOOP("Theme Parameters"), - .options = StyleParameters::ParameterSourceOption::UserEditable})); - + // todo: left for compatibility with older theme versions, to be removed before release Base::registerServiceImplementation( new StyleParameters::UserParameterSource( App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Themes/UserTokens"), + {.name = QT_TR_NOOP("Theme Parameters - Fallback"), + .options = StyleParameters::ParameterSourceOption::ReadOnly})); + + Base::registerServiceImplementation(themeParametersSource); + + Base::registerServiceImplementation( + new StyleParameters::UserParameterSource( + App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Themes/UserParameters"), {.name = QT_TR_NOOP("User Parameters"), .options = StyleParameters::ParameterSource::UserEditable})); diff --git a/src/Gui/StyleParameters/ParameterManager.cpp b/src/Gui/StyleParameters/ParameterManager.cpp index 8fa1727609..9aa9894eaf 100644 --- a/src/Gui/StyleParameters/ParameterManager.cpp +++ b/src/Gui/StyleParameters/ParameterManager.cpp @@ -223,7 +223,17 @@ void UserParameterSource::remove(const std::string& name) YamlParameterSource::YamlParameterSource(const std::string& filePath, const Metadata& metadata) : ParameterSource(metadata) - , filePath(filePath) +{ + changeFilePath(filePath); +} + +void YamlParameterSource::changeFilePath(const std::string& path) +{ + this->filePath = path; + reload(); +} + +void YamlParameterSource::reload() { QFile file(QString::fromStdString(filePath)); @@ -240,6 +250,7 @@ YamlParameterSource::YamlParameterSource(const std::string& filePath, const Meta std::string content = in.readAll().toStdString(); YAML::Node root = YAML::Load(content); + parameters.clear(); for (auto it = root.begin(); it != root.end(); ++it) { auto key = it->first.as(); auto value = it->second.as(); @@ -288,7 +299,7 @@ void YamlParameterSource::flush() QFile file(QString::fromStdString(filePath)); if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { - FC_TRACE("StyleParameters: Unable to open file " << filePath); + FC_WARN("StyleParameters: Unable to open file " << filePath); return; } diff --git a/src/Gui/StyleParameters/ParameterManager.h b/src/Gui/StyleParameters/ParameterManager.h index a2832a9030..fb13994494 100644 --- a/src/Gui/StyleParameters/ParameterManager.h +++ b/src/Gui/StyleParameters/ParameterManager.h @@ -403,6 +403,9 @@ public: */ explicit YamlParameterSource(const std::string& filePath, const Metadata& metadata = {}); + void changeFilePath(const std::string& path); + void reload(); + std::list all() const override; std::optional get(const std::string& name) const override; void define(const Parameter& param) override;