[GUI] Progress circa wallet and keys
- Fixes to wallet - Open or create wallet on startup - Add support for importing keys - Show user what level of ownership they have over accounts
This commit is contained in:
parent
4d05f09613
commit
03e16afdb2
14 changed files with 317 additions and 180 deletions
|
|
@ -4,6 +4,19 @@
|
||||||
|
|
||||||
#include <graphene/chain/account_object.hpp>
|
#include <graphene/chain/account_object.hpp>
|
||||||
|
|
||||||
|
void Account::setAccountObject(const graphene::chain::account_object& obj)
|
||||||
|
{
|
||||||
|
auto old_name = m_account.name;
|
||||||
|
m_account = obj;
|
||||||
|
if (old_name != m_account.name)
|
||||||
|
Q_EMIT nameChanged();
|
||||||
|
if (!m_loaded) {
|
||||||
|
m_loaded = true;
|
||||||
|
Q_EMIT loaded();
|
||||||
|
qDebug() << name() << "loaded.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QQmlListProperty<Balance> Account::balances()
|
QQmlListProperty<Balance> Account::balances()
|
||||||
{
|
{
|
||||||
auto count = [](QQmlListProperty<Balance>* list) {
|
auto count = [](QQmlListProperty<Balance>* list) {
|
||||||
|
|
@ -16,8 +29,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 +40,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 +70,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);
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,11 @@ 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(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,13 +33,7 @@ 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); }
|
||||||
QQmlListProperty<Balance> balances();
|
QQmlListProperty<Balance> balances();
|
||||||
|
|
@ -59,9 +55,10 @@ 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 loaded();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,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 +98,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 +151,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 +166,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()
|
||||||
|
|
@ -343,11 +350,11 @@ QList<QPair<QString,QString> > Wallet::getAllPublicKeys( bool only_if_private )c
|
||||||
|
|
||||||
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 +374,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 +423,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 +431,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 +454,3 @@ const flat_set<public_key_type>& Wallet::getAvailablePrivateKeys()const
|
||||||
{
|
{
|
||||||
return _available_private_keys;
|
return _available_private_keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
@ -26,7 +28,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 +38,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 +57,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 +72,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);
|
||||||
|
|
@ -150,3 +143,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)
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ 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
|
||||||
|
onAccountControlLevelChanged: console.log("New acl: " + accountControlLevel)
|
||||||
/// 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]})
|
||||||
: null
|
: null
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
|
|
@ -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.")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
programs/light_client/qml/TooltipArea.qml
Normal file
23
programs/light_client/qml/TooltipArea.qml
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ FormBase {
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue