diff --git a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml index f64dd622206..497fcfd2afc 100644 --- a/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml +++ b/share/qtcreator/qmldesigner/propertyEditorQmlSources/imports/HelperWidgets/ExtendedFunctionLogic.qml @@ -27,6 +27,7 @@ import QtQuick 2.1 import StudioControls 1.0 as StudioControls import StudioTheme 1.0 as StudioTheme import QtQuickDesignerTheme 1.0 +import HelperWidgets 2.0 Item { id: extendedFunctionButton @@ -144,62 +145,27 @@ Item { function show() { expressionDialogLoader.visible = true } + sourceComponent: Item { + id: bindingEditorParent - sourceComponent: Component { - Item { - id: expressionDialog - anchors.fill: parent + Component.onCompleted: { + var x = extendedFunctionButton.mapToGlobal(0,0).x - 200 + var y = extendedFunctionButton.mapToGlobal(0,0).y - 40 + bindingEditor.showWidget(x, y) + bindingEditor.text = backendValue.expression + } - Component.onCompleted: { - textField.text = backendValue.expression - textField.forceActiveFocus() + BindingEditor { + id: bindingEditor + + onRejected: { + hideWidget() + expressionDialogLoader.visible = false } - - Rectangle { - anchors.fill: parent - color: Theme.qmlDesignerBackgroundColorDarker() - opacity: 0.6 - } - - MouseArea { - anchors.fill: parent - onDoubleClicked: expressionDialog.visible = false - } - - Rectangle { - x: 4 - Component.onCompleted: { - var pos = itemPane.mapFromItem( - extendedFunctionButton.parent, 0, 0) - y = pos.y + 2 - } - - width: parent.width - 8 - height: 260 - - radius: 2 - color: Theme.qmlDesignerBackgroundColorDarkAlternate() - border.color: Theme.qmlDesignerBorderColor() - - Label { - x: 8 - y: 6 - font.bold: true - text: qsTr("Binding Editor") - } - ExpressionTextField { - id: textField - onRejected: expressionDialogLoader.visible = false - onAccepted: { - backendValue.expression = textField.text.trim() - expressionDialogLoader.visible = false - } - anchors.fill: parent - anchors.leftMargin: 8 - anchors.rightMargin: 8 - anchors.topMargin: 24 - anchors.bottomMargin: 32 - } + onAccepted: { + backendValue.expression = bindingEditor.text.trim() + hideWidget() + expressionDialogLoader.visible = false } } } diff --git a/share/qtcreator/qmldesigner/statesEditorQmlSources/StatesDelegate.qml b/share/qtcreator/qmldesigner/statesEditorQmlSources/StatesDelegate.qml index 26d4c21cc90..5f044b73f44 100644 --- a/share/qtcreator/qmldesigner/statesEditorQmlSources/StatesDelegate.qml +++ b/share/qtcreator/qmldesigner/statesEditorQmlSources/StatesDelegate.qml @@ -30,7 +30,6 @@ import HelperWidgets 2.0 import QtQuickDesignerTheme 1.0 Rectangle { - z: expressionTextField.visible ? 5 : 0 border.width: 1 property bool isBaseState property bool isCurrentState @@ -111,9 +110,10 @@ Rectangle { MenuItem { text: qsTr("Set when Condition") onTriggered: { - expressionTextField.text = delegateWhenConditionString - expressionTextField.visible = true - expressionTextField.forceActiveFocus() + var x = whenButton.mapToGlobal(0,0).x + 4 + var y = root.mapToGlobal(0,0).y - 32 + bindingEditor.showWidget(x, y) + bindingEditor.text = delegateWhenConditionString } } @@ -192,19 +192,27 @@ Rectangle { } } - ExpressionTextField { - id: expressionTextField + BindingEditor { + property string newWhenCondition - parent: root - visible: false - onAccepted: { - visible = false - statesEditorModel.setWhenCondition(internalNodeId, expressionTextField.text.trim()) + property Timer timer: Timer { + id: timer + running: false + interval: 50 + repeat: false + onTriggered: statesEditorModel.setWhenCondition(internalNodeId, bindingEditor.newWhenCondition) } - onRejected: visible = false + id: bindingEditor - anchors.fill: parent + onRejected: { + hideWidget() + } + onAccepted: { + bindingEditor.newWhenCondition = bindingEditor.text.trim() + timer.start() + hideWidget() + } } } diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index 1786026e731..26d00a63d34 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -498,6 +498,11 @@ extend_qtc_plugin(QmlDesigner rewritertransaction.cpp rewritertransaction.h ) +extend_qtc_plugin(QmlDesigner + SOURCES_PREFIX components/bindingeditor + SOURCES bindingeditor.cpp bindingeditor.h +) + extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/colortool SOURCES colortool.cpp colortool.h diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp new file mode 100644 index 00000000000..a1b54907021 --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "bindingeditor.h" + +#include + +#include "texteditorview.h" +#include "texteditorwidget.h" +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace QmlDesigner { + +static BindingEditor *s_lastBindingEditor = nullptr; + +const char BINDINGEDITOR_CONTEXT_ID[] = "BindingEditor.BindingEditorContext"; + +BindingEditorWidget::BindingEditorWidget() + : m_context(new BindingEditorContext(this)) +{ + Core::ICore::addContextObject(m_context); + + Core::Context context(BINDINGEDITOR_CONTEXT_ID); + + /* + * We have to register our own active auto completion shortcut, because the original short cut will + * use the cursor position of the original editor in the editor manager. + */ + + 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"))); + + connect(m_completionAction, &QAction::triggered, [this]() { + invokeAssist(TextEditor::Completion); + }); +} + +BindingEditorWidget::~BindingEditorWidget() +{ + unregisterAutoCompletion(); + + Core::ICore::removeContextObject(m_context); + delete m_context; +} + +void BindingEditorWidget::unregisterAutoCompletion() +{ + 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 +{ + Q_UNUSED(assistKind); + return new QmlJSEditor::QmlJSCompletionAssistInterface(document(), position(), QString(), assistReason, qmljsdocument->semanticInfo()); +} + +class BindingDocument : public QmlJSEditor::QmlJSEditorDocument +{ +public: + BindingDocument() : m_semanticHighlighter(new QmlJSEditor::SemanticHighlighter(this)) {} + ~BindingDocument() { delete m_semanticHighlighter; } + +protected: + void applyFontSettings() + { + TextDocument::applyFontSettings(); + m_semanticHighlighter->updateFontSettings(fontSettings()); + if (!isSemanticInfoOutdated()) { + m_semanticHighlighter->rerun(semanticInfo()); + } + } + + void triggerPendingUpdates() + { + TextDocument::triggerPendingUpdates(); // calls applyFontSettings if necessary + if (!isSemanticInfoOutdated()) { + m_semanticHighlighter->rerun(semanticInfo()); + } + } + +private: + QmlJSEditor::SemanticHighlighter *m_semanticHighlighter = nullptr; +}; + +class BindingEditorFactory : public TextEditor::TextEditorFactory +{ +public: + BindingEditorFactory() { + setId(BINDINGEDITOR_CONTEXT_ID); + setDisplayName(QCoreApplication::translate("OpenWith::Editors", BINDINGEDITOR_CONTEXT_ID)); + + + setDocumentCreator([]() { return new BindingDocument; }); + setEditorWidgetCreator([]() { return new BindingEditorWidget; }); + setEditorCreator([]() { return new QmlJSEditor::QmlJSEditor; }); + setAutoCompleterCreator([]() { return new QmlJSEditor::AutoCompleter; }); + setCommentDefinition(Utils::CommentDefinition::CppStyle); + setParenthesesMatchingEnabled(true); + setCodeFoldingSupported(true); + + addHoverHandler(new QmlJSEditor::QmlJSHoverHandler); + setCompletionAssistProvider(new QmlJSEditor::QmlJSCompletionAssistProvider); + } + + static void decorateEditor(TextEditor::TextEditorWidget *editor) + { + editor->textDocument()->setSyntaxHighlighter(new QmlJSEditor::QmlJSHighlighter); + 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")); + setModal(false); + + setupJSEditor(); + setupUIComponents(); + + QObject::connect(m_buttonBox, &QDialogButtonBox::accepted, + this, &BindingEditorDialog::accepted); + QObject::connect(m_buttonBox, &QDialogButtonBox::rejected, + this, &BindingEditorDialog::rejected); +} + +BindingEditorDialog::~BindingEditorDialog() +{ + delete m_editor; //m_editorWidget is handled by basetexteditor destructor + delete m_buttonBox; + delete m_verticalLayout; +} + +void BindingEditorDialog::showWidget(int x, int y) +{ + this->show(); + this->raise(); + move(QPoint(x, y)); + m_editorWidget->setFocus(); +} + +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::unregisterAutoCompletion() +{ + if (m_editorWidget) + m_editorWidget->unregisterAutoCompletion(); +} + +void BindingEditorDialog::setupJSEditor() +{ + static BindingEditorFactory f; + m_editor = qobject_cast(f.createEditor()); + m_editorWidget = qobject_cast(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( + qmlDesignerEditor->widget())->qmlJsEditorDocument(); + + m_editorWidget->setParent(this); + + 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_editorWidget->show(); +} + +void BindingEditorDialog::setupUIComponents() +{ + m_verticalLayout = new QVBoxLayout(this); + + 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_verticalLayout->addWidget(m_editorWidget); + m_verticalLayout->addWidget(m_buttonBox); + + this->resize(600,200); +} + +BindingEditor::BindingEditor(QObject *) +{ +} + +BindingEditor::~BindingEditor() +{ + hideWidget(); +} + +void BindingEditor::registerDeclarativeType() +{ + qmlRegisterType("HelperWidgets", 2, 0, "BindingEditor"); +} + +void BindingEditor::showWidget(int x, int y) +{ + if (s_lastBindingEditor) + s_lastBindingEditor->hideWidget(); + s_lastBindingEditor = this; + + m_dialog = new BindingEditorDialog(Core::ICore::dialogParent()); + + + QObject::connect(m_dialog, &BindingEditorDialog::accepted, + this, &BindingEditor::accepted); + QObject::connect(m_dialog, &BindingEditorDialog::rejected, + this, &BindingEditor::rejected); + + m_dialog->setAttribute(Qt::WA_DeleteOnClose); + m_dialog->showWidget(x, y); +} + +void BindingEditor::hideWidget() +{ + if (s_lastBindingEditor == this) + s_lastBindingEditor = nullptr; + if (m_dialog) + { + m_dialog->unregisterAutoCompletion(); //we have to do it separately, otherwise we have an autocompletion action override + m_dialog->close(); + } +} + +QString BindingEditor::bindingValue() const +{ + if (!m_dialog) + return {}; + + return m_dialog->editorValue(); +} + +void BindingEditor::setBindingValue(const QString &text) +{ + if (m_dialog) + m_dialog->setEditorValue(text); +} + + +} diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h new file mode 100644 index 00000000000..44fe6e3bf8b --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 BINDINGEDITOR_H +#define BINDINGEDITOR_H + +#include "texteditorview.h" +#include +#include + +#include +#include +#include + +#include + +#include + +class QTextEdit; +class QDialogButtonBox; +class QVBoxLayout; + +namespace QmlDesigner { + +class BindingEditorContext : public Core::IContext +{ + Q_OBJECT +public: + BindingEditorContext(QWidget *parent) : Core::IContext(parent) + { + setWidget(parent); + } +}; + +class BindingEditorWidget : public QmlJSEditor::QmlJSEditorWidget +{ + Q_OBJECT +public: + BindingEditorWidget(); + ~BindingEditorWidget(); + + void unregisterAutoCompletion(); + + TextEditor::AssistInterface *createAssistInterface(TextEditor::AssistKind assistKind, TextEditor::AssistReason assistReason) const; + + QmlJSEditor::QmlJSEditorDocument *qmljsdocument = nullptr; + BindingEditorContext *m_context = nullptr; + QAction *m_completionAction = nullptr; +}; + +class BindingEditorDialog : public QDialog +{ + Q_OBJECT + +public: + BindingEditorDialog(QWidget *parent = nullptr); + ~BindingEditorDialog() override; + + void showWidget(int x, int y); + + QString editorValue() const; + void setEditorValue(const QString &text); + + void unregisterAutoCompletion(); + +private: + void setupJSEditor(); + void setupUIComponents(); + +private: + TextEditor::BaseTextEditor *m_editor = nullptr; + BindingEditorWidget *m_editorWidget = nullptr; + QVBoxLayout *m_verticalLayout = nullptr; + QDialogButtonBox *m_buttonBox = nullptr; +}; + +class BindingEditor : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString text READ bindingValue WRITE setBindingValue) + +public: + BindingEditor(QObject *parent = nullptr); + ~BindingEditor(); + + static void registerDeclarativeType(); + + Q_INVOKABLE void showWidget(int x, int y); + Q_INVOKABLE void hideWidget(); + + QString bindingValue() const; + void setBindingValue(const QString &text); + +signals: + void accepted(); + void rejected(); + +private: + QPointer m_dialog; + +}; + +} + +QML_DECLARE_TYPE(QmlDesigner::BindingEditor) + +#endif //BINDINGEDITOR_H diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri new file mode 100644 index 00000000000..518905eb2a0 --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri @@ -0,0 +1,3 @@ +HEADERS += $$PWD/bindingeditor.h + +SOURCES += $$PWD/bindingeditor.cpp diff --git a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp index e475b982d7d..e6288ea861c 100644 --- a/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp +++ b/src/plugins/qmldesigner/components/propertyeditor/quick2propertyeditorview.cpp @@ -31,6 +31,7 @@ #include "gradientpresetdefaultlistmodel.h" #include "gradientpresetcustomlistmodel.h" #include "simplecolorpalettemodel.h" +#include "bindingeditor/bindingeditor.h" #include "qmlanchorbindingproxy.h" #include "theme.h" @@ -55,6 +56,7 @@ void Quick2PropertyEditorView::registerQmlTypes() GradientPresetCustomListModel::registerDeclarativeType(); SimpleColorPaletteModel::registerDeclarativeType(); Internal::QmlAnchorBindingProxy::registerDeclarativeType(); + BindingEditor::registerDeclarativeType(); } } diff --git a/src/plugins/qmldesigner/qmldesignerplugin.pro b/src/plugins/qmldesigner/qmldesignerplugin.pro index 52e3e19a44a..625770cb723 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.pro +++ b/src/plugins/qmldesigner/qmldesignerplugin.pro @@ -28,6 +28,7 @@ include(components/pathtool/pathtool.pri) include(components/timelineeditor/timelineeditor.pri) include(components/connectioneditor/connectioneditor.pri) include(components/curveeditor/curveeditor.pri) +include(components/bindingeditor/bindingeditor.pri) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 24f77ddedd4..a06195f3fcf 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -609,6 +609,8 @@ Project { name: "extension" prefix: "components/" files: [ + "bindingeditor/bindingeditor.cpp", + "bindingeditor/bindingeditor.h", "colortool/colortool.cpp", "colortool/colortool.h", "connectioneditor/addnewbackenddialog.h",