mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-05 10:17:30 -05:00
![Samuel Dobson](/assets/img/avatar_default.png)
764bfe4cba
[psbt] add file size limit (Sjors Provoost)1cd8dc2556
[gui] load PSBT (Sjors Provoost)f6895301f7
[gui] save PSBT to file (Sjors Provoost)1d05a9d80b
Move DEFAULT_MAX_RAW_TX_FEE_RATE to node/transaction.h (Sjors Provoost)86e22d23bb
[util] GetFileSize (Sjors Provoost)6ab3aad9a5
[gui] send dialog: split on_sendButton_clicked (Sjors Provoost) Pull request description: This adds: * a dialog after Create Unsigned, which lets you save a PSBT file in binary format, e.g. to an SD card * a "Load PSBT" menu entry lets you pick a PSBT file. We broadcast the transaction if complete ## Save flow <img width="482" alt="Schermafbeelding 2020-01-04 om 20 39 34" src="https://user-images.githubusercontent.com/10217/71765684-ba60d580-2f32-11ea-8dea-0c4398eb6e15.png"> <img width="287" alt="Schermafbeelding 2020-01-04 om 20 40 35" src="https://user-images.githubusercontent.com/10217/71765677-a0bf8e00-2f32-11ea-8172-12dfd34a89f3.png"> <img width="594" alt="Schermafbeelding 2020-01-04 om 20 41 12" src="https://user-images.githubusercontent.com/10217/71765681-aa48f600-2f32-11ea-8e2c-c4f6bf9f5309.png"> <img width="632" alt="Schermafbeelding 2020-01-04 om 20 41 28" src="https://user-images.githubusercontent.com/10217/71765691-d19fc300-2f32-11ea-97ff-70f5dd59987a.png"> By default the file name contains the destination address(es) and amount(s). We only use the binary format for files, in order to avoid compatibility hell. If we do want to add base64 file format support, we should use a different extension for that (`.psbt64`?). ## Load flow Select a file: <img width="649" alt="Schermafbeelding 2020-01-04 om 21 08 57" src="https://user-images.githubusercontent.com/10217/71766089-2ba28780-2f37-11ea-875d-074794b5707d.png"> Offer to send if complete: <img width="308" alt="Schermafbeelding 2020-01-04 om 21 09 06" src="https://user-images.githubusercontent.com/10217/71766088-2a715a80-2f37-11ea-807d-394c8b840c59.png"> Tell user if signatures are missing, offer to copy to clipboard: <img width="308" alt="Schermafbeelding 2020-01-04 om 21 15 57" src="https://user-images.githubusercontent.com/10217/71766115-702e2300-2f37-11ea-9f62-a6ede499c0fa.png"> Incomplete for another reason: <img width="309" alt="Schermafbeelding 2020-01-04 om 21 07 51" src="https://user-images.githubusercontent.com/10217/71766090-2c3b1e00-2f37-11ea-8a22-6188377b67a1.png"> ACKs for top commit: instagibbs: re-ACK764bfe4cba
achow101: ACK764bfe4cba
jb55: Tested ACK764bfe4cba
jonatack: ACK764bfe4c
promag: Code review ACK764bfe4cba
. Tree-SHA512: d284ed6895f3a271fb8ff879aac388ad217ddc13f72074725608e1c3d6d90650f6dc9e9e254479544dd71fc111516b02c8ff92158153208dc40fb2726b37d063
1473 lines
53 KiB
C++
1473 lines
53 KiB
C++
// Copyright (c) 2011-2020 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <qt/bitcoingui.h>
|
|
|
|
#include <qt/bitcoinunits.h>
|
|
#include <qt/clientmodel.h>
|
|
#include <qt/createwalletdialog.h>
|
|
#include <qt/guiconstants.h>
|
|
#include <qt/guiutil.h>
|
|
#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/walletcontroller.h>
|
|
#include <qt/walletframe.h>
|
|
#include <qt/walletmodel.h>
|
|
#include <qt/walletview.h>
|
|
#endif // ENABLE_WALLET
|
|
|
|
#ifdef Q_OS_MAC
|
|
#include <qt/macdockiconhandler.h>
|
|
#endif
|
|
|
|
#include <chain.h>
|
|
#include <chainparams.h>
|
|
#include <interfaces/handler.h>
|
|
#include <interfaces/node.h>
|
|
#include <ui_interface.h>
|
|
#include <util/system.h>
|
|
|
|
#include <QAction>
|
|
#include <QApplication>
|
|
#include <QComboBox>
|
|
#include <QDateTime>
|
|
#include <QDragEnterEvent>
|
|
#include <QListWidget>
|
|
#include <QMenu>
|
|
#include <QMenuBar>
|
|
#include <QMessageBox>
|
|
#include <QMimeData>
|
|
#include <QProgressDialog>
|
|
#include <QScreen>
|
|
#include <QSettings>
|
|
#include <QShortcut>
|
|
#include <QStackedWidget>
|
|
#include <QStatusBar>
|
|
#include <QStyle>
|
|
#include <QSystemTrayIcon>
|
|
#include <QTimer>
|
|
#include <QToolBar>
|
|
#include <QUrlQuery>
|
|
#include <QVBoxLayout>
|
|
#include <QWindow>
|
|
|
|
|
|
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 PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) :
|
|
QMainWindow(parent),
|
|
m_node(node),
|
|
trayIconMenu{new QMenu()},
|
|
platformStyle(_platformStyle),
|
|
m_network_style(networkStyle)
|
|
{
|
|
QSettings settings;
|
|
if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) {
|
|
// Restore failed (perhaps missing setting), center the window
|
|
move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center());
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
enableWallet = WalletModel::isWalletEnabled();
|
|
#endif // ENABLE_WALLET
|
|
QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon());
|
|
setWindowIcon(m_network_style->getTrayAndWindowIcon());
|
|
updateWindowTitle();
|
|
|
|
rpcConsole = new RPCConsole(node, _platformStyle, nullptr);
|
|
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);
|
|
Q_EMIT consoleShown(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
|
|
if (QSystemTrayIcon::isSystemTrayAvailable()) {
|
|
createTrayIcon();
|
|
}
|
|
notificator = new Notificator(QApplication::applicationName(), trayIcon, this);
|
|
|
|
// 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();
|
|
labelProxyIcon = new GUIUtil::ClickableLabel();
|
|
connectionsControl = new GUIUtil::ClickableLabel();
|
|
labelBlocksIcon = new GUIUtil::ClickableLabel();
|
|
if(enableWallet)
|
|
{
|
|
frameBlocksLayout->addStretch();
|
|
frameBlocksLayout->addWidget(unitDisplayControl);
|
|
frameBlocksLayout->addStretch();
|
|
frameBlocksLayout->addWidget(labelWalletEncryptionIcon);
|
|
frameBlocksLayout->addWidget(labelWalletHDStatusIcon);
|
|
}
|
|
frameBlocksLayout->addWidget(labelProxyIcon);
|
|
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, &GUIUtil::ClickableLabel::clicked, [this] {
|
|
m_node.setNetworkActive(!m_node.getNetworkActive());
|
|
});
|
|
connect(labelProxyIcon, &GUIUtil::ClickableLabel::clicked, [this] {
|
|
openOptionsDialogWithTab(OptionsDialog::TAB_NETWORK);
|
|
});
|
|
|
|
modalOverlay = new ModalOverlay(enableWallet, this->centralWidget());
|
|
connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay);
|
|
connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay);
|
|
#ifdef ENABLE_WALLET
|
|
if(enableWallet) {
|
|
connect(walletFrame, &WalletFrame::requestedSyncWarningInfo, this, &BitcoinGUI::showModalOverlay);
|
|
}
|
|
#endif
|
|
|
|
#ifdef Q_OS_MAC
|
|
m_app_nap_inhibitor = new CAppNapInhibitor;
|
|
#endif
|
|
}
|
|
|
|
BitcoinGUI::~BitcoinGUI()
|
|
{
|
|
// Unsubscribe from notifications from core
|
|
unsubscribeFromCoreSignals();
|
|
|
|
QSettings settings;
|
|
settings.setValue("MainWindowGeometry", saveGeometry());
|
|
if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
|
|
trayIcon->hide();
|
|
#ifdef Q_OS_MAC
|
|
delete m_app_nap_inhibitor;
|
|
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(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 bitcoin: URIs)"));
|
|
receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
|
|
receiveCoinsAction->setCheckable(true);
|
|
receiveCoinsAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_3));
|
|
tabGroup->addAction(receiveCoinsAction);
|
|
|
|
receiveCoinsMenuAction = new QAction(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, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage);
|
|
connect(sendCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(sendCoinsAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); });
|
|
connect(sendCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(sendCoinsMenuAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); });
|
|
connect(receiveCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage);
|
|
connect(receiveCoinsMenuAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage);
|
|
connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage);
|
|
#endif // ENABLE_WALLET
|
|
|
|
quitAction = new QAction(tr("E&xit"), this);
|
|
quitAction->setStatusTip(tr("Quit application"));
|
|
quitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
|
|
quitAction->setMenuRole(QAction::QuitRole);
|
|
aboutAction = new QAction(tr("&About %1").arg(PACKAGE_NAME), this);
|
|
aboutAction->setStatusTip(tr("Show information about %1").arg(PACKAGE_NAME));
|
|
aboutAction->setMenuRole(QAction::AboutRole);
|
|
aboutAction->setEnabled(false);
|
|
aboutQtAction = new QAction(tr("About &Qt"), this);
|
|
aboutQtAction->setStatusTip(tr("Show information about Qt"));
|
|
aboutQtAction->setMenuRole(QAction::AboutQtRole);
|
|
optionsAction = new QAction(tr("&Options..."), this);
|
|
optionsAction->setStatusTip(tr("Modify configuration options for %1").arg(PACKAGE_NAME));
|
|
optionsAction->setMenuRole(QAction::PreferencesRole);
|
|
optionsAction->setEnabled(false);
|
|
toggleHideAction = new QAction(tr("&Show / Hide"), this);
|
|
toggleHideAction->setStatusTip(tr("Show or hide the main Window"));
|
|
|
|
encryptWalletAction = new QAction(tr("&Encrypt Wallet..."), this);
|
|
encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet"));
|
|
encryptWalletAction->setCheckable(true);
|
|
backupWalletAction = new QAction(tr("&Backup Wallet..."), this);
|
|
backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
|
|
changePassphraseAction = new QAction(tr("&Change Passphrase..."), this);
|
|
changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
|
|
signMessageAction = new QAction(tr("Sign &message..."), this);
|
|
signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
|
|
verifyMessageAction = new QAction(tr("&Verify message..."), this);
|
|
verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
|
|
m_load_psbt_action = new QAction(tr("Load PSBT..."), this);
|
|
m_load_psbt_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction"));
|
|
|
|
openRPCConsoleAction = new QAction(tr("Node window"), this);
|
|
openRPCConsoleAction->setStatusTip(tr("Open node debugging and diagnostic console"));
|
|
// initially disable the debug window menu item
|
|
openRPCConsoleAction->setEnabled(false);
|
|
openRPCConsoleAction->setObjectName("openRPCConsoleAction");
|
|
|
|
usedSendingAddressesAction = new QAction(tr("&Sending addresses"), this);
|
|
usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels"));
|
|
usedReceivingAddressesAction = new QAction(tr("&Receiving addresses"), this);
|
|
usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels"));
|
|
|
|
openAction = new QAction(tr("Open &URI..."), this);
|
|
openAction->setStatusTip(tr("Open a bitcoin: URI"));
|
|
|
|
m_open_wallet_action = new QAction(tr("Open Wallet"), this);
|
|
m_open_wallet_action->setEnabled(false);
|
|
m_open_wallet_action->setStatusTip(tr("Open a wallet"));
|
|
m_open_wallet_menu = new QMenu(this);
|
|
|
|
m_close_wallet_action = new QAction(tr("Close Wallet..."), this);
|
|
m_close_wallet_action->setStatusTip(tr("Close wallet"));
|
|
|
|
m_create_wallet_action = new QAction(tr("Create Wallet..."), this);
|
|
m_create_wallet_action->setEnabled(false);
|
|
m_create_wallet_action->setStatusTip(tr("Create a new wallet"));
|
|
|
|
showHelpMessageAction = new QAction(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(PACKAGE_NAME));
|
|
|
|
connect(quitAction, &QAction::triggered, qApp, QApplication::quit);
|
|
connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked);
|
|
connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt);
|
|
connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked);
|
|
connect(toggleHideAction, &QAction::triggered, this, &BitcoinGUI::toggleHidden);
|
|
connect(showHelpMessageAction, &QAction::triggered, this, &BitcoinGUI::showHelpMessageClicked);
|
|
connect(openRPCConsoleAction, &QAction::triggered, this, &BitcoinGUI::showDebugWindow);
|
|
// prevents an open debug window from becoming stuck/unusable on client shutdown
|
|
connect(quitAction, &QAction::triggered, rpcConsole, &QWidget::hide);
|
|
|
|
#ifdef ENABLE_WALLET
|
|
if(walletFrame)
|
|
{
|
|
connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet);
|
|
connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet);
|
|
connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase);
|
|
connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); });
|
|
connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); });
|
|
connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
|
|
connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); });
|
|
connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses);
|
|
connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses);
|
|
connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked);
|
|
connect(m_open_wallet_menu, &QMenu::aboutToShow, [this] {
|
|
m_open_wallet_menu->clear();
|
|
for (const std::pair<const std::string, bool>& i : m_wallet_controller->listWalletDir()) {
|
|
const std::string& path = i.first;
|
|
QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path);
|
|
// Menu items remove single &. Single & are shown when && is in
|
|
// the string, but only the first occurrence. So replace only
|
|
// the first & with &&.
|
|
name.replace(name.indexOf(QChar('&')), 1, QString("&&"));
|
|
QAction* action = m_open_wallet_menu->addAction(name);
|
|
|
|
if (i.second) {
|
|
// This wallet is already loaded
|
|
action->setEnabled(false);
|
|
continue;
|
|
}
|
|
|
|
connect(action, &QAction::triggered, [this, path] {
|
|
auto activity = new OpenWalletActivity(m_wallet_controller, this);
|
|
connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet);
|
|
connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater);
|
|
activity->open(path);
|
|
});
|
|
}
|
|
if (m_open_wallet_menu->isEmpty()) {
|
|
QAction* action = m_open_wallet_menu->addAction(tr("No wallets available"));
|
|
action->setEnabled(false);
|
|
}
|
|
});
|
|
connect(m_close_wallet_action, &QAction::triggered, [this] {
|
|
m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this);
|
|
});
|
|
connect(m_create_wallet_action, &QAction::triggered, [this] {
|
|
auto activity = new CreateWalletActivity(m_wallet_controller, this);
|
|
connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet);
|
|
connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater);
|
|
activity->create();
|
|
});
|
|
}
|
|
#endif // ENABLE_WALLET
|
|
|
|
connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindowActivateConsole);
|
|
connect(new QShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::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(m_create_wallet_action);
|
|
file->addAction(m_open_wallet_action);
|
|
file->addAction(m_close_wallet_action);
|
|
file->addSeparator();
|
|
file->addAction(openAction);
|
|
file->addAction(backupWalletAction);
|
|
file->addAction(signMessageAction);
|
|
file->addAction(verifyMessageAction);
|
|
file->addAction(m_load_psbt_action);
|
|
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* window_menu = appMenuBar->addMenu(tr("&Window"));
|
|
|
|
QAction* minimize_action = window_menu->addAction(tr("Minimize"));
|
|
minimize_action->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_M));
|
|
connect(minimize_action, &QAction::triggered, [] {
|
|
QApplication::activeWindow()->showMinimized();
|
|
});
|
|
connect(qApp, &QApplication::focusWindowChanged, [minimize_action] (QWindow* window) {
|
|
minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized);
|
|
});
|
|
|
|
#ifdef Q_OS_MAC
|
|
QAction* zoom_action = window_menu->addAction(tr("Zoom"));
|
|
connect(zoom_action, &QAction::triggered, [] {
|
|
QWindow* window = qApp->focusWindow();
|
|
if (window->windowState() != Qt::WindowMaximized) {
|
|
window->showMaximized();
|
|
} else {
|
|
window->showNormal();
|
|
}
|
|
});
|
|
|
|
connect(qApp, &QApplication::focusWindowChanged, [zoom_action] (QWindow* window) {
|
|
zoom_action->setEnabled(window != nullptr);
|
|
});
|
|
#endif
|
|
|
|
if (walletFrame) {
|
|
#ifdef Q_OS_MAC
|
|
window_menu->addSeparator();
|
|
QAction* main_window_action = window_menu->addAction(tr("Main Window"));
|
|
connect(main_window_action, &QAction::triggered, [this] {
|
|
GUIUtil::bringToFront(this);
|
|
});
|
|
#endif
|
|
window_menu->addSeparator();
|
|
window_menu->addAction(usedSendingAddressesAction);
|
|
window_menu->addAction(usedReceivingAddressesAction);
|
|
}
|
|
|
|
window_menu->addSeparator();
|
|
for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) {
|
|
QAction* tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type));
|
|
tab_action->setShortcut(rpcConsole->tabShortcut(tab_type));
|
|
connect(tab_action, &QAction::triggered, [this, tab_type] {
|
|
rpcConsole->setTabFocus(tab_type);
|
|
showDebugWindow();
|
|
});
|
|
}
|
|
|
|
QMenu *help = appMenuBar->addMenu(tr("&Help"));
|
|
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();
|
|
m_wallet_selector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
|
|
connect(m_wallet_selector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex);
|
|
|
|
m_wallet_selector_label = new QLabel();
|
|
m_wallet_selector_label->setText(tr("Wallet:") + " ");
|
|
m_wallet_selector_label->setBuddy(m_wallet_selector);
|
|
|
|
m_wallet_selector_label_action = appToolBar->addWidget(m_wallet_selector_label);
|
|
m_wallet_selector_action = appToolBar->addWidget(m_wallet_selector);
|
|
|
|
m_wallet_selector_label_action->setVisible(false);
|
|
m_wallet_selector_action->setVisible(false);
|
|
#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, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections);
|
|
connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive);
|
|
|
|
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, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks);
|
|
|
|
// Receive and report messages from client model
|
|
connect(_clientModel, &ClientModel::message, [this](const QString &title, const QString &message, unsigned int style){
|
|
this->message(title, message, style);
|
|
});
|
|
|
|
// Show progress dialog
|
|
connect(_clientModel, &ClientModel::showProgress, this, &BitcoinGUI::showProgress);
|
|
|
|
rpcConsole->setClientModel(_clientModel);
|
|
|
|
updateProxyIcon();
|
|
|
|
#ifdef ENABLE_WALLET
|
|
if(walletFrame)
|
|
{
|
|
walletFrame->setClientModel(_clientModel);
|
|
}
|
|
#endif // ENABLE_WALLET
|
|
unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel());
|
|
|
|
OptionsModel* optionsModel = _clientModel->getOptionsModel();
|
|
if (optionsModel && trayIcon) {
|
|
// be aware of the tray icon disable state change reported by the OptionsModel object.
|
|
connect(optionsModel, &OptionsModel::hideTrayIconChanged, this, &BitcoinGUI::setTrayIconVisible);
|
|
|
|
// 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
|
|
void BitcoinGUI::setWalletController(WalletController* wallet_controller)
|
|
{
|
|
assert(!m_wallet_controller);
|
|
assert(wallet_controller);
|
|
|
|
m_wallet_controller = wallet_controller;
|
|
|
|
m_create_wallet_action->setEnabled(true);
|
|
m_open_wallet_action->setEnabled(true);
|
|
m_open_wallet_action->setMenu(m_open_wallet_menu);
|
|
|
|
connect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
|
|
connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
|
|
|
|
for (WalletModel* wallet_model : m_wallet_controller->getOpenWallets()) {
|
|
addWallet(wallet_model);
|
|
}
|
|
}
|
|
|
|
void BitcoinGUI::addWallet(WalletModel* walletModel)
|
|
{
|
|
if (!walletFrame) return;
|
|
if (!walletFrame->addWallet(walletModel)) return;
|
|
const QString display_name = walletModel->getDisplayName();
|
|
setWalletActionsEnabled(true);
|
|
rpcConsole->addWallet(walletModel);
|
|
m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel));
|
|
if (m_wallet_selector->count() == 2) {
|
|
m_wallet_selector_label_action->setVisible(true);
|
|
m_wallet_selector_action->setVisible(true);
|
|
}
|
|
}
|
|
|
|
void BitcoinGUI::removeWallet(WalletModel* walletModel)
|
|
{
|
|
if (!walletFrame) return;
|
|
|
|
labelWalletHDStatusIcon->hide();
|
|
labelWalletEncryptionIcon->hide();
|
|
|
|
int index = m_wallet_selector->findData(QVariant::fromValue(walletModel));
|
|
m_wallet_selector->removeItem(index);
|
|
if (m_wallet_selector->count() == 0) {
|
|
setWalletActionsEnabled(false);
|
|
} else if (m_wallet_selector->count() == 1) {
|
|
m_wallet_selector_label_action->setVisible(false);
|
|
m_wallet_selector_action->setVisible(false);
|
|
}
|
|
rpcConsole->removeWallet(walletModel);
|
|
walletFrame->removeWallet(walletModel);
|
|
updateWindowTitle();
|
|
}
|
|
|
|
void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
|
|
{
|
|
if (!walletFrame) return;
|
|
walletFrame->setCurrentWallet(wallet_model);
|
|
for (int index = 0; index < m_wallet_selector->count(); ++index) {
|
|
if (m_wallet_selector->itemData(index).value<WalletModel*>() == wallet_model) {
|
|
m_wallet_selector->setCurrentIndex(index);
|
|
break;
|
|
}
|
|
}
|
|
updateWindowTitle();
|
|
}
|
|
|
|
void BitcoinGUI::setCurrentWalletBySelectorIndex(int index)
|
|
{
|
|
WalletModel* wallet_model = m_wallet_selector->itemData(index).value<WalletModel*>();
|
|
if (wallet_model) setCurrentWallet(wallet_model);
|
|
}
|
|
|
|
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);
|
|
m_close_wallet_action->setEnabled(enabled);
|
|
}
|
|
|
|
void BitcoinGUI::createTrayIcon()
|
|
{
|
|
assert(QSystemTrayIcon::isSystemTrayAvailable());
|
|
|
|
#ifndef Q_OS_MAC
|
|
if (QSystemTrayIcon::isSystemTrayAvailable()) {
|
|
trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this);
|
|
QString toolTip = tr("%1 client").arg(PACKAGE_NAME) + " " + m_network_style->getTitleAddText();
|
|
trayIcon->setToolTip(toolTip);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void BitcoinGUI::createTrayIconMenu()
|
|
{
|
|
#ifndef Q_OS_MAC
|
|
// return if trayIcon is unset (only on non-macOSes)
|
|
if (!trayIcon)
|
|
return;
|
|
|
|
trayIcon->setContextMenu(trayIconMenu.get());
|
|
connect(trayIcon, &QSystemTrayIcon::activated, this, &BitcoinGUI::trayIconActivated);
|
|
#else
|
|
// Note: On macOS, the Dock icon is used to provide the tray's functionality.
|
|
MacDockIconHandler *dockIconHandler = MacDockIconHandler::instance();
|
|
connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, this, &BitcoinGUI::macosDockIconActivated);
|
|
trayIconMenu->setAsDockMenu();
|
|
#endif
|
|
|
|
// Configuration of the tray icon (or Dock icon) menu
|
|
#ifndef Q_OS_MAC
|
|
// Note: On macOS, the Dock icon's menu already has Show / Hide action.
|
|
trayIconMenu->addAction(toggleHideAction);
|
|
trayIconMenu->addSeparator();
|
|
#endif
|
|
if (enableWallet) {
|
|
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 macOS
|
|
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();
|
|
}
|
|
}
|
|
#else
|
|
void BitcoinGUI::macosDockIconActivated()
|
|
{
|
|
show();
|
|
activateWindow();
|
|
}
|
|
#endif
|
|
|
|
void BitcoinGUI::optionsClicked()
|
|
{
|
|
openOptionsDialogWithTab(OptionsDialog::TAB_MAIN);
|
|
}
|
|
|
|
void BitcoinGUI::aboutClicked()
|
|
{
|
|
if(!clientModel)
|
|
return;
|
|
|
|
HelpMessageDialog dlg(m_node, this, true);
|
|
dlg.exec();
|
|
}
|
|
|
|
void BitcoinGUI::showDebugWindow()
|
|
{
|
|
GUIUtil::bringToFront(rpcConsole);
|
|
Q_EMIT consoleShown(rpcConsole);
|
|
}
|
|
|
|
void BitcoinGUI::showDebugWindowActivateConsole()
|
|
{
|
|
rpcConsole->setTabFocus(RPCConsole::TabTypes::CONSOLE);
|
|
showDebugWindow();
|
|
}
|
|
|
|
void BitcoinGUI::showHelpMessageClicked()
|
|
{
|
|
helpMessageDialog->show();
|
|
}
|
|
|
|
#ifdef ENABLE_WALLET
|
|
void BitcoinGUI::openClicked()
|
|
{
|
|
OpenURIDialog dlg(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);
|
|
}
|
|
void BitcoinGUI::gotoLoadPSBT()
|
|
{
|
|
if (walletFrame) walletFrame->gotoLoadPSBT();
|
|
}
|
|
#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) / Params().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::openOptionsDialogWithTab(OptionsDialog::Tab tab)
|
|
{
|
|
if (!clientModel || !clientModel->getOptionsModel())
|
|
return;
|
|
|
|
OptionsDialog dlg(this, enableWallet);
|
|
dlg.setCurrentTab(tab);
|
|
dlg.setModel(clientModel->getOptionsModel());
|
|
dlg.exec();
|
|
}
|
|
|
|
void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool header)
|
|
{
|
|
// Disabling macOS App Nap on initial sync, disk and reindex operations.
|
|
#ifdef Q_OS_MAC
|
|
(m_node.isInitialBlockDownload() || m_node.getReindex() || m_node.getImporting()) ? m_app_nap_inhibitor->disableAppNap() : m_app_nap_inhibitor->enableAppNap();
|
|
#endif
|
|
|
|
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, QString message, unsigned int style, bool* ret)
|
|
{
|
|
// Default title. On macOS, the window title is ignored (as required by the macOS Guidelines).
|
|
QString strTitle{PACKAGE_NAME};
|
|
// Default to information icon
|
|
int nMBoxIcon = QMessageBox::Information;
|
|
int nNotifyIcon = Notificator::Information;
|
|
|
|
bool prefix = !(style & CClientUIInterface::MSG_NOPREFIX);
|
|
style &= ~CClientUIInterface::MSG_NOPREFIX;
|
|
|
|
QString msgType;
|
|
if (!title.isEmpty()) {
|
|
msgType = title;
|
|
} else {
|
|
switch (style) {
|
|
case CClientUIInterface::MSG_ERROR:
|
|
msgType = tr("Error");
|
|
if (prefix) message = tr("Error: %1").arg(message);
|
|
break;
|
|
case CClientUIInterface::MSG_WARNING:
|
|
msgType = tr("Warning");
|
|
if (prefix) message = tr("Warning: %1").arg(message);
|
|
break;
|
|
case CClientUIInterface::MSG_INFORMATION:
|
|
msgType = tr("Information");
|
|
// No need to prepend the prefix here.
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!msgType.isEmpty()) {
|
|
strTitle += " - " + msgType;
|
|
}
|
|
|
|
if (style & CClientUIInterface::ICON_ERROR) {
|
|
nMBoxIcon = QMessageBox::Critical;
|
|
nNotifyIcon = Notificator::Critical;
|
|
} else if (style & CClientUIInterface::ICON_WARNING) {
|
|
nMBoxIcon = QMessageBox::Warning;
|
|
nNotifyIcon = Notificator::Warning;
|
|
}
|
|
|
|
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);
|
|
mBox.setTextFormat(Qt::PlainText);
|
|
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, &BitcoinGUI::hide);
|
|
e->ignore();
|
|
}
|
|
else if((wsevt->oldState() & Qt::WindowMinimized) && !isMinimized())
|
|
{
|
|
QTimer::singleShot(0, this, &BitcoinGUI::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 CAmount& 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)<0 ? 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(bool privkeyDisabled, int hdEnabled)
|
|
{
|
|
labelWalletHDStatusIcon->setPixmap(platformStyle->SingleColorIcon(privkeyDisabled ? ":/icons/eye" : hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE));
|
|
labelWalletHDStatusIcon->setToolTip(privkeyDisabled ? tr("Private key <b>disabled</b>") : hdEnabled ? tr("HD key generation is <b>enabled</b>") : tr("HD key generation is <b>disabled</b>"));
|
|
labelWalletHDStatusIcon->show();
|
|
// 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().privateKeysDisabled(), walletModel->wallet().hdEnabled());
|
|
}
|
|
#endif // ENABLE_WALLET
|
|
|
|
void BitcoinGUI::updateProxyIcon()
|
|
{
|
|
std::string ip_port;
|
|
bool proxy_enabled = clientModel->getProxyInfo(ip_port);
|
|
|
|
if (proxy_enabled) {
|
|
if (labelProxyIcon->pixmap() == nullptr) {
|
|
QString ip_port_q = QString::fromStdString(ip_port);
|
|
labelProxyIcon->setPixmap(platformStyle->SingleColorIcon(":/icons/proxy").pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE));
|
|
labelProxyIcon->setToolTip(tr("Proxy is <b>enabled</b>: %1").arg(ip_port_q));
|
|
} else {
|
|
labelProxyIcon->show();
|
|
}
|
|
} else {
|
|
labelProxyIcon->hide();
|
|
}
|
|
}
|
|
|
|
void BitcoinGUI::updateWindowTitle()
|
|
{
|
|
QString window_title = PACKAGE_NAME;
|
|
#ifdef ENABLE_WALLET
|
|
if (walletFrame) {
|
|
WalletModel* const wallet_model = walletFrame->currentWalletModel();
|
|
if (wallet_model && !wallet_model->getWalletName().isEmpty()) {
|
|
window_title += " - " + wallet_model->getDisplayName();
|
|
}
|
|
}
|
|
#endif
|
|
if (!m_network_style->getTitleAddText().isEmpty()) {
|
|
window_title += " - " + m_network_style->getTitleAddText();
|
|
}
|
|
setWindowTitle(window_title);
|
|
}
|
|
|
|
void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
|
|
{
|
|
if(!clientModel)
|
|
return;
|
|
|
|
if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) {
|
|
hide();
|
|
} else {
|
|
GUIUtil::bringToFront(this);
|
|
}
|
|
}
|
|
|
|
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, QString(), 0, 100);
|
|
GUIUtil::PolishProgressDialog(progressDialog);
|
|
progressDialog->setWindowModality(Qt::ApplicationModal);
|
|
progressDialog->setMinimumDuration(0);
|
|
progressDialog->setAutoClose(false);
|
|
progressDialog->setValue(0);
|
|
} else if (nProgress == 100) {
|
|
if (progressDialog) {
|
|
progressDialog->close();
|
|
progressDialog->deleteLater();
|
|
progressDialog = nullptr;
|
|
}
|
|
} else if (progressDialog) {
|
|
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
|
|
bool invoked = 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));
|
|
assert(invoked);
|
|
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();
|
|
}
|
|
|
|
UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle *platformStyle) :
|
|
optionsModel(nullptr),
|
|
menu(nullptr)
|
|
{
|
|
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, GUIUtil::TextWidth(fm, BitcoinUnits::longName(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::longName(u)), this);
|
|
menuAction->setData(QVariant(u));
|
|
menu->addAction(menuAction);
|
|
}
|
|
connect(menu, &QMenu::triggered, this, &UnitDisplayStatusBarControl::onMenuSelection);
|
|
}
|
|
|
|
/** 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, &OptionsModel::displayUnitChanged, this, &UnitDisplayStatusBarControl::updateDisplayUnit);
|
|
|
|
// 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::longName(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());
|
|
}
|
|
}
|