QmlDesigner: Fix on-drag binding of created nodes to properties

Generic matching for dragged item to bindable properties didn't work
properly, missing appropriate matches. Now supported cases are
hardcoded with the expectation that in the future this will be handled
via metainfo hints.

Also fixed related issues with dragging images and shaders into
navigator.

Fixes: QDS-6198
Change-Id: Ide9360ef037997cbb388f4c2db35b79eded67aa6
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-02-15 15:45:59 +02:00
parent 6173b78860
commit 358b21ce9c
3 changed files with 169 additions and 99 deletions

View File

@@ -34,35 +34,64 @@ ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &i
const NodeMetaInfo &parentInfo, const NodeMetaInfo &parentInfo,
bool breakOnFirst) bool breakOnFirst)
{ {
TypeName typeName = insertInfo.typeName(); // TODO: Metainfo based matching system (QDS-6240)
TypeName superClass = insertInfo.directSuperClass().typeName();
TypeName nodePackage = insertInfo.typeName().replace(insertInfo.simplifiedTypeName(), "");
TypeName targetPackage = parentInfo.typeName().replace(parentInfo.simplifiedTypeName(), "");
if (!nodePackage.contains(targetPackage))
return;
// Common base types cause too many rarely valid matches, so they are ignored // Fall back to a hardcoded list of supported cases:
const QSet<TypeName> ignoredTypes {"<cpp>.QObject", // Texture
"<cpp>.QQuickItem", // -> DefaultMaterial
"<cpp>.QQuick3DObject", // -> PrincipledMaterial
"QtQuick.Item", // -> SpriteParticle3D
"QtQuick3D.Object3D", // -> TextureInput
"QtQuick3D.Node", // -> SceneEnvironment
"QtQuick3D.Particles3D.ParticleSystem3D"}; // Effect
const PropertyNameList targetNodeNameList = parentInfo.propertyNames(); // -> SceneEnvironment
for (const PropertyName &name : targetNodeNameList) { // Shader, Command, Buffer
if (!name.contains('.')) { // -> Pass
TypeName propType = parentInfo.propertyTypeName(name).replace("<cpp>.", targetPackage); // InstanceListEntry
// Skip properties that are not sub classes of anything // -> InstanceList
if (propType.contains('.') // Pass
&& !ignoredTypes.contains(propType) // -> Effect
&& (typeName == propType || propType == superClass)
&& parentInfo.propertyIsWritable(propType)) { const TypeName textureType = "QtQuick3D.Texture";
propertyList.append(QString::fromLatin1(name)); if (insertInfo.isSubclassOf(textureType)) {
if (breakOnFirst) const TypeName textureTypeCpp = "<cpp>.QQuick3DTexture";
break; if (parentInfo.isSubclassOf("QtQuick3D.DefaultMaterial")
|| parentInfo.isSubclassOf("QtQuick3D.PrincipledMaterial")) {
// All texture properties are valid targets
const PropertyNameList targetNodeNameList = parentInfo.propertyNames();
for (const PropertyName &name : targetNodeNameList) {
TypeName propType = parentInfo.propertyTypeName(name);
if (propType == textureType || propType == textureTypeCpp) {
propertyList.append(QString::fromLatin1(name));
if (breakOnFirst)
return;
}
} }
} else if (parentInfo.isSubclassOf("QtQuick3D.Particles3D.SpriteParticle3D")) {
propertyList.append("sprite");
} else if (parentInfo.isSubclassOf("QtQuick3D.TextureInput")) {
propertyList.append("texture");
} else if (parentInfo.isSubclassOf("QtQuick3D.SceneEnvironment")) {
propertyList.append("lightProbe");
} }
} else if (insertInfo.isSubclassOf("QtQuick3D.Effect")) {
if (parentInfo.isSubclassOf("QtQuick3D.SceneEnvironment"))
propertyList.append("effects");
} else if (insertInfo.isSubclassOf("QtQuick3D.Shader")) {
if (parentInfo.isSubclassOf("QtQuick3D.Pass"))
propertyList.append("shaders");
} else if (insertInfo.isSubclassOf("QtQuick3D.Command")) {
if (parentInfo.isSubclassOf("QtQuick3D.Pass"))
propertyList.append("commands");
} else if (insertInfo.isSubclassOf("QtQuick3D.Buffer")) {
if (parentInfo.isSubclassOf("QtQuick3D.Pass"))
propertyList.append("output");
} else if (insertInfo.isSubclassOf("QtQuick3D.InstanceListEntry")) {
if (parentInfo.isSubclassOf("QtQuick3D.InstanceList"))
propertyList.append("instances");
} else if (insertInfo.isSubclassOf("QtQuick3D.Pass")) {
if (parentInfo.isSubclassOf("QtQuick3D.Effect"))
propertyList.append("passes");
} }
} }
@@ -110,14 +139,6 @@ TypeName ChooseFromPropertyListDialog::selectedProperty() const
ChooseFromPropertyListDialog *ChooseFromPropertyListDialog::createIfNeeded( ChooseFromPropertyListDialog *ChooseFromPropertyListDialog::createIfNeeded(
const ModelNode &targetNode, const ModelNode &newNode, QWidget *parent) const ModelNode &targetNode, const ModelNode &newNode, QWidget *parent)
{ {
TypeName typeName = newNode.type();
// Component matches cases where you don't want to insert a plain component,
// such as layer.effect. Also, default property is often a Component (typically 'delegate'),
// and inserting into such property will silently overwrite implicit component, if any.
if (typeName == "QtQml.Component")
return nullptr;
const NodeMetaInfo info = newNode.metaInfo(); const NodeMetaInfo info = newNode.metaInfo();
const NodeMetaInfo targetInfo = targetNode.metaInfo(); const NodeMetaInfo targetInfo = targetNode.metaInfo();
ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(info, targetInfo); ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(info, targetInfo);

View File

@@ -581,21 +581,29 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
addImport(import); addImport(import);
} }
bool moveNodesAfter = true;
m_view->executeInTransaction("NavigatorTreeModel::dropMimeData", [&] { m_view->executeInTransaction("NavigatorTreeModel::dropMimeData", [&] {
for (const QString &assetPath : assetsPaths) { for (const QString &assetPath : assetsPaths) {
auto assetTypeAndData = AssetsLibraryWidget::getAssetTypeAndData(assetPath); auto assetTypeAndData = AssetsLibraryWidget::getAssetTypeAndData(assetPath);
QString assetType = assetTypeAndData.first; QString assetType = assetTypeAndData.first;
QString assetData = QString::fromUtf8(assetTypeAndData.second); QString assetData = QString::fromUtf8(assetTypeAndData.second);
if (assetType == "application/vnd.bauhaus.libraryresource.image") if (assetType == "application/vnd.bauhaus.libraryresource.image") {
currNode = handleItemLibraryImageDrop(assetPath, targetProperty, rowModelIndex); currNode = handleItemLibraryImageDrop(assetPath, targetProperty,
else if (assetType == "application/vnd.bauhaus.libraryresource.font") rowModelIndex, moveNodesAfter);
currNode = handleItemLibraryFontDrop(assetData, targetProperty, rowModelIndex); // assetData is fontFamily } else if (assetType == "application/vnd.bauhaus.libraryresource.font") {
else if (assetType == "application/vnd.bauhaus.libraryresource.shader") currNode = handleItemLibraryFontDrop(assetData, // assetData is fontFamily
currNode = handleItemLibraryShaderDrop(assetPath, assetData == "f", targetProperty, rowModelIndex); targetProperty, rowModelIndex);
else if (assetType == "application/vnd.bauhaus.libraryresource.sound") } else if (assetType == "application/vnd.bauhaus.libraryresource.shader") {
currNode = handleItemLibrarySoundDrop(assetPath, targetProperty, rowModelIndex); currNode = handleItemLibraryShaderDrop(assetPath, assetData == "f",
else if (assetType == "application/vnd.bauhaus.libraryresource.texture3d") targetProperty, rowModelIndex,
currNode = handleItemLibraryTexture3dDrop(assetPath, targetProperty, rowModelIndex); moveNodesAfter);
} else if (assetType == "application/vnd.bauhaus.libraryresource.sound") {
currNode = handleItemLibrarySoundDrop(assetPath, targetProperty,
rowModelIndex);
} else if (assetType == "application/vnd.bauhaus.libraryresource.texture3d") {
currNode = handleItemLibraryTexture3dDrop(assetPath, targetProperty,
rowModelIndex, moveNodesAfter);
}
if (currNode.isValid()) if (currNode.isValid())
addedNodes.append(currNode); addedNodes.append(currNode);
@@ -603,7 +611,8 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
}); });
if (!addedNodes.isEmpty()) { if (!addedNodes.isEmpty()) {
moveNodesInteractive(targetProperty, addedNodes, rowNumber); if (moveNodesAfter)
moveNodesInteractive(targetProperty, addedNodes, rowNumber);
m_view->setSelectedModelNodes(addedNodes); m_view->setSelectedModelNodes(addedNodes);
} }
} }
@@ -682,21 +691,22 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
dialog->exec(); dialog->exec();
if (soloProperty || dialog->result() == QDialog::Accepted) { if (soloProperty || dialog->result() == QDialog::Accepted) {
TypeName selectedProp = dialog->selectedProperty(); TypeName selectedProp = dialog->selectedProperty();
BindingProperty listProp = targetNode.bindingProperty(selectedProp);
if (targetNode.metaInfo().propertyIsListProperty(selectedProp)) { // Pass and TextureInput can't have children, so we have to move nodes under parent
if ((newModelNode.isSubclassOf("QtQuick3D.Shader") || newModelNode.isSubclassOf("QtQuick3D.Command")) if (((newModelNode.isSubclassOf("QtQuick3D.Shader")
&& targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass")) { || newModelNode.isSubclassOf("QtQuick3D.Command")
NodeAbstractProperty parentProp = targetProperty.parentProperty(); || newModelNode.isSubclassOf("QtQuick3D.Buffer"))
if (parentProp.isValid()) { && targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass"))
targetProperty = parentProp; || (newModelNode.isSubclassOf("QtQuick3D.Texture")
ModelNode targetModel = targetProperty.parentModelNode(); && targetProperty.parentModelNode().isSubclassOf("QtQuick3D.TextureInput"))) {
targetRowNumber = rowCount(indexForModelNode(targetModel)); if (moveNodeToParent(targetProperty, newQmlObjectNode)) {
// Move node to new parent within the same transaction as we don't targetProperty = targetProperty.parentProperty();
// want undo to place the node under invalid parent moveNodesAfter = false;
moveNodesInteractive(targetProperty, {newQmlObjectNode}, targetRowNumber, false);
moveNodesAfter = false;
}
} }
}
if (targetNode.metaInfo().propertyIsListProperty(selectedProp)) {
BindingProperty listProp = targetNode.bindingProperty(selectedProp);
listProp.addModelNodeToArray(newModelNode); listProp.addModelNodeToArray(newModelNode);
validContainer = true; validContainer = true;
} else { } else {
@@ -778,7 +788,8 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePath, ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePath,
NodeAbstractProperty targetProperty, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex) const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter)
{ {
QTC_ASSERT(m_view, return {}); QTC_ASSERT(m_view, return {});
@@ -788,7 +799,7 @@ ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePat
ModelNode newModelNode; ModelNode newModelNode;
if (!dropAsImage3dTexture(targetNode, targetProperty, imagePathRelative, newModelNode)) { if (!dropAsImage3dTexture(targetNode, targetProperty, imagePathRelative, newModelNode, outMoveNodesAfter)) {
if (targetNode.isSubclassOf("QtQuick.Image") || targetNode.isSubclassOf("QtQuick.BorderImage")) { if (targetNode.isSubclassOf("QtQuick.Image") || targetNode.isSubclassOf("QtQuick.BorderImage")) {
// if dropping an image on an existing image, set the source // if dropping an image on an existing image, set the source
targetNode.variantProperty("source").setValue(imagePathRelative); targetNode.variantProperty("source").setValue(imagePathRelative);
@@ -846,9 +857,23 @@ void NavigatorTreeModel::addImport(const QString &importName)
} }
} }
bool QmlDesigner::NavigatorTreeModel::moveNodeToParent(const NodeAbstractProperty &targetProperty,
const ModelNode &node)
{
NodeAbstractProperty parentProp = targetProperty.parentProperty();
if (parentProp.isValid()) {
ModelNode targetModel = parentProp.parentModelNode();
int targetRowNumber = rowCount(indexForModelNode(targetModel));
moveNodesInteractive(parentProp, {node}, targetRowNumber, false);
return true;
}
return false;
}
ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader, ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader,
NodeAbstractProperty targetProperty, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex) const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter)
{ {
QTC_ASSERT(m_view, return {}); QTC_ASSERT(m_view, return {});
@@ -863,29 +888,37 @@ ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderP
: "Shader.Vertex"); : "Shader.Vertex");
targetNode.variantProperty("shader").setValue(relPath); targetNode.variantProperty("shader").setValue(relPath);
} else { } else {
// create a new Shader m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryShaderDrop", [&] {
ItemLibraryEntry itemLibraryEntry; // create a new Shader
itemLibraryEntry.setName("Shader"); ItemLibraryEntry itemLibraryEntry;
itemLibraryEntry.setType("QtQuick3D.Shader", 1, 0); itemLibraryEntry.setName("Shader");
itemLibraryEntry.setType("QtQuick3D.Shader", 1, 0);
// set shader properties // set shader properties
PropertyName prop = "shader"; PropertyName prop = "shader";
QString type = "QByteArray"; QString type = "QByteArray";
QVariant val = relPath; QVariant val = relPath;
itemLibraryEntry.addProperty(prop, type, val); itemLibraryEntry.addProperty(prop, type, val);
prop = "stage"; prop = "stage";
type = "enum"; type = "enum";
val = isFragShader ? "Shader.Fragment" : "Shader.Vertex"; val = isFragShader ? "Shader.Fragment" : "Shader.Vertex";
itemLibraryEntry.addProperty(prop, type, val); itemLibraryEntry.addProperty(prop, type, val);
// create a texture // create a texture
newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {}, newModelNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, {},
targetProperty, false); targetProperty, false);
// Rename the node based on shader source // Rename the node based on shader source
QFileInfo fi(relPath); QFileInfo fi(relPath);
newModelNode.setIdWithoutRefactoring(m_view->generateNewId(fi.baseName(), newModelNode.setIdWithoutRefactoring(m_view->generateNewId(fi.baseName(),
"shader")); "shader"));
// Passes can't have children, so move shader node under parent
if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass")) {
BindingProperty listProp = targetNode.bindingProperty("shaders");
listProp.addModelNodeToArray(newModelNode);
outMoveNodesAfter = !moveNodeToParent(targetProperty, newModelNode);
}
});
} }
return newModelNode; return newModelNode;
@@ -932,7 +965,8 @@ ModelNode NavigatorTreeModel::handleItemLibrarySoundDrop(const QString &soundPat
ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3DPath, ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3DPath,
NodeAbstractProperty targetProperty, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex) const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter)
{ {
QTC_ASSERT(m_view, return {}); QTC_ASSERT(m_view, return {});
@@ -947,7 +981,7 @@ ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3
ModelNode newModelNode; ModelNode newModelNode;
if (!dropAsImage3dTexture(targetNode, targetProperty, imagePath, newModelNode)) { if (!dropAsImage3dTexture(targetNode, targetProperty, imagePath, newModelNode, outMoveNodesAfter)) {
m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryTexture3dDrop", [&] { m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryTexture3dDrop", [&] {
// create a standalone Texture3D at drop location // create a standalone Texture3D at drop location
newModelNode = createTextureNode(targetProperty, imagePath); newModelNode = createTextureNode(targetProperty, imagePath);
@@ -962,8 +996,23 @@ ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3
bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode, bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode,
const NodeAbstractProperty &targetProp, const NodeAbstractProperty &targetProp,
const QString &imagePath, const QString &imagePath,
ModelNode &newNode) ModelNode &newNode,
bool &outMoveNodesAfter)
{ {
auto bindToProperty = [&](const PropertyName &propName, bool sibling) {
m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] {
newNode = createTextureNode(targetProp, imagePath);
if (newNode.isValid()) {
targetNode.bindingProperty(propName).setExpression(newNode.validId());
// If dropping an image on e.g. TextureInput, create a texture on the same level as
// target, as the target doesn't support Texture children (QTBUG-86219)
if (sibling)
outMoveNodesAfter = !moveNodeToParent(targetProp, newNode);
}
});
};
if (targetNode.isSubclassOf("QtQuick3D.DefaultMaterial") if (targetNode.isSubclassOf("QtQuick3D.DefaultMaterial")
|| targetNode.isSubclassOf("QtQuick3D.PrincipledMaterial")) { || targetNode.isSubclassOf("QtQuick3D.PrincipledMaterial")) {
// if dropping an image on a material, create a texture instead of image // if dropping an image on a material, create a texture instead of image
@@ -986,16 +1035,13 @@ bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode,
delete dialog; delete dialog;
return true; return true;
} else if (targetNode.isSubclassOf("QtQuick3D.TextureInput")) { } else if (targetNode.isSubclassOf("QtQuick3D.TextureInput")) {
// If dropping an image on a TextureInput, create a texture on the same level as bindToProperty("texture", true);
// TextureInput, as the TextureInput doesn't support Texture children (QTBUG-86219) return newNode.isValid();
m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] { } else if (targetNode.isSubclassOf("QtQuick3D.Particles3D.SpriteParticle3D")) {
NodeAbstractProperty parentProp = targetProp.parentProperty(); bindToProperty("sprite", false);
newNode = createTextureNode(parentProp, imagePath); return newNode.isValid();
if (newNode.isValid()) { } else if (targetNode.isSubclassOf("QtQuick3D.SceneEnvironment")) {
// Automatically set the texture to texture property bindToProperty("lightProbe", false);
targetNode.bindingProperty("texture").setExpression(newNode.validId());
}
});
return newNode.isValid(); return newNode.isValid();
} else if (targetNode.isSubclassOf("QtQuick3D.Texture")) { } else if (targetNode.isSubclassOf("QtQuick3D.Texture")) {
// if dropping an image on an existing texture, set the source // if dropping an image on an existing texture, set the source

View File

@@ -115,21 +115,24 @@ private:
void handleInternalDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); void handleInternalDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex);
void handleItemLibraryItemDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex); void handleItemLibraryItemDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex);
ModelNode handleItemLibraryImageDrop(const QString &imagePath, NodeAbstractProperty targetProperty, ModelNode handleItemLibraryImageDrop(const QString &imagePath, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex); const QModelIndex &rowModelIndex, bool &outMoveNodesAfter);
ModelNode handleItemLibraryFontDrop(const QString &fontFamily, NodeAbstractProperty targetProperty, ModelNode handleItemLibraryFontDrop(const QString &fontFamily, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex); const QModelIndex &rowModelIndex);
ModelNode handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader, ModelNode handleItemLibraryShaderDrop(const QString &shaderPath, bool isFragShader,
NodeAbstractProperty targetProperty, const QModelIndex &rowModelIndex); NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter);
ModelNode handleItemLibrarySoundDrop(const QString &soundPath, NodeAbstractProperty targetProperty, ModelNode handleItemLibrarySoundDrop(const QString &soundPath, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex); const QModelIndex &rowModelIndex);
ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, NodeAbstractProperty targetProperty, ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex); const QModelIndex &rowModelIndex, bool &outMoveNodesAfter);
bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp, bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp,
const QString &imagePath, ModelNode &newNode); const QString &imagePath, ModelNode &newNode, bool &outMoveNodesAfter);
ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath); ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath);
QList<QPersistentModelIndex> nodesToPersistentIndex(const QList<ModelNode> &modelNodes); QList<QPersistentModelIndex> nodesToPersistentIndex(const QList<ModelNode> &modelNodes);
void addImport(const QString &importName); void addImport(const QString &importName);
QList<ModelNode> filteredList(const NodeListProperty &property, bool filter, bool reverseOrder) const; QList<ModelNode> filteredList(const NodeListProperty &property, bool filter, bool reverseOrder) const;
bool moveNodeToParent(const NodeAbstractProperty &targetProperty, const ModelNode &newModelNode);
QPointer<NavigatorView> m_view; QPointer<NavigatorView> m_view;
mutable QHash<ModelNode, QModelIndex> m_nodeIndexHash; mutable QHash<ModelNode, QModelIndex> m_nodeIndexHash;