diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index 29124a39a..991fd334a 100644 --- a/mix/AppContext.cpp +++ b/mix/AppContext.cpp @@ -37,6 +37,7 @@ #include "QVariableDefinition.h" #include "HttpServer.h" #include "AppContext.h" +#include "SortFilterProxyModel.h" using namespace dev; using namespace dev::eth; @@ -74,6 +75,7 @@ void AppContext::load() qmlRegisterType("org.ethereum.qml.QBoolType", 1, 0, "QBoolType"); qmlRegisterType("org.ethereum.qml.QVariableDeclaration", 1, 0, "QVariableDeclaration"); qmlRegisterType("org.ethereum.qml.RecordLogEntry", 1, 0, "RecordLogEntry"); + qmlRegisterType("org.ethereum.qml.SortFilterProxyModel", 1, 0, "SortFilterProxyModel"); QQmlComponent projectModelComponent(m_applicationEngine, QUrl("qrc:/qml/ProjectModel.qml")); QObject* projectModel = projectModelComponent.create(); if (projectModelComponent.isError()) @@ -86,6 +88,7 @@ void AppContext::load() m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel); qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); qmlRegisterType("HttpServer", 1, 0, "HttpServer"); + m_applicationEngine->load(QUrl("qrc:/qml/main.qml")); QWindow *window = qobject_cast(m_applicationEngine->rootObjects().at(0)); window->setIcon(QIcon(":/res/mix_256x256x32.png")); diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp index 97b808eb2..7f1f5d216 100644 --- a/mix/CodeEditorExtensionManager.cpp +++ b/mix/CodeEditorExtensionManager.cpp @@ -51,14 +51,6 @@ void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor) return; } -void CodeEditorExtensionManager::initExtensions() -{ - std::shared_ptr output = std::make_shared(m_appContext); - QObject::connect(m_appContext->codeModel(), &CodeModel::compilationComplete, this, &CodeEditorExtensionManager::applyCodeHighlight); - - initExtension(output); -} - void CodeEditorExtensionManager::initExtension(std::shared_ptr _ext) { if (!_ext->contentUrl().isEmpty()) @@ -93,5 +85,4 @@ void CodeEditorExtensionManager::setRightView(QQuickItem* _rightView) void CodeEditorExtensionManager::setHeaderView(QQuickItem* _headerView) { m_headerView = _headerView; - initExtensions(); //TODO: move this to a proper place } diff --git a/mix/CodeEditorExtensionManager.h b/mix/CodeEditorExtensionManager.h index fe6fbb33a..ebfe2d8a3 100644 --- a/mix/CodeEditorExtensionManager.h +++ b/mix/CodeEditorExtensionManager.h @@ -49,8 +49,6 @@ class CodeEditorExtensionManager: public QObject public: CodeEditorExtensionManager(); ~CodeEditorExtensionManager(); - /// Initialize all extensions. - void initExtensions(); /// Initialize extension. void initExtension(std::shared_ptr); /// Set current tab view diff --git a/mix/SortFilterProxyModel.cpp b/mix/SortFilterProxyModel.cpp new file mode 100644 index 000000000..6fb2cca0c --- /dev/null +++ b/mix/SortFilterProxyModel.cpp @@ -0,0 +1,156 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Yann + * @date 2015 + * Proxy used to filter a QML TableView. + */ + + +#include "SortFilterProxyModel.h" +#include +#include + +using namespace dev::mix; + +SortFilterProxyModel::SortFilterProxyModel(QObject* _parent) : QSortFilterProxyModel(_parent) +{ + connect(this, &SortFilterProxyModel::rowsInserted, this, &SortFilterProxyModel::countChanged); + connect(this, &SortFilterProxyModel::rowsRemoved, this, &SortFilterProxyModel::countChanged); +} + +int SortFilterProxyModel::count() const +{ + return rowCount(); +} + +QObject* SortFilterProxyModel::source() const +{ + return sourceModel(); +} + +void SortFilterProxyModel::setSource(QObject* _source) +{ + setSourceModel(qobject_cast(_source)); +} + +QByteArray SortFilterProxyModel::sortRole() const +{ + return roleNames().value(QSortFilterProxyModel::sortRole()); +} + +void SortFilterProxyModel::setSortRole(QByteArray const& _role) +{ + QSortFilterProxyModel::setSortRole(roleKey(_role)); +} + +void SortFilterProxyModel::setSortOrder(Qt::SortOrder _order) +{ + QSortFilterProxyModel::sort(0, _order); +} + +QString SortFilterProxyModel::filterString() const +{ + return filterRegExp().pattern(); +} + +void SortFilterProxyModel::setFilterString(QString const& _filter) +{ + setFilterRegExp(QRegExp(_filter, filterCaseSensitivity(), static_cast(filterSyntax()))); +} + +SortFilterProxyModel::FilterSyntax SortFilterProxyModel::filterSyntax() const +{ + return static_cast(filterRegExp().patternSyntax()); +} + +void SortFilterProxyModel::setFilterSyntax(SortFilterProxyModel::FilterSyntax _syntax) +{ + setFilterRegExp(QRegExp(filterString(), filterCaseSensitivity(), static_cast(_syntax))); +} + +QJSValue SortFilterProxyModel::get(int _idx) const +{ + QJSEngine *engine = qmlEngine(this); + QJSValue value = engine->newObject(); + if (_idx >= 0 && _idx < count()) + { + QHash roles = roleNames(); + QHashIterator it(roles); + while (it.hasNext()) + { + it.next(); + value.setProperty(QString::fromUtf8(it.value()), data(index(_idx, 0), it.key()).toString()); + } + } + return value; +} + +int SortFilterProxyModel::roleKey(QByteArray const& _role) const +{ + QHash roles = roleNames(); + QHashIterator it(roles); + while (it.hasNext()) + { + it.next(); + if (it.value() == _role) + return it.key(); + } + return -1; +} + +QHash SortFilterProxyModel::roleNames() const +{ + if (QAbstractItemModel* source = sourceModel()) + return source->roleNames(); + return QHash(); +} + +bool SortFilterProxyModel::filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const +{ + QAbstractItemModel* model = sourceModel(); + QModelIndex sourceIndex = model->index(_sourceRow, 0, _sourceParent); + if (!sourceIndex.isValid()) + return true; + + QString keyType = model->data(sourceIndex, roleKey(type.toUtf8())).toString(); + QString keyContent = model->data(sourceIndex, roleKey(content.toUtf8())).toString(); + return keyType.contains(m_filterType) && keyContent.contains(m_filterContent); +} + +void SortFilterProxyModel::setFilterType(QString const& _type) +{ + m_filterType = QRegExp(_type, filterCaseSensitivity(), static_cast(filterSyntax())); + setFilterRegExp(_type); +} + +QString SortFilterProxyModel::filterType() const +{ + return m_filterType.pattern(); +} + +void SortFilterProxyModel::setFilterContent(QString const& _content) +{ + m_filterContent = QRegExp(_content, filterCaseSensitivity(), static_cast(filterSyntax())); + setFilterRegExp(_content); +} + +QString SortFilterProxyModel::filterContent() const +{ + return m_filterContent.pattern(); +} + diff --git a/mix/SortFilterProxyModel.h b/mix/SortFilterProxyModel.h new file mode 100644 index 000000000..de9a2005f --- /dev/null +++ b/mix/SortFilterProxyModel.h @@ -0,0 +1,97 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** + * @author Yann + * @date 2015 + * Proxy used to filter a QML TableView. + */ + + +#pragma once + +#include +#include + +namespace dev +{ +namespace mix +{ + +class SortFilterProxyModel: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QObject* source READ source WRITE setSource) + + Q_PROPERTY(QByteArray sortRole READ sortRole WRITE setSortRole) + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) + + Q_PROPERTY(QString filterContent READ filterContent WRITE setFilterContent) + Q_PROPERTY(QString filterType READ filterType WRITE setFilterType) + Q_PROPERTY(QString filterString READ filterString WRITE setFilterString) + Q_PROPERTY(FilterSyntax filterSyntax READ filterSyntax WRITE setFilterSyntax) + + Q_ENUMS(FilterSyntax) + +public: + explicit SortFilterProxyModel(QObject* _parent = 0); + + QObject* source() const; + void setSource(QObject* _source); + + QByteArray sortRole() const; + void setSortRole(QByteArray const& _role); + + void setSortOrder(Qt::SortOrder _order); + + QString filterContent() const; + void setFilterContent(QString const& _content); + QString filterType() const; + void setFilterType(QString const& _type); + + QString filterString() const; + void setFilterString(QString const& _filter); + + enum FilterSyntax { + RegExp, + Wildcard, + FixedString + }; + + FilterSyntax filterSyntax() const; + void setFilterSyntax(FilterSyntax _syntax); + + int count() const; + Q_INVOKABLE QJSValue get(int _index) const; + +signals: + void countChanged(); + +protected: + int roleKey(QByteArray const& _role) const; + QHash roleNames() const; + bool filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const; + +private: + QRegExp m_filterType; + QRegExp m_filterContent; + const QString type = "type"; + const QString content = "content"; +}; + +} +} diff --git a/mix/qml/LogsPane.qml b/mix/qml/LogsPane.qml new file mode 100644 index 000000000..9d03b2af2 --- /dev/null +++ b/mix/qml/LogsPane.qml @@ -0,0 +1,302 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import org.ethereum.qml.SortFilterProxyModel 1.0 +import "." + +Rectangle +{ + function push(_level, _type, _content) + { + _content = _content.replace(/\n/g, " ") + logsModel.insert(0, { "type": _type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss dd.MM.yyyy"), "content": _content, "level": _level }); + } + + anchors.fill: parent + radius: 5 + color: LogsPaneStyle.generic.layout.backgroundColor + border.color: LogsPaneStyle.generic.layout.borderColor + border.width: LogsPaneStyle.generic.layout.borderWidth + ColumnLayout { + z: 2 + height: parent.height + width: parent.width + spacing: 0 + Row + { + id: rowAction + Layout.preferredHeight: LogsPaneStyle.generic.layout.headerHeight + height: LogsPaneStyle.generic.layout.headerHeight + anchors.leftMargin: LogsPaneStyle.generic.layout.leftMargin + anchors.left: parent.left + spacing: LogsPaneStyle.generic.layout.headerButtonSpacing + Button + { + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + action: clearAction + iconSource: "qrc:/qml/img/broom.png" + } + + Action { + id: clearAction + enabled: logsModel.count > 0 + tooltip: qsTr("Clear") + onTriggered: { + logsModel.clear() + } + } + + Button + { + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + action: copytoClipBoardAction + iconSource: "qrc:/qml/img/copy.png" + } + + Action { + id: copytoClipBoardAction + enabled: logsModel.count > 0 + tooltip: qsTr("Copy to Clipboard") + onTriggered: { + var content = ""; + for (var k = 0; k < logsModel.count; k++) + { + var log = logsModel.get(k); + content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; + } + appContext.toClipboard(content); + } + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height - 10 + color : "#808080" + } + + ToolButton { + id: javascriptButton + checkable: true + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("javascript") + } + tooltip: qsTr("JavaScript") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-3) + color: LogsPaneStyle.generic.layout.logLabelColor + anchors.centerIn: parent + text: qsTr("JavaScript") + } + } + } + } + + ToolButton { + id: runButton + checkable: true + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("run") + } + tooltip: qsTr("Run") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-3) + color: LogsPaneStyle.generic.layout.logLabelColor + anchors.centerIn: parent + text: qsTr("Run") + } + } + } + } + + ToolButton { + id: stateButton + checkable: true + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("state") + } + tooltip: qsTr("State") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-3) + color: "#5391d8" + anchors.centerIn: parent + text: qsTr("State") + } + } + } + } + + ToolButton { + id: compilationButton + checkable: true + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + checked: false + onCheckedChanged: { + proxyModel.toogleFilter("compilation") + } + tooltip: qsTr("Compilation") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-3) + color: "#5391d8" + anchors.centerIn: parent + text: qsTr("Compilation") + } + } + } + } + + DefaultTextField + { + id: searchBox + height: LogsPaneStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + width: LogsPaneStyle.generic.layout.headerInputWidth + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-3) + font.italic: true + onTextChanged: { + proxyModel.search(text); + } + } + } + + ListModel { + id: logsModel + } + + TableView { + id: logsTable + clip: true + Layout.fillWidth: true + Layout.preferredHeight: parent.height - rowAction.height + headerVisible : false + onDoubleClicked: + { + var log = logsModel.get((logsTable.currentRow)); + if (log) + appContext.toClipboard(log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content); + } + + model: SortFilterProxyModel { + id: proxyModel + source: logsModel + property var roles: ["-", "javascript", "run", "state"] + + Component.onCompleted: { + filterType = regEx(proxyModel.roles); + } + + function search(_value) + { + filterContent = _value; + } + + function toogleFilter(_value) + { + var count = roles.length; + for (var i in roles) + { + if (roles[i] === _value) + { + roles.splice(i, 1); + break; + } + } + if (count === roles.length) + roles.push(_value); + + filterType = regEx(proxyModel.roles); + } + + function regEx(_value) + { + return "(?:" + roles.join('|') + ")"; + } + + filterType: "(?:javascript|run|state)" + filterContent: "" + filterSyntax: SortFilterProxyModel.RegExp + filterCaseSensitivity: Qt.CaseInsensitive + } + TableViewColumn + { + role: "date" + title: qsTr("date") + width: LogsPaneStyle.generic.layout.dateWidth + delegate: itemDelegate + } + TableViewColumn + { + role: "type" + title: qsTr("type") + width: LogsPaneStyle.generic.layout.typeWidth + delegate: itemDelegate + } + TableViewColumn + { + role: "content" + title: qsTr("content") + width: LogsPaneStyle.generic.layout.contentWidth + delegate: itemDelegate + } + + rowDelegate: Item { + Rectangle { + width: logsTable.width - 4 + height: 17 + color: styleData.alternate ? "transparent" : LogsPaneStyle.generic.layout.logAlternateColor + } + } + } + + Component { + id: itemDelegate + DefaultLabel { + text: styleData.value; + font.family: LogsPaneStyle.generic.layout.logLabelFont + font.pointSize: Style.absoluteSize(-1) + color: { + if (proxyModel.get(styleData.row).level === "error") + return "red"; + else if (proxyModel.get(styleData.row).level === "warning") + return "orange"; + else + return "#808080"; + } + } + } + } +} diff --git a/mix/qml/LogsPaneStyle.qml b/mix/qml/LogsPaneStyle.qml new file mode 100644 index 000000000..59b80653a --- /dev/null +++ b/mix/qml/LogsPaneStyle.qml @@ -0,0 +1,29 @@ +pragma Singleton +import QtQuick 2.0 + +QtObject { + + function absoluteSize(rel) + { + return systemPointSize + rel; + } + + property QtObject generic: QtObject { + property QtObject layout: QtObject { + property string backgroundColor: "#f7f7f7" + property string borderColor: "#5391d8" + property int borderWidth: 1 + property int headerHeight: 35 + property int headerButtonSpacing: 5 + property int leftMargin: 10 + property int headerButtonHeight: 30 + property string logLabelColor: "#5391d8" + property string logLabelFont: "sans serif" + property int headerInputWidth: 200 + property int dateWidth: 150 + property int typeWidth: 80 + property int contentWidth: 700 + property string logAlternateColor: "#f0f0f0" + } + } +} diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 081e0cd95..7ac751a79 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -35,8 +35,8 @@ Rectangle { onCompilationComplete: { if (firstCompile) { firstCompile = false; - if (runOnProjectLoad) - startQuickDebugging(); + if (runOnProjectLoad) + startQuickDebugging(); } } } @@ -102,7 +102,6 @@ Rectangle { } CodeEditorExtensionManager { - headerView: headerPaneTabs; } Settings { @@ -116,6 +115,7 @@ Rectangle { ColumnLayout { + id: mainColumn anchors.fill: parent spacing: 0 Rectangle { @@ -133,21 +133,15 @@ Rectangle { } id: headerPaneContainer anchors.fill: parent - TabView { - id: headerPaneTabs - tabsVisible: false - antialiasing: true + StatusPane + { anchors.fill: parent - style: TabViewStyle { - frameOverlap: 1 - tab: Rectangle {} - frame: Rectangle { color: "transparent" } - } + webPreview: webPreview } } } - Rectangle{ + Rectangle { Layout.fillWidth: true height: 1 color: "#8c8c8c" @@ -168,9 +162,9 @@ Rectangle { { anchors.fill: parent handleDelegate: Rectangle { - width: 1 - height: 1 - color: "#8c8c8c" + width: 1 + height: 1 + color: "#8c8c8c" } orientation: Qt.Horizontal @@ -180,16 +174,18 @@ Rectangle { Layout.minimumWidth: 250 Layout.fillHeight: true } + Rectangle { id: contentView Layout.fillHeight: true Layout.fillWidth: true + SplitView { - handleDelegate: Rectangle { + handleDelegate: Rectangle { width: 1 height: 1 color: "#8c8c8c" - } + } id: codeWebSplitter anchors.fill: parent orientation: Qt.Vertical diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index 60a9a051e..20b30ce3b 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -8,6 +8,7 @@ import "." Rectangle { id: statusHeader objectName: "statusPane" + property variant webPreview function updateStatus(message) { @@ -15,7 +16,6 @@ Rectangle { { status.state = ""; status.text = qsTr("Compile successfully."); - logslink.visible = false; debugImg.state = "active"; } else @@ -23,39 +23,76 @@ Rectangle { status.state = "error"; var errorInfo = ErrorLocationFormater.extractErrorInfo(message, true); status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail; - logslink.visible = true; debugImg.state = ""; + errorMessage(status.text, "Compilation"); } debugRunActionIcon.enabled = codeModel.hasContract; } - function infoMessage(text) + function infoMessage(text, type) { status.state = ""; status.text = text - logslink.visible = false; + logPane.push("info", type, text); } - function errorMessage(text) + function warningMessage(text, type) + { + status.state = "warning"; + status.text = text + logPane.push("warning", type, text); + } + + function errorMessage(text, type) { status.state = "error"; status.text = text - logslink.visible = false; + logPane.push("error", type, text); + } + + Connections { + target: webPreview + onJavaScriptMessage: + { + if (_level === 0) + infoMessage(_content, "JavaScript") + else + { + var message = _sourceId.substring(_sourceId.lastIndexOf("/") + 1) + " - " + qsTr("line") + " " + _lineNb + " - " + _content; + if (_level === 1) + warningMessage(message, "JavaScript") + else + errorMessage(message, "JavaScript") + } + } } Connections { target:clientModel - onRunStarted: infoMessage(qsTr("Running transactions...")); - onRunFailed: errorMessage(qsTr("Error running transactions: " + _message)); - onRunComplete: infoMessage(qsTr("Run complete")); - onNewBlock: infoMessage(qsTr("New block created")); + onRunStarted: infoMessage(qsTr("Running transactions..."), "Run"); + onRunFailed: errorMessage(format(_message), "Run"); + onRunComplete: infoMessage(qsTr("Run complete"), "Run"); + onNewBlock: infoMessage(qsTr("New block created"), "State"); + + function format(_message) + { + var formatted = _message.match(/(?:)/); + if (formatted.length > 1) + formatted = formatted[1] + ": "; + else + return _message; + var exceptionInfos = _message.match(/(?:tag_)(.+)/g); + for (var k in exceptionInfos) + formatted += " " + exceptionInfos[k].replace("*]", "").replace("tag_", "").replace("=", ""); + return formatted; + } } Connections { target:projectModel - onDeploymentStarted: infoMessage(qsTr("Running deployment...")); - onDeploymentError: errorMessage(error); - onDeploymentComplete: infoMessage(qsTr("Deployment complete")); - onDeploymentStepChanged: infoMessage(message); + onDeploymentStarted: infoMessage(qsTr("Running deployment..."), "Deployment"); + onDeploymentError: errorMessage(error, "Deployment"); + onDeploymentComplete: infoMessage(qsTr("Deployment complete"), "Deployment"); + onDeploymentStepChanged: infoMessage(message, "Deployment"); } Connections { target: codeModel @@ -74,6 +111,24 @@ Rectangle { width: 500 height: 30 color: "#fcfbfc" + states: [ + State { + name: "logsOpened" + PropertyChanges { + target: statusContainer + border.color: "#5391d8" + border.width: 1 + } + }, + State { + name: "logsClosed" + PropertyChanges { + target: statusContainer + border.color: "#5391d8" + border.width: 0 + } + } + ] Text { anchors.verticalCenter: parent.verticalCenter @@ -98,6 +153,17 @@ Rectangle { target: statusContainer color: "#fffcd5" } + }, + State { + name: "warning" + PropertyChanges { + target: status + color: "orange" + } + PropertyChanges { + target: statusContainer + color: "#fffcd5" + } } ] onTextChanged: @@ -127,30 +193,73 @@ Rectangle { color: "transparent" } } + MouseArea { + anchors.fill: parent + onClicked: { + logsContainer.toggle(); + } + } } Action { id: toolTipInfo tooltip: "" } - } - Button - { - id: logslink - anchors.left: statusContainer.right - anchors.leftMargin: 9 - visible: false - anchors.verticalCenter: parent.verticalCenter - action: displayLogAction - iconSource: "qrc:/qml/img/search_filled.png" - } + Rectangle + { + function toggle() + { + if (logsContainer.state === "opened") + { + statusContainer.state = "logsClosed"; + logsContainer.state = "closed" + } + else + { + statusContainer.state = "logsOpened"; + logsContainer.state = "opened"; + logsContainer.focus = true; + forceActiveFocus(); + } + } - Action { - id: displayLogAction - tooltip: qsTr("Display Log") - onTriggered: { - mainContent.displayCompilationErrorIfAny(); + id: logsContainer + width: 1000 + height: 0 + anchors.topMargin: 10 + anchors.top: statusContainer.bottom + anchors.horizontalCenter: parent.horizontalCenter + visible: false + radius: 5 + Component.onCompleted: + { + var top = logsContainer; + while (top.parent) + top = top.parent + var coordinates = logsContainer.mapToItem(top, 0, 0) + logsContainer.parent = top; + logsContainer.x = coordinates.x + logsContainer.y = coordinates.y + } + LogsPane + { + id: logPane + } + states: [ + State { + name: "opened"; + PropertyChanges { target: logsContainer; height: 500; visible: true } + }, + State { + name: "closed"; + PropertyChanges { target: logsContainer; height: 0; visible: false } + } + ] + transitions: Transition { + NumberAnimation { properties: "height"; easing.type: Easing.InOutQuad; duration: 200 } + NumberAnimation { properties: "visible"; easing.type: Easing.InOutQuad; duration: 200 } + } } } @@ -168,7 +277,6 @@ Rectangle { { color: "transparent" anchors.fill: parent - Button { anchors.right: parent.right diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 6f03088a4..ba2975453 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -12,6 +12,7 @@ Item { id: webPreview property string pendingPageUrl: "" property bool initialized: false + signal javaScriptMessage(var _level, string _sourceId, var _lineNb, string _content) function setPreviewUrl(url) { if (!initialized) @@ -198,7 +199,6 @@ Item { { setPreviewUrl(text); } - focus: true } @@ -216,7 +216,9 @@ Item { anchors.verticalCenter: parent.verticalCenter width: 21 height: 21 + focus: true } + CheckBox { id: autoReloadOnSave checked: true @@ -227,6 +229,7 @@ Item { text: qsTr("Auto reload on save") } } + focus: true } } } @@ -240,7 +243,7 @@ Item { id: webView experimental.settings.localContentCanAccessRemoteUrls: true onJavaScriptConsoleMessage: { - console.log(sourceID + ":" + lineNumber + ":" + message); + webPreview.javaScriptMessage(level, sourceID, lineNumber, message); } onLoadingChanged: { if (!loading) { diff --git a/mix/qml/img/broom.png b/mix/qml/img/broom.png new file mode 100644 index 000000000..76a9a0e0c Binary files /dev/null and b/mix/qml/img/broom.png differ diff --git a/mix/qml/img/copy.png b/mix/qml/img/copy.png new file mode 100644 index 000000000..72a1e62bc Binary files /dev/null and b/mix/qml/img/copy.png differ diff --git a/mix/qml/qmldir b/mix/qml/qmldir index 0f3fcd88c..73c117941 100644 --- a/mix/qml/qmldir +++ b/mix/qml/qmldir @@ -5,3 +5,4 @@ singleton DebuggerPaneStyle 1.0 DebuggerPaneStyle.qml singleton StateStyle 1.0 StateStyle.qml singleton StatusPaneStyle 1.0 StatusPaneStyle.qml singleton WebPreviewStyle 1.0 WebPreviewStyle.qml +singleton LogsPaneStyle 1.0 LogsPaneStyle.qml diff --git a/mix/res.qrc b/mix/res.qrc index 515c5fadf..e953b3e35 100644 --- a/mix/res.qrc +++ b/mix/res.qrc @@ -111,5 +111,9 @@ qml/img/exit.png qml/img/run.png qml/img/note.png + qml/LogsPane.qml + qml/img/copy.png + qml/img/broom.png + qml/LogsPaneStyle.qml