Account switcher

This commit is contained in:
tobtoht 2021-05-22 15:42:26 +02:00
parent 21d29fa7ee
commit 50a77307f4
No known key found for this signature in database
GPG key ID: 1CADD27F41F45C3C
19 changed files with 421 additions and 44 deletions

View file

@ -41,6 +41,9 @@ AppContext::AppContext(Wallet *wallet)
connect(this->wallet.get(), &Wallet::connectionStatusChanged, [this]{
this->nodes->autoConnect();
});
connect(this->wallet.get(), &Wallet::currentSubaddressAccountChanged, [this]{
this->updateBalance();
});
connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError);
@ -54,6 +57,10 @@ AppContext::AppContext(Wallet *wallet)
// force trigger preferredFiat signal for history model
this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString());
connect(this->wallet->history(), &TransactionHistory::txNoteChanged, [this]{
this->wallet->history()->refresh(this->wallet->currentSubaddressAccount());
});
}
// ################## Transaction creation ##################

View file

@ -19,6 +19,7 @@
<file>assets/images/bitcoin.png</file>
<file>assets/images/camera_dark.png</file>
<file>assets/images/camera_white.png</file>
<file>assets/images/change_account.png</file>
<file>assets/images/clock1.png</file>
<file>assets/images/clock2.png</file>
<file>assets/images/clock3.png</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,107 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "AccountSwitcherDialog.h"
#include "ui_AccountSwitcherDialog.h"
#include "libwalletqt/SubaddressAccount.h"
#include "utils/Icons.h"
#include <QMenu>
AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent)
, ui(new Ui::AccountSwitcherDialog)
, m_ctx(std::move(ctx))
, m_model(m_ctx->wallet->subaddressAccountModel())
{
ui->setupUi(this);
ui->accounts->setContextMenuPolicy(Qt::CustomContextMenu);
m_ctx->wallet->subaddressAccount()->refresh();
ui->accounts->setModel(m_ctx->wallet->subaddressAccountModel());
ui->accounts->setSelectionMode(QAbstractItemView::SingleSelection);
ui->accounts->setSelectionBehavior(QAbstractItemView::SelectRows);
ui->accounts->hideColumn(SubaddressAccountModel::Column::Address);
ui->accounts->hideColumn(SubaddressAccountModel::Column::UnlockedBalance);
ui->accounts->setColumnWidth(SubaddressAccountModel::Column::Label, 200);
ui->accounts->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->accounts->header()->setSectionResizeMode(SubaddressAccountModel::Label, QHeaderView::Stretch);
connect(ui->accounts->selectionModel(), &QItemSelectionModel::currentChanged, this, &AccountSwitcherDialog::switchAccount);
connect(ui->accounts, &QTreeView::customContextMenuRequested, this, &AccountSwitcherDialog::showContextMenu);
connect(ui->accounts, &QTreeView::doubleClicked, [this](QModelIndex index){
if (!m_model) return;
if (!(m_model->flags(index) & Qt::ItemIsEditable)) {
this->switchAccount();
}
});
connect(ui->btn_newAccount, &QPushButton::clicked, [this]{
m_ctx->wallet->addSubaddressAccount("New account");
m_ctx->wallet->subaddressAccount()->refresh();
});
connect(m_ctx->wallet.get(), &Wallet::currentSubaddressAccountChanged, this, &AccountSwitcherDialog::updateSelection);
connect(m_ctx->wallet->subaddressAccount(), &SubaddressAccount::refreshFinished, this, &AccountSwitcherDialog::updateSelection);
this->updateSelection();
}
void AccountSwitcherDialog::switchAccount() {
QModelIndex index = ui->accounts->currentIndex();
m_ctx->wallet->switchSubaddressAccount(index.row());
}
void AccountSwitcherDialog::copyLabel() {
auto row = this->currentEntry();
if (!row)
return;
Utils::copyToClipboard(QString::fromStdString(row->getLabel()));
}
void AccountSwitcherDialog::copyBalance() {
auto row = this->currentEntry();
if (!row)
return;
Utils::copyToClipboard(QString::fromStdString(row->getBalance()));
}
void AccountSwitcherDialog::editLabel() {
QModelIndex index = ui->accounts->currentIndex().siblingAtColumn(m_ctx->wallet->subaddressAccountModel()->Column::Label);
ui->accounts->setCurrentIndex(index);
ui->accounts->edit(index);
}
void AccountSwitcherDialog::updateSelection() {
qDebug() << "test";
QModelIndex index = m_model->index(m_ctx->wallet->currentSubaddressAccount(), 0);
ui->accounts->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
void AccountSwitcherDialog::showContextMenu(const QPoint &point) {
QModelIndex index = ui->accounts->currentIndex();
if (!index.isValid()) {
return;
}
auto *menu = new QMenu(ui->accounts);
menu->addAction("Switch to account", this, &AccountSwitcherDialog::switchAccount);
menu->addAction("Copy label", this, &AccountSwitcherDialog::copyLabel);
menu->addAction("Copy balance", this, &AccountSwitcherDialog::copyBalance);
menu->addAction("Edit label", this, &AccountSwitcherDialog::editLabel);
menu->popup(ui->accounts->viewport()->mapToGlobal(point));
}
Monero::SubaddressAccountRow* AccountSwitcherDialog::currentEntry() {
QModelIndex index = ui->accounts->currentIndex();
return m_ctx->wallet->subaddressAccountModel()->entryFromIndex(index);
}
AccountSwitcherDialog::~AccountSwitcherDialog() {
delete ui;
}

View file

@ -0,0 +1,41 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_ACCOUNTSWITCHERDIALOG_H
#define FEATHER_ACCOUNTSWITCHERDIALOG_H
#include <QDialog>
#include "appcontext.h"
#include "model/SubaddressAccountModel.h"
namespace Ui {
class AccountSwitcherDialog;
}
class AccountSwitcherDialog : public QDialog
{
Q_OBJECT
public:
explicit AccountSwitcherDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
~AccountSwitcherDialog() override;
private slots:
void showContextMenu(const QPoint& point);
void updateSelection();
private:
void switchAccount();
void copyLabel();
void copyBalance();
void editLabel();
Monero::SubaddressAccountRow* currentEntry();
Ui::AccountSwitcherDialog *ui;
QSharedPointer<AppContext> m_ctx;
SubaddressAccountModel *m_model;
};
#endif //FEATHER_ACCOUNTSWITCHERDIALOG_H

View file

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AccountSwitcherDialog</class>
<widget class="QDialog" name="AccountSwitcherDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>521</width>
<height>275</height>
</rect>
</property>
<property name="windowTitle">
<string>Accounts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="accounts">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_newAccount">
<property name="text">
<string>New account</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AccountSwitcherDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AccountSwitcherDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -41,6 +41,10 @@ void Coins::refresh(quint32 accountIndex)
m_pimpl->refresh();
for (const auto i : m_pimpl->getAll()) {
if (i->subaddrAccount() != accountIndex) {
continue;
}
m_tinfo.append(new CoinsInfo(i, this));
}
}

