#include "configobject.hpp" #include #include #include #include namespace ZShell::config { ConfigObject::ConfigObject(QObject* parent) : ConfigNode(parent) {} void ConfigObject::loadFromJson(const QJsonValue& json) { const auto obj = json.toObject(); const auto* meta = metaObject(); QSet known; for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { auto prop = meta->property(i); const auto key = QString::fromUtf8(prop.name()); known.insert(key); if (!obj.contains(key)) continue; const auto jsonVal = obj.value(key); if (prop.metaType().flags().testFlag(QMetaType::PointerToQObject)) { if (auto* const node = prop.read(this).value()) { node->loadFromJson(jsonVal); continue; } } if (!prop.isWritable()) continue; if (prop.metaType().id() == QMetaType::QStringList) { QStringList list; const auto jsonArr = jsonVal.toArray(); for (const auto& v : jsonArr) list.append(v.toString()); prop.write(this, QVariant::fromValue(list)); m_loadedKeys.insert(key); continue; } if (prop.metaType().id() == QMetaType::QVariantList) { prop.write(this, jsonVal.toVariant()); m_loadedKeys.insert(key); continue; } prop.write(this, jsonVal.toVariant()); m_loadedKeys.insert(key); } m_extras = {}; for (auto it = obj.begin(); it != obj.end(); ++it) { if (!known.contains(it.key())) { m_extras.insert(it.key(), it.value()); } } } QJsonValue ConfigObject::toJson() const { QJsonObject obj; const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { const auto prop = meta->property(i); if (!prop.isReadable()) continue; const auto key = QString::fromUtf8(prop.name()); const auto value = prop.read(this); if (prop.metaType().flags().testFlag(QMetaType::PointerToQObject)) { if (auto* const node = value.value()) { const auto childJson = node->toJson(); if (!childJson.isUndefined()) obj.insert(key, childJson); continue; } } if (!m_loadedKeys.contains(key)) continue; if (!prop.isWritable()) continue; if (prop.metaType().id() == QMetaType::QStringList) { QJsonArray arr; const auto strList = value.toStringList(); for (const auto& s : strList) arr.append(s); obj.insert(key, arr); continue; } if (prop.metaType().id() == QMetaType::QVariantList) { obj.insert(key, QJsonArray::fromVariantList(value.toList())); continue; } obj.insert(key, QJsonValue::fromVariant(value)); } for (auto it = m_extras.begin(); it != m_extras.end(); ++it) obj.insert(it.key(), it.value()); if (obj.isEmpty()) return QJsonValue::Undefined; return obj; } void ConfigObject::clearLoadedKeys() { m_loadedKeys.clear(); m_extras = {}; const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { auto prop = meta->property(i); if (auto* const node = prop.read(this).value()) node->clearLoadedKeys(); } } QStringList ConfigObject::identityKeys() const { return {}; } QStringList ConfigObject::unknownKeys() const { auto keys = m_extras.keys(); const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { const auto prop = meta->property(i); auto* const node = prop.read(this).value(); if (!node) continue; const auto childKeys = node->unknownKeys(); for (const auto& childKey : childKeys) keys.append(joinPath(QString::fromUtf8(prop.name()), childKey)); } return keys; } QList ConfigObject::childNodes() const { QList nodes; const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { const auto prop = meta->property(i); if (auto* const node = prop.read(this).value()) nodes.append(node); } return nodes; } void ConfigObject::syncValuesFromGlobal() { const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { auto prop = meta->property(i); const auto key = QString::fromUtf8(prop.name()); if (auto* const node = prop.read(this).value()) { if (auto* const globalNode = prop.read(m_global).value()) node->syncFromGlobal(globalNode); continue; } if (!prop.isWritable()) continue; if (!m_loadedKeys.contains(key)) { auto val = prop.read(m_global); prop.write(this, val); m_loadedKeys.remove(key); } } } void ConfigObject::resyncFromGlobal() { if (!m_global) return; const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { auto prop = meta->property(i); const auto key = QString::fromUtf8(prop.name()); if (auto* const node = prop.read(this).value()) { node->resyncFromGlobal(); continue; } if (!prop.isWritable()) continue; if (!m_loadedKeys.contains(key)) { prop.write(this, prop.read(m_global)); m_loadedKeys.remove(key); } } } bool ConfigObject::isPropertyLoaded(const QString& name) const { return m_loadedKeys.contains(name); } bool ConfigObject::isGlobalOnly(const QString& name) const { return m_globalOnlyKeys.contains(name); } QStringList ConfigObject::globalOnlyKeys() const { return {m_globalOnlyKeys.begin(), m_globalOnlyKeys.end()}; } void ConfigObject::resetOption(const QString& name) { m_loadedKeys.remove(name); const int idx = metaObject()->indexOfProperty(name.toUtf8().constData()); if (idx < 0) return; const auto prop = metaObject()->property(idx); if (auto* const node = prop.read(this).value()) { node->clearLoadedKeys(); node->resyncFromGlobal(); return; } if (m_global && prop.isWritable()) prop.write(this, prop.read(m_global)); } void ConfigObject::onGlobalPropertiesChanged( const QMap& changed) { for (auto it = changed.begin(); it != changed.end(); ++it) { if (m_loadedKeys.contains(it.key()) || isGlobalOnly(it.key())) continue; int idx = metaObject()->indexOfProperty(it.key().toUtf8().constData()); if (idx >= 0) { metaObject()->property(idx).write(this, it.value()); m_loadedKeys.remove(it.key()); } } } QString ConfigObject::childPath(const ConfigNode* child) const { const auto* meta = metaObject(); for (int i = basePropertyOffset(); i < meta->propertyCount(); ++i) { const auto prop = meta->property(i); if (prop.read(this).value() == child) return QString::fromUtf8(prop.name()); } return {}; } void ConfigObject::markPropertyLoaded(const QString& name) { m_loadedKeys.insert(name); } void ConfigObject::markGlobalOnly(const QString& name) { m_globalOnlyKeys.insert(name); } } // namespace ZShell::config