From b433f90a55d04e8416488fab28145f2089708800 Mon Sep 17 00:00:00 2001 From: Nathan Hourt Date: Wed, 15 Jul 2015 17:48:44 -0400 Subject: [PATCH] [GUI] Use assets and balances in transfer form --- libraries/app/include/graphene/app/api.hpp | 10 +-- programs/light_client/CMakeLists.txt | 5 +- programs/light_client/ClientDataModel.cpp | 35 ++++---- programs/light_client/ClientDataModel.hpp | 93 ++++++++++++++------- programs/light_client/main.cpp | 1 + programs/light_client/qml/AccountPicker.qml | 25 ++++-- programs/light_client/qml/TransferForm.qml | 23 ++++- 7 files changed, 132 insertions(+), 60 deletions(-) diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index b1e96a34..109de097 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -71,7 +71,7 @@ namespace graphene { namespace app { */ optional get_block(uint32_t block_num)const; - /** + /** * @brief used to fetch an individual transaction. */ processed_transaction get_transaction( uint32_t block_num, uint32_t trx_in_block )const; @@ -120,7 +120,7 @@ namespace graphene { namespace app { /** * @brief Get an account's balances in various assets * @param id ID of the account to get balances for - * @param assets IDs of the assets to get balances of + * @param assets IDs of the assets to get balances of; if empty, get all assets account has a balance in * @return Balances of the account */ vector get_account_balances(account_id_type id, const flat_set& assets)const; @@ -194,7 +194,7 @@ namespace graphene { namespace app { * @return Map of witness names to corresponding IDs */ map lookup_witness_accounts(const string& lower_bound_name, uint32_t limit)const; - + /** * @brief Get names and IDs for registered committee_members * @param lower_bound_name Lower bound of the first name to return @@ -202,7 +202,7 @@ namespace graphene { namespace app { * @return Map of committee_member names to corresponding IDs */ map lookup_committee_member_accounts(const string& lower_bound_name, uint32_t limit)const; - + /** * @brief Get a list of witnesses by ID * @param witness_ids IDs of the witnesses to retrieve @@ -322,7 +322,7 @@ namespace graphene { namespace app { unsigned limit = 100, operation_history_id_type start = operation_history_id_type())const; - vector get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds, + vector get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds, fc::time_point_sec start, fc::time_point_sec end )const; flat_set get_market_history_buckets()const; private: diff --git a/programs/light_client/CMakeLists.txt b/programs/light_client/CMakeLists.txt index 54e8dfc9..5931383f 100644 --- a/programs/light_client/CMakeLists.txt +++ b/programs/light_client/CMakeLists.txt @@ -13,7 +13,10 @@ find_package(Qt5Quick) file(GLOB QML qml/*) -qt5_add_resources(QML_QRC qml/qml.qrc) +# Skip building QRC in debug mode, since we access the QML files directly on disk in debug mode +if (NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + qt5_add_resources(QML_QRC qml/qml.qrc) +endif() add_executable(light_client ClientDataModel.cpp ClientDataModel.hpp main.cpp ${QML_QRC} ${QML}) if (CMAKE_VERSION VERSION_LESS 3.0) diff --git a/programs/light_client/ClientDataModel.cpp b/programs/light_client/ClientDataModel.cpp index 7db26780..cd76b925 100644 --- a/programs/light_client/ClientDataModel.cpp +++ b/programs/light_client/ClientDataModel.cpp @@ -11,16 +11,13 @@ ChainDataModel::ChainDataModel( fc::thread& t, QObject* parent ) :QObject(parent),m_thread(&t){} -Asset* ChainDataModel::getAsset(qint64 id) +Asset* ChainDataModel::getAsset(ObjectId id) { auto& by_id_idx = m_assets.get<::by_id>(); auto itr = by_id_idx.find(id); if( itr == by_id_idx.end() ) { - auto tmp = new Asset; - QQmlEngine::setObjectOwnership(tmp, QQmlEngine::CppOwnership); - tmp->id = id; --m_account_query_num; - tmp->symbol = QString::number( --m_account_query_num); + auto tmp = new Asset(id, QString::number(--m_account_query_num), 0, this); auto result = m_assets.insert( tmp ); assert( result.second ); @@ -71,10 +68,7 @@ Asset* ChainDataModel::getAsset(QString symbol) auto itr = by_symbol_idx.find(symbol); if( itr == by_symbol_idx.end() ) { - auto tmp = new Asset; - QQmlEngine::setObjectOwnership(tmp, QQmlEngine::CppOwnership); - tmp->id = --m_account_query_num; - tmp->symbol = symbol; + auto tmp = new Asset(--m_account_query_num, symbol, 0, this); auto result = m_assets.insert( tmp ); assert( result.second ); @@ -123,10 +117,7 @@ Account* ChainDataModel::getAccount(ObjectId id) auto itr = by_id_idx.find(id); if( itr == by_id_idx.end() ) { - auto tmp = new Account; - QQmlEngine::setObjectOwnership(tmp, QQmlEngine::CppOwnership); - tmp->id = id; --m_account_query_num; - tmp->name = QString::number( --m_account_query_num); + auto tmp = new Account(id, QString::number(--m_account_query_num), this); auto result = m_accounts.insert( tmp ); assert( result.second ); @@ -174,10 +165,7 @@ Account* ChainDataModel::getAccount(QString name) auto itr = by_name_idx.find(name); if( itr == by_name_idx.end() ) { - auto tmp = new Account; - QQmlEngine::setObjectOwnership(tmp, QQmlEngine::CppOwnership); - tmp->id = --m_account_query_num; - tmp->name = name; + auto tmp = new Account(--m_account_query_num, name, this); auto result = m_accounts.insert( tmp ); assert( result.second ); @@ -221,6 +209,19 @@ Account* ChainDataModel::getAccount(QString name) QQmlListProperty Account::balances() { + // This entire block is dummy data. Throw it away when this function gets a real implementation. + if (m_balances.empty()) + { + auto asset = new Asset(0, QStringLiteral("CORE"), 5, this); + m_balances.append(new Balance); + m_balances.back()->setProperty("amount", 5000000); + m_balances.back()->setProperty("type", QVariant::fromValue(asset)); + asset = new Asset(0, QStringLiteral("USD"), 2, this); + m_balances.append(new Balance); + m_balances.back()->setProperty("amount", 3099); + m_balances.back()->setProperty("type", QVariant::fromValue(asset)); + } + return QQmlListProperty(this, m_balances); } diff --git a/programs/light_client/ClientDataModel.hpp b/programs/light_client/ClientDataModel.hpp index 9411dee7..1968a194 100644 --- a/programs/light_client/ClientDataModel.hpp +++ b/programs/light_client/ClientDataModel.hpp @@ -26,13 +26,21 @@ Q_DECLARE_METATYPE(std::function) class GrapheneObject : public QObject { Q_OBJECT - Q_PROPERTY(ObjectId id MEMBER id NOTIFY idChanged) + Q_PROPERTY(ObjectId id MEMBER m_id READ id NOTIFY idChanged) - public: - ObjectId id; + ObjectId m_id; - Q_SIGNALS: - void idChanged(); +public: + GrapheneObject(ObjectId id = -1, QObject* parent = nullptr) + : QObject(parent), m_id(id) + {} + + ObjectId id() const { + return m_id; + } + +Q_SIGNALS: + void idChanged(); }; class Crypto { Q_GADGET @@ -45,19 +53,34 @@ public: QML_DECLARE_TYPE(Crypto) -class Asset : public GrapheneObject { +class Asset : public GrapheneObject { Q_OBJECT - Q_PROPERTY(QString symbol MEMBER symbol) - Q_PROPERTY(quint32 precision MEMBER precision) + Q_PROPERTY(QString symbol MEMBER m_symbol READ symbol NOTIFY symbolChanged) + Q_PROPERTY(quint32 precision MEMBER m_precision NOTIFY precisionChanged) - public: - QString symbol; - quint32 precision; + QString m_symbol; + quint32 m_precision; +public: + Asset(ObjectId id = -1, QString symbol = QString(), quint32 precision = 0, QObject* parent = nullptr) + : GrapheneObject(id, parent), m_symbol(symbol), m_precision(precision) + {} - Q_SIGNALS: - void symbolChanged(); + QString symbol() const { + return m_symbol; + } + + quint64 precisionPower() const { + quint64 power = 1; + for (int i = 0; i < m_precision; ++i) + power *= 10; + return power; + } + +Q_SIGNALS: + void symbolChanged(); + void precisionChanged(); }; struct by_id; @@ -65,45 +88,59 @@ struct by_symbol_name; typedef multi_index_container< Asset*, indexed_by< - hashed_unique< tag, member >, - ordered_unique< tag, member > + hashed_unique< tag, const_mem_fun >, + ordered_unique< tag, const_mem_fun > > > asset_multi_index_type; class Balance : public GrapheneObject { Q_OBJECT - Q_PROPERTY(Asset* type MEMBER type) - Q_PROPERTY(qint64 amount MEMBER amount) + Q_PROPERTY(Asset* type MEMBER type NOTIFY typeChanged) + Q_PROPERTY(qint64 amount MEMBER amount NOTIFY amountChanged) Asset* type; qint64 amount; + +public: + // This ultimately needs to be replaced with a string equivalent + Q_INVOKABLE qreal amountReal() const { + return amount / qreal(type->precisionPower()); + } + +Q_SIGNALS: + void typeChanged(); + void amountChanged(); }; class Account : public GrapheneObject { Q_OBJECT - Q_PROPERTY(QString name MEMBER name NOTIFY nameChanged) - Q_PROPERTY(QQmlListProperty balances READ balances) + Q_PROPERTY(QString name MEMBER m_name READ name NOTIFY nameChanged) + Q_PROPERTY(QQmlListProperty balances READ balances NOTIFY balancesChanged) + QString m_name; QList m_balances; - public: - const QString& getName()const { return name; } - QQmlListProperty balances(); +public: + Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr) + : GrapheneObject(id, parent), m_name(name) + {} - QString name; + QString name()const { return m_name; } + QQmlListProperty balances(); - Q_SIGNALS: - void nameChanged(); +Q_SIGNALS: + void nameChanged(); + void balancesChanged(); }; struct by_account_name; typedef multi_index_container< Account*, indexed_by< - hashed_unique< tag, member >, - ordered_unique< tag, member > + hashed_unique< tag, const_mem_fun >, + ordered_unique< tag, const_mem_fun > > > account_multi_index_type; @@ -113,7 +150,7 @@ class ChainDataModel : public QObject { public: Q_INVOKABLE Account* getAccount(ObjectId id); Q_INVOKABLE Account* getAccount(QString name); - Q_INVOKABLE Asset* getAsset(qint64 id); + Q_INVOKABLE Asset* getAsset(ObjectId id); Q_INVOKABLE Asset* getAsset(QString symbol); ChainDataModel(){} diff --git a/programs/light_client/main.cpp b/programs/light_client/main.cpp index a2c12ac4..e3aaa035 100644 --- a/programs/light_client/main.cpp +++ b/programs/light_client/main.cpp @@ -28,6 +28,7 @@ int main(int argc, char *argv[]) #ifdef NDEBUG engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); #else + QQmlDebuggingEnabler enabler; engine.load(QUrl(QStringLiteral("qml/main.qml"))); #endif diff --git a/programs/light_client/qml/AccountPicker.qml b/programs/light_client/qml/AccountPicker.qml index d94549ad..19c0e758 100644 --- a/programs/light_client/qml/AccountPicker.qml +++ b/programs/light_client/qml/AccountPicker.qml @@ -9,8 +9,11 @@ import "." RowLayout { property Account account + property var balances: account? Object.keys(account.balances).map(function(key){return account.balances[key]}) + : null property alias placeholderText: accountNameField.placeholderText + property int showBalance: -1 function setFocus() { accountNameField.forceActiveFocus() @@ -29,25 +32,37 @@ RowLayout { width: parent.width onEditingFinished: accountDetails.update(text) } - Label { + Text { id: accountDetails + width: parent.width + height: text? implicitHeight : 0 + + Behavior on height { NumberAnimation{ easing.type: Easing.InOutQuad } } + function update(name) { if (!name) { text = "" + account = null return } account = app.model.getAccount(name) - if (account == null) + if (account == null) { text = qsTr("Error fetching account.") - else + } else { text = Qt.binding(function() { if (account == null) return qsTr("Account does not exist.") - return qsTr("Account ID: %1").arg(account.id < 0? qsTr("Loading...") - : account.id) + var text = qsTr("Account ID: %1").arg(account.id < 0? qsTr("Loading...") + : account.id) + if (showBalance >= 0) { + text += "\n" + qsTr("Balance: %1 %2").arg(balances[showBalance].amountReal()) + .arg(balances[showBalance].type.symbol) + } + return text }) + } } Behavior on text { diff --git a/programs/light_client/qml/TransferForm.qml b/programs/light_client/qml/TransferForm.qml index 8ff7beac..fc678543 100644 --- a/programs/light_client/qml/TransferForm.qml +++ b/programs/light_client/qml/TransferForm.qml @@ -27,28 +27,42 @@ Rectangle { AccountPicker { id: senderPicker width: parent.width + Layout.minimumWidth: Scaling.cm(5) Component.onCompleted: setFocus() placeholderText: qsTr("Sender") + showBalance: balances? balances.reduce(function(foundIndex, balance, index) { + if (foundIndex >= 0) return foundIndex + return balance.type.symbol === assetBox.currentText? index : -1 + }, -1) : -1 } AccountPicker { id: recipientPicker width: parent.width + Layout.minimumWidth: Scaling.cm(5) placeholderText: qsTr("Recipient") layoutDirection: Qt.RightToLeft } RowLayout { width: parent.width SpinBox { + id: amountField Layout.preferredWidth: Scaling.cm(4) Layout.minimumWidth: Scaling.cm(1.5) - enabled: senderPicker.account + enabled: maxBalance minimumValue: 0 - maximumValue: Number.POSITIVE_INFINITY + maximumValue: maxBalance? maxBalance.amountReal() : 0 + decimals: maxBalance? maxBalance.type.precision : 0 + + property Balance maxBalance: senderPicker.balances && senderPicker.showBalance >= 0? + senderPicker.balances[senderPicker.showBalance] : null } ComboBox { + id: assetBox Layout.minimumWidth: Scaling.cm(3) - enabled: senderPicker.account - model: ["CORE", "USD", "GOLD"] + enabled: Boolean(senderPicker.balances) + model: enabled? senderPicker.balances.filter(function(balance) { return balance.amount > 0 }) + .map(function(balance) { return balance.type.symbol }) + : ["Asset Type"] } Item { Layout.fillWidth: true } Button { @@ -58,6 +72,7 @@ Rectangle { Button { text: qsTr("Transfer") enabled: senderPicker.account + onClicked: console.log(amountField.value) } } }