From df486a809f11c38efcf1a9b5a9fbe95481096369 Mon Sep 17 00:00:00 2001 From: Vikas Pachdha Date: Fri, 21 Mar 2025 12:45:08 +0100 Subject: [PATCH] DesignSystem: Refactor design system bindings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor binding on collection or property name change break bindings to resolved values on property or collection deletion Task-number: QDS-14670 Change-Id: I38a7bcbf32b5dfbd580ee0a44107926725fdce6a Reviewed-by: Henning Gründl Reviewed-by: Thomas Hartmann --- .../designsystemview/collectionmodel.cpp | 11 +- .../libs/designsystem/dsconstants.h | 3 + .../qmldesigner/libs/designsystem/dsstore.cpp | 135 ++++++++++++++++-- .../qmldesigner/libs/designsystem/dsstore.h | 10 +- .../libs/designsystem/dsthememanager.cpp | 16 +++ .../libs/designsystem/dsthememanager.h | 2 + 6 files changed, 164 insertions(+), 13 deletions(-) diff --git a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp index dcffa13631f..53026c6ea68 100644 --- a/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp +++ b/src/plugins/qmldesigner/components/designsystemview/collectionmodel.cpp @@ -63,7 +63,9 @@ QVariant CollectionModel::data(const QModelIndex &index, int role) const const QVariant propertyValue = property->value.toString(); const QVariant displayValue = property->isBinding - ? m_store->resolvedDSBinding(propertyValue.toString()).value + ? m_store->resolvedDSBinding(propertyValue.toString()) + .value_or(ThemeProperty{}) + .value : property->value; switch (role) { @@ -193,6 +195,7 @@ bool CollectionModel::removeRows(int row, int count, const QModelIndex &parent) beginResetModel(); while (row < sentinelIndex) { auto [groupType, name] = m_propertyInfoList[row++]; + m_store->breakBindings(m_collection, name); m_collection->removeProperty(groupType, name); } updateCache(); @@ -235,7 +238,7 @@ bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, i switch (role) { case Qt::EditRole: { if (p.isBinding) { - if (!m_store->resolvedDSBinding(p.value.toString()).isValid()) + if (!m_store->resolvedDSBinding(p.value.toString())) return false; // Invalid binding, it must resolved to a valid property. } @@ -280,6 +283,10 @@ bool CollectionModel::setHeaderData(int section, if (auto propInfo = findPropertyName(section)) { auto [groupType, propName] = *propInfo; success = m_collection->renameProperty(groupType, propName, newName); + if (success) { + const auto collectionName = m_store->typeName(m_collection); + m_store->refactorBindings(m_collection, propName, newName); + } } } else { // Theme diff --git a/src/plugins/qmldesigner/libs/designsystem/dsconstants.h b/src/plugins/qmldesigner/libs/designsystem/dsconstants.h index 95f6400535a..d67f570712c 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsconstants.h +++ b/src/plugins/qmldesigner/libs/designsystem/dsconstants.h @@ -8,6 +8,8 @@ #include +#include + namespace QmlDesigner { @@ -35,4 +37,5 @@ constexpr const char *GroupId(const GroupType type) { return "unknown"; } +using DSBindingInfo = std::tuple; } diff --git a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp index fafff6ea61f..a22536265c9 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp +++ b/src/plugins/qmldesigner/libs/designsystem/dsstore.cpp @@ -19,7 +19,7 @@ #include namespace { - +Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg) constexpr char DesignModuleName[] = "DesignSystem"; std::optional dsModuleDir(QmlDesigner::ExternalDependenciesInterface &ed) @@ -83,6 +83,15 @@ std::optional modelSerializeHelper( return {}; } +std::optional> unpackDSBinding(QStringView binding) +{ + const auto parts = binding.split('.', Qt::SkipEmptyParts); + if (parts.size() != 3) + return {}; + + return std::make_tuple(parts[0], parts[1], parts[2]); +} + } // namespace namespace QmlDesigner { @@ -214,7 +223,16 @@ std::optional DSStore::typeName(DSThemeManager *collection) const bool DSStore::removeCollection(const QString &name) { - return m_collections.erase(name); + if (auto toRemove = collection(name)) { + for (auto &[_, currentCollection] : m_collections) { + if (toRemove == ¤tCollection) + continue; + breakBindings(¤tCollection, name); + } + save(); + return m_collections.erase(name); + } + return false; } bool DSStore::renameCollection(const QString &oldName, const QString &newName) @@ -233,6 +251,9 @@ bool DSStore::renameCollection(const QString &oldName, const QString &newName) auto handle = m_collections.extract(oldName); handle.key() = uniqueTypeName; m_collections.insert(std::move(handle)); + + refactorBindings(oldName, uniqueTypeName); + save(); return true; } @@ -251,21 +272,29 @@ QStringList DSStore::collectionNames() const return names; } -ThemeProperty DSStore::resolvedDSBinding(QStringView binding) const +std::optional DSStore::resolvedDSBinding(QStringView binding) const { - const auto parts = binding.split('.', Qt::SkipEmptyParts); - if (parts.size() != 3) - return {}; + if (auto parts = unpackDSBinding(binding)) { + auto &[collectionName, _, propertyName] = *parts; + return resolvedDSBinding(collectionName, propertyName); + } - const auto &collectionName = parts[0]; + qCDebug(dsLog) << "Resolving binding failed. Unexpected binding" << binding; + return {}; +} + +std::optional DSStore::resolvedDSBinding(QStringView collectionName, + QStringView propertyName) const +{ auto itr = m_collections.find(collectionName.toString()); if (itr == m_collections.end()) return {}; const DSThemeManager &boundCollection = itr->second; - const auto &propertyName = parts[2].toLatin1(); - if (const auto group = boundCollection.groupType(propertyName)) { - auto property = boundCollection.property(boundCollection.activeTheme(), *group, propertyName); + if (const auto group = boundCollection.groupType(propertyName.toLatin1())) { + auto property = boundCollection.property(boundCollection.activeTheme(), + *group, + propertyName.toLatin1()); if (property) return property->isBinding ? resolvedDSBinding(property->value.toString()) : *property; } @@ -273,6 +302,92 @@ ThemeProperty DSStore::resolvedDSBinding(QStringView binding) const return {}; } +void DSStore::refactorBindings(QStringView oldCollectionName, QStringView newCollectionName) +{ + for (auto &[_, currentCollection] : m_collections) { + for (const auto &[propName, themeId, gt, expression] : currentCollection.boundProperties()) { + auto bindingParts = unpackDSBinding(expression); + if (!bindingParts) { + qCDebug(dsLog) << "Refactor binding error. Unexpected binding" << expression; + continue; + } + + const auto &[boundCollection, groupName, boundProp] = *bindingParts; + if (boundCollection != oldCollectionName) + continue; + + const auto newBinding = QString("%1.%2.%3").arg(newCollectionName, groupName, boundProp); + currentCollection.updateProperty(themeId, gt, {propName, newBinding, true}); + } + } +} + +void DSStore::refactorBindings(DSThemeManager *srcCollection, PropertyName from, PropertyName to) +{ + auto srcCollectionName = typeName(srcCollection); + if (!srcCollectionName) + return; + + for (auto &[_, currentCollection] : m_collections) { + for (const auto &[propName, themeId, gt, expression] : currentCollection.boundProperties()) { + auto bindingParts = unpackDSBinding(expression); + if (!bindingParts) { + qCDebug(dsLog) << "Refactor binding error. Unexpected binding" << expression; + continue; + } + + const auto &[boundCollection, groupName, boundProp] = *bindingParts; + if (boundCollection != srcCollectionName || from != boundProp.toLatin1()) + continue; + + const auto newBinding = QString("%1.%2.%3") + .arg(boundCollection, groupName, QString::fromUtf8(to)); + currentCollection.updateProperty(themeId, gt, {propName, newBinding, true}); + } + } +} + +void DSStore::breakBindings(DSThemeManager *collection, PropertyName propertyName) +{ + auto collectionName = typeName(collection); + if (!collectionName) + return; + + for (auto &[_, currentCollection] : m_collections) { + for (const auto &[propName, themeId, gt, expression] : currentCollection.boundProperties()) { + auto bindingParts = unpackDSBinding(expression); + if (!bindingParts) { + qCDebug(dsLog) << "Error breaking binding. Unexpected binding" << expression; + continue; + } + const auto &[boundCollection, _, boundProp] = *bindingParts; + if (boundCollection != collectionName || propertyName != boundProp.toLatin1()) + continue; + + if (auto value = resolvedDSBinding(*collectionName, boundProp)) + currentCollection.updateProperty(themeId, gt, {propName, value->value}); + } + } +} + +void DSStore::breakBindings(DSThemeManager *collection, QStringView removeCollection) +{ + for (const auto &[propName, themeId, gt, expression] : collection->boundProperties()) { + auto bindingParts = unpackDSBinding(expression); + if (!bindingParts) { + qCDebug(dsLog) << "Error breaking binding. Unexpected binding" << expression; + continue; + } + + const auto &[boundCollection, _, boundProp] = *bindingParts; + if (boundCollection != removeCollection) + continue; + + if (auto value = resolvedDSBinding(boundCollection, boundProp)) + collection->updateProperty(themeId, gt, {propName, value->value}); + } +} + QString DSStore::uniqueCollectionName(const QString &hint) const { return UniqueName::generateTypeName(hint, "Collection", [this](const QString &t) { diff --git a/src/plugins/qmldesigner/libs/designsystem/dsstore.h b/src/plugins/qmldesigner/libs/designsystem/dsstore.h index 7912dd4da3f..60c66e467dc 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsstore.h +++ b/src/plugins/qmldesigner/libs/designsystem/dsstore.h @@ -38,7 +38,15 @@ public: std::optional moduleDirPath() const; QStringList collectionNames() const; - ThemeProperty resolvedDSBinding(QStringView binding) const; + std::optional resolvedDSBinding(QStringView binding) const; + std::optional resolvedDSBinding(QStringView collectionName, + QStringView propertyName) const; + + void refactorBindings(QStringView oldCollectionName, QStringView newCollectionName); + void refactorBindings(DSThemeManager *srcCollection, PropertyName from, PropertyName to); + + void breakBindings(DSThemeManager *collection, PropertyName propertyName); + void breakBindings(DSThemeManager *collection, QStringView removeCollection); QString uniqueCollectionName(const QString &hint) const; diff --git a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp index d891ba7b153..375fa1d4c8f 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp +++ b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.cpp @@ -357,6 +357,22 @@ std::optional DSThemeManager::load(ModelNode rootModelNode) return {}; } +std::vector DSThemeManager::boundProperties() const +{ + std::vector bindings; + for (auto &[gt, group] : m_groups) { + for (auto &[id, _] : m_themes) { + for (const auto &propName : group.propertyNames()) { + if (auto p = group.propertyValue(id, propName)) { + if (p->isBinding) + bindings.push_back({propName, id, gt, p->value.toString()}); + } + } + } + } + return bindings; +} + bool DSThemeManager::findPropertyType(const AbstractProperty &p, ThemeProperty *themeProp, GroupType *gt) const diff --git a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h index 27556292a6d..7559c0cf68c 100644 --- a/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h +++ b/src/plugins/qmldesigner/libs/designsystem/dsthememanager.h @@ -66,6 +66,8 @@ public: std::optional load(ModelNode rootModelNode); + std::vector boundProperties() const; + private: DSThemeGroup *propertyGroup(GroupType type); void addGroupAliases(ModelNode rootNode) const;