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,
bool breakOnFirst)
{
TypeName typeName = insertInfo.typeName();
TypeName superClass = insertInfo.directSuperClass().typeName();
TypeName nodePackage = insertInfo.typeName().replace(insertInfo.simplifiedTypeName(), "");
TypeName targetPackage = parentInfo.typeName().replace(parentInfo.simplifiedTypeName(), "");
if (!nodePackage.contains(targetPackage))
return;
// TODO: Metainfo based matching system (QDS-6240)
// Common base types cause too many rarely valid matches, so they are ignored
const QSet<TypeName> ignoredTypes {"<cpp>.QObject",
"<cpp>.QQuickItem",
"<cpp>.QQuick3DObject",
"QtQuick.Item",
"QtQuick3D.Object3D",
"QtQuick3D.Node",
"QtQuick3D.Particles3D.ParticleSystem3D"};
// Fall back to a hardcoded list of supported cases:
// Texture
// -> DefaultMaterial
// -> PrincipledMaterial
// -> SpriteParticle3D
// -> TextureInput
// -> SceneEnvironment
// Effect
// -> SceneEnvironment
// Shader, Command, Buffer
// -> Pass
// InstanceListEntry
// -> InstanceList
// Pass
// -> Effect
const TypeName textureType = "QtQuick3D.Texture";
if (insertInfo.isSubclassOf(textureType)) {
const TypeName textureTypeCpp = "<cpp>.QQuick3DTexture";
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) {
if (!name.contains('.')) {
TypeName propType = parentInfo.propertyTypeName(name).replace("<cpp>.", targetPackage);
// Skip properties that are not sub classes of anything
if (propType.contains('.')
&& !ignoredTypes.contains(propType)
&& (typeName == propType || propType == superClass)
&& parentInfo.propertyIsWritable(propType)) {
TypeName propType = parentInfo.propertyTypeName(name);
if (propType == textureType || propType == textureTypeCpp) {
propertyList.append(QString::fromLatin1(name));
if (breakOnFirst)
break;
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(
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 targetInfo = targetNode.metaInfo();
ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(info, targetInfo);

View File

@@ -581,21 +581,29 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
addImport(import);
}
bool moveNodesAfter = true;
m_view->executeInTransaction("NavigatorTreeModel::dropMimeData", [&] {
for (const QString &assetPath : assetsPaths) {
auto assetTypeAndData = AssetsLibraryWidget::getAssetTypeAndData(assetPath);
QString assetType = assetTypeAndData.first;
QString assetData = QString::fromUtf8(assetTypeAndData.second);
if (assetType == "application/vnd.bauhaus.libraryresource.image")
currNode = handleItemLibraryImageDrop(assetPath, targetProperty, rowModelIndex);
else if (assetType == "application/vnd.bauhaus.libraryresource.font")
currNode = handleItemLibraryFontDrop(assetData, targetProperty, rowModelIndex); // assetData is fontFamily
else if (assetType == "application/vnd.bauhaus.libraryresource.shader")
currNode = handleItemLibraryShaderDrop(assetPath, assetData == "f", targetProperty, rowModelIndex);
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);
if (assetType == "application/vnd.bauhaus.libraryresource.image") {
currNode = handleItemLibraryImageDrop(assetPath, targetProperty,
rowModelIndex, moveNodesAfter);
} else if (assetType == "application/vnd.bauhaus.libraryresource.font") {
currNode = handleItemLibraryFontDrop(assetData, // assetData is fontFamily
targetProperty, rowModelIndex);
} else if (assetType == "application/vnd.bauhaus.libraryresource.shader") {
currNode = handleItemLibraryShaderDrop(assetPath, assetData == "f",
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())
addedNodes.append(currNode);
@@ -603,6 +611,7 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData,
});
if (!addedNodes.isEmpty()) {
if (moveNodesAfter)
moveNodesInteractive(targetProperty, addedNodes, rowNumber);
m_view->setSelectedModelNodes(addedNodes);
}
@@ -682,21 +691,22 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
dialog->exec();
if (soloProperty || dialog->result() == QDialog::Accepted) {
TypeName selectedProp = dialog->selectedProperty();
BindingProperty listProp = targetNode.bindingProperty(selectedProp);
if (targetNode.metaInfo().propertyIsListProperty(selectedProp)) {
if ((newModelNode.isSubclassOf("QtQuick3D.Shader") || newModelNode.isSubclassOf("QtQuick3D.Command"))
&& targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass")) {
NodeAbstractProperty parentProp = targetProperty.parentProperty();
if (parentProp.isValid()) {
targetProperty = parentProp;
ModelNode targetModel = targetProperty.parentModelNode();
targetRowNumber = rowCount(indexForModelNode(targetModel));
// Move node to new parent within the same transaction as we don't
// want undo to place the node under invalid parent
moveNodesInteractive(targetProperty, {newQmlObjectNode}, targetRowNumber, false);
// Pass and TextureInput can't have children, so we have to move nodes under parent
if (((newModelNode.isSubclassOf("QtQuick3D.Shader")
|| newModelNode.isSubclassOf("QtQuick3D.Command")
|| newModelNode.isSubclassOf("QtQuick3D.Buffer"))
&& targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass"))
|| (newModelNode.isSubclassOf("QtQuick3D.Texture")
&& targetProperty.parentModelNode().isSubclassOf("QtQuick3D.TextureInput"))) {
if (moveNodeToParent(targetProperty, newQmlObjectNode)) {
targetProperty = targetProperty.parentProperty();
moveNodesAfter = false;
}
}
if (targetNode.metaInfo().propertyIsListProperty(selectedProp)) {
BindingProperty listProp = targetNode.bindingProperty(selectedProp);
listProp.addModelNodeToArray(newModelNode);
validContainer = true;
} else {
@@ -778,7 +788,8 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePath,
NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex)
const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter)
{
QTC_ASSERT(m_view, return {});
@@ -788,7 +799,7 @@ ModelNode NavigatorTreeModel::handleItemLibraryImageDrop(const QString &imagePat
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 dropping an image on an existing image, set the source
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,
NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex)
const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter)
{
QTC_ASSERT(m_view, return {});
@@ -863,6 +888,7 @@ ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderP
: "Shader.Vertex");
targetNode.variantProperty("shader").setValue(relPath);
} else {
m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryShaderDrop", [&] {
// create a new Shader
ItemLibraryEntry itemLibraryEntry;
itemLibraryEntry.setName("Shader");
@@ -886,6 +912,13 @@ ModelNode NavigatorTreeModel::handleItemLibraryShaderDrop(const QString &shaderP
QFileInfo fi(relPath);
newModelNode.setIdWithoutRefactoring(m_view->generateNewId(fi.baseName(),
"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;
@@ -932,7 +965,8 @@ ModelNode NavigatorTreeModel::handleItemLibrarySoundDrop(const QString &soundPat
ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3DPath,
NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex)
const QModelIndex &rowModelIndex,
bool &outMoveNodesAfter)
{
QTC_ASSERT(m_view, return {});
@@ -947,7 +981,7 @@ ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3
ModelNode newModelNode;
if (!dropAsImage3dTexture(targetNode, targetProperty, imagePath, newModelNode)) {
if (!dropAsImage3dTexture(targetNode, targetProperty, imagePath, newModelNode, outMoveNodesAfter)) {
m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryTexture3dDrop", [&] {
// create a standalone Texture3D at drop location
newModelNode = createTextureNode(targetProperty, imagePath);
@@ -962,8 +996,23 @@ ModelNode NavigatorTreeModel::handleItemLibraryTexture3dDrop(const QString &tex3
bool NavigatorTreeModel::dropAsImage3dTexture(const ModelNode &targetNode,
const NodeAbstractProperty &targetProp,
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")
|| targetNode.isSubclassOf("QtQuick3D.PrincipledMaterial")) {
// 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;
return true;
} else if (targetNode.isSubclassOf("QtQuick3D.TextureInput")) {
// If dropping an image on a TextureInput, create a texture on the same level as
// TextureInput, as the TextureInput doesn't support Texture children (QTBUG-86219)
m_view->executeInTransaction("NavigatorTreeModel::dropAsImage3dTexture", [&] {
NodeAbstractProperty parentProp = targetProp.parentProperty();
newNode = createTextureNode(parentProp, imagePath);
if (newNode.isValid()) {
// Automatically set the texture to texture property
targetNode.bindingProperty("texture").setExpression(newNode.validId());
}
});
bindToProperty("texture", true);
return newNode.isValid();
} else if (targetNode.isSubclassOf("QtQuick3D.Particles3D.SpriteParticle3D")) {
bindToProperty("sprite", false);
return newNode.isValid();
} else if (targetNode.isSubclassOf("QtQuick3D.SceneEnvironment")) {
bindToProperty("lightProbe", false);
return newNode.isValid();
} else if (targetNode.isSubclassOf("QtQuick3D.Texture")) {
// 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 handleItemLibraryItemDrop(const QMimeData *mimeData, int rowNumber, const QModelIndex &dropModelIndex);
ModelNode handleItemLibraryImageDrop(const QString &imagePath, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex);
const QModelIndex &rowModelIndex, bool &outMoveNodesAfter);
ModelNode handleItemLibraryFontDrop(const QString &fontFamily, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex);
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,
const QModelIndex &rowModelIndex);
ModelNode handleItemLibraryTexture3dDrop(const QString &tex3DPath, NodeAbstractProperty targetProperty,
const QModelIndex &rowModelIndex);
const QModelIndex &rowModelIndex, bool &outMoveNodesAfter);
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);
QList<QPersistentModelIndex> nodesToPersistentIndex(const QList<ModelNode> &modelNodes);
void addImport(const QString &importName);
QList<ModelNode> filteredList(const NodeListProperty &property, bool filter, bool reverseOrder) const;
bool moveNodeToParent(const NodeAbstractProperty &targetProperty, const ModelNode &newModelNode);
QPointer<NavigatorView> m_view;
mutable QHash<ModelNode, QModelIndex> m_nodeIndexHash;