diff --git a/programs/light_client/Account.hpp b/programs/light_client/Account.hpp index f816d6d2..6f190f21 100644 --- a/programs/light_client/Account.hpp +++ b/programs/light_client/Account.hpp @@ -27,19 +27,19 @@ class Account : public GrapheneObject { public: Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr) - : GrapheneObject(id, parent) + : GrapheneObject(id, parent) { m_account.name = name.toStdString(); } - void setAccountObject( const account_object& obj ) + void setAccountObject(const account_object& obj) { auto old_name = m_account.name; m_account = obj; - if( old_name != m_account.name ) + if (old_name != m_account.name) Q_EMIT nameChanged(); } - QString name()const { return toQString(m_account.name); } + QString name()const { return QString::fromStdString(m_account.name); } QQmlListProperty balances(); void setBalances(QList balances) { @@ -52,11 +52,11 @@ public: void update(const account_balance_object& balance); - /** + /** * Anything greater than 1.0 means full authority. * Anything between (0 and 1.0) means partial authority * 0 means no authority. - * + * * @return the percent of direct control the wallet has over the account. */ Q_INVOKABLE double getOwnerControl( Wallet* w )const; diff --git a/programs/light_client/ChainDataModel.cpp b/programs/light_client/ChainDataModel.cpp index 9f8c9ebe..159ef616 100644 --- a/programs/light_client/ChainDataModel.cpp +++ b/programs/light_client/ChainDataModel.cpp @@ -195,7 +195,7 @@ void ChainDataModel::getAccountImpl(QString accountIdentifier, Account* const * } else { m_accounts.modify(itr, [this,&accountPackage](Account* a){ a->setProperty("id", ObjectId(accountPackage->account.id.instance())); - a->setAccountObject( accountPackage->account ); + a->setAccountObject(accountPackage->account); // Set balances QList balances; diff --git a/programs/light_client/GrapheneObject.hpp b/programs/light_client/GrapheneObject.hpp index a14c89fd..0c53c562 100644 --- a/programs/light_client/GrapheneObject.hpp +++ b/programs/light_client/GrapheneObject.hpp @@ -5,8 +5,6 @@ #include -QString toQString( const std::string& s ); - using ObjectId = qint64; Q_DECLARE_METATYPE(ObjectId) diff --git a/programs/light_client/Transaction.cpp b/programs/light_client/Transaction.cpp index f50e9dac..64f54374 100644 --- a/programs/light_client/Transaction.cpp +++ b/programs/light_client/Transaction.cpp @@ -43,6 +43,7 @@ OperationBase* Transaction::operationAt(int index) const { void Transaction::appendOperation(OperationBase* op) { + op->setParent(this); m_transaction.operations.push_back(op->genericOperation()); Q_EMIT operationsChanged(); } diff --git a/programs/light_client/Transaction.hpp b/programs/light_client/Transaction.hpp index 1e268d4a..7081054b 100644 --- a/programs/light_client/Transaction.hpp +++ b/programs/light_client/Transaction.hpp @@ -18,14 +18,9 @@ public: 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) @@ -37,6 +32,16 @@ public slots: emit statusChanged(status); } + /** + * @brief Append the operation to the transaction + * @param op The operation to append. This Transaction will take ownership of the operation. + */ + void appendOperation(OperationBase* op); + void clearOperations() { + m_transaction.operations.clear(); + Q_EMIT operationsChanged(); + } + signals: void statusChanged(Status status); void operationsChanged(); diff --git a/programs/light_client/Wallet.cpp b/programs/light_client/Wallet.cpp index 42ed3bb7..dd62ca8d 100644 --- a/programs/light_client/Wallet.cpp +++ b/programs/light_client/Wallet.cpp @@ -3,10 +3,9 @@ #include #include -QString toQString( const std::string& s ) { QString result; result.fromStdString( s ); return result; } -QString toQString( public_key_type k ) { return toQString( fc::variant(k).as_string() ); } +QString toQString(public_key_type k) { return QString::fromStdString(fc::variant(k).as_string()); } -Wallet::Wallet( QObject* parent ) +Wallet::Wallet(QObject* parent) :QObject(parent) { } @@ -16,10 +15,10 @@ Wallet::~Wallet() close(); } -bool Wallet::open( QString file_path ) +bool Wallet::open(QString file_path) { - fc::path p( file_path.toStdString() ); - if( !fc::exists( p ) ) + fc::path p(file_path.toStdString()); + if( !fc::exists(p) ) { ilog( "Unable to open wallet file '${f}', it does not exist", ("f",p) ); return false; @@ -30,7 +29,7 @@ bool Wallet::open( QString file_path ) for( const auto& key : _data.encrypted_private_keys ) { if( key.second.label != string() ) - _label_to_key[toQString(key.second.label)] = toQString( key.first ); + _label_to_key[QString::fromStdString(key.second.label)] = toQString(key.first); if( key.second.encrypted_private_key.size() ) _available_private_keys.insert( key.first ); } @@ -194,7 +193,7 @@ bool Wallet::hasPrivateKey( QString pubkey, bool include_with_brain_key ) auto itr = _data.encrypted_private_keys.find(pub); if( itr == _data.encrypted_private_keys.end() ) return false; - if( itr->second.encrypted_private_key.size() ) + if( itr->second.encrypted_private_key.size() ) return true; if( include_with_brain_key && itr->second.brain_sequence >= 0 ) { @@ -217,7 +216,7 @@ QString Wallet::getPrivateKey( QString pubkey ) if( itr->second.encrypted_private_key.size() == 0 ) return QString(); auto plain = fc::aes_decrypt( _decrypted_master_key, itr->second.encrypted_private_key ); - return toQString( fc::raw::unpack(plain) ); + return QString::fromStdString( fc::raw::unpack(plain) ); } QString Wallet::getPublicKey( QString wif_private_key )const @@ -227,7 +226,7 @@ QString Wallet::getPublicKey( QString wif_private_key )const auto pub = public_key_type(priv->get_public_key()); - return toQString( fc::variant( pub ).as_string() ); + return QString::fromStdString( fc::variant( pub ).as_string() ); } QString Wallet::getActivePrivateKey( QString owner_pub_key, uint32_t seq ) @@ -251,7 +250,7 @@ QString Wallet::getActivePrivateKey( QString owner_pub_key, uint32_t seq ) _data.encrypted_private_keys[active_pub_key].brain_sequence = seq; _available_private_keys.insert( active_pub_key ); - return toQString(wif); + return QString::fromStdString(wif); } QString Wallet::getOwnerPrivateKey( uint32_t seq ) @@ -273,7 +272,7 @@ QString Wallet::getOwnerPrivateKey( uint32_t seq ) _data.encrypted_private_keys[owner_pub_key].brain_sequence = seq; _available_private_keys.insert( owner_pub_key ); - return toQString( wif ); + return QString::fromStdString( wif ); } QString Wallet::getActivePublicKey( QString active_pub, uint32_t seq ) @@ -294,11 +293,11 @@ QString Wallet::getKeyLabel( QString pubkey ) auto itr = _data.encrypted_private_keys.find( key ); if( itr == _data.encrypted_private_keys.end() ) return QString(); - return toQString( itr->second.label ); + return QString::fromStdString( itr->second.label ); } /** * The same label may not be assigned to more than one key, this method will - * fail if a key with the same label already exists. + * fail if a key with the same label already exists. * * @return true if the label was set */ @@ -310,7 +309,7 @@ bool Wallet::setKeyLabel( QString pubkey, QString label ) auto old_label = _data.encrypted_private_keys[pub].label; _data.encrypted_private_keys[pub].label = string(); if( old_label.size() ) - _label_to_key.erase( toQString( old_label ) ); + _label_to_key.erase( QString::fromStdString( old_label ) ); return true; } @@ -336,7 +335,7 @@ QList > Wallet::getAllPublicKeys( bool only_if_private )c for( const auto& item : _data.encrypted_private_keys ) { if( only_if_private && !item.second.encrypted_private_key.size() ) continue; - result.push_back( qMakePair( toQString( item.first ), toQString( item.second.label ) ) ); + result.push_back( qMakePair( toQString( item.first ), QString::fromStdString( item.second.label ) ) ); } return result; @@ -359,9 +358,9 @@ bool Wallet::importPublicKey( QString pubkey, QString label ) return setKeyLabel( pubkey, label ); } -/** +/** * @param wifkey a private key in (WIF) Wallet Import Format - * @pre !isLocked() + * @pre !isLocked() **/ bool Wallet::importPrivateKey( QString wifkey, QString label ) { @@ -390,14 +389,14 @@ bool Wallet::removePublicKey( QString pubkey ) auto itr = _data.encrypted_private_keys.find(pub); if( itr != _data.encrypted_private_keys.end() ) { - _label_to_key.erase( toQString(itr->second.label) ); + _label_to_key.erase( QString::fromStdString(itr->second.label) ); _data.encrypted_private_keys.erase(itr); return true; } return false; } -/** removes only the private key, keeping the public key and label +/** removes only the private key, keeping the public key and label * * @pre isOpen() && !isLocked() **/ @@ -416,7 +415,7 @@ bool Wallet::removePrivateKey( QString pubkey ) /** * @pre !isLocked() */ -vector Wallet::signDigest( const digest_type& d, +vector Wallet::signDigest( const digest_type& d, const set& keys )const { vector result; diff --git a/programs/light_client/Wallet.hpp b/programs/light_client/Wallet.hpp index 3e00b007..070634df 100644 --- a/programs/light_client/Wallet.hpp +++ b/programs/light_client/Wallet.hpp @@ -16,7 +16,6 @@ using graphene::chain::digest_type; using graphene::chain::signature_type; using fc::optional; -QString toQString( const std::string& s ); QString toQString( public_key_type k ); struct key_data @@ -38,12 +37,12 @@ struct wallet_file map encrypted_private_keys; }; -FC_REFLECT( wallet_file, +FC_REFLECT( wallet_file, (encrypted_brain_key) (brain_key_digest) (encrypted_master_key) (master_key_digest) - (encrypted_private_keys) + (encrypted_private_keys) ); @@ -57,6 +56,8 @@ FC_REFLECT( wallet_file, class Wallet : public QObject { Q_OBJECT + Q_PROPERTY(bool isOpen READ isOpen NOTIFY isOpenChanged) + Q_PROPERTY(bool isLocked READ isLocked NOTIFY isLockedChanged) public: Wallet( QObject* parent = nullptr ); ~Wallet(); @@ -86,7 +87,7 @@ class Wallet : public QObject /** * @pre !isLocked(); * @post save() - * @return WIF private key + * @return WIF private key */ Q_INVOKABLE QString getActivePrivateKey( QString owner_public_key, uint32_t sequence ); Q_INVOKABLE QString getActivePublicKey( QString owner_public_key, uint32_t sequence ); @@ -95,7 +96,7 @@ class Wallet : public QObject * @pre !isLocked(); * @pre hasBrainKey(); * @post save() - * @return WIF private key + * @return WIF private key */ Q_INVOKABLE QString getOwnerPrivateKey( uint32_t sequence ); Q_INVOKABLE QString getOwnerPublicKey( uint32_t sequence ); @@ -110,9 +111,9 @@ class Wallet : public QObject /** imports a public key and assigns it a label */ Q_INVOKABLE bool importPublicKey( QString pubkey, QString label = QString() ); - /** + /** * @param wifkey a private key in (WIF) Wallet Import Format - * @pre !isLocked() + * @pre !isLocked() **/ Q_INVOKABLE bool importPrivateKey( QString wifkey, QString label = QString() ); @@ -131,7 +132,7 @@ class Wallet : public QObject /** * @pre !isLocked() */ - vector signDigest( const digest_type& d, + vector signDigest( const digest_type& d, const set& keys )const; const flat_set& getAvailablePrivateKeys()const; @@ -149,5 +150,3 @@ class Wallet : public QObject map _label_to_key; QString _brain_key; }; - - diff --git a/programs/light_client/qml/FormBox.qml b/programs/light_client/qml/FormBox.qml index 653f40fe..dae5a1ec 100644 --- a/programs/light_client/qml/FormBox.qml +++ b/programs/light_client/qml/FormBox.qml @@ -29,7 +29,8 @@ Rectangle { var form = formType.createObject(formContainer, params) formContainer.data = [form] - form.finished.connect(function(){state = "HIDDEN"}) + form.canceled.connect(function(){state = "HIDDEN"; internal.callbackArgs = []}) + form.completed.connect(function(){state = "HIDDEN"; internal.callbackArgs = arguments}) if (closedCallback instanceof Function) internal.callback = closedCallback state = "SHOWN" @@ -89,6 +90,7 @@ Rectangle { if (internal.callback instanceof Function) internal.callback() internal.callback = undefined + internal.callbackArgs = [] } } }, @@ -145,5 +147,6 @@ Rectangle { QtObject { id: internal property var callback + property var callbackArgs } } diff --git a/programs/light_client/qml/FormFlipper.qml b/programs/light_client/qml/FormFlipper.qml new file mode 100644 index 00000000..52c7d09d --- /dev/null +++ b/programs/light_client/qml/FormFlipper.qml @@ -0,0 +1,51 @@ +import QtQuick 2.5 + +import Graphene.Client 0.1 + +Flipable { + id: flipable + anchors.fill: parent + + property Component frontComponent + property Component backComponent + property GrapheneApplication app + signal canceled + signal completed + + property bool flipped: false + + Component.onCompleted: { + back = backComponent.createObject(flipable, {app: app, enabled: Qt.binding(function(){return flipped})}) + front = frontComponent.createObject(flipable, {app: app, enabled: Qt.binding(function(){return !flipped})}) + front.canceled.connect(function() { canceled.apply(this, arguments) }) + front.completed.connect(function() { + if (back.hasOwnProperty("arguments")) + back.arguments = arguments + flipped = true + }) + back.canceled.connect(function() { + if (front.hasOwnProperty("arguments")) + front.arguments = arguments + flipped = false + }) + back.completed.connect(function() { completed.apply(this, arguments) }) + } + + transform: Rotation { + id: rotation + origin.x: flipable.width/2 + origin.y: flipable.height/2 + axis.x: 0; axis.y: 1; axis.z: 0 // set axis.y to 1 to rotate around y-axis + angle: 0 // the default angle + } + + states: State { + name: "back" + PropertyChanges { target: rotation; angle: 180 } + when: flipable.flipped + } + + transitions: Transition { + NumberAnimation { target: rotation; property: "angle"; duration: 500 } + } +} diff --git a/programs/light_client/qml/TransferForm.qml b/programs/light_client/qml/TransferForm.qml index 52294392..30044519 100644 --- a/programs/light_client/qml/TransferForm.qml +++ b/programs/light_client/qml/TransferForm.qml @@ -15,14 +15,21 @@ Rectangle { anchors.fill: parent property GrapheneApplication app - signal finished + signal canceled + signal completed(TransferOperation op) /// The Account object for the sender property alias senderAccount: senderPicker.account /// The Account object for the receiver property alias receiverAccount: recipientPicker.account - /// The operation created in this form - property var operation + + function operation() { + if (!transferButton.enabled) return app.operationBuilder.transfer(0,0,0,0, memoField.text, 0) + + return app.operationBuilder.transfer(senderPicker.account.id, recipientPicker.account.id, + amountField.value * amountField.precisionAdjustment, + amountField.maxBalance.type.id, memoField.text, 0) + } Component.onCompleted: console.log("Made a transfer form") Component.onDestruction: console.log("Destroyed a transfer form") @@ -77,6 +84,12 @@ Rectangle { property Balance maxBalance: assetField.enabled && senderPicker.showBalance >= 0? senderPicker.balances[senderPicker.showBalance] : null + property int precisionAdjustment: maxBalance? Math.pow(10, maxBalance.type.precision) : 1 + + // Workaround to preserve value in case form gets disabled then re-enabled + onEnabledChanged: if (!enabled) __valueBackup = value + onMaximumValueChanged: if (enabled && maximumValue > __valueBackup) value = __valueBackup + property real __valueBackup } ComboBox { id: assetField @@ -89,25 +102,21 @@ Rectangle { 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:
") + op.fee / precisionAdjustment + " CORE" + if (!senderPicker.account) + return "" + return qsTr("Fee:
") + operation().fee / amountField.precisionAdjustment + " CORE" } } Item { Layout.fillWidth: true } Button { text: qsTr("Cancel") - onClicked: finished() + onClicked: canceled() } Button { + id: transferButton text: qsTr("Transfer") enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value - onClicked: console.log(amountField.value) + onClicked: completed(operation) } } } diff --git a/programs/light_client/qml/main.qml b/programs/light_client/qml/main.qml index e6774b13..f117f342 100644 --- a/programs/light_client/qml/main.qml +++ b/programs/light_client/qml/main.qml @@ -60,10 +60,15 @@ ApplicationWindow { Button { text: "Transfer" - onClicked: formBox.showForm(Qt.createComponent("TransferForm.qml"), {}, - function() { - console.log("Closed form") - }) + onClicked: { + var front = Qt.createComponent("TransferForm.qml") + // TODO: make back into a preview and confirm dialog + var back = Qt.createComponent("TransferForm.qml") + formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back}, + function() { + console.log("Closed form") + }) + } } TextField { id: nameField diff --git a/programs/light_client/qml/qml.qrc b/programs/light_client/qml/qml.qrc index ed16a610..cda31e3e 100644 --- a/programs/light_client/qml/qml.qrc +++ b/programs/light_client/qml/qml.qrc @@ -3,6 +3,7 @@ main.qml TransferForm.qml FormBox.qml + FormFlipper.qml Scaling.qml Identicon.qml AccountPicker.qml