]> Nutra Git (v2) - gamesguru/feather.git/commitdiff
Receive: persistent settings, cleanup
authortobtoht <tob@featherwallet.org>
Thu, 16 Nov 2023 15:26:58 +0000 (16:26 +0100)
committertobtoht <tob@featherwallet.org>
Fri, 17 Nov 2023 13:02:49 +0000 (14:02 +0100)
17 files changed:
src/CMakeLists.txt
src/ReceiveWidget.cpp
src/ReceiveWidget.h
src/ReceiveWidget.ui
src/assets.qrc
src/assets/images/pin.png [new file with mode: 0644]
src/libwalletqt/Subaddress.cpp
src/libwalletqt/Subaddress.h
src/libwalletqt/Wallet.cpp
src/libwalletqt/rows/SubaddressRow.cpp [new file with mode: 0644]
src/libwalletqt/rows/SubaddressRow.h [new file with mode: 0644]
src/model/SubaddressModel.cpp
src/model/SubaddressModel.h
src/model/SubaddressProxyModel.cpp
src/model/SubaddressProxyModel.h
src/utils/config.cpp
src/utils/config.h

index 479d4ab96f67dd05ce55c7937dca6320e534b814..0803d61f04aa16f89a1e8d33937ccb293d039fd6 100644 (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
index 18fe62f2bb7304c46463272a927ea142726b3b14..c9079778eb1f37f626c515a464cb47fb100d0af4 100644 (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()));
index c48be31b43c408ed1e93963937eacef6cfde96a3..25e76042735813f9f95aa2f4b26a8029f46f469e 100644 (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
index b84ed585ef9c90c230a8901c5b042a35b2beda4f..ab25a11a145b2f43d9010290c1122aed5299c4f5 100644 (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">
     <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>
index a1656922fd18756761a77a398c2c1a967c178e77..c59556c0c785cc97ed378d9a1a2ecc973cdfd508 100644 (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>
diff --git a/src/assets/images/pin.png b/src/assets/images/pin.png
new file mode 100644 (file)
index 0000000..a1ea7cd
Binary files /dev/null and b/src/assets/images/pin.png differ
index d8e637811fcca2b5e5247db35798e1249cfcf61a..13374c90f151484095f66ec145a2fe4da0705a74 100644 (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
index 3f2cd1c7bd023e97b3e1eb25811f3a6ca5a52dc6..96c111ab61c9f269b6db7ca9ce1a873aef5de8a7 100644 (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
index 1f7b3d11182b2ff979f981010077bb95c37a8f61..9b6117d6edfb52974068a8edf353e77f544e81c2 100644 (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)
diff --git a/src/libwalletqt/rows/SubaddressRow.cpp b/src/libwalletqt/rows/SubaddressRow.cpp
new file mode 100644 (file)
index 0000000..8960087
--- /dev/null
@@ -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;
+}
\ No newline at end of file
diff --git a/src/libwalletqt/rows/SubaddressRow.h b/src/libwalletqt/rows/SubaddressRow.h
new file mode 100644 (file)
index 0000000..389b4a5
--- /dev/null
@@ -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
index 23a013bc31fee20f58dfd646f24c7674cb771378..dc29deb21549671a1e9dc19786e72040e21e10f5 100644 (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);
@@ -51,11 +52,20 @@ QVariant SubaddressModel::data(const QModelIndex &index, int role) const
         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:
@@ -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());
 }
\ No newline at end of file
index 4c8d0da4beadd6055b4769c5d4aee10702e0ce66..4f9a160fb84414a9d50d2f03d61edc4524ce7143 100644 (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;
 };
 
index 48b95e80b8079a658e0f38619feb2cca2e054807..12e06edc5587a12be37778ff3aedc7fdd015569f 100644 (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();
 
+    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());
 }
index e0a3b4b8a28d3c0e150e99b3cfc003869a8183e4..704037d5030ef99c8c4b74ef1820ef4520be12ad 100644 (file)
 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
index a89ec6886f4f49a3e9f185b68b53e7b289036bed..fcc92b72d4211e94878ef060707da775019ef579 100644 (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"), ""}},
index 9bb4790818b156f15883735a603f4c6a4d2a37c6..4ce33a5d8404b729196bc5c8347182a5d3249ab9 100644 (file)
@@ -50,6 +50,14 @@ public:
         showTabCalc,
         showTabXMRig,
         showSearchbar,
+        
+        // Receive
+        showUsedAddresses,
+        showHiddenAddresses,
+        showFullAddresses,
+        showChangeAddresses,
+        showAddressIndex,
+        showAddressLabels,
 
         // Mining
         miningMode,