diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index c9efbe6504..a97956c48d 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -364,7 +364,7 @@ RES_ANIMATION = $(wildcard $(srcdir)/qt/res/animation/spinner-*.png) BITCOIN_RC = qt/res/dash-qt-res.rc -BITCOIN_QT_INCLUDES = -DQT_NO_KEYWORDS +BITCOIN_QT_INCLUDES = -DQT_NO_KEYWORDS -DQT_USE_QSTRINGBUILDER qt_libbitcoinqt_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) \ $(QT_INCLUDES) $(QT_DBUS_INCLUDES) $(QR_CFLAGS) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index fc66231389..52bd0767b3 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -54,7 +54,6 @@ #include #include #include -#include #include #include #include @@ -492,8 +491,8 @@ void BitcoinApplication::handleRunawayException(const QString &message) { QMessageBox::critical( nullptr, tr("Runaway exception"), - tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(PACKAGE_NAME) % - QLatin1String("

") % GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT)); + tr("A fatal error occurred. %1 can no longer continue safely and will quit.").arg(PACKAGE_NAME) + + QLatin1String("

") + GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT)); ::exit(EXIT_FAILURE); } @@ -503,8 +502,8 @@ void BitcoinApplication::handleNonFatalException(const QString& message) QMessageBox::warning( nullptr, tr("Internal error"), tr("An internal error occurred. %1 will attempt to continue safely. This is " - "an unexpected bug which can be reported as described below.").arg(PACKAGE_NAME) % - QLatin1String("

") % GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT)); + "an unexpected bug which can be reported as described below.").arg(PACKAGE_NAME) + + QLatin1String("

