Receive: persistent settings, cleanup

This commit is contained in:
tobtoht 2023-11-16 16:26:58 +01:00
parent 0616384358
commit a13ca973cc
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
17 changed files with 445 additions and 280 deletions

View file

@ -41,6 +41,8 @@ file(GLOB SOURCE_FILES
"utils/os/*.cpp"
"libwalletqt/*.h"
"libwalletqt/*.cpp"
"libwalletqt/rows/*.h"
"libwalletqt/rows/*.cpp"
"daemon/*.h"
"daemon/*.cpp"
"model/*.h"
@ -129,6 +131,7 @@ target_include_directories(feather PUBLIC
${CMAKE_BINARY_DIR}/src/feather_autogen/include
${CMAKE_SOURCE_DIR}/monero/include
${CMAKE_SOURCE_DIR}/monero/src
${CMAKE_SOURCE_DIR}/monero/external
${CMAKE_SOURCE_DIR}/monero/external/easylogging++
${CMAKE_SOURCE_DIR}/monero/contrib/epee/include
${CMAKE_SOURCE_DIR}/src

View file

@ -9,6 +9,7 @@
#include "dialog/PaymentRequestDialog.h"
#include "dialog/QrCodeDialog.h"
#include "utils/config.h"
#include "utils/Icons.h"
#include "utils/Utils.h"
@ -22,7 +23,6 @@ ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent)
m_model = m_wallet->subaddressModel();
m_proxyModel = new SubaddressProxyModel(this, m_wallet->subaddress());
m_proxyModel->setSourceModel(m_model);
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
ui->addresses->setModel(m_proxyModel);
ui->addresses->setColumnHidden(SubaddressModel::isUsed, true);
@ -42,11 +42,32 @@ ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent)
// header context menu
ui->addresses->header()->setContextMenuPolicy(Qt::CustomContextMenu);
m_headerMenu = new QMenu(this);
m_showFullAddressesAction = m_headerMenu->addAction("Show full addresses", this, &ReceiveWidget::setShowFullAddresses);
m_showFullAddressesAction->setCheckable(true);
m_showChangeAddressesAction = m_headerMenu->addAction("Show change addresses", this, &ReceiveWidget::setShowChangeAddresses);
m_showChangeAddressesAction->setCheckable(true);
auto subMenu = new QMenu(this);
subMenu->setTitle("Columns");
this->addOption(m_headerMenu, "Show used addresses", Config::showUsedAddresses, [this](bool show){
m_proxyModel->invalidate();
});
this->addOption(m_headerMenu, "Show hidden addresses", Config::showHiddenAddresses, [this](bool show){
m_proxyModel->invalidate();
});
this->addOption(m_headerMenu, "Show full addresses", Config::showFullAddresses, [this](bool show){
m_proxyModel->invalidate();
});
this->addOption(m_headerMenu, "Show change address", Config::showChangeAddresses, [this](bool show){
m_proxyModel->invalidate();
});
m_headerMenu->addMenu(subMenu);
this->addOption(subMenu, "Show index", Config::showAddressIndex, [this](bool show){
ui->addresses->setColumnHidden(0, !show);
});
this->addOption(subMenu, "Show labels", Config::showAddressLabels, [this](bool show){
ui->addresses->setColumnHidden(2, !show);
});
connect(ui->addresses->header(), &QHeaderView::customContextMenuRequested, this, &ReceiveWidget::showHeaderMenu);
ui->toolBtn_options->setMenu(m_headerMenu);
// context menu
ui->addresses->setContextMenuPolicy(Qt::CustomContextMenu);
@ -59,14 +80,23 @@ ReceiveWidget::ReceiveWidget(Wallet *wallet, QWidget *parent)
connect(ui->qrCode, &ClickableLabel::clicked, this, &ReceiveWidget::showQrCodeDialog);
connect(ui->search, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter);
connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses);
connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses);
connect(ui->btn_createPaymentRequest, &QPushButton::clicked, this, &ReceiveWidget::createPaymentRequest);
}
void ReceiveWidget::addOption(QMenu *menu, const QString &text, Config::ConfigKey key, const std::function<void(bool show)>& func) {
// QMenu takes ownership of the returned QAction.
QAction *action = menu->addAction(text, func);
action->setCheckable(true);
bool toggled = conf()->get(key).toBool();
action->setChecked(toggled);
func(toggled);
connect(action, &QAction::toggled, [key](bool toggled){
conf()->set(key, toggled);
});
}
void ReceiveWidget::setSearchbarVisible(bool visible) {
ui->search->setVisible(visible);
ui->frame_search->setVisible(visible);
}
void ReceiveWidget::focusSearchbar() {
@ -91,28 +121,40 @@ void ReceiveWidget::editLabel() {
}
void ReceiveWidget::showContextMenu(const QPoint &point) {
Monero::SubaddressRow* row = this->currentEntry();
SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = QString::fromStdString(row->getAddress());
bool isUsed = row->isUsed();
auto *menu = new QMenu(ui->addresses);
menu->addAction("Copy address", this, &ReceiveWidget::copyAddress);
menu->addAction("Copy label", this, &ReceiveWidget::copyLabel);
menu->addAction("Edit label", this, &ReceiveWidget::editLabel);
if (isUsed) {
if (row->isUsed()) {
menu->addAction(m_showTransactionsAction);
}
QStringList hiddenAddresses = this->getHiddenAddresses();
if (hiddenAddresses.contains(address)) {
menu->addAction("Unhide address", this, &ReceiveWidget::showAddress);
} else {
menu->addAction("Hide address", this, &ReceiveWidget::hideAddress);
}
QAction *actionPin = menu->addAction("Pin address", [this](bool toggled){
SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = row->getAddress();
m_wallet->subaddress()->setPinned(address, toggled);
m_proxyModel->invalidate();
});
actionPin->setCheckable(true);
actionPin->setChecked(row->isPinned());
QAction *actionHide = menu->addAction("Hide address", [this](bool toggled){
SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = row->getAddress();
m_wallet->subaddress()->setHidden(address, toggled);
m_proxyModel->invalidate();
});
actionHide->setCheckable(true);
actionHide->setChecked(row->isHidden());
if (m_wallet->isHwBacked()) {
menu->addAction("Show on device", this, &ReceiveWidget::showOnDevice);
@ -143,66 +185,26 @@ void ReceiveWidget::onShowTransactions() {
emit showTransactions(address);
}
void ReceiveWidget::setShowChangeAddresses(bool show) {
if (!m_proxyModel) return;
m_proxyModel->setShowChangeAddresses(show);
}
void ReceiveWidget::setShowFullAddresses(bool show) {
if (!m_model) return;
m_model->setShowFullAddresses(show);
}
void ReceiveWidget::setShowUsedAddresses(bool show) {
if (!m_proxyModel) return;
m_proxyModel->setShowUsed(show);
}
void ReceiveWidget::setShowHiddenAddresses(bool show) {
if (!m_proxyModel) return;
m_proxyModel->setShowHidden(show);
}
void ReceiveWidget::setSearchFilter(const QString &filter) {
if (!m_proxyModel) return;
m_proxyModel->setSearchFilter(filter);
}
void ReceiveWidget::showHeaderMenu(const QPoint& position)
{
Q_UNUSED(position);
m_showFullAddressesAction->setChecked(m_model->isShowFullAddresses());
Q_UNUSED(position)
m_headerMenu->exec(QCursor::pos());
}
void ReceiveWidget::hideAddress()
{
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = QString::fromStdString(row->getAddress());
this->addHiddenAddress(address);
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
}
void ReceiveWidget::showAddress()
{
Monero::SubaddressRow* row = this->currentEntry();
if (!row) return;
QString address = QString::fromStdString(row->getAddress());
this->removeHiddenAddress(address);
m_proxyModel->setHiddenAddresses(this->getHiddenAddresses());
}
void ReceiveWidget::showOnDevice() {
Monero::SubaddressRow* row = this->currentEntry();
SubaddressRow* row = this->currentEntry();
if (!row) return;
m_wallet->deviceShowAddressAsync(m_wallet->currentSubaddressAccount(), row->getRowId(), "");
m_wallet->deviceShowAddressAsync(m_wallet->currentSubaddressAccount(), row->getRow(), "");
}
void ReceiveWidget::generateSubaddress() {
bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), "");
if (!r) {
Utils::showError(this, "Failed to generate subaddress", m_wallet->subaddress()->errorString());
Utils::showError(this, "Failed to generate subaddress", m_wallet->subaddress()->getError());
}
}
@ -235,28 +237,7 @@ void ReceiveWidget::showQrCodeDialog() {
dialog.exec();
}
QStringList ReceiveWidget::getHiddenAddresses() {
QString data = m_wallet->getCacheAttribute("feather.hiddenaddresses");
return data.split(",");
}
void ReceiveWidget::addHiddenAddress(const QString& address) {
QStringList hiddenAddresses = this->getHiddenAddresses();
if (!hiddenAddresses.contains(address)) {
hiddenAddresses.append(address);
}
QString data = hiddenAddresses.join(",");
m_wallet->setCacheAttribute("feather.hiddenaddresses", data);
}
void ReceiveWidget::removeHiddenAddress(const QString &address) {
QStringList hiddenAddresses = this->getHiddenAddresses();
hiddenAddresses.removeAll(address);
QString data = hiddenAddresses.join(",");
m_wallet->setCacheAttribute("feather.hiddenaddresses", data);
}
Monero::SubaddressRow* ReceiveWidget::currentEntry() {
SubaddressRow* ReceiveWidget::currentEntry() {
QModelIndexList list = ui->addresses->selectionModel()->selectedRows();
if (list.size() == 1) {
return m_model->entryFromIndex(m_proxyModel->mapToSource(list.first()));

View file

@ -13,6 +13,7 @@
#include "model/SubaddressProxyModel.h"
#include "model/SubaddressModel.h"
#include "qrcode/QrCode.h"
#include "utils/config.h"
namespace Ui {
class ReceiveWidget;
@ -34,10 +35,6 @@ public slots:
void copyLabel();
void editLabel();
void showContextMenu(const QPoint& point);
void setShowFullAddresses(bool show);
void setShowChangeAddresses(bool show);
void setShowUsedAddresses(bool show);
void setShowHiddenAddresses(bool show);
void setSearchFilter(const QString &filter);
void onShowTransactions();
void createPaymentRequest();
@ -47,8 +44,6 @@ signals:
private slots:
void showHeaderMenu(const QPoint& position);
void hideAddress();
void showAddress();
void showOnDevice();
void generateSubaddress();
@ -56,18 +51,14 @@ private:
QScopedPointer<Ui::ReceiveWidget> ui;
Wallet *m_wallet;
QMenu *m_headerMenu;
QAction *m_showFullAddressesAction;
QAction *m_showTransactionsAction;
QAction *m_showChangeAddressesAction;
SubaddressModel *m_model;
SubaddressProxyModel *m_proxyModel;
void addOption(QMenu *menu, const QString &text, Config::ConfigKey key, const std::function<void(bool show)>& func);
void updateQrCode();
void showQrCodeDialog();
QStringList getHiddenAddresses();
void addHiddenAddress(const QString& address);
void removeHiddenAddress(const QString& address);
Monero::SubaddressRow* currentEntry();
SubaddressRow* currentEntry();
};
#endif //FEATHER_RECEIVEWIDGET_H

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>878</width>
<height>512</height>
<height>403</height>
</rect>
</property>
<property name="windowTitle">
@ -27,16 +27,54 @@
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="search">
<property name="enabled">
<bool>true</bool>
<widget class="QFrame" name="frame_search">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Search...</string>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLineEdit" name="search">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Search...</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolBtn_options">
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="assets.qrc">
<normaloff>:/assets/images/preferences.svg</normaloff>:/assets/images/preferences.svg</iconset>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
@ -89,27 +127,6 @@
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="check_showUsed">
<property name="text">
<string>Show used</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="check_showHidden">
<property name="text">
<string>Show hidden</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="btn_generateSubaddress">
<property name="text">
@ -135,6 +152,8 @@
<header>model/SubaddressView.h</header>
</customwidget>
</customwidgets>
<resources/>
<resources>
<include location="assets.qrc"/>
</resources>
<connections/>
</ui>

View file

@ -65,6 +65,7 @@
<file>assets/images/password-show-off.svg</file>
<file>assets/images/password-show-on.svg</file>
<file>assets/images/person.svg</file>
<file>assets/images/pin.png</file>
<file>assets/images/preferences.svg</file>
<file>assets/images/qrcode.png</file>
<file>assets/images/qrcode_white.png</file>

BIN
src/assets/images/pin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -4,46 +4,20 @@
#include "Subaddress.h"
#include <QDebug>
Subaddress::Subaddress(Monero::Subaddress *subaddressImpl, QObject *parent)
Subaddress::Subaddress(Wallet *wallet, tools::wallet2 *wallet2, QObject *parent)
: QObject(parent)
, m_subaddressImpl(subaddressImpl)
, m_unusedLookahead(0)
, m_wallet(wallet)
, m_wallet2(wallet2)
{
getAll();
QString pinned = m_wallet->getCacheAttribute("feather.pinnedaddresses");
m_pinned = pinned.split(",");
QString hidden = m_wallet->getCacheAttribute("feather.hiddenaddresses");
m_hidden = hidden.split(",");
}
QString Subaddress::errorString() const
bool Subaddress::getRow(int index, std::function<void (SubaddressRow &row)> callback) const
{
return QString::fromStdString(m_subaddressImpl->errorString());
}
void Subaddress::getAll() const
{
emit refreshStarted();
{
QWriteLocker locker(&m_lock);
m_unusedLookahead = 0;
m_rows.clear();
for (auto &row: m_subaddressImpl->getAll()) {
m_rows.append(row);
if (row->isUsed())
m_unusedLookahead = 0;
else
m_unusedLookahead += 1;
}
}
emit refreshFinished();
}
bool Subaddress::getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const
{
QReadLocker locker(&m_lock);
if (index < 0 || index >= m_rows.size())
{
return false;
@ -53,48 +27,140 @@ bool Subaddress::getRow(int index, std::function<void (Monero::SubaddressRow &ro
return true;
}
bool Subaddress::addRow(quint32 accountIndex, const QString &label) const
bool Subaddress::addRow(quint32 accountIndex, const QString &label)
{
bool r = m_subaddressImpl->addRow(accountIndex, label.toStdString());
if (r)
getAll();
return r;
}
bool Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const
{
bool r = m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString());
if (r) {
getAll();
emit labelChanged();
// This can fail if hardware device is unplugged during operating, catch here to prevent crash
// Todo: Notify GUI that it was a device error
try
{
m_wallet2->add_subaddress(accountIndex, label.toStdString());
refresh(accountIndex);
}
catch (const std::exception& e)
{
if (m_wallet2->key_on_device()) {
}
m_errorString = QString::fromStdString(e.what());
return false;
}
return true;
}
bool Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label)
{
try {
m_wallet2->set_subaddress_label({accountIndex, addressIndex}, label.toStdString());
refresh(accountIndex);
}
catch (const std::exception& e)
{
return false;
}
return true;
}
bool Subaddress::setHidden(const QString &address, bool hidden)
{
if (hidden) {
if (m_hidden.contains(address)) {
return false;
}
m_hidden.append(address);
}
else {
if (!m_hidden.contains(address)) {
return false;
}
m_hidden.removeAll(address);
}
bool r = m_wallet->setCacheAttribute("feather.hiddenaddresses", m_hidden.join(","));
refresh(m_wallet->currentSubaddressAccount());
return r;
}
bool Subaddress::refresh(quint32 accountIndex) const
bool Subaddress::isHidden(const QString &address)
{
bool r = m_subaddressImpl->refresh(accountIndex);
getAll();
return m_hidden.contains(address);
};
bool Subaddress::setPinned(const QString &address, bool pinned)
{
if (pinned) {
if (m_pinned.contains(address)) {
return false;
}
m_pinned.append(address);
}
else {
if (!m_pinned.contains(address)) {
return false;
}
m_pinned.removeAll(address);
}
bool r = m_wallet->setCacheAttribute("feather.pinnedaddresses", m_pinned.join(","));
refresh(m_wallet->currentSubaddressAccount());
return r;
}
quint64 Subaddress::unusedLookahead() const
bool Subaddress::isPinned(const QString &address)
{
QReadLocker locker(&m_lock);
return m_unusedLookahead;
return m_pinned.contains(address);
}
quint64 Subaddress::count() const
bool Subaddress::refresh(quint32 accountIndex)
{
QReadLocker locker(&m_lock);
emit refreshStarted();
return m_rows.size();
this->clearRows();
for (qsizetype i = 0; i < m_wallet2->get_num_subaddresses(accountIndex); ++i)
{
QString address = QString::fromStdString(m_wallet2->get_subaddress_as_str({accountIndex, (uint32_t)i}));
auto* row = new SubaddressRow{this,
i,
address,
QString::fromStdString(m_wallet2->get_subaddress_label({accountIndex, (uint32_t)i})),
m_wallet2->get_subaddress_used({accountIndex, (uint32_t)i}),
this->isHidden(address),
this->isPinned(address)
};
m_rows.append(row);
}
// Make sure keys are intact. We NEVER want to display incorrect addresses in case of memory corruption.
bool keysCorrupt = m_wallet2->get_device_type() == hw::device::SOFTWARE && !m_wallet2->verify_keys();
if (keysCorrupt) {
clearRows();
LOG_ERROR("KEY INCONSISTENCY DETECTED, WALLET IS IN CORRUPT STATE.");
}
emit refreshFinished();
return !keysCorrupt;
}
Monero::SubaddressRow* Subaddress::row(int index) const
qsizetype Subaddress::count() const
{
return m_rows.length();
}
void Subaddress::clearRows() {
qDeleteAll(m_rows);
m_rows.clear();
}
SubaddressRow* Subaddress::row(int index) const {
return m_rows.value(index);
}
};
QString Subaddress::getError() const {
return m_errorString;
};

View file

@ -12,34 +12,52 @@
#include <QList>
#include <QDateTime>
#include <wallet/wallet2.h>
#include "Wallet.h"
#include "rows/SubaddressRow.h"
class Subaddress : public QObject
{
Q_OBJECT
public:
void getAll() const;
bool getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const;
bool addRow(quint32 accountIndex, const QString &label) const;
bool setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const;
bool refresh(quint32 accountIndex) const;
quint64 unusedLookahead() const;
quint64 count() const;
QString errorString() const;
Monero::SubaddressRow* row(int index) const;
bool getRow(int index, std::function<void (SubaddressRow &row)> callback) const;
bool addRow(quint32 accountIndex, const QString &label);
bool setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label);
bool setHidden(const QString& address, bool hidden);
bool isHidden(const QString& address);
bool setPinned(const QString& address, bool pinned);
bool isPinned(const QString& address);
bool refresh(quint32 accountIndex);
[[nodiscard]] qsizetype count() const;
void clearRows();
[[nodiscard]] SubaddressRow* row(int index) const;
QString getError() const;
signals:
void refreshStarted() const;
void refreshFinished() const;
void labelChanged() const;
public slots:
private:
explicit Subaddress(Monero::Subaddress * subaddressImpl, QObject *parent);
explicit Subaddress(Wallet *wallet, tools::wallet2 *wallet2, QObject *parent);
friend class Wallet;
mutable QReadWriteLock m_lock;
Monero::Subaddress * m_subaddressImpl;
mutable QList<Monero::SubaddressRow*> m_rows;
mutable quint64 m_unusedLookahead;
Wallet* m_wallet;
tools::wallet2 *m_wallet2;
QList<SubaddressRow*> m_rows;
QStringList m_pinned;
QStringList m_hidden;
QString m_errorString;
};
#endif // SUBADDRESS_H

View file

@ -40,7 +40,7 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent)
, m_daemonBlockChainTargetHeight(0)
, m_connectionStatus(Wallet::ConnectionStatus_Disconnected)
, m_currentSubaddressAccount(0)
, m_subaddress(new Subaddress(m_walletImpl->subaddress(), this))
, m_subaddress(new Subaddress(this, wallet->getWallet(), this))
, m_subaddressAccount(new SubaddressAccount(m_walletImpl->subaddressAccount(), this))
, m_refreshNow(false)
, m_refreshEnabled(false)

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "SubaddressRow.h"
bool SubaddressRow::setHidden(bool hidden) {
m_hidden = hidden;
}
bool SubaddressRow::setUsed(bool used) {
m_used = used;
}
bool SubaddressRow::setPinned(bool pinned) {
m_used = pinned;
}
qsizetype SubaddressRow::getRow() const {
return m_row;
}
const QString& SubaddressRow::getAddress() const {
return m_address;
}
const QString& SubaddressRow::getLabel() const {
return m_label;
}
bool SubaddressRow::isUsed() const {
return m_used;
}
bool SubaddressRow::isPinned() const {
return m_pinned;
}
bool SubaddressRow::isHidden() const {
return m_hidden;
}

View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_SUBADDRESSROW_H
#define FEATHER_SUBADDRESSROW_H
#include <QObject>
class SubaddressRow : public QObject
{
Q_OBJECT
public:
SubaddressRow(QObject *parent, qsizetype row, const QString& address, const QString &label, bool used, bool hidden, bool pinned)
: QObject(parent)
, m_row(row)
, m_address(address)
, m_label(label)
, m_used(used)
, m_hidden(hidden)
, m_pinned(pinned) {}
bool setUsed(bool used);
bool setHidden(bool hidden);
bool setPinned(bool pinned);
qsizetype getRow() const;
const QString& getAddress() const;
const QString& getLabel() const;
bool isUsed() const;
bool isHidden() const;
bool isPinned() const;
private:
qsizetype m_row;
QString m_address;
QString m_label;
bool m_used = false;
bool m_hidden = false;
bool m_pinned = false;
};
#endif //FEATHER_SUBADDRESSROW_H

View file

@ -8,13 +8,14 @@
#include <QColor>
#include <QBrush>
#include "utils/config.h"
#include "utils/ColorScheme.h"
#include "utils/Icons.h"
#include "utils/Utils.h"
SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress)
: QAbstractTableModel(parent)
, m_subaddress(subaddress)
, m_showFullAddresses(false)
{
connect(m_subaddress, &Subaddress::refreshStarted, this, &SubaddressModel::startReset);
connect(m_subaddress, &Subaddress::refreshFinished, this, &SubaddressModel::endReset);
@ -52,10 +53,19 @@ QVariant SubaddressModel::data(const QModelIndex &index, int role) const
QVariant result;
bool found = m_subaddress->getRow(index.row(), [this, &index, &role, &result](const Monero::SubaddressRow &subaddress) {
bool found = m_subaddress->getRow(index.row(), [this, &index, &role, &result](const SubaddressRow &subaddress) {
if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole){
result = parseSubaddressRow(subaddress, index, role);
}
else if (role == Qt::DecorationRole) {
if (subaddress.isPinned() && index.column() == ModelColumn::Index) {
result = QVariant(icons()->icon("pin.png"));
}
else if (subaddress.isHidden() && index.column() == ModelColumn::Index) {
result = QVariant(icons()->icon("eye_blind.png"));
}
}
else if (role == Qt::BackgroundRole) {
switch(index.column()) {
case Address:
@ -94,17 +104,25 @@ QVariant SubaddressModel::data(const QModelIndex &index, int role) const
return result;
}
QVariant SubaddressModel::parseSubaddressRow(const Monero::SubaddressRow &subaddress, const QModelIndex &index, int role) const
QVariant SubaddressModel::parseSubaddressRow(const SubaddressRow &subaddress, const QModelIndex &index, int role) const
{
bool showFull = conf()->get(Config::showFullAddresses).toBool();
switch (index.column()) {
case Index:
{
return "#" + QString::number(subaddress.getRowId()) + " ";
if (role == Qt::UserRole) {
if (subaddress.isPinned()) {
return -1;
} else {
return subaddress.getRow();
}
}
return "#" + QString::number(subaddress.getRow()) + " ";
}
case Address:
{
QString address = QString::fromStdString(subaddress.getAddress());
if (!m_showFullAddresses && role != Qt::UserRole) {
QString address = subaddress.getAddress();
if (!showFull && role != Qt::UserRole) {
address = Utils::displayAddress(address);
}
return address;
@ -117,7 +135,7 @@ QVariant SubaddressModel::parseSubaddressRow(const Monero::SubaddressRow &subadd
else if (index.row() == 0) {
return "Change";
}
return QString::fromStdString(subaddress.getLabel());
return subaddress.getLabel();
}
case isUsed:
return subaddress.isUsed();
@ -170,12 +188,6 @@ bool SubaddressModel::setData(const QModelIndex &index, const QVariant &value, i
return false;
}
void SubaddressModel::setShowFullAddresses(bool show)
{
m_showFullAddresses = show;
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
}
Qt::ItemFlags SubaddressModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
@ -187,19 +199,11 @@ Qt::ItemFlags SubaddressModel::flags(const QModelIndex &index) const
return QAbstractTableModel::flags(index);
}
bool SubaddressModel::isShowFullAddresses() const {
return m_showFullAddresses;
}
int SubaddressModel::unusedLookahead() const {
return m_subaddress->unusedLookahead();
}
void SubaddressModel::setCurrentSubaddressAccount(quint32 accountIndex) {
m_currentSubaddressAccount = accountIndex;
}
Monero::SubaddressRow* SubaddressModel::entryFromIndex(const QModelIndex &index) const {
SubaddressRow* SubaddressModel::entryFromIndex(const QModelIndex &index) const {
Q_ASSERT(index.isValid() && index.row() < m_subaddress->count());
return m_subaddress->row(index.row());
}

View file

@ -10,6 +10,8 @@
#include <QSortFilterProxyModel>
#include <QDebug>
#include "rows/SubaddressRow.h"
class Subaddress;
class SubaddressModel : public QAbstractTableModel
@ -36,13 +38,9 @@ public:
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
bool isShowFullAddresses() const;
void setShowFullAddresses(bool show);
Monero::SubaddressRow* entryFromIndex(const QModelIndex &index) const;
SubaddressRow* entryFromIndex(const QModelIndex &index) const;
void setCurrentSubaddressAccount(quint32 accountIndex);
int unusedLookahead() const;
public slots:
void startReset();
@ -50,9 +48,8 @@ public slots:
private:
Subaddress *m_subaddress;
QVariant parseSubaddressRow(const Monero::SubaddressRow &subaddress, const QModelIndex &index, int role) const;
QVariant parseSubaddressRow(const SubaddressRow &subaddress, const QModelIndex &index, int role) const;
bool m_showFullAddresses;
quint32 m_currentSubaddressAccount;
};

View file

@ -3,37 +3,47 @@
#include "SubaddressProxyModel.h"
SubaddressProxyModel::SubaddressProxyModel(QObject *parent, Subaddress *subaddress, bool showChange)
#include "utils/config.h"
SubaddressProxyModel::SubaddressProxyModel(QObject *parent, Subaddress *subaddress)
: QSortFilterProxyModel(parent)
, m_subaddress(subaddress)
, m_searchRegExp("")
, m_searchCaseSensitiveRegExp("")
, m_showChange(showChange)
{
m_searchRegExp.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
this->setSortRole(Qt::UserRole);
this->sort(0);
}
bool SubaddressProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{
QString address, label;
bool isUsed;
m_subaddress->getRow(sourceRow, [&isUsed, &address, &label](const Monero::SubaddressRow &subaddress){
isUsed = subaddress.isUsed();
address = QString::fromStdString(subaddress.getAddress());
label = QString::fromStdString(subaddress.getLabel());
});
bool showUsed = conf()->get(Config::showUsedAddresses).toBool();
bool showHidden = conf()->get(Config::showHiddenAddresses).toBool();
bool showChange = conf()->get(Config::showChangeAddresses).toBool();
// Hide primary/change addresses
if (!m_showChange && sourceRow == 0) {
SubaddressRow* subaddress = m_subaddress->row(sourceRow);
if (!subaddress) {
return false;
}
if (!m_showHidden && m_hiddenAddresses.contains(address)) {
// Pinned addresses are always shown
if (subaddress->isPinned()) {
return true;
}
// Hide primary/change addresses
if (!showChange && sourceRow == 0) {
return false;
}
if (!showHidden && subaddress->isHidden()) {
return false;
}
if (!m_searchRegExp.pattern().isEmpty()) {
return address.contains(m_searchCaseSensitiveRegExp) || label.contains(m_searchRegExp);
return subaddress->getAddress().contains(m_searchCaseSensitiveRegExp) || subaddress->getLabel().contains(m_searchRegExp);
}
return (m_showUsed || !isUsed);
return (showUsed || !subaddress->isUsed());
}

View file

@ -11,10 +11,10 @@
class SubaddressProxyModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit SubaddressProxyModel(QObject* parent, Subaddress *subaddress, bool hidePrimary = false);
bool filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const;
explicit SubaddressProxyModel(QObject* parent, Subaddress *subaddress);
[[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
public slots:
void setSearchFilter(const QString& searchString){
@ -23,35 +23,10 @@ public slots:
invalidateFilter();
}
void setShowUsed(const bool showUsed){
m_showUsed = showUsed;
invalidateFilter();
}
void setShowHidden(const bool showHidden){
m_showHidden = showHidden;
invalidateFilter();
}
void setHiddenAddresses(const QStringList& hiddenAddresses) {
m_hiddenAddresses = hiddenAddresses;
invalidateFilter();
}
void setShowChangeAddresses(const bool showChange) {
m_showChange = showChange;
invalidateFilter();
}
private:
Subaddress *m_subaddress;
QStringList m_hiddenAddresses;
QRegularExpression m_searchRegExp;
QRegularExpression m_searchCaseSensitiveRegExp;
bool m_showUsed = false;
bool m_showHidden = false;
bool m_showChange = false;
};
#endif //FEATHER_SUBADDRESSPROXYMODEL_H

View file

@ -48,6 +48,14 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::showTabCalc,{QS("showTabCalc"), true}},
{Config::showSearchbar,{QS("showSearchbar"), true}},
// Receive
{Config::showUsedAddresses,{QS("showUsedAddresses"), false}},
{Config::showHiddenAddresses,{QS("showHiddenAddresses"), false}},
{Config::showFullAddresses, {QS("showFullAddresses"), false}},
{Config::showChangeAddresses,{QS("showChangeAddresses"), false}},
{Config::showAddressIndex,{QS("showAddressIndex"), true}},
{Config::showAddressLabels,{QS("showAddressLabels"), true}},
// Mining
{Config::miningMode,{QS("miningMode"), Config::MiningMode::Pool}},
{Config::xmrigPath,{QS("xmrigPath"), ""}},

View file

@ -51,6 +51,14 @@ public:
showTabXMRig,
showSearchbar,
// Receive
showUsedAddresses,
showHiddenAddresses,
showFullAddresses,
showChangeAddresses,
showAddressIndex,
showAddressLabels,
// Mining
miningMode,
xmrigPath,