diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index c4a57d059..bfe070dd9 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1,1628 +1,1645 @@ // Copyright (c) 2011-2019 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 #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include #include #include #include #include #include #include #include #ifdef ENABLE_WALLET #include #include #include #include #endif // ENABLE_WALLET #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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), m_node(node), trayIconMenu{new QMenu()}, config(configIn), 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(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 GUIUtil::handleCloseWindowShortcut(this); } 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 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 %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(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("&Debug 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 %1: URI or payment request") .arg(QString::fromStdString( config->GetChainParams().CashAddrPrefix()))); 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)); + m_mask_values_action = new QAction(tr("&Mask values"), this); + m_mask_values_action->setShortcut( + QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_M)); + m_mask_values_action->setStatusTip( + tr("Mask the values in the Overview tab")); + m_mask_values_action->setCheckable(true); + 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(verifyMessageAction, &QAction::triggered, [this] { showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this] { gotoVerifyMessageTab(); }); connect(m_load_psbt_action, &QAction::triggered, [this] { gotoLoadPSBT(); }); 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 &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, this->config->GetChainParams()); 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, this->config->GetChainParams()); connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet); connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); activity->create(); }); + + connect(m_mask_values_action, &QAction::toggled, this, + &BitcoinGUI::setPrivacy); } #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(m_mask_values_action); + 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( &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, interfaces::BlockAndHeaderTipInfo *tip_info) { 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( tip_info->header_height, QDateTime::fromTime_t(tip_info->header_time)); setNumBlocks(tip_info->block_height, QDateTime::fromTime_t(tip_info->block_time), tip_info->verification_progress, false, SynchronizationState::INIT_DOWNLOAD); 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, tip_info->block_height, tip_info->block_time, tip_info->verification_progress); 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() == wallet_model) { m_wallet_selector->setCurrentIndex(index); break; } } updateWindowTitle(); } void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) { WalletModel *wallet_model = m_wallet_selector->itemData(index).value(); 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(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(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); } } 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(".
") + tr("Click to disable network activity."); } else { tooltip = tr("Network activity disabled.") + QString("
") + tr("Click to enable network activity again."); icon = ":/icons/network_disabled"; } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("") + tooltip + QString(""); 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::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, SynchronizationState sync_state) { // Disabling macOS App Nap on initial sync, disk and reindex operations. #ifdef Q_OS_MAC if (sync_state == SynchronizationState::POST_INIT) { m_app_nap_inhibitor->enableAppNap(); } else { m_app_nap_inhibitor->disableAppNap(); } #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(".
") + 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("
") + 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("
"); tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText); tooltip += QString("
"); tooltip += tr("Transactions after this will not yet be visible."); } // Don't word-wrap this (fixed-width) tooltip tooltip = QString("") + tooltip + QString(""); labelBlocksIcon->setToolTip(tooltip); progressBarLabel->setToolTip(tooltip); progressBar->setToolTip(tooltip); } void BitcoinGUI::message(const QString &title, QString message, unsigned int style, bool *ret, const QString &detailed_message) { // 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; QString msgType; if (!title.isEmpty()) { msgType = title; } else { switch (style) { case CClientUIInterface::MSG_ERROR: msgType = tr("Error"); message = tr("Error: %1").arg(message); break; case CClientUIInterface::MSG_WARNING: msgType = tr("Warning"); 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(nMBoxIcon), strTitle, message, buttons, this); mBox.setTextFormat(Qt::PlainText); mBox.setDetailedText(detailed_message); int r = mBox.exec(); if (ret != nullptr) { *ret = r == QMessageBox::Ok; } } else { notificator->notify(static_cast(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(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 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(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 disabled") : hdEnabled ? tr("HD key generation is enabled") : tr("HD key generation is disabled")); 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 encrypted and currently unlocked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled(false); break; case WalletModel::Locked: labelWalletEncryptionIcon->show(); labelWalletEncryptionIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/lock_closed") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelWalletEncryptionIcon->setToolTip( tr("Wallet is encrypted and currently locked")); encryptWalletAction->setChecked(true); changePassphraseAction->setEnabled(true); encryptWalletAction->setEnabled(false); 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->hasPixmap()) { QString ip_port_q = QString::fromStdString(ip_port); labelProxyIcon->setPixmap( platformStyle->SingleColorIcon(":/icons/proxy") .pixmap(STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE)); labelProxyIcon->setToolTip( tr("Proxy is enabled: %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 bilingual_str &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; // This is original message, in English, for googling and referencing. QString detailed_message; if (message.original != message.translated) { detailed_message = BitcoinGUI::tr("Original message:") + "\n" + QString::fromStdString(message.original); } // 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.translated)), Q_ARG(unsigned int, style), Q_ARG(bool *, &ret), Q_ARG(QString, detailed_message)); 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(); } +bool BitcoinGUI::isPrivacyModeActivated() const { + assert(m_mask_values_action); + return m_mask_values_action->isChecked(); +} + UnitDisplayStatusBarControl::UnitDisplayStatusBarControl( const PlatformStyle *platformStyle) : optionsModel(nullptr), menu(nullptr) { createContextMenu(); setToolTip(tr("Unit to show amounts in. Click to select another unit.")); QList 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()); } } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index cad44802a..f3a8cbe31 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -1,371 +1,375 @@ // Copyright (c) 2011-2019 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 #endif #include #include #include #include #include #include #include #ifdef Q_OS_MAC #include #endif #include class ClientModel; class Config; class HelpMessageDialog; class ModalOverlay; class NetworkStyle; class Notificator; class OptionsModel; class PlatformStyle; class RPCConsole; class SendCoinsRecipient; enum class SynchronizationState; class UnitDisplayStatusBarControl; class WalletController; class WalletFrame; class WalletModel; namespace interfaces { class Handler; class Node; struct BlockAndHeaderTipInfo; } // namespace interfaces QT_BEGIN_NAMESPACE class QAction; class QComboBox; class QMenu; class QProgressBar; class QProgressDialog; QT_END_NAMESPACE namespace GUIUtil { class ClickableLabel; class ClickableProgressBar; } // namespace GUIUtil /** * 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 = nullptr); ~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 = nullptr, interfaces::BlockAndHeaderTipInfo *tip_info = nullptr); #ifdef ENABLE_WALLET void setWalletController(WalletController *wallet_controller); #endif #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. */ void addWallet(WalletModel *walletModel); void removeWallet(WalletModel *walletModel); void removeAllWallets(); #endif // ENABLE_WALLET bool enableWallet = false; /** Disconnect core signals from GUI client */ void unsubscribeFromCoreSignals(); + bool isPrivacyModeActivated() const; + /** * Get the tray icon status. * Some systems have not "system tray" or "notification area" available. */ bool hasTrayIcon() const { return trayIcon; } 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; WalletController *m_wallet_controller{nullptr}; std::unique_ptr m_handler_message_box; std::unique_ptr m_handler_question; ClientModel *clientModel = nullptr; WalletFrame *walletFrame = nullptr; UnitDisplayStatusBarControl *unitDisplayControl = nullptr; QLabel *labelWalletEncryptionIcon = nullptr; QLabel *labelWalletHDStatusIcon = nullptr; GUIUtil::ClickableLabel *labelProxyIcon = nullptr; GUIUtil::ClickableLabel *connectionsControl = nullptr; GUIUtil::ClickableLabel *labelBlocksIcon = nullptr; QLabel *progressBarLabel = nullptr; GUIUtil::ClickableProgressBar *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 *m_load_psbt_action = 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; QAction *m_create_wallet_action{nullptr}; QAction *m_open_wallet_action{nullptr}; QMenu *m_open_wallet_menu{nullptr}; QAction *m_close_wallet_action{nullptr}; QAction *m_wallet_selector_label_action = nullptr; QAction *m_wallet_selector_action = nullptr; + QAction *m_mask_values_action{nullptr}; QLabel *m_wallet_selector_label = nullptr; QComboBox *m_wallet_selector = nullptr; QSystemTrayIcon *trayIcon = nullptr; const std::unique_ptr trayIconMenu; Notificator *notificator = nullptr; RPCConsole *rpcConsole = nullptr; HelpMessageDialog *helpMessageDialog = nullptr; ModalOverlay *modalOverlay = nullptr; #ifdef Q_OS_MAC CAppNapInhibitor *m_app_nap_inhibitor = nullptr; #endif /** Keep track of previous number of blocks, to detect progress */ int prevBlocks = 0; int spinnerFrame = 0; const Config *config; const PlatformStyle *platformStyle; const NetworkStyle *const m_network_style; /** 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(); /** 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(); /** Update UI with latest network info from model. */ void updateNetworkState(); void updateHeadersSyncProgressLabel(); /** Open the OptionsDialog on the specified tab index */ void openOptionsDialogWithTab(OptionsDialog::Tab tab); Q_SIGNALS: /** Signal raised when a URI was entered or dragged to the GUI */ void receivedURI(const QString &uri); /** Signal raised when RPC console shown */ void consoleShown(RPCConsole *console); + void setPrivacy(bool privacy); 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, SynchronizationState sync_state); /** * 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) * @param[in] detailed_message the text to be displayed in the details area */ void message(const QString &title, QString message, unsigned int style, bool *ret = nullptr, const QString &detailed_message = QString()); #ifdef ENABLE_WALLET void setCurrentWallet(WalletModel *wallet_model); void 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] hdEnabled current hd enabled status @see WalletModel::EncryptionStatus */ void setHDStatus(bool privkeyDisabled, 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: /** Set the proxy-enabled icon as shown in the UI. */ void updateProxyIcon(); void updateWindowTitle(); public 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 load Partially Signed Bitcoin Transaction dialog */ void gotoLoadPSBT(); /** 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); #else /** Handle macOS Dock icon clicked */ void macosDockIconActivated(); #endif /** Show window if hidden, unminimize when minimized, rise when obscured or * show if hidden and fToggleHidden is true */ void showNormalIfMinimized() { showNormalIfMinimized(false); } void showNormalIfMinimized(bool fToggleHidden); /** Simply calls showNormalIfMinimized(true) for use in SLOT() macro */ void toggleHidden(); /** called by a timer to check if ShutdownRequested() 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); 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/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index b14c4ea34..08a7aae06 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -1,541 +1,549 @@ OverviewPage 0 0 - 596 - 342 + 798 + 318 Form false QLabel { background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop:0 #F0D0A0, stop:1 #F8D488); color:#000000; } true 3 Qt::TextSelectableByMouse QFrame::StyledPanel QFrame::Raised 75 true Balances true 30 16777215 The displayed information may be out of date. Your wallet automatically synchronizes with the Bitcoin network after a connection is established, but this process has not completed yet. :/icons/warning :/icons/warning:/icons/warning 24 24 true Qt::Horizontal 40 20 12 + Monospace 75 true IBeamCursor Unconfirmed transactions to watch-only addresses - 0.000 000 00 BCH + 0.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Monospace 75 true IBeamCursor Total of transactions that have yet to be confirmed, and do not yet count toward the spendable balance - 0.000 000 00 BCH + 0.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Monospace 75 true IBeamCursor Mined balance in watch-only addresses that has not yet matured - 0.000 000 00 BCH + 0.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Qt::Horizontal 0 0 140 0 Qt::Horizontal Total: + Monospace 75 true IBeamCursor Mined balance that has not yet matured - 0.000 000 00 BCH + 0.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Qt::Horizontal 40 20 Immature: + Monospace 75 true IBeamCursor Your current total balance - 0.000 000 00 BCH + 21 000 000.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Monospace 75 true IBeamCursor Current total balance in watch-only addresses - 0.000 000 00 BCH + 21 000 000.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Watch-only: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Available: + Monospace 75 true IBeamCursor Your current spendable balance - 0.000 000 00 BCH + 21 000 000.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Monospace 75 true IBeamCursor Your current balance in watch-only addresses - 0.000 000 00 BCH + 21 000 000.00000000 BCH Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse Pending: Spendable: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Qt::Vertical 20 40 QFrame::StyledPanel QFrame::Raised 75 true Recent transactions true 30 16777215 The displayed information may be out of date. Your wallet automatically synchronizes with the Bitcoin network after a connection is established, but this process has not completed yet. :/icons/warning :/icons/warning:/icons/warning 24 24 true Qt::Horizontal 40 20 QListView { background: transparent; } QFrame::NoFrame Qt::ScrollBarAlwaysOff Qt::ScrollBarAlwaysOff QAbstractItemView::NoSelection Qt::Vertical 20 40 diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index 7d1c65bb1..479c12655 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -1,336 +1,356 @@ // 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. #include #include #include #include #include #include #include #include #include #include #include #include +#include #include +#include #define DECORATION_SIZE 54 #define NUM_ITEMS 5 Q_DECLARE_METATYPE(interfaces::WalletBalances) class TxViewDelegate : public QAbstractItemDelegate { Q_OBJECT public: explicit TxViewDelegate(const PlatformStyle *_platformStyle, QObject *parent = nullptr) : QAbstractItemDelegate(parent), unit(BitcoinUnits::base), platformStyle(_platformStyle) {} inline void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { painter->save(); QIcon icon = qvariant_cast( index.data(TransactionTableModel::RawDecorationRole)); QRect mainRect = option.rect; QRect decorationRect(mainRect.topLeft(), QSize(DECORATION_SIZE, DECORATION_SIZE)); int xspace = DECORATION_SIZE + 8; int ypad = 6; int halfheight = (mainRect.height() - 2 * ypad) / 2; QRect amountRect(mainRect.left() + xspace, mainRect.top() + ypad, mainRect.width() - xspace, halfheight); QRect addressRect(mainRect.left() + xspace, mainRect.top() + ypad + halfheight, mainRect.width() - xspace, halfheight); icon = platformStyle->SingleColorIcon(icon); icon.paint(painter, decorationRect); QDateTime date = index.data(TransactionTableModel::DateRole).toDateTime(); QString address = index.data(Qt::DisplayRole).toString(); Amount amount( int64_t( index.data(TransactionTableModel::AmountRole).toLongLong()) * SATOSHI); bool confirmed = index.data(TransactionTableModel::ConfirmedRole).toBool(); QVariant value = index.data(Qt::ForegroundRole); QColor foreground = option.palette.color(QPalette::Text); if (value.canConvert()) { QBrush brush = qvariant_cast(value); foreground = brush.color(); } painter->setPen(foreground); QRect boundingRect; painter->drawText(addressRect, Qt::AlignLeft | Qt::AlignVCenter, address, &boundingRect); if (index.data(TransactionTableModel::WatchonlyRole).toBool()) { QIcon iconWatchonly = qvariant_cast( index.data(TransactionTableModel::WatchonlyDecorationRole)); QRect watchonlyRect(boundingRect.right() + 5, mainRect.top() + ypad + halfheight, 16, halfheight); iconWatchonly.paint(painter, watchonlyRect); } if (amount < Amount::zero()) { foreground = COLOR_NEGATIVE; } else if (!confirmed) { foreground = COLOR_UNCONFIRMED; } else { foreground = option.palette.color(QPalette::Text); } painter->setPen(foreground); QString amountText = BitcoinUnits::formatWithUnit( unit, amount, true, BitcoinUnits::separatorAlways); if (!confirmed) { amountText = QString("[") + amountText + QString("]"); } painter->drawText(amountRect, Qt::AlignRight | Qt::AlignVCenter, amountText); painter->setPen(option.palette.color(QPalette::Text)); painter->drawText(amountRect, Qt::AlignLeft | Qt::AlignVCenter, GUIUtil::dateTimeStr(date)); painter->restore(); } inline QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { return QSize(DECORATION_SIZE, DECORATION_SIZE); } int unit; const PlatformStyle *platformStyle; }; #include OverviewPage::OverviewPage(const PlatformStyle *platformStyle, QWidget *parent) : QWidget(parent), ui(new Ui::OverviewPage), clientModel(nullptr), walletModel(nullptr), txdelegate(new TxViewDelegate(platformStyle, this)) { ui->setupUi(this); m_balances.balance = -SATOSHI; // use a SingleColorIcon for the "out of sync warning" icon QIcon icon = platformStyle->SingleColorIcon(":/icons/warning"); // also set the disabled icon because we are using a disabled QPushButton to // work around missing HiDPI support of QLabel // (https://bugreports.qt.io/browse/QTBUG-42503) icon.addPixmap(icon.pixmap(QSize(64, 64), QIcon::Normal), QIcon::Disabled); ui->labelTransactionsStatus->setIcon(icon); ui->labelWalletStatus->setIcon(icon); // Recent transactions ui->listTransactions->setItemDelegate(txdelegate); ui->listTransactions->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE)); ui->listTransactions->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2)); ui->listTransactions->setAttribute(Qt::WA_MacShowFocusRect, false); connect(ui->listTransactions, &QListView::clicked, this, &OverviewPage::handleTransactionClicked); // start with displaying the "out of sync" warnings showOutOfSyncWarning(true); connect(ui->labelWalletStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); connect(ui->labelTransactionsStatus, &QPushButton::clicked, this, &OverviewPage::handleOutOfSyncWarningClicks); } void OverviewPage::handleTransactionClicked(const QModelIndex &index) { if (filter) { Q_EMIT transactionClicked(filter->mapToSource(index)); } } void OverviewPage::handleOutOfSyncWarningClicks() { Q_EMIT outOfSyncWarningClicked(); } +void OverviewPage::setPrivacy(bool privacy) { + m_privacy = privacy; + if (m_balances.balance != -Amount::satoshi()) { + setBalance(m_balances); + } + + ui->listTransactions->setVisible(!m_privacy); + + const QString status_tip = + m_privacy ? tr("Privacy mode activated for the Overview tab. To unmask " + "the values, uncheck Settings->Mask values.") + : ""; + setStatusTip(status_tip); + QStatusTipEvent event(status_tip); + QApplication::sendEvent(this, &event); +} + OverviewPage::~OverviewPage() { delete ui; } void OverviewPage::setBalance(const interfaces::WalletBalances &balances) { int unit = walletModel->getOptionsModel()->getDisplayUnit(); m_balances = balances; if (walletModel->wallet().isLegacy()) { if (walletModel->wallet().privateKeysDisabled()) { - ui->labelBalance->setText(BitcoinUnits::formatWithUnit( - unit, balances.watch_only_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit( - unit, balances.unconfirmed_watch_only_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelImmature->setText(BitcoinUnits::formatWithUnit( - unit, balances.immature_watch_only_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelTotal->setText(BitcoinUnits::formatWithUnit( + ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.watch_only_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.unconfirmed_watch_only_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.immature_watch_only_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy( unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, - false, BitcoinUnits::separatorAlways)); + BitcoinUnits::separatorAlways, m_privacy)); } else { - ui->labelBalance->setText(BitcoinUnits::formatWithUnit( - unit, balances.balance, false, BitcoinUnits::separatorAlways)); - ui->labelUnconfirmed->setText(BitcoinUnits::formatWithUnit( - unit, balances.unconfirmed_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelImmature->setText(BitcoinUnits::formatWithUnit( - unit, balances.immature_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelTotal->setText(BitcoinUnits::formatWithUnit( + ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.balance, BitcoinUnits::separatorAlways, + m_privacy)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.unconfirmed_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.immature_balance, BitcoinUnits::separatorAlways, + m_privacy)); + ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy( unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, - false, BitcoinUnits::separatorAlways)); - ui->labelWatchAvailable->setText(BitcoinUnits::formatWithUnit( - unit, balances.watch_only_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelWatchPending->setText(BitcoinUnits::formatWithUnit( - unit, balances.unconfirmed_watch_only_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelWatchImmature->setText(BitcoinUnits::formatWithUnit( - unit, balances.immature_watch_only_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelWatchTotal->setText(BitcoinUnits::formatWithUnit( + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelWatchAvailable->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.watch_only_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelWatchPending->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.unconfirmed_watch_only_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelWatchImmature->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.immature_watch_only_balance, + BitcoinUnits::separatorAlways, m_privacy)); + ui->labelWatchTotal->setText(BitcoinUnits::formatWithPrivacy( unit, balances.watch_only_balance + balances.unconfirmed_watch_only_balance + balances.immature_watch_only_balance, - false, BitcoinUnits::separatorAlways)); + BitcoinUnits::separatorAlways, m_privacy)); } } else { - ui->labelBalance->setText(BitcoinUnits::formatWithUnit( - unit, balances.balance, false, BitcoinUnits::separatorAlways)); - ui->labelUnconfirmed->setText( - BitcoinUnits::formatWithUnit(unit, balances.unconfirmed_balance, - false, BitcoinUnits::separatorAlways)); - ui->labelImmature->setText( - BitcoinUnits::formatWithUnit(unit, balances.immature_balance, false, - BitcoinUnits::separatorAlways)); - ui->labelTotal->setText(BitcoinUnits::formatWithUnit( + ui->labelBalance->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.balance, BitcoinUnits::separatorAlways, m_privacy)); + ui->labelUnconfirmed->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.unconfirmed_balance, BitcoinUnits::separatorAlways, + m_privacy)); + ui->labelImmature->setText(BitcoinUnits::formatWithPrivacy( + unit, balances.immature_balance, BitcoinUnits::separatorAlways, + m_privacy)); + ui->labelTotal->setText(BitcoinUnits::formatWithPrivacy( unit, balances.balance + balances.unconfirmed_balance + balances.immature_balance, - false, BitcoinUnits::separatorAlways)); + BitcoinUnits::separatorAlways, m_privacy)); } // only show immature (newly mined) balance if it's non-zero, so as not to // complicate things for the non-mining users bool showImmature = balances.immature_balance != Amount::zero(); bool showWatchOnlyImmature = balances.immature_watch_only_balance != Amount::zero(); // for symmetry reasons also show immature label when the watch-only one is // shown ui->labelImmature->setVisible(showImmature || showWatchOnlyImmature); ui->labelImmatureText->setVisible(showImmature || showWatchOnlyImmature); // show watch-only immature balance ui->labelWatchImmature->setVisible( !walletModel->wallet().privateKeysDisabled() && showWatchOnlyImmature); } // show/hide watch-only labels void OverviewPage::updateWatchOnlyLabels(bool showWatchOnly) { // show spendable label (only when watch-only is active) ui->labelSpendable->setVisible(showWatchOnly); // show watch-only label ui->labelWatchonly->setVisible(showWatchOnly); // show watch-only balance separator line ui->lineWatchBalance->setVisible(showWatchOnly); // show watch-only available balance ui->labelWatchAvailable->setVisible(showWatchOnly); // show watch-only pending balance ui->labelWatchPending->setVisible(showWatchOnly); // show watch-only total balance ui->labelWatchTotal->setVisible(showWatchOnly); if (!showWatchOnly) { ui->labelWatchImmature->hide(); } } void OverviewPage::setClientModel(ClientModel *model) { this->clientModel = model; if (model) { // Show warning, for example if this is a prerelease version connect(model, &ClientModel::alertsChanged, this, &OverviewPage::updateAlerts); updateAlerts(model->getStatusBarWarnings()); } } void OverviewPage::setWalletModel(WalletModel *model) { this->walletModel = model; if (model && model->getOptionsModel()) { // Set up transaction list filter.reset(new TransactionFilterProxy()); filter->setSourceModel(model->getTransactionTableModel()); filter->setLimit(NUM_ITEMS); filter->setDynamicSortFilter(true); filter->setSortRole(Qt::EditRole); filter->setShowInactive(false); filter->sort(TransactionTableModel::Date, Qt::DescendingOrder); ui->listTransactions->setModel(filter.get()); ui->listTransactions->setModelColumn(TransactionTableModel::ToAddress); // Keep up to date with wallet interfaces::Wallet &wallet = model->wallet(); interfaces::WalletBalances balances = wallet.getBalances(); setBalance(balances); connect(model, &WalletModel::balanceChanged, this, &OverviewPage::setBalance); connect(model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &OverviewPage::updateDisplayUnit); updateWatchOnlyLabels(wallet.haveWatchOnly() && !model->wallet().privateKeysDisabled()); connect(model, &WalletModel::notifyWatchonlyChanged, [this](bool showWatchOnly) { updateWatchOnlyLabels( showWatchOnly && !walletModel->wallet().privateKeysDisabled()); }); } // update the display unit, to not use the default ("XEC") updateDisplayUnit(); } void OverviewPage::updateDisplayUnit() { if (walletModel && walletModel->getOptionsModel()) { if (m_balances.balance != -SATOSHI) { setBalance(m_balances); } // Update txdelegate->unit with the current unit txdelegate->unit = walletModel->getOptionsModel()->getDisplayUnit(); ui->listTransactions->update(); } } void OverviewPage::updateAlerts(const QString &warnings) { this->ui->labelAlerts->setVisible(!warnings.isEmpty()); this->ui->labelAlerts->setText(warnings); } void OverviewPage::showOutOfSyncWarning(bool fShow) { ui->labelWalletStatus->setVisible(fShow); ui->labelTransactionsStatus->setVisible(fShow); } diff --git a/src/qt/overviewpage.h b/src/qt/overviewpage.h index ca7ef5ef0..8bda00470 100644 --- a/src/qt/overviewpage.h +++ b/src/qt/overviewpage.h @@ -1,64 +1,66 @@ // 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_OVERVIEWPAGE_H #define BITCOIN_QT_OVERVIEWPAGE_H #include #include #include class ClientModel; class TransactionFilterProxy; class TxViewDelegate; class PlatformStyle; class WalletModel; namespace Ui { class OverviewPage; } QT_BEGIN_NAMESPACE class QModelIndex; QT_END_NAMESPACE /** Overview ("home") page widget */ class OverviewPage : public QWidget { Q_OBJECT public: explicit OverviewPage(const PlatformStyle *platformStyle, QWidget *parent = nullptr); ~OverviewPage(); void setClientModel(ClientModel *clientModel); void setWalletModel(WalletModel *walletModel); void showOutOfSyncWarning(bool fShow); public Q_SLOTS: void setBalance(const interfaces::WalletBalances &balances); + void setPrivacy(bool privacy); Q_SIGNALS: void transactionClicked(const QModelIndex &index); void outOfSyncWarningClicked(); private: Ui::OverviewPage *ui; ClientModel *clientModel; WalletModel *walletModel; interfaces::WalletBalances m_balances; + bool m_privacy{false}; TxViewDelegate *txdelegate; std::unique_ptr filter; private Q_SLOTS: void updateDisplayUnit(); void handleTransactionClicked(const QModelIndex &index); void updateAlerts(const QString &warnings); void updateWatchOnlyLabels(bool showWatchOnly); void handleOutOfSyncWarningClicks(); }; #endif // BITCOIN_QT_OVERVIEWPAGE_H diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index b2d3cf661..255463669 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -1,281 +1,281 @@ // Copyright (c) 2015-2021 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog. void ConfirmSend(QString *text = nullptr, bool cancel = false) { QTimer::singleShot(0, Qt::PreciseTimer, [text, cancel]() { for (QWidget *widget : QApplication::topLevelWidgets()) { if (widget->inherits("SendConfirmationDialog")) { SendConfirmationDialog *dialog = qobject_cast(widget); if (text) { *text = dialog->text(); } QAbstractButton *button = dialog->button( cancel ? QMessageBox::Cancel : QMessageBox::Yes); button->setEnabled(true); button->click(); } } }); } //! Send coins to address and return txid. TxId SendCoins(CWallet &wallet, SendCoinsDialog &sendCoinsDialog, const CTxDestination &address, Amount amount) { QVBoxLayout *entries = sendCoinsDialog.findChild("entries"); SendCoinsEntry *entry = qobject_cast(entries->itemAt(0)->widget()); entry->findChild("payTo")->setText( QString::fromStdString(EncodeCashAddr(address, Params()))); entry->findChild("payAmount")->setValue(amount); TxId txid; boost::signals2::scoped_connection c = wallet.NotifyTransactionChanged.connect( [&txid](CWallet *, const TxId &hash, ChangeType status) { if (status == CT_NEW) { txid = hash; } }); ConfirmSend(); bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked"); assert(invoked); return txid; } //! Find index of txid in transaction list. QModelIndex FindTx(const QAbstractItemModel &model, const uint256 &txid) { QString hash = QString::fromStdString(txid.ToString()); int rows = model.rowCount({}); for (int row = 0; row < rows; ++row) { QModelIndex index = model.index(row, 0, {}); if (model.data(index, TransactionTableModel::TxHashRole) == hash) { return index; } } return {}; } //! Simple qt wallet tests. // // Test widgets can be debugged interactively calling show() on them and // manually running the event loop, e.g.: // // sendCoinsDialog.show(); // QEventLoop().exec(); // // This also requires overriding the default minimal Qt platform: // // QT_QPA_PLATFORM=xcb src/qt/test/test_bitcoin-qt # Linux // QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt # Windows // QT_QPA_PLATFORM=cocoa src/qt/test/test_bitcoin-qt # macOS void TestGUI(interfaces::Node &node) { // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) { test.CreateAndProcessBlock( {}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey())); } node.setContext(&test.m_node); std::shared_ptr wallet = std::make_shared(node.context()->chain.get(), WalletLocation(), CreateMockWalletDatabase()); bool firstRun; wallet->LoadWallet(firstRun); { auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); wallet->SetAddressBook( GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive"); spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash()); } { WalletRescanReserver reserver(*wallet); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions( Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */); QCOMPARE(result.status, CWallet::ScanResult::SUCCESS); QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); QVERIFY(result.last_failed_block.IsNull()); } wallet->SetBroadcastTransactions(true); // Create widgets for sending coins and listing transactions. std::unique_ptr platformStyle( PlatformStyle::instantiate("other")); OptionsModel optionsModel; ClientModel clientModel(node, &optionsModel); AddWallet(wallet); WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get()); RemoveWallet(wallet); SendCoinsDialog sendCoinsDialog(platformStyle.get(), &walletModel); { // Check balance in send dialog QLabel *balanceLabel = sendCoinsDialog.findChild("labelBalance"); QString balanceText = balanceLabel->text(); int unit = walletModel.getOptionsModel()->getDisplayUnit(); Amount balance = walletModel.wallet().getBalance(); QString balanceComparison = BitcoinUnits::formatWithUnit( unit, balance, false, BitcoinUnits::separatorAlways); QCOMPARE(balanceText, balanceComparison); } // Send two transactions, and verify they are added to transaction list. TransactionTableModel *transactionTableModel = walletModel.getTransactionTableModel(); QCOMPARE(transactionTableModel->rowCount({}), 105); TxId txid1 = SendCoins(*wallet.get(), sendCoinsDialog, CTxDestination(PKHash()), 5 * COIN); TxId txid2 = SendCoins(*wallet.get(), sendCoinsDialog, CTxDestination(PKHash()), 10 * COIN); QCOMPARE(transactionTableModel->rowCount({}), 107); QVERIFY(FindTx(*transactionTableModel, txid1).isValid()); QVERIFY(FindTx(*transactionTableModel, txid2).isValid()); // Check current balance on OverviewPage OverviewPage overviewPage(platformStyle.get()); overviewPage.setWalletModel(&walletModel); QLabel *balanceLabel = overviewPage.findChild("labelBalance"); - QString balanceText = balanceLabel->text(); + QString balanceText = balanceLabel->text().trimmed(); int unit = walletModel.getOptionsModel()->getDisplayUnit(); Amount balance = walletModel.wallet().getBalance(); QString balanceComparison = BitcoinUnits::formatWithUnit( unit, balance, false, BitcoinUnits::separatorAlways); QCOMPARE(balanceText, balanceComparison); // Check Request Payment button ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get()); receiveCoinsDialog.setModel(&walletModel); RecentRequestsTableModel *requestTableModel = walletModel.getRecentRequestsTableModel(); // Label input QLineEdit *labelInput = receiveCoinsDialog.findChild("reqLabel"); labelInput->setText("TEST_LABEL_1"); // Amount input BitcoinAmountField *amountInput = receiveCoinsDialog.findChild("reqAmount"); amountInput->setValue(1 * SATOSHI); // Message input QLineEdit *messageInput = receiveCoinsDialog.findChild("reqMessage"); messageInput->setText("TEST_MESSAGE_1"); int initialRowCount = requestTableModel->rowCount({}); QPushButton *requestPaymentButton = receiveCoinsDialog.findChild("receiveButton"); requestPaymentButton->click(); for (QWidget *widget : QApplication::topLevelWidgets()) { if (widget->inherits("ReceiveRequestDialog")) { ReceiveRequestDialog *receiveRequestDialog = qobject_cast(widget); QTextEdit *rlist = receiveRequestDialog->QObject::findChild("outUri"); QString paymentText = rlist->toPlainText(); QStringList paymentTextList = paymentText.split('\n'); QCOMPARE(paymentTextList.at(0), QString("Payment information")); QVERIFY(paymentTextList.at(1).indexOf(QString("URI: ecregtest:")) != -1); QVERIFY(paymentTextList.at(2).indexOf(QString("Address:")) != -1); QCOMPARE(paymentTextList.at(3), QString("Amount: 0.01 ") + QString::fromStdString(Currency::get().ticker)); QCOMPARE(paymentTextList.at(4), QString("Label: TEST_LABEL_1")); QCOMPARE(paymentTextList.at(5), QString("Message: TEST_MESSAGE_1")); } } // Clear button QPushButton *clearButton = receiveCoinsDialog.findChild("clearButton"); clearButton->click(); QCOMPARE(labelInput->text(), QString("")); QCOMPARE(amountInput->value(), Amount::zero()); QCOMPARE(messageInput->text(), QString("")); // Check addition to history int currentRowCount = requestTableModel->rowCount({}); QCOMPARE(currentRowCount, initialRowCount + 1); // Check Remove button QTableView *table = receiveCoinsDialog.findChild("recentRequestsView"); table->selectRow(currentRowCount - 1); QPushButton *removeRequestButton = receiveCoinsDialog.findChild("removeRequestButton"); removeRequestButton->click(); QCOMPARE(requestTableModel->rowCount({}), currentRowCount - 1); } } // namespace void WalletTests::walletTests() { #ifdef Q_OS_MAC if (QApplication::platformName() == "minimal") { // Disable for mac on "minimal" platform to avoid crashes inside the Qt // framework when it tries to look up unimplemented cocoa functions, // and fails to handle returned nulls // (https://bugreports.qt.io/browse/QTBUG-49686). QWARN( "Skipping WalletTests on mac build with 'minimal' platform set due " "to Qt bugs. To run AppTests, invoke with 'QT_QPA_PLATFORM=cocoa " "test_bitcoin-qt' on mac, or else use a linux or windows build."); return; } #endif TestGUI(m_node); } diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 5a0399e8c..f00b62a1d 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -1,232 +1,234 @@ // Copyright (c) 2011-2019 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 #include #include #include #include #include #include WalletFrame::WalletFrame(const PlatformStyle *_platformStyle, BitcoinGUI *_gui) : QFrame(_gui), gui(_gui), platformStyle(_platformStyle) { // Leave HBox hook for adding a list view later QHBoxLayout *walletFrameLayout = new QHBoxLayout(this); setContentsMargins(0, 0, 0, 0); walletStack = new QStackedWidget(this); walletFrameLayout->setContentsMargins(0, 0, 0, 0); walletFrameLayout->addWidget(walletStack); QLabel *noWallet = new QLabel(tr("No wallet has been loaded.")); noWallet->setAlignment(Qt::AlignCenter); walletStack->addWidget(noWallet); } WalletFrame::~WalletFrame() {} void WalletFrame::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; for (auto i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { i.value()->setClientModel(_clientModel); } } bool WalletFrame::addWallet(WalletModel *walletModel) { if (!gui || !clientModel || !walletModel) { return false; } if (mapWalletViews.count(walletModel) > 0) { return false; } WalletView *walletView = new WalletView(platformStyle, walletModel, this); walletView->setClientModel(clientModel); walletView->showOutOfSyncWarning(bOutOfSync); + walletView->setPrivacy(gui->isPrivacyModeActivated()); WalletView *current_wallet_view = currentWalletView(); if (current_wallet_view) { walletView->setCurrentIndex(current_wallet_view->currentIndex()); } else { walletView->gotoOverviewPage(); } walletStack->addWidget(walletView); mapWalletViews[walletModel] = walletView; connect(walletView, &WalletView::outOfSyncWarningClicked, this, &WalletFrame::outOfSyncWarningClicked); connect(walletView, &WalletView::transactionClicked, gui, &BitcoinGUI::gotoHistoryPage); connect(walletView, &WalletView::coinsSent, gui, &BitcoinGUI::gotoHistoryPage); connect( walletView, &WalletView::message, [this](const QString &title, const QString &message, unsigned int style) { gui->message(title, message, style); }); connect(walletView, &WalletView::encryptionStatusChanged, gui, &BitcoinGUI::updateWalletStatus); connect(walletView, &WalletView::incomingTransaction, gui, &BitcoinGUI::incomingTransaction); connect(walletView, &WalletView::hdEnabledStatusChanged, gui, &BitcoinGUI::updateWalletStatus); + connect(gui, &BitcoinGUI::setPrivacy, walletView, &WalletView::setPrivacy); return true; } void WalletFrame::setCurrentWallet(WalletModel *wallet_model) { if (mapWalletViews.count(wallet_model) == 0) { return; } WalletView *walletView = mapWalletViews.value(wallet_model); walletStack->setCurrentWidget(walletView); assert(walletView); walletView->updateEncryptionStatus(); } void WalletFrame::removeWallet(WalletModel *wallet_model) { if (mapWalletViews.count(wallet_model) == 0) { return; } WalletView *walletView = mapWalletViews.take(wallet_model); walletStack->removeWidget(walletView); delete walletView; } void WalletFrame::removeAllWallets() { QMap::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { walletStack->removeWidget(i.value()); } mapWalletViews.clear(); } bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient) { WalletView *walletView = currentWalletView(); if (!walletView) { return false; } return walletView->handlePaymentRequest(recipient); } void WalletFrame::showOutOfSyncWarning(bool fShow) { bOutOfSync = fShow; QMap::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { i.value()->showOutOfSyncWarning(fShow); } } void WalletFrame::gotoOverviewPage() { QMap::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { i.value()->gotoOverviewPage(); } } void WalletFrame::gotoHistoryPage() { QMap::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { i.value()->gotoHistoryPage(); } } void WalletFrame::gotoReceiveCoinsPage() { QMap::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { i.value()->gotoReceiveCoinsPage(); } } void WalletFrame::gotoSendCoinsPage(QString addr) { QMap::const_iterator i; for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) { i.value()->gotoSendCoinsPage(addr); } } void WalletFrame::gotoSignMessageTab(QString addr) { WalletView *walletView = currentWalletView(); if (walletView) { walletView->gotoSignMessageTab(addr); } } void WalletFrame::gotoVerifyMessageTab(QString addr) { WalletView *walletView = currentWalletView(); if (walletView) { walletView->gotoVerifyMessageTab(addr); } } void WalletFrame::gotoLoadPSBT() { WalletView *walletView = currentWalletView(); if (walletView) { walletView->gotoLoadPSBT(); } } void WalletFrame::encryptWallet(bool status) { WalletView *walletView = currentWalletView(); if (walletView) { walletView->encryptWallet(status); } } void WalletFrame::backupWallet() { WalletView *walletView = currentWalletView(); if (walletView) { walletView->backupWallet(); } } void WalletFrame::changePassphrase() { WalletView *walletView = currentWalletView(); if (walletView) { walletView->changePassphrase(); } } void WalletFrame::unlockWallet() { WalletView *walletView = currentWalletView(); if (walletView) { walletView->unlockWallet(); } } void WalletFrame::usedSendingAddresses() { WalletView *walletView = currentWalletView(); if (walletView) { walletView->usedSendingAddresses(); } } void WalletFrame::usedReceivingAddresses() { WalletView *walletView = currentWalletView(); if (walletView) { walletView->usedReceivingAddresses(); } } WalletView *WalletFrame::currentWalletView() const { return qobject_cast(walletStack->currentWidget()); } WalletModel *WalletFrame::currentWalletModel() const { WalletView *wallet_view = currentWalletView(); return wallet_view ? wallet_view->getWalletModel() : nullptr; } void WalletFrame::outOfSyncWarningClicked() { Q_EMIT requestedSyncWarningInfo(); } diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 2f5a092a0..40b12b4b8 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -1,444 +1,446 @@ // 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. #include #include // For GetConfig #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include WalletView::WalletView(const PlatformStyle *_platformStyle, WalletModel *_walletModel, QWidget *parent) : QStackedWidget(parent), clientModel(nullptr), walletModel(_walletModel), platformStyle(_platformStyle) { // Create tabs overviewPage = new OverviewPage(platformStyle); transactionsPage = new QWidget(this); QVBoxLayout *vbox = new QVBoxLayout(); QHBoxLayout *hbox_buttons = new QHBoxLayout(); transactionView = new TransactionView(platformStyle, this); vbox->addWidget(transactionView); QPushButton *exportButton = new QPushButton(tr("&Export"), this); exportButton->setToolTip( tr("Export the data in the current tab to a file")); if (platformStyle->getImagesOnButtons()) { exportButton->setIcon(platformStyle->SingleColorIcon(":/icons/export")); } hbox_buttons->addStretch(); hbox_buttons->addWidget(exportButton); vbox->addLayout(hbox_buttons); transactionsPage->setLayout(vbox); receiveCoinsPage = new ReceiveCoinsDialog(platformStyle); sendCoinsPage = new SendCoinsDialog(platformStyle, walletModel); usedSendingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::SendingTab, this); usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this); addWidget(overviewPage); addWidget(transactionsPage); addWidget(receiveCoinsPage); addWidget(sendCoinsPage); connect(overviewPage, &OverviewPage::transactionClicked, this, &WalletView::transactionClicked); // Clicking on a transaction on the overview pre-selects the transaction on // the transaction history page connect(overviewPage, &OverviewPage::transactionClicked, transactionView, static_cast( &TransactionView::focusTransaction)); connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo); connect(sendCoinsPage, &SendCoinsDialog::coinsSent, this, &WalletView::coinsSent); // Highlight transaction after send connect(sendCoinsPage, &SendCoinsDialog::coinsSent, transactionView, static_cast( &TransactionView::focusTransaction)); // Clicking on "Export" allows to export the transaction list connect(exportButton, &QPushButton::clicked, transactionView, &TransactionView::exportClicked); // Pass through messages from sendCoinsPage connect(sendCoinsPage, &SendCoinsDialog::message, this, &WalletView::message); // Pass through messages from transactionView connect(transactionView, &TransactionView::message, this, &WalletView::message); + connect(this, &WalletView::setPrivacy, overviewPage, + &OverviewPage::setPrivacy); // Set the model properly. setWalletModel(walletModel); } WalletView::~WalletView() {} void WalletView::setClientModel(ClientModel *_clientModel) { this->clientModel = _clientModel; overviewPage->setClientModel(_clientModel); sendCoinsPage->setClientModel(_clientModel); if (walletModel) { walletModel->setClientModel(_clientModel); } } void WalletView::setWalletModel(WalletModel *_walletModel) { this->walletModel = _walletModel; // Put transaction list in tabs transactionView->setModel(_walletModel); overviewPage->setWalletModel(_walletModel); receiveCoinsPage->setModel(_walletModel); sendCoinsPage->setModel(_walletModel); usedReceivingAddressesPage->setModel( _walletModel ? _walletModel->getAddressTableModel() : nullptr); usedSendingAddressesPage->setModel( _walletModel ? _walletModel->getAddressTableModel() : nullptr); if (_walletModel) { // Receive and pass through messages from wallet model connect(_walletModel, &WalletModel::message, this, &WalletView::message); // Handle changes in encryption status connect(_walletModel, &WalletModel::encryptionStatusChanged, this, &WalletView::encryptionStatusChanged); updateEncryptionStatus(); // update HD status Q_EMIT hdEnabledStatusChanged(); // Balloon pop-up for new transaction connect(_walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &WalletView::processNewTransaction); // Ask for passphrase if needed connect(_walletModel, &WalletModel::requireUnlock, this, &WalletView::unlockWallet); // Show progress dialog connect(_walletModel, &WalletModel::showProgress, this, &WalletView::showProgress); } } void WalletView::processNewTransaction(const QModelIndex &parent, int start, int end) { // Prevent balloon-spam when initial block download is in progress if (!walletModel || !clientModel || clientModel->node().isInitialBlockDownload()) { return; } TransactionTableModel *ttm = walletModel->getTransactionTableModel(); if (!ttm || ttm->processingQueuedTransactions()) { return; } QString date = ttm->index(start, TransactionTableModel::Date, parent) .data() .toString(); qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent) .data(Qt::EditRole) .toULongLong(); QString type = ttm->index(start, TransactionTableModel::Type, parent) .data() .toString(); QModelIndex index = ttm->index(start, 0, parent); QString address = ttm->data(index, TransactionTableModel::AddressRole).toString(); QString label = GUIUtil::HtmlEscape( ttm->data(index, TransactionTableModel::LabelRole).toString()); Q_EMIT incomingTransaction( date, walletModel->getOptionsModel()->getDisplayUnit(), int64_t(amount) * SATOSHI, type, address, label, GUIUtil::HtmlEscape(walletModel->getWalletName())); } void WalletView::gotoOverviewPage() { setCurrentWidget(overviewPage); } void WalletView::gotoHistoryPage() { setCurrentWidget(transactionsPage); } void WalletView::gotoReceiveCoinsPage() { setCurrentWidget(receiveCoinsPage); } void WalletView::gotoSendCoinsPage(QString addr) { setCurrentWidget(sendCoinsPage); if (!addr.isEmpty()) { sendCoinsPage->setAddress(addr); } } void WalletView::gotoSignMessageTab(QString addr) { // calls show() in showTab_SM() SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(platformStyle, this); signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose); signVerifyMessageDialog->setModel(walletModel); signVerifyMessageDialog->showTab_SM(true); if (!addr.isEmpty()) { signVerifyMessageDialog->setAddress_SM(addr); } } void WalletView::gotoVerifyMessageTab(QString addr) { // calls show() in showTab_VM() SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(platformStyle, this); signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose); signVerifyMessageDialog->setModel(walletModel); signVerifyMessageDialog->showTab_VM(true); if (!addr.isEmpty()) { signVerifyMessageDialog->setAddress_VM(addr); } } void WalletView::gotoLoadPSBT() { QString filename = GUIUtil::getOpenFileName( this, tr("Load Transaction Data"), QString(), tr("Partially Signed Transaction (*.psbt)"), nullptr); if (filename.isEmpty()) { return; } if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == MAX_FILE_SIZE_PSBT) { Q_EMIT message(tr("Error"), tr("PSBT file must be smaller than 100 MiB"), CClientUIInterface::MSG_ERROR); return; } std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary); std::string dataStr(std::istreambuf_iterator{in}, {}); std::string error; PartiallySignedTransaction psbtx; if (!DecodeRawPSBT(psbtx, dataStr, error)) { Q_EMIT message(tr("Error"), tr("Unable to decode PSBT file") + "\n" + QString::fromStdString(error), CClientUIInterface::MSG_ERROR); return; } CMutableTransaction mtx; bool complete = false; PSBTAnalysis analysis = AnalyzePSBT(psbtx); QMessageBox msgBox; msgBox.setText("PSBT"); switch (analysis.next) { case PSBTRole::CREATOR: case PSBTRole::UPDATER: msgBox.setInformativeText( "PSBT is incomplete. Copy to clipboard for manual inspection?"); break; case PSBTRole::SIGNER: msgBox.setInformativeText( "Transaction needs more signatures. Copy to clipboard?"); break; case PSBTRole::FINALIZER: case PSBTRole::EXTRACTOR: complete = FinalizeAndExtractPSBT(psbtx, mtx); if (complete) { msgBox.setInformativeText( tr("Would you like to send this transaction?")); } else { // The analyzer missed something, e.g. if there are // final_scriptSig but with invalid signatures. msgBox.setInformativeText( tr("There was an unexpected problem processing the PSBT. " "Copy to clipboard for manual inspection?")); } } msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); switch (msgBox.exec()) { case QMessageBox::Yes: { if (complete) { std::string err_string; CTransactionRef tx = MakeTransactionRef(mtx); TransactionError result = BroadcastTransaction( *clientModel->node().context(), GetConfig(), tx, err_string, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, /* wait_callback */ false); if (result == TransactionError::OK) { Q_EMIT message(tr("Success"), tr("Broadcasted transaction successfully."), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL); } else { Q_EMIT message(tr("Error"), QString::fromStdString(err_string), CClientUIInterface::MSG_ERROR); } } else { // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", CClientUIInterface::MSG_INFORMATION); return; } } case QMessageBox::Cancel: break; default: assert(false); } } bool WalletView::handlePaymentRequest(const SendCoinsRecipient &recipient) { return sendCoinsPage->handlePaymentRequest(recipient); } void WalletView::showOutOfSyncWarning(bool fShow) { overviewPage->showOutOfSyncWarning(fShow); } void WalletView::updateEncryptionStatus() { Q_EMIT encryptionStatusChanged(); } void WalletView::encryptWallet(bool status) { if (!walletModel) { return; } AskPassphraseDialog dlg(status ? AskPassphraseDialog::Encrypt : AskPassphraseDialog::Decrypt, this); dlg.setModel(walletModel); dlg.exec(); updateEncryptionStatus(); } void WalletView::backupWallet() { QString filename = GUIUtil::getSaveFileName(this, tr("Backup Wallet"), QString(), tr("Wallet Data (*.dat)"), nullptr); if (filename.isEmpty()) { return; } if (!walletModel->wallet().backupWallet(filename.toLocal8Bit().data())) { Q_EMIT message( tr("Backup Failed"), tr("There was an error trying to save the wallet data to %1.") .arg(filename), CClientUIInterface::MSG_ERROR); } else { Q_EMIT message( tr("Backup Successful"), tr("The wallet data was successfully saved to %1.").arg(filename), CClientUIInterface::MSG_INFORMATION); } } void WalletView::changePassphrase() { AskPassphraseDialog dlg(AskPassphraseDialog::ChangePass, this); dlg.setModel(walletModel); dlg.exec(); } void WalletView::unlockWallet() { if (!walletModel) { return; } // Unlock wallet when requested by wallet model if (walletModel->getEncryptionStatus() == WalletModel::Locked) { AskPassphraseDialog dlg(AskPassphraseDialog::Unlock, this); dlg.setModel(walletModel); dlg.exec(); } } void WalletView::usedSendingAddresses() { if (!walletModel) { return; } GUIUtil::bringToFront(usedSendingAddressesPage); } void WalletView::usedReceivingAddresses() { if (!walletModel) { return; } GUIUtil::bringToFront(usedReceivingAddressesPage); } void WalletView::showProgress(const QString &title, int nProgress) { if (nProgress == 0) { progressDialog = new QProgressDialog(title, tr("Cancel"), 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) { if (progressDialog->wasCanceled()) { getWalletModel()->wallet().abortRescan(); } else { progressDialog->setValue(nProgress); } } } void WalletView::requestedSyncWarningInfo() { Q_EMIT outOfSyncWarningClicked(); } diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 72b819542..e11eb4ac5 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -1,141 +1,142 @@ // 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_WALLETVIEW_H #define BITCOIN_QT_WALLETVIEW_H #include #include class ClientModel; class OverviewPage; class PlatformStyle; class ReceiveCoinsDialog; class SendCoinsDialog; class SendCoinsRecipient; class TransactionView; class WalletModel; class AddressBookPage; QT_BEGIN_NAMESPACE class QModelIndex; class QProgressDialog; QT_END_NAMESPACE /** * WalletView class. This class represents the view to a single wallet. * It was added to support multiple wallet functionality. Each wallet gets its * own WalletView instance. * It communicates with both the client and the wallet models to give the user * an up-to-date view of the current core state. */ class WalletView : public QStackedWidget { Q_OBJECT public: WalletView(const PlatformStyle *platformStyle, WalletModel *walletModel, QWidget *parent); ~WalletView(); /** * 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); WalletModel *getWalletModel() { return walletModel; } /** * Set the wallet model. * The wallet model represents a bitcoin wallet, and offers access to the * list of transactions, address book and sending functionality. */ void setWalletModel(WalletModel *walletModel); bool handlePaymentRequest(const SendCoinsRecipient &recipient); void showOutOfSyncWarning(bool fShow); private: ClientModel *clientModel; WalletModel *walletModel; OverviewPage *overviewPage; QWidget *transactionsPage; ReceiveCoinsDialog *receiveCoinsPage; SendCoinsDialog *sendCoinsPage; AddressBookPage *usedSendingAddressesPage; AddressBookPage *usedReceivingAddressesPage; TransactionView *transactionView; QProgressDialog *progressDialog{nullptr}; const PlatformStyle *platformStyle; public Q_SLOTS: /** 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 = ""); /** Load Partially Signed Bitcoin Transaction */ void gotoLoadPSBT(); /** * Show incoming transaction notification for new transactions. * * The new items are those between start and end inclusive, under the given * parent item. */ void processNewTransaction(const QModelIndex &parent, int start, int end); /** Encrypt the wallet */ void encryptWallet(bool status); /** Backup the wallet */ void backupWallet(); /** Change encrypted wallet passphrase */ void changePassphrase(); /** Ask for passphrase to unlock wallet temporarily */ void unlockWallet(); /** Show used sending addresses */ void usedSendingAddresses(); /** Show used receiving addresses */ void usedReceivingAddresses(); /** Re-emit encryption status signal */ void updateEncryptionStatus(); /** Show progress dialog e.g. for rescan */ void showProgress(const QString &title, int nProgress); /** User has requested more information about the out of sync state */ void requestedSyncWarningInfo(); Q_SIGNALS: + void setPrivacy(bool privacy); void transactionClicked(); void coinsSent(); /** Fired when a message should be reported to the user */ void message(const QString &title, const QString &message, unsigned int style); /** Encryption status of wallet changed */ void encryptionStatusChanged(); /** HD-Enabled status of wallet changed (only possible during startup) */ void hdEnabledStatusChanged(); /** Notify that a new transaction appeared */ void incomingTransaction(const QString &date, int unit, const Amount amount, const QString &type, const QString &address, const QString &label, const QString &walletName); /** Notify that the out of sync warning icon has been pressed */ void outOfSyncWarningClicked(); }; #endif // BITCOIN_QT_WALLETVIEW_H