From 218c1e37699423cca7131ea32d97209bbc63980d Mon Sep 17 00:00:00 2001 From: Thomas Hartmann Date: Mon, 3 Jun 2019 15:43:16 +0200 Subject: [PATCH] QmlDesigner: Add multi-selection to property editor This implements basic multi selection for the property editor. The property editor shows the most common type. Values in the property editor show the values of the item that was selected first. Task-number: QDS-324 Change-Id: I5f03fa5aa9cfb0a0abaf285a29bf5f7e931635e5 Reviewed-by: Tim Jenssen --- .../QtQuick/ItemPane.qml | 4 + .../propertyeditorqmlbackend.cpp | 55 ++++++++++---- .../propertyeditor/propertyeditorqmlbackend.h | 7 +- .../propertyeditor/propertyeditorview.cpp | 73 ++++++++++++++----- .../propertyeditor/propertyeditorview.h | 3 + .../propertyeditor/qmlmodelnodeproxy.cpp | 31 ++++++++ .../propertyeditor/qmlmodelnodeproxy.h | 9 ++- 7 files changed, 145 insertions(+), 37 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml index 5c7f0302a39..2dbbbff76bb 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/QtQuick/ItemPane.qml @@ -76,6 +76,7 @@ Rectangle { typeLineEdit.forceActiveFocus() } tooltip: qsTr("Change the type of this item.") + enabled: !modelNodeBackend.multiSelection } ExpressionTextField { @@ -118,15 +119,18 @@ Rectangle { Layout.fillWidth: true showTranslateCheckBox: false showExtendedFunctionButton: false + enabled: !modelNodeBackend.multiSelection } // workaround: without this item the lineedit does not shrink to the // right size after resizing to a wider width Image { + visible: !modelNodeBackend.multiSelection Layout.preferredWidth: 16 Layout.preferredHeight: 16 source: hasAliasExport ? "image://icons/alias-export-checked" : "image://icons/alias-export-unchecked" ToolTipArea { + enabled: !modelNodeBackend.multiSelection anchors.fill: parent onClicked: toogleExportAlias() tooltip: qsTr("Toggles whether this item is exported as an alias property of the root item.") diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp index 284b6948ee0..40c5cfda6e9 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.cpp @@ -40,6 +40,7 @@ #include #include +#include #include #include @@ -281,13 +282,17 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q setupLayoutAttachedProperties(qmlObjectNode, propertyEditor); + // model node + m_backendModelNode.setup(qmlObjectNode.modelNode()); + context()->setContextProperty(QLatin1String("modelNodeBackend"), &m_backendModelNode); + // className auto valueObject = qobject_cast(variantToQObject(m_backendValuesPropertyMap.value(QLatin1String("className")))); if (!valueObject) valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); valueObject->setName("className"); valueObject->setModelNode(qmlObjectNode.modelNode()); - valueObject->setValue(qmlObjectNode.modelNode().simplifiedTypeName()); + valueObject->setValue(m_backendModelNode.simplifiedTypeName()); QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); m_backendValuesPropertyMap.insert(QLatin1String("className"), QVariant::fromValue(valueObject)); @@ -296,7 +301,7 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q if (!valueObject) valueObject = new PropertyEditorValue(&m_backendValuesPropertyMap); valueObject->setName("id"); - valueObject->setValue(qmlObjectNode.id()); + valueObject->setValue(m_backendModelNode.nodeId()); QObject::connect(valueObject, &PropertyEditorValue::valueChanged, &backendValuesPropertyMap(), &DesignerPropertyMap::valueChanged); m_backendValuesPropertyMap.insert(QLatin1String("id"), QVariant::fromValue(valueObject)); @@ -310,10 +315,6 @@ void PropertyEditorQmlBackend::setup(const QmlObjectNode &qmlObjectNode, const Q qCInfo(propertyEditorBenchmark) << "anchors:" << time.elapsed(); - // model node - m_backendModelNode.setup(qmlObjectNode.modelNode()); - context()->setContextProperty(QLatin1String("modelNodeBackend"), &m_backendModelNode); - qCInfo(propertyEditorBenchmark) << "context:" << time.elapsed(); contextObject()->setSpecificsUrl(qmlSpecificsFile); @@ -402,7 +403,7 @@ QString PropertyEditorQmlBackend::propertyEditorResourcesPath() { QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type, const NodeMetaInfo &superType, - const QmlObjectNode &objectNode) + const QmlObjectNode &node) { if (!templateConfiguration() || !templateConfiguration()->isValid()) return QString(); @@ -411,7 +412,7 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type, QString qmlTemplate = imports.join(QLatin1Char('\n')) + QLatin1Char('\n'); qmlTemplate += QStringLiteral("Section {\n"); - qmlTemplate += QStringLiteral("caption: \"%1\"\n").arg(objectNode.modelNode().simplifiedTypeName()); + qmlTemplate += QStringLiteral("caption: \"%1\"\n").arg(QString::fromUtf8(type.simplifiedTypeName())); qmlTemplate += QStringLiteral("SectionLayout {\n"); QList orderedList = type.propertyNames(); @@ -429,8 +430,8 @@ QString PropertyEditorQmlBackend::templateGeneration(const NodeMetaInfo &type, TypeName typeName = type.propertyTypeName(name); //alias resolution only possible with instance - if (typeName == "alias" && objectNode.isValid()) - typeName = objectNode.instanceType(name); + if (typeName == "alias" && node.isValid()) + typeName = node.instanceType(name); if (!superType.hasProperty(name) && type.propertyIsWritable(name) && !name.contains(".")) { foreach (const QmlJS::SimpleReaderNode::Ptr &node, templateConfiguration()->children()) @@ -469,6 +470,34 @@ TypeName PropertyEditorQmlBackend::fixTypeNameForPanes(const TypeName &typeName) return fixedTypeName; } +static NodeMetaInfo findCommonSuperClass(const NodeMetaInfo &first, const NodeMetaInfo &second) +{ + for (const NodeMetaInfo &info : first.superClasses()) { + if (second.isSubclassOf(info.typeName())) + return info; + } + return first; +} + +NodeMetaInfo PropertyEditorQmlBackend::findCommonAncestor(const ModelNode &node) +{ + QTC_ASSERT(node.isValid(), return {}); + QTC_ASSERT(node.metaInfo().isValid(), return {}); + + AbstractView *view = node.view(); + + if (view->selectedModelNodes().count() > 1) { + NodeMetaInfo commonClass = node.metaInfo(); + for (const ModelNode ¤tNode : view->selectedModelNodes()) { + if (currentNode.metaInfo().isValid() && !currentNode.isSubclassOf(commonClass.typeName(), -1, -1)) + commonClass = findCommonSuperClass(currentNode.metaInfo(), commonClass); + } + return commonClass; + } + + return node.metaInfo(); +} + TypeName PropertyEditorQmlBackend::qmlFileName(const NodeMetaInfo &nodeInfo) { const TypeName fixedTypeName = fixTypeNameForPanes(nodeInfo.typeName()); @@ -526,10 +555,10 @@ void PropertyEditorQmlBackend::setValueforLayoutAttachedProperties(const QmlObje setValue(qmlObjectNode, name, properDefaultLayoutAttachedProperties(qmlObjectNode, propertyName)); } -QUrl PropertyEditorQmlBackend::getQmlUrlForModelNode(const ModelNode &modelNode, TypeName &className) +QUrl PropertyEditorQmlBackend::getQmlUrlForMetaInfo(const NodeMetaInfo &metaInfo, TypeName &className) { - if (modelNode.isValid()) { - foreach (const NodeMetaInfo &info, modelNode.metaInfo().classHierarchy()) { + if (metaInfo.isValid()) { + foreach (const NodeMetaInfo &info, metaInfo.classHierarchy()) { QUrl fileUrl = fileToUrl(locateQmlFile(info, QString::fromUtf8(qmlFileName(info)))); if (fileUrl.isValid()) { className = info.typeName(); diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h index a0012a1cc17..51279a1fc64 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorqmlbackend.h @@ -68,11 +68,10 @@ public: PropertyEditorValue *propertyValueForName(const QString &propertyName); static QString propertyEditorResourcesPath(); - static QString templateGeneration(const NodeMetaInfo &type, const NodeMetaInfo &superType, - const QmlObjectNode &objectNode); + static QString templateGeneration(const NodeMetaInfo &type, const NodeMetaInfo &superType, const QmlObjectNode &node); static QUrl getQmlFileUrl(const TypeName &relativeTypeName, const NodeMetaInfo &info = NodeMetaInfo()); - static QUrl getQmlUrlForModelNode(const ModelNode &modelNode, TypeName &className); + static QUrl getQmlUrlForMetaInfo(const NodeMetaInfo &modelNode, TypeName &className); static bool checkIfUrlExists(const QUrl &url); @@ -83,6 +82,8 @@ public: void setupLayoutAttachedProperties(const QmlObjectNode &qmlObjectNode, PropertyEditorView *propertyEditor); + static NodeMetaInfo findCommonAncestor(const ModelNode &node); + private: void createPropertyEditorValue(const QmlObjectNode &qmlObjectNode, const PropertyName &name, const QVariant &value, diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp index 0467355bf43..d0c98c896ff 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.cpp @@ -213,20 +213,13 @@ void PropertyEditorView::changeValue(const QString &name) castedValue = QVariant(newColor); } - try { - if (!value->value().isValid()) { //reset - qmlObjectNode.removeProperty(propertyName); - } else { - if (castedValue.isValid() && !castedValue.isNull()) { - m_locked = true; - qmlObjectNode.setVariantProperty(propertyName, castedValue); - m_locked = false; - } + if (!value->value().isValid()) { //reset + removePropertyFromModel(propertyName); + } else { + if (castedValue.isValid() && !castedValue.isNull()) { + commitVariantValueToModel(propertyName, castedValue); } } - catch (const RewritingException &e) { - e.showException(); - } } void PropertyEditorView::changeExpression(const QString &propertyName) @@ -446,13 +439,16 @@ void PropertyEditorView::resetView() void PropertyEditorView::setupQmlBackend() { TypeName specificsClassName; - QUrl qmlFile(PropertyEditorQmlBackend::getQmlUrlForModelNode(m_selectedNode, specificsClassName)); + + const NodeMetaInfo commonAncestor = PropertyEditorQmlBackend::findCommonAncestor(m_selectedNode); + + const QUrl qmlFile(PropertyEditorQmlBackend::getQmlUrlForMetaInfo(commonAncestor, specificsClassName)); QUrl qmlSpecificsFile; TypeName diffClassName; - if (m_selectedNode.isValid()) { - diffClassName = m_selectedNode.metaInfo().typeName(); - foreach (const NodeMetaInfo &metaInfo, m_selectedNode.metaInfo().classHierarchy()) { + if (commonAncestor.isValid()) { + diffClassName = commonAncestor.typeName(); + foreach (const NodeMetaInfo &metaInfo, commonAncestor.classHierarchy()) { if (PropertyEditorQmlBackend::checkIfUrlExists(qmlSpecificsFile)) break; qmlSpecificsFile = PropertyEditorQmlBackend::getQmlFileUrl(metaInfo.typeName() + "Specifics", metaInfo); @@ -465,8 +461,8 @@ void PropertyEditorView::setupQmlBackend() QString specificQmlData; - if (m_selectedNode.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type()) - specificQmlData = PropertyEditorQmlBackend::templateGeneration(m_selectedNode.metaInfo(), model()->metaInfo(diffClassName), m_selectedNode); + if (commonAncestor.isValid() && m_selectedNode.metaInfo().isValid() && diffClassName != m_selectedNode.type()) + specificQmlData = PropertyEditorQmlBackend::templateGeneration(commonAncestor, model()->metaInfo(diffClassName), m_selectedNode); PropertyEditorQmlBackend *currentQmlBackend = m_qmlBackendHash.value(qmlFile.toString()); @@ -515,14 +511,51 @@ void PropertyEditorView::setupQmlBackend() } +void PropertyEditorView::commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value) +{ + m_locked = true; + try { + RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::commitVariantValueToMode"); + + for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) { + if (QmlObjectNode::isValidQmlObjectNode(node)) + QmlObjectNode(node).setVariantProperty(propertyName, value); + } + transaction.commit(); + } + catch (const RewritingException &e) { + e.showException(); + } + m_locked = false; +} + +void PropertyEditorView::removePropertyFromModel(const PropertyName &propertyName) +{ + m_locked = true; + try { + RewriterTransaction transaction = beginRewriterTransaction("PropertyEditorView::removePropertyFromModel"); + + for (const ModelNode &node : m_selectedNode.view()->selectedModelNodes()) { + if (QmlObjectNode::isValidQmlObjectNode(node)) + QmlObjectNode(node).removeProperty(propertyName); + } + + transaction.commit(); + } + catch (const RewritingException &e) { + e.showException(); + } + m_locked = false; +} + void PropertyEditorView::selectedNodesChanged(const QList &selectedNodeList, const QList &lastSelectedNodeList) { Q_UNUSED(lastSelectedNodeList); - if (selectedNodeList.isEmpty() || selectedNodeList.count() > 1) + if (selectedNodeList.isEmpty()) select(ModelNode()); - else if (m_selectedNode != selectedNodeList.constFirst()) + else select(selectedNodeList.constFirst()); } diff --git a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h index 85bd8286f52..e7f57cf1868 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h +++ b/src/plugins/qmldesigner/components/propertyeditor/propertyeditorview.h @@ -110,6 +110,9 @@ private: //functions void delayedResetView(); void setupQmlBackend(); + void commitVariantValueToModel(const PropertyName &propertyName, const QVariant &value); + void removePropertyFromModel(const PropertyName &propertyName); + private: //variables ModelNode m_selectedNode; QWidget *m_parent; diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp index 6f56b055c88..934c2846911 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.cpp @@ -23,6 +23,7 @@ ** ****************************************************************************/ +#include "abstractview.h" #include "qmlmodelnodeproxy.h" #include @@ -66,4 +67,34 @@ ModelNode QmlModelNodeProxy::modelNode() const return m_qmlItemNode.modelNode(); } +bool QmlModelNodeProxy::multiSelection() const +{ + if (!m_qmlItemNode.isValid()) + return false; + + return m_qmlItemNode.view()->selectedModelNodes().count() > 1; +} + +QString QmlModelNodeProxy::nodeId() const +{ + if (!m_qmlItemNode.isValid()) + return {}; + + if (multiSelection()) + return tr("multiselection"); + + return m_qmlItemNode.id(); +} + +QString QmlModelNodeProxy::simplifiedTypeName() const +{ + if (!m_qmlItemNode.isValid()) + return {}; + + if (multiSelection()) + return tr("multiselection"); + + return m_qmlItemNode.simplifiedTypeName(); +} + } diff --git a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h index 0a735833552..6037f32752a 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h +++ b/src/plugins/qmldesigner/components/propertyeditor/qmlmodelnodeproxy.h @@ -35,7 +35,8 @@ class QmlModelNodeProxy : public QObject { Q_OBJECT - Q_PROPERTY(QmlDesigner::ModelNode modelNode READ modelNode NOTIFY modelNodeChanged) + Q_PROPERTY(QmlDesigner::ModelNode modelNode READ modelNode NOTIFY modelNodeChanged) + Q_PROPERTY(bool multiSelection READ multiSelection NOTIFY modelNodeChanged) public: explicit QmlModelNodeProxy(QObject *parent = nullptr); @@ -51,6 +52,12 @@ public: ModelNode modelNode() const; + bool multiSelection() const; + + QString nodeId() const; + + QString simplifiedTypeName() const; + signals: void modelNodeChanged(); void selectionToBeChanged();