Files

192 lines
7.6 KiB
C++

#include "rootnodes.hpp"
#include <qjsondocument.h>
#include <qsavefile.h>
#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<ConfigRoot*>(layer);
if (!config->utilities()->toasts()->configLoaded()) return;
const auto issues = static_cast<int>(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<Root*>(layer))); \
} \
\
void Type::onTreeLoadFailed( \
settings::RootNode* layer, const QString& error) { \
detail::loadFailed( \
kind, error, m_layers.nameFor(static_cast<Root*>(layer))); \
} \
\
void Type::onTreeSaveFailed( \
settings::RootNode* layer, const QString& error) { \
detail::saveFailed( \
kind, error, m_layers.nameFor(static_cast<Root*>(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