QmlDesigner: Edit drag and drop binding and add navigator item highlight

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 <miikka.heikkinen@qt.io>
Reviewed-by: Tomi Korpipää <tomi.korpipaa@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Tony Leinonen
2021-12-09 13:54:18 +02:00
parent 7677dc4ba0
commit 13789ce2f9
7 changed files with 150 additions and 106 deletions

View File

@@ -41,6 +41,7 @@
#include "modelnodeoperations.h"
#include <metainfo.h>
#include <model.h>
#include <navigatorwidget.h>
#include <rewritingexception.h>
#include <qmldesignerconstants.h>
#include <qmldesignerplugin.h>
@@ -117,7 +118,14 @@ bool ItemLibraryWidget::eventFilter(QObject *obj, QEvent *event)
}
}
}
QWidget *view = QmlDesignerPlugin::instance()->viewManager().widget("Navigator");
if (view) {
NavigatorWidget *navView = qobject_cast<NavigatorWidget *>(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<NavigatorWidget *>(view);
if (navView) {
navView->setDragType("");
navView->update();
}
}
}
return QObject::eventFilter(obj, event);

View File

@@ -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<TypeName> ignoredTypes {"<cpp>.QObject",
"<cpp>.QQuickItem",
"<cpp>.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("<cpp>.", 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<TypeName> ignoredTypes {"<cpp>.QObject",
"<cpp>.QQuickItem",
"<cpp>.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;
}

View File

@@ -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;
};
}

View File

@@ -29,6 +29,8 @@
#include "navigatorview.h"
#include "navigatortreeview.h"
#include "navigatorwidget.h"
#include "choosefrompropertylistdialog.h"
#include "qproxystyle.h"
#include <metainfo.h>
@@ -230,6 +232,24 @@ void NameItemDelegate::paint(QPainter *painter,
painter->setPen(Theme::getColor(Theme::Color::DSnavigatorTextSelected));
}
ModelNode node = getModelNode(modelIndex);
NavigatorWidget *widget = qobject_cast<NavigatorWidget *>(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))

View File

@@ -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<NavigatorWidget *>(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) {

View File

@@ -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;
}
}

View File

@@ -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<NavigatorView> m_navigatorView;
QByteArray m_dragType;
};
}