2020-10-07 10:36:04 +00:00
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-12-26 19:56:06 +00:00
|
|
|
// Copyright (c) 2020-2021, The Monero Project.
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
#include <QPixmap>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QDesktopServices>
|
|
|
|
#include <QCoreApplication>
|
|
|
|
#include <QSystemTrayIcon>
|
|
|
|
#include <QFileDialog>
|
|
|
|
|
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "dialog/txconfdialog.h"
|
2020-10-16 03:05:05 +00:00
|
|
|
#include "dialog/txconfadvdialog.h"
|
2020-10-07 10:36:04 +00:00
|
|
|
#include "dialog/debuginfodialog.h"
|
|
|
|
#include "dialog/walletinfodialog.h"
|
|
|
|
#include "dialog/torinfodialog.h"
|
2020-10-12 22:01:06 +00:00
|
|
|
#include "dialog/viewonlydialog.h"
|
2020-10-16 03:05:05 +00:00
|
|
|
#include "dialog/broadcasttxdialog.h"
|
2020-11-10 11:38:37 +00:00
|
|
|
#include "dialog/tximportdialog.h"
|
2020-11-24 13:40:17 +00:00
|
|
|
#include "dialog/passworddialog.h"
|
2020-12-25 14:20:39 +00:00
|
|
|
#include "dialog/balancedialog.h"
|
2020-10-07 10:36:04 +00:00
|
|
|
#include "ui_mainwindow.h"
|
2020-12-25 14:20:39 +00:00
|
|
|
#include "globals.h"
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// libwalletqt
|
|
|
|
#include "libwalletqt/AddressBook.h"
|
|
|
|
|
|
|
|
MainWindow * MainWindow::pMainWindow = nullptr;
|
|
|
|
|
|
|
|
MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
|
|
|
|
QMainWindow(parent),
|
|
|
|
ui(new Ui::MainWindow),
|
2020-10-11 17:15:21 +00:00
|
|
|
m_ctx(ctx)
|
2020-10-07 10:36:04 +00:00
|
|
|
{
|
|
|
|
pMainWindow = this;
|
|
|
|
ui->setupUi(this);
|
|
|
|
|
|
|
|
m_windowSettings = new Settings(this);
|
2020-10-11 17:15:21 +00:00
|
|
|
m_aboutDialog = new AboutDialog(this);
|
|
|
|
m_windowCalc = new CalcWindow(this);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// light/dark theme
|
|
|
|
m_skins.insert("Native", "");
|
|
|
|
this->loadSkins();
|
|
|
|
QString skin = config()->get(Config::skin).toString();
|
|
|
|
qApp->setStyleSheet(m_skins[skin]);
|
|
|
|
|
|
|
|
this->screenDpiRef = 128;
|
|
|
|
this->screenGeo = QApplication::primaryScreen()->availableGeometry();
|
|
|
|
this->screenRect = QGuiApplication::primaryScreen()->geometry();
|
|
|
|
this->screenDpi = QGuiApplication::primaryScreen()->logicalDotsPerInch();
|
|
|
|
this->screenDpiPhysical = QGuiApplication::primaryScreen()->physicalDotsPerInch();
|
|
|
|
this->screenRatio = this->screenDpiPhysical / this->screenDpiRef;
|
|
|
|
qInfo() << QString("%1x%2 (%3 DPI)").arg(this->screenRect.width()).arg(this->screenRect.height()).arg(this->screenDpi);
|
|
|
|
|
|
|
|
this->restoreGeo();
|
|
|
|
|
|
|
|
this->create_status_bar();
|
|
|
|
|
|
|
|
// Bootstrap Tor/websockets
|
|
|
|
m_ctx->initTor();
|
|
|
|
m_ctx->initWS();
|
|
|
|
|
|
|
|
// update statusbar
|
|
|
|
connect(m_ctx->tor, &Tor::connectionStateChanged, [this](bool connected){
|
|
|
|
connected ? m_statusBtnTor->setIcon(QIcon(":/assets/images/tor_logo.png"))
|
|
|
|
: m_statusBtnTor->setIcon(QIcon(":/assets/images/tor_logo_disabled.png"));});
|
|
|
|
connect(m_ctx->nodes, &Nodes::updateStatus, [=](const QString &msg){
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText(msg);
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// menu connects
|
|
|
|
connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::menuQuitClicked);
|
|
|
|
connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::menuSettingsClicked);
|
|
|
|
connect(ui->actionCalculator, &QAction::triggered, this, &MainWindow::showCalcWindow);
|
|
|
|
|
|
|
|
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
|
|
|
|
ui->actionCreateDesktopEntry->setDisabled(true);
|
|
|
|
#endif
|
|
|
|
connect(ui->actionCreateDesktopEntry, &QAction::triggered, [=]{
|
|
|
|
auto msg = Utils::xdgDesktopEntryRegister() ? "Desktop entry created" : "Desktop entry not created due to an error.";
|
|
|
|
QMessageBox::information(this, "Desktop entry", msg);
|
|
|
|
});
|
|
|
|
connect(ui->actionReport_bug, &QAction::triggered, [this](){
|
|
|
|
QMessageBox::information(this, "Reporting Bugs",
|
|
|
|
"<body>Please report any bugs as issues on our git repo:<br>\n"
|
|
|
|
"<a href=\"https://git.wownero.com/feather/feather/issues\" style=\"color: #33A4DF\">https://git.wownero.com/feather/feather/issues</a><br/><br/>"
|
|
|
|
"\n"
|
|
|
|
"Before reporting a bug, upgrade to the most recent version of Feather "
|
|
|
|
"(latest release or git HEAD), and include the version number in your report. "
|
|
|
|
"Try to explain not only what the bug is, but how it occurs.</body>");
|
|
|
|
});
|
|
|
|
connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo);
|
2020-12-14 02:20:05 +00:00
|
|
|
connect(ui->actionOfficialWebsite, &QAction::triggered, [=] { Utils::externalLinkWarning(this, "https://featherwallet.org"); });
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2020-12-12 01:49:33 +00:00
|
|
|
#if defined(HAS_XMRTO)
|
2020-10-07 10:36:04 +00:00
|
|
|
// xmr.to connects/widget
|
|
|
|
connect(ui->xmrToWidget, &XMRToWidget::viewOrder, m_ctx->XMRTo, &XmrTo::onViewOrder);
|
|
|
|
connect(ui->xmrToWidget, &XMRToWidget::getRates, m_ctx->XMRTo, &XmrTo::onGetRates);
|
|
|
|
connect(ui->xmrToWidget, &XMRToWidget::createOrder, m_ctx->XMRTo, &XmrTo::createOrder);
|
|
|
|
connect(m_ctx->XMRTo, &XmrTo::ratesUpdated, ui->xmrToWidget, &XMRToWidget::onRatesUpdated);
|
|
|
|
connect(m_ctx->XMRTo, &XmrTo::connectionError, ui->xmrToWidget, &XMRToWidget::onConnectionError);
|
|
|
|
connect(m_ctx->XMRTo, &XmrTo::connectionSuccess, ui->xmrToWidget, &XMRToWidget::onConnectionSuccess);
|
|
|
|
connect(m_ctx, &AppContext::balanceUpdated, ui->xmrToWidget, &XMRToWidget::onBalanceUpdated);
|
2020-12-14 02:20:05 +00:00
|
|
|
connect(m_ctx->XMRTo, &XmrTo::openURL, this, [=](const QString &url){ Utils::externalLinkWarning(this, url); });
|
2021-01-19 22:39:31 +00:00
|
|
|
connect(m_ctx, &AppContext::walletClosed, ui->xmrToWidget, &XMRToWidget::onWalletClosed);
|
2020-10-07 10:36:04 +00:00
|
|
|
ui->xmrToWidget->setHistoryModel(m_ctx->XMRTo->tableModel);
|
|
|
|
#else
|
2020-12-12 01:49:33 +00:00
|
|
|
ui->tabExchanges->setTabVisible(0, false);
|
2020-10-07 10:36:04 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if defined(Q_OS_LINUX)
|
|
|
|
// system tray
|
|
|
|
m_trayIcon = new QSystemTrayIcon(QIcon(":/assets/images/appicons/64x64.png"));
|
|
|
|
m_trayIcon->show();
|
|
|
|
|
|
|
|
m_trayActionCalc = new QAction("Calc", this);
|
|
|
|
m_trayActionCalc->setStatusTip("Calculator");
|
|
|
|
|
|
|
|
m_trayActionSend = new QAction("Send", this);
|
|
|
|
m_trayActionSend->setStatusTip("Send XMR payment");
|
|
|
|
|
|
|
|
m_trayActionHistory = new QAction("History", this);
|
|
|
|
m_trayActionHistory->setStatusTip("View incoming transfers");
|
|
|
|
|
|
|
|
m_trayActionExit = new QAction("Quit", this);
|
|
|
|
m_trayActionExit->setStatusTip("Exit application");
|
|
|
|
|
|
|
|
m_trayMenu.addAction(m_trayActionSend);
|
|
|
|
m_trayMenu.addAction(m_trayActionHistory);
|
|
|
|
m_trayMenu.addAction(m_trayActionCalc);
|
|
|
|
m_trayMenu.addAction(m_trayActionExit);
|
|
|
|
m_trayIcon->setContextMenu(&m_trayMenu);
|
|
|
|
|
|
|
|
// @TODO: only init tray *after* boot
|
|
|
|
connect(m_trayActionCalc, &QAction::triggered, this, &MainWindow::showCalcWindow);
|
|
|
|
connect(m_trayActionSend, &QAction::triggered, this, &MainWindow::showSendTab);
|
|
|
|
connect(m_trayActionHistory, &QAction::triggered, this, &MainWindow::showHistoryTab);
|
|
|
|
connect(m_trayActionExit, &QAction::triggered, this, &QMainWindow::close);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// ticker widgets
|
|
|
|
m_tickerWidgets.append(new TickerWidget(this, "XMR"));
|
|
|
|
m_tickerWidgets.append(new TickerWidget(this, "BTC"));
|
|
|
|
for(auto tickerWidget: m_tickerWidgets) {
|
|
|
|
ui->tickerLayout->addWidget(tickerWidget);
|
|
|
|
}
|
|
|
|
|
2020-12-14 19:17:03 +00:00
|
|
|
m_balanceWidget = new TickerWidget(this, "XMR", "Balance", true, true);
|
2020-10-07 10:36:04 +00:00
|
|
|
ui->fiatTickerLayout->addWidget(m_balanceWidget);
|
|
|
|
|
|
|
|
// Send widget
|
2020-12-31 03:26:03 +00:00
|
|
|
connect(ui->sendWidget, &SendWidget::createTransaction, m_ctx, QOverload<const QString &, quint64, const QString &, bool>::of(&AppContext::onCreateTransaction));
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// Nodes
|
|
|
|
connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage);
|
|
|
|
connect(m_ctx->nodes, &Nodes::WSNodeExhausted, this, &MainWindow::showWSNodeExhaustedMessage);
|
|
|
|
|
2020-10-14 20:12:32 +00:00
|
|
|
// XMRig
|
2020-10-17 21:14:56 +00:00
|
|
|
#ifdef HAS_XMRIG
|
2020-10-16 19:01:18 +00:00
|
|
|
m_xmrig = new XMRigWidget(m_ctx, this);
|
|
|
|
ui->xmrRigLayout->addWidget(m_xmrig);
|
2020-10-17 21:14:56 +00:00
|
|
|
connect(m_ctx->XMRig, &XmRig::output, m_xmrig, &XMRigWidget::onProcessOutput);
|
|
|
|
connect(m_ctx->XMRig, &XmRig::error, m_xmrig, &XMRigWidget::onProcessError);
|
|
|
|
connect(m_ctx->XMRig, &XmRig::hashrate, m_xmrig, &XMRigWidget::onHashrate);
|
|
|
|
|
|
|
|
connect(m_ctx, &AppContext::walletClosed, m_xmrig, &XMRigWidget::onWalletClosed);
|
2020-10-16 19:01:18 +00:00
|
|
|
connect(m_ctx, &AppContext::walletOpened, m_xmrig, &XMRigWidget::onWalletOpened);
|
|
|
|
connect(m_ctx, &AppContext::XMRigDownloads, m_xmrig, &XMRigWidget::onDownloads);
|
2020-10-17 21:14:56 +00:00
|
|
|
|
2020-10-16 19:01:18 +00:00
|
|
|
connect(m_xmrig, &XMRigWidget::miningStarted, [=]{ m_ctx->setWindowTitle(true); });
|
|
|
|
connect(m_xmrig, &XMRigWidget::miningEnded, [=]{ m_ctx->setWindowTitle(false); });
|
2020-10-21 11:24:16 +00:00
|
|
|
#else
|
|
|
|
ui->tabWidget->setTabVisible(Tabs::XMRIG, false);
|
2020-10-17 21:14:56 +00:00
|
|
|
#endif
|
2020-10-14 20:12:32 +00:00
|
|
|
|
2020-12-11 13:32:14 +00:00
|
|
|
connect(ui->ccsWidget, &CCSWidget::selected, this, &MainWindow::showSendScreen);
|
|
|
|
connect(m_ctx, &AppContext::ccsUpdated, ui->ccsWidget->model(), &CCSModel::updateEntries);
|
|
|
|
connect(m_ctx, &AppContext::redditUpdated, ui->redditWidget->model(), &RedditModel::updatePosts);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2020-12-31 00:00:37 +00:00
|
|
|
connect(ui->redditWidget, &RedditWidget::setStatusText, this, &MainWindow::setStatusText);
|
|
|
|
|
2020-12-11 13:32:14 +00:00
|
|
|
connect(ui->tabHomeWidget, &QTabWidget::currentChanged, [](int index){
|
|
|
|
config()->set(Config::homeWidget, TabsHome(index));
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
connect(m_ctx, &AppContext::donationNag, [=]{
|
|
|
|
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);
|
|
|
|
switch (ret) {
|
|
|
|
case QMessageBox::Yes:
|
|
|
|
this->donateButtonClicked();
|
|
|
|
case QMessageBox::No:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// libwalletqt
|
2021-01-19 22:39:31 +00:00
|
|
|
connect(m_ctx, &AppContext::walletClosed, [this]{
|
|
|
|
this->onWalletClosed();
|
|
|
|
});
|
|
|
|
connect(m_ctx, &AppContext::walletClosed, ui->sendWidget, &SendWidget::onWalletClosed);
|
2020-10-07 10:36:04 +00:00
|
|
|
connect(m_ctx, &AppContext::balanceUpdated, this, &MainWindow::onBalanceUpdated);
|
|
|
|
connect(m_ctx, &AppContext::walletOpened, this, &MainWindow::onWalletOpened);
|
|
|
|
connect(m_ctx, &AppContext::walletOpenedError, this, &MainWindow::onWalletOpenedError);
|
|
|
|
connect(m_ctx, &AppContext::walletCreatedError, this, &MainWindow::onWalletCreatedError);
|
|
|
|
connect(m_ctx, &AppContext::walletCreated, this, &MainWindow::onWalletCreated);
|
|
|
|
connect(m_ctx, &AppContext::synchronized, this, &MainWindow::onSynchronized);
|
|
|
|
connect(m_ctx, &AppContext::blockchainSync, this, &MainWindow::onBlockchainSync);
|
|
|
|
connect(m_ctx, &AppContext::refreshSync, this, &MainWindow::onRefreshSync);
|
|
|
|
connect(m_ctx, &AppContext::createTransactionError, this, &MainWindow::onCreateTransactionError);
|
|
|
|
connect(m_ctx, &AppContext::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess);
|
|
|
|
connect(m_ctx, &AppContext::transactionCommitted, this, &MainWindow::onTransactionCommitted);
|
|
|
|
connect(m_ctx, &AppContext::walletOpenPasswordNeeded, this, &MainWindow::onWalletOpenPasswordRequired);
|
|
|
|
|
|
|
|
// Send
|
|
|
|
connect(m_ctx, &AppContext::initiateTransaction, ui->sendWidget, &SendWidget::onInitiateTransaction);
|
|
|
|
connect(m_ctx, &AppContext::endTransaction, ui->sendWidget, &SendWidget::onEndTransaction);
|
|
|
|
|
|
|
|
// XMR.to
|
|
|
|
connect(m_ctx, &AppContext::initiateTransaction, ui->xmrToWidget, &XMRToWidget::onInitiateTransaction);
|
|
|
|
connect(m_ctx, &AppContext::endTransaction, ui->xmrToWidget, &XMRToWidget::onEndTransaction);
|
|
|
|
|
2020-12-24 14:46:56 +00:00
|
|
|
connect(m_ctx, &AppContext::initiateTransaction, [this]{
|
|
|
|
m_statusDots = 0;
|
|
|
|
m_constructingTransaction = true;
|
|
|
|
m_txTimer.start(1000);
|
|
|
|
});
|
|
|
|
connect(m_ctx, &AppContext::endTransaction, [this]{
|
|
|
|
// Todo: endTransaction can fail to fire when the node is switched during tx creation
|
|
|
|
m_constructingTransaction = false;
|
|
|
|
m_txTimer.stop();
|
|
|
|
this->setStatusText(m_statusText);
|
|
|
|
});
|
|
|
|
connect(&m_txTimer, &QTimer::timeout, [this]{
|
|
|
|
m_statusLabelStatus->setText("Constructing transaction" + this->statusDots());
|
|
|
|
});
|
|
|
|
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
// testnet/stagenet warning
|
|
|
|
auto worthlessWarning = QString("Feather wallet is currently running in %1 mode. This is meant "
|
|
|
|
"for developers only. Your coins are WORTHLESS.");
|
|
|
|
if(m_ctx->networkType == NetworkType::STAGENET) {
|
|
|
|
if (config()->get(Config::warnOnStagenet).toBool()) {
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Warning", worthlessWarning.arg("stagenet"));
|
2020-10-07 10:36:04 +00:00
|
|
|
config()->set(Config::warnOnStagenet, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if(m_ctx->networkType == NetworkType::TESTNET){
|
|
|
|
if (config()->get(Config::warnOnTestnet).toBool()) {
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Warning", worthlessWarning.arg("testnet"));
|
2020-10-07 10:36:04 +00:00
|
|
|
config()->set(Config::warnOnTestnet, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(config()->get(Config::warnOnAlpha).toBool()) {
|
2020-12-09 20:14:19 +00:00
|
|
|
QString warning = "Feather Wallet is currently in beta.\n\nPlease report any bugs "
|
|
|
|
"you encounter on our Git repository, IRC or on /r/FeatherWallet.";
|
|
|
|
QMessageBox::warning(this, "Beta Warning", warning);
|
2020-10-07 10:36:04 +00:00
|
|
|
config()->set(Config::warnOnAlpha, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// settings connects
|
|
|
|
// Update ticker widget(s) on home tab when settings preferred fiat currency is changed
|
|
|
|
for(auto tickerWidget: m_tickerWidgets)
|
|
|
|
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, tickerWidget, &TickerWidget::init);
|
|
|
|
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceWidget, &TickerWidget::init);
|
|
|
|
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx, &AppContext::onPreferredFiatCurrencyChanged);
|
2020-10-15 02:17:57 +00:00
|
|
|
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, ui->sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged));
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// Skin
|
|
|
|
connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged);
|
|
|
|
|
|
|
|
// Wizard
|
|
|
|
connect(this, &MainWindow::closed, [=]{
|
|
|
|
if(m_wizard != nullptr)
|
|
|
|
m_wizard->close();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Receive
|
|
|
|
connect(ui->receiveWidget, &ReceiveWidget::generateSubaddress, [=]() {
|
|
|
|
m_ctx->currentWallet->subaddress()->addRow( m_ctx->currentWallet->currentSubaddressAccount(), "");
|
|
|
|
});
|
|
|
|
connect(ui->receiveWidget, &ReceiveWidget::showTransactions, [this](const QString &text) {
|
|
|
|
ui->historyWidget->setSearchText(text);
|
2020-10-21 11:24:16 +00:00
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// History
|
|
|
|
connect(ui->historyWidget, &HistoryWidget::viewOnBlockExplorer, this, &MainWindow::onViewOnBlockExplorer);
|
2020-12-14 22:07:23 +00:00
|
|
|
connect(ui->historyWidget, &HistoryWidget::resendTransaction, this, &MainWindow::onResendTransaction);
|
2020-12-30 03:58:17 +00:00
|
|
|
connect(m_ctx, &AppContext::walletRefreshed, ui->historyWidget, &HistoryWidget::onWalletRefreshed);
|
|
|
|
connect(m_ctx, &AppContext::walletOpened, ui->historyWidget, &HistoryWidget::onWalletOpened);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// Contacts
|
|
|
|
connect(ui->contactWidget, &ContactsWidget::fillAddress, ui->sendWidget, &SendWidget::fillAddress);
|
|
|
|
|
|
|
|
// Open alias
|
|
|
|
connect(ui->sendWidget, &SendWidget::resolveOpenAlias, m_ctx, &AppContext::onOpenAliasResolve);
|
|
|
|
connect(m_ctx, &AppContext::openAliasResolveError, ui->sendWidget, &SendWidget::onOpenAliasResolveError);
|
|
|
|
connect(m_ctx, &AppContext::openAliasResolved, ui->sendWidget, &SendWidget::onOpenAliasResolved);
|
|
|
|
|
|
|
|
// Coins
|
|
|
|
connect(ui->coinsWidget, &CoinsWidget::freeze, [=](int index) {
|
|
|
|
m_ctx->currentWallet->coins()->freeze(index);
|
|
|
|
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
|
|
|
m_ctx->updateBalance();
|
|
|
|
// subaddress account filtering should be done in Model maybe, so we can update data in coins() directly
|
|
|
|
});
|
|
|
|
connect(ui->coinsWidget, &CoinsWidget::freezeMulti, [&](const QVector<int>& indexes) {
|
|
|
|
for (int i : indexes) {
|
|
|
|
m_ctx->currentWallet->coins()->freeze(i);
|
|
|
|
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
|
|
|
m_ctx->updateBalance();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
connect(ui->coinsWidget, &CoinsWidget::thaw, [=](int index) {
|
|
|
|
m_ctx->currentWallet->coins()->thaw(index);
|
|
|
|
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
|
|
|
m_ctx->updateBalance();
|
|
|
|
});
|
|
|
|
connect(ui->coinsWidget, &CoinsWidget::thawMulti, [&](const QVector<int>& indexes) {
|
|
|
|
for (int i : indexes) {
|
|
|
|
m_ctx->currentWallet->coins()->thaw(i);
|
|
|
|
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
|
|
|
m_ctx->updateBalance();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
connect(ui->coinsWidget, &CoinsWidget::sweepOutput, m_ctx, &AppContext::onSweepOutput);
|
|
|
|
|
2021-01-19 22:39:31 +00:00
|
|
|
connect(m_ctx, &AppContext::walletAboutToClose, [=]{
|
2020-12-30 04:45:00 +00:00
|
|
|
if (!config()->get(Config::showTabHome).toBool())
|
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
|
|
|
|
else
|
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::HOME);
|
|
|
|
|
2020-12-31 06:54:19 +00:00
|
|
|
// Clear all tables when wallet is closed
|
2020-12-30 04:45:00 +00:00
|
|
|
ui->historyWidget->resetModel();
|
2020-12-31 06:54:19 +00:00
|
|
|
ui->contactWidget->resetModel();
|
|
|
|
ui->receiveWidget->resetModel();
|
|
|
|
ui->coinsWidget->resetModel();
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
2020-10-12 22:01:06 +00:00
|
|
|
|
|
|
|
// window title
|
|
|
|
connect(m_ctx, &AppContext::setTitle, this, &QMainWindow::setWindowTitle);
|
|
|
|
|
2020-10-22 03:50:59 +00:00
|
|
|
// init touchbar
|
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
m_touchbar = new KDMacTouchBar(this);
|
|
|
|
m_touchbarActionWelcome = new QAction(QIcon(":/assets/images/feather.png"), "Welcome to Feather!");
|
|
|
|
m_touchbarWalletItems = {ui->actionSettings, ui->actionCalculator, ui->actionKeys, ui->actionDonate_to_Feather};
|
|
|
|
m_touchbarWizardItems = {m_touchbarActionWelcome};
|
|
|
|
#endif
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
// setup some UI
|
|
|
|
this->initMain();
|
|
|
|
this->initWidgets();
|
|
|
|
this->initMenu();
|
2020-11-23 16:57:38 +00:00
|
|
|
|
|
|
|
connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::initMain() {
|
|
|
|
// show wizards or open wallet directly based on cmdargs
|
|
|
|
if(m_ctx->cmdargs->isSet("password"))
|
|
|
|
m_ctx->walletPassword = m_ctx->cmdargs->value("password");
|
|
|
|
|
|
|
|
QString openPath = "";
|
|
|
|
QString autoPath = config()->get(Config::autoOpenWalletPath).toString();
|
|
|
|
if(m_ctx->cmdargs->isSet("wallet-file"))
|
|
|
|
openPath = m_ctx->cmdargs->value("wallet-file");
|
|
|
|
else if(!autoPath.isEmpty())
|
|
|
|
if (autoPath.startsWith(QString::number(m_ctx->networkType)))
|
|
|
|
openPath = autoPath.remove(0, 1);
|
|
|
|
|
|
|
|
if(!openPath.isEmpty() && Utils::fileExists(openPath)) {
|
|
|
|
this->show();
|
|
|
|
this->setEnabled(true);
|
|
|
|
|
|
|
|
m_ctx->onOpenWallet(openPath, m_ctx->walletPassword);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->setEnabled(false);
|
|
|
|
this->show();
|
|
|
|
m_wizard = this->createWizard(WalletWizard::Page_Menu);
|
|
|
|
m_wizard->show();
|
2020-10-22 03:50:59 +00:00
|
|
|
this->touchbarShowWizard();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::initMenu() {
|
2020-11-25 13:35:28 +00:00
|
|
|
// setup shortcuts
|
|
|
|
ui->actionStore_wallet->setShortcut(QKeySequence("Ctrl+S"));
|
|
|
|
ui->actionRefresh_tabs->setShortcut(QKeySequence("Ctrl+R"));
|
|
|
|
ui->actionClose->setShortcut(QKeySequence("Ctrl+W"));
|
|
|
|
ui->actionShow_debug_info->setShortcut(QKeySequence("Ctrl+D"));
|
|
|
|
ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S"));
|
2020-12-07 23:26:48 +00:00
|
|
|
ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U"));
|
2020-11-25 13:35:28 +00:00
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
// hide/show tabs
|
|
|
|
m_tabShowHideSignalMapper = new QSignalMapper(this);
|
|
|
|
|
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");
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
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");
|
2020-10-21 11:24:16 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
|
2020-12-25 22:18:40 +00:00
|
|
|
#if defined(HAS_XMRTO)
|
|
|
|
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
|
2021-01-04 12:22:49 +00:00
|
|
|
ui->actionShow_Exchange->setVisible(false);
|
2020-12-25 22:18:40 +00:00
|
|
|
#endif
|
|
|
|
|
2020-10-17 21:14:56 +00:00
|
|
|
#if defined(HAS_XMRIG)
|
2020-10-14 20:12:32 +00:00
|
|
|
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");
|
2020-10-14 20:12:32 +00:00
|
|
|
#else
|
|
|
|
ui->actionShow_XMRig->setVisible(false);
|
|
|
|
#endif
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
// Wallet
|
|
|
|
connect(ui->actionInformation, &QAction::triggered, this, &MainWindow::showWalletInfoDialog);
|
|
|
|
connect(ui->actionSeed, &QAction::triggered, this, &MainWindow::showSeedDialog);
|
|
|
|
connect(ui->actionPassword, &QAction::triggered, this, &MainWindow::showPasswordDialog);
|
|
|
|
connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog);
|
2020-10-12 22:01:06 +00:00
|
|
|
connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog);
|
2020-12-14 19:44:37 +00:00
|
|
|
connect(ui->actionStore_wallet, &QAction::triggered, [this]{
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->currentWallet->store();
|
|
|
|
});
|
2020-12-14 19:44:37 +00:00
|
|
|
connect(ui->actionRefresh_tabs, &QAction::triggered, [this]{
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->refreshModels();
|
|
|
|
});
|
2020-12-14 19:44:37 +00:00
|
|
|
connect(ui->actionUpdate_balance, &QAction::triggered, [this]{
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->updateBalance();
|
|
|
|
});
|
2020-12-14 19:44:37 +00:00
|
|
|
connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent);
|
2020-10-14 18:18:25 +00:00
|
|
|
connect(ui->actionExportKeyImages, &QAction::triggered, this, &MainWindow::exportKeyImages);
|
|
|
|
connect(ui->actionImportKeyImages, &QAction::triggered, this, &MainWindow::importKeyImages);
|
|
|
|
connect(ui->actionExportOutputs, &QAction::triggered, this, &MainWindow::exportOutputs);
|
|
|
|
connect(ui->actionImportOutputs, &QAction::triggered, this, &MainWindow::importOutputs);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// set restore height
|
|
|
|
connect(ui->actionChange_restore_height, &QAction::triggered, this, &MainWindow::showRestoreHeightDialog);
|
2020-12-24 13:34:37 +00:00
|
|
|
connect(m_ctx, &AppContext::customRestoreHeightSet, [=](int height){
|
2020-10-07 10:36:04 +00:00
|
|
|
auto msg = QString("The restore height for this wallet has been set to %1. "
|
|
|
|
"Please re-open the wallet. Feather will now quit.").arg(height);
|
|
|
|
QMessageBox::information(this, "Cannot set custom restore height", msg);
|
|
|
|
this->menuQuitClicked();
|
|
|
|
});
|
|
|
|
|
|
|
|
// CSV tx export
|
|
|
|
connect(ui->actionExport_CSV, &QAction::triggered, [=]{
|
|
|
|
if(m_ctx->currentWallet == nullptr) return;
|
|
|
|
QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
|
2020-10-12 22:01:06 +00:00
|
|
|
if(fn.isEmpty()) return;
|
|
|
|
if(!fn.endsWith(".csv")) fn += ".csv";
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->currentWallet->history()->writeCSV(fn);
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::information(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Contact widget
|
|
|
|
connect(ui->actionExportContactsCSV, &QAction::triggered, [=]{
|
|
|
|
if(m_ctx->currentWallet == nullptr) return;
|
|
|
|
auto *model = m_ctx->currentWallet->addressBookModel();
|
|
|
|
if (model->rowCount() <= 0){
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Error", "Addressbook empty");
|
2020-10-07 10:36:04 +00:00
|
|
|
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))
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::information(this, "Address book exported", QString("Address book exported to %1").arg(fn));
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
|
|
|
|
2020-10-21 06:25:02 +00:00
|
|
|
connect(ui->actionImportContactsCSV, &QAction::triggered, this, &MainWindow::importContacts);
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
// Tools
|
|
|
|
connect(ui->actionSignVerify, &QAction::triggered, this, &MainWindow::menuSignVerifyClicked);
|
|
|
|
connect(ui->actionVerifyTxProof, &QAction::triggered, this, &MainWindow::menuVerifyTxProof);
|
2020-10-16 03:05:05 +00:00
|
|
|
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);
|
2020-11-10 11:38:37 +00:00
|
|
|
connect(ui->actionImport_transaction, &QAction::triggered, this, &MainWindow::importTransaction);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// About screen
|
|
|
|
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
|
|
|
|
connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked);
|
|
|
|
|
2020-12-12 15:59:44 +00:00
|
|
|
// Close wallet
|
2020-10-07 10:36:04 +00:00
|
|
|
connect(ui->actionClose, &QAction::triggered, this, &MainWindow::menuWalletCloseClicked);
|
|
|
|
}
|
|
|
|
|
|
|
|
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::initWidgets() {
|
2020-12-11 13:32:14 +00:00
|
|
|
int homeWidget = config()->get(Config::homeWidget).toInt();
|
|
|
|
ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget));
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
WalletWizard *MainWindow::createWizard(WalletWizard::Page startPage){
|
|
|
|
auto *wizard = new WalletWizard(m_ctx, startPage, this);
|
|
|
|
connect(wizard, &WalletWizard::openWallet, m_ctx, &AppContext::onOpenWallet);
|
2020-12-25 21:07:48 +00:00
|
|
|
connect(wizard, &WalletWizard::defaultWalletDirChanged, m_windowSettings, &Settings::updatePaths);
|
2020-10-07 10:36:04 +00:00
|
|
|
return wizard;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showWizard(WalletWizard::Page startPage) {
|
|
|
|
this->setEnabled(false);
|
2020-10-16 13:43:28 +00:00
|
|
|
if (m_wizard == nullptr)
|
|
|
|
m_wizard = this->createWizard(startPage);
|
|
|
|
m_wizard->setStartId(startPage);
|
|
|
|
m_wizard->restart();
|
|
|
|
m_wizard->setEnabled(true);
|
2020-10-07 10:36:04 +00:00
|
|
|
m_wizard->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onWalletClosed(WalletWizard::Page page) {
|
|
|
|
m_statusLabelBalance->clear();
|
|
|
|
m_statusLabelStatus->clear();
|
|
|
|
this->showWizard(page);
|
|
|
|
}
|
|
|
|
|
2020-10-22 03:50:59 +00:00
|
|
|
void MainWindow::touchbarShowWizard() {
|
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
m_touchbar->clear();
|
|
|
|
for(auto* action: m_touchbarWizardItems) m_touchbar->addAction(action);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::touchbarShowWallet() {
|
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
m_touchbar->clear();
|
|
|
|
for(auto* action: m_touchbarWalletItems) m_touchbar->addAction(action);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
void MainWindow::onWalletCreatedError(const QString &err) {
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Wallet creation error", err);
|
2020-10-07 10:36:04 +00:00
|
|
|
this->showWizard(WalletWizard::Page_CreateWallet);
|
|
|
|
}
|
|
|
|
|
2020-10-21 06:45:25 +00:00
|
|
|
void MainWindow::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) {
|
|
|
|
QFileInfo fileInfo(path);
|
2020-11-24 13:40:17 +00:00
|
|
|
|
|
|
|
auto dialog = new PasswordDialog(this, fileInfo.fileName(), invalidPassword);
|
|
|
|
switch (dialog->exec()) {
|
|
|
|
case QDialog::Rejected:
|
|
|
|
{
|
|
|
|
this->showWizard(WalletWizard::Page_OpenWallet);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ctx->walletPassword = dialog->password;
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->onOpenWallet(m_ctx->walletPath, m_ctx->walletPassword);
|
2020-11-24 13:40:17 +00:00
|
|
|
|
|
|
|
dialog->deleteLater();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onWalletOpenedError(const QString &err) {
|
|
|
|
qDebug() << Q_FUNC_INFO << QString("Wallet open error: %1").arg(err);
|
|
|
|
QMessageBox::warning(this, "Wallet open error", err);
|
|
|
|
this->setWindowTitle("Feather");
|
|
|
|
this->showWizard(WalletWizard::Page_OpenWallet);
|
2020-10-22 03:50:59 +00:00
|
|
|
this->touchbarShowWizard();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onWalletCreated(Wallet *wallet) {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
// emit signal on behalf of walletManager
|
|
|
|
m_ctx->walletManager->walletOpened(wallet);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onWalletOpened() {
|
|
|
|
qDebug() << Q_FUNC_INFO;
|
|
|
|
if(m_wizard != nullptr) {
|
|
|
|
m_wizard->hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
this->raise();
|
|
|
|
this->show();
|
|
|
|
this->activateWindow();
|
|
|
|
this->setEnabled(true);
|
|
|
|
if(!m_ctx->tor->torConnected)
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText("Wallet opened - Starting Tor (may take a while)");
|
2020-10-07 10:36:04 +00:00
|
|
|
else
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText("Wallet opened - Searching for node");
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
connect(m_ctx->currentWallet, &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged);
|
|
|
|
|
|
|
|
// receive page
|
|
|
|
m_ctx->currentWallet->subaddress()->refresh( m_ctx->currentWallet->currentSubaddressAccount());
|
|
|
|
ui->receiveWidget->setModel( m_ctx->currentWallet->subaddressModel(), m_ctx->currentWallet->subaddress());
|
|
|
|
if (m_ctx->currentWallet->subaddress()->count() == 1) {
|
|
|
|
for (int i = 0; i < 10; i++) {
|
|
|
|
m_ctx->currentWallet->subaddress()->addRow(m_ctx->currentWallet->currentSubaddressAccount(), "");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// history page
|
|
|
|
m_ctx->currentWallet->history()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
2020-10-19 23:05:42 +00:00
|
|
|
ui->historyWidget->setModel(m_ctx->currentWallet->historyModel(), m_ctx->currentWallet);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// contacts widget
|
|
|
|
ui->contactWidget->setModel(m_ctx->currentWallet->addressBookModel());
|
|
|
|
|
|
|
|
// coins page
|
|
|
|
m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
|
|
|
ui->coinsWidget->setModel(m_ctx->currentWallet->coinsModel(), m_ctx->currentWallet->coins());
|
2020-10-22 03:50:59 +00:00
|
|
|
|
|
|
|
this->touchbarShowWallet();
|
2020-11-14 09:57:06 +00:00
|
|
|
this->updatePasswordIcon();
|
2020-11-23 16:57:38 +00:00
|
|
|
|
2020-12-23 01:42:22 +00:00
|
|
|
m_updateBytes.start(100);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
|
2020-10-07 10:36:04 +00:00
|
|
|
qDebug() << Q_FUNC_INFO;
|
2020-11-02 09:37:36 +00:00
|
|
|
bool hide = config()->get(Config::hideBalance).toBool();
|
|
|
|
|
2020-12-31 02:14:48 +00:00
|
|
|
QString label_str = QString("Balance: %1 XMR").arg(Utils::balanceFormat(spendable));
|
2020-12-25 14:20:39 +00:00
|
|
|
if (balance > spendable)
|
2020-12-31 02:14:48 +00:00
|
|
|
label_str += QString(" (+%1 XMR unconfirmed)").arg(Utils::balanceFormat(balance - spendable));
|
2020-11-02 09:37:36 +00:00
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
if (hide)
|
2020-11-02 09:37:36 +00:00
|
|
|
label_str = "Balance: HIDDEN";
|
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
m_statusLabelBalance->setToolTip("Click for details");
|
2020-10-07 10:36:04 +00:00
|
|
|
m_statusLabelBalance->setText(label_str);
|
2020-11-02 09:37:36 +00:00
|
|
|
m_balanceWidget->setHidden(hide);
|
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
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
void MainWindow::onSynchronized() {
|
2020-11-23 16:57:38 +00:00
|
|
|
this->updateNetStats();
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText("Synchronized");
|
2020-10-07 10:36:04 +00:00
|
|
|
this->onConnectionStatusChanged(Wallet::ConnectionStatus_Connected);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onConnectionStatusChanged(int status)
|
|
|
|
{
|
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.
|
|
|
|
|
|
|
|
QString statusIcon;
|
|
|
|
QString statusMsg;
|
|
|
|
switch(status){
|
|
|
|
case Wallet::ConnectionStatus_Disconnected:
|
|
|
|
statusIcon = ":/assets/images/status_disconnected.svg";
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText("Disconnected");
|
2020-10-07 10:36:04 +00:00
|
|
|
break;
|
|
|
|
case Wallet::ConnectionStatus_Connected:
|
|
|
|
if (m_ctx->currentWallet->synchronized()) {
|
|
|
|
statusIcon = ":/assets/images/status_connected.svg";
|
|
|
|
} else {
|
|
|
|
statusIcon = ":/assets/images/status_waiting.svg";
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case Wallet::ConnectionStatus_Connecting:
|
|
|
|
statusIcon = ":/assets/images/status_lagging.svg";
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText("Connecting to daemon");
|
2020-10-07 10:36:04 +00:00
|
|
|
break;
|
|
|
|
case Wallet::ConnectionStatus_WrongVersion:
|
|
|
|
statusIcon = ":/assets/images/status_disconnected.svg";
|
2020-12-24 14:46:56 +00:00
|
|
|
this->setStatusText("Incompatible daemon");
|
2020-10-07 10:36:04 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
statusIcon = ":/assets/images/status_disconnected.svg";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_statusBtnConnectionStatusIndicator->setIcon(QIcon(statusIcon));
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin) {
|
|
|
|
auto tx_status = tx->status();
|
|
|
|
auto err = QString("Can't create transaction: ");
|
|
|
|
|
|
|
|
if(tx_status != PendingTransaction::Status_Ok){
|
|
|
|
auto tx_err = tx->errorString();
|
|
|
|
qCritical() << tx_err;
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
|
2020-10-07 10:36:04 +00:00
|
|
|
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
|
|
|
|
else
|
|
|
|
err = QString("%1 %2").arg(err).arg(tx_err);
|
|
|
|
|
|
|
|
qDebug() << Q_FUNC_INFO << err;
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Transactions error", err);
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->currentWallet->disposeTransaction(tx);
|
|
|
|
} else if (tx->txCount() == 0) {
|
|
|
|
err = QString("%1 %2").arg(err).arg("No unmixable outputs to sweep.");
|
|
|
|
qDebug() << Q_FUNC_INFO << err;
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Transaction error", err);
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->currentWallet->disposeTransaction(tx);
|
|
|
|
} else {
|
|
|
|
const auto &description = m_ctx->tmpTxDescription;
|
|
|
|
|
2020-10-16 03:05:05 +00:00
|
|
|
auto *dialog = new TxConfDialog(m_ctx, tx, address, description, mixin, this);
|
2020-10-07 10:36:04 +00:00
|
|
|
switch (dialog->exec()) {
|
|
|
|
case QDialog::Rejected:
|
2020-10-16 03:05:05 +00:00
|
|
|
{
|
|
|
|
if (!dialog->showAdvanced)
|
|
|
|
m_ctx->onCancelTransaction(tx, address);
|
2020-10-07 10:36:04 +00:00
|
|
|
break;
|
2020-10-16 03:05:05 +00:00
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
case QDialog::Accepted:
|
|
|
|
m_ctx->currentWallet->commitTransactionAsync(tx);
|
|
|
|
break;
|
|
|
|
}
|
2020-10-16 03:05:05 +00:00
|
|
|
|
|
|
|
if (dialog->showAdvanced) {
|
|
|
|
auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this);
|
|
|
|
dialog_adv->setTransaction(tx);
|
|
|
|
dialog_adv->exec();
|
|
|
|
dialog_adv->deleteLater();
|
|
|
|
}
|
|
|
|
dialog->deleteLater();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
|
|
|
|
if(status) { // success
|
|
|
|
QString body = QString("Successfully sent %1 transaction(s).").arg(txid.count());
|
|
|
|
QMessageBox::information(this, "Transactions sent", body);
|
|
|
|
ui->sendWidget->clearFields();
|
|
|
|
|
|
|
|
for(const auto &entry: txid) {
|
|
|
|
m_ctx->currentWallet->setUserNote(entry, m_ctx->tmpTxDescription);
|
|
|
|
AppContext::txDescriptionCache[entry] = m_ctx->tmpTxDescription;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_ctx->tmpTxDescription = "";
|
|
|
|
} 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::create_status_bar() {
|
2020-10-11 18:21:22 +00:00
|
|
|
#if defined(Q_OS_WIN)
|
|
|
|
// No seperators between statusbar widgets
|
|
|
|
this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}");
|
|
|
|
#endif
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
this->statusBar()->setFixedHeight(30);
|
|
|
|
|
|
|
|
m_statusLabelStatus = new QLabel("Idle", this);
|
|
|
|
m_statusLabelStatus->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
|
|
this->statusBar()->addWidget(m_statusLabelStatus);
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
m_statusLabelNetStats = new QLabel("", this);
|
|
|
|
m_statusLabelNetStats->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
|
|
this->statusBar()->addWidget(m_statusLabelNetStats);
|
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
m_statusLabelBalance = new ClickableLabel(this);
|
2020-12-31 02:14:48 +00:00
|
|
|
m_statusLabelBalance->setText("Balance: 0 XMR");
|
2020-10-07 10:36:04 +00:00
|
|
|
m_statusLabelBalance->setTextInteractionFlags(Qt::TextSelectableByMouse);
|
|
|
|
this->statusBar()->addPermanentWidget(m_statusLabelBalance);
|
2020-12-25 14:20:39 +00:00
|
|
|
connect(m_statusLabelBalance, &ClickableLabel::clicked, this, &MainWindow::showBalanceDialog);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
m_statusBtnConnectionStatusIndicator = new StatusBarButton(QIcon(":/assets/images/status_disconnected.svg"), "Connection status");
|
|
|
|
connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, this, &MainWindow::showConnectionStatusDialog);
|
|
|
|
this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator);
|
|
|
|
|
|
|
|
m_statusBtnPassword = new StatusBarButton(QIcon(":/assets/images/lock.svg"), "Password");
|
|
|
|
connect(m_statusBtnPassword, &StatusBarButton::clicked, this, &MainWindow::showPasswordDialog);
|
|
|
|
this->statusBar()->addPermanentWidget(m_statusBtnPassword);
|
|
|
|
|
|
|
|
m_statusBtnPreferences = new StatusBarButton(QIcon(":/assets/images/preferences.svg"), "Settings");
|
|
|
|
connect(m_statusBtnPreferences, &StatusBarButton::clicked, this, &MainWindow::menuSettingsClicked);
|
|
|
|
this->statusBar()->addPermanentWidget(m_statusBtnPreferences);
|
|
|
|
|
|
|
|
m_statusBtnSeed = new StatusBarButton(QIcon(":/assets/images/seed.png"), "Seed");
|
|
|
|
connect(m_statusBtnSeed, &StatusBarButton::clicked, this, &MainWindow::showSeedDialog);
|
|
|
|
this->statusBar()->addPermanentWidget(m_statusBtnSeed);
|
|
|
|
|
|
|
|
m_statusBtnTor = new StatusBarButton(QIcon(":/assets/images/tor_logo_disabled.png"), "Tor");
|
|
|
|
connect(m_statusBtnTor, &StatusBarButton::clicked, this, &MainWindow::menuTorClicked);
|
|
|
|
this->statusBar()->addPermanentWidget(m_statusBtnTor);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showWalletInfoDialog() {
|
|
|
|
auto *dialog = new WalletInfoDialog(m_ctx, this);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showSeedDialog() {
|
2020-11-11 22:09:35 +00:00
|
|
|
auto *dialog = new SeedDialog(m_ctx->currentWallet, this);
|
2020-10-07 10:36:04 +00:00
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showConnectionStatusDialog() {
|
2020-11-23 16:57:38 +00:00
|
|
|
auto status = m_ctx->currentWallet->connectionStatus();
|
2020-10-07 10:36:04 +00:00
|
|
|
bool synchronized = m_ctx->currentWallet->synchronized();
|
|
|
|
|
|
|
|
QString statusMsg;
|
|
|
|
switch(status){
|
|
|
|
case Wallet::ConnectionStatus_Disconnected:
|
|
|
|
statusMsg = "Wallet is disconnected from daemon.";
|
|
|
|
break;
|
|
|
|
case Wallet::ConnectionStatus_Connected: {
|
|
|
|
auto node = m_ctx->nodes->connection();
|
|
|
|
statusMsg = QString("Wallet is connected to %1").arg(node.full);
|
|
|
|
if (synchronized)
|
|
|
|
statusMsg += " and synchronized";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Wallet::ConnectionStatus_Connecting: {
|
|
|
|
auto node = m_ctx->nodes->connection();
|
|
|
|
statusMsg = QString("Wallet is connecting to %1").arg(node.full);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case Wallet::ConnectionStatus_WrongVersion:
|
|
|
|
statusMsg = "Wallet is connected to incompatible daemon.";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
statusMsg = "Unknown connection status (this should never happen).";
|
|
|
|
}
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
statusMsg += QString("\n\nTx: %1, Rx: %2").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesSent()),
|
|
|
|
Utils::formatBytes(m_ctx->currentWallet->getBytesReceived()));
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
QMessageBox::information(this, "Connection Status", statusMsg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showPasswordDialog() {
|
2020-11-14 09:57:06 +00:00
|
|
|
auto *pdialog = new PasswordChangeDialog(this, m_ctx->currentWallet);
|
|
|
|
pdialog->exec();
|
2020-10-07 10:36:04 +00:00
|
|
|
pdialog->deleteLater();
|
2020-11-14 09:57:06 +00:00
|
|
|
this->updatePasswordIcon();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::updatePasswordIcon() {
|
|
|
|
QIcon icon = m_ctx->currentWallet->getPassword().isEmpty() ? QIcon(":/assets/images/unlock.svg") : QIcon(":/assets/images/lock.svg");
|
|
|
|
m_statusBtnPassword->setIcon(icon);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showRestoreHeightDialog() {
|
|
|
|
// settings custom restore height is only available for 25 word seeds
|
|
|
|
auto seed = m_ctx->currentWallet->getCacheAttribute("feather.seed");
|
|
|
|
if(!seed.isEmpty()) {
|
|
|
|
const auto msg = "This wallet has a 14 word mnemonic seed which has the restore height embedded.";
|
|
|
|
QMessageBox::warning(this, "Cannot set custom restore height", msg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_restoreDialog = new RestoreDialog(m_ctx, this);
|
|
|
|
m_restoreDialog->show();
|
|
|
|
|
|
|
|
connect(m_restoreDialog, &RestoreDialog::accepted, [=]{
|
|
|
|
auto height = m_restoreDialog->getHeight();
|
|
|
|
m_restoreDialog->disconnect();
|
|
|
|
m_restoreDialog->deleteLater();
|
|
|
|
m_ctx->onSetRestoreHeight(height);
|
|
|
|
});
|
|
|
|
|
|
|
|
connect(m_restoreDialog, &RestoreDialog::rejected, [=]{
|
|
|
|
m_restoreDialog->disconnect();
|
|
|
|
m_restoreDialog->deleteLater();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showKeysDialog() {
|
|
|
|
auto *dialog = new KeysDialog(m_ctx, this);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
2020-10-12 22:01:06 +00:00
|
|
|
void MainWindow::showViewOnlyDialog() {
|
|
|
|
auto *dialog = new ViewOnlyDialog(m_ctx, this);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
void MainWindow::menuTorClicked() {
|
|
|
|
|
|
|
|
auto *dialog = new TorInfoDialog(m_ctx, this);
|
|
|
|
connect(m_ctx->tor, &Tor::logsUpdated, dialog, &TorInfoDialog::onLogsUpdated);
|
|
|
|
dialog->exec();
|
|
|
|
disconnect(m_ctx->tor, &Tor::logsUpdated, dialog, &TorInfoDialog::onLogsUpdated);
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuNewRestoreClicked() {
|
|
|
|
// TODO: implement later
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuQuitClicked() {
|
2020-10-16 03:15:46 +00:00
|
|
|
cleanupBeforeClose();
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
QCoreApplication::quit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuWalletCloseClicked() {
|
2021-01-19 22:39:31 +00:00
|
|
|
if (m_ctx->currentWallet == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
m_ctx->closeWallet(true, true);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuWalletOpenClicked() {
|
|
|
|
auto walletPath = config()->get(Config::walletPath).toString();
|
|
|
|
if(walletPath.isEmpty())
|
|
|
|
walletPath = m_ctx->defaultWalletDir;
|
|
|
|
QString path = QFileDialog::getOpenFileName(this, "Select your wallet file", walletPath, "Wallet file (*.keys)");
|
|
|
|
if(path.isEmpty()) return;
|
|
|
|
|
|
|
|
QFileInfo infoPath(path);
|
|
|
|
if(!infoPath.isReadable()) {
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Cannot read wallet file", "Permission error.");
|
2020-10-07 10:36:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(path == m_ctx->walletPath) {
|
2020-12-14 02:20:05 +00:00
|
|
|
QMessageBox::warning(this, "Wallet already opened", "Please open another wallet.");
|
2020-10-07 10:36:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-19 22:39:31 +00:00
|
|
|
m_ctx->closeWallet(false);
|
2020-10-07 10:36:04 +00:00
|
|
|
m_ctx->onOpenWallet(path, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuAboutClicked() {
|
|
|
|
m_aboutDialog->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuSettingsClicked() {
|
2020-11-24 19:32:51 +00:00
|
|
|
m_windowSettings->raise();
|
2020-10-07 10:36:04 +00:00
|
|
|
m_windowSettings->show();
|
2020-11-24 19:32:51 +00:00
|
|
|
m_windowSettings->activateWindow();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuSignVerifyClicked() {
|
|
|
|
auto *dialog = new SignVerifyDialog(m_ctx->currentWallet, this);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::menuVerifyTxProof() {
|
|
|
|
auto *dialog = new VerifyProofDialog(m_ctx->currentWallet, this);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::skinChanged(const QString &skinName) {
|
|
|
|
if(!m_skins.contains(skinName)) {
|
|
|
|
qWarning() << QString("No such skin %1").arg(skinName);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
config()->set(Config::skin, skinName);
|
|
|
|
qApp->setStyleSheet(m_skins[skinName]);
|
|
|
|
qDebug() << QString("Skin changed to %1").arg(skinName);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::closeEvent(QCloseEvent *event) {
|
2020-10-16 03:15:46 +00:00
|
|
|
cleanupBeforeClose();
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
QWidget::closeEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::donateButtonClicked() {
|
2020-12-14 00:59:32 +00:00
|
|
|
double donation = AppContext::prices->convert("EUR", "XMR", m_ctx->donationAmount);
|
|
|
|
if (donation <= 0)
|
|
|
|
donation = 0.1337;
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2020-12-14 00:59:32 +00:00
|
|
|
ui->sendWidget->fill(m_ctx->donationAddress, "Donation to the Feather development team", donation);
|
2020-10-21 11:24:16 +00:00
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::SEND);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showHistoryTab() {
|
|
|
|
this->raise();
|
2020-10-21 11:24:16 +00:00
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::HISTORY);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showSendTab() {
|
|
|
|
this->raise();
|
2020-10-21 11:24:16 +00:00
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::SEND);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showCalcWindow() {
|
|
|
|
m_windowCalc->show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showSendScreen(const CCSEntry &entry) {
|
|
|
|
ui->sendWidget->fill(entry);
|
2020-10-21 11:24:16 +00:00
|
|
|
ui->tabWidget->setCurrentIndex(Tabs::SEND);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::onViewOnBlockExplorer(const QString &txid) {
|
|
|
|
QString blockExplorerLink = Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), m_ctx->networkType, txid);
|
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) {
|
|
|
|
if (!AppContext::txCache.contains(txid)) {
|
|
|
|
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_ctx->nodes->autoConnect(true);
|
|
|
|
|
|
|
|
auto dialog = new BroadcastTxDialog(this, m_ctx, AppContext::txCache[txid]);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
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_ctx->currentWallet->addressBookModel();
|
|
|
|
QMapIterator<QString, QString> i(model->readCSV(targetFile));
|
|
|
|
int inserts = 0;
|
|
|
|
while (i.hasNext()) {
|
|
|
|
i.next();
|
|
|
|
bool addressValid = WalletManager::addressValid(i.value(), m_ctx->currentWallet->nettype());
|
|
|
|
if(addressValid) {
|
|
|
|
m_ctx->currentWallet->addressBook()->addRow(i.value(), "", i.key());
|
|
|
|
inserts++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QMessageBox::information(this, "Contacts imported", QString("Total contacts imported: %1").arg(inserts));
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
MainWindow *MainWindow::getInstance() {
|
|
|
|
return pMainWindow;
|
|
|
|
}
|
|
|
|
|
|
|
|
AppContext *MainWindow::getContext(){
|
|
|
|
return pMainWindow->m_ctx;
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::loadSkins() {
|
|
|
|
QString qdarkstyle = this->loadStylesheet(":qdarkstyle/style.qss");
|
|
|
|
if (!qdarkstyle.isEmpty())
|
|
|
|
m_skins.insert("QDarkStyle", qdarkstyle);
|
|
|
|
|
|
|
|
QString breeze_dark = this->loadStylesheet(":/dark.qss");
|
|
|
|
if (!breeze_dark.isEmpty())
|
|
|
|
m_skins.insert("Breeze/Dark", breeze_dark);
|
|
|
|
|
|
|
|
QString breeze_light = this->loadStylesheet(":/light.qss");
|
|
|
|
if (!breeze_light.isEmpty())
|
|
|
|
m_skins.insert("Breeze/Light", breeze_light);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString MainWindow::loadStylesheet(const QString &resource) {
|
|
|
|
QFile f(resource);
|
|
|
|
if (!f.exists()) {
|
|
|
|
printf("Unable to set stylesheet, file not found\n");
|
|
|
|
f.close();
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
f.open(QFile::ReadOnly | QFile::Text);
|
|
|
|
QTextStream ts(&f);
|
|
|
|
QString data = ts.readAll();
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
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() {
|
|
|
|
auto *dialog = new DebugInfoDialog(m_ctx, this);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showNodeExhaustedMessage() {
|
|
|
|
// Spawning dialogs inside a lambda can cause system freezes on linux so we have to do it this way ¯\_(ツ)_/¯
|
|
|
|
|
|
|
|
auto msg = "Feather is in 'custom node connection mode' but could not "
|
|
|
|
"find an eligible node to connect to. Please go to Settings->Node "
|
|
|
|
"and enter a node manually.";
|
|
|
|
QMessageBox::warning(this, "Could not connect to a node", msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::showWSNodeExhaustedMessage() {
|
|
|
|
auto msg = "Feather is in 'automatic node connection mode' but the "
|
2020-10-10 02:01:25 +00:00
|
|
|
"websocket server returned no available nodes. Please go to Settings->Node "
|
2020-10-07 10:36:04 +00:00
|
|
|
"and enter a node manually.";
|
|
|
|
QMessageBox::warning(this, "Could not connect to a node", msg);
|
|
|
|
}
|
|
|
|
|
2020-10-14 18:18:25 +00:00
|
|
|
void MainWindow::exportKeyImages() {
|
|
|
|
QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QDir::homePath(), "Key Images (*_keyImages)");
|
|
|
|
if (fn.isEmpty()) return;
|
|
|
|
if (!fn.endsWith("_keyImages")) fn += "_keyImages";
|
|
|
|
m_ctx->currentWallet->exportKeyImages(fn, true);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
if (!err.isEmpty()) {
|
|
|
|
QMessageBox::warning(this, "Key image export", QString("Failed to export key images.\nReason: %1").arg(err));
|
|
|
|
} 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;
|
|
|
|
m_ctx->currentWallet->importKeyImages(fn);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
if (!err.isEmpty()) {
|
|
|
|
QMessageBox::warning(this, "Key image import", QString("Failed to import key images.\n\n%1").arg(err));
|
|
|
|
} else {
|
|
|
|
QMessageBox::information(this, "Key image import", "Successfully imported key images");
|
|
|
|
m_ctx->refreshModels();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::exportOutputs() {
|
|
|
|
QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QDir::homePath(), "Outputs (*_outputs)");
|
|
|
|
if (fn.isEmpty()) return;
|
|
|
|
if (!fn.endsWith("_outputs")) fn += "_outputs";
|
|
|
|
m_ctx->currentWallet->exportOutputs(fn, true);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
if (!err.isEmpty()) {
|
|
|
|
QMessageBox::warning(this, "Outputs export", QString("Failed to export outputs.\nReason: %1").arg(err));
|
|
|
|
} 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;
|
|
|
|
m_ctx->currentWallet->importOutputs(fn);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
if (!err.isEmpty()) {
|
|
|
|
QMessageBox::warning(this, "Outputs import", QString("Failed to import outputs.\n\n%1").arg(err));
|
|
|
|
} else {
|
|
|
|
QMessageBox::information(this, "Outputs import", "Successfully imported outputs");
|
|
|
|
m_ctx->refreshModels();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-16 03:15:46 +00:00
|
|
|
void MainWindow::cleanupBeforeClose() {
|
2021-01-19 22:39:31 +00:00
|
|
|
m_ctx->closeWallet(false, true);
|
2020-10-16 03:15:46 +00:00
|
|
|
m_ctx->tor->stop();
|
|
|
|
|
|
|
|
this->saveGeo();
|
|
|
|
}
|
|
|
|
|
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_ctx->currentWallet->loadTxFile(fn);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
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_ctx->currentWallet->loadTxFromBase64Str(unsigned_tx);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
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_ctx->currentWallet->loadSignedTxFile(fn);
|
|
|
|
auto err = m_ctx->currentWallet->errorString();
|
|
|
|
if (!err.isEmpty()) {
|
|
|
|
QMessageBox::warning(this, "Load signed transaction from file", err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto *dialog = new TxConfAdvDialog(m_ctx, "", this);
|
|
|
|
dialog->setTransaction(tx);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::loadSignedTxFromText() {
|
|
|
|
auto dialog = new BroadcastTxDialog(this, m_ctx);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
|
|
|
|
auto *dialog = new TxConfAdvDialog(m_ctx, "", this);
|
|
|
|
dialog->setUnsignedTransaction(tx);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
2020-11-10 11:38:37 +00:00
|
|
|
void MainWindow::importTransaction() {
|
|
|
|
|
|
|
|
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) {
|
|
|
|
auto *dialog = new TxImportDialog(this, m_ctx);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
void MainWindow::updateNetStats() {
|
2021-01-19 22:39:31 +00:00
|
|
|
if (m_ctx->currentWallet == nullptr) {
|
2020-11-23 16:57:38 +00:00
|
|
|
m_statusLabelNetStats->setText("");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Connected && m_ctx->currentWallet->synchronized()) {
|
|
|
|
m_statusLabelNetStats->setText("");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesReceived())));
|
|
|
|
}
|
|
|
|
|
2020-12-14 19:44:37 +00:00
|
|
|
void MainWindow::rescanSpent() {
|
|
|
|
if (!m_ctx->currentWallet->rescanSpent()) {
|
|
|
|
QMessageBox::warning(this, "Rescan spent", m_ctx->currentWallet->errorString());
|
|
|
|
} else {
|
|
|
|
QMessageBox::information(this, "Rescan spent", "Successfully rescanned spent outputs.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
void MainWindow::showBalanceDialog() {
|
|
|
|
if (!m_ctx->currentWallet) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto *dialog = new BalanceDialog(this, m_ctx->currentWallet);
|
|
|
|
dialog->exec();
|
|
|
|
dialog->deleteLater();
|
|
|
|
}
|
|
|
|
|
2020-12-24 14:46:56 +00:00
|
|
|
QString MainWindow::statusDots() {
|
|
|
|
m_statusDots++;
|
|
|
|
m_statusDots = m_statusDots % 4;
|
|
|
|
return QString(".").repeated(m_statusDots);
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
MainWindow::~MainWindow() {
|
|
|
|
delete ui;
|
|
|
|
}
|