Merge #6168: backport: bitcoin-core#gui18, #121, #257, #263, #281, #335, #362, #828, bitcoin#21912, #21942, #21988

c7d3161b3b Merge bitcoin-core/gui#362: Add keyboard shortcuts to context menus (Hennadii Stepanov)
25f87b9434 Merge bitcoin-core/gui#121: Early subscribe core signals in transaction table model (Hennadii Stepanov)
ed56e28a7c Merge bitcoin-core/gui#335: test: Use QSignalSpy instead of QEventLoop (Hennadii Stepanov)
c52b75609b Merge bitcoin-core/gui#281: set shortcuts for console's resize buttons (W. J. van der Laan)
b442a59b6d Merge bitcoin/bitcoin#21988: doc: note that brew installed qt is not supported (W. J. van der Laan)
0e2e315fcc Merge bitcoin/bitcoin#21942: docs: improve make with parallel jobs description. (MarcoFalke)
c2735a8a67 Merge bitcoin/bitcoin#21912: doc: Remove mention of priority estimation (W. J. van der Laan)
1d56d207cd Merge bitcoin-core/gui#257: refactor: Use template function qOverload in signal-slot connections (Hennadii Stepanov)
b5fb559706 Merge bitcoin-core/gui#18: Add peertablesortproxy module (Hennadii Stepanov)
1cdd9fbdf5 refactor: use new QAction style for governance list and masternode list (Konstantin Akimov)
4f89c98dc4 Merge bitcoin-core/gui#263: Revamp context menus (Hennadii Stepanov)
c36bb8e6fb fix: use && in governance urls instead & (Konstantin Akimov)
1e585b1987 Merge bitcoin-core/gui#828: Rendering an amp characters in the wallet name for QMenu (Hennadii Stepanov)

Pull request description:

  ## Issue being fixed or feature implemented
  Just regular backports from bitcoin v22, mostly Qt related

  ## What was done?
  See commits for a list of backports.

  This PR also fixes a rendering an url on Governance tab if it has any '&' inside. see screenshots.

  Original url: `https://example.com/?test=nothing&to=see&&lol` - yes, double '&&' just for test even if url is silly.
  Failed behaviour:
  ![image](https://github.com/user-attachments/assets/ac45c192-7d0e-4cd2-97f8-060af8f3911b)
  Correctly rendered:
  ![image](https://github.com/user-attachments/assets/5e345197-776a-4bb8-9476-cab4aba3429e)

  ## How Has This Been Tested?
  Run unit/functional tests

  ## Breaking Changes
  N/A

  ## Checklist:
  - [x] I have performed a self-review of my own code
  - [ ] I have commented my code, particularly in hard-to-understand areas
  - [ ] I have added or updated relevant unit/integration/functional/e2e tests
  - [ ] I have made corresponding changes to the documentation
  - [x] I have assigned this pull request to a milestone

ACKs for top commit:
  PastaPastaPasta:
    utACK c7d3161b3b
  UdjinM6:
    utACK c7d3161b3b

Tree-SHA512: 67e7e8e0ec1a768d1f13baa48c123e4a415d3f32177a427d8117339a5eacf70864ebf46e9f1165bb8a3bf9c231f7929d33ac6aa19742e06a4e19d2f86dda6dc3
This commit is contained in:
pasta 2024-08-21 09:09:33 -05:00
commit f2940c4e9c
No known key found for this signature in database
GPG Key ID: 52527BEDABE87984
33 changed files with 264 additions and 390 deletions

View File

@ -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
```

View File

@ -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
```

View File

@ -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
```

View File

@ -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
```

View File

@ -22,7 +22,7 @@ To Build
```sh
./autogen.sh
./configure
make
make # use "-j N" for N parallel jobs
make install # optional
```

View File

@ -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

View File

@ -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 \

View File

@ -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);
#ifndef USE_QRCODE
showAddressQRCodeAction->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);
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
qrAction->setEnabled(false);
#endif
if (tab == SendingTab) {
contextMenu->addAction(tr("&Delete"), this, &AddressBookPage::on_deleteAddress_clicked);
}
// 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);
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);

View File

@ -54,7 +54,6 @@ private:
QString returnValue;
AddressBookSortFilterProxyModel *proxyModel;
QMenu *contextMenu;
QAction *deleteAction; // to be able to explicitly disable it
QString newAddressToSelect;
private Q_SLOTS:

