diff --git a/doc/build-freebsd.md b/doc/build-freebsd.md index 7525cca863320..d5a3004c8aa0b 100644 --- a/doc/build-freebsd.md +++ b/doc/build-freebsd.md @@ -131,6 +131,6 @@ This explicitly enables the GUI and disables legacy wallet support. If `qt5` is **Important**: Use `gmake` (the non-GNU `make` will exit with an error). ```bash -gmake # use -jX here for parallelism +gmake # use "-j N" for N parallel jobs gmake check # Run tests if Python 3 is available ``` diff --git a/doc/build-netbsd.md b/doc/build-netbsd.md index b455b7371e55a..c28cb4df5b4c9 100644 --- a/doc/build-netbsd.md +++ b/doc/build-netbsd.md @@ -79,6 +79,6 @@ Without wallet: Build and run the tests: ```bash -gmake # use -jX here for parallelism +gmake # use "-j N" here for N parallel jobs gmake check ``` diff --git a/doc/build-openbsd.md b/doc/build-openbsd.md index bbda6de352ca0..7b4efce1fd459 100644 --- a/doc/build-openbsd.md +++ b/doc/build-openbsd.md @@ -85,7 +85,7 @@ To configure with GUI: Build and run the tests: ```bash -gmake # use -jX here for parallelism +gmake # use "-j N" here for N parallel jobs gmake check ``` diff --git a/doc/build-osx.md b/doc/build-osx.md index eaae4584751ec..4bb04d0f1760e 100644 --- a/doc/build-osx.md +++ b/doc/build-osx.md @@ -138,6 +138,14 @@ Skip if you don't intend to use the GUI. brew install qt@5 ``` +Ensure that the `qt@5` package is installed, not the `qt` package. +If 'qt' is installed, the build process will fail. +if installed, remove the `qt` package with the following command: + +``` bash +brew uninstall qt +``` + Note: Building with Qt binaries downloaded from the Qt website is not officially supported. See the notes in [#7714](https://github.com/dashpay/dash/issues/7714). @@ -276,7 +284,7 @@ After configuration, you are ready to compile. Run the following in your terminal to compile Dash Core: ``` bash -make -jx # use -jX here for parallelism +make # use "-j N" here for N parallel jobs make check # Run tests if Python 3 is available ``` diff --git a/doc/build-unix.md b/doc/build-unix.md index 27f00b5b1e845..07dcd80676cfc 100644 --- a/doc/build-unix.md +++ b/doc/build-unix.md @@ -22,7 +22,7 @@ To Build ```sh ./autogen.sh ./configure -make +make # use "-j N" for N parallel jobs make install # optional ``` diff --git a/doc/files.md b/doc/files.md index 97c68727cb327..98a358a9394e8 100644 --- a/doc/files.md +++ b/doc/files.md @@ -65,7 +65,7 @@ Subdirectory | File(s) | Description `./` | `governance.dat` | stores data for governance objects `./` | `mncache.dat` | stores data for masternode list `./` | `netfulfilled.dat` | stores data about recently made network requests -`./` | `fee_estimates.dat` | Stores statistics used to estimate minimum transaction fees and priorities required for confirmation +`./` | `fee_estimates.dat` | Stores statistics used to estimate minimum transaction fees required for confirmation `./` | `guisettings.ini.bak` | Backup of former [GUI settings](#gui-settings) after `-resetguisettings` option is used `./` | `ip_asn.map` | IP addresses to Autonomous System Numbers (ASNs) mapping used for bucketing of the peers; path can be specified with the `-asmap` option `./` | `mempool.dat` | Dump of the mempool's transactions diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 885a0e5334006..de588ef74ba37 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -69,6 +69,7 @@ QT_MOC_CPP = \ qt/moc_optionsmodel.cpp \ qt/moc_overviewpage.cpp \ qt/moc_peertablemodel.cpp \ + qt/moc_peertablesortproxy.cpp \ qt/moc_paymentserver.cpp \ qt/moc_psbtoperationsdialog.cpp \ qt/moc_qrdialog.cpp \ @@ -146,6 +147,7 @@ BITCOIN_QT_H = \ qt/overviewpage.h \ qt/paymentserver.h \ qt/peertablemodel.h \ + qt/peertablesortproxy.h \ qt/psbtoperationsdialog.h \ qt/qrdialog.h \ qt/qrimagewidget.h \ @@ -227,6 +229,7 @@ BITCOIN_QT_BASE_CPP = \ qt/optionsdialog.cpp \ qt/optionsmodel.cpp \ qt/peertablemodel.cpp \ + qt/peertablesortproxy.cpp \ qt/qvalidatedlineedit.cpp \ qt/qvaluecombobox.cpp \ qt/rpcconsole.cpp \ diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index 6df4f7ebbf9fc..b9db3fa5eb76e 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -107,32 +107,20 @@ AddressBookPage::AddressBookPage(Mode _mode, Tabs _tab, QWidget* parent) : break; } - // Context menu actions - QAction *copyAddressAction = new QAction(tr("&Copy Address"), this); - QAction *copyLabelAction = new QAction(tr("Copy &Label"), this); - QAction *editAction = new QAction(tr("&Edit"), this); - QAction *showAddressQRCodeAction = new QAction(tr("&Show address QR code"), this); - deleteAction = new QAction(ui->deleteAddress->text(), this); + // Build context menu + contextMenu = new QMenu(this); + contextMenu->addAction(tr("&Copy Address"), this, &AddressBookPage::on_copyAddress_clicked); + contextMenu->addAction(tr("Copy &Label"), this, &AddressBookPage::onCopyLabelAction); + contextMenu->addAction(tr("&Edit"), this, &AddressBookPage::onEditAction); + [[maybe_unused]] QAction* qrAction = contextMenu->addAction(tr("Show address &QR code"), this, &AddressBookPage::on_showAddressQRCode_clicked); #ifndef USE_QRCODE - showAddressQRCodeAction->setEnabled(false); + qrAction->setEnabled(false); #endif - // Build context menu - contextMenu = new QMenu(this); - contextMenu->addAction(copyAddressAction); - contextMenu->addAction(copyLabelAction); - contextMenu->addAction(editAction); - if(tab == SendingTab) - contextMenu->addAction(deleteAction); - contextMenu->addSeparator(); - contextMenu->addAction(showAddressQRCodeAction); - - // Connect signals for context menu actions - connect(copyAddressAction, &QAction::triggered, this, &AddressBookPage::on_copyAddress_clicked); - connect(copyLabelAction, &QAction::triggered, this, &AddressBookPage::onCopyLabelAction); - connect(editAction, &QAction::triggered, this, &AddressBookPage::onEditAction); - connect(deleteAction, &QAction::triggered, this, &AddressBookPage::on_deleteAddress_clicked); - connect(showAddressQRCodeAction, &QAction::triggered, this, &AddressBookPage::on_showAddressQRCode_clicked); + if (tab == SendingTab) { + contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked); + } + connect(ui->tableView, &QWidget::customContextMenuRequested, this, &AddressBookPage::contextualMenu); connect(ui->closeButton, &QPushButton::clicked, this, &QDialog::accept); @@ -267,13 +255,11 @@ void AddressBookPage::selectionChanged() // In sending tab, allow deletion of selection ui->deleteAddress->setEnabled(true); ui->deleteAddress->setVisible(true); - deleteAction->setEnabled(true); break; case ReceivingTab: // Deleting receiving addresses, however, is not allowed ui->deleteAddress->setEnabled(false); ui->deleteAddress->setVisible(false); - deleteAction->setEnabled(false); break; } ui->copyAddress->setEnabled(true); diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index b1d1a495f64ab..a354a8b08210f 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -54,7 +54,6 @@ public Q_SLOTS: QString returnValue; AddressBookSortFilterProxyModel *proxyModel; QMenu *contextMenu; - QAction *deleteAction; // to be able to explicitly disable it QString newAddressToSelect; private Q_SLOTS: diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index d0336e597c371..3abef864e3f27 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -520,10 +520,9 @@ void BitcoinGUI::createActions() for (const std::pair& i : m_wallet_controller->listWalletDir()) { const std::string& path = i.first; QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); - // Menu items remove single &. Single & are shown when && is in - // the string, but only the first occurrence. So replace only - // the first & with &&. - name.replace(name.indexOf(QChar('&')), 1, QString("&&")); + // An single ampersand in the menu item's text sets a shortcut for this item. + // Single & are shown when && is in the string. So replace & with &&. + name.replace(QChar('&'), QString("&&")); QAction* action = m_open_wallet_menu->addAction(name); if (i.second) { @@ -747,7 +746,7 @@ void BitcoinGUI::createToolBars() #ifdef ENABLE_WALLET m_wallet_selector = new QComboBox(this); m_wallet_selector->setSizeAdjustPolicy(QComboBox::AdjustToContents); - connect(m_wallet_selector, static_cast(&QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex); + connect(m_wallet_selector, qOverload(&QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex); QVBoxLayout* walletSelectorLayout = new QVBoxLayout(this); walletSelectorLayout->addWidget(m_wallet_selector); @@ -2055,11 +2054,8 @@ void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) void UnitDisplayStatusBarControl::createContextMenu() { menu = new QMenu(this); - for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) - { - QAction *menuAction = new QAction(QString(BitcoinUnits::name(u)), this); - menuAction->setData(QVariant(u)); - menu->addAction(menuAction); + for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { + menu->addAction(BitcoinUnits::name(u))->setData(QVariant(u)); } connect(menu, &QMenu::triggered, this, &UnitDisplayStatusBarControl::onMenuSelection); } diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 9ee3c43cf9462..a69599f28e59d 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include @@ -42,7 +43,11 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO { cachedBestHeaderHeight = -1; cachedBestHeaderTime = -1; + peerTableModel = new PeerTableModel(m_node, this); + m_peer_table_sort_proxy = new PeerTableSortProxy(this); + m_peer_table_sort_proxy->setSourceModel(peerTableModel); + banTableModel = new BanTableModel(m_node, this); mnListCached = std::make_shared(); @@ -219,6 +224,11 @@ PeerTableModel *ClientModel::getPeerTableModel() return peerTableModel; } +PeerTableSortProxy* ClientModel::peerTableSortProxy() +{ + return m_peer_table_sort_proxy; +} + BanTableModel *ClientModel::getBanTableModel() { return banTableModel; diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 46068ca264ef6..e5933ab9f84f8 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -20,6 +20,7 @@ class BanTableModel; class CBlockIndex; class OptionsModel; class PeerTableModel; +class PeerTableSortProxy; enum class SynchronizationState; QT_BEGIN_NAMESPACE @@ -58,6 +59,7 @@ class ClientModel : public QObject interfaces::CoinJoin::Options& coinJoinOptions() const { return m_node.coinJoinOptions(); } OptionsModel *getOptionsModel(); PeerTableModel *getPeerTableModel(); + PeerTableSortProxy* peerTableSortProxy(); BanTableModel *getBanTableModel(); //! Return number of connections, default is in- and outbound (total) @@ -109,6 +111,7 @@ class ClientModel : public QObject std::unique_ptr m_handler_notify_additional_data_sync_progess_changed; OptionsModel *optionsModel; PeerTableModel *peerTableModel; + PeerTableSortProxy* m_peer_table_sort_proxy{nullptr}; BanTableModel *banTableModel; //! A thread to interact with m_node asynchronously diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 73cdc667866ab..8a88c6161bce3 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -62,32 +62,16 @@ CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _m GUIUtil::disableMacFocusRect(this); - // context menu actions - QAction *copyAddressAction = new QAction(tr("Copy address"), this); - QAction *copyLabelAction = new QAction(tr("Copy label"), this); - QAction *copyAmountAction = new QAction(tr("Copy amount"), this); - copyTransactionHashAction = new QAction(tr("Copy transaction ID"), this); // we need to enable/disable this - lockAction = new QAction(tr("Lock unspent"), this); // we need to enable/disable this - unlockAction = new QAction(tr("Unlock unspent"), this); // we need to enable/disable this - // context menu contextMenu = new QMenu(this); - contextMenu->addAction(copyAddressAction); - contextMenu->addAction(copyLabelAction); - contextMenu->addAction(copyAmountAction); - contextMenu->addAction(copyTransactionHashAction); + contextMenu->addAction(tr("&Copy address"), this, &CoinControlDialog::copyAddress); + contextMenu->addAction(tr("Copy &label"), this, &CoinControlDialog::copyLabel); + contextMenu->addAction(tr("Copy &amount"), this, &CoinControlDialog::copyAmount); + copyTransactionHashAction = contextMenu->addAction(tr("Copy transaction &ID"), this, &CoinControlDialog::copyTransactionHash); contextMenu->addSeparator(); - contextMenu->addAction(lockAction); - contextMenu->addAction(unlockAction); - - // context menu signals + lockAction = contextMenu->addAction(tr("L&ock unspent"), this, &CoinControlDialog::lockCoin); + unlockAction = contextMenu->addAction(tr("&Unlock unspent"), this, &CoinControlDialog::unlockCoin); connect(ui->treeWidget, &QWidget::customContextMenuRequested, this, &CoinControlDialog::showMenu); - connect(copyAddressAction, &QAction::triggered, this, &CoinControlDialog::copyAddress); - connect(copyLabelAction, &QAction::triggered, this, &CoinControlDialog::copyLabel); - connect(copyAmountAction, &QAction::triggered, this, &CoinControlDialog::copyAmount); - connect(copyTransactionHashAction, &QAction::triggered, this, &CoinControlDialog::copyTransactionHash); - connect(lockAction, &QAction::triggered, this, &CoinControlDialog::lockCoin); - connect(unlockAction, &QAction::triggered, this, &CoinControlDialog::unlockCoin); // clipboard actions QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this); diff --git a/src/qt/governancelist.cpp b/src/qt/governancelist.cpp index c5e664edca49c..807157d8b6762 100644 --- a/src/qt/governancelist.cpp +++ b/src/qt/governancelist.cpp @@ -388,10 +388,11 @@ void GovernanceList::showProposalContextMenu(const QPoint& pos) } // right click menu with option to open proposal url - QAction* openProposalUrl = new QAction(proposal->url(), this); + QString proposal_url = proposal->url(); + proposal_url.replace(QChar('&'), QString("&&")); + proposalContextMenu->clear(); - proposalContextMenu->addAction(openProposalUrl); - connect(openProposalUrl, &QAction::triggered, proposal, &Proposal::openUrl); + proposalContextMenu->addAction(proposal_url, proposal, &Proposal::openUrl); proposalContextMenu->exec(QCursor::pos()); } diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 983ae83752c19..a5875d0d94ce6 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -340,6 +340,11 @@ void setupAppearance(QWidget* parent, OptionsModel* model) } } +void AddButtonShortcut(QAbstractButton* button, const QKeySequence& shortcut) +{ + QObject::connect(new QShortcut(shortcut, button), &QShortcut::activated, [button]() { button->animateClick(); }); +} + bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out) { // return if URI is not valid or is no dash: URI diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index 07606cea0dd18..8f3cb7df71956 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -44,6 +44,7 @@ class QAction; class QButtonGroup; class QDateTime; class QFont; +class QKeySequence; class QLineEdit; class QMenu; class QPoint; @@ -135,6 +136,14 @@ namespace GUIUtil // Setup appearance settings if not done yet void setupAppearance(QWidget* parent, OptionsModel* model); + /** + * Connects an additional shortcut to a QAbstractButton. Works around the + * one shortcut limitation of the button's shortcut property. + * @param[in] button QAbstractButton to assign shortcut to + * @param[in] shortcut QKeySequence to use as shortcut + */ + void AddButtonShortcut(QAbstractButton* button, const QKeySequence& shortcut); + // Parse "dash:" URI into recipient object, return true on successful parsing bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out); bool parseBitcoinURI(QString uri, SendCoinsRecipient *out); diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index ed522c61f4d1c..aeb5507b91bc0 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -156,7 +156,7 @@ Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_si UpdatePruneLabels(prune_checked); UpdateFreeSpaceLabel(); }); - connect(ui->pruneGB, QOverload::of(&QSpinBox::valueChanged), [this](int prune_GB) { + connect(ui->pruneGB, qOverload(&QSpinBox::valueChanged), [this](int prune_GB) { m_prune_target_gb = prune_GB; UpdatePruneLabels(ui->prune->isChecked()); UpdateFreeSpaceLabel(); diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index 43ba572455df0..7b8cdb7fe1adf 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -81,15 +81,12 @@ MasternodeList::MasternodeList(QWidget* parent) : ui->checkBoxMyMasternodesOnly->setEnabled(false); - QAction* copyProTxHashAction = new QAction(tr("Copy ProTx Hash"), this); - QAction* copyCollateralOutpointAction = new QAction(tr("Copy Collateral Outpoint"), this); contextMenuDIP3 = new QMenu(this); - contextMenuDIP3->addAction(copyProTxHashAction); - contextMenuDIP3->addAction(copyCollateralOutpointAction); + contextMenuDIP3->addAction(tr("Copy ProTx Hash"), this, &MasternodeList::copyProTxHash_clicked); + contextMenuDIP3->addAction(tr("Copy Collateral Outpoint"), this, &MasternodeList::copyCollateralOutpoint_clicked); + connect(ui->tableWidgetMasternodesDIP3, &QTableWidget::customContextMenuRequested, this, &MasternodeList::showContextMenuDIP3); connect(ui->tableWidgetMasternodesDIP3, &QTableWidget::doubleClicked, this, &MasternodeList::extraInfoDIP3_clicked); - connect(copyProTxHashAction, &QAction::triggered, this, &MasternodeList::copyProTxHash_clicked); - connect(copyCollateralOutpointAction, &QAction::triggered, this, &MasternodeList::copyCollateralOutpoint_clicked); timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &MasternodeList::updateDIP3ListScheduled); diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 1431342697baf..825e2cbc7ec67 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -258,9 +258,9 @@ void OptionsDialog::setModel(OptionsModel *_model) /* Main */ connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning); - connect(ui->pruneSize, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); - connect(ui->databaseCache, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); - connect(ui->threadsScriptVerif, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); + connect(ui->pruneSize, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); + connect(ui->databaseCache, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); + connect(ui->threadsScriptVerif, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); /* Wallet */ connect(ui->showMasternodesTab, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->showGovernanceTab, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); @@ -270,8 +270,8 @@ void OptionsDialog::setModel(OptionsModel *_model) connect(ui->connectSocks, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->connectSocksTor, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); /* Display */ - connect(ui->digits, static_cast(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); }); - connect(ui->lang, static_cast(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); }); + connect(ui->digits, qOverload<>(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); }); + connect(ui->lang, qOverload<>(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); }); connect(ui->thirdPartyTxUrls, &QLineEdit::textChanged, [this]{ showRestartWarning(); }); connect(ui->coinJoinEnabled, &QCheckBox::clicked, [=](bool fChecked) { @@ -296,8 +296,8 @@ void OptionsDialog::setModel(OptionsModel *_model) } }); - connect(ui->coinJoinDenomsGoal, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomGoal); - connect(ui->coinJoinDenomsHardCap, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomHardCap); + connect(ui->coinJoinDenomsGoal, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomGoal); + connect(ui->coinJoinDenomsHardCap, qOverload(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomHardCap); #endif } diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 5c60e25f8f9f9..1dfc1953908e1 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -11,56 +11,19 @@ #include -#include #include #include -bool NodeLessThan::operator()(const CNodeCombinedStats &left, const CNodeCombinedStats &right) const -{ - const CNodeStats *pLeft = &(left.nodeStats); - const CNodeStats *pRight = &(right.nodeStats); - - if (order == Qt::DescendingOrder) - std::swap(pLeft, pRight); - - switch (static_cast(column)) { - case PeerTableModel::NetNodeId: - return pLeft->nodeid < pRight->nodeid; - case PeerTableModel::Address: - return pLeft->m_addr_name.compare(pRight->m_addr_name) < 0; - case PeerTableModel::ConnectionType: - return pLeft->m_conn_type < pRight->m_conn_type; - case PeerTableModel::Network: - return pLeft->m_network < pRight->m_network; - case PeerTableModel::Ping: - return pLeft->m_min_ping_time < pRight->m_min_ping_time; - case PeerTableModel::Sent: - return pLeft->nSendBytes < pRight->nSendBytes; - case PeerTableModel::Received: - return pLeft->nRecvBytes < pRight->nRecvBytes; - case PeerTableModel::Subversion: - return pLeft->cleanSubVer.compare(pRight->cleanSubVer) < 0; - } // no default case, so the compiler can warn about missing cases - assert(false); -} - // private implementation class PeerTablePriv { public: /** Local cache of peer information */ QList cachedNodeStats; - /** Column to sort nodes by (default to unsorted) */ - int sortColumn{-1}; - /** Order (ascending or descending) to sort nodes by */ - Qt::SortOrder sortOrder; - /** Index of rows by node ID */ - std::map mapNodeRows; /** Pull a full list of peers from vNodes into our cache */ void refreshPeers(interfaces::Node& node) { - { cachedNodeStats.clear(); interfaces::Node::NodesStats nodes_stats; @@ -75,17 +38,6 @@ class PeerTablePriv stats.nodeStateStats = std::get<2>(node_stats); cachedNodeStats.append(stats); } - } - - if (sortColumn >= 0) - // sort cacheNodeStats (use stable sort to prevent rows jumping around unnecessarily) - std::stable_sort(cachedNodeStats.begin(), cachedNodeStats.end(), NodeLessThan(sortColumn, sortOrder)); - - // build index map - mapNodeRows.clear(); - int row = 0; - for (const CNodeCombinedStats& stats : cachedNodeStats) - mapNodeRows.insert(std::pair(stats.nodeStats.nodeid, row++)); } int size() const @@ -196,10 +148,7 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const } // no default case, so the compiler can warn about missing cases assert(false); } else if (role == StatsRole) { - switch (index.column()) { - case NetNodeId: return QVariant::fromValue(rec); - default: return QVariant(); - } + return QVariant::fromValue(rec); } return QVariant(); @@ -241,19 +190,3 @@ void PeerTableModel::refresh() priv->refreshPeers(m_node); Q_EMIT layoutChanged(); } - -int PeerTableModel::getRowByNodeId(NodeId nodeid) -{ - std::map::iterator it = priv->mapNodeRows.find(nodeid); - if (it == priv->mapNodeRows.end()) - return -1; - - return it->second; -} - -void PeerTableModel::sort(int column, Qt::SortOrder order) -{ - priv->sortColumn = column; - priv->sortOrder = order; - refresh(); -} diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index 66228d609953e..3d195342f1966 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -30,18 +30,6 @@ struct CNodeCombinedStats { }; Q_DECLARE_METATYPE(CNodeCombinedStats*) -class NodeLessThan -{ -public: - NodeLessThan(int nColumn, Qt::SortOrder fOrder) : - column(nColumn), order(fOrder) {} - bool operator()(const CNodeCombinedStats &left, const CNodeCombinedStats &right) const; - -private: - int column; - Qt::SortOrder order; -}; - /** Qt model providing information about connected peers, similar to the "getpeerinfo" RPC call. Used by the rpc console UI. @@ -53,7 +41,6 @@ class PeerTableModel : public QAbstractTableModel public: explicit PeerTableModel(interfaces::Node& node, QObject* parent); ~PeerTableModel(); - int getRowByNodeId(NodeId nodeid); void startAutoRefresh(); void stopAutoRefresh(); @@ -80,7 +67,6 @@ class PeerTableModel : public QAbstractTableModel QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QModelIndex index(int row, int column, const QModelIndex &parent) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; - void sort(int column, Qt::SortOrder order) override; /*@}*/ public Q_SLOTS: diff --git a/src/qt/peertablesortproxy.cpp b/src/qt/peertablesortproxy.cpp new file mode 100644 index 0000000000000..ad966fbcabb40 --- /dev/null +++ b/src/qt/peertablesortproxy.cpp @@ -0,0 +1,43 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +#include +#include +#include + +PeerTableSortProxy::PeerTableSortProxy(QObject* parent) + : QSortFilterProxyModel(parent) +{ +} + +bool PeerTableSortProxy::lessThan(const QModelIndex& left_index, const QModelIndex& right_index) const +{ + const CNodeStats left_stats = Assert(sourceModel()->data(left_index, PeerTableModel::StatsRole).value())->nodeStats; + const CNodeStats right_stats = Assert(sourceModel()->data(right_index, PeerTableModel::StatsRole).value())->nodeStats; + + switch (static_cast(left_index.column())) { + case PeerTableModel::NetNodeId: + return left_stats.nodeid < right_stats.nodeid; + case PeerTableModel::Address: + return left_stats.m_addr_name.compare(right_stats.m_addr_name) < 0; + case PeerTableModel::ConnectionType: + return left_stats.m_conn_type < right_stats.m_conn_type; + case PeerTableModel::Network: + return left_stats.m_network < right_stats.m_network; + case PeerTableModel::Ping: + return left_stats.m_min_ping_time < right_stats.m_min_ping_time; + case PeerTableModel::Sent: + return left_stats.nSendBytes < right_stats.nSendBytes; + case PeerTableModel::Received: + return left_stats.nRecvBytes < right_stats.nRecvBytes; + case PeerTableModel::Subversion: + return left_stats.cleanSubVer.compare(right_stats.cleanSubVer) < 0; + } // no default case, so the compiler can warn about missing cases + assert(false); +} diff --git a/src/qt/peertablesortproxy.h b/src/qt/peertablesortproxy.h new file mode 100644 index 0000000000000..1879f6b400bc1 --- /dev/null +++ b/src/qt/peertablesortproxy.h @@ -0,0 +1,25 @@ +// Copyright (c) 2020 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_PEERTABLESORTPROXY_H +#define BITCOIN_QT_PEERTABLESORTPROXY_H + +#include + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +class PeerTableSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit PeerTableSortProxy(QObject* parent = nullptr); + +protected: + bool lessThan(const QModelIndex& left_index, const QModelIndex& right_index) const override; +}; + +#endif // BITCOIN_QT_PEERTABLESORTPROXY_H diff --git a/src/qt/qrimagewidget.cpp b/src/qt/qrimagewidget.cpp index dbb4d8dc805a0..b14416f60c383 100644 --- a/src/qt/qrimagewidget.cpp +++ b/src/qt/qrimagewidget.cpp @@ -27,12 +27,8 @@ QRImageWidget::QRImageWidget(QWidget *parent): QLabel(parent), contextMenu(nullptr) { contextMenu = new QMenu(this); - QAction *saveImageAction = new QAction(tr("&Save Image…"), this); - connect(saveImageAction, &QAction::triggered, this, &QRImageWidget::saveImage); - contextMenu->addAction(saveImageAction); - QAction *copyImageAction = new QAction(tr("&Copy Image"), this); - connect(copyImageAction, &QAction::triggered, this, &QRImageWidget::copyImage); - contextMenu->addAction(copyImageAction); + contextMenu->addAction(tr("&Save Image…"), this, &QRImageWidget::saveImage); + contextMenu->addAction(tr("&Copy Image"), this, &QRImageWidget::copyImage); } bool QRImageWidget::setQR(const QString& data, const QString& text) diff --git a/src/qt/qvaluecombobox.cpp b/src/qt/qvaluecombobox.cpp index 3b2d44c00d193..fa9f50f4d5086 100644 --- a/src/qt/qvaluecombobox.cpp +++ b/src/qt/qvaluecombobox.cpp @@ -7,7 +7,7 @@ QValueComboBox::QValueComboBox(QWidget *parent) : QComboBox(parent), role(Qt::UserRole) { - connect(this, static_cast(&QComboBox::currentIndexChanged), this, &QValueComboBox::handleSelectionChanged); + connect(this, qOverload(&QComboBox::currentIndexChanged), this, &QValueComboBox::handleSelectionChanged); } QVariant QValueComboBox::value() const diff --git a/src/qt/receivecoinsdialog.cpp b/src/qt/receivecoinsdialog.cpp index 5dcb2b66031f2..3fe733005d011 100644 --- a/src/qt/receivecoinsdialog.cpp +++ b/src/qt/receivecoinsdialog.cpp @@ -31,28 +31,14 @@ ReceiveCoinsDialog::ReceiveCoinsDialog(QWidget* parent) : ui->label_3}, GUIUtil::FontWeight::Normal, 15); GUIUtil::updateFonts(); - // context menu actions - QAction *copyURIAction = new QAction(tr("Copy URI"), this); - QAction* copyAddressAction = new QAction(tr("Copy address"), this); - copyLabelAction = new QAction(tr("Copy label"), this); - copyMessageAction = new QAction(tr("Copy message"), this); - copyAmountAction = new QAction(tr("Copy amount"), this); - // context menu contextMenu = new QMenu(this); - contextMenu->addAction(copyURIAction); - contextMenu->addAction(copyAddressAction); - contextMenu->addAction(copyLabelAction); - contextMenu->addAction(copyMessageAction); - contextMenu->addAction(copyAmountAction); - - // context menu signals + contextMenu->addAction(tr("Copy &URI"), this, &ReceiveCoinsDialog::copyURI); + contextMenu->addAction(tr("&Copy address"), this, &ReceiveCoinsDialog::copyAddress); + copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &ReceiveCoinsDialog::copyLabel); + copyMessageAction = contextMenu->addAction(tr("Copy &message"), this, &ReceiveCoinsDialog::copyMessage); + copyAmountAction = contextMenu->addAction(tr("Copy &amount"), this, &ReceiveCoinsDialog::copyAmount); connect(ui->recentRequestsView, &QWidget::customContextMenuRequested, this, &ReceiveCoinsDialog::showMenu); - connect(copyURIAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyURI); - connect(copyAddressAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyAddress); - connect(copyLabelAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyLabel); - connect(copyMessageAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyMessage); - connect(copyAmountAction, &QAction::triggered, this, &ReceiveCoinsDialog::copyAmount); connect(ui->clearButton, &QPushButton::clicked, this, &ReceiveCoinsDialog::clear); } diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index ecd53638ed6b4..9abc5c1799274 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -12,12 +12,13 @@ #include -#include -#include -#include #include #include #include +#include +#include +#include +#include #include #include #include @@ -694,7 +695,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // set up peer table - ui->peerWidget->setModel(model->getPeerTableModel()); + ui->peerWidget->setModel(model->peerTableSortProxy()); ui->peerWidget->verticalHeader()->hide(); ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); @@ -705,36 +706,18 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ ui->peerWidget->horizontalHeader()->setStretchLastSection(true); ui->peerWidget->setItemDelegateForColumn(PeerTableModel::NetNodeId, new PeerIdViewDelegate(this)); - // create peer table context menu actions - QAction* disconnectAction = new QAction(tr("&Disconnect"), this); - QAction* banAction1h = new QAction(ts.ban_for + " " + tr("1 &hour"), this); - QAction* banAction24h = new QAction(ts.ban_for + " " + tr("1 &day"), this); - QAction* banAction7d = new QAction(ts.ban_for + " " + tr("1 &week"), this); - QAction* banAction365d = new QAction(ts.ban_for + " " + tr("1 &year"), this); - // create peer table context menu peersTableContextMenu = new QMenu(this); - peersTableContextMenu->addAction(disconnectAction); - peersTableContextMenu->addAction(banAction1h); - peersTableContextMenu->addAction(banAction24h); - peersTableContextMenu->addAction(banAction7d); - peersTableContextMenu->addAction(banAction365d); - - connect(banAction1h, &QAction::triggered, [this] { banSelectedNode(60 * 60); }); - connect(banAction24h, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24); }); - connect(banAction7d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 7); }); - connect(banAction365d, &QAction::triggered, [this] { banSelectedNode(60 * 60 * 24 * 365); }); - - // peer table context menu signals + peersTableContextMenu->addAction(tr("&Disconnect"), this, &RPCConsole::disconnectSelectedNode); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &hour"), [this] { banSelectedNode(60 * 60); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 d&ay"), [this] { banSelectedNode(60 * 60 * 24); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &week"), [this] { banSelectedNode(60 * 60 * 24 * 7); }); + peersTableContextMenu->addAction(ts.ban_for + " " + tr("1 &year"), [this] { banSelectedNode(60 * 60 * 24 * 365); }); connect(ui->peerWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showPeersTableContextMenu); - connect(disconnectAction, &QAction::triggered, this, &RPCConsole::disconnectSelectedNode); // peer table signal handling - update peer details when selecting new node connect(ui->peerWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RPCConsole::updateDetailWidget); - // peer table signal handling - update peer details when new nodes are added to the model - connect(model->getPeerTableModel(), &PeerTableModel::layoutChanged, this, &RPCConsole::peerLayoutChanged); - // peer table signal handling - cache selected node ids - connect(model->getPeerTableModel(), &PeerTableModel::layoutAboutToBeChanged, this, &RPCConsole::peerLayoutAboutToChange); + connect(model->getPeerTableModel(), &PeerTableModel::layoutChanged, this, &RPCConsole::updateDetailWidget); // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); @@ -746,16 +729,10 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH); ui->banlistWidget->horizontalHeader()->setStretchLastSection(true); - // create ban table context menu action - QAction* unbanAction = new QAction(tr("&Unban"), this); - // create ban table context menu banTableContextMenu = new QMenu(this); - banTableContextMenu->addAction(unbanAction); - - // ban table context menu signals + banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode); connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu); - connect(unbanAction, &QAction::triggered, this, &RPCConsole::unbanSelectedNode); // ban table signal handling - clear peer details when clicking a peer in the ban table connect(ui->banlistWidget, &QTableView::clicked, this, &RPCConsole::clearSelectedNode); @@ -959,20 +936,23 @@ void RPCConsole::clear(bool keep_prompt) ).arg(consoleFontSize) ); -#ifdef Q_OS_MAC - QString clsKey = "(⌘)-L"; -#else - QString clsKey = "Ctrl-L"; -#endif - - message(CMD_REPLY, (tr("Welcome to the %1 RPC console.").arg(PACKAGE_NAME) + "
" + - tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg(""+clsKey+"") + "
" + - tr("Type %1 for an overview of available commands.").arg("help") + "
" + - tr("For more information on using this console type %1.").arg("help-console") + - "

" + - tr("WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.") + - "
"), - true); + message(CMD_REPLY, + tr("Welcome to the %1 RPC console.").arg(PACKAGE_NAME) + + "
" + + tr("Use up and down arrows to navigate history, and %1 to clear screen.") + .arg("" + ui->clearButton->shortcut().toString(QKeySequence::NativeText) + "") + + "
" + + tr("Use %1 and %2 to increase or decrease the font size.") + .arg("" + ui->fontBiggerButton->shortcut().toString(QKeySequence::NativeText) + "") + .arg("" + ui->fontSmallerButton->shortcut().toString(QKeySequence::NativeText) + "") + + "
" + + tr("Type %1 for an overview of available commands.").arg("help") + + "
" + + tr("For more information on using this console type %1.").arg("help-console") + + "

" + + tr("WARNING: Scammers have been active, telling users to type commands here, stealing their wallet contents. Do not use this console without fully understanding the ramifications of a command.") + + "
", + true); } void RPCConsole::keyPressEvent(QKeyEvent *event) @@ -1174,7 +1154,7 @@ void RPCConsole::startExecutor() executor->moveToThread(&thread); // Replies from executor object must go to this object - connect(executor, &RPCExecutor::reply, this, static_cast(&RPCConsole::message)); + connect(executor, &RPCExecutor::reply, this, qOverload(&RPCConsole::message)); // Requests from this object must go to executor connect(this, &RPCConsole::cmdRequest, executor, &RPCExecutor::request); @@ -1219,67 +1199,6 @@ void RPCConsole::setTrafficGraphRange(TrafficGraphData::GraphRange range) ui->lblGraphRange->setText(GUIUtil::formatDurationStr(std::chrono::minutes{TrafficGraphData::RangeMinutes[range]})); } -void RPCConsole::peerLayoutAboutToChange() -{ - cachedNodeids.clear(); - for (const QModelIndex& peer : GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId)) { - const auto stats = peer.data(PeerTableModel::StatsRole).value(); - cachedNodeids.append(stats->nodeStats.nodeid); - } -} - -void RPCConsole::peerLayoutChanged() -{ - if (!clientModel || !clientModel->getPeerTableModel()) - return; - - bool fUnselect = false; - bool fReselect = false; - - if (cachedNodeids.empty()) // no node selected yet - return; - - // find the currently selected row - int selectedRow = -1; - QModelIndexList selectedModelIndex = ui->peerWidget->selectionModel()->selectedIndexes(); - if (!selectedModelIndex.isEmpty()) { - selectedRow = selectedModelIndex.first().row(); - } - - // check if our detail node has a row in the table (it may not necessarily - // be at selectedRow since its position can change after a layout change) - int detailNodeRow = clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.first()); - - if (detailNodeRow < 0) - { - // detail node disappeared from table (node disconnected) - fUnselect = true; - } - else - { - if (detailNodeRow != selectedRow) - { - // detail node moved position - fUnselect = true; - fReselect = true; - } - } - - if (fUnselect && selectedRow >= 0) { - clearSelectedNode(); - } - - if (fReselect) - { - for(int i = 0; i < cachedNodeids.size(); i++) - { - ui->peerWidget->selectRow(clientModel->getPeerTableModel()->getRowByNodeId(cachedNodeids.at(i))); - } - } - - updateDetailWidget(); -} - void RPCConsole::updateDetailWidget() { const QList selected_peers = GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId); @@ -1369,8 +1288,18 @@ void RPCConsole::setButtonIcons() { const QSize consoleButtonsSize(BUTTON_ICONSIZE * 0.8, BUTTON_ICONSIZE * 0.8); GUIUtil::setIcon(ui->clearButton, "remove", GUIUtil::ThemedColor::RED, consoleButtonsSize); + GUIUtil::setIcon(ui->fontBiggerButton, "fontbigger", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); + //: Main shortcut to increase the RPC console font size. + ui->fontBiggerButton->setShortcut(tr("Ctrl++")); + //: Secondary shortcut to increase the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontBiggerButton, tr("Ctrl+=")); + GUIUtil::setIcon(ui->fontSmallerButton, "fontsmaller", GUIUtil::ThemedColor::BLUE, consoleButtonsSize); + //: Main shortcut to decrease the RPC console font size. + ui->fontSmallerButton->setShortcut(tr("Ctrl+-")); + //: Secondary shortcut to decrease the RPC console font size. + GUIUtil::AddButtonShortcut(ui->fontSmallerButton, tr("Ctrl+_")); } void RPCConsole::reloadThemedWidgets() diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 2834311ca5666..7cbb36c20f898 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -134,10 +134,6 @@ public Q_SLOTS: void browseHistory(int offset); /** Scroll console view to end */ void scrollToEnd(); - /** Handle selection caching before update */ - void peerLayoutAboutToChange(); - /** Handle updated peer information */ - void peerLayoutChanged(); /** Disconnect a selected node on the Peers tab */ void disconnectSelectedNode(); /** Ban a selected node on the Peers tab */ diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 6ce17d274c59e..067af81be674b 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -207,15 +207,15 @@ void SendCoinsDialog::setModel(WalletModel *_model) for (const int n : confTargets) { ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n)); } - connect(ui->confTargetSelector, static_cast(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel); - connect(ui->confTargetSelector, static_cast(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels); + connect(ui->confTargetSelector, qOverload(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel); + connect(ui->confTargetSelector, qOverload(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels); #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls); connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels); #else - connect(ui->groupFee, static_cast(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls); - connect(ui->groupFee, static_cast(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels); + connect(ui->groupFee, qOverload(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls); + connect(ui->groupFee, qOverload(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels); #endif connect(ui->customFee, &BitcoinAmountField::valueChanged, this, &SendCoinsDialog::coinControlUpdateLabels); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 38d446186b282..547ff055658cf 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -20,10 +20,10 @@ #endif #include -#include #include #include #include +#include #include #include #include @@ -34,13 +34,14 @@ namespace { //! Call getblockchaininfo RPC and check first field of JSON output. void TestRpcCommand(RPCConsole* console) { - QEventLoop loop; QTextEdit* messagesWidget = console->findChild("messagesWidget"); - QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, &QEventLoop::quit); QLineEdit* lineEdit = console->findChild("lineEdit"); + QSignalSpy mw_spy(messagesWidget, &QTextEdit::textChanged); + QVERIFY(mw_spy.isValid()); QTest::keyClicks(lineEdit, "getblockchaininfo"); QTest::keyClick(lineEdit, Qt::Key_Return); - loop.exec(); + QVERIFY(mw_spy.wait(1000)); + QCOMPARE(mw_spy.count(), 2); QString output = messagesWidget->toPlainText(); UniValue value; value.read(output.right(output.size() - output.indexOf("{")).toStdString()); diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index c08c7db2f92ef..a738428c6c84e 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -95,21 +95,23 @@ class TransactionTablePriv */ QList cachedWallet; - bool fQueueNotifications = false; + /** True when model finishes loading all wallet transactions on start */ + bool m_loaded = false; + /** True when transactions are being notified, for instance when scanning */ + bool m_loading = false; std::vector< TransactionNotification > vQueueNotifications; void NotifyTransactionChanged(const uint256 &hash, ChangeType status); void NotifyAddressBookChanged(const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status); - void ShowProgress(const std::string &title, int nProgress); + void DispatchNotifications(); /* Query entire wallet anew from core. */ void refreshWallet(interfaces::Wallet& wallet) { - qDebug() << "TransactionTablePriv::refreshWallet"; parent->beginResetModel(); + assert(!m_loaded); try { - cachedWallet.clear(); for (const auto& wtx : wallet.getWalletTxs()) { if (TransactionRecord::showTransaction()) { cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, wtx)); @@ -119,6 +121,8 @@ class TransactionTablePriv QMessageBox::critical(nullptr, PACKAGE_NAME, QString("Failed to refresh wallet table: ") + QString::fromStdString(e.what())); } parent->endResetModel(); + m_loaded = true; + DispatchNotifications(); } /* Update our model of the wallet incrementally, to synchronize our model of the wallet @@ -267,12 +271,12 @@ TransactionTableModel::TransactionTableModel(WalletModel *parent): fProcessingQueuedTransactions(false), cachedChainLockHeight(-1) { + subscribeToCoreSignals(); + columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address / Label") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit()); priv->refreshWallet(walletModel->wallet()); connect(walletModel->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &TransactionTableModel::updateDisplayUnit); - - subscribeToCoreSignals(); } TransactionTableModel::~TransactionTableModel() @@ -793,7 +797,7 @@ void TransactionTablePriv::NotifyTransactionChanged(const uint256 &hash, ChangeT TransactionNotification notification(hash, status, showTransaction); - if (fQueueNotifications) + if (!m_loaded || m_loading) { vQueueNotifications.push_back(notification); return; @@ -812,35 +816,30 @@ void TransactionTablePriv::NotifyAddressBookChanged(const CTxDestination &addres assert(invoked); } -void TransactionTablePriv::ShowProgress(const std::string &title, int nProgress) +void TransactionTablePriv::DispatchNotifications() { - if (nProgress == 0) - fQueueNotifications = true; + if (!m_loaded || m_loading) return; - if (nProgress == 100) - { - fQueueNotifications = false; - if (vQueueNotifications.size() < 10000) { - if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons - bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); + if (vQueueNotifications.size() < 10000) { + if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons + bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true)); + assert(invoked); + } + for (unsigned int i = 0; i < vQueueNotifications.size(); ++i) + { + if (vQueueNotifications.size() - i <= 10) { + bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); assert(invoked); } - for (unsigned int i = 0; i < vQueueNotifications.size(); ++i) - { - if (vQueueNotifications.size() - i <= 10) { - bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false)); - assert(invoked); - } - vQueueNotifications[i].invoke(parent); - } - } else { - // it's much faster to just refresh the whole thing instead - bool invoked = QMetaObject::invokeMethod(parent, "refreshWallet", Qt::QueuedConnection); - assert(invoked); + vQueueNotifications[i].invoke(parent); } - vQueueNotifications.clear(); + } else { + // it's much faster to just refresh the whole thing instead + bool invoked = QMetaObject::invokeMethod(parent, "refreshWallet", Qt::QueuedConnection); + assert(invoked); } + vQueueNotifications.clear(); } void TransactionTableModel::subscribeToCoreSignals() @@ -848,7 +847,10 @@ void TransactionTableModel::subscribeToCoreSignals() // Connect signals to wallet m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged(std::bind(&TransactionTablePriv::NotifyTransactionChanged, priv, std::placeholders::_1, std::placeholders::_2)); m_handler_address_book_changed = walletModel->wallet().handleAddressBookChanged(std::bind(&TransactionTablePriv::NotifyAddressBookChanged, priv, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); - m_handler_show_progress = walletModel->wallet().handleShowProgress(std::bind(&TransactionTablePriv::ShowProgress, priv, std::placeholders::_1, std::placeholders::_2)); + m_handler_show_progress = walletModel->wallet().handleShowProgress([this](const std::string&, int progress) { + priv->m_loading = progress < 100; + priv->DispatchNotifications(); + }); } void TransactionTableModel::unsubscribeFromCoreSignals() diff --git a/src/qt/transactionview.cpp b/src/qt/transactionview.cpp index b51361a64683e..d32283c0ec01a 100644 --- a/src/qt/transactionview.cpp +++ b/src/qt/transactionview.cpp @@ -145,60 +145,36 @@ TransactionView::TransactionView(QWidget* parent) : transactionView->setObjectName("transactionView"); - // Actions - abandonAction = new QAction(tr("Abandon transaction"), this); - resendAction = new QAction(tr("Resend transaction"), this); - copyAddressAction = new QAction(tr("Copy address"), this); - copyLabelAction = new QAction(tr("Copy label"), this); - QAction *copyAmountAction = new QAction(tr("Copy amount"), this); - QAction *copyTxIDAction = new QAction(tr("Copy transaction ID"), this); - QAction *copyTxHexAction = new QAction(tr("Copy raw transaction"), this); - QAction *copyTxPlainText = new QAction(tr("Copy full transaction details"), this); - QAction *editLabelAction = new QAction(tr("Edit address label"), this); - QAction *showDetailsAction = new QAction(tr("Show transaction details"), this); - QAction *showAddressQRCodeAction = new QAction(tr("Show address QR code"), this); + contextMenu = new QMenu(this); + contextMenu->setObjectName("contextMenu"); + copyAddressAction = contextMenu->addAction(tr("&Copy address"), this, &TransactionView::copyAddress); + copyLabelAction = contextMenu->addAction(tr("Copy &label"), this, &TransactionView::copyLabel); + contextMenu->addAction(tr("Copy &amount"), this, &TransactionView::copyAmount); + contextMenu->addAction(tr("Copy transaction &ID"), this, &TransactionView::copyTxID); + contextMenu->addAction(tr("Copy &raw transaction"), this, &TransactionView::copyTxHex); + contextMenu->addAction(tr("Copy full transaction &details"), this, &TransactionView::copyTxPlainText); + contextMenu->addAction(tr("&Show transaction details"), this, &TransactionView::showDetails); + contextMenu->addSeparator(); + abandonAction = contextMenu->addAction(tr("A&bandon transaction"), this, &TransactionView::abandonTx); + resendAction = contextMenu->addAction(tr("Rese&nd transaction"), this, &TransactionView::resendTx); + contextMenu->addAction(tr("&Edit address label"), this, &TransactionView::editLabel); + [[maybe_unused]] QAction* showAddressQRCodeAction = contextMenu->addAction(tr("Show address &QR code"), this, &TransactionView::showAddressQRCode); #ifndef USE_QRCODE showAddressQRCodeAction->setEnabled(false); #endif - contextMenu = new QMenu(this); - contextMenu->setObjectName("contextMenu"); - contextMenu->addAction(copyAddressAction); - contextMenu->addAction(copyLabelAction); - contextMenu->addAction(copyAmountAction); - contextMenu->addAction(copyTxIDAction); - contextMenu->addAction(copyTxHexAction); - contextMenu->addAction(copyTxPlainText); - contextMenu->addAction(showDetailsAction); - contextMenu->addAction(showAddressQRCodeAction); - contextMenu->addSeparator(); - contextMenu->addAction(abandonAction); - contextMenu->addAction(resendAction); - contextMenu->addAction(editLabelAction); - - connect(dateWidget, static_cast(&QComboBox::activated), this, &TransactionView::chooseDate); - connect(typeWidget, static_cast(&QComboBox::activated), this, &TransactionView::chooseType); - connect(watchOnlyWidget, static_cast(&QComboBox::activated), this, &TransactionView::chooseWatchonly); - connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay, static_cast(&QTimer::start)); + connect(dateWidget, qOverload(&QComboBox::activated), this, &TransactionView::chooseDate); + connect(typeWidget, qOverload(&QComboBox::activated), this, &TransactionView::chooseType); + connect(watchOnlyWidget, qOverload(&QComboBox::activated), this, &TransactionView::chooseWatchonly); + connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay, qOverload<>(&QTimer::start)); connect(amount_typing_delay, &QTimer::timeout, this, &TransactionView::changedAmount); - connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay, static_cast(&QTimer::start)); + connect(search_widget, &QLineEdit::textChanged, prefix_typing_delay, qOverload<>(&QTimer::start)); connect(prefix_typing_delay, &QTimer::timeout, this, &TransactionView::changedSearch); connect(transactionView, &QTableView::doubleClicked, this, &TransactionView::doubleClicked); connect(transactionView, &QTableView::clicked, this, &TransactionView::computeSum); connect(transactionView, &QTableView::customContextMenuRequested, this, &TransactionView::contextualMenu); - connect(abandonAction, &QAction::triggered, this, &TransactionView::abandonTx); - connect(resendAction, &QAction::triggered, this, &TransactionView::resendTx); - connect(copyAddressAction, &QAction::triggered, this, &TransactionView::copyAddress); - connect(copyLabelAction, &QAction::triggered, this, &TransactionView::copyLabel); - connect(copyAmountAction, &QAction::triggered, this, &TransactionView::copyAmount); - connect(copyTxIDAction, &QAction::triggered, this, &TransactionView::copyTxID); - connect(copyTxHexAction, &QAction::triggered, this, &TransactionView::copyTxHex); - connect(copyTxPlainText, &QAction::triggered, this, &TransactionView::copyTxPlainText); - connect(editLabelAction, &QAction::triggered, this, &TransactionView::editLabel); - connect(showDetailsAction, &QAction::triggered, this, &TransactionView::showDetails); - connect(showAddressQRCodeAction, &QAction::triggered, this, &TransactionView::showAddressQRCode); // Double-clicking on a transaction on the transaction history page shows details connect(this, &TransactionView::doubleClicked, this, &TransactionView::showDetails); } diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index ac56bfc9c86d8..48605227e783b 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -99,14 +99,14 @@ WalletView::WalletView(QWidget* parent) : connect(overviewPage, &OverviewPage::transactionClicked, this, &WalletView::transactionClicked); // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page - connect(overviewPage, &OverviewPage::transactionClicked, transactionView, static_cast(&TransactionView::focusTransaction)); + connect(overviewPage, &OverviewPage::transactionClicked, transactionView, qOverload(&TransactionView::focusTransaction)); connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo); connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); connect(coinJoinCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); // Highlight transaction after send - connect(sendCoinsPage, &SendCoinsDialog::coinsSent, transactionView, static_cast(&TransactionView::focusTransaction)); - connect(coinJoinCoinsPage, &SendCoinsDialog::coinsSent, transactionView, static_cast(&TransactionView::focusTransaction)); + connect(sendCoinsPage, &SendCoinsDialog::coinsSent, transactionView, qOverload(&TransactionView::focusTransaction)); + connect(coinJoinCoinsPage, &SendCoinsDialog::coinsSent, transactionView, qOverload(&TransactionView::focusTransaction)); // Update wallet with sum of selected transactions connect(transactionView, &TransactionView::trxAmount, this, &WalletView::trxAmount);