QmlDesigner: Extract unique property name by collection name

- The property names of the data store are defined after converting
collection names to a proper unique property name.
- Now, Spaces are supported in the collection names.
- Collection property names will remain the same by collection
renames, and only the modelName will change

Task-number: QDS-11462
Change-Id: I2031c2e0a9afc5388386dc6e54c66e75f0d13ded
Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io>
Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
Reviewed-by: Shrief Gabr <shrief.gabr@qt.io>
This commit is contained in:
Ali Kianian
2023-12-01 12:42:58 +02:00
parent 1a07fa87fd
commit f41b69159b
11 changed files with 240 additions and 82 deletions

View File

@@ -120,7 +120,7 @@ StudioControls.Dialog {
actionIndicator.visible: false actionIndicator.visible: false
translationIndicator.visible: false translationIndicator.visible: false
validator: RegularExpressionValidator { validator: RegularExpressionValidator {
regularExpression: /^\w+$/ regularExpression: /^[\w ]+$/
} }
Keys.onEnterPressed: btnImport.onClicked() Keys.onEnterPressed: btnImport.onClicked()

View File

@@ -87,7 +87,7 @@ StudioControls.Dialog {
actionIndicator.visible: false actionIndicator.visible: false
translationIndicator.visible: false translationIndicator.visible: false
validator: RegularExpressionValidator { validator: RegularExpressionValidator {
regularExpression: /^\w+$/ regularExpression: /^[\w ]+$/
} }
Keys.onEnterPressed: btnCreate.onClicked() Keys.onEnterPressed: btnCreate.onClicked()

View File

@@ -3,11 +3,9 @@
#include "collectioneditorutils.h" #include "collectioneditorutils.h"
#include "abstractview.h" #include "model.h"
#include "bindingproperty.h"
#include "nodemetainfo.h" #include "nodemetainfo.h"
#include "propertymetainfo.h" #include "propertymetainfo.h"
#include "variantproperty.h"
#include <variant> #include <variant>
@@ -137,33 +135,6 @@ QString getSourceCollectionType(const ModelNode &node)
return {}; return {};
} }
void assignCollectionToNode(AbstractView *view,
const ModelNode &modelNode,
const ModelNode &collectionSourceNode,
const QString &collectionName)
{
QTC_ASSERT(modelNode.isValid() && collectionSourceNode.isValid(), return);
QString sourceId = isDataStoreNode(collectionSourceNode) ? "DataStore"
: collectionSourceNode.id();
if (sourceId.isEmpty() || !canAcceptCollectionAsModel(modelNode))
return;
VariantProperty sourceProperty = collectionSourceNode.variantProperty(collectionName.toLatin1());
if (!sourceProperty.exists())
return;
BindingProperty modelProperty = modelNode.bindingProperty("model");
QString identifier = QString("%1.%2").arg(sourceId, QString::fromLatin1(sourceProperty.name()));
view->executeInTransaction("CollectionEditor::assignCollectionToNode",
[&modelProperty, &identifier]() {
modelProperty.setExpression(identifier);
});
}
Utils::FilePath dataStoreJsonFilePath() Utils::FilePath dataStoreJsonFilePath()
{ {
return collectionPath("models.json"); return collectionPath("models.json");

View File

@@ -24,11 +24,6 @@ QString getSourceCollectionType(const QmlDesigner::ModelNode &node);
QString getSourceCollectionPath(const QmlDesigner::ModelNode &dataStoreNode); QString getSourceCollectionPath(const QmlDesigner::ModelNode &dataStoreNode);
void assignCollectionToNode(AbstractView *view,
const ModelNode &modelNode,
const ModelNode &collectionSourceNode,
const QString &collectionName);
Utils::FilePath dataStoreJsonFilePath(); Utils::FilePath dataStoreJsonFilePath();
Utils::FilePath dataStoreQmlFilePath(); Utils::FilePath dataStoreQmlFilePath();

View File

@@ -403,8 +403,7 @@ void CollectionSourceModel::onSelectedCollectionChanged(CollectionListModel *col
m_previousSelectedList = collectionList; m_previousSelectedList = collectionList;
emit collectionSelected(collectionList->sourceNode(), emit collectionSelected(collectionList->collectionNameAt(collectionIndex));
collectionList->collectionNameAt(collectionIndex));
selectSourceIndex(sourceIndex(collectionList->sourceNode())); selectSourceIndex(sourceIndex(collectionList->sourceNode()));
} }
@@ -494,6 +493,7 @@ void CollectionSourceModel::onCollectionNameChanged(CollectionListModel *collect
return; return;
} }
emit collectionRenamed(oldName, newName);
updateCollectionList(nodeIndex); updateCollectionList(nodeIndex);
} }
} }
@@ -547,10 +547,12 @@ void CollectionSourceModel::onCollectionsRemoved(CollectionListModel *collection
if (document.isObject()) { if (document.isObject()) {
QJsonObject rootObject = document.object(); QJsonObject rootObject = document.object();
QStringList collectionsRemovedFromDocument;
for (const QString &collectionName : removedCollections) { for (const QString &collectionName : removedCollections) {
bool sourceContainsCollection = rootObject.contains(collectionName); bool sourceContainsCollection = rootObject.contains(collectionName);
if (sourceContainsCollection) { if (sourceContainsCollection) {
rootObject.remove(collectionName); rootObject.remove(collectionName);
collectionsRemovedFromDocument << collectionName;
} else { } else {
emitDeleteWarning(tr("The model group doesn't contain the model name (%1).") emitDeleteWarning(tr("The model group doesn't contain the model name (%1).")
.arg(sourceContainsCollection)); .arg(sourceContainsCollection));
@@ -572,6 +574,9 @@ void CollectionSourceModel::onCollectionsRemoved(CollectionListModel *collection
return; return;
} }
for (const QString &collectionName : std::as_const(collectionsRemovedFromDocument))
emit this->collectionRemoved(collectionName);
updateCollectionList(nodeIndex); updateCollectionList(nodeIndex);
} }
} }
@@ -602,7 +607,7 @@ void CollectionSourceModel::setSelectedIndex(int idx)
} else if (m_previousSelectedList) { } else if (m_previousSelectedList) {
m_previousSelectedList->selectCollectionIndex(-1); m_previousSelectedList->selectCollectionIndex(-1);
m_previousSelectedList = {}; m_previousSelectedList = {};
emit this->collectionSelected(sourceNodeAt(idx), {}); emit this->collectionSelected({});
} }
} }
} }
@@ -626,12 +631,12 @@ void CollectionSourceModel::updateCollectionList(QModelIndex index)
return; return;
ModelNode sourceNode = sourceNodeAt(index.row()); ModelNode sourceNode = sourceNodeAt(index.row());
QSharedPointer<CollectionListModel> currentList = m_collectionList.at(index.row()); QSharedPointer<CollectionListModel> oldList = m_collectionList.at(index.row());
QSharedPointer<CollectionListModel> newList = loadCollection(sourceNode, currentList); QSharedPointer<CollectionListModel> newList = loadCollection(sourceNode, oldList);
if (currentList != newList) { if (oldList != newList) {
m_collectionList.replace(index.row(), newList); m_collectionList.replace(index.row(), newList);
emit dataChanged(index, index, {CollectionsRole}); emit dataChanged(index, index, {CollectionsRole});
emit collectionNamesChanged(sourceNode, newList->stringList()); registerCollection(newList);
} }
} }
@@ -656,8 +661,8 @@ void CollectionSourceModel::registerCollection(const QSharedPointer<CollectionLi
onCollectionsRemoved(collectionList, removedCollections); onCollectionsRemoved(collectionList, removedCollections);
}, Qt::UniqueConnection); }, Qt::UniqueConnection);
if (collection->sourceNode()) if (collectionList->sourceNode().isValid())
emit collectionNamesChanged(collection->sourceNode(), collection->stringList()); emit collectionNamesInitialized(collection->stringList());
} }
QModelIndex CollectionSourceModel::indexOfNode(const ModelNode &node) const QModelIndex CollectionSourceModel::indexOfNode(const ModelNode &node) const