View File

@ -520,10 +520,9 @@ void BitcoinGUI::createActions()
for (const std::pair<const std::string, bool>& 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<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex);
connect(m_wallet_selector, qOverload<int>(&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);
}

View File

@ -9,6 +9,7 @@
#include <qt/guiconstants.h>
#include <qt/guiutil.h>
#include <qt/peertablemodel.h>
#include <qt/peertablesortproxy.h>
#include <evo/deterministicmns.h>
@ -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<CDeterministicMNList>();
@ -219,6 +224,11 @@ PeerTableModel *ClientModel::getPeerTableModel()
return peerTableModel;
}
PeerTableSortProxy* ClientModel::peerTableSortProxy()
{
return m_peer_table_sort_proxy;
}
BanTableModel *ClientModel::getBanTableModel()
{
return banTableModel;

View File

@ -20,6 +20,7 @@ class BanTableModel;
class CBlockIndex;
class OptionsModel;
class PeerTableModel;
class PeerTableSortProxy;
enum class SynchronizationState;
QT_BEGIN_NAMESPACE
@ -58,6 +59,7 @@ public:
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 @@ private:
std::unique_ptr<interfaces::Handler> 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

View File

@ -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);

View File

@ -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());
}

View File

@ -345,6 +345,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

View File

@ -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);

View File

@ -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<int>::of(&QSpinBox::valueChanged), [this](int prune_GB) {
connect(ui->pruneGB, qOverload<int>(&QSpinBox::valueChanged), [this](int prune_GB) {
m_prune_target_gb = prune_GB;
UpdatePruneLabels(ui->prune->isChecked());
UpdateFreeSpaceLabel();

View File

@ -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);

View File

@ -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<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
connect(ui->databaseCache, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
connect(ui->threadsScriptVerif, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
connect(ui->pruneSize, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
connect(ui->databaseCache, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
connect(ui->threadsScriptVerif, qOverload<int>(&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<void (QValueComboBox::*)()>(&QValueComboBox::valueChanged), [this]{ showRestartWarning(); });
connect(ui->lang, static_cast<void (QValueComboBox::*)()>(&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<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomGoal);
connect(ui->coinJoinDenomsHardCap, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomHardCap);
connect(ui->coinJoinDenomsGoal, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomGoal);
connect(ui->coinJoinDenomsHardCap, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::updateCoinJoinDenomHardCap);
#endif
}

View File

@ -11,56 +11,19 @@
#include <utility>
#include <QDebug>
#include <QList>
#include <QTimer>
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<PeerTableModel::ColumnIndex>(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<CNodeCombinedStats> 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<NodeId, int> 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 @@ public:
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<NodeId, int>(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<NodeId, int>::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();
}

View File

@ -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 @@ public:
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:

View File

@ -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 <qt/peertablesortproxy.h>
#include <qt/peertablemodel.h>
#include <util/check.h>
#include <QModelIndex>
#include <QString>
#include <QVariant>
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<CNodeCombinedStats*>())->nodeStats;
const CNodeStats right_stats = Assert(sourceModel()->data(right_index, PeerTableModel::StatsRole).value<CNodeCombinedStats*>())->nodeStats;
switch (static_cast<PeerTableModel::ColumnIndex>(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);
}

View File

@ -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 <QSortFilterProxyModel>
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

View File

@ -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)

View File

@ -7,7 +7,7 @@
QValueComboBox::QValueComboBox(QWidget *parent) :
QComboBox(parent), role(Qt::UserRole)
{
connect(this, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &QValueComboBox::handleSelectionChanged);
connect(this, qOverload<int>(&QComboBox::currentIndexChanged), this, &QValueComboBox::handleSelectionChanged);
}
QVariant QValueComboBox::value() const

View File

@ -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);
}

View File

@ -12,12 +12,13 @@
#include <evo/deterministicmns.h>
#include <qt/bantablemodel.h>
#include <qt/clientmodel.h>
#include <qt/walletmodel.h>
#include <chainparams.h>
#include <interfaces/node.h>
#include <netbase.h>
#include <qt/bantablemodel.h>
#include <qt/clientmodel.h>
#include <qt/peertablesortproxy.h>
#include <qt/walletmodel.h>
#include <rpc/client.h>
#include <rpc/server.h>
#include <util/strencodings.h>
@ -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) + "<br>" +
tr("Use up and down arrows to navigate history, and %1 to clear screen.").arg("<b>"+clsKey+"</b>") + "<br>" +
tr("Type %1 for an overview of available commands.").arg("<b>help</b>") + "<br>" +
tr("For more information on using this console type %1.").arg("<b>help-console</b>") +
"<br><span class=\"secwarning\"><br>" +
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.") +
"</span>"),
true);
message(CMD_REPLY,
tr("Welcome to the %1 RPC console.").arg(PACKAGE_NAME) +
"<br>" +
tr("Use up and down arrows to navigate history, and %1 to clear screen.")
.arg("<b>" + ui->clearButton->shortcut().toString(QKeySequence::NativeText) + "</b>") +
"<br>" +
tr("Use %1 and %2 to increase or decrease the font size.")
.arg("<b>" + ui->fontBiggerButton->shortcut().toString(QKeySequence::NativeText) + "</b>")
.arg("<b>" + ui->fontSmallerButton->shortcut().toString(QKeySequence::NativeText) + "</b>") +
"<br>" +
tr("Type %1 for an overview of available commands.").arg("<b>help</b>") +
"<br>" +
tr("For more information on using this console type %1.").arg("<b>help-console</b>") +
"<br><span class=\"secwarning\"><br>" +
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.") +
"</span>",
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<void (RPCConsole::*)(int, const QString&)>(&RPCConsole::message));
connect(executor, &RPCExecutor::reply, this, qOverload<int, const QString&>(&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<CNodeCombinedStats*>();
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<QModelIndex> 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()

View File

@ -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 */

View File

@ -210,15 +210,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<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
connect(ui->confTargetSelector, qOverload<int>(&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<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
#endif
connect(ui->customFee, &BitcoinAmountField::valueChanged, this, &SendCoinsDialog::coinControlUpdateLabels);

View File

@ -20,10 +20,10 @@
#endif
#include <QAction>
#include <QEventLoop>
#include <QLineEdit>
#include <QScopedPointer>
#include <QSettings>
#include <QSignalSpy>
#include <QTest>
#include <QTextEdit>
#include <QtGlobal>
@ -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<QTextEdit*>("messagesWidget");
QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, &QEventLoop::quit);
QLineEdit* lineEdit = console->findChild<QLineEdit*>("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());

View File

@ -95,21 +95,23 @@ public:
*/
QList<TransactionRecord> 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 @@ public:
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()
@ -806,7 +810,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;
@ -825,35 +829,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));
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);
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);
}
vQueueNotifications.clear();
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.clear();
}
void TransactionTableModel::subscribeToCoreSignals()
@ -861,7 +860,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()

