192 lines
7.6 KiB
C++
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
|