From 5cbcd454b2257a0f9c777b3502ed397f8901a458 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Thu, 19 Sep 2024 15:35:51 +0200 Subject: [PATCH] DesignSystem: R/W design system module from the library Task-number: QDS-13713 Change-Id: Ibc13272ac1e0b26352b84e74216b2fbfcd69ff4e Reviewed-by: Marco Bubke Reviewed-by: Thomas Hartmann --- .../libs/designsystem/CMakeLists.txt | 3 +- .../qmldesigner/libs/designsystem/dsstore.cpp | 265 ++++++++++++++++++ .../qmldesigner/libs/designsystem/dsstore.h | 48 ++++ .../libs/designsystem/dsthemegroup.cpp | 79 +++--- .../libs/designsystem/dsthemegroup.h | 14 +- .../libs/designsystem/dsthememanager.cpp | 159 +++++++++-- .../libs/designsystem/dsthememanager.h | 14 +- 7 files changed, 513 insertions(+), 69 deletions(-) create mode 100644 src/plugins/qmldesigner/libs/designsystem/dsstore.cpp create mode 100644 src/plugins/qmldesigner/libs/designsystem/dsstore.h diff --git a/src/plugins/qmldesigner/libs/designsystem/CMakeLists.txt b/src/plugins/qmldesigner/libs/designsystem/CMakeLists.txt index 05fde136802..b85e4a0dc22 100644 --- a/src/plugins/qmldesigner/libs/designsystem/CMakeLists.txt +++ b/src/plugins/qmldesigner/libs/designsystem/CMakeLists.txt @@ -1,9 +1,10 @@ add_qtc_library(DesignSystem STATIC PUBLIC_INCLUDES ${CMAKE_CURRENT_LIST_DIR} DEPENDS - Qt::Core Qt::Widgets QmlDesignerCore + Qt::Core Qt::Widgets QmlDesignerCore TextEditorSupport SOURCES dsconstants.h + dsstore.h dsstore.cpp dsthemegroup.h dsthemegroup.cpp dsthememanager.h dsthememanager.cpp ) diff --git a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp new file mode 100644 index 00000000000..26ab0f9001c --- /dev/null +++ b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp @@ -0,0 +1,265 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "dsstore.h" +#include "dsthememanager.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace { + +constexpr char DesignModuleName[] = "DesignSystem"; + +QString capitalize(QStringView str) +{ + if (str.isEmpty()) + return QString(); + QString tmp = str.toString(); + tmp[0] = str[0].toUpper(); + return tmp; +} + +std::optional dsModuleDir(QmlDesigner::ExternalDependenciesInterface &ed) +{ + auto componentsPath = QmlDesigner::GeneratedComponentUtils(ed).generatedComponentsPath(); + if (componentsPath.exists()) + return componentsPath.pathAppended(DesignModuleName); + + return {}; +} + +static QByteArray reformatQml(const QString &content) +{ + auto document = QmlJS::Document::create({}, QmlJS::Dialect::QmlQtQuick2); + document->setSource(content); + document->parseQml(); + if (document->isParsedCorrectly()) + return QmlJS::reformat(document).toUtf8(); + + return content.toUtf8(); +} + +std::optional modelSerializeHelper(QmlDesigner::ExternalDependenciesInterface &ed, + std::function callback, + const Utils::FilePath &targetDir, + const QString &typeName, + bool isSingelton = false) +{ + QString qmlText{"import QtQuick\nQtObject {}\n"}; + if (isSingelton) + qmlText.prepend("pragma Singleton\n"); + + QmlDesigner::ModelPointer model(QmlDesigner::Model::create("QtObject")); + QPlainTextEdit editor; + editor.setPlainText(qmlText); + QmlDesigner::NotIndentingTextEditModifier modifier(&editor); + QmlDesigner::RewriterView view(ed, QmlDesigner::RewriterView::Validate); + view.setPossibleImportsEnabled(false); + view.setCheckSemanticErrors(false); + view.setTextModifier(&modifier); + model->attachView(&view); + + try { + callback(model.get()); + } catch (const QmlDesigner::RewritingException &e) { + return e.description(); + } + + Utils::FileSaver saver(targetDir / (typeName + ".qml"), QIODevice::Text); + saver.write(reformatQml(modifier.text())); + if (!saver.finalize()) + return saver.errorString(); + + return {}; +} + +} // namespace + +namespace QmlDesigner { + +DSStore::DSStore(ExternalDependenciesInterface &ed) + : m_ed(ed) +{} + +DSStore::~DSStore() {} + +QString DSStore::moduleImportStr() const +{ + auto prefix = GeneratedComponentUtils(m_ed).generatedComponentTypePrefix(); + if (!prefix.isEmpty()) + return QString("%1.%2").arg(prefix).arg(DesignModuleName); + + return DesignModuleName; +} + +std::optional DSStore::load() +{ + if (auto moduleDir = dsModuleDir(m_ed)) + return load(*moduleDir); + + return tr("Can not locate design system module"); +} + +std::optional DSStore::load(const Utils::FilePath &dsModuleDirPath) +{ + // read qmldir + const auto qmldirFile = dsModuleDirPath / "qmldir"; + const Utils::expected_str contents = qmldirFile.fileContents(); + if (!contents) + return tr("Can not read Design System qmldir"); + + m_collectionTypeNames.clear(); + m_collections.clear(); + + // Parse qmldir + QString qmldirData = QString::fromUtf8(*contents); + QmlDirParser qmlDirParser; + qmlDirParser.parse(qmldirData); + + // load collections + QStringList collectionErrors; + auto addCollectionErr = [&collectionErrors](const QString &name, const QString &e) { + collectionErrors << QString("Error loading collection %1. %2").arg(name, e); + }; + for (auto component : qmlDirParser.components()) { + if (!component.fileName.isEmpty()) { + const auto collectionPath = dsModuleDirPath.pathAppended(component.fileName); + if (auto err = loadCollection(component.typeName, collectionPath)) + addCollectionErr(component.typeName, *err); + } else { + addCollectionErr(component.typeName, tr("Can not find component file.")); + } + } + + if (!collectionErrors.isEmpty()) + return collectionErrors.join("\n"); + + return {}; +} + +std::optional DSStore::save(bool mcuCompatible) const +{ + if (auto moduleDir = dsModuleDir(m_ed)) + return save(*moduleDir, mcuCompatible); + + return tr("Can not locate design system module"); +} + +std::optional DSStore::save(const Utils::FilePath &moduleDirPath, bool mcuCompatible) const +{ + if (!QDir().mkpath(moduleDirPath.absoluteFilePath().toString())) + return tr("Can not create design system module directory %1.").arg(moduleDirPath.toString()); + + // dump collections + QStringList singletons; + QStringList errors; + for (auto &[typeName, collection] : m_collections) { + if (auto err = writeQml(collection, typeName, moduleDirPath, mcuCompatible)) + errors << *err; + singletons << QString("singleton %1 1.0 %1.qml").arg(typeName); + } + + // Write qmldir + Utils::FileSaver saver(moduleDirPath / "qmldir", QIODevice::Text); + const QString qmldirContents = QString("Module %1\n%2").arg(moduleImportStr(), singletons.join("\n")); + saver.write(qmldirContents.toUtf8()); + if (!saver.finalize()) + errors << tr("Can not write design system qmldir. %1").arg(saver.errorString()); + + if (!errors.isEmpty()) + return errors.join("\n"); + + return {}; +} + +DSThemeManager *DSStore::addCollection(const QString &qmlTypeName) +{ + const QString uniqueTypeName = UniqueName::generateId(qmlTypeName, + "designSystem", + [this](const QString &t) { + return m_collections.contains(t); + }); + + const QString componentType = capitalize(uniqueTypeName); + auto [itr, success] = m_collections.try_emplace(componentType, DSThemeManager{}); + if (success) { + m_collectionTypeNames.insert({&itr->second, itr->first}); + return &itr->second; + } + return nullptr; +} + +std::optional DSStore::typeName(DSThemeManager *collection) const +{ + auto itr = m_collectionTypeNames.find(collection); + if (itr != m_collectionTypeNames.end()) + return itr->second; + + return {}; +} + +std::optional DSStore::loadCollection(const QString &typeName, + const Utils::FilePath &qmlFilePath) +{ + Utils::FileReader reader; + if (!reader.fetch(qmlFilePath, QFile::Text)) + return reader.errorString(); + + ModelPointer model(QmlDesigner::Model::create("QtObject")); + + QPlainTextEdit editor; + QString qmlContent = QString::fromUtf8(reader.data()); + editor.setPlainText(qmlContent); + + QmlDesigner::NotIndentingTextEditModifier modifier(&editor); + RewriterView view(m_ed, QmlDesigner::RewriterView::Validate); + // QDS-8366 + view.setPossibleImportsEnabled(false); + view.setCheckSemanticErrors(false); + view.setTextModifier(&modifier); + model->attachView(&view); + + if (auto dsMgr = addCollection(typeName)) + return dsMgr->load(model->rootModelNode()); + + return {}; +} + +std::optional DSStore::writeQml(const DSThemeManager &mgr, + const QString &typeName, + const Utils::FilePath &targetDir, + bool mcuCompatible) const +{ + if (mgr.themeCount() == 0) + return {}; + + const QString themeInterfaceType = mcuCompatible ? QString("%1Theme").arg(typeName) : "QtObject"; + if (mcuCompatible) { + auto decorateInterface = [&mgr](Model *interfaceModel) { + mgr.decorateThemeInterface(interfaceModel->rootModelNode()); + }; + + if (auto error = modelSerializeHelper(m_ed, decorateInterface, targetDir, themeInterfaceType)) + return tr("Can not write theme interface %1.\n%2").arg(themeInterfaceType, *error); + } + + auto decorateCollection = [&](Model *collectionModel) { + mgr.decorate(collectionModel->rootModelNode(), themeInterfaceType.toUtf8(), mcuCompatible); + }; + + if (auto error = modelSerializeHelper(m_ed, decorateCollection, targetDir, typeName, true)) + return tr("Can not write collection %1.\n%2").arg(typeName, *error); + + return {}; +} +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designsystem/dsstore.h b/src/plugins/qmldesigner/libs/designsystem/dsstore.h new file mode 100644 index 00000000000..585a38c39e0 --- /dev/null +++ b/src/plugins/qmldesigner/libs/designsystem/dsstore.h @@ -0,0 +1,48 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#pragma once + +#include +#include + +namespace QmlDesigner { +class DSThemeManager; +class ExternalDependenciesInterface; + +using DSCollections = std::map; + +class DSStore +{ + Q_DECLARE_TR_FUNCTIONS(DSStore); + +public: + DSStore(ExternalDependenciesInterface &ed); + ~DSStore(); + + QString moduleImportStr() const; + + std::optional load(); + std::optional load(const Utils::FilePath &dsModuleDirPath); + + std::optional save(bool mcuCompatible = false) const; + std::optional save(const Utils::FilePath &moduleDirPath, bool mcuCompatible = false) const; + + size_t collectionCount() const { return m_collections.size(); } + + DSThemeManager *addCollection(const QString &qmlTypeName); + std::optional typeName(DSThemeManager *collection) const; + +private: + std::optional loadCollection(const QString &typeName, const Utils::FilePath &qmlFilePath); + std::optional writeQml(const DSThemeManager &mgr, + const QString &typeName, + const Utils::FilePath &targetDir, + bool mcuCompatible) const; + +private: + ExternalDependenciesInterface &m_ed; + DSCollections m_collections; + std::map m_collectionTypeNames; +}; +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.cpp b/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.cpp index ec55d234a91..aa867a13e19 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.cpp +++ b/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.cpp @@ -3,10 +3,12 @@ #include "dsthemegroup.h" +#include #include +#include #include -#include #include +#include #include #include @@ -142,64 +144,73 @@ void DSThemeGroup::removeTheme(ThemeId theme) void DSThemeGroup::duplicateValues(ThemeId from, ThemeId to) { - for (auto itr = m_values.begin(); itr != m_values.end(); ++itr) { - auto &[propName, values] = *itr; - auto fromValueItr = values.find(from); + for (auto &[propName, values] : m_values) { + ThemeValues::iterator fromValueItr = values.find(from); if (fromValueItr != values.end()) values[to] = fromValueItr->second; } } -void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode, DECORATION_CONTEXT decorationContext) +void DSThemeGroup::decorate(ThemeId theme, ModelNode themeNode, bool wrapInGroups) { if (!count(theme)) return; // No props for this theme in this group. - ModelNode *targetNode = &themeNode; + ModelNode targetNode = themeNode; const auto typeName = groupTypeName(m_type); - if (decorationContext == DECORATION_CONTEXT::MPU) { + if (wrapInGroups) { // Create a group node const auto groupName = GroupId(m_type); auto groupNode = themeNode.model()->createModelNode("QtObject"); - auto groupProperty = themeNode.nodeProperty(groupName); + NodeProperty groupProperty = themeNode.nodeProperty(groupName); if (!groupProperty || !typeName || !groupNode) { qCDebug(dsLog) << "Adding group node failed." << groupName << theme; return; } groupProperty.setDynamicTypeNameAndsetModelNode("QtObject", groupNode); - targetNode = &groupNode; + targetNode = groupNode; } // Add properties - for (auto itr = m_values.begin(); itr != m_values.end(); ++itr) { - auto &[propName, values] = *itr; + for (auto &[propName, values] : m_values) { auto themeValue = values.find(theme); - if (themeValue != values.end()) { - auto &propData = themeValue->second; - if (propData.isBinding) { - auto bindingProp = targetNode->bindingProperty(propName); - if (!bindingProp) - continue; - - if (decorationContext == DECORATION_CONTEXT::MCU) - bindingProp.setExpression(propData.value.toString()); - else - bindingProp.setDynamicTypeNameAndExpression(*typeName, propData.value.toString()); - - } else { - auto nodeProp = targetNode->variantProperty(propName); - if (!nodeProp) - continue; - - if (decorationContext == DECORATION_CONTEXT::MCU) - nodeProp.setValue(propData.value); - else - nodeProp.setDynamicTypeNameAndValue(*typeName, propData.value); - } - } + if (themeValue != values.end()) + addProperty(targetNode, propName, themeValue->second); } +} +void DSThemeGroup::decorateComponent(ModelNode node) +{ + const auto typeName = groupTypeName(m_type); + // Add properties with type to the node + for (auto &[propName, values] : m_values) { + auto nodeProp = node.variantProperty(propName); + nodeProp.setDynamicTypeNameAndValue(*typeName, nodeProp.value()); + } +} + +void DSThemeGroup::addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const +{ + auto metaInfo = n.model()->metaInfo(n.type()); + const bool propDefined = metaInfo.property(propName).isValid(); + + const auto typeName = groupTypeName(m_type); + if (data.isBinding) { + if (propDefined) + n.bindingProperty(propName).setExpression(data.value.toString()); + else if (auto bindingProp = n.bindingProperty(propName)) + bindingProp.setDynamicTypeNameAndExpression(*typeName, data.value.toString()); + else + qCDebug(dsLog) << "Assigning invalid binding" << propName << n.id(); + } else { + if (propDefined) + n.variantProperty(propName).setValue(data.value); + else if (auto nodeProp = n.variantProperty(propName)) + nodeProp.setDynamicTypeNameAndValue(*typeName, data.value); + else + qCDebug(dsLog) << "Assigning invalid variant property" << propName << n.id(); + } } } diff --git a/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.h b/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.h index e61a915623e..d745da080ed 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.h +++ b/src/plugins/qmldesigner/libs/designsystem/dsthemegroup.h @@ -12,12 +12,6 @@ #include namespace QmlDesigner { -enum class DECORATION_CONTEXT { - MCU, - MPU, - COMPONENT_THEME, -}; - class DESIGNSYSTEM_EXPORT DSThemeGroup { struct PropertyData @@ -46,8 +40,6 @@ public: void updateProperty(ThemeId theme, PropertyName newName, const ThemeProperty &prop); void removeProperty(const PropertyName &name); - GroupType type() const { return m_type; } - size_t count(ThemeId theme) const; size_t count() const; bool isEmpty() const; @@ -55,7 +47,11 @@ public: void removeTheme(ThemeId theme); void duplicateValues(ThemeId from, ThemeId to); - void decorate(ThemeId theme, ModelNode themeNode, DECORATION_CONTEXT decorationContext); + void decorate(ThemeId theme, ModelNode themeNode, bool wrapInGroups = true); + void decorateComponent(ModelNode node); + +private: + void addProperty(ModelNode n, PropertyNameView propName, const PropertyData &data) const; private: const GroupType m_type; diff --git a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp index 8e6513392d4..d86174902bd 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp +++ b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp @@ -5,21 +5,39 @@ #include "dsconstants.h" #include "dsthemegroup.h" +#include "variantproperty.h" -#include #include +#include #include #include #include +#include + namespace { Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg) + +std::optional typeToGroupType(const QmlDesigner::TypeName type) +{ + if (type == "color") + return QmlDesigner::GroupType::Colors; + if (type == "bool") + return QmlDesigner::GroupType::Flags; + if (type == "real") + return QmlDesigner::GroupType::Numbers; + if (type == "string") + return QmlDesigner::GroupType::Strings; + + return {}; +} } namespace QmlDesigner { -DSThemeManager::DSThemeManager() {} +DSThemeManager::DSThemeManager() +{} DSThemeManager::~DSThemeManager() {} @@ -31,7 +49,8 @@ std::optional DSThemeManager::addTheme(const ThemeName &themeName) } const ThemeId newThemeId = m_themes.empty() ? 1 : m_themes.rbegin()->first + 1; - m_themes.insert({newThemeId, themeName}); + if (!m_themes.try_emplace(newThemeId, themeName).second) + return {}; // Copy the new theme properties from an old theme(first one). if (m_themes.size() > 1) @@ -59,16 +78,16 @@ void DSThemeManager::removeTheme(ThemeId id) if (!m_themes.contains(id)) return; - for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) - groupItr->second->removeTheme(id); + for (auto &[gt, group] : m_groups) + group->removeTheme(id); m_themes.erase(id); } void DSThemeManager::duplicateTheme(ThemeId from, ThemeId to) { - for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) - groupItr->second->duplicateValues(from, to); + for (auto &[gt, group] : m_groups) + group->duplicateValues(from, to); } std::optional DSThemeManager::property(ThemeId themeId, @@ -88,7 +107,7 @@ std::optional DSThemeManager::property(ThemeId themeId, bool DSThemeManager::addProperty(GroupType gType, const ThemeProperty &p) { if (!m_themes.size()) { - qCDebug(dsLog) << "Can not add proprty. Themes empty"; + qCDebug(dsLog) << "Can not add property. Themes empty"; return false; } @@ -141,44 +160,41 @@ void DSThemeManager::decorate(ModelNode rootNode, const QByteArray &nodeType, bo addGroupAliases(rootNode); auto model = rootNode.model(); - for (auto itr = m_themes.begin(); itr != m_themes.end(); ++itr) { + for (auto &[themeId, themeName] : m_themes) { auto themeNode = model->createModelNode(nodeType); - auto themeProperty = model->rootModelNode().nodeProperty(itr->second); + auto themeProperty = model->rootModelNode().nodeProperty(themeName); themeProperty.setDynamicTypeNameAndsetModelNode(nodeType, themeNode); // Add property groups - for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) - groupItr->second->decorate(itr->first, themeNode, isMCU ? DECORATION_CONTEXT::MCU : DECORATION_CONTEXT::MPU); + for (auto &[gt, group] : m_groups) + group->decorate(themeId, themeNode, !isMCU); } } -void DSThemeManager::decorateThemeComponent(ModelNode rootNode) const +void DSThemeManager::decorateThemeInterface(ModelNode rootNode) const { if (!m_themes.size()) return; - auto itr = m_themes.begin(); - for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) - groupItr->second->decorate(itr->first, rootNode, DECORATION_CONTEXT::COMPONENT_THEME); + for (auto &[gt, group] : m_groups) + group->decorateComponent(rootNode); } DSThemeGroup *DSThemeManager::propertyGroup(GroupType type) { auto itr = m_groups.find(type); if (itr == m_groups.end()) - itr = m_groups.insert({type, std::make_unique(type)}).first; + itr = m_groups.try_emplace(type, std::make_unique(type)).first; return itr->second.get(); } void DSThemeManager::addGroupAliases(ModelNode rootNode) const { - QSet groupNames; - for (auto groupItr = m_groups.begin(); groupItr != m_groups.end(); ++groupItr) { - DSThemeGroup *group = groupItr->second.get(); - const PropertyName groupName = GroupId(group->type()); + std::set groupNames; + for (auto &[groupType, group] : m_groups) { if (group->count()) - groupNames.insert(groupName); + groupNames.emplace(GroupId(groupType)); } for (const auto &name : groupNames) { @@ -187,4 +203,103 @@ void DSThemeManager::addGroupAliases(ModelNode rootNode) const p.setDynamicTypeNameAndExpression("QtObject", binding); } } + +std::optional DSThemeManager::load(ModelNode rootModelNode) +{ + // We need all properties under the theme node and its child nodes. + // The properties must have a unique name. + auto propWithSameName = [](const AbstractProperty &p1, const AbstractProperty &p2) { + return p1.name() == p2.name(); + }; + using PropMap = std::set; + using ThemeProps = std::map; + auto getAllProps = [](const ModelNode &n) -> PropMap { + PropMap props; + auto nodesUnderTheme = n.allSubModelNodesAndThisNode(); + for (auto &n : nodesUnderTheme) { + for (const AbstractProperty &p : n.properties()) { + if (!props.insert(p).second) + qCDebug(dsLog) << "Duplicate Property, Skipping" << n << p; + } + } + return props; + }; + + // First level child nodes are assumed to be the theme nodes. + QList themes = rootModelNode.nodeProperties(); + if (themes.isEmpty()) + return tr("No themes objects in the collection."); + + // Collect properties under each theme node. + ThemeProps themeProps; + for (auto &themeNodeProp : themes) { + ModelNode themeNode = themeNodeProp.modelNode(); + if (auto themeId = addTheme(themeNodeProp.name().toByteArray())) + themeProps.insert({*themeId, getAllProps(themeNode)}); + } + + // Get properties from the first theme. We expect other theme nodes + // have prpperties with same name and type. If not we don't consider those properties. + auto themeItr = themeProps.begin(); + // Add default properties + const PropMap &baseProps = themeItr->second; + for (const AbstractProperty &baseNodeProp : baseProps) { + GroupType basePropGroupType; + ThemeProperty basethemeProp; + if (findPropertyType(baseNodeProp, &basethemeProp, &basePropGroupType)) + addProperty(basePropGroupType, basethemeProp); + else + continue; + + // Update values for rest of the themes. + for (auto otherTheme = std::next(themeItr); otherTheme != themeProps.end(); ++otherTheme) { + const PropMap &otherThemeProps = otherTheme->second; + auto otherThemePropItr = otherThemeProps.find(baseNodeProp); + if (otherThemePropItr == otherThemeProps.end()) { + qCDebug(dsLog) << "Can't find expected prop" << baseNodeProp.name() << "in theme" + << otherTheme->first; + continue; + } + + GroupType otherGroup; + ThemeProperty otherThemeProp; + if (findPropertyType(*otherThemePropItr, &otherThemeProp, &otherGroup) + && otherGroup == basePropGroupType) { + updateProperty(otherTheme->first, basePropGroupType, otherThemeProp); + } else { + qCDebug(dsLog) << "Incompatible property" << baseNodeProp.name() + << " found in theme" << otherTheme->first; + } + } + } + + return {}; +} + +bool DSThemeManager::findPropertyType(const AbstractProperty &p, + ThemeProperty *themeProp, + GroupType *gt) const +{ + auto group = typeToGroupType(p.dynamicTypeName()); + if (!group) { + qCDebug(dsLog) << "Can't find suitable group for the property" << p.name(); + return false; + } + + *gt = *group; + PropertyName pName = p.name().toByteArray(); + if (auto variantProp = p.toVariantProperty()) { + themeProp->value = variantProp.value(); + themeProp->isBinding = false; + } else if (auto binding = p.toBindingProperty()) { + themeProp->value = binding.expression(); + themeProp->isBinding = true; + } else { + qCDebug(dsLog) << "Property type not supported for design system" << pName; + return false; + } + + themeProp->name = pName; + return true; +} } diff --git a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h index 0972dcd7893..9028af1c5a6 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h +++ b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h @@ -8,8 +8,11 @@ #include "dsconstants.h" #include "dsthemegroup.h" +#include #include +#include + namespace QmlDesigner { using ThemeName = PropertyName; @@ -18,6 +21,7 @@ class DSTheme; class DESIGNSYSTEM_EXPORT DSThemeManager { + Q_DECLARE_TR_FUNCTIONS(DSThemeManager); public: DSThemeManager(); @@ -27,7 +31,7 @@ public: DSThemeManager& operator=(const DSThemeManager&) = delete; DSThemeManager(DSThemeManager&&) = default; - DSThemeManager& operator=(DSThemeManager&&) = default; + DSThemeManager &operator=(DSThemeManager &&) = default; std::optional addTheme(const ThemeName &themeName); std::optional themeId(const ThemeName &themeName) const; @@ -44,13 +48,17 @@ public: void updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p); void updateProperty(ThemeId id, GroupType gType, const ThemeProperty &p, const PropertyName &newName); - void decorate(ModelNode rootNode, const QByteArray& nodeType, bool isMCU) const; - void decorateThemeComponent(ModelNode rootNode) const; + void decorate(ModelNode rootNode, const QByteArray &nodeType = "QtObject", bool isMCU = false) const; + void decorateThemeInterface(ModelNode rootNode) const; + + std::optional load(ModelNode rootModelNode); private: DSThemeGroup *propertyGroup(GroupType type); void addGroupAliases(ModelNode rootNode) const; + bool findPropertyType(const AbstractProperty &p, ThemeProperty *themeProp, GroupType *gt) const; + private: std::map m_themes; std::map> m_groups;