diff options
author | Felix Lange <fjl@twurst.com> | 2014-10-23 21:01:27 +0800 |
---|---|---|
committer | Felix Lange <fjl@twurst.com> | 2014-10-23 21:01:27 +0800 |
commit | 69baa465ea69ae60eed802445cf0132b9eb69934 (patch) | |
tree | b09da7582b5c4850d4db13aee808f2fef2f97de0 /cmd/mist/assets/qml | |
parent | 50fd46924900869e7210217c6a07979b544991c8 (diff) | |
parent | feef194829b07570e91873ed5d1e8cc51e8fa430 (diff) | |
download | go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.gz go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.bz2 go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.lz go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.xz go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.tar.zst go-tangerine-69baa465ea69ae60eed802445cf0132b9eb69934.zip |
Merge eth-go repository into go-ethereum
mist, etheruem have been moved to cmd/
Diffstat (limited to 'cmd/mist/assets/qml')
-rw-r--r-- | cmd/mist/assets/qml/QmlApp.qml | 22 | ||||
-rw-r--r-- | cmd/mist/assets/qml/first_run.qml | 155 | ||||
-rw-r--r-- | cmd/mist/assets/qml/main.qml | 908 | ||||
-rw-r--r-- | cmd/mist/assets/qml/muted.qml | 74 | ||||
-rw-r--r-- | cmd/mist/assets/qml/test_app.qml | 70 | ||||
-rw-r--r-- | cmd/mist/assets/qml/transactions.qml | 9 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/chain.qml | 253 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/history.qml | 51 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/info.qml | 196 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/jeffcoin/jeff.png | bin | 0 -> 84076 bytes | |||
-rw-r--r-- | cmd/mist/assets/qml/views/jeffcoin/jeffcoin.qml | 190 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/pending_tx.qml | 44 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/transaction.qml | 216 | ||||
-rw-r--r-- | cmd/mist/assets/qml/views/wallet.qml | 173 | ||||
-rw-r--r-- | cmd/mist/assets/qml/webapp.qml | 408 |
15 files changed, 2769 insertions, 0 deletions
diff --git a/cmd/mist/assets/qml/QmlApp.qml b/cmd/mist/assets/qml/QmlApp.qml new file mode 100644 index 000000000..f5c503f4c --- /dev/null +++ b/cmd/mist/assets/qml/QmlApp.qml @@ -0,0 +1,22 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import Ethereum 1.0 + +ApplicationWindow { + minimumWidth: 500 + maximumWidth: 500 + maximumHeight: 400 + minimumHeight: 400 + + function onNewBlockCb(block) { + console.log("Please overwrite onNewBlock(block):", block) + } + function onObjectChangeCb(stateObject) { + console.log("Please overwrite onObjectChangeCb(object)", stateObject) + } + function onStorageChangeCb(storageObject) { + var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":"); + console.log("Please overwrite onStorageChangeCb(object)", ev) + } +} diff --git a/cmd/mist/assets/qml/first_run.qml b/cmd/mist/assets/qml/first_run.qml new file mode 100644 index 000000000..0b1dac4c6 --- /dev/null +++ b/cmd/mist/assets/qml/first_run.qml @@ -0,0 +1,155 @@ +import QtQuick 2.0 +import Ethereum 1.0 + +// Which ones do we actually need? +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 + +ApplicationWindow { + id: wizardRoot + width: 500 + height: 400 + title: "Ethereal first run setup" + + Column { + spacing: 5 + anchors.leftMargin: 10 + anchors.left: parent.left + + Text { + visible: true + text: "<h2>Ethereal setup</h2>" + } + + Column { + id: restoreColumn + spacing: 5 + Text { + visible: true + font.pointSize: 14 + text: "Restore your Ethereum account" + id: restoreLabel + } + + TextField { + id: txPrivKey + width: 480 + placeholderText: "Private key or mnemonic words" + focus: true + onTextChanged: { + if(this.text.length == 64){ + detailLabel.text = "Private (hex) key detected." + actionButton.enabled = true + } + else if(this.text.split(" ").length == 24){ + detailLabel.text = "Mnemonic key detected." + actionButton.enabled = true + }else{ + detailLabel.text = "" + actionButton.enabled = false + } + } + } + Row { + spacing: 10 + Button { + id: actionButton + text: "Restore" + enabled: false + onClicked: { + var success = lib.importAndSetPrivKey(txPrivKey.text) + if(success){ + importedDetails.visible = true + restoreColumn.visible = false + newKey.visible = false + wizardRoot.height = 120 + } + } + } + Text { + id: detailLabel + font.pointSize: 12 + anchors.topMargin: 10 + } + } + } + Column { + id: importedDetails + visible: false + Text { + text: "<b>Your account has been imported. Please close the application and restart it again to let the changes take effect.</b>" + wrapMode: Text.WordWrap + width: 460 + } + } + Column { + spacing: 5 + id: newDetailsColumn + visible: false + Text { + font.pointSize: 14 + text: "Your account details" + } + Label { + text: "Address" + } + TextField { + id: addressInput + readOnly:true + width: 480 + } + Label { + text: "Private key" + } + TextField { + id: privkeyInput + readOnly:true + width: 480 + } + Label { + text: "Mnemonic words" + } + TextField { + id: mnemonicInput + readOnly:true + width: 480 + } + Label { + text: "<b>A new account has been created. Please take the time to write down the <i>24 words</i>. You can use those to restore your account at a later date.</b>" + wrapMode: Text.WordWrap + width: 480 + } + Label { + text: "Please restart the application once you have completed the steps above." + wrapMode: Text.WordWrap + width: 480 + } + } + + } + Button { + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.rightMargin: 10 + anchors.bottomMargin: 10 + id: newKey + text: "I don't have an account yet" + onClicked: { + var res = lib.createAndSetPrivKey() + mnemonicInput.text = res[0] + addressInput.text = res[1] + privkeyInput.text = res[2] + + // Hide restore + restoreColumn.visible = false + + // Show new details + newDetailsColumn.visible = true + newKey.visible = false + } + } +} diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml new file mode 100644 index 000000000..cfd227b49 --- /dev/null +++ b/cmd/mist/assets/qml/main.qml @@ -0,0 +1,908 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +import "../ext/filter.js" as Eth +import "../ext/http.js" as Http + +ApplicationWindow { + id: root + + property alias miningButtonText: miningButton.text + property var ethx : Eth.ethx + property var browser + + width: 1200 + height: 820 + minimumHeight: 300 + + title: "Mist" + + // This signal is used by the filter API. The filter API connects using this signal handler from + // the different QML files and plugins. + signal messages(var messages, int id); + function invokeFilterCallback(data, receiverSeed) { + //var messages = JSON.parse(data) + // Signal handler + messages(data, receiverSeed); + root.browser.view.messages(data, receiverSeed); + } + + TextField { + id: copyElementHax + visible: false + } + + function copyToClipboard(text) { + copyElementHax.text = text + copyElementHax.selectAll() + copyElementHax.copy() + } + + // Takes care of loading all default plugins + Component.onCompleted: { + var wallet = addPlugin("./views/wallet.qml", {noAdd: true, close: false, section: "ethereum", active: true}); + var browser = addPlugin("./webapp.qml", {noAdd: true, close: false, section: "ethereum", active: true}); + root.browser = browser; + + addPlugin("./views/transaction.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/chain.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/pending_tx.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/info.qml", {noAdd: true, close: false, section: "legacy"}); + + addPlugin("./views/jeffcoin/jeffcoin.qml", {noAdd: true, close: false, section: "apps"}) + + mainSplit.setView(wallet.view, wallet.menuItem); + + // Call the ready handler + gui.done(); + } + + function addViews(view, path, options) { + var views = mainSplit.addComponent(view, options) + views.menuItem.path = path + + mainSplit.views.push(views); + + if(!options.noAdd) { + gui.addPlugin(path) + } + + return views + } + + function addPlugin(path, options) { + try { + if(typeof(path) === "string" && /^https?/.test(path)) { + console.log('load http') + Http.request(path, function(o) { + if(o.status === 200) { + var view = Qt.createQmlObject(o.responseText, mainView, path) + addViews(view, path, options) + } + }) + + return + } + + var component = Qt.createComponent(path); + if(component.status != Component.Ready) { + if(component.status == Component.Error) { + ethx.note("error: ", component.errorString()); + } + + return + } + + var view = mainView.createView(component, options) + var views = addViews(view, path, options) + + return views + } catch(e) { + ethx.note(e) + } + } + + menuBar: MenuBar { + Menu { + title: "File" + MenuItem { + text: "Import App" + shortcut: "Ctrl+o" + onTriggered: { + generalFileDialog.show(true, importApp) + } + } + + /* + MenuItem { + text: "Browser" + onTriggered: eth.openBrowser() + } + */ + + MenuItem { + text: "Add plugin" + onTriggered: { + generalFileDialog.show(true, function(path) { + addPlugin(path, {close: true, section: "apps"}) + }) + } + } + + MenuSeparator {} + + MenuItem { + text: "Import key" + shortcut: "Ctrl+i" + onTriggered: { + generalFileDialog.show(true, function(path) { + gui.importKey(path) + }) + } + } + + MenuItem { + text: "Export keys" + shortcut: "Ctrl+e" + onTriggered: { + generalFileDialog.show(false, function(path) { + }) + } + } + + } + + Menu { + title: "Developer" + MenuItem { + iconSource: "../icecream.png" + text: "Debugger" + shortcut: "Ctrl+d" + onTriggered: eth.startDebugger() + } + + MenuItem { + text: "Import Tx" + onTriggered: { + txImportDialog.visible = true + } + } + + MenuItem { + text: "Run JS file" + onTriggered: { + generalFileDialog.show(true, function(path) { + eth.evalJavascriptFile(path) + }) + } + } + + MenuItem { + text: "Dump state" + onTriggered: { + generalFileDialog.show(false, function(path) { + // Empty hash for latest + gui.dumpState("", path) + }) + } + } + + MenuSeparator {} + + /* + MenuItem { + id: miningSpeed + text: "Mining: Turbo" + onTriggered: { + gui.toggleTurboMining() + if(text == "Mining: Turbo") { + text = "Mining: Normal"; + } else { + text = "Mining: Turbo"; + } + } + } + */ + } + + Menu { + title: "Network" + MenuItem { + text: "Add Peer" + shortcut: "Ctrl+p" + onTriggered: { + addPeerWin.visible = true + } + } + MenuItem { + text: "Show Peers" + shortcut: "Ctrl+e" + onTriggered: { + peerWindow.visible = true + } + } + } + + Menu { + title: "Help" + MenuItem { + text: "About" + onTriggered: { + aboutWin.visible = true + } + } + } + + Menu { + title: "GLOBAL SHORTCUTS" + visible: false + MenuItem { + visible: false + shortcut: "Ctrl+l" + onTriggered: { + url.focus = true + } + } + } + } + + statusBar: StatusBar { + height: 32 + id: statusBar + RowLayout { + Button { + id: miningButton + text: "Start Mining" + onClicked: { + gui.toggleMining() + } + } + + RowLayout { + Label { + id: walletValueLabel + + font.pixelSize: 10 + styleColor: "#797979" + } + } + } + + Label { + y: 6 + objectName: "miningLabel" + visible: true + font.pixelSize: 10 + anchors.right: lastBlockLabel.left + anchors.rightMargin: 5 + } + + Label { + y: 6 + id: lastBlockLabel + objectName: "lastBlockLabel" + visible: true + text: "" + font.pixelSize: 10 + anchors.right: peerGroup.left + anchors.rightMargin: 5 + } + + ProgressBar { + id: downloadIndicator + value: 0 + objectName: "downloadIndicator" + y: 3 + x: statusBar.width / 2 - this.width / 2 + width: 160 + } + + Label { + objectName: "downloadLabel" + y: 7 + anchors.left: downloadIndicator.right + anchors.leftMargin: 5 + font.pixelSize: 10 + text: "0 / 0" + } + + + RowLayout { + id: peerGroup + y: 7 + anchors.right: parent.right + MouseArea { + onDoubleClicked: peerWindow.visible = true + anchors.fill: parent + } + + Label { + id: peerLabel + font.pixelSize: 8 + text: "0 / 0" + } + Image { + id: peerImage + width: 10; height: 10 + source: "../network.png" + } + } + } + + + property var blockModel: ListModel { + id: blockModel + } + + SplitView { + property var views: []; + + id: mainSplit + anchors.fill: parent + resizing: false + + function setView(view, menu) { + for(var i = 0; i < views.length; i++) { + views[i].view.visible = false + views[i].menuItem.setSelection(false) + } + view.visible = true + + //menu.border.color = "#CCCCCC" + //menu.color = "#FFFFFFFF" + menu.setSelection(true) + } + + function addComponent(view, options) { + view.visible = false + view.anchors.fill = mainView + + var menuItem = menu.createMenuItem(view, options); + if( view.hasOwnProperty("menuItem") ) { + view.menuItem = menuItem; + } + + if( view.hasOwnProperty("onReady") ) { + view.onReady.call(view) + } + + if( options.active ) { + setView(view, menuItem) + } + + + return {view: view, menuItem: menuItem} + } + + /********************* + * Main menu. + ********************/ + Rectangle { + id: menu + Layout.minimumWidth: 210 + Layout.maximumWidth: 210 + anchors.top: parent.top + color: "#ececec" + + Component { + id: menuItemTemplate + Rectangle { + id: menuItem + property var view; + property var path; + property var closable; + + property alias title: label.text + property alias icon: icon.source + property alias secondaryTitle: secondary.text + function setSelection(on) { + sel.visible = on + } + + width: 206 + height: 28 + color: "#00000000" + + anchors { + left: parent.left + leftMargin: 4 + } + + Rectangle { + id: sel + visible: false + anchors.fill: parent + color: "#00000000" + Rectangle { + id: r + anchors.fill: parent + border.color: "#CCCCCC" + border.width: 1 + radius: 5 + color: "#FFFFFFFF" + } + Rectangle { + anchors { + top: r.top + bottom: r.bottom + right: r.right + } + width: 10 + color: "#FFFFFFFF" + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: 1 + color: "#CCCCCC" + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 1 + color: "#CCCCCC" + } + } + } + + 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.closable) { return; } + + if(this.view.hasOwnProperty("onDestroy")) { + this.view.onDestroy.call(this.view) + } + + this.view.destroy() + this.destroy() + gui.removePlugin(this.path) + } + } + } + + function createMenuItem(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 + + if(view.hasOwnProperty("iconSource")) { + comp.icon = view.iconSource; + } + comp.closable = options.close; + + return comp + } + + 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 { + anchors.right: parent.right + anchors.left: menu.right + anchors.bottom: parent.bottom + anchors.top: parent.top + color: "#00000000" + + Rectangle { + id: urlPane + height: 40 + color: "#00000000" + anchors { + left: parent.left + right: parent.right + leftMargin: 5 + rightMargin: 5 + top: parent.top + topMargin: 5 + } + TextField { + id: url + objectName: "url" + placeholderText: "DApp URL" + anchors { + left: parent.left + right: parent.right + top: parent.top + topMargin: 5 + rightMargin: 5 + leftMargin: 5 + } + + Keys.onReturnPressed: { + if(/^https?/.test(this.text)) { + root.browser.view.open(this.text); + mainSplit.setView(root.browser.view, root.browser.menuItem); + } else { + addPlugin(this.text, {close: true, section: "apps"}) + } + } + } + + } + + // Border + Rectangle { + id: divider + anchors { + left: parent.left + right: parent.right + top: urlPane.bottom + } + z: -1 + height: 1 + color: "#CCCCCC" + } + + Rectangle { + id: mainView + color: "#00000000" + anchors.right: parent.right + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.top: divider.bottom + + 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, {close: 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, caps: peer.caps}) + } + + 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" } + TableViewColumn{width: 80; role: "caps" ; title: "Capabilities" } + } + } + } + + 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 + anchors.top: parent.top + anchors.topMargin: 30 + font.pointSize: 12 + text: "<h2>Mist (0.6.5)</h2><h4>Amalthea</h4><br><h3>Development</h3>Jeffrey Wilcke<br>Viktor Trón<br><h3>Building</h3>Maran Hidskes" + } + } + + 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: 300 + maximumWidth: 300 + maximumHeight: 50 + minimumHeight: 50 + title: "Connect to peer" + + ComboBox { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: addPeerButton.left + anchors.leftMargin: 10 + anchors.rightMargin: 10 + onAccepted: { + eth.connectToPeer(addrField.currentText) + addPeerWin.visible = false + } + + editable: true + model: ListModel { id: pastPeers } + + Component.onCompleted: { + var ips = eth.pastPeers() + for(var i = 0; i < ips.length; i++) { + pastPeers.append({text: ips.get(i)}) + } + + pastPeers.insert(0, {text: "poc-7.ethdev.com:30303"}) + } + } + + Button { + id: addPeerButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 10 + text: "Add" + onClicked: { + eth.connectToPeer(addrField.currentText) + addPeerWin.visible = false + } + } + Component.onCompleted: { + addrField.focus = true + } + } + } diff --git a/cmd/mist/assets/qml/muted.qml b/cmd/mist/assets/qml/muted.qml new file mode 100644 index 000000000..fac8267c4 --- /dev/null +++ b/cmd/mist/assets/qml/muted.qml @@ -0,0 +1,74 @@ +import QtQuick 2.0 +import QtWebKit 3.0 +import QtWebKit.experimental 1.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Window 2.1; +import Ethereum 1.0 + +ApplicationWindow { + id: window + title: "muted" + width: 900 + height: 600 + minimumHeight: 300 + + property alias url: webView.url + property alias webView: webView + + + Item { + id: root + anchors.fill: parent + WebView { + objectName: "webView" + id: webView + anchors { + top: root.top + right: root.right + left: root.left + bottom: root.bottom + //bottom: sizeGrip.top + } + + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.onMessageReceived: { + var data = JSON.parse(message.data) + + switch(data.call) { + case "log": + console.log.apply(this, data.args) + break; + } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } + } + + /* + Rectangle { + id: sizeGrip + color: "gray" + height: 5 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height - sizeGrip.height + drag.axis: Drag.YAxis + } + } + */ + } +} diff --git a/cmd/mist/assets/qml/test_app.qml b/cmd/mist/assets/qml/test_app.qml new file mode 100644 index 000000000..c69587839 --- /dev/null +++ b/cmd/mist/assets/qml/test_app.qml @@ -0,0 +1,70 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import Ethereum 1.0 + +QmlApp { + minimumWidth: 350 + maximumWidth: 350 + maximumHeight: 80 + minimumHeight: 80 + + title: "Generic Coin" + + property string contractAddr: "f299f6c74515620e4c4cd8fe3d205b5c4f2e25c8" + property string addr: "2ef47100e0787b915105fd5e3f4ff6752079d5cb" + + Component.onCompleted: { + eth.watch(contractAddr, addr) + eth.watch(addr, contractAddr) + setAmount() + } + + function onStorageChangeCb(storageObject) { + setAmount() + } + + function setAmount(){ + var state = eth.getStateObject(contractAddr) + var storage = state.getStorage(addr) + amountLabel.text = storage + } + Column { + spacing: 5 + Row { + spacing: 20 + Label { + id: genLabel + text: "Generic coin balance:" + } + Label { + id: amountLabel + } + } + Row { + spacing: 20 + TextField { + id: address + placeholderText: "Address" + } + TextField { + id: amount + placeholderText: "Amount" + } + } + Button { + text: "Send coins" + onClicked: { + var privKey = eth.getKey().privateKey + if(privKey){ + var result = eth.transact(privKey, contractAddr, 0,"100000","250", "0x" + address.text + "\n" + amount.text) + resultTx.text = result.hash + } + } + } + Label { + id: resultTx + } + } + +} diff --git a/cmd/mist/assets/qml/transactions.qml b/cmd/mist/assets/qml/transactions.qml new file mode 100644 index 000000000..e9a035a85 --- /dev/null +++ b/cmd/mist/assets/qml/transactions.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; + +Rectangle { + id: transactionView + visible: false + Text { text: "TX VIEW" } +} diff --git a/cmd/mist/assets/qml/views/chain.qml b/cmd/mist/assets/qml/views/chain.qml new file mode 100644 index 000000000..c4ceecfc0 --- /dev/null +++ b/cmd/mist/assets/qml/views/chain.qml @@ -0,0 +1,253 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + id: root + property var title: "Block Chain" + property var menuItem + + objectName: "chainView" + visible: false + anchors.fill: parent + + TableView { + id: blockTable + width: parent.width + anchors.top: parent.top + anchors.bottom: parent.bottom + TableViewColumn{ role: "number" ; title: "#" ; width: 100 } + TableViewColumn{ role: "hash" ; title: "Hash" ; width: 560 } + TableViewColumn{ role: "txAmount" ; title: "Tx amount" ; width: 100 } + + model: blockModel + + itemDelegate: Item { + Text { + anchors { + left: parent.left + right: parent.right + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + color: styleData.textColor + elide: styleData.elideMode + text: styleData.value + font.pixelSize: 11 + MouseArea { + acceptedButtons: Qt.LeftButton | Qt.RightButton + propagateComposedEvents: true + anchors.fill: parent + onClicked: { + blockTable.selection.clear() + blockTable.selection.select(styleData.row) + + if(mouse.button == Qt.RightButton) { + contextMenu.row = styleData.row; + contextMenu.popup() + } + } + + onDoubleClicked: { + popup.visible = true + popup.setDetails(blockModel.get(styleData.row)) + } + } + } + + } + + Menu { + id: contextMenu + property var row + MenuItem { + text: "Details" + onTriggered: { + popup.visible = true + popup.setDetails(blockModel.get(contextMenu.row)) + } + } + + MenuSeparator{} + + MenuItem { + text: "Copy" + onTriggered: { + copyToClipboard(blockModel.get(contextMenu.row).hash) + } + } + + MenuItem { + text: "Dump State" + onTriggered: { + generalFileDialog.show(false, function(path) { + var hash = blockModel.get(contextMenu.row).hash; + + gui.dumpState(hash, path); + }); + } + } + } + } + + + + function addBlock(block, initial) { + if(initial == undefined){ + initial = false + } + + var amount = block.transactions.length; + var txs = []; + for(var i = 0; i < block.transactions.length; i++) { + var tx = JSON.parse(block.transactions.getAsJson(i)); + txs.push(tx); + } + + if(initial){ + blockModel.append({size: block.size, number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) + } else { + blockModel.insert(0, {size: block.size, number: block.number, name: block.name, gasLimit: block.gasLimit, gasUsed: block.gasUsed, coinbase: block.coinbase, hash: block.hash, txs: txs, txAmount: amount, time: block.time, prettyTime: convertToPretty(block.time)}) + } + } + + Window { + id: popup + visible: false + //flags: Qt.CustomizeWindowHint | Qt.Tool | Qt.WindowCloseButtonHint + property var block + width: root.width + height: 300 + Component{ + id: blockDetailsDelegate + Rectangle { + color: "#252525" + width: popup.width + height: 150 + Column { + anchors.leftMargin: 10 + anchors.topMargin: 5 + anchors.top: parent.top + anchors.left: parent.left + Text { text: '<h3>Block details</h3>'; color: "#F2F2F2"} + Text { text: '<b>Block number:</b> ' + number + " (Size: " + size + ")"; color: "#F2F2F2"} + Text { text: '<b>Hash:</b> ' + hash; color: "#F2F2F2"} + Text { text: '<b>Coinbase:</b> <' + name + '> ' + coinbase; color: "#F2F2F2"} + Text { text: '<b>Block found at:</b> ' + prettyTime; color: "#F2F2F2"} + Text { text: '<b>Gas used:</b> ' + gasUsed + " / " + gasLimit; color: "#F2F2F2"} + } + } + } + ListView { + model: singleBlock + delegate: blockDetailsDelegate + anchors.top: parent.top + height: 100 + anchors.leftMargin: 20 + id: listViewThing + Layout.maximumHeight: 40 + } + TableView { + id: txView + anchors.top: listViewThing.bottom + anchors.topMargin: 50 + width: parent.width + + TableViewColumn{width: 90; role: "value" ; title: "Value" } + TableViewColumn{width: 200; role: "hash" ; title: "Hash" } + TableViewColumn{width: 200; role: "sender" ; title: "Sender" } + TableViewColumn{width: 200;role: "address" ; title: "Receiver" } + TableViewColumn{width: 60; role: "gas" ; title: "Gas" } + TableViewColumn{width: 60; role: "gasPrice" ; title: "Gas Price" } + TableViewColumn{width: 60; role: "isContract" ; title: "Contract" } + + model: transactionModel + onClicked: { + var tx = transactionModel.get(row) + if(tx.data) { + popup.showContractData(tx) + }else{ + popup.height = 440 + } + } + } + + function showContractData(tx) { + txDetailsDebugButton.tx = tx + if(tx.createsContract) { + contractData.text = tx.data + contractLabel.text = "<h4> Transaction created contract " + tx.address + "</h4>" + }else{ + contractLabel.text = "<h4> Transaction ran contract " + tx.address + "</h4>" + contractData.text = tx.rawData + } + popup.height = 540 + } + + Rectangle { + id: txDetails + width: popup.width + height: 300 + anchors.left: listViewThing.left + anchors.top: txView.bottom + Label { + text: "<h4>Contract data</h4>" + anchors.top: parent.top + anchors.left: parent.left + id: contractLabel + anchors.leftMargin: 10 + } + Button { + property var tx + id: txDetailsDebugButton + anchors.right: parent.right + anchors.rightMargin: 10 + anchors.top: parent.top + anchors.topMargin: 10 + text: "Debug contract" + onClicked: { + if(tx && tx.createsContract){ + eth.startDbWithCode(tx.rawData) + }else { + eth.startDbWithContractAndData(tx.address, tx.rawData) + } + } + } + TextArea { + id: contractData + text: "Contract" + anchors.top: contractLabel.bottom + anchors.left: parent.left + anchors.bottom: popup.bottom + wrapMode: Text.Wrap + width: parent.width - 30 + height: 80 + anchors.leftMargin: 10 + } + } + property var transactionModel: ListModel { + id: transactionModel + } + property var singleBlock: ListModel { + id: singleBlock + } + function setDetails(bl){ + singleBlock.set(0, bl) + popup.height = 300 + transactionModel.clear() + if(bl.txs !== undefined){ + for(var i = 0; i < bl.txs.count; i++) { + transactionModel.insert(0, bl.txs.get(i)) + } + if(bl.txs.count > 0 && bl.txs.get(0).data){ + popup.showContractData(bl.txs.get(0)) + } + } + txView.forceActiveFocus() + } + } +} diff --git a/cmd/mist/assets/qml/views/history.qml b/cmd/mist/assets/qml/views/history.qml new file mode 100644 index 000000000..c72f8f3ae --- /dev/null +++ b/cmd/mist/assets/qml/views/history.qml @@ -0,0 +1,51 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "Transactions" + property var menuItem + + + id: historyView + visible: false + anchors.fill: parent + objectName: "transactionView" + + property var txModel: ListModel { + id: txModel + } + TableView { + id: txTableView + anchors.fill: parent + TableViewColumn{ role: "inout" ; title: "" ; width: 40 } + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "address" ; title: "Address" ; width: 430 } + TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 } + + model: txModel + } + + function addTx(tx, inout) { + var isContract + if (tx.contract == true){ + isContract = "Yes" + }else{ + isContract = "No" + } + + + var address; + if(inout == "recv") { + address = tx.sender; + } else { + address = tx.address; + } + + txModel.insert(0, {inout: inout, hash: tx.hash, address: address, value: tx.value, contract: isContract}) + } +} diff --git a/cmd/mist/assets/qml/views/info.qml b/cmd/mist/assets/qml/views/info.qml new file mode 100644 index 000000000..3ff551b05 --- /dev/null +++ b/cmd/mist/assets/qml/views/info.qml @@ -0,0 +1,196 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "Debug Info" + property var menuItem + + objectName: "infoView" + visible: false + anchors.fill: parent + + color: "#00000000" + + Column { + id: info + spacing: 3 + anchors.fill: parent + anchors.topMargin: 5 + anchors.leftMargin: 5 + + Label { + id: addressLabel + text: "Address" + } + TextField { + text: eth.key().address + width: 500 + } + + Label { + text: "Client ID" + } + TextField { + text: gui.getCustomIdentifier() + width: 500 + placeholderText: "Anonymous" + onTextChanged: { + gui.setCustomIdentifier(text) + } + } + + TextArea { + objectName: "statsPane" + width: parent.width + height: 200 + selectByMouse: true + readOnly: true + font.family: "Courier" + } + } + + RowLayout { + id: logLayout + width: parent.width + height: 200 + anchors.bottom: parent.bottom + + TableView { + id: addressView + width: parent.width + height: 200 + anchors { + left: parent.left + right: logLevelSlider.left + bottom: parent.bottom + top: parent.top + } + TableViewColumn{ role: "name"; title: "name" } + TableViewColumn{ role: "address"; title: "address"; width: 300} + + property var addressModel: ListModel { + id: addressModel + } + + model: addressModel + itemDelegate: Item { + Text { + anchors { + left: parent.left + right: parent.right + leftMargin: 10 + verticalCenter: parent.verticalCenter + } + color: styleData.textColor + elide: styleData.elideMode + text: styleData.value + font.pixelSize: 11 + MouseArea { + acceptedButtons: Qt.LeftButton | Qt.RightButton + propagateComposedEvents: true + anchors.fill: parent + onClicked: { + addressView.selection.clear() + addressView.selection.select(styleData.row) + + if(mouse.button == Qt.RightButton) { + contextMenu.row = styleData.row; + contextMenu.popup() + } + } + } + } + } + + Menu { + id: contextMenu + property var row; + + MenuItem { + text: "Copy" + onTriggered: { + copyToClipboard(addressModel.get(this.row).address) + } + } + } + } + + /* + TableView { + id: logView + headerVisible: false + anchors { + right: logLevelSlider.left + left: parent.left + bottom: parent.bottom + top: parent.top + } + + TableViewColumn{ role: "description" ; title: "log" } + + model: logModel + } + */ + + Slider { + id: logLevelSlider + value: gui.getLogLevelInt() + anchors { + right: parent.right + top: parent.top + bottom: parent.bottom + + rightMargin: 5 + leftMargin: 5 + topMargin: 5 + bottomMargin: 5 + } + + orientation: Qt.Vertical + maximumValue: 5 + stepSize: 1 + + onValueChanged: { + gui.setLogLevel(value) + } + } + } + + property var logModel: ListModel { + id: logModel + } + + function addDebugMessage(message){ + debuggerLog.append({value: message}) + } + + function addAddress(address) { + addressModel.append({name: address.name, address: address.address}) + } + + function clearAddress() { + addressModel.clear() + } + + function addLog(str) { + // Remove first item once we've reached max log items + if(logModel.count > 250) { + logModel.remove(0) + } + + if(str.len != 0) { + if(logView.flickableItem.atYEnd) { + logModel.append({description: str}) + logView.positionViewAtRow(logView.rowCount - 1, ListView.Contain) + } else { + logModel.append({description: str}) + } + } + + } +} diff --git a/cmd/mist/assets/qml/views/jeffcoin/jeff.png b/cmd/mist/assets/qml/views/jeffcoin/jeff.png Binary files differnew file mode 100644 index 000000000..2b9c6651a --- /dev/null +++ b/cmd/mist/assets/qml/views/jeffcoin/jeff.png diff --git a/cmd/mist/assets/qml/views/jeffcoin/jeffcoin.qml b/cmd/mist/assets/qml/views/jeffcoin/jeffcoin.qml new file mode 100644 index 000000000..23502d334 --- /dev/null +++ b/cmd/mist/assets/qml/views/jeffcoin/jeffcoin.qml @@ -0,0 +1,190 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 + +Rectangle { + id: root + property var title: "JeffCoin" + property var iconSource: "./views/jeffcoin/jeff.png" + property var menuItem + property var filter + property var address: "fc0a9436890478bb9b1c6ed7455c2535366f4a99" + + function insertTx(message, blockNumber) { + if(!message) return; + + var from = message.from + var to = message.input.substr(24, 40) + var value = eth.fromNumber(message.input.substr(64, 64)) + + var me = eth.key().address; + if((to == me|| from == me) && message.input.length == 128) { + var to = eth.lookupName(to) + var from = eth.lookupName(from) + txModel.insert(0, {confirmations: blockNumber - message.number, from: from, to: to, value: value}) + } + } + + function setBalance() { + var jeffCoinAmount = eth.fromNumber(eth.storageAt(address, eth.key().address)) + " JΞF" + menuItem.secondaryTitle = jeffCoinAmount + + balance.text = "<b>Balance</b>: " + jeffCoinAmount; + } + + function onReady() { + setBalance() + + filter = new ethx.watch({latest: -1, to: address}) + filter.changed(function(messages) { + setBalance() + + var blockNumber = eth.block(-1).number; + for(var i = 0; i < messages.length; i++) { + insertTx(messages.get(i), blockNumber); + } + }); + + var blockNumber = eth.block(-1).number; + var msgs = filter.messages() + for(var i = msgs.length-1; i >= 0; i--) { + var message = JSON.parse(msgs.getAsJson(i)) + + insertTx(message, blockNumber) + } + + var chainChanged = ethx.watch("chain") + chainChanged.changed(function() { + for(var i = 0; i < txModel.count; i++) { + var entry = txModel.get(i); + entry.confirmations++; + } + }); + } + + function onDestroy() { + filter.uninstall() + } + + ColumnLayout { + spacing: 10 + y: 40 + anchors.fill: parent + + Text { + id: balance + text: "<b>Balance</b>: " + eth.fromNumber(eth.storageAt(address, eth.key().address)) + " JΞF" + font.pixelSize: 24 + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 20 + } + } + + 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: "JΞF " + } + + // 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" + } + } + } + + 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 lookup = eth.lookupAddress(address) + if(lookup.length == 0) + lookup = address + + eth.transact({from: eth.key().privateKey, to:lookup, gas: "9000", gasPrice: "10000000000000", data: ["0x"+txTo.text, txValue.text]}) + } + } + } + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + top: newTxPane.bottom + topMargin: 10 + bottom: parent.bottom + } + TableView { + id: txTableView + anchors.fill : parent + TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } + TableViewColumn{ role: "from" ; title: "From" ; width: 280 } + TableViewColumn{ role: "to" ; title: "To" ; width: 280 } + TableViewColumn{ role: "confirmations" ; title: "Confirmations" ; width: 100 } + + model: ListModel { + id: txModel + Component.onCompleted: { + } + } + } + } + } +} diff --git a/cmd/mist/assets/qml/views/pending_tx.qml b/cmd/mist/assets/qml/views/pending_tx.qml new file mode 100644 index 000000000..4442a69db --- /dev/null +++ b/cmd/mist/assets/qml/views/pending_tx.qml @@ -0,0 +1,44 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "Pending Transactions" + property var menuItem + + objectName: "pendingTxView" + anchors.fill: parent + visible: false + id: pendingTxView + + property var pendingTxModel: ListModel { + id: pendingTxModel + } + + TableView { + id: pendingTxTableView + anchors.fill: parent + TableViewColumn{ role: "value" ; title: "Value" ; width: 100 } + TableViewColumn{ role: "from" ; title: "sender" ; width: 230 } + TableViewColumn{ role: "to" ; title: "Reciever" ; width: 230 } + TableViewColumn{ role: "contract" ; title: "Contract" ; width: 100 } + + model: pendingTxModel + } + + function addTx(tx, inout) { + var isContract + if (tx.contract == true){ + isContract = "Yes" + }else{ + isContract = "No" + } + + + pendingTxModel.insert(0, {hash: tx.hash, to: tx.address, from: tx.sender, value: tx.value, contract: isContract}) + } +} diff --git a/cmd/mist/assets/qml/views/transaction.qml b/cmd/mist/assets/qml/views/transaction.qml new file mode 100644 index 000000000..8792e31eb --- /dev/null +++ b/cmd/mist/assets/qml/views/transaction.qml @@ -0,0 +1,216 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + property var title: "New Transaction" + property var menuItem + + objectName: "newTxView" + visible: false + anchors.fill: parent + color: "#00000000" + + Column { + id: mainContractColumn + anchors.fill: parent + + + states: [ + State{ + name: "ERROR" + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: codeView; visible:true} + }, + State { + name: "DONE" + + PropertyChanges { target: txValue; visible:false} + PropertyChanges { target: txGas; visible:false} + PropertyChanges { target: txGasPrice; visible:false} + PropertyChanges { target: codeView; visible:false} + PropertyChanges { target: txButton; visible:false} + PropertyChanges { target: txDataLabel; visible:false} + PropertyChanges { target: atLabel; visible:false} + PropertyChanges { target: txFuelRecipient; visible:false} + PropertyChanges { target: valueDenom; visible:false} + PropertyChanges { target: gasDenom; visible:false} + + PropertyChanges { target: txResult; visible:true} + PropertyChanges { target: txOutput; visible:true} + PropertyChanges { target: newTxButton; visible:true} + }, + State { + name: "SETUP" + + PropertyChanges { target: txValue; visible:true; text: ""} + PropertyChanges { target: txGas; visible:true;} + PropertyChanges { target: txGasPrice; visible:true;} + PropertyChanges { target: codeView; visible:true; text: ""} + PropertyChanges { target: txButton; visible:true} + PropertyChanges { target: txDataLabel; visible:true} + PropertyChanges { target: valueDenom; visible:true} + PropertyChanges { target: gasDenom; visible:true} + + PropertyChanges { target: txResult; visible:false} + PropertyChanges { target: txOutput; visible:false} + PropertyChanges { target: newTxButton; visible:false} + } + ] + width: 400 + spacing: 5 + anchors.left: parent.left + anchors.top: parent.top + anchors.leftMargin: 5 + anchors.topMargin: 5 + + 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" } + } + + + TextField { + id: txFuelRecipient + placeholderText: "Address / Name or empty for contract" + //validator: RegExpValidator { regExp: /[a-f0-9]{40}/ } + width: 400 + } + + RowLayout { + TextField { + id: txValue + width: 222 + placeholderText: "Amount" + validator: RegExpValidator { regExp: /\d*/ } + onTextChanged: { + contractFormReady() + } + } + + ComboBox { + id: valueDenom + currentIndex: 6 + model: denomModel + } + } + + RowLayout { + TextField { + id: txGas + width: 50 + validator: RegExpValidator { regExp: /\d*/ } + placeholderText: "Gas" + text: "500" + } + Label { + id: atLabel + text: "@" + } + + TextField { + id: txGasPrice + width: 200 + placeholderText: "Gas price" + text: "10" + validator: RegExpValidator { regExp: /\d*/ } + } + + ComboBox { + id: gasDenom + currentIndex: 4 + model: denomModel + } + } + + Label { + id: txDataLabel + text: "Data" + } + + TextArea { + id: codeView + height: 300 + anchors.topMargin: 5 + width: 400 + onTextChanged: { + contractFormReady() + } + } + + + Button { + id: txButton + /* enabled: false */ + states: [ + State { + name: "READY" + PropertyChanges { target: txButton; /*enabled: true*/} + }, + State { + name: "NOTREADY" + PropertyChanges { target: txButton; /*enabled:false*/} + } + ] + text: "Send" + onClicked: { + var value = txValue.text + denomModel.get(valueDenom.currentIndex).zeros; + var gasPrice = txGasPrice.text + denomModel.get(gasDenom.currentIndex).zeros; + var res = gui.transact(txFuelRecipient.text, value, txGas.text, gasPrice, codeView.text) + if(res[1]) { + txResult.text = "Your contract <b>could not</b> be sent over the network:\n<b>" + txResult.text += res[1].error() + txResult.text += "</b>" + mainContractColumn.state = "ERROR" + } else { + txResult.text = "Your transaction has been submitted:\n" + txOutput.text = res[0].address + mainContractColumn.state = "DONE" + + console.log(res) + } + } + } + Text { + id: txResult + visible: false + } + TextField { + id: txOutput + visible: false + width: 530 + } + Button { + id: newTxButton + visible: false + text: "Create a new transaction" + onClicked: { + this.visible = false + txResult.text = "" + txOutput.text = "" + mainContractColumn.state = "SETUP" + } + } + } + + function contractFormReady(){ + if(codeView.text.length > 0 && txValue.text.length > 0 && txGas.text.length > 0 && txGasPrice.length > 0) { + txButton.state = "READY" + }else{ + txButton.state = "NOTREADY" + } + } +} diff --git a/cmd/mist/assets/qml/views/wallet.qml b/cmd/mist/assets/qml/views/wallet.qml new file mode 100644 index 000000000..a57e7869a --- /dev/null +++ b/cmd/mist/assets/qml/views/wallet.qml @@ -0,0 +1,173 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Layouts 1.0; +import QtQuick.Dialogs 1.0; +import QtQuick.Window 2.1; +import QtQuick.Controls.Styles 1.1 +import Ethereum 1.0 + +Rectangle { + id: root + property var title: "Wallet" + property var iconSource: "../facet.png" + property var menuItem + + objectName: "walletView" + anchors.fill: parent + + function onReady() { + menuItem.secondaryTitle = 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.fill: parent + + Text { + id: balance + text: "<b>Balance</b>: " + eth.numberToHuman(eth.balanceAt(eth.key().address)) + font.pixelSize: 24 + anchors { + horizontalCenter: parent.horizontalCenter + top: parent.top + topMargin: 20 + } + } + + 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 + } + + } + + 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({from: eth.key().privateKey, to: txTo.text, value: value, gas: "500", gasPrice: 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 filter = ethx.watch({latest: -1, from: eth.key().address}); + filter.changed(addTxs) + + addTxs(filter.messages()) + } + + function addTxs(messages) { + for(var i = 0; i < messages.length; i++) { + var message = messages.get(i); + var to = eth.lookupName(message.to); + var from = eth.lookupName(message.from); + txModel.insert(0, {num: txModel.count, from: from, to: to, value: eth.numberToHuman(message.value)}) + } + } + } + } + } + + } +} diff --git a/cmd/mist/assets/qml/webapp.qml b/cmd/mist/assets/qml/webapp.qml new file mode 100644 index 000000000..c35f325d5 --- /dev/null +++ b/cmd/mist/assets/qml/webapp.qml @@ -0,0 +1,408 @@ +import QtQuick 2.0 +import QtWebKit 3.0 +import QtWebKit.experimental 1.0 +import QtQuick.Controls 1.0; +import QtQuick.Controls.Styles 1.0 +import QtQuick.Layouts 1.0; +import QtQuick.Window 2.1; +import Ethereum 1.0 + +Rectangle { + id: window + property var title: "Browser" + property var iconSource: "../browser.png" + property var menuItem + + property alias url: webview.url + property alias webView: webview + + property var cleanPath: false + property var open: function(url) { + if(!window.cleanPath) { + var uri = url; + if(!/.*\:\/\/.*/.test(uri)) { + uri = "http://" + uri; + } + + var reg = /(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.eth)(.*)/ + + if(reg.test(uri)) { + uri.replace(reg, function(match, pre, domain, path) { + uri = pre; + + var lookup = eth.lookupDomain(domain.substring(0, domain.length - 4)); + var ip = []; + for(var i = 0, l = lookup.length; i < l; i++) { + ip.push(lookup.charCodeAt(i)) + } + + if(ip.length != 0) { + uri += lookup; + } else { + uri += domain; + } + + uri += path; + }); + } + + window.cleanPath = true; + + webview.url = uri; + + //uriNav.text = uri.text.replace(/(^https?\:\/\/(?:www\.)?)([a-zA-Z0-9_\-]*\.\w{2,3})(.*)/, "$1$2<span style='color:#CCC'>$3</span>"); + uriNav.text = uri; + } else { + // Prevent inf loop. + window.cleanPath = false; + } + } + + Component.onCompleted: { + webview.url = "http://etherian.io" + } + + signal messages(var messages, int id); + onMessages: { + // Bit of a cheat to get proper JSON + var m = JSON.parse(JSON.parse(JSON.stringify(messages))) + webview.postEvent("messages", [m, id]); + } + + Item { + objectName: "root" + id: root + anchors.fill: parent + state: "inspectorShown" + + RowLayout { + id: navBar + height: 40 + anchors { + left: parent.left + right: parent.right + leftMargin: 7 + } + + Button { + id: back + onClicked: { + webview.goBack() + } + style: ButtonStyle { + background: Image { + source: "../back.png" + width: 30 + height: 30 + } + } + } + + TextField { + anchors { + left: back.right + right: toggleInspector.left + leftMargin: 5 + rightMargin: 5 + } + text: "http://etherian.io" + id: uriNav + y: parent.height / 2 - this.height / 2 + + Keys.onReturnPressed: { + webview.url = this.text; + } + } + + Button { + id: toggleInspector + anchors { + right: parent.right + } + iconSource: "../bug.png" + onClicked: { + if(inspector.visible == true){ + inspector.visible = false + }else{ + inspector.visible = true + inspector.url = webview.experimental.remoteInspectorUrl + } + } + } + } + + + WebView { + objectName: "webView" + id: webview + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + top: navBar.bottom + } + + //property var cleanPath: false + onNavigationRequested: { + window.open(request.url.toString()); + } + + function sendMessage(data) { + webview.experimental.postMessage(JSON.stringify(data)) + } + + + experimental.preferences.javascriptEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.preferences.developerExtrasEnabled: true + experimental.userScripts: ["../ext/qt_messaging_adapter.js", "../ext/q.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] + experimental.onMessageReceived: { + console.log("[onMessageReceived]: ", message.data) + // TODO move to messaging.js + var data = JSON.parse(message.data) + + try { + switch(data.call) { + case "compile": + postData(data._seed, eth.compile(data.args[0])) + break + + case "getCoinBase": + postData(data._seed, eth.coinBase()) + + break + + case "getIsListening": + postData(data._seed, eth.isListening()) + + break + + case "getIsMining": + postData(data._seed, eth.isMining()) + + break + + case "getPeerCount": + postData(data._seed, eth.peerCount()) + + break + + case "getCountAt": + require(1) + postData(data._seed, eth.txCountAt(data.args[0])) + + break + + case "getCodeAt": + require(1) + var code = eth.codeAt(data.args[0]) + postData(data._seed, code); + + break + + case "getBlockByNumber": + var block = eth.blockByNumber(data.args[0]) + postData(data._seed, block) + + break + + case "getBlockByHash": + var block = eth.blockByHash(data.args[0]) + postData(data._seed, block) + + break + + case "transact": + require(5) + + var tx = eth.transact(data.args) + postData(data._seed, tx) + + break + + case "getStorageAt": + require(2); + + var storage = eth.storageAt(data.args[0], data.args[1]); + postData(data._seed, storage) + + break + + case "call": + require(1); + var ret = eth.call(data.args) + postData(data._seed, ret) + + break + + case "getEachStorage": + require(1); + var storage = JSON.parse(eth.eachStorage(data.args[0])) + postData(data._seed, storage) + + break + + case "getTransactionsFor": + require(1); + var txs = eth.transactionsFor(data.args[0], true) + postData(data._seed, txs) + + break + + case "getBalanceAt": + require(1); + + postData(data._seed, eth.balanceAt(data.args[0])); + + break + + case "getKey": + var key = eth.key().privateKey; + + postData(data._seed, key) + break + + case "watch": + require(2) + eth.watch(data.args[0], data.args[1]) + + case "disconnect": + require(1) + postData(data._seed, null) + + break; + + case "getSecretToAddress": + require(1) + + var addr = eth.secretToAddress(data.args[0]) + console.log("getsecret", addr) + postData(data._seed, addr) + + break; + + case "messages": + require(1); + + var messages = JSON.parse(eth.getMessages(data.args[0])) + postData(data._seed, messages) + + break + + case "mutan": + require(1) + + var code = eth.compileMutan(data.args[0]) + postData(data._seed, "0x"+code) + + break; + + case "newFilterString": + require(1) + var id = eth.newFilterString(data.args[0]) + postData(data._seed, id); + break; + case "newFilter": + require(1) + var id = eth.newFilter(data.args[0]) + + postData(data._seed, id); + break; + + case "getMessages": + require(1); + + var messages = eth.messages(data.args[0]); + var m = JSON.parse(JSON.parse(JSON.stringify(messages))) + postData(data._seed, m); + + break; + + case "deleteFilter": + require(1); + eth.uninstallFilter(data.args[0]) + break; + } + } catch(e) { + console.log(data.call + ": " + e) + + postData(data._seed, null); + } + } + + + function post(seed, data) { + postData(data._seed, data) + } + + function require(args, num) { + if(args.length < num) { + throw("required argument count of "+num+" got "+args.length); + } + } + function postData(seed, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + } + function postEvent(event, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) + } + + function onWatchedCb(data, id) { + var messages = JSON.parse(data) + postEvent("watched:"+id, messages) + } + + function onNewBlockCb(block) { + postEvent("block:new", block) + } + function onObjectChangeCb(stateObject) { + postEvent("object:"+stateObject.address(), stateObject) + } + function onStorageChangeCb(storageObject) { + var ev = ["storage", storageObject.stateAddress, storageObject.address].join(":"); + postEvent(ev, [storageObject.address, storageObject.value]) + } + } + + + Rectangle { + id: sizeGrip + color: "gray" + visible: false + height: 10 + anchors { + left: root.left + right: root.right + } + y: Math.round(root.height * 2 / 3) + + MouseArea { + anchors.fill: parent + drag.target: sizeGrip + drag.minimumY: 0 + drag.maximumY: root.height + drag.axis: Drag.YAxis + } + } + + WebView { + id: inspector + visible: false + anchors { + left: root.left + right: root.right + top: sizeGrip.bottom + bottom: root.bottom + } + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + } + } + ] + } +} |