diff options
author | Aurindam Jana <aurindam.jana@nokia.com> | 2012-02-08 15:54:59 +0100 |
---|---|---|
committer | Aurindam Jana <aurindam.jana@nokia.com> | 2012-02-16 14:13:21 +0100 |
commit | ba412ee505cc357d32bcee26e3562329b727807b (patch) | |
tree | a529da42ba4f25b46ce6a83ececf34dfe16e425d /src | |
parent | bb4f25b74871384eeab86ff42a3d6e230deb52b8 (diff) | |
download | qt-creator-ba412ee505cc357d32bcee26e3562329b727807b.tar.gz |
ScriptConsole: Use QTreeView
Use QTreeView instead of modified QPlainTextEdit to show the
console. Using a QTreeView gives more control over output
display as well as separates out the model from the view.
Change-Id: I436b13ed042c00d09d6de627b442bfd8d10d2236
Reviewed-by: Kai Koehne <kai.koehne@nokia.com>
Diffstat (limited to 'src')
20 files changed, 2004 insertions, 612 deletions
diff --git a/src/plugins/debugger/debugger.qrc b/src/plugins/debugger/debugger.qrc index 0f7ebee1ab..e1bf571107 100644 --- a/src/plugins/debugger/debugger.qrc +++ b/src/plugins/debugger/debugger.qrc @@ -30,5 +30,11 @@ <file>images/location_16.png</file> <file>images/location_24.png</file> <file>images/pin.xpm</file> + <file>images/collapse.png</file> + <file>images/error.png</file> + <file>images/expand.png</file> + <file>images/log.png</file> + <file>images/prompt.png</file> + <file>images/warning.png</file> </qresource> </RCC> diff --git a/src/plugins/debugger/images/collapse.png b/src/plugins/debugger/images/collapse.png Binary files differnew file mode 100644 index 0000000000..64ae3720c1 --- /dev/null +++ b/src/plugins/debugger/images/collapse.png diff --git a/src/plugins/debugger/images/error.png b/src/plugins/debugger/images/error.png Binary files differnew file mode 100644 index 0000000000..39768b9f39 --- /dev/null +++ b/src/plugins/debugger/images/error.png diff --git a/src/plugins/debugger/images/expand.png b/src/plugins/debugger/images/expand.png Binary files differnew file mode 100644 index 0000000000..7959bfc97e --- /dev/null +++ b/src/plugins/debugger/images/expand.png diff --git a/src/plugins/debugger/images/log.png b/src/plugins/debugger/images/log.png Binary files differnew file mode 100644 index 0000000000..e4766f228b --- /dev/null +++ b/src/plugins/debugger/images/log.png diff --git a/src/plugins/debugger/images/prompt.png b/src/plugins/debugger/images/prompt.png Binary files differnew file mode 100644 index 0000000000..a333a87198 --- /dev/null +++ b/src/plugins/debugger/images/prompt.png diff --git a/src/plugins/debugger/images/warning.png b/src/plugins/debugger/images/warning.png Binary files differnew file mode 100644 index 0000000000..3200efc4fd --- /dev/null +++ b/src/plugins/debugger/images/warning.png diff --git a/src/plugins/debugger/qml/consolebackend.cpp b/src/plugins/debugger/qml/consolebackend.cpp new file mode 100644 index 0000000000..0ffca2ad05 --- /dev/null +++ b/src/plugins/debugger/qml/consolebackend.cpp @@ -0,0 +1,179 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "consolebackend.h" +#include "qmlengine.h" +#include "qmladapter.h" + +#include "debuggerstringutils.h" + +#include <utils/qtcassert.h> + +namespace Debugger { +namespace Internal { + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleBackend +// +/////////////////////////////////////////////////////////////////////// + +ConsoleBackend::ConsoleBackend(QObject *parent) : + QObject(parent) +{ +} + +/////////////////////////////////////////////////////////////////////// +// +// QmlJSConsoleBackend +// +/////////////////////////////////////////////////////////////////////// + +QmlJSConsoleBackend::QmlJSConsoleBackend(QObject *parent) : + ConsoleBackend(parent), + m_engine(0), + m_inferiorStopped(false), + m_isValidContext(false), + m_Error(false) +{ +} + +void QmlJSConsoleBackend::setInferiorStopped(bool isInferiorStopped) +{ + m_inferiorStopped = isInferiorStopped; +} + +bool QmlJSConsoleBackend::inferiorStopped() const +{ + return m_inferiorStopped; +} + +void QmlJSConsoleBackend::setEngine(QmlEngine *engine) +{ + m_engine = engine; +} + +QmlEngine *QmlJSConsoleBackend::engine() const +{ + return m_engine; +} + +void QmlJSConsoleBackend::setIsValidContext(bool isValidContext) +{ + m_isValidContext = isValidContext; +} + +bool QmlJSConsoleBackend::isValidContext() const +{ + return m_isValidContext; +} + +void QmlJSConsoleBackend::onDebugQueryStateChanged( + QmlJsDebugClient::QDeclarativeDebugQuery::State state) +{ + QmlJsDebugClient::QDeclarativeDebugExpressionQuery *query = + qobject_cast<QmlJsDebugClient::QDeclarativeDebugExpressionQuery *>( + sender()); + if (query && state != QmlJsDebugClient::QDeclarativeDebugQuery::Error) + emit message(ConsoleItemModel::UndefinedType, + query->result().toString()); + else + emit message(ConsoleItemModel::ErrorType, + _("Error evaluating expression.")); + delete query; +} + +void QmlJSConsoleBackend::evaluate(const QString &script, + bool *returnKeyConsumed) +{ + *returnKeyConsumed = true; + m_Error = false; + //Check if string is only white spaces + if (!script.trimmed().isEmpty()) { + //Check for a valid context + if (m_isValidContext) { + //check if it can be evaluated + if (canEvaluateScript(script)) { + //Evaluate expression based on engine state + //When engine->state() == InferiorStopOk, the expression + //is sent to V8DebugService. In all other cases, the + //expression is evaluated by QDeclarativeEngine. + if (!m_inferiorStopped) { + QmlAdapter *adapter = m_engine->adapter(); + QTC_ASSERT(adapter, return); + QDeclarativeEngineDebug *engineDebug = + adapter->engineDebugClient(); + + int id = adapter->currentSelectedDebugId(); + if (engineDebug && id != -1) { + QDeclarativeDebugExpressionQuery *query = + engineDebug->queryExpressionResult(id, script); + connect(query, + SIGNAL(stateChanged( + QmlJsDebugClient::QDeclarativeDebugQuery + ::State)), + this, + SLOT(onDebugQueryStateChanged( + QmlJsDebugClient::QDeclarativeDebugQuery + ::State))); + } + } else { + emit evaluateExpression(script); + } + } else { + *returnKeyConsumed = false; + } + } else { + //Incase of invalid context, append the expression to history + //and show Error message + m_Error = true; + } + } +} + +void QmlJSConsoleBackend::emitErrorMessage() +{ + if (m_Error) + emit message( + ConsoleItemModel::ErrorType, + _("Cannot evaluate without a valid QML/JS Context")); +} + +bool QmlJSConsoleBackend::canEvaluateScript(const QString &script) +{ + m_interpreter.clearText(); + m_interpreter.appendText(script); + return m_interpreter.canEvaluate(); +} + +} +} diff --git a/src/plugins/debugger/qml/consolebackend.h b/src/plugins/debugger/qml/consolebackend.h new file mode 100644 index 0000000000..dd24b427df --- /dev/null +++ b/src/plugins/debugger/qml/consolebackend.h @@ -0,0 +1,100 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CONSOLEBACKEND_H +#define CONSOLEBACKEND_H + +#include "consoleitemmodel.h" +#include "interactiveinterpreter.h" + +#include <qmljsdebugclient/qdeclarativeenginedebug.h> + +#include <QtCore/QObject> + +namespace Debugger { +namespace Internal { + +class ConsoleBackend : public QObject +{ + Q_OBJECT +public: + explicit ConsoleBackend(QObject *parent = 0); + + virtual void emitErrorMessage() = 0; + + virtual void evaluate(const QString &, bool *returnKeyConsumed) = 0; + +signals: + void message(ConsoleItemModel::ItemType itemType, const QString &msg); +}; + +class QmlEngine; +class QmlJSConsoleBackend : public ConsoleBackend +{ + Q_OBJECT +public: + explicit QmlJSConsoleBackend(QObject *parent = 0); + + void setInferiorStopped(bool inferiorStopped); + bool inferiorStopped() const; + + void setEngine(QmlEngine *engine); + QmlEngine *engine() const; + + void setIsValidContext(bool isValidContext); + bool isValidContext() const; + + virtual void evaluate(const QString &, bool *returnKeyConsumed); + void emitErrorMessage(); + +private slots: + void onDebugQueryStateChanged( + QmlJsDebugClient::QDeclarativeDebugQuery::State state); + +signals: + void evaluateExpression(const QString &); + +private: + bool canEvaluateScript(const QString &script); + +private: + QmlEngine *m_engine; + InteractiveInterpreter m_interpreter; + bool m_inferiorStopped; + bool m_isValidContext; + bool m_Error; +}; + +} //Internal +} //Debugger + +#endif // CONSOLEBACKEND_H diff --git a/src/plugins/debugger/qml/consoleeditor.cpp b/src/plugins/debugger/qml/consoleeditor.cpp new file mode 100644 index 0000000000..cd655b621e --- /dev/null +++ b/src/plugins/debugger/qml/consoleeditor.cpp @@ -0,0 +1,278 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "consoleeditor.h" +#include "consoleitemmodel.h" +#include "consoleitemdelegate.h" +#include "consolebackend.h" + +#include "debuggerstringutils.h" + +#include <utils/qtcassert.h> + +#include <QtGui/QMenu> +#include <QtGui/QKeyEvent> + +namespace Debugger { +namespace Internal { + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleEditor +// +/////////////////////////////////////////////////////////////////////// + +ConsoleEditor::ConsoleEditor(const QModelIndex &index, + ConsoleBackend *backend, + QWidget *parent) : + QTextEdit(parent), + m_consoleBackend(backend), + m_historyIndex(index), + m_prompt(QLatin1String(":/debugger/images/prompt.png")), + m_startOfEditableArea(0) +{ + setFrameStyle(QFrame::NoFrame); + setUndoRedoEnabled(false); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + document()->addResource(QTextDocument::ImageResource, + QUrl(QLatin1String("prompt")), m_prompt); + QTextImageFormat format; + format.setName(QLatin1String("prompt")); + format.setHeight(9); + format.setWidth(9); + textCursor().insertImage(format); + textCursor().insertText(QLatin1String(" ")); + m_startOfEditableArea = textCursor().position(); + + ensureCursorVisible(); + setTextInteractionFlags(Qt::TextEditorInteraction); +} + +void ConsoleEditor::keyPressEvent(QKeyEvent *e) +{ + bool keyConsumed = false; + + switch (e->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + if (m_consoleBackend) { + m_consoleBackend->evaluate(getCurrentScript(), &keyConsumed); + if (keyConsumed) { + emit editingFinished(); + emit appendEditableRow(); + //emit error message if there is an error + m_consoleBackend->emitErrorMessage(); + } + } else { + emit editingFinished(); + emit appendEditableRow(); + keyConsumed = true; + } + break; + + case Qt::Key_Backspace: + if (textCursor().selectionStart() <= m_startOfEditableArea) + keyConsumed = true; + break; + + case Qt::Key_Delete: + if (textCursor().selectionStart() < m_startOfEditableArea) + keyConsumed = true; + break; + + case Qt::Key_Home: + { + QTextCursor c(textCursor()); + c.setPosition(m_startOfEditableArea); + setTextCursor(c); + keyConsumed = true; + } + break; + + case Qt::Key_Up: + handleUpKey(); + keyConsumed = true; + break; + + case Qt::Key_Down: + handleDownKey(); + keyConsumed = true; + break; + + // Ctrl+Left: Moves the cursor one word to the left. + // Left: Moves the cursor one character to the left. + case Qt::Key_Left: + if (textCursor().position() <= m_startOfEditableArea + || e->modifiers() & Qt::ControlModifier) { + QTextCursor c(textCursor()); + c.setPosition(m_startOfEditableArea); + setTextCursor(c); + keyConsumed = true; + } + break; + + // Ctrl+Right: Moves the cursor one word to the right. + // Right: Moves the cursor one character to the right. + case Qt::Key_Right: + if ( !(e->modifiers() & Qt::ControlModifier) + && textCursor().position() <= m_startOfEditableArea) { + QTextCursor c(textCursor()); + c.setPosition(m_startOfEditableArea); + setTextCursor(c); + keyConsumed = true; + } + break; + + // Ctrl+C, Ctrl+Insert: Allow to Copy the selected text to the clipboard. + case Qt::Key_C: + case Qt::Key_Insert: + if (textCursor().selectionStart() < m_startOfEditableArea && + !(e->modifiers() & Qt::ControlModifier)) + keyConsumed = true; + break; + + default: + // Disallow any other keys in the prompt area + if (textCursor().selectionStart() < m_startOfEditableArea) + keyConsumed = true; + break; + } + + if (!keyConsumed) + QTextEdit::keyPressEvent(e); +} + +void ConsoleEditor::contextMenuEvent(QContextMenuEvent *event) +{ + QTextCursor cursor = textCursor(); + bool editable = cursor.position() > m_startOfEditableArea; + QMenu *menu = new QMenu(); + QAction *a; + + a = menu->addAction(tr("Cu&t"), this, SLOT(cut())); + a->setEnabled(cursor.hasSelection() && editable); + + a = menu->addAction(tr("&Copy"), this, SLOT(copy())); + a->setEnabled(cursor.hasSelection()); + + a = menu->addAction(tr("&Paste"), this, SLOT(paste())); + a->setEnabled(canPaste() && editable); + + menu->addSeparator(); + a = menu->addAction(tr("Select &All"), this, SLOT(selectAll())); + a->setEnabled(!document()->isEmpty()); + + menu->addSeparator(); + menu->addAction(tr("C&lear"), this, SLOT(clear())); + + menu->exec(event->globalPos()); + + delete menu; +} + +void ConsoleEditor::focusOutEvent(QFocusEvent * /*e*/) +{ + emit editingFinished(); +} + +void ConsoleEditor::handleUpKey() +{ + QTC_ASSERT(m_historyIndex.isValid(), return); + int currentRow = m_historyIndex.row(); + const QAbstractItemModel *model = m_historyIndex.model(); + if (currentRow == model->rowCount() - 1) + m_cachedScript = getCurrentScript(); + + while (currentRow) { + currentRow--; + if (model->hasIndex(currentRow, 0)) { + QModelIndex index = model->index(currentRow, 0); + if (ConsoleItemModel::InputType == + (ConsoleItemModel::ItemType)model->data( + index, ConsoleItemModel::TypeRole).toInt()) { + m_historyIndex = index; + replaceCurrentScript(model->data( + index, Qt::DisplayRole). + toString()); + break; + } + } + } +} + +void ConsoleEditor::handleDownKey() +{ + QTC_ASSERT(m_historyIndex.isValid(), return); + int currentRow = m_historyIndex.row(); + const QAbstractItemModel *model = m_historyIndex.model(); + while (currentRow < model->rowCount() - 1) { + currentRow++; + if (model->hasIndex(currentRow, 0)) { + QModelIndex index = model->index(currentRow, 0); + if (ConsoleItemModel::InputType == + (ConsoleItemModel::ItemType)model->data( + index, ConsoleItemModel::TypeRole).toInt()) { + m_historyIndex = index; + if (currentRow == model->rowCount() - 1) + replaceCurrentScript(m_cachedScript); + else + replaceCurrentScript(model->data( + index, Qt::DisplayRole). + toString()); + break; + } + } + } +} + +QString ConsoleEditor::getCurrentScript() const +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(m_startOfEditableArea); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + QString script = cursor.selectedText(); + //remove WS + return script.trimmed(); +} + +void ConsoleEditor::replaceCurrentScript(const QString &script) +{ + QTextCursor cursor = textCursor(); + cursor.setPosition(m_startOfEditableArea); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + cursor.removeSelectedText(); + cursor.insertText(script); + setTextCursor(cursor); +} + +} //Internal +} //Debugger diff --git a/src/plugins/debugger/qml/consoleeditor.h b/src/plugins/debugger/qml/consoleeditor.h new file mode 100644 index 0000000000..6cdad218ec --- /dev/null +++ b/src/plugins/debugger/qml/consoleeditor.h @@ -0,0 +1,81 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CONSOLEEDITOR_H +#define CONSOLEEDITOR_H + +#include "consoleitemmodel.h" + +#include <QtGui/QTextEdit> +#include <QtCore/QModelIndex> + +namespace Debugger { +namespace Internal { + +class ConsoleBackend; +class ConsoleEditor : public QTextEdit +{ + Q_OBJECT +public: + explicit ConsoleEditor(const QModelIndex &index, + ConsoleBackend *backend = 0, + QWidget *parent = 0); + + QString getCurrentScript() const; + +protected: + void keyPressEvent(QKeyEvent *e); + void contextMenuEvent(QContextMenuEvent *event); + void focusOutEvent(QFocusEvent *e); + +signals: + void editingFinished(); + void appendEditableRow(); + +protected: + void handleUpKey(); + void handleDownKey(); + + void replaceCurrentScript(const QString &script); + +private: + ConsoleBackend *m_consoleBackend; + QModelIndex m_historyIndex; + QString m_cachedScript; + QImage m_prompt; + int m_startOfEditableArea; +}; + +} //Internal +} //Debugger + +#endif // CONSOLEEDITOR_H diff --git a/src/plugins/debugger/qml/consoleitemdelegate.cpp b/src/plugins/debugger/qml/consoleitemdelegate.cpp new file mode 100644 index 0000000000..c6a0fe3fa4 --- /dev/null +++ b/src/plugins/debugger/qml/consoleitemdelegate.cpp @@ -0,0 +1,321 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "consoleitemdelegate.h" +#include "consoleeditor.h" +#include "qmlengine.h" + +#include <QtGui/QPainter> +#include <QtGui/QTreeView> + +const char CONSOLE_LOG_BACKGROUND_COLOR[] = "#2378B7"; +const char CONSOLE_WARNING_BACKGROUND_COLOR[] = "#E6CD49"; +const char CONSOLE_ERROR_BACKGROUND_COLOR[] = "#ED471A"; +const char CONSOLE_EDITOR_BACKGROUND_COLOR[] = "#F7F7F7"; + +const char CONSOLE_LOG_TEXT_COLOR[] = "#333333"; +const char CONSOLE_WARNING_TEXT_COLOR[] = "#666666"; +const char CONSOLE_ERROR_TEXT_COLOR[] = "#1D5B93"; +const char CONSOLE_EDITOR_TEXT_COLOR[] = "#000000"; + +const char CONSOLE_BORDER_COLOR[] = "#DEDEDE"; +const float CONSOLE_ALPHA = 0.7f; + +namespace Debugger { +namespace Internal { + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleItemDelegate +// +/////////////////////////////////////////////////////////////////////// + +ConsoleItemDelegate::ConsoleItemDelegate(QObject *parent) : + QStyledItemDelegate(parent), + m_logIcon(QLatin1String(":/debugger/images/log.png")), + m_warningIcon(QLatin1String(":/debugger/images/warning.png")), + m_errorIcon(QLatin1String(":/debugger/images/error.png")), + m_expandIcon(QLatin1String(":/debugger/images/expand.png")), + m_collapseIcon(QLatin1String(":/debugger/images/collapse.png")), + m_prompt(QLatin1String(":/debugger/images/prompt.png")), + m_consoleBackend(0) +{ +} + +void ConsoleItemDelegate::emitSizeHintChanged(const QModelIndex &index) +{ + emit sizeHintChanged(index); +} + +void ConsoleItemDelegate::setConsoleBackend(ConsoleBackend *consoleBackend) +{ + m_consoleBackend = consoleBackend; +} + +void ConsoleItemDelegate::drawBackground(QPainter *painter, const QRect &rect, + ConsoleItemModel::ItemType itemType, + bool selected) const +{ + QColor backgroundColor; + switch (itemType) { + case ConsoleItemModel::LogType: + backgroundColor = QColor(CONSOLE_LOG_BACKGROUND_COLOR); + break; + case ConsoleItemModel::WarningType: + backgroundColor = QColor(CONSOLE_WARNING_BACKGROUND_COLOR); + break; + case ConsoleItemModel::ErrorType: + backgroundColor = QColor(CONSOLE_ERROR_BACKGROUND_COLOR); + break; + case ConsoleItemModel::InputType: + backgroundColor = QColor(CONSOLE_EDITOR_BACKGROUND_COLOR); + break; + default: + backgroundColor = QColor(CONSOLE_EDITOR_BACKGROUND_COLOR); + break; + } + if (selected) + backgroundColor.setAlphaF(0.5f); + else + backgroundColor.setAlphaF(1 - CONSOLE_ALPHA); + + painter->setBrush(backgroundColor); + + painter->setPen(Qt::NoPen); + painter->drawRect(rect); +} + +void ConsoleItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + painter->save(); + + //Set Colors + QColor textColor; + QIcon taskIcon; + ConsoleItemModel::ItemType type = + (ConsoleItemModel::ItemType)index.data( + ConsoleItemModel::TypeRole).toInt(); + switch (type) { + case ConsoleItemModel::LogType: + textColor = QColor(CONSOLE_LOG_TEXT_COLOR); + taskIcon = m_logIcon; + break; + case ConsoleItemModel::WarningType: + textColor = QColor(CONSOLE_WARNING_TEXT_COLOR); + taskIcon = m_warningIcon; + break; + case ConsoleItemModel::ErrorType: + textColor = QColor(CONSOLE_ERROR_TEXT_COLOR); + taskIcon = m_errorIcon; + break; + case ConsoleItemModel::InputType: + textColor = QColor(CONSOLE_EDITOR_TEXT_COLOR); + taskIcon = m_prompt; + break; + default: + textColor = QColor(CONSOLE_EDITOR_TEXT_COLOR); + break; + } + + //Paint background + drawBackground(painter, opt.rect, type, + bool(opt.state & QStyle::State_Selected)); + + //Calculate positions + const QTreeView *view = qobject_cast<const QTreeView *>(opt.widget); + int level = 0; + QModelIndex idx(index); + while (idx.parent() != QModelIndex()) { + idx = idx.parent(); + level++; + } + int width = view->width() - level * view->indentation(); + bool showTypeIcon = index.parent() == QModelIndex(); + bool showExpandableIcon = type != ConsoleItemModel::InputType && + type != ConsoleItemModel::UndefinedType; + + QRect rect(opt.rect.x(), opt.rect.top(), width, opt.rect.height()); + ConsoleItemPositions positions(rect, opt.font, showTypeIcon, + showExpandableIcon); + + // Paint TaskIconArea: + if (showTypeIcon) + painter->drawPixmap(positions.adjustedLeft(), positions.adjustedTop(), + taskIcon.pixmap(positions.typeIconWidth(), + positions.typeIconHeight())); + + // Set Text Color + painter->setPen(textColor); + // Paint TextArea: + // Layout the description + QTextLayout tl(index.data(Qt::DisplayRole).toString(), opt.font); + layoutText(tl, positions.textAreaWidth()); + tl.draw(painter, QPoint(positions.textAreaLeft(), positions.adjustedTop())); + + //skip if area is editable + if (showExpandableIcon) { + // Paint ExpandableIconArea: + QIcon expandCollapseIcon; + if (index.model()->rowCount(index)) { + if (view->isExpanded(index)) + expandCollapseIcon = m_collapseIcon; + else + expandCollapseIcon = m_expandIcon; + } + painter->drawPixmap(positions.expandCollapseIconLeft(), + positions.adjustedTop(), + expandCollapseIcon.pixmap( + positions.expandCollapseIconWidth(), + positions.expandCollapseIconHeight())); + } + + // Separator lines + painter->setPen(QColor(CONSOLE_BORDER_COLOR)); + if (!index.flags() & Qt::ItemIsEditable) + painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), + opt.rect.bottom()); + painter->restore(); +} + +QSize ConsoleItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItemV4 opt = option; + initStyleOption(&opt, index); + + const QTreeView *view = qobject_cast<const QTreeView *>(opt.widget); + int level = 0; + QModelIndex idx(index); + while (idx.parent() != QModelIndex()) { + idx = idx.parent(); + level++; + } + int width = view->width() - level * view->indentation(); + if (index.flags() & Qt::ItemIsEditable) + return QSize(width, view->height() * 1/2); + + ConsoleItemModel::ItemType type = + (ConsoleItemModel::ItemType)index.data( + ConsoleItemModel::TypeRole).toInt(); + bool showTypeIcon = index.parent() == QModelIndex(); + bool showExpandableIcon = type != ConsoleItemModel::InputType && + type != ConsoleItemModel::UndefinedType; + + QRect rect(level * view->indentation(), 0, width, 0); + ConsoleItemPositions positions(rect, opt.font, + showTypeIcon, + showExpandableIcon); + + QTextLayout tl(index.data(Qt::DisplayRole).toString(), option.font); + qreal height = layoutText(tl, positions.textAreaWidth()); + height += 2 * ConsoleItemPositions::ITEM_PADDING; + if (height < positions.minimumHeight()) + height = positions.minimumHeight(); + + return QSize(width, height); +} + +QWidget *ConsoleItemDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &/*option*/, + const QModelIndex &index) const + +{ + ConsoleEditor *editor = new ConsoleEditor(index, m_consoleBackend, parent); + connect(editor, SIGNAL(appendEditableRow()), + this, SIGNAL(appendEditableRow())); + connect(editor, SIGNAL(editingFinished()), + this, SLOT(commitAndCloseEditor())); + return editor; +} + +void ConsoleItemDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + ConsoleEditor *edtr = qobject_cast<ConsoleEditor *>(editor); + edtr->insertPlainText(index.data(Qt::DisplayRole).toString()); +} + +void ConsoleItemDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ + ConsoleEditor *edtr = qobject_cast<ConsoleEditor *>(editor); + model->setData(index, edtr->getCurrentScript(), Qt::DisplayRole); + model->setData(index, edtr->getCurrentScript(), ConsoleItemModel::TypeRole); +} + +void ConsoleItemDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &/*index*/) const +{ + QStyleOptionViewItemV4 opt = option; + editor->setGeometry(QRect(opt.rect.x(), opt.rect.top(), + opt.rect.width(), opt.rect.bottom())); +} + +void ConsoleItemDelegate::currentChanged(const QModelIndex ¤t, + const QModelIndex &previous) +{ + emit sizeHintChanged(current); + emit sizeHintChanged(previous); +} + +void ConsoleItemDelegate::commitAndCloseEditor() +{ + ConsoleEditor *editor = qobject_cast<ConsoleEditor *>(sender()); + emit commitData(editor); + emit closeEditor(editor); +} + +qreal ConsoleItemDelegate::layoutText(QTextLayout &tl, int width) const +{ + qreal height = 0; + tl.beginLayout(); + while (true) { + QTextLine line = tl.createLine(); + + if (!line.isValid()) + break; + line.setLeadingIncluded(true); + line.setLineWidth(width); + line.setPosition(QPoint(0, height)); + height += line.height(); + } + tl.endLayout(); + return height; +} + +} //Internal +} //Debugger diff --git a/src/plugins/debugger/qml/consoleitemdelegate.h b/src/plugins/debugger/qml/consoleitemdelegate.h new file mode 100644 index 0000000000..a7b79f9018 --- /dev/null +++ b/src/plugins/debugger/qml/consoleitemdelegate.h @@ -0,0 +1,174 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CONSOLEITEMDELEGATE_H +#define CONSOLEITEMDELEGATE_H + +#include "consoleitemmodel.h" + +#include <QTextLayout> +#include <QStyledItemDelegate> + +namespace Debugger { +namespace Internal { + +class ConsoleBackend; +class ConsoleItemDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit ConsoleItemDelegate(QObject *parent = 0); + void emitSizeHintChanged(const QModelIndex &index); + void setConsoleBackend(ConsoleBackend *consoleBackend); + + void drawBackground(QPainter *painter, const QRect &rect, + ConsoleItemModel::ItemType itemType, bool selected) const; + +public slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); + +protected: + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QSize sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const; + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, + const QModelIndex &index) const; + +signals: + void appendEditableRow(); + +private slots: + void commitAndCloseEditor(); + +private: + qreal layoutText(QTextLayout &tl, int width) const; + +private: + const QIcon m_logIcon; + const QIcon m_warningIcon; + const QIcon m_errorIcon; + const QIcon m_expandIcon; + const QIcon m_collapseIcon; + const QIcon m_prompt; + ConsoleBackend *m_consoleBackend; +}; + +/* + +----------------------------------------------------------------------------+ + | TYPEICONAREA EXPANDABLEICONAREA TEXTAREA | + +----------------------------------------------------------------------------+ + + */ +/* + +----------------------------------------------------------------------------+ + | PROMPTAREA EDITABLEAREA | + +----------------------------------------------------------------------------+ + + */ +class ConsoleItemPositions +{ +public: + ConsoleItemPositions(const QRect &rect, + const QFont &font = QFont(), + bool showTaskIconArea = true, + bool showExpandableIconArea = true) + : m_x(rect.x()), + m_width(rect.width()), + m_top(rect.top()), + m_bottom(rect.bottom()), + m_showTaskIconArea(showTaskIconArea), + m_showExpandableIconArea(showExpandableIconArea) + { + m_fontHeight = QFontMetrics(font).height(); + } + + int adjustedTop() const { return m_top + ITEM_PADDING; } + int adjustedLeft() const { return m_x + ITEM_PADDING; } + int adjustedRight() const { return m_width - ITEM_PADDING; } + int adjustedBottom() const { return m_bottom; } + int lineHeight() const { return m_fontHeight + 1; } + int minimumHeight() const { return typeIconHeight() + 2 * ITEM_PADDING; } + + //PROMPTAREA is same as TYPEICONAREA + int typeIconLeft() const { return adjustedLeft(); } + int typeIconWidth() const { return TASK_ICON_SIZE; } + int typeIconHeight() const { return TASK_ICON_SIZE; } + int typeIconRight() const { return m_showTaskIconArea ? + typeIconLeft() + typeIconWidth() : adjustedLeft(); } + QRect typeIcon() const { return + QRect(typeIconLeft(), adjustedTop(), + typeIconWidth(), typeIconHeight()); } + + int expandCollapseIconLeft() const { return typeIconRight() + + ITEM_SPACING; } + int expandCollapseIconWidth() const { return TASK_ICON_SIZE; } + int expandCollapseIconHeight() const { return TASK_ICON_SIZE; } + int expandCollapseIconRight() const { return m_showExpandableIconArea ? + expandCollapseIconLeft() + expandCollapseIconWidth() : + typeIconRight(); } + QRect expandCollapseIcon() const { return + QRect(expandCollapseIconLeft(), adjustedTop(), + expandCollapseIconWidth(), expandCollapseIconHeight()); } + + int textAreaLeft() const { return expandCollapseIconRight() + ITEM_SPACING; } + int textAreaWidth() const { return textAreaRight() - textAreaLeft(); } + int textAreaRight() const { return adjustedRight() - ITEM_SPACING; } + QRect textArea() const { return + QRect(textAreaLeft(), adjustedTop(), textAreaWidth(), lineHeight()); } + +private: + int m_x; + int m_width; + int m_top; + int m_bottom; + int m_fontHeight; + bool m_showTaskIconArea; + bool m_showExpandableIconArea; + +public: + static const int TASK_ICON_SIZE = 16; + static const int ITEM_PADDING = 2; + static const int ITEM_SPACING = 2 * ITEM_PADDING; + +}; +} //Internal +} //Debugger + +#endif // CONSOLEITEMDELEGATE_H diff --git a/src/plugins/debugger/qml/consoleitemmodel.cpp b/src/plugins/debugger/qml/consoleitemmodel.cpp new file mode 100644 index 0000000000..aaad6df3c8 --- /dev/null +++ b/src/plugins/debugger/qml/consoleitemmodel.cpp @@ -0,0 +1,348 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "consoleitemmodel.h" + +namespace Debugger { +namespace Internal { + +class ConsoleItem + { + public: + ConsoleItem(const QString &data = QString(), + ConsoleItemModel::ItemType type = ConsoleItemModel::UndefinedType, + ConsoleItem *parent = 0); + ~ConsoleItem(); + + ConsoleItem *child(int number); + int childCount() const; + QString text() const; + ConsoleItemModel::ItemType itemType() const; + bool insertChildren(int position, int count); + ConsoleItem *parent(); + bool removeChildren(int position, int count); + bool removeColumns(int position, int columns); + int childNumber() const; + bool setData(const QString &text, ConsoleItemModel::ItemType itemType); + bool setText(const QString &text); + bool setItemType(ConsoleItemModel::ItemType itemType); + + private: + QList<ConsoleItem *> m_childItems; + QString m_text; + ConsoleItemModel::ItemType m_itemType; + ConsoleItem *m_parentItem; + }; + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleItem +// +/////////////////////////////////////////////////////////////////////// + +ConsoleItem::ConsoleItem(const QString &text, + ConsoleItemModel::ItemType itemType, + ConsoleItem *parent) + : m_text(text), + m_itemType(itemType), + m_parentItem(parent) + +{ +} + +ConsoleItem::~ConsoleItem() +{ + qDeleteAll(m_childItems); +} + +ConsoleItem *ConsoleItem::child(int number) +{ + return m_childItems.value(number); +} + +int ConsoleItem::childCount() const +{ + return m_childItems.count(); +} + +int ConsoleItem::childNumber() const +{ + if (m_parentItem) + return m_parentItem->m_childItems.indexOf(const_cast<ConsoleItem *>(this)); + + return 0; +} + +QString ConsoleItem::text() const +{ + return m_text; +} + +ConsoleItemModel::ItemType ConsoleItem::itemType() const +{ + return m_itemType; +} +\ +bool ConsoleItem::insertChildren(int position, int count) +{ + if (position < 0 || position > m_childItems.size()) + return false; + + for (int row = 0; row < count; ++row) { + ConsoleItem *item = new ConsoleItem(QString(), + ConsoleItemModel::UndefinedType, this); + m_childItems.insert(position, item); + } + + return true; +} + +ConsoleItem *ConsoleItem::parent() +{ + return m_parentItem; +} + +bool ConsoleItem::removeChildren(int position, int count) +{ + if (position < 0 || position + count > m_childItems.size()) + return false; + + for (int row = 0; row < count; ++row) + delete m_childItems.takeAt(position); + + return true; +} + +bool ConsoleItem::removeColumns(int /*position*/, int /*columns*/) +{ + return false; +} + +bool ConsoleItem::setData(const QString &text, + ConsoleItemModel::ItemType itemType) +{ + m_text = text; + m_itemType = itemType; + return true; +} + +bool ConsoleItem::setText(const QString &text) +{ + m_text = text; + return true; +} + +bool ConsoleItem::setItemType(ConsoleItemModel::ItemType itemType) +{ + m_itemType = itemType; + return true; +} + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleItemModel +// +/////////////////////////////////////////////////////////////////////// + +ConsoleItemModel::ConsoleItemModel(QObject *parent) : + QAbstractItemModel(parent) +{ + m_rootItem = new ConsoleItem(); +} + +ConsoleItemModel::~ConsoleItemModel() +{ + delete m_rootItem; +} + +void ConsoleItemModel::clear() +{ + beginResetModel(); + reset(); + delete m_rootItem; + m_rootItem = new ConsoleItem(); + endResetModel(); + + //Insert an empty row + appendEditableRow(); +} + +void ConsoleItemModel::appendItem(ItemType itemType, QString message) +{ + int position = m_rootItem->childCount() - 1; + insertRow(position); + QModelIndex idx = index(position, 0); + if (getItem(idx)->setData(message, itemType)) + emit dataChanged(idx, idx); +} + +void ConsoleItemModel::appendEditableRow() +{ + int position = m_rootItem->childCount(); + insertRow(position); + QModelIndex idx = index(position, 0); + if (getItem(idx)->setData(QString(), ConsoleItemModel::InputType)) { + QModelIndex idx = index(position, 0); + emit dataChanged(idx, idx); + emit editableRowAppended(idx, QItemSelectionModel::ClearAndSelect); + } +} + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleEditor +// +/////////////////////////////////////////////////////////////////////// + +QVariant ConsoleItemModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + ConsoleItem *item = getItem(index); + + if (role == Qt::DisplayRole ) + return item->text(); + else if (role == ConsoleItemModel::TypeRole) + return int(item->itemType()); + else + return QVariant(); +} + +QModelIndex ConsoleItemModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (parent.isValid() && parent.column() != 0) + return QModelIndex(); + + if (column > 0) + return QModelIndex(); + + ConsoleItem *parentItem = getItem(parent); + + ConsoleItem *childItem = parentItem->child(row); + if (childItem) + return createIndex(row, column, childItem); + else + return QModelIndex(); +} + +QModelIndex ConsoleItemModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + ConsoleItem *childItem = getItem(index); + ConsoleItem *parentItem = childItem->parent(); + + if (parentItem == m_rootItem) + return QModelIndex(); + + return createIndex(parentItem->childNumber(), 0, parentItem); +} + +int ConsoleItemModel::rowCount(const QModelIndex &parent) const +{ + ConsoleItem *parentItem = getItem(parent); + + return parentItem->childCount(); +} + +int ConsoleItemModel::columnCount(const QModelIndex & /* parent */) const +{ + return 1; +} + +Qt::ItemFlags ConsoleItemModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + ConsoleItem *item = getItem(index); + if (item->parent() == m_rootItem && index.row() == m_rootItem->childCount() - 1) + return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +bool ConsoleItemModel::setData(const QModelIndex &index, const QVariant &value, + int role) +{ + ConsoleItem *item = getItem(index); + bool result = false; + if (role == Qt::DisplayRole ) + result = item->setText(value.toString()); + else if (role == ConsoleItemModel::TypeRole) + result = item->setItemType((ItemType)value.toInt()); + else if (value.canConvert(QVariant::String)) + result = item->setText(value.toString()); + + if (result) + emit dataChanged(index, index); + + return result; +} + +bool ConsoleItemModel::insertRows(int position, int rows, const QModelIndex &parent) +{ + ConsoleItem *parentItem = getItem(parent); + bool success; + + beginInsertRows(parent, position, position + rows - 1); + success = parentItem->insertChildren(position, rows); + endInsertRows(); + + return success; +} + +bool ConsoleItemModel::removeRows(int position, int rows, const QModelIndex &parent) +{ + ConsoleItem *parentItem = getItem(parent); + bool success = true; + + beginRemoveRows(parent, position, position + rows - 1); + success = parentItem->removeChildren(position, rows); + endRemoveRows(); + + return success; +} + +ConsoleItem *ConsoleItemModel::getItem(const QModelIndex &index) const +{ + if (index.isValid()) { + ConsoleItem *item = static_cast<ConsoleItem*>(index.internalPointer()); + if (item) return item; + } + return m_rootItem; +} + +} //Internal +} //Debugger diff --git a/src/plugins/debugger/qml/consoleitemmodel.h b/src/plugins/debugger/qml/consoleitemmodel.h new file mode 100644 index 0000000000..994d34d6e0 --- /dev/null +++ b/src/plugins/debugger/qml/consoleitemmodel.h @@ -0,0 +1,94 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CONSOLEITEMMODEL_H +#define CONSOLEITEMMODEL_H + +#include <QAbstractItemModel> +#include <QtGui/QItemSelectionModel> + +namespace Debugger { +namespace Internal { + +class ConsoleItem; +class ConsoleItemModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum ItemType { InputType, LogType, WarningType, ErrorType, + UndefinedType //Can be used for unknown and for Return values + }; + enum Roles { TypeRole = Qt::UserRole }; + + explicit ConsoleItemModel(QObject *parent = 0); + ~ConsoleItemModel(); + + void appendItem(ItemType,QString); + +public slots: + void clear(); + void appendEditableRow(); + +signals: + void editableRowAppended(const QModelIndex &index, + QItemSelectionModel::SelectionFlags flags); + +protected: + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + QModelIndex index(int row, int column, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + + Qt::ItemFlags flags(const QModelIndex &index) const; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole); + + bool insertRows(int position, int rows, + const QModelIndex &parent = QModelIndex()); + bool removeRows(int position, int rows, + const QModelIndex &parent = QModelIndex()); + + ConsoleItem *getItem(const QModelIndex &index) const; + +private: + ConsoleItem *m_rootItem; +}; + +} //Internal +} //Debugger + +#endif // CONSOLEITEMMODEL_H diff --git a/src/plugins/debugger/qml/consoletreeview.cpp b/src/plugins/debugger/qml/consoletreeview.cpp new file mode 100644 index 0000000000..3fed6e3f50 --- /dev/null +++ b/src/plugins/debugger/qml/consoletreeview.cpp @@ -0,0 +1,157 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#include "consoletreeview.h" +#include "consoleitemdelegate.h" +#include "consoleitemmodel.h" +#include "debuggerinternalconstants.h" + +#include <QtGui/QMouseEvent> +#include <QtGui/QProxyStyle> +#include <QtGui/QPainter> + +namespace Debugger { +namespace Internal { + +class ConsoleTreeViewStyle : public QProxyStyle +{ +public: + void drawPrimitive(PrimitiveElement element, + const QStyleOption *option, + QPainter *painter, + const QWidget *widget = 0) const + { + if (element != QStyle::PE_PanelItemViewRow) + QProxyStyle::drawPrimitive(element, option, painter, widget); + } + + int styleHint(StyleHint hint, const QStyleOption *option = 0, + const QWidget *widget = 0, + QStyleHintReturn *returnData = 0) const { + if (hint == SH_ItemView_ShowDecorationSelected) + return 0; + else + return QProxyStyle::styleHint(hint, option, widget, returnData); + } +}; + +/////////////////////////////////////////////////////////////////////// +// +// ConsoleTreeView +// +/////////////////////////////////////////////////////////////////////// + +ConsoleTreeView::ConsoleTreeView(QWidget *parent) : + QTreeView(parent) +{ + setFrameStyle(QFrame::NoFrame); + setHeaderHidden(true); + setRootIsDecorated(false); + setEditTriggers(QAbstractItemView::AllEditTriggers); + setStyleSheet(QLatin1String("QTreeView::branch:has-siblings:!adjoins-item {" + "border-image: none;" + "image: none; }" + "QTreeView::branch:has-siblings:adjoins-item {" + "border-image: none;" + "image: none; }" + "QTreeView::branch:!has-children:!has-siblings:adjoins-item {" + "border-image: none;" + "image: none; }" + "QTreeView::branch:has-children:!has-siblings:closed," + "QTreeView::branch:closed:has-children:has-siblings {" + "border-image: none;" + "image: none; }" + "QTreeView::branch:open:has-children:!has-siblings," + "QTreeView::branch:open:has-children:has-siblings {" + "border-image: none;" + "image: none; }")); + ConsoleTreeViewStyle *style = new ConsoleTreeViewStyle; + setStyle(style); + style->setParent(this); +} + +void ConsoleTreeView::setItemDelegate(QAbstractItemDelegate *delegate) +{ + connect(selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + delegate, SLOT(currentChanged(QModelIndex,QModelIndex))); + QTreeView::setItemDelegate(delegate); +} + +void ConsoleTreeView::mousePressEvent(QMouseEvent *event) +{ + QPoint pos = event->pos(); + QModelIndex index = indexAt(pos); + if (index.isValid()) { + ConsoleItemModel::ItemType type = + (ConsoleItemModel::ItemType)index.data( + ConsoleItemModel::TypeRole).toInt(); + bool showTypeIcon = index.parent() == QModelIndex(); + bool showExpandableIcon = type != ConsoleItemModel::InputType && + type != ConsoleItemModel::UndefinedType; + ConsoleItemPositions positions(visualRect(index), viewOptions().font, + showTypeIcon, showExpandableIcon); + + if (positions.expandCollapseIcon().contains(pos)) { + if (isExpanded(index)) + setExpanded(index, false); + else + setExpanded(index, true); + } else { + QTreeView::mousePressEvent(event); + } + } else { + selectionModel()->setCurrentIndex(model()->index( + model()->rowCount() - 1, 0), + QItemSelectionModel::ClearAndSelect); + } +} + +void ConsoleTreeView::resizeEvent(QResizeEvent *e) +{ + static_cast<ConsoleItemDelegate *>(itemDelegate())->emitSizeHintChanged( + selectionModel()->currentIndex()); + QTreeView::resizeEvent(e); +} + +void ConsoleTreeView::drawBranches(QPainter *painter, const QRect &rect, + const QModelIndex &index) const +{ + ConsoleItemModel::ItemType type = + (ConsoleItemModel::ItemType)index.data( + ConsoleItemModel::TypeRole).toInt(); + static_cast<ConsoleItemDelegate *>(itemDelegate())->drawBackground( + painter, rect, type, true); + QTreeView::drawBranches(painter, rect, index); +} + +} //Internal +} //Debugger diff --git a/src/plugins/debugger/qml/consoletreeview.h b/src/plugins/debugger/qml/consoletreeview.h new file mode 100644 index 0000000000..5790fbde6a --- /dev/null +++ b/src/plugins/debugger/qml/consoletreeview.h @@ -0,0 +1,59 @@ +/************************************************************************** +** +** This file is part of Qt Creator +** +** Copyright (c) 2012 Nokia Corporation and/or its subsidiary(-ies). +** +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** +** GNU Lesser General Public License Usage +** +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this file. +** Please review the following information to ensure the GNU Lesser General +** Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** Other Usage +** +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +**************************************************************************/ + +#ifndef CONSOLETREEVIEW_H +#define CONSOLETREEVIEW_H + +#include <QtGui/QTreeView> + +namespace Debugger { +namespace Internal { + +class ConsoleTreeView : public QTreeView +{ + Q_OBJECT +public: + explicit ConsoleTreeView(QWidget *parent = 0); + + void setItemDelegate(QAbstractItemDelegate *delegate); + +protected: + void mousePressEvent(QMouseEvent *event); + void resizeEvent(QResizeEvent *e); + void drawBranches(QPainter *painter, const QRect &rect, + const QModelIndex &index) const; +}; + +} //Internal +} //Debugger + +#endif // CONSOLETREEVIEW_H diff --git a/src/plugins/debugger/qml/qml.pri b/src/plugins/debugger/qml/qml.pri index a34c120add..74a8d2c3e8 100644 --- a/src/plugins/debugger/qml/qml.pri +++ b/src/plugins/debugger/qml/qml.pri @@ -10,7 +10,12 @@ HEADERS += \ $$PWD/qscriptdebuggerclient.h \ $$PWD/qmlv8debuggerclient.h \ $$PWD/interactiveinterpreter.h \ - $$PWD/qmlv8debuggerclientconstants.h + $$PWD/qmlv8debuggerclientconstants.h \ + $$PWD/consoletreeview.h \ + $$PWD/consoleitemmodel.h \ + $$PWD/consoleitemdelegate.h \ + $$PWD/consoleeditor.h \ + $$PWD/consolebackend.h SOURCES += \ $$PWD/qmlengine.cpp \ @@ -20,5 +25,10 @@ SOURCES += \ $$PWD/qmljsscriptconsole.cpp \ $$PWD/qscriptdebuggerclient.cpp \ $$PWD/qmlv8debuggerclient.cpp \ - $$PWD/interactiveinterpreter.cpp + $$PWD/interactiveinterpreter.cpp \ + $$PWD/consoletreeview.cpp \ + $$PWD/consoleitemmodel.cpp \ + $$PWD/consoleitemdelegate.cpp \ + $$PWD/consoleeditor.cpp \ + $$PWD/consolebackend.cpp diff --git a/src/plugins/debugger/qml/qmljsscriptconsole.cpp b/src/plugins/debugger/qml/qmljsscriptconsole.cpp index 1579875bd2..086ae12c75 100644 --- a/src/plugins/debugger/qml/qmljsscriptconsole.cpp +++ b/src/plugins/debugger/qml/qmljsscriptconsole.cpp @@ -31,20 +31,21 @@ **************************************************************************/ #include "qmljsscriptconsole.h" -#include "interactiveinterpreter.h" #include "qmladapter.h" #include "debuggerstringutils.h" - -#include <texteditor/fontsettings.h> -#include <texteditor/texteditorsettings.h> +#include "consoletreeview.h" +#include "consoleitemmodel.h" +#include "consoleitemdelegate.h" +#include "consolebackend.h" +#include "debuggerstringutils.h" #include <extensionsystem/pluginmanager.h> #include <coreplugin/coreconstants.h> #include <utils/statuslabel.h> #include <utils/styledbar.h> +#include <utils/savedaction.h> #include <coreplugin/icore.h> -#include <utils/qtcassert.h> #include <qmljsdebugclient/qdebugmessageclient.h> #include <debugger/qml/qmlcppengine.h> @@ -67,56 +68,6 @@ static const char SHOW_ERROR[] = "showError"; namespace Debugger { namespace Internal { -class QmlJSScriptConsolePrivate -{ -public: - QmlJSScriptConsolePrivate() - : adapter(0), - prompt(_("> ")), - startOfEditableArea(-1), - lastKnownPosition(0), - inferiorStopped(false), - hasContext(false) - { - scriptHistory.append(QString()); - scriptHistoryIndex = scriptHistory.count(); - } - - void appendToHistory(const QString &script); - bool canEvaluateScript(const QString &script); - - QmlAdapter *adapter; - - QString prompt; - int startOfEditableArea; - int lastKnownPosition; - - QStringList scriptHistory; - int scriptHistoryIndex; - - InteractiveInterpreter interpreter; - - bool inferiorStopped; - bool hasContext; - - QFlags<QmlJSScriptConsole::DebugLevelFlag> debugLevel; -}; - -void QmlJSScriptConsolePrivate::appendToHistory(const QString &script) -{ - scriptHistoryIndex = scriptHistory.count(); - scriptHistory.replace(scriptHistoryIndex - 1,script); - scriptHistory.append(QString()); - scriptHistoryIndex = scriptHistory.count(); -} - -bool QmlJSScriptConsolePrivate::canEvaluateScript(const QString &script) -{ - interpreter.clearText(); - interpreter.appendText(script); - return interpreter.canEvaluate(); -} - /////////////////////////////////////////////////////////////////////// // // QmlJSScriptConsoleWidget @@ -126,74 +77,151 @@ bool QmlJSScriptConsolePrivate::canEvaluateScript(const QString &script) QmlJSScriptConsoleWidget::QmlJSScriptConsoleWidget(QWidget *parent) : QWidget(parent) { + const int statusBarHeight = 25; + const int spacing = 7; + const int buttonWidth = 25; + QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setMargin(0); vbox->setSpacing(0); QWidget *statusbarContainer = new QWidget; - + statusbarContainer->setFixedHeight(statusBarHeight); QHBoxLayout *hbox = new QHBoxLayout(statusbarContainer); hbox->setMargin(0); hbox->setSpacing(0); + //Status Label + m_statusLabel = new Utils::StatusLabel; + hbox->addSpacing(spacing); + hbox->addWidget(m_statusLabel); + hbox->addWidget(new Utils::StyledSeparator); + + //Filters + hbox->addSpacing(spacing); + m_showLog = new QToolButton(this); + m_showLog->setAutoRaise(true); + m_showLog->setFixedWidth(buttonWidth); + m_showLogAction = new Utils::SavedAction(this); + m_showLogAction->setDefaultValue(true); + m_showLogAction->setSettingsKey(_(SCRIPT_CONSOLE), _(SHOW_LOG)); + m_showLogAction->setText(tr("Log")); + m_showLogAction->setCheckable(true); + m_showLogAction->setIcon(QIcon(_(":/debugger/images/log.png"))); +// connect(m_showLogAction, SIGNAL(toggled(bool)), this, SLOT(setDebugLevel())); + m_showLog->setDefaultAction(m_showLogAction); + + m_showWarning = new QToolButton(this); + m_showWarning->setAutoRaise(true); + m_showWarning->setFixedWidth(buttonWidth); + m_showWarningAction = new Utils::SavedAction(this); + m_showWarningAction->setDefaultValue(true); + m_showWarningAction->setSettingsKey(_(SCRIPT_CONSOLE), _(SHOW_WARNING)); + m_showWarningAction->setText(tr("Warning")); + m_showWarningAction->setCheckable(true); + m_showWarningAction->setIcon(QIcon(_(":/debugger/images/warning.png"))); +// connect(m_showWarningAction, SIGNAL(toggled(bool)), this, SLOT(setDebugLevel())); + m_showWarning->setDefaultAction(m_showWarningAction); + + m_showError = new QToolButton(this); + m_showError->setAutoRaise(true); + m_showError->setFixedWidth(buttonWidth); + m_showErrorAction = new Utils::SavedAction(this); + m_showErrorAction->setDefaultValue(true); + m_showErrorAction->setSettingsKey(_(SCRIPT_CONSOLE), _(SHOW_ERROR)); + m_showErrorAction->setText(tr("Error")); + m_showErrorAction->setCheckable(true); + m_showErrorAction->setIcon(QIcon(_(":/debugger/images/error.png"))); +// connect(m_showErrorAction, SIGNAL(toggled(bool)), this, SLOT(setDebugLevel())); + m_showError->setDefaultAction(m_showErrorAction); + + hbox->addWidget(m_showLog); + hbox->addSpacing(spacing); + hbox->addWidget(m_showWarning); + hbox->addSpacing(spacing); + hbox->addWidget(m_showError); + //Clear Button QToolButton *clearButton = new QToolButton; + clearButton->setAutoRaise(true); + clearButton->setFixedWidth(buttonWidth); QAction *clearAction = new QAction(tr("Clear Console"), this); clearAction->setIcon(QIcon(_(Core::Constants::ICON_CLEAN_PANE))); - clearButton->setDefaultAction(clearAction); - //Status Label - m_statusLabel = new Utils::StatusLabel; + hbox->addSpacing(spacing); + hbox->addWidget(clearButton); + hbox->addSpacing(spacing); + + m_consoleView = new ConsoleTreeView(this); + + m_model = new ConsoleItemModel(this); + m_model->clear(); + connect(clearAction, SIGNAL(triggered()), m_model, SLOT(clear())); + m_consoleView->setModel(m_model); + connect(m_model, + SIGNAL(editableRowAppended(QModelIndex,QItemSelectionModel::SelectionFlags)), + m_consoleView->selectionModel(), + SLOT(setCurrentIndex(QModelIndex,QItemSelectionModel::SelectionFlags))); + + m_consoleBackend = new QmlJSConsoleBackend(this); + connect(m_consoleBackend, SIGNAL(evaluateExpression(QString)), + this, SIGNAL(evaluateExpression(QString))); + connect(m_consoleBackend, SIGNAL(message(ConsoleItemModel::ItemType,QString)), + this, SLOT(appendOutput(ConsoleItemModel::ItemType,QString))); + + m_itemDelegate = new ConsoleItemDelegate(this); + connect(m_itemDelegate, SIGNAL(appendEditableRow()), + m_model, SLOT(appendEditableRow())); + m_itemDelegate->setConsoleBackend(m_consoleBackend); + m_consoleView->setItemDelegate(m_itemDelegate); - hbox->addWidget(m_statusLabel, 20, Qt::AlignLeft); - hbox->addWidget(new Utils::StyledSeparator); - m_showLog = new QCheckBox(tr("Log"), this); - m_showWarning = new QCheckBox(tr("Warning"), this); - m_showError = new QCheckBox(tr("Error"), this); - connect(m_showLog, SIGNAL(stateChanged(int)), this, SLOT(setDebugLevel())); - connect(m_showWarning, SIGNAL(stateChanged(int)), this, SLOT(setDebugLevel())); - connect(m_showError, SIGNAL(stateChanged(int)), this, SLOT(setDebugLevel())); - hbox->addWidget(m_showLog); - hbox->addWidget(m_showWarning); - hbox->addWidget(m_showError); - hbox->addWidget(new Utils::StyledSeparator); - hbox->addWidget(clearButton, 0, Qt::AlignRight); - - m_console = new QmlJSScriptConsole(this); - connect(m_console, SIGNAL(evaluateExpression(QString)), this, - SIGNAL(evaluateExpression(QString))); - connect(m_console, SIGNAL(updateStatusMessage(const QString &, int)), m_statusLabel, - SLOT(showStatusMessage(const QString &, int))); - connect(clearAction, SIGNAL(triggered()), m_console, SLOT(clear())); vbox->addWidget(statusbarContainer); - vbox->addWidget(m_console); + vbox->addWidget(m_consoleView); - QSettings *settings = Core::ICore::settings(); - settings->beginGroup(_(SCRIPT_CONSOLE)); - m_showLog->setChecked(settings->value(_(SHOW_LOG), true).toBool()); - m_showWarning->setChecked(settings->value(_(SHOW_WARNING), true).toBool()); - m_showError->setChecked(settings->value(_(SHOW_ERROR), true).toBool()); - settings->endGroup(); + readSettings(); + connect(Core::ICore::instance(), + SIGNAL(saveSettingsRequested()), SLOT(writeSettings())); } QmlJSScriptConsoleWidget::~QmlJSScriptConsoleWidget() { + writeSettings(); +} + +void QmlJSScriptConsoleWidget::readSettings() +{ + QSettings *settings = Core::ICore::settings(); + m_showLogAction->readSettings(settings); + m_showWarningAction->readSettings(settings); + m_showErrorAction->readSettings(settings); +} + +void QmlJSScriptConsoleWidget::writeSettings() const +{ QSettings *settings = Core::ICore::settings(); - settings->beginGroup(_(SCRIPT_CONSOLE)); - settings->setValue(_(SHOW_LOG), QVariant(m_showLog->isChecked())); - settings->setValue(_(SHOW_WARNING), QVariant(m_showWarning->isChecked())); - settings->setValue(_(SHOW_ERROR), QVariant(m_showError->isChecked())); - settings->endGroup(); + m_showLogAction->writeSettings(settings); + m_showWarningAction->writeSettings(settings); + m_showErrorAction->writeSettings(settings); } void QmlJSScriptConsoleWidget::setEngine(DebuggerEngine *engine) { - if (m_console->engine()) - disconnect(m_console->engine(), SIGNAL(stateChanged(Debugger::DebuggerState)), + QmlEngine *qmlEngine = m_consoleBackend->engine(); + if (qmlEngine) { + disconnect(qmlEngine, SIGNAL(stateChanged(Debugger::DebuggerState)), this, SLOT(onEngineStateChanged(Debugger::DebuggerState))); + disconnect(qmlEngine->stackHandler(), SIGNAL(currentIndexChanged()), + this, SLOT(onSelectionChanged())); + disconnect(qmlEngine->adapter(), SIGNAL(selectionChanged()), + this, SLOT(onSelectionChanged())); + disconnect(qmlEngine->adapter()->messageClient(), + SIGNAL(message(QtMsgType,QString)), + this, SLOT(appendMessage(QtMsgType,QString))); + qmlEngine = 0; + } - QmlEngine *qmlEngine = qobject_cast<QmlEngine *>(engine); + qmlEngine = qobject_cast<QmlEngine *>(engine); QmlCppEngine *qmlCppEngine = qobject_cast<QmlCppEngine *>(engine); if (qmlCppEngine) qmlEngine = qobject_cast<QmlEngine *>(qmlCppEngine->qmlEngine()); @@ -202,483 +230,97 @@ void QmlJSScriptConsoleWidget::setEngine(DebuggerEngine *engine) if (qmlEngine) { connect(qmlEngine, SIGNAL(stateChanged(Debugger::DebuggerState)), this, SLOT(onEngineStateChanged(Debugger::DebuggerState))); - + connect(qmlEngine->stackHandler(), SIGNAL(currentIndexChanged()), + this, SLOT(onSelectionChanged())); + connect(qmlEngine->adapter(), SIGNAL(selectionChanged()), + this, SLOT(onSelectionChanged())); + connect(qmlEngine->adapter()->messageClient(), + SIGNAL(message(QtMsgType,QString)), + this, SLOT(appendMessage(QtMsgType,QString))); onEngineStateChanged(qmlEngine->state()); } - - m_console->setEngine(qmlEngine); + m_consoleBackend->setEngine(qmlEngine); } void QmlJSScriptConsoleWidget::appendResult(const QString &result) { - m_console->appendResult(result); -} - -void QmlJSScriptConsoleWidget::setDebugLevel() -{ - QFlags<QmlJSScriptConsole::DebugLevelFlag> level; - - if (m_showLog->isChecked()) - level |= QmlJSScriptConsole::Log; - - if (m_showWarning->isChecked()) - level |= QmlJSScriptConsole::Warning; - - if (m_showError->isChecked()) - level |= QmlJSScriptConsole::Error; - - m_console->setDebugLevel(level); + m_model->appendItem(ConsoleItemModel::UndefinedType, result); } void QmlJSScriptConsoleWidget::onEngineStateChanged(Debugger::DebuggerState state) { if (state == InferiorRunOk || state == InferiorStopOk) { setEnabled(true); - m_console->setInferiorStopped(state == InferiorStopOk); + m_consoleBackend->setInferiorStopped(state == InferiorStopOk); } else { setEnabled(false); } } -/////////////////////////////////////////////////////////////////////// -// -// QmlJSScriptConsole -// -/////////////////////////////////////////////////////////////////////// - -QmlJSScriptConsole::QmlJSScriptConsole(QWidget *parent) - : QPlainTextEdit(parent), - d(new QmlJSScriptConsolePrivate()) -{ - connect(this, SIGNAL(cursorPositionChanged()), SLOT(onCursorPositionChanged())); - - setFrameStyle(QFrame::NoFrame); - setUndoRedoEnabled(false); - setBackgroundVisible(false); - const TextEditor::FontSettings &fs = TextEditor::TextEditorSettings::instance()->fontSettings(); - setFont(fs.font()); - - displayPrompt(); -} - -QmlJSScriptConsole::~QmlJSScriptConsole() -{ - delete d; -} - -void QmlJSScriptConsole::setPrompt(const QString &prompt) -{ - d->prompt = prompt; -} - -QString QmlJSScriptConsole::prompt() const -{ - return d->prompt; -} - -void QmlJSScriptConsole::setInferiorStopped(bool inferiorStopped) -{ - d->inferiorStopped = inferiorStopped; - onSelectionChanged(); -} - -void QmlJSScriptConsole::setEngine(QmlEngine *eng) -{ - if (d->adapter) { - disconnect(engine()->stackHandler(), SIGNAL(currentIndexChanged()), this, SLOT(onSelectionChanged())); - disconnect(d->adapter, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged())); - disconnect(d->adapter->messageClient(), SIGNAL(message(QtMsgType,QString)), - this, SLOT(insertDebugOutput(QtMsgType,QString))); - d->adapter = 0; - } - - if (eng) { - d->adapter = eng->adapter(); - connect(eng->stackHandler(), SIGNAL(currentIndexChanged()), this, SLOT(onSelectionChanged())); - connect(d->adapter, SIGNAL(selectionChanged()), this, SLOT(onSelectionChanged())); - connect(d->adapter->messageClient(), SIGNAL(message(QtMsgType,QString)), - this, SLOT(insertDebugOutput(QtMsgType,QString))); - } - - clear(); -} - -DebuggerEngine *QmlJSScriptConsole::engine() const -{ - if (d->adapter) - return d->adapter->debuggerEngine(); - return 0; -} - -void QmlJSScriptConsole::appendResult(const QString &message, const QColor &color) -{ - QTextCharFormat resultFormat; - resultFormat.setForeground(color); - QTextCursor cur = textCursor(); - - cur.setPosition(d->startOfEditableArea - d->prompt.length()); - cur.insertText(message, resultFormat); - cur.insertText(_("\n")); - - QList<QTextEdit::ExtraSelection> selections = extraSelections(); - QTextEdit::ExtraSelection sel; - sel.format = resultFormat; - sel.cursor = cur; - selections.append(sel); - - setExtraSelections(selections); - - d->startOfEditableArea += message.length() + 1; //1 for new line character -} - -void QmlJSScriptConsole::setDebugLevel(QFlags<DebugLevelFlag> level) +void QmlJSScriptConsoleWidget::onSelectionChanged() { - d->debugLevel = level; -} - -void QmlJSScriptConsole::clear() -{ - QPlainTextEdit::clear(); - displayPrompt(); -} + QmlEngine *qmlEngine = m_consoleBackend->engine(); + if (qmlEngine && qmlEngine->adapter()) { + const QString context = m_consoleBackend->inferiorStopped() ? + qmlEngine->stackHandler()->currentFrame().function : + qmlEngine->adapter()->currentSelectedDisplayName(); -void QmlJSScriptConsole::onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State state) -{ - QDeclarativeDebugExpressionQuery *query = qobject_cast<QDeclarativeDebugExpressionQuery *>(sender()); - - if (query && state != QDeclarativeDebugQuery::Error) { - QString result(query->result().toString()); - if (result == _("<undefined>") && d->inferiorStopped) { - //don't give up. check if we can still evaluate using javascript engine - emit evaluateExpression(getCurrentScript()); - } else { - appendResult(result); - } - } else { - QPlainTextEdit::appendPlainText(QString()); - moveCursor(QTextCursor::EndOfLine); + m_consoleBackend->setIsValidContext(!context.isEmpty()); + m_statusLabel->showStatusMessage(tr("Context: ").append(context), 0); } - delete query; } -void QmlJSScriptConsole::onSelectionChanged() +void QmlJSScriptConsoleWidget::appendOutput(ConsoleItemModel::ItemType itemType, + const QString &message) { - if (d->adapter) { - const QString context = d->inferiorStopped ? - d->adapter->debuggerEngine()->stackHandler()->currentFrame().function : - d->adapter->currentSelectedDisplayName(); + if (itemType == ConsoleItemModel::UndefinedType) + return m_model->appendItem(itemType, message); - d->hasContext = !context.isEmpty(); - emit updateStatusMessage(tr("Context: ").append(context), 0); + QtMsgType type; + switch (itemType) { + case ConsoleItemModel::LogType: + type = QtDebugMsg; + break; + case ConsoleItemModel::WarningType: + type = QtWarningMsg; + break; + case ConsoleItemModel::ErrorType: + type = QtCriticalMsg; + break; + default: + type = QtDebugMsg; + break; } + appendMessage(type, message); } -void QmlJSScriptConsole::insertDebugOutput(QtMsgType type, const QString &debugMsg) +void QmlJSScriptConsoleWidget::appendMessage(QtMsgType type, const QString &message) { - QColor color; + ConsoleItemModel::ItemType itemType; switch (type) { case QtDebugMsg: - if (!(d->debugLevel & Log)) + if (!m_showLogAction->isChecked()) return; - color = QColor(Qt::darkBlue); + itemType = ConsoleItemModel::LogType; break; case QtWarningMsg: - if (!(d->debugLevel & Warning)) + if (!m_showWarningAction->isChecked()) return; - color = QColor(Qt::darkYellow); + itemType = ConsoleItemModel::WarningType; break; case QtCriticalMsg: - if (!(d->debugLevel & Error)) + case QtFatalMsg: + if (!m_showErrorAction->isChecked()) return; - color = QColor(Qt::darkRed); + itemType = ConsoleItemModel::ErrorType; break; default: - color = QColor(Qt::black); - } - appendResult(debugMsg, color); -} - -void QmlJSScriptConsole::keyPressEvent(QKeyEvent *e) -{ - bool keyConsumed = false; - switch (e->key()) { - - case Qt::Key_Return: - case Qt::Key_Enter: - if (isEditableArea()) { - handleReturnKey(); - keyConsumed = true; - } - break; - - case Qt::Key_Backspace: { - QTextCursor cursor = textCursor(); - bool hasSelection = cursor.hasSelection(); - int selectionStart = cursor.selectionStart(); - if ((hasSelection && selectionStart < d->startOfEditableArea) - || (!hasSelection && selectionStart == d->startOfEditableArea)) { - keyConsumed = true; - } - break; - } - - case Qt::Key_Delete: - if (textCursor().selectionStart() < d->startOfEditableArea) { - keyConsumed = true; - } - break; - - case Qt::Key_Tab: - case Qt::Key_Backtab: - keyConsumed = true; - break; - - case Qt::Key_Left: - if (textCursor().position() == d->startOfEditableArea) { - keyConsumed = true; - } else if (e->modifiers() & Qt::ControlModifier && isEditableArea()) { - handleHomeKey(); - keyConsumed = true; - } - break; - - case Qt::Key_Up: - if (isEditableArea()) { - handleUpKey(); - keyConsumed = true; - } - break; - - case Qt::Key_Down: - if (isEditableArea()) { - handleDownKey(); - keyConsumed = true; - } - break; - - case Qt::Key_Home: - if (isEditableArea()) { - handleHomeKey(); - keyConsumed = true; - } - break; - - case Qt::Key_C: - case Qt::Key_Insert: { - //Fair to assume that for any selection beyond startOfEditableArea - //only copy function is allowed. - QTextCursor cursor = textCursor(); - bool hasSelection = cursor.hasSelection(); - int selectionStart = cursor.selectionStart(); - if (hasSelection && selectionStart < d->startOfEditableArea) { - if (!(e->modifiers() & Qt::ControlModifier)) - keyConsumed = true; - } - break; - } - - default: { - QTextCursor cursor = textCursor(); - bool hasSelection = cursor.hasSelection(); - int selectionStart = cursor.selectionStart(); - if (hasSelection && selectionStart < d->startOfEditableArea) { - keyConsumed = true; - } - break; + //this cannot happen as type has to + //be one of the above + //return since itemType is not known + return; } - } - - if (!keyConsumed) - QPlainTextEdit::keyPressEvent(e); -} - -void QmlJSScriptConsole::contextMenuEvent(QContextMenuEvent *event) -{ - QTextCursor cursor = textCursor(); - Qt::TextInteractionFlags flags = textInteractionFlags(); - bool hasSelection = cursor.hasSelection(); - int selectionStart = cursor.selectionStart(); - bool canBeEdited = true; - if (hasSelection && selectionStart < d->startOfEditableArea) { - canBeEdited = false; - } - - QMenu *menu = new QMenu(); - QAction *a; - - if ((flags & Qt::TextEditable) && canBeEdited) { - a = menu->addAction(tr("Cut"), this, SLOT(cut())); - a->setEnabled(cursor.hasSelection()); - } - - - a = menu->addAction(tr("Copy"), this, SLOT(copy())); - a->setEnabled(cursor.hasSelection()); - - if ((flags & Qt::TextEditable) && canBeEdited) { - a = menu->addAction(tr("Paste"), this, SLOT(paste())); - a->setEnabled(canPaste()); - } - - menu->addSeparator(); - a = menu->addAction(tr("Select All"), this, SLOT(selectAll())); - a->setEnabled(!document()->isEmpty()); - - menu->addSeparator(); - menu->addAction(tr("Clear"), this, SLOT(clear())); - - menu->exec(event->globalPos()); - - - delete menu; -} - -void QmlJSScriptConsole::mouseReleaseEvent(QMouseEvent *e) -{ - QPlainTextEdit::mouseReleaseEvent(e); - QTextCursor cursor = textCursor(); - if (e->button() == Qt::LeftButton && !cursor.hasSelection() && !isEditableArea()) { - cursor.setPosition(d->lastKnownPosition); - setTextCursor(cursor); - } -} - -void QmlJSScriptConsole::onCursorPositionChanged() -{ - if (!isEditableArea()) { - setTextInteractionFlags(Qt::TextSelectableByMouse); - } else { - d->lastKnownPosition = textCursor().position(); - setTextInteractionFlags(Qt::TextEditorInteraction); - } -} - -void QmlJSScriptConsole::displayPrompt() -{ - d->startOfEditableArea = textCursor().position() + d->prompt.length(); - QTextCursor cur = textCursor(); - cur.insertText(d->prompt); - cur.movePosition(QTextCursor::EndOfWord); - setTextCursor(cur); -} - -void QmlJSScriptConsole::handleReturnKey() -{ - QString currentScript = getCurrentScript(); - bool showPrompt = true; - bool showInvalidContextError = false; - - //Check if string is only white spaces - if (!currentScript.trimmed().isEmpty()) { - //Check for a valid context - if (d->hasContext) { - //check if it can be evaluated - if (d->canEvaluateScript(currentScript)) { - //Evaluate expression based on engine state - //When engine->state() == InferiorStopOk, the expression - //is sent to V8DebugService. In all other cases, the - //expression is evaluated by QDeclarativeEngine. - if (!d->inferiorStopped) { - QTC_ASSERT(d->adapter, return); - QDeclarativeEngineDebug *engineDebug = d->adapter->engineDebugClient(); - int id = d->adapter->currentSelectedDebugId(); - if (engineDebug && id != -1) { - QDeclarativeDebugExpressionQuery *query = - engineDebug->queryExpressionResult(id, currentScript, this); - connect(query, SIGNAL(stateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State)), - this, SLOT(onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State))); - } - } else { - emit evaluateExpression(currentScript); - } - - d->appendToHistory(currentScript); - } else { - //The expression is not complete, wait for more input - //Move to next line and do not show prompt - QPlainTextEdit::appendPlainText(QString()); - moveCursor(QTextCursor::EndOfLine); - showPrompt = false; - } - } else { - //Incase of invalid context, append the expression to history - //and show Error message - d->appendToHistory(currentScript); - showInvalidContextError = true; - } - } - - if (showPrompt) { - QTextCursor cur = textCursor(); - cur.movePosition(QTextCursor::End); - cur.insertText(_("\n")); - setTextCursor(cur); - displayPrompt(); - } - - //Show an error message - if (showInvalidContextError) - appendResult(QLatin1String("Cannot evaluate without a valid QML/JS Context")); -} - -void QmlJSScriptConsole::handleUpKey() -{ - //get the current script and update in script history - QString currentScript = getCurrentScript(); - d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript); - - if (d->scriptHistoryIndex > 1) - d->scriptHistoryIndex--; - - replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1)); -} - -void QmlJSScriptConsole::handleDownKey() -{ - //get the current script and update in script history - QString currentScript = getCurrentScript(); - d->scriptHistory.replace(d->scriptHistoryIndex - 1,currentScript); - - if (d->scriptHistoryIndex < d->scriptHistory.count()) - d->scriptHistoryIndex++; - - replaceCurrentScript(d->scriptHistory.at(d->scriptHistoryIndex - 1)); -} - -void QmlJSScriptConsole::handleHomeKey() -{ - QTextCursor cursor = textCursor(); - cursor.setPosition(d->startOfEditableArea); - setTextCursor(cursor); -} - -QString QmlJSScriptConsole::getCurrentScript() const -{ - QTextCursor cursor = textCursor(); - cursor.setPosition(d->startOfEditableArea); - while (cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor)) ; - QString script = cursor.selectedText(); - cursor.clearSelection(); - //remove trailing white space - int end = script.size() - 1; - while (end > 0 && script[end].isSpace()) - end--; - return script.left(end + 1); -} - -void QmlJSScriptConsole::replaceCurrentScript(const QString &script) -{ - QTextCursor cursor = textCursor(); - cursor.setPosition(d->startOfEditableArea); - while (cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor)) ; - cursor.deleteChar(); - cursor.insertText(script); - setTextCursor(cursor); -} - -bool QmlJSScriptConsole::isEditableArea() const -{ - return textCursor().position() >= d->startOfEditableArea; + m_model->appendItem(itemType, message); } } //Internal diff --git a/src/plugins/debugger/qml/qmljsscriptconsole.h b/src/plugins/debugger/qml/qmljsscriptconsole.h index aef7b72401..27997aebfa 100644 --- a/src/plugins/debugger/qml/qmljsscriptconsole.h +++ b/src/plugins/debugger/qml/qmljsscriptconsole.h @@ -33,16 +33,17 @@ #ifndef QMLJSSCRIPTCONSOLE_H #define QMLJSSCRIPTCONSOLE_H -#include <qmljsdebugclient/qdeclarativeenginedebug.h> +#include "consoleitemmodel.h" #include <debugger/debuggerconstants.h> -#include <QPlainTextEdit> +#include <QtGui/QWidget> QT_BEGIN_NAMESPACE -class QCheckBox; +class QToolButton; QT_END_NAMESPACE namespace Utils { class StatusLabel; +class SavedAction; } namespace Debugger { @@ -51,10 +52,9 @@ class DebuggerEngine; namespace Internal { -class QmlJSScriptConsolePrivate; -class QmlJSScriptConsole; -class QmlEngine; - +class ConsoleTreeView; +class ConsoleItemDelegate; +class QmlJSConsoleBackend; class QmlJSScriptConsoleWidget : public QWidget { Q_OBJECT @@ -63,90 +63,33 @@ public: ~QmlJSScriptConsoleWidget(); void setEngine(DebuggerEngine *engine); + void readSettings(); public slots: + void writeSettings() const; void appendResult(const QString &result); + void appendOutput(ConsoleItemModel::ItemType, const QString &message); + void appendMessage(QtMsgType type, const QString &message); signals: void evaluateExpression(const QString &expr); private slots: - void setDebugLevel(); void onEngineStateChanged(Debugger::DebuggerState state); - -private: - QmlJSScriptConsole *m_console; - Utils::StatusLabel *m_statusLabel; - QCheckBox *m_showLog; - QCheckBox *m_showWarning; - QCheckBox *m_showError; - -}; - -class QmlJSScriptConsole : public QPlainTextEdit -{ - Q_OBJECT - -public: - enum DebugLevelFlag { - None = 0, - Log = 1, - Warning = 2, - Error = 4 - }; - - explicit QmlJSScriptConsole(QWidget *parent = 0); - ~QmlJSScriptConsole(); - - inline void setTitle(const QString &title) - { setDocumentTitle(title); } - - inline QString title() const - { return documentTitle(); } - - void setPrompt(const QString &prompt); - QString prompt() const; - - void setInferiorStopped(bool inferiorStopped); - - void setEngine(QmlEngine *engine); - DebuggerEngine *engine() const; - - void appendResult(const QString &message, const QColor &color = QColor(Qt::darkGray)); - - void setDebugLevel(QFlags<DebugLevelFlag> level); - -public slots: - void clear(); - void onStateChanged(QmlJsDebugClient::QDeclarativeDebugQuery::State); - void insertDebugOutput(QtMsgType type, const QString &debugMsg); - -protected: - void keyPressEvent(QKeyEvent *e); - void contextMenuEvent(QContextMenuEvent *event); - void mouseReleaseEvent(QMouseEvent *e); - -signals: - void evaluateExpression(const QString &expr); - void updateStatusMessage(const QString &message, int timeoutMS); - -private slots: void onSelectionChanged(); - void onCursorPositionChanged(); private: - void displayPrompt(); - void handleReturnKey(); - void handleUpKey(); - void handleDownKey(); - void handleHomeKey(); - QString getCurrentScript() const; - void replaceCurrentScript(const QString &script); - bool isEditableArea() const; - -private: - QmlJSScriptConsolePrivate *d; - friend class QmlJSScriptConsolePrivate; + ConsoleTreeView *m_consoleView; + ConsoleItemModel *m_model; + ConsoleItemDelegate *m_itemDelegate; + QmlJSConsoleBackend *m_consoleBackend; + Utils::StatusLabel *m_statusLabel; + QToolButton *m_showLog; + QToolButton *m_showWarning; + QToolButton *m_showError; + Utils::SavedAction *m_showLogAction; + Utils::SavedAction *m_showWarningAction; + Utils::SavedAction *m_showErrorAction; }; } //Internal |