mirror of
https://github.com/feather-wallet/feather.git
synced 2024-11-16 17:27:38 +00:00
Receive: persistent settings, cleanup
This commit is contained in:
parent
0616384358
commit
a13ca973cc
17 changed files with 445 additions and 280 deletions
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -7,13 +7,34 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>878</width>
|
||||
<height>512</height>
|
||||
<height>403</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<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="QFrame" name="frame_search">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -39,6 +60,23 @@
|
|||
</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>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<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>
|
||||
|
|
|
@ -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
BIN
src/assets/images/pin.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
|
@ -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;
|
||||
};
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
40
src/libwalletqt/rows/SubaddressRow.cpp
Normal file
40
src/libwalletqt/rows/SubaddressRow.cpp
Normal 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;
|
||||
}
|
44
src/libwalletqt/rows/SubaddressRow.h
Normal file
44
src/libwalletqt/rows/SubaddressRow.h
Normal 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
|
|
@ -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());
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"), ""}},
|
||||
|
|
|
@ -51,6 +51,14 @@ public:
|
|||
showTabXMRig,
|
||||
showSearchbar,
|
||||
|
||||
// Receive
|
||||
showUsedAddresses,
|
||||
showHiddenAddresses,
|
||||
showFullAddresses,
|
||||
showChangeAddresses,
|
||||
showAddressIndex,
|
||||
showAddressLabels,
|
||||
|
||||
// Mining
|
||||
miningMode,
|
||||
xmrigPath,
|
||||
|
|
Loading…
Reference in a new issue