From 13789ce2f93579d23591010cdc5966d46ee04f99 Mon Sep 17 00:00:00 2001 From: Tony Leinonen Date: Thu, 9 Dec 2021 13:54:18 +0200 Subject: [PATCH] QmlDesigner: Edit drag and drop binding and add navigator item highlight MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a new component is drag and dropped only check current or super class instead of going trough all prototype classes. This will limit the possible bindings to a more reasonable level. Add navigator highlight for components that has properties where the new component can be bound to. Task-number: QDS-4969 Change-Id: Ib7091154a4c3c62ce995ad1b3f55830b8bca858b Reviewed-by: Miikka Heikkinen Reviewed-by: Tomi Korpipää Reviewed-by: Qt CI Bot Reviewed-by: Thomas Hartmann --- .../itemlibrary/itemlibrarywidget.cpp | 18 ++- .../choosefrompropertylistdialog.cpp | 70 ++++++---- .../navigator/choosefrompropertylistdialog.h | 11 ++ .../components/navigator/nameitemdelegate.cpp | 20 +++ .../navigator/navigatortreemodel.cpp | 123 ++++++------------ .../components/navigator/navigatorwidget.cpp | 10 ++ .../components/navigator/navigatorwidget.h | 4 + 7 files changed, 150 insertions(+), 106 deletions(-) diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp index 955f04a7ac3..9c7f86677b7 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarywidget.cpp @@ -41,6 +41,7 @@ #include "modelnodeoperations.h" #include #include +#include #include #include #include @@ -117,7 +118,14 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event) } } } - + QWidget *view = QmlDesignerPlugin::instance()->viewManager().widget("Navigator"); + if (view) { + NavigatorWidget *navView = qobject_cast(view); + if (navView) { + navView->setDragType(entry.typeName()); + navView->update(); + } + } auto drag = new QDrag(this); drag->setPixmap(Utils::StyleHelper::dpiSpecificImageFile(entry.libraryEntryIconPath())); drag->setMimeData(m_itemLibraryModel->getMimeData(entry)); @@ -143,6 +151,14 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event) } else if (event->type() == QMouseEvent::MouseButtonRelease) { m_itemToDrag = {}; m_assetsToDrag.clear(); + QWidget *view = QmlDesignerPlugin::instance()->viewManager().widget("Navigator"); + if (view) { + NavigatorWidget *navView = qobject_cast(view); + if (navView) { + navView->setDragType(""); + navView->update(); + } + } } return QObject::eventFilter(obj, event); diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp index 89fab73597f..fceb010cbe4 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.cpp @@ -29,12 +29,54 @@ namespace QmlDesigner { +// This will filter and return possible properties that the given type can be bound to +ChooseFromPropertyListFilter::ChooseFromPropertyListFilter(const NodeMetaInfo &insertInfo, + const NodeMetaInfo &parentInfo, + bool breakOnFirst) +{ + TypeName typeName = insertInfo.typeName(); + TypeName superClass = insertInfo.directSuperClass().typeName(); + TypeName nodePackage = insertInfo.typeName().replace(insertInfo.simplifiedTypeName().toStdString(), ""); + TypeName targetPackage = parentInfo.typeName().replace(parentInfo.simplifiedTypeName().toStdString(), ""); + if (!nodePackage.contains(targetPackage)) + return; + + // Common base types cause too many rarely valid matches, so they are ignored + const QSet ignoredTypes {".QObject", + ".QQuickItem", + ".QQuick3DObject", + "QtQuick.Item", + "QtQuick3D.Object3D", + "QtQuick3D.Node", + "QtQuick3D.Particles3D.ParticleSystem3D"}; + const PropertyNameList targetNodeNameList = parentInfo.propertyNames(); + for (const PropertyName &name : targetNodeNameList) { + if (!name.contains('.')) { + TypeName propType = parentInfo.propertyTypeName(name).replace(".", targetPackage); + // Skip properties that are not sub classes of anything + if (propType.contains('.') + && !ignoredTypes.contains(propType) + && (typeName == propType || propType == superClass) + && parentInfo.propertyIsWritable(propType)) { + propertyList.append(QString::fromLatin1(name)); + if (breakOnFirst) + break; + } + } + } +} + // This dialog displays specified properties and allows the user to choose one ChooseFromPropertyListDialog::ChooseFromPropertyListDialog(const QStringList &propNames, QWidget *parent) : QDialog(parent) , m_ui(new Ui::ChooseFromPropertyListDialog) { + if (propNames.count() == 1) { + m_selectedProperty = propNames.first().toLatin1(); + m_isSoloProperty = true; + return; + } m_ui->setupUi(this); setWindowTitle(tr("Select property")); m_ui->label->setText(tr("Bind to property:")); @@ -76,30 +118,12 @@ ChooseFromPropertyListDialog *ChooseFromPropertyListDialog::createIfNeeded( if (typeName == "QtQml.Component") return nullptr; - const NodeMetaInfo metaInfo = targetNode.metaInfo(); - const PropertyNameList propNames = metaInfo.propertyNames(); - QStringList matchingNames; + const NodeMetaInfo info = newNode.metaInfo(); + const NodeMetaInfo targetInfo = targetNode.metaInfo(); + ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(info, targetInfo); - // Common base types cause too many rarely valid matches, so they are ignored - const QSet ignoredTypes {".QObject", - ".QQuickItem", - ".QQuick3DObject", - "QtQuick.Item", - "QtQuick3D.Object3D", - "QtQuick3D.Node", - "QtQuick3D.Particles3D.ParticleSystem3D"}; - - for (const auto &propName : propNames) { - const TypeName testType = metaInfo.propertyTypeName(propName); - if (!ignoredTypes.contains(testType) - && metaInfo.propertyIsWritable(propName) - && (testType == typeName || newNode.isSubclassOf(testType))) { - matchingNames.append(QString::fromLatin1(propName)); - } - } - - if (!matchingNames.isEmpty()) - return new ChooseFromPropertyListDialog(matchingNames, parent); + if (!filter->propertyList.isEmpty()) + return new ChooseFromPropertyListDialog(filter->propertyList, parent); return nullptr; } diff --git a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h index 4cc59242f19..fc9e494ce31 100644 --- a/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h +++ b/src/plugins/qmldesigner/components/navigator/choosefrompropertylistdialog.h @@ -33,8 +33,17 @@ namespace QmlDesigner { namespace Ui { class ChooseFromPropertyListDialog; +class ChooseFromPropertyListFilter; } +class ChooseFromPropertyListFilter +{ +public: + ChooseFromPropertyListFilter(const NodeMetaInfo &metaInfo, const NodeMetaInfo &newInfo, bool breakOnFirst = false); + ~ChooseFromPropertyListFilter() {} + QStringList propertyList; +}; + class ChooseFromPropertyListDialog : public QDialog { Q_OBJECT @@ -43,6 +52,7 @@ public: ~ChooseFromPropertyListDialog(); TypeName selectedProperty() const; + bool isSoloProperty() const { return m_isSoloProperty; } static ChooseFromPropertyListDialog *createIfNeeded(const ModelNode &targetNode, const ModelNode &newNode, @@ -57,5 +67,6 @@ private: Ui::ChooseFromPropertyListDialog *m_ui; TypeName m_selectedProperty; + bool m_isSoloProperty = false; }; } diff --git a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp index 88c5d9d46cd..f7b8f51604f 100644 --- a/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp +++ b/src/plugins/qmldesigner/components/navigator/nameitemdelegate.cpp @@ -29,6 +29,8 @@ #include "navigatorview.h" #include "navigatortreeview.h" +#include "navigatorwidget.h" +#include "choosefrompropertylistdialog.h" #include "qproxystyle.h" #include @@ -230,6 +232,24 @@ void NameItemDelegate::paint(QPainter *painter, painter->setPen(Theme::getColor(Theme::Color::DSnavigatorTextSelected)); } + ModelNode node = getModelNode(modelIndex); + NavigatorWidget *widget = qobject_cast(styleOption.widget->parent()); + if (widget && !widget->dragType().isEmpty()) { + QByteArray dragType = widget->dragType(); + const NodeMetaInfo metaInfo = node.metaInfo(); + const NodeMetaInfo dragInfo = node.model()->metaInfo(dragType); + ChooseFromPropertyListFilter *filter = new ChooseFromPropertyListFilter(dragInfo, metaInfo, true); + + if (!filter->propertyList.isEmpty()) { + painter->setOpacity(0.5); + painter->fillRect(styleOption.rect.adjusted(0, delegateMargin, 0, -delegateMargin), + Theme::getColor(Theme::Color::DSnavigatorDropIndicatorBackground)); + painter->setOpacity(1.0); + painter->setPen(Theme::getColor(Theme::Color::DSnavigatorTextSelected)); + } + delete filter; + } + iconOffset = drawIcon(painter, styleOption, modelIndex); if (isThisOrAncestorLocked(modelIndex)) diff --git a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp index c42f351f5f7..ffdd105d39b 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatortreemodel.cpp @@ -25,6 +25,7 @@ #include "navigatortreemodel.h" #include "navigatorview.h" +#include "navigatorwidget.h" #include "choosefrompropertylistdialog.h" #include "qmldesignerplugin.h" #include "itemlibrarywidget.h" @@ -537,6 +538,10 @@ bool NavigatorTreeModel::dropMimeData(const QMimeData *mimeData, if (m_reverseItemOrder) rowNumber = rowCount(dropModelIndex) - rowNumber; + NavigatorWidget *widget = qobject_cast(m_view->widgetInfo().widget); + if (widget) + widget->setDragType(""); + if (dropModelIndex.model() == this) { if (mimeData->hasFormat("application/vnd.bauhaus.itemlibraryinfo")) { handleItemLibraryItemDrop(mimeData, rowNumber, dropModelIndex); @@ -661,92 +666,46 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] { newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false); ModelNode newModelNode = newQmlObjectNode.modelNode(); - auto insertIntoList = [&](const QByteArray &listPropertyName, const ModelNode &targetNode) { - if (targetNode.isValid()) { - BindingProperty listProp = targetNode.bindingProperty(listPropertyName); - if (listProp.isValid()) { - QString expression = listProp.expression(); - int bracketIndex = expression.indexOf(']'); - if (expression.isEmpty()) - expression = newModelNode.validId(); - else if (bracketIndex == -1) - expression = QStringLiteral("[%1,%2]").arg(expression).arg(newModelNode.validId()); - else - expression.insert(bracketIndex, QStringLiteral(",%1").arg(newModelNode.validId())); - listProp.setExpression(expression); - } - } - }; if (newModelNode.isValid()) { - if (newModelNode.isSubclassOf("QtQuick3D.Effect")) { - // Insert effects dropped to either View3D or SceneEnvironment into the - // SceneEnvironment's effects list - ModelNode targetEnv; - if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.SceneEnvironment")) { - targetEnv = targetProperty.parentModelNode(); - validContainer = true; - } else if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.View3D")) { - // see if View3D has environment set to it - BindingProperty envNodeProp = targetProperty.parentModelNode().bindingProperty("environment"); - if (envNodeProp.isValid()) { - ModelNode envNode = envNodeProp.resolveToModelNode(); - if (envNode.isValid()) - targetEnv = envNode; - } - validContainer = true; - } - insertIntoList("effects", targetEnv); - } else if (newModelNode.isSubclassOf("QtQuick3D.Material")) { - if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Model")) { - // Insert material dropped to a model node into the materials list of the model - ModelNode targetModel; - targetModel = targetProperty.parentModelNode(); - insertIntoList("materials", targetModel); - validContainer = true; - } else if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Node") - && targetProperty.parentModelNode().isComponent()) { - // Inserting materials under imported components is likely a mistake, so - // notify user with a helpful messagebox that suggests the correct action. - showMatToCompInfo = true; - } - } else if (newModelNode.isSubclassOf("QtQuick3D.Shader") - || newModelNode.isSubclassOf("QtQuick3D.Command")) { - const bool isShader = newModelNode.isSubclassOf("QtQuick3D.Shader"); - if (isShader || newModelNode.isSubclassOf("QtQuick3D.Command")) { - if (targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Pass")) { - // Shaders and commands inserted into a Pass will be added to proper list. - // They are also moved to the same level as the pass, as passes don't - // allow child nodes (QTBUG-86219). - ModelNode targetModel; - targetModel = targetProperty.parentModelNode(); - if (isShader) - insertIntoList("shaders", targetModel); - else - insertIntoList("commands", targetModel); - NodeAbstractProperty parentProp = targetProperty.parentProperty(); - if (parentProp.isValid()) { - targetProperty = parentProp; - 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 - moveNodesAfter = false; - moveNodesInteractive(targetProperty, {newQmlObjectNode}, targetRowNumber, false); - validContainer = true; - } - } - } - } else { - ModelNode targetNode = targetProperty.parentModelNode(); - ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( - targetNode, newModelNode, Core::ICore::dialogParent()); - if (dialog) { + ModelNode targetNode = targetProperty.parentModelNode(); + ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded( + targetNode, newModelNode, Core::ICore::dialogParent()); + if (dialog) { + bool soloProperty = dialog->isSoloProperty(); + if (!soloProperty) dialog->exec(); - if (dialog->result() == QDialog::Accepted) + 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); + moveNodesAfter = false; + } + } + listProp.addModelNodeToArray(newModelNode); + validContainer = true; + } else { targetNode.bindingProperty(dialog->selectedProperty()).setExpression(newModelNode.validId()); - delete dialog; + validContainer = true; + } } + delete dialog; + } + if (newModelNode.isSubclassOf("QtQuick3D.Material") + && targetProperty.parentModelNode().isSubclassOf("QtQuick3D.Node") + && targetProperty.parentModelNode().isComponent()) { + // Inserting materials under imported components is likely a mistake, so + // notify user with a helpful messagebox that suggests the correct action. + showMatToCompInfo = true; } if (!validContainer) { diff --git a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp index 4e77b7d4b1d..00cd4758885 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp +++ b/src/plugins/qmldesigner/components/navigator/navigatorwidget.cpp @@ -201,4 +201,14 @@ void NavigatorWidget::dropEvent(QDropEvent *dropEvent) actionManager.handleExternalAssetsDrop(dropEvent->mimeData()); } +void NavigatorWidget::setDragType(const QByteArray &type) +{ + m_dragType = type; +} + +QByteArray NavigatorWidget::dragType() const +{ + return m_dragType; +} + } diff --git a/src/plugins/qmldesigner/components/navigator/navigatorwidget.h b/src/plugins/qmldesigner/components/navigator/navigatorwidget.h index d8dc0938566..785c3978f30 100644 --- a/src/plugins/qmldesigner/components/navigator/navigatorwidget.h +++ b/src/plugins/qmldesigner/components/navigator/navigatorwidget.h @@ -56,6 +56,9 @@ public: void disableNavigator(); void enableNavigator(); + void setDragType(const QByteArray &type); + QByteArray dragType() const; + signals: void leftButtonClicked(); void rightButtonClicked(); @@ -73,6 +76,7 @@ private: NavigatorTreeView *m_treeView; QPointer m_navigatorView; + QByteArray m_dragType; }; }