diff options
Diffstat (limited to 'cmd/mist/assets/qml')
35 files changed, 4022 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/depricated_browser.qml b/cmd/mist/assets/qml/depricated_browser.qml new file mode 100644 index 000000000..7056dbbf3 --- /dev/null +++ b/cmd/mist/assets/qml/depricated_browser.qml @@ -0,0 +1,486 @@ +import QtQuick 2.1 +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 + anchors.fill: parent + color: "#00000000" + + property var title: "DApps" + property var iconSource: "../browser.png" + property var menuItem + property var hideUrl: true + + property alias url: webview.url + property alias windowTitle: webview.title + 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" + } + + function messages(messages, id) { + // Bit of a cheat to get proper JSON + var m = JSON.parse(JSON.parse(JSON.stringify(messages))) + webview.postEvent("eth_changed", id, m); + } + + function onShhMessage(message, id) { + webview.postEvent("shh_changed", id, message) + } + + 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: 10 + rightMargin: 10 + } + text: webview.url; + 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 + } + } + } + } + + // Border + Rectangle { + id: divider + anchors { + left: parent.left + right: parent.right + top: navBar.bottom + } + z: -1 + height: 1 + color: "#CCCCCC" + } + + ScrollView { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + top: divider.bottom + } + WebView { + objectName: "webView" + id: webview + anchors.fill: parent + + function sendMessage(data) { + webview.experimental.postMessage(JSON.stringify(data)) + } + + experimental.preferences.javascriptEnabled: true + experimental.preferences.webAudioEnabled: true + experimental.preferences.pluginsEnabled: true + experimental.preferences.navigatorQtObjectEnabled: true + experimental.preferences.developerExtrasEnabled: true + experimental.preferences.webGLEnabled: true + experimental.preferences.notificationsEnabled: true + experimental.preferences.localStorageEnabled: true + experimental.userAgent:"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Mist/0.1 Safari/537.36" + + experimental.itemSelector: MouseArea { + // To avoid conflicting with ListView.model when inside Initiator context. + property QtObject selectorModel: model + anchors.fill: parent + onClicked: selectorModel.reject() + + Menu { + visible: true + id: itemSelector + + Instantiator { + model: selectorModel.items + delegate: MenuItem { + text: model.text + onTriggered: { + selectorModel.accept(index) + } + } + onObjectAdded: itemSelector.insertItem(index, object) + onObjectRemoved: itemSelector.removeItem(object) + } + } + + Component.onCompleted: { + itemSelector.popup() + } + } + experimental.userScripts: ["../ext/q.js", "../ext/ethereum.js/lib/web3.js", "../ext/ethereum.js/lib/qt.js", "../ext/setup.js"] + experimental.onMessageReceived: { + //console.log("[onMessageReceived]: ", message.data) + var data = JSON.parse(message.data) + + try { + switch(data.call) { + case "eth_compile": + postData(data._id, eth.compile(data.args[0])) + break + + case "eth_coinbase": + postData(data._id, eth.coinBase()) + + case "eth_account": + postData(data._id, eth.key().address); + + case "eth_istening": + postData(data._id, eth.isListening()) + + break + + case "eth_mining": + postData(data._id, eth.isMining()) + + break + + case "eth_peerCount": + postData(data._id, eth.peerCount()) + + break + + case "eth_countAt": + require(1) + postData(data._id, eth.txCountAt(data.args[0])) + + break + + case "eth_codeAt": + require(1) + var code = eth.codeAt(data.args[0]) + postData(data._id, code); + + break + + case "eth_blockByNumber": + require(1) + var block = eth.blockByNumber(data.args[0]) + postData(data._id, block) + break + + case "eth_blockByHash": + require(1) + var block = eth.blockByHash(data.args[0]) + postData(data._id, block) + break + + require(2) + var block = eth.blockByHash(data.args[0]) + postData(data._id, block.transactions[data.args[1]]) + break + + case "eth_transactionByHash": + case "eth_transactionByNumber": + require(2) + + var block; + if (data.call === "transactionByHash") + block = eth.blockByHash(data.args[0]) + else + block = eth.blockByNumber(data.args[0]) + + var tx = block.transactions.get(data.args[1]) + + postData(data._id, tx) + break + + case "eth_uncleByHash": + case "eth_uncleByNumber": + require(2) + + var block; + if (data.call === "uncleByHash") + block = eth.blockByHash(data.args[0]) + else + block = eth.blockByNumber(data.args[0]) + + var uncle = block.uncles.get(data.args[1]) + + postData(data._id, uncle) + + break + + case "transact": + require(5) + + var tx = eth.transact(data.args) + postData(data._id, tx) + + break + + case "eth_stateAt": + require(2); + + var storage = eth.storageAt(data.args[0], data.args[1]); + postData(data._id, storage) + + break + + case "eth_call": + require(1); + var ret = eth.call(data.args) + postData(data._id, ret) + break + + case "eth_balanceAt": + require(1); + + postData(data._id, eth.balanceAt(data.args[0])); + break + + case "eth_watch": + require(2) + eth.watch(data.args[0], data.args[1]) + + case "eth_disconnect": + require(1) + postData(data._id, null) + break; + + case "eth_newFilterString": + require(1) + var id = eth.newFilterString(data.args[0], window) + postData(data._id, id); + break; + + case "eth_newFilter": + require(1) + var id = eth.newFilter(data.args[0], window) + + postData(data._id, id); + break; + + case "eth_filterLogs": + require(1); + + var messages = eth.messages(data.args[0]); + var m = JSON.parse(JSON.parse(JSON.stringify(messages))) + postData(data._id, m); + + break; + + case "eth_deleteFilter": + require(1); + eth.uninstallFilter(data.args[0]) + break; + + + case "shh_newFilter": + require(1); + var id = shh.watch(data.args[0], window); + postData(data._id, id); + break; + + case "shh_newIdentity": + var id = shh.newIdentity() + postData(data._id, id) + + break + + case "shh_post": + require(1); + + var params = data.args[0]; + var fields = ["payload", "to", "from"]; + for(var i = 0; i < fields.length; i++) { + params[fields[i]] = params[fields[i]] || ""; + } + if(typeof params.payload !== "object") { params.payload = [params.payload]; } //params.payload = params.payload.join(""); } + params.topics = params.topics || []; + params.priority = params.priority || 1000; + params.ttl = params.ttl || 100; + + shh.post(params.payload, params.to, params.from, params.topics, params.priority, params.ttl); + + break; + + case "shh_getMessages": + require(1); + + var m = shh.messages(data.args[0]); + var messages = JSON.parse(JSON.parse(JSON.stringify(m))); + postData(data._id, messages); + + break; + + case "ssh_newGroup": + postData(data._id, ""); + break; + } + } catch(e) { + console.log(data.call + ": " + e) + + postData(data._id, null); + } + } + + function post(seed, data) { + postData(data._id, 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, _id: seed})) + } + function postEvent(event, id, data) { + webview.experimental.postMessage(JSON.stringify({data: data, _id: id, _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 + } + } + ] + } +} 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/fonts/Simple-Line-Icons.ttf b/cmd/mist/assets/qml/fonts/Simple-Line-Icons.ttf Binary files differnew file mode 100755 index 000000000..2194f1f87 --- /dev/null +++ b/cmd/mist/assets/qml/fonts/Simple-Line-Icons.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-Black.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-Black.ttf Binary files differnew file mode 100644 index 000000000..be1a3108e --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-Black.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-BlackIt.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-BlackIt.ttf Binary files differnew file mode 100644 index 000000000..ac5c4ef7c --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-BlackIt.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-Bold.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-Bold.ttf Binary files differnew file mode 100644 index 000000000..f47161c6b --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-Bold.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-BoldIt.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-BoldIt.ttf Binary files differnew file mode 100644 index 000000000..6b3db698b --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-BoldIt.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-ExtraLight.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-ExtraLight.ttf Binary files differnew file mode 100644 index 000000000..0a3e51fdb --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-ExtraLight.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-ExtraLightIt.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-ExtraLightIt.ttf Binary files differnew file mode 100644 index 000000000..a0eb86aca --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-ExtraLightIt.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-It.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-It.ttf Binary files differnew file mode 100644 index 000000000..fcc95fc7c --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-It.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-Light.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-Light.ttf Binary files differnew file mode 100644 index 000000000..9cae13c97 --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-Light.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-LightIt.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-LightIt.ttf Binary files differnew file mode 100644 index 000000000..2ed784284 --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-LightIt.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-Regular.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-Regular.ttf Binary files differnew file mode 100644 index 000000000..8e8255e17 --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-Regular.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-Semibold.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-Semibold.ttf Binary files differnew file mode 100644 index 000000000..121ee9bbf --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-Semibold.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSansPro-SemiboldIt.ttf b/cmd/mist/assets/qml/fonts/SourceSansPro-SemiboldIt.ttf Binary files differnew file mode 100644 index 000000000..6ceaa885f --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSansPro-SemiboldIt.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSerifPro-Bold.ttf b/cmd/mist/assets/qml/fonts/SourceSerifPro-Bold.ttf Binary files differnew file mode 100644 index 000000000..ac7837fd9 --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSerifPro-Bold.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSerifPro-Regular.ttf b/cmd/mist/assets/qml/fonts/SourceSerifPro-Regular.ttf Binary files differnew file mode 100644 index 000000000..7201a8890 --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSerifPro-Regular.ttf diff --git a/cmd/mist/assets/qml/fonts/SourceSerifPro-Semibold.ttf b/cmd/mist/assets/qml/fonts/SourceSerifPro-Semibold.ttf Binary files differnew file mode 100644 index 000000000..db2fc804b --- /dev/null +++ b/cmd/mist/assets/qml/fonts/SourceSerifPro-Semibold.ttf diff --git a/cmd/mist/assets/qml/main.qml b/cmd/mist/assets/qml/main.qml new file mode 100644 index 000000000..a909916b7 --- /dev/null +++ b/cmd/mist/assets/qml/main.qml @@ -0,0 +1,1043 @@ +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 + + //flags: Qt.FramelessWindowHint + // Use this to make the window frameless. But then you'll need to do move and resize by hand + + property var ethx : Eth.ethx + property var catalog; + + width: 1200 + height: 820 + minimumHeight: 600 + minimumWidth: 800 + + title: "Mist" + + TextField { + id: copyElementHax + visible: false + } + + function copyToClipboard(text) { + copyElementHax.text = text + copyElementHax.selectAll() + copyElementHax.copy() + } + + // Takes care of loading all default plugins + Component.onCompleted: { + + catalog = addPlugin("./views/catalog.qml", {noAdd: true, close: false, section: "begin", active: true}); + + var walletWeb = addPlugin("./views/browser.qml", {noAdd: true, close: false, section: "ethereum", active: false}); + walletWeb.view.url = "http://ethereum-dapp-wallet.meteor.com/"; + walletWeb.menuItem.title = "Wallet"; + + addPlugin("./views/miner.qml", {noAdd: true, close: false, section: "ethereum", active: false}); + addPlugin("./views/transaction.qml", {noAdd: true, close: false, section: "legacy"}); + addPlugin("./views/whisper.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"}); + + mainSplit.setView(catalog.view, catalog.menuItem); + + //newBrowserTab("http://ethereum-dapp-catalog.meteor.com"); + + // Command setup + gui.sendCommand(0) + } + + function activeView(view, menuItem) { + mainSplit.setView(view, menuItem) + /*if (view.hideUrl) { + urlPane.visible = false; + mainView.anchors.top = rootView.top + } else { + urlPane.visible = true; + mainView.anchors.top = divider.bottom + }*/ + + } + + 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) { + console.log(e) + } + } + + function newBrowserTab(url) { + + var urlMatches = url.toString().match(/^[a-z]*\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); + var requestedDomain = urlMatches && urlMatches[1]; + + var domainAlreadyOpen = false; + + for(var i = 0; i < mainSplit.views.length; i++) { + if (mainSplit.views[i].view.url) { + var matches = mainSplit.views[i].view.url.toString().match(/^[a-z]*\:\/\/(?:www\.)?([^\/?#]+)(?:[\/?#]|$)/i); + var existingDomain = matches && matches[1]; + if (requestedDomain == existingDomain) { + domainAlreadyOpen = true; + + if (mainSplit.views[i].view.url != url){ + mainSplit.views[i].view.url = url; + } + + activeView(mainSplit.views[i].view, mainSplit.views[i].menuItem); + } + } + } + + if (!domainAlreadyOpen) { + var window = addPlugin("./views/browser.qml", {noAdd: true, close: true, section: "apps", active: true}); + window.view.url = url; + window.menuItem.title = "Mist"; + activeView(window.view, window.menuItem); + } + } + + + + menuBar: MenuBar { + Menu { + title: "File" + MenuItem { + text: "New tab" + shortcut: "Ctrl+t" + onTriggered: { + activeView(catalog.view, catalog.menuItem); + } + } + + 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 {} + } + + 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 + } + } + } + + } + + property var blockModel: ListModel { + id: blockModel + } + + SplitView { + property var views: []; + + id: mainSplit + anchors.fill: parent + //resizing: false // this is NOT where we remove that damning resizing handle.. + handleDelegate: Item { + //This handle is a way to remove the line between the split views + Rectangle { + anchors.fill: parent + } + } + + 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.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: 192 + Layout.maximumWidth: 192 + + FontLoader { + id: sourceSansPro + source: "fonts/SourceSansPro-Regular.ttf" + } + FontLoader { + source: "fonts/SourceSansPro-Semibold.ttf" + } + FontLoader { + source: "fonts/SourceSansPro-Bold.ttf" + } + FontLoader { + source: "fonts/SourceSansPro-Black.ttf" + } + FontLoader { + source: "fonts/SourceSansPro-Light.ttf" + } + FontLoader { + source: "fonts/SourceSansPro-ExtraLight.ttf" + } + FontLoader { + id: simpleLineIcons + source: "fonts/Simple-Line-Icons.ttf" + } + + Rectangle { + color: "steelblue" + anchors.fill: parent + + MouseArea { + anchors.fill: parent + property real lastMouseX: 0 + property real lastMouseY: 0 + onPressed: { + lastMouseX = mouseX + lastMouseY = mouseY + } + onPositionChanged: { + root.x += (mouseX - lastMouseX) + root.y += (mouseY - lastMouseY) + } + /*onDoubleClicked: { + //!maximized ? view.set_max() : view.set_normal()} + visibility = "Minimized" + }*/ + } + } + + + + anchors.top: parent.top + Rectangle { + width: parent.height + height: parent.width + anchors.centerIn: parent + rotation: 90 + + gradient: Gradient { + GradientStop { position: 0.0; color: "#E2DEDE" } + GradientStop { position: 0.1; color: "#EBE8E8" } + GradientStop { position: 1.0; color: "#EBE8E8" } + } + } + + Component { + id: menuItemTemplate + Rectangle { + id: menuItem + property var view; + property var path; + property var closable; + property var badgeContent; + + property alias title: label.text + property alias icon: icon.source + property alias secondaryTitle: secondary.text + property alias badgeNumber: badgeNumberLabel.text + property alias badgeIcon: badgeIconLabel.text + + function setSelection(on) { + sel.visible = on + + if (this.closable == true) { + closeIcon.visible = on + } + } + + function setAsBigButton(on) { + newAppButton.visible = on + label.visible = !on + buttonLabel.visible = on + } + + width: 192 + height: 55 + color: "#00000000" + + anchors { + left: parent.left + leftMargin: 4 + } + + Rectangle { + // New App Button + id: newAppButton + visible: false + anchors.fill: parent + anchors.rightMargin: 8 + border.width: 0 + radius: 5 + height: 55 + width: 180 + color: "#F3F1F3" + } + + Rectangle { + id: sel + visible: false + anchors.fill: parent + color: "#00000000" + Rectangle { + id: r + anchors.fill: parent + border.width: 0 + radius: 5 + color: "#FAFAFA" + } + Rectangle { + anchors { + top: r.top + bottom: r.bottom + right: r.right + } + width: 10 + color: "#FAFAFA" + border.width:0 + + Rectangle { + // Small line on top of selection. What's this for? + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: 1 + color: "#FAFAFA" + } + + Rectangle { + // Small line on bottom of selection. What's this for again? + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 1 + color: "#FAFAFA" + } + } + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + onClicked: { + activeView(view, menuItem); + } + onEntered: { + if (parent.closable == true) { + closeIcon.visible = sel.visible + } + } + onExited: { + closeIcon.visible = false + } + } + + Image { + id: icon + height: 28 + width: 28 + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: 6 + } + } + + Text { + id: buttonLabel + visible: false + text: "GO TO NEW APP" + font.family: sourceSansPro.name + font.weight: Font.DemiBold + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: "#AAA0A0" + } + + Text { + id: label + font.family: sourceSansPro.name + font.weight: Font.DemiBold + elide: Text.ElideRight + x:250 + color: "#665F5F" + font.pixelSize: 14 + anchors { + left: icon.right + right: parent.right + verticalCenter: parent.verticalCenter + leftMargin: 6 + rightMargin: 8 + verticalCenterOffset: (secondaryTitle == "") ? 0 : -10; + } + + + + + } + + Text { + id: secondary + font.family: sourceSansPro.name + font.weight: Font.Light + anchors { + left: icon.right + leftMargin: 6 + top: label.bottom + } + color: "#6691C2" + font.pixelSize: 12 + } + + Rectangle { + id: closeIcon + visible: false + width: 10 + height: 10 + color: "#FAFAFA" + anchors { + fill: icon + } + + MouseArea { + anchors.fill: parent + onClicked: { + menuItem.closeApp() + } + } + + Text { + + font.family: simpleLineIcons.name + anchors { + centerIn: parent + } + color: "#665F5F" + font.pixelSize: 20 + text: "\ue082" + } + } + + Rectangle { + id: badge + visible: (badgeContent == "icon" || badgeContent == "number" )? true : false + width: 32 + color: "#05000000" + anchors { + right: parent.right; + top: parent.top; + bottom: parent.bottom; + rightMargin: 4; + } + + Text { + id: badgeIconLabel + visible: (badgeContent == "icon") ? true : false; + font.family: simpleLineIcons.name + anchors { + centerIn: parent + } + horizontalAlignment: Text.AlignCenter + color: "#AAA0A0" + font.pixelSize: 20 + text: badgeIcon + } + + Text { + id: badgeNumberLabel + visible: (badgeContent == "number") ? true : false; + anchors { + centerIn: parent + } + horizontalAlignment: Text.AlignCenter + font.family: sourceSansPro.name + font.weight: Font.Light + color: "#AAA0A0" + font.pixelSize: 18 + text: badgeNumber + } + } + + + + function closeApp() { + if(!this.closable) { return; } + + if(this.view.hasOwnProperty("onDestroy")) { + this.view.onDestroy.call(this.view) + } + + this.view.destroy() + this.destroy() + for (var i = 0; i < mainSplit.views.length; i++) { + var view = mainSplit.views[i]; + if (view.menuItem === this) { + mainSplit.views.splice(i, 1); + break; + } + } + gui.removePlugin(this.path) + activeView(mainSplit.views[0].view, mainSplit.views[0].menuItem); + } + } + } + + function createMenuItem(view, options) { + if(options === undefined) { + options = {}; + } + + var section; + switch(options.section) { + case "begin": + section = menuBegin + break; + 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; + + if (options.section === "begin") { + comp.setAsBigButton(true) + } + + return comp + } + + ColumnLayout { + id: menuColumn + y: 10 + width: parent.width + anchors.left: parent.left + anchors.right: parent.right + spacing: 3 + + + + ColumnLayout { + id: menuBegin + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + Rectangle { + height: 55 + color: "transparent" + Text { + text: "ETHEREUM" + font.family: sourceSansPro.name + font.weight: Font.DemiBold + anchors { + left: parent.left + top: parent.verticalCenter + leftMargin: 16 + } + color: "#AAA0A0" + } + } + + + ColumnLayout { + id: menuDefault + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + Rectangle { + height: 55 + color: "transparent" + Text { + text: "APPS" + font.family: sourceSansPro.name + font.weight: Font.DemiBold + anchors { + left: parent.left + top: parent.verticalCenter + leftMargin: 16 + } + color: "#AAA0A0" + } + } + + ColumnLayout { + id: menuApps + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + + Rectangle { + height: 55 + color: "transparent" + Text { + text: "DEBUG" + font.family: sourceSansPro.name + font.weight: Font.DemiBold + anchors { + left: parent.left + top: parent.verticalCenter + leftMargin: 16 + } + color: "#AAA0A0" + } + } + + + ColumnLayout { + id: menuLegacy + spacing: 3 + anchors { + left: parent.left + right: parent.right + } + } + } + } + + /********************* + * Main view + ********************/ + Rectangle { + id: rootView + 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)) { + newBrowserTab(this.text); + } 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: 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, {close: true, section: "apps"}) + } + } + + function setWalletValue(value) { + //walletValueLabel.text = value + } + + function loadPlugin(name) { + console.log("Loading plugin" + name) + var view = mainView.addPlugin(name) + } + + function clearPeers() { peerModel.clear() } + function addPeer(peer) { peerModel.append(peer) } + + function setPeerCounters(text) { + //peerCounterLabel.text = text + } + + 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: 180; role: "addr" ; title: "Remote Address" } + TableViewColumn{width: 280; role: "nodeID" ; title: "Node ID" } + TableViewColumn{width: 100; role: "name" ; title: "Name" } + TableViewColumn{width: 40; role: "caps" ; title: "Capabilities" } + } + } + } + + Window { + id: aboutWin + visible: false + title: "About" + minimumWidth: 350 + maximumWidth: 350 + maximumHeight: 280 + minimumHeight: 280 + + Image { + id: aboutIcon + height: 150 + width: 150 + fillMode: Image.PreserveAspectFit + smooth: true + source: "../facet.png" + x: 10 + y: 30 + } + + Text { + anchors.left: aboutIcon.right + anchors.leftMargin: 10 + anchors.top: parent.top + anchors.topMargin: 30 + font.pointSize: 12 + text: "<h2>Mist (0.8.6)</h2><br><h3>Development</h3>Jeffrey Wilcke<br>Viktor Trón<br>Felix Lange<br>Taylor Gerring<br>Daniel Nagy<br>Gustav Simonsson<br><h3>UX/UI</h3>Alex van de Sande<br>Fabian Vogelsteller" + } + } + + 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: 400 + maximumWidth: 400 + maximumHeight: 50 + minimumHeight: 50 + title: "Connect to peer" + + TextField { + id: addrField + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + anchors.right: addPeerButton.left + anchors.leftMargin: 10 + anchors.rightMargin: 10 + placeholderText: "enode://<hex node id>:<IP address>:<port>" + onAccepted: { + if(addrField.text.length != 0) { + eth.connectToPeer(addrField.text) + addPeerWin.visible = false + } + } + } + + Button { + id: addPeerButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + anchors.rightMargin: 10 + text: "Connect" + onClicked: { + if(addrField.text.length != 0) { + eth.connectToPeer(addrField.text) + 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/browser.qml b/cmd/mist/assets/qml/views/browser.qml new file mode 100644 index 000000000..edecc8696 --- /dev/null +++ b/cmd/mist/assets/qml/views/browser.qml @@ -0,0 +1,506 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Controls.Styles 1.0 +import QtQuick.Layouts 1.0; +import QtWebEngine 1.0 +import QtWebEngine.experimental 1.0 +import QtQuick.Window 2.0; + +Rectangle { + id: window + anchors.fill: parent + color: "#00000000" + + property var title: "" + property var iconSource: "../browser.png" + property var menuItem + property var hideUrl: true + + property alias url: webview.url + property alias windowTitle: webview.title + 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; + } + } + + function showFullUrlBar(on){ + if (uriNav.focus == false ) { + if (on == false) { + clickAnywhereOnApp.visible = false + navBar.state = "titleVisible" + } else { + clickAnywhereOnApp.visible = true + navBar.state = "fullUrlVisible" + } + } + + } + + Component.onCompleted: { + } + + Item { + objectName: "root" + id: root + anchors { + fill: parent + } + + state: "inspectorShown" + + MouseArea { + id: clickAnywhereOnApp + z:15 + // Using a secondary screen to catch on mouse exits for the area, because + // there are many hover actions conflicting + + anchors { + top: parent.top + topMargin: 50 + right: parent.right + bottom: parent.bottom + left: parent.left + } + hoverEnabled: true + + onEntered: { + showFullUrlBar(false); + } + + onClicked: { + uriNav.focus = false + showFullUrlBar(false); + } + + // Rectangle { + // anchors.fill: parent + // color: "#88888888" + // } + } + + RowLayout { + id: navBar + height: 74 + z: 20 + anchors { + left: parent.left + right: parent.right + } + + Button { + id: back + z: 30 + onClicked: { + webview.goBack() + } + + anchors { + left: parent.left + leftMargin: 6 + } + + style: ButtonStyle { + background: Image { + source: (webview.canGoBack) ? + (control.hovered ? "../../backButtonHover.png" : "../../backButton.png") : + "../../backButtonDisabled.png" + width: 20 + height: 30 + } + } + } + + Rectangle { + id: appInfoPane + height: 28 + color: "#FFFFFF" + radius: 6 + z:2 + MouseArea { + anchors.fill: parent + z: 10 + hoverEnabled: true + + onEntered: { + showFullUrlBar(true); + } + /*onExited: { + showFullUrlBar(false); + }*/ + + } + + anchors { + left: back.right + right: parent.right + leftMargin: 10 + rightMargin: 10 + } + + Text { + id: appTitle + text: "LOADING" + font.bold: true + font.capitalization: Font.AllUppercase + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + elide: Text.ElideRight + + anchors { + left: parent.left + right: parent.horizontalCenter + top: parent.top + bottom: parent.bottom + leftMargin: 32 + } + color: "#928484" + } + + Text { + id: appDomain + text: "loading domain" + font.bold: false + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + elide: Text.ElideLeft + + anchors { + left: parent.horizontalCenter + right: parent.right + top: parent.top + bottom: parent.bottom + leftMargin: 32 + + } + color: "#C0AFAF" + } + + + TextField { + id: uriNav + opacity: 0.0 + + anchors { + left: parent.left + right: parent.right + leftMargin: 16 + } + + horizontalAlignment: Text.AlignHCenter + + style: TextFieldStyle { + textColor: "#928484" + background: Rectangle { + border.width: 0 + color: "transparent" + } + } + text: webview.url; + y: parent.height / 2 - this.height / 2 + z: 20 + activeFocusOnPress: true + Keys.onReturnPressed: { + // if there's no http, add it. + var url = this.text, + matches = url.match(/^([a-z]*\:\/\/)?([^\/.]+)(:?\/)(.*|$)/i), + requestedProtocol = (matches && matches[1] != "undefined")? "" : "http://"; + + webview.url = requestedProtocol + url; + } + } + + + } + + Rectangle { + id: appInfoPaneShadow + width: 10 + height: 30 + color: "#BDB6B6" + radius: 6 + z:1 + + anchors { + left: back.right + right: parent.right + leftMargin:10 + rightMargin:10 + top: parent.top + topMargin: 23 + } + } + + Rectangle { + id: navBarBackground + anchors.fill: parent + z:-1 + gradient: Gradient { + GradientStop { position: 0.0; color: "#F6F1F2" } + GradientStop { position: 1.0; color: "#DED5D5" } + } + } + + states: [ + State { + name: "fullUrlVisible" + PropertyChanges { + target: appTitle + anchors.rightMargin: -50 + opacity: 0.0 + } + PropertyChanges { + target: appDomain + anchors.leftMargin: -50 + opacity: 0.0 + } + PropertyChanges { + target: uriNav + anchors.leftMargin: 0 + opacity: 1.0 + } + }, + State { + name: "titleVisible" + + PropertyChanges { + target: appTitle + anchors.rightMargin: 10 + opacity: 1.0 + } + PropertyChanges { + target: appDomain + anchors.leftMargin: 10 + opacity: 1.0 + } + PropertyChanges { + target: uriNav + anchors.leftMargin: -50 + opacity: 0.0 + } + } + + ] + + transitions: [ + // This adds a transition that defaults to applying to all state changes + + Transition { + + // This applies a default NumberAnimation to any changes a state change makes to x or y properties + NumberAnimation { + properties: "anchors.leftMargin, anchors.rightMargin, opacity" + easing.type: Easing.InOutQuad //Easing.InOutBack + duration: 300 + } + } + ] + + } + + WebEngineView { + objectName: "webView" + id: webview + experimental.settings.javascriptCanAccessClipboard: true + //experimental.settings.localContentCanAccessRemoteUrls: true + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + top: navBar.bottom + } + z: 10 + + Timer { + interval: 2000; running: true; repeat: true + onTriggered: { + webview.runJavaScript("try{document.querySelector('meta[name=ethereum-dapp-info]').getAttribute('content')}catch(e){}", function(extraInfo) { + if (extraInfo) { + menuItem.secondaryTitle = extraInfo; + } + }); + webview.runJavaScript("try{document.querySelector('meta[name=ethereum-dapp-badge]').getAttribute('content')}catch(e){}", function(badge) { + if (badge) { + if (Number(badge)>0 && Number(badge)<999) { + menuItem.badgeNumber = Number(badge); + menuItem.badgeContent = "number" + } else if (badge == "warning") { + menuItem.badgeIcon = "\ue00e" + menuItem.badgeContent = "icon" + + } else if (badge == "ghost") { + menuItem.badgeIcon = "\ue01a" + menuItem.badgeContent = "icon" + + } else if (badge == "question") { + menuItem.badgeIcon = "\ue05d" + menuItem.badgeContent = "icon" + + } else if (badge == "info") { + menuItem.badgeIcon = "\ue08b" + menuItem.badgeContent = "icon" + + } else if (badge == "check") { + menuItem.badgeIcon = "\ue080" + menuItem.badgeContent = "icon" + + } else if (badge == "gear") { + menuItem.badgeIcon = "\ue09a" + menuItem.badgeContent = "icon" + + } else { + menuItem.badgeContent = "" + } + } else { + menuItem.badgeContent = "" + } + }); + } + } + + onLoadingChanged: { + if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + + webview.runJavaScript("document.title", function(pageTitle) { + menuItem.title = pageTitle; + }); + + webView.runJavaScript("try{document.querySelector(\"link[rel='icon']\").getAttribute(\"href\")}catch(e){}", function(sideIcon){ + if(sideIcon){ + menuItem.icon = webview.url + sideIcon; + }; + }); + + webView.runJavaScript("try{document.querySelector(\"meta[name='ethereum-dapp-url-bar-style']\").getAttribute(\"content\")}catch(e){}", function(topBarStyle){ + if (!topBarStyle) { + showFullUrlBar(true); + navBarBackground.visible = true; + back.visible = true; + appInfoPane.anchors.leftMargin = 0; + appInfoPaneShadow.anchors.leftMargin = 0; + webview.anchors.topMargin = 0; + return; + } + + if (topBarStyle=="transparent") { + // Adjust for a transparent sidebar Dapp + navBarBackground.visible = false; + back.visible = false; + appInfoPane.anchors.leftMargin = -16; + appInfoPaneShadow.anchors.leftMargin = -16; + webview.anchors.topMargin = -74; + webview.runJavaScript("document.querySelector('body').classList.add('ethereum-dapp-url-bar-style-transparent')") + + } else { + navBarBackground.visible = true; + back.visible = true; + appInfoPane.anchors.leftMargin = 0; + appInfoPaneShadow.anchors.leftMargin = 0; + webview.anchors.topMargin = 0; + }; + }); + + + webview.runJavaScript(eth.readFile("bignumber.min.js")); + webview.runJavaScript(eth.readFile("ethereum.js/dist/ethereum.js")); + webview.runJavaScript(eth.readFile("mist.js")); + + var cleanTitle = webview.url.toString() + var matches = cleanTitle.match(/^[a-z]*\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); + var domain = matches && matches[1]; + + + if (domain) + appDomain.text = domain //webview.url.replace("a", "z") + if (webview.title) + appTitle.text = webview.title + + showFullUrlBar(false); + } + } + onJavaScriptConsoleMessage: { + console.log(sourceID + ":" + lineNumber + ":" + JSON.stringify(message)); + } + } + + 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 + } + } + + WebEngineView { + id: inspector + visible: false + anchors { + left: root.left + right: root.right + top: sizeGrip.bottom + bottom: root.bottom + } + + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + } + } + ] + } +} diff --git a/cmd/mist/assets/qml/views/catalog.qml b/cmd/mist/assets/qml/views/catalog.qml new file mode 100644 index 000000000..29e133074 --- /dev/null +++ b/cmd/mist/assets/qml/views/catalog.qml @@ -0,0 +1,144 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.0; +import QtQuick.Controls.Styles 1.0 +import QtQuick.Layouts 1.0; +import QtWebEngine 1.0 +import QtWebEngine.experimental 1.0 +import QtQuick.Window 2.0; + + +Rectangle { + id: window + anchors.fill: parent + color: "#00000000" + + property var title: "Catalog" + property var iconSource: "" + property var menuItem + property var hideUrl: true + + property alias url: webview.url + property alias windowTitle: webview.title + 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; + } + } + + Item { + objectName: "root" + id: root + anchors.fill: parent + state: "inspectorShown" + + WebEngineView { + objectName: "webView" + id: webview + anchors.fill: parent + + property var protocol: "http://" + //property var domain: "localhost:3000" + property var domain: "ethereum-dapp-catalog.meteor.com" + url: protocol + domain + + experimental.settings.javascriptCanAccessClipboard: true + + + onJavaScriptConsoleMessage: { + console.log(sourceID + ":" + lineNumber + ":" + JSON.stringify(message)); + } + + onNavigationRequested: { + // this checks if the domain of the requested link is the same as the catalog's + // If it is, it opens on the same window, if it's not it opens a new tab + + var cleanTitle = request.url.toString() + var matches = cleanTitle.match(/^[a-z]*\:\/\/([^\/?#]+)(?:[\/?#]|$)/i); + var requestedDomain = matches && matches[1]; + + + if(request.navigationType==0){ + + if (requestedDomain === this.domain){ + request.action = WebEngineView.AcceptRequest; + } else { + request.action = WebEngineView.IgnoreRequest; + newBrowserTab(request.url); + } + + } + } + onLoadingChanged: { + if (loadRequest.status == WebEngineView.LoadSucceededStatus) { + webview.runJavaScript(eth.readFile("mist.js")); + } + } + } + + + + + + + WebEngineView { + id: inspector + visible: false + z:10 + anchors { + left: root.left + right: root.right + top: root.top + bottom: root.bottom + } + + } + + states: [ + State { + name: "inspectorShown" + PropertyChanges { + target: inspector + } + } + ] + } +} diff --git a/cmd/mist/assets/qml/views/chain.qml b/cmd/mist/assets/qml/views/chain.qml new file mode 100644 index 000000000..4d1bc0e03 --- /dev/null +++ b/cmd/mist/assets/qml/views/chain.qml @@ -0,0 +1,261 @@ +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({raw: block.raw, bloom: block.bloom, 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, {raw: block.raw, bloom: block.bloom, 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>Bloom:</b> ' + bloom; 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.right: parent.right + wrapMode: Text.Wrap + height: 80 + } + TextArea { + id: dumpData + anchors.top: contractData.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 300 + } + } + 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() + dumpData.text = bl.raw; + } + } +} 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..b2d2f521c --- /dev/null +++ b/cmd/mist/assets/qml/views/info.qml @@ -0,0 +1,184 @@ +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.coinbase() + width: 500 + } + + 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/miner.qml b/cmd/mist/assets/qml/views/miner.qml new file mode 100644 index 000000000..e239c7d7b --- /dev/null +++ b/cmd/mist/assets/qml/views/miner.qml @@ -0,0 +1,294 @@ +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: "Miner" + property var iconSource: "../mining-icon.png" + property var menuItem + + color: "#00000000" + + Label { + visible: false + id: lastBlockLabel + objectName: "lastBlockLabel" + text: "---" + onTextChanged: { + //menuItem.secondaryTitle = text + } + } + + Label { + objectName: "miningLabel" + visible: false + font.pixelSize: 10 + anchors.right: lastBlockLabel.left + anchors.rightMargin: 5 + onTextChanged: { + menuItem.secondaryTitle = text + } + } + + ColumnLayout { + spacing: 10 + anchors.fill: parent + + Rectangle { + id: mainPane + color: "#00000000" + anchors { + top: parent.top + bottom: localTxPane.top + left: parent.left + right: parent.right + } + + Rectangle { + id: menu + height: 25 + anchors { + left: parent.left + } + + RowLayout { + id: tools + anchors { + left: parent.left + right: parent.right + } + + Button { + text: "Start" + onClicked: { + eth.setGasPrice(minGasPrice.text || "10000000000000"); + eth.setExtra(blockExtra.text) + if (eth.toggleMining()) { + this.text = "Stop"; + } else { + this.text = "Start"; + } + } + } + + Rectangle { + id: minGasPriceRect + anchors.top: parent.top + anchors.topMargin: 2 + width: 200 + TextField { + id: minGasPrice + placeholderText: "Min Gas: 10000000000000" + width: 200 + validator: RegExpValidator { regExp: /\d*/ } + } + } + + Rectangle { + width: 300 + anchors { + left: minGasPriceRect.right + leftMargin: 5 + top: parent.top + topMargin: 2 + } + + TextField { + id: blockExtra + placeholderText: "Extra" + width: parent.width + maximumLength: 1024 + } + } + } + } + + Column { + anchors { + left: parent.left + right: parent.right + top: menu.bottom + topMargin: 5 + } + + Text { + text: "<b>Merged mining options</b>" + } + + TableView { + id: mergedMiningTable + height: 300 + anchors { + left: parent.left + right: parent.right + } + Component { + id: checkBoxDelegate + + Item { + id: test + CheckBox { + anchors.fill: parent + checked: styleData.value + + onClicked: { + var model = mergedMiningModel.get(styleData.row) + + if (this.checked) { + model.id = txModel.createLocalTx(model.address, "0", "5000", "0", "") + } else { + txModel.removeWithId(model.id); + model.id = 0; + } + } + } + } + } + TableViewColumn{ role: "checked" ; title: "" ; width: 40 ; delegate: checkBoxDelegate } + TableViewColumn{ role: "name" ; title: "Name" ; width: 480 } + model: ListModel { + objectName: "mergedMiningModel" + id: mergedMiningModel + function addMergedMiningOption(model) { + this.append(model); + } + } + Component.onCompleted: { + /* + // XXX Temp. replace with above eventually + var tmpItems = ["JEVCoin", "Some coin", "Other coin", "Etc coin"]; + var address = "e6716f9544a56c530d868e4bfbacb172315bdead"; + for (var i = 0; i < tmpItems.length; i++) { + mergedMiningModel.append({checked: false, name: tmpItems[i], address: address, id: 0, itemId: i}); + } + */ + } + } + } + } + + Rectangle { + id: localTxPane + color: "#ececec" + border.color: "#cccccc" + border.width: 1 + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 300 + + ColumnLayout { + spacing: 10 + anchors.fill: parent + RowLayout { + id: newLocalTx + anchors { + left: parent.left + leftMargin: 5 + top: parent.top + topMargin: 5 + bottomMargin: 5 + } + + Text { + text: "Local tx" + } + + Rectangle { + width: 250 + color: "#00000000" + anchors.top: parent.top + anchors.topMargin: 2 + + TextField { + id: to + placeholderText: "To" + width: 250 + validator: RegExpValidator { regExp: /[abcdefABCDEF1234567890]*/ } + } + } + TextField { + property var defaultGas: "5000" + id: gas + placeholderText: "Gas" + text: defaultGas + validator: RegExpValidator { regExp: /\d*/ } + } + TextField { + id: gasPrice + placeholderText: "Price" + validator: RegExpValidator { regExp: /\d*/ } + } + TextField { + id: value + placeholderText: "Amount" + text: "0" + validator: RegExpValidator { regExp: /\d*/ } + } + TextField { + id: data + placeholderText: "Data" + validator: RegExpValidator { regExp: /[abcdefABCDEF1234567890]*/ } + } + Button { + text: "Create" + onClicked: { + if (to.text.length == 40 && gasPrice.text.length != 0 && value.text.length != 0 && gas.text.length != 0) { + txModel.createLocalTx(to.text, gasPrice.text, gas.text, value.text, data.text); + + to.text = ""; gasPrice.text = ""; + gas.text = gas.defaultGas; + value.text = "0" + } + } + } + } + + TableView { + id: txTableView + anchors { + top: newLocalTx.bottom + topMargin: 5 + left: parent.left + right: parent.right + bottom: parent.bottom + } + TableViewColumn{ role: "to" ; title: "To" ; width: 480 } + TableViewColumn{ role: "gas" ; title: "Gas" ; width: 100 } + TableViewColumn{ role: "gasPrice" ; title: "Gas Price" ; width: 100 } + TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } + TableViewColumn{ role: "data" ; title: "Data" ; width: 100 } + + model: ListModel { + id: txModel + Component.onCompleted: { + } + function removeWithId(id) { + for (var i = 0; i < this.count; i++) { + if (txModel.get(i).id == id) { + this.remove(i); + eth.removeLocalTransaction(id) + break; + } + } + } + + function createLocalTx(to, gasPrice, gas, value, data) { + var id = eth.addLocalTransaction(to, data, gas, gasPrice, value) + txModel.insert(0, {to: to, gas: gas, gasPrice: gasPrice, value: value, data: data, id: id}); + + return id + } + } + } + } + } + } +} 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..3dcedeff2 --- /dev/null +++ b/cmd/mist/assets/qml/views/pending_tx.qml @@ -0,0 +1,53 @@ +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}) + } + + function removeTx(tx) { + for (var i = 0; i < pendingTxModel.count; i++) { + if (tx.hash === pendingTxModel.get(i).hash) { + pendingTxModel.remove(i); + break; + } + } + } +} diff --git a/cmd/mist/assets/qml/views/transaction.qml b/cmd/mist/assets/qml/views/transaction.qml new file mode 100644 index 000000000..df798a9c0 --- /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: 5 + model: denomModel + } + } + + RowLayout { + TextField { + id: txGas + width: 50 + validator: RegExpValidator { regExp: /\d*/ } + placeholderText: "Gas" + text: "5000" + } + 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.toString() + 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..2369390c3 --- /dev/null +++ b/cmd/mist/assets/qml/views/wallet.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 +import Ethereum 1.0 + +Rectangle { + id: root + property var title: "Wallet" + property var menuItem + + objectName: "walletView" + anchors.fill: parent + + Label { + objectName: "balanceLabel" + visible: false + onTextChanged: { + balance.text = text + menuItem.secondaryTitle = text + } + } + + function onReady() { + setBalance() + } + + function setBalance() { + balance.text = "<b>Balance</b>: " + eth.numberToHuman(eth.balanceAt(eth.coinbase())) + if(menuItem) + menuItem.secondaryTitle = eth.numberToHuman(eth.balanceAt(eth.coinbase())) + } + + 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 + 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: 5 + 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.coinbase(), to: txTo.text, value: value, gas: "500", gasPrice: gasPrice}) + } + } + } + } + + 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: 340 } + TableViewColumn{ role: "to" ; title: "To" ; width: 340 } + TableViewColumn{ role: "value" ; title: "Amount" ; width: 100 } + + model: ListModel { + id: txModel + Component.onCompleted: { + } + + function addTxs(messages) { + /* + setBalance() + + for(var i = 0; i < messages.length; i++) { + var message = messages.get(i); + var to = eth.lookupName(message.to); + var from; + if(message.from.length == 0) { + from = "- MINED -"; + } else { + 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/views/whisper.qml b/cmd/mist/assets/qml/views/whisper.qml new file mode 100644 index 000000000..dc097b806 --- /dev/null +++ b/cmd/mist/assets/qml/views/whisper.qml @@ -0,0 +1,74 @@ + +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: "Whisper Traffic" + property var menuItem + + objectName: "whisperView" + anchors.fill: parent + + property var identity: "" + Component.onCompleted: { + identity = shh.newIdentity() + + var t = shh.watch({}, root) + } + + function onShhMessage(message, i) { + whisperModel.insert(0, {from: message.from, payload: eth.toAscii(message.payload)}) + } + + RowLayout { + id: input + anchors { + left: parent.left + leftMargin: 20 + top: parent.top + topMargin: 20 + } + + TextField { + id: to + placeholderText: "To" + } + TextField { + id: data + placeholderText: "Data" + } + TextField { + id: topics + placeholderText: "topic1, topic2, topic3, ..." + } + Button { + text: "Send" + onClicked: { + shh.post([eth.toHex(data.text)], "", identity, topics.text.split(","), 500, 50) + } + } + } + + TableView { + id: txTableView + anchors { + top: input.bottom + topMargin: 10 + bottom: parent.bottom + left: parent.left + right: parent.right + } + TableViewColumn{ id: fromRole; role: "from" ; title: "From"; width: 300 } + TableViewColumn{ role: "payload" ; title: "Payload" ; width: parent.width - fromRole.width - 2 } + + model: ListModel { + id: whisperModel + } + } +} |