Merge branch 'master' of github.com:cryptonomex/graphene

This commit is contained in:
Daniel Larimer 2015-07-30 09:20:51 -04:00
commit 8b0a22d849
20 changed files with 446 additions and 191 deletions

@ -1 +1 @@
Subproject commit d11b48a015e0c339cc20f739ef1f8a5b3c57b913 Subproject commit 088dadd12481e75829b83c098f3bc36b7e296ca0

View file

@ -4,6 +4,29 @@
#include <graphene/chain/account_object.hpp> #include <graphene/chain/account_object.hpp>
void Account::setAccountObject(const graphene::chain::account_object& obj)
{
auto oldName = m_account.name;
auto oldMemoKey = memoKey();
m_account = obj;
if (oldName != m_account.name)
Q_EMIT nameChanged();
if (oldMemoKey != memoKey())
Q_EMIT memoKeyChanged();
if (!m_loaded) {
m_loaded = true;
Q_EMIT loaded();
qDebug() << name() << "loaded.";
}
}
QString Account::memoKey() const
{
return toQString(m_account.options.memo_key);
}
QQmlListProperty<Balance> Account::balances() QQmlListProperty<Balance> Account::balances()
{ {
auto count = [](QQmlListProperty<Balance>* list) { auto count = [](QQmlListProperty<Balance>* list) {
@ -16,8 +39,9 @@ QQmlListProperty<Balance> Account::balances()
return QQmlListProperty<Balance>(this, this, count, at); return QQmlListProperty<Balance>(this, this, count, at);
} }
double Account::getActiveControl( Wallet* w )const double Account::getActiveControl(Wallet* w, int depth)const
{ {
if (depth >= GRAPHENE_MAX_SIG_CHECK_DEPTH) return 0;
if (m_account.active.num_auths() == 0) return 0; if (m_account.active.num_auths() == 0) return 0;
if (m_account.active.weight_threshold == 0) return 0; if (m_account.active.weight_threshold == 0) return 0;
@ -26,10 +50,22 @@ double Account::getActiveControl( Wallet* w )const
{ {
if (w->hasPrivateKey(toQString(key.first))) weight += key.second; if (w->hasPrivateKey(toQString(key.first))) weight += key.second;
} }
ChainDataModel* model = qobject_cast<ChainDataModel*>(parent());
for (auto& acnt : m_account.active.account_auths) for (auto& acnt : m_account.active.account_auths)
{ {
// TODO: lookup Account, check to see if we have full control of it, and Account* account = model->getAccount(acnt.first.instance.value);
// add its weight if we do. Be sure to limit recursion depth if (!account->m_loaded) {
QEventLoop el;
connect(account, &Account::loaded, &el, &QEventLoop::quit);
QTimer::singleShot(1000, &el, SLOT(quit()));
el.exec();
if (!account->m_loaded)
// We don't have this account loaded yet... Oh well, move along
continue;
}
if (account->getActiveControl(w, depth + 1) >= 1.0)
weight += acnt.second;
} }
return double(weight) / double(m_account.active.weight_threshold); return double(weight) / double(m_account.active.weight_threshold);
@ -44,10 +80,16 @@ double Account::getOwnerControl( Wallet* w )const
{ {
if (w->hasPrivateKey(toQString(key.first))) weight += key.second; if (w->hasPrivateKey(toQString(key.first))) weight += key.second;
} }
ChainDataModel* model = qobject_cast<ChainDataModel*>(parent());
for (auto& acnt : m_account.owner.account_auths) for (auto& acnt : m_account.owner.account_auths)
{ {
// TODO: lookup Account, check to see if we have full *ACTIVE* control of it, and Account* account = model->getAccount(acnt.first.instance.value);
// add its weight if we do. Be sure to limit recursion depth if (!account->m_loaded)
// We don't have this account loaded yet... Oh well, move along
continue;
if (account->getActiveControl(w) >= 1.0)
weight += acnt.second;
} }
return double(weight) / double(m_account.owner.weight_threshold); return double(weight) / double(m_account.owner.weight_threshold);

View file

@ -21,9 +21,12 @@ class Account : public GrapheneObject {
Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged)
Q_PROPERTY(QQmlListProperty<Balance> balances READ balances NOTIFY balancesChanged) Q_PROPERTY(QQmlListProperty<Balance> balances READ balances NOTIFY balancesChanged)
Q_PROPERTY(QString memoKey READ memoKey NOTIFY memoKeyChanged)
Q_PROPERTY(bool isLoaded MEMBER m_loaded NOTIFY loaded)
account_object m_account; account_object m_account;
QList<Balance*> m_balances; QList<Balance*> m_balances;
bool m_loaded = false;
public: public:
Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr) Account(ObjectId id = -1, QString name = QString(), QObject* parent = nullptr)
@ -31,15 +34,10 @@ public:
{ {
m_account.name = name.toStdString(); 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)
Q_EMIT nameChanged();
}
QString name()const { return QString::fromStdString(m_account.name); } QString name()const { return QString::fromStdString(m_account.name); }
QString memoKey()const;
QQmlListProperty<Balance> balances(); QQmlListProperty<Balance> balances();
void setBalances(QList<Balance*> balances) { void setBalances(QList<Balance*> balances) {
@ -59,9 +57,11 @@ public:
* @return the percent of direct control the wallet has over the account. * @return the percent of direct control the wallet has over the account.
*/ */
Q_INVOKABLE double getOwnerControl(Wallet* w)const; Q_INVOKABLE double getOwnerControl(Wallet* w)const;
Q_INVOKABLE double getActiveControl( Wallet* w )const; Q_INVOKABLE double getActiveControl(Wallet* w , int depth = 0)const;
Q_SIGNALS: Q_SIGNALS:
void nameChanged(); void nameChanged();
void balancesChanged(); void balancesChanged();
void memoKeyChanged();
void loaded();
}; };

View file

@ -8,6 +8,8 @@
#include <fc/rpc/websocket_api.hpp> #include <fc/rpc/websocket_api.hpp>
#include <QStandardPaths>
using graphene::app::login_api; using graphene::app::login_api;
using graphene::app::database_api; using graphene::app::database_api;
@ -75,6 +77,11 @@ void GrapheneApplication::start(QString apiurl, QString user, QString pass)
} }
} }
QString GrapheneApplication::defaultDataPath()
{
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
}
Transaction* GrapheneApplication::createTransaction() const Transaction* GrapheneApplication::createTransaction() const
{ {
return new Transaction; return new Transaction;

View file

@ -64,6 +64,8 @@ public:
return m_isConnected; return m_isConnected;
} }
Q_INVOKABLE static QString defaultDataPath();
/// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction. /// Convenience method to get a Transaction in QML. Caller takes ownership of the new Transaction.
Q_INVOKABLE Transaction* createTransaction() const; Q_INVOKABLE Transaction* createTransaction() const;