View File

@@ -70,8 +70,11 @@ public:
signals: signals:
void selectedIndexChanged(int idx); void selectedIndexChanged(int idx);
void collectionSelected(const ModelNode &sourceNode, const QString &collectionName); void collectionSelected(const QString &collectionName);
void collectionNamesChanged(const ModelNode &sourceNode, QStringList collections); void collectionNamesInitialized(const QStringList &initialList);
void collectionRenamed(const QString &oldname, const QString &newName);
void collectionRemoved(const QString &collectionName);
void isEmptyChanged(bool); void isEmptyChanged(bool);
void warning(const QString &title, const QString &body); void warning(const QString &title, const QString &body);

View File

@@ -70,8 +70,8 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo()
connect(sourceModel, connect(sourceModel,
&CollectionSourceModel::collectionSelected, &CollectionSourceModel::collectionSelected,
this, this,
[this](const ModelNode &sourceNode, const QString &collection) { [this](const QString &collection) {
m_widget->collectionDetailsModel()->loadCollection(sourceNode, collection); m_widget->collectionDetailsModel()->loadCollection(dataStoreNode(), collection);
}); });
connect(sourceModel, &CollectionSourceModel::isEmptyChanged, this, [this](bool isEmpty) { connect(sourceModel, &CollectionSourceModel::isEmptyChanged, this, [this](bool isEmpty) {
@@ -80,11 +80,24 @@ QmlDesigner::WidgetInfo CollectionView::widgetInfo()
}); });
connect(sourceModel, connect(sourceModel,
&CollectionSourceModel::collectionNamesChanged, &CollectionSourceModel::collectionNamesInitialized,
this, this,
[this](const ModelNode &sourceNode, const QStringList &collectionNames) { [this](const QStringList &collectionNames) {
if (sourceNode == m_dataStore->modelNode()) m_dataStore->setCollectionNames(collectionNames);
m_dataStore->setCollectionNames(collectionNames); });
connect(sourceModel,
&CollectionSourceModel::collectionRenamed,
this,
[this](const QString &oldName, const QString &newName) {
m_dataStore->renameCollection(oldName, newName);
});
connect(sourceModel,
&CollectionSourceModel::collectionRemoved,
this,
[this](const QString &collectionName) {
m_dataStore->removeCollection(collectionName);
}); });
} }
@@ -200,6 +213,12 @@ void CollectionView::addResource(const QUrl &url, const QString &name, const QSt
}); });
} }
void CollectionView::assignCollectionToSelectedNode(const QString &collectionName)
{
QTC_ASSERT(dataStoreNode() && hasSingleSelectedModelNode(), return);
m_dataStore->assignCollectionToNode(this, singleSelectedModelNode(), collectionName);
}
void CollectionView::registerDeclarativeType() void CollectionView::registerDeclarativeType()
{ {
CollectionDetails::registerDeclarativeType(); CollectionDetails::registerDeclarativeType();

View File

@@ -43,6 +43,8 @@ public:
void addResource(const QUrl &url, const QString &name, const QString &type); void addResource(const QUrl &url, const QString &name, const QString &type);
void assignCollectionToSelectedNode(const QString &collectionName);
static void registerDeclarativeType(); static void registerDeclarativeType();
void resetDataStoreNode(); void resetDataStoreNode();

View File

@@ -337,17 +337,7 @@ bool CollectionWidget::addCollectionToDataStore(const QString &collectionName)
void CollectionWidget::assignCollectionToSelectedNode(const QString collectionName) void CollectionWidget::assignCollectionToSelectedNode(const QString collectionName)
{ {
ModelNode dsNode = dataStoreNode(); m_view->assignCollectionToSelectedNode(collectionName);
ModelNode targetNode = m_view->singleSelectedModelNode();
QTC_ASSERT(dsNode.isValid() && targetNode.isValid(), return);
if (dsNode.id().isEmpty()) {
warn(tr("Assigning the model"), tr("The model must have a valid id to be assigned."));
return;
}
CollectionEditor::assignCollectionToNode(m_view, targetNode, dsNode, collectionName);
} }
void CollectionWidget::ensureDataStoreExists() void CollectionWidget::ensureDataStoreExists()

View File

@@ -3,6 +3,7 @@
#include "datastoremodelnode.h" #include "datastoremodelnode.h"
#include "abstractview.h"
#include "collectioneditorconstants.h" #include "collectioneditorconstants.h"
#include "collectioneditorutils.h" #include "collectioneditorutils.h"
#include "model/qmltextgenerator.h" #include "model/qmltextgenerator.h"
@@ -42,6 +43,19 @@ QmlDesigner::PropertyNameList createNameList(const QmlDesigner::ModelNode &node)
return defaultsNodeProps + dynamicPropertyNames; return defaultsNodeProps + dynamicPropertyNames;
} }
bool isValidCollectionPropertyName(const QString &collectionId)
{
static const QmlDesigner::PropertyNameList reservedKeywords = {
QmlDesigner::CollectionEditor::SOURCEFILE_PROPERTY,
QmlDesigner::CollectionEditor::JSONBACKEND_TYPENAME,
"backend",
"models",
};
return QmlDesigner::ModelNode::isValidId(collectionId)
&& !reservedKeywords.contains(collectionId.toLatin1());
}
} // namespace } // namespace
namespace QmlDesigner { namespace QmlDesigner {
@@ -85,15 +99,13 @@ void DataStoreModelNode::reloadModel()
m_dataRelativePath = dataStoreJsonPath.relativePathFrom(dataStoreQmlPath).toFSPathString(); m_dataRelativePath = dataStoreJsonPath.relativePathFrom(dataStoreQmlPath).toFSPathString();
if (forceUpdate) { if (forceUpdate)
updateDataStoreProperties(); update();
updateSingletonFile();
}
} }
QStringList DataStoreModelNode::collectionNames() const QStringList DataStoreModelNode::collectionNames() const
{ {
return m_collectionNames; return m_collectionPropertyNames.keys();
} }
Model *DataStoreModelNode::model() const Model *DataStoreModelNode::model() const
@@ -137,23 +149,60 @@ void DataStoreModelNode::updateDataStoreProperties()
static TypeName childNodeTypename = "ChildListModel"; static TypeName childNodeTypename = "ChildListModel";
QSet<QString> collectionNamesToBeAdded;
const QStringList allCollectionNames = m_collectionPropertyNames.keys();
for (const QString &collectionName : allCollectionNames)
collectionNamesToBeAdded << collectionName;
const QList<AbstractProperty> formerPropertyNames = rootNode.dynamicProperties(); const QList<AbstractProperty> formerPropertyNames = rootNode.dynamicProperties();
for (const AbstractProperty &property : formerPropertyNames)
rootNode.removeProperty(property.name()); // Remove invalid collection names from the properties
for (const AbstractProperty &property : formerPropertyNames) {
if (!property.isNodeProperty())
continue;
NodeProperty nodeProprty = property.toNodeProperty();
if (!nodeProprty.hasDynamicTypeName(childNodeTypename))
continue;
ModelNode childNode = nodeProprty.modelNode();
if (childNode.hasProperty(CollectionEditor::JSONCHILDMODELNAME_PROPERTY)) {
QString modelName = childNode.property(CollectionEditor::JSONCHILDMODELNAME_PROPERTY)
.toVariantProperty()
.value()
.toString();
if (collectionNamesToBeAdded.contains(modelName)) {
m_collectionPropertyNames.insert(modelName, property.name());
collectionNamesToBeAdded.remove(modelName);
} else {
rootNode.removeProperty(property.name());
}
} else {
rootNode.removeProperty(property.name());
}
}
rootNode.setIdWithoutRefactoring("models"); rootNode.setIdWithoutRefactoring("models");
for (const QString &collectionName : std::as_const(m_collectionNames)) { QStringList collectionNamesLeft = collectionNamesToBeAdded.values();
PropertyName newName = collectionName.toLatin1(); Utils::sort(collectionNamesLeft);
for (const QString &collectionName : std::as_const(collectionNamesLeft)) {
PropertyName newPropertyName = getUniquePropertyName(collectionName);
if (newPropertyName.isEmpty()) {
qWarning() << __FUNCTION__ << __LINE__
<< QString("The property name cannot be generated from \"%1\"").arg(collectionName);
continue;
}
ModelNode collectionNode = model()->createModelNode(childNodeTypename); ModelNode collectionNode = model()->createModelNode(childNodeTypename);
VariantProperty modelNameProperty = collectionNode.variantProperty( VariantProperty modelNameProperty = collectionNode.variantProperty(
CollectionEditor::JSONCHILDMODELNAME_PROPERTY); CollectionEditor::JSONCHILDMODELNAME_PROPERTY);
modelNameProperty.setValue(newName); modelNameProperty.setValue(collectionName);
NodeProperty nodeProp = rootNode.nodeProperty(newName); NodeProperty nodeProp = rootNode.nodeProperty(newPropertyName);
nodeProp.setDynamicTypeNameAndsetModelNode(childNodeTypename, collectionNode); nodeProp.setDynamicTypeNameAndsetModelNode(childNodeTypename, collectionNode);
m_collectionPropertyNames.insert(collectionName, newPropertyName);
} }
// Backend Property // Backend Property
@@ -186,13 +235,127 @@ void DataStoreModelNode::updateSingletonFile()
file.finalize(); file.finalize();
} }
void DataStoreModelNode::update()
{
updateDataStoreProperties();
updateSingletonFile();
}
PropertyName DataStoreModelNode::getUniquePropertyName(const QString &collectionName)
{
ModelNode dataStoreNode = modelNode();
QTC_ASSERT(!collectionName.isEmpty() && dataStoreNode.isValid(), return {});
QString newProperty;
// convert to camel case
QStringList nameWords = collectionName.split(' ');
nameWords[0] = nameWords[0].at(0).toLower() + nameWords[0].mid(1);
for (int i = 1; i < nameWords.size(); ++i)
nameWords[i] = nameWords[i].at(0).toUpper() + nameWords[i].mid(1);
newProperty = nameWords.join("");
// if id starts with a number prepend an underscore
if (newProperty.at(0).isDigit())
newProperty.prepend('_');
// If the new id is not valid (e.g. qml keyword match), prepend an underscore
if (!isValidCollectionPropertyName(newProperty))
newProperty.prepend('_');
static const QRegularExpression rgx("\\d+$"); // matches a number at the end of a string
while (dataStoreNode.hasProperty(newProperty.toLatin1())) { // id exists
QRegularExpressionMatch match = rgx.match(newProperty);
if (match.hasMatch()) { // ends with a number, increment it
QString numStr = match.captured();
int num = numStr.toInt() + 1;
newProperty = newProperty.mid(0, match.capturedStart()) + QString::number(num);
} else {
newProperty.append('1');
}
}
return newProperty.toLatin1();
}
void DataStoreModelNode::setCollectionNames(const QStringList &newCollectionNames) void DataStoreModelNode::setCollectionNames(const QStringList &newCollectionNames)
{ {
if (m_collectionNames != newCollectionNames) { m_collectionPropertyNames.clear();
m_collectionNames = newCollectionNames; for (const QString &collectionName : newCollectionNames)
updateDataStoreProperties(); m_collectionPropertyNames.insert(collectionName, {});
updateSingletonFile(); update();
}
void DataStoreModelNode::renameCollection(const QString &oldName, const QString &newName)
{
ModelNode dataStoreNode = modelNode();
QTC_ASSERT(dataStoreNode.isValid(), return);
if (m_collectionPropertyNames.contains(oldName)) {
const PropertyName oldPropertyName = m_collectionPropertyNames.value(oldName);
if (!oldPropertyName.isEmpty() && dataStoreNode.hasProperty(oldPropertyName)) {
NodeProperty collectionNode = dataStoreNode.property(oldPropertyName).toNodeProperty();
if (collectionNode.isValid()) {
VariantProperty modelNameProperty = collectionNode.modelNode().variantProperty(
CollectionEditor::JSONCHILDMODELNAME_PROPERTY);
modelNameProperty.setValue(newName);
m_collectionPropertyNames.remove(oldName);
m_collectionPropertyNames.insert(newName, collectionNode.name());
update();
return;
}
qWarning() << __FUNCTION__ << __LINE__
<< "There is no valid node for the old collection name";
return;
}
qWarning() << __FUNCTION__ << __LINE__ << QString("Invalid old property name")
<< oldPropertyName;
return;
}
qWarning() << __FUNCTION__ << __LINE__
<< QString("There is no old collection name registered with this name \"%1\"").arg(oldName);
}
void DataStoreModelNode::removeCollection(const QString &collectionName)
{
if (m_collectionPropertyNames.contains(collectionName)) {
m_collectionPropertyNames.remove(collectionName);
update();
} }
} }
void DataStoreModelNode::assignCollectionToNode(AbstractView *view,
const ModelNode &targetNode,
const QString &collectionName)
{
QTC_ASSERT(targetNode.isValid(), return);
if (!CollectionEditor::canAcceptCollectionAsModel(targetNode))
return;
if (!m_collectionPropertyNames.contains(collectionName)) {
qWarning() << __FUNCTION__ << __LINE__ << "Collection doesn't exist in the DataStore"
<< collectionName;
return;
}
PropertyName propertyName = m_collectionPropertyNames.value(collectionName);
const ModelNode dataStore = modelNode();
VariantProperty sourceProperty = dataStore.variantProperty(propertyName);
if (!sourceProperty.exists()) {
qWarning() << __FUNCTION__ << __LINE__
<< "The source property doesn't exist in the DataStore.";
return;
}
BindingProperty modelProperty = targetNode.bindingProperty("model");
QString identifier = QString("DataStore.%1").arg(QString::fromLatin1(sourceProperty.name()));
view->executeInTransaction("assignCollectionToNode", [&modelProperty, &identifier]() {
modelProperty.setExpression(identifier);
});
}
} // namespace QmlDesigner } // namespace QmlDesigner

View File

@@ -5,6 +5,8 @@
#include <modelnode.h> #include <modelnode.h>
#include <QMap>
namespace QmlDesigner { namespace QmlDesigner {
class Model; class Model;
@@ -21,6 +23,12 @@ public:
ModelNode modelNode() const; ModelNode modelNode() const;
void setCollectionNames(const QStringList &newCollectionNames); void setCollectionNames(const QStringList &newCollectionNames);
void renameCollection(const QString &oldName, const QString &newName);
void removeCollection(const QString &collectionName);
void assignCollectionToNode(AbstractView *view,
const ModelNode &targetNode,
const QString &collectionName);
private: private:
QString getModelQmlText(); QString getModelQmlText();
@@ -28,9 +36,11 @@ private:
void reset(); void reset();
void updateDataStoreProperties(); void updateDataStoreProperties();
void updateSingletonFile(); void updateSingletonFile();
void update();
PropertyName getUniquePropertyName(const QString &collectionName);
ModelPointer m_model; ModelPointer m_model;
QStringList m_collectionNames; QMap<QString, PropertyName> m_collectionPropertyNames;
QString m_dataRelativePath; QString m_dataRelativePath;
}; };