From 1278c01c1cc4e42aa52b410523b5d4aedab2e38b Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Thu, 10 Oct 2019 16:02:34 +0200 Subject: [PATCH] QmlDesigner Tool bar for Binding Editor Task: QDS-843 Change-Id: I8faa5816067ee8de91fd225d856d6bac2a6eda58 Reviewed-by: Tim Jenssen --- .../HelperWidgets/ExtendedFunctionLogic.qml | 4 + .../bindingeditor/bindingeditor.cpp | 263 ++++++++++++++++-- .../components/bindingeditor/bindingeditor.h | 60 +++- 3 files changed, 302 insertions(+), 25 deletions(-) diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml index 80882ac213b..64fcf13d988 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml @@ -152,11 +152,15 @@ Item { var y = extendedFunctionButton.mapToGlobal(0,0).y - 40 bindingEditor.showWidget(x, y) bindingEditor.text = backendValue.expression + bindingEditor.prepareBindings() } BindingEditor { id: bindingEditor + backendValueProperty: backendValue + modelNodeBackendProperty: modelNodeBackend + onRejected: { hideWidget() expressionDialogLoader.visible = false diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp index 2fb95877879..c98789587f3 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp @@ -53,10 +53,21 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include #include +#include #include #include +#include +#include +#include namespace QmlDesigner { @@ -69,7 +80,7 @@ BindingEditorWidget::BindingEditorWidget() { Core::ICore::addContextObject(m_context); - Core::Context context(BINDINGEDITOR_CONTEXT_ID); + const Core::Context context(BINDINGEDITOR_CONTEXT_ID); /* * We have to register our own active auto completion shortcut, because the original short cut will @@ -77,8 +88,12 @@ BindingEditorWidget::BindingEditorWidget() */ m_completionAction = new QAction(tr("Trigger Completion"), this); - Core::Command *command = Core::ActionManager::registerAction(m_completionAction, TextEditor::Constants::COMPLETE_THIS, context); - command->setDefaultKeySequence(QKeySequence(Core::useMacShortcuts ? tr("Meta+Space") : tr("Ctrl+Space"))); + Core::Command *command = Core::ActionManager::registerAction( + m_completionAction, TextEditor::Constants::COMPLETE_THIS, context); + command->setDefaultKeySequence(QKeySequence( + Core::useMacShortcuts + ? tr("Meta+Space") + : tr("Ctrl+Space"))); connect(m_completionAction, &QAction::triggered, [this]() { invokeAssist(TextEditor::Completion); @@ -95,18 +110,34 @@ BindingEditorWidget::~BindingEditorWidget() void BindingEditorWidget::unregisterAutoCompletion() { - if (m_completionAction) - { + if (m_completionAction) { Core::ActionManager::unregisterAction(m_completionAction, TextEditor::Constants::COMPLETE_THIS); delete m_completionAction; m_completionAction = nullptr; } } -TextEditor::AssistInterface *BindingEditorWidget::createAssistInterface(TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const +bool BindingEditorWidget::event(QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QKeyEvent *keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { + emit returnKeyClicked(); + return true; + } else { + return QmlJSEditor::QmlJSEditorWidget::event(event); + } + } + return QmlJSEditor::QmlJSEditorWidget::event(event); +} + +TextEditor::AssistInterface *BindingEditorWidget::createAssistInterface( + TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const { Q_UNUSED(assistKind); - return new QmlJSEditor::QmlJSCompletionAssistInterface(document(), position(), QString(), assistReason, qmljsdocument->semanticInfo()); + return new QmlJSEditor::QmlJSCompletionAssistInterface( + document(), position(), QString(), + assistReason, qmljsdocument->semanticInfo()); } class BindingDocument : public QmlJSEditor::QmlJSEditorDocument @@ -122,17 +153,15 @@ protected: { TextDocument::applyFontSettings(); m_semanticHighlighter->updateFontSettings(fontSettings()); - if (!isSemanticInfoOutdated()) { + if (!isSemanticInfoOutdated()) m_semanticHighlighter->rerun(semanticInfo()); - } } void triggerPendingUpdates() { TextDocument::triggerPendingUpdates(); // calls applyFontSettings if necessary - if (!isSemanticInfoOutdated()) { + if (!isSemanticInfoOutdated()) m_semanticHighlighter->rerun(semanticInfo()); - } } private: @@ -142,7 +171,8 @@ private: class BindingEditorFactory : public TextEditor::TextEditorFactory { public: - BindingEditorFactory() { + BindingEditorFactory() + { setId(BINDINGEDITOR_CONTEXT_ID); setDisplayName(QCoreApplication::translate("OpenWith::Editors", BINDINGEDITOR_CONTEXT_ID)); @@ -162,17 +192,14 @@ public: static void decorateEditor(TextEditor::TextEditorWidget *editor) { editor->textDocument()->setSyntaxHighlighter(new QmlJSEditor::QmlJSHighlighter); - editor->textDocument()->setIndenter(new QmlJSEditor::Internal::Indenter(editor->textDocument()->document())); + editor->textDocument()->setIndenter(new QmlJSEditor::Internal::Indenter( + editor->textDocument()->document())); editor->setAutoCompleter(new QmlJSEditor::AutoCompleter); } }; BindingEditorDialog::BindingEditorDialog(QWidget *parent) : QDialog(parent) - , m_editor(nullptr) - , m_editorWidget(nullptr) - , m_verticalLayout(nullptr) - , m_buttonBox(nullptr) { setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); setWindowTitle(tr("Binding Editor")); @@ -185,12 +212,24 @@ BindingEditorDialog::BindingEditorDialog(QWidget *parent) this, &BindingEditorDialog::accepted); QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, this, &BindingEditorDialog::rejected); + QObject::connect(m_editorWidget, &BindingEditorWidget::returnKeyClicked, + this, &BindingEditorDialog::accepted); + + QObject::connect(m_comboBoxItem, QOverload::of(&QComboBox::currentIndexChanged), + this, &BindingEditorDialog::itemIDChanged); + QObject::connect(m_comboBoxProperty, QOverload::of(&QComboBox::currentIndexChanged), + this, &BindingEditorDialog::propertyIDChanged); + QObject::connect(m_editorWidget, &QPlainTextEdit::textChanged, + this, &BindingEditorDialog::textChanged); } 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; } @@ -216,6 +255,55 @@ void BindingEditorDialog::setEditorValue(const QString &text) m_editorWidget->document()->setPlainText(text); } +void BindingEditorDialog::setAllBindings(QList bindings) +{ + m_lock = true; + + m_bindings = bindings; + setupComboBoxes(); + adjustProperties(); + + m_lock = false; +} + +void BindingEditorDialog::adjustProperties() +{ + const QString expression = editorValue(); + QString item; + QString property; + QStringList expressionElements = expression.split("."); + + if (!expressionElements.isEmpty()) { + const int itemIndex = m_bindings.indexOf(expressionElements.at(0)); + + if (itemIndex != -1) { + item = expressionElements.at(0); + expressionElements.removeFirst(); + + if (!expressionElements.isEmpty()) { + const QString sum = expressionElements.join("."); + + if (m_bindings.at(itemIndex).properties.contains(sum)) + property = sum; + } + } + } + + if (item.isEmpty()) { + item = undefinedString; + if (m_comboBoxItem->findText(item) == -1) + m_comboBoxItem->addItem(item); + } + m_comboBoxItem->setCurrentText(item); + + if (property.isEmpty()) { + property = undefinedString; + if (m_comboBoxProperty->findText(property) == -1) + m_comboBoxProperty->addItem(property); + } + m_comboBoxProperty->setCurrentText(property); +} + void BindingEditorDialog::unregisterAutoCompletion() { if (m_editorWidget) @@ -237,7 +325,6 @@ void BindingEditorDialog::setupJSEditor() m_editorWidget->qmljsdocument = qobject_cast( qmlDesignerEditor->widget())->qmlJsEditorDocument(); - m_editorWidget->setParent(this); m_editorWidget->setLineNumbersVisible(false); m_editorWidget->setMarksVisible(false); @@ -245,24 +332,87 @@ void BindingEditorDialog::setupJSEditor() m_editorWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_editorWidget->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_editorWidget->setTabChangesFocus(true); - m_editorWidget->show(); } void BindingEditorDialog::setupUIComponents() { m_verticalLayout = new QVBoxLayout(this); + 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_buttonBox = new QDialogButtonBox(this); m_buttonBox->setOrientation(Qt::Horizontal); - m_buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); - m_editorWidget->setFrameStyle(QFrame::StyledPanel | QFrame::Raised); + m_buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + + 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(600,200); + this->resize(660, 240); } +void BindingEditorDialog::setupComboBoxes() +{ + m_comboBoxItem->clear(); + m_comboBoxProperty->clear(); + + for (auto bind : m_bindings) + m_comboBoxItem->addItem(bind.item); +} + +void BindingEditorDialog::itemIDChanged(int itemID) +{ + const QString previousProperty = m_comboBoxProperty->currentText(); + m_comboBoxProperty->clear(); + + if (m_bindings.size() > itemID && itemID != -1) { + m_comboBoxProperty->addItems(m_bindings.at(itemID).properties); + + if (!m_lock) + if (m_comboBoxProperty->findText(previousProperty) != -1) + m_comboBoxProperty->setCurrentText(previousProperty); + + const int undefinedItem = m_comboBoxItem->findText(undefinedString); + if ((undefinedItem != -1) && (m_comboBoxItem->itemText(itemID) != undefinedString)) + m_comboBoxItem->removeItem(undefinedItem); + } +} + +void BindingEditorDialog::propertyIDChanged(int propertyID) +{ + const int itemID = m_comboBoxItem->currentIndex(); + + if (!m_lock) + if (!m_comboBoxProperty->currentText().isEmpty() && (m_comboBoxProperty->currentText() != undefinedString)) + setEditorValue(m_comboBoxItem->itemText(itemID) + "." + m_comboBoxProperty->itemText(propertyID)); + + const int undefinedProperty = m_comboBoxProperty->findText(undefinedString); + if ((undefinedProperty != -1) && (m_comboBoxProperty->itemText(propertyID) != undefinedString)) + m_comboBoxProperty->removeItem(undefinedProperty); +} + +void BindingEditorDialog::textChanged() +{ + if (m_lock) + return; + + m_lock = true; + adjustProperties(); + m_lock = false; +} + + BindingEditor::BindingEditor(QObject *) { } @@ -320,5 +470,74 @@ void BindingEditor::setBindingValue(const QString &text) m_dialog->setEditorValue(text); } +void BindingEditor::setBackendValue(const QVariant &backendValue) +{ + if (!backendValue.isNull() && backendValue.isValid()) { + m_backendValue = backendValue; + const QObject *backendValueObj = backendValue.value(); + const PropertyEditorValue *propertyEditorValue = qobject_cast(backendValueObj); + + m_backendValueTypeName = propertyEditorValue->modelNode().metaInfo().propertyTypeName( + propertyEditorValue->name()); + + emit backendValueChanged(); + } +} + +void BindingEditor::setModelNodeBackend(const QVariant &modelNodeBackend) +{ + if (!modelNodeBackend.isNull() && modelNodeBackend.isValid()) { + m_modelNodeBackend = modelNodeBackend; + + emit modelNodeBackendChanged(); + } +} + +void BindingEditor::prepareBindings() +{ + if (m_backendValue.isNull() || m_modelNodeBackend.isNull()) + return; + + if (!(m_backendValue.isValid() && m_modelNodeBackend.isValid())) + return; + + const auto modelNodeBackendObject = m_modelNodeBackend.value(); + + const auto backendObjectCasted = + qobject_cast(modelNodeBackendObject); + + if (backendObjectCasted) { + const QmlDesigner::ModelNode a = backendObjectCasted->qmlObjectNode().modelNode(); + const QList allNodes = a.view()->allModelNodes(); + + QList bindings; + + for (auto objnode : allNodes) { + BindingEditorDialog::BindingOption binding; + for (auto propertyName : objnode.metaInfo().propertyNames()) + if (m_backendValueTypeName == objnode.metaInfo().propertyTypeName(propertyName)) + binding.properties.append(QString::fromUtf8(propertyName)); + + if (!binding.properties.isEmpty() && objnode.hasId()) { + binding.item = objnode.displayName(); + bindings.append(binding); + } + } + + if (!bindings.isEmpty() && !m_dialog.isNull()) + m_dialog->setAllBindings(bindings); + } +} + +QVariant BindingEditor::backendValue() const +{ + return m_backendValue; +} + +QVariant BindingEditor::modelNodeBackend() const +{ + return m_modelNodeBackend; +} + } diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h index 4c57ed28479..ccb3a1d4683 100644 --- a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h @@ -42,6 +42,8 @@ QT_BEGIN_NAMESPACE class QTextEdit; class QDialogButtonBox; class QVBoxLayout; +class QHBoxLayout; +class QComboBox; QT_END_NAMESPACE namespace QmlDesigner { @@ -49,6 +51,7 @@ namespace QmlDesigner { class BindingEditorContext : public Core::IContext { Q_OBJECT + public: BindingEditorContext(QWidget *parent) : Core::IContext(parent) { @@ -59,14 +62,22 @@ public: class BindingEditorWidget : public QmlJSEditor::QmlJSEditorWidget { Q_OBJECT + public: BindingEditorWidget(); - ~BindingEditorWidget(); + ~BindingEditorWidget() override; void unregisterAutoCompletion(); - TextEditor::AssistInterface *createAssistInterface(TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const; + bool event(QEvent *event) override; + TextEditor::AssistInterface *createAssistInterface(TextEditor::AssistKind assistKind, + TextEditor::AssistReason assistReason) const override; + +signals: + void returnKeyClicked(); + +public: QmlJSEditor::QmlJSEditorDocument *qmljsdocument = nullptr; BindingEditorContext *m_context = nullptr; QAction *m_completionAction = nullptr; @@ -76,6 +87,19 @@ class BindingEditorDialog : public QDialog { Q_OBJECT +public: + struct BindingOption + { + BindingOption() {} + BindingOption(const QString &value) { item = value; } + + bool operator==(const QString &value) const { return value == item; } + bool operator==(const BindingOption &value) const { return value.item == item; } + + QString item; + QStringList properties; + }; + public: BindingEditorDialog(QWidget *parent = nullptr); ~BindingEditorDialog() override; @@ -85,17 +109,32 @@ public: QString editorValue() const; void setEditorValue(const QString &text); + void setAllBindings(QList bindings); + void adjustProperties(); + void unregisterAutoCompletion(); private: void setupJSEditor(); void setupUIComponents(); + void setupComboBoxes(); + +public slots: + void itemIDChanged(int); + void propertyIDChanged(int); + void textChanged(); private: 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 m_bindings; + bool m_lock = false; + const QString undefinedString = "[Undefined]"; }; class BindingEditor : public QObject @@ -103,6 +142,8 @@ class BindingEditor : public QObject Q_OBJECT Q_PROPERTY(QString text READ bindingValue WRITE setBindingValue) + Q_PROPERTY(QVariant backendValueProperty READ backendValue WRITE setBackendValue NOTIFY backendValueChanged) + Q_PROPERTY(QVariant modelNodeBackendProperty READ modelNodeBackend WRITE setModelNodeBackend NOTIFY modelNodeBackendChanged) public: BindingEditor(QObject *parent = nullptr); @@ -116,13 +157,26 @@ public: QString bindingValue() const; void setBindingValue(const QString &text); + void setBackendValue(const QVariant &backendValue); + void setModelNodeBackend(const QVariant &modelNodeBackend); + + Q_INVOKABLE void prepareBindings(); + signals: void accepted(); void rejected(); + void backendValueChanged(); + void modelNodeBackendChanged(); + +private: + QVariant backendValue() const; + QVariant modelNodeBackend() const; private: QPointer m_dialog; - + QVariant m_backendValue; + QVariant m_modelNodeBackend; + TypeName m_backendValueTypeName; }; }