View file

@ -1,4 +1,7 @@
#include "Operations.hpp" #include "Operations.hpp"
#include "Wallet.hpp"
#include <graphene/utilities/key_conversion.hpp>
#include <fc/smart_ref_impl.hpp> #include <fc/smart_ref_impl.hpp>
@ -31,6 +34,38 @@ QString TransferOperation::memo() const {
return memo; return memo;
} }
bool TransferOperation::canEncryptMemo(Wallet* wallet, ChainDataModel* model) const
{
if (!m_op.memo) return false;
auto pub = model->getAccount(sender())->memoKey();
if (!wallet->hasPrivateKey(pub)) return false;
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
}
bool TransferOperation::canDecryptMemo(Wallet* wallet, ChainDataModel* model) const
{
if (!m_op.memo) return false;
auto pub = model->getAccount(receiver())->memoKey();
if (!wallet->hasPrivateKey(pub)) return false;
return graphene::utilities::wif_to_key(wallet->getPrivateKey(pub).toStdString()).valid();
}
QString TransferOperation::decryptedMemo(Wallet* wallet, ChainDataModel* model) const
{
fc::ecc::private_key privateKey;
fc::ecc::public_key publicKey;
if (canEncryptMemo(wallet, model)) {
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
publicKey = m_op.memo->to;
} else if (canDecryptMemo(wallet, model)) {
privateKey = *graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(receiver())->memoKey()).toStdString());
publicKey = m_op.memo->from;
} else return QString::null;
return QString::fromStdString(m_op.memo->get_message(privateKey, publicKey));
}
void TransferOperation::setMemo(QString memo) { void TransferOperation::setMemo(QString memo) {
if (memo == this->memo()) if (memo == this->memo())
return; return;
@ -41,3 +76,12 @@ void TransferOperation::setMemo(QString memo) {
m_op.memo->set_message({}, {}, memo.toStdString()); m_op.memo->set_message({}, {}, memo.toStdString());
Q_EMIT memoChanged(); Q_EMIT memoChanged();
} }
void TransferOperation::encryptMemo(Wallet* wallet, ChainDataModel* model)
{
if (!canEncryptMemo(wallet, model)) return;
auto privateKey = graphene::utilities::wif_to_key(wallet->getPrivateKey(model->getAccount(sender())->memoKey()).toStdString());
if (!privateKey) return;
m_op.memo->set_message(*privateKey, public_key_type(model->getAccount(receiver())->memoKey().toStdString()), memo().toStdString());
Q_EMIT memoChanged();
}

View file

@ -57,9 +57,13 @@ public:
qint64 amount() const { return m_op.amount.amount.value; } qint64 amount() const { return m_op.amount.amount.value; }
ObjectId amountType() const { return m_op.amount.asset_id.instance.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. The encryption step must be /// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
/// performed elsewhere. /// performed by calling encryptMemo()
QString memo() const; QString memo() const;
Q_INVOKABLE bool canEncryptMemo(Wallet* wallet, ChainDataModel* model) const;
Q_INVOKABLE bool canDecryptMemo(Wallet* wallet, ChainDataModel* model) const;
Q_INVOKABLE QString decryptedMemo(Wallet* wallet, ChainDataModel* model) const;
const graphene::chain::transfer_operation& operation() const { return m_op; } const graphene::chain::transfer_operation& operation() const { return m_op; }
graphene::chain::transfer_operation& operation() { return m_op; } graphene::chain::transfer_operation& operation() { return m_op; }
@ -101,8 +105,9 @@ public Q_SLOTS:
Q_EMIT amountTypeChanged(); Q_EMIT amountTypeChanged();
} }
/// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be /// This does not deal with encrypted memos. The memo stored here is unencrypted. The encryption step must be
/// performed elsewhere. /// performed by calling encryptMemo()
void setMemo(QString memo); void setMemo(QString memo);
void encryptMemo(Wallet* wallet, ChainDataModel* model);
Q_SIGNALS: Q_SIGNALS:
void feeChanged(); void feeChanged();

