You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
526 lines
11 KiB
526 lines
11 KiB
import QtQuick 2.2
|
|
import QtQuick.Controls 1.1
|
|
import QtQuick.Controls.Styles 1.1
|
|
import QtQuick.Dialogs 1.1
|
|
import QtQuick.Layouts 1.1
|
|
import Qt.labs.settings 1.0
|
|
import org.ethereum.qml.QEther 1.0
|
|
import "js/Debugger.js" as Debugger
|
|
import "js/ErrorLocationFormater.js" as ErrorLocationFormater
|
|
import "js/TransactionHelper.js" as TransactionHelper
|
|
import "js/QEtherHelper.js" as QEtherHelper
|
|
import "."
|
|
|
|
ColumnLayout {
|
|
id: blockChainPanel
|
|
property variant model
|
|
spacing: 0
|
|
property int previousWidth
|
|
property variant debugTrRequested: []
|
|
signal chainChanged
|
|
signal chainReloaded
|
|
|
|
Connections
|
|
{
|
|
target: codeModel
|
|
onContractRenamed: {
|
|
rebuild.startBlinking()
|
|
}
|
|
onNewContractCompiled: {
|
|
rebuild.startBlinking()
|
|
}
|
|
}
|
|
|
|
onChainChanged: {
|
|
rebuild.startBlinking()
|
|
}
|
|
|
|
onWidthChanged:
|
|
{
|
|
var minWidth = scenarioMinWidth - 20 // margin
|
|
if (width <= minWidth || previousWidth <= minWidth)
|
|
{
|
|
fromWidth = 100
|
|
toWidth = 100
|
|
valueWidth = 200
|
|
}
|
|
else
|
|
{
|
|
var diff = (width - previousWidth) / 3;
|
|
fromWidth = fromWidth + diff < 100 ? 100 : fromWidth + diff
|
|
toWidth = toWidth + diff < 100 ? 100 : toWidth + diff
|
|
valueWidth = valueWidth + diff < 200 ? 200 : valueWidth + diff
|
|
}
|
|
previousWidth = width
|
|
}
|
|
|
|
function load(scenario)
|
|
{
|
|
if (!scenario)
|
|
return;
|
|
if (model)
|
|
rebuild.startBlinking()
|
|
model = scenario
|
|
blockModel.clear()
|
|
for (var b in model.blocks)
|
|
blockModel.append(model.blocks[b])
|
|
previousWidth = width
|
|
}
|
|
|
|
property int statusWidth: 30
|
|
property int fromWidth: 150
|
|
property int toWidth: 100
|
|
property int valueWidth: 200
|
|
property int logsWidth: 40
|
|
property int debugActionWidth: 40
|
|
property int horizontalMargin: 10
|
|
property int cellSpacing: 10
|
|
|
|
RowLayout
|
|
{
|
|
id: header
|
|
spacing: 0
|
|
Layout.preferredHeight: 30
|
|
Rectangle
|
|
{
|
|
Layout.preferredWidth: statusWidth
|
|
Layout.preferredHeight: parent.height
|
|
color: "transparent"
|
|
Image {
|
|
id: debugImage
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
source: "qrc:/qml/img/recycleicon@2x.png"
|
|
width: statusWidth + 20
|
|
fillMode: Image.PreserveAspectFit
|
|
}
|
|
}
|
|
Rectangle
|
|
{
|
|
Layout.preferredWidth: fromWidth
|
|
Label
|
|
{
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
text: "From"
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: horizontalMargin
|
|
}
|
|
}
|
|
Label
|
|
{
|
|
text: "To"
|
|
Layout.preferredWidth: toWidth + cellSpacing
|
|
}
|
|
Label
|
|
{
|
|
text: "Value"
|
|
Layout.preferredWidth: valueWidth + cellSpacing
|
|
}
|
|
Label
|
|
{
|
|
text: "Logs"
|
|
Layout.preferredWidth: logsWidth + cellSpacing
|
|
}
|
|
Label
|
|
{
|
|
text: ""
|
|
Layout.preferredWidth: debugActionWidth
|
|
}
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
Layout.preferredHeight: 500
|
|
Layout.preferredWidth: parent.width
|
|
border.color: "#cccccc"
|
|
border.width: 2
|
|
color: "white"
|
|
ScrollView
|
|
{
|
|
id: blockChainScrollView
|
|
anchors.fill: parent
|
|
anchors.topMargin: 8
|
|
ColumnLayout
|
|
{
|
|
id: blockChainLayout
|
|
width: parent.width
|
|
spacing: 20
|
|
|
|
Block
|
|
{
|
|
scenario: blockChainPanel.model
|
|
Layout.preferredWidth: blockChainScrollView.width
|
|
Layout.preferredHeight: 60
|
|
blockIndex: -1
|
|
transactions: []
|
|
status: ""
|
|
number: -2
|
|
trHeight: 60
|
|
}
|
|
|
|
Repeater // List of blocks
|
|
{
|
|
id: blockChainRepeater
|
|
model: blockModel
|
|
Block
|
|
{
|
|
scenario: blockChainPanel.model
|
|
Layout.preferredWidth: blockChainScrollView.width
|
|
Layout.preferredHeight:
|
|
{
|
|
return calculateHeight()
|
|
}
|
|
blockIndex: index
|
|
transactions:
|
|
{
|
|
if (index >= 0)
|
|
return blockModel.get(index).transactions
|
|
else
|
|
return []
|
|
}
|
|
|
|
status:
|
|
{
|
|
if (index >= 0)
|
|
return blockModel.get(index).status
|
|
else
|
|
return ""
|
|
}
|
|
|
|
number:
|
|
{
|
|
if (index >= 0)
|
|
return blockModel.get(index).number
|
|
else
|
|
return 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ListModel
|
|
{
|
|
id: blockModel
|
|
|
|
function appendBlock(block)
|
|
{
|
|
blockModel.append(block);
|
|
}
|
|
|
|
function appendTransaction(tr)
|
|
{
|
|
blockModel.get(blockModel.count - 1).transactions.append(tr)
|
|
}
|
|
|
|
function removeTransaction(blockIndex, trIndex)
|
|
{
|
|
blockModel.get(blockIndex).transactions.remove(trIndex)
|
|
}
|
|
|
|
function removeLastBlock()
|
|
{
|
|
blockModel.remove(blockModel.count - 1)
|
|
}
|
|
|
|
function removeBlock(index)
|
|
{
|
|
blockModel.remove(index)
|
|
}
|
|
|
|
function getTransaction(block, tr)
|
|
{
|
|
return blockModel.get(block).transactions.get(tr)
|
|
}
|
|
|
|
function setTransaction(blockIndex, trIndex, tr)
|
|
{
|
|
blockModel.get(blockIndex).transactions.set(trIndex, tr)
|
|
}
|
|
|
|
function setTransactionProperty(blockIndex, trIndex, propertyName, value)
|
|
{
|
|
blockModel.get(blockIndex).transactions.set(trIndex, { propertyName: value })
|
|
}
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
Layout.preferredWidth: parent.width
|
|
RowLayout
|
|
{
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.top: parent.top
|
|
anchors.topMargin: 10
|
|
spacing: 20
|
|
|
|
Rectangle {
|
|
Layout.preferredWidth: 100
|
|
Layout.preferredHeight: 30
|
|
|
|
ScenarioButton {
|
|
id: rebuild
|
|
text: qsTr("Rebuild")
|
|
width: 100
|
|
height: 30
|
|
roundLeft: true
|
|
roundRight: true
|
|
onClicked:
|
|
{
|
|
if (ensureNotFuturetime.running)
|
|
return;
|
|
stopBlinking()
|
|
var retBlocks = [];
|
|
var bAdded = 0;
|
|
for (var j = 0; j < model.blocks.length; j++)
|
|
{
|
|
var b = model.blocks[j];
|
|
var block = {
|
|
hash: b.hash,
|
|
number: b.number,
|
|
transactions: [],
|
|
status: b.status
|
|
}
|
|
for (var k = 0; k < model.blocks[j].transactions.length; k++)
|
|
{
|
|
if (blockModel.get(j).transactions.get(k).saveStatus)
|
|
{
|
|
var tr = model.blocks[j].transactions[k]
|
|
tr.saveStatus = true
|
|
block.transactions.push(tr);
|
|
}
|
|
|
|
}
|
|
if (block.transactions.length > 0)
|
|
{
|
|
bAdded++
|
|
block.number = bAdded
|
|
block.status = "mined"
|
|
retBlocks.push(block)
|
|
}
|
|
|
|
}
|
|
if (retBlocks.length === 0)
|
|
retBlocks.push(projectModel.stateListModel.createEmptyBlock())
|
|
else
|
|
{
|
|
var last = retBlocks[retBlocks.length - 1]
|
|
last.number = -1
|
|
last.status = "pending"
|
|
}
|
|
|
|
model.blocks = retBlocks
|
|
blockModel.clear()
|
|
for (var j = 0; j < model.blocks.length; j++)
|
|
blockModel.append(model.blocks[j])
|
|
|
|
ensureNotFuturetime.start()
|
|
clientModel.setupScenario(model);
|
|
}
|
|
buttonShortcut: ""
|
|
sourceImg: "qrc:/qml/img/recycleicon@2x.png"
|
|
}
|
|
}
|
|
|
|
|
|
Rectangle
|
|
{
|
|
Layout.preferredWidth: 200
|
|
Layout.preferredHeight: 30
|
|
color: "transparent"
|
|
|
|
ScenarioButton {
|
|
id: addTransaction
|
|
text: qsTr("Add Tx")
|
|
onClicked:
|
|
{
|
|
var lastBlock = model.blocks[model.blocks.length - 1];
|
|
if (lastBlock.status === "mined")
|
|
{
|
|
var newblock = projectModel.stateListModel.createEmptyBlock()
|
|
blockModel.appendBlock(newblock)
|
|
model.blocks.push(newblock);
|
|
}
|
|
|
|
var item = TransactionHelper.defaultTransaction()
|
|
transactionDialog.stateAccounts = model.accounts
|
|
transactionDialog.execute = true
|
|
transactionDialog.open(model.blocks[model.blocks.length - 1].transactions.length, model.blocks.length - 1, item)
|
|
}
|
|
width: 100
|
|
height: 30
|
|
buttonShortcut: ""
|
|
sourceImg: "qrc:/qml/img/sendtransactionicon@2x.png"
|
|
roundLeft: true
|
|
roundRight: false
|
|
}
|
|
|
|
Timer
|
|
{
|
|
id: ensureNotFuturetime
|
|
interval: 1000
|
|
repeat: false
|
|
running: false
|
|
}
|
|
|
|
Rectangle
|
|
{
|
|
width: 1
|
|
height: parent.height
|
|
anchors.right: addBlockBtn.left
|
|
color: "#ededed"
|
|
}
|
|
|
|
ScenarioButton {
|
|
id: addBlockBtn
|
|
text: qsTr("Add Block..")
|
|
anchors.left: addTransaction.right
|
|
roundLeft: false
|
|
roundRight: true
|
|
onClicked:
|
|
{
|
|
if (ensureNotFuturetime.running)
|
|
return
|
|
if (clientModel.mining || clientModel.running)
|
|
return
|
|
if (model.blocks.length > 0)
|
|
{
|
|
var lastBlock = model.blocks[model.blocks.length - 1]
|
|
if (lastBlock.status === "pending")
|
|
{
|
|
ensureNotFuturetime.start()
|
|
clientModel.mine()
|
|
}
|
|
else
|
|
addNewBlock()
|
|
}
|
|
else
|
|
addNewBlock()
|
|
|
|
}
|
|
|
|
function addNewBlock()
|
|
{
|
|
var block = projectModel.stateListModel.createEmptyBlock()
|
|
model.blocks.push(block)
|
|
blockModel.appendBlock(block)
|
|
}
|
|
width: 100
|
|
height: 30
|
|
|
|
buttonShortcut: ""
|
|
sourceImg: "qrc:/qml/img/addblock@2x.png"
|
|
}
|
|
}
|
|
|
|
|
|
Connections
|
|
{
|
|
target: clientModel
|
|
onNewBlock:
|
|
{
|
|
if (!clientModel.running)
|
|
{
|
|
var lastBlock = model.blocks[model.blocks.length - 1]
|
|
lastBlock.status = "mined"
|
|
lastBlock.number = model.blocks.length
|
|
var lastB = blockModel.get(model.blocks.length - 1)
|
|
lastB.status = "mined"
|
|
lastB.number = model.blocks.length
|
|
addBlockBtn.addNewBlock()
|
|
}
|
|
}
|
|
onStateCleared:
|
|
{
|
|
}
|
|
onNewRecord:
|
|
{
|
|
var blockIndex = parseInt(_r.transactionIndex.split(":")[0]) - 1
|
|
var trIndex = parseInt(_r.transactionIndex.split(":")[1])
|
|
if (blockIndex <= model.blocks.length - 1)
|
|
{
|
|
var item = model.blocks[blockIndex]
|
|
if (trIndex <= item.transactions.length - 1)
|
|
{
|
|
var tr = item.transactions[trIndex]
|
|
tr.returned = _r.returned
|
|
tr.recordIndex = _r.recordIndex
|
|
tr.logs = _r.logs
|
|
tr.sender = _r.sender
|
|
var trModel = blockModel.getTransaction(blockIndex, trIndex)
|
|
trModel.returned = _r.returned
|
|
trModel.recordIndex = _r.recordIndex
|
|
trModel.logs = _r.logs
|
|
trModel.sender = _r.sender
|
|
blockModel.setTransaction(blockIndex, trIndex, trModel)
|
|
return;
|
|
}
|
|
}
|
|
// tr is not in the list.
|
|
var itemTr = TransactionHelper.defaultTransaction()
|
|
itemTr.saveStatus = false
|
|
itemTr.functionId = _r.function
|
|
itemTr.contractId = _r.contract
|
|
itemTr.gasAuto = true
|
|
itemTr.parameters = _r.parameters
|
|
itemTr.isContractCreation = itemTr.functionId === itemTr.contractId
|
|
itemTr.label = _r.label
|
|
itemTr.isFunctionCall = itemTr.functionId !== ""
|
|
itemTr.returned = _r.returned
|
|
itemTr.value = QEtherHelper.createEther(_r.value, QEther.Wei)
|
|
itemTr.sender = _r.sender
|
|
itemTr.recordIndex = _r.recordIndex
|
|
itemTr.logs = _r.logs
|
|
model.blocks[model.blocks.length - 1].transactions.push(itemTr)
|
|
blockModel.appendTransaction(itemTr)
|
|
}
|
|
onMiningComplete:
|
|
{
|
|
}
|
|
}
|
|
|
|
ScenarioButton {
|
|
id: newAccount
|
|
text: qsTr("New Account..")
|
|
onClicked: {
|
|
model.accounts.push(projectModel.stateListModel.newAccount("1000000", QEther.Ether))
|
|
}
|
|
Layout.preferredWidth: 100
|
|
Layout.preferredHeight: 30
|
|
buttonShortcut: ""
|
|
sourceImg: "qrc:/qml/img/newaccounticon@2x.png"
|
|
roundLeft: true
|
|
roundRight: true
|
|
}
|
|
}
|
|
}
|
|
|
|
TransactionDialog {
|
|
id: transactionDialog
|
|
property bool execute
|
|
onAccepted: {
|
|
var item = transactionDialog.getItem()
|
|
if (execute)
|
|
{
|
|
var lastBlock = model.blocks[model.blocks.length - 1];
|
|
if (lastBlock.status === "mined")
|
|
{
|
|
var newBlock = projectModel.stateListModel.createEmptyBlock();
|
|
model.blocks.push(newBlock);
|
|
blockModel.appendBlock(newBlock)
|
|
}
|
|
if (!clientModel.running)
|
|
clientModel.executeTr(item)
|
|
}
|
|
else {
|
|
model.blocks[blockIndex].transactions[transactionIndex] = item
|
|
blockModel.setTransaction(blockIndex, transactionIndex, item)
|
|
chainChanged()
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|