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