/ src / qt / bitcoingui.cpp
bitcoingui.cpp
   1  // Copyright (c) 2011-2022 The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <bitcoin-build-config.h> // IWYU pragma: keep
   6  
   7  #include <qt/bitcoingui.h>
   8  
   9  #include <qt/bitcoinunits.h>
  10  #include <qt/clientmodel.h>
  11  #include <qt/createwalletdialog.h>
  12  #include <qt/guiconstants.h>
  13  #include <qt/guiutil.h>
  14  #include <qt/modaloverlay.h>
  15  #include <qt/networkstyle.h>
  16  #include <qt/notificator.h>
  17  #include <qt/openuridialog.h>
  18  #include <qt/optionsdialog.h>
  19  #include <qt/optionsmodel.h>
  20  #include <qt/platformstyle.h>
  21  #include <qt/rpcconsole.h>
  22  #include <qt/utilitydialog.h>
  23  
  24  #ifdef ENABLE_WALLET
  25  #include <qt/walletcontroller.h>
  26  #include <qt/walletframe.h>
  27  #include <qt/walletmodel.h>
  28  #include <qt/walletview.h>
  29  #endif // ENABLE_WALLET
  30  
  31  #ifdef Q_OS_MACOS
  32  #include <qt/macdockiconhandler.h>
  33  #endif
  34  
  35  #include <chain.h>
  36  #include <chainparams.h>
  37  #include <common/system.h>
  38  #include <interfaces/handler.h>
  39  #include <interfaces/node.h>
  40  #include <node/interface_ui.h>
  41  #include <util/translation.h>
  42  #include <validation.h>
  43  
  44  #include <functional>
  45  
  46  #include <QAction>
  47  #include <QActionGroup>
  48  #include <QApplication>
  49  #include <QComboBox>
  50  #include <QCursor>
  51  #include <QDateTime>
  52  #include <QDragEnterEvent>
  53  #include <QInputDialog>
  54  #include <QKeySequence>
  55  #include <QListWidget>
  56  #include <QMenu>
  57  #include <QMenuBar>
  58  #include <QMessageBox>
  59  #include <QMimeData>
  60  #include <QProgressDialog>
  61  #include <QScreen>
  62  #include <QSettings>
  63  #include <QShortcut>
  64  #include <QStackedWidget>
  65  #include <QStatusBar>
  66  #include <QStyle>
  67  #include <QSystemTrayIcon>
  68  #include <QTimer>
  69  #include <QToolBar>
  70  #include <QUrlQuery>
  71  #include <QVBoxLayout>
  72  #include <QWindow>
  73  
  74  
  75  const std::string BitcoinGUI::DEFAULT_UIPLATFORM =
  76  #if defined(Q_OS_MACOS)
  77          "macosx"
  78  #elif defined(Q_OS_WIN)
  79          "windows"
  80  #else
  81          "other"
  82  #endif
  83          ;
  84  
  85  BitcoinGUI::BitcoinGUI(interfaces::Node& node, const PlatformStyle *_platformStyle, const NetworkStyle *networkStyle, QWidget *parent) :
  86      QMainWindow(parent),
  87      m_node(node),
  88      trayIconMenu{new QMenu()},
  89      platformStyle(_platformStyle),
  90      m_network_style(networkStyle)
  91  {
  92      QSettings settings;
  93      if (!restoreGeometry(settings.value("MainWindowGeometry").toByteArray())) {
  94          // Restore failed (perhaps missing setting), center the window
  95          move(QGuiApplication::primaryScreen()->availableGeometry().center() - frameGeometry().center());
  96      }
  97  
  98      setContextMenuPolicy(Qt::PreventContextMenu);
  99  
 100  #ifdef ENABLE_WALLET
 101      enableWallet = WalletModel::isWalletEnabled();
 102  #endif // ENABLE_WALLET
 103      QApplication::setWindowIcon(m_network_style->getTrayAndWindowIcon());
 104      setWindowIcon(m_network_style->getTrayAndWindowIcon());
 105      updateWindowTitle();
 106  
 107      rpcConsole = new RPCConsole(node, _platformStyle, nullptr);
 108      helpMessageDialog = new HelpMessageDialog(this, false);
 109  #ifdef ENABLE_WALLET
 110      if(enableWallet)
 111      {
 112          /** Create wallet frame and make it the central widget */
 113          walletFrame = new WalletFrame(_platformStyle, this);
 114          connect(walletFrame, &WalletFrame::createWalletButtonClicked, this, &BitcoinGUI::createWallet);
 115          connect(walletFrame, &WalletFrame::message, [this](const QString& title, const QString& message, unsigned int style) {
 116              this->message(title, message, style);
 117          });
 118          connect(walletFrame, &WalletFrame::currentWalletSet, [this] { updateWalletStatus(); });
 119          setCentralWidget(walletFrame);
 120      } else
 121  #endif // ENABLE_WALLET
 122      {
 123          /* When compiled without wallet or -disablewallet is provided,
 124           * the central widget is the rpc console.
 125           */
 126          setCentralWidget(rpcConsole);
 127          Q_EMIT consoleShown(rpcConsole);
 128      }
 129  
 130      modalOverlay = new ModalOverlay(enableWallet, this->centralWidget());
 131  
 132      // Accept D&D of URIs
 133      setAcceptDrops(true);
 134  
 135      // Create actions for the toolbar, menu bar and tray/dock icon
 136      // Needs walletFrame to be initialized
 137      createActions();
 138  
 139      // Create application menu bar
 140      createMenuBar();
 141  
 142      // Create the toolbars
 143      createToolBars();
 144  
 145      // Create system tray icon and notification
 146      if (QSystemTrayIcon::isSystemTrayAvailable()) {
 147          createTrayIcon();
 148      }
 149      notificator = new Notificator(QApplication::applicationName(), trayIcon, this);
 150  
 151      // Create status bar
 152      statusBar();
 153  
 154      // Disable size grip because it looks ugly and nobody needs it
 155      statusBar()->setSizeGripEnabled(false);
 156  
 157      // Status bar notification icons
 158      QFrame *frameBlocks = new QFrame();
 159      frameBlocks->setContentsMargins(0,0,0,0);
 160      frameBlocks->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
 161      QHBoxLayout *frameBlocksLayout = new QHBoxLayout(frameBlocks);
 162      frameBlocksLayout->setContentsMargins(3,0,3,0);
 163      frameBlocksLayout->setSpacing(3);
 164      unitDisplayControl = new UnitDisplayStatusBarControl(platformStyle);
 165      labelWalletEncryptionIcon = new GUIUtil::ThemedLabel(platformStyle);
 166      labelWalletHDStatusIcon = new GUIUtil::ThemedLabel(platformStyle);
 167      labelProxyIcon = new GUIUtil::ClickableLabel(platformStyle);
 168      connectionsControl = new GUIUtil::ClickableLabel(platformStyle);
 169      labelBlocksIcon = new GUIUtil::ClickableLabel(platformStyle);
 170      if(enableWallet)
 171      {
 172          frameBlocksLayout->addStretch();
 173          frameBlocksLayout->addWidget(unitDisplayControl);
 174          frameBlocksLayout->addStretch();
 175          frameBlocksLayout->addWidget(labelWalletEncryptionIcon);
 176          labelWalletEncryptionIcon->hide();
 177          frameBlocksLayout->addWidget(labelWalletHDStatusIcon);
 178          labelWalletHDStatusIcon->hide();
 179      }
 180      frameBlocksLayout->addWidget(labelProxyIcon);
 181      frameBlocksLayout->addStretch();
 182      frameBlocksLayout->addWidget(connectionsControl);
 183      frameBlocksLayout->addStretch();
 184      frameBlocksLayout->addWidget(labelBlocksIcon);
 185      frameBlocksLayout->addStretch();
 186  
 187      // Progress bar and label for blocks download
 188      progressBarLabel = new QLabel();
 189      progressBarLabel->setVisible(false);
 190      progressBar = new GUIUtil::ProgressBar();
 191      progressBar->setAlignment(Qt::AlignCenter);
 192      progressBar->setVisible(false);
 193  
 194      // Override style sheet for progress bar for styles that have a segmented progress bar,
 195      // as they make the text unreadable (workaround for issue #1071)
 196      // See https://doc.qt.io/qt-5/gallery.html
 197      QString curStyle = QApplication::style()->metaObject()->className();
 198      if(curStyle == "QWindowsStyle" || curStyle == "QWindowsXPStyle")
 199      {
 200          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; }");
 201      }
 202  
 203      statusBar()->addWidget(progressBarLabel);
 204      statusBar()->addWidget(progressBar);
 205      statusBar()->addPermanentWidget(frameBlocks);
 206  
 207      // Install event filter to be able to catch status tip events (QEvent::StatusTip)
 208      this->installEventFilter(this);
 209  
 210      // Initially wallet actions should be disabled
 211      setWalletActionsEnabled(false);
 212  
 213      // Subscribe to notifications from core
 214      subscribeToCoreSignals();
 215  
 216      connect(labelProxyIcon, &GUIUtil::ClickableLabel::clicked, [this] {
 217          openOptionsDialogWithTab(OptionsDialog::TAB_NETWORK);
 218      });
 219  
 220      connect(labelBlocksIcon, &GUIUtil::ClickableLabel::clicked, this, &BitcoinGUI::showModalOverlay);
 221      connect(progressBar, &GUIUtil::ClickableProgressBar::clicked, this, &BitcoinGUI::showModalOverlay);
 222  
 223  #ifdef Q_OS_MACOS
 224      m_app_nap_inhibitor = new CAppNapInhibitor;
 225  #endif
 226  
 227      GUIUtil::handleCloseWindowShortcut(this);
 228  }
 229  
 230  BitcoinGUI::~BitcoinGUI()
 231  {
 232      // Unsubscribe from notifications from core
 233      unsubscribeFromCoreSignals();
 234  
 235      QSettings settings;
 236      settings.setValue("MainWindowGeometry", saveGeometry());
 237      if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu)
 238          trayIcon->hide();
 239  #ifdef Q_OS_MACOS
 240      delete m_app_nap_inhibitor;
 241      MacDockIconHandler::cleanup();
 242  #endif
 243  
 244      delete rpcConsole;
 245  }
 246  
 247  void BitcoinGUI::createActions()
 248  {
 249      QActionGroup *tabGroup = new QActionGroup(this);
 250      connect(modalOverlay, &ModalOverlay::triggered, tabGroup, &QActionGroup::setEnabled);
 251  
 252      overviewAction = new QAction(platformStyle->SingleColorIcon(":/icons/overview"), tr("&Overview"), this);
 253      overviewAction->setStatusTip(tr("Show general overview of wallet"));
 254      overviewAction->setToolTip(overviewAction->statusTip());
 255      overviewAction->setCheckable(true);
 256      overviewAction->setShortcut(QKeySequence(QStringLiteral("Alt+1")));
 257      tabGroup->addAction(overviewAction);
 258  
 259      sendCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/send"), tr("&Send"), this);
 260      sendCoinsAction->setStatusTip(tr("Send coins to a Bitcoin address"));
 261      sendCoinsAction->setToolTip(sendCoinsAction->statusTip());
 262      sendCoinsAction->setCheckable(true);
 263      sendCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+2")));
 264      tabGroup->addAction(sendCoinsAction);
 265  
 266      receiveCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/receiving_addresses"), tr("&Receive"), this);
 267      receiveCoinsAction->setStatusTip(tr("Request payments (generates QR codes and bitcoin: URIs)"));
 268      receiveCoinsAction->setToolTip(receiveCoinsAction->statusTip());
 269      receiveCoinsAction->setCheckable(true);
 270      receiveCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+3")));
 271      tabGroup->addAction(receiveCoinsAction);
 272  
 273      historyAction = new QAction(platformStyle->SingleColorIcon(":/icons/history"), tr("&Transactions"), this);
 274      historyAction->setStatusTip(tr("Browse transaction history"));
 275      historyAction->setToolTip(historyAction->statusTip());
 276      historyAction->setCheckable(true);
 277      historyAction->setShortcut(QKeySequence(QStringLiteral("Alt+4")));
 278      tabGroup->addAction(historyAction);
 279  
 280  #ifdef ENABLE_WALLET
 281      // These showNormalIfMinimized are needed because Send Coins and Receive Coins
 282      // can be triggered from the tray menu, and need to show the GUI to be useful.
 283      connect(overviewAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
 284      connect(overviewAction, &QAction::triggered, this, &BitcoinGUI::gotoOverviewPage);
 285      connect(sendCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
 286      connect(sendCoinsAction, &QAction::triggered, [this]{ gotoSendCoinsPage(); });
 287      connect(receiveCoinsAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
 288      connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage);
 289      connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
 290      connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage);
 291  #endif // ENABLE_WALLET
 292  
 293      quitAction = new QAction(tr("E&xit"), this);
 294      quitAction->setStatusTip(tr("Quit application"));
 295      quitAction->setShortcut(QKeySequence(tr("Ctrl+Q")));
 296      quitAction->setMenuRole(QAction::QuitRole);
 297      aboutAction = new QAction(tr("&About %1").arg(CLIENT_NAME), this);
 298      aboutAction->setStatusTip(tr("Show information about %1").arg(CLIENT_NAME));
 299      aboutAction->setMenuRole(QAction::AboutRole);
 300      aboutAction->setEnabled(false);
 301      aboutQtAction = new QAction(tr("About &Qt"), this);
 302      aboutQtAction->setStatusTip(tr("Show information about Qt"));
 303      aboutQtAction->setMenuRole(QAction::AboutQtRole);
 304      optionsAction = new QAction(tr("&Options…"), this);
 305      optionsAction->setStatusTip(tr("Modify configuration options for %1").arg(CLIENT_NAME));
 306      optionsAction->setMenuRole(QAction::PreferencesRole);
 307      optionsAction->setEnabled(false);
 308  
 309      encryptWalletAction = new QAction(tr("&Encrypt Wallet…"), this);
 310      encryptWalletAction->setStatusTip(tr("Encrypt the private keys that belong to your wallet"));
 311      encryptWalletAction->setCheckable(true);
 312      backupWalletAction = new QAction(tr("&Backup Wallet…"), this);
 313      backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
 314      changePassphraseAction = new QAction(tr("&Change Passphrase…"), this);
 315      changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
 316      signMessageAction = new QAction(tr("Sign &message…"), this);
 317      signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them"));
 318      verifyMessageAction = new QAction(tr("&Verify message…"), this);
 319      verifyMessageAction->setStatusTip(tr("Verify messages to ensure they were signed with specified Bitcoin addresses"));
 320      m_load_psbt_action = new QAction(tr("&Load PSBT from file…"), this);
 321      m_load_psbt_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction"));
 322      m_load_psbt_clipboard_action = new QAction(tr("Load PSBT from &clipboard…"), this);
 323      m_load_psbt_clipboard_action->setStatusTip(tr("Load Partially Signed Bitcoin Transaction from clipboard"));
 324  
 325      openRPCConsoleAction = new QAction(tr("Node window"), this);
 326      openRPCConsoleAction->setStatusTip(tr("Open node debugging and diagnostic console"));
 327      // initially disable the debug window menu item
 328      openRPCConsoleAction->setEnabled(false);
 329      openRPCConsoleAction->setObjectName("openRPCConsoleAction");
 330  
 331      usedSendingAddressesAction = new QAction(tr("&Sending addresses"), this);
 332      usedSendingAddressesAction->setStatusTip(tr("Show the list of used sending addresses and labels"));
 333      usedReceivingAddressesAction = new QAction(tr("&Receiving addresses"), this);
 334      usedReceivingAddressesAction->setStatusTip(tr("Show the list of used receiving addresses and labels"));
 335  
 336      openAction = new QAction(tr("Open &URI…"), this);
 337      openAction->setStatusTip(tr("Open a bitcoin: URI"));
 338  
 339      m_open_wallet_action = new QAction(tr("Open Wallet"), this);
 340      m_open_wallet_action->setEnabled(false);
 341      m_open_wallet_action->setStatusTip(tr("Open a wallet"));
 342      m_open_wallet_menu = new QMenu(this);
 343  
 344      m_close_wallet_action = new QAction(tr("Close Wallet…"), this);
 345      m_close_wallet_action->setStatusTip(tr("Close wallet"));
 346  
 347      m_create_wallet_action = new QAction(tr("Create Wallet…"), this);
 348      m_create_wallet_action->setEnabled(false);
 349      m_create_wallet_action->setStatusTip(tr("Create a new wallet"));
 350  
 351      //: Name of the menu item that restores wallet from a backup file.
 352      m_restore_wallet_action = new QAction(tr("Restore Wallet…"), this);
 353      m_restore_wallet_action->setEnabled(false);
 354      //: Status tip for Restore Wallet menu item
 355      m_restore_wallet_action->setStatusTip(tr("Restore a wallet from a backup file"));
 356  
 357      m_close_all_wallets_action = new QAction(tr("Close All Wallets…"), this);
 358      m_close_all_wallets_action->setStatusTip(tr("Close all wallets"));
 359  
 360      m_migrate_wallet_action = new QAction(tr("Migrate Wallet"), this);
 361      m_migrate_wallet_action->setEnabled(false);
 362      m_migrate_wallet_action->setStatusTip(tr("Migrate a wallet"));
 363      m_migrate_wallet_menu = new QMenu(this);
 364  
 365      showHelpMessageAction = new QAction(tr("&Command-line options"), this);
 366      showHelpMessageAction->setMenuRole(QAction::NoRole);
 367      showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Bitcoin command-line options").arg(CLIENT_NAME));
 368  
 369      m_mask_values_action = new QAction(tr("&Mask values"), this);
 370      m_mask_values_action->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_M));
 371      m_mask_values_action->setStatusTip(tr("Mask the values in the Overview tab"));
 372      m_mask_values_action->setCheckable(true);
 373  
 374      connect(quitAction, &QAction::triggered, this, &BitcoinGUI::quitRequested);
 375      connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked);
 376      connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt);
 377      connect(optionsAction, &QAction::triggered, this, &BitcoinGUI::optionsClicked);
 378      connect(showHelpMessageAction, &QAction::triggered, this, &BitcoinGUI::showHelpMessageClicked);
 379      connect(openRPCConsoleAction, &QAction::triggered, this, &BitcoinGUI::showDebugWindow);
 380      // prevents an open debug window from becoming stuck/unusable on client shutdown
 381      connect(quitAction, &QAction::triggered, rpcConsole, &QWidget::hide);
 382  
 383  #ifdef ENABLE_WALLET
 384      if(walletFrame)
 385      {
 386          connect(encryptWalletAction, &QAction::triggered, walletFrame, &WalletFrame::encryptWallet);
 387          connect(backupWalletAction, &QAction::triggered, walletFrame, &WalletFrame::backupWallet);
 388          connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase);
 389          connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
 390          connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); });
 391          connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); });
 392          connect(m_load_psbt_clipboard_action, &QAction::triggered, [this]{ gotoLoadPSBT(true); });
 393          connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); });
 394          connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); });
 395          connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses);
 396          connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedReceivingAddresses);
 397          connect(openAction, &QAction::triggered, this, &BitcoinGUI::openClicked);
 398          connect(m_open_wallet_menu, &QMenu::aboutToShow, [this] {
 399              m_open_wallet_menu->clear();
 400              for (const auto& [path, info] : m_wallet_controller->listWalletDir()) {
 401                  const auto& [loaded, format] = info;
 402                  QString name = GUIUtil::WalletDisplayName(path);
 403                  // An single ampersand in the menu item's text sets a shortcut for this item.
 404                  // Single & are shown when && is in the string. So replace & with &&.
 405                  name.replace(QChar('&'), QString("&&"));
 406                  bool is_legacy = format == "bdb";
 407                  if (is_legacy) {
 408                      name += " (needs migration)";
 409                  }
 410                  QAction* action = m_open_wallet_menu->addAction(name);
 411  
 412                  if (loaded || is_legacy) {
 413                      // This wallet is already loaded or it is a legacy wallet
 414                      action->setEnabled(false);
 415                      continue;
 416                  }
 417  
 418                  connect(action, &QAction::triggered, [this, path] {
 419                      auto activity = new OpenWalletActivity(m_wallet_controller, this);
 420                      connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection);
 421                      connect(activity, &OpenWalletActivity::opened, rpcConsole, &RPCConsole::setCurrentWallet, Qt::QueuedConnection);
 422                      activity->open(path);
 423                  });
 424              }
 425              if (m_open_wallet_menu->isEmpty()) {
 426                  QAction* action = m_open_wallet_menu->addAction(tr("No wallets available"));
 427                  action->setEnabled(false);
 428              }
 429          });
 430          connect(m_restore_wallet_action, &QAction::triggered, [this] {
 431              //: Name of the wallet data file format.
 432              QString name_data_file = tr("Wallet Data");
 433  
 434              //: The title for Restore Wallet File Windows
 435              QString title_windows = tr("Load Wallet Backup");
 436  
 437              QString backup_file = GUIUtil::getOpenFileName(this, title_windows, QString(), name_data_file + QLatin1String(" (*.dat)"), nullptr);
 438              if (backup_file.isEmpty()) return;
 439  
 440              bool wallet_name_ok;
 441              /*: Title of pop-up window shown when the user is attempting to
 442                  restore a wallet. */
 443              QString title = tr("Restore Wallet");
 444              //: Label of the input field where the name of the wallet is entered.
 445              QString label = tr("Wallet Name");
 446              QString wallet_name = QInputDialog::getText(this, title, label, QLineEdit::Normal, "", &wallet_name_ok);
 447              if (!wallet_name_ok || wallet_name.isEmpty()) return;
 448  
 449              auto activity = new RestoreWalletActivity(m_wallet_controller, this);
 450              connect(activity, &RestoreWalletActivity::restored, this, &BitcoinGUI::setCurrentWallet, Qt::QueuedConnection);
 451              connect(activity, &RestoreWalletActivity::restored, rpcConsole, &RPCConsole::setCurrentWallet, Qt::QueuedConnection);
 452  
 453              auto backup_file_path = fs::PathFromString(backup_file.toStdString());
 454              activity->restore(backup_file_path, wallet_name.toStdString());
 455          });
 456          connect(m_close_wallet_action, &QAction::triggered, [this] {
 457              m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this);
 458          });
 459          connect(m_create_wallet_action, &QAction::triggered, this, &BitcoinGUI::createWallet);
 460          connect(m_close_all_wallets_action, &QAction::triggered, [this] {
 461              m_wallet_controller->closeAllWallets(this);
 462          });
 463          connect(m_migrate_wallet_menu, &QMenu::aboutToShow, [this] {
 464              m_migrate_wallet_menu->clear();
 465              for (const auto& [wallet_name, info] : m_wallet_controller->listWalletDir()) {
 466                  const auto& [loaded, format] = info;
 467  
 468                  if (format != "bdb") { // Skip already migrated wallets
 469                      continue;
 470                  }
 471  
 472                  QString name = GUIUtil::WalletDisplayName(wallet_name);
 473                  // An single ampersand in the menu item's text sets a shortcut for this item.
 474                  // Single & are shown when && is in the string. So replace & with &&.
 475                  name.replace(QChar('&'), QString("&&"));
 476                  QAction* action = m_migrate_wallet_menu->addAction(name);
 477  
 478                  connect(action, &QAction::triggered, [this, wallet_name] {
 479                      auto activity = new MigrateWalletActivity(m_wallet_controller, this);
 480                      connect(activity, &MigrateWalletActivity::migrated, this, &BitcoinGUI::setCurrentWallet);
 481                      activity->migrate(wallet_name);
 482                  });
 483              }
 484              if (m_migrate_wallet_menu->isEmpty()) {
 485                  QAction* action = m_migrate_wallet_menu->addAction(tr("No wallets available"));
 486                  action->setEnabled(false);
 487              }
 488          });
 489          connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy);
 490          connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);
 491      }
 492  #endif // ENABLE_WALLET
 493  
 494      connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindowActivateConsole);
 495      connect(new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_D), this), &QShortcut::activated, this, &BitcoinGUI::showDebugWindow);
 496  }
 497  
 498  void BitcoinGUI::createMenuBar()
 499  {
 500      appMenuBar = menuBar();
 501  
 502      // Configure the menus
 503      QMenu *file = appMenuBar->addMenu(tr("&File"));
 504      if(walletFrame)
 505      {
 506          file->addAction(m_create_wallet_action);
 507          file->addAction(m_open_wallet_action);
 508          file->addAction(m_close_wallet_action);
 509          file->addAction(m_close_all_wallets_action);
 510          file->addAction(m_migrate_wallet_action);
 511          file->addSeparator();
 512          file->addAction(backupWalletAction);
 513          file->addAction(m_restore_wallet_action);
 514          file->addSeparator();
 515          file->addAction(openAction);
 516          file->addAction(signMessageAction);
 517          file->addAction(verifyMessageAction);
 518          file->addAction(m_load_psbt_action);
 519          file->addAction(m_load_psbt_clipboard_action);
 520          file->addSeparator();
 521      }
 522      file->addAction(quitAction);
 523  
 524      QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
 525      if(walletFrame)
 526      {
 527          settings->addAction(encryptWalletAction);
 528          settings->addAction(changePassphraseAction);
 529          settings->addSeparator();
 530          settings->addAction(m_mask_values_action);
 531          settings->addSeparator();
 532      }
 533      settings->addAction(optionsAction);
 534  
 535      QMenu* window_menu = appMenuBar->addMenu(tr("&Window"));
 536  
 537      QAction* minimize_action = window_menu->addAction(tr("&Minimize"));
 538      minimize_action->setShortcut(QKeySequence(tr("Ctrl+M")));
 539      connect(minimize_action, &QAction::triggered, [] {
 540          QApplication::activeWindow()->showMinimized();
 541      });
 542      connect(qApp, &QApplication::focusWindowChanged, this, [minimize_action] (QWindow* window) {
 543          minimize_action->setEnabled(window != nullptr && (window->flags() & Qt::Dialog) != Qt::Dialog && window->windowState() != Qt::WindowMinimized);
 544      });
 545  
 546  #ifdef Q_OS_MACOS
 547      QAction* zoom_action = window_menu->addAction(tr("Zoom"));
 548      connect(zoom_action, &QAction::triggered, [] {
 549          QWindow* window = qApp->focusWindow();
 550          if (window->windowState() != Qt::WindowMaximized) {
 551              window->showMaximized();
 552          } else {
 553              window->showNormal();
 554          }
 555      });
 556  
 557      connect(qApp, &QApplication::focusWindowChanged, this, [zoom_action] (QWindow* window) {
 558          zoom_action->setEnabled(window != nullptr);
 559      });
 560  #endif
 561  
 562      if (walletFrame) {
 563  #ifdef Q_OS_MACOS
 564          window_menu->addSeparator();
 565          QAction* main_window_action = window_menu->addAction(tr("Main Window"));
 566          connect(main_window_action, &QAction::triggered, [this] {
 567              GUIUtil::bringToFront(this);
 568          });
 569  #endif
 570          window_menu->addSeparator();
 571          window_menu->addAction(usedSendingAddressesAction);
 572          window_menu->addAction(usedReceivingAddressesAction);
 573      }
 574  
 575      window_menu->addSeparator();
 576      for (RPCConsole::TabTypes tab_type : rpcConsole->tabs()) {
 577          QAction* tab_action = window_menu->addAction(rpcConsole->tabTitle(tab_type));
 578          tab_action->setShortcut(rpcConsole->tabShortcut(tab_type));
 579          connect(tab_action, &QAction::triggered, [this, tab_type] {
 580              rpcConsole->setTabFocus(tab_type);
 581              showDebugWindow();
 582          });
 583      }
 584  
 585      QMenu *help = appMenuBar->addMenu(tr("&Help"));
 586      help->addAction(showHelpMessageAction);
 587      help->addSeparator();
 588      help->addAction(aboutAction);
 589      help->addAction(aboutQtAction);
 590  }
 591  
 592  void BitcoinGUI::createToolBars()
 593  {
 594      if(walletFrame)
 595      {
 596          QToolBar *toolbar = addToolBar(tr("Tabs toolbar"));
 597          appToolBar = toolbar;
 598          toolbar->setMovable(false);
 599          toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
 600          toolbar->addAction(overviewAction);
 601          toolbar->addAction(sendCoinsAction);
 602          toolbar->addAction(receiveCoinsAction);
 603          toolbar->addAction(historyAction);
 604          overviewAction->setChecked(true);
 605  
 606  #ifdef ENABLE_WALLET
 607          QWidget *spacer = new QWidget();
 608          spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
 609          toolbar->addWidget(spacer);
 610  
 611          m_wallet_selector = new QComboBox();
 612          m_wallet_selector->setSizeAdjustPolicy(QComboBox::AdjustToContents);
 613          connect(m_wallet_selector, qOverload<int>(&QComboBox::currentIndexChanged), this, &BitcoinGUI::setCurrentWalletBySelectorIndex);
 614  
 615          m_wallet_selector_label = new QLabel();
 616          m_wallet_selector_label->setText(tr("Wallet:") + " ");
 617          m_wallet_selector_label->setBuddy(m_wallet_selector);
 618  
 619          m_wallet_selector_label_action = appToolBar->addWidget(m_wallet_selector_label);
 620          m_wallet_selector_action = appToolBar->addWidget(m_wallet_selector);
 621  
 622          m_wallet_selector_label_action->setVisible(false);
 623          m_wallet_selector_action->setVisible(false);
 624  #endif
 625      }
 626  }
 627  
 628  void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndHeaderTipInfo* tip_info)
 629  {
 630      this->clientModel = _clientModel;
 631      if(_clientModel)
 632      {
 633          // Create system tray menu (or setup the dock menu) that late to prevent users from calling actions,
 634          // while the client has not yet fully loaded
 635          createTrayIconMenu();
 636  
 637          // Keep up to date with client
 638          setNetworkActive(m_node.getNetworkActive());
 639          connect(connectionsControl, &GUIUtil::ClickableLabel::clicked, [this] {
 640              GUIUtil::PopupMenu(m_network_context_menu, QCursor::pos());
 641          });
 642          connect(_clientModel, &ClientModel::numConnectionsChanged, this, &BitcoinGUI::setNumConnections);
 643          connect(_clientModel, &ClientModel::networkActiveChanged, this, &BitcoinGUI::setNetworkActive);
 644  
 645          modalOverlay->setKnownBestHeight(tip_info->header_height, QDateTime::fromSecsSinceEpoch(tip_info->header_time), /*presync=*/false);
 646          setNumBlocks(tip_info->block_height, QDateTime::fromSecsSinceEpoch(tip_info->block_time), tip_info->verification_progress, SyncType::BLOCK_SYNC, SynchronizationState::INIT_DOWNLOAD);
 647          connect(_clientModel, &ClientModel::numBlocksChanged, this, &BitcoinGUI::setNumBlocks);
 648  
 649          // Receive and report messages from client model
 650          connect(_clientModel, &ClientModel::message, [this](const QString &title, const QString &message, unsigned int style){
 651              this->message(title, message, style);
 652          });
 653  
 654          // Show progress dialog
 655          connect(_clientModel, &ClientModel::showProgress, this, &BitcoinGUI::showProgress);
 656  
 657          rpcConsole->setClientModel(_clientModel, tip_info->block_height, tip_info->block_time, tip_info->verification_progress);
 658  
 659          updateProxyIcon();
 660  
 661  #ifdef ENABLE_WALLET
 662          if(walletFrame)
 663          {
 664              walletFrame->setClientModel(_clientModel);
 665          }
 666  #endif // ENABLE_WALLET
 667          unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel());
 668  
 669          OptionsModel* optionsModel = _clientModel->getOptionsModel();
 670          if (optionsModel && trayIcon) {
 671              // be aware of the tray icon disable state change reported by the OptionsModel object.
 672              connect(optionsModel, &OptionsModel::showTrayIconChanged, trayIcon, &QSystemTrayIcon::setVisible);
 673  
 674              // initialize the disable state of the tray icon with the current value in the model.
 675              trayIcon->setVisible(optionsModel->getShowTrayIcon());
 676          }
 677  
 678          m_mask_values_action->setChecked(_clientModel->getOptionsModel()->getOption(OptionsModel::OptionID::MaskValues).toBool());
 679      } else {
 680          // Shutdown requested, disable menus
 681          if (trayIconMenu)
 682          {
 683              // Disable context menu on tray icon
 684              trayIconMenu->clear();
 685          }
 686          // Propagate cleared model to child objects
 687          rpcConsole->setClientModel(nullptr);
 688  #ifdef ENABLE_WALLET
 689          if (walletFrame)
 690          {
 691              walletFrame->setClientModel(nullptr);
 692          }
 693  #endif // ENABLE_WALLET
 694          unitDisplayControl->setOptionsModel(nullptr);
 695          // Disable top bar menu actions
 696          appMenuBar->clear();
 697      }
 698  }
 699  
 700  #ifdef ENABLE_WALLET
 701  void BitcoinGUI::enableHistoryAction(bool privacy)
 702  {
 703      if (walletFrame->currentWalletModel()) {
 704          historyAction->setEnabled(!privacy);
 705          if (historyAction->isChecked()) gotoOverviewPage();
 706      }
 707  }
 708  
 709  void BitcoinGUI::setWalletController(WalletController* wallet_controller, bool show_loading_minimized)
 710  {
 711      assert(!m_wallet_controller);
 712      assert(wallet_controller);
 713  
 714      m_wallet_controller = wallet_controller;
 715  
 716      m_create_wallet_action->setEnabled(true);
 717      m_open_wallet_action->setEnabled(true);
 718      m_open_wallet_action->setMenu(m_open_wallet_menu);
 719      m_restore_wallet_action->setEnabled(true);
 720      m_migrate_wallet_action->setEnabled(true);
 721      m_migrate_wallet_action->setMenu(m_migrate_wallet_menu);
 722  
 723      GUIUtil::ExceptionSafeConnect(wallet_controller, &WalletController::walletAdded, this, &BitcoinGUI::addWallet);
 724      connect(wallet_controller, &WalletController::walletRemoved, this, &BitcoinGUI::removeWallet);
 725      connect(wallet_controller, &WalletController::destroyed, this, [this] {
 726          // wallet_controller gets destroyed manually, but it leaves our member copy dangling
 727          m_wallet_controller = nullptr;
 728      });
 729  
 730      auto activity = new LoadWalletsActivity(m_wallet_controller, this);
 731      activity->load(show_loading_minimized);
 732  }
 733  
 734  WalletController* BitcoinGUI::getWalletController()
 735  {
 736      return m_wallet_controller;
 737  }
 738  
 739  void BitcoinGUI::addWallet(WalletModel* walletModel)
 740  {
 741      if (!walletFrame || !m_wallet_controller) return;
 742  
 743      WalletView* wallet_view = new WalletView(walletModel, platformStyle, walletFrame);
 744      if (!walletFrame->addView(wallet_view)) return;
 745  
 746      rpcConsole->addWallet(walletModel);
 747      if (m_wallet_selector->count() == 0) {
 748          setWalletActionsEnabled(true);
 749      } else if (m_wallet_selector->count() == 1) {
 750          m_wallet_selector_label_action->setVisible(true);
 751          m_wallet_selector_action->setVisible(true);
 752      }
 753  
 754      connect(wallet_view, &WalletView::outOfSyncWarningClicked, this, &BitcoinGUI::showModalOverlay);
 755      connect(wallet_view, &WalletView::transactionClicked, this, &BitcoinGUI::gotoHistoryPage);
 756      connect(wallet_view, &WalletView::coinsSent, this, &BitcoinGUI::gotoHistoryPage);
 757      connect(wallet_view, &WalletView::message, [this](const QString& title, const QString& message, unsigned int style) {
 758          this->message(title, message, style);
 759      });
 760      connect(wallet_view, &WalletView::encryptionStatusChanged, this, &BitcoinGUI::updateWalletStatus);
 761      connect(wallet_view, &WalletView::incomingTransaction, this, &BitcoinGUI::incomingTransaction);
 762      connect(this, &BitcoinGUI::setPrivacy, wallet_view, &WalletView::setPrivacy);
 763      const bool privacy = isPrivacyModeActivated();
 764      wallet_view->setPrivacy(privacy);
 765      enableHistoryAction(privacy);
 766      const QString display_name = walletModel->getDisplayName();
 767      m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel));
 768  }
 769  
 770  void BitcoinGUI::removeWallet(WalletModel* walletModel)
 771  {
 772      if (!walletFrame) return;
 773  
 774      labelWalletHDStatusIcon->hide();
 775      labelWalletEncryptionIcon->hide();
 776  
 777      int index = m_wallet_selector->findData(QVariant::fromValue(walletModel));
 778      m_wallet_selector->removeItem(index);
 779      if (m_wallet_selector->count() == 0) {
 780          setWalletActionsEnabled(false);
 781          overviewAction->setChecked(true);
 782      } else if (m_wallet_selector->count() == 1) {
 783          m_wallet_selector_label_action->setVisible(false);
 784          m_wallet_selector_action->setVisible(false);
 785      }
 786      rpcConsole->removeWallet(walletModel);
 787      walletFrame->removeWallet(walletModel);
 788      updateWindowTitle();
 789  }
 790  
 791  void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
 792  {
 793      if (!walletFrame || !m_wallet_controller) return;
 794      walletFrame->setCurrentWallet(wallet_model);
 795      for (int index = 0; index < m_wallet_selector->count(); ++index) {
 796          if (m_wallet_selector->itemData(index).value<WalletModel*>() == wallet_model) {
 797              m_wallet_selector->setCurrentIndex(index);
 798              break;
 799          }
 800      }
 801      updateWindowTitle();
 802  }
 803  
 804  void BitcoinGUI::setCurrentWalletBySelectorIndex(int index)
 805  {
 806      WalletModel* wallet_model = m_wallet_selector->itemData(index).value<WalletModel*>();
 807      if (wallet_model) setCurrentWallet(wallet_model);
 808  }
 809  
 810  void BitcoinGUI::removeAllWallets()
 811  {
 812      if(!walletFrame)
 813          return;
 814      setWalletActionsEnabled(false);
 815      walletFrame->removeAllWallets();
 816  }
 817  #endif // ENABLE_WALLET
 818  
 819  void BitcoinGUI::setWalletActionsEnabled(bool enabled)
 820  {
 821      overviewAction->setEnabled(enabled);
 822      sendCoinsAction->setEnabled(enabled);
 823      receiveCoinsAction->setEnabled(enabled);
 824      historyAction->setEnabled(enabled);
 825      encryptWalletAction->setEnabled(enabled);
 826      backupWalletAction->setEnabled(enabled);
 827      changePassphraseAction->setEnabled(enabled);
 828      signMessageAction->setEnabled(enabled);
 829      verifyMessageAction->setEnabled(enabled);
 830      usedSendingAddressesAction->setEnabled(enabled);
 831      usedReceivingAddressesAction->setEnabled(enabled);
 832      openAction->setEnabled(enabled);
 833      m_close_wallet_action->setEnabled(enabled);
 834      m_close_all_wallets_action->setEnabled(enabled);
 835  }
 836  
 837  void BitcoinGUI::createTrayIcon()
 838  {
 839      assert(QSystemTrayIcon::isSystemTrayAvailable());
 840  
 841  #ifndef Q_OS_MACOS
 842      if (QSystemTrayIcon::isSystemTrayAvailable()) {
 843          trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this);
 844          QString toolTip = tr("%1 client").arg(CLIENT_NAME) + " " + m_network_style->getTitleAddText();
 845          trayIcon->setToolTip(toolTip);
 846      }
 847  #endif
 848  }
 849  
 850  void BitcoinGUI::createTrayIconMenu()
 851  {
 852  #ifndef Q_OS_MACOS
 853      if (!trayIcon) return;
 854  #endif // Q_OS_MACOS
 855  
 856      // Configuration of the tray icon (or Dock icon) menu.
 857      QAction* show_hide_action{nullptr};
 858  #ifndef Q_OS_MACOS
 859      // Note: On macOS, the Dock icon's menu already has Show / Hide action.
 860      show_hide_action = trayIconMenu->addAction(QString(), this, &BitcoinGUI::toggleHidden);
 861      trayIconMenu->addSeparator();
 862  #endif // Q_OS_MACOS
 863  
 864      QAction* send_action{nullptr};
 865      QAction* receive_action{nullptr};
 866      QAction* sign_action{nullptr};
 867      QAction* verify_action{nullptr};
 868      if (enableWallet) {
 869          send_action = trayIconMenu->addAction(sendCoinsAction->text(), sendCoinsAction, &QAction::trigger);
 870          receive_action = trayIconMenu->addAction(receiveCoinsAction->text(), receiveCoinsAction, &QAction::trigger);
 871          trayIconMenu->addSeparator();
 872          sign_action = trayIconMenu->addAction(signMessageAction->text(), signMessageAction, &QAction::trigger);
 873          verify_action = trayIconMenu->addAction(verifyMessageAction->text(), verifyMessageAction, &QAction::trigger);
 874          trayIconMenu->addSeparator();
 875      }
 876      QAction* options_action = trayIconMenu->addAction(optionsAction->text(), optionsAction, &QAction::trigger);
 877      options_action->setMenuRole(QAction::PreferencesRole);
 878      QAction* node_window_action = trayIconMenu->addAction(openRPCConsoleAction->text(), openRPCConsoleAction, &QAction::trigger);
 879      QAction* quit_action{nullptr};
 880  #ifndef Q_OS_MACOS
 881      // Note: On macOS, the Dock icon's menu already has Quit action.
 882      trayIconMenu->addSeparator();
 883      quit_action = trayIconMenu->addAction(quitAction->text(), quitAction, &QAction::trigger);
 884  
 885      trayIcon->setContextMenu(trayIconMenu.get());
 886      connect(trayIcon, &QSystemTrayIcon::activated, [this](QSystemTrayIcon::ActivationReason reason) {
 887          if (reason == QSystemTrayIcon::Trigger) {
 888              // Click on system tray icon triggers show/hide of the main window
 889              toggleHidden();
 890          }
 891      });
 892  #else
 893      // Note: On macOS, the Dock icon is used to provide the tray's functionality.
 894      MacDockIconHandler* dockIconHandler = MacDockIconHandler::instance();
 895      connect(dockIconHandler, &MacDockIconHandler::dockIconClicked, [this] {
 896          if (m_node.shutdownRequested()) return; // nothing to show, node is shutting down.
 897          show();
 898          activateWindow();
 899      });
 900      trayIconMenu->setAsDockMenu();
 901  #endif // Q_OS_MACOS
 902  
 903      connect(
 904          // Using QSystemTrayIcon::Context is not reliable.
 905          // See https://bugreports.qt.io/browse/QTBUG-91697
 906          trayIconMenu.get(), &QMenu::aboutToShow,
 907          [this, show_hide_action, send_action, receive_action, sign_action, verify_action, options_action, node_window_action, quit_action] {
 908              if (m_node.shutdownRequested()) return; // nothing to do, node is shutting down.
 909  
 910              if (show_hide_action) show_hide_action->setText(
 911                  (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this)) ?
 912                      tr("&Hide") :
 913                      tr("S&how"));
 914              if (QApplication::activeModalWidget()) {
 915                  for (QAction* a : trayIconMenu.get()->actions()) {
 916                      a->setEnabled(false);
 917                  }
 918              } else {
 919                  if (show_hide_action) show_hide_action->setEnabled(true);
 920                  if (enableWallet) {
 921                      send_action->setEnabled(sendCoinsAction->isEnabled());
 922                      receive_action->setEnabled(receiveCoinsAction->isEnabled());
 923                      sign_action->setEnabled(signMessageAction->isEnabled());
 924                      verify_action->setEnabled(verifyMessageAction->isEnabled());
 925                  }
 926                  options_action->setEnabled(optionsAction->isEnabled());
 927                  node_window_action->setEnabled(openRPCConsoleAction->isEnabled());
 928                  if (quit_action) quit_action->setEnabled(true);
 929              }
 930          });
 931  }
 932  
 933  void BitcoinGUI::optionsClicked()
 934  {
 935      openOptionsDialogWithTab(OptionsDialog::TAB_MAIN);
 936  }
 937  
 938  void BitcoinGUI::aboutClicked()
 939  {
 940      if(!clientModel)
 941          return;
 942  
 943      auto dlg = new HelpMessageDialog(this, /*about=*/true);
 944      GUIUtil::ShowModalDialogAsynchronously(dlg);
 945  }
 946  
 947  void BitcoinGUI::showDebugWindow()
 948  {
 949      GUIUtil::bringToFront(rpcConsole);
 950      Q_EMIT consoleShown(rpcConsole);
 951  }
 952  
 953  void BitcoinGUI::showDebugWindowActivateConsole()
 954  {
 955      rpcConsole->setTabFocus(RPCConsole::TabTypes::CONSOLE);
 956      showDebugWindow();
 957  }
 958  
 959  void BitcoinGUI::showHelpMessageClicked()
 960  {
 961      GUIUtil::bringToFront(helpMessageDialog);
 962  }
 963  
 964  #ifdef ENABLE_WALLET
 965  void BitcoinGUI::openClicked()
 966  {
 967      OpenURIDialog dlg(platformStyle, this);
 968      if(dlg.exec())
 969      {
 970          Q_EMIT receivedURI(dlg.getURI());
 971      }
 972  }
 973  
 974  void BitcoinGUI::gotoOverviewPage()
 975  {
 976      overviewAction->setChecked(true);
 977      if (walletFrame) walletFrame->gotoOverviewPage();
 978  }
 979  
 980  void BitcoinGUI::gotoHistoryPage()
 981  {
 982      historyAction->setChecked(true);
 983      if (walletFrame) walletFrame->gotoHistoryPage();
 984  }
 985  
 986  void BitcoinGUI::gotoReceiveCoinsPage()
 987  {
 988      receiveCoinsAction->setChecked(true);
 989      if (walletFrame) walletFrame->gotoReceiveCoinsPage();
 990  }
 991  
 992  void BitcoinGUI::gotoSendCoinsPage(QString addr)
 993  {
 994      sendCoinsAction->setChecked(true);
 995      if (walletFrame) walletFrame->gotoSendCoinsPage(addr);
 996  }
 997  
 998  void BitcoinGUI::gotoSignMessageTab(QString addr)
 999  {
1000      if (walletFrame) walletFrame->gotoSignMessageTab(addr);
1001  }
1002  
1003  void BitcoinGUI::gotoVerifyMessageTab(QString addr)
1004  {
1005      if (walletFrame) walletFrame->gotoVerifyMessageTab(addr);
1006  }
1007  void BitcoinGUI::gotoLoadPSBT(bool from_clipboard)
1008  {
1009      if (walletFrame) walletFrame->gotoLoadPSBT(from_clipboard);
1010  }
1011  #endif // ENABLE_WALLET
1012  
1013  void BitcoinGUI::updateNetworkState()
1014  {
1015      if (!clientModel) return;
1016      int count = clientModel->getNumConnections();
1017      QString icon;
1018      switch(count)
1019      {
1020      case 0: icon = ":/icons/connect_0"; break;
1021      case 1: case 2: case 3: icon = ":/icons/connect_1"; break;
1022      case 4: case 5: case 6: icon = ":/icons/connect_2"; break;
1023      case 7: case 8: case 9: icon = ":/icons/connect_3"; break;
1024      default: icon = ":/icons/connect_4"; break;
1025      }
1026  
1027      QString tooltip;
1028  
1029      if (m_node.getNetworkActive()) {
1030          //: A substring of the tooltip.
1031          tooltip = tr("%n active connection(s) to Bitcoin network.", "", count);
1032      } else {
1033          //: A substring of the tooltip.
1034          tooltip = tr("Network activity disabled.");
1035          icon = ":/icons/network_disabled";
1036      }
1037  
1038      // Don't word-wrap this (fixed-width) tooltip
1039      tooltip = QLatin1String("<nobr>") + tooltip + QLatin1String("<br>") +
1040                //: A substring of the tooltip. "More actions" are available via the context menu.
1041                tr("Click for more actions.") + QLatin1String("</nobr>");
1042      connectionsControl->setToolTip(tooltip);
1043  
1044      connectionsControl->setThemedPixmap(icon, STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1045  }
1046  
1047  void BitcoinGUI::setNumConnections(int count)
1048  {
1049      updateNetworkState();
1050  }
1051  
1052  void BitcoinGUI::setNetworkActive(bool network_active)
1053  {
1054      updateNetworkState();
1055      m_network_context_menu->clear();
1056      m_network_context_menu->addAction(
1057          //: A context menu item. The "Peers tab" is an element of the "Node window".
1058          tr("Show Peers tab"),
1059          [this] {
1060              rpcConsole->setTabFocus(RPCConsole::TabTypes::PEERS);
1061              showDebugWindow();
1062          });
1063      m_network_context_menu->addAction(
1064          network_active ?
1065              //: A context menu item.
1066              tr("Disable network activity") :
1067              //: A context menu item. The network activity was disabled previously.
1068              tr("Enable network activity"),
1069          [this, new_state = !network_active] { m_node.setNetworkActive(new_state); });
1070  }
1071  
1072  void BitcoinGUI::updateHeadersSyncProgressLabel()
1073  {
1074      int64_t headersTipTime = clientModel->getHeaderTipTime();
1075      int headersTipHeight = clientModel->getHeaderTipHeight();
1076      int estHeadersLeft = (GetTime() - headersTipTime) / Params().GetConsensus().nPowTargetSpacing;
1077      if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC)
1078          progressBarLabel->setText(tr("Syncing Headers (%1%)…").arg(QString::number(100.0 / (headersTipHeight+estHeadersLeft)*headersTipHeight, 'f', 1)));
1079  }
1080  
1081  void BitcoinGUI::updateHeadersPresyncProgressLabel(int64_t height, const QDateTime& blockDate)
1082  {
1083      int estHeadersLeft = blockDate.secsTo(QDateTime::currentDateTime()) / Params().GetConsensus().nPowTargetSpacing;
1084      if (estHeadersLeft > HEADER_HEIGHT_DELTA_SYNC)
1085          progressBarLabel->setText(tr("Pre-syncing Headers (%1%)…").arg(QString::number(100.0 / (height+estHeadersLeft)*height, 'f', 1)));
1086  }
1087  
1088  void BitcoinGUI::openOptionsDialogWithTab(OptionsDialog::Tab tab)
1089  {
1090      if (!clientModel || !clientModel->getOptionsModel())
1091          return;
1092  
1093      auto dlg = new OptionsDialog(this, enableWallet);
1094      connect(dlg, &OptionsDialog::quitOnReset, this, &BitcoinGUI::quitRequested);
1095      dlg->setCurrentTab(tab);
1096      dlg->setClientModel(clientModel);
1097      dlg->setModel(clientModel->getOptionsModel());
1098      GUIUtil::ShowModalDialogAsynchronously(dlg);
1099  }
1100  
1101  void BitcoinGUI::setNumBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
1102  {
1103  // Disabling macOS App Nap on initial sync, disk and reindex operations.
1104  #ifdef Q_OS_MACOS
1105      if (sync_state == SynchronizationState::POST_INIT) {
1106          m_app_nap_inhibitor->enableAppNap();
1107      } else {
1108          m_app_nap_inhibitor->disableAppNap();
1109      }
1110  #endif
1111  
1112      if (modalOverlay)
1113      {
1114          if (synctype != SyncType::BLOCK_SYNC)
1115              modalOverlay->setKnownBestHeight(count, blockDate, synctype == SyncType::HEADER_PRESYNC);
1116          else
1117              modalOverlay->tipUpdate(count, blockDate, nVerificationProgress);
1118      }
1119      if (!clientModel)
1120          return;
1121  
1122      // Prevent orphan statusbar messages (e.g. hover Quit in main menu, wait until chain-sync starts -> garbled text)
1123      statusBar()->clearMessage();
1124  
1125      // Acquire current block source
1126      BlockSource blockSource{clientModel->getBlockSource()};
1127      switch (blockSource) {
1128          case BlockSource::NETWORK:
1129              if (synctype == SyncType::HEADER_PRESYNC) {
1130                  updateHeadersPresyncProgressLabel(count, blockDate);
1131                  return;
1132              } else if (synctype == SyncType::HEADER_SYNC) {
1133                  updateHeadersSyncProgressLabel();
1134                  return;
1135              }
1136              progressBarLabel->setText(tr("Synchronizing with network…"));
1137              updateHeadersSyncProgressLabel();
1138              break;
1139          case BlockSource::DISK:
1140              if (synctype != SyncType::BLOCK_SYNC) {
1141                  progressBarLabel->setText(tr("Indexing blocks on disk…"));
1142              } else {
1143                  progressBarLabel->setText(tr("Processing blocks on disk…"));
1144              }
1145              break;
1146          case BlockSource::NONE:
1147              if (synctype != SyncType::BLOCK_SYNC) {
1148                  return;
1149              }
1150              progressBarLabel->setText(tr("Connecting to peers…"));
1151              break;
1152      }
1153  
1154      QString tooltip;
1155  
1156      QDateTime currentDate = QDateTime::currentDateTime();
1157      qint64 secs = blockDate.secsTo(currentDate);
1158  
1159      tooltip = tr("Processed %n block(s) of transaction history.", "", count);
1160  
1161      // Set icon state: spinning if catching up, tick otherwise
1162      if (secs < MAX_BLOCK_TIME_GAP) {
1163          tooltip = tr("Up to date") + QString(".<br>") + tooltip;
1164          labelBlocksIcon->setThemedPixmap(QStringLiteral(":/icons/synced"), STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1165  
1166  #ifdef ENABLE_WALLET
1167          if(walletFrame)
1168          {
1169              walletFrame->showOutOfSyncWarning(false);
1170              modalOverlay->showHide(true, true);
1171          }
1172  #endif // ENABLE_WALLET
1173  
1174          progressBarLabel->setVisible(false);
1175          progressBar->setVisible(false);
1176      }
1177      else
1178      {
1179          QString timeBehindText = GUIUtil::formatNiceTimeOffset(secs);
1180  
1181          progressBarLabel->setVisible(true);
1182          progressBar->setFormat(tr("%1 behind").arg(timeBehindText));
1183          progressBar->setMaximum(1000000000);
1184          progressBar->setValue(nVerificationProgress * 1000000000.0 + 0.5);
1185          progressBar->setVisible(true);
1186  
1187          tooltip = tr("Catching up…") + QString("<br>") + tooltip;
1188          if(count != prevBlocks)
1189          {
1190              labelBlocksIcon->setThemedPixmap(
1191                  QString(":/animation/spinner-%1").arg(spinnerFrame, 3, 10, QChar('0')),
1192                  STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1193              spinnerFrame = (spinnerFrame + 1) % SPINNER_FRAMES;
1194          }
1195          prevBlocks = count;
1196  
1197  #ifdef ENABLE_WALLET
1198          if(walletFrame)
1199          {
1200              walletFrame->showOutOfSyncWarning(true);
1201              modalOverlay->showHide();
1202          }
1203  #endif // ENABLE_WALLET
1204  
1205          tooltip += QString("<br>");
1206          tooltip += tr("Last received block was generated %1 ago.").arg(timeBehindText);
1207          tooltip += QString("<br>");
1208          tooltip += tr("Transactions after this will not yet be visible.");
1209      }
1210  
1211      // Don't word-wrap this (fixed-width) tooltip
1212      tooltip = QString("<nobr>") + tooltip + QString("</nobr>");
1213  
1214      labelBlocksIcon->setToolTip(tooltip);
1215      progressBarLabel->setToolTip(tooltip);
1216      progressBar->setToolTip(tooltip);
1217  }
1218  
1219  void BitcoinGUI::createWallet()
1220  {
1221  #ifdef ENABLE_WALLET
1222      auto activity = new CreateWalletActivity(getWalletController(), this);
1223      connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet);
1224      connect(activity, &CreateWalletActivity::created, rpcConsole, &RPCConsole::setCurrentWallet);
1225      activity->create();
1226  #endif // ENABLE_WALLET
1227  }
1228  
1229  void BitcoinGUI::message(const QString& title, QString message, unsigned int style, bool* ret, const QString& detailed_message)
1230  {
1231      // Default title. On macOS, the window title is ignored (as required by the macOS Guidelines).
1232      QString strTitle{CLIENT_NAME};
1233      // Default to information icon
1234      int nMBoxIcon = QMessageBox::Information;
1235      int nNotifyIcon = Notificator::Information;
1236  
1237      QString msgType;
1238      if (!title.isEmpty()) {
1239          msgType = title;
1240      } else {
1241          switch (style) {
1242          case CClientUIInterface::MSG_ERROR:
1243              msgType = tr("Error");
1244              message = tr("Error: %1").arg(message);
1245              break;
1246          case CClientUIInterface::MSG_WARNING:
1247              msgType = tr("Warning");
1248              message = tr("Warning: %1").arg(message);
1249              break;
1250          case CClientUIInterface::MSG_INFORMATION:
1251              msgType = tr("Information");
1252              // No need to prepend the prefix here.
1253              break;
1254          default:
1255              break;
1256          }
1257      }
1258  
1259      if (!msgType.isEmpty()) {
1260          strTitle += " - " + msgType;
1261      }
1262  
1263      if (style & CClientUIInterface::ICON_ERROR) {
1264          nMBoxIcon = QMessageBox::Critical;
1265          nNotifyIcon = Notificator::Critical;
1266      } else if (style & CClientUIInterface::ICON_WARNING) {
1267          nMBoxIcon = QMessageBox::Warning;
1268          nNotifyIcon = Notificator::Warning;
1269      }
1270  
1271      if (style & CClientUIInterface::MODAL) {
1272          // Check for buttons, use OK as default, if none was supplied
1273          QMessageBox::StandardButton buttons;
1274          if (!(buttons = (QMessageBox::StandardButton)(style & CClientUIInterface::BTN_MASK)))
1275              buttons = QMessageBox::Ok;
1276  
1277          showNormalIfMinimized();
1278          QMessageBox mBox(static_cast<QMessageBox::Icon>(nMBoxIcon), strTitle, message, buttons, this);
1279          mBox.setTextFormat(Qt::PlainText);
1280          mBox.setDetailedText(detailed_message);
1281          int r = mBox.exec();
1282          if (ret != nullptr)
1283              *ret = r == QMessageBox::Ok;
1284      } else {
1285          notificator->notify(static_cast<Notificator::Class>(nNotifyIcon), strTitle, message);
1286      }
1287  }
1288  
1289  void BitcoinGUI::changeEvent(QEvent *e)
1290  {
1291      if (e->type() == QEvent::PaletteChange) {
1292          overviewAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/overview")));
1293          sendCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/send")));
1294          receiveCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/receiving_addresses")));
1295          historyAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/history")));
1296      }
1297  
1298      QMainWindow::changeEvent(e);
1299  
1300  #ifndef Q_OS_MACOS // Ignored on Mac
1301      if(e->type() == QEvent::WindowStateChange)
1302      {
1303          if(clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getMinimizeToTray())
1304          {
1305              QWindowStateChangeEvent *wsevt = static_cast<QWindowStateChangeEvent*>(e);
1306              if(!(wsevt->oldState() & Qt::WindowMinimized) && isMinimized())
1307              {
1308                  QTimer::singleShot(0, this, &BitcoinGUI::hide);
1309                  e->ignore();
1310              }
1311              else if((wsevt->oldState() & Qt::WindowMinimized) && !isMinimized())
1312              {
1313                  QTimer::singleShot(0, this, &BitcoinGUI::show);
1314                  e->ignore();
1315              }
1316          }
1317      }
1318  #endif
1319  }
1320  
1321  void BitcoinGUI::closeEvent(QCloseEvent *event)
1322  {
1323  #ifndef Q_OS_MACOS // Ignored on Mac
1324      if(clientModel && clientModel->getOptionsModel())
1325      {
1326          if(!clientModel->getOptionsModel()->getMinimizeOnClose())
1327          {
1328              // close rpcConsole in case it was open to make some space for the shutdown window
1329              rpcConsole->close();
1330  
1331              Q_EMIT quitRequested();
1332          }
1333          else
1334          {
1335              QMainWindow::showMinimized();
1336              event->ignore();
1337          }
1338      }
1339  #else
1340      QMainWindow::closeEvent(event);
1341  #endif
1342  }
1343  
1344  void BitcoinGUI::showEvent(QShowEvent *event)
1345  {
1346      // enable the debug window when the main window shows up
1347      openRPCConsoleAction->setEnabled(true);
1348      aboutAction->setEnabled(true);
1349      optionsAction->setEnabled(true);
1350  }
1351  
1352  #ifdef ENABLE_WALLET
1353  void BitcoinGUI::incomingTransaction(const QString& date, BitcoinUnit unit, const CAmount& amount, const QString& type, const QString& address, const QString& label, const QString& walletName)
1354  {
1355      // On new transaction, make an info balloon
1356      QString msg = tr("Date: %1\n").arg(date) +
1357                    tr("Amount: %1\n").arg(BitcoinUnits::formatWithUnit(unit, amount, true));
1358      if (m_node.walletLoader().getWallets().size() > 1 && !walletName.isEmpty()) {
1359          msg += tr("Wallet: %1\n").arg(walletName);
1360      }
1361      msg += tr("Type: %1\n").arg(type);
1362      if (!label.isEmpty())
1363          msg += tr("Label: %1\n").arg(label);
1364      else if (!address.isEmpty())
1365          msg += tr("Address: %1\n").arg(address);
1366      message((amount)<0 ? tr("Sent transaction") : tr("Incoming transaction"),
1367               msg, CClientUIInterface::MSG_INFORMATION);
1368  }
1369  #endif // ENABLE_WALLET
1370  
1371  void BitcoinGUI::dragEnterEvent(QDragEnterEvent *event)
1372  {
1373      // Accept only URIs
1374      if(event->mimeData()->hasUrls())
1375          event->acceptProposedAction();
1376  }
1377  
1378  void BitcoinGUI::dropEvent(QDropEvent *event)
1379  {
1380      if(event->mimeData()->hasUrls())
1381      {
1382          for (const QUrl &uri : event->mimeData()->urls())
1383          {
1384              Q_EMIT receivedURI(uri.toString());
1385          }
1386      }
1387      event->acceptProposedAction();
1388  }
1389  
1390  bool BitcoinGUI::eventFilter(QObject *object, QEvent *event)
1391  {
1392      // Catch status tip events
1393      if (event->type() == QEvent::StatusTip)
1394      {
1395          // Prevent adding text from setStatusTip(), if we currently use the status bar for displaying other stuff
1396          if (progressBarLabel->isVisible() || progressBar->isVisible())
1397              return true;
1398      }
1399      return QMainWindow::eventFilter(object, event);
1400  }
1401  
1402  #ifdef ENABLE_WALLET
1403  bool BitcoinGUI::handlePaymentRequest(const SendCoinsRecipient& recipient)
1404  {
1405      // URI has to be valid
1406      if (walletFrame && walletFrame->handlePaymentRequest(recipient))
1407      {
1408          showNormalIfMinimized();
1409          gotoSendCoinsPage();
1410          return true;
1411      }
1412      return false;
1413  }
1414  
1415  void BitcoinGUI::setHDStatus(bool privkeyDisabled, int hdEnabled)
1416  {
1417      labelWalletHDStatusIcon->setThemedPixmap(privkeyDisabled ? QStringLiteral(":/icons/eye") : hdEnabled ? QStringLiteral(":/icons/hd_enabled") : QStringLiteral(":/icons/hd_disabled"), STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1418      labelWalletHDStatusIcon->setToolTip(privkeyDisabled ? tr("Private key <b>disabled</b>") : hdEnabled ? tr("HD key generation is <b>enabled</b>") : tr("HD key generation is <b>disabled</b>"));
1419      labelWalletHDStatusIcon->show();
1420  }
1421  
1422  void BitcoinGUI::setEncryptionStatus(int status)
1423  {
1424      switch(status)
1425      {
1426      case WalletModel::NoKeys:
1427          labelWalletEncryptionIcon->hide();
1428          encryptWalletAction->setChecked(false);
1429          changePassphraseAction->setEnabled(false);
1430          encryptWalletAction->setEnabled(false);
1431          break;
1432      case WalletModel::Unencrypted:
1433          labelWalletEncryptionIcon->hide();
1434          encryptWalletAction->setChecked(false);
1435          changePassphraseAction->setEnabled(false);
1436          encryptWalletAction->setEnabled(true);
1437          break;
1438      case WalletModel::Unlocked:
1439          labelWalletEncryptionIcon->show();
1440          labelWalletEncryptionIcon->setThemedPixmap(QStringLiteral(":/icons/lock_open"), STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1441          labelWalletEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>unlocked</b>"));
1442          encryptWalletAction->setChecked(true);
1443          changePassphraseAction->setEnabled(true);
1444          encryptWalletAction->setEnabled(false);
1445          break;
1446      case WalletModel::Locked:
1447          labelWalletEncryptionIcon->show();
1448          labelWalletEncryptionIcon->setThemedPixmap(QStringLiteral(":/icons/lock_closed"), STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1449          labelWalletEncryptionIcon->setToolTip(tr("Wallet is <b>encrypted</b> and currently <b>locked</b>"));
1450          encryptWalletAction->setChecked(true);
1451          changePassphraseAction->setEnabled(true);
1452          encryptWalletAction->setEnabled(false);
1453          break;
1454      }
1455  }
1456  
1457  void BitcoinGUI::updateWalletStatus()
1458  {
1459      assert(walletFrame);
1460  
1461      WalletView * const walletView = walletFrame->currentWalletView();
1462      if (!walletView) {
1463          return;
1464      }
1465      WalletModel * const walletModel = walletView->getWalletModel();
1466      setEncryptionStatus(walletModel->getEncryptionStatus());
1467      setHDStatus(walletModel->wallet().privateKeysDisabled(), walletModel->wallet().hdEnabled());
1468  }
1469  #endif // ENABLE_WALLET
1470  
1471  void BitcoinGUI::updateProxyIcon()
1472  {
1473      std::string ip_port;
1474      bool proxy_enabled = clientModel->getProxyInfo(ip_port);
1475  
1476      if (proxy_enabled) {
1477          if (!GUIUtil::HasPixmap(labelProxyIcon)) {
1478              QString ip_port_q = QString::fromStdString(ip_port);
1479              labelProxyIcon->setThemedPixmap((":/icons/proxy"), STATUSBAR_ICONSIZE, STATUSBAR_ICONSIZE);
1480              labelProxyIcon->setToolTip(tr("Proxy is <b>enabled</b>: %1").arg(ip_port_q));
1481          } else {
1482              labelProxyIcon->show();
1483          }
1484      } else {
1485          labelProxyIcon->hide();
1486      }
1487  }
1488  
1489  void BitcoinGUI::updateWindowTitle()
1490  {
1491      QString window_title = CLIENT_NAME;
1492  #ifdef ENABLE_WALLET
1493      if (walletFrame) {
1494          WalletModel* const wallet_model = walletFrame->currentWalletModel();
1495          if (wallet_model && !wallet_model->getWalletName().isEmpty()) {
1496              window_title += " - " + wallet_model->getDisplayName();
1497          }
1498      }
1499  #endif
1500      if (!m_network_style->getTitleAddText().isEmpty()) {
1501          window_title += " - " + m_network_style->getTitleAddText();
1502      }
1503      setWindowTitle(window_title);
1504  }
1505  
1506  void BitcoinGUI::showNormalIfMinimized(bool fToggleHidden)
1507  {
1508      if(!clientModel)
1509          return;
1510  
1511      if (!isHidden() && !isMinimized() && !GUIUtil::isObscured(this) && fToggleHidden) {
1512          hide();
1513      } else {
1514          GUIUtil::bringToFront(this);
1515      }
1516  }
1517  
1518  void BitcoinGUI::toggleHidden()
1519  {
1520      showNormalIfMinimized(true);
1521  }
1522  
1523  void BitcoinGUI::detectShutdown()
1524  {
1525      if (m_node.shutdownRequested())
1526      {
1527          if(rpcConsole)
1528              rpcConsole->hide();
1529          Q_EMIT quitRequested();
1530      }
1531  }
1532  
1533  void BitcoinGUI::showProgress(const QString &title, int nProgress)
1534  {
1535      if (nProgress == 0) {
1536          progressDialog = new QProgressDialog(title, QString(), 0, 100);
1537          GUIUtil::PolishProgressDialog(progressDialog);
1538          progressDialog->setWindowModality(Qt::ApplicationModal);
1539          progressDialog->setAutoClose(false);
1540          progressDialog->setValue(0);
1541      } else if (nProgress == 100) {
1542          if (progressDialog) {
1543              progressDialog->close();
1544              progressDialog->deleteLater();
1545              progressDialog = nullptr;
1546          }
1547      } else if (progressDialog) {
1548          progressDialog->setValue(nProgress);
1549      }
1550  }
1551  
1552  void BitcoinGUI::showModalOverlay()
1553  {
1554      if (modalOverlay && (progressBar->isVisible() || modalOverlay->isLayerVisible()))
1555          modalOverlay->toggleVisibility();
1556  }
1557  
1558  static bool ThreadSafeMessageBox(BitcoinGUI* gui, const bilingual_str& message, const std::string& caption, unsigned int style)
1559  {
1560      bool modal = (style & CClientUIInterface::MODAL);
1561      // The SECURE flag has no effect in the Qt GUI.
1562      // bool secure = (style & CClientUIInterface::SECURE);
1563      style &= ~CClientUIInterface::SECURE;
1564      bool ret = false;
1565  
1566      QString detailed_message; // This is original message, in English, for googling and referencing.
1567      if (message.original != message.translated) {
1568          detailed_message = BitcoinGUI::tr("Original message:") + "\n" + QString::fromStdString(message.original);
1569      }
1570  
1571      // In case of modal message, use blocking connection to wait for user to click a button
1572      bool invoked = QMetaObject::invokeMethod(gui, "message",
1573                                 modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection,
1574                                 Q_ARG(QString, QString::fromStdString(caption)),
1575                                 Q_ARG(QString, QString::fromStdString(message.translated)),
1576                                 Q_ARG(unsigned int, style),
1577                                 Q_ARG(bool*, &ret),
1578                                 Q_ARG(QString, detailed_message));
1579      assert(invoked);
1580      return ret;
1581  }
1582  
1583  void BitcoinGUI::subscribeToCoreSignals()
1584  {
1585      // Connect signals to client
1586      m_handler_message_box = m_node.handleMessageBox(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
1587      m_handler_question = m_node.handleQuestion(std::bind(ThreadSafeMessageBox, this, std::placeholders::_1, std::placeholders::_3, std::placeholders::_4));
1588  }
1589  
1590  void BitcoinGUI::unsubscribeFromCoreSignals()
1591  {
1592      // Disconnect signals from client
1593      m_handler_message_box->disconnect();
1594      m_handler_question->disconnect();
1595  }
1596  
1597  bool BitcoinGUI::isPrivacyModeActivated() const
1598  {
1599      assert(m_mask_values_action);
1600      return m_mask_values_action->isChecked();
1601  }
1602  
1603  UnitDisplayStatusBarControl::UnitDisplayStatusBarControl(const PlatformStyle* platformStyle)
1604      : m_platform_style{platformStyle}
1605  {
1606      createContextMenu();
1607      setToolTip(tr("Unit to show amounts in. Click to select another unit."));
1608      QList<BitcoinUnit> units = BitcoinUnits::availableUnits();
1609      int max_width = 0;
1610      const QFontMetrics fm(font());
1611      for (const BitcoinUnit unit : units) {
1612          max_width = qMax(max_width, GUIUtil::TextWidth(fm, BitcoinUnits::longName(unit)));
1613      }
1614      setMinimumSize(max_width, 0);
1615      setAlignment(Qt::AlignRight | Qt::AlignVCenter);
1616      setStyleSheet(QString("QLabel { color : %1 }").arg(m_platform_style->SingleColor().name()));
1617  }
1618  
1619  /** So that it responds to button clicks */
1620  void UnitDisplayStatusBarControl::mousePressEvent(QMouseEvent *event)
1621  {
1622      onDisplayUnitsClicked(event->pos());
1623  }
1624  
1625  void UnitDisplayStatusBarControl::changeEvent(QEvent* e)
1626  {
1627      if (e->type() == QEvent::PaletteChange) {
1628          QString style = QString("QLabel { color : %1 }").arg(m_platform_style->SingleColor().name());
1629          if (style != styleSheet()) {
1630              setStyleSheet(style);
1631          }
1632      }
1633  
1634      QLabel::changeEvent(e);
1635  }
1636  
1637  /** Creates context menu, its actions, and wires up all the relevant signals for mouse events. */
1638  void UnitDisplayStatusBarControl::createContextMenu()
1639  {
1640      menu = new QMenu(this);
1641      for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
1642          menu->addAction(BitcoinUnits::longName(u))->setData(QVariant::fromValue(u));
1643      }
1644      connect(menu, &QMenu::triggered, this, &UnitDisplayStatusBarControl::onMenuSelection);
1645  }
1646  
1647  /** Lets the control know about the Options Model (and its signals) */
1648  void UnitDisplayStatusBarControl::setOptionsModel(OptionsModel *_optionsModel)
1649  {
1650      if (_optionsModel)
1651      {
1652          this->optionsModel = _optionsModel;
1653  
1654          // be aware of a display unit change reported by the OptionsModel object.
1655          connect(_optionsModel, &OptionsModel::displayUnitChanged, this, &UnitDisplayStatusBarControl::updateDisplayUnit);
1656  
1657          // initialize the display units label with the current value in the model.
1658          updateDisplayUnit(_optionsModel->getDisplayUnit());
1659      }
1660  }
1661  
1662  /** When Display Units are changed on OptionsModel it will refresh the display text of the control on the status bar */
1663  void UnitDisplayStatusBarControl::updateDisplayUnit(BitcoinUnit newUnits)
1664  {
1665      setText(BitcoinUnits::longName(newUnits));
1666  }
1667  
1668  /** Shows context menu with Display Unit options by the mouse coordinates */
1669  void UnitDisplayStatusBarControl::onDisplayUnitsClicked(const QPoint& point)
1670  {
1671      QPoint globalPos = mapToGlobal(point);
1672      menu->exec(globalPos);
1673  }
1674  
1675  /** Tells underlying optionsModel to update its current display unit. */
1676  void UnitDisplayStatusBarControl::onMenuSelection(QAction* action)
1677  {
1678      if (action)
1679      {
1680          optionsModel->setDisplayUnit(action->data());
1681      }
1682  }