QmlDesigner New Binding Editor

Change-Id: I75597a575cf4c6b8c0795d9f83a245539efb9ed8
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Aleksei German
2019-08-09 14:19:10 +02:00
committed by Thomas Hartmann
parent a11320bbe9
commit 309021a73f
9 changed files with 505 additions and 66 deletions

View File

@@ -27,6 +27,7 @@ import QtQuick 2.1
import StudioControls 1.0 as StudioControls import StudioControls 1.0 as StudioControls
import StudioTheme 1.0 as StudioTheme import StudioTheme 1.0 as StudioTheme
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
import HelperWidgets 2.0
Item { Item {
id: extendedFunctionButton id: extendedFunctionButton
@@ -144,62 +145,27 @@ Item {
function show() { function show() {
expressionDialogLoader.visible = true expressionDialogLoader.visible = true
} }
sourceComponent: Item {
sourceComponent: Component { id: bindingEditorParent
Item {
id: expressionDialog
anchors.fill: parent
Component.onCompleted: { Component.onCompleted: {
textField.text = backendValue.expression var x = extendedFunctionButton.mapToGlobal(0,0).x - 200
textField.forceActiveFocus() var y = extendedFunctionButton.mapToGlobal(0,0).y - 40
bindingEditor.showWidget(x, y)
bindingEditor.text = backendValue.expression
} }
Rectangle { BindingEditor {
anchors.fill: parent id: bindingEditor
color: Theme.qmlDesignerBackgroundColorDarker()
opacity: 0.6
}
MouseArea { onRejected: {
anchors.fill: parent hideWidget()
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 expressionDialogLoader.visible = false
} }
anchors.fill: parent onAccepted: {
anchors.leftMargin: 8 backendValue.expression = bindingEditor.text.trim()
anchors.rightMargin: 8 hideWidget()
anchors.topMargin: 24 expressionDialogLoader.visible = false
anchors.bottomMargin: 32
}
} }
} }
} }

View File

@@ -30,7 +30,6 @@ import HelperWidgets 2.0
import QtQuickDesignerTheme 1.0 import QtQuickDesignerTheme 1.0
Rectangle { Rectangle {
z: expressionTextField.visible ? 5 : 0
border.width: 1 border.width: 1
property bool isBaseState property bool isBaseState
property bool isCurrentState property bool isCurrentState
@@ -111,9 +110,10 @@ Rectangle {
MenuItem { MenuItem {
text: qsTr("Set when Condition") text: qsTr("Set when Condition")
onTriggered: { onTriggered: {
expressionTextField.text = delegateWhenConditionString var x = whenButton.mapToGlobal(0,0).x + 4
expressionTextField.visible = true var y = root.mapToGlobal(0,0).y - 32
expressionTextField.forceActiveFocus() bindingEditor.showWidget(x, y)
bindingEditor.text = delegateWhenConditionString
} }
} }
@@ -192,19 +192,27 @@ Rectangle {
} }
} }
ExpressionTextField { BindingEditor {
id: expressionTextField property string newWhenCondition
parent: root property Timer timer: Timer {
visible: false id: timer
onAccepted: { running: false
visible = false interval: 50
statesEditorModel.setWhenCondition(internalNodeId, expressionTextField.text.trim()) 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()
}
} }
} }

View File

@@ -498,6 +498,11 @@ extend_qtc_plugin(QmlDesigner
rewritertransaction.cpp rewritertransaction.h rewritertransaction.cpp rewritertransaction.h
) )
extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/bindingeditor
SOURCES bindingeditor.cpp bindingeditor.h
)
extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner
SOURCES_PREFIX components/colortool SOURCES_PREFIX components/colortool
SOURCES colortool.cpp colortool.h SOURCES colortool.cpp colortool.h

View File

@@ -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 <qmldesignerplugin.h>
#include "texteditorview.h"
#include "texteditorwidget.h"
#include <texteditor/texteditor.h>
#include <coreplugin/icore.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <qmldesigner/qmldesignerplugin.h>
#include <qmldesigner/qmldesignerconstants.h>
#include <qmljseditor/qmljseditor.h>
#include <qmljseditor/qmljseditorconstants.h>
#include <qmljstools/qmljstoolsconstants.h>
#include <qmljseditor/qmljscompletionassist.h>
#include <qmljseditor/qmljshighlighter.h>
#include <qmljseditor/qmljshoverhandler.h>
#include <qmljstools/qmljsindenter.h>
#include <qmljseditor/qmljsautocompleter.h>
#include <qmljseditor/qmljseditordocument.h>
#include <qmljseditor/qmljssemantichighlighter.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditoractionhandler.h>
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/completionassistprovider.h>
#include <texteditor/syntaxhighlighter.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <QDialogButtonBox>
#include <QDebug>
#include <QVBoxLayout>
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<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->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<BindingEditor>("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);
}
}

View File

@@ -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 <texteditor/texteditor.h>
#include <QtQml>
#include <QWidget>
#include <QDialog>
#include <QPointer>
#include <qmljseditor/qmljseditor.h>
#include <memory>
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<BindingEditorDialog> m_dialog;
};
}
QML_DECLARE_TYPE(QmlDesigner::BindingEditor)
#endif //BINDINGEDITOR_H

View File

@@ -0,0 +1,3 @@
HEADERS += $$PWD/bindingeditor.h
SOURCES += $$PWD/bindingeditor.cpp

View File

@@ -31,6 +31,7 @@
#include "gradientpresetdefaultlistmodel.h" #include "gradientpresetdefaultlistmodel.h"
#include "gradientpresetcustomlistmodel.h" #include "gradientpresetcustomlistmodel.h"
#include "simplecolorpalettemodel.h" #include "simplecolorpalettemodel.h"
#include "bindingeditor/bindingeditor.h"
#include "qmlanchorbindingproxy.h" #include "qmlanchorbindingproxy.h"
#include "theme.h" #include "theme.h"
@@ -55,6 +56,7 @@ void Quick2PropertyEditorView::registerQmlTypes()
GradientPresetCustomListModel::registerDeclarativeType(); GradientPresetCustomListModel::registerDeclarativeType();
SimpleColorPaletteModel::registerDeclarativeType(); SimpleColorPaletteModel::registerDeclarativeType();
Internal::QmlAnchorBindingProxy::registerDeclarativeType(); Internal::QmlAnchorBindingProxy::registerDeclarativeType();
BindingEditor::registerDeclarativeType();
} }
} }

View File

@@ -28,6 +28,7 @@ include(components/pathtool/pathtool.pri)
include(components/timelineeditor/timelineeditor.pri) include(components/timelineeditor/timelineeditor.pri)
include(components/connectioneditor/connectioneditor.pri) include(components/connectioneditor/connectioneditor.pri)
include(components/curveeditor/curveeditor.pri) include(components/curveeditor/curveeditor.pri)
include(components/bindingeditor/bindingeditor.pri)
BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH) BUILD_PUPPET_IN_CREATOR_BINPATH = $$(BUILD_PUPPET_IN_CREATOR_BINPATH)

View File

@@ -609,6 +609,8 @@ Project {
name: "extension" name: "extension"
prefix: "components/" prefix: "components/"
files: [ files: [
"bindingeditor/bindingeditor.cpp",
"bindingeditor/bindingeditor.h",
"colortool/colortool.cpp", "colortool/colortool.cpp",
"colortool/colortool.h", "colortool/colortool.h",
"connectioneditor/addnewbackenddialog.h", "connectioneditor/addnewbackenddialog.h",