From d5d6f70f521b91e26c7058bd003c243e4b43a1f5 Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Sat, 11 Apr 2026 20:10:27 +1000 Subject: [PATCH] feat: add load/save + fail signals --- plugin/src/Caelestia/Config/config.cpp | 12 +---- plugin/src/Caelestia/Config/config.hpp | 5 +-- plugin/src/Caelestia/Config/configobject.cpp | 46 ++++++++++++++++---- plugin/src/Caelestia/Config/configobject.hpp | 19 +++++++- plugin/src/Caelestia/Config/tokens.cpp | 12 +---- plugin/src/Caelestia/Config/tokens.hpp | 5 +-- 6 files changed, 61 insertions(+), 38 deletions(-) diff --git a/plugin/src/Caelestia/Config/config.cpp b/plugin/src/Caelestia/Config/config.cpp index 21b90200..f78884f2 100644 --- a/plugin/src/Caelestia/Config/config.cpp +++ b/plugin/src/Caelestia/Config/config.cpp @@ -18,7 +18,7 @@ QString configDir() { } // namespace GlobalConfig::GlobalConfig(QObject* parent) - : ConfigObject(parent) + : RootConfig(parent) , m_appearance(new AppearanceConfig(this)) , m_general(new GeneralConfig(this)) , m_background(new BackgroundConfig(this)) @@ -47,7 +47,7 @@ GlobalConfig::GlobalConfig(QObject* parent) } GlobalConfig::GlobalConfig(GlobalConfig* fallback, const QString& filePath, QObject* parent) - : ConfigObject(parent) + : RootConfig(parent) , m_appearance(new AppearanceConfig(this)) , m_general(new GeneralConfig(this)) , m_background(new BackgroundConfig(this)) @@ -120,14 +120,6 @@ GlobalConfig* GlobalConfig::create(QQmlEngine* engine, QJSEngine* jsEngine) { return config; } -void GlobalConfig::save() { - saveToFile(); -} - -void GlobalConfig::reload() { - reloadFromFile(); -} - // Config (attached type) Config::Config(ConfigScope* scope, QObject* parent) diff --git a/plugin/src/Caelestia/Config/config.hpp b/plugin/src/Caelestia/Config/config.hpp index 61ef871c..2218e753 100644 --- a/plugin/src/Caelestia/Config/config.hpp +++ b/plugin/src/Caelestia/Config/config.hpp @@ -22,7 +22,7 @@ namespace caelestia::config { -class GlobalConfig : public ConfigObject { +class GlobalConfig : public RootConfig { Q_OBJECT QML_ELEMENT QML_SINGLETON @@ -50,9 +50,6 @@ public: [[nodiscard]] Q_INVOKABLE GlobalConfig* defaults(); static GlobalConfig* create(QQmlEngine*, QJSEngine*); - Q_INVOKABLE void save(); - Q_INVOKABLE void reload(); - ~GlobalConfig() override; void bindAppearanceTokens(); diff --git a/plugin/src/Caelestia/Config/configobject.cpp b/plugin/src/Caelestia/Config/configobject.cpp index 8b9001fd..93736837 100644 --- a/plugin/src/Caelestia/Config/configobject.cpp +++ b/plugin/src/Caelestia/Config/configobject.cpp @@ -298,12 +298,16 @@ void ConfigObject::setupFileBackend(const QString& path) { QFile file(m_filePath); if (!file.open(QIODevice::WriteOnly)) { - qCWarning(lcConfig) << "Failed to write" << m_filePath; + qCWarning(lcConfig, "Failed to write %s", qUtf8Printable(m_filePath)); + if (auto* root = qobject_cast(this)) + Q_EMIT root->fileSaveFailed(QStringLiteral("Failed to open file for writing")); return; } auto json = m_sparse ? toSparseJsonObject() : toJsonObject(); file.write(QJsonDocument(json).toJson(QJsonDocument::Indented)); + if (auto* root = qobject_cast(this)) + Q_EMIT root->fileSaved(); }); m_cooldownTimer->setSingleShot(true); @@ -330,12 +334,12 @@ void ConfigObject::saveToFile() { m_cooldownTimer->start(); } -void ConfigObject::reloadFromFile() { +bool ConfigObject::reloadFromFile() { QFile file(m_filePath); if (!file.open(QIODevice::ReadOnly)) { - qCDebug(lcConfig) << "Failed to open" << m_filePath; - return; + qCDebug(lcConfig, "Failed to open %s", qUtf8Printable(m_filePath)); + return false; } QJsonParseError error{}; @@ -344,14 +348,15 @@ void ConfigObject::reloadFromFile() { if (error.error != QJsonParseError::NoError) { if (m_retryTimer && m_parseRetries < 3) { m_parseRetries++; - qCDebug(lcConfig, "Failed to parse %s: %s - retrying (%d/3)", qPrintable(m_filePath), - qPrintable(error.errorString()), m_parseRetries); + qCDebug(lcConfig, "Failed to parse %s: %s - retrying (%d/3)", qUtf8Printable(m_filePath), + qUtf8Printable(error.errorString()), m_parseRetries); m_retryTimer->start(); } else { - qCWarning(lcConfig, "Failed to parse %s: %s", qPrintable(m_filePath), qPrintable(error.errorString())); + qCWarning( + lcConfig, "Failed to parse %s: %s", qUtf8Printable(m_filePath), qUtf8Printable(error.errorString())); m_parseRetries = 0; } - return; + return false; } m_parseRetries = 0; @@ -366,6 +371,8 @@ void ConfigObject::reloadFromFile() { qCDebug(lcConfig) << "Re-syncing" << metaObject()->className() << "from global after reload"; resyncFromGlobal(); } + + return true; } void ConfigObject::onFileChanged() { @@ -376,8 +383,29 @@ void ConfigObject::onFileChanged() { m_parseRetries = 0; if (m_retryTimer) m_retryTimer->stop(); - reloadFromFile(); + + bool ok = reloadFromFile(); + if (auto* root = qobject_cast(this)) { + if (ok) + Q_EMIT root->fileLoaded(); + else + Q_EMIT root->fileLoadFailed(QStringLiteral("Failed to load config file")); + } } } +// RootConfig + +RootConfig::RootConfig(QObject* parent) + : ConfigObject(parent) {} + +void RootConfig::save() { + saveToFile(); +} + +void RootConfig::reload() { + if (reloadFromFile()) + Q_EMIT fileLoaded(); +} + } // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/configobject.hpp b/plugin/src/Caelestia/Config/configobject.hpp index 096fb9e8..f9607fa7 100644 --- a/plugin/src/Caelestia/Config/configobject.hpp +++ b/plugin/src/Caelestia/Config/configobject.hpp @@ -59,7 +59,7 @@ public: // automatic file watching, debounced saving, and reload. void setupFileBackend(const QString& path); void saveToFile(); - void reloadFromFile(); + bool reloadFromFile(); // Per-monitor overlay support (Qt Resolve Mask pattern). // Eagerly syncs non-overridden properties from a global ConfigObject. @@ -115,4 +115,21 @@ private: QTimer* m_batchTimer = nullptr; }; +// Intermediate base for singleton config roots (GlobalConfig, TokenConfig). +// Provides save/reload with file lifecycle signals. +class RootConfig : public ConfigObject { + Q_OBJECT + +public: + explicit RootConfig(QObject* parent = nullptr); + + Q_INVOKABLE void save(); + Q_INVOKABLE void reload(); + + Q_SIGNAL void fileLoaded(); + Q_SIGNAL void fileLoadFailed(const QString& error); + Q_SIGNAL void fileSaved(); + Q_SIGNAL void fileSaveFailed(const QString& error); +}; + } // namespace caelestia::config diff --git a/plugin/src/Caelestia/Config/tokens.cpp b/plugin/src/Caelestia/Config/tokens.cpp index 7e6ca0e0..ac383ba5 100644 --- a/plugin/src/Caelestia/Config/tokens.cpp +++ b/plugin/src/Caelestia/Config/tokens.cpp @@ -18,7 +18,7 @@ QString configDir() { } // namespace TokenConfig::TokenConfig(QObject* parent) - : ConfigObject(parent) + : RootConfig(parent) , m_appearance(new AppearanceTokens(this)) , m_bar(new BarTokens(this)) , m_dashboard(new DashboardTokens(this)) @@ -41,7 +41,7 @@ TokenConfig::TokenConfig(QObject* parent) } TokenConfig::TokenConfig(TokenConfig* fallback, const QString& filePath, QObject* parent) - : ConfigObject(parent) + : RootConfig(parent) , m_appearance(new AppearanceTokens(this)) , m_bar(new BarTokens(this)) , m_dashboard(new DashboardTokens(this)) @@ -80,14 +80,6 @@ TokenConfig* TokenConfig::create(QQmlEngine* engine, QJSEngine*) { return new TokenConfig(engine); } -void TokenConfig::save() { - saveToFile(); -} - -void TokenConfig::reload() { - reloadFromFile(); -} - // Tokens (attached type) Tokens::Tokens(ConfigScope* scope, QObject* parent) diff --git a/plugin/src/Caelestia/Config/tokens.hpp b/plugin/src/Caelestia/Config/tokens.hpp index aae51f16..53c4a707 100644 --- a/plugin/src/Caelestia/Config/tokens.hpp +++ b/plugin/src/Caelestia/Config/tokens.hpp @@ -289,7 +289,7 @@ public: : ConfigObject(parent) {} }; -class TokenConfig : public ConfigObject { +class TokenConfig : public RootConfig { Q_OBJECT QML_ELEMENT QML_SINGLETON @@ -312,9 +312,6 @@ public: [[nodiscard]] Q_INVOKABLE TokenConfig* defaults(); static TokenConfig* create(QQmlEngine*, QJSEngine*); - Q_INVOKABLE void save(); - Q_INVOKABLE void reload(); - ~TokenConfig() override; private: