Gav Wood
10 years ago
14 changed files with 750 additions and 62 deletions
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @author Yann <yann@ethdev.com> |
||||
|
* @date 2015 |
||||
|
* Proxy used to filter a QML TableView. |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
#include "SortFilterProxyModel.h" |
||||
|
#include <QtDebug> |
||||
|
#include <QtQml> |
||||
|
|
||||
|
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<QAbstractItemModel*>(_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<QRegExp::PatternSyntax>(filterSyntax()))); |
||||
|
} |
||||
|
|
||||
|
SortFilterProxyModel::FilterSyntax SortFilterProxyModel::filterSyntax() const |
||||
|
{ |
||||
|
return static_cast<FilterSyntax>(filterRegExp().patternSyntax()); |
||||
|
} |
||||
|
|
||||
|
void SortFilterProxyModel::setFilterSyntax(SortFilterProxyModel::FilterSyntax _syntax) |
||||
|
{ |
||||
|
setFilterRegExp(QRegExp(filterString(), filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(_syntax))); |
||||
|
} |
||||
|
|
||||
|
QJSValue SortFilterProxyModel::get(int _idx) const |
||||
|
{ |
||||
|
QJSEngine *engine = qmlEngine(this); |
||||
|
QJSValue value = engine->newObject(); |
||||
|
if (_idx >= 0 && _idx < count()) |
||||
|
{ |
||||
|
QHash<int, QByteArray> roles = roleNames(); |
||||
|
QHashIterator<int, QByteArray> 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<int, QByteArray> roles = roleNames(); |
||||
|
QHashIterator<int, QByteArray> it(roles); |
||||
|
while (it.hasNext()) |
||||
|
{ |
||||
|
it.next(); |
||||
|
if (it.value() == _role) |
||||
|
return it.key(); |
||||
|
} |
||||
|
return -1; |
||||
|
} |
||||
|
|
||||
|
QHash<int, QByteArray> SortFilterProxyModel::roleNames() const |
||||
|
{ |
||||
|
if (QAbstractItemModel* source = sourceModel()) |
||||
|
return source->roleNames(); |
||||
|
return QHash<int, QByteArray>(); |
||||
|
} |
||||
|
|
||||
|
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<QRegExp::PatternSyntax>(filterSyntax())); |
||||
|
setFilterRegExp(_type); |
||||
|
} |
||||
|
|
||||
|
QString SortFilterProxyModel::filterType() const |
||||
|
{ |
||||
|
return m_filterType.pattern(); |
||||
|
} |
||||
|
|
||||
|
void SortFilterProxyModel::setFilterContent(QString const& _content) |
||||
|
{ |
||||
|
m_filterContent = QRegExp(_content, filterCaseSensitivity(), static_cast<QRegExp::PatternSyntax>(filterSyntax())); |
||||
|
setFilterRegExp(_content); |
||||
|
} |
||||
|
|
||||
|
QString SortFilterProxyModel::filterContent() const |
||||
|
{ |
||||
|
return m_filterContent.pattern(); |
||||
|
} |
||||
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @author Yann <yann@ethdev.com> |
||||
|
* @date 2015 |
||||
|
* Proxy used to filter a QML TableView. |
||||
|
*/ |
||||
|
|
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <QtCore/qsortfilterproxymodel.h> |
||||
|
#include <QtQml/qjsvalue.h> |
||||
|
|
||||
|
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<int, QByteArray> 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"; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
@ -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"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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" |
||||
|
} |
||||
|
} |
||||
|
} |
After Width: | Height: | Size: 501 B |
After Width: | Height: | Size: 342 B |
Loading…
Reference in new issue