2020-10-07 10:36:04 +00:00
// SPDX-License-Identifier: BSD-3-Clause
2024-01-01 17:07:58 +00:00
// SPDX-FileCopyrightText: 2020-2024 The Monero Project
2020-10-07 10:36:04 +00:00
2021-06-28 17:48:23 +00:00
# include "MainWindow.h"
# include "ui_MainWindow.h"
2021-05-02 18:22:38 +00:00
2021-06-28 17:20:16 +00:00
# include <QFileDialog>
2021-07-06 14:10:24 +00:00
# include <QInputDialog>
2021-06-28 17:48:23 +00:00
# include <QMessageBox>
2023-12-02 18:28:31 +00:00
# include <QCheckBox>
2021-06-28 17:20:16 +00:00
2021-06-28 17:48:23 +00:00
# include "constants.h"
2023-12-02 18:28:31 +00:00
# include "dialog/AddressCheckerIndexDialog.h"
2021-06-28 17:48:23 +00:00
# include "dialog/BalanceDialog.h"
2021-06-27 11:46:32 +00:00
# include "dialog/DebugInfoDialog.h"
2021-06-28 17:48:23 +00:00
# include "dialog/PasswordDialog.h"
2021-06-27 12:22:54 +00:00
# include "dialog/TxBroadcastDialog.h"
2021-06-28 17:48:23 +00:00
# include "dialog/TxConfAdvDialog.h"
# include "dialog/TxConfDialog.h"
2021-06-27 11:46:32 +00:00
# include "dialog/TxImportDialog.h"
2021-07-03 15:48:07 +00:00
# include "dialog/TxInfoDialog.h"
2021-06-28 17:48:23 +00:00
# include "dialog/ViewOnlyDialog.h"
# include "dialog/WalletInfoDialog.h"
2021-01-25 16:38:04 +00:00
# include "dialog/WalletCacheDebugDialog.h"
2020-10-07 10:36:04 +00:00
# include "libwalletqt/AddressBook.h"
2023-11-17 13:05:31 +00:00
# include "libwalletqt/rows/CoinsInfo.h"
2022-03-12 13:54:08 +00:00
# include "libwalletqt/Transfer.h"
2023-11-30 14:01:39 +00:00
# include "plugins/PluginRegistry.h"
2021-05-02 18:22:38 +00:00
# include "utils/AppData.h"
2021-06-28 17:48:23 +00:00
# include "utils/AsyncTask.h"
2021-05-02 18:22:38 +00:00
# include "utils/ColorScheme.h"
# include "utils/Icons.h"
2021-06-28 17:48:23 +00:00
# include "utils/SemanticVersion.h"
2021-05-18 15:59:18 +00:00
# include "utils/TorManager.h"
2021-06-28 17:48:23 +00:00
# include "utils/WebsocketNotifier.h"
2020-10-07 10:36:04 +00:00
2023-09-12 14:15:40 +00:00
# include "wallet/wallet_errors.h"
2023-12-02 18:28:31 +00:00
# ifdef WITH_SCANNER
# include "wizard/offline_tx_signing/OfflineTxSigningWizard.h"
2023-12-30 19:29:17 +00:00
# include "qrcode/scanner/URDialog.h"
2023-12-02 18:28:31 +00:00
# endif
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
# include "utils/updater/UpdateDialog.h"
# endif
2023-02-11 17:11:21 +00:00
2021-05-18 15:59:18 +00:00
MainWindow : : MainWindow ( WindowManager * windowManager , Wallet * wallet , QWidget * parent )
2021-05-02 18:22:38 +00:00
: QMainWindow ( parent )
, ui ( new Ui : : MainWindow )
2021-05-18 15:59:18 +00:00
, m_windowManager ( windowManager )
2023-03-01 02:05:56 +00:00
, m_wallet ( wallet )
, m_nodes ( new Nodes ( this , wallet ) )
, m_rpc ( new DaemonRpc ( this , " " ) )
2020-10-07 10:36:04 +00:00
{
ui - > setupUi ( this ) ;
2021-05-18 15:59:18 +00:00
// Ensure the destructor is called after closeEvent()
setAttribute ( Qt : : WA_DeleteOnClose ) ;
2021-05-02 18:22:38 +00:00
m_splashDialog = new SplashDialog ( this ) ;
2023-03-01 02:05:56 +00:00
m_accountSwitcherDialog = new AccountSwitcherDialog ( m_wallet , this ) ;
2020-10-07 10:36:04 +00:00
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
2023-02-01 14:40:12 +00:00
m_updater = QSharedPointer < Updater > ( new Updater ( this ) ) ;
2023-04-19 15:41:46 +00:00
# endif
2023-02-01 14:40:12 +00:00
2020-10-07 10:36:04 +00:00
this - > restoreGeo ( ) ;
2021-05-02 18:22:38 +00:00
this - > initStatusBar ( ) ;
2023-11-30 14:01:39 +00:00
this - > initPlugins ( ) ;
2021-05-02 18:22:38 +00:00
this - > initWidgets ( ) ;
this - > initMenu ( ) ;
2023-12-02 18:28:31 +00:00
this - > initOffline ( ) ;
2021-05-02 18:22:38 +00:00
this - > initWalletContext ( ) ;
2023-11-30 14:01:39 +00:00
emit uiSetup ( ) ;
2021-05-02 18:22:38 +00:00
2023-12-02 18:28:31 +00:00
this - > onOfflineMode ( conf ( ) - > get ( Config : : offlineMode ) . toBool ( ) ) ;
2023-11-30 14:01:39 +00:00
conf ( ) - > set ( Config : : restartRequired , false ) ;
2023-12-02 18:28:31 +00:00
2021-05-02 18:22:38 +00:00
// Websocket notifier
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
2023-02-01 14:40:12 +00:00
connect ( websocketNotifier ( ) , & WebsocketNotifier : : UpdatesReceived , m_updater . data ( ) , & Updater : : wsUpdatesReceived ) ;
2023-04-19 15:41:46 +00:00
# endif
2023-11-30 14:01:39 +00:00
2021-05-18 15:59:18 +00:00
websocketNotifier ( ) - > emitCache ( ) ; // Get cached data
2020-10-07 10:36:04 +00:00
2022-03-15 12:36:20 +00:00
connect ( m_windowManager , & WindowManager : : websocketStatusChanged , this , & MainWindow : : onWebsocketStatusChanged ) ;
2023-09-12 14:15:40 +00:00
this - > onWebsocketStatusChanged ( ! conf ( ) - > get ( Config : : disableWebsocket ) . toBool ( ) ) ;
2022-03-15 12:36:20 +00:00
2023-03-01 02:05:56 +00:00
connect ( m_windowManager , & WindowManager : : proxySettingsChanged , this , & MainWindow : : onProxySettingsChanged ) ;
connect ( m_windowManager , & WindowManager : : updateBalance , m_wallet , & Wallet : : updateBalance ) ;
connect ( m_windowManager , & WindowManager : : offlineMode , this , & MainWindow : : onOfflineMode ) ;
2023-02-11 17:11:21 +00:00
2021-05-18 15:59:18 +00:00
connect ( torManager ( ) , & TorManager : : connectionStateChanged , this , & MainWindow : : onTorConnectionStateChanged ) ;
this - > onTorConnectionStateChanged ( torManager ( ) - > torConnected ) ;
2020-10-07 10:36:04 +00:00
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
2023-02-01 14:40:12 +00:00
connect ( m_updater . data ( ) , & Updater : : updateAvailable , this , & MainWindow : : showUpdateNotification ) ;
2023-04-19 15:41:46 +00:00
# endif
2023-02-01 14:40:12 +00:00
2021-05-02 18:22:38 +00:00
ColorScheme : : updateFromWidget ( this ) ;
2021-10-22 11:05:40 +00:00
QTimer : : singleShot ( 1 , [ this ] { this - > updateWidgetIcons ( ) ; } ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
// Timers
connect ( & m_updateBytes , & QTimer : : timeout , this , & MainWindow : : updateNetStats ) ;
connect ( & m_txTimer , & QTimer : : timeout , [ this ] {
m_statusLabelStatus - > setText ( " Constructing transaction " + this - > statusDots ( ) ) ;
} ) ;
2020-10-07 10:36:04 +00:00
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : firstRun , false ) ;
2021-05-26 22:24:35 +00:00
2021-05-18 15:59:18 +00:00
this - > onWalletOpened ( ) ;
2020-10-17 21:14:56 +00:00
2021-05-18 15:59:18 +00:00
# ifdef DONATE_BEG
this - > donationNag ( ) ;
# endif
2022-03-04 16:20:17 +00:00
connect ( m_windowManager - > eventFilter , & EventFilter : : userActivity , this , & MainWindow : : userActivity ) ;
connect ( & m_checkUserActivity , & QTimer : : timeout , this , & MainWindow : : checkUserActivity ) ;
m_checkUserActivity . setInterval ( 5000 ) ;
m_checkUserActivity . start ( ) ;
2021-05-02 18:22:38 +00:00
}
2020-10-14 20:12:32 +00:00
2021-05-02 18:22:38 +00:00
void MainWindow : : initStatusBar ( ) {
# if defined(Q_OS_WIN)
2023-01-09 02:17:25 +00:00
// No separators between statusbar widgets
2021-05-02 18:22:38 +00:00
this - > statusBar ( ) - > setStyleSheet ( " QStatusBar::item {border: None;} " ) ;
# endif
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
this - > statusBar ( ) - > setFixedHeight ( 30 ) ;
2020-12-31 00:00:37 +00:00
2021-05-02 18:22:38 +00:00
m_statusLabelStatus = new QLabel ( " Idle " , this ) ;
m_statusLabelStatus - > setTextInteractionFlags ( Qt : : TextSelectableByMouse ) ;
this - > statusBar ( ) - > addWidget ( m_statusLabelStatus ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
m_statusLabelNetStats = new QLabel ( " " , this ) ;
m_statusLabelNetStats - > setTextInteractionFlags ( Qt : : TextSelectableByMouse ) ;
this - > statusBar ( ) - > addWidget ( m_statusLabelNetStats ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
m_statusUpdateAvailable = new QPushButton ( this ) ;
m_statusUpdateAvailable - > setFlat ( true ) ;
m_statusUpdateAvailable - > setCursor ( Qt : : PointingHandCursor ) ;
m_statusUpdateAvailable - > setIcon ( icons ( ) - > icon ( " tab_party.png " ) ) ;
m_statusUpdateAvailable - > hide ( ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusUpdateAvailable ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
m_statusLabelBalance = new ClickableLabel ( this ) ;
m_statusLabelBalance - > setText ( " Balance: 0 XMR " ) ;
m_statusLabelBalance - > setTextInteractionFlags ( Qt : : TextSelectableByMouse ) ;
m_statusLabelBalance - > setCursor ( Qt : : PointingHandCursor ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusLabelBalance ) ;
connect ( m_statusLabelBalance , & ClickableLabel : : clicked , this , & MainWindow : : showBalanceDialog ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
m_statusBtnConnectionStatusIndicator = new StatusBarButton ( icons ( ) - > icon ( " status_disconnected.svg " ) , " Connection status " , this ) ;
2022-03-15 10:12:08 +00:00
connect ( m_statusBtnConnectionStatusIndicator , & StatusBarButton : : clicked , [ this ] ( ) {
2023-02-11 17:24:18 +00:00
this - > onShowSettingsPage ( Settings : : Pages : : NETWORK ) ;
2022-03-15 10:12:08 +00:00
} ) ;
2021-05-02 18:22:38 +00:00
this - > statusBar ( ) - > addPermanentWidget ( m_statusBtnConnectionStatusIndicator ) ;
2023-02-11 17:11:21 +00:00
this - > onConnectionStatusChanged ( Wallet : : ConnectionStatus_Disconnected ) ;
2020-12-24 14:46:56 +00:00
2021-05-22 13:42:26 +00:00
m_statusAccountSwitcher = new StatusBarButton ( icons ( ) - > icon ( " change_account.png " ) , " Account switcher " , this ) ;
connect ( m_statusAccountSwitcher , & StatusBarButton : : clicked , this , & MainWindow : : showAccountSwitcherDialog ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusAccountSwitcher ) ;
2021-05-02 18:22:38 +00:00
m_statusBtnPassword = new StatusBarButton ( icons ( ) - > icon ( " lock.svg " ) , " Password " , this ) ;
connect ( m_statusBtnPassword , & StatusBarButton : : clicked , this , & MainWindow : : showPasswordDialog ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusBtnPassword ) ;
2020-12-24 14:46:56 +00:00
2021-05-02 18:22:38 +00:00
m_statusBtnPreferences = new StatusBarButton ( icons ( ) - > icon ( " preferences.svg " ) , " Settings " , this ) ;
connect ( m_statusBtnPreferences , & StatusBarButton : : clicked , this , & MainWindow : : menuSettingsClicked ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusBtnPreferences ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
m_statusBtnSeed = new StatusBarButton ( icons ( ) - > icon ( " seed.png " ) , " Seed " , this ) ;
connect ( m_statusBtnSeed , & StatusBarButton : : clicked , this , & MainWindow : : showSeedDialog ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusBtnSeed ) ;
2020-10-07 10:36:04 +00:00
2023-02-11 17:11:21 +00:00
m_statusBtnProxySettings = new StatusBarButton ( icons ( ) - > icon ( " tor_logo_disabled.png " ) , " Proxy settings " , this ) ;
connect ( m_statusBtnProxySettings , & StatusBarButton : : clicked , this , & MainWindow : : menuProxySettingsClicked ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusBtnProxySettings ) ;
this - > onProxySettingsChanged ( ) ;
2020-10-07 10:36:04 +00:00
2021-07-01 21:00:47 +00:00
m_statusBtnHwDevice = new StatusBarButton ( this - > hardwareDevicePairedIcon ( ) , this - > getHardwareDevice ( ) , this ) ;
2021-05-02 18:22:38 +00:00
connect ( m_statusBtnHwDevice , & StatusBarButton : : clicked , this , & MainWindow : : menuHwDeviceClicked ) ;
this - > statusBar ( ) - > addPermanentWidget ( m_statusBtnHwDevice ) ;
m_statusBtnHwDevice - > hide ( ) ;
}
2020-10-07 10:36:04 +00:00
2023-11-30 14:01:39 +00:00
void MainWindow : : initPlugins ( ) {
const QStringList enabledPlugins = conf ( ) - > get ( Config : : enabledPlugins ) . toStringList ( ) ;
for ( const auto & plugin_creator : PluginRegistry : : getPluginCreators ( ) ) {
Plugin * plugin = plugin_creator ( ) ;
if ( ! PluginRegistry : : getInstance ( ) . isPluginEnabled ( plugin - > id ( ) ) ) {
continue ;
}
qDebug ( ) < < " Initializing plugin: " < < plugin - > id ( ) ;
plugin - > initialize ( m_wallet , this ) ;
connect ( plugin , & Plugin : : setStatusText , this , & MainWindow : : setStatusText ) ;
connect ( plugin , & Plugin : : fillSendTab , this , & MainWindow : : fillSendTab ) ;
connect ( this , & MainWindow : : updateIcons , plugin , & Plugin : : skinChanged ) ;
connect ( this , & MainWindow : : aboutToQuit , plugin , & Plugin : : aboutToQuit ) ;
connect ( this , & MainWindow : : uiSetup , plugin , & Plugin : : uiSetup ) ;
2020-10-07 10:36:04 +00:00
2023-11-30 14:01:39 +00:00
m_plugins . append ( plugin ) ;
}
std : : sort ( m_plugins . begin ( ) , m_plugins . end ( ) , [ ] ( Plugin * a , Plugin * b ) {
return a - > idx ( ) < b - > idx ( ) ;
} ) ;
}
void MainWindow : : initWidgets ( ) {
2021-05-02 18:22:38 +00:00
// [History]
2023-03-01 02:05:56 +00:00
m_historyWidget = new HistoryWidget ( m_wallet , this ) ;
2021-05-18 15:59:18 +00:00
ui - > historyWidgetLayout - > addWidget ( m_historyWidget ) ;
connect ( m_historyWidget , & HistoryWidget : : viewOnBlockExplorer , this , & MainWindow : : onViewOnBlockExplorer ) ;
connect ( m_historyWidget , & HistoryWidget : : resendTransaction , this , & MainWindow : : onResendTransaction ) ;
2021-05-02 18:22:38 +00:00
2021-05-10 15:05:10 +00:00
// [Send]
2023-03-01 02:05:56 +00:00
m_sendWidget = new SendWidget ( m_wallet , this ) ;
2021-05-10 15:05:10 +00:00
ui - > sendWidgetLayout - > addWidget ( m_sendWidget ) ;
2021-05-18 15:59:18 +00:00
// --------------
2023-03-01 02:05:56 +00:00
m_contactsWidget = new ContactsWidget ( m_wallet , this ) ;
2021-05-18 15:59:18 +00:00
ui - > contactsWidgetLayout - > addWidget ( m_contactsWidget ) ;
2021-05-10 15:05:10 +00:00
2021-05-02 18:22:38 +00:00
// [Receive]
2023-03-01 02:05:56 +00:00
m_receiveWidget = new ReceiveWidget ( m_wallet , this ) ;
2021-05-18 15:59:18 +00:00
ui - > receiveWidgetLayout - > addWidget ( m_receiveWidget ) ;
connect ( m_receiveWidget , & ReceiveWidget : : showTransactions , [ this ] ( const QString & text ) {
m_historyWidget - > setSearchText ( text ) ;
2023-11-30 14:01:39 +00:00
ui - > tabWidget - > setCurrentIndex ( this - > findTab ( " History " ) ) ;
2020-10-07 10:36:04 +00:00
} ) ;
2021-05-18 15:59:18 +00:00
connect ( m_contactsWidget , & ContactsWidget : : fillAddress , m_sendWidget , & SendWidget : : fillAddress ) ;
2020-10-07 10:36:04 +00:00
2021-05-10 15:05:10 +00:00
// [Coins]
2023-03-01 02:05:56 +00:00
m_coinsWidget = new CoinsWidget ( m_wallet , this ) ;
2021-05-10 15:05:10 +00:00
ui - > coinsWidgetLayout - > addWidget ( m_coinsWidget ) ;
2020-10-07 10:36:04 +00:00
2023-11-30 14:01:39 +00:00
// [Plugins..]
for ( auto * plugin : m_plugins ) {
if ( ! plugin - > hasParent ( ) ) {
qDebug ( ) < < " Adding tab: " < < plugin - > displayName ( ) ;
2020-11-23 16:57:38 +00:00
2023-11-30 14:01:39 +00:00
if ( plugin - > insertFirst ( ) ) {
ui - > tabWidget - > insertTab ( 0 , plugin - > tab ( ) , icons ( ) - > icon ( plugin - > icon ( ) ) , plugin - > displayName ( ) ) ;
} else {
ui - > tabWidget - > addTab ( plugin - > tab ( ) , icons ( ) - > icon ( plugin - > icon ( ) ) , plugin - > displayName ( ) ) ;
}
2021-07-07 17:06:31 +00:00
2023-11-30 14:01:39 +00:00
for ( auto * child : m_plugins ) {
if ( child - > hasParent ( ) & & child - > parent ( ) = = plugin - > id ( ) ) {
plugin - > addSubPlugin ( child ) ;
}
}
}
}
2022-03-20 22:30:48 +00:00
ui - > frame_coinControl - > setVisible ( false ) ;
connect ( ui - > btn_resetCoinControl , & QPushButton : : clicked , [ this ] {
2023-03-01 02:05:56 +00:00
m_wallet - > setSelectedInputs ( { } ) ;
2022-03-20 22:30:48 +00:00
} ) ;
2023-01-19 14:12:16 +00:00
2023-12-30 13:17:11 +00:00
m_walletUnlockWidget = new WalletUnlockWidget ( this , m_wallet ) ;
2023-01-19 14:12:16 +00:00
m_walletUnlockWidget - > setWalletName ( this - > walletName ( ) ) ;
ui - > walletUnlockLayout - > addWidget ( m_walletUnlockWidget ) ;
connect ( m_walletUnlockWidget , & WalletUnlockWidget : : closeWallet , this , & MainWindow : : close ) ;
connect ( m_walletUnlockWidget , & WalletUnlockWidget : : unlockWallet , this , & MainWindow : : unlockWallet ) ;
2023-11-30 14:01:39 +00:00
ui - > tabWidget - > setCurrentIndex ( 0 ) ;
2023-01-19 14:12:16 +00:00
ui - > stackedWidget - > setCurrentIndex ( 0 ) ;
2020-10-07 10:36:04 +00:00
}
2021-05-02 18:22:38 +00:00
void MainWindow : : initMenu ( ) {
// TODO: Rename actions to follow style
// [File]
2021-05-18 15:59:18 +00:00
connect ( ui - > actionOpen , & QAction : : triggered , this , & MainWindow : : menuOpenClicked ) ;
connect ( ui - > actionNew_Restore , & QAction : : triggered , this , & MainWindow : : menuNewRestoreClicked ) ;
2023-01-19 14:12:16 +00:00
connect ( ui - > actionLock , & QAction : : triggered , this , & MainWindow : : lockWallet ) ;
2021-05-18 15:59:18 +00:00
connect ( ui - > actionClose , & QAction : : triggered , this , & MainWindow : : menuWalletCloseClicked ) ; // Close current wallet
connect ( ui - > actionQuit , & QAction : : triggered , this , & MainWindow : : menuQuitClicked ) ; // Quit application
connect ( ui - > actionSettings , & QAction : : triggered , this , & MainWindow : : menuSettingsClicked ) ;
2020-10-07 10:36:04 +00:00
2021-07-08 12:03:54 +00:00
// [File] -> [Recently open]
m_clearRecentlyOpenAction = new QAction ( " Clear history " , ui - > menuFile ) ;
connect ( m_clearRecentlyOpenAction , & QAction : : triggered , this , & MainWindow : : menuClearHistoryClicked ) ;
2021-05-02 18:22:38 +00:00
// [Wallet]
connect ( ui - > actionInformation , & QAction : : triggered , this , & MainWindow : : showWalletInfoDialog ) ;
2021-05-22 13:42:26 +00:00
connect ( ui - > actionAccount , & QAction : : triggered , this , & MainWindow : : showAccountSwitcherDialog ) ;
2021-05-02 18:22:38 +00:00
connect ( ui - > actionPassword , & QAction : : triggered , this , & MainWindow : : showPasswordDialog ) ;
connect ( ui - > actionSeed , & QAction : : triggered , this , & MainWindow : : showSeedDialog ) ;
connect ( ui - > actionKeys , & QAction : : triggered , this , & MainWindow : : showKeysDialog ) ;
connect ( ui - > actionViewOnly , & QAction : : triggered , this , & MainWindow : : showViewOnlyDialog ) ;
// [Wallet] -> [Advanced]
2021-08-14 14:53:22 +00:00
connect ( ui - > actionStore_wallet , & QAction : : triggered , this , & MainWindow : : tryStoreWallet ) ;
2023-03-01 02:05:56 +00:00
connect ( ui - > actionUpdate_balance , & QAction : : triggered , [ this ] { m_wallet - > updateBalance ( ) ; } ) ;
connect ( ui - > actionRefresh_tabs , & QAction : : triggered , [ this ] { m_wallet - > refreshModels ( ) ; } ) ;
2021-05-02 18:22:38 +00:00
connect ( ui - > actionRescan_spent , & QAction : : triggered , this , & MainWindow : : rescanSpent ) ;
connect ( ui - > actionWallet_cache_debug , & QAction : : triggered , this , & MainWindow : : showWalletCacheDebugDialog ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
// [Wallet] -> [History]
connect ( ui - > actionExport_CSV , & QAction : : triggered , this , & MainWindow : : onExportHistoryCSV ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
// [Wallet] -> [Contacts]
connect ( ui - > actionExportContactsCSV , & QAction : : triggered , this , & MainWindow : : onExportContactsCSV ) ;
connect ( ui - > actionImportContactsCSV , & QAction : : triggered , this , & MainWindow : : importContacts ) ;
2020-11-25 13:35:28 +00:00
2021-05-02 18:22:38 +00:00
// [View]
2020-10-07 10:36:04 +00:00
m_tabShowHideSignalMapper = new QSignalMapper ( this ) ;
2021-05-23 14:58:18 +00:00
connect ( ui - > actionShow_Searchbar , & QAction : : toggled , this , & MainWindow : : toggleSearchbar ) ;
2023-09-12 14:15:40 +00:00
ui - > actionShow_Searchbar - > setChecked ( conf ( ) - > get ( Config : : showSearchbar ) . toBool ( ) ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
// Show/Hide Coins
2020-10-07 10:36:04 +00:00
connect ( ui - > actionShow_Coins , & QAction : : triggered , m_tabShowHideSignalMapper , QOverload < > : : of ( & QSignalMapper : : map ) ) ;
2023-11-30 14:01:39 +00:00
m_tabShowHideMapper [ " Coins " ] = new ToggleTab ( ui - > tabCoins , " Coins " , " Coins " , ui - > actionShow_Coins ) ;
2020-10-07 10:36:04 +00:00
m_tabShowHideSignalMapper - > setMapping ( ui - > actionShow_Coins , " Coins " ) ;
2020-10-21 11:24:16 +00:00
2023-11-30 14:01:39 +00:00
// Show/Hide Plugins..
for ( const auto & plugin : m_plugins ) {
if ( plugin - > parent ( ) ! = " " ) {
continue ;
}
2020-12-25 22:18:40 +00:00
2023-11-30 14:01:39 +00:00
auto * pluginAction = new QAction ( QString ( " Show %1 " ) . arg ( plugin - > displayName ( ) ) , this ) ;
ui - > menuView - > insertAction ( plugin - > insertFirst ( ) ? ui - > actionPlaceholderBegin : ui - > actionPlaceholderEnd , pluginAction ) ;
connect ( pluginAction , & QAction : : triggered , m_tabShowHideSignalMapper , QOverload < > : : of ( & QSignalMapper : : map ) ) ;
m_tabShowHideMapper [ plugin - > displayName ( ) ] = new ToggleTab ( plugin - > tab ( ) , plugin - > displayName ( ) , plugin - > displayName ( ) , pluginAction ) ;
m_tabShowHideSignalMapper - > setMapping ( pluginAction , plugin - > displayName ( ) ) ;
}
ui - > actionPlaceholderBegin - > setVisible ( false ) ;
ui - > actionPlaceholderEnd - > setVisible ( false ) ;
2020-10-07 10:36:04 +00:00
2023-11-30 14:01:39 +00:00
QStringList enabledTabs = conf ( ) - > get ( Config : : enabledTabs ) . toStringList ( ) ;
2020-10-07 10:36:04 +00:00
for ( const auto & key : m_tabShowHideMapper . keys ( ) ) {
const auto toggleTab = m_tabShowHideMapper . value ( key ) ;
2023-11-30 14:01:39 +00:00
bool show = enabledTabs . contains ( key ) ;
2020-10-07 10:36:04 +00:00
toggleTab - > menuAction - > setText ( ( show ? QString ( " Hide " ) : QString ( " Show " ) ) + toggleTab - > name ) ;
ui - > tabWidget - > setTabVisible ( ui - > tabWidget - > indexOf ( toggleTab - > tab ) , show ) ;
}
connect ( m_tabShowHideSignalMapper , & QSignalMapper : : mappedString , this , & MainWindow : : menuToggleTabVisible ) ;
2021-05-02 18:22:38 +00:00
// [Tools]
connect ( ui - > actionSignVerify , & QAction : : triggered , this , & MainWindow : : menuSignVerifyClicked ) ;
connect ( ui - > actionVerifyTxProof , & QAction : : triggered , this , & MainWindow : : menuVerifyTxProof ) ;
2023-12-02 18:28:31 +00:00
connect ( ui - > actionKeyImageSync , & QAction : : triggered , this , & MainWindow : : showKeyImageSyncWizard ) ;
2021-05-02 18:22:38 +00:00
connect ( ui - > actionLoadSignedTxFromFile , & QAction : : triggered , this , & MainWindow : : loadSignedTx ) ;
connect ( ui - > actionLoadSignedTxFromText , & QAction : : triggered , this , & MainWindow : : loadSignedTxFromText ) ;
connect ( ui - > actionImport_transaction , & QAction : : triggered , this , & MainWindow : : importTransaction ) ;
2023-12-02 18:28:31 +00:00
connect ( ui - > actionTransmitOverUR , & QAction : : triggered , this , & MainWindow : : showURDialog ) ;
2021-05-02 18:22:38 +00:00
connect ( ui - > actionPay_to_many , & QAction : : triggered , this , & MainWindow : : payToMany ) ;
2021-07-06 14:10:24 +00:00
connect ( ui - > actionAddress_checker , & QAction : : triggered , this , & MainWindow : : showAddressChecker ) ;
2021-05-02 18:22:38 +00:00
connect ( ui - > actionCreateDesktopEntry , & QAction : : triggered , this , & MainWindow : : onCreateDesktopEntry ) ;
2023-12-02 18:28:31 +00:00
if ( m_wallet - > viewOnly ( ) ) {
ui - > actionKeyImageSync - > setText ( " Key image sync " ) ;
}
2021-05-02 18:22:38 +00:00
// TODO: Allow creating desktop entry on Windows and Mac
# if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
ui - > actionCreateDesktopEntry - > setDisabled ( true ) ;
# endif
2020-10-07 10:36:04 +00:00
2022-02-25 19:48:14 +00:00
# ifndef SELF_CONTAINED
ui - > actionCreateDesktopEntry - > setVisible ( false ) ;
# endif
2021-05-02 18:22:38 +00:00
// [Help]
connect ( ui - > actionAbout , & QAction : : triggered , this , & MainWindow : : menuAboutClicked ) ;
2023-02-01 14:40:12 +00:00
# if defined(CHECK_UPDATES)
connect ( ui - > actionCheckForUpdates , & QAction : : triggered , this , & MainWindow : : showUpdateDialog ) ;
# else
ui - > actionCheckForUpdates - > setVisible ( false ) ;
# endif
2023-02-12 15:24:02 +00:00
2021-05-02 18:22:38 +00:00
connect ( ui - > actionOfficialWebsite , & QAction : : triggered , [ this ] ( ) { Utils : : externalLinkWarning ( this , " https://featherwallet.org " ) ; } ) ;
connect ( ui - > actionDonate_to_Feather , & QAction : : triggered , this , & MainWindow : : donateButtonClicked ) ;
2023-01-09 02:17:25 +00:00
connect ( ui - > actionDocumentation , & QAction : : triggered , this , & MainWindow : : onShowDocumentation ) ;
2021-05-02 18:22:38 +00:00
connect ( ui - > actionReport_bug , & QAction : : triggered , this , & MainWindow : : onReportBug ) ;
connect ( ui - > actionShow_debug_info , & QAction : : triggered , this , & MainWindow : : showDebugInfo ) ;
2020-10-07 10:36:04 +00:00
2021-05-02 18:22:38 +00:00
// Setup shortcuts
ui - > actionStore_wallet - > setShortcut ( QKeySequence ( " Ctrl+S " ) ) ;
ui - > actionRefresh_tabs - > setShortcut ( QKeySequence ( " Ctrl+R " ) ) ;
2021-05-18 15:59:18 +00:00
ui - > actionOpen - > setShortcut ( QKeySequence ( " Ctrl+O " ) ) ;
ui - > actionNew_Restore - > setShortcut ( QKeySequence ( " Ctrl+N " ) ) ;
2023-01-19 14:12:16 +00:00
ui - > actionLock - > setShortcut ( QKeySequence ( " Ctrl+L " ) ) ;
2021-05-02 18:22:38 +00:00
ui - > actionClose - > setShortcut ( QKeySequence ( " Ctrl+W " ) ) ;
ui - > actionShow_debug_info - > setShortcut ( QKeySequence ( " Ctrl+D " ) ) ;
ui - > actionSettings - > setShortcut ( QKeySequence ( " Ctrl+Alt+S " ) ) ;
ui - > actionUpdate_balance - > setShortcut ( QKeySequence ( " Ctrl+U " ) ) ;
2021-05-23 14:58:18 +00:00
ui - > actionShow_Searchbar - > setShortcut ( QKeySequence ( " Ctrl+F " ) ) ;
2021-10-21 21:13:25 +00:00
ui - > actionDocumentation - > setShortcut ( QKeySequence ( " F1 " ) ) ;
2021-05-02 18:22:38 +00:00
}
2020-10-07 10:36:04 +00:00
2023-12-02 18:28:31 +00:00
void MainWindow : : initOffline ( ) {
// TODO: check if we have any cameras available
2023-12-11 23:25:42 +00:00
ui - > btn_help - > setFocusPolicy ( Qt : : NoFocus ) ;
ui - > btn_viewOnlyDetails - > setFocusPolicy ( Qt : : NoFocus ) ;
ui - > btn_checkAddress - > setFocusPolicy ( Qt : : NoFocus ) ;
ui - > btn_signTransaction - > setFocusPolicy ( Qt : : StrongFocus ) ;
ui - > btn_signTransaction - > setFocus ( ) ;
2023-12-02 18:28:31 +00:00
connect ( ui - > btn_help , & QPushButton : : clicked , [ this ] {
windowManager ( ) - > showDocs ( this , " offline_tx_signing " ) ;
} ) ;
2023-12-05 21:06:48 +00:00
connect ( ui - > btn_viewOnlyDetails , & QPushButton : : clicked , [ this ] {
this - > showViewOnlyDialog ( ) ;
} ) ;
2023-12-02 18:28:31 +00:00
connect ( ui - > btn_checkAddress , & QPushButton : : clicked , [ this ] {
AddressCheckerIndexDialog dialog { m_wallet , this } ;
dialog . exec ( ) ;
} ) ;
connect ( ui - > btn_signTransaction , & QPushButton : : clicked , [ this ] {
this - > showKeyImageSyncWizard ( ) ;
} ) ;
switch ( conf ( ) - > get ( Config : : offlineTxSigningMethod ) . toInt ( ) ) {
2023-12-30 19:29:17 +00:00
case Config : : OTSMethod : : FileTransfer :
2023-12-02 18:28:31 +00:00
ui - > radio_airgapFiles - > setChecked ( true ) ;
break ;
default :
ui - > radio_airgapUR - > setChecked ( true ) ;
}
// We can't use rich text for radio buttons
connect ( ui - > label_airgapUR , & ClickableLabel : : clicked , [ this ] {
ui - > radio_airgapUR - > setChecked ( true ) ;
} ) ;
connect ( ui - > label_airgapFiles , & ClickableLabel : : clicked , [ this ] {
ui - > radio_airgapFiles - > setChecked ( true ) ;
} ) ;
connect ( ui - > radio_airgapFiles , & QCheckBox : : toggled , [ this ] ( bool checked ) {
if ( checked ) {
2023-12-30 19:29:17 +00:00
conf ( ) - > set ( Config : : offlineTxSigningMethod , Config : : OTSMethod : : FileTransfer ) ;
2023-12-02 18:28:31 +00:00
}
} ) ;
connect ( ui - > radio_airgapUR , & QCheckBox : : toggled , [ this ] ( bool checked ) {
if ( checked ) {
2023-12-30 19:29:17 +00:00
conf ( ) - > set ( Config : : offlineTxSigningMethod , Config : : OTSMethod : : UnifiedResources ) ;
2023-12-02 18:28:31 +00:00
}
} ) ;
}
2021-05-02 18:22:38 +00:00
void MainWindow : : initWalletContext ( ) {
2023-03-01 02:05:56 +00:00
connect ( m_wallet , & Wallet : : balanceUpdated , this , & MainWindow : : onBalanceUpdated ) ;
2023-12-30 13:17:11 +00:00
connect ( m_wallet , & Wallet : : syncStatus , this , & MainWindow : : onSyncStatus ) ;
2023-09-12 14:15:40 +00:00
connect ( m_wallet , & Wallet : : transactionCreated , this , & MainWindow : : onTransactionCreated ) ;
2023-03-01 02:05:56 +00:00
connect ( m_wallet , & Wallet : : transactionCommitted , this , & MainWindow : : onTransactionCommitted ) ;
connect ( m_wallet , & Wallet : : initiateTransaction , this , & MainWindow : : onInitiateTransaction ) ;
connect ( m_wallet , & Wallet : : keysCorrupted , this , & MainWindow : : onKeysCorrupted ) ;
connect ( m_wallet , & Wallet : : selectedInputsChanged , this , & MainWindow : : onSelectedInputsChanged ) ;
2021-05-02 18:22:38 +00:00
2021-05-18 15:59:18 +00:00
// Wallet
2023-03-01 02:05:56 +00:00
connect ( m_wallet , & Wallet : : connectionStatusChanged , [ this ] ( int status ) {
2023-02-11 17:11:21 +00:00
// Order is important, first inform UI about a potential disconnect, then reconnect
this - > onConnectionStatusChanged ( status ) ;
2023-03-01 02:05:56 +00:00
m_nodes - > autoConnect ( ) ;
} ) ;
connect ( m_wallet , & Wallet : : currentSubaddressAccountChanged , this , & MainWindow : : updateTitle ) ;
connect ( m_wallet , & Wallet : : walletPassphraseNeeded , this , & MainWindow : : onWalletPassphraseNeeded ) ;
connect ( m_wallet , & Wallet : : unconfirmedMoneyReceived , this , [ this ] ( const QString & txId , uint64_t amount ) {
2024-01-03 01:19:17 +00:00
if ( m_wallet - > isSynchronized ( ) & & ! m_locked ) {
2023-03-01 02:05:56 +00:00
auto notify = QString ( " %1 XMR (pending) " ) . arg ( WalletManager : : displayAmount ( amount , false ) ) ;
2023-03-29 08:46:53 +00:00
m_windowManager - > notify ( " Payment received " , notify , 5000 ) ;
2023-03-01 02:05:56 +00:00
}
} ) ;
// Device
connect ( m_wallet , & Wallet : : deviceButtonRequest , this , & MainWindow : : onDeviceButtonRequest ) ;
connect ( m_wallet , & Wallet : : deviceButtonPressed , this , & MainWindow : : onDeviceButtonPressed ) ;
connect ( m_wallet , & Wallet : : deviceError , this , & MainWindow : : onDeviceError ) ;
connect ( m_wallet , & Wallet : : donationSent , this , [ ] {
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : donateBeg , - 1 ) ;
2023-03-01 02:05:56 +00:00
} ) ;
2023-03-02 12:41:33 +00:00
connect ( m_wallet , & Wallet : : multiBroadcast , this , & MainWindow : : onMultiBroadcast ) ;
2021-05-02 18:22:38 +00:00
}
2020-10-07 10:36:04 +00:00
void MainWindow : : menuToggleTabVisible ( const QString & key ) {
const auto toggleTab = m_tabShowHideMapper [ key ] ;
2023-11-30 14:01:39 +00:00
QStringList enabledTabs = conf ( ) - > get ( Config : : enabledTabs ) . toStringList ( ) ;
bool show = enabledTabs . contains ( key ) ;
2020-10-07 10:36:04 +00:00
show = ! show ;
2023-11-30 14:01:39 +00:00
if ( show ) {
enabledTabs . append ( key ) ;
} else {
enabledTabs . removeAll ( key ) ;
}
conf ( ) - > set ( Config : : enabledTabs , enabledTabs ) ;
2020-10-07 10:36:04 +00:00
ui - > tabWidget - > setTabVisible ( ui - > tabWidget - > indexOf ( toggleTab - > tab ) , show ) ;
toggleTab - > menuAction - > setText ( ( show ? QString ( " Hide " ) : QString ( " Show " ) ) + toggleTab - > name ) ;
}
2021-07-08 12:03:54 +00:00
void MainWindow : : menuClearHistoryClicked ( ) {
2023-09-12 14:15:40 +00:00
conf ( ) - > remove ( Config : : recentlyOpenedWallets ) ;
2021-07-08 12:03:54 +00:00
this - > updateRecentlyOpenedMenu ( ) ;
}
2021-05-18 15:59:18 +00:00
QString MainWindow : : walletName ( ) {
2023-03-01 02:05:56 +00:00
return QFileInfo ( m_wallet - > cachePath ( ) ) . fileName ( ) ;
2020-10-07 10:36:04 +00:00
}
2021-05-18 15:59:18 +00:00
QString MainWindow : : walletCachePath ( ) {
2023-03-01 02:05:56 +00:00
return m_wallet - > cachePath ( ) ;
2020-10-07 10:36:04 +00:00
}
2021-05-18 15:59:18 +00:00
QString MainWindow : : walletKeysPath ( ) {
2023-03-01 02:05:56 +00:00
return m_wallet - > keysPath ( ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : onWalletOpened ( ) {
qDebug ( ) < < Q_FUNC_INFO ;
2021-05-02 18:22:38 +00:00
m_splashDialog - > hide ( ) ;
2023-03-01 02:05:56 +00:00
m_wallet - > setRingDatabase ( Utils : : ringDatabasePath ( ) ) ;
2022-07-01 11:46:10 +00:00
2023-03-01 02:05:56 +00:00
m_wallet - > updateBalance ( ) ;
if ( m_wallet - > isHwBacked ( ) ) {
2021-05-02 18:22:38 +00:00
m_statusBtnHwDevice - > show ( ) ;
}
2021-03-08 20:03:20 +00:00
this - > bringToFront ( ) ;
2020-10-07 10:36:04 +00:00
this - > setEnabled ( true ) ;
// receive page
2023-03-01 02:05:56 +00:00
m_wallet - > subaddress ( ) - > refresh ( m_wallet - > currentSubaddressAccount ( ) ) ;
if ( m_wallet - > subaddress ( ) - > count ( ) = = 1 ) {
2020-10-07 10:36:04 +00:00
for ( int i = 0 ; i < 10 ; i + + ) {
2023-03-01 02:05:56 +00:00
m_wallet - > subaddress ( ) - > addRow ( m_wallet - > currentSubaddressAccount ( ) , " " ) ;
2020-10-07 10:36:04 +00:00
}
}
2023-03-01 02:05:56 +00:00
m_wallet - > subaddressModel ( ) - > setCurrentSubaddressAccount ( m_wallet - > currentSubaddressAccount ( ) ) ;
2020-10-07 10:36:04 +00:00
// history page
2023-11-17 13:05:31 +00:00
m_wallet - > history ( ) - > refresh ( ) ;
2020-10-07 10:36:04 +00:00
// coins page
2023-11-17 13:05:31 +00:00
m_wallet - > coins ( ) - > refresh ( ) ;
2023-03-01 02:05:56 +00:00
m_coinsWidget - > setModel ( m_wallet - > coinsModel ( ) , m_wallet - > coins ( ) ) ;
m_wallet - > coinsModel ( ) - > setCurrentSubaddressAccount ( m_wallet - > currentSubaddressAccount ( ) ) ;
2021-07-02 14:12:07 +00:00
// Coin labeling uses set_tx_note, so we need to refresh history too
2023-03-01 02:05:56 +00:00
connect ( m_wallet - > coins ( ) , & Coins : : descriptionChanged , [ this ] {
2023-11-17 13:05:31 +00:00
m_wallet - > history ( ) - > refresh ( ) ;
2021-07-02 14:12:07 +00:00
} ) ;
// Vice versa
2023-03-01 02:05:56 +00:00
connect ( m_wallet - > history ( ) , & TransactionHistory : : txNoteChanged , [ this ] {
2023-11-17 13:05:31 +00:00
m_wallet - > coins ( ) - > refresh ( ) ;
2021-07-02 14:12:07 +00:00
} ) ;
2020-10-22 03:50:59 +00:00
2020-11-14 09:57:06 +00:00
this - > updatePasswordIcon ( ) ;
2021-05-23 13:58:28 +00:00
this - > updateTitle ( ) ;
2023-03-01 02:05:56 +00:00
m_nodes - > allowConnection ( ) ;
m_nodes - > connectToNode ( ) ;
2021-05-02 18:22:38 +00:00
m_updateBytes . start ( 250 ) ;
2021-05-18 15:59:18 +00:00
2023-09-12 14:15:40 +00:00
if ( conf ( ) - > get ( Config : : writeRecentlyOpenedWallets ) . toBool ( ) ) {
2023-03-01 02:05:56 +00:00
this - > addToRecentlyOpened ( m_wallet - > cachePath ( ) ) ;
2023-02-11 17:11:21 +00:00
}
2020-10-07 10:36:04 +00:00
}
2020-12-25 14:20:39 +00:00
void MainWindow : : onBalanceUpdated ( quint64 balance , quint64 spendable ) {
2023-09-12 14:15:40 +00:00
bool hide = conf ( ) - > get ( Config : : hideBalance ) . toBool ( ) ;
int displaySetting = conf ( ) - > get ( Config : : balanceDisplay ) . toInt ( ) ;
int decimals = conf ( ) - > get ( Config : : amountPrecision ) . toInt ( ) ;
2020-11-02 09:37:36 +00:00
2021-05-22 14:12:39 +00:00
QString balance_str = " Balance: " ;
if ( hide ) {
balance_str + = " HIDDEN " ;
}
else if ( displaySetting = = Config : : totalBalance ) {
2021-05-25 17:11:45 +00:00
balance_str + = QString ( " %1 XMR " ) . arg ( WalletManager : : displayAmount ( balance , false , decimals ) ) ;
2021-05-04 23:09:19 +00:00
}
2021-05-22 14:12:39 +00:00
else if ( displaySetting = = Config : : spendable | | displaySetting = = Config : : spendablePlusUnconfirmed ) {
2021-05-25 17:11:45 +00:00
balance_str + = QString ( " %1 XMR " ) . arg ( WalletManager : : displayAmount ( spendable , false , decimals ) ) ;
2021-05-04 23:09:19 +00:00
2021-05-22 14:12:39 +00:00
if ( displaySetting = = Config : : spendablePlusUnconfirmed & & balance > spendable ) {
2021-05-25 17:11:45 +00:00
balance_str + = QString ( " (+%1 XMR unconfirmed) " ) . arg ( WalletManager : : displayAmount ( balance - spendable , false , decimals ) ) ;
2021-05-22 14:12:39 +00:00
}
}
2020-11-02 09:37:36 +00:00
2020-12-25 14:20:39 +00:00
m_statusLabelBalance - > setToolTip ( " Click for details " ) ;
2021-05-22 14:12:39 +00:00
m_statusLabelBalance - > setText ( balance_str ) ;
2020-10-07 10:36:04 +00:00
}
2020-12-31 00:00:37 +00:00
void MainWindow : : setStatusText ( const QString & text , bool override , int timeout ) {
if ( override ) {
m_statusOverrideActive = true ;
m_statusLabelStatus - > setText ( text ) ;
QTimer : : singleShot ( timeout , [ this ] {
m_statusOverrideActive = false ;
this - > setStatusText ( m_statusText ) ;
} ) ;
return ;
}
2020-12-24 14:46:56 +00:00
m_statusText = text ;
2020-12-31 00:00:37 +00:00
if ( ! m_statusOverrideActive & & ! m_constructingTransaction ) {
2020-12-24 14:46:56 +00:00
m_statusLabelStatus - > setText ( text ) ;
2020-12-31 00:00:37 +00:00
}
2020-12-24 14:46:56 +00:00
}
2021-08-14 14:53:22 +00:00
void MainWindow : : tryStoreWallet ( ) {
2023-03-01 02:05:56 +00:00
if ( m_wallet - > connectionStatus ( ) = = Wallet : : ConnectionStatus : : ConnectionStatus_Synchronizing ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Unable to save wallet " , " Can't save wallet during synchronization " , { " Wait until synchronization is finished and try again " } , " synchronization " ) ;
2021-08-14 14:53:22 +00:00
return ;
}
2023-03-01 02:05:56 +00:00
m_wallet - > store ( ) ;
2021-08-14 14:53:22 +00:00
}
2022-03-15 12:36:20 +00:00
void MainWindow : : onWebsocketStatusChanged ( bool enabled ) {
ui - > actionShow_Home - > setVisible ( enabled ) ;
2023-11-30 14:01:39 +00:00
QStringList enabledTabs = conf ( ) - > get ( Config : : enabledTabs ) . toStringList ( ) ;
for ( const auto & plugin : m_plugins ) {
if ( plugin - > hasParent ( ) ) {
continue ;
}
if ( plugin - > requiresWebsocket ( ) ) {
// TODO: unload plugins
ui - > tabWidget - > setTabVisible ( this - > findTab ( plugin - > displayName ( ) ) , enabled & & enabledTabs . contains ( plugin - > displayName ( ) ) ) ;
}
}
2022-03-15 12:36:20 +00:00
2022-07-03 16:24:33 +00:00
m_historyWidget - > setWebsocketEnabled ( enabled ) ;
2023-02-13 09:41:07 +00:00
m_sendWidget - > setWebsocketEnabled ( enabled ) ;
2022-03-15 12:36:20 +00:00
}
2023-02-11 17:11:21 +00:00
void MainWindow : : onProxySettingsChanged ( ) {
2023-03-01 02:05:56 +00:00
m_nodes - > connectToNode ( ) ;
2023-09-12 14:15:40 +00:00
int proxy = conf ( ) - > get ( Config : : proxy ) . toInt ( ) ;
2023-02-11 17:11:21 +00:00
if ( proxy = = Config : : Proxy : : Tor ) {
this - > onTorConnectionStateChanged ( torManager ( ) - > torConnected ) ;
m_statusBtnProxySettings - > show ( ) ;
return ;
}
if ( proxy = = Config : : Proxy : : i2p ) {
m_statusBtnProxySettings - > setIcon ( icons ( ) - > icon ( " i2p.png " ) ) ;
m_statusBtnProxySettings - > show ( ) ;
return ;
}
m_statusBtnProxySettings - > hide ( ) ;
}
2023-03-01 02:05:56 +00:00
void MainWindow : : onOfflineMode ( bool offline ) {
2023-12-02 18:28:31 +00:00
m_wallet - > setOffline ( offline ) ;
if ( m_wallet - > viewOnly ( ) ) {
2023-03-01 02:05:56 +00:00
return ;
}
2023-12-02 18:28:31 +00:00
if ( ui - > stackedWidget - > currentIndex ( ) ! = Stack : : LOCKED ) {
ui - > stackedWidget - > setCurrentIndex ( offline ? Stack : : OFFLINE : Stack : : WALLET ) ;
}
ui - > actionPay_to_many - > setVisible ( ! offline ) ;
ui - > menuView - > setDisabled ( offline ) ;
m_statusLabelBalance - > setVisible ( ! offline ) ;
m_statusBtnProxySettings - > setVisible ( ! offline ) ;
2023-03-01 02:05:56 +00:00
}
2023-03-02 12:41:33 +00:00
void MainWindow : : onMultiBroadcast ( const QMap < QString , QString > & txHexMap ) {
QMapIterator < QString , QString > i ( txHexMap ) ;
while ( i . hasNext ( ) ) {
i . next ( ) ;
for ( const auto & node : m_nodes - > nodes ( ) ) {
QString address = node . toURL ( ) ;
qDebug ( ) < < QString ( " Relaying %1 to: %2 " ) . arg ( i . key ( ) , address ) ;
m_rpc - > setDaemonAddress ( address ) ;
m_rpc - > sendRawTransaction ( i . value ( ) ) ;
}
}
}
2023-12-30 13:17:11 +00:00
void MainWindow : : onSyncStatus ( quint64 height , quint64 target , bool daemonSync ) {
if ( height > = ( target - 1 ) ) {
this - > updateNetStats ( ) ;
}
this - > setStatusText ( Utils : : formatSyncStatus ( height , target , daemonSync ) ) ;
2024-01-08 13:25:17 +00:00
m_statusLabelStatus - > setToolTip ( QString ( " Wallet height: %1 " ) . arg ( QString : : number ( height ) ) ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : onConnectionStatusChanged ( int status )
{
2023-12-30 11:43:10 +00:00
// Note: Wallet does not emit this signal unless status is changed, so calling this function from MainWindow may
// result in the wrong connection status being displayed.
2020-11-23 16:57:38 +00:00
qDebug ( ) < < " Wallet connection status changed " < < Utils : : QtEnumToString ( static_cast < Wallet : : ConnectionStatus > ( status ) ) ;
2020-10-07 10:36:04 +00:00
// Update connection info in status bar.
2021-05-02 18:22:38 +00:00
QIcon icon ;
2023-09-12 14:15:40 +00:00
if ( conf ( ) - > get ( Config : : offlineMode ) . toBool ( ) ) {
2023-02-11 17:11:21 +00:00
icon = icons ( ) - > icon ( " status_offline.svg " ) ;
2023-12-02 18:28:31 +00:00
this - > setStatusText ( " Offline mode " ) ;
2023-02-11 17:11:21 +00:00
} else {
switch ( status ) {
case Wallet : : ConnectionStatus_Disconnected :
icon = icons ( ) - > icon ( " status_disconnected.svg " ) ;
this - > setStatusText ( " Disconnected " ) ;
break ;
case Wallet : : ConnectionStatus_Connecting :
icon = icons ( ) - > icon ( " status_lagging.svg " ) ;
this - > setStatusText ( " Connecting to node " ) ;
break ;
case Wallet : : ConnectionStatus_WrongVersion :
icon = icons ( ) - > icon ( " status_disconnected.svg " ) ;
this - > setStatusText ( " Incompatible node " ) ;
break ;
case Wallet : : ConnectionStatus_Synchronizing :
icon = icons ( ) - > icon ( " status_waiting.svg " ) ;
break ;
case Wallet : : ConnectionStatus_Synchronized :
icon = icons ( ) - > icon ( " status_connected.svg " ) ;
break ;
default :
icon = icons ( ) - > icon ( " status_disconnected.svg " ) ;
break ;
}
2020-10-07 10:36:04 +00:00
}
2021-05-02 18:22:38 +00:00
m_statusBtnConnectionStatusIndicator - > setIcon ( icon ) ;
2020-10-07 10:36:04 +00:00
}
2023-09-12 14:15:40 +00:00
void MainWindow : : onTransactionCreated ( PendingTransaction * tx , const QVector < QString > & address ) {
// Clean up some UI
m_constructingTransaction = false ;
m_txTimer . stop ( ) ;
this - > setStatusText ( m_statusText ) ;
if ( m_wallet - > isHwBacked ( ) ) {
m_splashDialog - > hide ( ) ;
}
2021-08-19 18:22:53 +00:00
if ( tx - > status ( ) ! = PendingTransaction : : Status_Ok ) {
2023-12-13 14:24:02 +00:00
if ( m_showDeviceError ) {
// The hardware devices has disconnected during tx construction.
// Due to a macOS-specific Qt bug, we have to prevent it from stacking two QMessageBoxes, otherwise
// the UI becomes unresponsive. The reconnect dialog should take priority.
m_wallet - > disposeTransaction ( tx ) ;
return ;
}
2023-09-12 14:15:40 +00:00
QString errMsg = tx - > errorString ( ) ;
2020-10-07 10:36:04 +00:00
2023-09-12 14:15:40 +00:00
Utils : : Message message { this , Utils : : ERROR , " Failed to construct transaction " , errMsg } ;
2020-10-07 10:36:04 +00:00
2023-09-12 14:15:40 +00:00
if ( tx - > getException ( ) ) {
try
{
std : : rethrow_exception ( tx - > getException ( ) ) ;
}
catch ( const tools : : error : : daemon_busy & e ) {
message . description = QString ( " Node was unable to respond. Failed request: %1 " ) . arg ( QString : : fromStdString ( e . request ( ) ) ) ;
message . helpItems = { " Try sending the transaction again. " , " If this keeps happening, connect to a different node. " } ;
}
catch ( const tools : : error : : no_connection_to_daemon & e ) {
message . description = QString ( " Connection to node lost. Failed request: %1 " ) . arg ( QString : : fromStdString ( e . request ( ) ) ) ;
message . helpItems = { " Try sending the transaction again. " , " If this keeps happening, connect to a different node. " } ;
}
catch ( const tools : : error : : wallet_rpc_error & e ) {
message . description = QString ( " RPC error: %1 " ) . arg ( QString : : fromStdString ( e . to_string ( ) ) ) ;
message . helpItems = { " Try sending the transaction again. " , " If this keeps happening, connect to a different node. " } ;
}
catch ( const tools : : error : : get_outs_error & e ) {
message . description = " Failed to get enough decoy outputs from node " ;
message . helpItems = { " Your transaction has too many inputs. Try sending a lower amount. " } ;
}
catch ( const tools : : error : : not_enough_unlocked_money & e ) {
2023-10-25 23:23:22 +00:00
QString error ;
if ( e . fee ( ) > e . available ( ) ) {
error = QString ( " Transaction fee exceeds spendable balance. \n \n Spendable balance: %1 \n Transaction fee: %2 " ) . arg ( WalletManager : : displayAmount ( e . available ( ) ) , WalletManager : : displayAmount ( e . fee ( ) ) ) ;
}
else {
error = QString ( " Spendable balance insufficient to pay for transaction. \n \n Spendable balance: %1 \n Transaction needs: %2 " ) . arg ( WalletManager : : displayAmount ( e . available ( ) ) , WalletManager : : displayAmount ( e . tx_amount ( ) + e . fee ( ) ) ) ;
}
message . description = error ;
2023-09-12 14:15:40 +00:00
message . helpItems = { " Wait for more balance to unlock. " , " Click 'Help' to learn more about how balance works. " } ;
message . doc = " balance " ;
}
catch ( const tools : : error : : not_enough_money & e ) {
message . description = QString ( " Not enough money to transfer \n \n Total balance: %1 \n Transaction amount: %2 " ) . arg ( WalletManager : : displayAmount ( e . available ( ) ) , WalletManager : : displayAmount ( e . tx_amount ( ) ) ) ;
message . helpItems = { " If you are trying to send your entire balance, click 'Max'. " } ;
message . doc = " balance " ;
}
catch ( const tools : : error : : tx_not_possible & e ) {
message . description = QString ( " Not enough money to transfer. Transaction amount + fee exceeds available balance. " ) ;
message . helpItems = { " If you're trying to send your entire balance, click 'Max'. " } ;
message . doc = " balance " ;
}
catch ( const tools : : error : : not_enough_outs_to_mix & e ) {
message . description = " Not enough outputs for specified ring size. " ;
}
catch ( const tools : : error : : tx_not_constructed & ) {
message . description = " Transaction was not constructed " ;
message . helpItems = { " You have found a bug. Please contact the developers. " } ;
message . doc = " report_an_issue " ;
}
catch ( const tools : : error : : tx_rejected & e ) {
// TODO: provide helptext
message . description = QString ( " Transaction was rejected by node. Reason: %1. " ) . arg ( QString : : fromStdString ( e . status ( ) ) ) ;
}
catch ( const tools : : error : : tx_sum_overflow & e ) {
message . description = " Transaction tries to spend an unrealistic amount of XMR " ;
message . helpItems = { " You have found a bug. Please contact the developers. " } ;
message . doc = " report_an_issue " ;
}
catch ( const tools : : error : : zero_amount & ) {
message . description = " Destination amount is zero " ;
message . helpItems = { " You have found a bug. Please contact the developers. " } ;
message . doc = " report_an_issue " ;
}
catch ( const tools : : error : : zero_destination & ) {
message . description = " Transaction has no destination " ;
message . helpItems = { " You have found a bug. Please contact the developers. " } ;
message . doc = " report_an_issue " ;
}
catch ( const tools : : error : : tx_too_big & e ) {
message . description = " Transaction too big " ;
message . helpItems = { " Try sending a smaller amount. " } ;
}
catch ( const tools : : error : : transfer_error & e ) {
message . description = QString ( " Unknown transfer error: %1 " ) . arg ( QString : : fromStdString ( e . what ( ) ) ) ;
message . helpItems = { " You have found a bug. Please contact the developers. " } ;
message . doc = " report_an_issue " ;
}
catch ( const tools : : error : : wallet_internal_error & e ) {
2023-10-26 00:16:09 +00:00
bool bug = true ;
2023-09-12 14:15:40 +00:00
QString msg = e . what ( ) ;
message . description = QString ( " Internal error: %1 " ) . arg ( QString : : fromStdString ( e . what ( ) ) ) ;
2023-10-26 00:16:09 +00:00
2023-09-12 14:15:40 +00:00
if ( msg . contains ( " Daemon response did not include the requested real output " ) ) {
QString currentNode = m_nodes - > connection ( ) . toAddress ( ) ;
message . description + = QString ( " \n You are currently connected to: %1 \n \n "
" This node may be acting maliciously. You are strongly recommended to disconnect from this node. "
" Please report this incident to the developers. " ) . arg ( currentNode ) ;
message . doc = " report_an_issue " ;
}
2023-10-26 00:16:09 +00:00
if ( msg . startsWith ( " No unlocked balance " ) ) {
// TODO: We're sending ALL, but fractional outputs got ignored
message . description = " Spendable balance insufficient to pay for transaction fee. " ;
bug = false ;
}
2021-05-22 18:13:48 +00:00
2023-10-26 00:16:09 +00:00
if ( bug ) {
message . helpItems = { " You have found a bug. Please contact the developers. " } ;
message . doc = " report_an_issue " ;
}
2023-09-12 14:15:40 +00:00
}
catch ( const std : : exception & e ) {
message . description = QString : : fromStdString ( e . what ( ) ) ;
}
2021-05-22 18:13:48 +00:00
}
2023-09-12 14:15:40 +00:00
Utils : : showMsg ( message ) ;
2023-03-01 02:05:56 +00:00
m_wallet - > disposeTransaction ( tx ) ;
2021-08-19 18:22:53 +00:00
return ;
2021-05-22 18:13:48 +00:00
}
else if ( tx - > txCount ( ) = = 0 ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Failed to construct transaction " , " No transactions were constructed " , { " You have found a bug. Please contact the developers. " } , " report_an_issue " ) ;
2023-03-01 02:05:56 +00:00
m_wallet - > disposeTransaction ( tx ) ;
2021-08-19 18:22:53 +00:00
return ;
2021-05-22 18:13:48 +00:00
}
2022-03-12 13:54:08 +00:00
else if ( tx - > txCount ( ) > 1 ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Failed to construct transaction " , " Split transactions are not supported " , { " Try sending a smaller amount. " } ) ;
2023-03-01 02:05:56 +00:00
m_wallet - > disposeTransaction ( tx ) ;
2022-03-12 13:54:08 +00:00
return ;
}
2022-03-12 13:56:03 +00:00
// This is a weak check to see if we send to all specified destination addresses
// This is here to catch rare memory corruption errors during transaction construction
// TODO: also check that amounts match
tx - > refresh ( ) ;
QSet < QString > outputAddresses ;
for ( const auto & output : tx - > transaction ( 0 ) - > outputs ( ) ) {
2022-07-26 16:59:17 +00:00
outputAddresses . insert ( WalletManager : : baseAddressFromIntegratedAddress ( output - > address ( ) , constants : : networkType ) ) ;
2022-03-12 13:56:03 +00:00
}
QSet < QString > destAddresses ;
for ( const auto & addr : address ) {
2022-07-26 16:59:17 +00:00
// TODO: Monero core bug, integrated address is not added to dests for transactions spending ALL
destAddresses . insert ( WalletManager : : baseAddressFromIntegratedAddress ( addr , constants : : networkType ) ) ;
2022-03-12 13:56:03 +00:00
}
if ( ! outputAddresses . contains ( destAddresses ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Transaction fails sanity check " , " Constructed transaction doesn't appear to send to (all) specified destination address(es). Try creating the transaction again. " ) ;
2023-03-01 02:05:56 +00:00
m_wallet - > disposeTransaction ( tx ) ;
2022-03-12 13:56:03 +00:00
return ;
}
2021-01-26 23:55:27 +00:00
2023-03-01 02:05:56 +00:00
m_wallet - > addCacheTransaction ( tx - > txid ( ) [ 0 ] , tx - > signedTxToHex ( 0 ) ) ;
2021-08-19 18:22:53 +00:00
2023-12-02 18:28:31 +00:00
// Offline transaction signing
if ( m_wallet - > viewOnly ( ) ) {
# ifdef WITH_SCANNER
OfflineTxSigningWizard wizard ( this , m_wallet , tx ) ;
wizard . exec ( ) ;
if ( ! wizard . readyToCommit ( ) ) {
return ;
} else {
tx = wizard . signedTx ( ) ;
}
if ( tx - > txCount ( ) = = 0 ) {
Utils : : showError ( this , " Failed to load transaction " , " No transactions were found " , { " You have found a bug. Please contact the developers. " } , " report_an_issue " ) ;
m_wallet - > disposeTransaction ( tx ) ;
return ;
}
# else
Utils : : showError ( this , " Can't open offline transaction signing wizard " , " Feather was built without webcam QR scanner support " ) ;
return ;
# endif
}
2021-08-19 18:22:53 +00:00
// Show advanced dialog on multi-destination transactions
2023-12-02 18:28:31 +00:00
if ( address . size ( ) > 1 ) {
2023-03-01 02:05:56 +00:00
TxConfAdvDialog dialog_adv { m_wallet , m_wallet - > tmpTxDescription , this } ;
dialog_adv . setTransaction ( tx , ! m_wallet - > viewOnly ( ) ) ;
2021-08-19 18:22:53 +00:00
dialog_adv . exec ( ) ;
return ;
}
2020-10-16 03:05:05 +00:00
2023-03-01 02:05:56 +00:00
TxConfDialog dialog { m_wallet , tx , address [ 0 ] , m_wallet - > tmpTxDescription , this } ;
2021-08-19 18:22:53 +00:00
switch ( dialog . exec ( ) ) {
case QDialog : : Rejected :
{
2023-03-01 02:05:56 +00:00
if ( ! dialog . showAdvanced ) {
m_wallet - > disposeTransaction ( tx ) ;
}
2021-08-19 18:22:53 +00:00
break ;
2020-10-16 03:05:05 +00:00
}
2021-08-19 18:22:53 +00:00
case QDialog : : Accepted :
2023-03-01 02:05:56 +00:00
m_wallet - > commitTransaction ( tx , m_wallet - > tmpTxDescription ) ;
2021-08-19 18:22:53 +00:00
break ;
}
if ( dialog . showAdvanced ) {
2023-03-01 02:05:56 +00:00
TxConfAdvDialog dialog_adv { m_wallet , m_wallet - > tmpTxDescription , this } ;
2021-08-19 18:22:53 +00:00
dialog_adv . setTransaction ( tx ) ;
dialog_adv . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
}
2023-03-02 12:41:33 +00:00
void MainWindow : : onTransactionCommitted ( bool success , PendingTransaction * tx , const QStringList & txid ) {
2023-09-12 14:15:40 +00:00
if ( ! success ) {
2023-12-02 18:28:31 +00:00
QString error = tx - > errorString ( ) ;
if ( m_wallet - > viewOnly ( ) & & error . contains ( " double spend " ) ) {
m_wallet - > setForceKeyImageSync ( true ) ;
}
2023-12-14 15:37:46 +00:00
if ( error . contains ( " no connection to daemon " ) ) {
2024-01-02 16:41:51 +00:00
QMessageBox box ( this ) ;
box . setWindowTitle ( " Question " ) ;
box . setText ( " Unable to send transaction " ) ;
box . setInformativeText ( " No connection to node. Retry sending transaction? " ) ;
QPushButton * manual = box . addButton ( " Broadcast manually " , QMessageBox : : HelpRole ) ;
box . addButton ( QMessageBox : : No ) ;
box . addButton ( QMessageBox : : Yes ) ;
box . exec ( ) ;
if ( box . clickedButton ( ) = = manual ) {
if ( txid . empty ( ) ) {
Utils : : showError ( this , " Unable to open tx broadcaster " , " Cached transaction not found " ) ;
return ;
}
this - > onResendTransaction ( txid [ 0 ] ) ;
}
else if ( box . result ( ) = = QMessageBox : : Yes ) {
2023-12-14 15:37:46 +00:00
m_wallet - > commitTransaction ( tx , m_wallet - > tmpTxDescription ) ;
}
return ;
}
2023-12-02 18:28:31 +00:00
Utils : : showError ( this , " Failed to send transaction " , error ) ;
2023-09-12 14:15:40 +00:00
return ;
2020-10-07 10:36:04 +00:00
}
2023-09-12 14:15:40 +00:00
QMessageBox msgBox { this } ;
QPushButton * showDetailsButton = msgBox . addButton ( " Show details " , QMessageBox : : ActionRole ) ;
msgBox . addButton ( QMessageBox : : Ok ) ;
QString body = QString ( " Successfully sent %1 transaction(s). " ) . arg ( txid . count ( ) ) ;
msgBox . setText ( body ) ;
msgBox . setWindowTitle ( " Transaction sent " ) ;
msgBox . setIcon ( QMessageBox : : Icon : : Information ) ;
msgBox . exec ( ) ;
if ( msgBox . clickedButton ( ) = = showDetailsButton ) {
this - > showHistoryTab ( ) ;
2023-11-17 13:05:31 +00:00
TransactionRow * txInfo = m_wallet - > history ( ) - > transaction ( txid . first ( ) ) ;
2023-09-12 14:15:40 +00:00
auto * dialog = new TxInfoDialog ( m_wallet , txInfo , this ) ;
connect ( dialog , & TxInfoDialog : : resendTranscation , this , & MainWindow : : onResendTransaction ) ;
dialog - > show ( ) ;
dialog - > setAttribute ( Qt : : WA_DeleteOnClose ) ;
2020-10-07 10:36:04 +00:00
}
2023-09-12 14:15:40 +00:00
m_sendWidget - > clearFields ( ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : showWalletInfoDialog ( ) {
2023-03-01 02:05:56 +00:00
WalletInfoDialog dialog { m_wallet , this } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : showSeedDialog ( ) {
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isHwBacked ( ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Seed unavailable " , " Wallet keys are stored on a hardware device " , { } , " show_wallet_seed " ) ;
2021-05-02 18:22:38 +00:00
return ;
}
2023-03-01 02:05:56 +00:00
if ( m_wallet - > viewOnly ( ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Seed unavailable " , " Wallet is view-only " , { " To obtain your private spendkey go to Wallet -> Keys " } , " show_wallet_seed " ) ;
2021-03-14 21:12:02 +00:00
return ;
}
2023-03-01 02:05:56 +00:00
if ( ! m_wallet - > isDeterministic ( ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Seed unavailable " , " Wallet is non-deterministic and has no seed " ,
{ " To obtain wallet keys go to Wallet -> Keys " } , " show_wallet_seed " ) ;
2021-03-14 21:12:02 +00:00
return ;
}
2021-06-04 18:48:44 +00:00
if ( ! this - > verifyPassword ( ) ) {
return ;
}
2023-03-01 02:05:56 +00:00
SeedDialog dialog { m_wallet , this } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : showPasswordDialog ( ) {
2023-03-01 02:05:56 +00:00
PasswordChangeDialog dialog { this , m_wallet } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-11-14 09:57:06 +00:00
this - > updatePasswordIcon ( ) ;
}
void MainWindow : : updatePasswordIcon ( ) {
2023-03-01 02:05:56 +00:00
bool emptyPassword = m_wallet - > verifyPassword ( " " ) ;
2023-01-31 16:00:50 +00:00
QIcon icon = emptyPassword ? icons ( ) - > icon ( " unlock.svg " ) : icons ( ) - > icon ( " lock.svg " ) ;
2020-11-14 09:57:06 +00:00
m_statusBtnPassword - > setIcon ( icon ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : showKeysDialog ( ) {
2021-06-04 18:48:44 +00:00
if ( ! this - > verifyPassword ( ) ) {
return ;
}
2023-03-01 02:05:56 +00:00
KeysDialog dialog { m_wallet , this } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
2020-10-12 22:01:06 +00:00
void MainWindow : : showViewOnlyDialog ( ) {
2023-12-11 22:52:44 +00:00
if ( ! this - > verifyPassword ( ) ) {
return ;
}
2023-03-01 02:05:56 +00:00
ViewOnlyDialog dialog { m_wallet , this } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-10-12 22:01:06 +00:00
}
2023-12-02 18:28:31 +00:00
void MainWindow : : showKeyImageSyncWizard ( ) {
# ifdef WITH_SCANNER
OfflineTxSigningWizard wizard { this , m_wallet } ;
wizard . exec ( ) ;
if ( wizard . readyToSign ( ) ) {
TxConfAdvDialog dialog { m_wallet , " " , this , true } ;
dialog . setUnsignedTransaction ( wizard . unsignedTransaction ( ) ) ;
auto r = dialog . exec ( ) ;
if ( r ! = QDialog : : Accepted ) {
return ;
}
wizard . setStartId ( OfflineTxSigningWizard : : Page_ExportSignedTx ) ;
wizard . restart ( ) ;
wizard . exec ( ) ;
}
# else
Utils : : showError ( this , " Can't open offline transaction signing wizard " , " Feather was built without webcam QR scanner support " ) ;
# endif
}
2021-05-02 18:22:38 +00:00
void MainWindow : : menuHwDeviceClicked ( ) {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Hardware device " , QString ( " This wallet is backed by a %1 hardware device. " ) . arg ( this - > getHardwareDevice ( ) ) ) ;
2021-05-02 18:22:38 +00:00
}
2021-05-18 15:59:18 +00:00
void MainWindow : : menuOpenClicked ( ) {
m_windowManager - > wizardOpenWallet ( ) ;
}
2020-10-07 10:36:04 +00:00
void MainWindow : : menuNewRestoreClicked ( ) {
2021-05-18 15:59:18 +00:00
m_windowManager - > showWizard ( WalletWizard : : Page_Menu ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : menuQuitClicked ( ) {
2021-05-18 15:59:18 +00:00
this - > close ( ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : menuWalletCloseClicked ( ) {
2021-05-18 15:59:18 +00:00
m_windowManager - > showWizard ( WalletWizard : : Page_Menu ) ;
this - > close ( ) ;
2020-10-07 10:36:04 +00:00
}
2023-02-11 17:11:21 +00:00
void MainWindow : : menuProxySettingsClicked ( ) {
this - > menuSettingsClicked ( true ) ;
}
2020-10-07 10:36:04 +00:00
void MainWindow : : menuAboutClicked ( ) {
2021-05-02 18:22:38 +00:00
AboutDialog dialog { this } ;
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
2023-02-11 17:11:21 +00:00
void MainWindow : : menuSettingsClicked ( bool showProxyTab ) {
2023-03-01 02:05:56 +00:00
m_windowManager - > showSettings ( m_nodes , this , showProxyTab ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : menuSignVerifyClicked ( ) {
2023-03-01 02:05:56 +00:00
SignVerifyDialog dialog { m_wallet , this } ;
2021-05-02 18:22:38 +00:00
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : menuVerifyTxProof ( ) {
2023-03-01 02:05:56 +00:00
VerifyProofDialog dialog { m_wallet , this } ;
2021-05-02 18:22:38 +00:00
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
2022-03-11 13:56:07 +00:00
void MainWindow : : onShowSettingsPage ( int page ) {
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : lastSettingsPage , page ) ;
2022-03-11 13:56:07 +00:00
this - > menuSettingsClicked ( ) ;
}
2020-10-07 10:36:04 +00:00
void MainWindow : : skinChanged ( const QString & skinName ) {
2021-01-28 22:48:14 +00:00
ColorScheme : : updateFromWidget ( this ) ;
2021-06-25 14:14:49 +00:00
this - > updateWidgetIcons ( ) ;
}
2021-02-03 19:40:35 +00:00
2021-06-25 14:14:49 +00:00
void MainWindow : : updateWidgetIcons ( ) {
m_sendWidget - > skinChanged ( ) ;
2023-11-30 14:01:39 +00:00
emit updateIcons ( ) ;
2021-07-01 21:00:47 +00:00
m_statusBtnHwDevice - > setIcon ( this - > hardwareDevicePairedIcon ( ) ) ;
}
QIcon MainWindow : : hardwareDevicePairedIcon ( ) {
QString filename ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isLedger ( ) )
2021-07-01 21:00:47 +00:00
filename = " ledger.png " ;
2023-03-01 02:05:56 +00:00
else if ( m_wallet - > isTrezor ( ) )
2021-07-01 21:00:47 +00:00
filename = ColorScheme : : darkScheme ? " trezor_white.png " : " trezor.png " ;
return icons ( ) - > icon ( filename ) ;
}
QIcon MainWindow : : hardwareDeviceUnpairedIcon ( ) {
QString filename ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isLedger ( ) )
2021-07-01 21:00:47 +00:00
filename = " ledger_unpaired.png " ;
2023-03-01 02:05:56 +00:00
else if ( m_wallet - > isTrezor ( ) )
2021-07-01 21:00:47 +00:00
filename = ColorScheme : : darkScheme ? " trezor_unpaired_white.png " : " trezor_unpaired.png " ;
return icons ( ) - > icon ( filename ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : closeEvent ( QCloseEvent * event ) {
2021-05-18 15:59:18 +00:00
qDebug ( ) < < Q_FUNC_INFO ;
if ( ! this - > cleanedUp ) {
this - > cleanedUp = true ;
2023-11-30 14:01:39 +00:00
emit aboutToQuit ( ) ;
2022-07-02 20:41:46 +00:00
2021-05-25 13:30:19 +00:00
m_historyWidget - > resetModel ( ) ;
2021-05-18 15:59:18 +00:00
m_updateBytes . stop ( ) ;
m_txTimer . stop ( ) ;
2021-07-09 22:25:13 +00:00
// Wallet signal may fire after AppContext is gone, causing segv
2023-03-01 02:05:56 +00:00
m_wallet - > disconnect ( ) ;
this - > disconnect ( ) ;
2020-10-07 10:36:04 +00:00
2021-05-18 15:59:18 +00:00
this - > saveGeo ( ) ;
m_windowManager - > closeWindow ( this ) ;
}
event - > accept ( ) ;
2020-10-07 10:36:04 +00:00
}
2023-02-11 17:11:21 +00:00
void MainWindow : : changeEvent ( QEvent * event )
{
if ( ( event - > type ( ) = = QEvent : : WindowStateChange ) & & this - > isMinimized ( ) ) {
2023-09-12 14:15:40 +00:00
if ( conf ( ) - > get ( Config : : lockOnMinimize ) . toBool ( ) ) {
2023-02-11 17:11:21 +00:00
this - > lockWallet ( ) ;
}
} else {
QMainWindow : : changeEvent ( event ) ;
}
}
2020-10-07 10:36:04 +00:00
void MainWindow : : donateButtonClicked ( ) {
2023-10-16 20:13:59 +00:00
m_sendWidget - > fill ( constants : : donationAddress , constants : : donationDescription ) ;
2023-11-30 14:01:39 +00:00
ui - > tabWidget - > setCurrentIndex ( this - > findTab ( " Send " ) ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : showHistoryTab ( ) {
this - > raise ( ) ;
2023-11-30 14:01:39 +00:00
ui - > tabWidget - > setCurrentIndex ( this - > findTab ( " History " ) ) ;
2020-10-07 10:36:04 +00:00
}
2022-06-23 17:48:30 +00:00
void MainWindow : : fillSendTab ( const QString & address , const QString & description ) {
m_sendWidget - > fill ( address , description ) ;
2023-11-30 14:01:39 +00:00
ui - > tabWidget - > setCurrentIndex ( this - > findTab ( " Send " ) ) ;
2020-10-07 10:36:04 +00:00
}
2021-01-26 23:55:27 +00:00
void MainWindow : : payToMany ( ) {
2023-11-30 14:01:39 +00:00
ui - > tabWidget - > setCurrentIndex ( this - > findTab ( " Send " ) ) ;
2021-05-10 15:05:10 +00:00
m_sendWidget - > payToMany ( ) ;
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Pay to many " , " Enter a list of outputs in the 'Pay to' field. \n "
" One output per line. \n "
" Format: address, amount \n "
" A maximum of 16 addresses may be specified. " ) ;
2021-01-26 23:55:27 +00:00
}
2020-10-07 10:36:04 +00:00
void MainWindow : : onViewOnBlockExplorer ( const QString & txid ) {
2024-01-07 19:19:38 +00:00
QString blockExplorerLink = Utils : : blockExplorerLink ( txid ) ;
if ( blockExplorerLink . isEmpty ( ) ) {
Utils : : showError ( this , " Unable to open block explorer " , " No block explorer configured " , { " Go to Settings -> Misc -> Block explorer " } ) ;
return ;
}
2020-12-14 02:20:05 +00:00
Utils : : externalLinkWarning ( this , blockExplorerLink ) ;
2020-10-07 10:36:04 +00:00
}
2020-12-14 22:07:23 +00:00
void MainWindow : : onResendTransaction ( const QString & txid ) {
2023-03-01 02:05:56 +00:00
QString txHex = m_wallet - > getCacheTransaction ( txid ) ;
2021-07-04 21:17:10 +00:00
if ( txHex . isEmpty ( ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Unable to resend transaction " , " Transaction was not found in the transaction cache. " ) ;
2020-12-14 22:07:23 +00:00
return ;
}
// Connect to a different node so chances of successful relay are higher
2023-03-01 02:05:56 +00:00
m_nodes - > autoConnect ( true ) ;
2020-12-14 22:07:23 +00:00
2023-03-01 02:05:56 +00:00
TxBroadcastDialog dialog { this , m_nodes , txHex } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-12-14 22:07:23 +00:00
}
2020-10-21 06:25:02 +00:00
void MainWindow : : importContacts ( ) {
const QString targetFile = QFileDialog : : getOpenFileName ( this , " Import CSV file " , QDir : : homePath ( ) , " CSV Files (*.csv) " ) ;
if ( targetFile . isEmpty ( ) ) return ;
2023-03-01 02:05:56 +00:00
auto * model = m_wallet - > addressBookModel ( ) ;
2020-10-21 06:25:02 +00:00
QMapIterator < QString , QString > i ( model - > readCSV ( targetFile ) ) ;
int inserts = 0 ;
while ( i . hasNext ( ) ) {
i . next ( ) ;
2023-03-01 02:05:56 +00:00
bool addressValid = WalletManager : : addressValid ( i . value ( ) , m_wallet - > nettype ( ) ) ;
2020-10-21 06:25:02 +00:00
if ( addressValid ) {
2023-11-17 13:05:31 +00:00
m_wallet - > addressBook ( ) - > addRow ( i . value ( ) , i . key ( ) ) ;
2020-10-21 06:25:02 +00:00
inserts + + ;
}
}
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Contacts imported " , QString ( " Total contacts imported: %1 " ) . arg ( inserts ) ) ;
2020-10-21 06:25:02 +00:00
}
2020-10-07 10:36:04 +00:00
void MainWindow : : saveGeo ( ) {
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : geometry , QString ( saveGeometry ( ) . toBase64 ( ) ) ) ;
conf ( ) - > set ( Config : : windowState , QString ( saveState ( ) . toBase64 ( ) ) ) ;
2020-10-07 10:36:04 +00:00
}
void MainWindow : : restoreGeo ( ) {
2023-09-12 14:15:40 +00:00
bool geo = this - > restoreGeometry ( QByteArray : : fromBase64 ( conf ( ) - > get ( Config : : geometry ) . toByteArray ( ) ) ) ;
bool windowState = this - > restoreState ( QByteArray : : fromBase64 ( conf ( ) - > get ( Config : : windowState ) . toByteArray ( ) ) ) ;
2020-10-07 10:36:04 +00:00
qDebug ( ) < < " Restored window state: " < < geo < < " " < < windowState ;
}
void MainWindow : : showDebugInfo ( ) {
2023-03-01 02:05:56 +00:00
DebugInfoDialog dialog { m_wallet , m_nodes , this } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2021-01-25 16:38:04 +00:00
}
void MainWindow : : showWalletCacheDebugDialog ( ) {
2022-03-04 12:42:14 +00:00
if ( ! this - > verifyPassword ( ) ) {
return ;
}
2023-03-01 02:05:56 +00:00
WalletCacheDebugDialog dialog { m_wallet , this } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-10-07 10:36:04 +00:00
}
2021-05-22 13:42:26 +00:00
void MainWindow : : showAccountSwitcherDialog ( ) {
2023-01-17 21:06:08 +00:00
m_accountSwitcherDialog - > show ( ) ;
2023-03-13 18:46:31 +00:00
m_accountSwitcherDialog - > update ( ) ;
2021-05-22 13:42:26 +00:00
}
2021-07-06 14:10:24 +00:00
void MainWindow : : showAddressChecker ( ) {
QString address = QInputDialog : : getText ( this , " Address Checker " , " Address: " ) ;
if ( address . isEmpty ( ) ) {
return ;
}
if ( ! WalletManager : : addressValid ( address , constants : : networkType ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Invalid address " , " The address you entered is not a valid XMR address for the current network type. " ) ;
2021-07-06 14:10:24 +00:00
return ;
}
2023-03-01 02:05:56 +00:00
SubaddressIndex index = m_wallet - > subaddressIndex ( address ) ;
2021-07-06 14:10:24 +00:00
if ( ! index . isValid ( ) ) {
// TODO: probably mention lookahead here
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " This address does not belong to this wallet " , " " ) ;
2021-07-06 14:10:24 +00:00
return ;
} else {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , QString ( " This address belongs to Account #%1 " ) . arg ( index . major ) ) ;
2021-07-06 14:10:24 +00:00
}
}
2023-12-02 18:28:31 +00:00
void MainWindow : : showURDialog ( ) {
2023-12-30 19:29:17 +00:00
# ifdef WITH_SCANNER
2023-12-02 18:28:31 +00:00
URDialog dialog { this } ;
dialog . exec ( ) ;
2023-12-30 19:29:17 +00:00
# else
Utils : : showError ( this , " Unable to open UR dialog " , " Feather was built without webcam scanner support " ) ;
# endif
2020-10-16 03:05:05 +00:00
}
void MainWindow : : loadSignedTx ( ) {
2023-06-17 23:12:35 +00:00
QString fn = QFileDialog : : getOpenFileName ( this , " Select transaction to load " , QDir : : homePath ( ) , " Transaction (*signed_monero_tx);;All Files (*) " ) ;
2020-10-16 03:05:05 +00:00
if ( fn . isEmpty ( ) ) return ;
2023-03-01 02:05:56 +00:00
PendingTransaction * tx = m_wallet - > loadSignedTxFile ( fn ) ;
auto err = m_wallet - > errorString ( ) ;
2020-10-16 03:05:05 +00:00
if ( ! err . isEmpty ( ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Unable to load signed transaction " , err ) ;
2020-10-16 03:05:05 +00:00
return ;
}
2023-12-02 18:28:31 +00:00
TxConfAdvDialog dialog { m_wallet , " " , this , true } ;
2021-05-18 15:59:18 +00:00
dialog . setTransaction ( tx ) ;
dialog . exec ( ) ;
2020-10-16 03:05:05 +00:00
}
void MainWindow : : loadSignedTxFromText ( ) {
2023-03-01 02:05:56 +00:00
TxBroadcastDialog dialog { this , m_nodes } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-10-16 03:05:05 +00:00
}
2020-11-10 11:38:37 +00:00
void MainWindow : : importTransaction ( ) {
2023-09-12 14:15:40 +00:00
if ( conf ( ) - > get ( Config : : torPrivacyLevel ) . toInt ( ) = = Config : : allTorExceptNode ) {
2021-05-04 23:09:19 +00:00
// TODO: don't show if connected to local node
auto result = QMessageBox : : warning ( this , " Warning " , " Using this feature may allow a remote node to associate the transaction with your IP address. \n "
" \n "
" Connect to a trusted node or run Feather over Tor if network level metadata leakage is included in your threat model. " ,
QMessageBox : : Ok | QMessageBox : : Cancel ) ;
if ( result ! = QMessageBox : : Ok ) {
return ;
}
2020-11-10 11:38:37 +00:00
}
2021-05-04 23:09:19 +00:00
2023-03-01 02:05:56 +00:00
TxImportDialog dialog ( this , m_wallet ) ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-11-10 11:38:37 +00:00
}
2021-05-02 18:22:38 +00:00
void MainWindow : : onDeviceError ( const QString & error ) {
2023-03-01 02:05:56 +00:00
qCritical ( ) < < " Device error: " < < error ;
2021-05-02 18:22:38 +00:00
if ( m_showDeviceError ) {
return ;
}
2021-07-01 21:00:47 +00:00
m_statusBtnHwDevice - > setIcon ( this - > hardwareDeviceUnpairedIcon ( ) ) ;
2021-05-02 18:22:38 +00:00
while ( true ) {
m_showDeviceError = true ;
auto result = QMessageBox : : question ( this , " Hardware device " , " Lost connection to hardware device. Attempt to reconnect? " ) ;
if ( result = = QMessageBox : : Yes ) {
2023-03-01 02:05:56 +00:00
bool r = m_wallet - > reconnectDevice ( ) ;
2021-05-02 18:22:38 +00:00
if ( r ) {
break ;
}
}
2021-05-18 15:59:18 +00:00
if ( result = = QMessageBox : : No ) {
this - > menuWalletCloseClicked ( ) ;
2021-05-02 18:22:38 +00:00
return ;
}
}
2021-07-01 21:00:47 +00:00
m_statusBtnHwDevice - > setIcon ( this - > hardwareDevicePairedIcon ( ) ) ;
2023-03-01 02:05:56 +00:00
m_wallet - > startRefresh ( ) ;
2021-05-02 18:22:38 +00:00
m_showDeviceError = false ;
}
2021-07-01 21:00:47 +00:00
void MainWindow : : onDeviceButtonRequest ( quint64 code ) {
2021-07-09 21:48:43 +00:00
qDebug ( ) < < " DeviceButtonRequest, code: " < < code ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isTrezor ( ) ) {
2021-07-01 21:00:47 +00:00
switch ( code ) {
2021-07-09 21:48:43 +00:00
case 1 :
{
m_splashDialog - > setMessage ( " Action required on device: Enter your PIN to continue " ) ;
m_splashDialog - > setIcon ( QPixmap ( " :/assets/images/key.png " ) ) ;
m_splashDialog - > show ( ) ;
m_splashDialog - > setEnabled ( true ) ;
break ;
}
2021-07-02 16:06:19 +00:00
case 8 :
2021-07-09 21:48:43 +00:00
default :
2021-07-01 21:00:47 +00:00
{
2021-07-02 16:06:19 +00:00
// Annoyingly, this code is used for a variety of actions, including:
// Confirm refresh: Do you really want to start refresh?
// Confirm export: Do you really want to export tx_key?
if ( m_constructingTransaction ) { // This code is also used when signing a tx, we handle this elsewhere
2021-07-01 21:00:47 +00:00
break ;
}
2021-07-02 16:06:19 +00:00
m_splashDialog - > setMessage ( " Confirm action on device to proceed " ) ;
2023-03-29 09:26:10 +00:00
m_splashDialog - > setIcon ( QPixmap ( " :/assets/images/confirmed.svg " ) ) ;
2021-07-01 21:00:47 +00:00
m_splashDialog - > show ( ) ;
m_splashDialog - > setEnabled ( true ) ;
break ;
}
}
}
}
void MainWindow : : onDeviceButtonPressed ( ) {
if ( m_constructingTransaction ) {
return ;
}
m_splashDialog - > hide ( ) ;
}
2021-07-08 00:34:27 +00:00
void MainWindow : : onWalletPassphraseNeeded ( bool on_device ) {
auto button = QMessageBox : : question ( nullptr , " Wallet Passphrase Needed " , " Enter passphrase on hardware wallet? \n \n "
" It is recommended to enter passphrase on "
" the hardware wallet for better security. " ,
QMessageBox : : Yes | QMessageBox : : No , QMessageBox : : Yes ) ;
if ( button = = QMessageBox : : Yes ) {
2023-03-01 02:05:56 +00:00
m_wallet - > onPassphraseEntered ( " " , true , false ) ;
2021-07-08 00:34:27 +00:00
return ;
}
bool ok ;
QString passphrase = QInputDialog : : getText ( nullptr , " Wallet Passphrase Needed " , " Enter passphrase: " , QLineEdit : : EchoMode : : Password , " " , & ok ) ;
if ( ok ) {
2023-03-01 02:05:56 +00:00
m_wallet - > onPassphraseEntered ( passphrase , false , false ) ;
2021-07-08 00:34:27 +00:00
} else {
2023-03-01 02:05:56 +00:00
m_wallet - > onPassphraseEntered ( passphrase , false , true ) ;
2021-07-08 00:34:27 +00:00
}
}
2020-11-23 16:57:38 +00:00
void MainWindow : : updateNetStats ( ) {
2023-03-01 02:05:56 +00:00
if ( ! m_wallet | | m_wallet - > connectionStatus ( ) = = Wallet : : ConnectionStatus_Disconnected
| | m_wallet - > connectionStatus ( ) = = Wallet : : ConnectionStatus_Synchronized )
2021-07-06 19:29:12 +00:00
{
m_statusLabelNetStats - > hide ( ) ;
2020-11-23 16:57:38 +00:00
return ;
}
2021-05-02 18:22:38 +00:00
2021-07-06 19:29:12 +00:00
m_statusLabelNetStats - > show ( ) ;
2023-03-01 02:05:56 +00:00
m_statusLabelNetStats - > setText ( QString ( " (D: %1) " ) . arg ( Utils : : formatBytes ( m_wallet - > getBytesReceived ( ) ) ) ) ;
2020-11-23 16:57:38 +00:00
}
2020-12-14 19:44:37 +00:00
void MainWindow : : rescanSpent ( ) {
2023-12-02 18:28:31 +00:00
QMessageBox warning { this } ;
warning . setWindowTitle ( " Warning " ) ;
warning . setText ( " Rescanning spent outputs reveals which outputs you own to the node. "
" Make sure you are connected to a trusted node. \n \n "
" Do you want to proceed? " ) ;
warning . setStandardButtons ( QMessageBox : : Yes | QMessageBox : : No ) ;
auto r = warning . exec ( ) ;
if ( r = = QMessageBox : : No ) {
return ;
}
2023-03-01 02:05:56 +00:00
if ( ! m_wallet - > rescanSpent ( ) ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Failed to rescan spent outputs " , m_wallet - > errorString ( ) ) ;
2020-12-14 19:44:37 +00:00
} else {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Successfully rescanned spent outputs " ) ;
2020-12-14 19:44:37 +00:00
}
}
2020-12-25 14:20:39 +00:00
void MainWindow : : showBalanceDialog ( ) {
2023-03-01 02:05:56 +00:00
BalanceDialog dialog { this , m_wallet } ;
2021-05-18 15:59:18 +00:00
dialog . exec ( ) ;
2020-12-25 14:20:39 +00:00
}
2020-12-24 14:46:56 +00:00
QString MainWindow : : statusDots ( ) {
m_statusDots + + ;
m_statusDots = m_statusDots % 4 ;
return QString ( " . " ) . repeated ( m_statusDots ) ;
}
2021-05-18 15:59:18 +00:00
void MainWindow : : showOrHide ( ) {
if ( this - > isHidden ( ) )
this - > bringToFront ( ) ;
else
this - > hide ( ) ;
}
2021-03-08 20:03:20 +00:00
void MainWindow : : bringToFront ( ) {
ensurePolished ( ) ;
setWindowState ( ( windowState ( ) & ~ Qt : : WindowMinimized ) | Qt : : WindowActive ) ;
show ( ) ;
raise ( ) ;
activateWindow ( ) ;
}
2023-02-11 17:11:21 +00:00
void MainWindow : : onPreferredFiatCurrencyChanged ( ) {
m_sendWidget - > onPreferredFiatCurrencyChanged ( ) ;
}
void MainWindow : : onHideUpdateNotifications ( bool hidden ) {
if ( hidden ) {
m_statusUpdateAvailable - > hide ( ) ;
}
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
2023-02-11 17:11:21 +00:00
else if ( m_updater - > state = = Updater : : State : : UPDATE_AVAILABLE ) {
m_statusUpdateAvailable - > show ( ) ;
}
2023-04-19 15:41:46 +00:00
# endif
2023-02-11 17:11:21 +00:00
}
2021-05-18 15:59:18 +00:00
void MainWindow : : onTorConnectionStateChanged ( bool connected ) {
2023-09-12 14:15:40 +00:00
if ( conf ( ) - > get ( Config : : proxy ) . toInt ( ) ! = Config : : Proxy : : Tor ) {
2023-02-11 17:11:21 +00:00
return ;
}
2021-05-18 15:59:18 +00:00
if ( connected )
2023-02-11 17:11:21 +00:00
m_statusBtnProxySettings - > setIcon ( icons ( ) - > icon ( " tor_logo.png " ) ) ;
2021-05-18 15:59:18 +00:00
else
2023-02-11 17:11:21 +00:00
m_statusBtnProxySettings - > setIcon ( icons ( ) - > icon ( " tor_logo_disabled.png " ) ) ;
2021-05-02 18:22:38 +00:00
}
2023-02-01 14:40:12 +00:00
void MainWindow : : showUpdateNotification ( ) {
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
2023-09-12 14:15:40 +00:00
if ( conf ( ) - > get ( Config : : hideUpdateNotifications ) . toBool ( ) ) {
2023-02-11 17:11:21 +00:00
return ;
}
2023-02-01 14:40:12 +00:00
QString versionDisplay { m_updater - > version } ;
2021-05-02 18:22:38 +00:00
versionDisplay . replace ( " beta " , " Beta " ) ;
QString updateText = QString ( " Update to Feather %1 is available " ) . arg ( versionDisplay ) ;
m_statusUpdateAvailable - > setText ( updateText ) ;
2021-05-04 23:09:19 +00:00
m_statusUpdateAvailable - > setToolTip ( " Click to Download update. " ) ;
2021-05-02 18:22:38 +00:00
m_statusUpdateAvailable - > show ( ) ;
2021-05-04 23:09:19 +00:00
m_statusUpdateAvailable - > disconnect ( ) ;
2023-02-01 14:40:12 +00:00
connect ( m_statusUpdateAvailable , & StatusBarButton : : clicked , this , & MainWindow : : showUpdateDialog ) ;
2023-04-19 15:41:46 +00:00
# endif
2021-05-02 18:22:38 +00:00
}
2023-02-01 14:40:12 +00:00
void MainWindow : : showUpdateDialog ( ) {
2023-04-19 15:41:46 +00:00
# ifdef CHECK_UPDATES
2023-02-01 14:40:12 +00:00
UpdateDialog updateDialog { this , m_updater } ;
2021-05-18 15:59:18 +00:00
connect ( & updateDialog , & UpdateDialog : : restartWallet , m_windowManager , & WindowManager : : restartApplication ) ;
2021-05-02 18:22:38 +00:00
updateDialog . exec ( ) ;
2023-04-19 15:41:46 +00:00
# endif
2021-05-02 18:22:38 +00:00
}
2021-03-24 01:37:54 +00:00
2021-05-02 18:22:38 +00:00
void MainWindow : : onInitiateTransaction ( ) {
m_statusDots = 0 ;
m_constructingTransaction = true ;
m_txTimer . start ( 1000 ) ;
2021-05-04 23:09:19 +00:00
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isHwBacked ( ) ) {
2021-05-04 23:09:19 +00:00
QString message = " Constructing transaction: action may be required on device. " ;
m_splashDialog - > setMessage ( message ) ;
m_splashDialog - > setIcon ( QPixmap ( " :/assets/images/unconfirmed.png " ) ) ;
m_splashDialog - > show ( ) ;
m_splashDialog - > setEnabled ( true ) ;
}
2021-05-02 18:22:38 +00:00
}
2022-03-12 12:53:46 +00:00
void MainWindow : : onKeysCorrupted ( ) {
if ( ! m_criticalWarningShown ) {
m_criticalWarningShown = true ;
2024-03-08 16:35:57 +00:00
Utils : : showError ( this , " Potential wallet file corruption detected " ,
" WARNING! \n \n "
" To prevent LOSS OF FUNDS do NOT continue to use this wallet file. \n \n "
" Restore your wallet from seed, keys, or device. \n \n "
" Please report this incident to the Feather developers. \n \n "
" WARNING! " , { } , " report_an_issue " ) ;
2023-12-15 13:10:55 +00:00
m_sendWidget - > disallowSending ( ) ;
2022-03-12 12:53:46 +00:00
}
}
2022-03-20 22:30:48 +00:00
void MainWindow : : onSelectedInputsChanged ( const QStringList & selectedInputs ) {
int numInputs = selectedInputs . size ( ) ;
ui - > frame_coinControl - > setStyleSheet ( ColorScheme : : GREEN . asStylesheet ( true ) ) ;
ui - > frame_coinControl - > setVisible ( numInputs > 0 ) ;
if ( numInputs > 0 ) {
quint64 totalAmount = 0 ;
2023-03-01 02:05:56 +00:00
auto coins = m_wallet - > coins ( ) - > coinsFromKeyImage ( selectedInputs ) ;
2022-03-20 22:30:48 +00:00
for ( const auto coin : coins ) {
totalAmount + = coin - > amount ( ) ;
}
QString text = QString ( " Coin control active: %1 selected outputs, %2 XMR " ) . arg ( QString : : number ( numInputs ) , WalletManager : : displayAmount ( totalAmount ) ) ;
ui - > label_coinControl - > setText ( text ) ;
}
}
2023-03-29 09:00:41 +00:00
void MainWindow : : onExportHistoryCSV ( ) {
2021-05-02 18:22:38 +00:00
QString fn = QFileDialog : : getSaveFileName ( this , " Save CSV file " , QDir : : homePath ( ) , " CSV (*.csv) " ) ;
if ( fn . isEmpty ( ) )
return ;
if ( ! fn . endsWith ( " .csv " ) )
fn + = " .csv " ;
2023-03-01 02:05:56 +00:00
m_wallet - > history ( ) - > writeCSV ( fn ) ;
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " CSV export " , QString ( " Transaction history exported to %1 " ) . arg ( fn ) ) ;
2021-05-02 18:22:38 +00:00
}
2023-03-29 09:00:41 +00:00
void MainWindow : : onExportContactsCSV ( ) {
2023-03-01 02:05:56 +00:00
auto * model = m_wallet - > addressBookModel ( ) ;
2021-05-02 18:22:38 +00:00
if ( model - > rowCount ( ) < = 0 ) {
2023-09-12 14:15:40 +00:00
Utils : : showInfo ( this , " Unable to export contacts " , " No contacts to export " ) ;
2021-05-02 18:22:38 +00:00
return ;
}
const QString targetDir = QFileDialog : : getExistingDirectory ( this , " Select CSV output directory " , QDir : : homePath ( ) , QFileDialog : : ShowDirsOnly ) ;
if ( targetDir . isEmpty ( ) ) return ;
2023-03-29 09:00:41 +00:00
qint64 now = QDateTime : : currentMSecsSinceEpoch ( ) ;
2021-05-02 18:22:38 +00:00
QString fn = QString ( " %1/monero-contacts_%2.csv " ) . arg ( targetDir , QString : : number ( now / 1000 ) ) ;
2023-09-12 14:15:40 +00:00
if ( model - > writeCSV ( fn ) ) {
Utils : : showInfo ( this , " Contacts exported successfully " , QString ( " Exported to: %1 " ) . arg ( fn ) ) ;
}
2021-05-02 18:22:38 +00:00
}
2023-03-29 09:00:41 +00:00
void MainWindow : : onCreateDesktopEntry ( ) {
2021-05-02 18:22:38 +00:00
auto msg = Utils : : xdgDesktopEntryRegister ( ) ? " Desktop entry created " : " Desktop entry not created due to an error. " ;
QMessageBox : : information ( this , " Desktop entry " , msg ) ;
}
2023-01-09 02:17:25 +00:00
void MainWindow : : onShowDocumentation ( ) {
2023-09-12 14:15:40 +00:00
// TODO: welcome page
m_windowManager - > showDocs ( this ) ;
2021-10-21 21:13:25 +00:00
}
2023-03-29 09:00:41 +00:00
void MainWindow : : onReportBug ( ) {
2023-09-12 14:15:40 +00:00
m_windowManager - > showDocs ( this , " report_an_issue " ) ;
2021-05-02 18:22:38 +00:00
}
QString MainWindow : : getHardwareDevice ( ) {
2023-03-01 02:05:56 +00:00
if ( ! m_wallet - > isHwBacked ( ) )
2021-05-02 18:22:38 +00:00
return " " ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isTrezor ( ) )
2021-05-02 18:22:38 +00:00
return " Trezor " ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > isLedger ( ) )
2021-05-02 18:22:38 +00:00
return " Ledger " ;
return " Unknown " ;
}
2021-05-23 13:58:28 +00:00
void MainWindow : : updateTitle ( ) {
2023-03-01 02:05:56 +00:00
QString title = QString ( " %1 (#%2) " ) . arg ( this - > walletName ( ) , QString : : number ( m_wallet - > currentSubaddressAccount ( ) ) ) ;
2021-05-23 13:58:28 +00:00
2023-11-30 14:01:39 +00:00
if ( m_wallet - > viewOnly ( ) ) {
2021-05-02 18:22:38 +00:00
title + = " [view-only] " ;
2023-11-30 14:01:39 +00:00
}
2021-05-02 18:22:38 +00:00
2021-05-23 13:58:28 +00:00
title + = " - Feather " ;
2021-05-02 18:22:38 +00:00
this - > setWindowTitle ( title ) ;
2021-03-24 01:37:54 +00:00
}
2021-05-18 15:59:18 +00:00
void MainWindow : : donationNag ( ) {
2023-03-01 02:05:56 +00:00
if ( m_wallet - > nettype ( ) ! = NetworkType : : Type : : MAINNET )
2021-05-18 15:59:18 +00:00
return ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > viewOnly ( ) )
2021-05-18 15:59:18 +00:00
return ;
2023-03-01 02:05:56 +00:00
if ( m_wallet - > balanceAll ( ) = = 0 )
2022-06-23 10:41:16 +00:00
return ;
2023-09-12 14:15:40 +00:00
auto donationCounter = conf ( ) - > get ( Config : : donateBeg ) . toInt ( ) ;
2021-05-18 15:59:18 +00:00
if ( donationCounter = = - 1 )
return ;
donationCounter + + ;
if ( donationCounter % constants : : donationBoundary = = 0 ) {
auto msg = " Feather is a 100% community-sponsored endeavor. Please consider supporting "
" the project financially. Get rid of this message by donating any amount. " ;
int ret = QMessageBox : : information ( this , " Donate to Feather " , msg , QMessageBox : : Yes , QMessageBox : : No ) ;
if ( ret = = QMessageBox : : Yes ) {
this - > donateButtonClicked ( ) ;
}
}
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : donateBeg , donationCounter ) ;
2021-05-18 15:59:18 +00:00
}
2023-01-26 15:05:54 +00:00
void MainWindow : : addToRecentlyOpened ( QString keysFile ) {
2023-09-12 14:15:40 +00:00
auto recent = conf ( ) - > get ( Config : : recentlyOpenedWallets ) . toList ( ) ;
2021-05-18 15:59:18 +00:00
2023-01-26 15:05:54 +00:00
if ( Utils : : isPortableMode ( ) ) {
QDir appPath { Utils : : applicationPath ( ) } ;
keysFile = appPath . relativeFilePath ( keysFile ) ;
}
2021-05-18 15:59:18 +00:00
if ( recent . contains ( keysFile ) ) {
recent . removeOne ( keysFile ) ;
}
recent . insert ( 0 , keysFile ) ;
QList < QVariant > recent_ ;
int count = 0 ;
for ( const auto & file : recent ) {
if ( Utils : : fileExists ( file . toString ( ) ) ) {
recent_ . append ( file ) ;
count + + ;
}
if ( count > = 5 ) {
break ;
}
}
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : recentlyOpenedWallets , recent_ ) ;
2021-07-08 12:03:54 +00:00
this - > updateRecentlyOpenedMenu ( ) ;
}
void MainWindow : : updateRecentlyOpenedMenu ( ) {
2021-05-18 15:59:18 +00:00
ui - > menuRecently_open - > clear ( ) ;
2023-09-12 14:15:40 +00:00
const QStringList recentWallets = conf ( ) - > get ( Config : : recentlyOpenedWallets ) . toStringList ( ) ;
2021-07-08 12:03:54 +00:00
for ( const auto & walletPath : recentWallets ) {
QFileInfo fileInfo { walletPath } ;
2023-01-26 15:05:54 +00:00
ui - > menuRecently_open - > addAction ( fileInfo . fileName ( ) , m_windowManager , std : : bind ( & WindowManager : : tryOpenWallet , m_windowManager , fileInfo . absoluteFilePath ( ) , " " ) ) ;
2021-05-18 15:59:18 +00:00
}
2021-07-08 12:03:54 +00:00
ui - > menuRecently_open - > addSeparator ( ) ;
ui - > menuRecently_open - > addAction ( m_clearRecentlyOpenAction ) ;
2021-05-18 15:59:18 +00:00
}
2022-07-03 17:41:08 +00:00
bool MainWindow : : verifyPassword ( bool sensitive ) {
2022-05-27 09:49:32 +00:00
bool incorrectPassword = false ;
2021-06-04 18:48:44 +00:00
while ( true ) {
2022-07-03 17:41:08 +00:00
PasswordDialog passwordDialog { this - > walletName ( ) , incorrectPassword , sensitive , this } ;
2022-03-04 16:20:17 +00:00
int ret = passwordDialog . exec ( ) ;
if ( ret = = QDialog : : Rejected ) {
2021-06-04 18:48:44 +00:00
return false ;
}
2023-01-31 16:00:50 +00:00
2023-03-01 02:05:56 +00:00
if ( ! m_wallet - > verifyPassword ( passwordDialog . password ) ) {
2022-05-27 09:49:32 +00:00
incorrectPassword = true ;
2021-06-04 18:48:44 +00:00
continue ;
}
break ;
}
return true ;
}
2022-03-04 16:20:17 +00:00
void MainWindow : : userActivity ( ) {
m_userLastActive = QDateTime : : currentSecsSinceEpoch ( ) ;
}
2023-01-19 14:12:16 +00:00
void MainWindow : : closeQDialogChildren ( QObject * object ) {
for ( QObject * child : object - > children ( ) ) {
if ( auto * childDlg = dynamic_cast < QDialog * > ( child ) ) {
qDebug ( ) < < " Closing dialog: " < < childDlg - > objectName ( ) ;
childDlg - > close ( ) ;
}
this - > closeQDialogChildren ( child ) ;
}
}
2022-03-04 16:20:17 +00:00
void MainWindow : : checkUserActivity ( ) {
2023-09-12 14:15:40 +00:00
if ( ! conf ( ) - > get ( Config : : inactivityLockEnabled ) . toBool ( ) ) {
2022-03-04 16:20:17 +00:00
return ;
}
if ( m_constructingTransaction ) {
return ;
}
2023-09-12 14:15:40 +00:00
if ( ( m_userLastActive + ( conf ( ) - > get ( Config : : inactivityLockTimeout ) . toInt ( ) * 60 ) ) < QDateTime : : currentSecsSinceEpoch ( ) ) {
2022-03-04 16:20:17 +00:00
qInfo ( ) < < " Locking wallet for inactivity " ;
2023-01-19 14:12:16 +00:00
this - > lockWallet ( ) ;
}
}
void MainWindow : : lockWallet ( ) {
if ( m_locked ) {
return ;
2022-03-04 16:20:17 +00:00
}
2023-01-19 14:12:16 +00:00
if ( m_constructingTransaction ) {
2023-09-12 14:15:40 +00:00
Utils : : showError ( this , " Unable to lock wallet " , " Can't lock wallet during transaction construction " ) ;
2023-01-19 14:12:16 +00:00
return ;
}
m_walletUnlockWidget - > reset ( ) ;
// Close all open QDialogs
this - > closeQDialogChildren ( this ) ;
ui - > tabWidget - > hide ( ) ;
this - > statusBar ( ) - > hide ( ) ;
this - > menuBar ( ) - > hide ( ) ;
ui - > stackedWidget - > setCurrentIndex ( 1 ) ;
m_checkUserActivity . stop ( ) ;
m_locked = true ;
}
void MainWindow : : unlockWallet ( const QString & password ) {
if ( ! m_locked ) {
return ;
}
2023-03-01 02:05:56 +00:00
if ( ! m_wallet - > verifyPassword ( password ) ) {
2023-01-19 14:12:16 +00:00
m_walletUnlockWidget - > incorrectPassword ( ) ;
return ;
}
m_walletUnlockWidget - > reset ( ) ;
ui - > tabWidget - > show ( ) ;
this - > statusBar ( ) - > show ( ) ;
this - > menuBar ( ) - > show ( ) ;
ui - > stackedWidget - > setCurrentIndex ( 0 ) ;
2023-12-02 18:28:31 +00:00
this - > onOfflineMode ( conf ( ) - > get ( Config : : offlineMode ) . toBool ( ) ) ;
2023-01-19 14:12:16 +00:00
m_checkUserActivity . start ( ) ;
m_locked = false ;
2022-03-04 16:20:17 +00:00
}
2021-05-23 14:58:18 +00:00
void MainWindow : : toggleSearchbar ( bool visible ) {
2023-09-12 14:15:40 +00:00
conf ( ) - > set ( Config : : showSearchbar , visible ) ;
2021-05-23 14:58:18 +00:00
m_historyWidget - > setSearchbarVisible ( visible ) ;
m_receiveWidget - > setSearchbarVisible ( visible ) ;
m_contactsWidget - > setSearchbarVisible ( visible ) ;
2021-06-28 17:20:16 +00:00
m_coinsWidget - > setSearchbarVisible ( visible ) ;
2021-05-23 14:58:18 +00:00
int currentTab = ui - > tabWidget - > currentIndex ( ) ;
2023-11-30 14:01:39 +00:00
if ( currentTab = = this - > findTab ( " History " ) )
2021-05-23 14:58:18 +00:00
m_historyWidget - > focusSearchbar ( ) ;
2023-11-30 14:01:39 +00:00
else if ( currentTab = = this - > findTab ( " Send " ) )
2021-05-23 14:58:18 +00:00
m_contactsWidget - > focusSearchbar ( ) ;
2023-11-30 14:01:39 +00:00
else if ( currentTab = = this - > findTab ( " Receive " ) )
2021-05-23 14:58:18 +00:00
m_receiveWidget - > focusSearchbar ( ) ;
2023-11-30 14:01:39 +00:00
else if ( currentTab = = this - > findTab ( " Coins " ) )
2021-07-02 14:51:46 +00:00
m_coinsWidget - > focusSearchbar ( ) ;
2021-05-23 14:58:18 +00:00
}
2023-11-30 14:01:39 +00:00
int MainWindow : : findTab ( const QString & title ) {
for ( int i = 0 ; i < ui - > tabWidget - > count ( ) ; i + + ) {
if ( ui - > tabWidget - > tabText ( i ) = = title ) {
return i ;
}
}
return - 1 ;
}
2023-03-01 02:05:56 +00:00
MainWindow : : ~ MainWindow ( ) {
qDebug ( ) < < " ~MainWindow " ;
2023-03-29 09:00:41 +00:00
}