View file

@ -22,6 +22,10 @@ public:
return m_transaction.operations.size(); return m_transaction.operations.size();
} }
graphene::chain::signed_transaction& internalTransaction() {
return m_transaction;
}
public slots: public slots:
void setStatus(Status status) void setStatus(Status status)
{ {
@ -51,5 +55,5 @@ private:
Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged) Q_PROPERTY(QQmlListProperty<OperationBase> operations READ operations NOTIFY operationsChanged)
Status m_status = Unbroadcasted; Status m_status = Unbroadcasted;
graphene::chain::transaction m_transaction; graphene::chain::signed_transaction m_transaction;
}; };

View file

@ -1,5 +1,9 @@
#include "Transaction.hpp"
#include "Wallet.hpp" #include "Wallet.hpp"
#include <graphene/utilities/key_conversion.hpp> #include <graphene/utilities/key_conversion.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <fc/crypto/aes.hpp> #include <fc/crypto/aes.hpp>
#include <fc/io/json.hpp> #include <fc/io/json.hpp>
@ -20,7 +24,7 @@ bool Wallet::open(QString file_path)
fc::path p(file_path.toStdString()); fc::path p(file_path.toStdString());
if( !fc::exists(p) ) if( !fc::exists(p) )
{ {
ilog( "Unable to open wallet file '${f}', it does not exist", ("f",p) ); ilog("Unable to open wallet file '${f}'; it does not exist", ("f",p));
return false; return false;
} }
@ -98,10 +102,12 @@ bool Wallet::create( QString file_path, QString password, QString brain_key )
_data.brain_key_digest = fc::sha512::hash(brainkey.c_str(), brainkey.size()); _data.brain_key_digest = fc::sha512::hash(brainkey.c_str(), brainkey.size());
_data.encrypted_brain_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(brainkey)); _data.encrypted_brain_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(brainkey));
QFileInfo(file_path).absoluteDir().mkpath(".");
fc::json::save_to_file(_data, p); fc::json::save_to_file(_data, p);
_wallet_file_path = p; _wallet_file_path = p;
ilog("Created wallet file '${f}'", ("f", p));
return false; return true;
} }
bool Wallet::loadBrainKey(QString brain_key) bool Wallet::loadBrainKey(QString brain_key)
@ -149,10 +155,11 @@ QString Wallet::getBrainKey()
bool Wallet::isLocked()const bool Wallet::isLocked()const
{ {
if( !isOpen() ) return true; if( !isOpen() ) return true;
return false; return _decrypted_master_key == fc::sha512();
} }
bool Wallet::unlock(QString password) bool Wallet::unlock(QString password)
{ {
try {
if( !isLocked() ) return true; if( !isLocked() ) return true;
auto pw_str = password.toStdString(); auto pw_str = password.toStdString();
auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size()); auto password_hash = fc::sha512::hash(pw_str.c_str(), pw_str.size());
@ -163,6 +170,10 @@ bool Wallet::unlock( QString password )
Q_EMIT isLockedChanged(isLocked()); Q_EMIT isLockedChanged(isLocked());
return !isLocked(); return !isLocked();
} catch (const fc::exception& e) {
elog(e.to_detail_string());
return false;
}
} }
bool Wallet::lock() bool Wallet::lock()
@ -341,13 +352,23 @@ QList<QPair<QString,QString> > Wallet::getAllPublicKeys( bool only_if_private )c
return result; return result;
} }
void Wallet::sign(Transaction* transaction) const
{
if (transaction == nullptr) return;
auto& trx = transaction->internalTransaction();
flat_set<public_key_type> pubKeys = getAvailablePrivateKeys();
trx.signatures = signDigest(trx.digest(), set<public_key_type>(pubKeys.begin(), pubKeys.end()));
idump((trx));
}
QString Wallet::getPublicKey(QString label) QString Wallet::getPublicKey(QString label)
{ {
if( !isOpen() ) return QString(); if( !isOpen() ) return QString::null;
auto itr = _label_to_key.find(label); auto itr = _label_to_key.find(label);
if( itr != _label_to_key.end() ) if( itr != _label_to_key.end() )
return QString(); return QString::null;
return itr->second; return itr->second;
} }
@ -367,12 +388,13 @@ bool Wallet::importPrivateKey( QString wifkey, QString label )
if( !isOpen() ) return false; if( !isOpen() ) return false;
if( isLocked() ) return false; if( isLocked() ) return false;
auto pub = getPublicKey( wifkey ); auto priv = graphene::utilities::wif_to_key(wifkey.toStdString());
if( pub == QString() ) return false; if (!priv) return false;
auto p = priv->get_public_key();
auto pub = toQString(p);
importPublicKey(pub, label); importPublicKey(pub, label);
auto p = fc::variant( pub.toStdString() ).as<public_key_type>();
_data.encrypted_private_keys[p].encrypted_private_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(wifkey.toStdString())); _data.encrypted_private_keys[p].encrypted_private_key = fc::aes_encrypt(_decrypted_master_key, fc::raw::pack(wifkey.toStdString()));
_available_private_keys.insert(p); _available_private_keys.insert(p);
@ -415,8 +437,7 @@ bool Wallet::removePrivateKey( QString pubkey )
/** /**
* @pre !isLocked() * @pre !isLocked()
*/ */
vector<signature_type> Wallet::signDigest( const digest_type& d, vector<signature_type> Wallet::signDigest(const digest_type& d, const set<public_key_type>& keys)const
const set<public_key_type>& keys )const
{ {
vector<signature_type> result; vector<signature_type> result;
if( !isOpen() ) return result; if( !isOpen() ) return result;
@ -424,7 +445,6 @@ vector<signature_type> Wallet::signDigest( const digest_type& d,
result.reserve( keys.size() ); result.reserve( keys.size() );
vector<fc::ecc::private_key> priv_keys;
for( const auto& key : keys ) for( const auto& key : keys )
{ {
auto itr = _data.encrypted_private_keys.find(key); auto itr = _data.encrypted_private_keys.find(key);
@ -448,5 +468,3 @@ const flat_set<public_key_type>& Wallet::getAvailablePrivateKeys()const
{ {
return _available_private_keys; return _available_private_keys;
} }

View file

@ -2,9 +2,11 @@
#pragma GCC diagnostic ignored "-Wunknown-pragmas" #pragma GCC diagnostic ignored "-Wunknown-pragmas"
#include <graphene/chain/protocol/types.hpp> #include <graphene/chain/protocol/types.hpp>
#include <QObject> #include <QObject>
#include <QList> #include <QList>
#include <QPair> #include <QPair>
#include <QtQml>
using std::string; using std::string;
using std::vector; using std::vector;
@ -16,6 +18,8 @@ using graphene::chain::digest_type;
using graphene::chain::signature_type; using graphene::chain::signature_type;
using fc::optional; using fc::optional;
class Transaction;
QString toQString(public_key_type k); QString toQString(public_key_type k);
struct key_data struct key_data
@ -26,7 +30,6 @@ struct key_data
int32_t brain_sequence = -1; int32_t brain_sequence = -1;
optional<public_key_type> owner; /// if this key was derived from an owner key + sequence optional<public_key_type> owner; /// if this key was derived from an owner key + sequence
}; };
FC_REFLECT( key_data, (label)(encrypted_private_key)(brain_sequence)(owner) );
struct wallet_file struct wallet_file
{ {
@ -37,14 +40,6 @@ struct wallet_file
map<public_key_type, key_data> encrypted_private_keys; map<public_key_type, key_data> encrypted_private_keys;
}; };
FC_REFLECT( wallet_file,
(encrypted_brain_key)
(brain_key_digest)
(encrypted_master_key)
(master_key_digest)
(encrypted_private_keys)
);
/** /**
* @class Wallet * @class Wallet
@ -64,7 +59,7 @@ class Wallet : public QObject
Q_INVOKABLE bool open(QString file_path); Q_INVOKABLE bool open(QString file_path);
Q_INVOKABLE bool close(); Q_INVOKABLE bool close();
Q_INVOKABLE bool isOpen()const; bool isOpen()const;
Q_INVOKABLE bool save(); Q_INVOKABLE bool save();
Q_INVOKABLE bool saveAs(QString file_path); Q_INVOKABLE bool saveAs(QString file_path);
Q_INVOKABLE bool create(QString file_path, QString password, QString brain_key = QString()); Q_INVOKABLE bool create(QString file_path, QString password, QString brain_key = QString());
@ -79,7 +74,7 @@ class Wallet : public QObject
/** @pre hasBrainKey() */ /** @pre hasBrainKey() */
Q_INVOKABLE QString getBrainKey(); Q_INVOKABLE QString getBrainKey();
Q_INVOKABLE bool isLocked()const; bool isLocked()const;
Q_INVOKABLE bool unlock(QString password); Q_INVOKABLE bool unlock(QString password);
Q_INVOKABLE bool lock(); Q_INVOKABLE bool lock();
Q_INVOKABLE bool changePassword(QString new_password); Q_INVOKABLE bool changePassword(QString new_password);
@ -132,6 +127,7 @@ class Wallet : public QObject
/** /**
* @pre !isLocked() * @pre !isLocked()
*/ */
Q_INVOKABLE void sign(Transaction* transaction) const;
vector<signature_type> signDigest(const digest_type& d, vector<signature_type> signDigest(const digest_type& d,
const set<public_key_type>& keys)const; const set<public_key_type>& keys)const;
@ -150,3 +146,13 @@ class Wallet : public QObject
map<QString,QString> _label_to_key; map<QString,QString> _label_to_key;
QString _brain_key; QString _brain_key;
}; };
QML_DECLARE_TYPE(Wallet)
FC_REFLECT( key_data, (label)(encrypted_private_key)(brain_sequence)(owner) )
FC_REFLECT( wallet_file,
(encrypted_brain_key)
(brain_key_digest)
(encrypted_master_key)
(master_key_digest)
(encrypted_private_keys)
)

View file

@ -7,6 +7,7 @@
#include "Transaction.hpp" #include "Transaction.hpp"
#include "Operations.hpp" #include "Operations.hpp"
#include "Balance.hpp" #include "Balance.hpp"
#include "Wallet.hpp"
class Crypto { class Crypto {
Q_GADGET Q_GADGET
@ -37,6 +38,7 @@ int main(int argc, char *argv[])
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<Wallet>("Graphene.Client", 0, 1, "Wallet");
qmlRegisterType<GrapheneApplication>("Graphene.Client", 0, 1, "GrapheneApplication"); qmlRegisterType<GrapheneApplication>("Graphene.Client", 0, 1, "GrapheneApplication");
qmlRegisterType<Transaction>("Graphene.Client", 0, 1, "Transaction"); qmlRegisterType<Transaction>("Graphene.Client", 0, 1, "Transaction");

View file

@ -17,8 +17,11 @@ RowLayout {
/// The Account object the user has selected /// The Account object the user has selected
property Account account property Account account
/// A real in the range [0,1] representing the amount of control the wallet has over this account
property real accountControlLevel: account && account.isLoaded? account.getActiveControl(app.wallet) : 0
/// An Array of Balance objects held by account /// An Array of Balance objects held by account
property var balances: account? Object.keys(account.balances).map(function(key){return account.balances[key]}) property var balances: account? Object.keys(account.balances).map(function(key){return account.balances[key]})
.filter(function(balance){return balance.amount > 0})
: null : null
/// Set the name field to have active focus /// Set the name field to have active focus
@ -32,6 +35,8 @@ RowLayout {
name: account && account.name == accountNameField.text? accountNameField.text : "" name: account && account.name == accountNameField.text? accountNameField.text : ""
width: Scaling.cm(2) width: Scaling.cm(2)
height: Scaling.cm(2) height: Scaling.cm(2)
showOwnership: accountControlLevel > 0
fullOwnership: accountControlLevel >= 1
} }
Column { Column {
Layout.fillWidth: true Layout.fillWidth: true
@ -65,7 +70,7 @@ RowLayout {
text = Qt.binding(function() { text = Qt.binding(function() {
if (account == null) if (account == null)
return qsTr("Account does not exist.") return qsTr("Account does not exist.")
var text = qsTr("Account ID: %1").arg(account.id < 0? qsTr("Loading...") var text = qsTr("Account ID: %1").arg(!account.isLoaded? qsTr("Loading...")
: account.id) : account.id)
if (showBalance >= 0) { if (showBalance >= 0) {
var bal = balances[showBalance] var bal = balances[showBalance]

View file

@ -90,7 +90,7 @@ Rectangle {
greySheet.closed() greySheet.closed()
formContainer.data = [] formContainer.data = []
if (internal.callback instanceof Function) if (internal.callback instanceof Function)
internal.callback() internal.callback.apply(this, internal.callbackArgs)
internal.callback = undefined internal.callback = undefined
internal.callbackArgs = [] internal.callbackArgs = []
} }

View file

@ -7,6 +7,9 @@ Canvas {
contextType: "2d" contextType: "2d"
property var name property var name
property bool showOwnership: false
property bool fullOwnership: false
onNameChanged: requestPaint() onNameChanged: requestPaint()
onPaint: { onPaint: {
@ -36,4 +39,28 @@ Canvas {
draw_circle(context, 2*radius, centerY, radius) draw_circle(context, 2*radius, centerY, radius)
} }
} }
Rectangle {
anchors {
bottom: parent.bottom
left: parent.left
right: parent.right
}
height: parent.height / 4
color: fullOwnership? "green" : "blue"
opacity: .6
visible: showOwnership
Text {
anchors.centerIn: parent
color: "white"
font.pixelSize: parent.height * 2/3
font.bold: true
text: fullOwnership? qsTr("FULL") : qsTr("PARTIAL")
}
TooltipArea {
text: fullOwnership? qsTr("You have full control of this account, and can use it to perform actions directly.")
: qsTr("You have partial control of this account, and can vote for it to take an action.")
}
}
} }

View file

@ -0,0 +1,23 @@
import QtQuick 2.4
import QtQuick.Controls.Private 1.0
// TooltipArea.qml
// This file contains private Qt Quick modules that might change in future versions of Qt
// Tested on: Qt 5.4.1
MouseArea {
id: _root
property string text: ""
anchors.fill: parent
hoverEnabled: _root.enabled
onExited: Tooltip.hideText()
onCanceled: Tooltip.hideText()
Timer {
interval: 1000
running: _root.enabled && _root.containsMouse && _root.text.length
onTriggered: Tooltip.showText(_root, Qt.point(_root.mouseX, _root.mouseY), _root.text)
}
}

View file

@ -24,10 +24,8 @@ FormBase {
onDisplay: { onDisplay: {
trx = app.createTransaction() trx = app.createTransaction()
console.log(JSON.stringify(arg))
for (var op in arg) for (var op in arg)
trx.appendOperation(arg[op]) trx.appendOperation(arg[op])
console.log(JSON.stringify(trx))
} }
Component { Component {
@ -64,4 +62,37 @@ FormBase {
Loader { Loader {
sourceComponent: trx && Array.prototype.slice.call(trx.operations).length > 0? transactionDelegate : undefined sourceComponent: trx && Array.prototype.slice.call(trx.operations).length > 0? transactionDelegate : undefined
} }
RowLayout {
Layout.fillWidth: true
Item { Layout.fillWidth: true }
Button {
text: qsTr("Cancel")
onClicked: {
canceled({})
trx = null
}
}
TextField {
id: passwordField
Layout.preferredWidth: app.wallet.isLocked? Scaling.cm(4) : 0
echoMode: TextInput.Password
placeholderText: qsTr("Wallet password")
visible: width > 0
onAccepted: finishButton.clicked()
Behavior on Layout.preferredWidth { NumberAnimation { duration: 200; easing.type: Easing.InOutQuad } }
}
Button {
id: finishButton
text: app.wallet.isLocked? qsTr("Unlock") : qsTr("Finish")
onClicked: {
if (app.wallet.isLocked)
app.wallet.unlock(passwordField.text)
else {
app.wallet.sign(trx)
completed(trx)
}
}
}
}
} }

View file

@ -92,19 +92,21 @@ FormBase {
Text { Text {
font.pixelSize: assetField.height / 2.5 font.pixelSize: assetField.height / 2.5
text: { text: {
if (!senderPicker.account) if (!senderPicker.account || !amountField.maxBalance)
return "" return ""
return qsTr("Fee:<br/>") + operation().fee / amountField.precisionAdjustment + " CORE" return qsTr("Fee:<br/>") + amountField.maxBalance.type.formatAmount(operation().fee) + " CORE"
} }
} }
Item { Layout.fillWidth: true } Item { Layout.fillWidth: true }
Button { Button {
text: qsTr("Cancel") text: qsTr("Cancel")
onClicked: canceled() onClicked: canceled({})
} }
Button { Button {
id: transferButton id: transferButton
text: qsTr("Transfer") text: !senderAccount ||
!senderAccount.isLoaded ||
senderPicker.accountControlLevel >= 1? qsTr("Transfer") : qsTr("Propose")
enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value enabled: senderPicker.account && recipientPicker.account && senderPicker.account !== recipientPicker.account && amountField.value
onClicked: completed([operation()]) onClicked: completed([operation()])
} }

View file

@ -31,12 +31,20 @@ ApplicationWindow {
statusBar: StatusBar { statusBar: StatusBar {
Label { Label {
anchors.right: parent.right anchors.right: parent.right
text: app.isConnected? qsTr("Connected") : qsTr("Disconnected") text: qsTr("Network: %1 Wallet: %2").arg(app.isConnected? qsTr("Connected") : qsTr("Disconnected"))
.arg(app.wallet.isLocked? qsTr("Locked") : qsTr("Unlocked"))
} }
} }
GrapheneApplication { GrapheneApplication {
id: app id: app
Component.onCompleted: {
var walletFile = appSettings.walletPath + "/wallet.json"
if (!wallet.open(walletFile)) {
// TODO: onboarding experience
wallet.create(walletFile, "default password", "default brain key")
}
}
} }
Timer { Timer {
running: !app.isConnected running: !app.isConnected
@ -48,6 +56,8 @@ ApplicationWindow {
Settings { Settings {
id: appSettings id: appSettings
category: "appSettings" category: "appSettings"
property string walletPath: app.defaultDataPath()
} }
Connections { Connections {
target: app target: app
@ -65,8 +75,8 @@ ApplicationWindow {
// TODO: make back into a preview and confirm dialog // TODO: make back into a preview and confirm dialog
var back = Qt.createComponent("TransactionConfirmationForm.qml") var back = Qt.createComponent("TransactionConfirmationForm.qml")
formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back}, formBox.showForm(Qt.createComponent("FormFlipper.qml"), {frontComponent: front, backComponent: back},
function() { function(arg) {
console.log("Closed form") console.log("Closed form: " + JSON.stringify(arg))
}) })
} }
} }
@ -152,6 +162,32 @@ ApplicationWindow {
} }
} }
} }
TextField {
id: passwordField
echoMode: TextInput.Password
}
Button {
text: app.wallet.isLocked? "Unlock wallet" : "Lock wallet"
onClicked: {
if (app.wallet.isLocked)
app.wallet.unlock(passwordField.text)
else
app.wallet.lock()
}
}
TextField {
id: keyField
placeholderText: "Private key"
}
TextField {
id: keyLabelField
placeholderText: "Key label"
}
Button {
text: "Import key"
enabled: !app.wallet.isLocked && keyField.text && keyLabelField.text
onClicked: app.wallet.importPrivateKey(keyField.text, keyLabelField.text)
}
} }
FormBox { FormBox {

View file

@ -9,6 +9,7 @@
<file>Scaling.qml</file> <file>Scaling.qml</file>
<file>Identicon.qml</file> <file>Identicon.qml</file>
<file>AccountPicker.qml</file> <file>AccountPicker.qml</file>
<file>TooltipArea.qml</file>
<file>jdenticon/jdenticon-1.0.1.min.js</file> <file>jdenticon/jdenticon-1.0.1.min.js</file>
</qresource> </qresource>
</RCC> </RCC>