Commit 759db2b7 authored by Thomas Hartmann's avatar Thomas Hartmann Committed by Tim Jenssen

QmlDesigner: Polish error handling for integrated text editor

The integrated text editor requires a couple of fixes and features
in the error handling.

The errors are now handled by the model and not the document management
anymore.
The text editor does not get disabled if there is an error. Instead
we show the error in a status bar.
The form editor is blocked if there is a QML an error and we show the
error message inside the form editor.

Change-Id: I4bfb9b33b09e444ec1de31dd531ce83b32cbcf88
Reviewed-by: Tim Jenssen's avatarTim Jenssen <tim.jenssen@qt.io>
parent 8b67458a
......@@ -264,6 +264,25 @@ void DebugView::auxiliaryDataChanged(const ModelNode &node, const PropertyName &
}
}
void DebugView::documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &warnings)
{
if (isDebugViewEnabled()) {
QTextStream message;
QString string;
message.setString(&string);
foreach (const RewriterError &error, errors) {
message << error.toString();
}
foreach (const RewriterError &warning, warnings) {
message << warning.toString();
}
log("::documentMessageChanged:", string);
}
}
void DebugView::rewriterBeginTransaction()
{
if (isDebugViewEnabled())
......
......@@ -65,6 +65,7 @@ public:
void propertiesRemoved(const QList<AbstractProperty> &propertyList) override;
void auxiliaryDataChanged(const ModelNode &node, const PropertyName &name, const QVariant &data) override;
void documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &warnings) override;
void rewriterBeginTransaction() override;
void rewriterEndTransaction() override;
......
......@@ -258,7 +258,7 @@ WidgetInfo FormEditorView::widgetInfo()
if (!m_formEditorWidget)
createFormEditorWidget();
return createWidgetInfo(m_formEditorWidget.data(), 0, "FormEditor", WidgetInfo::CentralPane, 0, tr("Form Editor"));
return createWidgetInfo(m_formEditorWidget.data(), 0, "FormEditor", WidgetInfo::CentralPane, 0, tr("Form Editor"), DesignerWidgetFlags::IgnoreErrors);
}
FormEditorWidget *FormEditorView::formEditorWidget()
......@@ -284,6 +284,17 @@ void FormEditorView::selectedNodesChanged(const QList<ModelNode> &selectedNodeLi
m_scene->update();
}
void FormEditorView::documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &warnings)
{
if (!errors.isEmpty())
formEditorWidget()->showErrorMessageBox(errors);
else
formEditorWidget()->hideErrorMessageBox();
if (!warnings.isEmpty())
formEditorWidget()->showWarningMessageBox(warnings);
}
void FormEditorView::customNotification(const AbstractView * /*view*/, const QString &identifier, const QList<ModelNode> &/*nodeList*/, const QList<QVariant> &/*data*/)
{
if (identifier == QStringLiteral("puppet crashed"))
......@@ -511,6 +522,17 @@ double FormEditorView::spacing() const
return m_formEditorWidget->spacing();
}
void FormEditorView::gotoError(int line, int column)
{
if (m_gotoErrorCallback)
m_gotoErrorCallback(line, column);
}
void FormEditorView::setGotoErrorCallback(std::function<void (int, int)> gotoErrorCallback)
{
m_gotoErrorCallback = gotoErrorCallback;
}
QList<ModelNode> FormEditorView::adjustStatesForModelNodes(const QList<ModelNode> &nodeList) const
{
QList<ModelNode> adjustedNodeList;
......
......@@ -26,6 +26,7 @@
#include <abstractview.h>
#include <functional>
#include <memory>
QT_BEGIN_NAMESPACE
......@@ -74,6 +75,9 @@ public:
void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList) override;
virtual void documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &warnings) override;
void customNotification(const AbstractView *view, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
// FormEditorView
......@@ -109,6 +113,8 @@ public:
double containerPadding() const;
double spacing() const;
void deActivateItemCreator();
void gotoError(int, int);
void setGotoErrorCallback(std::function<void(int, int)> gotoErrorCallback);
protected:
void reset();
......@@ -135,6 +141,7 @@ private: //variables
std::unique_ptr<DragTool> m_dragTool;
AbstractFormEditorTool *m_currentTool;
int m_transactionCounter;
std::function<void(int, int)> m_gotoErrorCallback;
};
} // namespace QmlDesigner
......@@ -252,6 +252,34 @@ void FormEditorWidget::setFocus()
m_graphicsView->setFocus(Qt::OtherFocusReason);
}
void FormEditorWidget::showErrorMessageBox(const QList<RewriterError> &errors)
{
errorWidget()->setErrors(errors);
errorWidget()->setVisible(true);
m_graphicsView->setDisabled(true);
m_toolBox->setDisabled(true);
}
void FormEditorWidget::hideErrorMessageBox()
{
if (!m_documentErrorWidget.isNull())
errorWidget()->setVisible(false);
m_graphicsView->setDisabled(false);
m_toolBox->setDisabled(false);
}
void FormEditorWidget::showWarningMessageBox(const QList<RewriterError> &warnings)
{
if (!errorWidget()->warningsEnabled())
return;
errorWidget()->setWarnings(warnings);
errorWidget()->setVisible(true);
m_graphicsView->setDisabled(true);
m_toolBox->setDisabled(true);
}
ZoomAction *FormEditorWidget::zoomAction() const
{
return m_zoomAction.data();
......@@ -325,6 +353,19 @@ QRectF FormEditorWidget::rootItemRect() const
return m_graphicsView->rootItemRect();
}
DocumentWarningWidget *FormEditorWidget::errorWidget()
{
if (m_documentErrorWidget.isNull()) {
m_documentErrorWidget = new DocumentWarningWidget(this);
connect(m_documentErrorWidget.data(), &DocumentWarningWidget::gotoCodeClicked, [=]
(const QString &, int codeLine, int codeColumn) {
m_formEditorView->gotoError(codeLine, codeColumn);
});
}
return m_documentErrorWidget;
}
}
......@@ -24,6 +24,8 @@
****************************************************************************/
#pragma once
#include <documentwarningwidget.h>
#include <QWidget>
#include <QPointer>
......@@ -72,9 +74,15 @@ public:
void setFocus();
void showErrorMessageBox(const QList<RewriterError> &errors);
void hideErrorMessageBox();
void showWarningMessageBox(const QList<RewriterError> &warnings);
protected:
void wheelEvent(QWheelEvent *event);
QActionGroup *toolActionGroup() const;
DocumentWarningWidget *errorWidget();
private slots:
void changeTransformTool(bool checked);
......@@ -99,6 +107,7 @@ private:
QPointer<LineEditAction> m_rootHeightAction;
QPointer<BackgroundAction> m_backgroundAction;
QPointer<QAction> m_resetAction;
QPointer<DocumentWarningWidget> m_documentErrorWidget;
};
} // namespace QmlDesigner
VPATH += $$PWD
SOURCES += texteditorview.cpp \
texteditorwidget.cpp
texteditorwidget.cpp \
$$PWD/texteditorstatusbar.cpp
HEADERS += texteditorview.h \
texteditorwidget.h
texteditorwidget.h \
$$PWD/texteditorstatusbar.h
/****************************************************************************
**
** Copyright (C) 2016 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 "texteditorstatusbar.h"
#include <utils/theme/theme.h>
#include <QVBoxLayout>
namespace QmlDesigner {
TextEditorStatusBar::TextEditorStatusBar(QWidget *parent) : QToolBar(parent), m_label(new QLabel(this))
{
QWidget *spacer = new QWidget(this);
spacer->setMinimumWidth(50);
addWidget(spacer);
addWidget(m_label);
/* We have to set another .css, since the central widget has already a style sheet */
m_label->setStyleSheet(QString("QLabel { color :%1 }").arg(Utils::creatorTheme()->color(Utils::Theme::TextColorError).name()));
}
void TextEditorStatusBar::clearText()
{
m_label->clear();
}
void TextEditorStatusBar::setText(const QString &text)
{
m_label->setText(text);
}
} // namespace QmlDesigner
/****************************************************************************
**
** Copyright (C) 2016 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.
**
****************************************************************************/
#pragma once
#include <QToolBar>
#include <QLabel>
namespace QmlDesigner {
class TextEditorStatusBar : public QToolBar
{
Q_OBJECT
public:
explicit TextEditorStatusBar(QWidget *parent = 0);
void clearText();
void setText(const QString &text);
private:
QLabel *m_label;
};
} // namespace QmlDesigner
......@@ -51,7 +51,6 @@ TextEditorView::TextEditorView(QObject *parent)
: AbstractView(parent)
, m_widget(new TextEditorWidget(this))
{
// not completely sure that we need this to just call the right help method ->
Internal::TextEditorContext *textEditorContext = new Internal::TextEditorContext(m_widget.get());
Core::ICore::addContextObject(textEditorContext);
}
......@@ -96,7 +95,7 @@ void TextEditorView::nodeReparented(const ModelNode &/*node*/, const NodeAbstrac
WidgetInfo TextEditorView::widgetInfo()
{
return createWidgetInfo(m_widget.get(), 0, "TextEditor", WidgetInfo::CentralPane, 0, tr("Text Editor"));
return createWidgetInfo(m_widget.get(), 0, "TextEditor", WidgetInfo::CentralPane, 0, tr("Text Editor"), DesignerWidgetFlags::IgnoreErrors);
}
QString TextEditorView::contextHelpId() const
......@@ -123,6 +122,16 @@ void TextEditorView::customNotification(const AbstractView * /*view*/, const QSt
{
}
void TextEditorView::documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &)
{
if (errors.isEmpty()) {
m_widget->clearStatusBar();
} else {
const RewriterError error = errors.first();
m_widget->setStatusText(QString("%1 (Line: %2)").arg(error.description()).arg(error.line()));
}
}
bool TextEditorView::changeToMoveTool()
{
return true;
......@@ -181,6 +190,12 @@ void TextEditorView::rewriterEndTransaction()
{
}
void TextEditorView::gotoCursorPosition(int line, int column)
{
if (m_widget)
m_widget->gotoCursorPosition(line, column);
}
void TextEditorView::instancePropertyChanged(const QList<QPair<ModelNode, PropertyName> > &/*propertyList*/)
{
}
......
......@@ -59,6 +59,7 @@ public:
void selectedNodesChanged(const QList<ModelNode> &selectedNodeList,
const QList<ModelNode> &lastSelectedNodeList) override;
void customNotification(const AbstractView *view, const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data) override;
void documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &warnings) override;
// TextEditorView
WidgetInfo widgetInfo() override;
......@@ -87,6 +88,8 @@ public:
void deActivateItemCreator();
void gotoCursorPosition(int line, int column);
private:
std::unique_ptr<TextEditorWidget> m_widget;
};
......
......@@ -25,7 +25,9 @@
#include "texteditorwidget.h"
#include <texteditorstatusbar.h>
#include <texteditorview.h>
#include <rewriterview.h>
#include <theming.h>
......@@ -38,10 +40,13 @@ namespace QmlDesigner {
TextEditorWidget::TextEditorWidget(TextEditorView *textEditorView) : QWidget()
, m_textEditorView(textEditorView)
, m_statusBar(new TextEditorStatusBar(this))
{
QBoxLayout *layout = new QVBoxLayout(this);
layout->setMargin(0);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
layout->addWidget(m_statusBar);
m_updateSelectionTimer.setSingleShot(true);
m_updateSelectionTimer.setInterval(200);
......@@ -52,7 +57,9 @@ TextEditorWidget::TextEditorWidget(TextEditorView *textEditorView) : QWidget()
void TextEditorWidget::setTextEditor(TextEditor::BaseTextEditor *textEditor) {
m_textEditor.reset(textEditor);
layout()->removeWidget(m_statusBar);
layout()->addWidget(textEditor->editorWidget());
layout()->addWidget(m_statusBar);
connect(textEditor->editorWidget(), &QPlainTextEdit::cursorPositionChanged,
......@@ -104,4 +111,22 @@ void TextEditorWidget::jumpTextCursorToSelectedModelNode()
}
}
void TextEditorWidget::gotoCursorPosition(int line, int column)
{
if (m_textEditor) {
m_textEditor->editorWidget()->gotoLine(line, column);
m_textEditor->editorWidget()->setFocus();
}
}
void TextEditorWidget::setStatusText(const QString &text)
{
m_statusBar->setText(text);
}
void TextEditorWidget::clearStatusBar()
{
m_statusBar->clearText();
}
} // namespace QmlDesigner
......@@ -34,6 +34,7 @@
namespace QmlDesigner {
class TextEditorView;
class TextEditorStatusBar;
class TextEditorWidget : public QWidget {
......@@ -51,6 +52,10 @@ public:
QString contextHelpId() const;
void jumpTextCursorToSelectedModelNode();
void gotoCursorPosition(int line, int column);
void setStatusText(const QString &text);
void clearStatusBar();
private:
void updateSelectionByCursorPosition();
......@@ -58,6 +63,7 @@ private:
std::unique_ptr<TextEditor::BaseTextEditor> m_textEditor;
QPointer<TextEditorView> m_textEditorView;
QTimer m_updateSelectionTimer;
TextEditorStatusBar *m_statusBar;
};
} // namespace QmlDesigner
......@@ -30,6 +30,7 @@
#include <model.h>
#include <modelnode.h>
#include <abstractproperty.h>
#include <rewritererror.h>
#include <rewritertransaction.h>
#include <commondefines.h>
......@@ -55,6 +56,11 @@ class NodeInstanceView;
class RewriterView;
class QmlModelState;
enum DesignerWidgetFlags {
DisableOnError,
IgnoreErrors
};
class WidgetInfo {
public:
......@@ -93,18 +99,13 @@ public:
CentralPane // not used
};
WidgetInfo()
: widget(0),
toolBarWidgetFactory(0)
{
}
QString uniqueId;
QString tabName;
QWidget *widget;
QWidget *widget = nullptr;
int placementPriority;
PlacementHint placementHint;
ToolBarWidgetFactoryInterface *toolBarWidgetFactory;
ToolBarWidgetFactoryInterface *toolBarWidgetFactory = nullptr;
DesignerWidgetFlags widgetFlags = DesignerWidgetFlags::DisableOnError;
};
class QMLDESIGNERCORE_EXPORT AbstractView : public QObject
......@@ -162,6 +163,8 @@ public:
QList<ModelNode> allModelNodes() const;
void emitDocumentMessage(const QList<RewriterError> &errors, const QList<RewriterError> &warnings = QList<RewriterError>());
void emitDocumentMessage(const QString &error);
void emitCustomNotification(const QString &identifier);
void emitCustomNotification(const QString &identifier, const QList<ModelNode> &nodeList);
void emitCustomNotification(const QString &identifier, const QList<ModelNode> &nodeList, const QList<QVariant> &data);
......@@ -227,6 +230,8 @@ public:
virtual void scriptFunctionsChanged(const ModelNode &node, const QStringList &scriptFunctionList);
virtual void documentMessagesChanged(const QList<RewriterError> &errors, const QList<RewriterError> &warnings);
void changeRootNodeType(const TypeName &type, int majorVersion, int minorVersion);
NodeInstanceView *nodeInstanceView() const;
......@@ -256,7 +261,7 @@ protected:
const QString &uniqueId = QString(),
WidgetInfo::PlacementHint placementHint = WidgetInfo::NoPane,
int placementPriority = 0,
const QString &tabName = QString());
const QString &tabName = QString(), DesignerWidgetFlags widgetFlags = DesignerWidgetFlags::DisableOnError);
private: //functions
QList<ModelNode> toModelNodeList(const QList<Internal::InternalNodePointer> &nodeList) const;
......
......@@ -26,6 +26,9 @@
#pragma once
#include <qmldesignercorelib_global.h>
#include <rewritererror.h>
#include <QObject>
#include <QPair>
......@@ -108,6 +111,7 @@ public:
TextModifier *textModifier() const;
void setTextModifier(TextModifier *textModifier);
void setDocumentMessages(const QList<RewriterError> &errors, const QList<RewriterError> &warnings);
protected:
Model();
......
......@@ -133,10 +133,6 @@ public:
void sendToken(const QString &token, int number, const QVector<ModelNode> &nodeVector);
signals:
void qmlPuppetCrashed();
void qmlPuppetError(const QString &errorMessage);
protected:
void timerEvent(QTimerEvent *event) override;
......
......@@ -34,6 +34,8 @@
#include <QTimer>
#include <QUrl>
#include <functional>
namespace QmlJS {
class Document;
class ScopeChain;
......@@ -154,8 +156,7 @@ public:
QList<CppTypeData> getCppTypes();
signals:
void errorsChanged(const QList<RewriterError> &errors);
void setWidgetStatusCallback(std::function<void(bool)> setWidgetStatusCallback);
public slots:
void qmlTextChanged();
......@@ -172,6 +173,7 @@ protected: // functions
void applyModificationGroupChanges();
void applyChanges();
void amendQmlText();
void notifyErrorsAndWarnings(const QList<RewriterError> &errors);
private: //variables
TextModifier *m_textModifier = nullptr;
......@@ -190,6 +192,7 @@ private: //variables
QString m_lastCorrectQmlSource;
QTimer m_amendTimer;
bool m_instantQmlTextUpdate = false;
std::function<void(bool)> m_setWidgetStatusCallback;
};
} //QmlDesigner
......@@ -84,14 +84,15 @@
namespace QmlDesigner {
static void showCannotConnectToPuppetWarningAndSwitchToEditMode()
void NodeInstanceServerProxy::showCannotConnectToPuppetWarningAndSwitchToEditMode()
{
#ifndef QMLDESIGNER_TEST
Core::AsynchronousMessageBox::warning(QCoreApplication::translate("NodeInstanceServerProxy", "Cannot Connect to QML Emulation Layer (QML Puppet)"),
QCoreApplication::translate("NodeInstanceServerProxy", "The executable of the QML emulation layer (QML Puppet) may not be responding. "
"Switching to another kit might help."));
Core::AsynchronousMessageBox::warning(tr("Cannot Connect to QML Emulation Layer (QML Puppet)"),
tr("The executable of the QML emulation layer (QML Puppet) may not be responding. "
"Switching to another kit might help."));
QmlDesignerPlugin::instance()->switchToTextModeDeferred();
m_nodeInstanceView->emitDocumentMessage(tr("Cannot Connect to QML Emulation Layer (QML Puppet)"));
#endif
}
......@@ -192,12 +193,7 @@ NodeInstanceServerProxy::NodeInstanceServerProxy(NodeInstanceView *nodeInstanceV