diff options
Diffstat (limited to 'src/qt/rpcconsole.cpp')
-rw-r--r-- | src/qt/rpcconsole.cpp | 249 |
1 files changed, 174 insertions, 75 deletions
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 731d6a4eef..56f55363b2 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -34,9 +34,13 @@ #include <wallet/wallet.h> #endif +#include <QAbstractButton> +#include <QAbstractItemModel> #include <QDateTime> #include <QFont> #include <QKeyEvent> +#include <QLatin1String> +#include <QLocale> #include <QMenu> #include <QMessageBox> #include <QScreen> @@ -44,9 +48,10 @@ #include <QSettings> #include <QString> #include <QStringList> +#include <QStyledItemDelegate> #include <QTime> #include <QTimer> - +#include <QVariant> const int CONSOLE_HISTORY = 50; const int INITIAL_TRAFFIC_GRAPH_MINS = 30; @@ -128,6 +133,20 @@ public: } }; +class PeerIdViewDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit PeerIdViewDelegate(QObject* parent = nullptr) + : QStyledItemDelegate(parent) {} + + QString displayText(const QVariant& value, const QLocale& locale) const override + { + // Additional spaces should visually separate right-aligned content + // from the next column to the right. + return value.toString() + QLatin1String(" "); + } +}; #include <qt/rpcconsole.moc> @@ -269,6 +288,7 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes } if (breakParsing) break; + [[fallthrough]]; } case STATE_ARGUMENT: // In or after argument case STATE_EATING_SPACES_IN_ARG: @@ -382,6 +402,7 @@ bool RPCConsole::RPCParseCommandLine(interfaces::Node* node, std::string &strRes strResult = lastResult.get_str(); else strResult = lastResult.write(2); + [[fallthrough]]; case STATE_ARGUMENT: case STATE_EATING_SPACES: return true; @@ -469,6 +490,9 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty 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<QString> CONNECTION_TYPE_DOC{ tr("Inbound: initiated by peer"), @@ -495,17 +519,29 @@ RPCConsole::RPCConsole(interfaces::Node& node, const PlatformStyle *_platformSty ui->openDebugLogfileButton->setIcon(platformStyle->SingleColorIcon(":/icons/export")); } ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove")); + ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(":/icons/fontbigger")); + //: 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+=")); + ui->fontSmallerButton->setIcon(platformStyle->SingleColorIcon(":/icons/fontsmaller")); + //: 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+_")); + + ui->promptIcon->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/prompticon"))); // Install event filter for up and down arrow ui->lineEdit->installEventFilter(this); 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 @@ -542,6 +578,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 ui; @@ -630,23 +669,27 @@ 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)); // create peer table context menu peersTableContextMenu = new QMenu(this); - 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 day"), [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); }); + 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); // 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(), &QAbstractItemModel::dataChanged, [this] { updateDetailWidget(); }); // set up ban table ui->banlistWidget->setModel(model->getBanTableModel()); @@ -654,13 +697,16 @@ 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 banTableContextMenu = new QMenu(this); - banTableContextMenu->addAction(tr("Unban"), this, &RPCConsole::unbanSelectedNode); + banTableContextMenu->addAction(tr("&Unban"), this, &RPCConsole::unbanSelectedNode); connect(ui->banlistWidget, &QTableView::customContextMenuRequested, this, &RPCConsole::showBanTableContextMenu); // ban table signal handling - clear peer details when clicking a peer in the ban table @@ -806,20 +852,29 @@ void RPCConsole::clear(bool keep_prompt) ).arg(fixedFontInfo.family(), QString("%1pt").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); + 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, + "<b>" + ui->clearButton->shortcut().toString(QKeySequence::NativeText) + "</b>", + "<b>" + ui->fontBiggerButton->shortcut().toString(QKeySequence::NativeText) + "</b>", + "<b>" + ui->fontSmallerButton->shortcut().toString(QKeySequence::NativeText) + "</b>", + "<b>help</b>", + "<b>help-console</b>", + "<span class=\"secwarning\">", + "<span>"); + + message(CMD_REPLY, welcome_message, true); } void RPCConsole::keyPressEvent(QKeyEvent *event) @@ -830,6 +885,25 @@ void RPCConsole::keyPressEvent(QKeyEvent *event) } } +void RPCConsole::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::PaletteChange) { + ui->clearButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/remove"))); + ui->fontBiggerButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/fontbigger"))); + ui->fontSmallerButton->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/fontsmaller"))); + ui->promptIcon->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/prompticon"))); + + for (int i = 0; ICON_MAPPING[i].url; ++i) { + ui->messagesWidget->document()->addResource( + QTextDocument::ImageResource, + QUrl(ICON_MAPPING[i].url), + platformStyle->SingleColorImage(ICON_MAPPING[i].source).scaled(QSize(consoleFontSize * 2, consoleFontSize * 2), Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + } + + QWidget::changeEvent(e); +} + void RPCConsole::message(int category, const QString &message, bool html) { QTime time = QTime::currentTime(); @@ -892,57 +966,71 @@ void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage) 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; + } + + if (m_is_executing) { + return; + } - cmdBeforeBrowsing = QString(); + ui->lineEdit->clear(); #ifdef ENABLE_WALLET - WalletModel* wallet_model = ui->WalletSelector->currentData().value<WalletModel*>(); + WalletModel* wallet_model = ui->WalletSelector->currentData().value<WalletModel*>(); - 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(); + m_last_wallet_model = wallet_model; + } +#endif // ENABLE_WALLET - // Scroll console view to end - scrollToEnd(); + 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) @@ -972,7 +1060,13 @@ void RPCConsole::startExecutor() executor->moveToThread(&thread); // Replies from executor object must go to this object - connect(executor, &RPCExecutor::reply, this, qOverload<int, const QString&>(&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); @@ -1036,7 +1130,7 @@ void RPCConsole::updateDetailWidget() const auto stats = selected_peers.first().data(PeerTableModel::StatsRole).value<CNodeCombinedStats*>(); // update the detail ui with latest node information QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) + " "); - peerAddrDetails += tr("(peer id: %1)").arg(QString::number(stats->nodeStats.nodeid)); + peerAddrDetails += tr("(peer: %1)").arg(QString::number(stats->nodeStats.nodeid)); if (!stats->nodeStats.addrLocal.empty()) peerAddrDetails += "<br />" + tr("via %1").arg(QString::fromStdString(stats->nodeStats.addrLocal)); ui->peerHeading->setText(peerAddrDetails); @@ -1047,7 +1141,7 @@ void RPCConsole::updateDetailWidget() if (stats->nodeStats.m_bip152_highbandwidth_from) bip152_hb_settings += (bip152_hb_settings.isEmpty() ? ts.from : QLatin1Char('/') + ts.from); if (bip152_hb_settings.isEmpty()) bip152_hb_settings = ts.no; ui->peerHighBandwidth->setText(bip152_hb_settings); - const int64_t time_now{GetSystemTimeInSeconds()}; + const int64_t time_now{GetTimeSeconds()}; ui->peerConnTime->setText(GUIUtil::formatDurationStr(time_now - stats->nodeStats.nTimeConnected)); ui->peerLastBlock->setText(TimeDurationField(time_now, stats->nodeStats.nLastBlockTime)); ui->peerLastTx->setText(TimeDurationField(time_now, stats->nodeStats.nLastTXTime)); @@ -1062,7 +1156,7 @@ void RPCConsole::updateDetailWidget() ui->peerSubversion->setText(QString::fromStdString(stats->nodeStats.cleanSubVer)); ui->peerConnectionType->setText(GUIUtil::ConnectionTypeToQString(stats->nodeStats.m_conn_type, /* prepend_direction */ true)); ui->peerNetwork->setText(GUIUtil::NetworkToQString(stats->nodeStats.m_network)); - if (stats->nodeStats.m_permissionFlags == PF_NONE) { + if (stats->nodeStats.m_permissionFlags == NetPermissionFlags::None) { ui->peerPermissions->setText(ts.na); } else { QStringList permissions; @@ -1113,6 +1207,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()) |