DesignSystem: Refactor design system bindings

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 <henning.gruendl@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Vikas Pachdha
2025-03-21 12:45:08 +01:00
committed by Thomas Hartmann
parent 0c6f18ac2a
commit df486a809f
6 changed files with 164 additions and 13 deletions

View File

@@ -63,7 +63,9 @@ QVariant CollectionModel::data(const QModelIndex &index, int role) const
const QVariant propertyValue = property->value.toString(); const QVariant propertyValue = property->value.toString();
const QVariant displayValue = property->isBinding const QVariant displayValue = property->isBinding
? m_store->resolvedDSBinding(propertyValue.toString()).value ? m_store->resolvedDSBinding(propertyValue.toString())
.value_or(ThemeProperty{})
.value
: property->value; : property->value;
switch (role) { switch (role) {
@@ -193,6 +195,7 @@ bool CollectionModel::removeRows(int row, int count, const QModelIndex &parent)
beginResetModel(); beginResetModel();
while (row < sentinelIndex) { while (row < sentinelIndex) {
auto [groupType, name] = m_propertyInfoList[row++]; auto [groupType, name] = m_propertyInfoList[row++];
m_store->breakBindings(m_collection, name);
m_collection->removeProperty(groupType, name); m_collection->removeProperty(groupType, name);
} }
updateCache(); updateCache();
@@ -235,7 +238,7 @@ bool CollectionModel::setData(const QModelIndex &index, const QVariant &value, i
switch (role) { switch (role) {
case Qt::EditRole: { case Qt::EditRole: {
if (p.isBinding) { 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. 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)) { if (auto propInfo = findPropertyName(section)) {
auto [groupType, propName] = *propInfo; auto [groupType, propName] = *propInfo;
success = m_collection->renameProperty(groupType, propName, newName); 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 { } else {
// Theme // Theme

View File

@@ -8,6 +8,8 @@
#include <QVariant> #include <QVariant>
#include <tuple>
namespace QmlDesigner namespace QmlDesigner
{ {
@@ -35,4 +37,5 @@ constexpr const char *GroupId(const GroupType type) {
return "unknown"; return "unknown";
} }
using DSBindingInfo = std::tuple<PropertyName, ThemeId, GroupType, QStringView>;
} }

View File

@@ -19,7 +19,7 @@
#include <QScopeGuard> #include <QScopeGuard>
namespace { namespace {
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
constexpr char DesignModuleName[] = "DesignSystem"; constexpr char DesignModuleName[] = "DesignSystem";
std::optional<Utils::FilePath> dsModuleDir(QmlDesigner::ExternalDependenciesInterface &ed) std::optional<Utils::FilePath> dsModuleDir(QmlDesigner::ExternalDependenciesInterface &ed)
@@ -83,6 +83,15 @@ std::optional<QString> modelSerializeHelper(
return {}; return {};
} }
std::optional<std::tuple<QStringView, QStringView, QStringView>> 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
namespace QmlDesigner { namespace QmlDesigner {
@@ -214,7 +223,16 @@ std::optional<QString> DSStore::typeName(DSThemeManager *collection) const
bool DSStore::removeCollection(const QString &name) bool DSStore::removeCollection(const QString &name)
{ {
if (auto toRemove = collection(name)) {
for (auto &[_, currentCollection] : m_collections) {
if (toRemove == &currentCollection)
continue;
breakBindings(&currentCollection, name);
}
save();
return m_collections.erase(name); return m_collections.erase(name);
}
return false;
} }
bool DSStore::renameCollection(const QString &oldName, const QString &newName) 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); auto handle = m_collections.extract(oldName);
handle.key() = uniqueTypeName; handle.key() = uniqueTypeName;
m_collections.insert(std::move(handle)); m_collections.insert(std::move(handle));
refactorBindings(oldName, uniqueTypeName);
save();
return true; return true;
} }
@@ -251,21 +272,29 @@ QStringList DSStore::collectionNames() const
return names; return names;
} }
ThemeProperty DSStore::resolvedDSBinding(QStringView binding) const std::optional<ThemeProperty> DSStore::resolvedDSBinding(QStringView binding) const
{ {
const auto parts = binding.split('.', Qt::SkipEmptyParts); if (auto parts = unpackDSBinding(binding)) {
if (parts.size() != 3) auto &[collectionName, _, propertyName] = *parts;
return {}; return resolvedDSBinding(collectionName, propertyName);
}
const auto &collectionName = parts[0]; qCDebug(dsLog) << "Resolving binding failed. Unexpected binding" << binding;
return {};
}
std::optional<ThemeProperty> DSStore::resolvedDSBinding(QStringView collectionName,
QStringView propertyName) const
{
auto itr = m_collections.find(collectionName.toString()); auto itr = m_collections.find(collectionName.toString());
if (itr == m_collections.end()) if (itr == m_collections.end())
return {}; return {};
const DSThemeManager &boundCollection = itr->second; const DSThemeManager &boundCollection = itr->second;
const auto &propertyName = parts[2].toLatin1(); if (const auto group = boundCollection.groupType(propertyName.toLatin1())) {
if (const auto group = boundCollection.groupType(propertyName)) { auto property = boundCollection.property(boundCollection.activeTheme(),
auto property = boundCollection.property(boundCollection.activeTheme(), *group, propertyName); *group,
propertyName.toLatin1());
if (property) if (property)
return property->isBinding ? resolvedDSBinding(property->value.toString()) : *property; return property->isBinding ? resolvedDSBinding(property->value.toString()) : *property;
} }
@@ -273,6 +302,92 @@ ThemeProperty DSStore::resolvedDSBinding(QStringView binding) const
return {}; 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 QString DSStore::uniqueCollectionName(const QString &hint) const
{ {
return UniqueName::generateTypeName(hint, "Collection", [this](const QString &t) { return UniqueName::generateTypeName(hint, "Collection", [this](const QString &t) {

View File

@@ -38,7 +38,15 @@ public:
std::optional<Utils::FilePath> moduleDirPath() const; std::optional<Utils::FilePath> moduleDirPath() const;
QStringList collectionNames() const; QStringList collectionNames() const;
ThemeProperty resolvedDSBinding(QStringView binding) const; std::optional<ThemeProperty> resolvedDSBinding(QStringView binding) const;
std::optional<ThemeProperty> 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; QString uniqueCollectionName(const QString &hint) const;

View File

@@ -357,6 +357,22 @@ std::optional<QString> DSThemeManager::load(ModelNode rootModelNode)
return {}; return {};
} }
std::vector<DSBindingInfo> DSThemeManager::boundProperties() const
{
std::vector<DSBindingInfo> 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, bool DSThemeManager::findPropertyType(const AbstractProperty &p,
ThemeProperty *themeProp, ThemeProperty *themeProp,
GroupType *gt) const GroupType *gt) const

View File

@@ -66,6 +66,8 @@ public:
std::optional<QString> load(ModelNode rootModelNode); std::optional<QString> load(ModelNode rootModelNode);
std::vector<DSBindingInfo> boundProperties() const;
private: private:
DSThemeGroup *propertyGroup(GroupType type); DSThemeGroup *propertyGroup(GroupType type);
void addGroupAliases(ModelNode rootNode) const; void addGroupAliases(ModelNode rootNode) const;