Merge pull request #123 from feather-wallet/docs_browser

Documentation browser, improved error messages
This commit is contained in:
tobtoht 2023-10-03 21:07:33 +02:00 committed by GitHub
commit 3aae9ddcda
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
88 changed files with 1423 additions and 713 deletions

3
.gitignore vendored
View file

@ -13,9 +13,12 @@ feather_autogen/
feather.cbp feather.cbp
src/config-feather.h src/config-feather.h
src/assets_tor.qrc src/assets_tor.qrc
src/assets_docs.qrc
feather.AppDir/* feather.AppDir/*
src/assets/tor/* src/assets/tor/*
src/assets/docs/*
!src/assets/tor/.gitkeep !src/assets/tor/.gitkeep
!src/assets/docs/.gitkeep
contrib/installers/windows/setup.nsi contrib/installers/windows/setup.nsi
githash.txt githash.txt
guix/guix-build-* guix/guix-build-*

3
.gitmodules vendored
View file

@ -7,3 +7,6 @@
[submodule "src/third-party/polyseed"] [submodule "src/third-party/polyseed"]
path = src/third-party/polyseed path = src/third-party/polyseed
url = https://github.com/tevador/polyseed.git url = https://github.com/tevador/polyseed.git
[submodule "external/feather-docs"]
path = external/feather-docs
url = https://github.com/feather-wallet/feather-docs.git

View file

@ -145,6 +145,7 @@ if(UNIX AND NOT APPLE)
endif() endif()
endif() endif()
include(GenerateDocs)
include(TorQrcGenerator) include(TorQrcGenerator)
# To build Feather with embedded (and static) Tor, pass CMake -DTOR_DIR=/path/to/tor/ # To build Feather with embedded (and static) Tor, pass CMake -DTOR_DIR=/path/to/tor/

20
cmake/GenerateDocs.cmake Normal file
View file

@ -0,0 +1,20 @@
message(STATUS "Generating docs")
find_package(Python3 COMPONENTS Interpreter)
if(Python3_Interpreter_FOUND)
execute_process(COMMAND python3 contrib/docs/generate.py
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
FILE(GLOB DOCS LIST_DIRECTORIES false "src/assets/docs/*")
foreach(FILE ${DOCS})
cmake_path(GET FILE FILENAME FILE_REL)
list(APPEND QRC_LIST " <file alias=\"${FILE_REL}\">${FILE}</file>")
endforeach()
list(JOIN QRC_LIST "\n" QRC_DATA)
configure_file("cmake/assets_docs.qrc" "${CMAKE_CURRENT_SOURCE_DIR}/src/assets_docs.qrc")
else()
message(WARNING "No Python3 interpreter, skipping docs.")
endif()

5
cmake/assets_docs.qrc Normal file
View file

@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="/docs">
@QRC_DATA@
</qresource>
</RCC>

63
contrib/docs/generate.py Normal file
View file

@ -0,0 +1,63 @@
import os
import glob
DOCS_DIR = "external/feather-docs/content/_guides"
OUT_DIR = "src/assets/docs"
CATEGORY_MAP = {
"getting-started": "1. Getting started",
"howto": "2. How to",
"faq": "3. Faq",
"advanced": "4. Advanced",
"troubleshooting": "5. Troubleshooting",
"help": "6. Help",
}
if not os.path.isdir(DOCS_DIR):
print("feather-docs submodule not found. Run `git submodule update --init --recursive`")
exit(1)
outfiles = glob.glob(f"{OUT_DIR}/*.md")
for file in outfiles:
os.remove(file)
files = glob.glob(f"{DOCS_DIR}/*.md")
for file in files:
with open(file) as f:
doc = f.read()
if not doc:
continue
# yaml frontmatter missing
if doc.count("---") < 2:
continue
_, front, body = doc.split("---", 2)
front = {x: y.strip(" \"") for (x, y) in [x.split(':', 1) for x in front.splitlines()[1:]]}
if not all((x in front) for x in ['category', 'nav_title']):
continue
if front['category'] not in CATEGORY_MAP:
continue
title = front['nav_title'].replace("(", "\\(").replace(")", "\\)")
# We use this format to insert metadata while preventing it from showing up in QTextBrowser
# This is easier than adding item tags to .qrc files and parsing the XML
# We need to be able to setSource on a resource directly, otherwise history doesn't work
docString = f"""
[nav_title]: # ({title})
[category]: # ({CATEGORY_MAP[front['category']]})
## {front['title']}
{body}
"""
_, filename = file.rsplit('/', 1)
with open(f"{OUT_DIR}/{filename}", 'w') as f:
print(filename)
f.write(docString)

1
external/feather-docs vendored Submodule

@ -0,0 +1 @@
Subproject commit 02da18df08dc29e5debb5f08ad5c2f56f150bd8d

2
monero

@ -1 +1 @@
Subproject commit ecca08f4c0b1e5a012cdd20110b015c540bce0c6 Subproject commit 4fbdcd093828f6252e4c6fe10631b829ee6f46dc

View file

@ -29,7 +29,7 @@ if (CHECK_UPDATES)
add_subdirectory(openpgp) add_subdirectory(openpgp)
endif() endif()
qt_add_resources(RESOURCES assets.qrc assets_tor.qrc) qt_add_resources(RESOURCES assets.qrc assets_tor.qrc assets_docs.qrc)
# Compile source files (.h/.cpp) # Compile source files (.h/.cpp)
file(GLOB SOURCE_FILES file(GLOB SOURCE_FILES

View file

@ -101,7 +101,7 @@ void CalcWidget::initComboBox() {
QList<QString> cryptoKeys = appData()->prices.markets.keys(); QList<QString> cryptoKeys = appData()->prices.markets.keys();
QList<QString> fiatKeys = appData()->prices.rates.keys(); QList<QString> fiatKeys = appData()->prices.rates.keys();
QStringList enabledCrypto = config()->get(Config::cryptoSymbols).toStringList(); QStringList enabledCrypto = conf()->get(Config::cryptoSymbols).toStringList();
QStringList filteredCryptoKeys; QStringList filteredCryptoKeys;
for (const auto& symbol : cryptoKeys) { for (const auto& symbol : cryptoKeys) {
if (enabledCrypto.contains(symbol)) { if (enabledCrypto.contains(symbol)) {
@ -109,11 +109,11 @@ void CalcWidget::initComboBox() {
} }
} }
QStringList enabledFiat = config()->get(Config::fiatSymbols).toStringList(); QStringList enabledFiat = conf()->get(Config::fiatSymbols).toStringList();
auto preferredFiat = config()->get(Config::preferredFiatCurrency).toString(); auto preferredFiat = conf()->get(Config::preferredFiatCurrency).toString();
if (!enabledFiat.contains(preferredFiat) && fiatKeys.contains(preferredFiat)) { if (!enabledFiat.contains(preferredFiat) && fiatKeys.contains(preferredFiat)) {
enabledFiat.append(preferredFiat); enabledFiat.append(preferredFiat);
config()->set(Config::fiatSymbols, enabledFiat); conf()->set(Config::fiatSymbols, enabledFiat);
} }
QStringList filteredFiatKeys; QStringList filteredFiatKeys;
for (const auto &symbol : fiatKeys) { for (const auto &symbol : fiatKeys) {

View file

@ -131,13 +131,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>

View file

@ -211,24 +211,22 @@ void CoinsWidget::onSweepOutputs() {
QString keyImage = coin->keyImage(); QString keyImage = coin->keyImage();
if (!coin->keyImageKnown()) { if (!coin->keyImageKnown()) {
QMessageBox::warning(this, "Unable to sweep outputs", "Unable to create transaction: selected output has unknown key image"); Utils::showError(this, "Unable to sweep outputs", "Selected output has unknown key image");
return; return;
} }
if (coin->spent()) { if (coin->spent()) {
QMessageBox::warning(this, "Unable to sweep outputs", "Unable to create transaction: selected output was already spent"); Utils::showError(this, "Unable to sweep outputs", "Selected output was already spent");
return; return;
} }
if (coin->frozen()) { if (coin->frozen()) {
QMessageBox::warning(this, "Unable to sweep outputs", "Unable to create transaction: selected output is frozen.\n\n" Utils::showError(this, "Unable to sweep outputs", "Selected output is frozen", {"Thaw the selected output(s) before spending"}, "freeze_thaw_outputs");
"Thaw the selected output(s) before spending.");
return; return;
} }
if (!coin->unlocked()) { if (!coin->unlocked()) {
QMessageBox::warning(this, "Unable to sweep outputs", "Unable to create transaction: selected output is locked.\n\n" Utils::showError(this, "Unable to sweep outputs", "Selected output is locked", {"Wait until the output has reached the required number of confirmation before spending."});
"Wait until the output has reached the required number of confirmation before spending.");
return; return;
} }

View file

@ -127,7 +127,7 @@ void ContactsWidget::newContact(QString address, QString name)
bool addressValid = WalletManager::addressValid(address, m_wallet->nettype()); bool addressValid = WalletManager::addressValid(address, m_wallet->nettype());
if (!addressValid) { if (!addressValid) {
QMessageBox::warning(this, "Invalid address", "Invalid address"); Utils::showError(this, "Unable to add contact", "Invalid address", {"Use 'Tools -> Address checker' to check if the address is valid."}, "add_contact");
return; return;
} }
@ -141,12 +141,12 @@ void ContactsWidget::newContact(QString address, QString name)
}); });
if (address == address_entry) { if (address == address_entry) {
QMessageBox::warning(this, "Unable to add contact", "Duplicate address"); Utils::showError(this, "Unable to add contact", "Address already exists in contacts", {}, "add_contact");
ui->contacts->setCurrentIndex(m_model->index(i,0)); // Highlight duplicate address ui->contacts->setCurrentIndex(m_model->index(i,0)); // Highlight duplicate address
return; return;
} }
if (name == name_entry) { if (name == name_entry) {
QMessageBox::warning(this, "Unable to add contact", "Duplicate label"); Utils::showError(this, "Unable to add contact", "Label already exists in contacts", {}, "add_contact");
this->newContact(address, name); this->newContact(address, name);
return; return;
} }

View file

@ -45,17 +45,17 @@ HistoryWidget::HistoryWidget(Wallet *wallet, QWidget *parent)
connect(ui->btn_moreInfo, &QPushButton::clicked, this, &HistoryWidget::showSyncNoticeMsg); connect(ui->btn_moreInfo, &QPushButton::clicked, this, &HistoryWidget::showSyncNoticeMsg);
connect(ui->btn_close, &QPushButton::clicked, [this]{ connect(ui->btn_close, &QPushButton::clicked, [this]{
config()->set(Config::showHistorySyncNotice, false); conf()->set(Config::showHistorySyncNotice, false);
ui->syncNotice->hide(); ui->syncNotice->hide();
}); });
connect(m_wallet, &Wallet::walletRefreshed, this, &HistoryWidget::onWalletRefreshed); connect(m_wallet, &Wallet::walletRefreshed, this, &HistoryWidget::onWalletRefreshed);
ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool()); ui->syncNotice->setVisible(conf()->get(Config::showHistorySyncNotice).toBool());
ui->history->setHistoryModel(m_model); ui->history->setHistoryModel(m_model);
// Load view state // Load view state
QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()); QByteArray historyViewState = QByteArray::fromBase64(conf()->get(Config::GUI_HistoryViewState).toByteArray());
if (!historyViewState.isEmpty()) { if (!historyViewState.isEmpty()) {
ui->history->setViewState(historyViewState); ui->history->setViewState(historyViewState);
} }
@ -109,8 +109,8 @@ void HistoryWidget::onResendTransaction() {
void HistoryWidget::resetModel() void HistoryWidget::resetModel()
{ {
// Save view state // Save view state
config()->set(Config::GUI_HistoryViewState, ui->history->viewState().toBase64()); conf()->set(Config::GUI_HistoryViewState, ui->history->viewState().toBase64());
config()->sync(); conf()->sync();
ui->history->setModel(nullptr); ui->history->setModel(nullptr);
} }
@ -164,8 +164,8 @@ void HistoryWidget::copy(copyField field) {
case copyField::Description: case copyField::Description:
return tx->description(); return tx->description();
case copyField::Date: case copyField::Date:
return tx->timestamp().toString(QString("%1 %2").arg(config()->get(Config::dateFormat).toString(), return tx->timestamp().toString(QString("%1 %2").arg(conf()->get(Config::dateFormat).toString(),
config()->get(Config::timeFormat).toString())); conf()->get(Config::timeFormat).toString()));
case copyField::Amount: case copyField::Amount:
return WalletManager::displayAmount(tx->balanceDelta()); return WalletManager::displayAmount(tx->balanceDelta());
default: default:

View file

@ -32,6 +32,8 @@
#include "utils/TorManager.h" #include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h" #include "utils/WebsocketNotifier.h"
#include "wallet/wallet_errors.h"
#ifdef CHECK_UPDATES #ifdef CHECK_UPDATES
#include "utils/updater/UpdateDialog.h" #include "utils/updater/UpdateDialog.h"
#endif #endif
@ -82,7 +84,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
websocketNotifier()->emitCache(); // Get cached data websocketNotifier()->emitCache(); // Get cached data
connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged); connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
this->onWebsocketStatusChanged(!config()->get(Config::disableWebsocket).toBool()); this->onWebsocketStatusChanged(!conf()->get(Config::disableWebsocket).toBool());
connect(m_windowManager, &WindowManager::proxySettingsChanged, this, &MainWindow::onProxySettingsChanged); connect(m_windowManager, &WindowManager::proxySettingsChanged, this, &MainWindow::onProxySettingsChanged);
connect(m_windowManager, &WindowManager::updateBalance, m_wallet, &Wallet::updateBalance); connect(m_windowManager, &WindowManager::updateBalance, m_wallet, &Wallet::updateBalance);
@ -104,7 +106,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
m_statusLabelStatus->setText("Constructing transaction" + this->statusDots()); m_statusLabelStatus->setText("Constructing transaction" + this->statusDots());
}); });
config()->set(Config::firstRun, false); conf()->set(Config::firstRun, false);
this->onWalletOpened(); this->onWalletOpened();
@ -183,7 +185,7 @@ void MainWindow::initStatusBar() {
} }
void MainWindow::initWidgets() { void MainWindow::initWidgets() {
int homeWidget = config()->get(Config::homeWidget).toInt(); int homeWidget = conf()->get(Config::homeWidget).toInt();
ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget)); ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget));
// [History] // [History]
@ -295,7 +297,7 @@ void MainWindow::initMenu() {
// [View] // [View]
m_tabShowHideSignalMapper = new QSignalMapper(this); m_tabShowHideSignalMapper = new QSignalMapper(this);
connect(ui->actionShow_Searchbar, &QAction::toggled, this, &MainWindow::toggleSearchbar); connect(ui->actionShow_Searchbar, &QAction::toggled, this, &MainWindow::toggleSearchbar);
ui->actionShow_Searchbar->setChecked(config()->get(Config::showSearchbar).toBool()); ui->actionShow_Searchbar->setChecked(conf()->get(Config::showSearchbar).toBool());
// Show/Hide Home // Show/Hide Home
connect(ui->actionShow_Home, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map)); connect(ui->actionShow_Home, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map));
@ -333,7 +335,7 @@ void MainWindow::initMenu() {
for (const auto &key: m_tabShowHideMapper.keys()) { for (const auto &key: m_tabShowHideMapper.keys()) {
const auto toggleTab = m_tabShowHideMapper.value(key); const auto toggleTab = m_tabShowHideMapper.value(key);
const bool show = config()->get(toggleTab->configKey).toBool(); const bool show = conf()->get(toggleTab->configKey).toBool();
toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name); toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show); ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
} }
@ -416,11 +418,9 @@ void MainWindow::initWalletContext() {
connect(m_wallet, &Wallet::synchronized, this, &MainWindow::onSynchronized); //TODO connect(m_wallet, &Wallet::synchronized, this, &MainWindow::onSynchronized); //TODO
connect(m_wallet, &Wallet::blockchainSync, this, &MainWindow::onBlockchainSync); connect(m_wallet, &Wallet::blockchainSync, this, &MainWindow::onBlockchainSync);
connect(m_wallet, &Wallet::refreshSync, this, &MainWindow::onRefreshSync); connect(m_wallet, &Wallet::refreshSync, this, &MainWindow::onRefreshSync);
connect(m_wallet, &Wallet::createTransactionError, this, &MainWindow::onCreateTransactionError); connect(m_wallet, &Wallet::transactionCreated, this, &MainWindow::onTransactionCreated);
connect(m_wallet, &Wallet::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess);
connect(m_wallet, &Wallet::transactionCommitted, this, &MainWindow::onTransactionCommitted); connect(m_wallet, &Wallet::transactionCommitted, this, &MainWindow::onTransactionCommitted);
connect(m_wallet, &Wallet::initiateTransaction, this, &MainWindow::onInitiateTransaction); 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::keysCorrupted, this, &MainWindow::onKeysCorrupted);
connect(m_wallet, &Wallet::selectedInputsChanged, this, &MainWindow::onSelectedInputsChanged); connect(m_wallet, &Wallet::selectedInputsChanged, this, &MainWindow::onSelectedInputsChanged);
@ -446,7 +446,7 @@ void MainWindow::initWalletContext() {
connect(m_wallet, &Wallet::deviceError, this, &MainWindow::onDeviceError); connect(m_wallet, &Wallet::deviceError, this, &MainWindow::onDeviceError);
connect(m_wallet, &Wallet::donationSent, this, []{ connect(m_wallet, &Wallet::donationSent, this, []{
config()->set(Config::donateBeg, -1); conf()->set(Config::donateBeg, -1);
}); });
connect(m_wallet, &Wallet::multiBroadcast, this, &MainWindow::onMultiBroadcast); connect(m_wallet, &Wallet::multiBroadcast, this, &MainWindow::onMultiBroadcast);
@ -454,15 +454,15 @@ void MainWindow::initWalletContext() {
void MainWindow::menuToggleTabVisible(const QString &key){ void MainWindow::menuToggleTabVisible(const QString &key){
const auto toggleTab = m_tabShowHideMapper[key]; const auto toggleTab = m_tabShowHideMapper[key];
bool show = config()->get(toggleTab->configKey).toBool(); bool show = conf()->get(toggleTab->configKey).toBool();
show = !show; show = !show;
config()->set(toggleTab->configKey, show); conf()->set(toggleTab->configKey, show);
ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show); ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name); toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
} }
void MainWindow::menuClearHistoryClicked() { void MainWindow::menuClearHistoryClicked() {
config()->remove(Config::recentlyOpenedWallets); conf()->remove(Config::recentlyOpenedWallets);
this->updateRecentlyOpenedMenu(); this->updateRecentlyOpenedMenu();
} }
@ -478,30 +478,6 @@ QString MainWindow::walletKeysPath() {
return m_wallet->keysPath(); return m_wallet->keysPath();
} }
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: upgrade your Ledger device firmware to the latest version using Ledger Live.\n"
"Then upgrade the Monero app for the Ledger device to the latest version.";
}
else if (errMsg.contains("Wrong Device Status")) {
errMsg += "\n\nThe device may need to be unlocked.";
}
else if (errMsg.contains("Wrong Channel")) {
errMsg += "\n\nRestart the hardware device and try again.";
}
QMessageBox::warning(this, "Wallet error", errMsg);
}
void MainWindow::onWalletOpened() { void MainWindow::onWalletOpened() {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
m_splashDialog->hide(); m_splashDialog->hide();
@ -548,15 +524,15 @@ void MainWindow::onWalletOpened() {
m_nodes->connectToNode(); m_nodes->connectToNode();
m_updateBytes.start(250); m_updateBytes.start(250);
if (config()->get(Config::writeRecentlyOpenedWallets).toBool()) { if (conf()->get(Config::writeRecentlyOpenedWallets).toBool()) {
this->addToRecentlyOpened(m_wallet->cachePath()); this->addToRecentlyOpened(m_wallet->cachePath());
} }
} }
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) { void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
bool hide = config()->get(Config::hideBalance).toBool(); bool hide = conf()->get(Config::hideBalance).toBool();
int displaySetting = config()->get(Config::balanceDisplay).toInt(); int displaySetting = conf()->get(Config::balanceDisplay).toInt();
int decimals = config()->get(Config::amountPrecision).toInt(); int decimals = conf()->get(Config::amountPrecision).toInt();
QString balance_str = "Balance: "; QString balance_str = "Balance: ";
if (hide) { if (hide) {
@ -598,8 +574,7 @@ void MainWindow::setStatusText(const QString &text, bool override, int timeout)
void MainWindow::tryStoreWallet() { void MainWindow::tryStoreWallet() {
if (m_wallet->connectionStatus() == Wallet::ConnectionStatus::ConnectionStatus_Synchronizing) { if (m_wallet->connectionStatus() == Wallet::ConnectionStatus::ConnectionStatus_Synchronizing) {
QMessageBox::warning(this, "Save wallet", "Unable to save wallet during synchronization.\n\n" Utils::showError(this, "Unable to save wallet", "Can't save wallet during synchronization", {"Wait until synchronization is finished and try again"}, "synchronization");
"Wait until synchronization is finished and try again.");
return; return;
} }
@ -611,9 +586,9 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
ui->actionShow_calc->setVisible(enabled); ui->actionShow_calc->setVisible(enabled);
ui->actionShow_Exchange->setVisible(enabled); ui->actionShow_Exchange->setVisible(enabled);
ui->tabWidget->setTabVisible(Tabs::HOME, enabled && config()->get(Config::showTabHome).toBool()); ui->tabWidget->setTabVisible(Tabs::HOME, enabled && conf()->get(Config::showTabHome).toBool());
ui->tabWidget->setTabVisible(Tabs::CALC, enabled && config()->get(Config::showTabCalc).toBool()); ui->tabWidget->setTabVisible(Tabs::CALC, enabled && conf()->get(Config::showTabCalc).toBool());
ui->tabWidget->setTabVisible(Tabs::EXCHANGES, enabled && config()->get(Config::showTabExchange).toBool()); ui->tabWidget->setTabVisible(Tabs::EXCHANGES, enabled && conf()->get(Config::showTabExchange).toBool());
m_historyWidget->setWebsocketEnabled(enabled); m_historyWidget->setWebsocketEnabled(enabled);
m_sendWidget->setWebsocketEnabled(enabled); m_sendWidget->setWebsocketEnabled(enabled);
@ -626,7 +601,7 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
void MainWindow::onProxySettingsChanged() { void MainWindow::onProxySettingsChanged() {
m_nodes->connectToNode(); m_nodes->connectToNode();
int proxy = config()->get(Config::proxy).toInt(); int proxy = conf()->get(Config::proxy).toInt();
if (proxy == Config::Proxy::Tor) { if (proxy == Config::Proxy::Tor) {
this->onTorConnectionStateChanged(torManager()->torConnected); this->onTorConnectionStateChanged(torManager()->torConnected);
@ -688,7 +663,7 @@ void MainWindow::onConnectionStatusChanged(int status)
// Update connection info in status bar. // Update connection info in status bar.
QIcon icon; QIcon icon;
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
icon = icons()->icon("status_offline.svg"); icon = icons()->icon("status_offline.svg");
this->setStatusText("Offline"); this->setStatusText("Offline");
} else { } else {
@ -720,41 +695,123 @@ void MainWindow::onConnectionStatusChanged(int status)
m_statusBtnConnectionStatusIndicator->setIcon(icon); m_statusBtnConnectionStatusIndicator->setIcon(icon);
} }
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) { void MainWindow::onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address) {
QString err{"Can't create transaction: "}; // Clean up some UI
m_constructingTransaction = false;
m_txTimer.stop();
this->setStatusText(m_statusText);
if (m_wallet->isHwBacked()) {
m_splashDialog->hide();
}
if (tx->status() != PendingTransaction::Status_Ok) { if (tx->status() != PendingTransaction::Status_Ok) {
QString tx_err = tx->errorString(); QString errMsg = tx->errorString();
qCritical() << tx_err;
if (m_wallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion) Utils::Message message{this, Utils::ERROR, "Failed to construct transaction", errMsg};
err = QString("%1 Wrong node version: %2").arg(err, tx_err);
else
err = QString("%1 %2").arg(err, tx_err);
if (tx_err.contains("Node response did not include the requested real output")) { if (tx->getException()) {
QString currentNode = m_nodes->connection().toAddress(); try
{
std::rethrow_exception(tx->getException());
}
catch (const tools::error::daemon_busy &e) {
message.description = QString("Node was unable to respond. Failed request: %1").arg(QString::fromStdString(e.request()));
message.helpItems = {"Try sending the transaction again.", "If this keeps happening, connect to a different node."};
}
catch (const tools::error::no_connection_to_daemon &e) {
message.description = QString("Connection to node lost. Failed request: %1").arg(QString::fromStdString(e.request()));
message.helpItems = {"Try sending the transaction again.", "If this keeps happening, connect to a different node."};
}
catch (const tools::error::wallet_rpc_error &e) {
message.description = QString("RPC error: %1").arg(QString::fromStdString(e.to_string()));
message.helpItems = {"Try sending the transaction again.", "If this keeps happening, connect to a different node."};
}
catch (const tools::error::get_outs_error &e) {
message.description = "Failed to get enough decoy outputs from node";
message.helpItems = {"Your transaction has too many inputs. Try sending a lower amount."};
}
catch (const tools::error::not_enough_unlocked_money &e) {
message.description = QString("Not enough unlocked balance.\n\nUnlocked balance: %1\nTransaction spends: %2").arg(e.available(), e.tx_amount());
message.helpItems = {"Wait for more balance to unlock.", "Click 'Help' to learn more about how balance works."};
message.doc = "balance";
}
catch (const tools::error::not_enough_money &e) {
message.description = QString("Not enough money to transfer\n\nTotal balance: %1\nTransaction amount: %2").arg(WalletManager::displayAmount(e.available()), WalletManager::displayAmount(e.tx_amount()));
message.helpItems = {"If you are trying to send your entire balance, click 'Max'."};
message.doc = "balance";
}
catch (const tools::error::tx_not_possible &e) {
message.description = QString("Not enough money to transfer. Transaction amount + fee exceeds available balance.");
message.helpItems = {"If you're trying to send your entire balance, click 'Max'."};
message.doc = "balance";
}
catch (const tools::error::not_enough_outs_to_mix &e) {
message.description = "Not enough outputs for specified ring size.";
}
catch (const tools::error::tx_not_constructed&) {
message.description = "Transaction was not constructed";
message.helpItems = {"You have found a bug. Please contact the developers."};
message.doc = "report_an_issue";
}
catch (const tools::error::tx_rejected &e) {
// TODO: provide helptext
message.description = QString("Transaction was rejected by node. Reason: %1.").arg(QString::fromStdString(e.status()));
}
catch (const tools::error::tx_sum_overflow &e) {
message.description = "Transaction tries to spend an unrealistic amount of XMR";
message.helpItems = {"You have found a bug. Please contact the developers."};
message.doc = "report_an_issue";
}
catch (const tools::error::zero_amount&) {
message.description = "Destination amount is zero";
message.helpItems = {"You have found a bug. Please contact the developers."};
message.doc = "report_an_issue";
}
catch (const tools::error::zero_destination&) {
message.description = "Transaction has no destination";
message.helpItems = {"You have found a bug. Please contact the developers."};
message.doc = "report_an_issue";
}
catch (const tools::error::tx_too_big &e) {
message.description = "Transaction too big";
message.helpItems = {"Try sending a smaller amount."};
}
catch (const tools::error::transfer_error &e) {
message.description = QString("Unknown transfer error: %1").arg(QString::fromStdString(e.what()));
message.helpItems = {"You have found a bug. Please contact the developers."};
message.doc = "report_an_issue";
}
catch (const tools::error::wallet_internal_error &e) {
QString msg = e.what();
message.description = QString("Internal error: %1").arg(QString::fromStdString(e.what()));
if (msg.contains("Daemon response did not include the requested real output")) {
QString currentNode = m_nodes->connection().toAddress();
message.description += 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 the developers.").arg(currentNode);
message.doc = "report_an_issue";
}
err += QString("\nYou are currently connected to: %1\n\n" message.helpItems = {"You have found a bug. Please contact the developers."};
"This node may be acting maliciously. You are strongly recommended to disconnect from this node." message.doc = "report_an_issue";
"Please report this incident to dev@featherwallet.org, #feather on OFTC or /r/FeatherWallet.").arg(currentNode); }
catch (const std::exception &e) {
message.description = QString::fromStdString(e.what());
}
} }
qDebug() << Q_FUNC_INFO << err; Utils::showMsg(message);
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx); m_wallet->disposeTransaction(tx);
return; return;
} }
else if (tx->txCount() == 0) { else if (tx->txCount() == 0) {
err = QString("%1 %2").arg(err, "No unmixable outputs to sweep."); Utils::showError(this, "Failed to construct transaction", "No transactions were constructed", {"You have found a bug. Please contact the developers."}, "report_an_issue");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx); m_wallet->disposeTransaction(tx);
return; return;
} }
else if (tx->txCount() > 1) { else if (tx->txCount() > 1) {
err = QString("%1 %2").arg(err, "Split transactions are not supported. Try sending a smaller amount."); Utils::showError(this, "Failed to construct transaction", "Split transactions are not supported", {"Try sending a smaller amount."});
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx); m_wallet->disposeTransaction(tx);
return; return;
} }
@ -773,9 +830,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
destAddresses.insert(WalletManager::baseAddressFromIntegratedAddress(addr, constants::networkType)); destAddresses.insert(WalletManager::baseAddressFromIntegratedAddress(addr, constants::networkType));
} }
if (!outputAddresses.contains(destAddresses)) { 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."); Utils::showError(this, "Transaction fails sanity check", "Constructed transaction doesn't appear to send to (all) specified destination address(es). Try creating the transaction again.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
m_wallet->disposeTransaction(tx); m_wallet->disposeTransaction(tx);
return; return;
} }
@ -812,40 +867,29 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
} }
void MainWindow::onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid) { void MainWindow::onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid) {
if (success) { if (!success) {
QMessageBox msgBox{this}; Utils::showError(this, "Failed to send transaction", tx->errorString());
QPushButton *showDetailsButton = msgBox.addButton("Show details", QMessageBox::ActionRole); return;
msgBox.addButton(QMessageBox::Ok);
QString body = QString("Successfully sent %1 transaction(s).").arg(txid.count());
msgBox.setText(body);
msgBox.setWindowTitle("Transaction sent");
msgBox.setIcon(QMessageBox::Icon::Information);
msgBox.exec();
if (msgBox.clickedButton() == showDetailsButton) {
this->showHistoryTab();
TransactionInfo *txInfo = m_wallet->history()->transaction(txid.first());
auto *dialog = new TxInfoDialog(m_wallet, txInfo, this);
connect(dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction);
dialog->show();
dialog->setAttribute(Qt::WA_DeleteOnClose);
}
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); QMessageBox msgBox{this};
QPushButton *showDetailsButton = msgBox.addButton("Show details", QMessageBox::ActionRole);
msgBox.addButton(QMessageBox::Ok);
QString body = QString("Successfully sent %1 transaction(s).").arg(txid.count());
msgBox.setText(body);
msgBox.setWindowTitle("Transaction sent");
msgBox.setIcon(QMessageBox::Icon::Information);
msgBox.exec();
if (msgBox.clickedButton() == showDetailsButton) {
this->showHistoryTab();
TransactionInfo *txInfo = m_wallet->history()->transaction(txid.first());
auto *dialog = new TxInfoDialog(m_wallet, txInfo, this);
connect(dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction);
dialog->show();
dialog->setAttribute(Qt::WA_DeleteOnClose);
}
m_sendWidget->clearFields();
} }
void MainWindow::showWalletInfoDialog() { void MainWindow::showWalletInfoDialog() {
@ -855,17 +899,18 @@ void MainWindow::showWalletInfoDialog() {
void MainWindow::showSeedDialog() { void MainWindow::showSeedDialog() {
if (m_wallet->isHwBacked()) { if (m_wallet->isHwBacked()) {
QMessageBox::information(this, "Information", "Seed unavailable: Wallet keys are stored on hardware device."); Utils::showInfo(this, "Seed unavailable", "Wallet keys are stored on a hardware device", {}, "show_wallet_seed");
return; return;
} }
if (m_wallet->viewOnly()) { if (m_wallet->viewOnly()) {
QMessageBox::information(this, "Information", "Wallet is view-only and has no seed.\n\nTo obtain wallet keys go to Wallet -> View-Only"); Utils::showInfo(this, "Seed unavailable", "Wallet is view-only", {"To obtain your private spendkey go to Wallet -> Keys"}, "show_wallet_seed");
return; return;
} }
if (!m_wallet->isDeterministic()) { if (!m_wallet->isDeterministic()) {
QMessageBox::information(this, "Information", "Wallet is non-deterministic and has no seed.\n\nTo obtain wallet keys go to Wallet -> Keys"); Utils::showInfo(this, "Seed unavailable", "Wallet is non-deterministic and has no seed",
{"To obtain wallet keys go to Wallet -> Keys"}, "show_wallet_seed");
return; return;
} }
@ -904,7 +949,7 @@ void MainWindow::showViewOnlyDialog() {
} }
void MainWindow::menuHwDeviceClicked() { void MainWindow::menuHwDeviceClicked() {
QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice())); Utils::showInfo(this, "Hardware device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
} }
void MainWindow::menuOpenClicked() { void MainWindow::menuOpenClicked() {
@ -948,7 +993,7 @@ void MainWindow::menuVerifyTxProof() {
} }
void MainWindow::onShowSettingsPage(int page) { void MainWindow::onShowSettingsPage(int page) {
config()->set(Config::lastSettingsPage, page); conf()->set(Config::lastSettingsPage, page);
this->menuSettingsClicked(); this->menuSettingsClicked();
} }
@ -992,7 +1037,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
if (!this->cleanedUp) { if (!this->cleanedUp) {
this->cleanedUp = true; this->cleanedUp = true;
config()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex()); conf()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
m_historyWidget->resetModel(); m_historyWidget->resetModel();
@ -1013,7 +1058,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
void MainWindow::changeEvent(QEvent* event) void MainWindow::changeEvent(QEvent* event)
{ {
if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) { if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) {
if (config()->get(Config::lockOnMinimize).toBool()) { if (conf()->get(Config::lockOnMinimize).toBool()) {
this->lockWallet(); this->lockWallet();
} }
} else { } else {
@ -1043,10 +1088,10 @@ void MainWindow::showCalcWindow() {
void MainWindow::payToMany() { void MainWindow::payToMany() {
ui->tabWidget->setCurrentIndex(Tabs::SEND); ui->tabWidget->setCurrentIndex(Tabs::SEND);
m_sendWidget->payToMany(); m_sendWidget->payToMany();
QMessageBox::information(this, "Pay to many", "Enter a list of outputs in the 'Pay to' field.\n" Utils::showInfo(this, "Pay to many", "Enter a list of outputs in the 'Pay to' field.\n"
"One output per line.\n" "One output per line.\n"
"Format: address, amount\n" "Format: address, amount\n"
"A maximum of 16 addresses may be specified."); "A maximum of 16 addresses may be specified.");
} }
void MainWindow::showSendScreen(const CCSEntry &entry) { // TODO: rename this function void MainWindow::showSendScreen(const CCSEntry &entry) { // TODO: rename this function
@ -1055,14 +1100,14 @@ void MainWindow::showSendScreen(const CCSEntry &entry) { // TODO: rename this fu
} }
void MainWindow::onViewOnBlockExplorer(const QString &txid) { void MainWindow::onViewOnBlockExplorer(const QString &txid) {
QString blockExplorerLink = Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, txid); QString blockExplorerLink = Utils::blockExplorerLink(conf()->get(Config::blockExplorer).toString(), constants::networkType, txid);
Utils::externalLinkWarning(this, blockExplorerLink); Utils::externalLinkWarning(this, blockExplorerLink);
} }
void MainWindow::onResendTransaction(const QString &txid) { void MainWindow::onResendTransaction(const QString &txid) {
QString txHex = m_wallet->getCacheTransaction(txid); QString txHex = m_wallet->getCacheTransaction(txid);
if (txHex.isEmpty()) { if (txHex.isEmpty()) {
QMessageBox::warning(this, "Unable to resend transaction", "Transaction was not found in transaction cache. Unable to resend."); Utils::showError(this, "Unable to resend transaction", "Transaction was not found in the transaction cache.");
return; return;
} }
@ -1089,17 +1134,17 @@ void MainWindow::importContacts() {
} }
} }
QMessageBox::information(this, "Contacts imported", QString("Total contacts imported: %1").arg(inserts)); Utils::showInfo(this, "Contacts imported", QString("Total contacts imported: %1").arg(inserts));
} }
void MainWindow::saveGeo() { void MainWindow::saveGeo() {
config()->set(Config::geometry, QString(saveGeometry().toBase64())); conf()->set(Config::geometry, QString(saveGeometry().toBase64()));
config()->set(Config::windowState, QString(saveState().toBase64())); conf()->set(Config::windowState, QString(saveState().toBase64()));
} }
void MainWindow::restoreGeo() { void MainWindow::restoreGeo() {
bool geo = this->restoreGeometry(QByteArray::fromBase64(config()->get(Config::geometry).toByteArray())); bool geo = this->restoreGeometry(QByteArray::fromBase64(conf()->get(Config::geometry).toByteArray()));
bool windowState = this->restoreState(QByteArray::fromBase64(config()->get(Config::windowState).toByteArray())); bool windowState = this->restoreState(QByteArray::fromBase64(conf()->get(Config::windowState).toByteArray()));
qDebug() << "Restored window state: " << geo << " " << windowState; qDebug() << "Restored window state: " << geo << " " << windowState;
} }
@ -1129,17 +1174,17 @@ void MainWindow::showAddressChecker() {
} }
if (!WalletManager::addressValid(address, constants::networkType)) { if (!WalletManager::addressValid(address, constants::networkType)) {
QMessageBox::warning(this, "Address Checker", "Invalid address."); Utils::showInfo(this, "Invalid address", "The address you entered is not a valid XMR address for the current network type.");
return; return;
} }
SubaddressIndex index = m_wallet->subaddressIndex(address); SubaddressIndex index = m_wallet->subaddressIndex(address);
if (!index.isValid()) { if (!index.isValid()) {
// TODO: probably mention lookahead here // TODO: probably mention lookahead here
QMessageBox::warning(this, "Address Checker", "This address does not belong to this wallet."); Utils::showInfo(this, "This address does not belong to this wallet", "");
return; return;
} else { } else {
QMessageBox::information(this, "Address Checker", QString("This address belongs to Account #%1").arg(index.major)); Utils::showInfo(this, QString("This address belongs to Account #%1").arg(index.major));
} }
} }
@ -1149,9 +1194,9 @@ void MainWindow::exportKeyImages() {
if (!fn.endsWith("_keyImages")) fn += "_keyImages"; if (!fn.endsWith("_keyImages")) fn += "_keyImages";
bool r = m_wallet->exportKeyImages(fn, true); bool r = m_wallet->exportKeyImages(fn, true);
if (!r) { if (!r) {
QMessageBox::warning(this, "Key image export", QString("Failed to export key images.\nReason: %1").arg(m_wallet->errorString())); Utils::showError(this, "Failed to export key images", m_wallet->errorString());
} else { } else {
QMessageBox::information(this, "Key image export", "Successfully exported key images."); Utils::showInfo(this, "Successfully exported key images");
} }
} }
@ -1160,9 +1205,9 @@ void MainWindow::importKeyImages() {
if (fn.isEmpty()) return; if (fn.isEmpty()) return;
bool r = m_wallet->importKeyImages(fn); bool r = m_wallet->importKeyImages(fn);
if (!r) { if (!r) {
QMessageBox::warning(this, "Key image import", QString("Failed to import key images.\n\n%1").arg(m_wallet->errorString())); Utils::showError(this, "Failed to import key images", m_wallet->errorString());
} else { } else {
QMessageBox::information(this, "Key image import", "Successfully imported key images"); Utils::showInfo(this, "Successfully imported key images");
m_wallet->refreshModels(); m_wallet->refreshModels();
} }
} }
@ -1173,9 +1218,9 @@ void MainWindow::exportOutputs() {
if (!fn.endsWith("_outputs")) fn += "_outputs"; if (!fn.endsWith("_outputs")) fn += "_outputs";
bool r = m_wallet->exportOutputs(fn, true); bool r = m_wallet->exportOutputs(fn, true);
if (!r) { if (!r) {
QMessageBox::warning(this, "Outputs export", QString("Failed to export outputs.\nReason: %1").arg(m_wallet->errorString())); Utils::showError(this, "Failed to export outputs", m_wallet->errorString());
} else { } else {
QMessageBox::information(this, "Outputs export", "Successfully exported outputs."); Utils::showInfo(this, "Successfully exported outputs.");
} }
} }
@ -1184,9 +1229,9 @@ void MainWindow::importOutputs() {
if (fn.isEmpty()) return; if (fn.isEmpty()) return;
bool r = m_wallet->importOutputs(fn); bool r = m_wallet->importOutputs(fn);
if (!r) { if (!r) {
QMessageBox::warning(this, "Outputs import", QString("Failed to import outputs.\n\n%1").arg(m_wallet->errorString())); Utils::showError(this, "Failed to import outputs", m_wallet->errorString());
} else { } else {
QMessageBox::information(this, "Outputs import", "Successfully imported outputs"); Utils::showInfo(this, "Successfully imported outputs");
m_wallet->refreshModels(); m_wallet->refreshModels();
} }
} }
@ -1197,7 +1242,7 @@ void MainWindow::loadUnsignedTx() {
UnsignedTransaction *tx = m_wallet->loadTxFile(fn); UnsignedTransaction *tx = m_wallet->loadTxFile(fn);
auto err = m_wallet->errorString(); auto err = m_wallet->errorString();
if (!err.isEmpty()) { if (!err.isEmpty()) {
QMessageBox::warning(this, "Load transaction from file", QString("Failed to load transaction.\n\n%1").arg(err)); Utils::showError(this, "Failed to load transaction", err);
return; return;
} }
@ -1207,13 +1252,13 @@ void MainWindow::loadUnsignedTx() {
void MainWindow::loadUnsignedTxFromClipboard() { void MainWindow::loadUnsignedTxFromClipboard() {
QString unsigned_tx = Utils::copyFromClipboard(); QString unsigned_tx = Utils::copyFromClipboard();
if (unsigned_tx.isEmpty()) { if (unsigned_tx.isEmpty()) {
QMessageBox::warning(this, "Load unsigned transaction from clipboard", "Clipboard is empty"); Utils::showError(this, "Unable to load unsigned transaction", "Clipboard is empty");
return; return;
} }
UnsignedTransaction *tx = m_wallet->loadTxFromBase64Str(unsigned_tx); UnsignedTransaction *tx = m_wallet->loadTxFromBase64Str(unsigned_tx);
auto err = m_wallet->errorString(); auto err = m_wallet->errorString();
if (!err.isEmpty()) { if (!err.isEmpty()) {
QMessageBox::warning(this, "Load unsigned transaction from clipboard", QString("Failed to load transaction.\n\n%1").arg(err)); Utils::showError(this, "Unable to load unsigned transaction", err);
return; return;
} }
@ -1226,7 +1271,7 @@ void MainWindow::loadSignedTx() {
PendingTransaction *tx = m_wallet->loadSignedTxFile(fn); PendingTransaction *tx = m_wallet->loadSignedTxFile(fn);
auto err = m_wallet->errorString(); auto err = m_wallet->errorString();
if (!err.isEmpty()) { if (!err.isEmpty()) {
QMessageBox::warning(this, "Load signed transaction from file", err); Utils::showError(this, "Unable to load signed transaction", err);
return; return;
} }
@ -1247,7 +1292,7 @@ void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
} }
void MainWindow::importTransaction() { void MainWindow::importTransaction() {
if (config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptNode) { if (conf()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptNode) {
// TODO: don't show if connected to local node // 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" auto result = QMessageBox::warning(this, "Warning", "Using this feature may allow a remote node to associate the transaction with your IP address.\n"
@ -1365,9 +1410,9 @@ void MainWindow::updateNetStats() {
void MainWindow::rescanSpent() { void MainWindow::rescanSpent() {
if (!m_wallet->rescanSpent()) { if (!m_wallet->rescanSpent()) {
QMessageBox::warning(this, "Rescan spent", m_wallet->errorString()); Utils::showError(this, "Failed to rescan spent outputs", m_wallet->errorString());
} else { } else {
QMessageBox::information(this, "Rescan spent", "Successfully rescanned spent outputs."); Utils::showInfo(this, "Successfully rescanned spent outputs");
} }
} }
@ -1417,7 +1462,7 @@ void MainWindow::onHideUpdateNotifications(bool hidden) {
} }
void MainWindow::onTorConnectionStateChanged(bool connected) { void MainWindow::onTorConnectionStateChanged(bool connected) {
if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) { if (conf()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
return; return;
} }
@ -1429,7 +1474,7 @@ void MainWindow::onTorConnectionStateChanged(bool connected) {
void MainWindow::showUpdateNotification() { void MainWindow::showUpdateNotification() {
#ifdef CHECK_UPDATES #ifdef CHECK_UPDATES
if (config()->get(Config::hideUpdateNotifications).toBool()) { if (conf()->get(Config::hideUpdateNotifications).toBool()) {
return; return;
} }
@ -1467,21 +1512,10 @@ void MainWindow::onInitiateTransaction() {
} }
} }
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);
if (m_wallet->isHwBacked()) {
m_splashDialog->hide();
}
}
void MainWindow::onKeysCorrupted() { void MainWindow::onKeysCorrupted() {
if (!m_criticalWarningShown) { if (!m_criticalWarningShown) {
m_criticalWarningShown = true; 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!"); Utils::showError(this, "Wallet keys are corrupted", "WARNING!\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(); m_sendWidget->disableSendButton();
} }
} }
@ -1505,22 +1539,19 @@ void MainWindow::onSelectedInputsChanged(const QStringList &selectedInputs) {
} }
void MainWindow::onExportHistoryCSV() { void MainWindow::onExportHistoryCSV() {
if (m_wallet == nullptr)
return;
QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)"); QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
if (fn.isEmpty()) if (fn.isEmpty())
return; return;
if (!fn.endsWith(".csv")) if (!fn.endsWith(".csv"))
fn += ".csv"; fn += ".csv";
m_wallet->history()->writeCSV(fn); m_wallet->history()->writeCSV(fn);
QMessageBox::information(this, "CSV export", QString("Transaction history exported to %1").arg(fn)); Utils::showInfo(this, "CSV export", QString("Transaction history exported to %1").arg(fn));
} }
void MainWindow::onExportContactsCSV() { void MainWindow::onExportContactsCSV() {
if (m_wallet == nullptr) return;
auto *model = m_wallet->addressBookModel(); auto *model = m_wallet->addressBookModel();
if (model->rowCount() <= 0){ if (model->rowCount() <= 0){
QMessageBox::warning(this, "Error", "Addressbook empty"); Utils::showInfo(this, "Unable to export contacts", "No contacts to export");
return; return;
} }
@ -1529,8 +1560,9 @@ void MainWindow::onExportContactsCSV() {
qint64 now = QDateTime::currentMSecsSinceEpoch(); qint64 now = QDateTime::currentMSecsSinceEpoch();
QString fn = QString("%1/monero-contacts_%2.csv").arg(targetDir, QString::number(now / 1000)); QString fn = QString("%1/monero-contacts_%2.csv").arg(targetDir, QString::number(now / 1000));
if(model->writeCSV(fn)) if (model->writeCSV(fn)) {
QMessageBox::information(this, "Address book exported", QString("Address book exported to %1").arg(fn)); Utils::showInfo(this, "Contacts exported successfully", QString("Exported to: %1").arg(fn));
}
} }
void MainWindow::onCreateDesktopEntry() { void MainWindow::onCreateDesktopEntry() {
@ -1539,11 +1571,12 @@ void MainWindow::onCreateDesktopEntry() {
} }
void MainWindow::onShowDocumentation() { void MainWindow::onShowDocumentation() {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org"); // TODO: welcome page
m_windowManager->showDocs(this);
} }
void MainWindow::onReportBug() { void MainWindow::onReportBug() {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org/guides/report-an-issue"); m_windowManager->showDocs(this, "report_an_issue");
} }
QString MainWindow::getHardwareDevice() { QString MainWindow::getHardwareDevice() {
@ -1581,7 +1614,7 @@ void MainWindow::donationNag() {
if (m_wallet->balanceAll() == 0) if (m_wallet->balanceAll() == 0)
return; return;
auto donationCounter = config()->get(Config::donateBeg).toInt(); auto donationCounter = conf()->get(Config::donateBeg).toInt();
if (donationCounter == -1) if (donationCounter == -1)
return; return;
@ -1594,11 +1627,11 @@ void MainWindow::donationNag() {
this->donateButtonClicked(); this->donateButtonClicked();
} }
} }
config()->set(Config::donateBeg, donationCounter); conf()->set(Config::donateBeg, donationCounter);
} }
void MainWindow::addToRecentlyOpened(QString keysFile) { void MainWindow::addToRecentlyOpened(QString keysFile) {
auto recent = config()->get(Config::recentlyOpenedWallets).toList(); auto recent = conf()->get(Config::recentlyOpenedWallets).toList();
if (Utils::isPortableMode()) { if (Utils::isPortableMode()) {
QDir appPath{Utils::applicationPath()}; QDir appPath{Utils::applicationPath()};
@ -1622,14 +1655,14 @@ void MainWindow::addToRecentlyOpened(QString keysFile) {
} }
} }
config()->set(Config::recentlyOpenedWallets, recent_); conf()->set(Config::recentlyOpenedWallets, recent_);
this->updateRecentlyOpenedMenu(); this->updateRecentlyOpenedMenu();
} }
void MainWindow::updateRecentlyOpenedMenu() { void MainWindow::updateRecentlyOpenedMenu() {
ui->menuRecently_open->clear(); ui->menuRecently_open->clear();
const QStringList recentWallets = config()->get(Config::recentlyOpenedWallets).toStringList(); const QStringList recentWallets = conf()->get(Config::recentlyOpenedWallets).toStringList();
for (const auto &walletPath : recentWallets) { for (const auto &walletPath : recentWallets) {
QFileInfo fileInfo{walletPath}; QFileInfo fileInfo{walletPath};
ui->menuRecently_open->addAction(fileInfo.fileName(), m_windowManager, std::bind(&WindowManager::tryOpenWallet, m_windowManager, fileInfo.absoluteFilePath(), "")); ui->menuRecently_open->addAction(fileInfo.fileName(), m_windowManager, std::bind(&WindowManager::tryOpenWallet, m_windowManager, fileInfo.absoluteFilePath(), ""));
@ -1671,7 +1704,7 @@ void MainWindow::closeQDialogChildren(QObject *object) {
} }
void MainWindow::checkUserActivity() { void MainWindow::checkUserActivity() {
if (!config()->get(Config::inactivityLockEnabled).toBool()) { if (!conf()->get(Config::inactivityLockEnabled).toBool()) {
return; return;
} }
@ -1679,7 +1712,7 @@ void MainWindow::checkUserActivity() {
return; return;
} }
if ((m_userLastActive + (config()->get(Config::inactivityLockTimeout).toInt()*60)) < QDateTime::currentSecsSinceEpoch()) { if ((m_userLastActive + (conf()->get(Config::inactivityLockTimeout).toInt()*60)) < QDateTime::currentSecsSinceEpoch()) {
qInfo() << "Locking wallet for inactivity"; qInfo() << "Locking wallet for inactivity";
this->lockWallet(); this->lockWallet();
} }
@ -1691,7 +1724,7 @@ void MainWindow::lockWallet() {
} }
if (m_constructingTransaction) { if (m_constructingTransaction) {
QMessageBox::warning(this, "Lock wallet", "Unable to lock wallet during transaction construction"); Utils::showError(this, "Unable to lock wallet", "Can't lock wallet during transaction construction");
return; return;
} }
m_walletUnlockWidget->reset(); m_walletUnlockWidget->reset();
@ -1731,7 +1764,7 @@ void MainWindow::unlockWallet(const QString &password) {
} }
void MainWindow::toggleSearchbar(bool visible) { void MainWindow::toggleSearchbar(bool visible) {
config()->set(Config::showSearchbar, visible); conf()->set(Config::showSearchbar, visible);
m_historyWidget->setSearchbarVisible(visible); m_historyWidget->setSearchbarVisible(visible);
m_receiveWidget->setSearchbarVisible(visible); m_receiveWidget->setSearchbarVisible(visible);

View file

@ -149,7 +149,6 @@ private slots:
void onTorConnectionStateChanged(bool connected); void onTorConnectionStateChanged(bool connected);
void showUpdateDialog(); void showUpdateDialog();
void onInitiateTransaction(); void onInitiateTransaction();
void onEndTransaction();
void onKeysCorrupted(); void onKeysCorrupted();
void onSelectedInputsChanged(const QStringList &selectedInputs); void onSelectedInputsChanged(const QStringList &selectedInputs);
@ -158,8 +157,7 @@ private slots:
void onSynchronized(); void onSynchronized();
void onWalletOpened(); void onWalletOpened();
void onConnectionStatusChanged(int status); void onConnectionStatusChanged(int status);
void onCreateTransactionError(const QString &message); void onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address);
void onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
// Dialogs // Dialogs
@ -218,7 +216,6 @@ private:
void setStatusText(const QString &text, bool override = false, int timeout = 1000); void setStatusText(const QString &text, bool override = false, int timeout = 1000);
void showBalanceDialog(); void showBalanceDialog();
QString statusDots(); QString statusDots();
void displayWalletErrorMsg(const QString &err);
QString getHardwareDevice(); QString getHardwareDevice();
void updateTitle(); void updateTitle();
void donationNag(); void donationNag();

View file

@ -202,7 +202,7 @@ void ReceiveWidget::showOnDevice() {
void ReceiveWidget::generateSubaddress() { void ReceiveWidget::generateSubaddress() {
bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), ""); bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), "");
if (!r) { if (!r) {
QMessageBox::warning(this, "Warning", QString("Failed to generate subaddress:\n\n%1").arg(m_wallet->subaddress()->errorString())); Utils::showError(this, "Failed to generate subaddress", m_wallet->subaddress()->errorString());
} }
} }

View file

@ -35,7 +35,7 @@ SendWidget::SendWidget(Wallet *wallet, QWidget *parent)
ui->lineAmount->setValidator(validator); ui->lineAmount->setValidator(validator);
connect(m_wallet, &Wallet::initiateTransaction, this, &SendWidget::onInitiateTransaction); connect(m_wallet, &Wallet::initiateTransaction, this, &SendWidget::onInitiateTransaction);
connect(m_wallet, &Wallet::endTransaction, this, &SendWidget::onEndTransaction); connect(m_wallet, &Wallet::transactionCreated, this, &SendWidget::onEndTransaction);
connect(WalletManager::instance(), &WalletManager::openAliasResolved, this, &SendWidget::onOpenAliasResolved); connect(WalletManager::instance(), &WalletManager::openAliasResolved, this, &SendWidget::onOpenAliasResolved);
@ -52,15 +52,17 @@ SendWidget::SendWidget(Wallet *wallet, QWidget *parent)
ui->label_conversionAmount->hide(); ui->label_conversionAmount->hide();
ui->btn_openAlias->hide(); ui->btn_openAlias->hide();
ui->label_PayTo->setHelpText("Recipient of the funds.\n\n" ui->label_PayTo->setHelpText("Recipient of the funds",
"You may enter a Monero address, or an alias (email-like address that forwards to a Monero address)"); "You may enter a Monero address, or an alias (email-like address that forwards to a Monero address)",
ui->label_Description->setHelpText("Description of the transaction (optional).\n\n" "send_transaction");
ui->label_Description->setHelpText("Description of the transaction (optional)",
"The description is not sent to the recipient of the funds. It is stored in your wallet cache, " "The description is not sent to the recipient of the funds. It is stored in your wallet cache, "
"and displayed in the 'History' tab."); "and displayed in the 'History' tab.",
ui->label_Amount->setHelpText("Amount to be sent.\n\nThis is the exact amount the recipient will receive. " "send_transaction");
ui->label_Amount->setHelpText("Amount to be sent","This is the exact amount the recipient will receive. "
"In addition to this amount a transaction fee will be subtracted from your balance. " "In addition to this amount a transaction fee will be subtracted from your balance. "
"You will be able to review the transaction fee before the transaction is broadcast.\n\n" "You will be able to review the transaction fee before the transaction is broadcast.\n\n"
"To send all your balance, click the Max button to the right."); "To send all your balance, click the Max button to the right.","send_transaction");
ui->lineAddress->setNetType(constants::networkType); ui->lineAddress->setNetType(constants::networkType);
this->setupComboBox(); this->setupComboBox();
@ -124,7 +126,7 @@ void SendWidget::scanClicked() {
#if defined(WITH_SCANNER) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if defined(WITH_SCANNER) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto cameras = QCameraInfo::availableCameras(); auto cameras = QCameraInfo::availableCameras();
if (cameras.count() < 1) { if (cameras.count() < 1) {
QMessageBox::warning(this, "QR code scanner", "No available cameras found."); Utils::showError(this, "Can't open QR scanner", "No available cameras found");
return; return;
} }
@ -135,7 +137,7 @@ void SendWidget::scanClicked() {
#elif defined(WITH_SCANNER) #elif defined(WITH_SCANNER)
auto cameras = QMediaDevices::videoInputs(); auto cameras = QMediaDevices::videoInputs();
if (cameras.empty()) { if (cameras.empty()) {
QMessageBox::warning(this, "QR code scanner", "No available cameras found."); Utils::showError(this, "Can't open QR scanner", "No available cameras found");
return; return;
} }
@ -144,27 +146,26 @@ void SendWidget::scanClicked() {
ui->lineAddress->setText(dialog->decodedString); ui->lineAddress->setText(dialog->decodedString);
dialog->deleteLater(); dialog->deleteLater();
#else #else
QMessageBox::warning(this, "QR scanner", "Feather was built without webcam QR scanner support."); Utils::showError(this, "Can't open QR scanner", "Feather was built without webcam QR scanner support");
#endif #endif
} }
void SendWidget::sendClicked() { void SendWidget::sendClicked() {
if (!m_wallet->isConnected()) { if (!m_wallet->isConnected()) {
QMessageBox::warning(this, "Error", "Unable to create transaction:\n\n" Utils::showError(this, "Unable to create transaction", "Wallet is not connected to a node.",
"Wallet is not connected to a node.\n" {"Wait for the wallet to automatically connect to a node.", "Go to File -> Settings -> Network -> Node to manually connect to a node."},
"Go to File -> Settings -> Node to manually connect to a node."); "nodes");
return; return;
} }
if (!m_wallet->isSynchronized()) { if (!m_wallet->isSynchronized()) {
QMessageBox::warning(this, "Error", "Wallet is not synchronized, unable to create transaction.\n\n" Utils::showError(this, "Unable to create transaction", "Wallet is not synchronized", {"Wait for wallet synchronization to complete"}, "synchronization");
"Wait for synchronization to complete.");
return; return;
} }
QString recipient = ui->lineAddress->text().simplified().remove(' '); QString recipient = ui->lineAddress->text().simplified().remove(' ');
if (recipient.isEmpty()) { if (recipient.isEmpty()) {
QMessageBox::warning(this, "Error", "No destination address was entered."); Utils::showError(this, "Unable to create transaction", "No address was entered", {"Enter an address in the 'Pay to' field."}, "send_transaction");
return; return;
} }
@ -176,7 +177,7 @@ void SendWidget::sendClicked() {
errorText += QString("Line #%1:\n%2\n").arg(QString::number(error.idx + 1), error.error); errorText += QString("Line #%1:\n%2\n").arg(QString::number(error.idx + 1), error.error);
} }
QMessageBox::warning(this, "Error", QString("Invalid lines found:\n\n%1").arg(errorText)); Utils::showError(this, "Unable to create transaction", QString("Invalid address lines found:\n\n%1").arg(errorText), {}, "pay_to_many");
return; return;
} }
@ -184,7 +185,7 @@ void SendWidget::sendClicked() {
if (!outputs.empty()) { // multi destination transaction if (!outputs.empty()) { // multi destination transaction
if (outputs.size() > 16) { if (outputs.size() > 16) {
QMessageBox::warning(this, "Error", "Maximum number of outputs (16) exceeded."); Utils::showError(this, "Unable to create transaction", "Maximum number of outputs (16) exceeded.", {}, "pay_to_many");
return; return;
} }
@ -204,7 +205,7 @@ void SendWidget::sendClicked() {
quint64 amount = this->amount(); quint64 amount = this->amount();
if (amount == 0 && !sendAll) { if (amount == 0 && !sendAll) {
QMessageBox::warning(this, "Error", "No amount was entered."); Utils::showError(this, "Unable to create transaction", "No amount was entered", {}, "send_transaction", "Amount field");
return; return;
} }
@ -213,6 +214,18 @@ void SendWidget::sendClicked() {
amount = WalletManager::amountFromDouble(this->conversionAmount()); amount = WalletManager::amountFromDouble(this->conversionAmount());
} }
quint64 unlocked_balance = m_wallet->unlockedBalance();
quint64 total_balance = m_wallet->balance();
if (total_balance == 0) {
Utils::showError(this, "Unable to create transaction", "No money to spend");
return;
}
if (!sendAll && amount > unlocked_balance) {
Utils::showError(this, "Unable to create transaction", QString("Not enough money to spend.\n\n"
"Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance)));
return;
}
m_wallet->createTransaction(recipient, amount, description, sendAll); m_wallet->createTransaction(recipient, amount, description, sendAll);
} }
@ -242,7 +255,7 @@ void SendWidget::updateConversionLabel() {
return; return;
} }
if (config()->get(Config::disableWebsocket).toBool()) { if (conf()->get(Config::disableWebsocket).toBool()) {
return; return;
} }
@ -252,7 +265,7 @@ void SendWidget::updateConversionLabel() {
return QString("~%1 XMR").arg(QString::number(this->conversionAmount(), 'f')); return QString("~%1 XMR").arg(QString::number(this->conversionAmount(), 'f'));
} else { } else {
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); auto preferredFiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
double conversionAmount = appData()->prices.convert("XMR", preferredFiatCurrency, this->amountDouble()); double conversionAmount = appData()->prices.convert("XMR", preferredFiatCurrency, this->amountDouble());
return QString("~%1 %2").arg(QString::number(conversionAmount, 'f', 2), preferredFiatCurrency); return QString("~%1 %2").arg(QString::number(conversionAmount, 'f', 2), preferredFiatCurrency);
} }
@ -291,19 +304,18 @@ void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &ad
ui->btn_openAlias->setEnabled(true); ui->btn_openAlias->setEnabled(true);
if (address.isEmpty()) { if (address.isEmpty()) {
this->onOpenAliasResolveError("Could not resolve OpenAlias."); Utils::showError(this, "Unable to resolve OpenAlias", "Address empty.");
return; return;
} }
if (!dnssecValid) { if (!dnssecValid) {
this->onOpenAliasResolveError("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed."); Utils::showError(this, "Unable to resolve OpenAlias", "Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed.");
return; return;
} }
bool valid = WalletManager::addressValid(address, constants::networkType); bool valid = WalletManager::addressValid(address, constants::networkType);
if (!valid) { if (!valid) {
this->onOpenAliasResolveError(QString("Address validation error. Perhaps it is of the wrong network type.\n\n" Utils::showError(this, "Unable to resolve OpenAlias", QString("Address validation failed.\n\nOpenAlias: %1\nAddress: %2").arg(openAlias, address));
"OpenAlias: %1\nAddress: %2").arg(openAlias, address));
return; return;
} }
@ -311,10 +323,6 @@ void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &ad
ui->btn_openAlias->hide(); ui->btn_openAlias->hide();
} }
void SendWidget::onOpenAliasResolveError(const QString &msg) {
QMessageBox::warning(this, "OpenAlias error", msg);
}
void SendWidget::clearFields() { void SendWidget::clearFields() {
ui->lineAddress->clear(); ui->lineAddress->clear();
ui->lineAmount->clear(); ui->lineAmount->clear();
@ -363,7 +371,7 @@ void SendWidget::onDataPasted(const QString &data) {
} }
} }
else { else {
QMessageBox::warning(this, "Error", "No Qr Code found."); Utils::showError(this, "Unable to decode QR code", "No QR code found.");
} }
} }
@ -371,7 +379,7 @@ void SendWidget::setupComboBox() {
ui->comboCurrencySelection->clear(); ui->comboCurrencySelection->clear();
QStringList defaultCurrencies = {"XMR", "USD", "EUR", "CNY", "JPY", "GBP"}; QStringList defaultCurrencies = {"XMR", "USD", "EUR", "CNY", "JPY", "GBP"};
QString preferredCurrency = config()->get(Config::preferredFiatCurrency).toString(); QString preferredCurrency = conf()->get(Config::preferredFiatCurrency).toString();
if (defaultCurrencies.contains(preferredCurrency)) { if (defaultCurrencies.contains(preferredCurrency)) {
defaultCurrencies.removeOne(preferredCurrency); defaultCurrencies.removeOne(preferredCurrency);

View file

@ -36,7 +36,6 @@ public slots:
void currencyComboChanged(int index); void currencyComboChanged(int index);
void fillAddress(const QString &address); void fillAddress(const QString &address);
void updateConversionLabel(); void updateConversionLabel();
void onOpenAliasResolveError(const QString &err);
void onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid); void onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid);
void onPreferredFiatCurrencyChanged(); void onPreferredFiatCurrencyChanged();
void disableSendButton(); void disableSendButton();

View file

@ -14,6 +14,7 @@
#include "utils/Icons.h" #include "utils/Icons.h"
#include "utils/WebsocketNotifier.h" #include "utils/WebsocketNotifier.h"
#include "widgets/NetworkProxyWidget.h" #include "widgets/NetworkProxyWidget.h"
#include "WindowManager.h"
Settings::Settings(Nodes *nodes, QWidget *parent) Settings::Settings(Nodes *nodes, QWidget *parent)
: QDialog(parent) : QDialog(parent)
@ -36,7 +37,7 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
ui->selector->setSelectionMode(QAbstractItemView::SingleSelection); ui->selector->setSelectionMode(QAbstractItemView::SingleSelection);
ui->selector->setSelectionBehavior(QAbstractItemView::SelectRows); ui->selector->setSelectionBehavior(QAbstractItemView::SelectRows);
// ui->selector->setCurrentRow(config()->get(Config::lastSettingsPage).toInt()); // ui->selector->setCurrentRow(conf()->get(Config::lastSettingsPage).toInt());
new QListWidgetItem(icons()->icon("interface_32px.png"), "Appearance", ui->selector, Pages::APPEARANCE); new QListWidgetItem(icons()->icon("interface_32px.png"), "Appearance", ui->selector, Pages::APPEARANCE);
new QListWidgetItem(icons()->icon("nw_32px.png"), "Network", ui->selector, Pages::NETWORK); new QListWidgetItem(icons()->icon("nw_32px.png"), "Network", ui->selector, Pages::NETWORK);
@ -61,11 +62,11 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
emit proxySettingsChanged(); emit proxySettingsChanged();
} }
config()->set(Config::lastSettingsPage, ui->selector->currentRow()); conf()->set(Config::lastSettingsPage, ui->selector->currentRow());
this->close(); this->close();
}); });
this->setSelection(config()->get(Config::lastSettingsPage).toInt()); this->setSelection(conf()->get(Config::lastSettingsPage).toInt());
this->adjustSize(); this->adjustSize();
} }
@ -73,7 +74,7 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
void Settings::setupAppearanceTab() { void Settings::setupAppearanceTab() {
// [Theme] // [Theme]
this->setupThemeComboBox(); this->setupThemeComboBox();
auto settingsTheme = config()->get(Config::skin).toString(); auto settingsTheme = conf()->get(Config::skin).toString();
if (m_themes.contains(settingsTheme)) { if (m_themes.contains(settingsTheme)) {
ui->comboBox_theme->setCurrentIndex(m_themes.indexOf(settingsTheme)); ui->comboBox_theme->setCurrentIndex(m_themes.indexOf(settingsTheme));
} }
@ -85,10 +86,10 @@ void Settings::setupAppearanceTab() {
for (int i = 0; i <= 12; i++) { for (int i = 0; i <= 12; i++) {
ui->comboBox_amountPrecision->addItem(QString::number(i)); ui->comboBox_amountPrecision->addItem(QString::number(i));
} }
ui->comboBox_amountPrecision->setCurrentIndex(config()->get(Config::amountPrecision).toInt()); ui->comboBox_amountPrecision->setCurrentIndex(conf()->get(Config::amountPrecision).toInt());
connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){ connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::amountPrecision, pos); conf()->set(Config::amountPrecision, pos);
emit updateBalance(); emit updateBalance();
}); });
@ -97,33 +98,33 @@ void Settings::setupAppearanceTab() {
for (const auto & format : m_dateFormats) { for (const auto & format : m_dateFormats) {
ui->comboBox_dateFormat->addItem(now.toString(format)); ui->comboBox_dateFormat->addItem(now.toString(format));
} }
QString dateFormatSetting = config()->get(Config::dateFormat).toString(); QString dateFormatSetting = conf()->get(Config::dateFormat).toString();
if (m_dateFormats.contains(dateFormatSetting)) { if (m_dateFormats.contains(dateFormatSetting)) {
ui->comboBox_dateFormat->setCurrentIndex(m_dateFormats.indexOf(dateFormatSetting)); ui->comboBox_dateFormat->setCurrentIndex(m_dateFormats.indexOf(dateFormatSetting));
} }
connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){ connect(ui->comboBox_dateFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::dateFormat, m_dateFormats.at(pos)); conf()->set(Config::dateFormat, m_dateFormats.at(pos));
}); });
// [Time format] // [Time format]
for (const auto & format : m_timeFormats) { for (const auto & format : m_timeFormats) {
ui->comboBox_timeFormat->addItem(now.toString(format)); ui->comboBox_timeFormat->addItem(now.toString(format));
} }
QString timeFormatSetting = config()->get(Config::timeFormat).toString(); QString timeFormatSetting = conf()->get(Config::timeFormat).toString();
if (m_timeFormats.contains(timeFormatSetting)) { if (m_timeFormats.contains(timeFormatSetting)) {
ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting)); ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting));
} }
connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){ connect(ui->comboBox_timeFormat, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::timeFormat, m_timeFormats.at(pos)); conf()->set(Config::timeFormat, m_timeFormats.at(pos));
}); });
// [Balance display] // [Balance display]
ui->comboBox_balanceDisplay->setCurrentIndex(config()->get(Config::balanceDisplay).toInt()); ui->comboBox_balanceDisplay->setCurrentIndex(conf()->get(Config::balanceDisplay).toInt());
connect(ui->comboBox_balanceDisplay, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){ connect(ui->comboBox_balanceDisplay, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int pos){
config()->set(Config::balanceDisplay, pos); conf()->set(Config::balanceDisplay, pos);
emit updateBalance(); emit updateBalance();
}); });
@ -138,14 +139,14 @@ void Settings::setupAppearanceTab() {
fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index); fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index);
} }
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); auto preferredFiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
if (!preferredFiatCurrency.isEmpty() && fiatCurrencies.contains(preferredFiatCurrency)) { if (!preferredFiatCurrency.isEmpty() && fiatCurrencies.contains(preferredFiatCurrency)) {
ui->comboBox_fiatCurrency->setCurrentText(preferredFiatCurrency); ui->comboBox_fiatCurrency->setCurrentText(preferredFiatCurrency);
} }
connect(ui->comboBox_fiatCurrency, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){ connect(ui->comboBox_fiatCurrency, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
QString selection = ui->comboBox_fiatCurrency->itemText(index); QString selection = ui->comboBox_fiatCurrency->itemText(index);
config()->set(Config::preferredFiatCurrency, selection); conf()->set(Config::preferredFiatCurrency, selection);
emit preferredFiatCurrencyChanged(selection); emit preferredFiatCurrencyChanged(selection);
}); });
} }
@ -167,16 +168,16 @@ void Settings::setupNetworkTab() {
// Websocket // Websocket
// [Obtain third-party data] // [Obtain third-party data]
ui->checkBox_enableWebsocket->setChecked(!config()->get(Config::disableWebsocket).toBool()); ui->checkBox_enableWebsocket->setChecked(!conf()->get(Config::disableWebsocket).toBool());
connect(ui->checkBox_enableWebsocket, &QCheckBox::toggled, [this](bool checked){ connect(ui->checkBox_enableWebsocket, &QCheckBox::toggled, [this](bool checked){
config()->set(Config::disableWebsocket, !checked); conf()->set(Config::disableWebsocket, !checked);
this->enableWebsocket(checked); this->enableWebsocket(checked);
}); });
// Overview // Overview
ui->checkBox_offlineMode->setChecked(config()->get(Config::offlineMode).toBool()); ui->checkBox_offlineMode->setChecked(conf()->get(Config::offlineMode).toBool());
connect(ui->checkBox_offlineMode, &QCheckBox::toggled, [this](bool checked){ connect(ui->checkBox_offlineMode, &QCheckBox::toggled, [this](bool checked){
config()->set(Config::offlineMode, checked); conf()->set(Config::offlineMode, checked);
emit offlineMode(checked); emit offlineMode(checked);
this->enableWebsocket(!checked); this->enableWebsocket(!checked);
}); });
@ -184,7 +185,7 @@ void Settings::setupNetworkTab() {
void Settings::setupStorageTab() { void Settings::setupStorageTab() {
// Paths // Paths
ui->lineEdit_defaultWalletDir->setText(config()->get(Config::walletDirectory).toString()); ui->lineEdit_defaultWalletDir->setText(conf()->get(Config::walletDirectory).toString());
ui->lineEdit_configDir->setText(Config::defaultConfigDir().path()); ui->lineEdit_configDir->setText(Config::defaultConfigDir().path());
ui->lineEdit_applicationDir->setText(Utils::applicationPath()); ui->lineEdit_applicationDir->setText(Utils::applicationPath());
@ -195,16 +196,16 @@ void Settings::setupStorageTab() {
} }
connect(ui->btn_browseDefaultWalletDir, &QPushButton::clicked, [this]{ connect(ui->btn_browseDefaultWalletDir, &QPushButton::clicked, [this]{
QString walletDirOld = config()->get(Config::walletDirectory).toString(); QString walletDirOld = conf()->get(Config::walletDirectory).toString();
QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", walletDirOld, QFileDialog::ShowDirsOnly); QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", walletDirOld, QFileDialog::ShowDirsOnly);
if (walletDir.isEmpty()) if (walletDir.isEmpty())
return; return;
config()->set(Config::walletDirectory, walletDir); conf()->set(Config::walletDirectory, walletDir);
ui->lineEdit_defaultWalletDir->setText(walletDir); ui->lineEdit_defaultWalletDir->setText(walletDir);
}); });
connect(ui->btn_openWalletDir, &QPushButton::clicked, []{ connect(ui->btn_openWalletDir, &QPushButton::clicked, []{
QDesktopServices::openUrl(QUrl::fromLocalFile(config()->get(Config::walletDirectory).toString())); QDesktopServices::openUrl(QUrl::fromLocalFile(conf()->get(Config::walletDirectory).toString()));
}); });
connect(ui->btn_openConfigDir, &QPushButton::clicked, []{ connect(ui->btn_openConfigDir, &QPushButton::clicked, []{
@ -215,25 +216,25 @@ void Settings::setupStorageTab() {
// Logging // Logging
// [Write log files to disk] // [Write log files to disk]
ui->checkBox_enableLogging->setChecked(!config()->get(Config::disableLogging).toBool()); ui->checkBox_enableLogging->setChecked(!conf()->get(Config::disableLogging).toBool());
connect(ui->checkBox_enableLogging, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_enableLogging, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::disableLogging, !toggled); conf()->set(Config::disableLogging, !toggled);
WalletManager::instance()->setLogLevel(toggled ? config()->get(Config::logLevel).toInt() : -1); WalletManager::instance()->setLogLevel(toggled ? conf()->get(Config::logLevel).toInt() : -1);
}); });
// [Log level] // [Log level]
ui->comboBox_logLevel->setCurrentIndex(config()->get(Config::logLevel).toInt()); ui->comboBox_logLevel->setCurrentIndex(conf()->get(Config::logLevel).toInt());
connect(ui->comboBox_logLevel, QOverload<int>::of(&QComboBox::currentIndexChanged), [](int index){ connect(ui->comboBox_logLevel, QOverload<int>::of(&QComboBox::currentIndexChanged), [](int index){
config()->set(Config::logLevel, index); conf()->set(Config::logLevel, index);
if (!config()->get(Config::disableLogging).toBool()) { if (!conf()->get(Config::disableLogging).toBool()) {
WalletManager::instance()->setLogLevel(index); WalletManager::instance()->setLogLevel(index);
} }
}); });
// [Write stack trace to disk on crash] // [Write stack trace to disk on crash]
ui->checkBox_writeStackTraceToDisk->setChecked(config()->get(Config::writeStackTraceToDisk).toBool()); ui->checkBox_writeStackTraceToDisk->setChecked(conf()->get(Config::writeStackTraceToDisk).toBool());
connect(ui->checkBox_writeStackTraceToDisk, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_writeStackTraceToDisk, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::writeStackTraceToDisk, toggled); conf()->set(Config::writeStackTraceToDisk, toggled);
}); });
// [Open log file] // [Open log file]
@ -243,57 +244,57 @@ void Settings::setupStorageTab() {
// Misc // Misc
// [Save recently opened wallet to config file] // [Save recently opened wallet to config file]
ui->checkBox_writeRecentlyOpened->setChecked(config()->get(Config::writeRecentlyOpenedWallets).toBool()); ui->checkBox_writeRecentlyOpened->setChecked(conf()->get(Config::writeRecentlyOpenedWallets).toBool());
connect(ui->checkBox_writeRecentlyOpened, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_writeRecentlyOpened, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::writeRecentlyOpenedWallets, toggled); conf()->set(Config::writeRecentlyOpenedWallets, toggled);
if (!toggled) { if (!toggled) {
config()->set(Config::recentlyOpenedWallets, {}); conf()->set(Config::recentlyOpenedWallets, {});
} }
}); });
} }
void Settings::setupDisplayTab() { void Settings::setupDisplayTab() {
// [Hide balance] // [Hide balance]
ui->checkBox_hideBalance->setChecked(config()->get(Config::hideBalance).toBool()); ui->checkBox_hideBalance->setChecked(conf()->get(Config::hideBalance).toBool());
connect(ui->checkBox_hideBalance, &QCheckBox::toggled, [this](bool toggled){ connect(ui->checkBox_hideBalance, &QCheckBox::toggled, [this](bool toggled){
config()->set(Config::hideBalance, toggled); conf()->set(Config::hideBalance, toggled);
emit updateBalance(); emit updateBalance();
}); });
// [Hide update notifications] // [Hide update notifications]
ui->checkBox_hideUpdateNotifications->setChecked(config()->get(Config::hideUpdateNotifications).toBool()); ui->checkBox_hideUpdateNotifications->setChecked(conf()->get(Config::hideUpdateNotifications).toBool());
connect(ui->checkBox_hideUpdateNotifications, &QCheckBox::toggled, [this](bool toggled){ connect(ui->checkBox_hideUpdateNotifications, &QCheckBox::toggled, [this](bool toggled){
config()->set(Config::hideUpdateNotifications, toggled); conf()->set(Config::hideUpdateNotifications, toggled);
emit hideUpdateNotifications(toggled); emit hideUpdateNotifications(toggled);
}); });
// [Hide transaction notifications] // [Hide transaction notifications]
ui->checkBox_hideTransactionNotifications->setChecked(config()->get(Config::hideNotifications).toBool()); ui->checkBox_hideTransactionNotifications->setChecked(conf()->get(Config::hideNotifications).toBool());
connect(ui->checkBox_hideTransactionNotifications, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_hideTransactionNotifications, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::hideNotifications, toggled); conf()->set(Config::hideNotifications, toggled);
}); });
// [Warn before opening external link] // [Warn before opening external link]
ui->checkBox_warnOnExternalLink->setChecked(config()->get(Config::warnOnExternalLink).toBool()); ui->checkBox_warnOnExternalLink->setChecked(conf()->get(Config::warnOnExternalLink).toBool());
connect(ui->checkBox_warnOnExternalLink, &QCheckBox::clicked, this, [this]{ connect(ui->checkBox_warnOnExternalLink, &QCheckBox::clicked, this, [this]{
bool state = ui->checkBox_warnOnExternalLink->isChecked(); bool state = ui->checkBox_warnOnExternalLink->isChecked();
config()->set(Config::warnOnExternalLink, state); conf()->set(Config::warnOnExternalLink, state);
}); });
// [Lock wallet on inactivity] // [Lock wallet on inactivity]
ui->checkBox_lockOnInactivity->setChecked(config()->get(Config::inactivityLockEnabled).toBool()); ui->checkBox_lockOnInactivity->setChecked(conf()->get(Config::inactivityLockEnabled).toBool());
ui->spinBox_lockOnInactivity->setValue(config()->get(Config::inactivityLockTimeout).toInt()); ui->spinBox_lockOnInactivity->setValue(conf()->get(Config::inactivityLockTimeout).toInt());
connect(ui->checkBox_lockOnInactivity, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_lockOnInactivity, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::inactivityLockEnabled, toggled); conf()->set(Config::inactivityLockEnabled, toggled);
}); });
connect(ui->spinBox_lockOnInactivity, QOverload<int>::of(&QSpinBox::valueChanged), [](int value){ connect(ui->spinBox_lockOnInactivity, QOverload<int>::of(&QSpinBox::valueChanged), [](int value){
config()->set(Config::inactivityLockTimeout, value); conf()->set(Config::inactivityLockTimeout, value);
}); });
// [Lock wallet on minimize] // [Lock wallet on minimize]
ui->checkBox_lockOnMinimize->setChecked(config()->get(Config::lockOnMinimize).toBool()); ui->checkBox_lockOnMinimize->setChecked(conf()->get(Config::lockOnMinimize).toBool());
connect(ui->checkBox_lockOnMinimize, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_lockOnMinimize, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::lockOnMinimize, toggled); conf()->set(Config::lockOnMinimize, toggled);
}); });
} }
@ -303,12 +304,12 @@ void Settings::setupMemoryTab() {
void Settings::setupTransactionsTab() { void Settings::setupTransactionsTab() {
// [Multibroadcast outgoing transactions] // [Multibroadcast outgoing transactions]
ui->checkBox_multibroadcast->setChecked(config()->get(Config::multiBroadcast).toBool()); ui->checkBox_multibroadcast->setChecked(conf()->get(Config::multiBroadcast).toBool());
connect(ui->checkBox_multibroadcast, &QCheckBox::toggled, [](bool toggled){ connect(ui->checkBox_multibroadcast, &QCheckBox::toggled, [](bool toggled){
config()->set(Config::multiBroadcast, toggled); conf()->set(Config::multiBroadcast, toggled);
}); });
connect(ui->btn_multibroadcast, &QPushButton::clicked, [this]{ connect(ui->btn_multibroadcast, &QPushButton::clicked, [this]{
QMessageBox::information(this, "Multibroadcasting", "Multibroadcasting relays outgoing transactions to all nodes in your selected node list. This may improve transaction relay speed and reduces the chance of your transaction failing."); Utils::showInfo(this, "Multibroadcasting", "Multibroadcasting relays outgoing transactions to all nodes in your node list. This may improve transaction relay speed and reduces the chance of your transaction failing.");
}); });
// Hide unimplemented settings // Hide unimplemented settings
@ -318,18 +319,18 @@ void Settings::setupTransactionsTab() {
void Settings::setupMiscTab() { void Settings::setupMiscTab() {
// [Block explorer] // [Block explorer]
ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(config()->get(Config::blockExplorer).toString())); ui->comboBox_blockExplorer->setCurrentIndex(ui->comboBox_blockExplorer->findText(conf()->get(Config::blockExplorer).toString()));
connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{ connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString blockExplorer = ui->comboBox_blockExplorer->currentText(); QString blockExplorer = ui->comboBox_blockExplorer->currentText();
config()->set(Config::blockExplorer, blockExplorer); conf()->set(Config::blockExplorer, blockExplorer);
emit blockExplorerChanged(blockExplorer); emit blockExplorerChanged(blockExplorer);
}); });
// [Reddit frontend] // [Reddit frontend]
ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(config()->get(Config::redditFrontend).toString())); ui->comboBox_redditFrontend->setCurrentIndex(ui->comboBox_redditFrontend->findText(conf()->get(Config::redditFrontend).toString()));
connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{ connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString redditFrontend = ui->comboBox_redditFrontend->currentText(); QString redditFrontend = ui->comboBox_redditFrontend->currentText();
config()->set(Config::redditFrontend, redditFrontend); conf()->set(Config::redditFrontend, redditFrontend);
}); });
// [LocalMonero frontend] // [LocalMonero frontend]
@ -339,10 +340,10 @@ void Settings::setupMiscTab() {
"http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion"); "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion");
ui->comboBox_localMoneroFrontend->addItem("yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p", ui->comboBox_localMoneroFrontend->addItem("yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p",
"http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p"); "http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p");
ui->comboBox_localMoneroFrontend->setCurrentIndex(ui->comboBox_localMoneroFrontend->findData(config()->get(Config::localMoneroFrontend).toString())); ui->comboBox_localMoneroFrontend->setCurrentIndex(ui->comboBox_localMoneroFrontend->findData(conf()->get(Config::localMoneroFrontend).toString()));
connect(ui->comboBox_localMoneroFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{ connect(ui->comboBox_localMoneroFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), [this]{
QString localMoneroFrontend = ui->comboBox_localMoneroFrontend->currentData().toString(); QString localMoneroFrontend = ui->comboBox_localMoneroFrontend->currentData().toString();
config()->set(Config::localMoneroFrontend, localMoneroFrontend); conf()->set(Config::localMoneroFrontend, localMoneroFrontend);
}); });
} }
@ -376,7 +377,7 @@ void Settings::setSelection(int index) {
} }
void Settings::enableWebsocket(bool enabled) { void Settings::enableWebsocket(bool enabled) {
if (enabled && !config()->get(Config::offlineMode).toBool() && !config()->get(Config::disableWebsocket).toBool()) { if (enabled && !conf()->get(Config::offlineMode).toBool() && !conf()->get(Config::disableWebsocket).toBool()) {
websocketNotifier()->websocketClient->restart(); websocketNotifier()->websocketClient->restart();
} else { } else {
websocketNotifier()->websocketClient->stop(); websocketNotifier()->websocketClient->stop();

View file

@ -342,7 +342,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTabWidget" name="tabWidget_2"> <widget class="QTabWidget" name="tabWidget_storage">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
</property> </property>
@ -921,6 +921,9 @@
<property name="text"> <property name="text">
<string>?</string> <string>?</string>
</property> </property>
<property name="flat">
<bool>true</bool>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View file

@ -7,6 +7,7 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QWindow>
#include "constants.h" #include "constants.h"
#include "dialog/PasswordDialog.h" #include "dialog/PasswordDialog.h"
@ -18,9 +19,8 @@
#include "utils/TorManager.h" #include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h" #include "utils/WebsocketNotifier.h"
WindowManager::WindowManager(QObject *parent, EventFilter *eventFilter) WindowManager::WindowManager(QObject *parent)
: QObject(parent) : QObject(parent)
, eventFilter(eventFilter)
{ {
m_walletManager = WalletManager::instance(); m_walletManager = WalletManager::instance();
m_splashDialog = new SplashDialog(); m_splashDialog = new SplashDialog();
@ -45,7 +45,7 @@ WindowManager::WindowManager(QObject *parent, EventFilter *eventFilter)
this->showCrashLogs(); this->showCrashLogs();
if (!config()->get(Config::firstRun).toBool() || TailsOS::detect() || WhonixOS::detect()) { if (!conf()->get(Config::firstRun).toBool() || TailsOS::detect() || WhonixOS::detect()) {
this->onInitialNetworkConfigured(); this->onInitialNetworkConfigured();
} }
@ -56,6 +56,12 @@ WindowManager::WindowManager(QObject *parent, EventFilter *eventFilter)
} }
} }
QPointer<WindowManager> WindowManager::m_instance(nullptr);
void WindowManager::setEventFilter(EventFilter *ef) {
eventFilter = ef;
}
WindowManager::~WindowManager() { WindowManager::~WindowManager() {
qDebug() << "~WindowManager"; qDebug() << "~WindowManager";
m_cleanupThread->quit(); m_cleanupThread->quit();
@ -82,6 +88,7 @@ void WindowManager::close() {
m_wizard->deleteLater(); m_wizard->deleteLater();
m_splashDialog->deleteLater(); m_splashDialog->deleteLater();
m_tray->deleteLater(); m_tray->deleteLater();
m_docsDialog->deleteLater();
torManager()->stop(); torManager()->stop();
@ -109,13 +116,13 @@ void WindowManager::startupWarning() {
// Stagenet / Testnet // Stagenet / Testnet
auto worthlessWarning = QString("Feather wallet is currently running in %1 mode. This is meant " auto worthlessWarning = QString("Feather wallet is currently running in %1 mode. This is meant "
"for developers only. Your coins are WORTHLESS."); "for developers only. Your coins are WORTHLESS.");
if (constants::networkType == NetworkType::STAGENET && config()->get(Config::warnOnStagenet).toBool()) { if (constants::networkType == NetworkType::STAGENET && conf()->get(Config::warnOnStagenet).toBool()) {
this->showWarningMessageBox("Warning", worthlessWarning.arg("stagenet")); this->showWarningMessageBox("Warning", worthlessWarning.arg("stagenet"));
config()->set(Config::warnOnStagenet, false); conf()->set(Config::warnOnStagenet, false);
} }
else if (constants::networkType == NetworkType::TESTNET && config()->get(Config::warnOnTestnet).toBool()){ else if (constants::networkType == NetworkType::TESTNET && conf()->get(Config::warnOnTestnet).toBool()){
this->showWarningMessageBox("Warning", worthlessWarning.arg("testnet")); this->showWarningMessageBox("Warning", worthlessWarning.arg("testnet"));
config()->set(Config::warnOnTestnet, false); conf()->set(Config::warnOnTestnet, false);
} }
} }
@ -173,6 +180,43 @@ void WindowManager::showSettings(Nodes *nodes, QWidget *parent, bool showProxyTa
settings.exec(); settings.exec();
} }
// ######################## DOCS ########################
void WindowManager::showDocs(QObject *parent, const QString &doc, bool modal) {
if (!m_docsDialog) {
m_docsDialog = new DocsDialog();
}
m_docsDialog->setModal(modal);
m_docsDialog->show();
if (m_docsDialog->isMinimized()) {
m_docsDialog->showNormal();
}
m_docsDialog->raise();
m_docsDialog->activateWindow();
QWindow *window = Utils::windowForQObject(parent);
if (window != nullptr) {
QPoint centerOfParent = window->frameGeometry().center();
m_docsDialog->move(centerOfParent.x() - m_docsDialog->width() / 2, centerOfParent.y() - m_docsDialog->height() / 2);
} else {
qDebug() << "Unable to center docs window";
}
if (!doc.isEmpty()) {
m_docsDialog->showDoc(doc);
}
}
void WindowManager::setDocsHighlight(const QString &highlight) {
if (!m_docsDialog) {
return;
}
m_docsDialog->updateHighlights(highlight);
}
// ######################## WALLET OPEN ######################## // ######################## WALLET OPEN ########################
void WindowManager::tryOpenWallet(const QString &path, const QString &password) { void WindowManager::tryOpenWallet(const QString &path, const QString &password) {
@ -192,7 +236,7 @@ void WindowManager::tryOpenWallet(const QString &path, const QString &password)
} }
if (!Utils::fileExists(path)) { if (!Utils::fileExists(path)) {
this->handleWalletError(QString("Wallet not found: %1").arg(path)); this->handleWalletError({nullptr, Utils::ERROR, "Unable to open wallet", QString("Wallet not found: %1").arg(path)});
return; return;
} }
@ -202,30 +246,27 @@ void WindowManager::tryOpenWallet(const QString &path, const QString &password)
void WindowManager::onWalletOpened(Wallet *wallet) { void WindowManager::onWalletOpened(Wallet *wallet) {
if (!wallet) { if (!wallet) {
QString err{"Unable to open wallet"}; this->handleWalletError({nullptr, Utils::ERROR, "Unable to open wallet", "This should never happen. If you encounter this error, please report it to the developers.", {}, "report_an_issue"});
this->handleWalletError(err);
return; return;
} }
auto status = wallet->status(); auto status = wallet->status();
if (status != Wallet::Status_Ok) { if (status != Wallet::Status_Ok) {
QString errMsg = wallet->errorString(); QString errMsg = wallet->errorString();
QString keysPath = wallet->keysPath();
QString cachePath = wallet->cachePath();
wallet->deleteLater(); wallet->deleteLater();
if (status == Wallet::Status_BadPassword) { if (status == Wallet::Status_BadPassword) {
// Don't show incorrect password when we try with empty password for the first time // Don't show incorrect password when we try with empty password for the first time
bool showIncorrectPassword = m_openWalletTriedOnce; bool showIncorrectPassword = m_openWalletTriedOnce;
m_openWalletTriedOnce = true; m_openWalletTriedOnce = true;
this->onWalletOpenPasswordRequired(showIncorrectPassword, keysPath); this->onWalletOpenPasswordRequired(showIncorrectPassword, wallet->keysPath());
} }
else if (errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) { else if (errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) {
qCritical() << errMsg; qCritical() << errMsg;
WalletManager::clearWalletCache(cachePath); WalletManager::clearWalletCache(wallet->cachePath());
errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg); errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg);
this->handleWalletError(errMsg); this->handleWalletError({nullptr, Utils::ERROR, "Unable to open wallet", errMsg});
} else { } else {
this->handleWalletError(errMsg); this->handleWalletError({nullptr, Utils::ERROR, "Unable to open wallet", errMsg});
} }
return; return;
} }
@ -273,7 +314,7 @@ void WindowManager::onWalletOpenPasswordRequired(bool invalidPassword, const QSt
} }
bool WindowManager::autoOpenWallet() { bool WindowManager::autoOpenWallet() {
QString autoPath = config()->get(Config::autoOpenWalletPath).toString(); QString autoPath = conf()->get(Config::autoOpenWalletPath).toString();
if (!autoPath.isEmpty() && autoPath.startsWith(QString::number(constants::networkType))) { if (!autoPath.isEmpty() && autoPath.startsWith(QString::number(constants::networkType))) {
autoPath.remove(0, 1); autoPath.remove(0, 1);
} }
@ -288,14 +329,13 @@ bool WindowManager::autoOpenWallet() {
void WindowManager::tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage, void WindowManager::tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage,
const QString &seedOffset, const QString &subaddressLookahead, bool newWallet) { const QString &seedOffset, const QString &subaddressLookahead, bool newWallet) {
if(Utils::fileExists(path)) { if (Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", QString("File already exists: %1").arg(path)});
this->handleWalletError(err);
return; return;
} }
if (seed.mnemonic.isEmpty()) { if (seed.mnemonic.isEmpty()) {
this->handleWalletError("Mnemonic seed error. Failed to write wallet."); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Mnemonic seed is emopty"});
return; return;
} }
@ -308,7 +348,7 @@ void WindowManager::tryCreateWallet(Seed seed, const QString &path, const QStrin
} }
if (!wallet) { if (!wallet) {
this->handleWalletError("Failed to write wallet"); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet"});
return; return;
} }
@ -325,8 +365,7 @@ void WindowManager::tryCreateWallet(Seed seed, const QString &path, const QStrin
void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight, const QString &subaddressLookahead) void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString &password, const QString &deviceName, int restoreHeight, const QString &subaddressLookahead)
{ {
if (Utils::fileExists(path)) { if (Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet from device", QString("File already exists: %1").arg(path)});
this->handleWalletError(err);
return; return;
} }
@ -337,8 +376,7 @@ void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString
void WindowManager::tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address, void WindowManager::tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address,
const QString &viewkey, const QString &spendkey, quint64 restoreHeight, const QString &subaddressLookahead) { const QString &viewkey, const QString &spendkey, quint64 restoreHeight, const QString &subaddressLookahead) {
if (Utils::fileExists(path)) { if (Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", QString("File already exists: %1").arg(path)});
this->handleWalletError(err);
return; return;
} }
@ -348,20 +386,17 @@ void WindowManager::tryCreateWalletFromKeys(const QString &path, const QString &
} }
else { else {
if (!spendkey.isEmpty() && !WalletManager::keyValid(spendkey, address, false, constants::networkType)) { if (!spendkey.isEmpty() && !WalletManager::keyValid(spendkey, address, false, constants::networkType)) {
auto err = QString("Failed to create wallet. Invalid spendkey provided.").arg(path); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Invalid spendkey provided"});
this->handleWalletError(err);
return; return;
} }
if (!WalletManager::addressValid(address, constants::networkType)) { if (!WalletManager::addressValid(address, constants::networkType)) {
auto err = QString("Failed to create wallet. Invalid address provided.").arg(path); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Invalid address provided"});
this->handleWalletError(err);
return; return;
} }
if (!WalletManager::keyValid(viewkey, address, true, constants::networkType)) { if (!WalletManager::keyValid(viewkey, address, true, constants::networkType)) {
auto err = QString("Failed to create wallet. Invalid viewkey provided.").arg(path); this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Invalid viewkey provided"});
this->handleWalletError(err);
return; return;
} }
@ -376,10 +411,59 @@ void WindowManager::onWalletCreated(Wallet *wallet) {
// Currently only called when a wallet is created from device. // Currently only called when a wallet is created from device.
auto state = wallet->status(); auto state = wallet->status();
if (state != Wallet::Status_Ok) { if (state != Wallet::Status_Ok) {
qDebug() << Q_FUNC_INFO << QString("Wallet open error: %1").arg(wallet->errorString()); QString error = wallet->errorString();
this->displayWalletErrorMessage(wallet->errorString()); QStringList helpItems;
m_splashDialog->hide(); QString link;
QString doc;
// Ledger
if (error.contains("No device found")) {
error = "No Ledger device found.";
helpItems = {"Make sure the Monero app is open on the device.", "If the problem persists, try restarting Feather."};
doc = "create_wallet_hardware_device";
}
else if (error.contains("Unable to open device")) {
error = "Unable to open device.";
helpItems = {"The device might be in use by a different application."};
#if defined(Q_OS_LINUX)
helpItems.append("On Linux you may need to follow the instructions in the link below before the device can be opened:\n"
"https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues");
link = "https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues";
#endif
}
// Trezor
else if (error.contains("Unable to claim libusb device")) {
error = "Unable to claim Trezor device";
helpItems = {"Please make sure the device is not used by another program and try again."};
}
else if (error.contains("Cannot get a device address")) {
error = "Cannot get a device address";
helpItems = {"Restart the Trezor device and try again"};
}
else if (error.contains("Could not connect to the device Trezor") || error.contains("Device connect failed")) {
error = "Could not connect to the Trezor device";
helpItems = {"Make sure the device is connected to your computer and unlocked."};
#if defined(Q_OS_LINUX)
helpItems.append("On Linux you may need to follow the instructions in the link below before the device can be opened:\n"
"https://wiki.trezor.io/Udev_rules");
link = "https://wiki.trezor.io/Udev_rules";
#endif
}
if (error.contains("SW_CLIENT_NOT_SUPPORTED")) {
helpItems = {"Upgrade your Ledger device firmware to the latest version using Ledger Live.\n"
"Then upgrade the Monero app for the Ledger device to the latest version."};
}
else if (error.contains("Wrong Device Status")) {
helpItems = {"The device may need to be unlocked."};
}
else if (error.contains("Wrong Channel")) {
helpItems = {"Restart the hardware device and try again."};
}
this->showWizard(WalletWizard::Page_Menu); this->showWizard(WalletWizard::Page_Menu);
Utils::showMsg({m_wizard, Utils::ERROR, "Failed to create wallet from device", error, helpItems, doc, "", link});
m_splashDialog->hide();
m_openingWallet = false; m_openingWallet = false;
return; return;
} }
@ -389,76 +473,11 @@ void WindowManager::onWalletCreated(Wallet *wallet) {
// ######################## ERROR HANDLING ######################## // ######################## ERROR HANDLING ########################
void WindowManager::handleWalletError(const QString &message) { void WindowManager::handleWalletError(const Utils::Message &message) {
qCritical() << message; Utils::showMsg(message);
this->displayWalletErrorMessage(message);
this->initWizard(); this->initWizard();
} }
void WindowManager::displayWalletErrorMessage(const QString &message) {
QString errMsg = QString("Error: %1").arg(message);
QString link;
// Ledger
if (message.contains("No device found")) {
errMsg += "\n\nThis wallet is backed by a Ledger 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 (message.contains("Unable to open device")) {
errMsg += "\n\nThe device might be in use by a different application.";
#if defined(Q_OS_LINUX)
errMsg += "\n\nNote: On Linux you may need to follow the instructions in the link below before the device can be opened:\n"
"https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues";
link = "https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues";
#endif
}
// TREZOR
if (message.contains("Unable to claim libusb device")) {
errMsg += "\n\nThis wallet is backed by a Trezor hardware device. Feather was unable to access the device. "
"Please make sure it is not opened by another program and try again.";
}
if (message.contains("Cannot get a device address")) {
errMsg += "\n\nRestart the Trezor hardware device and try again.";
}
if (message.contains("Could not connect to the device Trezor") || message.contains("Device connect failed")) {
errMsg += "\n\nThis wallet is backed by a Trezor hardware device. Make sure the device is connected to your computer and unlocked.";
#if defined(Q_OS_LINUX)
errMsg += "\n\nNote: On Linux you may need to follow the instructions in the link below before the device can be opened:\n"
"https://wiki.trezor.io/Udev_rules";
link = "https://wiki.trezor.io/Udev_rules";
#endif
}
if (message.contains("SW_CLIENT_NOT_SUPPORTED")) {
errMsg += "\n\nIncompatible version: upgrade your Ledger device firmware to the latest version using Ledger Live.\n"
"Then upgrade the Monero app for the Ledger device to the latest version.";
}
else if (message.contains("Wrong Device Status")) {
errMsg += "\n\nThe device may need to be unlocked.";
}
else if (message.contains("Wrong Channel")) {
errMsg += "\n\nRestart the hardware device and try again.";
}
QMessageBox msgBox;
msgBox.setWindowIcon(icons()->icon("appicons/64x64.png"));
msgBox.setIcon(QMessageBox::Warning);
msgBox.setText(errMsg);
msgBox.setWindowTitle("Wallet error");
msgBox.setStandardButtons(QMessageBox::Ok);
msgBox.setDefaultButton(QMessageBox::Ok);
QPushButton *openLinkButton = nullptr;
if (!link.isEmpty()) {
openLinkButton = msgBox.addButton("Open link", QMessageBox::ActionRole);
}
msgBox.exec();
if (openLinkButton && msgBox.clickedButton() == openLinkButton) {
Utils::externalLinkWarning(nullptr, link);
}
}
void WindowManager::showCrashLogs() { void WindowManager::showCrashLogs() {
QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"}; QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
QFile crashLogFile{crashLogPath}; QFile crashLogFile{crashLogPath};
@ -586,7 +605,7 @@ void WindowManager::notify(const QString &title, const QString &message, int dur
return; return;
} }
if (config()->get(Config::hideNotifications).toBool()) { if (conf()->get(Config::hideNotifications).toBool()) {
return; return;
} }
@ -614,11 +633,11 @@ void WindowManager::onProxySettingsChanged() {
torManager()->start(); torManager()->start();
QNetworkProxy proxy{QNetworkProxy::NoProxy}; QNetworkProxy proxy{QNetworkProxy::NoProxy};
if (config()->get(Config::proxy).toInt() != Config::Proxy::None) { if (conf()->get(Config::proxy).toInt() != Config::Proxy::None) {
QString host = config()->get(Config::socks5Host).toString(); QString host = conf()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort(); quint16 port = conf()->get(Config::socks5Port).toString().toUShort();
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
host = torManager()->featherTorHost; host = torManager()->featherTorHost;
port = torManager()->featherTorPort; port = torManager()->featherTorPort;
} }
@ -659,7 +678,7 @@ WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) {
void WindowManager::initWizard() { void WindowManager::initWizard() {
auto startPage = WalletWizard::Page_Menu; auto startPage = WalletWizard::Page_Menu;
if (config()->get(Config::firstRun).toBool() && !(TailsOS::detect() || WhonixOS::detect())) { if (conf()->get(Config::firstRun).toBool() && !(TailsOS::detect() || WhonixOS::detect())) {
startPage = WalletWizard::Page_Network; startPage = WalletWizard::Page_Network;
} }
@ -699,7 +718,7 @@ void WindowManager::initSkins() {
if (!breeze_light.isEmpty()) if (!breeze_light.isEmpty())
m_skins.insert("Breeze/Light", breeze_light); m_skins.insert("Breeze/Light", breeze_light);
QString skin = config()->get(Config::skin).toString(); QString skin = conf()->get(Config::skin).toString();
qApp->setStyleSheet(m_skins[skin]); qApp->setStyleSheet(m_skins[skin]);
} }
@ -725,7 +744,7 @@ void WindowManager::onChangeTheme(const QString &skinName) {
return; return;
} }
config()->set(Config::skin, skinName); conf()->set(Config::skin, skinName);
qApp->setStyleSheet(m_skins[skinName]); qApp->setStyleSheet(m_skins[skinName]);
qDebug() << QString("Skin changed to %1").arg(skinName); qDebug() << QString("Skin changed to %1").arg(skinName);
@ -747,4 +766,13 @@ void WindowManager::patchMacStylesheet() {
qApp->setStyleSheet(styleSheet); qApp->setStyleSheet(styleSheet);
#endif #endif
}
WindowManager* WindowManager::instance()
{
if (!m_instance) {
m_instance = new WindowManager(QCoreApplication::instance());
}
return m_instance;
} }

View file

@ -6,21 +6,25 @@
#include <QObject> #include <QObject>
#include "dialog/DocsDialog.h"
#include "dialog/TorInfoDialog.h" #include "dialog/TorInfoDialog.h"
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "utils/nodes.h" #include "utils/nodes.h"
#include "wizard/WalletWizard.h" #include "wizard/WalletWizard.h"
#include "Utils.h"
class MainWindow; class MainWindow;
class WindowManager : public QObject { class WindowManager : public QObject {
Q_OBJECT Q_OBJECT
public: public:
explicit WindowManager(QObject *parent, EventFilter *eventFilter); explicit WindowManager(QObject *parent);
~WindowManager() override; ~WindowManager() override;
void setEventFilter(EventFilter *eventFilter);
void wizardOpenWallet(); void wizardOpenWallet();
void close(); void close();
void closeWindow(MainWindow *window); void closeWindow(MainWindow *window);
@ -30,10 +34,15 @@ public:
void showSettings(Nodes *nodes, QWidget *parent, bool showProxyTab = false); void showSettings(Nodes *nodes, QWidget *parent, bool showProxyTab = false);
void showDocs(QObject *parent, const QString &doc = "", bool modal = false);
void setDocsHighlight(const QString &highlight);
void notify(const QString &title, const QString &message, int duration); void notify(const QString &title, const QString &message, int duration);
EventFilter *eventFilter; EventFilter *eventFilter;
static WindowManager* instance();
signals: signals:
void proxySettingsChanged(); void proxySettingsChanged();
void websocketStatusChanged(bool enabled); void websocketStatusChanged(bool enabled);
@ -66,7 +75,7 @@ private:
void initWizard(); void initWizard();
WalletWizard* createWizard(WalletWizard::Page startPage); WalletWizard* createWizard(WalletWizard::Page startPage);
void handleWalletError(const QString &message); void handleWalletError(const Utils::Message &message);
void displayWalletErrorMessage(const QString &message); void displayWalletErrorMessage(const QString &message);
void initSkins(); void initSkins();
@ -80,11 +89,14 @@ private:
void quitAfterLastWindow(); void quitAfterLastWindow();
static QPointer<WindowManager> m_instance;
QVector<MainWindow*> m_windows; QVector<MainWindow*> m_windows;
WalletManager *m_walletManager; WalletManager *m_walletManager;
WalletWizard *m_wizard = nullptr; WalletWizard *m_wizard = nullptr;
SplashDialog *m_splashDialog = nullptr; SplashDialog *m_splashDialog = nullptr;
DocsDialog *m_docsDialog = nullptr;
QSystemTrayIcon *m_tray; QSystemTrayIcon *m_tray;
@ -97,5 +109,9 @@ private:
QThread *m_cleanupThread; QThread *m_cleanupThread;
}; };
inline WindowManager* windowManager()
{
return WindowManager::instance();
}
#endif //FEATHER_WINDOWMANAGER_H #endif //FEATHER_WINDOWMANAGER_H

0
src/assets/docs/.gitkeep Normal file
View file

View file

@ -5,6 +5,8 @@
#include <QtWidgets> #include <QtWidgets>
#include "Utils.h"
DoublePixmapLabel::DoublePixmapLabel(QWidget *parent) DoublePixmapLabel::DoublePixmapLabel(QWidget *parent)
: QLabel(parent) : QLabel(parent)
{} {}
@ -32,21 +34,22 @@ StatusBarButton::StatusBarButton(const QIcon &icon, const QString &tooltip, QWid
setCursor(QCursor(Qt::PointingHandCursor)); setCursor(QCursor(Qt::PointingHandCursor));
} }
void HelpLabel::setHelpText(const QString &text) void HelpLabel::setHelpText(const QString &text, const QString &informativeText, const QString &doc)
{ {
this->help_text = text; m_text = text;
m_informativeText = informativeText;
m_doc = doc;
} }
HelpLabel::HelpLabel(QWidget *parent) : QLabel(parent) HelpLabel::HelpLabel(QWidget *parent) : QLabel(parent)
{ {
this->help_text = "help_text";
this->font = QFont(); this->font = QFont();
} }
void HelpLabel::mouseReleaseEvent(QMouseEvent *event) void HelpLabel::mouseReleaseEvent(QMouseEvent *event)
{ {
Q_UNUSED(event) Q_UNUSED(event)
QMessageBox::information(this, "Help", this->help_text); Utils::showInfo(this, m_text, m_informativeText, {}, m_doc);
} }
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)

View file

@ -42,9 +42,8 @@ class HelpLabel : public QLabel
Q_OBJECT Q_OBJECT
public: public:
QString help_text;
QFont font; QFont font;
void setHelpText(const QString &text); void setHelpText(const QString &text, const QString &informativeText, const QString &doc = "");
explicit HelpLabel(QWidget * parent); explicit HelpLabel(QWidget * parent);
@ -56,6 +55,11 @@ protected:
void enterEvent(QEnterEvent *event) override; void enterEvent(QEnterEvent *event) override;
#endif #endif
void leaveEvent(QEvent *event) override; void leaveEvent(QEvent *event) override;
private:
QString m_text;
QString m_informativeText;
QString m_doc;
}; };
class ClickableLabel : public QLabel { class ClickableLabel : public QLabel {

View file

@ -9,23 +9,29 @@
BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet) BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet)
: WindowModalDialog(parent) : WindowModalDialog(parent)
, m_wallet(wallet)
, ui(new Ui::BalanceDialog) , ui(new Ui::BalanceDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->label_unconfirmed_help->setHelpText("Outputs require 10 confirmations before they become spendable. " connect(m_wallet, &Wallet::balanceUpdated, this, &BalanceDialog::updateBalance);
"This will take 20 minutes on average.");
ui->label_unconfirmed_help->setHelpText("Unconfirmed balance", "Outputs require 10 confirmations before they become spendable. "
"This will take 20 minutes on average.", "balance");
ui->label_unconfirmed->setText(WalletManager::displayAmount(wallet->balance() - wallet->unlockedBalance()));
ui->label_unconfirmed->setFont(Utils::getMonospaceFont()); ui->label_unconfirmed->setFont(Utils::getMonospaceFont());
ui->label_spendable->setText(WalletManager::displayAmount(wallet->unlockedBalance()));
ui->label_spendable->setFont(Utils::getMonospaceFont()); ui->label_spendable->setFont(Utils::getMonospaceFont());
ui->label_total->setText(WalletManager::displayAmount(wallet->balance()));
ui->label_total->setFont(Utils::getMonospaceFont()); ui->label_total->setFont(Utils::getMonospaceFont());
this->updateBalance();
this->adjustSize(); this->adjustSize();
} }
void BalanceDialog::updateBalance() {
ui->label_unconfirmed->setText(WalletManager::displayAmount(m_wallet->balance() - m_wallet->unlockedBalance()));
ui->label_spendable->setText(WalletManager::displayAmount(m_wallet->unlockedBalance()));
ui->label_total->setText(WalletManager::displayAmount(m_wallet->balance()));
}
BalanceDialog::~BalanceDialog() = default; BalanceDialog::~BalanceDialog() = default;

View file

@ -22,7 +22,10 @@ public:
~BalanceDialog() override; ~BalanceDialog() override;
private: private:
void updateBalance();
QScopedPointer<Ui::BalanceDialog> ui; QScopedPointer<Ui::BalanceDialog> ui;
Wallet *m_wallet;
}; };
#endif //FEATHER_BALANCEDIALOG_H #endif //FEATHER_BALANCEDIALOG_H

View file

@ -19,8 +19,8 @@ CalcConfigDialog::CalcConfigDialog(QWidget *parent)
connect(ui->btn_deselectAll, &QPushButton::clicked, this, &CalcConfigDialog::deselectAll); connect(ui->btn_deselectAll, &QPushButton::clicked, this, &CalcConfigDialog::deselectAll);
connect(ui->buttonBox, &QDialogButtonBox::accepted, [this]{ connect(ui->buttonBox, &QDialogButtonBox::accepted, [this]{
config()->set(Config::fiatSymbols, this->checkedFiat()); conf()->set(Config::fiatSymbols, this->checkedFiat());
config()->set(Config::cryptoSymbols, this->checkedCrypto()); conf()->set(Config::cryptoSymbols, this->checkedCrypto());
this->accept(); this->accept();
}); });
@ -75,8 +75,8 @@ void CalcConfigDialog::fillListWidgets() {
QStringList cryptoCurrencies = appData()->prices.markets.keys(); QStringList cryptoCurrencies = appData()->prices.markets.keys();
QStringList fiatCurrencies = appData()->prices.rates.keys(); QStringList fiatCurrencies = appData()->prices.rates.keys();
QStringList checkedCryptoCurrencies = config()->get(Config::cryptoSymbols).toStringList(); QStringList checkedCryptoCurrencies = conf()->get(Config::cryptoSymbols).toStringList();
QStringList checkedFiatCurrencies = config()->get(Config::fiatSymbols).toStringList(); QStringList checkedFiatCurrencies = conf()->get(Config::fiatSymbols).toStringList();
ui->list_crypto->addItems(cryptoCurrencies); ui->list_crypto->addItems(cryptoCurrencies);
ui->list_fiat->addItems(fiatCurrencies); ui->list_fiat->addItems(fiatCurrencies);

View file

@ -61,13 +61,13 @@ void DebugInfoDialog::updateInfo() {
ui->label_remoteNode->setText(node.toAddress()); ui->label_remoteNode->setText(node.toAddress());
ui->label_walletStatus->setText(this->statusToString(m_wallet->connectionStatus())); ui->label_walletStatus->setText(this->statusToString(m_wallet->connectionStatus()));
QString websocketStatus = Utils::QtEnumToString(websocketNotifier()->websocketClient->webSocket->state()).remove("State"); QString websocketStatus = Utils::QtEnumToString(websocketNotifier()->websocketClient->webSocket->state()).remove("State");
if (config()->get(Config::disableWebsocket).toBool()) { if (conf()->get(Config::disableWebsocket).toBool()) {
websocketStatus = "Disabled"; websocketStatus = "Disabled";
} }
ui->label_websocketStatus->setText(websocketStatus); ui->label_websocketStatus->setText(websocketStatus);
QString proxy = [](){ QString proxy = [](){
int proxy = config()->get(Config::proxy).toInt(); int proxy = conf()->get(Config::proxy).toInt();
switch (proxy) { switch (proxy) {
case 0: case 0:
return "None"; return "None";
@ -84,7 +84,7 @@ void DebugInfoDialog::updateInfo() {
ui->label_proxy->setText(proxy); ui->label_proxy->setText(proxy);
ui->label_torStatus->setText(torStatus); ui->label_torStatus->setText(torStatus);
ui->label_torLevel->setText(config()->get(Config::torPrivacyLevel).toString()); ui->label_torLevel->setText(conf()->get(Config::torPrivacyLevel).toString());
QString seedType = [this](){ QString seedType = [this](){
if (m_wallet->isHwBacked()) if (m_wallet->isHwBacked())
@ -107,7 +107,7 @@ void DebugInfoDialog::updateInfo() {
}(); }();
QString networkType = Utils::QtEnumToString(m_wallet->nettype()); QString networkType = Utils::QtEnumToString(m_wallet->nettype());
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
networkType += " (offline)"; networkType += " (offline)";
} }
ui->label_netType->setText(networkType); ui->label_netType->setText(networkType);

225
src/dialog/DocsDialog.cpp Normal file
View file

@ -0,0 +1,225 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "DocsDialog.h"
#include "ui_DocsDialog.h"
#include <QScrollBar>
#include "utils/Utils.h"
#include "ColorScheme.h"
DocsDialog::DocsDialog(QWidget *parent)
: WindowModalDialog(parent)
, ui(new Ui::DocsDialog)
{
ui->setupUi(this);
ui->toolButton_backward->setIcon(style()->standardIcon(QStyle::SP_ArrowBack));
ui->toolButton_forward->setIcon(style()->standardIcon(QStyle::SP_ArrowForward));
ui->splitter->setStretchFactor(1, 8);
QRegularExpression navTitleRe{R"(\[nav_title\]: # \((.+?)\)\n)"};
QRegularExpression categoryRe{R"(\[category\]: # \((.+?)\)\n)"};
QSet<QString> categories;
QDirIterator it(":/docs/", QDirIterator::Subdirectories);
while (it.hasNext()) {
QString resource = it.next();
QString docString = Utils::loadQrc(resource);
resource = "qrc" + resource;
// Extract navigation title from metadata
QRegularExpressionMatch navTitleMatch = navTitleRe.match(docString);
if (!navTitleMatch.hasMatch()) {
continue;
}
QString navTitle = navTitleMatch.captured(1);
navTitle.replace("\\(", "(").replace("\\)", ")");
// Extract category from metadata
QRegularExpressionMatch categoryMatch = categoryRe.match(docString);
if (!categoryMatch.hasMatch()) {
continue;
}
QString category = categoryMatch.captured(1);
m_categoryIndex[category].append(resource);
m_navTitleIndex[resource] = navTitle;
categories += category;
m_docs[resource] = docString.toLower();
}
// Add categories and docs to index
QList<QTreeWidgetItem *> items;
for (const auto& category : categories) {
auto *categoryWidget = new QTreeWidgetItem(static_cast<QTreeWidget *>(nullptr), {category});
QList<QTreeWidgetItem *> subItems;
for (const auto& resource : m_categoryIndex[category]) {
auto *docWidget = new QTreeWidgetItem(static_cast<QTreeWidget *>(nullptr), {m_navTitleIndex[resource]});
docWidget->setData(0, Qt::UserRole,resource);
subItems.append(docWidget);
m_items[resource] = docWidget;
}
categoryWidget->addChildren(subItems);
items.append(categoryWidget);
}
ui->index->addTopLevelItems(items);
ui->index->sortItems(0, Qt::SortOrder::AscendingOrder);
connect(ui->index, &QTreeWidget::itemClicked, [this](QTreeWidgetItem *current, int column){
if (ui->index->indexOfTopLevelItem(current) != -1) {
current->setExpanded(!current->isExpanded());
return;
}
QString resource = current->data(0, Qt::UserRole).toString();
this->showDoc(resource);
});
connect(ui->textBrowser, &QTextBrowser::anchorClicked, [this](const QUrl &url){
QString doc = url.toString();
if (!doc.startsWith("qrc")) {
Utils::externalLinkWarning(this, doc);
this->showDoc(m_currentSource);
return;
}
doc.replace("-", "_");
doc += ".md";
this->showDoc(doc);
});
connect(ui->textBrowser, &QTextBrowser::sourceChanged, [this](const QUrl& source){
this->updateHighlights(ui->search->text());
QString newSource = source.toString();
if (m_currentSource == newSource) {
return;
}
m_currentSource = newSource;
if (m_items.contains(m_currentSource)) {
ui->index->setCurrentItem(m_items[m_currentSource]);
}
});
connect(ui->textBrowser, &QTextBrowser::backwardAvailable, [this](bool available){
ui->toolButton_backward->setEnabled(available);
});
connect(ui->textBrowser, &QTextBrowser::forwardAvailable, [this](bool available){
ui->toolButton_forward->setEnabled(available);
});
connect(ui->toolButton_backward, &QToolButton::clicked, [this]{
ui->textBrowser->backward();
});
connect(ui->toolButton_forward, &QToolButton::clicked, [this]{
ui->textBrowser->forward();
});
connect(ui->search, &QLineEdit::textEdited, [this](const QString &text){
this->filterIndex(text);
this->updateHighlights(ui->search->text());
});
this->showDoc("report_an_issue");
}
void DocsDialog::filterIndex(const QString &text) {
QTreeWidgetItemIterator it(ui->index);
while (*it) {
QString resource = (*it)->data(0, Qt::UserRole).toString();
bool docContainsText = m_docs[resource].contains(text, Qt::CaseInsensitive);
bool titleContainsText = (*it)->text(0).contains(text, Qt::CaseInsensitive);
if (titleContainsText && !text.isEmpty()) {
ColorScheme::updateFromWidget(this);
(*it)->setBackground(0, ColorScheme::YELLOW.asColor(true));
} else {
(*it)->setBackground(0, Qt::transparent);
}
if (docContainsText || titleContainsText) {
(*it)->setHidden(false);
QTreeWidgetItem *parent = (*it)->parent();
if (parent) {
parent->setHidden(false);
parent->setExpanded(true);
}
} else {
(*it)->setHidden(true);
}
++it;
}
}
void DocsDialog::showDoc(const QString &doc, const QString &highlight) {
QString resource = doc;
if (!resource.startsWith("qrc")) {
resource = "qrc:/docs/" + doc + ".md";
}
if (m_items.contains(resource)) {
ui->index->setCurrentItem(m_items[doc]);
}
QString file = resource;
file.remove("qrc");
if (!QFile::exists(file)) {
Utils::showError(this, "Unable to load document", "File does not exist");
ui->textBrowser->setSource(m_currentSource);
return;
}
ui->textBrowser->setSource(QUrl(resource));
}
void DocsDialog::updateHighlights(const QString &searchString, bool scrollToCursor) {
QTextDocument *document = ui->textBrowser->document();
QTextCursor cursor(document);
// Clear highlighting
QTextCursor cursor2(document);
cursor2.select(QTextCursor::Document);
QTextCharFormat defaultFormat;
defaultFormat.setBackground(Qt::transparent);
cursor2.mergeCharFormat(defaultFormat);
bool firstFind = true;
if (!searchString.isEmpty()) {
while (!cursor.isNull() && !cursor.atEnd()) {
cursor = document->find(searchString, cursor);
if (!cursor.isNull()) {
if (firstFind) {
// Scroll to first match
QRect cursorRect = ui->textBrowser->cursorRect(cursor);
int positionToScroll = ui->textBrowser->verticalScrollBar()->value() + cursorRect.top();
ui->textBrowser->verticalScrollBar()->setValue(positionToScroll);
firstFind = false;
}
ColorScheme::updateFromWidget(this);
QTextCharFormat colorFormat(cursor.charFormat());
colorFormat.setBackground(ColorScheme::YELLOW.asColor(true));
cursor.mergeCharFormat(colorFormat);
}
}
}
}
DocsDialog::~DocsDialog() = default;

42
src/dialog/DocsDialog.h Normal file
View file

@ -0,0 +1,42 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_DOCSDIALOG_H
#define FEATHER_DOCSDIALOG_H
#include <QDialog>
#include <QStringListModel>
#include <QTreeWidgetItem>
#include "components.h"
namespace Ui {
class DocsDialog;
}
class DocsDialog : public WindowModalDialog
{
Q_OBJECT
public:
explicit DocsDialog(QWidget *parent = nullptr);
~DocsDialog() override;
void filterIndex(const QString &text);
void showDoc(const QString &doc, const QString& highlight = "");
void updateHighlights(const QString &highlight, bool scrollToCursor = false);
bool wordMatch(QString &search);
private:
QScopedPointer<Ui::DocsDialog> ui;
QString m_currentSource = "";
QMap<QString, QString> m_docs;
QMap<QString, QStringList> m_categoryIndex;
QMap<QString, QString> m_navTitleIndex;
QMap<QString, QTreeWidgetItem *> m_items;
};
#endif //FEATHER_DOCSDIALOG_H

134
src/dialog/DocsDialog.ui Normal file
View file

@ -0,0 +1,134 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DocsDialog</class>
<widget class="QDialog" name="DocsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>757</width>
<height>399</height>
</rect>
</property>
<property name="windowTitle">
<string>Docs</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>9</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>9</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="verticalLayoutWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="search">
<property name="placeholderText">
<string>Search..</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton_backward">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton_forward">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTreeWidget" name="index">
<attribute name="headerVisible">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Index</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QTextBrowser" name="textBrowser"/>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>DocsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>DocsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -4,7 +4,7 @@
#include "PasswordChangeDialog.h" #include "PasswordChangeDialog.h"
#include "ui_PasswordChangeDialog.h" #include "ui_PasswordChangeDialog.h"
#include <QMessageBox> #include "Utils.h"
PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet) PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet)
: WindowModalDialog(parent) : WindowModalDialog(parent)
@ -50,7 +50,7 @@ void PasswordChangeDialog::setPassword() {
QString newPassword = ui->lineEdit_newPassword->text(); QString newPassword = ui->lineEdit_newPassword->text();
if (!m_wallet->verifyPassword(currentPassword)) { if (!m_wallet->verifyPassword(currentPassword)) {
QMessageBox::warning(this, "Error", "Incorrect password"); Utils::showError(this, "Incorrect password", "");
ui->lineEdit_currentPassword->setText(""); ui->lineEdit_currentPassword->setText("");
ui->lineEdit_currentPassword->setFocus(); ui->lineEdit_currentPassword->setFocus();
return; return;
@ -61,7 +61,7 @@ void PasswordChangeDialog::setPassword() {
this->accept(); this->accept();
} }
else { else {
QMessageBox::warning(this, "Error", QString("Error: %1").arg(m_wallet->errorString())); Utils::showError(this, "Unable to change password", m_wallet->errorString());
} }
} }

View file

@ -43,8 +43,8 @@ SeedDialog::SeedDialog(Wallet *wallet, QWidget *parent)
ui->frameRestoreHeight->setVisible(toggled); ui->frameRestoreHeight->setVisible(toggled);
}); });
ui->label_restoreHeightHelp->setHelpText("Should you restore your wallet in the future, " ui->label_restoreHeightHelp->setHelpText("", "Should you restore your wallet in the future, "
"specifying this block number will recover your wallet quicker."); "specifying this block number will recover your wallet quicker.", "restore_height");
this->adjustSize(); this->adjustSize();
} }

View file

@ -49,8 +49,12 @@ void SignVerifyDialog::signMessage() {
void SignVerifyDialog::verifyMessage() { void SignVerifyDialog::verifyMessage() {
bool verified = m_wallet->verifySignedMessage(ui->message->toPlainText(), ui->address->text(), ui->signature->text()); bool verified = m_wallet->verifySignedMessage(ui->message->toPlainText(), ui->address->text(), ui->signature->text());
verified ? QMessageBox::information(this, "Information", "Signature is valid")
: QMessageBox::warning(this, "Warning", "Signature failed to verify"); if (verified) {
Utils::showInfo(this, "Signature is valid");
} else {
Utils::showError(this, "Signature is not valid");
}
} }
void SignVerifyDialog::copyToClipboard() { void SignVerifyDialog::copyToClipboard() {

View file

@ -45,14 +45,13 @@ void TxBroadcastDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) {
} }
if (!resp.ok) { if (!resp.ok) {
QMessageBox::warning(this, "Transaction broadcast", resp.status); Utils::showError(this, "Failed to broadcast transaction", resp.status);
return; return;
} }
this->accept(); this->accept();
QMessageBox::information(this, "Transaction broadcast", "Transaction submitted successfully.\n\n" Utils::showInfo(this, "Transaction submitted successfully", "If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab.");
"If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab.");
} }
TxBroadcastDialog::~TxBroadcastDialog() = default; TxBroadcastDialog::~TxBroadcastDialog() = default;

View file

@ -111,7 +111,7 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) {
} }
void TxConfAdvDialog::setAmounts(quint64 amount, quint64 fee) { void TxConfAdvDialog::setAmounts(quint64 amount, quint64 fee) {
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString(); QString preferredCur = conf()->get(Config::preferredFiatCurrency).toString();
auto convert = [preferredCur](double amount){ auto convert = [preferredCur](double amount){
return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2); return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);
@ -164,33 +164,54 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) {
void TxConfAdvDialog::signTransaction() { void TxConfAdvDialog::signTransaction() {
QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch())); QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
QString fn = QFileDialog::getSaveFileName(this, "Save signed transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)"); QString fn = QFileDialog::getSaveFileName(this, "Save signed transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)");
if(fn.isEmpty()) return; if (fn.isEmpty()) {
return;
}
m_utx->sign(fn) ? QMessageBox::information(this, "Sign transaction", "Transaction saved successfully") bool success = m_utx->sign(fn);
: QMessageBox::warning(this, "Sign transaction", "Failed to save transaction to file.");
if (success) {
Utils::showInfo(this, "Transaction saved successfully");
} else {
Utils::showError(this, "Failed to save transaction to file");
}
} }
void TxConfAdvDialog::unsignedSaveFile() { void TxConfAdvDialog::unsignedSaveFile() {
QString defaultName = QString("%1_unsigned_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch())); QString defaultName = QString("%1_unsigned_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*unsigned_monero_tx)"); QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*unsigned_monero_tx)");
if(fn.isEmpty()) return; if (fn.isEmpty()) {
return;
}
m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully") bool success = m_tx->saveToFile(fn);
: QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file.");
if (success) {
Utils::showInfo(this, "Transaction saved successfully");
} else {
Utils::showError(this, "Failed to save transaction to file");
}
} }
void TxConfAdvDialog::signedSaveFile() { void TxConfAdvDialog::signedSaveFile() {
QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch())); QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)"); QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)");
if(fn.isEmpty()) return; if (fn.isEmpty()) {
return;
}
m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully") bool success = m_tx->saveToFile(fn);
: QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file.");
if (success) {
Utils::showInfo(this, "Transaction saved successfully");
} else {
Utils::showError(this, "Failed to save transaction to file");
}
} }
void TxConfAdvDialog::unsignedQrCode() { void TxConfAdvDialog::unsignedQrCode() {
if (m_tx->unsignedTxToBin().size() > 2953) { if (m_tx->unsignedTxToBin().size() > 2953) {
QMessageBox::warning(this, "Unable to show QR code", "Transaction size exceeds the maximum size for QR codes (2953 bytes)."); Utils::showError(this, "Unable to show QR code", "Transaction size exceeds the maximum size for QR codes (2953 bytes)");
return; return;
} }
@ -209,7 +230,7 @@ void TxConfAdvDialog::signedCopy() {
void TxConfAdvDialog::txKeyCopy() { void TxConfAdvDialog::txKeyCopy() {
if (m_wallet->isHwBacked()) { if (m_wallet->isHwBacked()) {
QMessageBox::warning(this, "Unable to get tx private key", "Unable to get tx secret key: wallet is backed by hardware device"); Utils::showError(this, "Unable to copy transaction private key", "Function not supported for hardware wallets");
return; return;
} }

View file

@ -4,8 +4,6 @@
#include "TxConfDialog.h" #include "TxConfDialog.h"
#include "ui_TxConfDialog.h" #include "ui_TxConfDialog.h"
#include <QMessageBox>
#include "constants.h" #include "constants.h"
#include "TxConfAdvDialog.h" #include "TxConfAdvDialog.h"
#include "utils/AppData.h" #include "utils/AppData.h"
@ -26,7 +24,7 @@ TxConfDialog::TxConfDialog(Wallet *wallet, PendingTransaction *tx, const QString
ui->label_warning->setText("You are about to send a transaction.\nVerify the information below."); ui->label_warning->setText("You are about to send a transaction.\nVerify the information below.");
ui->label_note->hide(); ui->label_note->hide();
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString(); QString preferredCur = conf()->get(Config::preferredFiatCurrency).toString();
auto convert = [preferredCur](double amount){ auto convert = [preferredCur](double amount){
return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2); return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);

View file

@ -24,20 +24,19 @@ void TxImportDialog::onImport() {
QString txid = ui->line_txid->text(); QString txid = ui->line_txid->text();
if (m_wallet->haveTransaction(txid)) { if (m_wallet->haveTransaction(txid)) {
QMessageBox::warning(this, "Warning", "This transaction already exists in the wallet. " Utils::showWarning(this, "Transaction already exists in wallet", "If you can't find it in your history, "
"If you can't find it in your history, " "check if it belongs to a different account (Wallet -> Account)");
"check if it belongs to a different account (Wallet -> Account)");
return; return;
} }
if (m_wallet->importTransaction(txid)) { if (m_wallet->importTransaction(txid)) {
if (!m_wallet->haveTransaction(txid)) { if (!m_wallet->haveTransaction(txid)) {
QMessageBox::warning(this, "Import transaction", "This transaction does not belong to this wallet."); Utils::showError(this, "Unable to import transaction", "This transaction does not belong to the wallet");
return; return;
} }
QMessageBox::information(this, "Import transaction", "Transaction imported successfully."); Utils::showInfo(this, "Transaction imported successfully", "");
} else { } else {
QMessageBox::warning(this, "Import transaction", "Transaction import failed."); Utils::showError(this, "Failed to import transaction", "");
} }
m_wallet->refreshModels(); m_wallet->refreshModels();
} }

View file

@ -116,7 +116,7 @@ void TxInfoDialog::setData(TransactionInfo *tx) {
ui->label_status->setText("Status: Unconfirmed (in mempool)"); ui->label_status->setText("Status: Unconfirmed (in mempool)");
} }
else { else {
QString dateTimeFormat = QString("%1 %2").arg(config()->get(Config::dateFormat).toString(), config()->get(Config::timeFormat).toString()); QString dateTimeFormat = QString("%1 %2").arg(conf()->get(Config::dateFormat).toString(), conf()->get(Config::timeFormat).toString());
QString date = tx->timestamp().toString(dateTimeFormat); QString date = tx->timestamp().toString(dateTimeFormat);
QString statusText = QString("Status: Included in block %1 (%2 confirmations) on %3").arg(blockHeight, QString::number(tx->confirmations()), date); QString statusText = QString("Status: Included in block %1 (%2 confirmations) on %3").arg(blockHeight, QString::number(tx->confirmations()), date);
ui->label_status->setText(statusText); ui->label_status->setText(statusText);
@ -160,14 +160,14 @@ void TxInfoDialog::copyTxID() {
void TxInfoDialog::copyTxKey() { void TxInfoDialog::copyTxKey() {
if (m_wallet->isHwBacked()) { if (m_wallet->isHwBacked()) {
QMessageBox::warning(this, "Unable to get tx private key", "Unable to get tx secret key: wallet is backed by hardware device"); Utils::showError(this, "Unable to get transaction secret key", "Function not supported on hardware device");
return; return;
} }
m_wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){ m_wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){
QString txKey = map.value("tx_key").toString(); QString txKey = map.value("tx_key").toString();
if (txKey.isEmpty()) { if (txKey.isEmpty()) {
QMessageBox::warning(this, "Unable to copy transaction key", "Transaction key unknown"); Utils::showError(this, "Unable to copy transaction secret key", "Transaction secret key is unknown");
} else { } else {
Utils::copyToClipboard(txKey); Utils::copyToClipboard(txKey);
QMessageBox::information(this, "Transaction key copied", "Transaction key copied to clipboard."); QMessageBox::information(this, "Transaction key copied", "Transaction key copied to clipboard.");
@ -181,7 +181,7 @@ void TxInfoDialog::createTxProof() {
} }
void TxInfoDialog::viewOnBlockExplorer() { void TxInfoDialog::viewOnBlockExplorer() {
Utils::externalLinkWarning(this, Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, m_txid)); Utils::externalLinkWarning(this, Utils::blockExplorerLink(conf()->get(Config::blockExplorer).toString(), constants::networkType, m_txid));
} }
TxInfoDialog::~TxInfoDialog() = default; TxInfoDialog::~TxInfoDialog() = default;

View file

@ -156,7 +156,7 @@ void TxProofDialog::getFormattedProof() {
TxProof proof = this->getProof(); TxProof proof = this->getProof();
if (!proof.error.isEmpty()) { if (!proof.error.isEmpty()) {
QMessageBox::warning(this, "Get formatted proof", QString("Failed to get proof signature: %1").arg(proof.error)); Utils::showError(this, "Failed to get formatted proof", proof.error);
return; return;
} }
@ -216,7 +216,7 @@ void TxProofDialog::getSignature() {
TxProof proof = this->getProof(); TxProof proof = this->getProof();
if (!proof.error.isEmpty()) { if (!proof.error.isEmpty()) {
QMessageBox::warning(this, "Get proof signature", QString("Failed to get proof signature: %1").arg(proof.error)); Utils::showError(this, "Failed to get transaction proof", proof.error);
return; return;
} }

View file

@ -179,8 +179,12 @@ void VerifyProofDialog::proofStatus(bool success, const QString &message) {
} }
else { else {
ui->btn_verify->setEnabled(true); ui->btn_verify->setEnabled(true);
success ? QMessageBox::information(this, "Information", message)
: QMessageBox::warning(this, "Warning", message); if (success) {
Utils::showInfo(this, message);
} else {
Utils::showError(this, message);
}
} }
} }

View file

@ -28,15 +28,15 @@ WalletInfoDialog::WalletInfoDialog(Wallet *wallet, QWidget *parent)
connect(ui->btn_openWalletDir, &QPushButton::clicked, this, &WalletInfoDialog::openWalletDir); connect(ui->btn_openWalletDir, &QPushButton::clicked, this, &WalletInfoDialog::openWalletDir);
ui->label_keysFileLabel->setHelpText("The \"keys file\" stores the wallet keys and wallet settings. " ui->label_keysFileLabel->setHelpText("", "The \"keys file\" stores the wallet keys and wallet settings. "
"It is encrypted with the wallet password (if set).\n\n" "It is encrypted with the wallet password (if set).\n\n"
"Your funds will be irreversibly lost if you delete this file " "Your funds will be irreversibly lost if you delete this file "
"without having a backup of your mnemonic seed or private keys."); "without having a backup of your mnemonic seed or private keys.", "wallet_files");
ui->label_cacheFileLabel->setHelpText("The \"cache file\" stores transaction data, contacts, address labels, " ui->label_cacheFileLabel->setHelpText("", "The \"cache file\" stores transaction data, contacts, address labels, "
"block hashes, the 14-word seed (if applicable), and other miscellaneous information. " "block hashes, the 14-word seed (if applicable), and other miscellaneous information. "
"It is encrypted with the wallet password (if set).\n\n" "It is encrypted with the wallet password (if set).\n\n"
"Warning: Transaction keys and the 14-word seed CANNOT be recovered if this file is deleted."); "Warning: Transaction keys and the 14-word seed CANNOT be recovered if this file is deleted.", "wallet_files");
this->adjustSize(); this->adjustSize();
} }

View file

@ -15,6 +15,10 @@ QString PendingTransaction::errorString() const
return QString::fromStdString(m_pimpl->errorString()); return QString::fromStdString(m_pimpl->errorString());
} }
const std::exception_ptr PendingTransaction::getException() const {
return m_pimpl->getException();
}
bool PendingTransaction::commit() bool PendingTransaction::commit()
{ {
return m_pimpl->commit(); return m_pimpl->commit();

View file

@ -27,11 +27,11 @@ public:
Priority_Medium = Monero::PendingTransaction::Priority_Medium, Priority_Medium = Monero::PendingTransaction::Priority_Medium,
Priority_High = Monero::PendingTransaction::Priority_High Priority_High = Monero::PendingTransaction::Priority_High
}; };
Q_ENUM(Priority)
Status status() const; Status status() const;
QString errorString() const; QString errorString() const;
const std::exception_ptr getException() const;
bool commit(); bool commit();
bool saveToFile(const QString &fileName); bool saveToFile(const QString &fileName);
quint64 amount() const; quint64 amount() const;

View file

@ -176,7 +176,7 @@ bool TransactionHistory::writeCSV(const QString &path) {
// calc historical fiat price // calc historical fiat price
QString fiatAmount; QString fiatAmount;
QString preferredFiatSymbol = config()->get(Config::preferredFiatCurrency).toString(); QString preferredFiatSymbol = conf()->get(Config::preferredFiatCurrency).toString();
const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd")); const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
double fiat_price = usd_price * amount; double fiat_price = usd_price * amount;

View file

@ -74,12 +74,10 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent)
this->history()->refresh(this->currentSubaddressAccount()); this->history()->refresh(this->currentSubaddressAccount());
}); });
connect(this, &Wallet::createTransactionError, this, &Wallet::onCreateTransactionError);
connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed); connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed);
connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock); connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock);
connect(this, &Wallet::updated, this, &Wallet::onUpdated); connect(this, &Wallet::updated, this, &Wallet::onUpdated);
connect(this, &Wallet::heightsRefreshed, this, &Wallet::onHeightsRefreshed); connect(this, &Wallet::heightsRefreshed, this, &Wallet::onHeightsRefreshed);
connect(this, &Wallet::transactionCreated, this, &Wallet::onTransactionCreated);
connect(this, &Wallet::transactionCommitted, this, &Wallet::onTransactionCommitted); connect(this, &Wallet::transactionCommitted, this, &Wallet::onTransactionCommitted);
} }
@ -671,21 +669,6 @@ void Wallet::setSelectedInputs(const QStringList &selectedInputs) {
void Wallet::createTransaction(const QString &address, quint64 amount, const QString &description, bool all) { void Wallet::createTransaction(const QString &address, quint64 amount, const QString &description, bool all) {
this->tmpTxDescription = description; this->tmpTxDescription = description;
if (!all && amount == 0) {
emit createTransactionError("Cannot send nothing");
return;
}
quint64 unlocked_balance = this->unlockedBalance();
if (!all && amount > unlocked_balance) {
emit createTransactionError(QString("Not enough money to spend.\n\n"
"Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance)));
return;
} else if (unlocked_balance == 0) {
emit createTransactionError("No money to spend");
return;
}
qInfo() << "Creating transaction"; qInfo() << "Creating transaction";
m_scheduler.run([this, all, address, amount] { m_scheduler.run([this, all, address, amount] {
std::set<uint32_t> subaddr_indices; std::set<uint32_t> subaddr_indices;
@ -695,7 +678,7 @@ void Wallet::createTransaction(const QString &address, quint64 amount, const QSt
currentSubaddressAccount(), subaddr_indices, m_selectedInputs); currentSubaddressAccount(), subaddr_indices, m_selectedInputs);
QVector<QString> addresses{address}; QVector<QString> addresses{address};
emit transactionCreated(ptImpl, addresses); this->onTransactionCreated(ptImpl, addresses);
}); });
emit initiateTransaction(); emit initiateTransaction();
@ -704,16 +687,6 @@ void Wallet::createTransaction(const QString &address, quint64 amount, const QSt
void Wallet::createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description) { void Wallet::createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description) {
this->tmpTxDescription = description; this->tmpTxDescription = description;
quint64 total_amount = 0;
for (auto &amount : amounts) {
total_amount += amount;
}
auto unlocked_balance = this->unlockedBalance();
if (total_amount > unlocked_balance) {
emit createTransactionError("Not enough money to spend");
}
qInfo() << "Creating transaction"; qInfo() << "Creating transaction";
m_scheduler.run([this, addresses, amounts] { m_scheduler.run([this, addresses, amounts] {
std::vector<std::string> dests; std::vector<std::string> dests;
@ -731,7 +704,7 @@ void Wallet::createTransactionMultiDest(const QVector<QString> &addresses, const
static_cast<Monero::PendingTransaction::Priority>(this->tx_priority), static_cast<Monero::PendingTransaction::Priority>(this->tx_priority),
currentSubaddressAccount(), subaddr_indices, m_selectedInputs); currentSubaddressAccount(), subaddr_indices, m_selectedInputs);
emit transactionCreated(ptImpl, addresses); this->onTransactionCreated(ptImpl, addresses);
}); });
emit initiateTransaction(); emit initiateTransaction();
@ -751,7 +724,7 @@ void Wallet::sweepOutputs(const QVector<QString> &keyImages, QString address, bo
Monero::PendingTransaction *ptImpl = m_walletImpl->createTransactionSelected(kis, address.toStdString(), outputs, static_cast<Monero::PendingTransaction::Priority>(this->tx_priority)); Monero::PendingTransaction *ptImpl = m_walletImpl->createTransactionSelected(kis, address.toStdString(), outputs, static_cast<Monero::PendingTransaction::Priority>(this->tx_priority));
QVector<QString> addresses {address}; QVector<QString> addresses {address};
emit transactionCreated(ptImpl, addresses); this->onTransactionCreated(ptImpl, addresses);
}); });
emit initiateTransaction(); emit initiateTransaction();
@ -759,11 +732,6 @@ void Wallet::sweepOutputs(const QVector<QString> &keyImages, QString address, bo
// Phase 2: Transaction construction completed // Phase 2: Transaction construction completed
void Wallet::onCreateTransactionError(const QString &msg) {
this->tmpTxDescription = "";
emit endTransaction();
}
void Wallet::onTransactionCreated(Monero::PendingTransaction *mtx, const QVector<QString> &address) { void Wallet::onTransactionCreated(Monero::PendingTransaction *mtx, const QVector<QString> &address) {
qDebug() << Q_FUNC_INFO; qDebug() << Q_FUNC_INFO;
@ -775,11 +743,8 @@ void Wallet::onTransactionCreated(Monero::PendingTransaction *mtx, const QVector
} }
} }
// Let UI know that the transaction was constructed
emit endTransaction();
// tx created, but not sent yet. ask user to verify first. // tx created, but not sent yet. ask user to verify first.
emit createTransactionSuccess(tx, address); emit transactionCreated(tx, address);
} }
// Phase 3: Commit or dispose // Phase 3: Commit or dispose
@ -821,7 +786,7 @@ void Wallet::onTransactionCommitted(bool success, PendingTransaction *tx, const
// Nodes - even well-connected, properly configured ones - consistently fail to relay transactions // Nodes - even well-connected, properly configured ones - consistently fail to relay transactions
// To mitigate transactions failing we just send the transaction to every node we know about over Tor // To mitigate transactions failing we just send the transaction to every node we know about over Tor
if (config()->get(Config::multiBroadcast).toBool()) { if (conf()->get(Config::multiBroadcast).toBool()) {
// Let MainWindow handle this // Let MainWindow handle this
emit multiBroadcast(txHexMap); emit multiBroadcast(txHexMap);
} }

View file

@ -300,7 +300,6 @@ public:
void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description); void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void sweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs); void sweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs);
void onCreateTransactionError(const QString &msg);
void commitTransaction(PendingTransaction *tx, const QString &description=""); void commitTransaction(PendingTransaction *tx, const QString &description="");
void onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid, const QMap<QString, QString> &txHexMap); void onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid, const QMap<QString, QString> &txHexMap);
@ -416,9 +415,6 @@ signals:
void transactionProofVerified(TxProofResult result); void transactionProofVerified(TxProofResult result);
void spendProofVerified(QPair<bool, bool> result); void spendProofVerified(QPair<bool, bool> result);
// emitted when transaction is created async
void transactionCreated(Monero::PendingTransaction *ptImpl, QVector<QString> address);
void connectionStatusChanged(int status) const; void connectionStatusChanged(int status) const;
void currentSubaddressAccountChanged() const; void currentSubaddressAccountChanged() const;
@ -428,13 +424,12 @@ signals:
void balanceUpdated(quint64 balance, quint64 spendable); void balanceUpdated(quint64 balance, quint64 spendable);
void keysCorrupted(); void keysCorrupted();
void endTransaction(); void transactionCreated(PendingTransaction *tx, const QVector<QString> &address);
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void donationSent(); void donationSent();
void walletRefreshed(); void walletRefreshed();
void initiateTransaction(); void initiateTransaction();
void createTransactionError(QString message);
void selectedInputsChanged(const QStringList &selectedInputs); void selectedInputsChanged(const QStringList &selectedInputs);

View file

@ -42,7 +42,7 @@ void signal_handler(int signum) {
std::cout << keyStream.str(); std::cout << keyStream.str();
// Write stack trace to disk // Write stack trace to disk
if (config()->get(Config::writeStackTraceToDisk).toBool()) { if (conf()->get(Config::writeStackTraceToDisk).toBool()) {
QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"}; QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
std::ofstream out(crashLogPath.toStdString()); std::ofstream out(crashLogPath.toStdString());
out << QString("Version: %1-%2\n").arg(FEATHER_VERSION, FEATHER_COMMIT).toStdString(); out << QString("Version: %1-%2\n").arg(FEATHER_VERSION, FEATHER_COMMIT).toStdString();
@ -165,12 +165,12 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
bool logLevelFromEnv; bool logLevelFromEnv;
int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv); int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv);
if (logLevelFromEnv) { if (logLevelFromEnv) {
config()->set(Config::logLevel, logLevel); conf()->set(Config::logLevel, logLevel);
} else { } else {
logLevel = config()->get(Config::logLevel).toInt(); logLevel = conf()->get(Config::logLevel).toInt();
} }
if (parser.isSet("quiet") || config()->get(Config::disableLogging).toBool()) { if (parser.isSet("quiet") || conf()->get(Config::disableLogging).toBool()) {
qWarning() << "Logging is disabled"; qWarning() << "Logging is disabled";
WalletManager::instance()->setLogLevel(-1); WalletManager::instance()->setLogLevel(-1);
} }
@ -179,23 +179,23 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
} }
// Setup wallet directory // Setup wallet directory
QString walletDir = config()->get(Config::walletDirectory).toString(); QString walletDir = conf()->get(Config::walletDirectory).toString();
if (walletDir.isEmpty() || Utils::isPortableMode()) { if (walletDir.isEmpty() || Utils::isPortableMode()) {
walletDir = Utils::defaultWalletDir(); walletDir = Utils::defaultWalletDir();
config()->set(Config::walletDirectory, walletDir); conf()->set(Config::walletDirectory, walletDir);
} }
if (!QDir().mkpath(walletDir)) if (!QDir().mkpath(walletDir))
qCritical() << "Unable to create dir: " << walletDir; qCritical() << "Unable to create dir: " << walletDir;
// Prestium initial config // Prestium initial config
if (config()->get(Config::firstRun).toBool() && Prestium::detect()) { if (conf()->get(Config::firstRun).toBool() && Prestium::detect()) {
config()->set(Config::proxy, Config::Proxy::i2p); conf()->set(Config::proxy, Config::Proxy::i2p);
config()->set(Config::socks5Port, Prestium::i2pPort()); conf()->set(Config::socks5Port, Prestium::i2pPort());
config()->set(Config::hideUpdateNotifications, true); conf()->set(Config::hideUpdateNotifications, true);
} }
if (parser.isSet("use-local-tor")) if (parser.isSet("use-local-tor"))
config()->set(Config::useLocalTor, true); conf()->set(Config::useLocalTor, true);
parser.process(app); // Parse again for --help and --version parser.process(app); // Parse again for --help and --version
@ -239,10 +239,11 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
pool->setMaxThreadCount(8); pool->setMaxThreadCount(8);
} }
WindowManager windowManager(QCoreApplication::instance(), &filter); auto wm = windowManager();
wm->setEventFilter(&filter);
QObject::connect(&app, &SingleApplication::instanceStarted, [&windowManager]() { QObject::connect(&app, &SingleApplication::instanceStarted, [&wm]() {
windowManager.raise(); wm->raise();
}); });
return QApplication::exec(); return QApplication::exec();

View file

@ -60,7 +60,7 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
else if (role == Qt::DecorationRole) { else if (role == Qt::DecorationRole) {
switch (index.column()) { switch (index.column()) {
case NodeModel::URL: { case NodeModel::URL: {
if (m_nodeSource == NodeSource::websocket && !config()->get(Config::offlineMode).toBool()) { if (m_nodeSource == NodeSource::websocket && !conf()->get(Config::offlineMode).toBool()) {
return QVariant(node.online ? m_online : m_offline); return QVariant(node.online ? m_online : m_offline);
} }
return QVariant(); return QVariant();

View file

@ -148,8 +148,8 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
if (role == Qt::UserRole) { if (role == Qt::UserRole) {
return row; return row;
} }
return tInfo.timestamp().toString(QString("%1 %2 ").arg(config()->get(Config::dateFormat).toString(), return tInfo.timestamp().toString(QString("%1 %2 ").arg(conf()->get(Config::dateFormat).toString(),
config()->get(Config::timeFormat).toString())); conf()->get(Config::timeFormat).toString()));
} }
case Column::Description: case Column::Description:
return tInfo.description(); return tInfo.description();
@ -174,7 +174,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
double usd_amount = usd_price * (tInfo.balanceDelta() / constants::cdiv); double usd_amount = usd_price * (tInfo.balanceDelta() / constants::cdiv);
QString preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); QString preferredFiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
if (preferredFiatCurrency != "USD") { if (preferredFiatCurrency != "USD") {
usd_amount = appData()->prices.convert("USD", preferredFiatCurrency, usd_amount); usd_amount = appData()->prices.convert("USD", preferredFiatCurrency, usd_amount);
} }
@ -201,7 +201,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
} }
QString TransactionHistoryModel::formatAmount(quint64 amount) const { QString TransactionHistoryModel::formatAmount(quint64 amount) const {
return QString::number(amount / constants::cdiv, 'f', config()->get(Config::amountPrecision).toInt()); return QString::number(amount / constants::cdiv, 'f', conf()->get(Config::amountPrecision).toInt());
} }
QVariant TransactionHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const { QVariant TransactionHistoryModel::headerData(int section, Qt::Orientation orientation, int role) const {

View file

@ -62,7 +62,7 @@ void WalletKeysFilesModel::updateDirectories() {
m_walletDirectories << walletDirRoot; m_walletDirectories << walletDirRoot;
m_walletDirectories << QDir::homePath(); m_walletDirectories << QDir::homePath();
QString walletDirectory = config()->get(Config::walletDirectory).toString(); QString walletDirectory = conf()->get(Config::walletDirectory).toString();
if (!walletDirectory.isEmpty()) if (!walletDirectory.isEmpty())
m_walletDirectories << walletDirectory; m_walletDirectories << walletDirectory;

View file

@ -70,7 +70,7 @@ void BountiesWidget::showContextMenu(const QPoint &pos) {
} }
QString BountiesWidget::getLink(const QString &permaLink) { QString BountiesWidget::getLink(const QString &permaLink) {
QString frontend = config()->get(Config::bountiesFrontend).toString(); QString frontend = conf()->get(Config::bountiesFrontend).toString();
return QString("%1/%2").arg(frontend, permaLink); return QString("%1/%2").arg(frontend, permaLink);
} }

View file

@ -107,11 +107,11 @@ QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, con
} }
QString LocalMoneroApi::getBaseUrl() { QString LocalMoneroApi::getBaseUrl() {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && conf()->get(Config::torOnlyAllowOnion).toBool()) {
return "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion/api/v1"; return "http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion/api/v1";
} }
if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
return "http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p/api/v1"; return "http://yeyar743vuwmm6fpgf3x6bzmj7fxb5uxhuoxx4ea76wqssdi4f3q.b32.i2p/api/v1";
} }

View file

@ -41,7 +41,7 @@ void LocalMoneroInfoDialog::setLabelText(QLabel *label, LocalMoneroModel::Column
void LocalMoneroInfoDialog::onGoToOffer() { void LocalMoneroInfoDialog::onGoToOffer() {
QJsonObject offerData = m_model->getOffer(m_row); QJsonObject offerData = m_model->getOffer(m_row);
QString frontend = config()->get(Config::localMoneroFrontend).toString(); QString frontend = conf()->get(Config::localMoneroFrontend).toString();
QString offerUrl = QString("%1/ad/%2").arg(frontend, offerData["data"].toObject()["ad_id"].toString()); QString offerUrl = QString("%1/ad/%2").arg(frontend, offerData["data"].toObject()["ad_id"].toString());
Utils::externalLinkWarning(this, offerUrl); Utils::externalLinkWarning(this, offerUrl);
} }

View file

@ -22,7 +22,7 @@ LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, Wallet *wallet)
QPixmap logo(":/assets/images/localMonero_logo.png"); QPixmap logo(":/assets/images/localMonero_logo.png");
ui->logo->setPixmap(logo.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)); ui->logo->setPixmap(logo.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation));
ui->combo_currency->addItem(config()->get(Config::preferredFiatCurrency).toString()); ui->combo_currency->addItem(conf()->get(Config::preferredFiatCurrency).toString());
m_network = new Networking(this); m_network = new Networking(this);
m_api = new LocalMoneroApi(this, m_network); m_api = new LocalMoneroApi(this, m_network);
@ -74,7 +74,7 @@ void LocalMoneroWidget::onSearchClicked() {
} }
void LocalMoneroWidget::onSignUpClicked() { void LocalMoneroWidget::onSignUpClicked() {
QString signupUrl = QString("%1/signup").arg(config()->get(Config::localMoneroFrontend).toString()); QString signupUrl = QString("%1/signup").arg(conf()->get(Config::localMoneroFrontend).toString());
Utils::externalLinkWarning(this, signupUrl); Utils::externalLinkWarning(this, signupUrl);
} }
@ -82,7 +82,7 @@ void LocalMoneroWidget::onApiResponse(const LocalMoneroApi::LocalMoneroResponse
ui->btn_search->setEnabled(true); ui->btn_search->setEnabled(true);
if (!resp.ok) { if (!resp.ok) {
QMessageBox::warning(this, "LocalMonero error", QString("Request failed:\n\n%1").arg(resp.message)); Utils::showError(this, "LocalMonero request failed", resp.message);
return; return;
} }
@ -162,7 +162,7 @@ void LocalMoneroWidget::openOfferUrl() {
} }
QJsonObject offerData = m_model->getOffer(index.row()); QJsonObject offerData = m_model->getOffer(index.row());
QString frontend = config()->get(Config::localMoneroFrontend).toString(); QString frontend = conf()->get(Config::localMoneroFrontend).toString();
QString offerUrl = QString("%1/ad/%2").arg(frontend, offerData["data"].toObject()["ad_id"].toString()); QString offerUrl = QString("%1/ad/%2").arg(frontend, offerData["data"].toObject()["ad_id"].toString());

View file

@ -71,7 +71,7 @@ void RedditWidget::showContextMenu(const QPoint &pos) {
} }
QString RedditWidget::getLink(const QString &permaLink) { QString RedditWidget::getLink(const QString &permaLink) {
QString redditFrontend = config()->get(Config::redditFrontend).toString(); QString redditFrontend = conf()->get(Config::redditFrontend).toString();
return QString("https://%1%2").arg(redditFrontend, permaLink); return QString("https://%1%2").arg(redditFrontend, permaLink);
} }

View file

@ -23,6 +23,8 @@ RevuoWidget::RevuoWidget(QWidget *parent)
m_contextMenu->addAction("Open link", this, &RevuoWidget::onOpenLink); m_contextMenu->addAction("Open link", this, &RevuoWidget::onOpenLink);
m_contextMenu->addAction("Donate to author", this, &RevuoWidget::onDonate); m_contextMenu->addAction("Donate to author", this, &RevuoWidget::onDonate);
ui->splitter->setStretchFactor(1, 5);
connect(ui->listWidget, &QListWidget::currentTextChanged, this, &RevuoWidget::onSelectItem); connect(ui->listWidget, &QListWidget::currentTextChanged, this, &RevuoWidget::onSelectItem);
connect(ui->listWidget, &QListWidget::customContextMenuRequested, this, &RevuoWidget::showContextMenu); connect(ui->listWidget, &QListWidget::customContextMenuRequested, this, &RevuoWidget::showContextMenu);
} }

View file

@ -7,7 +7,6 @@
#include <QDesktopServices> #include <QDesktopServices>
#include <QFileDialog> #include <QFileDialog>
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox>
#include <QScrollBar> #include <QScrollBar>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QTableWidget> #include <QTableWidget>
@ -42,10 +41,10 @@ XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
// XMRig executable // XMRig executable
connect(ui->btn_browse, &QPushButton::clicked, this, &XMRigWidget::onBrowseClicked); connect(ui->btn_browse, &QPushButton::clicked, this, &XMRigWidget::onBrowseClicked);
ui->lineEdit_path->setText(config()->get(Config::xmrigPath).toString()); ui->lineEdit_path->setText(conf()->get(Config::xmrigPath).toString());
// Run as admin/root // Run as admin/root
bool elevated = config()->get(Config::xmrigElevated).toBool(); bool elevated = conf()->get(Config::xmrigElevated).toBool();
if (elevated) { if (elevated) {
ui->radio_elevateYes->setChecked(true); ui->radio_elevateYes->setChecked(true);
} else { } else {
@ -62,7 +61,7 @@ XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
ui->threadSlider->setMinimum(1); ui->threadSlider->setMinimum(1);
ui->threadSlider->setMaximum(QThread::idealThreadCount()); ui->threadSlider->setMaximum(QThread::idealThreadCount());
int threads = config()->get(Config::xmrigThreads).toInt(); int threads = conf()->get(Config::xmrigThreads).toInt();
ui->threadSlider->setValue(threads); ui->threadSlider->setValue(threads);
ui->label_threads->setText(QString("CPU threads: %1").arg(threads)); ui->label_threads->setText(QString("CPU threads: %1").arg(threads));
@ -70,14 +69,14 @@ XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
// Mining mode // Mining mode
connect(ui->combo_miningMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XMRigWidget::onMiningModeChanged); connect(ui->combo_miningMode, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XMRigWidget::onMiningModeChanged);
ui->combo_miningMode->setCurrentIndex(config()->get(Config::miningMode).toInt()); ui->combo_miningMode->setCurrentIndex(conf()->get(Config::miningMode).toInt());
// Pool/node address // Pool/node address
this->updatePools(); this->updatePools();
connect(ui->combo_pools, &QComboBox::currentTextChanged, this, &XMRigWidget::onPoolChanged); connect(ui->combo_pools, &QComboBox::currentTextChanged, this, &XMRigWidget::onPoolChanged);
connect(ui->btn_poolConfig, &QPushButton::clicked, [this]{ connect(ui->btn_poolConfig, &QPushButton::clicked, [this]{
QStringList pools = config()->get(Config::pools).toStringList(); QStringList pools = conf()->get(Config::pools).toStringList();
bool ok; bool ok;
QString poolStr = QInputDialog::getMultiLineText(this, "Pool addresses", "Set pool addresses (one per line):", pools.join("\n"), &ok); QString poolStr = QInputDialog::getMultiLineText(this, "Pool addresses", "Set pool addresses (one per line):", pools.join("\n"), &ok);
if (!ok) { if (!ok) {
@ -86,20 +85,20 @@ XMRigWidget::XMRigWidget(Wallet *wallet, QWidget *parent)
QStringList newPools = poolStr.split("\n"); QStringList newPools = poolStr.split("\n");
newPools.removeAll(""); newPools.removeAll("");
newPools.removeDuplicates(); newPools.removeDuplicates();
config()->set(Config::pools, newPools); conf()->set(Config::pools, newPools);
this->updatePools(); this->updatePools();
}); });
ui->lineEdit_solo->setText(config()->get(Config::xmrigDaemon).toString()); ui->lineEdit_solo->setText(conf()->get(Config::xmrigDaemon).toString());
connect(ui->lineEdit_solo, &QLineEdit::textChanged, [this](){ connect(ui->lineEdit_solo, &QLineEdit::textChanged, [this](){
config()->set(Config::xmrigDaemon, ui->lineEdit_solo->text()); conf()->set(Config::xmrigDaemon, ui->lineEdit_solo->text());
}); });
// Network settings // Network settings
connect(ui->check_tls, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTLSToggled); connect(ui->check_tls, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTLSToggled);
connect(ui->relayTor, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTorToggled); connect(ui->relayTor, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTorToggled);
ui->check_tls->setChecked(config()->get(Config::xmrigNetworkTLS).toBool()); ui->check_tls->setChecked(conf()->get(Config::xmrigNetworkTLS).toBool());
ui->relayTor->setChecked(config()->get(Config::xmrigNetworkTor).toBool()); ui->relayTor->setChecked(conf()->get(Config::xmrigNetworkTor).toBool());
// Receiving address // Receiving address
auto username = m_wallet->getCacheAttribute("feather.xmrig_username"); auto username = m_wallet->getCacheAttribute("feather.xmrig_username");
@ -144,18 +143,18 @@ void XMRigWidget::onWalletClosed() {
} }
void XMRigWidget::onThreadsValueChanged(int threads) { void XMRigWidget::onThreadsValueChanged(int threads) {
config()->set(Config::xmrigThreads, threads); conf()->set(Config::xmrigThreads, threads);
ui->label_threads->setText(QString("CPU threads: %1").arg(threads)); ui->label_threads->setText(QString("CPU threads: %1").arg(threads));
} }
void XMRigWidget::onPoolChanged(const QString &pool) { void XMRigWidget::onPoolChanged(const QString &pool) {
if (!pool.isEmpty()) { if (!pool.isEmpty()) {
config()->set(Config::xmrigPool, pool); conf()->set(Config::xmrigPool, pool);
} }
} }
void XMRigWidget::onXMRigElevationChanged(bool elevated) { void XMRigWidget::onXMRigElevationChanged(bool elevated) {
config()->set(Config::xmrigElevated, elevated); conf()->set(Config::xmrigElevated, elevated);
} }
void XMRigWidget::onBrowseClicked() { void XMRigWidget::onBrowseClicked() {
@ -163,7 +162,7 @@ void XMRigWidget::onBrowseClicked() {
if (fileName.isEmpty()) { if (fileName.isEmpty()) {
return; return;
} }
config()->set(Config::xmrigPath, fileName); conf()->set(Config::xmrigPath, fileName);
ui->lineEdit_path->setText(fileName); ui->lineEdit_path->setText(fileName);
} }
@ -176,14 +175,14 @@ void XMRigWidget::onUsePrimaryAddressClicked() {
} }
void XMRigWidget::onStartClicked() { void XMRigWidget::onStartClicked() {
QString xmrigPath = config()->get(Config::xmrigPath).toString(); QString xmrigPath = conf()->get(Config::xmrigPath).toString();
if (!this->checkXMRigPath()) { if (!this->checkXMRigPath()) {
return; return;
} }
QString address = [this](){ QString address = [this](){
if (ui->combo_miningMode->currentIndex() == Config::MiningMode::Pool) { if (ui->combo_miningMode->currentIndex() == Config::MiningMode::Pool) {
return config()->get(Config::xmrigPool).toString(); return conf()->get(Config::xmrigPool).toString();
} else { } else {
return ui->lineEdit_solo->text().trimmed(); return ui->lineEdit_solo->text().trimmed();
} }
@ -306,33 +305,33 @@ void XMRigWidget::showContextMenu(const QPoint &pos) {
} }
void XMRigWidget::updatePools() { void XMRigWidget::updatePools() {
QStringList pools = config()->get(Config::pools).toStringList(); QStringList pools = conf()->get(Config::pools).toStringList();
if (pools.isEmpty()) { if (pools.isEmpty()) {
pools = m_defaultPools; pools = m_defaultPools;
config()->set(Config::pools, pools); conf()->set(Config::pools, pools);
} }
ui->combo_pools->clear(); ui->combo_pools->clear();
ui->combo_pools->insertItems(0, pools); ui->combo_pools->insertItems(0, pools);
QString preferredPool = config()->get(Config::xmrigPool).toString(); QString preferredPool = conf()->get(Config::xmrigPool).toString();
if (pools.contains(preferredPool)) { if (pools.contains(preferredPool)) {
ui->combo_pools->setCurrentIndex(pools.indexOf(preferredPool)); ui->combo_pools->setCurrentIndex(pools.indexOf(preferredPool));
} else { } else {
preferredPool = pools.at(0); preferredPool = pools.at(0);
config()->set(Config::xmrigPool, preferredPool); conf()->set(Config::xmrigPool, preferredPool);
} }
} }
void XMRigWidget::printConsoleInfo() { void XMRigWidget::printConsoleInfo() {
ui->console->appendPlainText(QString("Detected %1 CPU threads.").arg(QThread::idealThreadCount())); ui->console->appendPlainText(QString("Detected %1 CPU threads.").arg(QThread::idealThreadCount()));
if (this->checkXMRigPath()) { if (this->checkXMRigPath()) {
QString path = config()->get(Config::xmrigPath).toString(); QString path = conf()->get(Config::xmrigPath).toString();
ui->console->appendPlainText(QString("XMRig path set to %1").arg(path)); ui->console->appendPlainText(QString("XMRig path set to %1").arg(path));
} }
} }
void XMRigWidget::onMiningModeChanged(int mode) { void XMRigWidget::onMiningModeChanged(int mode) {
config()->set(Config::miningMode, mode); conf()->set(Config::miningMode, mode);
if (mode == Config::MiningMode::Pool) { if (mode == Config::MiningMode::Pool) {
ui->poolFrame->show(); ui->poolFrame->show();
@ -348,11 +347,11 @@ void XMRigWidget::onMiningModeChanged(int mode) {
} }
void XMRigWidget::onNetworkTLSToggled(bool checked) { void XMRigWidget::onNetworkTLSToggled(bool checked) {
config()->set(Config::xmrigNetworkTLS, checked); conf()->set(Config::xmrigNetworkTLS, checked);
} }
void XMRigWidget::onNetworkTorToggled(bool checked) { void XMRigWidget::onNetworkTorToggled(bool checked) {
config()->set(Config::xmrigNetworkTor, checked); conf()->set(Config::xmrigNetworkTor, checked);
} }
void XMRigWidget::onXMRigStateChanged(QProcess::ProcessState state) { void XMRigWidget::onXMRigStateChanged(QProcess::ProcessState state) {
@ -385,7 +384,7 @@ void XMRigWidget::setMiningStarted() {
} }
bool XMRigWidget::checkXMRigPath() { bool XMRigWidget::checkXMRigPath() {
QString path = config()->get(Config::xmrigPath).toString(); QString path = conf()->get(Config::xmrigPath).toString();
if (path.isEmpty()) { if (path.isEmpty()) {
ui->console->appendPlainText("No XMRig executable is set. Please configure on the Settings tab."); ui->console->appendPlainText("No XMRig executable is set. Please configure on the Settings tab.");

View file

@ -67,8 +67,8 @@ void XmRig::start(const QString &path, int threads, const QString &address, cons
arguments << "--no-color"; arguments << "--no-color";
arguments << "-t" << QString::number(threads); arguments << "-t" << QString::number(threads);
if (tor) { if (tor) {
QString host = config()->get(Config::socks5Host).toString(); QString host = conf()->get(Config::socks5Host).toString();
QString port = config()->get(Config::socks5Port).toString(); QString port = conf()->get(Config::socks5Port).toString();
if (!torManager()->isLocalTor()) { if (!torManager()->isLocalTor()) {
host = torManager()->featherTorHost; host = torManager()->featherTorHost;
port = QString::number(torManager()->featherTorPort); port = QString::number(torManager()->featherTorPort);
@ -123,7 +123,7 @@ void XmRig::handleProcessError(QProcess::ProcessError err) {
if (err == QProcess::ProcessError::Crashed) if (err == QProcess::ProcessError::Crashed)
emit error("XMRig crashed or killed"); emit error("XMRig crashed or killed");
else if (err == QProcess::ProcessError::FailedToStart) { else if (err == QProcess::ProcessError::FailedToStart) {
auto path = config()->get(Config::xmrigPath).toString(); auto path = conf()->get(Config::xmrigPath).toString();
emit error(QString("XMRig binary failed to start: %1").arg(path)); emit error(QString("XMRig binary failed to start: %1").arg(path));
} }
} }

View file

@ -11,6 +11,8 @@
#include <QImageCapture> #include <QImageCapture>
#include <QVideoFrame> #include <QVideoFrame>
#include "Utils.h"
QrCodeScanDialog::QrCodeScanDialog(QWidget *parent) QrCodeScanDialog::QrCodeScanDialog(QWidget *parent)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::QrCodeScanDialog) , ui(new Ui::QrCodeScanDialog)
@ -85,7 +87,7 @@ void QrCodeScanDialog::onDecoded(int type, const QString &data) {
void QrCodeScanDialog::displayCameraError() void QrCodeScanDialog::displayCameraError()
{ {
if (m_camera->error() != QCamera::NoError) { if (m_camera->error() != QCamera::NoError) {
QMessageBox::warning(this, tr("Camera Error"), m_camera->errorString()); Utils::showError(this, "Camera error", m_camera->errorString());
} }
} }

View file

@ -38,7 +38,7 @@ QNetworkAccessManager* getNetworkClearnet()
QNetworkAccessManager* getNetwork(const QString &address) QNetworkAccessManager* getNetwork(const QString &address)
{ {
if (config()->get(Config::proxy).toInt() == Config::Proxy::None) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::None) {
return getNetworkClearnet(); return getNetworkClearnet();
} }

View file

@ -17,7 +17,7 @@ void Networking::setUserAgent(const QString &userAgent) {
} }
QNetworkReply* Networking::get(QObject *parent, const QString &url) { QNetworkReply* Networking::get(QObject *parent, const QString &url) {
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
return nullptr; return nullptr;
} }
@ -33,7 +33,7 @@ QNetworkReply* Networking::get(QObject *parent, const QString &url) {
} }
QNetworkReply* Networking::getJson(QObject *parent, const QString &url) { QNetworkReply* Networking::getJson(QObject *parent, const QString &url) {
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
return nullptr; return nullptr;
} }
@ -50,7 +50,7 @@ QNetworkReply* Networking::getJson(QObject *parent, const QString &url) {
} }
QNetworkReply* Networking::postJson(QObject *parent, const QString &url, const QJsonObject &data) { QNetworkReply* Networking::postJson(QObject *parent, const QString &url, const QJsonObject &data) {
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
return nullptr; return nullptr;
} }

View file

@ -46,7 +46,7 @@ void TorManager::init() {
m_started = false; m_started = false;
} }
featherTorPort = config()->get(Config::torManagedPort).toString().toUShort(); featherTorPort = conf()->get(Config::torManagedPort).toString().toUShort();
} }
void TorManager::stop() { void TorManager::stop() {
@ -118,13 +118,13 @@ void TorManager::checkConnection() {
this->setConnectionState(code == 0); this->setConnectionState(code == 0);
} }
else if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) { else if (conf()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
this->setConnectionState(false); this->setConnectionState(false);
} }
else if (m_localTor) { else if (m_localTor) {
QString host = config()->get(Config::socks5Host).toString(); QString host = conf()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort(); quint16 port = conf()->get(Config::socks5Port).toString().toUShort();
this->setConnectionState(Utils::portOpen(host, port)); this->setConnectionState(Utils::portOpen(host, port));
} }
@ -237,8 +237,8 @@ bool TorManager::isStarted() {
} }
bool TorManager::shouldStartTorDaemon() { bool TorManager::shouldStartTorDaemon() {
QString torHost = config()->get(Config::socks5Host).toString(); QString torHost = conf()->get(Config::socks5Host).toString();
quint16 torPort = config()->get(Config::socks5Port).toString().toUShort(); quint16 torPort = conf()->get(Config::socks5Port).toString().toUShort();
QString torHostPort = QString("%1:%2").arg(torHost, QString::number(torPort)); QString torHostPort = QString("%1:%2").arg(torHost, QString::number(torPort));
// Don't start a Tor daemon if Feather is run with Torsocks // Don't start a Tor daemon if Feather is run with Torsocks
@ -258,12 +258,12 @@ bool TorManager::shouldStartTorDaemon() {
#endif #endif
// Don't start a Tor daemon if our proxy config isn't set to Tor // Don't start a Tor daemon if our proxy config isn't set to Tor
if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) { if (conf()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
return false; return false;
} }
// Don't start a Tor daemon if --use-local-tor is specified // Don't start a Tor daemon if --use-local-tor is specified
if (config()->get(Config::useLocalTor).toBool()) { if (conf()->get(Config::useLocalTor).toBool()) {
return false; return false;
} }

View file

@ -16,6 +16,7 @@
#include "utils/config.h" #include "utils/config.h"
#include "utils/os/tails.h" #include "utils/os/tails.h"
#include "utils/os/whonix.h" #include "utils/os/whonix.h"
#include "WindowManager.h"
namespace Utils { namespace Utils {
bool fileExists(const QString &path) { bool fileExists(const QString &path) {
@ -46,6 +47,17 @@ QByteArray fileOpenQRC(const QString &path) {
return data; return data;
} }
QString loadQrc(const QString &qrc) {
QFile file(qrc);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Failed to open resource:" << qrc;
return QString();
}
QTextStream in(&file);
return in.readAll();
}
bool fileWrite(const QString &path, const QString &data) { bool fileWrite(const QString &path, const QString &data) {
QFile file(path); QFile file(path);
if (file.open(QIODevice::WriteOnly)) { if (file.open(QIODevice::WriteOnly)) {
@ -292,7 +304,7 @@ bool xdgDesktopEntryRegister() {
} }
bool portOpen(const QString &hostname, quint16 port) { // TODO: this call should be async bool portOpen(const QString &hostname, quint16 port) { // TODO: this call should be async
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
return false; return false;
} }
@ -469,16 +481,15 @@ QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettyp
} }
void externalLinkWarning(QWidget *parent, const QString &url){ void externalLinkWarning(QWidget *parent, const QString &url){
if (!config()->get(Config::warnOnExternalLink).toBool()) { if (!conf()->get(Config::warnOnExternalLink).toBool()) {
QDesktopServices::openUrl(QUrl(url)); QDesktopServices::openUrl(QUrl(url));
return; return;
} }
QString body = QString("You are about to open the following link:\n\n%1").arg(url);
QMessageBox linkWarning(parent); QMessageBox linkWarning(parent);
linkWarning.setWindowTitle("External link warning"); linkWarning.setWindowTitle("External link warning");
linkWarning.setText(body); linkWarning.setText("You are about to open the following link:");
linkWarning.setInformativeText(url);
QPushButton *copy = linkWarning.addButton("Copy link", QMessageBox::HelpRole); QPushButton *copy = linkWarning.addButton("Copy link", QMessageBox::HelpRole);
linkWarning.addButton(QMessageBox::Cancel); linkWarning.addButton(QMessageBox::Cancel);
linkWarning.addButton(QMessageBox::Ok); linkWarning.addButton(QMessageBox::Ok);
@ -574,4 +585,68 @@ bool isLocalUrl(const QUrl &url) {
QRegularExpression localNetwork(R"((^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.))"); QRegularExpression localNetwork(R"((^127\.)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.))");
return (localNetwork.match(url.host()).hasMatch() || url.host() == "localhost"); return (localNetwork.match(url.host()).hasMatch() || url.host() == "localhost");
} }
void showError(QWidget *parent, const QString &title, const QString &description, const QStringList &helpItems, const QString &doc, const QString &highlight, const QString &link) {
showMsg(parent, QMessageBox::Warning, "Error", title, description, helpItems, doc, highlight, link);
}
void showInfo(QWidget *parent, const QString &title, const QString &description, const QStringList &helpItems, const QString &doc, const QString &highlight, const QString &link) {
showMsg(parent, QMessageBox::Information,"Info", title, description, helpItems, doc, highlight, link);
}
void showWarning(QWidget *parent, const QString &title, const QString &description, const QStringList &helpItems, const QString &doc, const QString &highlight, const QString &link) {
showMsg(parent, QMessageBox::Warning, "Warning", title, description, helpItems, doc, highlight, link);
}
void showMsg(QWidget *parent, QMessageBox::Icon icon, const QString &windowTitle, const QString &title, const QString &description, const QStringList &helpItems, const QString &doc, const QString &highlight, const QString &link) {
QString informativeText = description;
if (!helpItems.isEmpty()) {
informativeText += "\n";
}
for (const auto &item : helpItems) {
informativeText += QString("\n• %1").arg(item);
}
auto *msgBox = new QMessageBox(parent);
msgBox->setText(title);
msgBox->setInformativeText(informativeText);
msgBox->setIcon(icon);
msgBox->setWindowTitle(windowTitle);
msgBox->setStandardButtons(doc.isEmpty() ? QMessageBox::Ok : (QMessageBox::Ok | QMessageBox::Help));
msgBox->setDefaultButton(QMessageBox::Ok);
QPushButton *openLinkButton = nullptr;
if (!link.isEmpty()) {
openLinkButton = msgBox->addButton("Open link", QMessageBox::ActionRole);
}
msgBox->exec();
if (openLinkButton && msgBox->clickedButton() == openLinkButton) {
Utils::externalLinkWarning(parent, link);
}
if (msgBox->result() == QMessageBox::Help) {
windowManager()->showDocs(parent, doc);
windowManager()->setDocsHighlight(highlight);
}
msgBox->deleteLater();
}
void showMsg(const Message &m) {
showMsg(m.parent, QMessageBox::Warning, "Error", m.title, m.description, m.helpItems, m.doc, m.highlight);
}
QWindow *windowForQObject(QObject* object) {
while (object) {
if (auto widget = qobject_cast<QWidget*>(object)) {
if (widget->windowHandle()) {
return widget->windowHandle();
}
}
object = object->parent();
}
return nullptr;
}
} }

View file

@ -7,15 +7,40 @@
#include <QRegularExpression> #include <QRegularExpression>
#include <QStandardItemModel> #include <QStandardItemModel>
#include <QTextCharFormat> #include <QTextCharFormat>
#include <QMessageBox>
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
#include "networktype.h" #include "networktype.h"
namespace Utils namespace Utils
{ {
enum MessageType
{
INFO = 0,
WARNING,
ERROR
};
struct Message
{
QWidget *parent;
MessageType type;
QString title;
QString description;
QStringList helpItems;
QString doc;
QString highlight;
QString link;
QString toString() const {
return QString("%1: %2").arg(title, description);
}
};
bool fileExists(const QString &path); bool fileExists(const QString &path);
QByteArray fileOpen(const QString &path); QByteArray fileOpen(const QString &path);
QByteArray fileOpenQRC(const QString &path); QByteArray fileOpenQRC(const QString &path);
QString loadQrc(const QString &qrc);
bool fileWrite(const QString &path, const QString &data); bool fileWrite(const QString &path, const QString &data);
bool pixmapWrite(const QString &path, const QPixmap &pixmap); bool pixmapWrite(const QString &path, const QPixmap &pixmap);
QStringList fileFind(const QRegularExpression &pattern, const QString &baseDir, int level, int depth, int maxPerDir); QStringList fileFind(const QRegularExpression &pattern, const QString &baseDir, int level, int depth, int maxPerDir);
@ -74,6 +99,14 @@ namespace Utils
QString QtEnumToString (QEnum value) { QString QtEnumToString (QEnum value) {
return QString::fromStdString(std::string(QMetaEnum::fromType<QEnum>().valueToKey(value))); return QString::fromStdString(std::string(QMetaEnum::fromType<QEnum>().valueToKey(value)));
} }
void showError(QWidget *parent, const QString &title, const QString &description = "", const QStringList &helpItems = {}, const QString &doc = "", const QString &highlight = "", const QString &link = "");
void showInfo(QWidget *parent, const QString &title, const QString &description = "", const QStringList &helpItems = {}, const QString &doc = "", const QString &highlight = "", const QString &link = "");
void showWarning(QWidget *parent, const QString &title, const QString &description = "", const QStringList &helpItems = {}, const QString &doc = "", const QString &highlight = "", const QString &link = "");
void showMsg(QWidget *parent, QMessageBox::Icon icon, const QString &windowTitle, const QString &title, const QString &description, const QStringList &helpItems = {}, const QString &doc = "", const QString &highlight = "", const QString &link = "");
void showMsg(const Message &message);
QWindow* windowForQObject(QObject* object);
} }
#endif //FEATHER_UTILS_H #endif //FEATHER_UTILS_H

View file

@ -45,11 +45,11 @@ void WebsocketClient::start() {
return; return;
} }
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
return; return;
} }
if (config()->get(Config::disableWebsocket).toBool()) { if (conf()->get(Config::disableWebsocket).toBool()) {
return; return;
} }
@ -107,11 +107,11 @@ void WebsocketClient::nextWebsocketUrl() {
} }
Config::Proxy WebsocketClient::networkType() { Config::Proxy WebsocketClient::networkType() {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && conf()->get(Config::torOnlyAllowOnion).toBool()) {
// Websocket performance with onion services is abysmal, connect to clearnet server unless instructed otherwise // Websocket performance with onion services is abysmal, connect to clearnet server unless instructed otherwise
return Config::Proxy::Tor; return Config::Proxy::Tor;
} }
else if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) { else if (conf()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
return Config::Proxy::i2p; return Config::Proxy::i2p;
} }
else { else {

View file

@ -171,7 +171,7 @@ private:
QHash<QString, QVariant> m_defaults; QHash<QString, QVariant> m_defaults;
}; };
inline Config* config() inline Config* conf()
{ {
return Config::instance(); return Config::instance();
} }

View file

@ -32,7 +32,7 @@ bool NodeList::addNode(const QString &node, NetworkType::Type networkType, NodeL
netTypeObj[sourceStr] = sourceArray; netTypeObj[sourceStr] = sourceArray;
obj[networkTypeStr] = netTypeObj; obj[networkTypeStr] = netTypeObj;
config()->set(Config::nodes, obj); conf()->set(Config::nodes, obj);
return true; return true;
} }
@ -49,7 +49,7 @@ void NodeList::setNodes(const QStringList &nodes, NetworkType::Type networkType,
netTypeObj[sourceStr] = sourceArray; netTypeObj[sourceStr] = sourceArray;
obj[networkTypeStr] = netTypeObj; obj[networkTypeStr] = netTypeObj;
config()->set(Config::nodes, obj); conf()->set(Config::nodes, obj);
} }
QStringList NodeList::getNodes(NetworkType::Type networkType, NodeList::Type source) { QStringList NodeList::getNodes(NetworkType::Type networkType, NodeList::Type source) {
@ -70,11 +70,11 @@ QStringList NodeList::getNodes(NetworkType::Type networkType, NodeList::Type sou
} }
QJsonObject NodeList::getConfigData() { QJsonObject NodeList::getConfigData() {
QJsonObject obj = config()->get(Config::nodes).toJsonObject(); QJsonObject obj = conf()->get(Config::nodes).toJsonObject();
// Load old config format // Load old config format
if (obj.isEmpty()) { if (obj.isEmpty()) {
auto jsonData = config()->get(Config::nodes).toByteArray(); auto jsonData = conf()->get(Config::nodes).toByteArray();
if (Utils::validateJSON(jsonData)) { if (Utils::validateJSON(jsonData)) {
QJsonDocument doc = QJsonDocument::fromJson(jsonData); QJsonDocument doc = QJsonDocument::fromJson(jsonData);
obj = doc.object(); obj = doc.object();
@ -208,11 +208,11 @@ void Nodes::connectToNode(const FeatherNode &node) {
return; return;
} }
if (config()->get(Config::offlineMode).toBool()) { if (conf()->get(Config::offlineMode).toBool()) {
return; return;
} }
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && conf()->get(Config::torOnlyAllowOnion).toBool()) {
if (!node.isOnion() && !node.isLocal()) { if (!node.isOnion() && !node.isLocal()) {
// We only want to connect to .onion nodes, but local nodes get an exception. // We only want to connect to .onion nodes, but local nodes get an exception.
return; return;
@ -230,11 +230,11 @@ void Nodes::connectToNode(const FeatherNode &node) {
QString proxyAddress; QString proxyAddress;
if (useSocks5Proxy(node)) { if (useSocks5Proxy(node)) {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && !torManager()->isLocalTor()) {
proxyAddress = QString("%1:%2").arg(torManager()->featherTorHost, QString::number(torManager()->featherTorPort)); proxyAddress = QString("%1:%2").arg(torManager()->featherTorHost, QString::number(torManager()->featherTorPort));
} else { } else {
proxyAddress = QString("%1:%2").arg(config()->get(Config::socks5Host).toString(), proxyAddress = QString("%1:%2").arg(conf()->get(Config::socks5Host).toString(),
config()->get(Config::socks5Port).toString()); conf()->get(Config::socks5Port).toString());
} }
} }
@ -400,7 +400,7 @@ void Nodes::setCustomNodes(const QList<FeatherNode> &nodes) {
} }
void Nodes::onWalletRefreshed() { void Nodes::onWalletRefreshed() {
if (config()->get(Config::proxy) == Config::Proxy::Tor && config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptInitSync) { if (conf()->get(Config::proxy) == Config::Proxy::Tor && conf()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptInitSync) {
// Don't reconnect if we're connected to a local node (traffic will not be routed through Tor) // Don't reconnect if we're connected to a local node (traffic will not be routed through Tor)
if (m_connection.isLocal()) if (m_connection.isLocal())
return; return;
@ -414,15 +414,15 @@ void Nodes::onWalletRefreshed() {
} }
bool Nodes::useOnionNodes() { bool Nodes::useOnionNodes() {
if (config()->get(Config::proxy) != Config::Proxy::Tor) { if (conf()->get(Config::proxy) != Config::Proxy::Tor) {
return false; return false;
} }
if (config()->get(Config::torOnlyAllowOnion).toBool()) { if (conf()->get(Config::torOnlyAllowOnion).toBool()) {
return true; return true;
} }
auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt(); auto privacyLevel = conf()->get(Config::torPrivacyLevel).toInt();
if (privacyLevel == Config::allTor) { if (privacyLevel == Config::allTor) {
return true; return true;
} }
@ -433,7 +433,7 @@ bool Nodes::useOnionNodes() {
} }
if (appData()->heights.contains(constants::networkType)) { if (appData()->heights.contains(constants::networkType)) {
int initSyncThreshold = config()->get(Config::initSyncThreshold).toInt(); int initSyncThreshold = conf()->get(Config::initSyncThreshold).toInt();
int networkHeight = appData()->heights[constants::networkType]; int networkHeight = appData()->heights[constants::networkType];
if (m_wallet && m_wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) { if (m_wallet && m_wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) {
@ -446,7 +446,7 @@ bool Nodes::useOnionNodes() {
} }
bool Nodes::useI2PNodes() { bool Nodes::useI2PNodes() {
if (config()->get(Config::proxy) == Config::Proxy::i2p) { if (conf()->get(Config::proxy) == Config::Proxy::i2p) {
return true; return true;
} }
@ -464,7 +464,7 @@ bool Nodes::useSocks5Proxy(const FeatherNode &node) {
return false; return false;
} }
if (config()->get(Config::proxy).toInt() == Config::Proxy::None) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::None) {
return false; return false;
} }
@ -477,12 +477,12 @@ bool Nodes::useSocks5Proxy(const FeatherNode &node) {
return true; return true;
} }
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor) {
// Don't use socks5 proxy if initial sync traffic is excluded. // Don't use socks5 proxy if initial sync traffic is excluded.
return this->useOnionNodes(); return this->useOnionNodes();
} }
if (config()->get(Config::proxy).toInt() != Config::Proxy::None) { if (conf()->get(Config::proxy).toInt() != Config::Proxy::None) {
return true; return true;
} }
} }
@ -556,7 +556,7 @@ FeatherNode Nodes::connection() {
} }
NodeSource Nodes::source() { NodeSource Nodes::source() {
return static_cast<NodeSource>(config()->get(Config::nodeSource).toInt()); return static_cast<NodeSource>(conf()->get(Config::nodeSource).toInt());
} }
int Nodes::modeHeight(const QList<FeatherNode> &nodes) { int Nodes::modeHeight(const QList<FeatherNode> &nodes) {

View file

@ -173,10 +173,10 @@ QString Updater::getPlatformTag() {
} }
QString Updater::getWebsiteUrl() { QString Updater::getWebsiteUrl() {
if (config()->get(Config::proxy).toInt() == Config::Proxy::Tor && config()->get(Config::torOnlyAllowOnion).toBool()) { if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && conf()->get(Config::torOnlyAllowOnion).toBool()) {
return "http://featherdvtpi7ckdbkb2yxjfwx3oyvr3xjz3oo4rszylfzjdg6pbm3id.onion"; return "http://featherdvtpi7ckdbkb2yxjfwx3oyvr3xjz3oo4rszylfzjdg6pbm3id.onion";
} }
else if (config()->get(Config::proxy).toInt() == Config::Proxy::i2p) { else if (conf()->get(Config::proxy).toInt() == Config::Proxy::i2p) {
return "http://rwzulgcql2y3n6os2jhmhg6un2m33rylazfnzhf56likav47aylq.b32.i2p"; return "http://rwzulgcql2y3n6os2jhmhg6un2m33rylazfnzhf56likav47aylq.b32.i2p";
} }
else { else {

View file

@ -18,7 +18,7 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
ui->comboBox_proxy->setCurrentIndex(config()->get(Config::proxy).toInt()); ui->comboBox_proxy->setCurrentIndex(conf()->get(Config::proxy).toInt());
connect(ui->comboBox_proxy, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){ connect(ui->comboBox_proxy, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
this->onProxySettingsChanged(); this->onProxySettingsChanged();
ui->frame_proxy->setVisible(index != Config::Proxy::None); ui->frame_proxy->setVisible(index != Config::Proxy::None);
@ -27,19 +27,19 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
this->updatePort(); this->updatePort();
}); });
int proxy = config()->get(Config::proxy).toInt(); int proxy = conf()->get(Config::proxy).toInt();
ui->frame_proxy->setVisible(proxy != Config::Proxy::None); ui->frame_proxy->setVisible(proxy != Config::Proxy::None);
ui->frame_tor->setVisible(proxy == Config::Proxy::Tor); ui->frame_tor->setVisible(proxy == Config::Proxy::Tor);
ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText())); ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText()));
// [Host] // [Host]
ui->line_host->setText(config()->get(Config::socks5Host).toString()); ui->line_host->setText(conf()->get(Config::socks5Host).toString());
connect(ui->line_host, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged); connect(ui->line_host, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Port] // [Port]
auto *portValidator = new QRegularExpressionValidator{QRegularExpression("[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]")}; auto *portValidator = new QRegularExpressionValidator{QRegularExpression("[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]")};
ui->line_port->setValidator(portValidator); ui->line_port->setValidator(portValidator);
ui->line_port->setText(config()->get(Config::socks5Port).toString()); ui->line_port->setText(conf()->get(Config::socks5Port).toString());
connect(ui->line_port, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged); connect(ui->line_port, &QLineEdit::textChanged, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Tor settings] // [Tor settings]
@ -49,7 +49,7 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
ui->checkBox_torManaged->setEnabled(false); ui->checkBox_torManaged->setEnabled(false);
ui->checkBox_torManaged->setToolTip("Feather was bundled without Tor"); ui->checkBox_torManaged->setToolTip("Feather was bundled without Tor");
#else #else
ui->checkBox_torManaged->setChecked(!config()->get(Config::useLocalTor).toBool()); ui->checkBox_torManaged->setChecked(!conf()->get(Config::useLocalTor).toBool());
connect(ui->checkBox_torManaged, &QCheckBox::toggled, [this](bool toggled){ connect(ui->checkBox_torManaged, &QCheckBox::toggled, [this](bool toggled){
this->updatePort(); this->updatePort();
this->onProxySettingsChanged(); this->onProxySettingsChanged();
@ -60,15 +60,15 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
#endif #endif
// [Only allow connections to onion services] // [Only allow connections to onion services]
ui->checkBox_torOnlyAllowOnion->setChecked(config()->get(Config::torOnlyAllowOnion).toBool()); ui->checkBox_torOnlyAllowOnion->setChecked(conf()->get(Config::torOnlyAllowOnion).toBool());
connect(ui->checkBox_torOnlyAllowOnion, &QCheckBox::toggled, this, &NetworkProxyWidget::onProxySettingsChanged); connect(ui->checkBox_torOnlyAllowOnion, &QCheckBox::toggled, this, &NetworkProxyWidget::onProxySettingsChanged);
// [Node traffic] // [Node traffic]
ui->comboBox_torNodeTraffic->setCurrentIndex(config()->get(Config::torPrivacyLevel).toInt()); ui->comboBox_torNodeTraffic->setCurrentIndex(conf()->get(Config::torPrivacyLevel).toInt());
connect(ui->comboBox_torNodeTraffic, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &NetworkProxyWidget::onProxySettingsChanged); connect(ui->comboBox_torNodeTraffic, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &NetworkProxyWidget::onProxySettingsChanged);
// [Show Tor logs] // [Show Tor logs]
ui->frame_torShowLogs->setVisible(!config()->get(Config::useLocalTor).toBool()); ui->frame_torShowLogs->setVisible(!conf()->get(Config::useLocalTor).toBool());
#if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER) #if !defined(HAS_TOR_BIN) && !defined(PLATFORM_INSTALLER)
ui->frame_torShowLogs->setVisible(false); ui->frame_torShowLogs->setVisible(false);
#endif #endif
@ -110,12 +110,12 @@ void NetworkProxyWidget::updatePort() {
} }
void NetworkProxyWidget::setProxySettings() { void NetworkProxyWidget::setProxySettings() {
config()->set(Config::proxy, ui->comboBox_proxy->currentIndex()); conf()->set(Config::proxy, ui->comboBox_proxy->currentIndex());
config()->set(Config::socks5Host, ui->line_host->text()); conf()->set(Config::socks5Host, ui->line_host->text());
config()->set(Config::socks5Port, ui->line_port->text()); conf()->set(Config::socks5Port, ui->line_port->text());
config()->set(Config::useLocalTor, !ui->checkBox_torManaged->isChecked()); conf()->set(Config::useLocalTor, !ui->checkBox_torManaged->isChecked());
config()->set(Config::torOnlyAllowOnion, ui->checkBox_torOnlyAllowOnion->isChecked()); conf()->set(Config::torOnlyAllowOnion, ui->checkBox_torOnlyAllowOnion->isChecked());
config()->set(Config::torPrivacyLevel, ui->comboBox_torNodeTraffic->currentIndex()); conf()->set(Config::torPrivacyLevel, ui->comboBox_torNodeTraffic->currentIndex());
m_proxySettingsChanged = false; m_proxySettingsChanged = false;
} }

View file

@ -25,7 +25,7 @@ NodeWidget::NodeWidget(QWidget *parent)
bool custom = (id == 0); bool custom = (id == 0);
ui->stackedWidget->setCurrentIndex(custom); ui->stackedWidget->setCurrentIndex(custom);
ui->frame_addCustomNodes->setVisible(custom); ui->frame_addCustomNodes->setVisible(custom);
config()->set(Config::nodeSource, custom); conf()->set(Config::nodeSource, custom);
emit nodeSourceChanged(static_cast<NodeSource>(custom)); emit nodeSourceChanged(static_cast<NodeSource>(custom));
}); });
@ -47,8 +47,8 @@ NodeWidget::NodeWidget(QWidget *parent)
connect(ui->treeView_websocket, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect); connect(ui->treeView_websocket, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
connect(ui->treeView_custom, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect); connect(ui->treeView_custom, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
int index = config()->get(Config::nodeSource).toInt(); int index = conf()->get(Config::nodeSource).toInt();
ui->stackedWidget->setCurrentIndex(config()->get(Config::nodeSource).toInt()); ui->stackedWidget->setCurrentIndex(conf()->get(Config::nodeSource).toInt());
ui->frame_addCustomNodes->setVisible(index); ui->frame_addCustomNodes->setVisible(index);
this->onWebsocketStatusChanged(); this->onWebsocketStatusChanged();
@ -71,7 +71,7 @@ void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
} }
void NodeWidget::onWebsocketStatusChanged() { void NodeWidget::onWebsocketStatusChanged() {
bool disabled = config()->get(Config::disableWebsocket).toBool() || config()->get(Config::offlineMode).toBool(); bool disabled = conf()->get(Config::disableWebsocket).toBool() || conf()->get(Config::offlineMode).toBool();
ui->treeView_websocket->setColumnHidden(1, disabled); ui->treeView_websocket->setColumnHidden(1, disabled);
} }

View file

@ -72,7 +72,7 @@ BalanceTickerWidget::BalanceTickerWidget(QWidget *parent, Wallet *wallet, bool t
void BalanceTickerWidget::updateDisplay() { void BalanceTickerWidget::updateDisplay() {
double balance = (m_totalBalance ? m_wallet->balanceAll() : m_wallet->balance()) / constants::cdiv; double balance = (m_totalBalance ? m_wallet->balanceAll() : m_wallet->balance()) / constants::cdiv;
QString fiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); QString fiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
double balanceFiatAmount = appData()->prices.convert("XMR", fiatCurrency, balance); double balanceFiatAmount = appData()->prices.convert("XMR", fiatCurrency, balance);
if (balanceFiatAmount < 0) if (balanceFiatAmount < 0)
return; return;
@ -91,7 +91,7 @@ PriceTickerWidget::PriceTickerWidget(QWidget *parent, Wallet *wallet, QString sy
} }
void PriceTickerWidget::updateDisplay() { void PriceTickerWidget::updateDisplay() {
QString fiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); QString fiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
double price = appData()->prices.convert(m_symbol, fiatCurrency, 1.0); double price = appData()->prices.convert(m_symbol, fiatCurrency, 1.0);
if (price < 0) if (price < 0)
return; return;

View file

@ -7,7 +7,6 @@
#include <QCheckBox> #include <QCheckBox>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QMessageBox>
#include <QPushButton> #include <QPushButton>
PageHardwareDevice::PageHardwareDevice(WizardFields *fields, QWidget *parent) PageHardwareDevice::PageHardwareDevice(WizardFields *fields, QWidget *parent)

View file

@ -7,6 +7,8 @@
#include <QFileDialog> #include <QFileDialog>
#include "config-feather.h"
PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent) PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent)
: QWizardPage(parent) : QWizardPage(parent)
, ui(new Ui::PageMenu) , ui(new Ui::PageMenu)
@ -16,9 +18,9 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
ui->setupUi(this); ui->setupUi(this);
this->setButtonText(QWizard::FinishButton, "Open recent wallet"); this->setButtonText(QWizard::FinishButton, "Open recent wallet");
QString settingsSkin = config()->get(Config::skin).toString(); ui->label_version->setText(QString("Feather %1 — by dsc & tobtoht").arg(FEATHER_VERSION));
connect(ui->btn_openSettings, &QPushButton::clicked, this, &PageMenu::showSettings); QString settingsSkin = conf()->get(Config::skin).toString();
} }
void PageMenu::initializePage() { void PageMenu::initializePage() {

View file

@ -22,9 +22,6 @@ public:
bool validatePage() override; bool validatePage() override;
int nextId() const override; int nextId() const override;
signals:
void showSettings();
private: private:
Ui::PageMenu *ui; Ui::PageMenu *ui;
WalletKeysFilesModel *m_walletKeysFilesModel; WalletKeysFilesModel *m_walletKeysFilesModel;

View file

@ -88,55 +88,17 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QPushButton" name="btn_openSettings"> <widget class="QLabel" name="label_version">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text"> <property name="text">
<string>Settings</string> <string>by dsc &amp; tobtoht</string>
</property> </property>
</widget> </widget>
</item> </item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>by dsc &amp; tobtoht</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>banner: themonera.art</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
</layout> </layout>

View file

@ -65,7 +65,7 @@ int PageNetwork::nextId() const {
bool PageNetwork::validatePage() { bool PageNetwork::validatePage() {
int id = ui->btnGroup_network->checkedId(); int id = ui->btnGroup_network->checkedId();
config()->set(Config::nodeSource, id); conf()->set(Config::nodeSource, id);
if (id == Button::CUSTOM) { if (id == Button::CUSTOM) {
NodeList nodeList; NodeList nodeList;

View file

@ -21,7 +21,7 @@ int PageNetworkWebsocket::nextId() const {
bool PageNetworkWebsocket::validatePage() { bool PageNetworkWebsocket::validatePage() {
bool disabled = ui->btn_disable->isChecked(); bool disabled = ui->btn_disable->isChecked();
config()->set(Config::disableWebsocket, disabled); conf()->set(Config::disableWebsocket, disabled);
emit initialNetworkConfigured(); emit initialNetworkConfigured();

View file

@ -5,7 +5,6 @@
#include "ui_PageOpenWallet.h" #include "ui_PageOpenWallet.h"
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox>
#include "constants.h" #include "constants.h"
#include "WalletWizard.h" #include "WalletWizard.h"
@ -40,7 +39,7 @@ PageOpenWallet::PageOpenWallet(WalletKeysFilesModel *wallets, QWidget *parent)
connect(ui->walletTable, &QTreeView::doubleClicked, this, &PageOpenWallet::nextPage); connect(ui->walletTable, &QTreeView::doubleClicked, this, &PageOpenWallet::nextPage);
connect(ui->btnBrowse, &QPushButton::clicked, [this]{ connect(ui->btnBrowse, &QPushButton::clicked, [this]{
QString walletDir = config()->get(Config::walletDirectory).toString(); QString walletDir = conf()->get(Config::walletDirectory).toString();
m_walletFile = QFileDialog::getOpenFileName(this, "Select your wallet file", walletDir, "Wallet file (*.keys)"); m_walletFile = QFileDialog::getOpenFileName(this, "Select your wallet file", walletDir, "Wallet file (*.keys)");
if (m_walletFile.isEmpty()) if (m_walletFile.isEmpty())
return; return;
@ -86,19 +85,19 @@ void PageOpenWallet::nextPage() {
bool PageOpenWallet::validatePage() { bool PageOpenWallet::validatePage() {
if (m_walletFile.isEmpty()) { if (m_walletFile.isEmpty()) {
QMessageBox::warning(this, "No wallet file selected", "Please select a wallet from the list."); Utils::showError(this, "Can't open wallet", "No wallet file selected");
return false; return false;
} }
QFileInfo infoPath(m_walletFile); QFileInfo infoPath(m_walletFile);
if (!infoPath.isReadable()) { if (!infoPath.isReadable()) {
QMessageBox::warning(this, "Permission error", "Cannot read wallet file."); Utils::showError(this, "Can't open wallet", "No permission to read wallet file");
return false; return false;
} }
// Clear autoOpen if openOnStartup is not checked // Clear autoOpen if openOnStartup is not checked
auto autoWallet = ui->openOnStartup->isChecked() ? QString("%1%2").arg(constants::networkType).arg(m_walletFile) : ""; auto autoWallet = ui->openOnStartup->isChecked() ? QString("%1%2").arg(constants::networkType).arg(m_walletFile) : "";
config()->set(Config::autoOpenWalletPath, autoWallet); conf()->set(Config::autoOpenWalletPath, autoWallet);
emit openWallet(m_walletFile); emit openWallet(m_walletFile);
return true; return true;

View file

@ -8,7 +8,6 @@
#include "utils/Utils.h" #include "utils/Utils.h"
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox>
PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent) PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent)
: QWizardPage(parent) : QWizardPage(parent)
@ -22,7 +21,7 @@ PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent)
ui->lockIcon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation)); ui->lockIcon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
connect(ui->btnChange, &QPushButton::clicked, [=] { connect(ui->btnChange, &QPushButton::clicked, [=] {
QString currentWalletDir = config()->get(Config::walletDirectory).toString(); QString currentWalletDir = conf()->get(Config::walletDirectory).toString();
QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", currentWalletDir, QFileDialog::ShowDirsOnly); QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", currentWalletDir, QFileDialog::ShowDirsOnly);
if (walletDir.isEmpty()) { if (walletDir.isEmpty()) {
return; return;
@ -39,7 +38,7 @@ PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent)
void PageWalletFile::initializePage() { void PageWalletFile::initializePage() {
this->setTitle(m_fields->modeText); this->setTitle(m_fields->modeText);
ui->line_walletDir->setText(config()->get(Config::walletDirectory).toString()); ui->line_walletDir->setText(conf()->get(Config::walletDirectory).toString());
ui->line_walletName->setText(this->defaultWalletName()); ui->line_walletName->setText(this->defaultWalletName());
ui->check_defaultWalletDirectory->setVisible(false); ui->check_defaultWalletDirectory->setVisible(false);
ui->check_defaultWalletDirectory->setChecked(false); ui->check_defaultWalletDirectory->setChecked(false);
@ -91,9 +90,9 @@ bool PageWalletFile::validatePage() {
m_fields->walletDir = ui->line_walletDir->text(); m_fields->walletDir = ui->line_walletDir->text();
QString walletDir = ui->line_walletDir->text(); QString walletDir = ui->line_walletDir->text();
bool dirChanged = config()->get(Config::walletDirectory).toString() != walletDir; bool dirChanged = conf()->get(Config::walletDirectory).toString() != walletDir;
if (dirChanged && ui->check_defaultWalletDirectory->isChecked()) { if (dirChanged && ui->check_defaultWalletDirectory->isChecked()) {
config()->set(Config::walletDirectory, walletDir); conf()->set(Config::walletDirectory, walletDir);
} }
return true; return true;

View file

@ -9,7 +9,6 @@
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QPlainTextEdit> #include <QPlainTextEdit>
#include <QPushButton> #include <QPushButton>
#include <QMessageBox>
#include <monero_seed/wordlist.hpp> // tevador 14 word #include <monero_seed/wordlist.hpp> // tevador 14 word
#include "utils/Seed.h" #include "utils/Seed.h"
@ -152,17 +151,17 @@ bool PageWalletRestoreSeed::validatePage() {
Seed _seed = Seed(m_fields->seedType, seedSplit, constants::networkType); Seed _seed = Seed(m_fields->seedType, seedSplit, constants::networkType);
if (_seed.encrypted) { if (_seed.encrypted) {
QMessageBox::warning(this, "Encrypted seed", QString("This seed is encrypted. Encrypted seeds are not supported")); Utils::showError(this, "Encrypted seed", "This seed is encrypted. Encrypted seeds are not supported");
return false; return false;
} }
if (!_seed.errorString.isEmpty()) { if (!_seed.errorString.isEmpty()) {
QMessageBox::warning(this, "Invalid seed", QString("Invalid seed:\n\n%1").arg(_seed.errorString)); Utils::showError(this, "Invalid seed", _seed.errorString);
ui->seedEdit->setStyleSheet(errStyle); ui->seedEdit->setStyleSheet(errStyle);
return false; return false;
} }
if (!_seed.correction.isEmpty()) { if (!_seed.correction.isEmpty()) {
QMessageBox::information(this, "Corrected erasure", QString("xxxx -> %1").arg(_seed.correction)); Utils::showInfo(this, "Corrected erasure", QString("xxxx -> %1").arg(_seed.correction));
} }
m_fields->seed = _seed; m_fields->seed = _seed;

View file

@ -31,6 +31,7 @@ private:
seedType() seedType()
{ {
completer.setModel(&completerModel); completer.setModel(&completerModel);
completer.setCompletionMode(QCompleter::UnfilteredPopupCompletion);
completer.setModelSorting(QCompleter::CaseSensitivelySortedModel); completer.setModelSorting(QCompleter::CaseSensitivelySortedModel);
completer.setCaseSensitivity(Qt::CaseSensitive); completer.setCaseSensitivity(Qt::CaseSensitive);
completer.setWrapAround(false); completer.setWrapAround(false);

View file

@ -122,7 +122,7 @@ bool PageWalletSeed::validatePage() {
QMessageBox seedWarning(this); QMessageBox seedWarning(this);
seedWarning.setWindowTitle("Warning!"); seedWarning.setWindowTitle("Warning!");
seedWarning.setText("• Never disclose your seed\n" seedWarning.setInformativeText("• Never disclose your seed\n"
"• Never type it on a website\n" "• Never type it on a website\n"
"• Store it safely (offline)\n" "• Store it safely (offline)\n"
"• Do not lose your seed!"); "• Do not lose your seed!");

View file

@ -122,7 +122,7 @@
<item> <item>
<widget class="QLabel" name="label_14"> <widget class="QLabel" name="label_14">
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather uses &lt;span style=&quot; font-weight:600;&quot;&gt;Polyseed&lt;/span&gt;. For more information visit: docs.featherwallet.org/guides/seed-scheme&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Feather uses &lt;span style=&quot; font-weight:600;&quot;&gt;Polyseed&lt;/span&gt;. For more information click &lt;span style=&quot; font-weight:700;&quot;&gt;Help&lt;/span&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@ -222,7 +222,7 @@
<item> <item>
<widget class="QFrame" name="frame_seedDisplay"> <widget class="QFrame" name="frame_seedDisplay">
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::StyledPanel</enum> <enum>QFrame::NoFrame</enum>
</property> </property>
<property name="frameShadow"> <property name="frameShadow">
<enum>QFrame::Raised</enum> <enum>QFrame::Raised</enum>

View file

@ -19,6 +19,7 @@
#include "PageNetworkProxy.h" #include "PageNetworkProxy.h"
#include "PageNetworkWebsocket.h" #include "PageNetworkWebsocket.h"
#include "constants.h" #include "constants.h"
#include "WindowManager.h"
#include <QLineEdit> #include <QLineEdit>
#include <QVBoxLayout> #include <QVBoxLayout>
@ -64,18 +65,42 @@ WalletWizard::WalletWizard(QWidget *parent)
setPixmap(QWizard::WatermarkPixmap, QPixmap(":/assets/images/banners/3.png")); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/assets/images/banners/3.png"));
setWizardStyle(WizardStyle::ModernStyle); setWizardStyle(WizardStyle::ModernStyle);
setOption(QWizard::NoBackButtonOnStartPage); setOption(QWizard::NoBackButtonOnStartPage);
setOption(QWizard::HaveHelpButton, true);
setOption(QWizard::HaveCustomButton1, true);
// Set up a custom button layout
QList<QWizard::WizardButton> layout;
layout << QWizard::HelpButton;
layout << QWizard::CustomButton1;
layout << QWizard::Stretch;
layout << QWizard::BackButton;
layout << QWizard::NextButton;
layout << QWizard::FinishButton;
this->setButtonLayout(layout);
auto *settingsButton = new QPushButton("Settings", this);
this->setButton(QWizard::CustomButton1, settingsButton);
settingsButton->setVisible(false);
connect(this, &QWizard::currentIdChanged, [this, settingsButton](int currentId){
settingsButton->setVisible(currentId == Page_Menu);
auto helpButton = this->button(QWizard::HelpButton);
helpButton->setVisible(!this->helpPage().isEmpty());
});
connect(settingsButton, &QPushButton::clicked, this, &WalletWizard::showSettings);
connect(networkWebsocketPage, &PageNetworkWebsocket::initialNetworkConfigured, [this](){ connect(networkWebsocketPage, &PageNetworkWebsocket::initialNetworkConfigured, [this](){
emit initialNetworkConfigured(); emit initialNetworkConfigured();
}); });
connect(menuPage, &PageMenu::showSettings, this, &WalletWizard::showSettings);
connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet); connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet);
connect(openWalletPage, &PageOpenWallet::openWallet, [=](const QString &path){ connect(openWalletPage, &PageOpenWallet::openWallet, [=](const QString &path){
emit openWallet(path, ""); emit openWallet(path, "");
}); });
connect(this, &QWizard::helpRequested, this, &WalletWizard::showHelp);
} }
void WalletWizard::resetFields() { void WalletWizard::resetFields() {
@ -133,4 +158,39 @@ void WalletWizard::onCreateWallet() {
bool newWallet = m_wizardFields.mode == WizardMode::CreateWallet; bool newWallet = m_wizardFields.mode == WizardMode::CreateWallet;
emit createWallet(m_wizardFields.seed, walletPath, m_wizardFields.password, m_wizardFields.seedLanguage, m_wizardFields.seedOffsetPassphrase, m_wizardFields.subaddressLookahead, newWallet); emit createWallet(m_wizardFields.seed, walletPath, m_wizardFields.password, m_wizardFields.seedLanguage, m_wizardFields.seedOffsetPassphrase, m_wizardFields.subaddressLookahead, newWallet);
}
QString WalletWizard::helpPage() {
QString doc;
switch (this->currentId()) {
case Page_Menu: {
doc = "about";
break;
}
case Page_CreateWalletSeed: {
doc = "seed_scheme";
break;
}
case Page_WalletFile: {
doc = "wallet_files";
break;
}
case Page_HardwareDevice: {
doc = "create_wallet_hardware_device";
break;
}
case Page_SetRestoreHeight: {
doc = "restore_height";
break;
}
}
return doc;
}
void WalletWizard::showHelp() {
QString doc = this->helpPage();
if (!doc.isEmpty()) {
windowManager()->showDocs(this, doc);
}
} }

View file

@ -100,6 +100,8 @@ signals:
private slots: private slots:
void onCreateWallet(); void onCreateWallet();
QString helpPage();
void showHelp();
private: private:
WalletKeysFilesModel *m_walletKeysFilesModel; WalletKeysFilesModel *m_walletKeysFilesModel;