") + GUIUtil::MakeHtmlLink(message, PACKAGE_BUGREPORT)); } WId BitcoinApplication::getMainWinId() const diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 5191b1d17d..98ba9297da 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +115,11 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, connect(walletFrame, &WalletFrame::message, [this](const QString& title, const QString& message, unsigned int style) { this->message(title, message, style); }); + connect(walletFrame, &WalletFrame::createWalletButtonClicked, [this] { + auto activity = new CreateWalletActivity(getWalletController(), this); + connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); + activity->create(); + }); } else #endif // ENABLE_WALLET { @@ -211,8 +217,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, // Subscribe to notifications from core subscribeToCoreSignals(); - // Jump to peers tab by clicking on connections icon - connect(labelConnectionsIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showPeers); connect(labelProxyIcon, &GUIUtil::ClickableLabel::clicked, [this] { openOptionsDialogWithTab(OptionsDialog::TAB_NETWORK); }); @@ -220,11 +224,6 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, modalOverlay = new ModalOverlay(enableWallet, this->centralWidget()); connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay); connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay); -#ifdef ENABLE_WALLET - if(enableWallet) { - connect(walletFrame, &WalletFrame::requestedSyncWarningInfo, this, &BitcoinGUI::showModalOverlay); - } -#endif #ifdef Q_OS_MAC m_app_nap_inhibitor = new CAppNapInhibitor; @@ -812,8 +811,11 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH } // Keep up to date with client - updateNetworkState(); + setNetworkActive(m_node.getNetworkActive()); setNumConnections(_clientModel->getNumConnections()); + connect(labelConnectionsIcon, &GUIUtil::ClickableLabel::clicked, [this] { + GUIUtil::PopupMenu(m_network_context_menu, QCursor::pos()); + }); connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections); connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive); @@ -911,13 +913,28 @@ WalletController* BitcoinGUI::getWalletController() void BitcoinGUI::addWallet(WalletModel* walletModel) { if (!walletFrame) return; - if (!walletFrame->addWallet(walletModel)) return; + + WalletView* wallet_view = new WalletView(walletFrame); + if (!walletFrame->addWallet(walletModel, wallet_view)) return; + rpcConsole->addWallet(walletModel); if (m_wallet_selector->count() == 0) { setWalletActionsEnabled(true); } else if (m_wallet_selector->count() == 1) { m_wallet_selector_action->setVisible(true); } + + connect(wallet_view, &WalletView::outOfSyncWarningClicked, this, &BitcoinGUI::showModalOverlay); + connect(wallet_view, &WalletView::transactionClicked, this, &BitcoinGUI::gotoHistoryPage); + connect(wallet_view, &WalletView::coinsSent, this, &BitcoinGUI::gotoHistoryPage); + connect(wallet_view, &WalletView::message, [this](const QString& title, const QString& message, unsigned int style) { + this->message(title, message, style); + }); + connect(wallet_view, &WalletView::encryptionStatusChanged, this, &BitcoinGUI::updateWalletStatus); + connect(wallet_view, &WalletView::incomingTransaction, this, &BitcoinGUI::incomingTransaction); + connect(wallet_view, &WalletView::hdEnabledStatusChanged, this, &BitcoinGUI::updateWalletStatus); + connect(this, &BitcoinGUI::setPrivacy, wallet_view, &WalletView::setPrivacy); + wallet_view->setPrivacy(isPrivacyModeActivated()); const QString display_name = walletModel->getDisplayName(); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); } @@ -1270,14 +1287,21 @@ void BitcoinGUI::updateNetworkState() nCountPrev = count; fNetworkActivePrev = fNetworkActive; + QString tooltip; if (fNetworkActive) { - labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Dash network", "", count)); + //: A substring of the tooltip. + tooltip = tr("%n active connection(s) to Dash network", "", count); } else { - labelConnectionsIcon->setToolTip(tr("Network activity disabled")); + tooltip = tr("Network activity disabled"); icon = "connect_4"; color = GUIUtil::ThemedColor::RED; } + // Don't word-wrap this (fixed-width) tooltip + tooltip = QLatin1String("") + tooltip + QLatin1String("
") + + //: A substring of the tooltip. "More actions" are available via the context menu. + tr("Click for more actions.") + QLatin1String("
"); + if (fNetworkActive && count == 0) { startConnectingAnimation(); } @@ -1285,6 +1309,7 @@ void BitcoinGUI::updateNetworkState() stopConnectingAnimation(); labelConnectionsIcon->setPixmap(GUIUtil::getIcon(icon, color).pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); } + labelConnectionsIcon->setToolTip(tooltip); } void BitcoinGUI::setNumConnections(int count) @@ -1292,9 +1317,24 @@ void BitcoinGUI::setNumConnections(int count) updateNetworkState(); } -void BitcoinGUI::setNetworkActive(bool networkActive) +void BitcoinGUI::setNetworkActive(bool network_active) { updateNetworkState(); + m_network_context_menu->clear(); + m_network_context_menu->addAction( + //: A context menu item. The "Peers tab" is an element of the "Node window". + tr("Show Peers tab"), + [this] { + rpcConsole->setTabFocus(RPCConsole::TabTypes::PEERS); + showDebugWindow(); + }); + m_network_context_menu->addAction( + network_active ? + //: A context menu item. + tr("Disable network activity") : + //: A context menu item. The network activity was disabled previously. + tr("Enable network activity"), + [this, new_state = !network_active] { m_node.setNetworkActive(new_state); }); } void BitcoinGUI::updateHeadersSyncProgressLabel() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 5831ed3715..7b86531fbe 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -51,7 +52,6 @@ class QAction; class QButtonGroup; class QComboBox; class QDateTime; -class QMenu; class QProgressBar; class QProgressDialog; class QToolButton; @@ -190,6 +190,8 @@ private: ModalOverlay* modalOverlay = nullptr; QButtonGroup* tabGroup = nullptr; + QMenu* m_network_context_menu = new QMenu(this); + #ifdef Q_OS_MAC CAppNapInhibitor* m_app_nap_inhibitor = nullptr; #endif @@ -264,7 +266,7 @@ public Q_SLOTS: /** Set number of connections shown in the UI */ void setNumConnections(int count); /** Set network state shown in the UI */ - void setNetworkActive(bool networkActive); + void setNetworkActive(bool network_active); /** Get restart command-line parameters and request restart */ void handleRestart(QStringList args); /** Set number of blocks and last block date shown in the UI */ diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a67efe2552..649b009f25 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -69,7 +69,6 @@ #include #include #include -#include #include // for Qt::mightBeRichText #include #include @@ -1931,7 +1930,7 @@ QString MakeHtmlLink(const QString& source, const QString& link) { return QString(source).replace( link, - QLatin1String("") % link % QLatin1String("")); + QLatin1String("") + link + QLatin1String("")); } void PrintSlotException( diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 85c4cf4077..874a255b84 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -26,6 +26,7 @@ #endif #include +#include #include #include @@ -376,7 +377,7 @@ static ProxySetting GetProxySetting(QSettings &settings, const QString &name) static void SetProxySetting(QSettings &settings, const QString &name, const ProxySetting &ip_port) { - settings.setValue(name, ip_port.ip + ":" + ip_port.port); + settings.setValue(name, QString{ip_port.ip + QLatin1Char(':') + ip_port.port}); } static const QString GetDefaultProxyAddress() diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 16c2a5dfae..30b238d4db 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -199,11 +199,6 @@ void OverviewPage::handleTransactionClicked(const QModelIndex &index) Q_EMIT transactionClicked(filter->mapToSource(index)); } -void OverviewPage::handleOutOfSyncWarningClicks() -{ - Q_EMIT outOfSyncWarningClicked(); -} - void OverviewPage::setPrivacy(bool privacy) { m_privacy = privacy; @@ -469,7 +464,7 @@ void OverviewPage::updateCoinJoinProgress() ui->coinJoinProgress->setValue(progress); - QString strToolPip = ("" + tr("Overall progress") + ": %1%
" + + QString strToolPip = QString("" + tr("Overall progress") + ": %1%
" + tr("Denominated") + ": %2%
" + tr("Partially mixed") + ": %3%
" + tr("Mixed") + ": %4%
" + diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index 41af14cb8c..f922e8ff74 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -70,7 +70,6 @@ private Q_SLOTS: void handleTransactionClicked(const QModelIndex &index); void updateAlerts(const QString &warnings); void updateWatchOnlyLabels(bool showWatchOnly); - void handleOutOfSyncWarningClicks(); }; #endif // BITCOIN_QT_OVERVIEWPAGE_H diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp index 1dfc195390..ecd305a77d 100644 --- a/src/qt/peertablemodel.cpp +++ b/src/qt/peertablemodel.cpp @@ -14,53 +14,11 @@ #include #include -// private implementation -class PeerTablePriv -{ -public: - /** Local cache of peer information */ - QList cachedNodeStats; - - /** Pull a full list of peers from vNodes into our cache */ - void refreshPeers(interfaces::Node& node) - { - cachedNodeStats.clear(); - - interfaces::Node::NodesStats nodes_stats; - node.getNodesStats(nodes_stats); - cachedNodeStats.reserve(nodes_stats.size()); - - for (const auto& node_stats : nodes_stats) - { - CNodeCombinedStats stats; - stats.nodeStats = std::get<0>(node_stats); - stats.fNodeStateStatsAvailable = std::get<1>(node_stats); - stats.nodeStateStats = std::get<2>(node_stats); - cachedNodeStats.append(stats); - } - } - - int size() const - { - return cachedNodeStats.size(); - } - - CNodeCombinedStats *index(int idx) - { - if (idx >= 0 && idx < cachedNodeStats.size()) - return &cachedNodeStats[idx]; - - return nullptr; - } -}; - PeerTableModel::PeerTableModel(interfaces::Node& node, QObject* parent) : QAbstractTableModel(parent), m_node(node), timer(nullptr) { - priv.reset(new PeerTablePriv()); - // set up timer for auto refresh timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &PeerTableModel::refresh); @@ -85,15 +43,15 @@ void PeerTableModel::stopAutoRefresh() timer->stop(); } -int PeerTableModel::rowCount(const QModelIndex &parent) const +int PeerTableModel::rowCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; } - return priv->size(); + return m_peers_data.size(); } -int PeerTableModel::columnCount(const QModelIndex &parent) const +int PeerTableModel::columnCount(const QModelIndex& parent) const { if (parent.isValid()) { return 0; @@ -101,7 +59,7 @@ int PeerTableModel::columnCount(const QModelIndex &parent) const return columns.length(); } -QVariant PeerTableModel::data(const QModelIndex &index, int role) const +QVariant PeerTableModel::data(const QModelIndex& index, int role) const { if(!index.isValid()) return QVariant(); @@ -115,7 +73,7 @@ QVariant PeerTableModel::data(const QModelIndex &index, int role) const return (qint64)rec->nodeStats.nodeid; case Address: // prepend to peer address down-arrow symbol for inbound connection and up-arrow for outbound connection - return QString(rec->nodeStats.fInbound ? "↓ " : "↑ ") + QString::fromStdString(rec->nodeStats.m_addr_name); + return QString::fromStdString((rec->nodeStats.fInbound ? "↓ " : "↑ ") + rec->nodeStats.m_addr_name); case ConnectionType: return GUIUtil::ConnectionTypeToQString(rec->nodeStats.m_conn_type, /* prepend_direction */ false); case Network: @@ -174,19 +132,52 @@ Qt::ItemFlags PeerTableModel::flags(const QModelIndex &index) const return retval; } -QModelIndex PeerTableModel::index(int row, int column, const QModelIndex &parent) const +QModelIndex PeerTableModel::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent); - CNodeCombinedStats *data = priv->index(row); - if (data) - return createIndex(row, column, data); + if (0 <= row && row < rowCount() && 0 <= column && column < columnCount()) { + return createIndex(row, column, const_cast(&m_peers_data[row])); + } + return QModelIndex(); } void PeerTableModel::refresh() { - Q_EMIT layoutAboutToBeChanged(); - priv->refreshPeers(m_node); - Q_EMIT layoutChanged(); + interfaces::Node::NodesStats nodes_stats; + m_node.getNodesStats(nodes_stats); + decltype(m_peers_data) new_peers_data; + new_peers_data.reserve(nodes_stats.size()); + for (const auto& node_stats : nodes_stats) { + const CNodeCombinedStats stats{std::get<0>(node_stats), std::get<2>(node_stats), std::get<1>(node_stats)}; + new_peers_data.append(stats); + } + + // Handle peer addition or removal as suggested in Qt Docs. See: + // - https://doc.qt.io/qt-5/model-view-programming.html#inserting-and-removing-rows + // - https://doc.qt.io/qt-5/model-view-programming.html#resizable-models + // We take advantage of the fact that the std::vector returned + // by interfaces::Node::getNodesStats is sorted by nodeid. + for (int i = 0; i < m_peers_data.size();) { + if (i < new_peers_data.size() && m_peers_data.at(i).nodeStats.nodeid == new_peers_data.at(i).nodeStats.nodeid) { + ++i; + continue; + } + // A peer has been removed from the table. + beginRemoveRows(QModelIndex(), i, i); + m_peers_data.erase(m_peers_data.begin() + i); + endRemoveRows(); + } + + if (m_peers_data.size() < new_peers_data.size()) { + // Some peers have been added to the end of the table. + beginInsertRows(QModelIndex(), m_peers_data.size(), new_peers_data.size() - 1); + m_peers_data.swap(new_peers_data); + endInsertRows(); + } else { + m_peers_data.swap(new_peers_data); + } + + Q_EMIT changed(); } diff --git a/src/qt/peertablemodel.h b/src/qt/peertablemodel.h index 3d195342f1..0ff1b5dba7 100644 --- a/src/qt/peertablemodel.h +++ b/src/qt/peertablemodel.h @@ -8,10 +8,11 @@ #include // For CNodeStateStats #include -#include - #include +#include +#include #include +#include class PeerTablePriv; @@ -61,18 +62,23 @@ public: /** @name Methods overridden from QAbstractTableModel @{*/ - int rowCount(const QModelIndex &parent) const override; - int columnCount(const QModelIndex &parent) const override; - QVariant data(const QModelIndex &index, int role) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; - QModelIndex index(int row, int column, const QModelIndex &parent) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; /*@}*/ public Q_SLOTS: void refresh(); +Q_SIGNALS: + void changed(); + private: + //! Internal peer data structure. + QList m_peers_data{}; interfaces::Node& m_node; const QStringList columns{ /*: Title of Peers Table column which contains a @@ -99,7 +105,6 @@ private: /*: Title of Peers Table column which contains the peer's User Agent string. */ tr("User Agent")}; - std::unique_ptr priv; QTimer *timer; }; diff --git a/src/qt/recentrequeststablemodel.cpp b/src/qt/recentrequeststablemodel.cpp index 5c9623f9ff..c28fef6490 100644 --- a/src/qt/recentrequeststablemodel.cpp +++ b/src/qt/recentrequeststablemodel.cpp @@ -17,6 +17,9 @@ #include +#include +#include + RecentRequestsTableModel::RecentRequestsTableModel(WalletModel *parent) : QAbstractTableModel(parent), walletModel(parent) { @@ -126,7 +129,11 @@ void RecentRequestsTableModel::updateAmountColumnTitle() /** Gets title for amount column including current display unit if optionsModel reference available. */ QString RecentRequestsTableModel::getAmountTitle() { - return (this->walletModel->getOptionsModel() != nullptr) ? tr("Requested") + " ("+BitcoinUnits::name(this->walletModel->getOptionsModel()->getDisplayUnit()) + ")" : ""; + if (!walletModel->getOptionsModel()) return {}; + return tr("Requested") + + QLatin1String(" (") + + BitcoinUnits::name(this->walletModel->getOptionsModel()->getDisplayUnit()) + + QLatin1Char(')'); } QModelIndex RecentRequestsTableModel::index(int row, int column, const QModelIndex &parent) const diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 969fd9af22..b9c68c9c43 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -37,6 +37,7 @@ #include #endif +#include #include #include #include @@ -504,6 +505,9 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags ui->splitter->restoreState(settings.value("RPCConsoleWidgetPeersTabSplitterSizes").toByteArray()); } + m_peer_widget_header_state = settings.value("PeersTabPeerHeaderState").toByteArray(); + m_banlist_widget_header_state = settings.value("PeersTabBanlistHeaderState").toByteArray(); + constexpr QChar nonbreaking_hyphen(8209); const std::vector CONNECTION_TYPE_DOC{ //: Explanatory text for an inbound peer connection. @@ -547,9 +551,9 @@ RPCConsole::RPCConsole(interfaces::Node& node, QWidget* parent, Qt::WindowFlags ui->lineEdit->setMaxLength(16 * 1024 * 1024); ui->messagesWidget->installEventFilter(this); - connect(ui->clearButton, &QPushButton::clicked, [this] { clear(); }); - connect(ui->fontBiggerButton, &QPushButton::clicked, this, &RPCConsole::fontBigger); - connect(ui->fontSmallerButton, &QPushButton::clicked, this, &RPCConsole::fontSmaller); + connect(ui->clearButton, &QAbstractButton::clicked, [this] { clear(); }); + connect(ui->fontBiggerButton, &QAbstractButton::clicked, this, &RPCConsole::fontBigger); + connect(ui->fontSmallerButton, &QAbstractButton::clicked, this, &RPCConsole::fontSmaller); connect(ui->btnClearTrafficGraph, &QPushButton::clicked, ui->trafficGraph, &TrafficGraphWidget::clear); // disable the wallet selector by default @@ -609,6 +613,9 @@ RPCConsole::~RPCConsole() settings.setValue("RPCConsoleWidgetPeersTabSplitterSizes", ui->splitter->saveState()); } + settings.setValue("PeersTabPeerHeaderState", m_peer_widget_header_state); + settings.setValue("PeersTabBanlistHeaderState", m_banlist_widget_header_state); + m_node.rpcUnsetTimerInterface(rpcTimerInterface); delete rpcTimerInterface; delete pageButtons; @@ -700,9 +707,12 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ ui->peerWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->peerWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); ui->peerWidget->setContextMenuPolicy(Qt::CustomContextMenu); - ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH); - ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH); - ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH); + + if (!ui->peerWidget->horizontalHeader()->restoreState(m_peer_widget_header_state)) { + ui->peerWidget->setColumnWidth(PeerTableModel::Address, ADDRESS_COLUMN_WIDTH); + ui->peerWidget->setColumnWidth(PeerTableModel::Subversion, SUBVERSION_COLUMN_WIDTH); + ui->peerWidget->setColumnWidth(PeerTableModel::Ping, PING_COLUMN_WIDTH); + } ui->peerWidget->horizontalHeader()->setStretchLastSection(true); ui->peerWidget->setItemDelegateForColumn(PeerTableModel::NetNodeId, new PeerIdViewDelegate(this)); @@ -717,7 +727,7 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ // peer table signal handling - update peer details when selecting new node connect(ui->peerWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &RPCConsole::updateDetailWidget); - connect(model->getPeerTableModel(), &PeerTableModel::layoutChanged, this, &RPCConsole::updateDetailWidget); + connect(model->getPeerTableModel(), &PeerTableModel::changed, this, &RPCConsole::updateDetailWidget); // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); @@ -725,8 +735,11 @@ void RPCConsole::setClientModel(ClientModel *model, int bestblock_height, int64_ ui->banlistWidget->setSelectionBehavior(QAbstractItemView::SelectRows); ui->banlistWidget->setSelectionMode(QAbstractItemView::SingleSelection); ui->banlistWidget->setContextMenuPolicy(Qt::CustomContextMenu); - ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH); - ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH); + + if (!ui->banlistWidget->horizontalHeader()->restoreState(m_banlist_widget_header_state)) { + ui->banlistWidget->setColumnWidth(BanTableModel::Address, BANSUBNET_COLUMN_WIDTH); + ui->banlistWidget->setColumnWidth(BanTableModel::Bantime, BANTIME_COLUMN_WIDTH); + } ui->banlistWidget->horizontalHeader()->setStretchLastSection(true); // create ban table context menu @@ -936,23 +949,29 @@ void RPCConsole::clear(bool keep_prompt) ).arg(consoleFontSize) ); - 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); + static const QString welcome_message = + /*: RPC console welcome message. + Placeholders %7 and %8 are style tags for the warning content, and + they are not space separated from the rest of the text intentionally. */ + tr("Welcome to the %1 RPC console.\n" + "Use up and down arrows to navigate history, and %2 to clear screen.\n" + "Use %3 and %4 to increase or decrease the font size.\n" + "Type %5 for an overview of available commands.\n" + "For more information on using this console, type %6.\n" + "\n" + "%7WARNING: 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.%8") + .arg(PACKAGE_NAME, + "" + ui->clearButton->shortcut().toString(QKeySequence::NativeText) + "", + "" + ui->fontBiggerButton->shortcut().toString(QKeySequence::NativeText) + "", + "" + ui->fontSmallerButton->shortcut().toString(QKeySequence::NativeText) + "", + "help", + "help-console", + "", + ""); + + message(CMD_REPLY, welcome_message, true); } void RPCConsole::keyPressEvent(QKeyEvent *event) @@ -1074,57 +1093,71 @@ void RPCConsole::showPage(int index) void RPCConsole::on_lineEdit_returnPressed() { - QString cmd = ui->lineEdit->text(); + QString cmd = ui->lineEdit->text().trimmed(); - if(!cmd.isEmpty()) - { - std::string strFilteredCmd; - try { - std::string dummy; - if (!RPCParseCommandLine(nullptr, dummy, cmd.toStdString(), false, &strFilteredCmd)) { - // Failed to parse command, so we cannot even filter it for the history - throw std::runtime_error("Invalid command line"); - } - } catch (const std::exception& e) { - QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what())); - return; + if (cmd.isEmpty()) { + return; + } + + std::string strFilteredCmd; + try { + std::string dummy; + if (!RPCParseCommandLine(nullptr, dummy, cmd.toStdString(), false, &strFilteredCmd)) { + // Failed to parse command, so we cannot even filter it for the history + throw std::runtime_error("Invalid command line"); } + } catch (const std::exception& e) { + QMessageBox::critical(this, "Error", QString("Error: ") + QString::fromStdString(e.what())); + return; + } - ui->lineEdit->clear(); + // A special case allows to request shutdown even a long-running command is executed. + if (cmd == QLatin1String("stop")) { + std::string dummy; + RPCExecuteCommandLine(m_node, dummy, cmd.toStdString()); + return; + } - cmdBeforeBrowsing = QString(); + if (m_is_executing) { + return; + } + + ui->lineEdit->clear(); #ifdef ENABLE_WALLET - WalletModel* wallet_model = ui->WalletSelector->currentData().value(); + WalletModel* wallet_model = ui->WalletSelector->currentData().value(); - if (m_last_wallet_model != wallet_model) { - if (wallet_model) { - message(CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(wallet_model->getWalletName())); - } else { - message(CMD_REQUEST, tr("Executing command without any wallet")); - } - m_last_wallet_model = wallet_model; + if (m_last_wallet_model != wallet_model) { + if (wallet_model) { + message(CMD_REQUEST, tr("Executing command using \"%1\" wallet").arg(wallet_model->getWalletName())); + } else { + message(CMD_REQUEST, tr("Executing command without any wallet")); } -#endif - - message(CMD_REQUEST, QString::fromStdString(strFilteredCmd)); - Q_EMIT cmdRequest(cmd, m_last_wallet_model); - - cmd = QString::fromStdString(strFilteredCmd); - - // Remove command, if already in history - history.removeOne(cmd); - // Append command to history - history.append(cmd); - // Enforce maximum history size - while(history.size() > CONSOLE_HISTORY) - history.removeFirst(); - // Set pointer to end of history - historyPtr = history.size(); - - // Scroll console view to end - scrollToEnd(); + m_last_wallet_model = wallet_model; } +#endif // ENABLE_WALLET + + message(CMD_REQUEST, QString::fromStdString(strFilteredCmd)); + //: A console message indicating an entered command is currently being executed. + message(CMD_REPLY, tr("Executing…")); + m_is_executing = true; + Q_EMIT cmdRequest(cmd, m_last_wallet_model); + + cmd = QString::fromStdString(strFilteredCmd); + + // Remove command, if already in history + history.removeOne(cmd); + // Append command to history + history.append(cmd); + // Enforce maximum history size + while (history.size() > CONSOLE_HISTORY) { + history.removeFirst(); + } + // Set pointer to end of history + historyPtr = history.size(); + + // Scroll console view to end + scrollToEnd(); } void RPCConsole::browseHistory(int offset) @@ -1154,7 +1187,13 @@ void RPCConsole::startExecutor() executor->moveToThread(&thread); // Replies from executor object must go to this object - connect(executor, &RPCExecutor::reply, this, qOverload(&RPCConsole::message)); + connect(executor, &RPCExecutor::reply, this, [this](int category, const QString& command) { + // Remove "Executing…" message. + ui->messagesWidget->undo(); + message(category, command); + scrollToEnd(); + m_is_executing = false; + }); // Requests from this object must go to executor connect(this, &RPCConsole::cmdRequest, executor, &RPCExecutor::request); @@ -1330,6 +1369,11 @@ void RPCConsole::showEvent(QShowEvent *event) void RPCConsole::hideEvent(QHideEvent *event) { + // It is too late to call QHeaderView::saveState() in ~RPCConsole(), as all of + // the columns of QTableView child widgets will have zero width at that moment. + m_peer_widget_header_state = ui->peerWidget->horizontalHeader()->saveState(); + m_banlist_widget_header_state = ui->banlistWidget->horizontalHeader()->saveState(); + QWidget::hideEvent(event); if (!clientModel || !clientModel->getPeerTableModel()) diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 7cbb36c20f..9fd94324fe 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -12,9 +12,10 @@ #include #include -#include +#include #include #include +#include class ClientModel; class RPCTimerInterface; @@ -189,6 +190,9 @@ private: QCompleter *autoCompleter = nullptr; QThread thread; WalletModel* m_last_wallet_model{nullptr}; + bool m_is_executing{false}; + QByteArray m_peer_widget_header_state; + QByteArray m_banlist_widget_header_state; /** Update UI with latest network info from model. */ void updateNetworkState(); diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp index 547ff05565..872099a1bd 100644 --- a/src/qt/test/apptests.cpp +++ b/src/qt/test/apptests.cpp @@ -41,7 +41,7 @@ void TestRpcCommand(RPCConsole* console) QTest::keyClicks(lineEdit, "getblockchaininfo"); QTest::keyClick(lineEdit, Qt::Key_Return); QVERIFY(mw_spy.wait(1000)); - QCOMPARE(mw_spy.count(), 2); + QCOMPARE(mw_spy.count(), 4); QString output = messagesWidget->toPlainText(); UniValue value; value.read(output.right(output.size() - output.indexOf("{")).toStdString()); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 15140e9645..3a8b297037 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -201,7 +201,7 @@ void TestGUI(interfaces::Node& node) QCOMPARE(uri.count("amount=0.00000001"), 2); QCOMPARE(receiveRequestDialog->QObject::findChild("amount_tag")->text(), QString("Amount:")); - QCOMPARE(receiveRequestDialog->QObject::findChild("amount_content")->text(), QString("0.00000001 ") + BitcoinUnits::name(unit)); + QCOMPARE(receiveRequestDialog->QObject::findChild("amount_content")->text(), QString::fromStdString("0.00000001 ") + BitcoinUnits::name(unit)); QCOMPARE(uri.count("label=TEST_LABEL_1"), 2); QCOMPARE(receiveRequestDialog->QObject::findChild("label_tag")->text(), QString("Label:")); diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 0e89ffe556..2f90ca679f 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -26,6 +26,8 @@ #include #include +#include + QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const interfaces::WalletTxStatus& status, bool inMempool, int numBlocks) { if (!status.is_final) @@ -44,19 +46,20 @@ QString TransactionDesc::FormatTxStatus(const interfaces::WalletTx& wtx, const i bool fChainLocked = status.is_chainlocked; if (nDepth == 0) { - strTxStatus = tr("0/unconfirmed, %1").arg((inMempool ? tr("in memory pool") : tr("not in memory pool"))) + (status.is_abandoned ? ", "+tr("abandoned") : ""); + const QString abandoned{status.is_abandoned ? QLatin1String(", ") + tr("abandoned") : QString()}; + strTxStatus = tr("0/unconfirmed, %1").arg((inMempool ? tr("in memory pool") : tr("not in memory pool"))) + abandoned; } else if (!fChainLocked && nDepth < 6) { strTxStatus = tr("%1/unconfirmed").arg(nDepth); } else { strTxStatus = tr("%1 confirmations").arg(nDepth); if (fChainLocked) { - strTxStatus += ", " + tr("locked via ChainLocks"); + strTxStatus += QLatin1String(", ") + tr("locked via ChainLocks"); return strTxStatus; } } if (status.is_islocked) { - strTxStatus += ", " + tr("verified via InstantSend"); + strTxStatus += QLatin1String(", ") + tr("verified via InstantSend"); } return strTxStatus; diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 20ac423b1c..0a7b0066c3 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include @@ -466,9 +468,9 @@ QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const { QString watchAddress; - if (tooltip) { + if (tooltip && wtx->involvesWatchAddress) { // Mark transactions involving watch-only addresses by adding " (watch-only)" - watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : ""; + watchAddress = QLatin1String(" (") + tr("watch-only") + QLatin1Char(')'); } switch(wtx->type) diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index bc313e2f33..0148003e20 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -7,14 +7,11 @@ #include #include #include -#include -#include #include #include #include #include #include -#include #include #include #include @@ -30,9 +27,8 @@ #include #include -WalletFrame::WalletFrame(BitcoinGUI* _gui) - : QFrame(_gui), - gui(_gui), +WalletFrame::WalletFrame(QWidget* parent) + : QFrame(parent), m_size_hint(OverviewPage{nullptr}.sizeHint()) { // Leave HBox hook for adding a list view later @@ -53,11 +49,7 @@ WalletFrame::WalletFrame(BitcoinGUI* _gui) // A button for create wallet dialog QPushButton* create_wallet_button = new QPushButton(tr("Create a new wallet"), walletStack); - connect(create_wallet_button, &QPushButton::clicked, [this] { - auto activity = new CreateWalletActivity(gui->getWalletController(), this); - connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); - activity->create(); - }); + connect(create_wallet_button, &QPushButton::clicked, this, &WalletFrame::createWalletButtonClicked); no_wallet_layout->addWidget(create_wallet_button, 0, Qt::AlignHCenter | Qt::AlignTop); no_wallet_group->setLayout(no_wallet_layout); @@ -86,17 +78,15 @@ void WalletFrame::setClientModel(ClientModel *_clientModel) } } -bool WalletFrame::addWallet(WalletModel *walletModel) +bool WalletFrame::addWallet(WalletModel* walletModel, WalletView* walletView) { - if (!gui || !clientModel || !walletModel) return false; + if (!clientModel || !walletModel) return false; if (mapWalletViews.count(walletModel) > 0) return false; - WalletView* walletView = new WalletView(this); walletView->setClientModel(clientModel); walletView->setWalletModel(walletModel); walletView->showOutOfSyncWarning(bOutOfSync); - walletView->setPrivacy(gui->isPrivacyModeActivated()); WalletView* current_wallet_view = currentWalletView(); if (current_wallet_view) { @@ -108,17 +98,6 @@ bool WalletFrame::addWallet(WalletModel *walletModel) walletStack->addWidget(walletView); mapWalletViews[walletModel] = walletView; - connect(walletView, &WalletView::outOfSyncWarningClicked, this, &WalletFrame::outOfSyncWarningClicked); - connect(walletView, &WalletView::transactionClicked, gui, &BitcoinGUI::gotoHistoryPage); - connect(walletView, &WalletView::coinsSent, gui, &BitcoinGUI::gotoHistoryPage); - connect(walletView, &WalletView::message, [this](const QString& title, const QString& message, unsigned int style) { - gui->message(title, message, style); - }); - connect(walletView, &WalletView::encryptionStatusChanged, gui, &BitcoinGUI::updateWalletStatus); - connect(walletView, &WalletView::incomingTransaction, gui, &BitcoinGUI::incomingTransaction); - connect(walletView, &WalletView::hdEnabledStatusChanged, gui, &BitcoinGUI::updateWalletStatus); - connect(gui, &BitcoinGUI::setPrivacy, walletView, &WalletView::setPrivacy); - return true; } @@ -364,8 +343,3 @@ WalletModel* WalletFrame::currentWalletModel() const WalletView* wallet_view = currentWalletView(); return wallet_view ? wallet_view->getWalletModel() : nullptr; } - -void WalletFrame::outOfSyncWarningClicked() -{ - Q_EMIT requestedSyncWarningInfo(); -} diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 8764448457..4b76dc40ec 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -9,7 +9,6 @@ #include #include -class BitcoinGUI; class ClientModel; class SendCoinsRecipient; class WalletModel; @@ -33,12 +32,12 @@ class WalletFrame : public QFrame Q_OBJECT public: - explicit WalletFrame(BitcoinGUI* _gui = nullptr); + explicit WalletFrame(QWidget* parent); ~WalletFrame(); void setClientModel(ClientModel *clientModel); - bool addWallet(WalletModel *walletModel); + bool addWallet(WalletModel* walletModel, WalletView* walletView); void setCurrentWallet(WalletModel* wallet_model); void removeWallet(WalletModel* wallet_model); void removeAllWallets(); @@ -51,12 +50,11 @@ public: Q_SIGNALS: void message(const QString& title, const QString& message, unsigned int style); - /** Notify that the user has requested more information about the out-of-sync warning */ - void requestedSyncWarningInfo(); + + void createWalletButtonClicked(); private: QStackedWidget *walletStack; - BitcoinGUI *gui; ClientModel *clientModel; QMap mapWalletViews; QGroupBox* no_wallet_group; @@ -110,8 +108,6 @@ public Q_SLOTS: void usedSendingAddresses(); /** Show used receiving addresses */ void usedReceivingAddresses(); - /** Pass on signal over requested out-of-sync-warning information */ - void outOfSyncWarningClicked(); }; #endif // BITCOIN_QT_WALLETFRAME_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 48605227e7..6869854ced 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -100,7 +100,7 @@ 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, qOverload(&TransactionView::focusTransaction)); - connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo); + connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::outOfSyncWarningClicked); connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); connect(coinJoinCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); @@ -408,11 +408,6 @@ void WalletView::showProgress(const QString &title, int nProgress) } } -void WalletView::requestedSyncWarningInfo() -{ - Q_EMIT outOfSyncWarningClicked(); -} - /** Update wallet with the sum of the selected transactions */ void WalletView::trxAmount(QString amount) { diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 0b3b32dbc1..8886de7ecb 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -122,10 +122,6 @@ public Q_SLOTS: /** Show progress dialog e.g. for rescan */ void showProgress(const QString &title, int nProgress); - /** User has requested more information about the out of sync state */ - void requestedSyncWarningInfo(); - - /** Update selected DASH amount from transactionview */ void trxAmount(QString amount); Q_SIGNALS: diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index bef1c57471..5ecaa2faae 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -115,7 +115,7 @@ bool LoadWallets(interfaces::Chain& chain, interfaces::CoinJoin::Loader& coinjoi if (!database && status == DatabaseStatus::FAILED_NOT_FOUND) { continue; } - chain.initMessage(_("Loading wallet...").translated); + chain.initMessage(_("Loading wallet…").translated); std::shared_ptr pwallet = database ? CWallet::Create(&chain, &coinjoin_loader, name, std::move(database), options.create_flags, error_string, warnings) : nullptr; if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n"))); if (!pwallet) { diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 414eeb16aa..edc7b41c9e 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -238,7 +238,7 @@ std::shared_ptr LoadWalletInternal(interfaces::Chain& chain, interfaces return nullptr; } - chain.initMessage(_("Loading wallet...").translated); + chain.initMessage(_("Loading wallet…").translated); std::shared_ptr wallet = CWallet::Create(&chain, &coinjoin_loader, name, std::move(database), options.create_flags, error, warnings); if (!wallet) { error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error; @@ -304,7 +304,7 @@ std::shared_ptr CreateWallet(interfaces::Chain& chain, interfaces::Coin } // Make the wallet - chain.initMessage(_("Loading wallet...").translated); + chain.initMessage(_("Loading wallet…").translated); std::shared_ptr wallet = CWallet::Create(&chain, &coinjoin_loader, name, std::move(database), wallet_creation_flags, error, warnings); if (!wallet) { error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error; diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 90417d9138..8cb0a9ae7e 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -16,7 +16,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "index/coinstatsindex -> node/coinstats -> index/coinstatsindex" "policy/fees -> txmempool -> policy/fees" "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel" - "qt/bitcoingui -> qt/walletframe -> qt/bitcoingui" "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel" "qt/transactiontablemodel -> qt/walletmodel -> qt/transactiontablemodel" "txmempool -> validation -> txmempool"