diff options
author | Henning Gruendl <henning.gruendl@qt.io> | 2020-08-18 17:52:44 +0200 |
---|---|---|
committer | Henning Gründl <henning.gruendl@qt.io> | 2020-09-16 10:14:56 +0000 |
commit | 513d1e7e02c1cc48daa74522f837aa0c0e2c716b (patch) | |
tree | f7fa25049c1da3cc29dc3b5f9ddc5168ec5298f5 | |
parent | a489fc18eb89fbb3fd8f80a92ec91abbde64b4a9 (diff) |
QmlDesigner: Improve connection editor dialog
* Add ComboBoxes to connection editor dialog
* Add type toggle to dialog
* Add slots and properties to ComboBoxes
* Parse connection expression and fill ComboBoxes
Task-number: QDS-2498
Task-number: QDS-2495
Task-number: QDS-2496
Change-Id: I2cca6d4c85d1508e54d4ad8863056f22ad777ae6
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
16 files changed, 1526 insertions, 233 deletions
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 6c0e25c37a..0938acb5b5 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -586,8 +586,11 @@ extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/bindingeditor SOURCES bindingeditor.cpp bindingeditor.h actioneditor.cpp actioneditor.h + abstracteditordialog.cpp abstracteditordialog.h + actioneditordialog.cpp actioneditordialog.h bindingeditordialog.cpp bindingeditordialog.h bindingeditorwidget.cpp bindingeditorwidget.h + connectionvisitor.cpp connectionvisitor.h ) extend_qtc_plugin(QmlDesigner diff --git a/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp new file mode 100644 index 0000000000..872a004fcc --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.cpp @@ -0,0 +1,183 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "abstracteditordialog.h" + +#include <texteditor/texteditor.h> + +#include <qmldesigner/qmldesignerplugin.h> +#include <qmljseditor/qmljseditor.h> +#include <qmljseditor/qmljseditordocument.h> +#include <texteditor/textdocument.h> + +#include <QDialogButtonBox> +#include <QPushButton> +#include <QVBoxLayout> +#include <QHBoxLayout> +#include <QPlainTextEdit> + +namespace QmlDesigner { + +AbstractEditorDialog::AbstractEditorDialog(QWidget *parent, const QString &title) + : QDialog(parent) + , m_titleString(title) +{ + setWindowFlag(Qt::Tool, true); + setWindowTitle(defaultTitle()); + setModal(false); + + setupJSEditor(); + setupUIComponents(); + + QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, + this, &AbstractEditorDialog::accepted); + QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, + this, &AbstractEditorDialog::rejected); + QObject::connect(m_editorWidget, &BindingEditorWidget::returnKeyClicked, + this, &AbstractEditorDialog::accepted); + QObject::connect(m_editorWidget, &QPlainTextEdit::textChanged, + this, &AbstractEditorDialog::textChanged); +} + +AbstractEditorDialog::~AbstractEditorDialog() +{ + delete m_editor; // m_editorWidget is handled by basetexteditor destructor + delete m_buttonBox; + delete m_comboBoxLayout; + delete m_verticalLayout; +} + +void AbstractEditorDialog::showWidget() +{ + this->show(); + this->raise(); + m_editorWidget->setFocus(); +} + +void AbstractEditorDialog::showWidget(int x, int y) +{ + showWidget(); + move(QPoint(x, y)); +} + +QString AbstractEditorDialog::editorValue() const +{ + if (!m_editorWidget) + return {}; + + return m_editorWidget->document()->toPlainText(); +} + +void AbstractEditorDialog::setEditorValue(const QString &text) +{ + if (m_editorWidget) + m_editorWidget->document()->setPlainText(text); +} + +void AbstractEditorDialog::unregisterAutoCompletion() +{ + if (m_editorWidget) + m_editorWidget->unregisterAutoCompletion(); +} + +QString AbstractEditorDialog::defaultTitle() const +{ + return m_titleString; +} + +void AbstractEditorDialog::setupJSEditor() +{ + static BindingEditorFactory f; + m_editor = qobject_cast<TextEditor::BaseTextEditor*>(f.createEditor()); + m_editorWidget = qobject_cast<BindingEditorWidget*>(m_editor->editorWidget()); + + Core::Context context = m_editor->context(); + context.prepend(BINDINGEDITOR_CONTEXT_ID); + m_editorWidget->m_context->setContext(context); + + auto qmlDesignerEditor = QmlDesignerPlugin::instance()->currentDesignDocument()->textEditor(); + + m_editorWidget->qmljsdocument = qobject_cast<QmlJSEditor::QmlJSEditorWidget *>( + qmlDesignerEditor->widget())->qmlJsEditorDocument(); + + m_editorWidget->setLineNumbersVisible(false); + m_editorWidget->setMarksVisible(false); + m_editorWidget->setCodeFoldingSupported(false); + m_editorWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_editorWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_editorWidget->setTabChangesFocus(true); +} + +void AbstractEditorDialog::setupUIComponents() +{ + m_verticalLayout = new QVBoxLayout(this); + + m_comboBoxLayout = new QHBoxLayout; + + m_editorWidget->setParent(this); + m_editorWidget->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); + m_editorWidget->show(); + + m_buttonBox = new QDialogButtonBox(this); + m_buttonBox->setOrientation(Qt::Horizontal); + m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + + m_verticalLayout->addLayout(m_comboBoxLayout); + m_verticalLayout->addWidget(m_editorWidget); + m_verticalLayout->addWidget(m_buttonBox); + + this->resize(660, 240); +} + +bool AbstractEditorDialog::isNumeric(const TypeName &type) +{ + static QList<TypeName> numericTypes = {"double", "int", "real"}; + return numericTypes.contains(type); +} + +bool AbstractEditorDialog::isColor(const TypeName &type) +{ + static QList<TypeName> colorTypes = {"QColor", "color"}; + return colorTypes.contains(type); +} + +bool AbstractEditorDialog::isVariant(const TypeName &type) +{ + static QList<TypeName> variantTypes = {"alias", "unknown", "variant", "var"}; + return variantTypes.contains(type); +} + +void AbstractEditorDialog::textChanged() +{ + if (m_lock) + return; + + m_lock = true; + adjustProperties(); + m_lock = false; +} + +} // QmlDesigner namespace diff --git a/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.h b/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.h new file mode 100644 index 0000000000..ed8cdd0a13 --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/abstracteditordialog.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef ABSTRACTEDITORDIALOG_H +#define ABSTRACTEDITORDIALOG_H + +#include <bindingeditor/bindingeditorwidget.h> +#include <qmldesignercorelib_global.h> +#include <texteditor/texteditor.h> + +#include <QDialog> + +QT_BEGIN_NAMESPACE +class QDialogButtonBox; +class QVBoxLayout; +class QHBoxLayout; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class AbstractEditorDialog : public QDialog +{ + Q_OBJECT + +public: + AbstractEditorDialog(QWidget *parent = nullptr, const QString &title = tr("Untitled Editor")); + ~AbstractEditorDialog() override; + + void showWidget(); + void showWidget(int x, int y); + + QString editorValue() const; + void setEditorValue(const QString &text); + + virtual void adjustProperties()= 0; + + void unregisterAutoCompletion(); + + QString defaultTitle() const; + + BindingEditorWidget *bindingEditorWidget() const + { + return m_editorWidget; + } + +protected: + void setupJSEditor(); + void setupUIComponents(); + + static bool isNumeric(const TypeName &type); + static bool isColor(const TypeName &type); + static bool isVariant(const TypeName &type); + +public slots: + void textChanged(); + +protected: + TextEditor::BaseTextEditor *m_editor = nullptr; + BindingEditorWidget *m_editorWidget = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; + QDialogButtonBox *m_buttonBox = nullptr; + QHBoxLayout *m_comboBoxLayout = nullptr; + bool m_lock = false; + QString m_titleString; + + const QString undefinedString = {"[Undefined]"}; +}; + +} + +#endif //ABSTRACTEDITORDIALOG_H diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp index ac86377816..486e33640d 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.cpp @@ -28,6 +28,7 @@ #include <qmldesignerplugin.h> #include <coreplugin/icore.h> #include <coreplugin/actionmanager/actionmanager.h> +#include <bindingeditor/actioneditordialog.h> #include <metainfo.h> #include <qmlmodelnodeproxy.h> @@ -35,6 +36,14 @@ #include <nodelistproperty.h> #include <propertyeditorvalue.h> +#include <bindingproperty.h> +#include <variantproperty.h> + +#include <qmljs/qmljsscopechain.h> +#include <qmljs/qmljsvalueowner.h> + +static Q_LOGGING_CATEGORY(ceLog, "qtc.qmldesigner.connectioneditor", QtWarningMsg) + namespace QmlDesigner { static ActionEditor *s_lastActionEditor = nullptr; @@ -58,15 +67,14 @@ void ActionEditor::prepareDialog() { if (s_lastActionEditor) s_lastActionEditor->hideWidget(); - s_lastActionEditor = this; - m_dialog = new BindingEditorDialog(Core::ICore::dialogParent(), - BindingEditorDialog::DialogType::ActionDialog); + s_lastActionEditor = this; + m_dialog = new ActionEditorDialog(Core::ICore::dialogParent()); - QObject::connect(m_dialog, &BindingEditorDialog::accepted, + QObject::connect(m_dialog, &AbstractEditorDialog::accepted, this, &ActionEditor::accepted); - QObject::connect(m_dialog, &BindingEditorDialog::rejected, + QObject::connect(m_dialog, &AbstractEditorDialog::rejected, this, &ActionEditor::rejected); m_dialog->setAttribute(Qt::WA_DeleteOnClose); @@ -88,14 +96,14 @@ void ActionEditor::hideWidget() { if (s_lastActionEditor == this) s_lastActionEditor = nullptr; - if (m_dialog) - { - m_dialog->unregisterAutoCompletion(); //we have to do it separately, otherwise we have an autocompletion action override + + if (m_dialog) { + m_dialog->unregisterAutoCompletion(); // we have to do it separately, otherwise we have an autocompletion action override m_dialog->close(); } } -QString ActionEditor::bindingValue() const +QString ActionEditor::connectionValue() const { if (!m_dialog) return {}; @@ -103,7 +111,7 @@ QString ActionEditor::bindingValue() const return m_dialog->editorValue(); } -void ActionEditor::setBindingValue(const QString &text) +void ActionEditor::setConnectionValue(const QString &text) { if (m_dialog) m_dialog->setEditorValue(text); @@ -129,11 +137,160 @@ void ActionEditor::setModelIndex(const QModelIndex &index) m_index = index; } +void ActionEditor::setModelNode(const ModelNode &modelNode) +{ + if (modelNode.isValid()) + m_modelNode = modelNode; +} + +bool isLiteral(QmlJS::AST::Node *ast) +{ + if (QmlJS::AST::cast<QmlJS::AST::StringLiteral *>(ast) + || QmlJS::AST::cast<QmlJS::AST::NumericLiteral *>(ast) + || QmlJS::AST::cast<QmlJS::AST::TrueLiteral *>(ast) + || QmlJS::AST::cast<QmlJS::AST::FalseLiteral *>(ast)) + return true; + else + return false; +} + +void ActionEditor::prepareConnections() +{ + if (!m_modelNode.isValid()) + return; + + BindingEditorWidget *bindingEditorWidget = m_dialog->bindingEditorWidget(); + + if (!bindingEditorWidget) { + qCInfo(ceLog) << Q_FUNC_INFO << "BindingEditorWidget is missing!"; + return; + } + + if (!bindingEditorWidget->qmlJsEditorDocument()) { + qCInfo(ceLog) << Q_FUNC_INFO << "QmlJsEditorDocument is missing!"; + return; + } + + // Prepare objects for analysing slots + const QmlJSTools::SemanticInfo &semanticInfo = bindingEditorWidget->qmljsdocument->semanticInfo(); + const QList<QmlJS::AST::Node *> path = semanticInfo.rangePath(0); + const QmlJS::ContextPtr &context = semanticInfo.context; + const QmlJS::ScopeChain &scopeChain = semanticInfo.scopeChain(path); + + static QList<TypeName> typeWhiteList({"string", + "real", "int", "double", + "bool", + "QColor", "color", + "QtQuick.Item", "QQuickItem"}); + + static QList<PropertyName> methodBlackList({"toString", "destroy"}); + + QList<ActionEditorDialog::ConnectionOption> connections; + QList<ActionEditorDialog::SingletonOption> singletons; + QStringList states; + + const QList<QmlDesigner::ModelNode> allNodes = m_modelNode.view()->allModelNodes(); + for (const auto &modelNode : allNodes) { + // Skip nodes without specified id + if (!modelNode.hasId()) + continue; + + ActionEditorDialog::ConnectionOption connection(modelNode.id()); + + for (const auto &propertyName : modelNode.metaInfo().propertyNames()) { + if (!typeWhiteList.contains(modelNode.metaInfo().propertyTypeName(propertyName))) + continue; + + const QString name = QString::fromUtf8(propertyName); + TypeName type = modelNode.metaInfo().propertyTypeName(propertyName); + if (type.contains("<cpp>.")) + type.remove(0, 6); + + connection.properties.append(ActionEditorDialog::PropertyOption(name, type)); + } + + for (const VariantProperty &variantProperty : modelNode.variantProperties()) { + if (variantProperty.isValid()) { + if (variantProperty.isDynamic()) { + if (!typeWhiteList.contains(variantProperty.dynamicTypeName())) + continue; + + const QString name = QString::fromUtf8(variantProperty.name()); + TypeName type = variantProperty.dynamicTypeName(); + if (type.contains("<cpp>.")) + type.remove(0, 6); + + connection.properties.append(ActionEditorDialog::PropertyOption(name, type)); + } + } + } + + for (const auto &slotName : modelNode.metaInfo().slotNames()) { + QmlJS::Document::MutablePtr newDoc = QmlJS::Document::create( + QLatin1String("<expression>"), QmlJS::Dialect::JavaScript); + newDoc->setSource(QLatin1String(slotName)); + newDoc->parseExpression(); + + QmlJS::AST::ExpressionNode *expression = newDoc->expression(); + + if (expression && !isLiteral(expression)) { + QmlJS::ValueOwner *interp = context->valueOwner(); + const QmlJS::Value *value = interp->convertToObject(scopeChain.evaluate(expression)); + + if (const QmlJS::FunctionValue *f = value->asFunctionValue()) { + // Only add slots with zero arguments + if (f->namedArgumentCount() == 0 && !methodBlackList.contains(slotName)) + connection.methods.append(QString::fromUtf8(slotName)); + } + } + } + + connection.methods.removeDuplicates(); + connections.append(connection); + } + + // Singletons + if (RewriterView *rv = m_modelNode.view()->rewriterView()) { + for (const QmlTypeData &data : rv->getQMLTypes()) { + if (!data.typeName.isEmpty()) { + NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo(data.typeName.toUtf8()); + if (metaInfo.isValid()) { + ActionEditorDialog::SingletonOption singelton; + for (const PropertyName &propertyName : metaInfo.propertyNames()) { + TypeName type = metaInfo.propertyTypeName(propertyName); + + if (!typeWhiteList.contains(type)) + continue; + + const QString name = QString::fromUtf8(propertyName); + if (type.contains("<cpp>.")) + type.remove(0, 6); + + singelton.properties.append(ActionEditorDialog::PropertyOption(name, type)); + } + + if (!singelton.properties.isEmpty()) { + singelton.item = data.typeName; + singletons.append(singelton); + } + } + } + } + } + + // States + for (const QmlModelState &state : QmlItemNode(m_modelNode).states().allStates()) + states.append(state.name()); + + + if (!connections.isEmpty() && !m_dialog.isNull()) + m_dialog->setAllConnections(connections, singletons, states); +} + void ActionEditor::updateWindowName() { - if (!m_dialog.isNull()) - { - m_dialog->setWindowTitle(tr("Connection Editor")); + if (!m_dialog.isNull()) { + m_dialog->setWindowTitle(m_dialog->defaultTitle()); m_dialog->raise(); } } diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h index 7c53dffb60..c0356e81c4 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditor.h @@ -26,7 +26,7 @@ #ifndef ACTIONEDITOR_H #define ACTIONEDITOR_H -#include <bindingeditor/bindingeditordialog.h> +#include <bindingeditor/actioneditordialog.h> #include <qmldesignercorelib_global.h> #include <modelnode.h> @@ -40,7 +40,7 @@ class ActionEditor : public QObject { Q_OBJECT - Q_PROPERTY(QString text READ bindingValue WRITE setBindingValue) + Q_PROPERTY(QString text READ connectionValue WRITE setConnectionValue) public: ActionEditor(QObject *parent = nullptr); @@ -52,14 +52,18 @@ public: Q_INVOKABLE void showWidget(int x, int y); Q_INVOKABLE void hideWidget(); - QString bindingValue() const; - void setBindingValue(const QString &text); + QString connectionValue() const; + void setConnectionValue(const QString &text); bool hasModelIndex() const; void resetModelIndex(); QModelIndex modelIndex() const; void setModelIndex(const QModelIndex &index); + void setModelNode(const ModelNode &modelNode); + + void prepareConnections(); + Q_INVOKABLE void updateWindowName(); signals: @@ -67,14 +71,12 @@ signals: void rejected(); private: - QVariant backendValue() const; - QVariant modelNodeBackend() const; - QVariant stateModelNode() const; void prepareDialog(); private: - QPointer<BindingEditorDialog> m_dialog; + QPointer<ActionEditorDialog> m_dialog; QModelIndex m_index; + QmlDesigner::ModelNode m_modelNode; }; } diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp new file mode 100644 index 0000000000..bcd99de716 --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.cpp @@ -0,0 +1,656 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "actioneditordialog.h" + +#include "connectionvisitor.h" + +#include <texteditor/texteditor.h> + +#include <qmldesigner/qmldesignerplugin.h> +#include <qmljseditor/qmljseditor.h> +#include <qmljseditor/qmljseditordocument.h> +#include <texteditor/textdocument.h> + +#include <QDialogButtonBox> +#include <QPushButton> +#include <QHBoxLayout> +#include <QComboBox> +#include <QPlainTextEdit> + +static Q_LOGGING_CATEGORY(ceLog, "qtc.qmldesigner.connectioneditor", QtWarningMsg) + +namespace QmlDesigner { + +ActionEditorDialog::ActionEditorDialog(QWidget *parent) + : AbstractEditorDialog(parent, tr("Connection Editor")) +{ + setupUIComponents(); + + QObject::connect(m_comboBoxType, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::Type); }); + + // Action connections + QObject::connect(m_actionTargetItem, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::TargetItem); }); + QObject::connect(m_actionMethod, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::TargetProperty); }); + + // Assignment connections + QObject::connect(m_assignmentTargetItem, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::TargetItem); }); + QObject::connect(m_assignmentTargetProperty, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::TargetProperty); }); + QObject::connect(m_assignmentSourceItem, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::SourceItem); }); + QObject::connect(m_assignmentSourceProperty, QOverload<int>::of(&QComboBox::activated), + [this] (int idx) { this->updateComboBoxes(idx, ComboBox::SourceProperty); }); +} + +ActionEditorDialog::~ActionEditorDialog() +{ +} + +void ActionEditorDialog::adjustProperties() +{ + // Analyze the current connection editor statement/expression + const auto qmlJSDocument = bindingEditorWidget()->qmlJsEditorDocument(); + auto doc = QmlJS::Document::create(QLatin1String("<expression>"), QmlJS::Dialect::JavaScript); + doc->setSource(qmlJSDocument->plainText()); + bool parseResult = doc->parseExpression(); + + if (!parseResult) { + qCInfo(ceLog) << Q_FUNC_INFO << "Couldn't parse the expression!"; + return; + } + + auto astNode = doc->ast(); + if (!astNode) { + qCInfo(ceLog) << Q_FUNC_INFO << "There was no AST::Node in the document!"; + return; + } + + ConnectionVisitor qmlVisitor; + QmlJS::AST::Node::accept(astNode, &qmlVisitor); + + const auto expression = qmlVisitor.expression(); + + if (expression.isEmpty()) { + // Set all ComboBoxes to [Undefined], add connections to target item ComboBox + fillAndSetTargetItem(undefinedString); + fillAndSetTargetProperty(undefinedString); + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(undefinedString); + return; + } + + bool typeDone = false; + bool targetDone = false; + + for (int i = 0; i < expression.count(); ++i) { + switch (expression[i].first) { + + case QmlJS::AST::Node::Kind::Kind_CallExpression: + { + setType(ConnectionType::Action); + typeDone = true; + } + break; + + case QmlJS::AST::Node::Kind::Kind_BinaryExpression: + { + setType(ConnectionType::Assignment); + typeDone = true; + } + break; + + case QmlJS::AST::Node::Kind::Kind_FieldMemberExpression: + { + QString fieldMember = expression[i].second; + ++i;// Increment index to get IdentifierExpression or next FieldMemberExpression + while (expression[i].first == QmlJS::AST::Node::Kind::Kind_FieldMemberExpression) { + fieldMember.prepend(expression[i].second + "."); + ++i; // Increment index to get IdentifierExpression + } + + if (targetDone && m_comboBoxType->currentIndex() != ConnectionType::Action) { + fillAndSetSourceItem(expression[i].second); + fillAndSetSourceProperty(fieldMember); + } else { + if (typeDone) { + fillAndSetTargetItem(expression[i].second); + fillAndSetTargetProperty(fieldMember); + } else { // e.g. 'element.width' + // In this case Assignment is more likley + setType(ConnectionType::Assignment); + fillAndSetTargetItem(expression[i].second); + fillAndSetTargetProperty(fieldMember); + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(undefinedString); + } + targetDone = true; + } + + } + break; + + case QmlJS::AST::Node::Kind::Kind_TrueLiteral: + case QmlJS::AST::Node::Kind::Kind_FalseLiteral: + case QmlJS::AST::Node::Kind::Kind_NumericLiteral: + case QmlJS::AST::Node::Kind::Kind_StringLiteral: + { + if (targetDone) { + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(expression[i].second, expression[i].first); + } else { + // In this case Assignment is more likley + setType(ConnectionType::Assignment); + fillAndSetTargetItem(undefinedString); + fillAndSetTargetProperty(undefinedString); + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(expression[i].second, expression[i].first); + } + } + break; + + case QmlJS::AST::Node::Kind::Kind_IdentifierExpression: + { + if (typeDone) { + if (m_comboBoxType->currentIndex() == ConnectionType::Assignment) { // e.g. 'element = rectangle + if (targetDone) { + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(undefinedString); + } else { + fillAndSetTargetItem(expression[i].second); + fillAndSetTargetProperty(undefinedString); + targetDone = true; + } + } else { // e.g. 'print("blabla")' + fillAndSetTargetItem(undefinedString); + fillAndSetTargetProperty(undefinedString); + targetDone = true; + } + } else { // e.g. 'element' + // In this case Assignment is more likley + setType(ConnectionType::Assignment); + fillAndSetTargetItem(expression[i].second); + fillAndSetTargetProperty(undefinedString); + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(undefinedString); + } + } + break; + + default: + { + fillAndSetTargetItem(undefinedString); + fillAndSetTargetProperty(undefinedString); + fillAndSetSourceItem(undefinedString); + fillAndSetSourceProperty(undefinedString); + } + break; + } + } +} + +void ActionEditorDialog::setAllConnections(const QList<ConnectionOption> &connections, + const QList<SingletonOption> &singletons, + const QStringList &states) +{ + m_lock = true; + + m_connections = connections; + m_singletons = singletons; + m_states = states; + adjustProperties(); + + m_lock = false; +} + +void ActionEditorDialog::updateComboBoxes(int index, ComboBox type) +{ + Q_UNUSED(index) + + const int currentType = m_comboBoxType->currentIndex(); + const int currentStack = m_stackedLayout->currentIndex(); + bool typeChanged = false; + + if (type == ComboBox::Type) { + if (currentType != currentStack) + typeChanged = true; + else + return; // Prevent rebuild of expression if type didn't change + } + + if (typeChanged) { + m_stackedLayout->setCurrentIndex(currentType); + if (currentStack == ConnectionType::Action) { + // Previous type was Action + const auto targetItem = m_actionTargetItem->currentText(); + fillAndSetTargetItem(targetItem, true); + fillAndSetTargetProperty(QString(), true); + fillAndSetSourceItem(QString(), true); + fillAndSetSourceProperty(QString(), QmlJS::AST::Node::Kind::Kind_Undefined, true); + } else { + // Previous type was Assignment + const auto targetItem = m_assignmentTargetItem->currentText(); + fillAndSetTargetItem(targetItem, true); + fillAndSetTargetProperty(QString(), true); + } + } else { + if (currentType == ConnectionType::Action) { + // Prevent rebuild of expression if undefinedString item was selected + switch (type) { + case ComboBox::TargetItem: + if (m_actionTargetItem->currentText() == undefinedString) + return; + break; + case ComboBox::TargetProperty: + if (m_actionMethod->currentText() == undefinedString) + return; + break; + default: + break; + } + + fillAndSetTargetItem(m_actionTargetItem->currentText()); + fillAndSetTargetProperty(m_actionMethod->currentText(), true); + } else { // ConnectionType::Assignment + const auto targetItem = m_assignmentTargetItem->currentText(); + const auto targetProperty = m_assignmentTargetProperty->currentText(); + const auto sourceItem = m_assignmentSourceItem->currentText(); + const auto sourceProperty = m_assignmentSourceProperty->currentText(); + + // Prevent rebuild of expression if undefinedString item was selected + switch (type) { + case ComboBox::TargetItem: + if (targetItem == undefinedString) + return; + break; + case ComboBox::TargetProperty: + if (targetProperty == undefinedString) + return; + break; + case ComboBox::SourceItem: + if (sourceItem == undefinedString) + return; + break; + case ComboBox::SourceProperty: + if (sourceProperty == undefinedString) + return; + break; + default: + break; + } + + fillAndSetTargetItem(targetItem, true); + fillAndSetTargetProperty(targetProperty, true); + + const auto sourcePropertyType = m_assignmentSourceProperty->currentData().value<TypeName>(); + + if (type == ComboBox::SourceItem) { + fillAndSetSourceItem(sourceItem, true); + + if (sourcePropertyType == specificItem) + fillAndSetSourceProperty(QString(), + QmlJS::AST::Node::Kind::Kind_Undefined, + true); + else + fillAndSetSourceProperty(sourceProperty, + QmlJS::AST::Node::Kind::Kind_Undefined, + true); + } else if (type == ComboBox::SourceProperty) { + if (sourcePropertyType == specificItem) { + fillAndSetSourceItem(QString(), false); + fillAndSetSourceProperty(sourceProperty, + QmlJS::AST::Node::Kind::Kind_StringLiteral, + false); + } else { + fillAndSetSourceProperty(sourceProperty); + } + } else { + if (sourcePropertyType == specificItem) { + fillAndSetSourceItem(QString(), false); + fillAndSetSourceProperty(sourceProperty, + QmlJS::AST::Node::Kind::Kind_StringLiteral, + false); + } else { + fillAndSetSourceItem(sourceItem, true); + fillAndSetSourceProperty(sourceProperty, + QmlJS::AST::Node::Kind::Kind_Undefined, + true); + } + } + } + } + + // Compose expression + QString value; + if (currentType == ConnectionType::Action) { + const auto targetItem = m_actionTargetItem->currentText(); + const auto method = m_actionMethod->currentText(); + + if (targetItem != undefinedString && method != undefinedString){ + value = targetItem + "." + method + "()"; + } else if (targetItem != undefinedString && method == undefinedString) { + value = targetItem; + } + } else { // ConnectionType::Assignment + const auto targetItem = m_assignmentTargetItem->currentText(); + const auto targetProperty = m_assignmentTargetProperty->currentText(); + const auto sourceItem = m_assignmentSourceItem->currentText(); + const auto sourceProperty = m_assignmentSourceProperty->currentText(); + + QString lhs; + + if (targetItem != undefinedString && targetProperty != undefinedString) { + lhs = targetItem + "." + targetProperty; + } else if (targetItem != undefinedString && targetProperty == undefinedString) { + lhs = targetItem; + } + + QString rhs; + + if (sourceItem != undefinedString && sourceProperty != undefinedString) { + rhs = sourceItem + "." + sourceProperty; + } else if (sourceItem != undefinedString && sourceProperty == undefinedString) { + rhs = sourceItem; + } else if (sourceItem == undefinedString && sourceProperty != undefinedString) { + const QString data = m_assignmentTargetProperty->currentData().toString(); + if (data == "string") { + rhs = "\"" + sourceProperty + "\""; + } else { + rhs = sourceProperty; + } + } + + if (!lhs.isEmpty() && !rhs.isEmpty()) { + value = lhs + " = " + rhs; + } else { + value = lhs + rhs; + } + } + + { + const QSignalBlocker blocker(m_editorWidget); + setEditorValue(value); + } +} + +void ActionEditorDialog::setupUIComponents() +{ + m_comboBoxType = new QComboBox(this); + + QMetaEnum metaEnum = QMetaEnum::fromType<ConnectionType>(); + for (int i = 0; i != metaEnum.keyCount(); ++i) { + const char *key = QMetaEnum::fromType<ConnectionType>().valueToKey(i); + m_comboBoxType->addItem(QString::fromLatin1(key)); + } + + m_comboBoxLayout->addWidget(m_comboBoxType); + + m_stackedLayout = new QStackedLayout(); + + m_actionLayout = new QHBoxLayout(); + m_assignmentLayout = new QHBoxLayout(); + + m_actionPlaceholder = new QWidget(this); + m_actionPlaceholder->setLayout(m_actionLayout); + + m_assignmentPlaceholder = new QWidget(this); + m_assignmentPlaceholder->setLayout(m_assignmentLayout); + + // Setup action ComboBoxes + m_actionTargetItem = new QComboBox(this); + m_actionMethod = new QComboBox(this); + m_actionLayout->addWidget(m_actionTargetItem); + m_actionLayout->addWidget(m_actionMethod); + + // Setup assignment ComboBoxes + m_assignmentTargetItem = new QComboBox(this); + m_assignmentTargetProperty = new QComboBox(this); + m_assignmentSourceItem = new QComboBox(this); + m_assignmentSourceProperty = new QComboBox(this); + m_assignmentLayout->addWidget(m_assignmentTargetItem); + m_assignmentLayout->addWidget(m_assignmentTargetProperty); + m_assignmentLayout->addWidget(m_assignmentSourceItem); + m_assignmentLayout->addWidget(m_assignmentSourceProperty); + + m_stackedLayout->addWidget(m_actionPlaceholder); + m_stackedLayout->addWidget(m_assignmentPlaceholder); + + m_comboBoxLayout->addItem(m_stackedLayout); + + this->resize(720, 240); +} + +void ActionEditorDialog::setType(ConnectionType type) +{ + m_comboBoxType->setCurrentIndex(type); + m_stackedLayout->setCurrentIndex(type); +} + +void ActionEditorDialog::fillAndSetTargetItem(const QString &value, bool useDefault) +{ + if (m_comboBoxType->currentIndex() == ConnectionType::Action) { + m_actionTargetItem->clear(); + for (const auto &connection : m_connections) { + if (!connection.methods.isEmpty()) + m_actionTargetItem->addItem(connection.item); + } + + if (m_actionTargetItem->findText(value) != -1) { + m_actionTargetItem->setCurrentText(value); + } else { + if (useDefault && m_actionTargetItem->count()) + m_actionTargetItem->setCurrentIndex(0); + else + insertAndSetUndefined(m_actionTargetItem); + } + } else { // ConnectionType::Assignment + m_assignmentTargetItem->clear(); + for (const auto &connection : m_connections) { + if (!connection.properties.isEmpty()) + m_assignmentTargetItem->addItem(connection.item); + } + + if (m_assignmentTargetItem->findText(value) != -1) { + m_assignmentTargetItem->setCurrentText(value); + } else { + if (useDefault && m_actionTargetItem->count()) + m_actionTargetItem->setCurrentIndex(0); + else + insertAndSetUndefined(m_assignmentTargetItem); + } + } +} + +void ActionEditorDialog::fillAndSetTargetProperty(const QString &value, bool useDefault) +{ + if (m_comboBoxType->currentIndex() == ConnectionType::Action) { + m_actionMethod->clear(); + const QString targetItem = m_actionTargetItem->currentText(); + const int idx = m_connections.indexOf(targetItem); + + if (idx == -1) { + insertAndSetUndefined(m_actionMethod); + } else { + m_actionMethod->addItems(m_connections[idx].methods); + + if (m_actionMethod->findText(value) != -1) { + m_actionMethod->setCurrentText(value); + } else { + if (useDefault && m_actionMethod->count()) + m_actionMethod->setCurrentIndex(0); + else + insertAndSetUndefined(m_actionMethod); + } + } + } else { // ConnectionType::Assignment + m_assignmentTargetProperty->clear(); + const QString targetItem = m_assignmentTargetItem->currentText(); + const int idx = m_connections.indexOf(targetItem); + + if (idx == -1) { + insertAndSetUndefined(m_assignmentTargetProperty); + } else { + for (const auto &property : m_connections[idx].properties) + m_assignmentTargetProperty->addItem(property.name, property.type); + + if (m_assignmentTargetProperty->findText(value) != -1) { + m_assignmentTargetProperty->setCurrentText(value); + } else { + if (useDefault && m_assignmentTargetProperty->count()) + m_assignmentTargetProperty->setCurrentIndex(0); + else + insertAndSetUndefined(m_assignmentTargetProperty); + } + } + } +} + +void ActionEditorDialog::fillAndSetSourceItem(const QString &value, bool useDefault) +{ + m_assignmentSourceItem->clear(); + const TypeName targetPropertyType = m_assignmentTargetProperty->currentData().value<TypeName>(); + + if (!targetPropertyType.isEmpty()) { + for (const ConnectionOption &connection : m_connections) { + if (!connection.containsType(targetPropertyType)) + continue; + + m_assignmentSourceItem->addItem(connection.item); + } + + // Add Constants + for (const SingletonOption &singleton : m_singletons) { + if (!singleton.containsType(targetPropertyType)) + continue; + + m_assignmentSourceItem->addItem(singleton.item, singletonItem); + } + } + + if (m_assignmentSourceItem->findText(value) != -1) { + m_assignmentSourceItem->setCurrentText(value); + } else { + if (useDefault && m_assignmentSourceItem->count()) + m_assignmentSourceItem->setCurrentIndex(0); + else + insertAndSetUndefined(m_assignmentSourceItem); + } +} + +void ActionEditorDialog::fillAndSetSourceProperty(const QString &value, + QmlJS::AST::Node::Kind kind, + bool useDefault) +{ + m_assignmentSourceProperty->clear(); + const TypeName targetPropertyType = m_assignmentTargetProperty->currentData().value<TypeName>(); + const QString targetProperty = m_assignmentTargetProperty->currentText(); + + if (kind != QmlJS::AST::Node::Kind::Kind_Undefined) { + if (targetPropertyType == "bool") { + m_assignmentSourceProperty->addItem("true", specificItem); + m_assignmentSourceProperty->addItem("false", specificItem); + + if (m_assignmentSourceProperty->findText(value) != -1) + m_assignmentSourceProperty->setCurrentText(value); + else + insertAndSetUndefined(m_assignmentSourceProperty); + } else if (targetProperty == "state") { + for (const auto &state : m_states) + m_assignmentSourceProperty->addItem(state, specificItem); + + if (m_assignmentSourceProperty->findText(value) != -1) + m_assignmentSourceProperty->setCurrentText(value); + else + insertAndSetUndefined(m_assignmentSourceProperty); + } else { + m_assignmentSourceProperty->insertItem(0, value, specificItem); + m_assignmentSourceProperty->setCurrentIndex(0); + } + + } else { + const TypeName sourceItemType = m_assignmentSourceItem->currentData().value<TypeName>(); + const QString sourceItem = m_assignmentSourceItem->currentText(); + // We need to distinguish between singleton (Constants) and standard item + const int idx = (sourceItemType == singletonItem) ? m_singletons.indexOf(sourceItem) + : m_connections.indexOf(sourceItem); + + if (idx == -1) { + insertAndSetUndefined(m_assignmentSourceProperty); + } else { + int specificsEnd = -1; + // Add type specific items + if (targetPropertyType == "bool") { + m_assignmentSourceProperty->addItem("true", specificItem); + m_assignmentSourceProperty->addItem("false", specificItem); + specificsEnd = 2; + } else if (targetProperty == "state") { + for (const auto &state : m_states) + m_assignmentSourceProperty->addItem(state, specificItem); + + specificsEnd = m_states.count(); + } + + if (specificsEnd != -1) + m_assignmentSourceProperty->insertSeparator(specificsEnd); + + if (sourceItemType == singletonItem) { + for (const auto &property : m_singletons[idx].properties) { + if (targetPropertyType.isEmpty() // TODO isEmpty correct?! + || property.type == targetPropertyType + || (isNumeric(property.type) && isNumeric(targetPropertyType))) + m_assignmentSourceProperty->addItem(property.name, property.type); + } + } else { + for (const auto &property : m_connections[idx].properties) { + if (targetPropertyType.isEmpty() // TODO isEmpty correct?! + || property.type == targetPropertyType + || (isNumeric(property.type) && isNumeric(targetPropertyType))) + m_assignmentSourceProperty->addItem(property.name, property.type); + } + } + + if (m_assignmentSourceProperty->findText(value) != -1 && !value.isEmpty()) { + m_assignmentSourceProperty->setCurrentText(value); + } else { + if (useDefault && m_assignmentSourceProperty->count()) + m_assignmentSourceProperty->setCurrentIndex(specificsEnd + 1); + else + insertAndSetUndefined(m_assignmentSourceProperty); + } + } + } +} + +void ActionEditorDialog::insertAndSetUndefined(QComboBox *comboBox) +{ + comboBox->insertItem(0, undefinedString); + comboBox->setCurrentIndex(0); +} + +} // QmlDesigner namespace diff --git a/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h new file mode 100644 index 0000000000..e19c7fe657 --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/actioneditordialog.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef ACTIONEDITORDIALOG_H +#define ACTIONEDITORDIALOG_H + +#include <bindingeditor/abstracteditordialog.h> +#include <qmljs/parser/qmljsast_p.h> + +#include <QStackedLayout> + +QT_BEGIN_NAMESPACE +class QComboBox; +QT_END_NAMESPACE + +namespace QmlDesigner { + +class ActionEditorDialog : public AbstractEditorDialog +{ + Q_OBJECT + +public: + enum ConnectionType { Action, Assignment }; + Q_ENUM(ConnectionType) + + enum ComboBox { Type, TargetItem, TargetProperty, SourceItem, SourceProperty }; + Q_ENUM(ComboBox) + + class PropertyOption + { + public: + PropertyOption() {} + PropertyOption(const QString &n, const TypeName &t) + : name(n) + , type(t) + {} + + bool operator==(const QString &value) const { return value == name; } + bool operator==(const PropertyOption &value) const { return value.name == name; } + + QString name; + TypeName type; + }; + + class SingletonOption + { + public: + SingletonOption() {} + SingletonOption(const QString &value) { item = value; } + + bool containsType(const TypeName &t) const + { + for (const auto &p : properties) { + if (t == p.type || (isNumeric(t) && isNumeric(p.type))) + return true; + } + + return false; + } + + bool operator==(const QString &value) const { return value == item; } + bool operator==(const SingletonOption &value) const { return value.item == item; } + + QString item; + QList<PropertyOption> properties; + }; + + class ConnectionOption : public SingletonOption + { + public: + ConnectionOption() {} + ConnectionOption(const QString &value) : SingletonOption(value) {} + + QStringList methods; + }; + + + ActionEditorDialog(QWidget *parent = nullptr); + ~ActionEditorDialog() override; + + void adjustProperties() override; + + void setAllConnections(const QList<ConnectionOption> &connections, + const QList<SingletonOption> &singeltons, + const QStringList &states); + + void updateComboBoxes(int idx, ComboBox type); + +private: + void setupUIComponents(); + + void setType(ConnectionType type); + + void fillAndSetTargetItem(const QString &value, bool useDefault = false); + void fillAndSetTargetProperty(const QString &value, bool useDefault = false); + + void fillAndSetSourceItem(const QString &value, bool useDefault = false); + void fillAndSetSourceProperty(const QString &value, + QmlJS::AST::Node::Kind kind = QmlJS::AST::Node::Kind::Kind_Undefined, + bool useDefault = false); + + void insertAndSetUndefined(QComboBox *comboBox); + +private: + QComboBox *m_comboBoxType = nullptr; + + QStackedLayout *m_stackedLayout = nullptr; + + QWidget *m_actionPlaceholder = nullptr; + QWidget *m_assignmentPlaceholder = nullptr; + + QHBoxLayout *m_actionLayout = nullptr; + QHBoxLayout *m_assignmentLayout = nullptr; + + QComboBox *m_actionTargetItem = nullptr; + QComboBox *m_actionMethod = nullptr; + + QComboBox *m_assignmentTargetItem = nullptr; + QComboBox *m_assignmentTargetProperty = nullptr; + QComboBox *m_assignmentSourceItem = nullptr; + QComboBox *m_assignmentSourceProperty = nullptr; // Value + + QList<ConnectionOption> m_connections; + QList<SingletonOption> m_singletons; + QStringList m_states; + + const TypeName specificItem = {"specific"}; + const TypeName singletonItem = {"singleton"}; +}; + +} + +#endif //ACTIONEDITORDIALOG_H diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp index d2bdd3cbb8..5d7f8281d0 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp @@ -28,6 +28,7 @@ #include <qmldesignerplugin.h> #include <coreplugin/icore.h> #include <coreplugin/actionmanager/actionmanager.h> +#include <bindingeditor/bindingeditordialog.h> #include <metainfo.h> #include <qmlmodelnodeproxy.h> @@ -60,14 +61,14 @@ void BindingEditor::prepareDialog() { if (s_lastBindingEditor) s_lastBindingEditor->hideWidget(); + s_lastBindingEditor = this; m_dialog = new BindingEditorDialog(Core::ICore::dialogParent()); - - QObject::connect(m_dialog, &BindingEditorDialog::accepted, + QObject::connect(m_dialog, &AbstractEditorDialog::accepted, this, &BindingEditor::accepted); - QObject::connect(m_dialog, &BindingEditorDialog::rejected, + QObject::connect(m_dialog, &AbstractEditorDialog::rejected, this, &BindingEditor::rejected); m_dialog->setAttribute(Qt::WA_DeleteOnClose); @@ -89,8 +90,8 @@ void BindingEditor::hideWidget() { if (s_lastBindingEditor == this) s_lastBindingEditor = nullptr; - if (m_dialog) - { + + if (m_dialog) { m_dialog->unregisterAutoCompletion(); //we have to do it separately, otherwise we have an autocompletion action override m_dialog->close(); } @@ -118,8 +119,7 @@ void BindingEditor::setBackendValue(const QVariant &backendValue) const PropertyEditorValue *propertyEditorValue = qobject_cast<const PropertyEditorValue *>(backendValueObj); const ModelNode node = propertyEditorValue->modelNode(); - if (node.isValid()) - { + if (node.isValid()) { m_backendValueTypeName = node.metaInfo().propertyTypeName(propertyEditorValue->name()); if (m_backendValueTypeName == "alias" || m_backendValueTypeName == "unknown") @@ -141,9 +141,8 @@ void BindingEditor::setModelNodeBackend(const QVariant &modelNodeBackend) const auto backendObjectCasted = qobject_cast<const QmlDesigner::QmlModelNodeProxy *>(modelNodeBackendObject); - if (backendObjectCasted) { + if (backendObjectCasted) m_modelNode = backendObjectCasted->qmlObjectNode().modelNode(); - } emit modelNodeBackendChanged(); } @@ -151,8 +150,7 @@ void BindingEditor::setModelNodeBackend(const QVariant &modelNodeBackend) void BindingEditor::setStateModelNode(const QVariant &stateModelNode) { - if (stateModelNode.isValid()) - { + if (stateModelNode.isValid()) { m_stateModelNode = stateModelNode; m_modelNode = m_stateModelNode.value<QmlDesigner::ModelNode>(); @@ -188,21 +186,21 @@ void BindingEditor::prepareBindings() const QList<TypeName> variantTypes = {"alias", "unknown", "variant", "var"}; const QList<TypeName> numericTypes = {"double", "real", "int"}; const QList<TypeName> colorTypes = {"QColor", "color"}; - auto isNumeric = [&numericTypes](TypeName compareType) { return numericTypes.contains(compareType); }; - auto isColor = [&colorTypes](TypeName compareType) { return colorTypes.contains(compareType); }; + auto isVariant = [&variantTypes](const TypeName &compareType) { return variantTypes.contains(compareType); }; + auto isNumeric = [&numericTypes](const TypeName &compareType) { return numericTypes.contains(compareType); }; + auto isColor = [&colorTypes](const TypeName &compareType) { return colorTypes.contains(compareType); }; - const bool skipTypeFiltering = variantTypes.contains(m_backendValueTypeName); + const bool skipTypeFiltering = isVariant(m_backendValueTypeName); const bool targetTypeIsNumeric = isNumeric(m_backendValueTypeName); for (const auto &objnode : allNodes) { BindingEditorDialog::BindingOption binding; - for (const auto &propertyName : objnode.metaInfo().propertyNames()) - { + for (const auto &propertyName : objnode.metaInfo().propertyNames()) { TypeName propertyTypeName = objnode.metaInfo().propertyTypeName(propertyName); if (skipTypeFiltering || (m_backendValueTypeName == propertyTypeName) - || variantTypes.contains(propertyTypeName) + || isVariant(propertyTypeName) || (targetTypeIsNumeric && isNumeric(propertyTypeName))) { binding.properties.append(QString::fromUtf8(propertyName)); } @@ -215,7 +213,7 @@ void BindingEditor::prepareBindings() const TypeName dynamicTypeName = bindingProperty.dynamicTypeName(); if (skipTypeFiltering || (dynamicTypeName == m_backendValueTypeName) - || variantTypes.contains(dynamicTypeName) + || isVariant(dynamicTypeName) || (targetTypeIsNumeric && isNumeric(dynamicTypeName))) { binding.properties.append(QString::fromUtf8(bindingProperty.name())); } @@ -228,7 +226,7 @@ void BindingEditor::prepareBindings() const TypeName dynamicTypeName = variantProperty.dynamicTypeName(); if (skipTypeFiltering || (dynamicTypeName == m_backendValueTypeName) - || variantTypes.contains(dynamicTypeName) + || isVariant(dynamicTypeName) || (targetTypeIsNumeric && isNumeric(dynamicTypeName))) { binding.properties.append(QString::fromUtf8(variantProperty.name())); } @@ -243,7 +241,7 @@ void BindingEditor::prepareBindings() } //singletons: - if (RewriterView* rv = m_modelNode.view()->rewriterView()) { + if (RewriterView *rv = m_modelNode.view()->rewriterView()) { for (const QmlTypeData &data : rv->getQMLTypes()) { if (!data.typeName.isEmpty()) { NodeMetaInfo metaInfo = m_modelNode.view()->model()->metaInfo(data.typeName.toUtf8()); @@ -256,7 +254,7 @@ void BindingEditor::prepareBindings() if (skipTypeFiltering || (m_backendValueTypeName == propertyTypeName) - || (variantTypes.contains(propertyTypeName)) + || (isVariant(propertyTypeName)) || (targetTypeIsNumeric && isNumeric(propertyTypeName)) || (isColor(m_backendValueTypeName) && isColor(propertyTypeName))) { binding.properties.append(QString::fromUtf8(propertyName)); @@ -281,9 +279,7 @@ void BindingEditor::prepareBindings() void BindingEditor::updateWindowName() { if (!m_dialog.isNull() && !m_backendValueTypeName.isEmpty()) - { m_dialog->setWindowTitle(m_dialog->defaultTitle() + " [" + m_backendValueTypeName + "]"); - } } QVariant BindingEditor::backendValue() const diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri index 925cea3539..ff2920ffab 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri @@ -1,9 +1,15 @@ HEADERS += $$PWD/bindingeditor.h HEADERS += $$PWD/actioneditor.h +HEADERS += $$PWD/abstracteditordialog.h +HEADERS += $$PWD/actioneditordialog.h HEADERS += $$PWD/bindingeditordialog.h HEADERS += $$PWD/bindingeditorwidget.h +HEADERS += $$PWD/connectionvisitor.h SOURCES += $$PWD/bindingeditor.cpp SOURCES += $$PWD/actioneditor.cpp +SOURCES += $$PWD/abstracteditordialog.cpp +SOURCES += $$PWD/actioneditordialog.cpp SOURCES += $$PWD/bindingeditordialog.cpp SOURCES += $$PWD/bindingeditorwidget.cpp +SOURCES += $$PWD/connectionvisitor.cpp diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp index 9d2b1cb9e0..b74d805f08 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -41,80 +41,19 @@ namespace QmlDesigner { -BindingEditorDialog::BindingEditorDialog(QWidget *parent, DialogType type) - : QDialog(parent) - , m_dialogType(type) +BindingEditorDialog::BindingEditorDialog(QWidget *parent) + : AbstractEditorDialog(parent, tr("Binding Editor")) { - setWindowFlag(Qt::Tool, true); - setWindowTitle(defaultTitle()); - setModal(false); - - setupJSEditor(); setupUIComponents(); - QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, - this, &BindingEditorDialog::accepted); - QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, - this, &BindingEditorDialog::rejected); - QObject::connect(m_editorWidget, &BindingEditorWidget::returnKeyClicked, - this, &BindingEditorDialog::accepted); - - if (m_dialogType == DialogType::BindingDialog) { - QObject::connect(m_comboBoxItem, QOverload<int>::of(&QComboBox::currentIndexChanged), - this, &BindingEditorDialog::itemIDChanged); - QObject::connect(m_comboBoxProperty, QOverload<int>::of(&QComboBox::currentIndexChanged), - this, &BindingEditorDialog::propertyIDChanged); - QObject::connect(m_editorWidget, &QPlainTextEdit::textChanged, - this, &BindingEditorDialog::textChanged); - } + QObject::connect(m_comboBoxItem, QOverload<int>::of(&QComboBox::currentIndexChanged), + this, &BindingEditorDialog::itemIDChanged); + QObject::connect(m_comboBoxProperty, QOverload<int>::of(&QComboBox::currentIndexChanged), + this, &BindingEditorDialog::propertyIDChanged); } BindingEditorDialog::~BindingEditorDialog() { - delete m_editor; //m_editorWidget is handled by basetexteditor destructor - delete m_buttonBox; - delete m_comboBoxItem; - delete m_comboBoxProperty; - delete m_comboBoxLayout; - delete m_verticalLayout; -} - -void BindingEditorDialog::showWidget() -{ - this->show(); - this->raise(); - m_editorWidget->setFocus(); -} - -void BindingEditorDialog::showWidget(int x, int y) -{ - showWidget(); - move(QPoint(x, y)); -} - -QString BindingEditorDialog::editorValue() const -{ - if (!m_editorWidget) - return {}; - - return m_editorWidget->document()->toPlainText(); -} - -void BindingEditorDialog::setEditorValue(const QString &text) -{ - if (m_editorWidget) - m_editorWidget->document()->setPlainText(text); -} - -void BindingEditorDialog::setAllBindings(QList<BindingEditorDialog::BindingOption> bindings) -{ - m_lock = true; - - m_bindings = bindings; - setupComboBoxes(); - adjustProperties(); - - m_lock = false; } void BindingEditorDialog::adjustProperties() @@ -155,69 +94,26 @@ void BindingEditorDialog::adjustProperties() m_comboBoxProperty->setCurrentText(property); } -void BindingEditorDialog::unregisterAutoCompletion() +void BindingEditorDialog::setAllBindings(QList<BindingOption> bindings) { - if (m_editorWidget) - m_editorWidget->unregisterAutoCompletion(); -} - -QString BindingEditorDialog::defaultTitle() const -{ - return titleString; -} - -void BindingEditorDialog::setupJSEditor() -{ - static BindingEditorFactory f; - m_editor = qobject_cast<TextEditor::BaseTextEditor*>(f.createEditor()); - m_editorWidget = qobject_cast<BindingEditorWidget*>(m_editor->editorWidget()); - - Core::Context context = m_editor->context(); - context.prepend(BINDINGEDITOR_CONTEXT_ID); - m_editorWidget->m_context->setContext(context); - - auto qmlDesignerEditor = QmlDesignerPlugin::instance()->currentDesignDocument()->textEditor(); - - m_editorWidget->qmljsdocument = qobject_cast<QmlJSEditor::QmlJSEditorWidget *>( - qmlDesignerEditor->widget())->qmlJsEditorDocument(); + m_lock = true; + m_bindings = bindings; + setupComboBoxes(); + adjustProperties(); - m_editorWidget->setLineNumbersVisible(false); - m_editorWidget->setMarksVisible(false); - m_editorWidget->setCodeFoldingSupported(false); - m_editorWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_editorWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); - m_editorWidget->setTabChangesFocus(true); + m_lock = false; } void BindingEditorDialog::setupUIComponents() { - m_verticalLayout = new QVBoxLayout(this); - - if (m_dialogType == DialogType::BindingDialog) { - m_comboBoxLayout = new QHBoxLayout; - m_comboBoxItem = new QComboBox(this); - m_comboBoxProperty = new QComboBox(this); - } - - m_editorWidget->setParent(this); - m_editorWidget->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); - m_editorWidget->show(); + m_comboBoxItem = new QComboBox(this); + m_comboBoxProperty = new QComboBox(this); - m_buttonBox = new QDialogButtonBox(this); - m_buttonBox->setOrientation(Qt::Horizontal); - m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + m_comboBoxLayout->addWidget(m_comboBoxItem); + m_comboBoxLayout->addWidget(m_comboBoxProperty); - if (m_dialogType == DialogType::BindingDialog) { - m_comboBoxLayout->addWidget(m_comboBoxItem); - m_comboBoxLayout->addWidget(m_comboBoxProperty); - m_verticalLayout->addLayout(m_comboBoxLayout); - } - m_verticalLayout->addWidget(m_editorWidget); - m_verticalLayout->addWidget(m_buttonBox); - - this->resize(660, 240); + //this->resize(660, 240); } void BindingEditorDialog::setupComboBoxes() @@ -260,14 +156,4 @@ void BindingEditorDialog::propertyIDChanged(int propertyID) m_comboBoxProperty->removeItem(undefinedProperty); } -void BindingEditorDialog::textChanged() -{ - if (m_lock) - return; - - m_lock = true; - adjustProperties(); - m_lock = false; -} - } // QmlDesigner namespace diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h index fdcdca0762..0c37abaa3a 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditordialog.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2019 The Qt Company Ltd. +** Copyright (C) 2020 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. @@ -26,21 +26,15 @@ #ifndef BINDINGEDITORDIALOG_H #define BINDINGEDITORDIALOG_H -#include <bindingeditor/bindingeditorwidget.h> -#include <texteditor/texteditor.h> - -#include <QDialog> +#include <bindingeditor/abstracteditordialog.h> QT_BEGIN_NAMESPACE -class QDialogButtonBox; -class QVBoxLayout; -class QHBoxLayout; class QComboBox; QT_END_NAMESPACE namespace QmlDesigner { -class BindingEditorDialog : public QDialog +class BindingEditorDialog : public AbstractEditorDialog { Q_OBJECT @@ -57,52 +51,26 @@ public: QStringList properties; }; - enum DialogType { - Unknown = 0, - BindingDialog = 1, - ActionDialog = 2 - }; - -public: - BindingEditorDialog(QWidget *parent = nullptr, DialogType type = DialogType::BindingDialog); + BindingEditorDialog(QWidget *parent = nullptr); ~BindingEditorDialog() override; - void showWidget(); - void showWidget(int x, int y); + void adjustProperties() override; - QString editorValue() const; - void setEditorValue(const QString &text); - - void setAllBindings(QList<BindingEditorDialog::BindingOption> bindings); - void adjustProperties(); - - void unregisterAutoCompletion(); - - QString defaultTitle() const; + void setAllBindings(QList<BindingOption> bindings); private: - void setupJSEditor(); void setupUIComponents(); void setupComboBoxes(); public slots: void itemIDChanged(int); void propertyIDChanged(int); - void textChanged(); private: - DialogType m_dialogType = DialogType::BindingDialog; - TextEditor::BaseTextEditor *m_editor = nullptr; - BindingEditorWidget *m_editorWidget = nullptr; - QVBoxLayout *m_verticalLayout = nullptr; - QDialogButtonBox *m_buttonBox = nullptr; - QHBoxLayout *m_comboBoxLayout = nullptr; QComboBox *m_comboBoxItem = nullptr; QComboBox *m_comboBoxProperty = nullptr; - QList<BindingEditorDialog::BindingOption> m_bindings; - bool m_lock = false; - const QString undefinedString = {"[Undefined]"}; - const QString titleString = {tr("Binding Editor")}; + + QList<BindingOption> m_bindings; }; } diff --git a/src/plugins/qmldesigner/components/bindingeditor/connectionvisitor.cpp b/src/plugins/qmldesigner/components/bindingeditor/connectionvisitor.cpp new file mode 100644 index 0000000000..ddf328978f --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/connectionvisitor.cpp @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "connectionvisitor.h" + +namespace QmlDesigner { + +ConnectionVisitor::ConnectionVisitor() +{ +} + +bool ConnectionVisitor::visit(QmlJS::AST::StringLiteral *ast) +{ + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_StringLiteral, + ast->value.toString())); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::NumericLiteral *ast) +{ + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_NumericLiteral, + QString::number(ast->value))); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::TrueLiteral *ast) +{ + Q_UNUSED(ast) + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_TrueLiteral, QString("true"))); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::FalseLiteral *ast) +{ + Q_UNUSED(ast) + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_FalseLiteral, QString("false"))); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::BinaryExpression *ast) +{ + Q_UNUSED(ast) + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_BinaryExpression, + QString())); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::CallExpression *ast) +{ + Q_UNUSED(ast) + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_CallExpression, + QString())); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::ArgumentList *ast) +{ + Q_UNUSED(ast) + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_ArgumentList, + QString())); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::FunctionExpression *ast) +{ + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_FunctionExpression, + ast->name.toString())); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::FieldMemberExpression *ast) +{ + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_FieldMemberExpression, + ast->name.toString())); + return true; +} + +bool ConnectionVisitor::visit(QmlJS::AST::IdentifierExpression *ast) +{ + m_expression.append(qMakePair(QmlJS::AST::Node::Kind::Kind_IdentifierExpression, + ast->name.toString())); + return true; +} + +void ConnectionVisitor::throwRecursionDepthError() +{ + qWarning("Warning: Hit maximum recursion depth while visiting AST in ConnectionVisitor"); +} + +} // QmlDesigner namespace diff --git a/src/plugins/qmldesigner/components/bindingeditor/connectionvisitor.h b/src/plugins/qmldesigner/components/bindingeditor/connectionvisitor.h new file mode 100644 index 0000000000..abcebcdb8e --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/connectionvisitor.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2020 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#ifndef CONNECTIONVISITOR_H +#define CONNECTIONVISITOR_H + +#include <qmljs/qmljsdocument.h> +#include <qmljs/parser/qmljsastvisitor_p.h> +#include <qmljs/parser/qmljsast_p.h> + +namespace QmlDesigner { + +class ConnectionVisitor : public QmlJS::AST::Visitor +{ +public: + explicit ConnectionVisitor(); + + bool visit(QmlJS::AST::StringLiteral *ast) override; + bool visit(QmlJS::AST::NumericLiteral *ast) override; + bool visit(QmlJS::AST::TrueLiteral *ast) override; + bool visit(QmlJS::AST::FalseLiteral *ast) override; + + bool visit(QmlJS::AST::BinaryExpression *ast) override; + bool visit(QmlJS::AST::CallExpression *ast) override; + + bool visit(QmlJS::AST::ArgumentList *ast) override; + bool visit(QmlJS::AST::FunctionExpression *ast) override; // unused + + bool visit(QmlJS::AST::FieldMemberExpression *ast) override; + bool visit(QmlJS::AST::IdentifierExpression *ast) override; + + void throwRecursionDepthError() override; + + const QList<QPair<QmlJS::AST::Node::Kind, QString>> &expression() const { + return m_expression; + } + +private: + QList<QPair<QmlJS::AST::Node::Kind, QString>> m_expression; +}; + +} + +#endif //CONNECTIONVISITOR_H diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp index 25ea5814a9..c650be4221 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.cpp @@ -60,7 +60,7 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : QFrame(parent), ui(new Ui::ConnectionViewWidget) { - m_connectonEditor = new QmlDesigner::ActionEditor(this); + m_connectionEditor = new QmlDesigner::ActionEditor(this); m_bindingEditor = new QmlDesigner::BindingEditor(this); m_dynamicEditor = new QmlDesigner::BindingEditor(this); @@ -111,7 +111,7 @@ ConnectionViewWidget::ConnectionViewWidget(QWidget *parent) : ConnectionViewWidget::~ConnectionViewWidget() { - delete m_connectonEditor; + delete m_connectionEditor; delete m_bindingEditor; delete m_dynamicEditor; delete ui; @@ -161,10 +161,14 @@ void ConnectionViewWidget::contextMenuEvent(QContextMenuEvent *event) menu.addAction(tr("Open Connection Editor"), [&]() { if (index.isValid()) { - m_connectonEditor->showWidget(); - m_connectonEditor->setBindingValue(index.data().toString()); - m_connectonEditor->setModelIndex(index); - m_connectonEditor->updateWindowName(); + auto *connectionModel = qobject_cast<ConnectionModel *>(targetView->model()); + ModelNode node = connectionModel->connectionView()->rootModelNode(); + m_connectionEditor->showWidget(); + m_connectionEditor->setConnectionValue(index.data().toString()); + m_connectionEditor->setModelIndex(index); + m_connectionEditor->setModelNode(node); + m_connectionEditor->prepareConnections(); + m_connectionEditor->updateWindowName(); } }); @@ -455,29 +459,29 @@ void ConnectionViewWidget::addButtonClicked() void ConnectionViewWidget::editorForConnection() { - QObject::connect(m_connectonEditor, &QmlDesigner::ActionEditor::accepted, + QObject::connect(m_connectionEditor, &QmlDesigner::ActionEditor::accepted, [&]() { - if (m_connectonEditor->hasModelIndex()) { + if (m_connectionEditor->hasModelIndex()) { ConnectionModel *connectionModel = qobject_cast<ConnectionModel *>(ui->connectionView->model()); if (connectionModel->connectionView()->isWidgetEnabled() - && (connectionModel->rowCount() > m_connectonEditor->modelIndex().row())) { + && (connectionModel->rowCount() > m_connectionEditor->modelIndex().row())) { connectionModel->connectionView() ->executeInTransaction("ConnectionView::setSignal", [this, connectionModel]() { SignalHandlerProperty signalHandler = connectionModel->signalHandlerPropertyForRow( - m_connectonEditor->modelIndex().row()); - signalHandler.setSource(m_connectonEditor->bindingValue()); + m_connectionEditor->modelIndex().row()); + signalHandler.setSource(m_connectionEditor->connectionValue()); }); } - m_connectonEditor->resetModelIndex(); + m_connectionEditor->resetModelIndex(); } - m_connectonEditor->hideWidget(); + m_connectionEditor->hideWidget(); }); - QObject::connect(m_connectonEditor, &QmlDesigner::ActionEditor::rejected, + QObject::connect(m_connectionEditor, &QmlDesigner::ActionEditor::rejected, [&]() { - m_connectonEditor->resetModelIndex(); - m_connectonEditor->hideWidget(); + m_connectionEditor->resetModelIndex(); + m_connectionEditor->hideWidget(); }); } diff --git a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h index 68319e16b5..dc12d53564 100644 --- a/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h +++ b/src/plugins/qmldesigner/components/connectioneditor/connectionviewwidget.h @@ -107,7 +107,7 @@ private: private: Ui::ConnectionViewWidget *ui; - QmlDesigner::ActionEditor *m_connectonEditor; //editor for connections in connection view + QmlDesigner::ActionEditor *m_connectionEditor; //editor for connections in connection view QmlDesigner::BindingEditor *m_bindingEditor; //editor for properties in binding view QmlDesigner::BindingEditor *m_dynamicEditor; //editor for properties in dynamic view diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 17c6b4282a..4678d7d27a 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -693,10 +693,16 @@ Project { "bindingeditor/bindingeditor.h", "bindingeditor/actioneditor.cpp", "bindingeditor/actioneditor.h", + "bindingeditor/abstracteditordialog.cpp", + "bindingeditor/abstracteditordialog.h", + "bindingeditor/actioneditordialog.cpp", + "bindingeditor/actioneditordialog.h", "bindingeditor/bindingeditordialog.cpp", "bindingeditor/bindingeditordialog.h", "bindingeditor/bindingeditorwidget.cpp", "bindingeditor/bindingeditorwidget.h", + "bindingeditor/connectionvisitor.cpp", + "bindingeditor/connectionvisitor.h", "colortool/colortool.cpp", "colortool/colortool.h", "connectioneditor/addnewbackenddialog.h", |