Page MenuHomePhabricator

No OneTemporary

diff --git a/doc/release-notes.md b/doc/release-notes.md
index d7eb555725..76ee5aad18 100644
--- a/doc/release-notes.md
+++ b/doc/release-notes.md
@@ -1,7 +1,9 @@
Bitcoin ABC version 0.20.5 is now available from:
<https://download.bitcoinabc.org/0.20.5/>
This release includes the following features and fixes:
- Wallets loaded dynamically through the RPC interface may now be displayed in
the bitcoin-qt GUI.
+ - The default wallet will now be labeled `[default wallet]` in the bitcoin-qt
+ GUI if no name is provided by the `-wallet` option on start up.
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 08998de71e..cefe06e518 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -1,1340 +1,1347 @@
// Copyright (c) 2011-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <qt/bitcoingui.h>
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <init.h>
#include <interfaces/handler.h>
#include <interfaces/node.h>
#include <qt/bitcoinunits.h>
#include <qt/clientmodel.h>
#include <qt/guiconstants.h>
#include <qt/guiutil.h>
#ifdef Q_OS_MAC
#include <qt/macdockiconhandler.h>
#endif
#include <qt/modaloverlay.h>
#include <qt/networkstyle.h>
#include <qt/notificator.h>
#include <qt/openuridialog.h>
#include <qt/optionsdialog.h>
#include <qt/optionsmodel.h>
#include <qt/platformstyle.h>
#include <qt/rpcconsole.h>
#include <qt/utilitydialog.h>
#ifdef ENABLE_WALLET
#include <qt/walletframe.h>
#include <qt/walletmodel.h>
#include <qt/walletview.h>
#endif // ENABLE_WALLET
#include <ui_interface.h>
#include <util/system.h>
#include <QAction>
#include <QApplication>
#include <QComboBox>
#include <QDateTime>
#include <QDesktopWidget>
#include <QDragEnterEvent>
#include <QListWidget>
#include <QMenuBar>
#include <QMessageBox>
#include <QMimeData>
#include <QProgressDialog>
#include <QSettings>
#include <QShortcut>
#include <QStackedWidget>
#include <QStatusBar>
#include <QStyle>
#include <QTimer>
#include <QToolBar>
#include <QUrlQuery>
#include <QVBoxLayout>
const std::string BitcoinGUI::DEFAULT_UIPLATFORM =
#if defined(Q_OS_MAC)
"macosx"
#elif defined(Q_OS_WIN)
"windows"
#else
"other"
#endif
;
BitcoinGUI::BitcoinGUI(interfaces::Node &node, const Config *configIn,
const PlatformStyle *_platformStyle,
const NetworkStyle *networkStyle, QWidget *parent)
: QMainWindow(parent), enableWallet(false), m_node(node),
platformStyle(_platformStyle), config(configIn) {
QSettings settings;
if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) {
// Restore failed (perhaps missing setting), center the window
move(QApplication::desktop()->availableGeometry().center() -
frameGeometry().center());
}
QString windowTitle = tr(PACKAGE_NAME) + " - ";
#ifdef ENABLE_WALLET
enableWallet = WalletModel::isWalletEnabled();
#endif // ENABLE_WALLET
if (enableWallet) {
windowTitle += tr("Wallet");
} else {
windowTitle += tr("Node");
}
windowTitle += " " + networkStyle->getTitleAddText();
#ifndef Q_OS_MAC
QApplication::setWindowIcon(networkStyle->getTrayAndWindowIcon());
setWindowIcon(networkStyle->getTrayAndWindowIcon());
#else
MacDockIconHandler::instance()->setIcon(networkStyle->getAppIcon());
#endif
setWindowTitle(windowTitle);
rpcConsole = new RPCConsole(node, _platformStyle, 0);
helpMessageDialog = new HelpMessageDialog(node, this, false);
#ifdef ENABLE_WALLET
if (enableWallet) {
/** Create wallet frame and make it the central widget */
walletFrame = new WalletFrame(_platformStyle, this);
setCentralWidget(walletFrame);
} else
#endif // ENABLE_WALLET
{
/**
* When compiled without wallet or -disablewallet is provided, the
* central widget is the rpc console.
*/
setCentralWidget(rpcConsole);
}
// Accept D&D of URIs
setAcceptDrops(true);
// Create actions for the toolbar, menu bar and tray/dock icon
// Needs walletFrame to be initialized
createActions();
// Create application menu bar
createMenuBar();
// Create the toolbars
createToolBars();
// Create system tray icon and notification
createTrayIcon(networkStyle);
// Create status bar
statusBar();
// Disable size grip because it looks ugly and nobody needs it
statusBar()->setSizeGripEnabled(false);
// Status bar notification icons
QFrame *frameBlocks = new QFrame();
frameBlocks->setContentsMargins(0, 0, 0, 0);
frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks);
frameBlocksLayout->setContentsMargins(3, 0, 3, 0);
frameBlocksLayout->setSpacing(3);
unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle);
labelWalletEncryptionIcon = new QLabel();
labelWalletHDStatusIcon = new QLabel();
connectionsControl = new GUIUtil::ClickableLabel();
labelBlocksIcon = new GUIUtil::ClickableLabel();
if (enableWallet) {
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(unitDisplayControl);
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(labelWalletEncryptionIcon);
frameBlocksLayout->addWidget(labelWalletHDStatusIcon);
}
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(connectionsControl);
frameBlocksLayout->addStretch();
frameBlocksLayout->addWidget(labelBlocksIcon);
frameBlocksLayout->addStretch();
// Progress bar and label for blocks download
progressBarLabel = new QLabel();
progressBarLabel->setVisible(false);
progressBar = new GUIUtil::ProgressBar();
progressBar->setAlignment(Qt::AlignCenter);
progressBar->setVisible(false);
// Override style sheet for progress bar for styles that have a segmented
// progress bar, as they make the text unreadable (workaround for issue
// #1071)
// See https://doc.qt.io/qt-5/gallery.html
QString curStyle = QApplication::style()->metaObject()->className();
if (curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle") {
progressBar->setStyleSheet(
"QProgressBar { background-color: #e8e8e8; border: 1px solid grey; "
"border-radius: 7px; padding: 1px; text-align: center; } "
"QProgressBar::chunk { background: QLinearGradient(x1: 0, y1: 0, "
"x2: 1, y2: 0, stop: 0 #FF8000, stop: 1 orange); border-radius: "
"7px; margin: 0px; }");
}
statusBar()->addWidget(progressBarLabel);
statusBar()->addWidget(progressBar);
statusBar()->addPermanentWidget(frameBlocks);
// Install event filter to be able to catch status tip events
// (QEvent::StatusTip)
this->installEventFilter(this);
// Initially wallet actions should be disabled
setWalletActionsEnabled(false);
// Subscribe to notifications from core
subscribeToCoreSignals();
connect(connectionsControl, SIGNAL(clicked(QPoint)), this,
SLOT(toggleNetworkActive()));
modalOverlay = new ModalOverlay(this->centralWidget());
#ifdef ENABLE_WALLET
if (enableWallet) {
connect(walletFrame, SIGNAL(requestedSyncWarningInfo()), this,
SLOT(showModalOverlay()));
connect(labelBlocksIcon, SIGNAL(clicked(QPoint)), this,
SLOT(showModalOverlay()));
connect(progressBar, SIGNAL(clicked(QPoint)), this,
SLOT(showModalOverlay()));
}
#endif
}
BitcoinGUI::~BitcoinGUI() {
// Unsubscribe from notifications from core
unsubscribeFromCoreSignals();
QSettings settings;
settings.setValue("MainWindowGeometry", saveGeometry());
// Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
if (trayIcon) {
trayIcon->hide();
}
#ifdef Q_OS_MAC
delete appMenuBar;
MacDockIconHandler::cleanup();
#endif
delete rpcConsole;
}
void BitcoinGUI::createActions() {
QActionGroup *tabGroup = new QActionGroup(this);
overviewAction =
new QAction(platformStyle->SingleColorIcon(":/icons/overview"),
tr("&Overview"), this);
overviewAction->setStatusTip(tr("Show general overview of wallet"));
overviewAction->setToolTip(overviewAction->statusTip());
overviewAction->setCheckable(true);
overviewAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_1));
tabGroup->addAction(overviewAction);
sendCoinsAction = new QAction(
platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this);
sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address"));
sendCoinsAction->setToolTip(sendCoinsAction->statusTip());
sendCoinsAction->setCheckable(true);
sendCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_2));
tabGroup->addAction(sendCoinsAction);
sendCoinsMenuAction =
new QAction(platformStyle->TextColorIcon(":/icons/send"),
sendCoinsAction->text(), this);
sendCoinsMenuAction->setStatusTip(sendCoinsAction->statusTip());
sendCoinsMenuAction->setToolTip(sendCoinsMenuAction->statusTip());
receiveCoinsAction = new QAction(
platformStyle->SingleColorIcon(":/icons/receiving_addresses"),
tr("&Receive"), this);
receiveCoinsAction->setStatusTip(
tr("Request payments (generates QR codes and %1: URIs)")
.arg(QString::fromStdString(
config->GetChainParams().CashAddrPrefix())));
receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
receiveCoinsAction->setCheckable(true);
receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
tabGroup->addAction(receiveCoinsAction);
receiveCoinsMenuAction =
new QAction(platformStyle->TextColorIcon(":/icons/receiving_addresses"),
receiveCoinsAction->text(), this);
receiveCoinsMenuAction->setStatusTip(receiveCoinsAction->statusTip());
receiveCoinsMenuAction->setToolTip(receiveCoinsMenuAction->statusTip());
historyAction =
new QAction(platformStyle->SingleColorIcon(":/icons/history"),
tr("&Transactions"), this);
historyAction->setStatusTip(tr("Browse transaction history"));
historyAction->setToolTip(historyAction->statusTip());
historyAction->setCheckable(true);
historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4));
tabGroup->addAction(historyAction);
#ifdef ENABLE_WALLET
// These showNormalIfMinimized are needed because Send Coins and Receive
// Coins can be triggered from the tray menu, and need to show the GUI to be
// useful.
connect(overviewAction, SIGNAL(triggered()), this,
SLOT(showNormalIfMinimized()));
connect(overviewAction, SIGNAL(triggered()), this,
SLOT(gotoOverviewPage()));
connect(sendCoinsAction, SIGNAL(triggered()), this,
SLOT(showNormalIfMinimized()));
connect(sendCoinsAction, SIGNAL(triggered()), this,
SLOT(gotoSendCoinsPage()));
connect(sendCoinsMenuAction, SIGNAL(triggered()), this,
SLOT(showNormalIfMinimized()));
connect(sendCoinsMenuAction, SIGNAL(triggered()), this,
SLOT(gotoSendCoinsPage()));
connect(receiveCoinsAction, SIGNAL(triggered()), this,
SLOT(showNormalIfMinimized()));
connect(receiveCoinsAction, SIGNAL(triggered()), this,
SLOT(gotoReceiveCoinsPage()));
connect(receiveCoinsMenuAction, SIGNAL(triggered()), this,
SLOT(showNormalIfMinimized()));
connect(receiveCoinsMenuAction, SIGNAL(triggered()), this,
SLOT(gotoReceiveCoinsPage()));
connect(historyAction, SIGNAL(triggered()), this,
SLOT(showNormalIfMinimized()));
connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage()));
#endif // ENABLE_WALLET
quitAction = new QAction(platformStyle->TextColorIcon(":/icons/quit"),
tr("E&xit"), this);
quitAction->setStatusTip(tr("Quit application"));
quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
quitAction->setMenuRole(QAction::QuitRole);
aboutAction = new QAction(platformStyle->TextColorIcon(":/icons/about"),
tr("&About %1").arg(tr(PACKAGE_NAME)), this);
aboutAction->setStatusTip(
tr("Show information about %1").arg(tr(PACKAGE_NAME)));
aboutAction->setMenuRole(QAction::AboutRole);
aboutAction->setEnabled(false);
aboutQtAction =
new QAction(platformStyle->TextColorIcon(":/icons/about_qt"),
tr("About &Qt"), this);
aboutQtAction->setStatusTip(tr("Show information about Qt"));
aboutQtAction->setMenuRole(QAction::AboutQtRole);
optionsAction = new QAction(platformStyle->TextColorIcon(":/icons/options"),
tr("&Options..."), this);
optionsAction->setStatusTip(
tr("Modify configuration options for %1").arg(tr(PACKAGE_NAME)));
optionsAction->setMenuRole(QAction::PreferencesRole);
optionsAction->setEnabled(false);
toggleHideAction =
new QAction(platformStyle->TextColorIcon(":/icons/about"),
tr("&Show / Hide"), this);
toggleHideAction->setStatusTip(tr("Show or hide the main Window"));
encryptWalletAction =
new QAction(platformStyle->TextColorIcon(":/icons/lock_closed"),
tr("&Encrypt Wallet..."), this);
encryptWalletAction->setStatusTip(
tr("Encrypt the private keys that belong to your wallet"));
encryptWalletAction->setCheckable(true);
backupWalletAction =
new QAction(platformStyle->TextColorIcon(":/icons/filesave"),
tr("&Backup Wallet..."), this);
backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
changePassphraseAction =
new QAction(platformStyle->TextColorIcon(":/icons/key"),
tr("&Change Passphrase..."), this);
changePassphraseAction->setStatusTip(
tr("Change the passphrase used for wallet encryption"));
signMessageAction =
new QAction(platformStyle->TextColorIcon(":/icons/edit"),
tr("Sign &message..."), this);
signMessageAction->setStatusTip(
tr("Sign messages with your Bitcoin addresses to prove you own them"));
verifyMessageAction =
new QAction(platformStyle->TextColorIcon(":/icons/verify"),
tr("&Verify message..."), this);
verifyMessageAction->setStatusTip(
tr("Verify messages to ensure they were signed with specified Bitcoin "
"addresses"));
openRPCConsoleAction =
new QAction(platformStyle->TextColorIcon(":/icons/debugwindow"),
tr("&Debug window"), this);
openRPCConsoleAction->setStatusTip(
tr("Open debugging and diagnostic console"));
// initially disable the debug window menu item
openRPCConsoleAction->setEnabled(false);
usedSendingAddressesAction =
new QAction(platformStyle->TextColorIcon(":/icons/address-book"),
tr("&Sending addresses..."), this);
usedSendingAddressesAction->setStatusTip(
tr("Show the list of used sending addresses and labels"));
usedReceivingAddressesAction =
new QAction(platformStyle->TextColorIcon(":/icons/address-book"),
tr("&Receiving addresses..."), this);
usedReceivingAddressesAction->setStatusTip(
tr("Show the list of used receiving addresses and labels"));
openAction = new QAction(platformStyle->TextColorIcon(":/icons/open"),
tr("Open &URI..."), this);
openAction->setStatusTip(
tr("Open a %1: URI or payment request")
.arg(QString::fromStdString(
config->GetChainParams().CashAddrPrefix())));
showHelpMessageAction =
new QAction(platformStyle->TextColorIcon(":/icons/info"),
tr("&Command-line options"), this);
showHelpMessageAction->setMenuRole(QAction::NoRole);
showHelpMessageAction->setStatusTip(
tr("Show the %1 help message to get a list with possible Bitcoin "
"command-line options")
.arg(tr(PACKAGE_NAME)));
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
connect(aboutAction, SIGNAL(triggered()), this, SLOT(aboutClicked()));
connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked()));
connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHidden()));
connect(showHelpMessageAction, SIGNAL(triggered()), this,
SLOT(showHelpMessageClicked()));
connect(openRPCConsoleAction, SIGNAL(triggered()), this,
SLOT(showDebugWindow()));
// prevents an open debug window from becoming stuck/unusable on client
// shutdown
connect(quitAction, SIGNAL(triggered()), rpcConsole, SLOT(hide()));
#ifdef ENABLE_WALLET
if (walletFrame) {
connect(encryptWalletAction, SIGNAL(triggered(bool)), walletFrame,
SLOT(encryptWallet(bool)));
connect(backupWalletAction, SIGNAL(triggered()), walletFrame,
SLOT(backupWallet()));
connect(changePassphraseAction, SIGNAL(triggered()), walletFrame,
SLOT(changePassphrase()));
connect(signMessageAction, SIGNAL(triggered()), this,
SLOT(gotoSignMessageTab()));
connect(verifyMessageAction, SIGNAL(triggered()), this,
SLOT(gotoVerifyMessageTab()));
connect(usedSendingAddressesAction, SIGNAL(triggered()), walletFrame,
SLOT(usedSendingAddresses()));
connect(usedReceivingAddressesAction, SIGNAL(triggered()), walletFrame,
SLOT(usedReceivingAddresses()));
connect(openAction, SIGNAL(triggered()), this, SLOT(openClicked()));
}
#endif // ENABLE_WALLET
new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this,
SLOT(showDebugWindowActivateConsole()));
new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this,
SLOT(showDebugWindow()));
}
void BitcoinGUI::createMenuBar() {
#ifdef Q_OS_MAC
// Create a decoupled menu bar on Mac which stays even if the window is
// closed
appMenuBar = new QMenuBar();
#else
// Get the main window's menu bar on other platforms
appMenuBar = menuBar();
#endif
// Configure the menus
QMenu *file = appMenuBar->addMenu(tr("&File"));
if (walletFrame) {
file->addAction(openAction);
file->addAction(backupWalletAction);
file->addAction(signMessageAction);
file->addAction(verifyMessageAction);
file->addSeparator();
file->addAction(usedSendingAddressesAction);
file->addAction(usedReceivingAddressesAction);
file->addSeparator();
}
file->addAction(quitAction);
QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
if (walletFrame) {
settings->addAction(encryptWalletAction);
settings->addAction(changePassphraseAction);
settings->addSeparator();
}
settings->addAction(optionsAction);
QMenu *help = appMenuBar->addMenu(tr("&Help"));
if (walletFrame) {
help->addAction(openRPCConsoleAction);
}
help->addAction(showHelpMessageAction);
help->addSeparator();
help->addAction(aboutAction);
help->addAction(aboutQtAction);
}
void BitcoinGUI::createToolBars() {
if (walletFrame) {
QToolBar *toolbar = addToolBar(tr("Tabs toolbar"));
appToolBar = toolbar;
toolbar->setContextMenuPolicy(Qt::PreventContextMenu);
toolbar->setMovable(false);
toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolbar->addAction(overviewAction);
toolbar->addAction(sendCoinsAction);
toolbar->addAction(receiveCoinsAction);
toolbar->addAction(historyAction);
overviewAction->setChecked(true);
#ifdef ENABLE_WALLET
QWidget *spacer = new QWidget();
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
toolbar->addWidget(spacer);
m_wallet_selector = new QComboBox();
- connect(m_wallet_selector, SIGNAL(currentIndexChanged(const QString &)),
- this, SLOT(setCurrentWallet(const QString &)));
+ connect(m_wallet_selector, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(setCurrentWalletBySelectorIndex(int)));
#endif
}
}
void BitcoinGUI::setClientModel(ClientModel *_clientModel) {
this->clientModel = _clientModel;
if (_clientModel) {
// Create system tray menu (or setup the dock menu) that late to prevent
// users from calling actions, while the client has not yet fully loaded
createTrayIconMenu();
// Keep up to date with client
updateNetworkState();
connect(_clientModel, SIGNAL(numConnectionsChanged(int)), this,
SLOT(setNumConnections(int)));
connect(_clientModel, SIGNAL(networkActiveChanged(bool)), this,
SLOT(setNetworkActive(bool)));
modalOverlay->setKnownBestHeight(
_clientModel->getHeaderTipHeight(),
QDateTime::fromTime_t(_clientModel->getHeaderTipTime()));
setNumBlocks(m_node.getNumBlocks(),
QDateTime::fromTime_t(m_node.getLastBlockTime()),
m_node.getVerificationProgress(), false);
connect(_clientModel,
SIGNAL(numBlocksChanged(int, QDateTime, double, bool)), this,
SLOT(setNumBlocks(int, QDateTime, double, bool)));
// Receive and report messages from client model
connect(_clientModel, SIGNAL(message(QString, QString, unsigned int)),
this, SLOT(message(QString, QString, unsigned int)));
// Show progress dialog
connect(_clientModel, SIGNAL(showProgress(QString, int)), this,
SLOT(showProgress(QString, int)));
rpcConsole->setClientModel(_clientModel);
#ifdef ENABLE_WALLET
if (walletFrame) {
walletFrame->setClientModel(_clientModel);
}
#endif // ENABLE_WALLET
unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel());
OptionsModel *optionsModel = _clientModel->getOptionsModel();
if (optionsModel) {
// be aware of the tray icon disable state change reported by the
// OptionsModel object.
connect(optionsModel, SIGNAL(hideTrayIconChanged(bool)), this,
SLOT(setTrayIconVisible(bool)));
// initialize the disable state of the tray icon with the current
// value in the model.
setTrayIconVisible(optionsModel->getHideTrayIcon());
}
} else {
// Disable possibility to show main window via action
toggleHideAction->setEnabled(false);
if (trayIconMenu) {
// Disable context menu on tray icon
trayIconMenu->clear();
}
// Propagate cleared model to child objects
rpcConsole->setClientModel(nullptr);
#ifdef ENABLE_WALLET
if (walletFrame) {
walletFrame->setClientModel(nullptr);
}
#endif // ENABLE_WALLET
unitDisplayControl->setOptionsModel(nullptr);
}
}
#ifdef ENABLE_WALLET
bool BitcoinGUI::addWallet(WalletModel *walletModel) {
if (!walletFrame) return false;
const QString name = walletModel->getWalletName();
+ QString display_name =
+ name.isEmpty() ? "[" + tr("default wallet") + "]" : name;
setWalletActionsEnabled(true);
- m_wallet_selector->addItem(name);
+ m_wallet_selector->addItem(display_name, name);
if (m_wallet_selector->count() == 2) {
m_wallet_selector_label = new QLabel();
m_wallet_selector_label->setText(tr("Wallet:") + " ");
m_wallet_selector_label->setBuddy(m_wallet_selector);
appToolBar->addWidget(m_wallet_selector_label);
appToolBar->addWidget(m_wallet_selector);
}
rpcConsole->addWallet(walletModel);
return walletFrame->addWallet(walletModel);
}
bool BitcoinGUI::setCurrentWallet(const QString &name) {
if (!walletFrame) return false;
return walletFrame->setCurrentWallet(name);
}
+bool BitcoinGUI::setCurrentWalletBySelectorIndex(int index) {
+ QString internal_name = m_wallet_selector->itemData(index).toString();
+ return setCurrentWallet(internal_name);
+}
+
void BitcoinGUI::removeAllWallets() {
if (!walletFrame) return;
setWalletActionsEnabled(false);
walletFrame->removeAllWallets();
}
#endif // ENABLE_WALLET
void BitcoinGUI::setWalletActionsEnabled(bool enabled) {
overviewAction->setEnabled(enabled);
sendCoinsAction->setEnabled(enabled);
sendCoinsMenuAction->setEnabled(enabled);
receiveCoinsAction->setEnabled(enabled);
receiveCoinsMenuAction->setEnabled(enabled);
historyAction->setEnabled(enabled);
encryptWalletAction->setEnabled(enabled);
backupWalletAction->setEnabled(enabled);
changePassphraseAction->setEnabled(enabled);
signMessageAction->setEnabled(enabled);
verifyMessageAction->setEnabled(enabled);
usedSendingAddressesAction->setEnabled(enabled);
usedReceivingAddressesAction->setEnabled(enabled);
openAction->setEnabled(enabled);
}
void BitcoinGUI::createTrayIcon(const NetworkStyle *networkStyle) {
#ifndef Q_OS_MAC
trayIcon = new QSystemTrayIcon(this);
QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " +
networkStyle->getTitleAddText();
trayIcon->setToolTip(toolTip);
trayIcon->setIcon(networkStyle->getTrayAndWindowIcon());
trayIcon->hide();
#endif
notificator =
new Notificator(QApplication::applicationName(), trayIcon, this);
}
void BitcoinGUI::createTrayIconMenu() {
#ifndef Q_OS_MAC
// return if trayIcon is unset (only on non-Mac OSes)
if (!trayIcon) return;
trayIconMenu = new QMenu(this);
trayIcon->setContextMenu(trayIconMenu);
connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this, SLOT(trayIconActivated(QSystemTrayIcon::ActivationReason)));
#else
// Note: On Mac, the dock icon is used to provide the tray's functionality.
MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
dockIconHandler->setMainWindow(static_cast<QMainWindow *>(this));
trayIconMenu = dockIconHandler->dockMenu();
#endif
// Configuration of the tray icon (or dock icon) icon menu
trayIconMenu->addAction(toggleHideAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(sendCoinsMenuAction);
trayIconMenu->addAction(receiveCoinsMenuAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(signMessageAction);
trayIconMenu->addAction(verifyMessageAction);
trayIconMenu->addSeparator();
trayIconMenu->addAction(optionsAction);
trayIconMenu->addAction(openRPCConsoleAction);
#ifndef Q_OS_MAC // This is built-in on Mac
trayIconMenu->addSeparator();
trayIconMenu->addAction(quitAction);
#endif
}
#ifndef Q_OS_MAC
void BitcoinGUI::trayIconActivated(QSystemTrayIcon::ActivationReason reason) {
if (reason == QSystemTrayIcon::Trigger) {
// Click on system tray icon triggers show/hide of the main window
toggleHidden();
}
}
#endif
void BitcoinGUI::optionsClicked() {
if (!clientModel || !clientModel->getOptionsModel()) return;
OptionsDialog dlg(this, enableWallet);
dlg.setModel(clientModel->getOptionsModel());
dlg.exec();
}
void BitcoinGUI::aboutClicked() {
if (!clientModel) return;
HelpMessageDialog dlg(m_node, this, true);
dlg.exec();
}
void BitcoinGUI::showDebugWindow() {
rpcConsole->showNormal();
rpcConsole->show();
rpcConsole->raise();
rpcConsole->activateWindow();
}
void BitcoinGUI::showDebugWindowActivateConsole() {
rpcConsole->setTabFocus(RPCConsole::TAB_CONSOLE);
showDebugWindow();
}
void BitcoinGUI::showHelpMessageClicked() {
helpMessageDialog->show();
}
#ifdef ENABLE_WALLET
void BitcoinGUI::openClicked() {
OpenURIDialog dlg(config->GetChainParams(), this);
if (dlg.exec()) {
Q_EMIT receivedURI(dlg.getURI());
}
}
void BitcoinGUI::gotoOverviewPage() {
overviewAction->setChecked(true);
if (walletFrame) walletFrame->gotoOverviewPage();
}
void BitcoinGUI::gotoHistoryPage() {
historyAction->setChecked(true);
if (walletFrame) walletFrame->gotoHistoryPage();
}
void BitcoinGUI::gotoReceiveCoinsPage() {
receiveCoinsAction->setChecked(true);
if (walletFrame) walletFrame->gotoReceiveCoinsPage();
}
void BitcoinGUI::gotoSendCoinsPage(QString addr) {
sendCoinsAction->setChecked(true);
if (walletFrame) walletFrame->gotoSendCoinsPage(addr);
}
void BitcoinGUI::gotoSignMessageTab(QString addr) {
if (walletFrame) walletFrame->gotoSignMessageTab(addr);
}
void BitcoinGUI::gotoVerifyMessageTab(QString addr) {
if (walletFrame) walletFrame->gotoVerifyMessageTab(addr);
}
#endif // ENABLE_WALLET
void BitcoinGUI::updateNetworkState() {
int count = clientModel->getNumConnections();
QString icon;
switch (count) {
case 0:
icon = ":/icons/connect_0";
break;
case 1:
case 2:
case 3:
icon = ":/icons/connect_1";
break;
case 4:
case 5:
case 6:
icon = ":/icons/connect_2";
break;
case 7:
case 8:
case 9:
icon = ":/icons/connect_3";
break;
default:
icon = ":/icons/connect_4";
break;
}
QString tooltip;
if (m_node.getNetworkActive()) {
tooltip = tr("%n active connection(s) to Bitcoin network", "", count) +
QString(".<br>") + tr("Click to disable network activity.");
} else {
tooltip = tr("Network activity disabled.") + QString("<br>") +
tr("Click to enable network activity again.");
icon = ":/icons/network_disabled";
}
// Don't word-wrap this (fixed-width) tooltip
tooltip = QString("<nobr>") + tooltip + QString("</nobr>");
connectionsControl->setToolTip(tooltip);
connectionsControl->setPixmap(platformStyle->SingleColorIcon(icon).pixmap(
STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
}
void BitcoinGUI::setNumConnections(int count) {
updateNetworkState();
}
void BitcoinGUI::setNetworkActive(bool networkActive) {
updateNetworkState();
}
void BitcoinGUI::updateHeadersSyncProgressLabel() {
int64_t headersTipTime = clientModel->getHeaderTipTime();
int headersTipHeight = clientModel->getHeaderTipHeight();
int estHeadersLeft =
(GetTime() - headersTipTime) /
config->GetChainParams().GetConsensus().nPowTargetSpacing;
if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC) {
progressBarLabel->setText(
tr("Syncing Headers (%1%)...")
.arg(QString::number(100.0 /
(headersTipHeight + estHeadersLeft) *
headersTipHeight,
'f', 1)));
}
}
void BitcoinGUI::setNumBlocks(int count, const QDateTime &blockDate,
double nVerificationProgress, bool header) {
if (modalOverlay) {
if (header) {
modalOverlay->setKnownBestHeight(count, blockDate);
} else {
modalOverlay->tipUpdate(count, blockDate, nVerificationProgress);
}
}
if (!clientModel) {
return;
}
// Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait
// until chain-sync starts -> garbled text)
statusBar()->clearMessage();
// Acquire current block source
enum BlockSource blockSource = clientModel->getBlockSource();
switch (blockSource) {
case BlockSource::NETWORK:
if (header) {
updateHeadersSyncProgressLabel();
return;
}
progressBarLabel->setText(tr("Synchronizing with network..."));
updateHeadersSyncProgressLabel();
break;
case BlockSource::DISK:
if (header) {
progressBarLabel->setText(tr("Indexing blocks on disk..."));
} else {
progressBarLabel->setText(tr("Processing blocks on disk..."));
}
break;
case BlockSource::REINDEX:
progressBarLabel->setText(tr("Reindexing blocks on disk..."));
break;
case BlockSource::NONE:
if (header) {
return;
}
progressBarLabel->setText(tr("Connecting to peers..."));
break;
}
QString tooltip;
QDateTime currentDate = QDateTime::currentDateTime();
qint64 secs = blockDate.secsTo(currentDate);
tooltip = tr("Processed %n block(s) of transaction history.", "", count);
// Set icon state: spinning if catching up, tick otherwise
if (secs < MAX_BLOCK_TIME_GAP) {
tooltip = tr("Up to date") + QString(".<br>") + tooltip;
labelBlocksIcon->setPixmap(
platformStyle->SingleColorIcon(":/icons/synced")
.pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
#ifdef ENABLE_WALLET
if (walletFrame) {
walletFrame->showOutOfSyncWarning(false);
modalOverlay->showHide(true, true);
}
#endif // ENABLE_WALLET
progressBarLabel->setVisible(false);
progressBar->setVisible(false);
} else {
QString timeBehindText = GUIUtil::formatNiceTimeOffset(secs);
progressBarLabel->setVisible(true);
progressBar->setFormat(tr("%1 behind").arg(timeBehindText));
progressBar->setMaximum(1000000000);
progressBar->setValue(nVerificationProgress * 1000000000.0 + 0.5);
progressBar->setVisible(true);
tooltip = tr("Catching up...") + QString("<br>") + tooltip;
if (count != prevBlocks) {
labelBlocksIcon->setPixmap(
platformStyle
->SingleColorIcon(QString(":/movies/spinner-%1")
.arg(spinnerFrame, 3, 10, QChar('0')))
.pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES;
}
prevBlocks = count;
#ifdef ENABLE_WALLET
if (walletFrame) {
walletFrame->showOutOfSyncWarning(true);
modalOverlay->showHide();
}
#endif // ENABLE_WALLET
tooltip += QString("<br>");
tooltip +=
tr("Last received block was generated %1 ago.").arg(timeBehindText);
tooltip += QString("<br>");
tooltip += tr("Transactions after this will not yet be visible.");
}
// Don't word-wrap this (fixed-width) tooltip
tooltip = QString("<nobr>") + tooltip + QString("</nobr>");
labelBlocksIcon->setToolTip(tooltip);
progressBarLabel->setToolTip(tooltip);
progressBar->setToolTip(tooltip);
}
void BitcoinGUI::message(const QString &title, const QString &message,
unsigned int style, bool *ret) {
// default title
QString strTitle = tr("Bitcoin");
// Default to information icon
int nMBoxIcon = QMessageBox::Information;
int nNotifyIcon = Notificator::Information;
QString msgType;
// Prefer supplied title over style based title
if (!title.isEmpty()) {
msgType = title;
} else {
switch (style) {
case CClientUIInterface::MSG_ERROR:
msgType = tr("Error");
break;
case CClientUIInterface::MSG_WARNING:
msgType = tr("Warning");
break;
case CClientUIInterface::MSG_INFORMATION:
msgType = tr("Information");
break;
default:
break;
}
}
// Append title to "Bitcoin - "
if (!msgType.isEmpty()) {
strTitle += " - " + msgType;
}
// Check for error/warning icon
if (style & CClientUIInterface::ICON_ERROR) {
nMBoxIcon = QMessageBox::Critical;
nNotifyIcon = Notificator::Critical;
} else if (style & CClientUIInterface::ICON_WARNING) {
nMBoxIcon = QMessageBox::Warning;
nNotifyIcon = Notificator::Warning;
}
// Display message
if (style & CClientUIInterface::MODAL) {
// Check for buttons, use OK as default, if none was supplied
QMessageBox::StandardButton buttons;
if (!(buttons = (QMessageBox::StandardButton)(
style & CClientUIInterface::BTN_MASK)))
buttons = QMessageBox::Ok;
showNormalIfMinimized();
QMessageBox mBox(static_cast<QMessageBox::Icon>(nMBoxIcon), strTitle,
message, buttons, this);
int r = mBox.exec();
if (ret != nullptr) {
*ret = r == QMessageBox::Ok;
}
} else
notificator->notify(static_cast<Notificator::Class>(nNotifyIcon),
strTitle, message);
}
void BitcoinGUI::changeEvent(QEvent *e) {
QMainWindow::changeEvent(e);
#ifndef Q_OS_MAC // Ignored on Mac
if (e->type() == QEvent::WindowStateChange) {
if (clientModel && clientModel->getOptionsModel() &&
clientModel->getOptionsModel()->getMinimizeToTray()) {
QWindowStateChangeEvent *wsevt =
static_cast<QWindowStateChangeEvent *>(e);
if (!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized()) {
QTimer::singleShot(0, this, SLOT(hide()));
e->ignore();
} else if ((wsevt->oldState() & Qt::WindowMinimized) &&
!isMinimized()) {
QTimer::singleShot(0, this, SLOT(show()));
e->ignore();
}
}
}
#endif
}
void BitcoinGUI::closeEvent(QCloseEvent *event) {
#ifndef Q_OS_MAC // Ignored on Mac
if (clientModel && clientModel->getOptionsModel()) {
if (!clientModel->getOptionsModel()->getMinimizeOnClose()) {
// close rpcConsole in case it was open to make some space for the
// shutdown window
rpcConsole->close();
QApplication::quit();
} else {
QMainWindow::showMinimized();
event->ignore();
}
}
#else
QMainWindow::closeEvent(event);
#endif
}
void BitcoinGUI::showEvent(QShowEvent *event) {
// enable the debug window when the main window shows up
openRPCConsoleAction->setEnabled(true);
aboutAction->setEnabled(true);
optionsAction->setEnabled(true);
}
#ifdef ENABLE_WALLET
void BitcoinGUI::incomingTransaction(const QString &date, int unit,
const Amount amount, const QString &type,
const QString &address,
const QString &label,
const QString &walletName) {
// On new transaction, make an info balloon
QString msg = tr("Date: %1\n").arg(date) +
tr("Amount: %1\n")
.arg(BitcoinUnits::formatWithUnit(unit, amount, true));
if (m_node.getWallets().size() > 1 && !walletName.isEmpty()) {
msg += tr("Wallet: %1\n").arg(walletName);
}
msg += tr("Type: %1\n").arg(type);
if (!label.isEmpty()) {
msg += tr("Label: %1\n").arg(label);
} else if (!address.isEmpty()) {
msg += tr("Address: %1\n").arg(address);
}
message(amount < Amount::zero() ? tr("Sent transaction")
: tr("Incoming transaction"),
msg, CClientUIInterface::MSG_INFORMATION);
}
#endif // ENABLE_WALLET
void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event) {
// Accept only URIs
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void BitcoinGUI::dropEvent(QDropEvent *event) {
if (event->mimeData()->hasUrls()) {
for (const QUrl &uri : event->mimeData()->urls()) {
Q_EMIT receivedURI(uri.toString());
}
}
event->acceptProposedAction();
}
bool BitcoinGUI::eventFilter(QObject *object, QEvent *event) {
// Catch status tip events
if (event->type() == QEvent::StatusTip) {
// Prevent adding text from setStatusTip(), if we currently use the
// status bar for displaying other stuff
if (progressBarLabel->isVisible() || progressBar->isVisible()) {
return true;
}
}
return QMainWindow::eventFilter(object, event);
}
#ifdef ENABLE_WALLET
bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient &recipient) {
// URI has to be valid
if (walletFrame && walletFrame->handlePaymentRequest(recipient)) {
showNormalIfMinimized();
gotoSendCoinsPage();
return true;
}
return false;
}
void BitcoinGUI::setHDStatus(int hdEnabled) {
labelWalletHDStatusIcon->setPixmap(
platformStyle
->SingleColorIcon(hdEnabled ? ":/icons/hd_enabled"
: ":/icons/hd_disabled")
.pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
labelWalletHDStatusIcon->setToolTip(
hdEnabled ? tr("HD key generation is <b>enabled</b>")
: tr("HD key generation is <b>disabled</b>"));
// eventually disable the QLabel to set its opacity to 50%
labelWalletHDStatusIcon->setEnabled(hdEnabled);
}
void BitcoinGUI::setEncryptionStatus(int status) {
switch (status) {
case WalletModel::Unencrypted:
labelWalletEncryptionIcon->hide();
encryptWalletAction->setChecked(false);
changePassphraseAction->setEnabled(false);
encryptWalletAction->setEnabled(true);
break;
case WalletModel::Unlocked:
labelWalletEncryptionIcon->show();
labelWalletEncryptionIcon->setPixmap(
platformStyle->SingleColorIcon(":/icons/lock_open")
.pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
labelWalletEncryptionIcon->setToolTip(
tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>"));
encryptWalletAction->setChecked(true);
changePassphraseAction->setEnabled(true);
encryptWalletAction->setEnabled(
false); // TODO: decrypt currently not supported
break;
case WalletModel::Locked:
labelWalletEncryptionIcon->show();
labelWalletEncryptionIcon->setPixmap(
platformStyle->SingleColorIcon(":/icons/lock_closed")
.pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
labelWalletEncryptionIcon->setToolTip(
tr("Wallet is <b>encrypted</b> and currently <b>locked</b>"));
encryptWalletAction->setChecked(true);
changePassphraseAction->setEnabled(true);
encryptWalletAction->setEnabled(
false); // TODO: decrypt currently not supported
break;
}
}
void BitcoinGUI::updateWalletStatus() {
if (!walletFrame) {
return;
}
WalletView *const walletView = walletFrame->currentWalletView();
if (!walletView) {
return;
}
WalletModel *const walletModel = walletView->getWalletModel();
setEncryptionStatus(walletModel->getEncryptionStatus());
setHDStatus(walletModel->wallet().hdEnabled());
}
#endif // ENABLE_WALLET
void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden) {
if (!clientModel) {
return;
}
// activateWindow() (sometimes) helps with keyboard focus on Windows
if (isHidden()) {
show();
activateWindow();
} else if (isMinimized()) {
showNormal();
activateWindow();
} else if (GUIUtil::isObscured(this)) {
raise();
activateWindow();
} else if (fToggleHidden) {
hide();
}
}
void BitcoinGUI::toggleHidden() {
showNormalIfMinimized(true);
}
void BitcoinGUI::detectShutdown() {
if (m_node.shutdownRequested()) {
if (rpcConsole) {
rpcConsole->hide();
}
qApp->quit();
}
}
void BitcoinGUI::showProgress(const QString &title, int nProgress) {
if (nProgress == 0) {
progressDialog = new QProgressDialog(title, "", 0, 100);
progressDialog->setWindowModality(Qt::ApplicationModal);
progressDialog->setMinimumDuration(0);
progressDialog->setCancelButton(0);
progressDialog->setAutoClose(false);
progressDialog->setValue(0);
} else if (progressDialog) {
if (nProgress == 100) {
progressDialog->close();
progressDialog->deleteLater();
} else {
progressDialog->setValue(nProgress);
}
}
}
void BitcoinGUI::setTrayIconVisible(bool fHideTrayIcon) {
if (trayIcon) {
trayIcon->setVisible(!fHideTrayIcon);
}
}
void BitcoinGUI::showModalOverlay() {
if (modalOverlay &&
(progressBar->isVisible() || modalOverlay->isLayerVisible())) {
modalOverlay->toggleVisibility();
}
}
static bool ThreadSafeMessageBox(BitcoinGUI *gui, const std::string &message,
const std::string &caption,
unsigned int style) {
bool modal = (style & CClientUIInterface::MODAL);
// The SECURE flag has no effect in the Qt GUI.
// bool secure = (style & CClientUIInterface::SECURE);
style &= ~CClientUIInterface::SECURE;
bool ret = false;
// In case of modal message, use blocking connection to wait for user to
// click a button
QMetaObject::invokeMethod(gui, "message",
modal ? GUIUtil::blockingGUIThreadConnection()
: Qt::QueuedConnection,
Q_ARG(QString, QString::fromStdString(caption)),
Q_ARG(QString, QString::fromStdString(message)),
Q_ARG(unsigned int, style), Q_ARG(bool *, &ret));
return ret;
}
void BitcoinGUI::subscribeToCoreSignals() {
// Connect signals to client
m_handler_message_box = m_node.handleMessageBox(
std::bind(ThreadSafeMessageBox, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
m_handler_question = m_node.handleQuestion(
std::bind(ThreadSafeMessageBox, this, std::placeholders::_1,
std::placeholders::_3, std::placeholders::_4));
}
void BitcoinGUI::unsubscribeFromCoreSignals() {
// Disconnect signals from client
m_handler_message_box->disconnect();
m_handler_question->disconnect();
}
void BitcoinGUI::toggleNetworkActive() {
m_node.setNetworkActive(!m_node.getNetworkActive());
}
UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(
const PlatformStyle *platformStyle)
: optionsModel(0), menu(0) {
createContextMenu();
setToolTip(tr("Unit to show amounts in. Click to select another unit."));
QList<BitcoinUnits::Unit> units = BitcoinUnits::availableUnits();
int max_width = 0;
const QFontMetrics fm(font());
for (const BitcoinUnits::Unit unit : units) {
max_width = qMax(max_width, fm.width(BitcoinUnits::name(unit)));
}
setMinimumSize(max_width, 0);
setAlignment(Qt::AlignRight | Qt::AlignVCenter);
setStyleSheet(QString("QLabel { color : %1 }")
.arg(platformStyle->SingleColor().name()));
}
/** So that it responds to button clicks */
void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event) {
onDisplayUnitsClicked(event->pos());
}
/** Creates context menu, its actions, and wires up all the relevant signals for
* mouse events. */
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);
}
connect(menu, SIGNAL(triggered(QAction *)), this,
SLOT(onMenuSelection(QAction *)));
}
/** Lets the control know about the Options Model (and its signals) */
void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel) {
if (_optionsModel) {
this->optionsModel = _optionsModel;
// be aware of a display unit change reported by the OptionsModel
// object.
connect(_optionsModel, SIGNAL(displayUnitChanged(int)), this,
SLOT(updateDisplayUnit(int)));
// initialize the display units label with the current value in the
// model.
updateDisplayUnit(_optionsModel->getDisplayUnit());
}
}
/** When Display Units are changed on OptionsModel it will refresh the display
* text of the control on the status bar */
void UnitDisplayStatusBarControl::updateDisplayUnit(int newUnits) {
setText(BitcoinUnits::name(newUnits));
}
/** Shows context menu with Display Unit options by the mouse coordinates */
void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint &point) {
QPoint globalPos = mapToGlobal(point);
menu->exec(globalPos);
}
/** Tells underlying optionsModel to update its current display unit. */
void UnitDisplayStatusBarControl::onMenuSelection(QAction *action) {
if (action) {
optionsModel->setDisplayUnit(action->data());
}
}
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 226ef982f1..c4720400ea 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -1,313 +1,314 @@
// Copyright (c) 2011-2016 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_BITCOINGUI_H
#define BITCOIN_QT_BITCOINGUI_H
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <amount.h>
#include <QLabel>
#include <QMainWindow>
#include <QMap>
#include <QMenu>
#include <QPoint>
#include <QSystemTrayIcon>
#include <memory>
class ClientModel;
class NetworkStyle;
class Notificator;
class OptionsModel;
class PlatformStyle;
class RPCConsole;
class SendCoinsRecipient;
class UnitDisplayStatusBarControl;
class WalletFrame;
class WalletModel;
class HelpMessageDialog;
class ModalOverlay;
class Config;
namespace interfaces {
class Handler;
class Node;
} // namespace interfaces
QT_BEGIN_NAMESPACE
class QAction;
class QComboBox;
class QProgressBar;
class QProgressDialog;
QT_END_NAMESPACE
/**
* Bitcoin GUI main class. This class represents the main window of the Bitcoin
* UI. It communicates with both the client and wallet models to give the user
* an up-to-date view of the current core state.
*/
class BitcoinGUI : public QMainWindow {
Q_OBJECT
public:
static const std::string DEFAULT_UIPLATFORM;
explicit BitcoinGUI(interfaces::Node &node, const Config *,
const PlatformStyle *platformStyle,
const NetworkStyle *networkStyle, QWidget *parent = 0);
~BitcoinGUI();
/**
* Set the client model.
* The client model represents the part of the core that communicates with
* the P2P network, and is wallet-agnostic.
*/
void setClientModel(ClientModel *clientModel);
#ifdef ENABLE_WALLET
/**
* Set the wallet model.
* The wallet model represents a bitcoin wallet, and offers access to the
* list of transactions, address book and sending functionality.
*/
bool addWallet(WalletModel *walletModel);
void removeAllWallets();
#endif // ENABLE_WALLET
bool enableWallet = false;
protected:
void changeEvent(QEvent *e) override;
void closeEvent(QCloseEvent *event) override;
void showEvent(QShowEvent *event) override;
void dragEnterEvent(QDragEnterEvent *event) override;
void dropEvent(QDropEvent *event) override;
bool eventFilter(QObject *object, QEvent *event) override;
private:
interfaces::Node &m_node;
std::unique_ptr<interfaces::Handler> m_handler_message_box;
std::unique_ptr<interfaces::Handler> m_handler_question;
ClientModel *clientModel = nullptr;
WalletFrame *walletFrame = nullptr;
UnitDisplayStatusBarControl *unitDisplayControl = nullptr;
QLabel *labelWalletEncryptionIcon = nullptr;
QLabel *labelWalletHDStatusIcon = nullptr;
QLabel *connectionsControl = nullptr;
QLabel *labelBlocksIcon = nullptr;
QLabel *progressBarLabel = nullptr;
QProgressBar *progressBar = nullptr;
QProgressDialog *progressDialog = nullptr;
QMenuBar *appMenuBar = nullptr;
QToolBar *appToolBar = nullptr;
QAction *overviewAction = nullptr;
QAction *historyAction = nullptr;
QAction *quitAction = nullptr;
QAction *sendCoinsAction = nullptr;
QAction *sendCoinsMenuAction = nullptr;
QAction *usedSendingAddressesAction = nullptr;
QAction *usedReceivingAddressesAction = nullptr;
QAction *signMessageAction = nullptr;
QAction *verifyMessageAction = nullptr;
QAction *aboutAction = nullptr;
QAction *receiveCoinsAction = nullptr;
QAction *receiveCoinsMenuAction = nullptr;
QAction *optionsAction = nullptr;
QAction *toggleHideAction = nullptr;
QAction *encryptWalletAction = nullptr;
QAction *backupWalletAction = nullptr;
QAction *changePassphraseAction = nullptr;
QAction *aboutQtAction = nullptr;
QAction *openRPCConsoleAction = nullptr;
QAction *openAction = nullptr;
QAction *showHelpMessageAction = nullptr;
QLabel *m_wallet_selector_label = nullptr;
QComboBox *m_wallet_selector = nullptr;
QSystemTrayIcon *trayIcon = nullptr;
QMenu *trayIconMenu = nullptr;
Notificator *notificator = nullptr;
RPCConsole *rpcConsole = nullptr;
HelpMessageDialog *helpMessageDialog = nullptr;
ModalOverlay *modalOverlay = nullptr;
/** Keep track of previous number of blocks, to detect progress */
int prevBlocks = 0;
int spinnerFrame = 0;
const PlatformStyle *platformStyle;
const Config *config;
/** Create the main UI actions. */
void createActions();
/** Create the menu bar and sub-menus. */
void createMenuBar();
/** Create the toolbars */
void createToolBars();
/** Create system tray icon and notification */
void createTrayIcon(const NetworkStyle *networkStyle);
/** Create system tray menu (or setup the dock menu) */
void createTrayIconMenu();
/** Enable or disable all wallet-related actions */
void setWalletActionsEnabled(bool enabled);
/** Connect core signals to GUI client */
void subscribeToCoreSignals();
/** Disconnect core signals from GUI client */
void unsubscribeFromCoreSignals();
/** Update UI with latest network info from model. */
void updateNetworkState();
void updateHeadersSyncProgressLabel();
Q_SIGNALS:
/** Signal raised when a URI was entered or dragged to the GUI */
void receivedURI(const QString &uri);
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);
/** Set number of blocks and last block date shown in the UI */
void setNumBlocks(int count, const QDateTime &blockDate,
double nVerificationProgress, bool headers);
/** Notify the user of an event from the core network or transaction
handling code.
@param[in] title the message box / notification title
@param[in] message the displayed text
@param[in] style modality and style definitions (icon and used
buttons - buttons only for message boxes)
@see CClientUIInterface::MessageBoxFlags
@param[in] ret pointer to a bool that will be modified to whether
Ok was clicked (modal only)
*/
void message(const QString &title, const QString &message,
unsigned int style, bool *ret = nullptr);
#ifdef ENABLE_WALLET
bool setCurrentWallet(const QString &name);
+ bool setCurrentWalletBySelectorIndex(int index);
/** Set the UI status indicators based on the currently selected wallet.
*/
void updateWalletStatus();
private:
/** Set the encryption status as shown in the UI.
@param[in] status current encryption status
@see WalletModel::EncryptionStatus
*/
void setEncryptionStatus(int status);
/** Set the hd-enabled status as shown in the UI.
@param[in] status current hd enabled status
@see WalletModel::EncryptionStatus
*/
void setHDStatus(int hdEnabled);
public Q_SLOTS:
bool handlePaymentRequest(const SendCoinsRecipient &recipient);
/** Show incoming transaction notification for new transactions. */
void incomingTransaction(const QString &date, int unit, const Amount amount,
const QString &type, const QString &address,
const QString &label, const QString &walletName);
#endif // ENABLE_WALLET
private Q_SLOTS:
#ifdef ENABLE_WALLET
/** Switch to overview (home) page */
void gotoOverviewPage();
/** Switch to history (transactions) page */
void gotoHistoryPage();
/** Switch to receive coins page */
void gotoReceiveCoinsPage();
/** Switch to send coins page */
void gotoSendCoinsPage(QString addr = "");
/** Show Sign/Verify Message dialog and switch to sign message tab */
void gotoSignMessageTab(QString addr = "");
/** Show Sign/Verify Message dialog and switch to verify message tab */
void gotoVerifyMessageTab(QString addr = "");
/** Show open dialog */
void openClicked();
#endif // ENABLE_WALLET
/** Show configuration dialog */
void optionsClicked();
/** Show about dialog */
void aboutClicked();
/** Show debug window */
void showDebugWindow();
/** Show debug window and set focus to the console */
void showDebugWindowActivateConsole();
/** Show help message dialog */
void showHelpMessageClicked();
#ifndef Q_OS_MAC
/** Handle tray icon clicked */
void trayIconActivated(QSystemTrayIcon::ActivationReason reason);
#endif
/** Show window if hidden, unminimize when minimized, rise when obscured or
* show if hidden and fToggleHidden is true */
void showNormalIfMinimized(bool fToggleHidden = false);
/** Simply calls showNormalIfMinimized(true) for use in SLOT() macro */
void toggleHidden();
/** called by a timer to check if fRequestShutdown has been set **/
void detectShutdown();
/** Show progress dialog e.g. for verifychain */
void showProgress(const QString &title, int nProgress);
/** When hideTrayIcon setting is changed in OptionsModel hide or show the
* icon accordingly. */
void setTrayIconVisible(bool);
/** Toggle networking */
void toggleNetworkActive();
void showModalOverlay();
};
class UnitDisplayStatusBarControl : public QLabel {
Q_OBJECT
public:
explicit UnitDisplayStatusBarControl(const PlatformStyle *platformStyle);
/** Lets the control know about the Options Model (and its signals) */
void setOptionsModel(OptionsModel *optionsModel);
protected:
/** So that it responds to left-button clicks */
void mousePressEvent(QMouseEvent *event) override;
private:
OptionsModel *optionsModel;
QMenu *menu;
/** Shows context menu with Display Unit options by the mouse coordinates */
void onDisplayUnitsClicked(const QPoint &point);
/** Creates context menu, its actions, and wires up all the relevant signals
* for mouse events. */
void createContextMenu();
private Q_SLOTS:
/** When Display Units are changed on OptionsModel it will refresh the
* display text of the control on the status bar */
void updateDisplayUnit(int newUnits);
/** Tells underlying optionsModel to update its current display unit. */
void onMenuSelection(QAction *action);
};
#endif // BITCOIN_QT_BITCOINGUI_H
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index 47cf15b1f4..c4e0107e9b 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -1,1441 +1,1443 @@
// Copyright (c) 2011-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#if defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#endif
#include <qt/rpcconsole.h>
#include <chainparams.h>
#include <config.h>
#include <interfaces/node.h>
#include <netbase.h>
#include <qt/bantablemodel.h>
#include <qt/clientmodel.h>
#include <qt/forms/ui_debugwindow.h>
#include <qt/guiutil.h>
#include <qt/platformstyle.h>
#include <qt/walletmodel.h>
#include <rpc/client.h>
#include <rpc/server.h>
#include <util/system.h>
#ifdef ENABLE_WALLET
#include <wallet/wallet.h>
#include <db_cxx.h>
#endif
#include <QDesktopWidget>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QScrollBar>
#include <QSettings>
#include <QSignalMapper>
#include <QStringList>
#include <QThread>
#include <QTime>
#include <QTimer>
#include <univalue.h>
// TODO: add a scrollback limit, as there is currently none
// TODO: make it possible to filter out categories (esp debug messages when
// implemented)
// TODO: receive errors and debug messages through ClientModel
const int CONSOLE_HISTORY = 50;
const int INITIAL_TRAFFIC_GRAPH_MINS = 30;
const QSize FONT_RANGE(4, 40);
const char fontSizeSettingsKey[] = "consoleFontSize";
const struct {
const char *url;
const char *source;
} ICON_MAPPING[] = {{"cmd-request", ":/icons/tx_input"},
{"cmd-reply", ":/icons/tx_output"},
{"cmd-error", ":/icons/tx_output"},
{"misc", ":/icons/tx_inout"},
{nullptr, nullptr}};
namespace {
// don't add private key handling cmd's to the history
const QStringList historyFilter = QStringList() << "importprivkey"
<< "importmulti"
<< "sethdseed"
<< "signmessagewithprivkey"
<< "signrawtransactionwithkey"
<< "walletpassphrase"
<< "walletpassphrasechange"
<< "encryptwallet";
} // namespace
/* Object for executing console RPC commands in a separate thread.
*/
class RPCExecutor : public QObject {
Q_OBJECT
public:
RPCExecutor(interfaces::Node &node) : m_node(node) {}
public Q_SLOTS:
void request(const QString &command, const QString &walletID);
Q_SIGNALS:
void reply(int category, const QString &command);
private:
interfaces::Node &m_node;
};
/** Class for handling RPC timers
* (used for e.g. re-locking the wallet after a timeout)
*/
class QtRPCTimerBase : public QObject, public RPCTimerBase {
Q_OBJECT
public:
QtRPCTimerBase(std::function<void()> &_func, int64_t millis) : func(_func) {
timer.setSingleShot(true);
connect(&timer, SIGNAL(timeout()), this, SLOT(timeout()));
timer.start(millis);
}
~QtRPCTimerBase() {}
private Q_SLOTS:
void timeout() { func(); }
private:
QTimer timer;
std::function<void()> func;
};
class QtRPCTimerInterface : public RPCTimerInterface {
public:
~QtRPCTimerInterface() {}
const char *Name() override { return "Qt"; }
RPCTimerBase *NewTimer(std::function<void()> &func,
int64_t millis) override {
return new QtRPCTimerBase(func, millis);
}
};
#include <qt/rpcconsole.moc>
/**
* Split shell command line into a list of arguments and optionally execute the
* command(s).
* Aims to emulate \c bash and friends.
*
* - Command nesting is possible with parenthesis; for example:
* validateaddress(getnewaddress())
* - Arguments are delimited with whitespace or comma
* - Extra whitespace at the beginning and end and between arguments will be
* ignored
* - Text can be "double" or 'single' quoted
* - The backslash \c \ is used as escape character
* - Outside quotes, any character can be escaped
* - Within double quotes, only escape \c " and backslashes before a \c " or
* another backslash
* - Within single quotes, no escaping is possible and no special
* interpretation takes place
*
* @param[in] node optional node to execute command on
* @param[out] result stringified Result from the executed command(chain)
* @param[in] strCommand Command line to split
* @param[in] fExecute set true if you want the command to be executed
* @param[out] pstrFilteredOut Command line, filtered to remove any sensitive
* data
*/
bool RPCConsole::RPCParseCommandLine(interfaces::Node *node,
std::string &strResult,
const std::string &strCommand,
const bool fExecute,
std::string *const pstrFilteredOut,
const std::string *walletID) {
std::vector<std::vector<std::string>> stack;
stack.push_back(std::vector<std::string>());
enum CmdParseState {
STATE_EATING_SPACES,
STATE_EATING_SPACES_IN_ARG,
STATE_EATING_SPACES_IN_BRACKETS,
STATE_ARGUMENT,
STATE_SINGLEQUOTED,
STATE_DOUBLEQUOTED,
STATE_ESCAPE_OUTER,
STATE_ESCAPE_DOUBLEQUOTED,
STATE_COMMAND_EXECUTED,
STATE_COMMAND_EXECUTED_INNER
} state = STATE_EATING_SPACES;
std::string curarg;
UniValue lastResult;
unsigned nDepthInsideSensitive = 0;
size_t filter_begin_pos = 0, chpos;
std::vector<std::pair<size_t, size_t>> filter_ranges;
auto add_to_current_stack = [&](const std::string &strArg) {
if (stack.back().empty() && (!nDepthInsideSensitive) &&
historyFilter.contains(QString::fromStdString(strArg),
Qt::CaseInsensitive)) {
nDepthInsideSensitive = 1;
filter_begin_pos = chpos;
}
// Make sure stack is not empty before adding something
if (stack.empty()) {
stack.push_back(std::vector<std::string>());
}
stack.back().push_back(strArg);
};
auto close_out_params = [&]() {
if (nDepthInsideSensitive) {
if (!--nDepthInsideSensitive) {
assert(filter_begin_pos);
filter_ranges.push_back(
std::make_pair(filter_begin_pos, chpos));
filter_begin_pos = 0;
}
}
stack.pop_back();
};
std::string strCommandTerminated = strCommand;
if (strCommandTerminated.back() != '\n') strCommandTerminated += "\n";
for (chpos = 0; chpos < strCommandTerminated.size(); ++chpos) {
char ch = strCommandTerminated[chpos];
switch (state) {
case STATE_COMMAND_EXECUTED_INNER:
case STATE_COMMAND_EXECUTED: {
bool breakParsing = true;
switch (ch) {
case '[':
curarg.clear();
state = STATE_COMMAND_EXECUTED_INNER;
break;
default:
if (state == STATE_COMMAND_EXECUTED_INNER) {
if (ch != ']') {
// append char to the current argument (which is
// also used for the query command)
curarg += ch;
break;
}
if (curarg.size() && fExecute) {
// if we have a value query, query arrays with
// index and objects with a string key
UniValue subelement;
if (lastResult.isArray()) {
for (char argch : curarg) {
if (!std::isdigit(argch)) {
throw std::runtime_error(
"Invalid result query");
}
}
subelement =
lastResult[atoi(curarg.c_str())];
} else if (lastResult.isObject()) {
subelement = find_value(lastResult, curarg);
} else {
// no array or object: abort
throw std::runtime_error(
"Invalid result query");
}
lastResult = subelement;
}
state = STATE_COMMAND_EXECUTED;
break;
}
// don't break parsing when the char is required for the
// next argument
breakParsing = false;
// pop the stack and return the result to the current
// command arguments
close_out_params();
// don't stringify the json in case of a string to avoid
// doublequotes
if (lastResult.isStr()) {
curarg = lastResult.get_str();
} else {
curarg = lastResult.write(2);
}
// if we have a non empty result, use it as stack
// argument otherwise as general result
if (curarg.size()) {
if (stack.size()) {
add_to_current_stack(curarg);
} else {
strResult = curarg;
}
}
curarg.clear();
// assume eating space state
state = STATE_EATING_SPACES;
}
if (breakParsing) {
break;
}
}
// FALLTHROUGH
case STATE_ARGUMENT: // In or after argument
case STATE_EATING_SPACES_IN_ARG:
case STATE_EATING_SPACES_IN_BRACKETS:
case STATE_EATING_SPACES: // Handle runs of whitespace
switch (ch) {
case '"':
state = STATE_DOUBLEQUOTED;
break;
case '\'':
state = STATE_SINGLEQUOTED;
break;
case '\\':
state = STATE_ESCAPE_OUTER;
break;
case '(':
case ')':
case '\n':
if (state == STATE_EATING_SPACES_IN_ARG) {
throw std::runtime_error("Invalid Syntax");
}
if (state == STATE_ARGUMENT) {
if (ch == '(' && stack.size() &&
stack.back().size() > 0) {
if (nDepthInsideSensitive) {
++nDepthInsideSensitive;
}
stack.push_back(std::vector<std::string>());
}
// don't allow commands after executed commands on
// baselevel
if (!stack.size()) {
throw std::runtime_error("Invalid Syntax");
}
add_to_current_stack(curarg);
curarg.clear();
state = STATE_EATING_SPACES_IN_BRACKETS;
}
if ((ch == ')' || ch == '\n') && stack.size() > 0) {
if (fExecute) {
// Convert argument list to JSON objects in
// method-dependent way, and pass it along with
// the method name to the dispatcher.
UniValue params = RPCConvertValues(
stack.back()[0],
std::vector<std::string>(
stack.back().begin() + 1,
stack.back().end()));
std::string method = stack.back()[0];
std::string uri;
#ifdef ENABLE_WALLET
if (walletID) {
QByteArray encodedName =
QUrl::toPercentEncoding(
QString::fromStdString(*walletID));
uri = "/wallet/" +
std::string(encodedName.constData(),
encodedName.length());
}
#endif
GlobalConfig config;
assert(node);
lastResult = node->executeRpc(config, method,
params, uri);
}
state = STATE_COMMAND_EXECUTED;
curarg.clear();
}
break;
case ' ':
case ',':
case '\t':
if (state == STATE_EATING_SPACES_IN_ARG &&
curarg.empty() && ch == ',') {
throw std::runtime_error("Invalid Syntax");
} else if (state == STATE_ARGUMENT) {
// Space ends argument
add_to_current_stack(curarg);
curarg.clear();
}
if ((state == STATE_EATING_SPACES_IN_BRACKETS ||
state == STATE_ARGUMENT) &&
ch == ',') {
state = STATE_EATING_SPACES_IN_ARG;
break;
}
state = STATE_EATING_SPACES;
break;
default:
curarg += ch;
state = STATE_ARGUMENT;
}
break;
case STATE_SINGLEQUOTED: // Single-quoted string
switch (ch) {
case '\'':
state = STATE_ARGUMENT;
break;
default:
curarg += ch;
}
break;
case STATE_DOUBLEQUOTED: // Double-quoted string
switch (ch) {
case '"':
state = STATE_ARGUMENT;
break;
case '\\':
state = STATE_ESCAPE_DOUBLEQUOTED;
break;
default:
curarg += ch;
}
break;
case STATE_ESCAPE_OUTER: // '\' outside quotes
curarg += ch;
state = STATE_ARGUMENT;
break;
case STATE_ESCAPE_DOUBLEQUOTED: // '\' in double-quoted text
if (ch != '"' && ch != '\\') {
// keep '\' for everything but the quote and '\' itself
curarg += '\\';
}
curarg += ch;
state = STATE_DOUBLEQUOTED;
break;
}
}
if (pstrFilteredOut) {
if (STATE_COMMAND_EXECUTED == state) {
assert(!stack.empty());
close_out_params();
}
*pstrFilteredOut = strCommand;
for (auto i = filter_ranges.rbegin(); i != filter_ranges.rend(); ++i) {
pstrFilteredOut->replace(i->first, i->second - i->first, "(…)");
}
}
// final state
switch (state) {
case STATE_COMMAND_EXECUTED:
if (lastResult.isStr()) {
strResult = lastResult.get_str();
} else {
strResult = lastResult.write(2);
}
// FALLTHROUGH
case STATE_ARGUMENT:
case STATE_EATING_SPACES:
return true;
default: // ERROR to end in one of the other states
return false;
}
}
void RPCExecutor::request(const QString &command, const QString &walletID) {
try {
std::string result;
std::string executableCommand = command.toStdString() + "\n";
// Catch the console-only-help command before RPC call is executed and
// reply with help text as-if a RPC reply.
if (executableCommand == "help-console\n") {
Q_EMIT reply(
RPCConsole::CMD_REPLY,
QString(("\n"
"This console accepts RPC commands using the standard "
"syntax.\n"
" example: getblockhash 0\n\n"
"This console can also accept RPC commands using "
"parenthesized syntax.\n"
" example: getblockhash(0)\n\n"
"Commands may be nested when specified with the "
"parenthesized syntax.\n"
" example: getblock(getblockhash(0) 1)\n\n"
"A space or a comma can be used to delimit arguments "
"for either syntax.\n"
" example: getblockhash 0\n"
" getblockhash,0\n\n"
"Named results can be queried with a non-quoted key "
"string in brackets.\n"
" example: getblock(getblockhash(0) true)[tx]\n\n"
"Results without keys can be queried using an integer "
"in brackets.\n"
" example: "
"getblock(getblockhash(0),true)[tx][0]\n\n")));
return;
}
std::string wallet_id = walletID.toStdString();
if (!RPCConsole::RPCExecuteCommandLine(
m_node, result, executableCommand, nullptr,
walletID.isNull() ? nullptr : &wallet_id)) {
Q_EMIT reply(RPCConsole::CMD_ERROR,
QString("Parse error: unbalanced ' or \""));
return;
}
Q_EMIT reply(RPCConsole::CMD_REPLY, QString::fromStdString(result));
} catch (UniValue &objError) {
// Nice formatting for standard-format error
try {
int code = find_value(objError, "code").get_int();
std::string message = find_value(objError, "message").get_str();
Q_EMIT reply(RPCConsole::CMD_ERROR,
QString::fromStdString(message) + " (code " +
QString::number(code) + ")");
} catch (const std::runtime_error &) {
// raised when converting to invalid type, i.e. missing code or
// message. Show raw JSON object.
Q_EMIT reply(RPCConsole::CMD_ERROR,
QString::fromStdString(objError.write()));
}
} catch (const std::exception &e) {
Q_EMIT reply(RPCConsole::CMD_ERROR,
QString("Error: ") + QString::fromStdString(e.what()));
}
}
RPCConsole::RPCConsole(interfaces::Node &node,
const PlatformStyle *_platformStyle, QWidget *parent)
: QWidget(parent), m_node(node), ui(new Ui::RPCConsole),
platformStyle(_platformStyle) {
ui->setupUi(this);
QSettings settings;
if (!restoreGeometry(
settings.value("RPCConsoleWindowGeometry").toByteArray())) {
// Restore failed (perhaps missing setting), center the window
move(QApplication::desktop()->availableGeometry().center() -
frameGeometry().center());
}
QChar nonbreaking_hyphen(8209);
ui->dataDir->setToolTip(
ui->dataDir->toolTip().arg(QString(nonbreaking_hyphen) + "datadir"));
ui->blocksDir->setToolTip(ui->blocksDir->toolTip().arg(
QString(nonbreaking_hyphen) + "blocksdir"));
ui->openDebugLogfileButton->setToolTip(
ui->openDebugLogfileButton->toolTip().arg(tr(PACKAGE_NAME)));
if (platformStyle->getImagesOnButtons()) {
ui->openDebugLogfileButton->setIcon(
platformStyle->SingleColorIcon(":/icons/export"));
}
ui->clearButton->setIcon(platformStyle->SingleColorIcon(":/icons/remove"));
ui->fontBiggerButton->setIcon(
platformStyle->SingleColorIcon(":/icons/fontbigger"));
ui->fontSmallerButton->setIcon(
platformStyle->SingleColorIcon(":/icons/fontsmaller"));
// Install event filter for up and down arrow
ui->lineEdit->installEventFilter(this);
ui->messagesWidget->installEventFilter(this);
connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
connect(ui->fontBiggerButton, SIGNAL(clicked()), this, SLOT(fontBigger()));
connect(ui->fontSmallerButton, SIGNAL(clicked()), this,
SLOT(fontSmaller()));
connect(ui->btnClearTrafficGraph, SIGNAL(clicked()), ui->trafficGraph,
SLOT(clear()));
// disable the wallet selector by default
ui->WalletSelector->setVisible(false);
ui->WalletSelectorLabel->setVisible(false);
// set library version labels
#ifdef ENABLE_WALLET
ui->berkeleyDBVersion->setText(DbEnv::version(0, 0, 0));
#else
ui->label_berkeleyDBVersion->hide();
ui->berkeleyDBVersion->hide();
#endif
// Register RPC timer interface
rpcTimerInterface = new QtRPCTimerInterface();
// avoid accidentally overwriting an existing, non QTThread
// based timer interface
m_node.rpcSetTimerInterfaceIfUnset(rpcTimerInterface);
setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS);
ui->detailWidget->hide();
ui->peerHeading->setText(tr("Select a peer to view detailed information."));
consoleFontSize =
settings.value(fontSizeSettingsKey, QFontInfo(QFont()).pointSize())
.toInt();
clear();
}
RPCConsole::~RPCConsole() {
QSettings settings;
settings.setValue("RPCConsoleWindowGeometry", saveGeometry());
m_node.rpcUnsetTimerInterface(rpcTimerInterface);
delete rpcTimerInterface;
delete ui;
}
bool RPCConsole::eventFilter(QObject *obj, QEvent *event) {
// Special key handling
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyevt = static_cast<QKeyEvent *>(event);
int key = keyevt->key();
Qt::KeyboardModifiers mod = keyevt->modifiers();
switch (key) {
case Qt::Key_Up:
if (obj == ui->lineEdit) {
browseHistory(-1);
return true;
}
break;
case Qt::Key_Down:
if (obj == ui->lineEdit) {
browseHistory(1);
return true;
}
break;
case Qt::Key_PageUp: /* pass paging keys to messages widget */
case Qt::Key_PageDown:
if (obj == ui->lineEdit) {
QApplication::postEvent(ui->messagesWidget,
new QKeyEvent(*keyevt));
return true;
}
break;
case Qt::Key_Return:
case Qt::Key_Enter:
// forward these events to lineEdit
if (obj == autoCompleter->popup()) {
QApplication::postEvent(ui->lineEdit,
new QKeyEvent(*keyevt));
return true;
}
break;
default:
// Typing in messages widget brings focus to line edit, and
// redirects key there. Exclude most combinations and keys that
// emit no text, except paste shortcuts.
if (obj == ui->messagesWidget &&
((!mod && !keyevt->text().isEmpty() &&
key != Qt::Key_Tab) ||
((mod & Qt::ControlModifier) && key == Qt::Key_V) ||
((mod & Qt::ShiftModifier) && key == Qt::Key_Insert))) {
ui->lineEdit->setFocus();
QApplication::postEvent(ui->lineEdit,
new QKeyEvent(*keyevt));
return true;
}
}
}
return QWidget::eventFilter(obj, event);
}
void RPCConsole::setClientModel(ClientModel *model) {
clientModel = model;
ui->trafficGraph->setClientModel(model);
if (model && clientModel->getPeerTableModel() &&
clientModel->getBanTableModel()) {
// Keep up to date with client
setNumConnections(model->getNumConnections());
connect(model, SIGNAL(numConnectionsChanged(int)), this,
SLOT(setNumConnections(int)));
interfaces::Node &node = clientModel->node();
setNumBlocks(node.getNumBlocks(),
QDateTime::fromTime_t(node.getLastBlockTime()),
node.getVerificationProgress(), false);
connect(model, SIGNAL(numBlocksChanged(int, QDateTime, double, bool)),
this, SLOT(setNumBlocks(int, QDateTime, double, bool)));
updateNetworkState();
connect(model, SIGNAL(networkActiveChanged(bool)), this,
SLOT(setNetworkActive(bool)));
updateTrafficStats(node.getTotalBytesRecv(), node.getTotalBytesSent());
connect(model, SIGNAL(bytesChanged(quint64, quint64)), this,
SLOT(updateTrafficStats(quint64, quint64)));
connect(model, SIGNAL(mempoolSizeChanged(long, size_t)), this,
SLOT(setMempoolSize(long, size_t)));
// set up peer table
ui->peerWidget->setModel(model->getPeerTableModel());
ui->peerWidget->verticalHeader()->hide();
ui->peerWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
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);
ui->peerWidget->horizontalHeader()->setStretchLastSection(true);
// create peer table context menu actions
QAction *disconnectAction = new QAction(tr("&Disconnect"), this);
QAction *banAction1h =
new QAction(tr("Ban for") + " " + tr("1 &hour"), this);
QAction *banAction24h =
new QAction(tr("Ban for") + " " + tr("1 &day"), this);
QAction *banAction7d =
new QAction(tr("Ban for") + " " + tr("1 &week"), this);
QAction *banAction365d =
new QAction(tr("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);
// Add a signal mapping to allow dynamic context menu arguments. We need
// to use int (instead of int64_t), because signal mapper only supports
// int or objects, which is okay because max bantime (1 year) is <
// int_max.
QSignalMapper *signalMapper = new QSignalMapper(this);
signalMapper->setMapping(banAction1h, 60 * 60);
signalMapper->setMapping(banAction24h, 60 * 60 * 24);
signalMapper->setMapping(banAction7d, 60 * 60 * 24 * 7);
signalMapper->setMapping(banAction365d, 60 * 60 * 24 * 365);
connect(banAction1h, SIGNAL(triggered()), signalMapper, SLOT(map()));
connect(banAction24h, SIGNAL(triggered()), signalMapper, SLOT(map()));
connect(banAction7d, SIGNAL(triggered()), signalMapper, SLOT(map()));
connect(banAction365d, SIGNAL(triggered()), signalMapper, SLOT(map()));
connect(signalMapper, SIGNAL(mapped(int)), this,
SLOT(banSelectedNode(int)));
// peer table context menu signals
connect(ui->peerWidget,
SIGNAL(customContextMenuRequested(const QPoint &)), this,
SLOT(showPeersTableContextMenu(const QPoint &)));
connect(disconnectAction, SIGNAL(triggered()), this,
SLOT(disconnectSelectedNode()));
// peer table signal handling - update peer details when selecting new
// node
connect(
ui->peerWidget->selectionModel(),
SIGNAL(selectionChanged(const QItemSelection &,
const QItemSelection &)),
this,
SLOT(peerSelected(const QItemSelection &, const QItemSelection &)));
// peer table signal handling - update peer details when new nodes are
// added to the model
connect(model->getPeerTableModel(), SIGNAL(layoutChanged()), this,
SLOT(peerLayoutChanged()));
// peer table signal handling - cache selected node ids
connect(model->getPeerTableModel(), SIGNAL(layoutAboutToBeChanged()),
this, SLOT(peerLayoutAboutToChange()));
// set up ban table
ui->banlistWidget->setModel(model->getBanTableModel());
ui->banlistWidget->verticalHeader()->hide();
ui->banlistWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
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);
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
connect(ui->banlistWidget,
SIGNAL(customContextMenuRequested(const QPoint &)), this,
SLOT(showBanTableContextMenu(const QPoint &)));
connect(unbanAction, SIGNAL(triggered()), this,
SLOT(unbanSelectedNode()));
// ban table signal handling - clear peer details when clicking a peer
// in the ban table
connect(ui->banlistWidget, SIGNAL(clicked(const QModelIndex &)), this,
SLOT(clearSelectedNode()));
// ban table signal handling - ensure ban table is shown or hidden (if
// empty)
connect(model->getBanTableModel(), SIGNAL(layoutChanged()), this,
SLOT(showOrHideBanTableIfRequired()));
showOrHideBanTableIfRequired();
// Provide initial values
ui->clientVersion->setText(model->formatFullVersion());
ui->clientUserAgent->setText(model->formatSubVersion());
ui->dataDir->setText(model->dataDir());
ui->blocksDir->setText(model->blocksDir());
ui->startupTime->setText(model->formatClientStartupTime());
ui->networkName->setText(
QString::fromStdString(Params().NetworkIDString()));
// Setup autocomplete and attach it
QStringList wordList;
std::vector<std::string> commandList = m_node.listRpcCommands();
for (size_t i = 0; i < commandList.size(); ++i) {
wordList << commandList[i].c_str();
wordList << ("help " + commandList[i]).c_str();
}
wordList << "help-console";
wordList.sort();
autoCompleter = new QCompleter(wordList, this);
autoCompleter->setModelSorting(QCompleter::CaseSensitivelySortedModel);
ui->lineEdit->setCompleter(autoCompleter);
autoCompleter->popup()->installEventFilter(this);
// Start thread to execute RPC commands.
startExecutor();
}
if (!model) {
// Client model is being set to 0, this means shutdown() is about to be
// called. Make sure we clean up the executor thread
Q_EMIT stopExecutor();
thread.wait();
}
}
#ifdef ENABLE_WALLET
void RPCConsole::addWallet(WalletModel *const walletModel) {
const QString name = walletModel->getWalletName();
// use name for text and internal data object (to allow to move to a wallet
// id later)
- ui->WalletSelector->addItem(name, name);
+ QString display_name =
+ name.isEmpty() ? "[" + tr("default wallet") + "]" : name;
+ ui->WalletSelector->addItem(display_name, name);
if (ui->WalletSelector->count() == 2 && !isVisible()) {
// First wallet added, set to default so long as the window isn't
// presently visible (and potentially in use)
ui->WalletSelector->setCurrentIndex(1);
}
if (ui->WalletSelector->count() > 2) {
ui->WalletSelector->setVisible(true);
ui->WalletSelectorLabel->setVisible(true);
}
}
#endif
static QString categoryClass(int category) {
switch (category) {
case RPCConsole::CMD_REQUEST:
return "cmd-request";
break;
case RPCConsole::CMD_REPLY:
return "cmd-reply";
break;
case RPCConsole::CMD_ERROR:
return "cmd-error";
break;
default:
return "misc";
}
}
void RPCConsole::fontBigger() {
setFontSize(consoleFontSize + 1);
}
void RPCConsole::fontSmaller() {
setFontSize(consoleFontSize - 1);
}
void RPCConsole::setFontSize(int newSize) {
QSettings settings;
// don't allow an insane font size
if (newSize < FONT_RANGE.width() || newSize > FONT_RANGE.height()) return;
// temp. store the console content
QString str = ui->messagesWidget->toHtml();
// replace font tags size in current content
str.replace(QString("font-size:%1pt").arg(consoleFontSize),
QString("font-size:%1pt").arg(newSize));
// store the new font size
consoleFontSize = newSize;
settings.setValue(fontSizeSettingsKey, consoleFontSize);
// clear console (reset icon sizes, default stylesheet) and re-add the
// content
float oldPosFactor = 1.0 /
ui->messagesWidget->verticalScrollBar()->maximum() *
ui->messagesWidget->verticalScrollBar()->value();
clear(false);
ui->messagesWidget->setHtml(str);
ui->messagesWidget->verticalScrollBar()->setValue(
oldPosFactor * ui->messagesWidget->verticalScrollBar()->maximum());
}
void RPCConsole::clear(bool clearHistory) {
ui->messagesWidget->clear();
if (clearHistory) {
history.clear();
historyPtr = 0;
}
ui->lineEdit->clear();
ui->lineEdit->setFocus();
// Add smoothly scaled icon images.
// (when using width/height on an img, Qt uses nearest instead of linear
// interpolation)
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));
}
// Set default style sheet
QFontInfo fixedFontInfo(GUIUtil::fixedPitchFont());
ui->messagesWidget->document()->setDefaultStyleSheet(
QString("table { }"
"td.time { color: #808080; font-size: %2; padding-top: 3px; } "
"td.message { font-family: %1; font-size: %2; "
"white-space:pre-wrap; } "
"td.cmd-request { color: #006060; } "
"td.cmd-error { color: red; } "
".secwarning { color: red; }"
"b { color: #006060; } ")
.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(tr(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);
}
void RPCConsole::keyPressEvent(QKeyEvent *event) {
if (windowType() != Qt::Widget && event->key() == Qt::Key_Escape) {
close();
}
}
void RPCConsole::message(int category, const QString &message, bool html) {
QTime time = QTime::currentTime();
QString timeString = time.toString();
QString out;
out += "<table><tr><td class=\"time\" width=\"65\">" + timeString + "</td>";
out += "<td class=\"icon\" width=\"32\"><img src=\"" +
categoryClass(category) + "\"></td>";
out += "<td class=\"message " + categoryClass(category) +
"\" valign=\"middle\">";
if (html) {
out += message;
} else {
out += GUIUtil::HtmlEscape(message, false);
}
out += "</td></tr></table>";
ui->messagesWidget->append(out);
}
void RPCConsole::updateNetworkState() {
QString connections =
QString::number(clientModel->getNumConnections()) + " (";
connections +=
tr("In:") + " " +
QString::number(clientModel->getNumConnections(CONNECTIONS_IN)) + " / ";
connections +=
tr("Out:") + " " +
QString::number(clientModel->getNumConnections(CONNECTIONS_OUT)) + ")";
if (!clientModel->node().getNetworkActive()) {
connections += " (" + tr("Network activity disabled") + ")";
}
ui->numberOfConnections->setText(connections);
}
void RPCConsole::setNumConnections(int count) {
if (!clientModel) {
return;
}
updateNetworkState();
}
void RPCConsole::setNetworkActive(bool networkActive) {
updateNetworkState();
}
void RPCConsole::setNumBlocks(int count, const QDateTime &blockDate,
double nVerificationProgress, bool headers) {
if (!headers) {
ui->numberOfBlocks->setText(QString::number(count));
ui->lastBlockTime->setText(blockDate.toString());
}
}
void RPCConsole::setMempoolSize(long numberOfTxs, size_t dynUsage) {
ui->mempoolNumberTxs->setText(QString::number(numberOfTxs));
if (dynUsage < 1000000) {
ui->mempoolSize->setText(QString::number(dynUsage / 1000.0, 'f', 2) +
" KB");
} else {
ui->mempoolSize->setText(QString::number(dynUsage / 1000000.0, 'f', 2) +
" MB");
}
}
void RPCConsole::on_lineEdit_returnPressed() {
QString cmd = ui->lineEdit->text();
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;
}
ui->lineEdit->clear();
cmdBeforeBrowsing = QString();
QString walletID;
#ifdef ENABLE_WALLET
const int wallet_index = ui->WalletSelector->currentIndex();
if (wallet_index > 0) {
walletID = (QString)ui->WalletSelector->itemData(wallet_index)
.value<QString>();
}
if (m_last_wallet_id != walletID) {
if (walletID.isNull()) {
message(CMD_REQUEST,
tr("Executing command without any wallet"));
} else {
message(
CMD_REQUEST,
tr("Executing command using \"%1\" wallet").arg(walletID));
}
m_last_wallet_id = walletID;
}
#endif
message(CMD_REQUEST, QString::fromStdString(strFilteredCmd));
Q_EMIT cmdRequest(cmd, walletID);
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) {
// store current text when start browsing through the history
if (historyPtr == history.size()) {
cmdBeforeBrowsing = ui->lineEdit->text();
}
historyPtr += offset;
if (historyPtr < 0) {
historyPtr = 0;
}
if (historyPtr > history.size()) {
historyPtr = history.size();
}
QString cmd;
if (historyPtr < history.size()) {
cmd = history.at(historyPtr);
} else if (!cmdBeforeBrowsing.isNull()) {
cmd = cmdBeforeBrowsing;
}
ui->lineEdit->setText(cmd);
}
void RPCConsole::startExecutor() {
RPCExecutor *executor = new RPCExecutor(m_node);
executor->moveToThread(&thread);
// Replies from executor object must go to this object
connect(executor, SIGNAL(reply(int, QString)), this,
SLOT(message(int, QString)));
// Requests from this object must go to executor
connect(this, SIGNAL(cmdRequest(QString, QString)), executor,
SLOT(request(QString, QString)));
// On stopExecutor signal
// - quit the Qt event loop in the execution thread
connect(this, SIGNAL(stopExecutor()), &thread, SLOT(quit()));
// - queue executor for deletion (in execution thread)
connect(&thread, SIGNAL(finished()), executor, SLOT(deleteLater()),
Qt::DirectConnection);
// Default implementation of QThread::run() simply spins up an event loop in
// the thread, which is what we want.
thread.start();
}
void RPCConsole::on_tabWidget_currentChanged(int index) {
if (ui->tabWidget->widget(index) == ui->tab_console) {
ui->lineEdit->setFocus();
} else if (ui->tabWidget->widget(index) != ui->tab_peers) {
clearSelectedNode();
}
}
void RPCConsole::on_openDebugLogfileButton_clicked() {
GUIUtil::openDebugLogfile();
}
void RPCConsole::scrollToEnd() {
QScrollBar *scrollbar = ui->messagesWidget->verticalScrollBar();
scrollbar->setValue(scrollbar->maximum());
}
void RPCConsole::on_sldGraphRange_valueChanged(int value) {
const int multiplier = 5; // each position on the slider represents 5 min
int mins = value * multiplier;
setTrafficGraphRange(mins);
}
void RPCConsole::setTrafficGraphRange(int mins) {
ui->trafficGraph->setGraphRangeMins(mins);
ui->lblGraphRange->setText(GUIUtil::formatDurationStr(mins * 60));
}
void RPCConsole::updateTrafficStats(quint64 totalBytesIn,
quint64 totalBytesOut) {
ui->lblBytesIn->setText(GUIUtil::formatBytes(totalBytesIn));
ui->lblBytesOut->setText(GUIUtil::formatBytes(totalBytesOut));
}
void RPCConsole::peerSelected(const QItemSelection &selected,
const QItemSelection &deselected) {
Q_UNUSED(deselected);
if (!clientModel || !clientModel->getPeerTableModel() ||
selected.indexes().isEmpty())
return;
const CNodeCombinedStats *stats =
clientModel->getPeerTableModel()->getNodeStats(
selected.indexes().first().row());
if (stats) updateNodeDetail(stats);
}
void RPCConsole::peerLayoutAboutToChange() {
QModelIndexList selected =
ui->peerWidget->selectionModel()->selectedIndexes();
cachedNodeids.clear();
for (int i = 0; i < selected.size(); i++) {
const CNodeCombinedStats *stats =
clientModel->getPeerTableModel()->getNodeStats(
selected.at(i).row());
cachedNodeids.append(stats->nodeStats.nodeid);
}
}
void RPCConsole::peerLayoutChanged() {
if (!clientModel || !clientModel->getPeerTableModel()) {
return;
}
const CNodeCombinedStats *stats = nullptr;
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;
}
// get fresh stats on the detail node.
stats = clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
}
if (fUnselect && selectedRow >= 0) {
clearSelectedNode();
}
if (fReselect) {
for (int i = 0; i < cachedNodeids.size(); i++) {
ui->peerWidget->selectRow(
clientModel->getPeerTableModel()->getRowByNodeId(
cachedNodeids.at(i)));
}
}
if (stats) updateNodeDetail(stats);
}
void RPCConsole::updateNodeDetail(const CNodeCombinedStats *stats) {
// update the detail ui with latest node information
QString peerAddrDetails(QString::fromStdString(stats->nodeStats.addrName) +
" ");
peerAddrDetails +=
tr("(node id: %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);
ui->peerServices->setText(
GUIUtil::formatServicesStr(stats->nodeStats.nServices));
ui->peerLastSend->setText(
stats->nodeStats.nLastSend
? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() -
stats->nodeStats.nLastSend)
: tr("never"));
ui->peerLastRecv->setText(
stats->nodeStats.nLastRecv
? GUIUtil::formatDurationStr(GetSystemTimeInSeconds() -
stats->nodeStats.nLastRecv)
: tr("never"));
ui->peerBytesSent->setText(
GUIUtil::formatBytes(stats->nodeStats.nSendBytes));
ui->peerBytesRecv->setText(
GUIUtil::formatBytes(stats->nodeStats.nRecvBytes));
ui->peerConnTime->setText(GUIUtil::formatDurationStr(
GetSystemTimeInSeconds() - stats->nodeStats.nTimeConnected));
ui->peerPingTime->setText(
GUIUtil::formatPingTime(stats->nodeStats.dPingTime));
ui->peerPingWait->setText(
GUIUtil::formatPingTime(stats->nodeStats.dPingWait));
ui->peerMinPing->setText(
GUIUtil::formatPingTime(stats->nodeStats.dMinPing));
ui->timeoffset->setText(
GUIUtil::formatTimeOffset(stats->nodeStats.nTimeOffset));
ui->peerVersion->setText(
QString("%1").arg(QString::number(stats->nodeStats.nVersion)));
ui->peerSubversion->setText(
QString::fromStdString(stats->nodeStats.cleanSubVer));
ui->peerDirection->setText(stats->nodeStats.fInbound ? tr("Inbound")
: tr("Outbound"));
ui->peerHeight->setText(
QString("%1").arg(QString::number(stats->nodeStats.nStartingHeight)));
ui->peerWhitelisted->setText(stats->nodeStats.fWhitelisted ? tr("Yes")
: tr("No"));
// This check fails for example if the lock was busy and
// nodeStateStats couldn't be fetched.
if (stats->fNodeStateStatsAvailable) {
// Ban score is init to 0
ui->peerBanScore->setText(
QString("%1").arg(stats->nodeStateStats.nMisbehavior));
// Sync height is init to -1
if (stats->nodeStateStats.nSyncHeight > -1) {
ui->peerSyncHeight->setText(
QString("%1").arg(stats->nodeStateStats.nSyncHeight));
} else {
ui->peerSyncHeight->setText(tr("Unknown"));
}
// Common height is init to -1
if (stats->nodeStateStats.nCommonHeight > -1) {
ui->peerCommonHeight->setText(
QString("%1").arg(stats->nodeStateStats.nCommonHeight));
} else {
ui->peerCommonHeight->setText(tr("Unknown"));
}
}
ui->detailWidget->show();
}
void RPCConsole::resizeEvent(QResizeEvent *event) {
QWidget::resizeEvent(event);
}
void RPCConsole::showEvent(QShowEvent *event) {
QWidget::showEvent(event);
if (!clientModel || !clientModel->getPeerTableModel()) {
return;
}
// start PeerTableModel auto refresh
clientModel->getPeerTableModel()->startAutoRefresh();
}
void RPCConsole::hideEvent(QHideEvent *event) {
QWidget::hideEvent(event);
if (!clientModel || !clientModel->getPeerTableModel()) {
return;
}
// stop PeerTableModel auto refresh
clientModel->getPeerTableModel()->stopAutoRefresh();
}
void RPCConsole::showPeersTableContextMenu(const QPoint &point) {
QModelIndex index = ui->peerWidget->indexAt(point);
if (index.isValid()) peersTableContextMenu->exec(QCursor::pos());
}
void RPCConsole::showBanTableContextMenu(const QPoint &point) {
QModelIndex index = ui->banlistWidget->indexAt(point);
if (index.isValid()) banTableContextMenu->exec(QCursor::pos());
}
void RPCConsole::disconnectSelectedNode() {
// Get selected peer addresses
QList<QModelIndex> nodes =
GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
for (int i = 0; i < nodes.count(); i++) {
// Get currently selected peer address
NodeId id = nodes.at(i).data().toLongLong();
// Find the node, disconnect it and clear the selected node
if (m_node.disconnect(id)) {
clearSelectedNode();
}
}
}
void RPCConsole::banSelectedNode(int bantime) {
if (!clientModel) {
return;
}
// Get selected peer addresses
QList<QModelIndex> nodes =
GUIUtil::getEntryData(ui->peerWidget, PeerTableModel::NetNodeId);
for (int i = 0; i < nodes.count(); i++) {
// Get currently selected peer address
NodeId id = nodes.at(i).data().toLongLong();
// Get currently selected peer address
int detailNodeRow =
clientModel->getPeerTableModel()->getRowByNodeId(id);
if (detailNodeRow < 0) {
return;
}
// Find possible nodes, ban it and clear the selected node
const CNodeCombinedStats *stats =
clientModel->getPeerTableModel()->getNodeStats(detailNodeRow);
if (stats) {
m_node.ban(stats->nodeStats.addr, BanReasonManuallyAdded, bantime);
m_node.disconnect(stats->nodeStats.addr);
}
}
clearSelectedNode();
clientModel->getBanTableModel()->refresh();
}
void RPCConsole::unbanSelectedNode() {
if (!clientModel) {
return;
}
// Get selected ban addresses
QList<QModelIndex> nodes =
GUIUtil::getEntryData(ui->banlistWidget, BanTableModel::Address);
for (int i = 0; i < nodes.count(); i++) {
// Get currently selected ban address
QString strNode = nodes.at(i).data().toString();
CSubNet possibleSubnet;
LookupSubNet(strNode.toStdString().c_str(), possibleSubnet);
if (possibleSubnet.IsValid() && m_node.unban(possibleSubnet)) {
clientModel->getBanTableModel()->refresh();
}
}
}
void RPCConsole::clearSelectedNode() {
ui->peerWidget->selectionModel()->clearSelection();
cachedNodeids.clear();
ui->detailWidget->hide();
ui->peerHeading->setText(tr("Select a peer to view detailed information."));
}
void RPCConsole::showOrHideBanTableIfRequired() {
if (!clientModel) {
return;
}
bool visible = clientModel->getBanTableModel()->shouldShow();
ui->banlistWidget->setVisible(visible);
ui->banHeading->setVisible(visible);
}
void RPCConsole::setTabFocus(enum TabTypes tabType) {
ui->tabWidget->setCurrentIndex(tabType);
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Mar 2, 12:16 (20 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5187817
Default Alt Text
(116 KB)

Event Timeline