|
|
|
import QtQuick 2.2
|
|
|
|
import QtQuick.Controls 1.1
|
|
|
|
import QtQuick.Dialogs 1.2
|
|
|
|
import QtQuick.Layouts 1.1
|
|
|
|
import QtQuick.Window 2.0
|
|
|
|
import QtQuick.Controls.Styles 1.3
|
|
|
|
import org.ethereum.qml.QEther 1.0
|
|
|
|
import "js/QEtherHelper.js" as QEtherHelper
|
|
|
|
import "js/TransactionHelper.js" as TransactionHelper
|
|
|
|
import "."
|
|
|
|
|
|
|
|
Dialog {
|
|
|
|
id: modalStateDialog
|
|
|
|
modality: Qt.ApplicationModal
|
|
|
|
|
|
|
|
width: 630
|
|
|
|
height: 660
|
|
|
|
title: qsTr("Edit State")
|
|
|
|
visible: false
|
|
|
|
|
|
|
|
property alias stateTitle: titleField.text
|
|
|
|
property alias isDefault: defaultCheckBox.checked
|
|
|
|
property alias model: transactionsModel
|
|
|
|
property alias transactionDialog: transactionDialog
|
|
|
|
property alias minerComboBox: comboMiner
|
|
|
|
property alias newAccAction: newAccountAction
|
|
|
|
property int stateIndex
|
|
|
|
property var stateTransactions: []
|
|
|
|
property var stateAccounts: []
|
|
|
|
property var stateContracts: []
|
|
|
|
signal accepted
|
|
|
|
|
|
|
|
StateDialogStyle {
|
|
|
|
id: stateDialogStyle
|
|
|
|
}
|
|
|
|
|
|
|
|
function open(index, item, setDefault) {
|
|
|
|
stateIndex = index
|
|
|
|
stateTitle = item.title
|
|
|
|
transactionsModel.clear()
|
|
|
|
|
|
|
|
stateTransactions = []
|
|
|
|
var transactions = item.transactions
|
|
|
|
for (var t = 0; t < transactions.length; t++) {
|
|
|
|
transactionsModel.append(item.transactions[t])
|
|
|
|
stateTransactions.push(item.transactions[t])
|
|
|
|
}
|
|
|
|
|
|
|
|
accountsModel.clear()
|
|
|
|
stateAccounts = []
|
|
|
|
var miner = 0
|
|
|
|
for (var k = 0; k < item.accounts.length; k++) {
|
|
|
|
accountsModel.append(item.accounts[k])
|
|
|
|
stateAccounts.push(item.accounts[k])
|
|
|
|
if (item.miner && item.accounts[k].name === item.miner.name)
|
|
|
|
miner = k
|
|
|
|
}
|
|
|
|
|
|
|
|
stateContracts = []
|
|
|
|
if (item.contracts) {
|
|
|
|
for (k = 0; k < item.contracts.length; k++) {
|
|
|
|
contractsModel.append(item.contracts[k])
|
|
|
|
stateContracts.push(item.contracts[k])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
visible = true
|
|
|
|
isDefault = setDefault
|
|
|
|
titleField.focus = true
|
|
|
|
defaultCheckBox.enabled = !isDefault
|
|
|
|
comboMiner.model = stateAccounts
|
|
|
|
comboMiner.currentIndex = miner
|
|
|
|
forceActiveFocus()
|
|
|
|
}
|
|
|
|
|
|
|
|
function acceptAndClose() {
|
|
|
|
close()
|
|
|
|
accepted()
|
|
|
|
}
|
|
|
|
|
|
|
|
function close() {
|
|
|
|
visible = false
|
|
|
|
}
|
|
|
|
|
|
|
|
function getItem() {
|
|
|
|
var item = {
|
|
|
|
title: stateDialog.stateTitle,
|
|
|
|
transactions: stateTransactions,
|
|
|
|
accounts: stateAccounts,
|
|
|
|
contracts: stateContracts
|
|
|
|
}
|
|
|
|
for (var k = 0; k < stateAccounts.length; k++) {
|
|
|
|
if (stateAccounts[k].name === comboMiner.currentText) {
|
|
|
|
item.miner = stateAccounts[k]
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return item
|
|
|
|
}
|
|
|
|
|
|
|
|
contentItem: Rectangle {
|
|
|
|
color: stateDialogStyle.generic.backgroundColor
|
|
|
|
Rectangle {
|
|
|
|
color: stateDialogStyle.generic.backgroundColor
|
|
|
|
anchors.top: parent.top
|
|
|
|
anchors.margins: 10
|
|
|
|
anchors.fill: parent
|
|
|
|
ColumnLayout {
|
|
|
|
anchors.fill: parent
|
|
|
|
anchors.margins: 10
|
|
|
|
ColumnLayout {
|
|
|
|
id: dialogContent
|
|
|
|
anchors.top: parent.top
|
|
|
|
RowLayout {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
DefaultLabel {
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
text: qsTr("Title")
|
|
|
|
}
|
|
|
|
DefaultTextField {
|
|
|
|
id: titleField
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CommonSeparator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
DefaultLabel {
|
|
|
|
id: contractsLabel
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
wrapMode: Text.WrapAnywhere
|
|
|
|
text: qsTr("Genesis\nContracts")
|
|
|
|
}
|
|
|
|
|
|
|
|
Button {
|
|
|
|
id: importStateButton
|
|
|
|
anchors.top: contractsLabel.bottom
|
|
|
|
anchors.topMargin: 10
|
|
|
|
action: importStateAction
|
|
|
|
}
|
|
|
|
|
|
|
|
Action {
|
|
|
|
id: importStateAction
|
|
|
|
tooltip: qsTr("Import genesis state from JSON file")
|
|
|
|
text: qsTr("Import...")
|
|
|
|
onTriggered: {
|
|
|
|
importJsonFileDialog.open()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
FileDialog {
|
|
|
|
id: importJsonFileDialog
|
|
|
|
visible: false
|
|
|
|
title: qsTr("Select State File")
|
|
|
|
nameFilters: Qt.platform.os === "osx" ? [] : [qsTr("JSON files (*.json)", "All files (*)")] //qt 5.4 segfaults with filter string on OSX
|
|
|
|
onAccepted: {
|
|
|
|
var path = importJsonFileDialog.fileUrl.toString()
|
|
|
|
var jsonData = fileIo.readFile(path)
|
|
|
|
if (jsonData) {
|
|
|
|
var json = JSON.parse(jsonData)
|
|
|
|
for (var address in json) {
|
|
|
|
var account = {
|
|
|
|
address: address,
|
|
|
|
name: (json[address].name ? json[address].name : address),
|
|
|
|
balance: QEtherHelper.createEther(json[address].wei, QEther.Wei),
|
|
|
|
code: json[address].code,
|
|
|
|
storage: json[address].storage
|
|
|
|
}
|
|
|
|
if (account.code) {
|
|
|
|
contractsModel.append(account)
|
|
|
|
stateContracts.push(account)
|
|
|
|
} else {
|
|
|
|
accountsModel.append(account)
|
|
|
|
stateAccounts.push(account)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TableView {
|
|
|
|
id: genesisContractsView
|
|
|
|
Layout.fillWidth: true
|
|
|
|
model: contractsModel
|
|
|
|
headerVisible: false
|
|
|
|
TableViewColumn {
|
|
|
|
role: "name"
|
|
|
|
title: qsTr("Name")
|
|
|
|
width: 230
|
|
|
|
delegate: Item {
|
|
|
|
RowLayout {
|
|
|
|
height: 25
|
|
|
|
width: parent.width
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
Button {
|
|
|
|
iconSource: "qrc:/qml/img/delete_sign.png"
|
|
|
|
action: deleteContractAction
|
|
|
|
}
|
|
|
|
|
|
|
|
Action {
|
|
|
|
id: deleteContractAction
|
|
|
|
tooltip: qsTr("Delete Contract")
|
|
|
|
onTriggered: {
|
|
|
|
stateContracts.splice(styleData.row, 1)
|
|
|
|
contractsModel.remove(styleData.row)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DefaultTextField {
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
onTextChanged: {
|
|
|
|
if (styleData.row > -1)
|
|
|
|
stateContracts[styleData.row].name = text
|
|
|
|
}
|
|
|
|
text: styleData.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TableViewColumn {
|
|
|
|
role: "balance"
|
|
|
|
title: qsTr("Balance")
|
|
|
|
width: 200
|
|
|
|
delegate: Item {
|
|
|
|
Ether {
|
|
|
|
edit: true
|
|
|
|
displayFormattedValue: false
|
|
|
|
value: styleData.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rowDelegate: Rectangle {
|
|
|
|
color: styleData.alternate ? "transparent" : "#f0f0f0"
|
|
|
|
height: 30
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CommonSeparator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
DefaultLabel {
|
|
|
|
id: accountsLabel
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
text: qsTr("Accounts")
|
|
|
|
}
|
|
|
|
|
|
|
|
Button {
|
|
|
|
id: newAccountButton
|
|
|
|
anchors.top: accountsLabel.bottom
|
|
|
|
anchors.topMargin: 10
|
|
|
|
iconSource: "qrc:/qml/img/plus.png"
|
|
|
|
action: newAccountAction
|
|
|
|
}
|
|
|
|
|
|
|
|
Action {
|
|
|
|
id: newAccountAction
|
|
|
|
tooltip: qsTr("Add new Account")
|
|
|
|
onTriggered: {
|
|
|
|
add()
|
|
|
|
}
|
|
|
|
|
|
|
|
function add() {
|
|
|
|
var account = stateListModel.newAccount(
|
|
|
|
"1000000", QEther.Ether)
|
|
|
|
stateAccounts.push(account)
|
|
|
|
accountsModel.append(account)
|
|
|
|
return account
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageDialog {
|
|
|
|
id: alertAlreadyUsed
|
|
|
|
text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.")
|
|
|
|
icon: StandardIcon.Warning
|
|
|
|
standardButtons: StandardButton.Ok
|
|
|
|
}
|
|
|
|
|
|
|
|
TableView {
|
|
|
|
id: accountsView
|
|
|
|
Layout.fillWidth: true
|
|
|
|
model: accountsModel
|
|
|
|
headerVisible: false
|
|
|
|
TableViewColumn {
|
|
|
|
role: "name"
|
|
|
|
title: qsTr("Name")
|
|
|
|
width: 230
|
|
|
|
delegate: Item {
|
|
|
|
RowLayout {
|
|
|
|
height: 25
|
|
|
|
width: parent.width
|
|
|
|
Button {
|
|
|
|
iconSource: "qrc:/qml/img/delete_sign.png"
|
|
|
|
action: deleteAccountAction
|
|
|
|
}
|
|
|
|
|
|
|
|
Action {
|
|
|
|
id: deleteAccountAction
|
|
|
|
tooltip: qsTr("Delete Account")
|
|
|
|
onTriggered: {
|
|
|
|
if (transactionsModel.isUsed(
|
|
|
|
stateAccounts[styleData.row].secret))
|
|
|
|
alertAlreadyUsed.open()
|
|
|
|
else {
|
|
|
|
if (stateAccounts[styleData.row].name
|
|
|
|
=== comboMiner.currentText)
|
|
|
|
comboMiner.currentIndex = 0
|
|
|
|
stateAccounts.splice(
|
|
|
|
styleData.row,
|
|
|
|
1)
|
|
|
|
accountsModel.remove(
|
|
|
|
styleData.row)
|
|
|
|
comboMiner.model = stateAccounts //TODO: filter accounts wo private keys
|
|
|
|
comboMiner.update()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DefaultTextField {
|
|
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
|
|
onTextChanged: {
|
|
|
|
if (styleData.row > -1) {
|
|
|
|
stateAccounts[styleData.row].name = text
|
|
|
|
var index = comboMiner.currentIndex
|
|
|
|
comboMiner.model = stateAccounts
|
|
|
|
comboMiner.currentIndex = index
|
|
|
|
}
|
|
|
|
}
|
|
|
|
text: {
|
|
|
|
return styleData.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TableViewColumn {
|
|
|
|
role: "balance"
|
|
|
|
title: qsTr("Balance")
|
|
|
|
width: 200
|
|
|
|
delegate: Item {
|
|
|
|
Ether {
|
|
|
|
edit: true
|
|
|
|
displayFormattedValue: false
|
|
|
|
value: styleData.value
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rowDelegate: Rectangle {
|
|
|
|
color: styleData.alternate ? "transparent" : "#f0f0f0"
|
|
|
|
height: 30
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CommonSeparator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
DefaultLabel {
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
text: qsTr("Miner")
|
|
|
|
}
|
|
|
|
ComboBox {
|
|
|
|
id: comboMiner
|
|
|
|
textRole: "name"
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CommonSeparator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
DefaultLabel {
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
text: qsTr("Default")
|
|
|
|
}
|
|
|
|
CheckBox {
|
|
|
|
id: defaultCheckBox
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CommonSeparator {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
}
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
Layout.fillWidth: true
|
|
|
|
|
|
|
|
Rectangle {
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
DefaultLabel {
|
|
|
|
id: transactionsLabel
|
|
|
|
Layout.preferredWidth: 85
|
|
|
|
text: qsTr("Transactions")
|
|
|
|
}
|
|
|
|
|
|
|
|
Button {
|
|
|
|
anchors.top: transactionsLabel.bottom
|
|
|
|
anchors.topMargin: 10
|
|
|
|
iconSource: "qrc:/qml/img/plus.png"
|
|
|
|
action: newTrAction
|
|
|
|
}
|
|
|
|
|
|
|
|
Action {
|
|
|
|
id: newTrAction
|
|
|
|
tooltip: qsTr("Create a new transaction")
|
|
|
|
onTriggered: transactionsModel.addTransaction()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TableView {
|
|
|
|
id: transactionsView
|
|
|
|
Layout.fillWidth: true
|
|
|
|
model: transactionsModel
|
|
|
|
headerVisible: false
|
|
|
|
TableViewColumn {
|
|
|
|
role: "label"
|
|
|
|
title: qsTr("Name")
|
|
|
|
width: 150
|
|
|
|
delegate: Item {
|
|
|
|
RowLayout {
|
|
|
|
height: 30
|
|
|
|
width: parent.width
|
|
|
|
Button {
|
|
|
|
iconSource: "qrc:/qml/img/delete_sign.png"
|
|
|
|
action: deleteTransactionAction
|
|
|
|
}
|
|
|
|
|
|
|
|
Action {
|
|
|
|
id: deleteTransactionAction
|
|
|
|
tooltip: qsTr("Delete")
|
|
|
|
onTriggered: transactionsModel.deleteTransaction(
|
|
|
|
styleData.row)
|
|
|
|
}
|
|
|
|
|
|
|
|
Button {
|
|
|
|
iconSource: "qrc:/qml/img/edit.png"
|
|
|
|
action: editAction
|
|
|
|
visible: styleData.row
|
|
|
|
>= 0 ? !transactionsModel.get(
|
|
|
|
styleData.row).stdContract : false
|
|
|
|
width: 10
|
|
|
|
height: 10
|
|
|
|
Action {
|
|
|
|
id: editAction
|
|
|
|
tooltip: qsTr("Edit")
|
|
|
|
onTriggered: transactionsModel.editTransaction(
|
|
|
|
styleData.row)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
DefaultLabel {
|
|
|
|
Layout.preferredWidth: 150
|
|
|
|
text: {
|
|
|
|
if (styleData.row >= 0)
|
|
|
|
return transactionsModel.get(
|
|
|
|
styleData.row).label
|
|
|
|
else
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rowDelegate: Rectangle {
|
|
|
|
color: styleData.alternate ? "transparent" : "#f0f0f0"
|
|
|
|
height: 30
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
RowLayout {
|
|
|
|
anchors.bottom: parent.bottom
|
|
|
|
anchors.right: parent.right
|
|
|
|
|
|
|
|
Button {
|
|
|
|
text: qsTr("Delete")
|
|
|
|
enabled: !modalStateDialog.isDefault
|
|
|
|
onClicked: {
|
|
|
|
projectModel.stateListModel.deleteState(stateIndex)
|
|
|
|
close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Button {
|
|
|
|
text: qsTr("OK")
|
|
|
|
onClicked: {
|
|
|
|
if (titleField.text === "")
|
|
|
|
alertDialog.open()
|
|
|
|
else
|
|
|
|
{
|
|
|
|
close()
|
|
|
|
accepted()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Button {
|
|
|
|
text: qsTr("Cancel")
|
|
|
|
onClicked: close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageDialog
|
|
|
|
{
|
|
|
|
id: alertDialog
|
|
|
|
text: qsTr("Please provide a name.")
|
|
|
|
}
|
|
|
|
|
|
|
|
ListModel {
|
|
|
|
id: accountsModel
|
|
|
|
|
|
|
|
function removeAccount(_i) {
|
|
|
|
accountsModel.remove(_i)
|
|
|
|
stateAccounts.splice(_i, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ListModel {
|
|
|
|
id: contractsModel
|
|
|
|
|
|
|
|
function removeContract(_i) {
|
|
|
|
contractsModel.remove(_i)
|
|
|
|
stateContracts.splice(_i, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ListModel {
|
|
|
|
id: transactionsModel
|
|
|
|
|
|
|
|
function editTransaction(index) {
|
|
|
|
transactionDialog.stateAccounts = stateAccounts
|
|
|
|
transactionDialog.open(index,
|
|
|
|
transactionsModel.get(index))
|
|
|
|
}
|
|
|
|
|
|
|
|
function addTransaction() {
|
|
|
|
// Set next id here to work around Qt bug
|
|
|
|
// https://bugreports.qt-project.org/browse/QTBUG-41327
|
|
|
|
// Second call to signal handler would just edit the item that was just created, no harm done
|
|
|
|
var item = TransactionHelper.defaultTransaction()
|
|
|
|
transactionDialog.stateAccounts = stateAccounts
|
|
|
|
transactionDialog.open(transactionsModel.count, item)
|
|
|
|
}
|
|
|
|
|
|
|
|
function deleteTransaction(index) {
|
|
|
|
stateTransactions.splice(index, 1)
|
|
|
|
transactionsModel.remove(index)
|
|
|
|
}
|
|
|
|
|
|
|
|
function isUsed(secret) {
|
|
|
|
for (var i in stateTransactions) {
|
|
|
|
if (stateTransactions[i].sender === secret)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TransactionDialog {
|
|
|
|
id: transactionDialog
|
|
|
|
onAccepted: {
|
|
|
|
var item = transactionDialog.getItem()
|
|
|
|
if (transactionDialog.transactionIndex < transactionsModel.count) {
|
|
|
|
transactionsModel.set(
|
|
|
|
transactionDialog.transactionIndex,
|
|
|
|
item)
|
|
|
|
stateTransactions[transactionDialog.transactionIndex] = item
|
|
|
|
} else {
|
|
|
|
transactionsModel.append(item)
|
|
|
|
stateTransactions.push(item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|