forked from qt-creator/qt-creator
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:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
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());
|
||||
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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user