diff --git a/cmake/DtkTools/DtkDConfigMacros.cmake b/cmake/DtkTools/DtkDConfigMacros.cmake new file mode 100644 index 00000000..a5f68957 --- /dev/null +++ b/cmake/DtkTools/DtkDConfigMacros.cmake @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +# SPDX-License-Identifier: LGPL-3.0-or-later + +include(MacroAddFileDependencies) +include(CMakeParseArguments) + +# Define the helper function +function(dtk_add_config_to_cpp OUTPUT_VAR JSON_FILE) + if(NOT EXISTS ${JSON_FILE}) + message(FATAL_ERROR "JSON file ${JSON_FILE} does not exist.") + endif() + + cmake_parse_arguments( + "arg" + "" + "OUTPUT_FILE_NAME;CLASS_NAME;USE_QPROPERTY;GET_IS_DEFAULT_VALUE" + "" + ${ARGN} + ) + + # Generate the output header file name + get_filename_component(FILE_NAME_WLE ${JSON_FILE} NAME_WLE) + if(DEFINED arg_OUTPUT_FILE_NAME) + set(OUTPUT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/${arg_OUTPUT_FILE_NAME}") + else() + set(OUTPUT_HEADER "${CMAKE_CURRENT_BINARY_DIR}/dconfig_${FILE_NAME_WLE}.hpp") + endif() + + # Check if CLASS_NAME is set + if(DEFINED arg_CLASS_NAME) + set(CLASS_NAME_ARG -c ${arg_CLASS_NAME}) + else() + set(CLASS_NAME_ARG "") + endif() + + if(NOT DEFINED arg_USE_QPROPERTY) + if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + set(arg_USE_QPROPERTY ON) + endif() + endif() + + if (arg_USE_QPROPERTY) + set(USE_QPROPERTY_ARG "--use-qproperty") + else() + set(USE_QPROPERTY_ARG "") + endif() + + if (arg_GET_IS_DEFAULT_VALUE) + set(GET_IS_DEFAULT_VALUE_ARG "--get-is-default-value") + else() + set(GET_IS_DEFAULT_VALUE_ARG "") + endif() + + # Add a custom command to run dconfig2cpp + add_custom_command( + OUTPUT ${OUTPUT_HEADER} + COMMAND ${DTK_DCONFIG2CPP} -o ${OUTPUT_HEADER} ${CLASS_NAME_ARG} ${USE_QPROPERTY_ARG} ${GET_IS_DEFAULT_VALUE_ARG} ${JSON_FILE} + DEPENDS ${JSON_FILE} ${DTK_XML2CPP} + COMMENT "Generating ${OUTPUT_HEADER} from ${JSON_FILE}" + VERBATIM + ) + + # Add the generated header to the specified output variable + set(${OUTPUT_VAR} ${${OUTPUT_VAR}} ${OUTPUT_HEADER} PARENT_SCOPE) +endfunction() diff --git a/cmake/DtkTools/DtkToolsConfig.cmake.in b/cmake/DtkTools/DtkToolsConfig.cmake.in index b3a14f17..074003b5 100644 --- a/cmake/DtkTools/DtkToolsConfig.cmake.in +++ b/cmake/DtkTools/DtkToolsConfig.cmake.in @@ -10,5 +10,7 @@ endif () include("${CMAKE_CURRENT_LIST_DIR}/Dtk@DTK_VERSION_MAJOR@SettingsToolsMacros.cmake") include("${CMAKE_CURRENT_LIST_DIR}/Dtk@DTK_VERSION_MAJOR@ToolsTargets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/DtkDBusMacros.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/DtkDConfigMacros.cmake") get_target_property(DTK_XML2CPP Dtk@DTK_VERSION_MAJOR@::Xml2Cpp LOCATION) +get_target_property(DTK_DCONFIG2CPP Dtk@DTK_VERSION_MAJOR@::DConfig2Cpp LOCATION) diff --git a/docs/global/dconfig.zh_CN.dox b/docs/global/dconfig.zh_CN.dox index 853d3d0b..a37c82a0 100644 --- a/docs/global/dconfig.zh_CN.dox +++ b/docs/global/dconfig.zh_CN.dox @@ -349,6 +349,11 @@ sudo make install @param[in] appId 配置文件所属的应用Id @note 需要在QCoreApplication构造前设置。 +@fn static void DConfig::globalThread() +@brief 一个服务于 DConfig 的公用线程,一般用于 dconfig2cpp 生成的代码,此线程在构造时会自动调用 QThread::start 以满足 dconfig2cpp 的需求。 +@return 此线程默认为 running 状态 +@note 请不要析构它,它会在应用程序退出时释放 + @fn QString Dtk::Core::DConfig::backendName() @brief 配置策略后端名称 @return 配置策略后端名称 diff --git a/dtkcore.cmake b/dtkcore.cmake index 95c98d41..df7f7cfc 100644 --- a/dtkcore.cmake +++ b/dtkcore.cmake @@ -99,6 +99,9 @@ install(FILES cmake/DtkTools/DtkSettingsToolsMacros.cmake install(FILES cmake/DtkTools/DtkDBusMacros.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Dtk${DTK_VERSION_MAJOR}Tools") +install(FILES ${CMAKE_SOURCE_DIR}/cmake/DtkTools/DtkDConfigMacros.cmake + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Dtk${DTK_VERSION_MAJOR}Tools") + if (NOT DTK_VERSION_MAJOR) set(DCONFIG_DEPRECATED_FUNCS [=[ # deprecated since dtk6 diff --git a/include/global/dconfig.h b/include/global/dconfig.h index 8759a489..c4c633b0 100644 --- a/include/global/dconfig.h +++ b/include/global/dconfig.h @@ -31,7 +31,7 @@ class LIBDTKCORESHARED_EXPORT DConfig : public QObject, public DObject Q_OBJECT D_DECLARE_PRIVATE(DConfig) - Q_PROPERTY(QStringList keyList READ keyList FINAL) + Q_PROPERTY(QStringList keyList READ keyList CONSTANT FINAL) public: explicit DConfig(const QString &name, const QString &subpath = QString(), @@ -50,6 +50,7 @@ class LIBDTKCORESHARED_EXPORT DConfig : public QObject, public DObject QObject *parent = nullptr); static void setAppId(const QString &appId); + static QThread *globalThread(); QString backendName() const; diff --git a/src/dconfig.cpp b/src/dconfig.cpp index ef7325af..2d57ee5d 100644 --- a/src/dconfig.cpp +++ b/src/dconfig.cpp @@ -87,6 +87,7 @@ static QString NoAppId; @fn QString DConfigBackend::name() const = 0 @brief The unique identity of the backend configuration + */ /*! @~english @@ -669,6 +670,29 @@ void DConfig::setAppId(const QString &appId) qCDebug(cfLog, "Explicitly specify application Id as appId=%s for config.", qPrintable(appId)); } +class DConfigThread : public QThread +{ +public: + DConfigThread() { + setObjectName("DConfigGlobalThread"); + start(); + } + + ~DConfigThread() override { + if (isRunning()) { + quit(); + wait(); + } + } +}; + +Q_GLOBAL_STATIC(DConfigThread, _globalThread) + +QThread *DConfig::globalThread() +{ + return _globalThread; +} + /*! @~english * @brief Use custom configuration policy backend to construct objects diff --git a/src/log/LogManager.cpp b/src/log/LogManager.cpp index 5ff0c194..4af93469 100644 --- a/src/log/LogManager.cpp +++ b/src/log/LogManager.cpp @@ -4,13 +4,14 @@ #include #include "LogManager.h" -#include "dconfig.h" #include #include #include #include #include + #include "dstandardpaths.h" +#include "dconfig_org_deepin_dtk_preference.hpp" DCORE_BEGIN_NAMESPACE @@ -40,7 +41,7 @@ class DLogManagerPrivate { { } - DConfig *createDConfig(const QString &appId); + dconfig_org_deepin_dtk_preference *createDConfig(const QString &appId); void initLoggingRules(); void updateLoggingRules(); @@ -49,33 +50,22 @@ class DLogManagerPrivate { ConsoleAppender* m_consoleAppender = nullptr; RollingFileAppender* m_rollingFileAppender = nullptr; JournalAppender* m_journalAppender = nullptr; - QScopedPointer m_dsgConfig; - QScopedPointer m_fallbackConfig; + QScopedPointer m_dsgConfig; + QScopedPointer m_fallbackConfig; DLogManager *q_ptr = nullptr; Q_DECLARE_PUBLIC(DLogManager) }; -DConfig *DLogManagerPrivate::createDConfig(const QString &appId) +dconfig_org_deepin_dtk_preference *DLogManagerPrivate::createDConfig(const QString &appId) { if (appId.isEmpty()) return nullptr; - DConfig *config = DConfig::create(appId, "org.deepin.dtk.preference"); - if (!config->isValid()) { - qWarning() << "Logging rules config is invalid, please check `appId` [" << appId << "]arg is correct"; - delete config; - config = nullptr; - return nullptr; - } - - QObject::connect(config, &DConfig::valueChanged, config, [this](const QString &key) { - if (key != RULES_KEY) - return; - - updateLoggingRules(); - }); + auto config = dconfig_org_deepin_dtk_preference::create(appId); + QObject::connect(config, &dconfig_org_deepin_dtk_preference::rulesChanged, + config, [this](){ updateLoggingRules(); }); return config; } @@ -89,25 +79,43 @@ void DLogManagerPrivate::initLoggingRules() QString dsgAppId = DSGApplication::id(); m_dsgConfig.reset(createDConfig(dsgAppId)); + if (m_dsgConfig) { + QObject::connect(m_dsgConfig.data(), &dconfig_org_deepin_dtk_preference::configInitializeSucceed, + m_dsgConfig.data(), [this](){ updateLoggingRules(); }); + QObject::connect(m_dsgConfig.data(), &dconfig_org_deepin_dtk_preference::configInitializeFailed, + m_dsgConfig.data(), [this, dsgAppId] { + m_dsgConfig.reset(); + qWarning() << "Logging rules config is invalid, please check `appId` [" << dsgAppId << "]arg is correct"; + }); + } + QString fallbackId = qgetenv("DTK_LOGGING_FALLBACK_APPID"); // 2. fallbackId 和 dsgAppId 非空且不等时,都创建和监听变化 if (!fallbackId.isEmpty() && fallbackId != dsgAppId) m_fallbackConfig.reset(createDConfig(fallbackId)); - // 3. 默认值和非默认值时,非默认值优先 - updateLoggingRules(); + // 3. 默认值和非默认值时,非默认值优先 + if (m_fallbackConfig) { + QObject::connect(m_fallbackConfig.data(), &dconfig_org_deepin_dtk_preference::configInitializeSucceed, + m_fallbackConfig.data(), [this](){ updateLoggingRules(); }); + QObject::connect(m_fallbackConfig.data(), &dconfig_org_deepin_dtk_preference::configInitializeFailed, + m_fallbackConfig.data(), [this, fallbackId] { + m_fallbackConfig.reset(); + qWarning() << "Logging rules config is invalid, please check `appId` [" << fallbackId << "]arg is correct"; + }); + } } void DLogManagerPrivate::updateLoggingRules() { QVariant var; // 4. 优先看 dsgConfig 是否默认值,其次 fallback 是否默认值 - if (m_dsgConfig && !m_dsgConfig->isDefaultValue(RULES_KEY)) { - var = m_dsgConfig->value(RULES_KEY); - } else if (m_fallbackConfig && !m_fallbackConfig->isDefaultValue(RULES_KEY)) { - var = m_fallbackConfig->value(RULES_KEY); - } else if (m_dsgConfig) { - var = m_dsgConfig->value(RULES_KEY); + if (m_dsgConfig && m_dsgConfig->isInitializeSucceed() && !m_dsgConfig->rulesIsDefaultValue()) { + var = m_dsgConfig->rules(); + } else if (m_fallbackConfig && m_dsgConfig->isInitializeSucceed() && !m_fallbackConfig->rulesIsDefaultValue()) { + var = m_fallbackConfig->rules(); + } else if (m_dsgConfig && m_dsgConfig->isInitializeSucceed()) { + var = m_dsgConfig->rules(); } if (var.isValid()) diff --git a/src/log/dconfig_org_deepin_dtk_preference.hpp b/src/log/dconfig_org_deepin_dtk_preference.hpp new file mode 100644 index 00000000..811d5039 --- /dev/null +++ b/src/log/dconfig_org_deepin_dtk_preference.hpp @@ -0,0 +1,483 @@ +/** + * This file is generated by dconfig2cpp. + * Command line arguments: /home/zccrs/projects/dtkcore/build/unknown-Debug/tools/dconfig2cpp/dconfig2cpp --get-is-default-value /home/zccrs/projects/dtkcommon/configs/org.deepin.dtk.preference.json + * Generation time: 2025-02-20T19:16:13 + * JSON file version: 1.0 + * + * WARNING: DO NOT MODIFY THIS FILE MANUALLY. + * If you need to change the content, please modify the dconfig2cpp tool. + */ + +#ifndef DCONFIG_ORG_DEEPIN_DTK_PREFERENCE_H +#define DCONFIG_ORG_DEEPIN_DTK_PREFERENCE_H + +#include +#include +#include +#include +#include +#include + +class dconfig_org_deepin_dtk_preference : public QObject { + Q_OBJECT + + Q_PROPERTY(bool autoDisplayFeature READ autoDisplayFeature WRITE setAutoDisplayFeature NOTIFY autoDisplayFeatureChanged RESET resetAutoDisplayFeature) + Q_PROPERTY(bool featureUpdated READ featureUpdated WRITE setFeatureUpdated NOTIFY featureUpdatedChanged RESET resetFeatureUpdated) + Q_PROPERTY(bool keyboardsearchDisabled READ keyboardsearchDisabled WRITE setKeyboardsearchDisabled NOTIFY keyboardsearchDisabledChanged RESET resetKeyboardsearchDisabled) + Q_PROPERTY(QString rules READ rules WRITE setRules NOTIFY rulesChanged RESET resetRules) + Q_PROPERTY(qlonglong themeType READ themeType WRITE setThemeType NOTIFY themeTypeChanged RESET resetThemeType) + Q_PROPERTY(qlonglong titlebarHeight READ titlebarHeight WRITE setTitlebarHeight NOTIFY titlebarHeightChanged RESET resetTitlebarHeight) + Q_PROPERTY(bool underlineShortcut READ underlineShortcut WRITE setUnderlineShortcut NOTIFY underlineShortcutChanged RESET resetUnderlineShortcut) +public: + explicit dconfig_org_deepin_dtk_preference(QThread *thread, DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &appId, const QString &subpath, QObject *parent) + : QObject(parent) { + if (!thread->isRunning()) { + qWarning() << QLatin1String("Warning: The provided thread is not running."); + } + Q_ASSERT(QThread::currentThread() != thread); + auto worker = new QObject(); + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, [=, this]() { + DTK_CORE_NAMESPACE::DConfig *config = nullptr; + if (backend) { + if (appId.isNull()) { + config = DTK_CORE_NAMESPACE::DConfig::create(backend, name, subpath, nullptr); + } else { + config = DTK_CORE_NAMESPACE::DConfig::create(backend, appId, name, subpath, nullptr); + } + } else { + if (appId.isNull()) { + config = DTK_CORE_NAMESPACE::DConfig::create(name, subpath, nullptr); + } else { + config = DTK_CORE_NAMESPACE::DConfig::create(appId, name, subpath, nullptr); + } + } + if (!config) { + qWarning() << QLatin1String("Failed to create DConfig instance."); + worker->deleteLater(); + return; + } + config->moveToThread(QThread::currentThread()); + initializeInConfigThread(config); + worker->deleteLater(); + }); + } + static dconfig_org_deepin_dtk_preference* create(const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread()) + { return new dconfig_org_deepin_dtk_preference(thread, nullptr, QStringLiteral(u"\u006f\u0072\u0067\u002e\u0064\u0065\u0065\u0070\u0069\u006e\u002e\u0064\u0074\u006b\u002e\u0070\u0072\u0065\u0066\u0065\u0072\u0065\u006e\u0063\u0065"), appId, subpath, parent); } + static dconfig_org_deepin_dtk_preference* create(DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread()) + { return new dconfig_org_deepin_dtk_preference(thread, backend, QStringLiteral(u"\u006f\u0072\u0067\u002e\u0064\u0065\u0065\u0070\u0069\u006e\u002e\u0064\u0074\u006b\u002e\u0070\u0072\u0065\u0066\u0065\u0072\u0065\u006e\u0063\u0065"), appId, subpath, parent); } + static dconfig_org_deepin_dtk_preference* createByName(const QString &name, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread()) + { return new dconfig_org_deepin_dtk_preference(thread, nullptr, name, appId, subpath, parent); } + static dconfig_org_deepin_dtk_preference* createByName(DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread()) + { return new dconfig_org_deepin_dtk_preference(thread, backend, name, appId, subpath, parent); } + ~dconfig_org_deepin_dtk_preference() { + if (m_config.loadRelaxed()) { + m_config.loadRelaxed()->deleteLater(); + } + } + + DTK_CORE_NAMESPACE::DConfig *config() const { + return m_config.loadRelaxed(); + } + + bool isInitializeSucceed() const { + return m_status.loadRelaxed() == static_cast(Status::Succeed); + } + + bool isInitializeFailed() const { + return m_status.loadRelaxed() == static_cast(Status::Failed); + } + + bool isInitializing() const { + return m_status.loadRelaxed() == static_cast(Status::Invalid); + } + + bool autoDisplayFeature() const { + return p_autoDisplayFeature; + } + void setAutoDisplayFeature(const bool &value) { + auto oldValue = p_autoDisplayFeature; + p_autoDisplayFeature = value; + markPropertySet(0); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u0061\u0075\u0074\u006f\u0044\u0069\u0073\u0070\u006c\u0061\u0079\u0046\u0065\u0061\u0074\u0075\u0072\u0065"), value); + }); + } + if (p_autoDisplayFeature != oldValue) { + Q_EMIT autoDisplayFeatureChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u0061\u0075\u0074\u006f\u0044\u0069\u0073\u0070\u006c\u0061\u0079\u0046\u0065\u0061\u0074\u0075\u0072\u0065"), value); + } + } + void resetAutoDisplayFeature() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u0061\u0075\u0074\u006f\u0044\u0069\u0073\u0070\u006c\u0061\u0079\u0046\u0065\u0061\u0074\u0075\u0072\u0065")); + }); + } + } + bool autoDisplayFeatureIsDefaultValue() const { + return !testPropertySet(0); + } + bool featureUpdated() const { + return p_featureUpdated; + } + void setFeatureUpdated(const bool &value) { + auto oldValue = p_featureUpdated; + p_featureUpdated = value; + markPropertySet(1); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u0066\u0065\u0061\u0074\u0075\u0072\u0065\u0055\u0070\u0064\u0061\u0074\u0065\u0064"), value); + }); + } + if (p_featureUpdated != oldValue) { + Q_EMIT featureUpdatedChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u0066\u0065\u0061\u0074\u0075\u0072\u0065\u0055\u0070\u0064\u0061\u0074\u0065\u0064"), value); + } + } + void resetFeatureUpdated() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u0066\u0065\u0061\u0074\u0075\u0072\u0065\u0055\u0070\u0064\u0061\u0074\u0065\u0064")); + }); + } + } + bool featureUpdatedIsDefaultValue() const { + return !testPropertySet(1); + } + bool keyboardsearchDisabled() const { + return p_keyboardsearchDisabled; + } + void setKeyboardsearchDisabled(const bool &value) { + auto oldValue = p_keyboardsearchDisabled; + p_keyboardsearchDisabled = value; + markPropertySet(2); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u006b\u0065\u0079\u0062\u006f\u0061\u0072\u0064\u0073\u0065\u0061\u0072\u0063\u0068\u0044\u0069\u0073\u0061\u0062\u006c\u0065\u0064"), value); + }); + } + if (p_keyboardsearchDisabled != oldValue) { + Q_EMIT keyboardsearchDisabledChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u006b\u0065\u0079\u0062\u006f\u0061\u0072\u0064\u0073\u0065\u0061\u0072\u0063\u0068\u0044\u0069\u0073\u0061\u0062\u006c\u0065\u0064"), value); + } + } + void resetKeyboardsearchDisabled() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u006b\u0065\u0079\u0062\u006f\u0061\u0072\u0064\u0073\u0065\u0061\u0072\u0063\u0068\u0044\u0069\u0073\u0061\u0062\u006c\u0065\u0064")); + }); + } + } + bool keyboardsearchDisabledIsDefaultValue() const { + return !testPropertySet(2); + } + QString rules() const { + return p_rules; + } + void setRules(const QString &value) { + auto oldValue = p_rules; + p_rules = value; + markPropertySet(3); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u0072\u0075\u006c\u0065\u0073"), value); + }); + } + if (p_rules != oldValue) { + Q_EMIT rulesChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u0072\u0075\u006c\u0065\u0073"), value); + } + } + void resetRules() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u0072\u0075\u006c\u0065\u0073")); + }); + } + } + bool rulesIsDefaultValue() const { + return !testPropertySet(3); + } + qlonglong themeType() const { + return p_themeType; + } + void setThemeType(const qlonglong &value) { + auto oldValue = p_themeType; + p_themeType = value; + markPropertySet(4); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u0074\u0068\u0065\u006d\u0065\u0054\u0079\u0070\u0065"), value); + }); + } + if (p_themeType != oldValue) { + Q_EMIT themeTypeChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u0074\u0068\u0065\u006d\u0065\u0054\u0079\u0070\u0065"), value); + } + } + void resetThemeType() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u0074\u0068\u0065\u006d\u0065\u0054\u0079\u0070\u0065")); + }); + } + } + bool themeTypeIsDefaultValue() const { + return !testPropertySet(4); + } + qlonglong titlebarHeight() const { + return p_titlebarHeight; + } + void setTitlebarHeight(const qlonglong &value) { + auto oldValue = p_titlebarHeight; + p_titlebarHeight = value; + markPropertySet(5); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u0074\u0069\u0074\u006c\u0065\u0062\u0061\u0072\u0048\u0065\u0069\u0067\u0068\u0074"), value); + }); + } + if (p_titlebarHeight != oldValue) { + Q_EMIT titlebarHeightChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u0074\u0069\u0074\u006c\u0065\u0062\u0061\u0072\u0048\u0065\u0069\u0067\u0068\u0074"), value); + } + } + void resetTitlebarHeight() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u0074\u0069\u0074\u006c\u0065\u0062\u0061\u0072\u0048\u0065\u0069\u0067\u0068\u0074")); + }); + } + } + bool titlebarHeightIsDefaultValue() const { + return !testPropertySet(5); + } + bool underlineShortcut() const { + return p_underlineShortcut; + } + void setUnderlineShortcut(const bool &value) { + auto oldValue = p_underlineShortcut; + p_underlineShortcut = value; + markPropertySet(6); + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this, value]() { + m_config.loadRelaxed()->setValue(QStringLiteral(u"\u0075\u006e\u0064\u0065\u0072\u006c\u0069\u006e\u0065\u0053\u0068\u006f\u0072\u0074\u0063\u0075\u0074"), value); + }); + } + if (p_underlineShortcut != oldValue) { + Q_EMIT underlineShortcutChanged(); + Q_EMIT valueChanged(QStringLiteral(u"\u0075\u006e\u0064\u0065\u0072\u006c\u0069\u006e\u0065\u0053\u0068\u006f\u0072\u0074\u0063\u0075\u0074"), value); + } + } + void resetUnderlineShortcut() { + if (auto config = m_config.loadRelaxed()) { + QMetaObject::invokeMethod(config, [this]() { + m_config.loadRelaxed()->reset(QStringLiteral(u"\u0075\u006e\u0064\u0065\u0072\u006c\u0069\u006e\u0065\u0053\u0068\u006f\u0072\u0074\u0063\u0075\u0074")); + }); + } + } + bool underlineShortcutIsDefaultValue() const { + return !testPropertySet(6); + } +Q_SIGNALS: + void configInitializeFailed(DTK_CORE_NAMESPACE::DConfig *config); + void configInitializeSucceed(DTK_CORE_NAMESPACE::DConfig *config); + void valueChanged(const QString &key, const QVariant &value); + + void autoDisplayFeatureChanged(); + void featureUpdatedChanged(); + void keyboardsearchDisabledChanged(); + void rulesChanged(); + void themeTypeChanged(); + void titlebarHeightChanged(); + void underlineShortcutChanged(); +private: + void initializeInConfigThread(DTK_CORE_NAMESPACE::DConfig *config) { + Q_ASSERT(!m_config.loadRelaxed()); + m_config.storeRelaxed(config); + if (!config->isValid()) { + m_status.storeRelaxed(static_cast(Status::Failed)); + Q_EMIT configInitializeFailed(config); + return; + } + + if (testPropertySet(0)) { + config->setValue(QStringLiteral(u"\u0061\u0075\u0074\u006f\u0044\u0069\u0073\u0070\u006c\u0061\u0079\u0046\u0065\u0061\u0074\u0075\u0072\u0065"), QVariant::fromValue(p_autoDisplayFeature)); + } else { + updateValue(QStringLiteral(u"\u0061\u0075\u0074\u006f\u0044\u0069\u0073\u0070\u006c\u0061\u0079\u0046\u0065\u0061\u0074\u0075\u0072\u0065"), QVariant::fromValue(p_autoDisplayFeature)); + } + if (testPropertySet(1)) { + config->setValue(QStringLiteral(u"\u0066\u0065\u0061\u0074\u0075\u0072\u0065\u0055\u0070\u0064\u0061\u0074\u0065\u0064"), QVariant::fromValue(p_featureUpdated)); + } else { + updateValue(QStringLiteral(u"\u0066\u0065\u0061\u0074\u0075\u0072\u0065\u0055\u0070\u0064\u0061\u0074\u0065\u0064"), QVariant::fromValue(p_featureUpdated)); + } + if (testPropertySet(2)) { + config->setValue(QStringLiteral(u"\u006b\u0065\u0079\u0062\u006f\u0061\u0072\u0064\u0073\u0065\u0061\u0072\u0063\u0068\u0044\u0069\u0073\u0061\u0062\u006c\u0065\u0064"), QVariant::fromValue(p_keyboardsearchDisabled)); + } else { + updateValue(QStringLiteral(u"\u006b\u0065\u0079\u0062\u006f\u0061\u0072\u0064\u0073\u0065\u0061\u0072\u0063\u0068\u0044\u0069\u0073\u0061\u0062\u006c\u0065\u0064"), QVariant::fromValue(p_keyboardsearchDisabled)); + } + if (testPropertySet(3)) { + config->setValue(QStringLiteral(u"\u0072\u0075\u006c\u0065\u0073"), QVariant::fromValue(p_rules)); + } else { + updateValue(QStringLiteral(u"\u0072\u0075\u006c\u0065\u0073"), QVariant::fromValue(p_rules)); + } + if (testPropertySet(4)) { + config->setValue(QStringLiteral(u"\u0074\u0068\u0065\u006d\u0065\u0054\u0079\u0070\u0065"), QVariant::fromValue(p_themeType)); + } else { + updateValue(QStringLiteral(u"\u0074\u0068\u0065\u006d\u0065\u0054\u0079\u0070\u0065"), QVariant::fromValue(p_themeType)); + } + if (testPropertySet(5)) { + config->setValue(QStringLiteral(u"\u0074\u0069\u0074\u006c\u0065\u0062\u0061\u0072\u0048\u0065\u0069\u0067\u0068\u0074"), QVariant::fromValue(p_titlebarHeight)); + } else { + updateValue(QStringLiteral(u"\u0074\u0069\u0074\u006c\u0065\u0062\u0061\u0072\u0048\u0065\u0069\u0067\u0068\u0074"), QVariant::fromValue(p_titlebarHeight)); + } + if (testPropertySet(6)) { + config->setValue(QStringLiteral(u"\u0075\u006e\u0064\u0065\u0072\u006c\u0069\u006e\u0065\u0053\u0068\u006f\u0072\u0074\u0063\u0075\u0074"), QVariant::fromValue(p_underlineShortcut)); + } else { + updateValue(QStringLiteral(u"\u0075\u006e\u0064\u0065\u0072\u006c\u0069\u006e\u0065\u0053\u0068\u006f\u0072\u0074\u0063\u0075\u0074"), QVariant::fromValue(p_underlineShortcut)); + } + + connect(config, &DTK_CORE_NAMESPACE::DConfig::valueChanged, this, [this](const QString &key) { + updateValue(key); + }, Qt::DirectConnection); + + m_status.storeRelaxed(static_cast(Status::Succeed)); + Q_EMIT configInitializeSucceed(config); + } + void updateValue(const QString &key, const QVariant &fallback = QVariant()) { + Q_ASSERT(QThread::currentThread() == m_config.loadRelaxed()->thread()); + const QVariant &value = m_config.loadRelaxed()->value(key, fallback); + if (key == QStringLiteral(u"\u0061\u0075\u0074\u006f\u0044\u0069\u0073\u0070\u006c\u0061\u0079\u0046\u0065\u0061\u0074\u0075\u0072\u0065")) { + markPropertySet(0, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_autoDisplayFeature != newValue) { + p_autoDisplayFeature = newValue; + Q_EMIT autoDisplayFeatureChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + if (key == QStringLiteral(u"\u0066\u0065\u0061\u0074\u0075\u0072\u0065\u0055\u0070\u0064\u0061\u0074\u0065\u0064")) { + markPropertySet(1, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_featureUpdated != newValue) { + p_featureUpdated = newValue; + Q_EMIT featureUpdatedChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + if (key == QStringLiteral(u"\u006b\u0065\u0079\u0062\u006f\u0061\u0072\u0064\u0073\u0065\u0061\u0072\u0063\u0068\u0044\u0069\u0073\u0061\u0062\u006c\u0065\u0064")) { + markPropertySet(2, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_keyboardsearchDisabled != newValue) { + p_keyboardsearchDisabled = newValue; + Q_EMIT keyboardsearchDisabledChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + if (key == QStringLiteral(u"\u0072\u0075\u006c\u0065\u0073")) { + markPropertySet(3, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_rules != newValue) { + p_rules = newValue; + Q_EMIT rulesChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + if (key == QStringLiteral(u"\u0074\u0068\u0065\u006d\u0065\u0054\u0079\u0070\u0065")) { + markPropertySet(4, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_themeType != newValue) { + p_themeType = newValue; + Q_EMIT themeTypeChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + if (key == QStringLiteral(u"\u0074\u0069\u0074\u006c\u0065\u0062\u0061\u0072\u0048\u0065\u0069\u0067\u0068\u0074")) { + markPropertySet(5, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_titlebarHeight != newValue) { + p_titlebarHeight = newValue; + Q_EMIT titlebarHeightChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + if (key == QStringLiteral(u"\u0075\u006e\u0064\u0065\u0072\u006c\u0069\u006e\u0065\u0053\u0068\u006f\u0072\u0074\u0063\u0075\u0074")) { + markPropertySet(6, !m_config.loadRelaxed()->isDefaultValue(key)); + auto newValue = qvariant_cast(value); + QMetaObject::invokeMethod(this, [this, newValue, key, value]() { + Q_ASSERT(QThread::currentThread() == this->thread()); + if (p_underlineShortcut != newValue) { + p_underlineShortcut = newValue; + Q_EMIT underlineShortcutChanged(); + Q_EMIT valueChanged(key, value); + } + }); + return; + } + } + inline void markPropertySet(const int index, bool on = true) { + if (index < 32) { + if (on) + m_propertySetStatus0.fetchAndOrOrdered(1 << (index - 0)); + else + m_propertySetStatus0.fetchAndAndOrdered(1 << (index - 0)); + return; + } + Q_UNREACHABLE(); + } + inline bool testPropertySet(const int index) const { + if (index < 32) { + return (m_propertySetStatus0.loadRelaxed() & (1 << (index - 0))); + } + Q_UNREACHABLE(); + } + + QAtomicPointer m_config = nullptr; + +public: + enum class Status { + Invalid = 0, + Succeed = 1, + Failed = 2 + }; +private: + QAtomicInteger m_status = static_cast(Status::Invalid); + + bool p_autoDisplayFeature { false }; + bool p_featureUpdated { false }; + bool p_keyboardsearchDisabled { false }; + // Default value: "" + QString p_rules { QLatin1String("") }; + qlonglong p_themeType { 0 }; + qlonglong p_titlebarHeight { -1 }; + bool p_underlineShortcut { false }; + QAtomicInteger m_propertySetStatus0 = 0; +}; + +#endif // DCONFIG_ORG_DEEPIN_DTK_PREFERENCE_H diff --git a/src/log/log.cmake b/src/log/log.cmake index ae64fcad..cfba5e86 100644 --- a/src/log/log.cmake +++ b/src/log/log.cmake @@ -3,6 +3,7 @@ file(GLOB LOG_HEADER ) set(LOG_SOURCE ${CMAKE_CURRENT_LIST_DIR}/LogManager.cpp + ${CMAKE_CURRENT_LIST_DIR}/dconfig_org_deepin_dtk_preference.hpp ) set(log_SRCS diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index f3ef2a58..63b8fc92 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -3,3 +3,4 @@ add_subdirectory(deepin-os-release) add_subdirectory(qdbusxml2cpp) add_subdirectory(settings) add_subdirectory(ch2py) +add_subdirectory(dconfig2cpp) diff --git a/tools/dconfig2cpp/CMakeLists.txt b/tools/dconfig2cpp/CMakeLists.txt new file mode 100644 index 00000000..74f1d3c3 --- /dev/null +++ b/tools/dconfig2cpp/CMakeLists.txt @@ -0,0 +1,26 @@ +set(TARGET_NAME dconfig2cpp) +set(BIN_NAME ${TARGET_NAME}${DTK_VERSION_MAJOR}) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core) + +add_executable(${BIN_NAME} + main.cpp +) + +target_link_libraries( + ${BIN_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core +) + +set_target_properties( + ${BIN_NAME} PROPERTIES + OUTPUT_NAME ${TARGET_NAME} + EXPORT_NAME DConfig2Cpp +) + +install( + TARGETS ${BIN_NAME} + EXPORT Dtk${DTK_VERSION_MAJOR}ToolsTargets + DESTINATION ${TOOL_INSTALL_DIR} +) diff --git a/tools/dconfig2cpp/main.cpp b/tools/dconfig2cpp/main.cpp new file mode 100644 index 00000000..9fa4cd7f --- /dev/null +++ b/tools/dconfig2cpp/main.cpp @@ -0,0 +1,495 @@ +// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static QString toUnicodeEscape(const QString& input) { + QString result; + for (QChar ch : input) { + result += QString("\\u%1").arg(ch.unicode(), 4, 16, QChar('0')); + } + return result; +} + +// Converts a QJsonValue to a corresponding C++ code representation +static QString jsonValueToCppCode(const QJsonValue &value){ + if (value.isBool()) { + return value.toBool() ? QLatin1String("true") : QLatin1String("false"); + } else if (value.isDouble()) { + const auto variantValue = value.toVariant(); + if (variantValue.userType() == QVariant(static_cast(1)).userType()) { + return QString::number(value.toInt()); + } else if (variantValue.userType() == QVariant(static_cast(1)).userType()) { + return QString::number(variantValue.toLongLong()); + } + + return QString::number(value.toDouble()); + } else if (value.isString()) { + const auto string = value.toString(); + if (string.isEmpty()) { + return QLatin1String("QLatin1String(\"\")"); + } + return QString("QStringLiteral(u\"%1\")").arg(toUnicodeEscape(string)); + } else if (value.isNull()) { + return "QVariant::fromValue(nullptr)"; + } else if (value.isArray()) { + QStringList elements; + const auto array = value.toArray(); + for (const QJsonValue &element : array) { + elements << "QVariant(" + jsonValueToCppCode(element) + ")"; + } + return "QList{" + elements.join(", ") + "}"; + } else if (value.isObject()) { + QStringList elements; + QJsonObject obj = value.toObject(); + for (auto it = obj.begin(); it != obj.end(); ++it) { + elements << QString("{QStringLiteral(u\"%1\"), QVariant(%2)}") + .arg(toUnicodeEscape(it.key()), + jsonValueToCppCode(it.value())); + } + return "QVariantMap{" + elements.join(", ") + "}"; + } else { + return "QVariant()"; + } +} + +int main(int argc, char *argv[]) { + QCoreApplication app(argc, argv); + QCommandLineParser parser; + parser.setApplicationDescription(QLatin1String("DConfig to C++ class generator")); + parser.addHelpOption(); + + // Define command line options + QCommandLineOption classNameOption(QStringList() << QLatin1String("c") << QLatin1String("class-name"), + QLatin1String("Name of the generated class"), + QLatin1String("className")); + parser.addOption(classNameOption); + + QCommandLineOption sourceFileOption(QStringList() << QLatin1String("o") << QLatin1String("output"), + QLatin1String("Path to the output source(header only) file"), + QLatin1String("sourceFile")); + parser.addOption(sourceFileOption); + + QCommandLineOption cppPropertyOption(QStringList() << QLatin1String("use-qproperty"), + QLatin1String("Generate Qt/C++ properties use QProperty")); + parser.addOption(cppPropertyOption); + + QCommandLineOption forceRequestThread(QStringList() << QLatin1String("force-request-thread"), + QLatin1String("Force request thread to create DConfig instance")); + parser.addOption(forceRequestThread); + + QCommandLineOption getIsDefaultValue(QStringList() << QLatin1String("get-is-default-value"), + QLatin1String("Generate *IsDefaultValue method")); + parser.addOption(getIsDefaultValue); + + QCommandLineOption noComment(QStringList() << QLatin1String("no-comment"), + QLatin1String("Do not generate comments in the generated code")); + parser.addOption(noComment); + + parser.addPositionalArgument(QLatin1String("json-file"), QLatin1String("Path to the input JSON file")); + parser.process(app); + + const QStringList args = parser.positionalArguments(); + if (args.size() != 1) { + parser.showHelp(-1); + } + + QString className = parser.value(classNameOption); + const QString jsonFileName = QFileInfo(args.first()).completeBaseName(); + if (className.isEmpty()) { + className = QLatin1String("dconfig_") + QString(jsonFileName).replace('.', '_'); + } + + QString sourceFilePath = parser.value(sourceFileOption); + if (sourceFilePath.isEmpty()) { + sourceFilePath = className.toLower() + QLatin1String(".hpp"); + } + + QFile file(args.first()); + if (!file.open(QIODevice::ReadOnly)) { + qWarning() << QLatin1String("Failed to open file:") << args.first(); + return -1; + } + + QByteArray data = file.readAll(); + QJsonDocument doc = QJsonDocument::fromJson(data); + QJsonObject root = doc.object(); + + // Check magic value + if (root[QLatin1String("magic")].toString() != QLatin1String("dsg.config.meta")) { + qWarning() << QLatin1String("Invalid magic value in JSON file"); + return -1; + } + + // Generate header and source files + QFile headerFile(sourceFilePath); + if (!headerFile.open(QIODevice::WriteOnly | QIODevice::Text)) { + qWarning() << QLatin1String("Failed to open file for writing:") << sourceFilePath; + return -1; + } + + QTextStream headerStream(&headerFile); + + // Extract version and add it as a comment in the generated code + QString version = root[QLatin1String("version")].toString(); + + // Generate header and source file comments + QString commandLineArgs = QCoreApplication::arguments().join(QLatin1String(" ")); + QString generationTime = QDateTime::currentDateTime().toString(Qt::ISODate); + + if (!parser.isSet(noComment)) { + QString headerComment = QString( + "/**\n" + " * This file is generated by dconfig2cpp.\n" + " * Command line arguments: %1\n" + " * Generation time: %2\n" + " * JSON file version: %3\n" + " *\n" + " * WARNING: DO NOT MODIFY THIS FILE MANUALLY.\n" + " * If you need to change the content, please modify the dconfig2cpp tool.\n" + " */\n\n" + ).arg(commandLineArgs, generationTime, version); + + headerStream << headerComment; + } + + QJsonObject contents = root[QLatin1String("contents")].toObject(); + + // Write header file content + headerStream << "#ifndef " << className.toUpper() << "_H\n"; + headerStream << "#define " << className.toUpper() << "_H\n\n"; + headerStream << "#include \n"; + headerStream << "#include \n"; + headerStream << "#include \n"; + headerStream << "#include \n"; + headerStream << "#include \n"; + + if (parser.isSet(cppPropertyOption)) { + headerStream << "#include \n"; + } + + headerStream << "#include \n\n"; + headerStream << "class " << className << " : public QObject {\n"; + headerStream << " Q_OBJECT\n\n"; + + struct Property { + QString typeName; + QString propertyName; + QString capitalizedPropertyName; + QString propertyNameString; + QJsonValue defaultValue; + }; + QList properties; + + static QStringList usedKeywords = { + className, + "create", + "createByName", + "config", + "isInitializeSucceed", + "isInitializeFailed", + "isInitializing", + "m_config", + "m_status", + }; + + for (int i = 0; i <= (contents.size()) / 32; ++i) { + usedKeywords << QLatin1String("m_propertySetStatus") + QString::number(i); + } + + // Iterate over JSON contents to extract properties + for (auto it = contents.begin(); it != contents.end(); ++it) { + QJsonObject obj = it.value().toObject(); + QString propertyName = it.key(); + QString typeName; + const auto value = obj[QLatin1String("value")]; + if (value.isBool()) { + typeName = "bool"; + } else if (value.isArray()) { + typeName = "QList"; + } else if (value.isObject()) { + typeName = "QVariantMap"; + } else if (value.isDouble()) { + const auto variantValue = value.toVariant(); + typeName = QString::fromLatin1(variantValue.typeName()); + } else if (value.isString()) { + typeName = "QString"; + } else { + typeName = "QVariant"; + } + + QString capitalizedPropertyName = propertyName; + if (!capitalizedPropertyName.isEmpty() && capitalizedPropertyName[0].isLower()) { + capitalizedPropertyName[0] = capitalizedPropertyName[0].toUpper(); + } + + properties.append(Property({ + typeName, + propertyName, + capitalizedPropertyName, + "QStringLiteral(u\"" + toUnicodeEscape(propertyName) + "\")", + obj[QLatin1String("value")] + })); + + const QString readFunction = usedKeywords.contains(propertyName) ? QLatin1String(" READ get") + capitalizedPropertyName + : QLatin1String(" READ ") + propertyName; + headerStream << " Q_PROPERTY(" << typeName << " " << propertyName << readFunction + << " WRITE set" << capitalizedPropertyName << " NOTIFY " << propertyName << "Changed" + << " RESET reset" << capitalizedPropertyName << ")\n"; + } + headerStream << "public:\n" + << " explicit " << className + << R"((QThread *thread, DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &appId, const QString &subpath, QObject *parent) + : QObject(parent) { + if (!thread->isRunning()) { + qWarning() << QLatin1String("Warning: The provided thread is not running."); + } + Q_ASSERT(QThread::currentThread() != thread); + auto worker = new QObject(); + worker->moveToThread(thread); + QMetaObject::invokeMethod(worker, [=, this]() { + DTK_CORE_NAMESPACE::DConfig *config = nullptr; + if (backend) { + if (appId.isNull()) { + config = DTK_CORE_NAMESPACE::DConfig::create(backend, name, subpath, nullptr); + } else { + config = DTK_CORE_NAMESPACE::DConfig::create(backend, appId, name, subpath, nullptr); + } + } else { + if (appId.isNull()) { + config = DTK_CORE_NAMESPACE::DConfig::create(name, subpath, nullptr); + } else { + config = DTK_CORE_NAMESPACE::DConfig::create(appId, name, subpath, nullptr); + } + } + if (!config) { + qWarning() << QLatin1String("Failed to create DConfig instance."); + worker->deleteLater(); + return; + } + config->moveToThread(QThread::currentThread()); + initializeInConfigThread(config); + worker->deleteLater(); + }); + } +)"; + + const QString jsonFileString = "QStringLiteral(u\"" + toUnicodeEscape(jsonFileName) + "\")"; + // Generate constructors + if (parser.isSet(forceRequestThread)) + headerStream << " static " << className << "* create(QThread *thread, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr)\n"; + else + headerStream << " static " << className << "* create(const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread())\n"; + headerStream << " { return new " << className << "(thread, nullptr, " << jsonFileString << ", appId, subpath, parent); }\n"; + if (parser.isSet(forceRequestThread)) + headerStream << " static " << className << "* create(QThread *thread, DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr)\n"; + else + headerStream << " static " << className << "* create(DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread())\n"; + headerStream << " { return new " << className << "(thread, backend, " << jsonFileString << ", appId, subpath, parent); }\n"; + if (parser.isSet(forceRequestThread)) + headerStream << " static " << className << "* createByName(QThread *thread, const QString &name, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr)\n"; + else + headerStream << " static " << className << "* createByName(const QString &name, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread())\n"; + headerStream << " { return new " << className << "(thread, nullptr, name, appId, subpath, parent); }\n"; + if (parser.isSet(forceRequestThread)) + headerStream << " static " << className << "* createByName(QThread *thread, DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr)\n"; + else + headerStream << " static " << className << "* createByName(DTK_CORE_NAMESPACE::DConfigBackend *backend, const QString &name, const QString &appId = {}, const QString &subpath = {}, QObject *parent = nullptr, QThread *thread = DTK_CORE_NAMESPACE::DConfig::globalThread())\n"; + headerStream << " { return new " << className << "(thread, backend, name, appId, subpath, parent); }\n"; + + // Destructor + headerStream << " ~" << className << R"(() { + if (m_config.loadRelaxed()) { + m_config.loadRelaxed()->deleteLater(); + } + } + + DTK_CORE_NAMESPACE::DConfig *config() const { + return m_config.loadRelaxed(); + } + + bool isInitializeSucceed() const { + return m_status.loadRelaxed() == static_cast(Status::Succeed); + } + + bool isInitializeFailed() const { + return m_status.loadRelaxed() == static_cast(Status::Failed); + } + + bool isInitializing() const { + return m_status.loadRelaxed() == static_cast(Status::Invalid); + } + +)"; + + // Generate property getter and setter methods + for (int i = 0; i < properties.size(); ++i) { + const Property &property = properties[i]; + const QString readFunction = usedKeywords.contains(property.propertyName) + ? "get" + property.capitalizedPropertyName + : property.propertyName; + assert(!usedKeywords.contains(readFunction)); + + headerStream << " " << property.typeName << " " << readFunction << "() const {\n" + << " return p_" << property.propertyName << ";\n }\n"; + headerStream << " void set" << property.capitalizedPropertyName << "(const " << property.typeName << " &value) {\n" + << " auto oldValue = p_" << property.propertyName << ";\n" + << " p_" << property.propertyName << " = value;\n" + << " markPropertySet(" << i << ");\n" + << " if (auto config = m_config.loadRelaxed()) {\n" + << " QMetaObject::invokeMethod(config, [this, value]() {\n" + << " m_config.loadRelaxed()->setValue(" << property.propertyNameString << ", value);\n" + << " });\n" + << " }\n" + << " if (p_" << property.propertyName << " != oldValue) {\n" + << " Q_EMIT " << property.propertyName << "Changed();\n" + << " Q_EMIT valueChanged(" << property.propertyNameString << ", value);\n" + << " }\n" + << " }\n" + << " void reset" << property.capitalizedPropertyName << "() {\n" + << " if (auto config = m_config.loadRelaxed()) {\n" + << " QMetaObject::invokeMethod(config, [this]() {\n" + << " m_config.loadRelaxed()->reset(" << property.propertyNameString << ");\n" + << " });\n" + << " }\n" + << " }\n"; + if (parser.isSet(cppPropertyOption)) { + headerStream << " QBindable<" << property.typeName << "> bindable" << property.capitalizedPropertyName << "() {\n" + << " return QBindable<" << property.typeName << ">(this, " << property.propertyNameString << ");\n" + << " }\n"; + } + if (parser.isSet(getIsDefaultValue)) { + headerStream << " bool " << property.propertyName << "IsDefaultValue() const {\n" + << " return !testPropertySet(" << i << ");\n" + << " }\n"; + } + } + + // Generate signals for property changes + headerStream << "Q_SIGNALS:\n" + << " void configInitializeFailed(DTK_CORE_NAMESPACE::DConfig *config);\n" + << " void configInitializeSucceed(DTK_CORE_NAMESPACE::DConfig *config);\n" + << " void valueChanged(const QString &key, const QVariant &value);\n\n"; + for (const Property &property : std::as_const(properties)) { + headerStream << " void " << property.propertyName << "Changed();\n"; + } + + // Generate private methods and members + headerStream << "private:\n"; + + headerStream << " void initializeInConfigThread(DTK_CORE_NAMESPACE::DConfig *config) {\n" + << " Q_ASSERT(!m_config.loadRelaxed());\n" + << " m_config.storeRelaxed(config);\n" + << " if (!config->isValid()) {\n" + << " m_status.storeRelaxed(static_cast(Status::Failed));\n" + << " Q_EMIT configInitializeFailed(config);\n" + << " return;\n" + << " }\n\n"; + for (int i = 0; i < properties.size(); ++i) { + const Property &property = properties[i]; + headerStream << " if (testPropertySet(" << i << ")) {\n"; + headerStream << " config->setValue(" << property.propertyNameString << ", QVariant::fromValue(p_" << property.propertyName << "));\n"; + headerStream << " } else {\n"; + headerStream << " updateValue(" << property.propertyNameString << ", QVariant::fromValue(p_" << property.propertyName << "));\n"; + headerStream << " }\n"; + } + headerStream << R"( + connect(config, &DTK_CORE_NAMESPACE::DConfig::valueChanged, this, [this](const QString &key) { + updateValue(key); + }, Qt::DirectConnection); + + m_status.storeRelaxed(static_cast(Status::Succeed)); + Q_EMIT configInitializeSucceed(config); + } + void updateValue(const QString &key, const QVariant &fallback = QVariant()) { + Q_ASSERT(QThread::currentThread() == m_config.loadRelaxed()->thread()); + const QVariant &value = m_config.loadRelaxed()->value(key, fallback); +)"; + for (int i = 0; i < properties.size(); ++i) { + const Property &property = properties.at(i); + headerStream << " if (key == " << property.propertyNameString << ") {\n"; + + if (parser.isSet(getIsDefaultValue)) + headerStream << " markPropertySet(" << i << ", !m_config.loadRelaxed()->isDefaultValue(key));\n"; + + headerStream << " auto newValue = qvariant_cast<" << property.typeName << ">(value);\n" + << " QMetaObject::invokeMethod(this, [this, newValue, key, value]() {\n" + << " Q_ASSERT(QThread::currentThread() == this->thread());\n" + << " if (p_" << property.propertyName << " != newValue) {\n" + << " p_" << property.propertyName << " = newValue;\n" + << " Q_EMIT " << property.propertyName << "Changed();\n" + << " Q_EMIT valueChanged(key, value);\n" + << " }\n" + << " });\n" + << " return;\n" + << " }\n"; + } + headerStream << " }\n"; + + // Mark property as set + headerStream << " inline void markPropertySet(const int index, bool on = true) {\n"; + for (int i = 0; i <= (properties.size()) / 32; ++i) { + headerStream << " if (index < " << (i + 1) * 32 << ") {\n" + << " if (on)\n" + << " m_propertySetStatus" << QString::number(i) << ".fetchAndOrOrdered(1 << (index - " << i * 32 << "));\n" + << " else\n" + << " m_propertySetStatus" << QString::number(i) << ".fetchAndAndOrdered(1 << (index - " << i * 32 << "));\n" + << " return;\n" + << " }\n"; + } + headerStream << " Q_UNREACHABLE();\n }\n"; + + // Test if property is set + headerStream << " inline bool testPropertySet(const int index) const {\n"; + for (int i = 0; i <= (properties.size()) / 32; ++i) { + headerStream << " if (index < " << (i + 1) * 32 << ") {\n"; + headerStream << " return (m_propertySetStatus" << QString::number(i) << ".loadRelaxed() & (1 << (index - " << i * 32 << ")));\n"; + headerStream << " }\n"; + } + headerStream << " Q_UNREACHABLE();\n" + << " }\n"; + + // Member variables + headerStream << R"( + QAtomicPointer m_config = nullptr; + +public: + enum class Status { + Invalid = 0, + Succeed = 1, + Failed = 2 + }; +private: + QAtomicInteger m_status = static_cast(Status::Invalid); + +)"; + + // Property variables + for (const Property &property : std::as_const(properties)) { + if (property.typeName == QLatin1String("int") || property.typeName == QLatin1String("qint64")) { + headerStream << " // Note: If you expect a double type, add 'e' to the number in the JSON value field, e.g., \"value\": 1.0e, not just 1.0\n"; + } else if (property.typeName == QLatin1String("QString")) { + headerStream << " // Default value: \"" << property.defaultValue.toString().replace("\n", "\\n").replace("\r", "\\r") << "\"\n"; + } + headerStream << " " << property.typeName << " p_" << property.propertyName << " { "; + headerStream << jsonValueToCppCode(property.defaultValue) << " };\n"; + } + + // Property set status variables + for (int i = 0; i <= (properties.size()) / 32; ++i) { + headerStream << " QAtomicInteger m_propertySetStatus" << QString::number(i) << " = 0;\n"; + } + headerStream << "};\n\n"; + headerStream << "#endif // " << className.toUpper() << "_H\n"; + + return 0; +}