feather/src/MainWindow.cpp

1749 lines
66 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: BSD-3-Clause
2023-01-02 19:30:11 +00:00
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
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>
2021-06-28 17:20:16 +00:00
2021-05-02 18:22:38 +00:00
#include "config-feather.h"
2021-06-28 17:48:23 +00:00
#include "constants.h"
#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 11:46:32 +00:00
#include "dialog/TorInfoDialog.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"
2021-05-02 18:22:38 +00:00
#include "dialog/UpdateDialog.h"
#include "libwalletqt/AddressBook.h"
2022-03-20 22:30:48 +00:00
#include "libwalletqt/CoinsInfo.h"
2022-03-12 13:54:08 +00:00
#include "libwalletqt/Transfer.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/NetworkManager.h"
2021-05-25 13:06:38 +00:00
#include "utils/os/tails.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/Updater.h"
#include "utils/WebsocketNotifier.h"
2023-02-11 17:11:21 +00:00
//#include "misc_log_ex.h"
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)
, m_wallet(wallet)
, m_nodes(new Nodes(this, wallet))
, m_rpc(new DaemonRpc(this, ""))
{
ui->setupUi(this);
2023-02-11 17:11:21 +00:00
// MCWARNING("feather", "Platform tag: " << this->getPlatformTag().toStdString());
2022-12-26 15:25:16 +00:00
2021-05-18 15:59:18 +00:00
// Ensure the destructor is called after closeEvent()
setAttribute(Qt::WA_DeleteOnClose);
m_windowCalc = new CalcWindow(this);
2021-05-02 18:22:38 +00:00
m_splashDialog = new SplashDialog(this);
m_accountSwitcherDialog = new AccountSwitcherDialog(m_wallet, this);
2023-02-01 14:40:12 +00:00
m_updater = QSharedPointer<Updater>(new Updater(this));
this->restoreGeo();
2021-05-02 18:22:38 +00:00
this->initStatusBar();
this->initWidgets();
this->initMenu();
this->initHome();
this->initWalletContext();
// Websocket notifier
connect(websocketNotifier(), &WebsocketNotifier::CCSReceived, ui->ccsWidget->model(), &CCSModel::updateEntries);
2022-06-23 17:48:30 +00:00
connect(websocketNotifier(), &WebsocketNotifier::BountyReceived, ui->bountiesWidget->model(), &BountiesModel::updateBounties);
2021-05-02 18:22:38 +00:00
connect(websocketNotifier(), &WebsocketNotifier::RedditReceived, ui->redditWidget->model(), &RedditModel::updatePosts);
2022-05-25 20:40:56 +00:00
connect(websocketNotifier(), &WebsocketNotifier::RevuoReceived, ui->revuoWidget, &RevuoWidget::updateItems);
2023-02-01 14:40:12 +00:00
connect(websocketNotifier(), &WebsocketNotifier::UpdatesReceived, m_updater.data(), &Updater::wsUpdatesReceived);
2021-05-02 18:22:38 +00:00
#ifdef HAS_XMRIG
connect(websocketNotifier(), &WebsocketNotifier::XMRigDownloadsReceived, m_xmrig, &XMRigWidget::onDownloads);
#endif
2021-05-18 15:59:18 +00:00
websocketNotifier()->emitCache(); // Get cached data
connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
this->onWebsocketStatusChanged(!config()->get(Config::disableWebsocket).toBool());
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);
2023-02-01 14:40:12 +00:00
connect(m_updater.data(), &Updater::updateAvailable, this, &MainWindow::showUpdateNotification);
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();});
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());
});
2021-05-26 22:20:48 +00:00
config()->set(Config::firstRun, false);
2021-05-18 15:59:18 +00:00
this->onWalletOpened();
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
}
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
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);
2021-05-02 18:22:38 +00:00
m_statusLabelNetStats = new QLabel("", this);
m_statusLabelNetStats->setTextInteractionFlags(Qt::TextSelectableByMouse);
this->statusBar()->addWidget(m_statusLabelNetStats);
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);
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);
2021-05-02 18:22:38 +00:00
m_statusBtnConnectionStatusIndicator = new StatusBarButton(icons()->icon("status_disconnected.svg"), "Connection status", this);
connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, [this](){
2023-02-11 17:24:18 +00:00
this->onShowSettingsPage(Settings::Pages::NETWORK);
});
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);
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);
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();
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();
}
2021-05-02 18:22:38 +00:00
void MainWindow::initWidgets() {
int homeWidget = config()->get(Config::homeWidget).toInt();
ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget));
2021-05-02 18:22:38 +00:00
// [History]
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]
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
// --------------
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]
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);
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
});
2021-05-18 15:59:18 +00:00
connect(m_contactsWidget, &ContactsWidget::fillAddress, m_sendWidget, &SendWidget::fillAddress);
2021-05-10 15:05:10 +00:00
// [Coins]
m_coinsWidget = new CoinsWidget(m_wallet, this);
2021-05-10 15:05:10 +00:00
ui->coinsWidgetLayout->addWidget(m_coinsWidget);
2021-05-02 18:22:38 +00:00
#ifdef HAS_LOCALMONERO
m_localMoneroWidget = new LocalMoneroWidget(this, m_wallet);
2021-05-02 18:22:38 +00:00
ui->localMoneroLayout->addWidget(m_localMoneroWidget);
#else
ui->tabWidgetExchanges->setTabVisible(0, false);
#endif
2021-05-02 18:22:38 +00:00
#ifdef HAS_XMRIG
m_xmrig = new XMRigWidget(m_wallet, this);
2021-05-02 18:22:38 +00:00
ui->xmrRigLayout->addWidget(m_xmrig);
2021-05-23 13:58:28 +00:00
connect(m_xmrig, &XMRigWidget::miningStarted, [this]{ this->updateTitle(); });
connect(m_xmrig, &XMRigWidget::miningEnded, [this]{ this->updateTitle(); });
2021-05-02 18:22:38 +00:00
#else
ui->tabWidget->setTabVisible(Tabs::XMRIG, false);
#endif
#if defined(Q_OS_MACOS)
ui->line->hide();
#endif
2022-03-20 22:30:48 +00:00
ui->frame_coinControl->setVisible(false);
connect(ui->btn_resetCoinControl, &QPushButton::clicked, [this]{
m_wallet->setSelectedInputs({});
2022-03-20 22:30:48 +00:00
});
2023-01-19 14:12:16 +00:00
m_walletUnlockWidget = new WalletUnlockWidget(this);
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);
ui->stackedWidget->setCurrentIndex(0);
}
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);
// [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]
connect(ui->actionStore_wallet, &QAction::triggered, this, &MainWindow::tryStoreWallet);
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);
2021-05-02 18:22:38 +00:00
// [Wallet] -> [Advanced] -> [Export]
connect(ui->actionExportOutputs, &QAction::triggered, this, &MainWindow::exportOutputs);
connect(ui->actionExportKeyImages, &QAction::triggered, this, &MainWindow::exportKeyImages);
2021-05-02 18:22:38 +00:00
// [Wallet] -> [Advanced] -> [Import]
connect(ui->actionImportOutputs, &QAction::triggered, this, &MainWindow::importOutputs);
connect(ui->actionImportKeyImages, &QAction::triggered, this, &MainWindow::importKeyImages);
2021-03-24 01:37:54 +00:00
2021-05-02 18:22:38 +00:00
// [Wallet] -> [History]
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
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]
m_tabShowHideSignalMapper = new QSignalMapper(this);
2021-05-23 14:58:18 +00:00
connect(ui->actionShow_Searchbar, &QAction::toggled, this, &MainWindow::toggleSearchbar);
ui->actionShow_Searchbar->setChecked(config()->get(Config::showSearchbar).toBool());
2021-05-02 18:22:38 +00:00
// Show/Hide Home
2020-12-30 04:45:00 +00:00
connect(ui->actionShow_Home, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Home"] = new ToggleTab(ui->tabHome, "Home", "Home", ui->actionShow_Home, Config::showTabHome);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Home, "Home");
2021-05-02 18:22:38 +00:00
// Show/Hide Coins
connect(ui->actionShow_Coins, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Coins"] = new ToggleTab(ui->tabCoins, "Coins", "Coins", ui->actionShow_Coins, Config::showTabCoins);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Coins, "Coins");
2021-05-02 18:22:38 +00:00
// Show/Hide Calc
connect(ui->actionShow_calc, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Calc"] = new ToggleTab(ui->tabCalc, "Calc", "Calc", ui->actionShow_calc, Config::showTabCalc);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_calc, "Calc");
2021-05-02 18:22:38 +00:00
// Show/Hide Exchange
#if defined(HAS_LOCALMONERO)
connect(ui->actionShow_Exchange, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
m_tabShowHideMapper["Exchange"] = new ToggleTab(ui->tabExchange, "Exchange", "Exchange", ui->actionShow_Exchange, Config::showTabExchange);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_Exchange, "Exchange");
#else
ui->actionShow_Exchange->setVisible(false);
2021-02-01 16:53:08 +00:00
ui->tabWidget->setTabVisible(Tabs::EXCHANGES, false);
2021-05-02 18:22:38 +00:00
#endif
2020-12-25 22:18:40 +00:00
2021-05-02 18:22:38 +00:00
// Show/Hide Mining
#if defined(HAS_XMRIG)
connect(ui->actionShow_XMRig, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
2020-12-12 14:22:31 +00:00
m_tabShowHideMapper["Mining"] = new ToggleTab(ui->tabXmrRig, "Mining", "Mining", ui->actionShow_XMRig, Config::showTabXMRig);
m_tabShowHideSignalMapper->setMapping(ui->actionShow_XMRig, "Mining");
#else
ui->actionShow_XMRig->setVisible(false);
#endif
for (const auto &key: m_tabShowHideMapper.keys()) {
const auto toggleTab = m_tabShowHideMapper.value(key);
const bool show = config()->get(toggleTab->configKey).toBool();
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);
connect(ui->actionLoadUnsignedTxFromFile, &QAction::triggered, this, &MainWindow::loadUnsignedTx);
connect(ui->actionLoadUnsignedTxFromClipboard, &QAction::triggered, this, &MainWindow::loadUnsignedTxFromClipboard);
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);
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->actionCalculator, &QAction::triggered, this, &MainWindow::showCalcWindow);
connect(ui->actionCreateDesktopEntry, &QAction::triggered, this, &MainWindow::onCreateDesktopEntry);
// TODO: Allow creating desktop entry on Windows and Mac
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
ui->actionCreateDesktopEntry->setDisabled(true);
#endif
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
#if defined(Q_OS_MACOS)
ui->actionCheckForUpdates->setVisible(false);
#endif
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);
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"));
ui->actionDocumentation->setShortcut(QKeySequence("F1"));
2021-05-02 18:22:38 +00:00
}
2021-05-02 18:22:38 +00:00
void MainWindow::initHome() {
// Ticker widgets
m_tickerWidgets.append(new PriceTickerWidget(this, m_wallet, "XMR"));
m_tickerWidgets.append(new PriceTickerWidget(this, m_wallet, "BTC"));
m_tickerWidgets.append(new RatioTickerWidget(this, m_wallet, "XMR", "BTC"));
2022-06-10 11:42:55 +00:00
for (const auto &widget : m_tickerWidgets) {
2021-05-24 23:10:45 +00:00
ui->tickerLayout->addWidget(widget);
2021-05-02 18:22:38 +00:00
}
2021-05-24 23:10:45 +00:00
m_balanceTickerWidget = new BalanceTickerWidget(this, m_wallet, false);
2021-05-24 23:10:45 +00:00
ui->fiatTickerLayout->addWidget(m_balanceTickerWidget);
2021-05-02 18:22:38 +00:00
connect(ui->ccsWidget, &CCSWidget::selected, this, &MainWindow::showSendScreen);
2022-06-23 17:48:30 +00:00
connect(ui->bountiesWidget, &BountiesWidget::donate, this, &MainWindow::fillSendTab);
2021-05-02 18:22:38 +00:00
connect(ui->redditWidget, &RedditWidget::setStatusText, this, &MainWindow::setStatusText);
2022-05-25 20:40:56 +00:00
connect(ui->revuoWidget, &RevuoWidget::donate, [this](const QString &address, const QString &description){
m_sendWidget->fill(address, description);
ui->tabWidget->setCurrentIndex(Tabs::SEND);
});
2021-05-02 18:22:38 +00:00
}
void MainWindow::initWalletContext() {
connect(m_wallet, &Wallet::balanceUpdated, this, &MainWindow::onBalanceUpdated);
connect(m_wallet, &Wallet::synchronized, this, &MainWindow::onSynchronized); //TODO
connect(m_wallet, &Wallet::blockchainSync, this, &MainWindow::onBlockchainSync);
connect(m_wallet, &Wallet::refreshSync, this, &MainWindow::onRefreshSync);
connect(m_wallet, &Wallet::createTransactionError, this, &MainWindow::onCreateTransactionError);
connect(m_wallet, &Wallet::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess);
connect(m_wallet, &Wallet::transactionCommitted, this, &MainWindow::onTransactionCommitted);
connect(m_wallet, &Wallet::initiateTransaction, this, &MainWindow::onInitiateTransaction);
connect(m_wallet, &Wallet::endTransaction, this, &MainWindow::onEndTransaction);
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
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);
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){
if (m_wallet->isSynchronized()) {
auto notify = QString("%1 XMR (pending)").arg(WalletManager::displayAmount(amount, false));
Utils::desktopNotify("Payment received", notify, 5000);
}
});
// 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, []{
config()->set(Config::donateBeg, -1);
});
connect(m_wallet, &Wallet::multiBroadcast, this, [this](PendingTransaction *tx){
quint64 count = tx->txCount();
for (quint64 i = 0; i < count; i++) {
QString txData = tx->signedTxToHex(i);
for (const auto& node: m_nodes->nodes()) {
QString address = node.toURL();
qDebug() << QString("Relaying %1 to: %2").arg(tx->txid()[i], address);
m_rpc->setDaemonAddress(address);
m_rpc->sendRawTransaction(txData);
}
}
2023-02-11 17:11:21 +00:00
});
2021-05-02 18:22:38 +00:00
}
void MainWindow::menuToggleTabVisible(const QString &key){
const auto toggleTab = m_tabShowHideMapper[key];
bool show = config()->get(toggleTab->configKey).toBool();
show = !show;
config()->set(toggleTab->configKey, show);
ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
}
void MainWindow::menuClearHistoryClicked() {
config()->remove(Config::recentlyOpenedWallets);
this->updateRecentlyOpenedMenu();
}
2021-05-18 15:59:18 +00:00
QString MainWindow::walletName() {
return QFileInfo(m_wallet->cachePath()).fileName();
}
2021-05-18 15:59:18 +00:00
QString MainWindow::walletCachePath() {
return m_wallet->cachePath();
}
2021-05-18 15:59:18 +00:00
QString MainWindow::walletKeysPath() {
return m_wallet->keysPath();
}
2021-05-02 18:22:38 +00:00
void MainWindow::displayWalletErrorMsg(const QString &err) {
QString errMsg = err;
if (err.contains("No device found")) {
errMsg += "\n\nThis wallet is backed by a hardware device. Make sure the Monero app is opened on the device.\n"
"You may need to restart Feather before the device can get detected.";
}
if (errMsg.contains("Unable to open device")) {
errMsg += "\n\nThe device might be in use by a different application.";
}
if (errMsg.contains("SW_CLIENT_NOT_SUPPORTED")) {
errMsg += "\n\nIncompatible version: you may need to upgrade the Monero app on the Ledger device to the latest version.";
}
else if (errMsg.contains("Wrong Device Status")) {
2021-05-04 23:09:19 +00:00
errMsg += "\n\nThe device may need to be unlocked.";
}
else if (errMsg.contains("Wrong Channel")) {
errMsg += "\n\nRestart the hardware device and try again.";
2021-05-02 18:22:38 +00:00
}
QMessageBox::warning(this, "Wallet error", errMsg);
}
void MainWindow::onWalletOpened() {
qDebug() << Q_FUNC_INFO;
2021-05-02 18:22:38 +00:00
m_splashDialog->hide();
m_wallet->setRingDatabase(Utils::ringDatabasePath());
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();
this->setEnabled(true);
// receive page
m_wallet->subaddress()->refresh(m_wallet->currentSubaddressAccount());
if (m_wallet->subaddress()->count() == 1) {
for (int i = 0; i < 10; i++) {
m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), "");
}
}
m_wallet->subaddressModel()->setCurrentSubaddressAccount(m_wallet->currentSubaddressAccount());
// history page
m_wallet->history()->refresh(m_wallet->currentSubaddressAccount());
// coins page
m_wallet->coins()->refresh(m_wallet->currentSubaddressAccount());
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
connect(m_wallet->coins(), &Coins::descriptionChanged, [this] {
m_wallet->history()->refresh(m_wallet->currentSubaddressAccount());
2021-07-02 14:12:07 +00:00
});
// Vice versa
connect(m_wallet->history(), &TransactionHistory::txNoteChanged, [this] {
m_wallet->coins()->refresh(m_wallet->currentSubaddressAccount());
2021-07-02 14:12:07 +00:00
});
2020-11-14 09:57:06 +00:00
this->updatePasswordIcon();
2021-05-23 13:58:28 +00:00
this->updateTitle();
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-02-11 17:11:21 +00:00
if (config()->get(Config::writeRecentlyOpenedWallets).toBool()) {
this->addToRecentlyOpened(m_wallet->cachePath());
2023-02-11 17:11:21 +00:00
}
}
2020-12-25 14:20:39 +00:00
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
2020-11-02 09:37:36 +00:00
bool hide = config()->get(Config::hideBalance).toBool();
2021-05-22 14:12:39 +00:00
int displaySetting = config()->get(Config::balanceDisplay).toInt();
int decimals = config()->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) {
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) {
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) {
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);
2021-05-24 23:10:45 +00:00
m_balanceTickerWidget->setHidden(hide);
}
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
}
void MainWindow::tryStoreWallet() {
if (m_wallet->connectionStatus() == Wallet::ConnectionStatus::ConnectionStatus_Synchronizing) {
QMessageBox::warning(this, "Save wallet", "Unable to save wallet during synchronization.\n\n"
"Wait until synchronization is finished and try again.");
return;
}
m_wallet->store();
}
void MainWindow::onWebsocketStatusChanged(bool enabled) {
ui->actionShow_Home->setVisible(enabled);
ui->actionShow_calc->setVisible(enabled);
ui->actionShow_Exchange->setVisible(enabled);
ui->tabWidget->setTabVisible(Tabs::HOME, enabled && config()->get(Config::showTabHome).toBool());
ui->tabWidget->setTabVisible(Tabs::CALC, enabled && config()->get(Config::showTabCalc).toBool());
ui->tabWidget->setTabVisible(Tabs::EXCHANGES, enabled && config()->get(Config::showTabExchange).toBool());
m_historyWidget->setWebsocketEnabled(enabled);
m_sendWidget->setWebsocketEnabled(enabled);
#ifdef HAS_XMRIG
m_xmrig->setDownloadsTabEnabled(enabled);
#endif
}
2023-02-11 17:11:21 +00:00
void MainWindow::onProxySettingsChanged() {
m_nodes->connectToNode();
2023-02-11 17:11:21 +00:00
int proxy = config()->get(Config::proxy).toInt();
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();
}
void MainWindow::onOfflineMode(bool offline) {
if (!m_wallet) {
return;
}
m_wallet->setOffline(offline);
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Disconnected);
}
void MainWindow::onSynchronized() {
this->updateNetStats();
2020-12-24 14:46:56 +00:00
this->setStatusText("Synchronized");
}
void MainWindow::onBlockchainSync(int height, int target) {
2020-12-23 01:42:22 +00:00
QString blocks = (target >= height) ? QString::number(target - height) : "?";
QString heightText = QString("Blockchain sync: %1 blocks remaining").arg(blocks);
2020-12-24 14:46:56 +00:00
this->setStatusText(heightText);
}
void MainWindow::onRefreshSync(int height, int target) {
2020-12-23 01:42:22 +00:00
QString blocks = (target >= height) ? QString::number(target - height) : "?";
2020-12-30 03:58:17 +00:00
QString heightText = QString("Wallet sync: %1 blocks remaining").arg(blocks);
2020-12-24 14:46:56 +00:00
this->setStatusText(heightText);
}
void MainWindow::onConnectionStatusChanged(int status)
{
qDebug() << "Wallet connection status changed " << Utils::QtEnumToString(static_cast<Wallet::ConnectionStatus>(status));
// Update connection info in status bar.
2021-05-02 18:22:38 +00:00
QIcon icon;
2023-02-11 17:11:21 +00:00
if (config()->get(Config::offlineMode).toBool()) {
icon = icons()->icon("status_offline.svg");
this->setStatusText("Offline");
} 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;
}
}
2021-05-02 18:22:38 +00:00
m_statusBtnConnectionStatusIndicator->setIcon(icon);
}
2021-01-26 23:55:27 +00:00
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
QString err{"Can't create transaction: "};
if (tx->status() != PendingTransaction::Status_Ok) {
QString tx_err = tx->errorString();
qCritical() << tx_err;
if (m_wallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
2022-02-11 11:50:09 +00:00
err = QString("%1 Wrong node version: %2").arg(err, tx_err);
else
2022-02-11 11:50:09 +00:00
err = QString("%1 %2").arg(err, tx_err);
if (tx_err.contains("Node response did not include the requested real output")) {
QString currentNode = m_nodes->connection().toAddress();
err += QString("\nYou 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 dev@featherwallet.org, #feather on OFTC or /r/FeatherWallet.").arg(currentNode);
}
qDebug() << Q_FUNC_INFO << err;
2021-05-04 23:09:19 +00:00
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx);
return;
}
else if (tx->txCount() == 0) {
2022-02-11 11:50:09 +00:00
err = QString("%1 %2").arg(err, "No unmixable outputs to sweep.");
qDebug() << Q_FUNC_INFO << err;
2021-05-04 23:09:19 +00:00
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx);
return;
}
2022-03-12 13:54:08 +00:00
else if (tx->txCount() > 1) {
err = QString("%1 %2").arg(err, "Split transactions are not supported. Try sending a smaller amount.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx);
2022-03-12 13:54:08 +00:00
return;
}
// 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()) {
outputAddresses.insert(WalletManager::baseAddressFromIntegratedAddress(output->address(), constants::networkType));
}
QSet<QString> destAddresses;
for (const auto &addr : address) {
// TODO: Monero core bug, integrated address is not added to dests for transactions spending ALL
destAddresses.insert(WalletManager::baseAddressFromIntegratedAddress(addr, constants::networkType));
}
if (!outputAddresses.contains(destAddresses)) {
err = QString("%1 %2").arg(err, "Constructed transaction doesn't appear to send to (all) specified destination address(es). Try creating the transaction again.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx);
return;
}
2021-01-26 23:55:27 +00:00
m_wallet->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0));
// Show advanced dialog on multi-destination transactions
if (address.size() > 1 || m_wallet->viewOnly()) {
TxConfAdvDialog dialog_adv{m_wallet, m_wallet->tmpTxDescription, this};
dialog_adv.setTransaction(tx, !m_wallet->viewOnly());
dialog_adv.exec();
return;
}
2020-10-16 03:05:05 +00:00
TxConfDialog dialog{m_wallet, tx, address[0], m_wallet->tmpTxDescription, this};
switch (dialog.exec()) {
case QDialog::Rejected:
{
if (!dialog.showAdvanced) {
m_wallet->disposeTransaction(tx);
}
break;
2020-10-16 03:05:05 +00:00
}
case QDialog::Accepted:
m_wallet->commitTransaction(tx, m_wallet->tmpTxDescription);
break;
}
if (dialog.showAdvanced) {
TxConfAdvDialog dialog_adv{m_wallet, m_wallet->tmpTxDescription, this};
dialog_adv.setTransaction(tx);
dialog_adv.exec();
}
}
void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
2021-03-08 20:03:20 +00:00
if (status) { // success
2021-07-03 15:48:07 +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());
2021-07-03 15:48:07 +00:00
msgBox.setText(body);
msgBox.setWindowTitle("Transaction sent");
msgBox.setIcon(QMessageBox::Icon::Information);
msgBox.exec();
if (msgBox.clickedButton() == showDetailsButton) {
this->showHistoryTab();
TransactionInfo *txInfo = m_wallet->history()->transaction(txid.first());
auto *dialog = new TxInfoDialog(m_wallet, txInfo, this);
2021-10-21 17:56:40 +00:00
connect(dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction);
dialog->show();
dialog->setAttribute(Qt::WA_DeleteOnClose);
2021-07-03 15:48:07 +00:00
}
2021-05-10 15:05:10 +00:00
m_sendWidget->clearFields();
} else {
auto err = tx->errorString();
QString body = QString("Error committing transaction: %1").arg(err);
QMessageBox::warning(this, "Transaction failed", body);
}
}
void MainWindow::onCreateTransactionError(const QString &message) {
auto msg = QString("Error while creating transaction: %1").arg(message);
if (msg.contains("failed to get random outs")) {
msg += "\n\nYour transaction has too many inputs. Try sending a lower amount.";
}
QMessageBox::warning(this, "Transaction failed", msg);
}
void MainWindow::showWalletInfoDialog() {
WalletInfoDialog dialog{m_wallet, this};
2021-05-18 15:59:18 +00:00
dialog.exec();
}
void MainWindow::showSeedDialog() {
if (m_wallet->isHwBacked()) {
2021-05-04 23:09:19 +00:00
QMessageBox::information(this, "Information", "Seed unavailable: Wallet keys are stored on hardware device.");
2021-05-02 18:22:38 +00:00
return;
}
if (m_wallet->viewOnly()) {
2021-03-14 21:12:02 +00:00
QMessageBox::information(this, "Information", "Wallet is view-only and has no seed.\n\nTo obtain wallet keys go to Wallet -> View-Only");
return;
}
if (!m_wallet->isDeterministic()) {
2021-03-14 21:12:02 +00:00
QMessageBox::information(this, "Information", "Wallet is non-deterministic and has no seed.\n\nTo obtain wallet keys go to Wallet -> Keys");
return;
}
if (!this->verifyPassword()) {
return;
}
SeedDialog dialog{m_wallet, this};
2021-05-18 15:59:18 +00:00
dialog.exec();
}
void MainWindow::showPasswordDialog() {
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() {
bool emptyPassword = m_wallet->verifyPassword("");
QIcon icon = emptyPassword ? icons()->icon("unlock.svg") : icons()->icon("lock.svg");
2020-11-14 09:57:06 +00:00
m_statusBtnPassword->setIcon(icon);
}
void MainWindow::showKeysDialog() {
if (!this->verifyPassword()) {
return;
}
KeysDialog dialog{m_wallet, this};
2021-05-18 15:59:18 +00:00
dialog.exec();
}
void MainWindow::showViewOnlyDialog() {
ViewOnlyDialog dialog{m_wallet, this};
2021-05-18 15:59:18 +00:00
dialog.exec();
}
2021-05-02 18:22:38 +00:00
void MainWindow::menuHwDeviceClicked() {
QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
}
2021-05-18 15:59:18 +00:00
void MainWindow::menuOpenClicked() {
m_windowManager->wizardOpenWallet();
}
void MainWindow::menuNewRestoreClicked() {
2021-05-18 15:59:18 +00:00
m_windowManager->showWizard(WalletWizard::Page_Menu);
}
void MainWindow::menuQuitClicked() {
2021-05-18 15:59:18 +00:00
this->close();
}
void MainWindow::menuWalletCloseClicked() {
2021-05-18 15:59:18 +00:00
m_windowManager->showWizard(WalletWizard::Page_Menu);
this->close();
}
2023-02-11 17:11:21 +00:00
void MainWindow::menuProxySettingsClicked() {
this->menuSettingsClicked(true);
}
void MainWindow::menuAboutClicked() {
2021-05-02 18:22:38 +00:00
AboutDialog dialog{this};
dialog.exec();
}
2023-02-11 17:11:21 +00:00
void MainWindow::menuSettingsClicked(bool showProxyTab) {
m_windowManager->showSettings(m_nodes, this, showProxyTab);
}
void MainWindow::menuSignVerifyClicked() {
SignVerifyDialog dialog{m_wallet, this};
2021-05-02 18:22:38 +00:00
dialog.exec();
}
void MainWindow::menuVerifyTxProof() {
VerifyProofDialog dialog{m_wallet, this};
2021-05-02 18:22:38 +00:00
dialog.exec();
}
2022-03-11 13:56:07 +00:00
void MainWindow::onShowSettingsPage(int page) {
config()->set(Config::lastSettingsPage, page);
this->menuSettingsClicked();
}
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-06-25 14:14:49 +00:00
void MainWindow::updateWidgetIcons() {
m_sendWidget->skinChanged();
2021-05-02 18:22:38 +00:00
#ifdef HAS_LOCALMONERO
m_localMoneroWidget->skinChanged();
#endif
ui->conversionWidget->skinChanged();
2022-05-25 20:40:56 +00:00
ui->revuoWidget->skinChanged();
2021-07-01 21:00:47 +00:00
m_statusBtnHwDevice->setIcon(this->hardwareDevicePairedIcon());
}
QIcon MainWindow::hardwareDevicePairedIcon() {
QString filename;
if (m_wallet->isLedger())
2021-07-01 21:00:47 +00:00
filename = "ledger.png";
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;
if (m_wallet->isLedger())
2021-07-01 21:00:47 +00:00
filename = "ledger_unpaired.png";
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);
}
void MainWindow::closeEvent(QCloseEvent *event) {
2021-05-18 15:59:18 +00:00
qDebug() << Q_FUNC_INFO;
if (!this->cleanedUp) {
this->cleanedUp = true;
2022-07-02 20:41:46 +00:00
config()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
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
m_wallet->disconnect();
this->disconnect();
2021-05-18 15:59:18 +00:00
this->saveGeo();
m_windowManager->closeWindow(this);
}
event->accept();
}
2023-02-11 17:11:21 +00:00
void MainWindow::changeEvent(QEvent* event)
{
if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) {
if (config()->get(Config::lockOnMinimize).toBool()) {
this->lockWallet();
}
} else {
QMainWindow::changeEvent(event);
}
}
void MainWindow::donateButtonClicked() {
2022-02-23 23:44:13 +00:00
m_sendWidget->fill(constants::donationAddress, "Donation to the Feather development team");
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
void MainWindow::showHistoryTab() {
this->raise();
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
}
void MainWindow::showSendTab() {
this->raise();
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
2022-06-23 17:48:30 +00:00
void MainWindow::fillSendTab(const QString &address, const QString &description) {
m_sendWidget->fill(address, description);
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
void MainWindow::showCalcWindow() {
m_windowCalc->show();
}
2021-01-26 23:55:27 +00:00
void MainWindow::payToMany() {
ui->tabWidget->setCurrentIndex(Tabs::SEND);
2021-05-10 15:05:10 +00:00
m_sendWidget->payToMany();
2021-01-26 23:55:27 +00:00
QMessageBox::information(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-07-02 16:01:11 +00:00
void MainWindow::showSendScreen(const CCSEntry &entry) { // TODO: rename this function
m_sendWidget->fill(entry.address, QString("CCS: %1").arg(entry.title));
ui->tabWidget->setCurrentIndex(Tabs::SEND);
}
void MainWindow::onViewOnBlockExplorer(const QString &txid) {
2021-05-18 15:59:18 +00:00
QString blockExplorerLink = Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, txid);
2020-12-14 02:20:05 +00:00
Utils::externalLinkWarning(this, blockExplorerLink);
}
2020-12-14 22:07:23 +00:00
void MainWindow::onResendTransaction(const QString &txid) {
QString txHex = m_wallet->getCacheTransaction(txid);
if (txHex.isEmpty()) {
2020-12-14 22:07:23 +00:00
QMessageBox::warning(this, "Unable to resend transaction", "Transaction was not found in transaction cache. Unable to resend.");
return;
}
// Connect to a different node so chances of successful relay are higher
m_nodes->autoConnect(true);
2020-12-14 22:07:23 +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;
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();
bool addressValid = WalletManager::addressValid(i.value(), m_wallet->nettype());
2020-10-21 06:25:02 +00:00
if(addressValid) {
m_wallet->addressBook()->addRow(i.value(), "", i.key());
2020-10-21 06:25:02 +00:00
inserts++;
}
}
QMessageBox::information(this, "Contacts imported", QString("Total contacts imported: %1").arg(inserts));
}
void MainWindow::saveGeo() {
config()->set(Config::geometry, QString(saveGeometry().toBase64()));
config()->set(Config::windowState, QString(saveState().toBase64()));
}
void MainWindow::restoreGeo() {
bool geo = this->restoreGeometry(QByteArray::fromBase64(config()->get(Config::geometry).toByteArray()));
bool windowState = this->restoreState(QByteArray::fromBase64(config()->get(Config::windowState).toByteArray()));
qDebug() << "Restored window state: " << geo << " " << windowState;
}
void MainWindow::showDebugInfo() {
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;
}
WalletCacheDebugDialog dialog{m_wallet, this};
2021-05-18 15:59:18 +00:00
dialog.exec();
}
2021-05-22 13:42:26 +00:00
void MainWindow::showAccountSwitcherDialog() {
m_accountSwitcherDialog->show();
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)) {
QMessageBox::warning(this, "Address Checker", "Invalid address.");
return;
}
SubaddressIndex index = m_wallet->subaddressIndex(address);
2021-07-06 14:10:24 +00:00
if (!index.isValid()) {
// TODO: probably mention lookahead here
QMessageBox::warning(this, "Address Checker", "This address does not belong to this wallet.");
return;
} else {
QMessageBox::information(this, "Address Checker", QString("This address belongs to Account #%1").arg(index.major));
}
}
2020-10-14 18:18:25 +00:00
void MainWindow::exportKeyImages() {
QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Key Images (*_keyImages)");
2020-10-14 18:18:25 +00:00
if (fn.isEmpty()) return;
if (!fn.endsWith("_keyImages")) fn += "_keyImages";
bool r = m_wallet->exportKeyImages(fn, true);
if (!r) {
QMessageBox::warning(this, "Key image export", QString("Failed to export key images.\nReason: %1").arg(m_wallet->errorString()));
2020-10-14 18:18:25 +00:00
} else {
QMessageBox::information(this, "Key image export", "Successfully exported key images.");
}
}
void MainWindow::importKeyImages() {
QString fn = QFileDialog::getOpenFileName(this, "Import key image file", QDir::homePath(), "Key Images (*_keyImages)");
if (fn.isEmpty()) return;
bool r = m_wallet->importKeyImages(fn);
if (!r) {
QMessageBox::warning(this, "Key image import", QString("Failed to import key images.\n\n%1").arg(m_wallet->errorString()));
2020-10-14 18:18:25 +00:00
} else {
QMessageBox::information(this, "Key image import", "Successfully imported key images");
m_wallet->refreshModels();
2020-10-14 18:18:25 +00:00
}
}
void MainWindow::exportOutputs() {
QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Outputs (*_outputs)");
2020-10-14 18:18:25 +00:00
if (fn.isEmpty()) return;
if (!fn.endsWith("_outputs")) fn += "_outputs";
bool r = m_wallet->exportOutputs(fn, true);
if (!r) {
QMessageBox::warning(this, "Outputs export", QString("Failed to export outputs.\nReason: %1").arg(m_wallet->errorString()));
2020-10-14 18:18:25 +00:00
} else {
QMessageBox::information(this, "Outputs export", "Successfully exported outputs.");
}
}
void MainWindow::importOutputs() {
QString fn = QFileDialog::getOpenFileName(this, "Import outputs file", QDir::homePath(), "Outputs (*_outputs)");
if (fn.isEmpty()) return;
bool r = m_wallet->importOutputs(fn);
if (!r) {
QMessageBox::warning(this, "Outputs import", QString("Failed to import outputs.\n\n%1").arg(m_wallet->errorString()));
2020-10-14 18:18:25 +00:00
} else {
QMessageBox::information(this, "Outputs import", "Successfully imported outputs");
m_wallet->refreshModels();
2020-10-14 18:18:25 +00:00
}
}
2020-10-16 03:05:05 +00:00
void MainWindow::loadUnsignedTx() {
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*unsigned_monero_tx)");
if (fn.isEmpty()) return;
UnsignedTransaction *tx = m_wallet->loadTxFile(fn);
auto err = m_wallet->errorString();
2020-10-16 03:05:05 +00:00
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load transaction from file", QString("Failed to load transaction.\n\n%1").arg(err));
return;
}
this->createUnsignedTxDialog(tx);
}
void MainWindow::loadUnsignedTxFromClipboard() {
QString unsigned_tx = Utils::copyFromClipboard();
if (unsigned_tx.isEmpty()) {
QMessageBox::warning(this, "Load unsigned transaction from clipboard", "Clipboard is empty");
return;
}
UnsignedTransaction *tx = m_wallet->loadTxFromBase64Str(unsigned_tx);
auto err = m_wallet->errorString();
2020-10-16 03:05:05 +00:00
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load unsigned transaction from clipboard", QString("Failed to load transaction.\n\n%1").arg(err));
return;
}
this->createUnsignedTxDialog(tx);
}
void MainWindow::loadSignedTx() {
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx)");
if (fn.isEmpty()) return;
PendingTransaction *tx = m_wallet->loadSignedTxFile(fn);
auto err = m_wallet->errorString();
2020-10-16 03:05:05 +00:00
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load signed transaction from file", err);
return;
}
TxConfAdvDialog dialog{m_wallet, "", this};
2021-05-18 15:59:18 +00:00
dialog.setTransaction(tx);
dialog.exec();
2020-10-16 03:05:05 +00:00
}
void MainWindow::loadSignedTxFromText() {
TxBroadcastDialog dialog{this, m_nodes};
2021-05-18 15:59:18 +00:00
dialog.exec();
2020-10-16 03:05:05 +00:00
}
void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
TxConfAdvDialog dialog{m_wallet, "", this};
2021-05-18 15:59:18 +00:00
dialog.setUnsignedTransaction(tx);
dialog.exec();
2020-10-16 03:05:05 +00:00
}
2020-11-10 11:38:37 +00:00
void MainWindow::importTransaction() {
2021-05-04 23:09:19 +00:00
if (config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptNode) {
// 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
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) {
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) {
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());
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) {
qDebug() << "DeviceButtonRequest, code: " << code;
if (m_wallet->isTrezor()) {
2021-07-01 21:00:47 +00:00
switch (code) {
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;
}
case 8:
default:
2021-07-01 21:00:47 +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;
}
m_splashDialog->setMessage("Confirm action on device to proceed");
2021-07-01 21:00:47 +00:00
m_splashDialog->setIcon(QPixmap(":/assets/images/confirmed.png"));
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) {
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) {
m_wallet->onPassphraseEntered(passphrase, false, false);
2021-07-08 00:34:27 +00:00
} else {
m_wallet->onPassphraseEntered(passphrase, false, true);
2021-07-08 00:34:27 +00:00
}
}
void MainWindow::updateNetStats() {
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();
return;
}
2021-05-02 18:22:38 +00:00
2021-07-06 19:29:12 +00:00
m_statusLabelNetStats->show();
m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_wallet->getBytesReceived())));
}
2020-12-14 19:44:37 +00:00
void MainWindow::rescanSpent() {
if (!m_wallet->rescanSpent()) {
QMessageBox::warning(this, "Rescan spent", m_wallet->errorString());
2020-12-14 19:44:37 +00:00
} else {
QMessageBox::information(this, "Rescan spent", "Successfully rescanned spent outputs.");
}
}
2020-12-25 14:20:39 +00:00
void MainWindow::showBalanceDialog() {
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() {
for (const auto &widget : m_tickerWidgets) {
widget->updateDisplay();
}
m_balanceTickerWidget->updateDisplay();
m_sendWidget->onPreferredFiatCurrencyChanged();
}
void MainWindow::onHideUpdateNotifications(bool hidden) {
if (hidden) {
m_statusUpdateAvailable->hide();
}
else if (m_updater->state == Updater::State::UPDATE_AVAILABLE) {
m_statusUpdateAvailable->show();
}
}
2021-05-18 15:59:18 +00:00
void MainWindow::onTorConnectionStateChanged(bool connected) {
2023-02-11 17:11:21 +00:00
if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
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-02-11 17:11:21 +00:00
if (config()->get(Config::hideUpdateNotifications).toBool()) {
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);
2021-05-02 18:22:38 +00:00
}
2023-02-01 14:40:12 +00:00
void MainWindow::showUpdateDialog() {
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();
}
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
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
}
void MainWindow::onEndTransaction() {
// Todo: endTransaction can fail to fire when the node is switched during tx creation
m_constructingTransaction = false;
m_txTimer.stop();
this->setStatusText(m_statusText);
2021-05-04 23:09:19 +00:00
if (m_wallet->isHwBacked()) {
2021-05-04 23:09:19 +00:00
m_splashDialog->hide();
}
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;
QMessageBox::warning(this, "Critical error", "WARNING!\n\nThe wallet keys are corrupted.\n\nTo prevent LOSS OF FUNDS do NOT continue to use this wallet file.\n\nRestore your wallet from seed.\n\nPlease report this incident to the Feather developers.\n\nWARNING!");
m_sendWidget->disableSendButton();
}
}
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;
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);
}
}
2021-05-02 18:22:38 +00:00
void MainWindow::onExportHistoryCSV(bool checked) {
if (m_wallet == nullptr)
2021-05-02 18:22:38 +00:00
return;
QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
if (fn.isEmpty())
return;
if (!fn.endsWith(".csv"))
fn += ".csv";
m_wallet->history()->writeCSV(fn);
2021-05-02 18:22:38 +00:00
QMessageBox::information(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
}
void MainWindow::onExportContactsCSV(bool checked) {
if (m_wallet == nullptr) return;
auto *model = m_wallet->addressBookModel();
2021-05-02 18:22:38 +00:00
if (model->rowCount() <= 0){
QMessageBox::warning(this, "Error", "Addressbook empty");
return;
}
const QString targetDir = QFileDialog::getExistingDirectory(this, "Select CSV output directory ", QDir::homePath(), QFileDialog::ShowDirsOnly);
if(targetDir.isEmpty()) return;
qint64 now = QDateTime::currentDateTime().currentMSecsSinceEpoch();
QString fn = QString("%1/monero-contacts_%2.csv").arg(targetDir, QString::number(now / 1000));
if(model->writeCSV(fn))
QMessageBox::information(this, "Address book exported", QString("Address book exported to %1").arg(fn));
}
void MainWindow::onCreateDesktopEntry(bool checked) {
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() {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org");
}
2021-05-02 18:22:38 +00:00
void MainWindow::onReportBug(bool checked) {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org/guides/report-an-issue");
2021-05-02 18:22:38 +00:00
}
QString MainWindow::getHardwareDevice() {
if (!m_wallet->isHwBacked())
2021-05-02 18:22:38 +00:00
return "";
if (m_wallet->isTrezor())
2021-05-02 18:22:38 +00:00
return "Trezor";
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() {
QString title = QString("%1 (#%2)").arg(this->walletName(), QString::number(m_wallet->currentSubaddressAccount()));
2021-05-23 13:58:28 +00:00
if (m_wallet->viewOnly())
2021-05-02 18:22:38 +00:00
title += " [view-only]";
2021-06-14 18:01:40 +00:00
#ifdef HAS_XMRIG
2021-05-23 13:58:28 +00:00
if (m_xmrig->isMining())
2021-05-02 18:22:38 +00:00
title += " [mining]";
2021-06-14 18:01:40 +00:00
#endif
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() {
if (m_wallet->nettype() != NetworkType::Type::MAINNET)
2021-05-18 15:59:18 +00:00
return;
if (m_wallet->viewOnly())
2021-05-18 15:59:18 +00:00
return;
if (m_wallet->balanceAll() == 0)
return;
2021-05-18 15:59:18 +00:00
auto donationCounter = config()->get(Config::donateBeg).toInt();
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();
}
}
config()->set(Config::donateBeg, donationCounter);
}
void MainWindow::addToRecentlyOpened(QString keysFile) {
2021-05-18 15:59:18 +00:00
auto recent = config()->get(Config::recentlyOpenedWallets).toList();
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;
}
}
config()->set(Config::recentlyOpenedWallets, recent_);
this->updateRecentlyOpenedMenu();
}
void MainWindow::updateRecentlyOpenedMenu() {
2021-05-18 15:59:18 +00:00
ui->menuRecently_open->clear();
const QStringList recentWallets = config()->get(Config::recentlyOpenedWallets).toStringList();
for (const auto &walletPath : recentWallets) {
QFileInfo fileInfo{walletPath};
ui->menuRecently_open->addAction(fileInfo.fileName(), m_windowManager, std::bind(&WindowManager::tryOpenWallet, m_windowManager, fileInfo.absoluteFilePath(), ""));
2021-05-18 15:59:18 +00:00
}
ui->menuRecently_open->addSeparator();
ui->menuRecently_open->addAction(m_clearRecentlyOpenAction);
2021-05-18 15:59:18 +00:00
}
bool MainWindow::verifyPassword(bool sensitive) {
2022-05-27 09:49:32 +00:00
bool incorrectPassword = false;
while (true) {
PasswordDialog passwordDialog{this->walletName(), incorrectPassword, sensitive, this};
2022-03-04 16:20:17 +00:00
int ret = passwordDialog.exec();
if (ret == QDialog::Rejected) {
return false;
}
if (!m_wallet->verifyPassword(passwordDialog.password)) {
2022-05-27 09:49:32 +00:00
incorrectPassword = true;
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() {
if (!config()->get(Config::inactivityLockEnabled).toBool()) {
return;
}
if (m_constructingTransaction) {
return;
}
if ((m_userLastActive + (config()->get(Config::inactivityLockTimeout).toInt()*60)) < QDateTime::currentSecsSinceEpoch()) {
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) {
QMessageBox::warning(this, "Lock wallet", "Unable to lock wallet during transaction construction");
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;
}
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);
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) {
config()->set(Config::showSearchbar, visible);
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();
if (currentTab == Tabs::HISTORY)
m_historyWidget->focusSearchbar();
else if (currentTab == Tabs::SEND)
m_contactsWidget->focusSearchbar();
else if (currentTab == Tabs::RECEIVE)
m_receiveWidget->focusSearchbar();
2021-07-02 14:51:46 +00:00
else if (currentTab == Tabs::COINS)
m_coinsWidget->focusSearchbar();
2021-05-23 14:58:18 +00:00
}
MainWindow::~MainWindow() {
qDebug() << "~MainWindow";
};