feat: support auto generate the Qt code from a dconfig's json

To help developer get/set dconfig in the non GUI thread.
This commit is contained in:
JiDe Zhang 2024-12-03 20:41:10 +08:00 committed by zccrs
parent 90b86255cb
commit a9a569b42a
9 changed files with 623 additions and 1 deletions

View File

@ -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()

View File

@ -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)

View File

@ -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 配置策略后端名称

View File

@ -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

View File

@ -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 @@ public:
QObject *parent = nullptr);
static void setAppId(const QString &appId);
static QThread *globalThread();
QString backendName() const;

View File

@ -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

View File

@ -3,3 +3,4 @@ add_subdirectory(deepin-os-release)
add_subdirectory(qdbusxml2cpp)
add_subdirectory(settings)
add_subdirectory(ch2py)
add_subdirectory(dconfig2cpp)

View File

@ -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}
)

495
tools/dconfig2cpp/main.cpp Normal file
View File

@ -0,0 +1,495 @@
// SPDX-FileCopyrightText: 2024 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QJsonArray>
#include <QFile>
#include <QDebug>
#include <QCommandLineParser>
#include <QFileInfo>
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<int>(1)).userType()) {
return QString::number(value.toInt());
} else if (variantValue.userType() == QVariant(static_cast<qint64>(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<QVariant>{" + 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 <QThread>\n";
headerStream << "#include <QVariant>\n";
headerStream << "#include <QDebug>\n";
headerStream << "#include <QAtomicPointer>\n";
headerStream << "#include <QAtomicInteger>\n";
if (parser.isSet(cppPropertyOption)) {
headerStream << "#include <QProperty>\n";
}
headerStream << "#include <DConfig>\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<Property> 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<QVariant>";
} 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<int>(Status::Succeed);
}
bool isInitializeFailed() const {
return m_status.loadRelaxed() == static_cast<int>(Status::Failed);
}
bool isInitializing() const {
return m_status.loadRelaxed() == static_cast<int>(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<int>(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<int>(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<DTK_CORE_NAMESPACE::DConfig> m_config = nullptr;
public:
enum class Status {
Invalid = 0,
Succeed = 1,
Failed = 2
};
private:
QAtomicInteger<int> m_status = static_cast<int>(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<quint32> m_propertySetStatus" << QString::number(i) << " = 0;\n";
}
headerStream << "};\n\n";
headerStream << "#endif // " << className.toUpper() << "_H\n";
return 0;
}