"utils/os/*.cpp"
"libwalletqt/*.h"
"libwalletqt/*.cpp"
+ "libwalletqt/rows/*.h"
+ "libwalletqt/rows/*.cpp"
"daemon/*.h"
"daemon/*.cpp"
"model/*.h"
${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
#include "dialog/PaymentRequestDialog.h"
#include "dialog/QrCodeDialog.h"
+#include "utils/config.h"
#include "utils/Icons.h"
#include "utils/Utils.h"
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);
// 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);
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() {
}
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);
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());
}
}
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()));
#include "model/SubaddressProxyModel.h"
#include "model/SubaddressModel.h"
#include "qrcode/QrCode.h"
+#include "utils/config.h"
namespace Ui {
class ReceiveWidget;
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();
private slots:
void showHeaderMenu(const QPoint& position);
- void hideAddress();
- void showAddress();
void showOnDevice();
void generateSubaddress();
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
<x>0</x>
<y>0</y>
<width>878</width>
- <height>512</height>
+ <height>403</height>
</rect>
</property>
<property name="windowTitle">
<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>
</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">
<header>model/SubaddressView.h</header>
</customwidget>
</customwidgets>
- <resources/>
+ <resources>
+ <include location="assets.qrc"/>
+ </resources>
<connections/>
</ui>
<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>
#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 Subaddress::errorString() const
-{
- return QString::fromStdString(m_subaddressImpl->errorString());
+ QString hidden = m_wallet->getCacheAttribute("feather.hiddenaddresses");
+ m_hidden = hidden.split(",");
}
-void Subaddress::getAll() const
+bool Subaddress::getRow(int index, std::function<void (SubaddressRow &row)> callback) const
{
- emit refreshStarted();
-
+ if (index < 0 || index >= m_rows.size())
{
- QWriteLocker locker(&m_lock);
-
- m_unusedLookahead = 0;
+ return false;
+ }
- m_rows.clear();
- for (auto &row: m_subaddressImpl->getAll()) {
- m_rows.append(row);
+ callback(*m_rows.value(index));
+ return true;
+}
- if (row->isUsed())
- m_unusedLookahead = 0;
- else
- m_unusedLookahead += 1;
+bool Subaddress::addRow(quint32 accountIndex, const QString &label)
+{
+ // 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;
}
- emit refreshFinished();
+ return true;
}
-bool Subaddress::getRow(int index, std::function<void (Monero::SubaddressRow &row)> callback) const
+bool Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label)
{
- QReadLocker locker(&m_lock);
-
- if (index < 0 || index >= m_rows.size())
+ try {
+ m_wallet2->set_subaddress_label({accountIndex, addressIndex}, label.toStdString());
+ refresh(accountIndex);
+ }
+ catch (const std::exception& e)
{
return false;
}
- callback(*m_rows.value(index));
return true;
}
-bool Subaddress::addRow(quint32 accountIndex, const QString &label) const
+bool Subaddress::setHidden(const QString &address, bool hidden)
{
- bool r = m_subaddressImpl->addRow(accountIndex, label.toStdString());
-
- if (r)
- getAll();
-
+ 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::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const
+bool Subaddress::isHidden(const QString &address)
+{
+ return m_hidden.contains(address);
+};
+
+bool Subaddress::setPinned(const QString &address, bool pinned)
{
- bool r = m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString());
- if (r) {
- getAll();
- emit labelChanged();
+ 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;
}
-bool Subaddress::refresh(quint32 accountIndex) const
+bool Subaddress::isPinned(const QString &address)
{
- bool r = m_subaddressImpl->refresh(accountIndex);
- getAll();
- return r;
+ return m_pinned.contains(address);
}
-quint64 Subaddress::unusedLookahead() const
+bool Subaddress::refresh(quint32 accountIndex)
{
- QReadLocker locker(&m_lock);
+ emit refreshStarted();
+
+ 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.");
+ }
- return m_unusedLookahead;
+ emit refreshFinished();
+
+ return !keysCorrupt;
}
-quint64 Subaddress::count() const
+qsizetype Subaddress::count() const
{
- QReadLocker locker(&m_lock);
+ return m_rows.length();
+}
- return m_rows.size();
+void Subaddress::clearRows() {
+ qDeleteAll(m_rows);
+ m_rows.clear();
}
-Monero::SubaddressRow* Subaddress::row(int index) const
-{
+SubaddressRow* Subaddress::row(int index) const {
return m_rows.value(index);
-}
\ No newline at end of file
+};
+
+QString Subaddress::getError() const {
+ return m_errorString;
+};
\ No newline at end of file
#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
, 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)
--- /dev/null
+// 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;
+}
\ No newline at end of file
--- /dev/null
+// 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
#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);
return QVariant();
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:
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;
else if (index.row() == 0) {
return "Change";
}
- return QString::fromStdString(subaddress.getLabel());
+ return subaddress.getLabel();
}
case isUsed:
return subaddress.isUsed();
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())
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());
}
\ No newline at end of file
#include <QSortFilterProxyModel>
#include <QDebug>
+#include "rows/SubaddressRow.h"
+
class Subaddress;
class SubaddressModel : public QAbstractTableModel
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();
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;
};
#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();
+ SubaddressRow* subaddress = m_subaddress->row(sourceRow);
+ if (!subaddress) {
+ return false;
+ }
+
+ // Pinned addresses are always shown
+ if (subaddress->isPinned()) {
+ return true;
+ }
+
// Hide primary/change addresses
- if (!m_showChange && sourceRow == 0) {
+ if (!showChange && sourceRow == 0) {
return false;
}
- if (!m_showHidden && m_hiddenAddresses.contains(address)) {
+ 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());
}
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){
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
{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"), ""}},
showTabCalc,
showTabXMRig,
showSearchbar,
+
+ // Receive
+ showUsedAddresses,
+ showHiddenAddresses,
+ showFullAddresses,
+ showChangeAddresses,
+ showAddressIndex,
+ showAddressLabels,
// Mining
miningMode,