From 8cab71c5843cc07647e50db39dd2125c338795d1 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 22 Jul 2015 17:38:44 -0400 Subject: [PATCH] [GUI] Initial work on Transaction type --- programs/light_client/CMakeLists.txt | 1 + programs/light_client/Operations.cpp | 22 ++-- programs/light_client/Operations.hpp | 144 ++++++++++++++++----- programs/light_client/Transaction.cpp | 48 +++++++ programs/light_client/Transaction.hpp | 50 +++++++ programs/light_client/main.cpp | 7 +- programs/light_client/qml/TransferForm.qml | 1 + 7 files changed, 228 insertions(+), 45 deletions(-) create mode 100644 programs/light_client/Transaction.cpp create mode 100644 programs/light_client/Transaction.hpp diff --git a/programs/light_client/CMakeLists.txt b/programs/light_client/CMakeLists.txt index 36608db3..0bedf46d 100644 --- a/programs/light_client/CMakeLists.txt +++ b/programs/light_client/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable(light_client Asset.cpp Account.cpp Balance.cpp + Transaction.cpp main.cpp ${QML_QRC} ${QML}) if (CMAKE_VERSION VERSION_LESS 3.0) diff --git a/programs/light_client/Operations.cpp b/programs/light_client/Operations.cpp index 934d7cae..2aa2bf48 100644 --- a/programs/light_client/Operations.cpp +++ b/programs/light_client/Operations.cpp @@ -2,21 +2,21 @@ #include -TransferOperation OperationBuilder::transfer(ObjectId sender, ObjectId receiver, qint64 amount, +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); + TransferOperation* op = new TransferOperation; + 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(); - op.operation().memo = graphene::chain::memo_data(); - op.operation().memo->set_message(dummyPrivate, dummyPublic, memo.toStdString()); - op.setFee(op.operation().calculate_fee(feeParameters).value); + 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; } diff --git a/programs/light_client/Operations.hpp b/programs/light_client/Operations.hpp index 73572f86..1a6eed03 100644 --- a/programs/light_client/Operations.hpp +++ b/programs/light_client/Operations.hpp @@ -5,51 +5,128 @@ #include #include +#include -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) +class OperationBase : public QObject { + Q_OBJECT + Q_PROPERTY(OperationType operationType READ operationType CONSTANT STORED false) + +public: + enum OperationType { + TransferOperationType = graphene::chain::operation::tag::value + }; + Q_ENUM(OperationType); + + OperationBase(QObject* parent = nullptr) + : QObject(parent) {} + virtual ~OperationBase() {} + + virtual OperationType operationType() const = 0; + virtual graphene::chain::operation genericOperation() const = 0; +}; +QML_DECLARE_INTERFACE(OperationBase) + +class TransferOperation : public OperationBase { + Q_OBJECT + Q_PROPERTY(qint64 fee READ fee WRITE setFee NOTIFY feeChanged) + Q_PROPERTY(ObjectId feeType READ feeType WRITE setFeeType NOTIFY feeTypeChanged) + Q_PROPERTY(ObjectId sender READ sender WRITE setSender NOTIFY senderChanged) + Q_PROPERTY(ObjectId receiver READ receiver WRITE setReceiver NOTIFY receiverChanged) + Q_PROPERTY(qint64 amount READ amount WRITE setAmount NOTIFY amountChanged) + Q_PROPERTY(ObjectId amountType READ amountType WRITE setAmountType NOTIFY amountTypeChanged) + Q_PROPERTY(QString memo READ memo WRITE setMemo NOTIFY memoChanged) 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; } + TransferOperation(){} + TransferOperation(const graphene::chain::transfer_operation& op) + : m_op(op) {} - 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; } + virtual OperationBase::OperationType operationType() const override { + return OperationBase::TransferOperationType; + } + virtual graphene::chain::operation genericOperation() const override { + return m_op; + } + qint64 fee() const { return m_op.fee.amount.value; } + ObjectId feeType() const { return m_op.fee.asset_id.instance.value; } + ObjectId sender() const { return m_op.from.instance.value; } + ObjectId receiver() const { return m_op.to.instance.value; } + qint64 amount() const { return m_op.amount.amount.value; } + ObjectId amountType() const { return m_op.amount.asset_id.instance.value; } /// 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; } + QString memo() const { return m_memo; } const graphene::chain::transfer_operation& operation() const { return m_op; } graphene::chain::transfer_operation& operation() { return m_op; } -}; -Q_DECLARE_METATYPE(TransferOperation) +public Q_SLOTS: + void setFee(qint64 arg) { + if (arg == fee()) + return; + m_op.fee.amount = arg; + Q_EMIT feeChanged(); + } + void setFeeType(ObjectId arg) { + if (arg == feeType()) + return; + m_op.fee.asset_id = arg; + Q_EMIT feeTypeChanged(); + } + void setSender(ObjectId arg) { + if (arg == sender()) + return; + m_op.from = arg; + Q_EMIT senderChanged(); + } + void setReceiver(ObjectId arg) { + if (arg == receiver()) + return; + m_op.to = arg; + Q_EMIT receiverChanged(); + } + void setAmount(qint64 arg) { + if (arg == amount()) + return; + m_op.amount.amount = arg; + Q_EMIT amountChanged(); + } + void setAmountType(ObjectId arg) { + if (arg == amountType()) + return; + m_op.amount.asset_id = arg; + Q_EMIT amountTypeChanged(); + } + /// 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. + void setMemo(QString memo) { + if (memo == m_memo) + return; + m_memo = memo; + Q_EMIT memoChanged(); + } + +Q_SIGNALS: + void feeChanged(); + void feeTypeChanged(); + void senderChanged(); + void receiverChanged(); + void amountChanged(); + void amountTypeChanged(); + void memoChanged(); +}; +QML_DECLARE_TYPE(TransferOperation) + +/** + * @brief The OperationBuilder class creates operations which are inspectable by the GUI + * + * @note All operations returned by OperationBuilder are heap allocated on-demand and do not have parents. The caller + * must take ownership of these objects to prevent them from leaking. + */ class OperationBuilder : public QObject { Q_OBJECT @@ -59,6 +136,7 @@ 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); + Q_INVOKABLE TransferOperation* transfer(ObjectId sender, ObjectId receiver, + qint64 amount, ObjectId amountType, QString memo, ObjectId feeType); + }; diff --git a/programs/light_client/Transaction.cpp b/programs/light_client/Transaction.cpp new file mode 100644 index 00000000..f50e9dac --- /dev/null +++ b/programs/light_client/Transaction.cpp @@ -0,0 +1,48 @@ +#include "Transaction.hpp" +#include "Operations.hpp" + +#include + +struct OperationConverter { + using result_type = OperationBase*; + + OperationBase* operator()(const graphene::chain::transfer_operation& op) const { + auto ret = new TransferOperation(op); + QObject::connect(ret, &QObject::destroyed, []{qDebug("Cleaned up operation");}); + return ret; + } + + template + OperationBase* operator()(const Op&) const { + elog("NYI: OperationConverter for ${type}", ("type", fc::get_typename::name())); + abort(); + } +}; + +QQmlListProperty Transaction::operations() +{ + auto append = [](QQmlListProperty* list, OperationBase* op) { + static_cast(list->data)->appendOperation(op); + }; + auto count = [](QQmlListProperty* list) { + return static_cast(list->data)->operationCount(); + }; + auto at = [](QQmlListProperty* list, int index) { + return static_cast(list->data)->operationAt(index); + }; + auto clear = [](QQmlListProperty* list) { + static_cast(list->data)->clearOperations(); + }; + + return QQmlListProperty(this, this, append, count, at, clear); +} + +OperationBase* Transaction::operationAt(int index) const { + return m_transaction.operations[index].visit(OperationConverter()); +} + +void Transaction::appendOperation(OperationBase* op) +{ + m_transaction.operations.push_back(op->genericOperation()); + Q_EMIT operationsChanged(); +} diff --git a/programs/light_client/Transaction.hpp b/programs/light_client/Transaction.hpp new file mode 100644 index 00000000..1e268d4a --- /dev/null +++ b/programs/light_client/Transaction.hpp @@ -0,0 +1,50 @@ +#pragma once +#pragma GCC diagnostic ignored "-Wunknown-pragmas" + +#include + +#include +#include + +class OperationBase; +class Transaction : public QObject { + Q_OBJECT + +public: + enum Status { Unbroadcasted, Pending, Complete, Failed }; + Q_ENUM(Status); + + Status status() const { return m_status; } + QQmlListProperty operations(); + + OperationBase* operationAt(int index) const; + void appendOperation(OperationBase* op); + int operationCount() const { + return m_transaction.operations.size(); + } + void clearOperations() { + m_transaction.operations.clear(); + Q_EMIT operationsChanged(); + } + +public slots: + void setStatus(Status status) + { + if (status == m_status) + return; + + m_status = status; + emit statusChanged(status); + } + +signals: + void statusChanged(Status status); + void operationsChanged(); + +private: + Q_PROPERTY(Status status READ status WRITE setStatus NOTIFY statusChanged) + Q_PROPERTY(QQmlListProperty operations READ operations NOTIFY operationsChanged) + + Status m_status; + graphene::chain::transaction m_transaction; +}; diff --git a/programs/light_client/main.cpp b/programs/light_client/main.cpp index cd9dc41a..0226fb63 100644 --- a/programs/light_client/main.cpp +++ b/programs/light_client/main.cpp @@ -4,6 +4,7 @@ #include "GrapheneApplication.hpp" #include "ChainDataModel.hpp" +#include "Transaction.hpp" #include "Operations.hpp" #include "Balance.hpp" @@ -27,13 +28,17 @@ int main(int argc, char *argv[]) qRegisterMetaType>(); qRegisterMetaType(); - qRegisterMetaType(); qmlRegisterType("Graphene.Client", 0, 1, "Asset"); qmlRegisterType("Graphene.Client", 0, 1, "Balance"); qmlRegisterType("Graphene.Client", 0, 1, "Account"); qmlRegisterType("Graphene.Client", 0, 1, "DataModel"); qmlRegisterType("Graphene.Client", 0, 1, "GrapheneApplication"); + qmlRegisterType("Graphene.Client", 0, 1, "Transaction"); + + qmlRegisterInterface("OperationBase"); + qmlRegisterType("Graphene.Client", 0, 1, "TransferOperation"); + qmlRegisterUncreatableType("Graphene.Client", 0, 1, "OperationBuilder", QStringLiteral("OperationBuilder cannot be created from QML")); diff --git a/programs/light_client/qml/TransferForm.qml b/programs/light_client/qml/TransferForm.qml index d3f9b416..52294392 100644 --- a/programs/light_client/qml/TransferForm.qml +++ b/programs/light_client/qml/TransferForm.qml @@ -95,6 +95,7 @@ Rectangle { var op = app.operationBuilder.transfer(0, 0, amountField.value * precisionAdjustment, balance.type.id, memoField.text, 0) + return qsTr("Fee:
") + op.fee / precisionAdjustment + " CORE" } }