diff --git a/tests/auto/qml/qmlsettingsgenerator/generatortest.xml b/tests/auto/qml/qmlsettingsgenerator/generatortest.xml new file mode 100644 index 0000000..1bc9c4d --- /dev/null +++ b/tests/auto/qml/qmlsettingsgenerator/generatortest.xml @@ -0,0 +1,70 @@ + + + QtCore/QDateTime + QtCore/QUrl + + + + + + + + + + QUrl::fromLocalFile(QStringLiteral("/path/to/something")) + + + + + + + + + + + + + qRound(42.8) + + + + + + + + + + + + "Hello World" + { + "test1", + "test2", + "test3" + } + + diff --git a/tests/auto/qml/qmlsettingsgenerator/qmlsettingsgenerator.pro b/tests/auto/qml/qmlsettingsgenerator/qmlsettingsgenerator.pro index 9c5d2d0..4e1c584 100644 --- a/tests/auto/qml/qmlsettingsgenerator/qmlsettingsgenerator.pro +++ b/tests/auto/qml/qmlsettingsgenerator/qmlsettingsgenerator.pro @@ -10,7 +10,9 @@ SOURCES += \ tst_qmlsettingsgenerator.cpp QML_SETTINGS_DEFINITIONS += \ - ../../mvvmcore/settingsgenerator/generatortest.xml + generatortest.xml + +SETTINGS_DEFINITIONS += $$QML_SETTINGS_DEFINITIONS DISTFILES += \ tst_qmlsettingsgenerator.qml diff --git a/tools/settingsgenerator/cppsettingsgenerator.cpp b/tools/settingsgenerator/cppsettingsgenerator.cpp index a3d0adf..780abfa 100644 --- a/tools/settingsgenerator/cppsettingsgenerator.cpp +++ b/tools/settingsgenerator/cppsettingsgenerator.cpp @@ -53,7 +53,7 @@ void CppSettingsGenerator::writeHeader(const SettingsType &settings) _hdr << "\n"; // create the class - _hdr << "class " << QString{settings.prefix ? settings.prefix.value() + QLatin1Char(' ') + settings.name.value() : settings.name.value()} << " : public QObject\n" + _hdr << "class " << (settings.prefix ? settings.prefix.value() + QLatin1Char(' ') + settings.name.value() : settings.name.value()) << " : public QObject\n" << "{\n" << "\tQ_OBJECT\n\n" << "\tQ_PROPERTY(QtMvvm::ISettingsAccessor *accessor READ accessor CONSTANT FINAL)\n\n" @@ -82,6 +82,8 @@ void CppSettingsGenerator::writeNodeElementDeclarations(const NodeContentGroup & writeListEntryDeclaration(nonstd::get(cNode), typeMappings, intendent); else if(nonstd::holds_alternative(cNode)) writeNodeElementDeclarations(nonstd::get(cNode), typeMappings, intendent); + else + Q_UNREACHABLE(); } } @@ -196,6 +198,8 @@ void CppSettingsGenerator::writeNodeElementDefinitions(const SettingsGeneratorBa writeListEntryDefinition(nonstd::get(cNode), typeMappings, baseKey, keyChain); else if(nonstd::holds_alternative(cNode)) writeNodeElementDefinitions(nonstd::get(cNode), typeMappings, baseKey, keyChain); + else + Q_UNREACHABLE(); } } diff --git a/tools/settingsgenerator/qmlsettingsgenerator.cpp b/tools/settingsgenerator/qmlsettingsgenerator.cpp index bd973eb..4aaa025 100644 --- a/tools/settingsgenerator/qmlsettingsgenerator.cpp +++ b/tools/settingsgenerator/qmlsettingsgenerator.cpp @@ -17,9 +17,13 @@ void QmlSettingsGenerator::process(const QString &inPath) settings.name.value().prepend(QStringLiteral("QMLTYPE_")); fixTrContext(settings, QFileInfo{inPath}.fileName()); + _name = settings.name.value(); + _prefixName = settings.prefix ? settings.prefix.value() + QLatin1Char(' ') + _name : _name; + _typeMappings = settings.typeMappings; + if(!_hdrFile.open(QIODevice::WriteOnly | QIODevice::Text)) throw FileException{_hdrFile}; - writeHeader(settings); + writeHeader(settings, QFileInfo{inPath}.completeBaseName() + QStringLiteral(".h")); _hdrFile.close(); if(!_srcFile.open(QIODevice::WriteOnly | QIODevice::Text)) @@ -28,7 +32,7 @@ void QmlSettingsGenerator::process(const QString &inPath) _srcFile.close(); } -void QmlSettingsGenerator::writeHeader(const SettingsGeneratorBase::SettingsType &settings) +void QmlSettingsGenerator::writeHeader(const SettingsGeneratorBase::SettingsType &settings, const QString &inHdrPath) { QString incGuard = QFileInfo{_hdrFile.fileName()} .completeBaseName() @@ -38,10 +42,12 @@ void QmlSettingsGenerator::writeHeader(const SettingsGeneratorBase::SettingsType << "#define " << incGuard << "\n\n"; // write the includes + const auto &hdrPath = settings.qml.has_value() ? settings.qml.value().header.value_or(inHdrPath) : inHdrPath; auto includes = QList { {false, QStringLiteral("QtCore/QObject")}, + {false, QStringLiteral("QtCore/QScopedPointer")}, {false, QStringLiteral("QtQml/QQmlListProperty")}, - {false, QStringLiteral("QtMvvmCore/ISettingsAccessor")} + {false, hdrPath} } + settings.includes; for(const auto &inc : includes) { if(inc.local) @@ -51,16 +57,24 @@ void QmlSettingsGenerator::writeHeader(const SettingsGeneratorBase::SettingsType } _hdr << "\n"; + writeListTypeClasses(settings); + + // create all the qmltype classes + int offset; + QList childOffsets; + std::tie(offset, childOffsets) = writeNodeContentClassesDeclarations(settings); + // create the class - _hdr << "class " << QString{settings.prefix ? settings.prefix.value() + QLatin1Char(' ') + settings.name.value() : settings.name.value()} << " : public QObject\n" + _hdr << "class " << _prefixName << " : public QObject\n" << "{\n" << "\tQ_OBJECT\n\n" - << "\tQ_PROPERTY(QtMvvm::ISettingsAccessor *accessor READ accessor CONSTANT FINAL)\n\n" - << "public:\n" - << "\texplicit " << settings.name.value() << "(QObject *parent = nullptr);\n" - << "\tQtMvvm::ISettingsAccessor *accessor() const;\n\n"; + << "\tQ_PROPERTY(QtMvvm::ISettingsAccessor *accessor READ accessor CONSTANT FINAL)\n\n"; + writeNodeClassPropertiesDeclarations(settings, childOffsets); + _hdr << "public:\n" + << "\texplicit " << settings.name.value() << "(QObject *parent = nullptr);\n" + << "\tQtMvvm::ISettingsAccessor *accessor() const;\n\n"; _hdr << "\nprivate:\n" << "\tQtMvvm::ISettingsAccessor *_accessor;\n" @@ -68,7 +82,187 @@ void QmlSettingsGenerator::writeHeader(const SettingsGeneratorBase::SettingsType << "#endif //" << incGuard << '\n'; } +void QmlSettingsGenerator::writeListTypeClasses(const SettingsGeneratorBase::SettingsType &settings) +{ + // write the list wrapper base class + _hdr << "class " << _prefixName << "_ListType : public QObject\n" + << "{\n" + << "\tQ_OBJECT\n\n" + << "\tQ_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged)\n\n" + << "public:\n" + << "\tclass Accessor {\n" + << "\tpublic:\n" + << "\t\tinline virtual ~Accessor() = default;\n" + << "\t\tvirtual QVariant value() const = 0;\n" + << "\t\tvirtual void setValue(const QVariant &value) = 0;\n" + << "\t};\n\n" + << "\texplicit " << _prefixName << "_ListType(QObject *parent);\n\n" + << "\tQVariant value() const { return _data ? _data->value() : _buffer; }\n" + << "\tvoid setValue(const QVariant &value) { if(_data) _data->setValue(value); else _buffer = value; }\n" + << "\tvoid setAccessor(Accessor *accessor) { _data.reset(accessor); if(_buffer.isValid()) _data->setValue(_buffer); _buffer.clear(); }\n\n" + << "Q_SIGNALS:\n" + << "\tvoid valueChanged(const QVariant &value);\n\n" + << "private:\n" + << "\tQScopedPointer _data;\n" + << "\tQVariant _buffer;\n" + << "};\n\n"; + + // write the generic variant + _hdr << "template \n" + << "class " << _prefixName << "_ListType_Accessor : public " << _prefixName << "_ListType::Accessor\n" + << "{\n" + << "public:\n" + << "\tstruct ListData {\n" + << "\t\tQtMvvm::SettingsEntry> &entry;\n" + << "\t\tQList<" << _prefixName << "_ListType> elements;\n" + << "\t};\n\n" + << "\t" << _prefixName << "_ListType_Accessor(QtMvvm::SettingsEntry> &entry, int index) : \n" + << "\t\t_entry{entry},\n" + << "\t\t_index{index}\n" + << "\t{}\n" + << "\tQVariant value() const final { return QVariant::fromValue(_entry.getAt(_index)); }\n" + << "\tvoid setValue(const QVariant &value) final { _entry.setAt(_index, value.template value()); }\n\n" + << "private:\n" + << "\tfriend class " << _prefixName << ";\n" + << "\tQtMvvm::SettingsEntry> &_entry;\n" + << "\tconst int _index;\n\n" + << "\tstatic void append(QQmlListProperty<" << _prefixName << "_ListType>* list, " << _prefixName << "_ListType* element) {\n" + << "\t\tconst auto data = reinterpret_cast(list->data);\n" + << "\t\tconst auto maxIndex = data->entry.size();\n" + << "\t\tfor(auto index = data->elements.size(); index <= maxIndex; index++) {\n" + << "\t\t\t" << _prefixName << "_ListType *elem;\n" + << "\t\t\tif(index == maxIndex) {\n" + << "\t\t\t\telem = element;\n" + << "\t\t\t\telem->setParent(list->object);\n" + << "\t\t\t} else\n" + << "\t\t\t\telem = new " << _prefixName << "_ListType{list->object};\n" + << "\t\t\tdata->elements.append(elem);\n" + << "\t\t\telem->setAccessor(new " << _prefixName << "_ListType_Accessor{data->entry, index});\n" + << "\t\t\tdata->entry.addChangeCallback(elem, [index, elem](int i, const T &d) {\n" + << "\t\t\t\tif(i == index)\n" + << "\t\t\t\t\temit elem->valueChanged(QVariant::fromValue(d));\n" + << "\t\t\t});\n" + << "\t\t}\n" + << "\t}\n\n" + << "\tstatic " << _prefixName << "_ListType *at(QQmlListProperty<" << _prefixName << "_ListType>* list, int index) {\n" + << "\t\tconst auto data = reinterpret_cast(list->data);\n" + << "\t\treturn data->elements.size() > index ? data->elements.value(index) : nullptr;\n" + << "\t}\n\n" + << "\tstatic int count(QQmlListProperty<" << _prefixName << "_ListType>* list) {\n" + << "\t\treturn reinterpret_cast(list->data)->elements.size();\n" + << "\t}\n\n" + << "\tstatic void clear(QQmlListProperty<" << _prefixName << "_ListType>* list) {\n" + << "\t\tconst auto data = reinterpret_cast(list->data);\n" + << "\t\tfor(auto elem : qAsConst(data->elements))\n" + << "\t\t\telem->deleteLater();\n" + << "\t\tdata->elements.clear();\n" + << "\t\tdata->entry.reset(false);\n" + << "\t}\n" + << "};\n\n"; +} + +std::tuple> QmlSettingsGenerator::writeNodeContentClassesDeclarations(const SettingsGeneratorBase::NodeContentGroup &node, int offset) +{ + QList offsetList; + for(const auto &cNode : node.contentNodes) { + if(nonstd::holds_alternative(cNode)) { + offset = writeNodeClassDeclaration(nonstd::get(cNode), offset); + offsetList.append(offset - 1); + } else if(nonstd::holds_alternative(cNode)) { + if(!nonstd::get(cNode).contentNodes.isEmpty()) { + offset = writeNodeClassDeclaration(nonstd::get(cNode), offset); + offsetList.append(offset - 1); + } else + offsetList.append(-1); + } else if(nonstd::holds_alternative(cNode)) { + if(!nonstd::get(cNode).contentNodes.isEmpty()) { + offset = writeNodeClassDeclaration(nonstd::get(cNode), offset); + offsetList.append(offset - 1); + } else + offsetList.append(-1); + } else if(nonstd::holds_alternative(cNode)) { + QList subList; + std::tie(offset, subList) = writeNodeContentClassesDeclarations(nonstd::get(cNode), offset); + offsetList.append(subList); + } else + Q_UNREACHABLE(); + } + return std::make_tuple(offset, offsetList); +} + +void QmlSettingsGenerator::writeNodeClassPropertiesDeclarations(const SettingsGeneratorBase::NodeContentGroup &node, QList &childOffsets) +{ + for(const auto &cNode : node.contentNodes) { + if(nonstd::holds_alternative(cNode)) + writeNodePropertyDeclaration(nonstd::get(cNode), childOffsets.takeFirst()); + else if(nonstd::holds_alternative(cNode)) + writeEntryPropertyDeclaration(nonstd::get(cNode), childOffsets.takeFirst()); + else if(nonstd::holds_alternative(cNode)) + writeEntryPropertyDeclaration(nonstd::get(cNode), childOffsets.takeFirst()); + else if(nonstd::holds_alternative(cNode)) + writeNodeClassPropertiesDeclarations(nonstd::get(cNode), childOffsets); + else + Q_UNREACHABLE(); + } +} + +void QmlSettingsGenerator::writeNodePropertyDeclaration(const SettingsGeneratorBase::NodeType &entry, int classIndex, const QString &overwriteName) +{ + const auto &mName = overwriteName.isNull() ? entry.key : overwriteName; + _hdr << "\tQ_PROPERTY(" << _name << "_" << classIndex << "* " << mName + << " MEMBER _" << mName << " CONSTANT)\n"; + + _hdr << "\t" << _name << "_" << classIndex << "* _" << mName << ";\n\n"; +} + +void QmlSettingsGenerator::writeEntryPropertyDeclaration(const SettingsGeneratorBase::EntryType &entry, int classIndex) +{ + const auto &mType = _typeMappings.value(entry.type, entry.type); + if(mType != QStringLiteral("void")) { + _hdr << "\tQ_PROPERTY(" << mType << " " << entry.key + << " READ get_" << entry.key + << " WRITE set_" << entry.key + << " NOTIFY notify_" << entry.key << ")\n"; + + _hdr << "\t" << mType << " get_" << entry.key << "() const;\n" + << "\tvoid set_" << entry.key << "(const " << mType << "& value);\n" + << "Q_SIGNALS:\n" + << "\tvoid notify_" << entry.key << "();\n" + << "private:\n\n"; + } + + if(!entry.contentNodes.isEmpty()) + writeNodePropertyDeclaration(entry, classIndex, entry.qmlGroupKey.value_or(entry.key + QStringLiteral("Group"))); +} + +int QmlSettingsGenerator::writeNodeClassDeclaration(const SettingsGeneratorBase::NodeType &node, int offset) +{ + QList childOffsets; + std::tie(offset, childOffsets) = writeNodeContentClassesDeclarations(node, offset); + + _hdr << "class " << _prefixName << "_" << offset << " : public QObject // " << node.key << "\n" + << "{\n" + << "\tQ_OBJECT\n\n"; + + _hdr << "public:\n" + << "\t" << _name << "_" << offset << "(QtMvvm::ISettingsAccessor *accessor, const QString &parentKey, QObject *parent);\n\n" + << "private:\n" + << "\tQtMvvm::ISettingsAccessor *_accessor;\n" + << "\tQString _nodeKey;\n\n"; + + writeNodeClassPropertiesDeclarations(node, childOffsets); + + _hdr << "};\n\n"; + + return ++offset; +} + void QmlSettingsGenerator::writeSource(const SettingsGeneratorBase::SettingsType &settings) { + _src << "#include \"" << _hdrFile.fileName() << "\"\n\n"; + _src << "QtMvvm::ISettingsAccessor *" << settings.name.value() << "::accessor() const\n" + << "{\n" + << "\treturn _accessor;\n" + << "}\n\n"; } diff --git a/tools/settingsgenerator/qmlsettingsgenerator.h b/tools/settingsgenerator/qmlsettingsgenerator.h index 15dae40..0e878ec 100644 --- a/tools/settingsgenerator/qmlsettingsgenerator.h +++ b/tools/settingsgenerator/qmlsettingsgenerator.h @@ -2,6 +2,7 @@ #define QMLSETTINGSGENERATOR_H #include "settingsgeneratorimpl.h" +#include class QmlSettingsGenerator : public SettingsGeneratorImpl { @@ -11,15 +12,27 @@ public: void process(const QString &inPath); - void writeHeader(const SettingsType &settings); - void writeSource(const SettingsType &settings); - private: QFile _hdrFile; QFile _srcFile; QTextStream _hdr; QTextStream _src; + + QString _name; + QString _prefixName; + QHash _typeMappings; + + void writeHeader(const SettingsType &settings, const QString &inHdrPath); + + void writeListTypeClasses(const SettingsType &settings); + std::tuple> writeNodeContentClassesDeclarations(const NodeContentGroup &node, int offset = 0); + void writeNodeClassPropertiesDeclarations(const NodeContentGroup &node, QList &childOffsets); + void writeNodePropertyDeclaration(const NodeType &entry, int classIndex, const QString &overwriteName = {}); + void writeEntryPropertyDeclaration(const EntryType &entry, int classIndex); + int writeNodeClassDeclaration(const NodeType &node, int offset); + + void writeSource(const SettingsType &settings); }; #endif // QMLSETTINGSGENERATOR_H diff --git a/tools/settingsgenerator/qsettingsgenerator.xsd b/tools/settingsgenerator/qsettingsgenerator.xsd index e638652..d0ce70c 100644 --- a/tools/settingsgenerator/qsettingsgenerator.xsd +++ b/tools/settingsgenerator/qsettingsgenerator.xsd @@ -19,6 +19,15 @@ + + + + + + + + + @@ -93,7 +102,6 @@ - @@ -102,6 +110,7 @@ +