View file

@ -62,3 +62,8 @@ quint64 SubaddressAccount::count() const
return m_rows.size();
}
Monero::SubaddressAccountRow* SubaddressAccount::row(int index) const
{
return m_rows.value(index);
}

View file

@ -22,6 +22,7 @@ public:
Q_INVOKABLE void setLabel(quint32 accountIndex, const QString &label) const;
Q_INVOKABLE void refresh() const;
quint64 count() const;
Monero::SubaddressAccountRow* row(int index) const;
signals:
void refreshStarted() const;

View file

@ -104,7 +104,6 @@ void TransactionHistory::refresh(quint32 accountIndex)
void TransactionHistory::setTxNote(const QString &txid, const QString &note)
{
m_pimpl->setTxNote(txid.toStdString(), note.toStdString());
this->refresh(0); // todo: get actual account index
emit txNoteChanged();
}

View file

@ -60,6 +60,7 @@ private:
// history contains locked transfers
mutable bool m_locked;
quint32 lastAccountIndex = 0;
};
#endif // TRANSACTIONHISTORY_H

View file

@ -375,6 +375,8 @@ void Wallet::switchSubaddressAccount(quint32 accountIndex)
}
m_subaddress->refresh(m_currentSubaddressAccount);
m_history->refresh(m_currentSubaddressAccount);
m_coins->refresh(m_currentSubaddressAccount);
this->subaddressModel()->setCurrentSubaddressAcount(m_currentSubaddressAccount);
emit currentSubaddressAccountChanged();
}
}

View file

@ -20,6 +20,7 @@
#include "dialog/balancedialog.h"
#include "dialog/WalletCacheDebugDialog.h"
#include "dialog/UpdateDialog.h"
#include "dialog/AccountSwitcherDialog.h"
#include "constants.h"
#include "libwalletqt/AddressBook.h"
#include "utils/AsyncTask.h"
@ -128,6 +129,10 @@ void MainWindow::initStatusBar() {
connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, this, &MainWindow::showConnectionStatusDialog);
this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator);
m_statusAccountSwitcher = new StatusBarButton(icons()->icon("change_account.png"), "Account switcher", this);
connect(m_statusAccountSwitcher, &StatusBarButton::clicked, this, &MainWindow::showAccountSwitcherDialog);
this->statusBar()->addPermanentWidget(m_statusAccountSwitcher);
m_statusBtnPassword = new StatusBarButton(icons()->icon("lock.svg"), "Password", this);
connect(m_statusBtnPassword, &StatusBarButton::clicked, this, &MainWindow::showPasswordDialog);
this->statusBar()->addPermanentWidget(m_statusBtnPassword);
@ -212,6 +217,7 @@ void MainWindow::initMenu() {
// [Wallet]
connect(ui->actionInformation, &QAction::triggered, this, &MainWindow::showWalletInfoDialog);
connect(ui->actionAccount, &QAction::triggered, this, &MainWindow::showAccountSwitcherDialog);
connect(ui->actionPassword, &QAction::triggered, this, &MainWindow::showPasswordDialog);
connect(ui->actionSeed, &QAction::triggered, this, &MainWindow::showSeedDialog);
connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog);
@ -439,12 +445,13 @@ void MainWindow::onWalletOpened() {
this->setStatusText("Wallet opened - Searching for node");
// receive page
m_ctx->wallet->subaddress()->refresh( m_ctx->wallet->currentSubaddressAccount());
m_ctx->wallet->subaddress()->refresh(m_ctx->wallet->currentSubaddressAccount());
if (m_ctx->wallet->subaddress()->count() == 1) {
for (int i = 0; i < 10; i++) {
m_ctx->wallet->subaddress()->addRow(m_ctx->wallet->currentSubaddressAccount(), "");
}
}
m_ctx->wallet->subaddressModel()->setCurrentSubaddressAcount(m_ctx->wallet->currentSubaddressAccount());
// history page
m_ctx->wallet->history()->refresh(m_ctx->wallet->currentSubaddressAccount());
@ -903,6 +910,11 @@ void MainWindow::showWalletCacheDebugDialog() {
dialog.exec();
}
void MainWindow::showAccountSwitcherDialog() {
AccountSwitcherDialog dialog{m_ctx, this};
dialog.exec();
}
void MainWindow::showNodeExhaustedMessage() {
// Spawning dialogs inside a lambda can cause system freezes on linux so we have to do it this way ¯\_(ツ)_/¯

View file

@ -155,6 +155,7 @@ private slots:
void showKeysDialog();
void showViewOnlyDialog();
void showWalletCacheDebugDialog();
void showAccountSwitcherDialog();
void donateButtonClicked();
void showCalcWindow();
@ -230,6 +231,7 @@ private:
ClickableLabel *m_statusLabelBalance;
QLabel *m_statusLabelStatus;
QLabel *m_statusLabelNetStats;
StatusBarButton *m_statusAccountSwitcher;
StatusBarButton *m_statusBtnConnectionStatusIndicator;
StatusBarButton *m_statusBtnPassword;
StatusBarButton *m_statusBtnPreferences;

View file

@ -403,6 +403,7 @@
<addaction name="actionInformation"/>
<addaction name="menuAdvanced"/>
<addaction name="separator"/>
<addaction name="actionAccount"/>
<addaction name="actionPassword"/>
<addaction name="actionSeed"/>
<addaction name="actionKeys"/>
@ -742,6 +743,30 @@
<string>t</string>
</property>
</action>
<action name="actiond">
<property name="text">
<string>d</string>
</property>
</action>
<action name="actionPrimary_account">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Primary account</string>
</property>
</action>
<action name="actionAccount">
<property name="text">
<string>Account</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View file

@ -7,7 +7,8 @@
#include <QDebug>
SubaddressAccountModel::SubaddressAccountModel(QObject *parent, SubaddressAccount *subaddressAccount)
: QAbstractListModel(parent), m_subaddressAccount(subaddressAccount)
: QAbstractTableModel(parent)
, m_subaddressAccount(subaddressAccount)
{
connect(m_subaddressAccount, &SubaddressAccount::refreshStarted, this, &SubaddressAccountModel::startReset);
connect(m_subaddressAccount, &SubaddressAccount::refreshFinished, this, &SubaddressAccountModel::endReset);
@ -16,13 +17,26 @@ SubaddressAccountModel::SubaddressAccountModel(QObject *parent, SubaddressAccoun
void SubaddressAccountModel::startReset(){
beginResetModel();
}
void SubaddressAccountModel::endReset(){
endResetModel();
}
int SubaddressAccountModel::rowCount(const QModelIndex &) const
int SubaddressAccountModel::rowCount(const QModelIndex &parent) const
{
return m_subaddressAccount->count();
if (parent.isValid()) {
return 0;
} else {
return m_subaddressAccount->count();
}
}
int SubaddressAccountModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return Column::COUNT;
}
QVariant SubaddressAccountModel::data(const QModelIndex &index, int role) const
@ -32,24 +46,12 @@ QVariant SubaddressAccountModel::data(const QModelIndex &index, int role) const
QVariant result;
bool found = m_subaddressAccount->getRow(index.row(), [&result, &role](const Monero::SubaddressAccountRow &row) {
switch (role) {
case SubaddressAccountAddressRole:
result = QString::fromStdString(row.getAddress());
break;
case SubaddressAccountLabelRole:
result = QString::fromStdString(row.getLabel());
break;
case SubaddressAccountBalanceRole:
result = QString::fromStdString(row.getBalance());
break;
case SubaddressAccountUnlockedBalanceRole:
result = QString::fromStdString(row.getUnlockedBalance());
break;
default:
qCritical() << "Unimplemented role" << role;
bool found = m_subaddressAccount->getRow(index.row(), [this, &index, &result, &role](const Monero::SubaddressAccountRow &row) {
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) {
result = parseSubaddressAccountRow(row, index, role);
}
});
if (!found) {
qCritical("%s: internal error: invalid index %d", __FUNCTION__, index.row());
}
@ -57,15 +59,80 @@ QVariant SubaddressAccountModel::data(const QModelIndex &index, int role) const
return result;
}
QHash<int, QByteArray> SubaddressAccountModel::roleNames() const
QVariant SubaddressAccountModel::parseSubaddressAccountRow(const Monero::SubaddressAccountRow &row,
const QModelIndex &index, int role) const
{
static QHash<int, QByteArray> roleNames;
if (roleNames.empty())
{
roleNames.insert(SubaddressAccountAddressRole, "address");
roleNames.insert(SubaddressAccountLabelRole, "label");
roleNames.insert(SubaddressAccountBalanceRole, "balance");
roleNames.insert(SubaddressAccountUnlockedBalanceRole, "unlockedBalance");
switch (index.column()) {
case Number:
return QString("#%1").arg(QString::number(index.row()));
case Address:
return QString::fromStdString(row.getAddress());
case Label:
return QString::fromStdString(row.getLabel());
case Balance:
return QString::fromStdString(row.getBalance());
case UnlockedBalance:
return QString::fromStdString(row.getUnlockedBalance());
default:
return QVariant();
}
return roleNames;
}
QVariant SubaddressAccountModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole) {
return QVariant();
}
if (orientation == Qt::Horizontal)
{
switch (section) {
case Address:
return QString("Address");
case Label:
return QString("Label");
case Balance:
return QString("Balance");
case UnlockedBalance:
return QString("Spendable balance");
default:
return QVariant();
}
}
return QVariant();
}
bool SubaddressAccountModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
const int row = index.row();
switch (index.column()) {
case Label:
m_subaddressAccount->setLabel(row, value.toString());
break;
default:
return false;
}
emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
return true;
}
return false;
}
Qt::ItemFlags SubaddressAccountModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
if (index.column() == Label)
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
return QAbstractTableModel::flags(index);
}
Monero::SubaddressAccountRow* SubaddressAccountModel::entryFromIndex(const QModelIndex &index) const {
Q_ASSERT(index.isValid() && index.row() < m_subaddressAccount->count());
return m_subaddressAccount->row(index.row());
}

View file

@ -4,35 +4,44 @@
#ifndef SUBADDRESSACCOUNTMODEL_H
#define SUBADDRESSACCOUNTMODEL_H
#include <QAbstractListModel>
#include "Subaddress.h"
#include <QAbstractTableModel>
class SubaddressAccount;
class SubaddressAccountModel : public QAbstractListModel
class SubaddressAccountModel : public QAbstractTableModel
{
Q_OBJECT
public:
enum SubaddressAccountRowRole {
SubaddressAccountRole = Qt::UserRole + 1, // for the SubaddressAccountRow object;
SubaddressAccountAddressRole,
SubaddressAccountLabelRole,
SubaddressAccountBalanceRole,
SubaddressAccountUnlockedBalanceRole,
enum Column {
Number,
Address,
Label,
Balance,
UnlockedBalance,
COUNT
};
Q_ENUM(SubaddressAccountRowRole)
SubaddressAccountModel(QObject *parent, SubaddressAccount *subaddressAccount);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
Monero::SubaddressAccountRow* entryFromIndex(const QModelIndex &index) const;
public slots:
void startReset();
void endReset();
private:
QVariant parseSubaddressAccountRow(const Monero::SubaddressAccountRow &row, const QModelIndex &index, int role) const;
SubaddressAccount *m_subaddressAccount;
};

View file

@ -11,9 +11,9 @@
#include <QBrush>
SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress)
: QAbstractTableModel(parent),
m_subaddress(subaddress),
m_showFullAddresses(false)
: QAbstractTableModel(parent)
, m_subaddress(subaddress)
, m_showFullAddresses(false)
{
connect(m_subaddress, &Subaddress::refreshStarted, this, &SubaddressModel::startReset);
connect(m_subaddress, &Subaddress::refreshFinished, this, &SubaddressModel::endReset);
@ -143,7 +143,7 @@ bool SubaddressModel::setData(const QModelIndex &index, const QVariant &value, i
switch (index.column()) {
case Label:
m_subaddress->setLabel(0, row, value.toString()); // Todo: get actual account index
m_subaddress->setLabel(m_currentSubaddressAcount, row, value.toString());
break;
default:
return false;
@ -180,6 +180,10 @@ int SubaddressModel::unusedLookahead() const {
return m_subaddress->unusedLookahead();
}
void SubaddressModel::setCurrentSubaddressAcount(quint32 accountIndex) {
m_currentSubaddressAcount = accountIndex;
}
Monero::SubaddressRow* SubaddressModel::entryFromIndex(const QModelIndex &index) const {
Q_ASSERT(index.isValid() && index.row() < m_subaddress->count());
return m_subaddress->row(index.row());

View file

@ -40,6 +40,7 @@ public:
Monero::SubaddressRow* entryFromIndex(const QModelIndex &index) const;
void setCurrentSubaddressAcount(quint32 accountIndex);
int unusedLookahead() const;
public slots:
@ -51,6 +52,7 @@ private:
QVariant parseSubaddressRow(const Monero::SubaddressRow &subaddress, const QModelIndex &index, int role) const;
bool m_showFullAddresses;
quint32 m_currentSubaddressAcount;
};
#endif // SUBADDRESSMODEL_H