From 309021a73fdca28f1ca0cb54d6b9d7cef83c2863 Mon Sep 17 00:00:00 2001 From: Aleksei German Date: Fri, 9 Aug 2019 14:19:10 +0200 Subject: QmlDesigner New Binding Editor Change-Id: I75597a575cf4c6b8c0795d9f83a245539efb9ed8 Reviewed-by: Thomas Hartmann --- .../components/bindingeditor/bindingeditor.cpp | 322 +++++++++++++++++++++ .../components/bindingeditor/bindingeditor.h | 130 +++++++++ .../components/bindingeditor/bindingeditor.pri | 3 + 3 files changed, 455 insertions(+) create mode 100644 src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp create mode 100644 src/plugins/qmldesigner/components/bindingeditor/bindingeditor.h create mode 100644 src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri (limited to 'src/plugins/qmldesigner/components/bindingeditor') diff --git a/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.cpp new file mode 100644 index 0000000000..a1b5490702 --- /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 0000000000..44fe6e3bf8 --- /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 0000000000..518905eb2a --- /dev/null +++ b/src/plugins/qmldesigner/components/bindingeditor/bindingeditor.pri @@ -0,0 +1,3 @@ +HEADERS += $$PWD/bindingeditor.h + +SOURCES += $$PWD/bindingeditor.cpp -- cgit v1.2.1