View File

@ -146,60 +146,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<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseDate);
connect(typeWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseType);
connect(watchOnlyWidget, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated), this, &TransactionView::chooseWatchonly);
connect(amountWidget, &QLineEdit::textChanged, amount_typing_delay, static_cast<void (QTimer::*)()>(&QTimer::start));
connect(dateWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseDate);
connect(typeWidget, qOverload<int>(&QComboBox::activated), this, &TransactionView::chooseType);
connect(watchOnlyWidget, qOverload<int>(&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<void (QTimer::*)()>(&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);
}

View File

@ -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<void (TransactionView::*)(const QModelIndex&)>(&TransactionView::focusTransaction));
connect(overviewPage, &OverviewPage::transactionClicked, transactionView, qOverload<const QModelIndex&>(&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<void (TransactionView::*)(const uint256&)>(&TransactionView::focusTransaction));
connect(coinJoinCoinsPage, &SendCoinsDialog::coinsSent, transactionView, static_cast<void (TransactionView::*)(const uint256&)>(&TransactionView::focusTransaction));
connect(sendCoinsPage, &SendCoinsDialog::coinsSent, transactionView, qOverload<const uint256&>(&TransactionView::focusTransaction));
connect(coinJoinCoinsPage, &SendCoinsDialog::coinsSent, transactionView, qOverload<const uint256&>(&TransactionView::focusTransaction));
// Update wallet with sum of selected transactions
connect(transactionView, &TransactionView::trxAmount, this, &WalletView::trxAmount);