limenkagui.cpp raw

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