From ecc2c609d4dfe210c66c9316cf5b060f9b3a4ff0 Mon Sep 17 00:00:00 2001 From: obscuren Date: Wed, 20 Aug 2014 10:00:02 +0200 Subject: [PATCH] Implemented QML message filtering --- ethereal/assets/ext/filter.js | 23 + ethereal/assets/qml/views/wallet.qml | 138 ++++- ethereal/assets/qml/wallet.qml | 894 ++++++++++++++------------- ethereal/gui.go | 48 +- ethereal/ui_lib.go | 34 +- 5 files changed, 673 insertions(+), 464 deletions(-) create mode 100644 ethereal/assets/ext/filter.js diff --git a/ethereal/assets/ext/filter.js b/ethereal/assets/ext/filter.js new file mode 100644 index 0000000000..20f0b36a30 --- /dev/null +++ b/ethereal/assets/ext/filter.js @@ -0,0 +1,23 @@ +var Filter = function(eth, options) { + this.callbacks = {}; + this.seed = Math.floor(Math.random() * 1000000); + this.eth = eth; + + eth.registerFilter(options, this.seed); +}; + +Filter.prototype.changed = function(callback) { + var cbseed = Math.floor(Math.random() * 1000000); + this.eth.registerFilterCallback(this.seed, cbseed); + + var self = this; + message.connect(function(messages, seed, callbackSeed) { + if(seed == self.seed && callbackSeed == cbseed) { + callback.call(self, messages); + } + }); +}; + +Filter.prototype.uninstall = function() { + eth.uninstallFilter(this.seed) +} diff --git a/ethereal/assets/qml/views/wallet.qml b/ethereal/assets/qml/views/wallet.qml index 22b09640b4..9afb1f89e9 100644 --- a/ethereal/assets/qml/views/wallet.qml +++ b/ethereal/assets/qml/views/wallet.qml @@ -17,43 +17,145 @@ Rectangle { function onReady() { menuItem.secondary = eth.numberToHuman(eth.balanceAt(eth.key().address)) + } + ListModel { + id: denomModel + ListElement { text: "Wei" ; zeros: "" } + ListElement { text: "Ada" ; zeros: "000" } + ListElement { text: "Babbage" ; zeros: "000000" } + ListElement { text: "Shannon" ; zeros: "000000000" } + ListElement { text: "Szabo" ; zeros: "000000000000" } + ListElement { text: "Finney" ; zeros: "000000000000000" } + ListElement { text: "Ether" ; zeros: "000000000000000000" } + ListElement { text: "Einstein" ;zeros: "000000000000000000000" } + ListElement { text: "Douglas" ; zeros: "000000000000000000000000000000000000000000" } } ColumnLayout { spacing: 10 y: 40 - anchors { - left: parent.left - right: parent.right - } + anchors.fill: parent Text { + id: balance text: "Balance: " + eth.numberToHuman(eth.balanceAt(eth.key().address)) font.pixelSize: 24 anchors { horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 20 } } - TableView { - id: txTableView + Rectangle { + id: newTxPane + color: "#ececec" + border.color: "#cccccc" + border.width: 1 anchors { + top: balance.bottom + topMargin: 10 left: parent.left + leftMargin: 5 right: parent.right + rightMargin: 5 + } + height: 100 + + RowLayout { + id: amountFields + spacing: 10 + anchors { + top: parent.top + topMargin: 20 + left: parent.left + leftMargin: 20 + } + + Text { + text: "Ξ " + } + + // There's something off with the row layout where textfields won't listen to the width setting + Rectangle { + width: 50 + height: 20 + TextField { + id: txValue + width: parent.width + placeholderText: "0.00" + } + } + + ComboBox { + id: valueDenom + currentIndex: 6 + model: denomModel + } + } - TableViewColumn{ role: "num" ; title: "#" ; width: 30 } - TableViewColumn{ role: "from" ; title: "From" ; width: 280 } - TableViewColumn{ role: "to" ; title: "To" ; width: 280 } - TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } - - model: ListModel { - id: txModel - Component.onCompleted: { - var messages = JSON.parse(eth.messages({latest: -1, from: "e6716f9544a56c530d868e4bfbacb172315bdead"})) - for(var i = 0; i < messages.length; i++) { - var message = messages[i]; - this.insert(0, {num: i, from: message.from, to: message.to, value: eth.numberToHuman(message.value)}) + + RowLayout { + id: toFields + spacing: 10 + anchors { + top: amountFields.bottom + topMargin: 5 + left: parent.left + leftMargin: 20 + } + + Text { + text: "To" + } + + Rectangle { + width: 200 + height: 20 + TextField { + id: txTo + width: parent.width + placeholderText: "Address or name" + } + } + + Button { + text: "Send" + onClicked: { + var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros; + var gasPrice = "10000000000000" + var res = eth.transact(eth.key().privateKey, txTo.text, value, "500", gasPrice, "") + console.log(res) + } + } + } + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: newTxPane.bottom + topMargin: 10 + bottom: parent.bottom + } + TableView { + id: txTableView + anchors.fill : parent + TableViewColumn{ role: "num" ; title: "#" ; width: 30 } + TableViewColumn{ role: "from" ; title: "From" ; width: 280 } + TableViewColumn{ role: "to" ; title: "To" ; width: 280 } + TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } + + model: ListModel { + id: txModel + Component.onCompleted: { + var messages = JSON.parse(eth.messages({latest: -1, from: "e6716f9544a56c530d868e4bfbacb172315bdead"})) + for(var i = 0; i < messages.length; i++) { + var message = messages[i]; + this.insert(0, {num: i, from: message.from, to: message.to, value: eth.numberToHuman(message.value)}) + } } } } diff --git a/ethereal/assets/qml/wallet.qml b/ethereal/assets/qml/wallet.qml index 89126f6008..122ee049e5 100644 --- a/ethereal/assets/qml/wallet.qml +++ b/ethereal/assets/qml/wallet.qml @@ -6,17 +6,28 @@ import QtQuick.Window 2.1; import QtQuick.Controls.Styles 1.1 import Ethereum 1.0 +import "../ext/filter.js" as Eth ApplicationWindow { id: root property alias miningButtonText: miningButton.text + width: 900 height: 600 minimumHeight: 300 - title: "Ethereal" + title: "Ether browser" + + // This signal is used by the filter API. The filter API connects using this signal handler from + // the different QML files and plugins. + signal message(var callback, int seed, int seedCallback); + function invokeFilterCallback(data, receiverSeed, callbackSeed) { + var messages = JSON.parse(data) + // Signal handler + message(data, receiverSeed, callbackSeed); + } TextField { id: copyElementHax @@ -31,17 +42,17 @@ ApplicationWindow { // Takes care of loading all default plugins Component.onCompleted: { - var walletView = addPlugin("./views/wallet.qml", {section: "ethereum"}) - - var historyView = addPlugin("./views/history.qml", {section: "legacy"}) - var newTxView = addPlugin("./views/transaction.qml", {section: "legacy"}) - var chainView = addPlugin("./views/chain.qml", {section: "legacy"}) - var infoView = addPlugin("./views/info.qml", {section: "legacy"}) - var pendingTxView = addPlugin("./views/pending_tx.qml", {section: "legacy"}) - var pendingTxView = addPlugin("./views/javascript.qml", {section: "legacy"}) + var walletView = addPlugin("./views/wallet.qml", {noAdd: true, section: "ethereum", active: true}) + var historyView = addPlugin("./views/history.qml", {noAdd: true, section: "legacy"}) + var newTxView = addPlugin("./views/transaction.qml", {noAdd: true, section: "legacy"}) + var chainView = addPlugin("./views/chain.qml", {noAdd: true, section: "legacy"}) + var infoView = addPlugin("./views/info.qml", {noAdd: true, section: "legacy"}) + var pendingTxView = addPlugin("./views/pending_tx.qml", {noAdd: true, section: "legacy"}) + var pendingTxView = addPlugin("./views/javascript.qml", {noAdd: true, section: "legacy"}) // Call the ready handler gui.done() + } function addPlugin(path, options) { @@ -50,10 +61,20 @@ ApplicationWindow { if(component.status == Component.Error) { console.debug("Error:"+ component.errorString()); } + return } - return mainSplit.addComponent(component, options) + var views = mainSplit.addComponent(component, options) + views.menuItem.path = path + + mainSplit.views.push(views); + + if(!options.noAdd) { + gui.addPlugin(path) + } + + return views.view } MenuBar { @@ -76,7 +97,7 @@ ApplicationWindow { text: "Add plugin" onTriggered: { generalFileDialog.show(true, function(path) { - addPlugin(path, {canClose: true}) + addPlugin(path, {canClose: true, section: "apps"}) }) } } @@ -268,454 +289,441 @@ ApplicationWindow { function addComponent(component, options) { var view = mainView.createView(component, options) + view.visible = false + view.anchors.fill = mainView - if(!view.hasOwnProperty("iconFile")) { + if( !view.hasOwnProperty("iconFile") ) { console.log("Could not load plugin. Property 'iconFile' not found on view."); return; } var menuItem = menu.createMenuItem(view.iconFile, view, options); - if(view.hasOwnProperty("menuItem")) { + if( view.hasOwnProperty("menuItem") ) { view.menuItem = menuItem; } - mainSplit.views.push({view: view, menuItem: menuItem}); - if(view.hasOwnProperty("onReady")) { + if( view.hasOwnProperty("onReady") ) { view.onReady.call(view) } - return view - } - - /********************* - * Main menu. - ********************/ - Rectangle { - id: menu - Layout.minimumWidth: 180 - Layout.maximumWidth: 180 - anchors.top: parent.top - color: "#ececec" - - Component { - id: menuItemTemplate - Rectangle { - id: menuItem - property var view; - - property alias title: label.text - property alias icon: icon.source - property alias secondary: secondary.text - - width: 180 - height: 28 - border.color: "#00000000" - border.width: 1 - radius: 5 - color: "#00000000" - - anchors { - left: parent.left - leftMargin: 4 - } - - Image { - id: icon - height: 20 - width: 20 - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - } - - Text { - id: label - anchors { - left: icon.right - verticalCenter: parent.verticalCenter - leftMargin: 3 - } - - //font.bold: true - color: "#0D0A01" - font.pixelSize: 12 - } - - Text { - id: secondary - anchors { - right: parent.right - rightMargin: 8 - verticalCenter: parent.verticalCenter - } - color: "#AEADBE" - font.pixelSize: 12 - } - - MouseArea { - anchors.fill: parent - onClicked: { - mainSplit.setView(view, menuItem) - } - } - } + if( options.active ) { + setView(view, menuItem) } - function createMenuItem(icon, view, options) { - if(options === undefined) { - options = {}; - } - - var section; - switch(options.section) { - case "ethereum": - section = menuDefault; - break; - case "legacy": - section = menuLegacy; - break; - default: - section = menuApps; - break; - } - - var comp = menuItemTemplate.createObject(section) - - comp.view = view - comp.title = view.title - comp.icon = view.iconFile - /* - if(view.secondary !== undefined) { - comp.secondary = view.secondary - } - */ - - return comp - - /* - if(options.canClose) { - //comp.closeButton.visible = options.canClose - } - */ - } - ColumnLayout { - id: menuColumn - y: 10 - width: parent.width - anchors.left: parent.left - anchors.right: parent.right - spacing: 3 - - Text { - text: "ETHEREUM" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuDefault - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - Text { - text: "LEGACY" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - ColumnLayout { - id: menuLegacy - spacing: 3 - anchors { - left: parent.left - right: parent.right - } - } - - Text { - text: "APPS" - font.bold: true - anchors { - left: parent.left - leftMargin: 5 - } - color: "#888888" - } - - /* - Rectangle { - width: 180 - height: 28 - border.color: "#CCCCCC" - border.width: 1 - radius: 5 - color: "#FFFFFF" - - anchors { - left: parent.left - leftMargin: 4 - } - - Image { - id: icon - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - } - source: "../pick.png" - } - - Text { - anchors { - left: icon.right - verticalCenter: parent.verticalCenter - } - - text: "Wallet" - font.bold: true - color: "#0D0A01" - } - - Text { - anchors { - right: parent.right - rightMargin: 8 - verticalCenter: parent.verticalCenter - } - color: "#AEADBE" - text: "12e15 Ξ" - font.pixelSize: 12 - } - } - */ - } + return {view: view, menuItem: menuItem} } /********************* - * Main view + * Main menu. ********************/ - Rectangle { - id: mainView - color: "#00000000" - - anchors.right: parent.right - anchors.left: menu.right - anchors.bottom: parent.bottom - anchors.top: parent.top - - function createView(component) { - var view = component.createObject(mainView) - - return view; - } - } - - - } - - - /****************** - * Dialogs - *****************/ - FileDialog { - id: generalFileDialog - property var callback; - onAccepted: { - var path = this.fileUrl.toString(); - callback.call(this, path); - } - - function show(selectExisting, callback) { - generalFileDialog.callback = callback; - generalFileDialog.selectExisting = selectExisting; - - this.open(); - } - } - - - /****************** - * Wallet functions - *****************/ - function importApp(path) { - var ext = path.split('.').pop() - if(ext == "html" || ext == "htm") { - eth.openHtml(path) - }else if(ext == "qml"){ - eth.openQml(path) - } - } - - function setWalletValue(value) { - walletValueLabel.text = value - } - - function loadPlugin(name) { - console.log("Loading plugin" + name) - mainView.addPlugin(name) - } - - function setPeers(text) { - peerLabel.text = text - } - - function addPeer(peer) { - // We could just append the whole peer object but it cries if you try to alter them - peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) - } - - function resetPeers(){ - peerModel.clear() - } - - function timeAgo(unixTs){ - var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 - return (lapsed + " seconds ago") - } - - function convertToPretty(unixTs){ - var a = new Date(unixTs*1000); - var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; - var year = a.getFullYear(); - var month = months[a.getMonth()]; - var date = a.getDate(); - var hour = a.getHours(); - var min = a.getMinutes(); - var sec = a.getSeconds(); - var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; - return time; - } - - /********************** - * Windows - *********************/ - Window { - id: peerWindow - //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint - height: 200 - width: 700 - Rectangle { - anchors.fill: parent - property var peerModel: ListModel { - id: peerModel - } - TableView { - anchors.fill: parent - id: peerTable - model: peerModel - TableViewColumn{width: 100; role: "ip" ; title: "IP" } - TableViewColumn{width: 60; role: "port" ; title: "Port" } - TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } - TableViewColumn{width: 100; role: "latency"; title: "Latency" } - TableViewColumn{width: 260; role: "version" ; title: "Version" } - } - } - } - - Window { - id: aboutWin - visible: false - title: "About" - minimumWidth: 350 - maximumWidth: 350 - maximumHeight: 200 - minimumHeight: 200 - - Image { - id: aboutIcon - height: 150 - width: 150 - fillMode: Image.PreserveAspectFit - smooth: true - source: "../facet.png" - x: 10 - y: 10 - } - - Text { - anchors.left: aboutIcon.right - anchors.leftMargin: 10 - font.pointSize: 12 - text: "

Ethereal - Adrastea


Development

Jeffrey Wilcke
Maran Hidskes
Viktor Trón
" - } - } - - Window { - id: txImportDialog - minimumWidth: 270 - maximumWidth: 270 - maximumHeight: 50 - minimumHeight: 50 - TextField { - id: txImportField - width: 170 - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - onAccepted: { - } - } - Button { - anchors.left: txImportField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Import" - onClicked: { - eth.importTx(txImportField.text) - txImportField.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } - - Window { - id: addPeerWin - visible: false - minimumWidth: 230 - maximumWidth: 230 - maximumHeight: 50 - minimumHeight: 50 - - TextField { - id: addrField - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - anchors.leftMargin: 10 - placeholderText: "address:port" - onAccepted: { - eth.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Button { - anchors.left: addrField.right - anchors.verticalCenter: parent.verticalCenter - anchors.leftMargin: 5 - text: "Add" - onClicked: { - eth.connectToPeer(addrField.text) - addPeerWin.visible = false - } - } - Component.onCompleted: { - addrField.focus = true - } - } -} + Rectangle { + id: menu + Layout.minimumWidth: 180 + Layout.maximumWidth: 180 + anchors.top: parent.top + color: "#ececec" + + Component { + id: menuItemTemplate + Rectangle { + id: menuItem + property var view; + property var path; + + property alias title: label.text + property alias icon: icon.source + property alias secondary: secondary.text + + width: 180 + height: 28 + border.color: "#00000000" + border.width: 1 + radius: 5 + color: "#00000000" + + anchors { + left: parent.left + leftMargin: 4 + } + + MouseArea { + anchors.fill: parent + onClicked: { + mainSplit.setView(view, menuItem) + } + } + + Image { + id: icon + height: 20 + width: 20 + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: 3 + } + MouseArea { + anchors.fill: parent + onClicked: { + menuItem.closeApp() + } + } + } + + Text { + id: label + anchors { + left: icon.right + verticalCenter: parent.verticalCenter + leftMargin: 3 + } + + color: "#0D0A01" + font.pixelSize: 12 + } + + Text { + id: secondary + anchors { + right: parent.right + rightMargin: 8 + verticalCenter: parent.verticalCenter + } + color: "#AEADBE" + font.pixelSize: 12 + } + + + function closeApp() { + if(this.view.hasOwnProperty("onDestroy")) { + this.view.onDestroy.call(this.view) + } + + this.view.destroy() + this.destroy() + gui.removePlugin(this.path) + } + } + } + + function createMenuItem(icon, view, options) { + if(options === undefined) { + options = {}; + } + + var section; + switch(options.section) { + case "ethereum": + section = menuDefault; + break; + case "legacy": + section = menuLegacy; + break; + default: + section = menuApps; + break; + } + + var comp = menuItemTemplate.createObject(section) + + comp.view = view + comp.title = view.title + comp.icon = view.iconFile + /* + if(view.secondary !== undefined) { + comp.secondary = view.secondary + } + */ + + return comp + + /* + if(options.canClose) { + //comp.closeButton.visible = options.canClose + } + */ + } + + ColumnLayout { + id: menuColumn + y: 10 + width: parent.width + anchors.left: parent.left + anchors.right: parent.right + spacing: 3 + + Text { + text: "ETHEREUM" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuDefault + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + + Text { + text: "APPS" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuApps + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + Text { + text: "DEBUG" + font.bold: true + anchors { + left: parent.left + leftMargin: 5 + } + color: "#888888" + } + + ColumnLayout { + id: menuLegacy + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + } + } + + /********************* + * Main view + ********************/ + Rectangle { + id: mainView + color: "#00000000" + + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + + function createView(component) { + var view = component.createObject(mainView) + + return view; + } + } + + + } + + + /****************** + * Dialogs + *****************/ + FileDialog { + id: generalFileDialog + property var callback; + onAccepted: { + var path = this.fileUrl.toString(); + callback.call(this, path); + } + + function show(selectExisting, callback) { + generalFileDialog.callback = callback; + generalFileDialog.selectExisting = selectExisting; + + this.open(); + } + } + + + /****************** + * Wallet functions + *****************/ + function importApp(path) { + var ext = path.split('.').pop() + if(ext == "html" || ext == "htm") { + eth.openHtml(path) + }else if(ext == "qml"){ + addPlugin(path, {canClose: true, section: "apps"}) + } + } + + + function setWalletValue(value) { + walletValueLabel.text = value + } + + function loadPlugin(name) { + console.log("Loading plugin" + name) + var view = mainView.addPlugin(name) + } + + function setPeers(text) { + peerLabel.text = text + } + + function addPeer(peer) { + // We could just append the whole peer object but it cries if you try to alter them + peerModel.append({ip: peer.ip, port: peer.port, lastResponse:timeAgo(peer.lastSend), latency: peer.latency, version: peer.version}) + } + + function resetPeers(){ + peerModel.clear() + } + + function timeAgo(unixTs){ + var lapsed = (Date.now() - new Date(unixTs*1000)) / 1000 + return (lapsed + " seconds ago") + } + + function convertToPretty(unixTs){ + var a = new Date(unixTs*1000); + var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + var year = a.getFullYear(); + var month = months[a.getMonth()]; + var date = a.getDate(); + var hour = a.getHours(); + var min = a.getMinutes(); + var sec = a.getSeconds(); + var time = date+' '+month+' '+year+' '+hour+':'+min+':'+sec ; + return time; + } + + /********************** + * Windows + *********************/ + Window { + id: peerWindow + //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint + height: 200 + width: 700 + Rectangle { + anchors.fill: parent + property var peerModel: ListModel { + id: peerModel + } + TableView { + anchors.fill: parent + id: peerTable + model: peerModel + TableViewColumn{width: 100; role: "ip" ; title: "IP" } + TableViewColumn{width: 60; role: "port" ; title: "Port" } + TableViewColumn{width: 140; role: "lastResponse"; title: "Last event" } + TableViewColumn{width: 100; role: "latency"; title: "Latency" } + TableViewColumn{width: 260; role: "version" ; title: "Version" } + } + } + } + + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 350 + maximumWidth: 350 + maximumHeight: 200 + minimumHeight: 200 + + Image { + id: aboutIcon + height: 150 + width: 150 + fillMode: Image.PreserveAspectFit + smooth: true + source: "../facet.png" + x: 10 + y: 10 + } + + Text { + anchors.left: aboutIcon.right + anchors.leftMargin: 10 + font.pointSize: 12 + text: "

Ethereal - Adrastea


Development

Jeffrey Wilcke
Maran Hidskes
Viktor Trón
" + } + } + + Window { + id: txImportDialog + minimumWidth: 270 + maximumWidth: 270 + maximumHeight: 50 + minimumHeight: 50 + TextField { + id: txImportField + width: 170 + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + onAccepted: { + } + } + Button { + anchors.left: txImportField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Import" + onClicked: { + eth.importTx(txImportField.text) + txImportField.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + + Window { + id: addPeerWin + visible: false + minimumWidth: 230 + maximumWidth: 230 + maximumHeight: 50 + minimumHeight: 50 + + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.leftMargin: 10 + placeholderText: "address:port" + onAccepted: { + eth.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + Button { + anchors.left: addrField.right + anchors.verticalCenter: parent.verticalCenter + anchors.leftMargin: 5 + text: "Add" + onClicked: { + eth.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + } diff --git a/ethereal/gui.go b/ethereal/gui.go index 1765c3fb26..3f989fe511 100644 --- a/ethereal/gui.go +++ b/ethereal/gui.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/json" "fmt" "math/big" "os" @@ -24,6 +25,11 @@ import ( var logger = ethlog.NewLogger("GUI") +type plugin struct { + Name string `json:"name"` + Path string `json:"path"` +} + type Gui struct { // The main application window win *qml.Window @@ -48,6 +54,8 @@ type Gui struct { clientIdentity *ethwire.SimpleClientIdentity config *ethutil.ConfigManager + plugins map[string]plugin + miner *ethminer.Miner } @@ -59,8 +67,16 @@ func NewWindow(ethereum *eth.Ethereum, config *ethutil.ConfigManager, clientIden } pipe := ethpipe.NewJSPipe(ethereum) + gui := &Gui{eth: ethereum, txDb: db, pipe: pipe, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config, plugins: make(map[string]plugin)} + data, err := ethutil.ReadAllFile(ethutil.Config.ExecPath + "/plugins.json") + if err != nil { + fmt.Println(err) + } + fmt.Println(string(data)) - return &Gui{eth: ethereum, txDb: db, pipe: pipe, logLevel: ethlog.LogLevel(logLevel), Session: session, open: false, clientIdentity: clientIdentity, config: config} + json.Unmarshal([]byte(data), &gui.plugins) + + return gui } func (gui *Gui) Start(assetPath string) { @@ -193,6 +209,7 @@ func (self *Gui) DumpState(hash, path string) { // The done handler will be called by QML when all views have been loaded func (gui *Gui) Done() { gui.qmlDone = true + } func (gui *Gui) ImportKey(filePath string) { @@ -375,6 +392,10 @@ func (gui *Gui) update() { gui.readPreviousTransactions() }() + for _, plugin := range gui.plugins { + gui.win.Root().Call("addPlugin", plugin.Path, "") + } + var ( blockChan = make(chan ethreact.Event, 100) txChan = make(chan ethreact.Event, 100) @@ -504,7 +525,16 @@ func (gui *Gui) address() []byte { } func (gui *Gui) Transact(recipient, value, gas, gasPrice, d string) (*ethpipe.JSReceipt, error) { - data := ethutil.Bytes2Hex(utils.FormatTransactionData(d)) + var data string + if len(recipient) == 0 { + code, err := ethutil.Compile(d, false) + if err != nil { + return nil, err + } + data = ethutil.Bytes2Hex(code) + } else { + data = ethutil.Bytes2Hex(utils.FormatTransactionData(d)) + } return gui.pipe.Transact(gui.privateKey(), recipient, value, gas, gasPrice, data) } @@ -528,6 +558,20 @@ func (gui *Gui) GetLogLevel() ethlog.LogLevel { return gui.logLevel } +func (self *Gui) AddPlugin(pluginPath string) { + self.plugins[pluginPath] = plugin{Name: "SomeName", Path: pluginPath} + + json, _ := json.MarshalIndent(self.plugins, "", " ") + ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json) +} + +func (self *Gui) RemovePlugin(pluginPath string) { + delete(self.plugins, pluginPath) + + json, _ := json.MarshalIndent(self.plugins, "", " ") + ethutil.WriteFile(ethutil.Config.ExecPath+"/plugins.json", json) +} + // this extra function needed to give int typecast value to gui widget // that sets initial loglevel to default func (gui *Gui) GetLogLevelInt() int { diff --git a/ethereal/ui_lib.go b/ethereal/ui_lib.go index ade9bf3815..7b2627c49c 100644 --- a/ethereal/ui_lib.go +++ b/ethereal/ui_lib.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum/eth-go/ethchain" "github.com/ethereum/eth-go/ethcrypto" "github.com/ethereum/eth-go/ethpipe" + "github.com/ethereum/eth-go/ethstate" "github.com/ethereum/eth-go/ethutil" "github.com/ethereum/go-ethereum/javascript" "gopkg.in/qml.v1" @@ -34,10 +35,13 @@ type UiLib struct { DbWindow *DebuggerWindow jsEngine *javascript.JSRE + + filterCallbacks map[int][]int + filters map[int]*GuiFilter } func NewUiLib(engine *qml.Engine, eth *eth.Ethereum, assetPath string) *UiLib { - return &UiLib{JSPipe: ethpipe.NewJSPipe(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth)} + return &UiLib{JSPipe: ethpipe.NewJSPipe(eth), engine: engine, eth: eth, assetPath: assetPath, jsEngine: javascript.NewJSRE(eth), filterCallbacks: make(map[int][]int), filters: make(map[int]*GuiFilter)} } func (self *UiLib) LookupDomain(domain string) string { @@ -155,3 +159,31 @@ func (self *UiLib) StartDebugger() { dbWindow.Show() } + +func (self *UiLib) RegisterFilter(object map[string]interface{}, seed int) { + filter := &GuiFilter{ethpipe.NewJSFilterFromMap(object, self.eth), seed} + self.filters[seed] = filter + + filter.MessageCallback = func(messages ethstate.Messages) { + for _, callbackSeed := range self.filterCallbacks[seed] { + self.win.Root().Call("invokeFilterCallback", filter.MessagesToJson(messages), seed, callbackSeed) + } + } +} + +func (self *UiLib) RegisterFilterCallback(seed, cbSeed int) { + self.filterCallbacks[seed] = append(self.filterCallbacks[seed], cbSeed) +} + +func (self *UiLib) UninstallFilter(seed int) { + filter := self.filters[seed] + if filter != nil { + filter.Uninstall() + delete(self.filters, seed) + } +} + +type GuiFilter struct { + *ethpipe.JSFilter + seed int +}