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

View File

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

View File

@@ -19,7 +19,7 @@
#include <QScopeGuard>
namespace {
Q_LOGGING_CATEGORY(dsLog, "qtc.designer.designSystem", QtInfoMsg)
constexpr char DesignModuleName[] = "DesignSystem";
std::optional<Utils::FilePath> dsModuleDir(QmlDesigner::ExternalDependenciesInterface &ed)
@@ -83,6 +83,15 @@ std::optional<QString> modelSerializeHelper(
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 QmlDesigner {
@@ -214,7 +223,16 @@ std::optional<QString> DSStore::typeName(DSThemeManager *collection) const
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 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<ThemeProperty> 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<ThemeProperty> 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) {

View File

@@ -38,7 +38,15 @@ public:
std::optional<Utils::FilePath> moduleDirPath() 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;

View File

@@ -357,6 +357,22 @@ std::optional<QString> DSThemeManager::load(ModelNode rootModelNode)
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,
ThemeProperty *themeProp,
GroupType *gt) const

View File

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