[GUI] Early implementation of operations
This commit is contained in:
parent
d827c5df28
commit
f72f07b05b
8 changed files with 170 additions and 17 deletions
|
|
@ -18,7 +18,8 @@ if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
qt5_add_resources(QML_QRC qml/qml.qrc)
|
qt5_add_resources(QML_QRC qml/qml.qrc)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_executable(light_client ClientDataModel.cpp ClientDataModel.hpp main.cpp ${QML_QRC} ${QML})
|
add_executable(light_client ClientDataModel.cpp ClientDataModel.hpp Operations.cpp main.cpp ${QML_QRC} ${QML})
|
||||||
|
|
||||||
if (CMAKE_VERSION VERSION_LESS 3.0)
|
if (CMAKE_VERSION VERSION_LESS 3.0)
|
||||||
add_dependencies(light_client gen_qrc)
|
add_dependencies(light_client gen_qrc)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include "ClientDataModel.hpp"
|
#include "ClientDataModel.hpp"
|
||||||
|
#include "Operations.hpp"
|
||||||
|
|
||||||
#include <graphene/app/api.hpp>
|
#include <graphene/app/api.hpp>
|
||||||
#include <graphene/chain/protocol/protocol.hpp>
|
#include <graphene/chain/protocol/protocol.hpp>
|
||||||
|
|
@ -18,7 +19,16 @@ QString idToString(graphene::db::object_id_type id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ChainDataModel::ChainDataModel(fc::thread& t, QObject* parent)
|
ChainDataModel::ChainDataModel(fc::thread& t, QObject* parent)
|
||||||
:QObject(parent),m_rpc_thread(&t){}
|
:QObject(parent),m_rpc_thread(&t){}
|
||||||
|
|
||||||
|
void ChainDataModel::setDatabaseAPI(fc::api<database_api> dbapi) {
|
||||||
|
m_db_api = dbapi;
|
||||||
|
m_rpc_thread->async([this] {
|
||||||
|
m_global_properties = m_db_api->get_global_properties();
|
||||||
|
m_db_api->subscribe_to_objects([this](const variant& v) { m_global_properties = v.as<global_property_object>(); },
|
||||||
|
{m_global_properties.id});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Asset* ChainDataModel::getAsset(ObjectId id)
|
Asset* ChainDataModel::getAsset(ObjectId id)
|
||||||
{
|
{
|
||||||
|
|
@ -275,6 +285,7 @@ GrapheneApplication::GrapheneApplication(QObject* parent)
|
||||||
this, &GrapheneApplication::execute);
|
this, &GrapheneApplication::execute);
|
||||||
|
|
||||||
m_model = new ChainDataModel(m_thread, this);
|
m_model = new ChainDataModel(m_thread, this);
|
||||||
|
m_operationBuilder = new OperationBuilder(*m_model, this);
|
||||||
|
|
||||||
connect(m_model, &ChainDataModel::queueExecute,
|
connect(m_model, &ChainDataModel::queueExecute,
|
||||||
this, &GrapheneApplication::execute);
|
this, &GrapheneApplication::execute);
|
||||||
|
|
@ -329,6 +340,7 @@ void GrapheneApplication::start(QString apiurl, QString user, QString pass)
|
||||||
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
|
Q_EMIT exceptionThrown(QString::fromStdString(e.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const
|
Q_SLOT void GrapheneApplication::execute(const std::function<void()>& func)const
|
||||||
{
|
{
|
||||||
func();
|
func();
|
||||||
|
|
@ -341,3 +353,21 @@ void Balance::update(const account_balance_object& update)
|
||||||
emit amountChanged();
|
emit amountChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Asset::update(const asset_object& asset)
|
||||||
|
{
|
||||||
|
if (asset.id.instance() != id())
|
||||||
|
setProperty("id", QVariant::fromValue(asset.id.instance()));
|
||||||
|
if (asset.symbol != m_symbol.toStdString()) {
|
||||||
|
m_symbol = QString::fromStdString(asset.symbol);
|
||||||
|
Q_EMIT symbolChanged();
|
||||||
|
}
|
||||||
|
if (asset.precision != m_precision) {
|
||||||
|
m_precision = asset.precision;
|
||||||
|
Q_EMIT precisionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (asset.options.core_exchange_rate != coreExchangeRate)
|
||||||
|
coreExchangeRate = asset.options.core_exchange_rate;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,8 @@ class Asset : public GrapheneObject {
|
||||||
QString m_symbol;
|
QString m_symbol;
|
||||||
quint32 m_precision;
|
quint32 m_precision;
|
||||||
|
|
||||||
|
graphene::chain::price coreExchangeRate;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Asset(ObjectId id = -1, QString symbol = QString(), quint32 precision = 0, QObject* parent = nullptr)
|
Asset(ObjectId id = -1, QString symbol = QString(), quint32 precision = 0, QObject* parent = nullptr)
|
||||||
: GrapheneObject(id, parent), m_symbol(symbol), m_precision(precision)
|
: GrapheneObject(id, parent), m_symbol(symbol), m_precision(precision)
|
||||||
|
|
@ -80,6 +82,8 @@ public:
|
||||||
return power;
|
return power;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update(const graphene::chain::asset_object& asset);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void symbolChanged();
|
void symbolChanged();
|
||||||
void precisionChanged();
|
void precisionChanged();
|
||||||
|
|
@ -177,36 +181,43 @@ public:
|
||||||
ChainDataModel(){}
|
ChainDataModel(){}
|
||||||
ChainDataModel( fc::thread& t, QObject* parent = nullptr );
|
ChainDataModel( fc::thread& t, QObject* parent = nullptr );
|
||||||
|
|
||||||
void setDatabaseAPI( fc::api<graphene::app::database_api> dbapi ){ m_db_api = dbapi; }
|
void setDatabaseAPI(fc::api<graphene::app::database_api> dbapi);
|
||||||
|
|
||||||
|
const graphene::chain::global_property_object& global_properties() const { return m_global_properties; }
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void queueExecute( const std::function<void()>& );
|
void queueExecute( const std::function<void()>& );
|
||||||
void exceptionThrown( QString message );
|
void exceptionThrown( QString message );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
fc::thread* m_rpc_thread = nullptr;
|
fc::thread* m_rpc_thread = nullptr;
|
||||||
std::string m_api_url;
|
std::string m_api_url;
|
||||||
fc::api<graphene::app::database_api> m_db_api;
|
fc::api<graphene::app::database_api> m_db_api;
|
||||||
|
|
||||||
ObjectId m_account_query_num = -1;
|
graphene::chain::global_property_object m_global_properties;
|
||||||
account_multi_index_type m_accounts;
|
|
||||||
asset_multi_index_type m_assets;
|
ObjectId m_account_query_num = -1;
|
||||||
|
account_multi_index_type m_accounts;
|
||||||
|
asset_multi_index_type m_assets;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class OperationBuilder;
|
||||||
class GrapheneApplication : public QObject {
|
class GrapheneApplication : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
Q_PROPERTY(ChainDataModel* model READ model CONSTANT)
|
Q_PROPERTY(ChainDataModel* model READ model CONSTANT)
|
||||||
|
Q_PROPERTY(OperationBuilder* operationBuilder READ operationBuilder CONSTANT)
|
||||||
Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged)
|
Q_PROPERTY(bool isConnected READ isConnected NOTIFY isConnectedChanged)
|
||||||
|
|
||||||
|
|
||||||
fc::thread m_thread;
|
fc::thread m_thread;
|
||||||
ChainDataModel* m_model = nullptr;
|
ChainDataModel* m_model = nullptr;
|
||||||
bool m_isConnected = false;
|
OperationBuilder* m_operationBuilder = nullptr;
|
||||||
|
bool m_isConnected = false;
|
||||||
|
|
||||||
boost::signals2::scoped_connection m_connectionClosed;
|
boost::signals2::scoped_connection m_connectionClosed;
|
||||||
|
|
||||||
std::shared_ptr<fc::http::websocket_client> m_client;
|
std::shared_ptr<fc::http::websocket_client> m_client;
|
||||||
fc::future<void> m_done;
|
fc::future<void> m_done;
|
||||||
|
|
||||||
void setIsConnected(bool v);
|
void setIsConnected(bool v);
|
||||||
|
|
@ -218,10 +229,12 @@ public:
|
||||||
GrapheneApplication(QObject* parent = nullptr);
|
GrapheneApplication(QObject* parent = nullptr);
|
||||||
~GrapheneApplication();
|
~GrapheneApplication();
|
||||||
|
|
||||||
ChainDataModel* model() const
|
ChainDataModel* model() const {
|
||||||
{
|
|
||||||
return m_model;
|
return m_model;
|
||||||
}
|
}
|
||||||
|
OperationBuilder* operationBuilder() const {
|
||||||
|
return m_operationBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
Q_INVOKABLE void start(QString apiUrl,
|
Q_INVOKABLE void start(QString apiUrl,
|
||||||
QString user,
|
QString user,
|
||||||
|
|
|
||||||
22
programs/light_client/Operations.cpp
Normal file
22
programs/light_client/Operations.cpp
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#include "Operations.hpp"
|
||||||
|
|
||||||
|
#include <fc/smart_ref_impl.hpp>
|
||||||
|
|
||||||
|
TransferOperation OperationBuilder::transfer(ObjectId sender, ObjectId receiver, qint64 amount,
|
||||||
|
ObjectId amountType, QString memo, ObjectId feeType)
|
||||||
|
{
|
||||||
|
static fc::ecc::private_key dummyPrivate = fc::ecc::private_key::generate();
|
||||||
|
static fc::ecc::public_key dummyPublic = fc::ecc::private_key::generate().get_public_key();
|
||||||
|
TransferOperation op;
|
||||||
|
op.setSender(sender);
|
||||||
|
op.setReceiver(receiver);
|
||||||
|
op.setAmount(amount);
|
||||||
|
op.setAmountType(amountType);
|
||||||
|
op.setMemo(memo);
|
||||||
|
op.setFeeType(feeType);
|
||||||
|
auto feeParameters = model.global_properties().parameters.current_fees->get<graphene::chain::transfer_operation>();
|
||||||
|
op.operation().memo = graphene::chain::memo_data();
|
||||||
|
op.operation().memo->set_message(dummyPrivate, dummyPublic, memo.toStdString());
|
||||||
|
op.setFee(op.operation().calculate_fee(feeParameters).value);
|
||||||
|
return op;
|
||||||
|
}
|
||||||
64
programs/light_client/Operations.hpp
Normal file
64
programs/light_client/Operations.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ClientDataModel.hpp"
|
||||||
|
|
||||||
|
#include <graphene/chain/protocol/transfer.hpp>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
class TransferOperation {
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(qint64 fee READ fee)
|
||||||
|
Q_PROPERTY(ObjectId feeType READ feeType)
|
||||||
|
Q_PROPERTY(ObjectId sender READ sender WRITE setSender)
|
||||||
|
Q_PROPERTY(ObjectId receiver READ receiver WRITE setReceiver)
|
||||||
|
Q_PROPERTY(qint64 amount READ amount WRITE setAmount)
|
||||||
|
Q_PROPERTY(ObjectId amountType READ amountType WRITE setAmountType)
|
||||||
|
Q_PROPERTY(QString memo READ memo WRITE setMemo)
|
||||||
|
|
||||||
|
graphene::chain::transfer_operation m_op;
|
||||||
|
QString m_memo;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Q_INVOKABLE qint64 fee() const { return m_op.fee.amount.value; }
|
||||||
|
Q_INVOKABLE void setFee(qint64 fee) { m_op.fee.amount = fee; }
|
||||||
|
|
||||||
|
Q_INVOKABLE ObjectId feeType() const { return m_op.fee.asset_id.instance.value; }
|
||||||
|
Q_INVOKABLE void setFeeType(ObjectId feeType) { m_op.fee.asset_id = feeType; }
|
||||||
|
|
||||||
|
Q_INVOKABLE ObjectId sender() const { return m_op.from.instance.value; }
|
||||||
|
Q_INVOKABLE void setSender(ObjectId sender) { m_op.from = sender; }
|
||||||
|
|
||||||
|
Q_INVOKABLE ObjectId receiver() const { return m_op.to.instance.value; }
|
||||||
|
Q_INVOKABLE void setReceiver(ObjectId receiver) { m_op.to = receiver; }
|
||||||
|
|
||||||
|
Q_INVOKABLE qint64 amount() const { return m_op.amount.amount.value; }
|
||||||
|
Q_INVOKABLE void setAmount(qint64 amount) { m_op.amount.amount = amount; }
|
||||||
|
|
||||||
|
Q_INVOKABLE ObjectId amountType() const { return m_op.amount.asset_id.instance.value; }
|
||||||
|
Q_INVOKABLE void setAmountType(ObjectId assetType) { m_op.amount.asset_id = assetType; }
|
||||||
|
|
||||||
|
/// This does not deal with encrypted memos. The memo stored here is unencrypted, and does not get stored in the
|
||||||
|
/// underlying graphene operation. The encryption and storage steps must be handled elsewhere.
|
||||||
|
Q_INVOKABLE QString memo() const { return m_memo; }
|
||||||
|
/// This does not deal with encrypted memos. The memo stored here is unencrypted, and does not get stored in the
|
||||||
|
/// underlying graphene operation. The encryption and storage steps must be handled elsewhere.
|
||||||
|
Q_INVOKABLE void setMemo(QString memo) { m_memo = memo; }
|
||||||
|
|
||||||
|
const graphene::chain::transfer_operation& operation() const { return m_op; }
|
||||||
|
graphene::chain::transfer_operation& operation() { return m_op; }
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(TransferOperation)
|
||||||
|
|
||||||
|
class OperationBuilder : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
ChainDataModel& model;
|
||||||
|
|
||||||
|
public:
|
||||||
|
OperationBuilder(ChainDataModel& model, QObject* parent = nullptr)
|
||||||
|
: QObject(parent), model(model){}
|
||||||
|
|
||||||
|
Q_INVOKABLE TransferOperation transfer(ObjectId sender, ObjectId receiver,
|
||||||
|
qint64 amount, ObjectId amountType, QString memo, ObjectId feeType);
|
||||||
|
};
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <QtQml>
|
#include <QtQml>
|
||||||
|
|
||||||
#include "ClientDataModel.hpp"
|
#include "ClientDataModel.hpp"
|
||||||
|
#include "Operations.hpp"
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
|
@ -14,12 +15,15 @@ int main(int argc, char *argv[])
|
||||||
|
|
||||||
qRegisterMetaType<std::function<void()>>();
|
qRegisterMetaType<std::function<void()>>();
|
||||||
qRegisterMetaType<ObjectId>();
|
qRegisterMetaType<ObjectId>();
|
||||||
|
qRegisterMetaType<TransferOperation>();
|
||||||
|
|
||||||
qmlRegisterType<Asset>("Graphene.Client", 0, 1, "Asset");
|
qmlRegisterType<Asset>("Graphene.Client", 0, 1, "Asset");
|
||||||
qmlRegisterType<Balance>("Graphene.Client", 0, 1, "Balance");
|
qmlRegisterType<Balance>("Graphene.Client", 0, 1, "Balance");
|
||||||
qmlRegisterType<Account>("Graphene.Client", 0, 1, "Account");
|
qmlRegisterType<Account>("Graphene.Client", 0, 1, "Account");
|
||||||
qmlRegisterType<ChainDataModel>("Graphene.Client", 0, 1, "DataModel");
|
qmlRegisterType<ChainDataModel>("Graphene.Client", 0, 1, "DataModel");
|
||||||
qmlRegisterType<GrapheneApplication>("Graphene.Client", 0, 1, "GrapheneApplication");
|
qmlRegisterType<GrapheneApplication>("Graphene.Client", 0, 1, "GrapheneApplication");
|
||||||
|
qmlRegisterUncreatableType<OperationBuilder>("Graphene.Client", 0, 1, "OperationBuilder",
|
||||||
|
QStringLiteral("OperationBuilder cannot be created from QML"));
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
QVariant crypto;
|
QVariant crypto;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ RowLayout {
|
||||||
accountNameField.forceActiveFocus()
|
accountNameField.forceActiveFocus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signal balanceClicked(var balance)
|
||||||
|
|
||||||
Identicon {
|
Identicon {
|
||||||
name: account && account.name == accountNameField.text? accountNameField.text : ""
|
name: account && account.name == accountNameField.text? accountNameField.text : ""
|
||||||
width: Scaling.cm(2)
|
width: Scaling.cm(2)
|
||||||
|
|
@ -44,6 +46,7 @@ RowLayout {
|
||||||
id: accountDetails
|
id: accountDetails
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: text? implicitHeight : 0
|
height: text? implicitHeight : 0
|
||||||
|
onLinkActivated: if (link === "balance") balanceClicked(balances[showBalance].amountReal())
|
||||||
|
|
||||||
Behavior on height { NumberAnimation{ easing.type: Easing.InOutQuad } }
|
Behavior on height { NumberAnimation{ easing.type: Easing.InOutQuad } }
|
||||||
|
|
||||||
|
|
@ -66,7 +69,8 @@ RowLayout {
|
||||||
: account.id)
|
: account.id)
|
||||||
if (showBalance >= 0) {
|
if (showBalance >= 0) {
|
||||||
var bal = balances[showBalance]
|
var bal = balances[showBalance]
|
||||||
text += "\n" + qsTr("Balance: %1 %2").arg(String(bal.amountReal())).arg(bal.type.symbol)
|
text += "<br/>" + qsTr("Balance: <a href='balance'>%1</a> %2").arg(String(bal.amountReal()))
|
||||||
|
.arg(bal.type.symbol)
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ Rectangle {
|
||||||
property alias senderAccount: senderPicker.account
|
property alias senderAccount: senderPicker.account
|
||||||
/// The Account object for the receiver
|
/// The Account object for the receiver
|
||||||
property alias receiverAccount: recipientPicker.account
|
property alias receiverAccount: recipientPicker.account
|
||||||
|
/// The operation created in this form
|
||||||
|
property var operation
|
||||||
|
|
||||||
Component.onCompleted: console.log("Made a transfer form")
|
Component.onCompleted: console.log("Made a transfer form")
|
||||||
Component.onDestruction: console.log("Destroyed a transfer form")
|
Component.onDestruction: console.log("Destroyed a transfer form")
|
||||||
|
|
@ -47,6 +49,7 @@ Rectangle {
|
||||||
if (foundIndex >= 0) return foundIndex
|
if (foundIndex >= 0) return foundIndex
|
||||||
return balance.type.symbol === assetField.currentText? index : -1
|
return balance.type.symbol === assetField.currentText? index : -1
|
||||||
}, -1) : -1
|
}, -1) : -1
|
||||||
|
onBalanceClicked: amountField.value = balance
|
||||||
}
|
}
|
||||||
AccountPicker {
|
AccountPicker {
|
||||||
id: recipientPicker
|
id: recipientPicker
|
||||||
|
|
@ -77,12 +80,24 @@ Rectangle {
|
||||||
}
|
}
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: assetField
|
id: assetField
|
||||||
Layout.minimumWidth: Scaling.cm(3)
|
Layout.minimumWidth: Scaling.cm(1)
|
||||||
enabled: senderPicker.balances instanceof Array && senderPicker.balances.length > 0
|
enabled: senderPicker.balances instanceof Array && senderPicker.balances.length > 0
|
||||||
model: enabled? senderPicker.balances.filter(function(balance) { return balance.amount > 0 })
|
model: enabled? senderPicker.balances.filter(function(balance) { return balance.amount > 0 })
|
||||||
.map(function(balance) { return balance.type.symbol })
|
.map(function(balance) { return balance.type.symbol })
|
||||||
: ["Asset Type"]
|
: ["Asset Type"]
|
||||||
}
|
}
|
||||||
|
Text {
|
||||||
|
font.pixelSize: assetField.height / 2.5
|
||||||
|
text: {
|
||||||
|
var balance = amountField.maxBalance
|
||||||
|
if (!balance || !balance.type) return ""
|
||||||
|
var precisionAdjustment = Math.pow(10, balance.type.precision)
|
||||||
|
|
||||||
|
var op = app.operationBuilder.transfer(0, 0, amountField.value * precisionAdjustment,
|
||||||
|
balance.type.id, memoField.text, 0)
|
||||||
|
return qsTr("Fee:<br/>") + op.fee / precisionAdjustment + " CORE"
|
||||||
|
}
|
||||||
|
}
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
Button {
|
Button {
|
||||||
text: qsTr("Cancel")
|
text: qsTr("Cancel")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue