From 50a77307f477fd63a722a1a3fff9bafc38c7f37a Mon Sep 17 00:00:00 2001 From: tobtoht <thotbot@protonmail.com> Date: Sat, 22 May 2021 15:42:26 +0200 Subject: [PATCH] Account switcher --- src/appcontext.cpp | 7 ++ src/assets.qrc | 1 + src/assets/images/change_account.png | Bin 0 -> 1939 bytes src/dialog/AccountSwitcherDialog.cpp | 107 +++++++++++++++++++++ src/dialog/AccountSwitcherDialog.h | 41 +++++++++ src/dialog/AccountSwitcherDialog.ui | 88 ++++++++++++++++++ src/libwalletqt/Coins.cpp | 4 + src/libwalletqt/SubaddressAccount.cpp | 5 + src/libwalletqt/SubaddressAccount.h | 1 + src/libwalletqt/TransactionHistory.cpp | 1 - src/libwalletqt/TransactionHistory.h | 1 + src/libwalletqt/Wallet.cpp | 2 + src/mainwindow.cpp | 14 ++- src/mainwindow.h | 2 + src/mainwindow.ui | 25 +++++ src/model/SubaddressAccountModel.cpp | 123 +++++++++++++++++++------ src/model/SubaddressAccountModel.h | 29 ++++-- src/model/SubaddressModel.cpp | 12 ++- src/model/SubaddressModel.h | 2 + 19 files changed, 421 insertions(+), 44 deletions(-) create mode 100644 src/assets/images/change_account.png create mode 100644 src/dialog/AccountSwitcherDialog.cpp create mode 100644 src/dialog/AccountSwitcherDialog.h create mode 100644 src/dialog/AccountSwitcherDialog.ui diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 6ae6b9f..96b4563 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -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 ################## diff --git a/src/assets.qrc b/src/assets.qrc index 8f7488a..9956d8b 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -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> diff --git a/src/assets/images/change_account.png b/src/assets/images/change_account.png new file mode 100644 index 0000000000000000000000000000000000000000..f24a4ba220b3284a6ec57e84f44b60ce333810a1 GIT binary patch literal 1939 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 Z<G*z*J*qF3T-yKu002ovPDHLkV1hg7#BTrq literal 0 HcmV?d00001 diff --git a/src/dialog/AccountSwitcherDialog.cpp b/src/dialog/AccountSwitcherDialog.cpp new file mode 100644 index 0000000..dfa66db --- /dev/null +++ b/src/dialog/AccountSwitcherDialog.cpp @@ -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; +} diff --git a/src/dialog/AccountSwitcherDialog.h b/src/dialog/AccountSwitcherDialog.h new file mode 100644 index 0000000..59cc79b --- /dev/null +++ b/src/dialog/AccountSwitcherDialog.h @@ -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 diff --git a/src/dialog/AccountSwitcherDialog.ui b/src/dialog/AccountSwitcherDialog.ui new file mode 100644 index 0000000..d332d8e --- /dev/null +++ b/src/dialog/AccountSwitcherDialog.ui @@ -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> diff --git a/src/libwalletqt/Coins.cpp b/src/libwalletqt/Coins.cpp index c275cab..d0440db 100644 --- a/src/libwalletqt/Coins.cpp +++ b/src/libwalletqt/Coins.cpp @@ -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)); } } diff --git a/src/libwalletqt/SubaddressAccount.cpp b/src/libwalletqt/SubaddressAccount.cpp index db885e8..5aa304a 100644 --- a/src/libwalletqt/SubaddressAccount.cpp +++ b/src/libwalletqt/SubaddressAccount.cpp @@ -62,3 +62,8 @@ quint64 SubaddressAccount::count() const return m_rows.size(); } + +Monero::SubaddressAccountRow* SubaddressAccount::row(int index) const +{ + return m_rows.value(index); +} \ No newline at end of file diff --git a/src/libwalletqt/SubaddressAccount.h b/src/libwalletqt/SubaddressAccount.h index e2db11e..7cc380e 100644 --- a/src/libwalletqt/SubaddressAccount.h +++ b/src/libwalletqt/SubaddressAccount.h @@ -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; diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp index 42d2349..e355275 100644 --- a/src/libwalletqt/TransactionHistory.cpp +++ b/src/libwalletqt/TransactionHistory.cpp @@ -104,7 +104,6 @@ void TransactionHistory::refresh(quint32 accountIndex) void TransactionHistory::setTxNote(const QString &txid, const QString ¬e) { m_pimpl->setTxNote(txid.toStdString(), note.toStdString()); - this->refresh(0); // todo: get actual account index emit txNoteChanged(); } diff --git a/src/libwalletqt/TransactionHistory.h b/src/libwalletqt/TransactionHistory.h index c00a387..15b0ba2 100644 --- a/src/libwalletqt/TransactionHistory.h +++ b/src/libwalletqt/TransactionHistory.h @@ -60,6 +60,7 @@ private: // history contains locked transfers mutable bool m_locked; + quint32 lastAccountIndex = 0; }; #endif // TRANSACTIONHISTORY_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 6edffa2..29bd6c9 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -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(); } } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index a535881..7be6943 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -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 ¯\_(ツ)_/¯ diff --git a/src/mainwindow.h b/src/mainwindow.h index e59e4ae..4d14251 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -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; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 5d503c8..4ecec6b 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -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> diff --git a/src/model/SubaddressAccountModel.cpp b/src/model/SubaddressAccountModel.cpp index 8a1f75f..53e9109 100644 --- a/src/model/SubaddressAccountModel.cpp +++ b/src/model/SubaddressAccountModel.cpp @@ -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()); +} \ No newline at end of file diff --git a/src/model/SubaddressAccountModel.h b/src/model/SubaddressAccountModel.h index cbc7019..b49f5ba 100644 --- a/src/model/SubaddressAccountModel.h +++ b/src/model/SubaddressAccountModel.h @@ -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; }; diff --git a/src/model/SubaddressModel.cpp b/src/model/SubaddressModel.cpp index 7b170b3..bad56e3 100644 --- a/src/model/SubaddressModel.cpp +++ b/src/model/SubaddressModel.cpp @@ -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()); diff --git a/src/model/SubaddressModel.h b/src/model/SubaddressModel.h index 0b8eb0c..3056c02 100644 --- a/src/model/SubaddressModel.h +++ b/src/model/SubaddressModel.h @@ -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