From f59d0cd8aeadd833f988d28004eaf6519d1550a1 Mon Sep 17 00:00:00 2001 From: pablomartin4btc Date: Fri, 8 Sep 2023 19:33:09 -0300 Subject: [PATCH 1/5] gui: Refactor AddressType from ReceiveCoinsDialog Extract and refactor ui->addressType combo content filling into GUIUtil, having the translated descriptions in one place and allowing other Qt widget containers to easily create similar combo boxes with customizable tooltips per item and default selected item, both specified by the caller. --- src/qt/guiutil.cpp | 37 +++++++++++++++++++++++++++++++++++ src/qt/guiutil.h | 9 +++++++++ src/qt/receivecoinsdialog.cpp | 20 ++++++++----------- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 2369f6b6312..e9da0b3e5ef 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,14 @@ using namespace std::chrono_literals; namespace GUIUtil { +std::map outputTypeDescriptionsMap(){ + return { + {OutputType::LEGACY, {QObject::tr("Base58 (Legacy)"), false}}, + {OutputType::P2SH_SEGWIT, {QObject::tr("Base58 (P2SH-SegWit)"), false}}, + {OutputType::BECH32, {QObject::tr("Bech32 (SegWit)"), false}}, + {OutputType::BECH32M, {QObject::tr("Bech32m (Taproot)"), true}}}; +} + QString dateTimeStr(const QDateTime &date) { return QLocale::system().toString(date.date(), QLocale::ShortFormat) + QString(" ") + date.toString("hh:mm"); @@ -1017,4 +1026,32 @@ QString WalletDisplayName(const std::string& name) { return WalletDisplayName(QString::fromStdString(name)); } + +void AddItemsToAddressTypeCombo(QComboBox* addressType, bool taprootEnabled, const std::map& outputTypeTooltipsMap, std::optional defaultType) +{ + auto add_address_type = [&](OutputType type, QString description) { + const auto index{addressType->count()}; + // getting the tooltip (provided by the caller) that will be displayed on the item in the combo + QString tooltip{}; + auto it{outputTypeTooltipsMap.find(type)}; + // if can't find it no tooltip will be displayed + if (it != outputTypeTooltipsMap.end()) tooltip = it->second; + addressType->addItem(description, static_cast(type)); + addressType->setItemData(index, tooltip, Qt::ToolTipRole); + // setting default selected value for the combo if it has been passed + if (defaultType.has_value() && defaultType.value() == type) addressType->setCurrentIndex(index); + }; + + // Adding all output types defined in the map to the combo + for (const auto& pair : outputTypeDescriptionsMap()) { + OutputType type{pair.first}; + OutputTypeInfo typeInfo{pair.second}; + // if it requires taproot check it with arg taprootEnabled + // eg call from receivecoindialog.cpp + if ((!typeInfo.requiresTaprootEnabled) || (typeInfo.requiresTaprootEnabled == taprootEnabled)) { + add_address_type(type, typeInfo.description); + } + } +} + } // namespace GUIUtil diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 45251987943..198c03c0614 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,7 @@ QT_BEGIN_NAMESPACE class QAbstractButton; class QAbstractItemView; class QAction; +class QComboBox; class QDateTime; class QDialog; class QFont; @@ -439,6 +441,13 @@ namespace GUIUtil QString WalletDisplayName(const std::string& name); QString WalletDisplayName(const QString& name); + struct OutputTypeInfo { + const QString description; + const bool requiresTaprootEnabled; + }; + std::map outputTypeDescriptionsMap(); + + void AddItemsToAddressTypeCombo(QComboBox* addressType, bool taprootEnabled, const std::map& outputTypeTooltipsMap, std::optional defaultType = std::nullopt); } // namespace GUIUtil #endif // BITCOIN_QT_GUIUTIL_H diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index be7741e8a8c..75581eb697b 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -86,19 +86,15 @@ void ReceiveCoinsDialog::setModel(WalletModel *_model) &QItemSelectionModel::selectionChanged, this, &ReceiveCoinsDialog::recentRequestsView_selectionChanged); + /** Tooltip for each address type that will be displayed on the combo*/ + std::map addressTypeTooltipMap{ + {OutputType::LEGACY, QObject::tr("Not recommended due to higher fees and less protection against typos.")}, + {OutputType::P2SH_SEGWIT, QObject::tr("Generates an address compatible with older wallets.")}, + {OutputType::BECH32, QObject::tr("Generates a native segwit address (BIP-173). Some old wallets don't support it.")}, + {OutputType::BECH32M, QObject::tr("Bech32m (BIP-350) is an upgrade to Bech32, wallet support is still limited.")}}; + // Populate address type dropdown and select default - auto add_address_type = [&](OutputType type, const QString& text, const QString& tooltip) { - const auto index = ui->addressType->count(); - ui->addressType->addItem(text, (int) type); - ui->addressType->setItemData(index, tooltip, Qt::ToolTipRole); - if (model->wallet().getDefaultAddressType() == type) ui->addressType->setCurrentIndex(index); - }; - add_address_type(OutputType::LEGACY, tr("Base58 (Legacy)"), tr("Not recommended due to higher fees and less protection against typos.")); - add_address_type(OutputType::P2SH_SEGWIT, tr("Base58 (P2SH-SegWit)"), tr("Generates an address compatible with older wallets.")); - add_address_type(OutputType::BECH32, tr("Bech32 (SegWit)"), tr("Generates a native segwit address (BIP-173). Some old wallets don't support it.")); - if (model->wallet().taprootEnabled()) { - add_address_type(OutputType::BECH32M, tr("Bech32m (Taproot)"), tr("Bech32m (BIP-350) is an upgrade to Bech32, wallet support is still limited.")); - } + GUIUtil::AddItemsToAddressTypeCombo(ui->addressType, model->wallet().taprootEnabled(), addressTypeTooltipMap, model->wallet().getDefaultAddressType()); // Set the button to be enabled or disabled based on whether the wallet can give out new addresses. ui->receiveButton->setEnabled(model->wallet().canGetAddresses()); From c65eb2383eb716d679e8a8ddba088027b52e01c6 Mon Sep 17 00:00:00 2001 From: pablomartin4btc Date: Sat, 9 Sep 2023 15:15:40 -0300 Subject: [PATCH 2/5] wallet: Correct output type for Segwit address Current OutputTypeFromDestination at outputtype, returns OUTPUT_TYPE_STRING_LEGACY for Segwit addresses, as p2sh-segwit requires extra info from the wallet to figure that out, this change adds a correct way to obtain the desired result from the wallet interface. So far this will be needed from the UI to identify such type, as currently a user could select this output type to create an address (receivecoinsdialog) but no display of it exists. --- src/interfaces/wallet.h | 3 +++ src/wallet/interfaces.cpp | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h index df1ced48a71..19808147eeb 100644 --- a/src/interfaces/wallet.h +++ b/src/interfaces/wallet.h @@ -101,6 +101,9 @@ class Wallet //! Get public key. virtual bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) = 0; + //! Get Output type from a Destination. + virtual OutputType getOutputType(const CTxDestination& dest) = 0; + //! Sign message virtual SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) = 0; diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 21e8a0b3bd2..67479a37ed9 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -166,6 +166,13 @@ class WalletImpl : public Wallet } return false; } + OutputType getOutputType(const CTxDestination& dest) override + { + CScript script = GetScriptForDestination(dest); + std::unique_ptr provider = m_wallet->GetSolvingProvider(script); + std::unique_ptr desc = provider ? InferDescriptor(script, *provider) : InferDescriptor(script, FlatSigningProvider()); + return desc->GetOutputType().value_or(OutputType::UNKNOWN); + } SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) override { return m_wallet->SignMessage(message, pkhash, str_sig); From c72c4247b7c85f7913844fc38bf1121a0a4e0e4b Mon Sep 17 00:00:00 2001 From: pablomartin4btc Date: Sat, 9 Sep 2023 16:44:06 -0300 Subject: [PATCH 3/5] gui: Add Address Type Column to AddressBookPage Add new address type column to the addresstablemodel, use the getOutputType function from the wallet interface to display the correct address type in the new column content. Update the address book page to set the new column resize mode and to display the new column only when the receiving tab is enabled so it must be hidden on the sending tab. Update AddressBookFilterProxyModel::filterAcceptsRow so the filter works also on the new addressType column content. Also the searLineEdit greyed text reflects that the new field/ column addressType will be included in the search/ filter by but only when the receiving tab is enabled. Add the new column to the export feature. --- src/qt/addressbookpage.cpp | 19 ++++++++++++++++--- src/qt/addresstablemodel.cpp | 25 ++++++++++++++++--------- src/qt/addresstablemodel.h | 6 +++--- src/qt/walletmodel.cpp | 6 ++++-- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index f2f9371c650..698eeeac18b 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -46,14 +46,21 @@ class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel } auto address = model->index(row, AddressTableModel::Address, parent); + auto addressType = model->index(row, AddressTableModel::Type, parent); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) const auto pattern = filterRegularExpression(); #else const auto pattern = filterRegExp(); #endif - return (model->data(address).toString().contains(pattern) || - model->data(label).toString().contains(pattern)); + auto filterBy = model->data(address).toString().contains(pattern) || + model->data(label).toString().contains(pattern); + + if (m_type == AddressTableModel::Receive) { + filterBy = filterBy || model->data(addressType).toString().contains(pattern); + } + + return filterBy; } }; @@ -95,11 +102,13 @@ AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, ui->labelExplanation->setText(tr("These are your Bitcoin addresses for sending payments. Always check the amount and the receiving address before sending coins.")); ui->deleteAddress->setVisible(true); ui->newAddress->setVisible(true); + ui->searchLineEdit->setPlaceholderText("Enter address or label to search"); break; case ReceivingTab: ui->labelExplanation->setText(tr("These are your Bitcoin addresses for receiving payments. Use the 'Create new receiving address' button in the receive tab to create new addresses.\nSigning is only possible with addresses of the type 'legacy'.")); ui->deleteAddress->setVisible(false); ui->newAddress->setVisible(false); + ui->searchLineEdit->setPlaceholderText("Enter address, address type or label to search"); break; } @@ -140,8 +149,11 @@ void AddressBookPage::setModel(AddressTableModel *_model) ui->tableView->sortByColumn(0, Qt::AscendingOrder); // Set column widths - ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::Stretch); + ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Label, QHeaderView::ResizeToContents); ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Address, QHeaderView::ResizeToContents); + ui->tableView->horizontalHeader()->setSectionResizeMode(AddressTableModel::Type, QHeaderView::ResizeToContents); + // Show the "Address type" column only on the Receiving tab + ui->tableView->setColumnHidden(AddressTableModel::ColumnIndex::Type, (tab == SendingTab)); connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AddressBookPage::selectionChanged); @@ -285,6 +297,7 @@ void AddressBookPage::on_exportButton_clicked() // name, column, role writer.setModel(proxyModel); writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); + writer.addColumn("Address Type", AddressTableModel::Type, Qt::EditRole); writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole); if(!writer.write()) { diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index efdc3966d1d..7ed548a5ea4 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -30,10 +30,13 @@ struct AddressTableEntry Type type; QString label; QString address; + QString addressType; AddressTableEntry() = default; - AddressTableEntry(Type _type, const QString &_label, const QString &_address): - type(_type), label(_label), address(_address) {} + AddressTableEntry(Type _type, const QString &_label, const QString &_address, const OutputType &_addresstype): + type(_type), label(_label), address(_address) { + addressType = GUIUtil::outputTypeDescriptionsMap().at(_addresstype).description; + } }; struct AddressTableEntryLessThan @@ -87,7 +90,8 @@ class AddressTablePriv address.purpose, address.is_mine); cachedAddressTable.append(AddressTableEntry(addressType, QString::fromStdString(address.name), - QString::fromStdString(EncodeDestination(address.dest)))); + QString::fromStdString(EncodeDestination(address.dest)), + wallet.getOutputType(address.dest))); } } // std::lower_bound() and std::upper_bound() require our cachedAddressTable list to be sorted in asc order @@ -96,7 +100,7 @@ class AddressTablePriv std::sort(cachedAddressTable.begin(), cachedAddressTable.end(), AddressTableEntryLessThan()); } - void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status) + void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status, const OutputType &addresstype) { // Find address / label in model QList::iterator lower = std::lower_bound( @@ -117,7 +121,7 @@ class AddressTablePriv break; } parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); - cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address)); + cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address, addresstype)); parent->endInsertRows(); break; case CT_UPDATED: @@ -164,7 +168,7 @@ class AddressTablePriv AddressTableModel::AddressTableModel(WalletModel *parent, bool pk_hash_only) : QAbstractTableModel(parent), walletModel(parent) { - columns << tr("Label") << tr("Address"); + columns << tr("Label") << tr("Address Type") << tr("Address"); priv = new AddressTablePriv(this); priv->refreshAddressTable(parent->wallet(), pk_hash_only); } @@ -208,6 +212,8 @@ QVariant AddressTableModel::data(const QModelIndex &index, int role) const } case Address: return rec->address; + case Type: + return rec->addressType; } // no default case, so the compiler can warn about missing cases assert(false); } else if (role == Qt::FontRole) { @@ -216,6 +222,8 @@ QVariant AddressTableModel::data(const QModelIndex &index, int role) const return QFont(); case Address: return GUIUtil::fixedPitchFont(); + case Type: + return QFont(); } // no default case, so the compiler can warn about missing cases assert(false); } else if (role == TypeRole) { @@ -332,11 +340,10 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex &par } } -void AddressTableModel::updateEntry(const QString &address, - const QString &label, bool isMine, wallet::AddressPurpose purpose, int status) +void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status, const OutputType &addressType) { // Update address book model from Bitcoin core - priv->updateEntry(address, label, isMine, purpose, status); + priv->updateEntry(address, label, isMine, purpose, status, addressType); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address, const OutputType address_type) diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 44808364ec9..79e8416b92e 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -35,7 +35,8 @@ class AddressTableModel : public QAbstractTableModel enum ColumnIndex { Label = 0, /**< User specified label */ - Address = 1 /**< Bitcoin address */ + Type = 1, /**< Address type */ + Address = 2 /**< Bitcoin address */ }; enum RoleIndex { @@ -104,8 +105,7 @@ class AddressTableModel : public QAbstractTableModel public Q_SLOTS: /* Update address list from core. */ - void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status); - + void updateEntry(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status, const OutputType &address_type); friend class AddressTablePriv; }; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 0a01c0a45b1..d6641e074b5 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -137,8 +137,10 @@ void WalletModel::updateTransaction() void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, wallet::AddressPurpose purpose, int status) { - if(addressTableModel) - addressTableModel->updateEntry(address, label, isMine, purpose, status); + if (addressTableModel) { + CTxDestination destination = DecodeDestination(address.toStdString()); + addressTableModel->updateEntry(address, label, isMine, purpose, status, m_wallet->getOutputType(destination)); + } } void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly) From 3facd07eda16661411837d454a4ec63b3261ef83 Mon Sep 17 00:00:00 2001 From: pablomartin4btc Date: Sat, 9 Sep 2023 23:18:03 -0300 Subject: [PATCH 4/5] gui: Extend address book filter for nested filtering Extend AddressBookFilterProxyModel to allow using nested filters to be applied on top of it. If future needs arise for similar filters outside the address book page, the code could be easily refactored and moved in a new subclass of QSortFilterProxyModel, perhaps with limits on nested levels. For safety and performance reasons, the code of the filter proxy model class declaration (in addressbookpage.h) and its instance creation were updated by aligning it with TransactionFilterProxy in overviewpage.h as this addresses situations of unexpected crashes, such as segfault on closing the app, double free or corruption, or stack smashing detection. --- src/qt/addressbookpage.cpp | 33 ++++++++++++++++++++++++++++----- src/qt/addressbookpage.h | 2 +- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 698eeeac18b..a5b1dc61cd9 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -24,17 +24,37 @@ class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel { const QString m_type; + const bool m_nestedFilterEnabled; public: - AddressBookSortFilterProxyModel(const QString& type, QObject* parent) + AddressBookSortFilterProxyModel(const QString& type, QObject* parent, bool enableNestedFilter) : QSortFilterProxyModel(parent) , m_type(type) + , m_nestedFilterEnabled(enableNestedFilter) { setDynamicSortFilter(true); setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); } + AddressBookSortFilterProxyModel* nestedProxyModel() const noexcept { + if (!m_nestedFilterEnabled) return const_cast(this); + + // Lazily create the nested proxy model + if (!nextedFilterProxyModel) { + auto nextedFilterProxyModel = std::make_unique( + m_type, + const_cast(this), + false); + nextedFilterProxyModel->setSourceModel(const_cast(this)); + } + return nextedFilterProxyModel.get(); + } + + bool isNestedFilterEnabled() const { + return m_nestedFilterEnabled; + } + protected: bool filterAcceptsRow(int row, const QModelIndex& parent) const override { @@ -62,6 +82,9 @@ class AddressBookSortFilterProxyModel final : public QSortFilterProxyModel return filterBy; } + +private: + std::unique_ptr nextedFilterProxyModel{nullptr}; }; AddressBookPage::AddressBookPage(const PlatformStyle *platformStyle, Mode _mode, Tabs _tab, QWidget *parent) : @@ -140,12 +163,12 @@ void AddressBookPage::setModel(AddressTableModel *_model) return; auto type = tab == ReceivingTab ? AddressTableModel::Receive : AddressTableModel::Send; - proxyModel = new AddressBookSortFilterProxyModel(type, this); + proxyModel.reset(new AddressBookSortFilterProxyModel(type, this, false)); proxyModel->setSourceModel(_model); - connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel, &QSortFilterProxyModel::setFilterWildcard); + connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel.get(), &QSortFilterProxyModel::setFilterWildcard); - ui->tableView->setModel(proxyModel); + ui->tableView->setModel(proxyModel->nestedProxyModel()); ui->tableView->sortByColumn(0, Qt::AscendingOrder); // Set column widths @@ -295,7 +318,7 @@ void AddressBookPage::on_exportButton_clicked() CSVModelWriter writer(filename); // name, column, role - writer.setModel(proxyModel); + writer.setModel(proxyModel.get()); writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); writer.addColumn("Address Type", AddressTableModel::Type, Qt::EditRole); writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole); diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index b649da4ac8f..28d27b500a4 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -53,7 +53,7 @@ public Q_SLOTS: Mode mode; Tabs tab; QString returnValue; - AddressBookSortFilterProxyModel *proxyModel; + std::unique_ptr proxyModel{nullptr}; QMenu *contextMenu; QString newAddressToSelect; void updateWindowsTitleWithWalletName(); From cbcc62569ef4aa6d8c3da5516453fe02020f3347 Mon Sep 17 00:00:00 2001 From: pablomartin4btc Date: Sun, 10 Sep 2023 00:18:35 -0300 Subject: [PATCH 5/5] gui: Add a combo widget to filter by address type Introduce a label and a combobox UI widgets for address type filtering on the address book page. These 2 widgets are been displayed only on the Receiving tab. To ensure that the combo box selection updates the filter model correctly, an intermediary signal (addressTypeChanged) and a slot (handleAddressTypeChanged) were necessary due to the incompatibility between QComboBox::currentIndexChanged and QSortFilterProxyModel::setFilterFixedString. Update filter model to use nested filtering so when selected item changes on address type combo it will update the filter pattern on top of what the parent filter has already, which is lead by the searchLineEdit widget and all references of the current proxyModel (eg mapToSource, mapFromSource) to the nested and final filter model. Use GUIUtil::AddItemsToAddressTypeCombo to populate the combo with the default output types descriptions passing a defined local map for the output types tooltips that will be displayed for each item, similarly to the address types combobox in receivecoinsdialog. --- src/qt/addressbookpage.cpp | 57 ++++++++++++++++++++++++++++++--- src/qt/addressbookpage.h | 16 +++++++++ src/qt/forms/addressbookpage.ui | 10 ++++++ 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index a5b1dc61cd9..bba133f00e8 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -163,7 +163,7 @@ void AddressBookPage::setModel(AddressTableModel *_model) return; auto type = tab == ReceivingTab ? AddressTableModel::Receive : AddressTableModel::Send; - proxyModel.reset(new AddressBookSortFilterProxyModel(type, this, false)); + proxyModel.reset(new AddressBookSortFilterProxyModel(type, this, true)); proxyModel->setSourceModel(_model); connect(ui->searchLineEdit, &QLineEdit::textChanged, proxyModel.get(), &QSortFilterProxyModel::setFilterWildcard); @@ -186,6 +186,8 @@ void AddressBookPage::setModel(AddressTableModel *_model) selectionChanged(); this->updateWindowsTitleWithWalletName(); + + this->setupAddressTypeCombo(); } void AddressBookPage::on_copyAddress_clicked() @@ -214,7 +216,7 @@ void AddressBookPage::onEditAction() EditAddressDialog::EditSendingAddress : EditAddressDialog::EditReceivingAddress, this); dlg->setModel(model); - QModelIndex origIndex = proxyModel->mapToSource(indexes.at(0)); + QModelIndex origIndex = proxyModel->nestedProxyModel()->mapToSource(indexes.at(0)); dlg->loadRow(origIndex.row()); GUIUtil::ShowModalDialogAsynchronously(dlg); } @@ -318,7 +320,7 @@ void AddressBookPage::on_exportButton_clicked() CSVModelWriter writer(filename); // name, column, role - writer.setModel(proxyModel.get()); + writer.setModel(proxyModel->nestedProxyModel()); writer.addColumn("Label", AddressTableModel::Label, Qt::EditRole); writer.addColumn("Address Type", AddressTableModel::Type, Qt::EditRole); writer.addColumn("Address", AddressTableModel::Address, Qt::EditRole); @@ -342,7 +344,7 @@ void AddressBookPage::contextualMenu(const QPoint &point) void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int /*end*/) { - QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent)); + QModelIndex idx = proxyModel.get()->mapFromSource(model->index(begin, AddressTableModel::Address, parent)); if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) { // Select row of newly created address, once @@ -364,3 +366,50 @@ void AddressBookPage::updateWindowsTitleWithWalletName() } } } + +std::map AddressBookPage::addressTypeTooltipMap() { + return {{OutputType::LEGACY, QObject::tr("Not recommended due to higher fees and less protection against typos.")}, + {OutputType::P2SH_SEGWIT, QObject::tr("An address compatible with older wallets.")}, + {OutputType::BECH32, QObject::tr("Native segwit address (BIP-173). Some old wallets don't support it.")}, + {OutputType::BECH32M, QObject::tr("Bech32m (BIP-350) is an upgrade to Bech32, wallet support is still limited.")}}; +} + +QString AddressBookPage::showAllTypes() const{ + return QObject::tr("All"); +} + +QString AddressBookPage::showAllTypesToolTip() const{ + return QObject::tr("Select an address type to filter by."); +} + +void AddressBookPage::handleAddressTypeChanged(int index) +{ + QString selectedValue = ui->addressType->currentText(); + // If show all types is selected then clear the selected value + // that will be sent to the filter so it shows everything + if (selectedValue == showAllTypes()) selectedValue.clear(); + // Emit a signal with the selected value + Q_EMIT addressTypeChanged(selectedValue); + // Forcing the resize as if it was selected an item with + // shorter content and right after a longer one, the + // columns are not resizing properly, this fixes it + ui->tableView->resizeColumnsToContents(); +} + +void AddressBookPage::initializeAddressTypeCombo() +{ + const auto index = ui->addressType->count(); + ui->addressType->addItem(showAllTypes(), index); + ui->addressType->setItemData(index, showAllTypesToolTip(), Qt::ToolTipRole); + ui->addressType->setCurrentIndex(index); +} + +void AddressBookPage::setupAddressTypeCombo() +{ + this->initializeAddressTypeCombo(); + ui->labelAddressType->setVisible(tab == ReceivingTab); + ui->addressType->setVisible(tab == ReceivingTab); + GUIUtil::AddItemsToAddressTypeCombo(ui->addressType, true, this->addressTypeTooltipMap()); + connect(ui->addressType, qOverload(&QComboBox::currentIndexChanged), this, &AddressBookPage::handleAddressTypeChanged); + connect(this, &AddressBookPage::addressTypeChanged, proxyModel->nestedProxyModel(), &QSortFilterProxyModel::setFilterFixedString); +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index 28d27b500a4..5cef233f368 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_QT_ADDRESSBOOKPAGE_H #define BITCOIN_QT_ADDRESSBOOKPAGE_H +#include + #include class AddressBookSortFilterProxyModel; @@ -57,6 +59,13 @@ public Q_SLOTS: QMenu *contextMenu; QString newAddressToSelect; void updateWindowsTitleWithWalletName(); + void initializeAddressTypeCombo(); + /** Default selected item of the address type combo to display all address types */ + QString showAllTypes() const; + QString showAllTypesToolTip() const; + /** Tooltip for each address type that will be displayed on the combo*/ + std::map addressTypeTooltipMap(); + void setupAddressTypeCombo(); private Q_SLOTS: /** Delete currently selected address entry */ @@ -78,9 +87,16 @@ private Q_SLOTS: void contextualMenu(const QPoint &point); /** New entry/entries were added to address table */ void selectNewAddress(const QModelIndex &parent, int begin, int /*end*/); + /** Address type combo selection changed */ + void handleAddressTypeChanged(int index); Q_SIGNALS: void sendCoins(QString addr); + /** Emitted when the addressType combobox is changed (handled by handleAddressTypeChange). + * This signal is used as a workaround to connect the combobox and the proxy model filter, + * preventing the compiler error "Signal and slot arguments are not compatible."*/ + void addressTypeChanged(const QString &addressType); + }; #endif // BITCOIN_QT_ADDRESSBOOKPAGE_H diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index 7ac216286c8..c751d257aa0 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -109,6 +109,16 @@ + + + + Show address type: + + + + + +