#include "rootnodes.hpp" #include #include #include "../toaster.hpp" #include "common.hpp" #include "migration.hpp" namespace ZShell::config { using Qt::StringLiterals::operator""_s; namespace { QString nameFor(const QString& key) { return key.isEmpty() ? u"global"_s : key; } } // namespace namespace detail { void loaded(ConfigKind kind, settings::RootNode* layer, const QString& screen) { if (kind != ConfigKind::Shell || !screen.isEmpty()) return; auto* const config = static_cast(layer); if (!config->utilities()->toasts()->configLoaded()) return; const auto issues = static_cast(config->diagnostics().count()); const auto message = issues > 0 ? QStringLiteral("Config loaded with %1 issue%2") .arg(issues) .arg(issues == 1 ? "" : "s") : QStringLiteral("Config loaded successfully!"); Toaster::instance()->toast( QStringLiteral("Config loaded"), message, issues > 0 ? QStringLiteral("settings_alert") : QStringLiteral("rule_settings"), issues > 0 ? Toast::Type::Warning : Toast::Type::Info); } void loadFailed(ConfigKind kind, const QString& error, const QString& screen) { QString title; if (kind == ConfigKind::Tokens) title = screen.isEmpty() ? QStringLiteral("Failed to parse token config") : QStringLiteral("Failed to parse token config for %1") .arg(screen); else title = screen.isEmpty() ? QStringLiteral("Failed to parse config") : QStringLiteral("Failed to parse config for %1").arg(screen); Toaster::instance()->toast( title, error, QStringLiteral("settings_alert"), Toast::Type::Warning); } void saveFailed(ConfigKind kind, const QString& error, const QString& screen) { if (kind != ConfigKind::Shell) return; const auto title = screen.isEmpty() ? QStringLiteral("Failed to save config") : QStringLiteral("Failed to save config for %1").arg(screen); Toaster::instance()->toast( title, error, QStringLiteral("settings_alert"), Toast::Type::Error); } } // namespace detail ConfigRoot::ConfigRoot( const QString& path, ConfigRoot* fallback, QObject* parent) : RootNode(path, fallback, parent) { migrateFile(path); bindTokens(); qCDebug(lcConfig) << "Created config root for" << nameFor(key()); } void ConfigRoot::migrateFile(const QString& path) { QFile file(path); if (!file.exists() || !file.open(QIODevice::ReadOnly)) return; const auto doc = QJsonDocument::fromJson(file.readAll()); if (!doc.isObject()) return; const QJsonObject raw = doc.object(); const QJsonObject migrated = ConfigMigrations::apply(raw); if (migrated == raw) return; file.close(); QSaveFile save(path); if (!save.open(QIODevice::WriteOnly)) { qCWarning(lcConfig) << "Failed to open" << path << "for migration write:" << save.errorString(); return; } if (save.write(QJsonDocument(migrated).toJson(QJsonDocument::Indented)) != save.size()) { qCWarning(lcConfig) << "Failed to write migrated config:" << save.errorString(); return; } if (!save.commit()) { qCWarning(lcConfig) << "Failed to commit migrated config:" << save.errorString(); return; } qCDebug(lcConfig) << "Migrated config file" << path; } void ConfigRoot::bindTokens() { qCDebug(lcConfig) << "Binding appearance to token values for" << nameFor(key()); auto* const tokens = TokensSingleton::instance()->appearance(); m_appearance->rounding()->bindTokens(tokens->rounding()); m_appearance->spacing()->bindTokens(tokens->spacing()); m_appearance->padding()->bindTokens(tokens->padding()); m_appearance->anim()->durations()->bindTokens(tokens->animDurations()); } TokensRoot::TokensRoot( const QString& path, TokensRoot* fallback, QObject* parent) : RootNode(path, fallback, parent) { qCDebug(lcConfig) << "Created tokens root for" << nameFor(key()); } #define SINGLETON_IMPL(Type, Root, file, kind) \ Type* Type::instance() { \ static Type instance; \ return &instance; \ } \ \ Type* Type::create(QQmlEngine*, QJSEngine*) { \ QQmlEngine::setObjectOwnership(instance(), QQmlEngine::CppOwnership); \ return instance(); \ } \ \ Root* Type::forScreen(const QString& screen) { \ bool created; \ auto* const layer = m_layers.get(screen, this, &created); \ if (created) initLayer(layer); \ return layer; \ } \ \ Type::Type(QObject* parent) \ : Root(configDir() + u'/' + file, nullptr, parent) \ , m_layers(monitorConfigDir(), file, this) { \ initLayer(this); \ } \ \ void Type::onTreeLoaded(settings::RootNode* layer) { \ detail::loaded( \ kind, layer, m_layers.nameFor(static_cast(layer))); \ } \ \ void Type::onTreeLoadFailed( \ settings::RootNode* layer, const QString& error) { \ detail::loadFailed( \ kind, error, m_layers.nameFor(static_cast(layer))); \ } \ \ void Type::onTreeSaveFailed( \ settings::RootNode* layer, const QString& error) { \ detail::saveFailed( \ kind, error, m_layers.nameFor(static_cast(layer))); \ } \ \ void Type::initLayer(Root* layer) { \ QObject::connect(layer, &Root::treeLoaded, this, &Type::onTreeLoaded); \ QObject::connect( \ layer, &Root::treeLoadFailed, this, &Type::onTreeLoadFailed); \ QObject::connect( \ layer, &Root::treeSaveFailed, this, &Type::onTreeSaveFailed); \ layer->load(); \ } SINGLETON_IMPL( ConfigSingleton, ConfigRoot, u"config.json"_s, detail::ConfigKind::Shell) SINGLETON_IMPL( TokensSingleton, TokensRoot, u"tokens.json"_s, detail::ConfigKind::Tokens) #undef SINGLETON_IMPL } // namespace ZShell::config