Documentation browser, improved error messages

This commit is contained in:
tobtoht 2023-09-12 16:15:40 +02:00
parent 15360df99e
commit 951f16e602
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
88 changed files with 1423 additions and 713 deletions

3
.gitignore vendored
View file

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

3
.gitmodules vendored
View file

@ -7,3 +7,6 @@
[submodule "src/third-party/polyseed"]
path = src/third-party/polyseed
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()
include(GenerateDocs)
include(TorQrcGenerator)
# 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)
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)
file(GLOB SOURCE_FILES

View file

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

View file

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

View file

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

View file

@ -127,7 +127,7 @@ void ContactsWidget::newContact(QString address, QString name)
bool addressValid = WalletManager::addressValid(address, m_wallet->nettype());
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;
}
@ -141,12 +141,12 @@ void ContactsWidget::newContact(QString address, QString name)
});
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
return;
}
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);
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_close, &QPushButton::clicked, [this]{
config()->set(Config::showHistorySyncNotice, false);
conf()->set(Config::showHistorySyncNotice, false);
ui->syncNotice->hide();
});
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);
// 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()) {
ui->history->setViewState(historyViewState);
}
@ -109,8 +109,8 @@ void HistoryWidget::onResendTransaction() {
void HistoryWidget::resetModel()
{
// Save view state
config()->set(Config::GUI_HistoryViewState, ui->history->viewState().toBase64());
config()->sync();
conf()->set(Config::GUI_HistoryViewState, ui->history->viewState().toBase64());
conf()->sync();
ui->history->setModel(nullptr);
}
@ -164,8 +164,8 @@ void HistoryWidget::copy(copyField field) {
case copyField::Description:
return tx->description();
case copyField::Date:
return tx->timestamp().toString(QString("%1 %2").arg(config()->get(Config::dateFormat).toString(),
config()->get(Config::timeFormat).toString()));
return tx->timestamp().toString(QString("%1 %2").arg(conf()->get(Config::dateFormat).toString(),
conf()->get(Config::timeFormat).toString()));
case copyField::Amount:
return WalletManager::displayAmount(tx->balanceDelta());
default:

View file

@ -32,6 +32,8 @@
#include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h"
#include "wallet/wallet_errors.h"
#ifdef CHECK_UPDATES
#include "utils/updater/UpdateDialog.h"
#endif
@ -82,7 +84,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
websocketNotifier()->emitCache(); // Get cached data
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::updateBalance, m_wallet, &Wallet::updateBalance);
@ -104,7 +106,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
m_statusLabelStatus->setText("Constructing transaction" + this->statusDots());
});
config()->set(Config::firstRun, false);
conf()->set(Config::firstRun, false);
this->onWalletOpened();
@ -183,7 +185,7 @@ void MainWindow::initStatusBar() {
}
void MainWindow::initWidgets() {
int homeWidget = config()->get(Config::homeWidget).toInt();
int homeWidget = conf()->get(Config::homeWidget).toInt();
ui->tabHomeWidget->setCurrentIndex(TabsHome(homeWidget));
// [History]
@ -295,7 +297,7 @@ void MainWindow::initMenu() {
// [View]
m_tabShowHideSignalMapper = new QSignalMapper(this);
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
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()) {
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);
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::blockchainSync, this, &MainWindow::onBlockchainSync);
connect(m_wallet, &Wallet::refreshSync, this, &MainWindow::onRefreshSync);
connect(m_wallet, &Wallet::createTransactionError, this, &MainWindow::onCreateTransactionError);
connect(m_wallet, &Wallet::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess);
connect(m_wallet, &Wallet::transactionCreated, this, &MainWindow::onTransactionCreated);
connect(m_wallet, &Wallet::transactionCommitted, this, &MainWindow::onTransactionCommitted);
connect(m_wallet, &Wallet::initiateTransaction, this, &MainWindow::onInitiateTransaction);
connect(m_wallet, &Wallet::endTransaction, this, &MainWindow::onEndTransaction);
connect(m_wallet, &Wallet::keysCorrupted, this, &MainWindow::onKeysCorrupted);
connect(m_wallet, &Wallet::selectedInputsChanged, this, &MainWindow::onSelectedInputsChanged);
@ -446,7 +446,7 @@ void MainWindow::initWalletContext() {
connect(m_wallet, &Wallet::deviceError, this, &MainWindow::onDeviceError);
connect(m_wallet, &Wallet::donationSent, this, []{
config()->set(Config::donateBeg, -1);
conf()->set(Config::donateBeg, -1);
});
connect(m_wallet, &Wallet::multiBroadcast, this, &MainWindow::onMultiBroadcast);
@ -454,15 +454,15 @@ void MainWindow::initWalletContext() {
void MainWindow::menuToggleTabVisible(const QString &key){
const auto toggleTab = m_tabShowHideMapper[key];
bool show = config()->get(toggleTab->configKey).toBool();
bool show = conf()->get(toggleTab->configKey).toBool();
show = !show;
config()->set(toggleTab->configKey, show);
conf()->set(toggleTab->configKey, show);
ui->tabWidget->setTabVisible(ui->tabWidget->indexOf(toggleTab->tab), show);
toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name);
}
void MainWindow::menuClearHistoryClicked() {
config()->remove(Config::recentlyOpenedWallets);
conf()->remove(Config::recentlyOpenedWallets);
this->updateRecentlyOpenedMenu();
}
@ -478,30 +478,6 @@ QString MainWindow::walletKeysPath() {
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() {
qDebug() << Q_FUNC_INFO;
m_splashDialog->hide();
@ -548,15 +524,15 @@ void MainWindow::onWalletOpened() {
m_nodes->connectToNode();
m_updateBytes.start(250);
if (config()->get(Config::writeRecentlyOpenedWallets).toBool()) {
if (conf()->get(Config::writeRecentlyOpenedWallets).toBool()) {
this->addToRecentlyOpened(m_wallet->cachePath());
}
}
void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) {
bool hide = config()->get(Config::hideBalance).toBool();
int displaySetting = config()->get(Config::balanceDisplay).toInt();
int decimals = config()->get(Config::amountPrecision).toInt();
bool hide = conf()->get(Config::hideBalance).toBool();
int displaySetting = conf()->get(Config::balanceDisplay).toInt();
int decimals = conf()->get(Config::amountPrecision).toInt();
QString balance_str = "Balance: ";
if (hide) {
@ -598,8 +574,7 @@ void MainWindow::setStatusText(const QString &text, bool override, int timeout)
void MainWindow::tryStoreWallet() {
if (m_wallet->connectionStatus() == Wallet::ConnectionStatus::ConnectionStatus_Synchronizing) {
QMessageBox::warning(this, "Save wallet", "Unable to save wallet during synchronization.\n\n"
"Wait until synchronization is finished and try again.");
Utils::showError(this, "Unable to save wallet", "Can't save wallet during synchronization", {"Wait until synchronization is finished and try again"}, "synchronization");
return;
}
@ -611,9 +586,9 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
ui->actionShow_calc->setVisible(enabled);
ui->actionShow_Exchange->setVisible(enabled);
ui->tabWidget->setTabVisible(Tabs::HOME, enabled && config()->get(Config::showTabHome).toBool());
ui->tabWidget->setTabVisible(Tabs::CALC, enabled && config()->get(Config::showTabCalc).toBool());
ui->tabWidget->setTabVisible(Tabs::EXCHANGES, enabled && config()->get(Config::showTabExchange).toBool());
ui->tabWidget->setTabVisible(Tabs::HOME, enabled && conf()->get(Config::showTabHome).toBool());
ui->tabWidget->setTabVisible(Tabs::CALC, enabled && conf()->get(Config::showTabCalc).toBool());
ui->tabWidget->setTabVisible(Tabs::EXCHANGES, enabled && conf()->get(Config::showTabExchange).toBool());
m_historyWidget->setWebsocketEnabled(enabled);
m_sendWidget->setWebsocketEnabled(enabled);
@ -626,7 +601,7 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
void MainWindow::onProxySettingsChanged() {
m_nodes->connectToNode();
int proxy = config()->get(Config::proxy).toInt();
int proxy = conf()->get(Config::proxy).toInt();
if (proxy == Config::Proxy::Tor) {
this->onTorConnectionStateChanged(torManager()->torConnected);
@ -688,7 +663,7 @@ void MainWindow::onConnectionStatusChanged(int status)
// Update connection info in status bar.
QIcon icon;
if (config()->get(Config::offlineMode).toBool()) {
if (conf()->get(Config::offlineMode).toBool()) {
icon = icons()->icon("status_offline.svg");
this->setStatusText("Offline");
} else {
@ -720,41 +695,123 @@ void MainWindow::onConnectionStatusChanged(int status)
m_statusBtnConnectionStatusIndicator->setIcon(icon);
}
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
QString err{"Can't create transaction: "};
if (tx->status() != PendingTransaction::Status_Ok) {
QString tx_err = tx->errorString();
qCritical() << tx_err;
void MainWindow::onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address) {
// Clean up some UI
m_constructingTransaction = false;
m_txTimer.stop();
this->setStatusText(m_statusText);
if (m_wallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
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")) {
QString currentNode = m_nodes->connection().toAddress();
err += QString("\nYou are currently connected to: %1\n\n"
"This node may be acting maliciously. You are strongly recommended to disconnect from this node."
"Please report this incident to dev@featherwallet.org, #feather on OFTC or /r/FeatherWallet.").arg(currentNode);
if (m_wallet->isHwBacked()) {
m_splashDialog->hide();
}
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
if (tx->status() != PendingTransaction::Status_Ok) {
QString errMsg = tx->errorString();
Utils::Message message{this, Utils::ERROR, "Failed to construct transaction", errMsg};
if (tx->getException()) {
try
{
std::rethrow_exception(tx->getException());
}
catch (const tools::error::daemon_busy &e) {
message.description = QString("Node was unable to respond. Failed request: %1").arg(QString::fromStdString(e.request()));
message.helpItems = {"Try sending the transaction again.", "If this keeps happening, connect to a different node."};
}
catch (const tools::error::no_connection_to_daemon &e) {
message.description = QString("Connection to node lost. Failed request: %1").arg(QString::fromStdString(e.request()));
message.helpItems = {"Try sending the transaction again.", "If this keeps happening, connect to a different node."};
}
catch (const tools::error::wallet_rpc_error &e) {
message.description = QString("RPC error: %1").arg(QString::fromStdString(e.to_string()));
message.helpItems = {"Try sending the transaction again.", "If this keeps happening, connect to a different node."};
}
catch (const tools::error::get_outs_error &e) {
message.description = "Failed to get enough decoy outputs from node";
message.helpItems = {"Your transaction has too many inputs. Try sending a lower amount."};
}
catch (const tools::error::not_enough_unlocked_money &e) {
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";
}
message.helpItems = {"You have found a bug. Please contact the developers."};
message.doc = "report_an_issue";
}
catch (const std::exception &e) {
message.description = QString::fromStdString(e.what());
}
}
Utils::showMsg(message);
m_wallet->disposeTransaction(tx);
return;
}
else if (tx->txCount() == 0) {
err = QString("%1 %2").arg(err, "No unmixable outputs to sweep.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
Utils::showError(this, "Failed to construct transaction", "No transactions were constructed", {"You have found a bug. Please contact the developers."}, "report_an_issue");
m_wallet->disposeTransaction(tx);
return;
}
else if (tx->txCount() > 1) {
err = QString("%1 %2").arg(err, "Split transactions are not supported. Try sending a smaller amount.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
Utils::showError(this, "Failed to construct transaction", "Split transactions are not supported", {"Try sending a smaller amount."});
m_wallet->disposeTransaction(tx);
return;
}
@ -773,9 +830,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
destAddresses.insert(WalletManager::baseAddressFromIntegratedAddress(addr, constants::networkType));
}
if (!outputAddresses.contains(destAddresses)) {
err = QString("%1 %2").arg(err, "Constructed transaction doesn't appear to send to (all) specified destination address(es). Try creating the transaction again.");
qDebug() << Q_FUNC_INFO << err;
this->displayWalletErrorMsg(err);
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.");
m_wallet->disposeTransaction(tx);
return;
}
@ -812,7 +867,11 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
}
void MainWindow::onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid) {
if (success) {
if (!success) {
Utils::showError(this, "Failed to send transaction", tx->errorString());
return;
}
QMessageBox msgBox{this};
QPushButton *showDetailsButton = msgBox.addButton("Show details", QMessageBox::ActionRole);
msgBox.addButton(QMessageBox::Ok);
@ -831,21 +890,6 @@ void MainWindow::onTransactionCommitted(bool success, PendingTransaction *tx, co
}
m_sendWidget->clearFields();
} else {
auto err = tx->errorString();
QString body = QString("Error committing transaction: %1").arg(err);
QMessageBox::warning(this, "Transaction failed", body);
}
}
void MainWindow::onCreateTransactionError(const QString &message) {
auto msg = QString("Error while creating transaction: %1").arg(message);
if (msg.contains("failed to get random outs")) {
msg += "\n\nYour transaction has too many inputs. Try sending a lower amount.";
}
QMessageBox::warning(this, "Transaction failed", msg);
}
void MainWindow::showWalletInfoDialog() {
@ -855,17 +899,18 @@ void MainWindow::showWalletInfoDialog() {
void MainWindow::showSeedDialog() {
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;
}
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;
}
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;
}
@ -904,7 +949,7 @@ void MainWindow::showViewOnlyDialog() {
}
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() {
@ -948,7 +993,7 @@ void MainWindow::menuVerifyTxProof() {
}
void MainWindow::onShowSettingsPage(int page) {
config()->set(Config::lastSettingsPage, page);
conf()->set(Config::lastSettingsPage, page);
this->menuSettingsClicked();
}
@ -992,7 +1037,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
if (!this->cleanedUp) {
this->cleanedUp = true;
config()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
conf()->set(Config::homeWidget, ui->tabHomeWidget->currentIndex());
m_historyWidget->resetModel();
@ -1013,7 +1058,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
void MainWindow::changeEvent(QEvent* event)
{
if ((event->type() == QEvent::WindowStateChange) && this->isMinimized()) {
if (config()->get(Config::lockOnMinimize).toBool()) {
if (conf()->get(Config::lockOnMinimize).toBool()) {
this->lockWallet();
}
} else {
@ -1043,7 +1088,7 @@ void MainWindow::showCalcWindow() {
void MainWindow::payToMany() {
ui->tabWidget->setCurrentIndex(Tabs::SEND);
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"
"Format: address, amount\n"
"A maximum of 16 addresses may be specified.");
@ -1055,14 +1100,14 @@ void MainWindow::showSendScreen(const CCSEntry &entry) { // TODO: rename this fu
}
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);
}
void MainWindow::onResendTransaction(const QString &txid) {
QString txHex = m_wallet->getCacheTransaction(txid);
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;
}
@ -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() {
config()->set(Config::geometry, QString(saveGeometry().toBase64()));
config()->set(Config::windowState, QString(saveState().toBase64()));
conf()->set(Config::geometry, QString(saveGeometry().toBase64()));
conf()->set(Config::windowState, QString(saveState().toBase64()));
}
void MainWindow::restoreGeo() {
bool geo = this->restoreGeometry(QByteArray::fromBase64(config()->get(Config::geometry).toByteArray()));
bool windowState = this->restoreState(QByteArray::fromBase64(config()->get(Config::windowState).toByteArray()));
bool geo = this->restoreGeometry(QByteArray::fromBase64(conf()->get(Config::geometry).toByteArray()));
bool windowState = this->restoreState(QByteArray::fromBase64(conf()->get(Config::windowState).toByteArray()));
qDebug() << "Restored window state: " << geo << " " << windowState;
}
@ -1129,17 +1174,17 @@ void MainWindow::showAddressChecker() {
}
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;
}
SubaddressIndex index = m_wallet->subaddressIndex(address);
if (!index.isValid()) {
// 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;
} 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";
bool r = m_wallet->exportKeyImages(fn, true);
if (!r) {
QMessageBox::warning(this, "Key image export", QString("Failed to export key images.\nReason: %1").arg(m_wallet->errorString()));
Utils::showError(this, "Failed to export key images", m_wallet->errorString());
} 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;
bool r = m_wallet->importKeyImages(fn);
if (!r) {
QMessageBox::warning(this, "Key image import", QString("Failed to import key images.\n\n%1").arg(m_wallet->errorString()));
Utils::showError(this, "Failed to import key images", m_wallet->errorString());
} else {
QMessageBox::information(this, "Key image import", "Successfully imported key images");
Utils::showInfo(this, "Successfully imported key images");
m_wallet->refreshModels();
}
}
@ -1173,9 +1218,9 @@ void MainWindow::exportOutputs() {
if (!fn.endsWith("_outputs")) fn += "_outputs";
bool r = m_wallet->exportOutputs(fn, true);
if (!r) {
QMessageBox::warning(this, "Outputs export", QString("Failed to export outputs.\nReason: %1").arg(m_wallet->errorString()));
Utils::showError(this, "Failed to export outputs", m_wallet->errorString());
} 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;
bool r = m_wallet->importOutputs(fn);
if (!r) {
QMessageBox::warning(this, "Outputs import", QString("Failed to import outputs.\n\n%1").arg(m_wallet->errorString()));
Utils::showError(this, "Failed to import outputs", m_wallet->errorString());
} else {
QMessageBox::information(this, "Outputs import", "Successfully imported outputs");
Utils::showInfo(this, "Successfully imported outputs");
m_wallet->refreshModels();
}
}
@ -1197,7 +1242,7 @@ void MainWindow::loadUnsignedTx() {
UnsignedTransaction *tx = m_wallet->loadTxFile(fn);
auto err = m_wallet->errorString();
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;
}
@ -1207,13 +1252,13 @@ void MainWindow::loadUnsignedTx() {
void MainWindow::loadUnsignedTxFromClipboard() {
QString unsigned_tx = Utils::copyFromClipboard();
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;
}
UnsignedTransaction *tx = m_wallet->loadTxFromBase64Str(unsigned_tx);
auto err = m_wallet->errorString();
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;
}
@ -1226,7 +1271,7 @@ void MainWindow::loadSignedTx() {
PendingTransaction *tx = m_wallet->loadSignedTxFile(fn);
auto err = m_wallet->errorString();
if (!err.isEmpty()) {
QMessageBox::warning(this, "Load signed transaction from file", err);
Utils::showError(this, "Unable to load signed transaction", err);
return;
}
@ -1247,7 +1292,7 @@ void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
}
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
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() {
if (!m_wallet->rescanSpent()) {
QMessageBox::warning(this, "Rescan spent", m_wallet->errorString());
Utils::showError(this, "Failed to rescan spent outputs", m_wallet->errorString());
} 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) {
if (config()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
if (conf()->get(Config::proxy).toInt() != Config::Proxy::Tor) {
return;
}
@ -1429,7 +1474,7 @@ void MainWindow::onTorConnectionStateChanged(bool connected) {
void MainWindow::showUpdateNotification() {
#ifdef CHECK_UPDATES
if (config()->get(Config::hideUpdateNotifications).toBool()) {
if (conf()->get(Config::hideUpdateNotifications).toBool()) {
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() {
if (!m_criticalWarningShown) {
m_criticalWarningShown = true;
QMessageBox::warning(this, "Critical error", "WARNING!\n\nThe wallet keys are corrupted.\n\nTo prevent LOSS OF FUNDS do NOT continue to use this wallet file.\n\nRestore your wallet from seed.\n\nPlease report this incident to the Feather developers.\n\nWARNING!");
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();
}
}
@ -1505,22 +1539,19 @@ void MainWindow::onSelectedInputsChanged(const QStringList &selectedInputs) {
}
void MainWindow::onExportHistoryCSV() {
if (m_wallet == nullptr)
return;
QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
if (fn.isEmpty())
return;
if (!fn.endsWith(".csv"))
fn += ".csv";
m_wallet->history()->writeCSV(fn);
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() {
if (m_wallet == nullptr) return;
auto *model = m_wallet->addressBookModel();
if (model->rowCount() <= 0){
QMessageBox::warning(this, "Error", "Addressbook empty");
Utils::showInfo(this, "Unable to export contacts", "No contacts to export");
return;
}
@ -1529,8 +1560,9 @@ void MainWindow::onExportContactsCSV() {
qint64 now = QDateTime::currentMSecsSinceEpoch();
QString fn = QString("%1/monero-contacts_%2.csv").arg(targetDir, QString::number(now / 1000));
if(model->writeCSV(fn))
QMessageBox::information(this, "Address book exported", QString("Address book exported to %1").arg(fn));
if (model->writeCSV(fn)) {
Utils::showInfo(this, "Contacts exported successfully", QString("Exported to: %1").arg(fn));
}
}
void MainWindow::onCreateDesktopEntry() {
@ -1539,11 +1571,12 @@ void MainWindow::onCreateDesktopEntry() {
}
void MainWindow::onShowDocumentation() {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org");
// TODO: welcome page
m_windowManager->showDocs(this);
}
void MainWindow::onReportBug() {
Utils::externalLinkWarning(this, "https://docs.featherwallet.org/guides/report-an-issue");
m_windowManager->showDocs(this, "report_an_issue");
}
QString MainWindow::getHardwareDevice() {
@ -1581,7 +1614,7 @@ void MainWindow::donationNag() {
if (m_wallet->balanceAll() == 0)
return;
auto donationCounter = config()->get(Config::donateBeg).toInt();
auto donationCounter = conf()->get(Config::donateBeg).toInt();
if (donationCounter == -1)
return;
@ -1594,11 +1627,11 @@ void MainWindow::donationNag() {
this->donateButtonClicked();
}
}
config()->set(Config::donateBeg, donationCounter);
conf()->set(Config::donateBeg, donationCounter);
}
void MainWindow::addToRecentlyOpened(QString keysFile) {
auto recent = config()->get(Config::recentlyOpenedWallets).toList();
auto recent = conf()->get(Config::recentlyOpenedWallets).toList();
if (Utils::isPortableMode()) {
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();
}
void MainWindow::updateRecentlyOpenedMenu() {
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) {
QFileInfo fileInfo{walletPath};
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() {
if (!config()->get(Config::inactivityLockEnabled).toBool()) {
if (!conf()->get(Config::inactivityLockEnabled).toBool()) {
return;
}
@ -1679,7 +1712,7 @@ void MainWindow::checkUserActivity() {
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";
this->lockWallet();
}
@ -1691,7 +1724,7 @@ void MainWindow::lockWallet() {
}
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;
}
m_walletUnlockWidget->reset();
@ -1731,7 +1764,7 @@ void MainWindow::unlockWallet(const QString &password) {
}
void MainWindow::toggleSearchbar(bool visible) {
config()->set(Config::showSearchbar, visible);
conf()->set(Config::showSearchbar, visible);
m_historyWidget->setSearchbarVisible(visible);
m_receiveWidget->setSearchbarVisible(visible);

View file

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

View file

@ -202,7 +202,7 @@ void ReceiveWidget::showOnDevice() {
void ReceiveWidget::generateSubaddress() {
bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), "");
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);
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);
@ -52,15 +52,17 @@ SendWidget::SendWidget(Wallet *wallet, QWidget *parent)
ui->label_conversionAmount->hide();
ui->btn_openAlias->hide();
ui->label_PayTo->setHelpText("Recipient of the funds.\n\n"
"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"
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)",
"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, "
"and displayed in the 'History' tab.");
ui->label_Amount->setHelpText("Amount to be sent.\n\nThis is the exact amount the recipient will receive. "
"and displayed in the 'History' tab.",
"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. "
"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);
this->setupComboBox();
@ -124,7 +126,7 @@ void SendWidget::scanClicked() {
#if defined(WITH_SCANNER) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
auto cameras = QCameraInfo::availableCameras();
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;
}
@ -135,7 +137,7 @@ void SendWidget::scanClicked() {
#elif defined(WITH_SCANNER)
auto cameras = QMediaDevices::videoInputs();
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;
}
@ -144,27 +146,26 @@ void SendWidget::scanClicked() {
ui->lineAddress->setText(dialog->decodedString);
dialog->deleteLater();
#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
}
void SendWidget::sendClicked() {
if (!m_wallet->isConnected()) {
QMessageBox::warning(this, "Error", "Unable to create transaction:\n\n"
"Wallet is not connected to a node.\n"
"Go to File -> Settings -> Node to manually connect to a node.");
Utils::showError(this, "Unable to create transaction", "Wallet is not connected to a node.",
{"Wait for the wallet to automatically connect to a node.", "Go to File -> Settings -> Network -> Node to manually connect to a node."},
"nodes");
return;
}
if (!m_wallet->isSynchronized()) {
QMessageBox::warning(this, "Error", "Wallet is not synchronized, unable to create transaction.\n\n"
"Wait for synchronization to complete.");
Utils::showError(this, "Unable to create transaction", "Wallet is not synchronized", {"Wait for wallet synchronization to complete"}, "synchronization");
return;
}
QString recipient = ui->lineAddress->text().simplified().remove(' ');
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;
}
@ -176,7 +177,7 @@ void SendWidget::sendClicked() {
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;
}
@ -184,7 +185,7 @@ void SendWidget::sendClicked() {
if (!outputs.empty()) { // multi destination transaction
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;
}
@ -204,7 +205,7 @@ void SendWidget::sendClicked() {
quint64 amount = this->amount();
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;
}
@ -213,6 +214,18 @@ void SendWidget::sendClicked() {
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);
}
@ -242,7 +255,7 @@ void SendWidget::updateConversionLabel() {
return;
}
if (config()->get(Config::disableWebsocket).toBool()) {
if (conf()->get(Config::disableWebsocket).toBool()) {
return;
}
@ -252,7 +265,7 @@ void SendWidget::updateConversionLabel() {
return QString("~%1 XMR").arg(QString::number(this->conversionAmount(), 'f'));
} else {
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString();
auto preferredFiatCurrency = conf()->get(Config::preferredFiatCurrency).toString();
double conversionAmount = appData()->prices.convert("XMR", preferredFiatCurrency, this->amountDouble());
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);
if (address.isEmpty()) {
this->onOpenAliasResolveError("Could not resolve OpenAlias.");
Utils::showError(this, "Unable to resolve OpenAlias", "Address empty.");
return;
}
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;
}
bool valid = WalletManager::addressValid(address, constants::networkType);
if (!valid) {
this->onOpenAliasResolveError(QString("Address validation error. Perhaps it is of the wrong network type.\n\n"
"OpenAlias: %1\nAddress: %2").arg(openAlias, address));
Utils::showError(this, "Unable to resolve OpenAlias", QString("Address validation failed.\n\nOpenAlias: %1\nAddress: %2").arg(openAlias, address));
return;
}
@ -311,10 +323,6 @@ void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &ad
ui->btn_openAlias->hide();
}
void SendWidget::onOpenAliasResolveError(const QString &msg) {
QMessageBox::warning(this, "OpenAlias error", msg);
}
void SendWidget::clearFields() {
ui->lineAddress->clear();
ui->lineAmount->clear();
@ -363,7 +371,7 @@ void SendWidget::onDataPasted(const QString &data) {
}
}
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();
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)) {
defaultCurrencies.removeOne(preferredCurrency);

View file

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

View file

@ -14,6 +14,7 @@
#include "utils/Icons.h"
#include "utils/WebsocketNotifier.h"
#include "widgets/NetworkProxyWidget.h"
#include "WindowManager.h"
Settings::Settings(Nodes *nodes, QWidget *parent)
: QDialog(parent)
@ -36,7 +37,7 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
ui->selector->setSelectionMode(QAbstractItemView::SingleSelection);
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("nw_32px.png"), "Network", ui->selector, Pages::NETWORK);
@ -61,11 +62,11 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
emit proxySettingsChanged();
}
config()->set(Config::lastSettingsPage, ui->selector->currentRow());
conf()->set(Config::lastSettingsPage, ui->selector->currentRow());
this->close();
});
this->setSelection(config()->get(Config::lastSettingsPage).toInt());
this->setSelection(conf()->get(Config::lastSettingsPage).toInt());
this->adjustSize();
}
@ -73,7 +74,7 @@ Settings::Settings(Nodes *nodes, QWidget *parent)
void Settings::setupAppearanceTab() {
// [Theme]
this->setupThemeComboBox();
auto settingsTheme = config()->get(Config::skin).toString();
auto settingsTheme = conf()->get(Config::skin).toString();
if (m_themes.contains(settingsTheme)) {
ui->comboBox_theme->setCurrentIndex(m_themes.indexOf(settingsTheme));
}
@ -85,10 +86,10 @@ void Settings::setupAppearanceTab() {
for (int i = 0; i <= 12; 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){
config()->set(Config::amountPrecision, pos);
conf()->set(Config::amountPrecision, pos);
emit updateBalance();
});
@ -97,33 +98,33 @@ void Settings::setupAppearanceTab() {
for (const auto & format : m_dateFormats) {
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)) {
ui->comboBox_dateFormat->setCurrentIndex(m_dateFormats.indexOf(dateFormatSetting));
}
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]
for (const auto & format : m_timeFormats) {
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)) {
ui->comboBox_timeFormat->setCurrentIndex(m_timeFormats.indexOf(timeFormatSetting));
}
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]
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){
config()->set(Config::balanceDisplay, pos);
conf()->set(Config::balanceDisplay, pos);
emit updateBalance();
});
@ -138,14 +139,14 @@ void Settings::setupAppearanceTab() {
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)) {
ui->comboBox_fiatCurrency->setCurrentText(preferredFiatCurrency);
}
connect(ui->comboBox_fiatCurrency, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index){
QString selection = ui->comboBox_fiatCurrency->itemText(index);
config()->set(Config::preferredFiatCurrency, selection);
conf()->set(Config::preferredFiatCurrency, selection);
emit preferredFiatCurrencyChanged(selection);
});
}
@ -167,16 +168,16 @@ void Settings::setupNetworkTab() {
// Websocket
// [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){
config()->set(Config::disableWebsocket, !checked);
conf()->set(Config::disableWebsocket, !checked);
this->enableWebsocket(checked);
});
// 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){
config()->set(Config::offlineMode, checked);
conf()->set(Config::offlineMode, checked);
emit offlineMode(checked);
this->enableWebsocket(!checked);
});
@ -184,7 +185,7 @@ void Settings::setupNetworkTab() {
void Settings::setupStorageTab() {
// 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_applicationDir->setText(Utils::applicationPath());
@ -195,16 +196,16 @@ void Settings::setupStorageTab() {
}
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);
if (walletDir.isEmpty())
return;
config()->set(Config::walletDirectory, walletDir);
conf()->set(Config::walletDirectory, walletDir);
ui->lineEdit_defaultWalletDir->setText(walletDir);
});
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, []{
@ -215,25 +216,25 @@ void Settings::setupStorageTab() {
// Logging
// [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){
config()->set(Config::disableLogging, !toggled);
WalletManager::instance()->setLogLevel(toggled ? config()->get(Config::logLevel).toInt() : -1);
conf()->set(Config::disableLogging, !toggled);
WalletManager::instance()->setLogLevel(toggled ? conf()->get(Config::logLevel).toInt() : -1);
});
// [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){
config()->set(Config::logLevel, index);
if (!config()->get(Config::disableLogging).toBool()) {
conf()->set(Config::logLevel, index);
if (!conf()->get(Config::disableLogging).toBool()) {
WalletManager::instance()->setLogLevel(index);
}
});
// [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){
config()->set(Config::writeStackTraceToDisk, toggled);
conf()->set(Config::writeStackTraceToDisk, toggled);
});
// [Open log file]
@ -243,57 +244,57 @@ void Settings::setupStorageTab() {
// Misc
// [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){
config()->set(Config::writeRecentlyOpenedWallets, toggled);
conf()->set(Config::writeRecentlyOpenedWallets, toggled);
if (!toggled) {
config()->set(Config::recentlyOpenedWallets, {});
conf()->set(Config::recentlyOpenedWallets, {});
}
});
}
void Settings::setupDisplayTab() {
// [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){
config()->set(Config::hideBalance, toggled);
conf()->set(Config::hideBalance, toggled);
emit updateBalance();
});
// [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){
config()->set(Config::hideUpdateNotifications, toggled);
conf()->set(Config::hideUpdateNotifications, toggled);
emit hideUpdateNotifications(toggled);
});
// [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){
config()->set(Config::hideNotifications, toggled);
conf()->set(Config::hideNotifications, toggled);
});
// [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]{
bool state = ui->checkBox_warnOnExternalLink->isChecked();
config()->set(Config::warnOnExternalLink, state);
conf()->set(Config::warnOnExternalLink, state);
});
// [Lock wallet on inactivity]
ui->checkBox_lockOnInactivity->setChecked(config()->get(Config::inactivityLockEnabled).toBool());
ui->spinBox_lockOnInactivity->setValue(config()->get(Config::inactivityLockTimeout).toInt());
ui->checkBox_lockOnInactivity->setChecked(conf()->get(Config::inactivityLockEnabled).toBool());
ui->spinBox_lockOnInactivity->setValue(conf()->get(Config::inactivityLockTimeout).toInt());
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){
config()->set(Config::inactivityLockTimeout, value);
conf()->set(Config::inactivityLockTimeout, value);
});
// [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){
config()->set(Config::lockOnMinimize, toggled);
conf()->set(Config::lockOnMinimize, toggled);
});
}
@ -303,12 +304,12 @@ void Settings::setupMemoryTab() {
void Settings::setupTransactionsTab() {
// [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){
config()->set(Config::multiBroadcast, toggled);
conf()->set(Config::multiBroadcast, toggled);
});
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
@ -318,18 +319,18 @@ void Settings::setupTransactionsTab() {
void Settings::setupMiscTab() {
// [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]{
QString blockExplorer = ui->comboBox_blockExplorer->currentText();
config()->set(Config::blockExplorer, blockExplorer);
conf()->set(Config::blockExplorer, blockExplorer);
emit blockExplorerChanged(blockExplorer);
});
// [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]{
QString redditFrontend = ui->comboBox_redditFrontend->currentText();
config()->set(Config::redditFrontend, redditFrontend);
conf()->set(Config::redditFrontend, redditFrontend);
});
// [LocalMonero frontend]
@ -339,10 +340,10 @@ void Settings::setupMiscTab() {
"http://nehdddktmhvqklsnkjqcbpmb63htee2iznpcbs5tgzctipxykpj6yrid.onion");
ui->comboBox_localMoneroFrontend->addItem("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]{
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) {
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();
} else {
websocketNotifier()->websocketClient->stop();

View file

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

View file

@ -7,6 +7,7 @@
#include <QDialogButtonBox>
#include <QInputDialog>
#include <QMessageBox>
#include <QWindow>
#include "constants.h"
#include "dialog/PasswordDialog.h"
@ -18,9 +19,8 @@
#include "utils/TorManager.h"
#include "utils/WebsocketNotifier.h"
WindowManager::WindowManager(QObject *parent, EventFilter *eventFilter)
WindowManager::WindowManager(QObject *parent)
: QObject(parent)
, eventFilter(eventFilter)
{
m_walletManager = WalletManager::instance();
m_splashDialog = new SplashDialog();
@ -45,7 +45,7 @@ WindowManager::WindowManager(QObject *parent, EventFilter *eventFilter)
this->showCrashLogs();
if (!config()->get(Config::firstRun).toBool() || TailsOS::detect() || WhonixOS::detect()) {
if (!conf()->get(Config::firstRun).toBool() || TailsOS::detect() || WhonixOS::detect()) {
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() {
qDebug() << "~WindowManager";
m_cleanupThread->quit();
@ -82,6 +88,7 @@ void WindowManager::close() {
m_wizard->deleteLater();
m_splashDialog->deleteLater();
m_tray->deleteLater();
m_docsDialog->deleteLater();
torManager()->stop();
@ -109,13 +116,13 @@ void WindowManager::startupWarning() {
// Stagenet / Testnet
auto worthlessWarning = QString("Feather wallet is currently running in %1 mode. This is meant "
"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"));
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"));
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();
}
// ######################## 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 ########################
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)) {
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;
}
@ -202,30 +246,27 @@ void WindowManager::tryOpenWallet(const QString &path, const QString &password)
void WindowManager::onWalletOpened(Wallet *wallet) {
if (!wallet) {
QString err{"Unable to open wallet"};
this->handleWalletError(err);
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"});
return;
}
auto status = wallet->status();
if (status != Wallet::Status_Ok) {
QString errMsg = wallet->errorString();
QString keysPath = wallet->keysPath();
QString cachePath = wallet->cachePath();
wallet->deleteLater();
if (status == Wallet::Status_BadPassword) {
// Don't show incorrect password when we try with empty password for the first time
bool showIncorrectPassword = m_openWalletTriedOnce;
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")) {
qCritical() << errMsg;
WalletManager::clearWalletCache(cachePath);
WalletManager::clearWalletCache(wallet->cachePath());
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 {
this->handleWalletError(errMsg);
this->handleWalletError({nullptr, Utils::ERROR, "Unable to open wallet", errMsg});
}
return;
}
@ -273,7 +314,7 @@ void WindowManager::onWalletOpenPasswordRequired(bool invalidPassword, const QSt
}
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))) {
autoPath.remove(0, 1);
}
@ -289,13 +330,12 @@ bool WindowManager::autoOpenWallet() {
void WindowManager::tryCreateWallet(Seed seed, const QString &path, const QString &password, const QString &seedLanguage,
const QString &seedOffset, const QString &subaddressLookahead, bool newWallet) {
if (Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
this->handleWalletError(err);
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", QString("File already exists: %1").arg(path)});
return;
}
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;
}
@ -308,7 +348,7 @@ void WindowManager::tryCreateWallet(Seed seed, const QString &path, const QStrin
}
if (!wallet) {
this->handleWalletError("Failed to write wallet");
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet"});
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)
{
if (Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
this->handleWalletError(err);
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet from device", QString("File already exists: %1").arg(path)});
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,
const QString &viewkey, const QString &spendkey, quint64 restoreHeight, const QString &subaddressLookahead) {
if (Utils::fileExists(path)) {
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
this->handleWalletError(err);
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", QString("File already exists: %1").arg(path)});
return;
}
@ -348,20 +386,17 @@ void WindowManager::tryCreateWalletFromKeys(const QString &path, const QString &
}
else {
if (!spendkey.isEmpty() && !WalletManager::keyValid(spendkey, address, false, constants::networkType)) {
auto err = QString("Failed to create wallet. Invalid spendkey provided.").arg(path);
this->handleWalletError(err);
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Invalid spendkey provided"});
return;
}
if (!WalletManager::addressValid(address, constants::networkType)) {
auto err = QString("Failed to create wallet. Invalid address provided.").arg(path);
this->handleWalletError(err);
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Invalid address provided"});
return;
}
if (!WalletManager::keyValid(viewkey, address, true, constants::networkType)) {
auto err = QString("Failed to create wallet. Invalid viewkey provided.").arg(path);
this->handleWalletError(err);
this->handleWalletError({nullptr, Utils::ERROR, "Failed to create wallet", "Invalid viewkey provided"});
return;
}
@ -376,10 +411,59 @@ void WindowManager::onWalletCreated(Wallet *wallet) {
// Currently only called when a wallet is created from device.
auto state = wallet->status();
if (state != Wallet::Status_Ok) {
qDebug() << Q_FUNC_INFO << QString("Wallet open error: %1").arg(wallet->errorString());
this->displayWalletErrorMessage(wallet->errorString());
m_splashDialog->hide();
QString error = wallet->errorString();
QStringList helpItems;
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);
Utils::showMsg({m_wizard, Utils::ERROR, "Failed to create wallet from device", error, helpItems, doc, "", link});
m_splashDialog->hide();
m_openingWallet = false;
return;
}
@ -389,76 +473,11 @@ void WindowManager::onWalletCreated(Wallet *wallet) {
// ######################## ERROR HANDLING ########################
void WindowManager::handleWalletError(const QString &message) {
qCritical() << message;
this->displayWalletErrorMessage(message);
void WindowManager::handleWalletError(const Utils::Message &message) {
Utils::showMsg(message);
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() {
QString crashLogPath{Config::defaultConfigDir().path() + "/crash_report.txt"};
QFile crashLogFile{crashLogPath};
@ -586,7 +605,7 @@ void WindowManager::notify(const QString &title, const QString &message, int dur
return;
}
if (config()->get(Config::hideNotifications).toBool()) {
if (conf()->get(Config::hideNotifications).toBool()) {
return;
}
@ -614,11 +633,11 @@ void WindowManager::onProxySettingsChanged() {
torManager()->start();
QNetworkProxy proxy{QNetworkProxy::NoProxy};
if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort();
if (conf()->get(Config::proxy).toInt() != Config::Proxy::None) {
QString host = conf()->get(Config::socks5Host).toString();
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;
port = torManager()->featherTorPort;
}
@ -659,7 +678,7 @@ WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) {
void WindowManager::initWizard() {
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;
}
@ -699,7 +718,7 @@ void WindowManager::initSkins() {
if (!breeze_light.isEmpty())
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]);
}
@ -725,7 +744,7 @@ void WindowManager::onChangeTheme(const QString &skinName) {
return;
}
config()->set(Config::skin, skinName);
conf()->set(Config::skin, skinName);
qApp->setStyleSheet(m_skins[skinName]);
qDebug() << QString("Skin changed to %1").arg(skinName);
@ -748,3 +767,12 @@ void WindowManager::patchMacStylesheet() {
qApp->setStyleSheet(styleSheet);
#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 "dialog/DocsDialog.h"
#include "dialog/TorInfoDialog.h"
#include "libwalletqt/WalletManager.h"
#include "libwalletqt/Wallet.h"
#include "MainWindow.h"
#include "utils/nodes.h"
#include "wizard/WalletWizard.h"
#include "Utils.h"
class MainWindow;
class WindowManager : public QObject {
Q_OBJECT
public:
explicit WindowManager(QObject *parent, EventFilter *eventFilter);
explicit WindowManager(QObject *parent);
~WindowManager() override;
void setEventFilter(EventFilter *eventFilter);
void wizardOpenWallet();
void close();
void closeWindow(MainWindow *window);
@ -30,10 +34,15 @@ public:
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);
EventFilter *eventFilter;
static WindowManager* instance();
signals:
void proxySettingsChanged();
void websocketStatusChanged(bool enabled);
@ -66,7 +75,7 @@ private:
void initWizard();
WalletWizard* createWizard(WalletWizard::Page startPage);
void handleWalletError(const QString &message);
void handleWalletError(const Utils::Message &message);
void displayWalletErrorMessage(const QString &message);
void initSkins();
@ -80,11 +89,14 @@ private:
void quitAfterLastWindow();
static QPointer<WindowManager> m_instance;
QVector<MainWindow*> m_windows;
WalletManager *m_walletManager;
WalletWizard *m_wizard = nullptr;
SplashDialog *m_splashDialog = nullptr;
DocsDialog *m_docsDialog = nullptr;
QSystemTrayIcon *m_tray;
@ -97,5 +109,9 @@ private:
QThread *m_cleanupThread;
};
inline WindowManager* windowManager()
{
return WindowManager::instance();
}
#endif //FEATHER_WINDOWMANAGER_H

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

View file

@ -5,6 +5,8 @@
#include <QtWidgets>
#include "Utils.h"
DoublePixmapLabel::DoublePixmapLabel(QWidget *parent)
: QLabel(parent)
{}
@ -32,21 +34,22 @@ StatusBarButton::StatusBarButton(const QIcon &icon, const QString &tooltip, QWid
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)
{
this->help_text = "help_text";
this->font = QFont();
}
void HelpLabel::mouseReleaseEvent(QMouseEvent *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)

View file

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

View file

@ -9,23 +9,29 @@
BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet)
: WindowModalDialog(parent)
, m_wallet(wallet)
, ui(new Ui::BalanceDialog)
{
ui->setupUi(this);
ui->label_unconfirmed_help->setHelpText("Outputs require 10 confirmations before they become spendable. "
"This will take 20 minutes on average.");
connect(m_wallet, &Wallet::balanceUpdated, this, &BalanceDialog::updateBalance);
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_spendable->setText(WalletManager::displayAmount(wallet->unlockedBalance()));
ui->label_spendable->setFont(Utils::getMonospaceFont());
ui->label_total->setText(WalletManager::displayAmount(wallet->balance()));
ui->label_total->setFont(Utils::getMonospaceFont());
this->updateBalance();
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;

View file

@ -22,7 +22,10 @@ public:
~BalanceDialog() override;
private:
void updateBalance();
QScopedPointer<Ui::BalanceDialog> ui;
Wallet *m_wallet;
};
#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->buttonBox, &QDialogButtonBox::accepted, [this]{
config()->set(Config::fiatSymbols, this->checkedFiat());
config()->set(Config::cryptoSymbols, this->checkedCrypto());
conf()->set(Config::fiatSymbols, this->checkedFiat());
conf()->set(Config::cryptoSymbols, this->checkedCrypto());
this->accept();
});
@ -75,8 +75,8 @@ void CalcConfigDialog::fillListWidgets() {
QStringList cryptoCurrencies = appData()->prices.markets.keys();
QStringList fiatCurrencies = appData()->prices.rates.keys();
QStringList checkedCryptoCurrencies = config()->get(Config::cryptoSymbols).toStringList();
QStringList checkedFiatCurrencies = config()->get(Config::fiatSymbols).toStringList();
QStringList checkedCryptoCurrencies = conf()->get(Config::cryptoSymbols).toStringList();
QStringList checkedFiatCurrencies = conf()->get(Config::fiatSymbols).toStringList();
ui->list_crypto->addItems(cryptoCurrencies);
ui->list_fiat->addItems(fiatCurrencies);

View file

@ -61,13 +61,13 @@ void DebugInfoDialog::updateInfo() {
ui->label_remoteNode->setText(node.toAddress());
ui->label_walletStatus->setText(this->statusToString(m_wallet->connectionStatus()));
QString websocketStatus = Utils::QtEnumToString(websocketNotifier()->websocketClient->webSocket->state()).remove("State");
if (config()->get(Config::disableWebsocket).toBool()) {
if (conf()->get(Config::disableWebsocket).toBool()) {
websocketStatus = "Disabled";
}
ui->label_websocketStatus->setText(websocketStatus);
QString proxy = [](){
int proxy = config()->get(Config::proxy).toInt();
int proxy = conf()->get(Config::proxy).toInt();
switch (proxy) {
case 0:
return "None";
@ -84,7 +84,7 @@ void DebugInfoDialog::updateInfo() {
ui->label_proxy->setText(proxy);
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](){
if (m_wallet->isHwBacked())
@ -107,7 +107,7 @@ void DebugInfoDialog::updateInfo() {
}();
QString networkType = Utils::QtEnumToString(m_wallet->nettype());
if (config()->get(Config::offlineMode).toBool()) {
if (conf()->get(Config::offlineMode).toBool()) {
networkType += " (offline)";
}
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 "ui_PasswordChangeDialog.h"
#include <QMessageBox>
#include "Utils.h"
PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet)
: WindowModalDialog(parent)
@ -50,7 +50,7 @@ void PasswordChangeDialog::setPassword() {
QString newPassword = ui->lineEdit_newPassword->text();
if (!m_wallet->verifyPassword(currentPassword)) {
QMessageBox::warning(this, "Error", "Incorrect password");
Utils::showError(this, "Incorrect password", "");
ui->lineEdit_currentPassword->setText("");
ui->lineEdit_currentPassword->setFocus();
return;
@ -61,7 +61,7 @@ void PasswordChangeDialog::setPassword() {
this->accept();
}
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->label_restoreHeightHelp->setHelpText("Should you restore your wallet in the future, "
"specifying this block number will recover your wallet quicker.");
ui->label_restoreHeightHelp->setHelpText("", "Should you restore your wallet in the future, "
"specifying this block number will recover your wallet quicker.", "restore_height");
this->adjustSize();
}

View file

@ -49,8 +49,12 @@ void SignVerifyDialog::signMessage() {
void SignVerifyDialog::verifyMessage() {
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() {

View file

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

View file

@ -111,7 +111,7 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) {
}
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){
return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);
@ -164,33 +164,54 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) {
void TxConfAdvDialog::signTransaction() {
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)");
if(fn.isEmpty()) return;
if (fn.isEmpty()) {
return;
}
m_utx->sign(fn) ? QMessageBox::information(this, "Sign transaction", "Transaction saved successfully")
: QMessageBox::warning(this, "Sign transaction", "Failed to save transaction to file.");
bool success = m_utx->sign(fn);
if (success) {
Utils::showInfo(this, "Transaction saved successfully");
} else {
Utils::showError(this, "Failed to save transaction to file");
}
}
void TxConfAdvDialog::unsignedSaveFile() {
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)");
if(fn.isEmpty()) return;
if (fn.isEmpty()) {
return;
}
m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully")
: QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file.");
bool success = m_tx->saveToFile(fn);
if (success) {
Utils::showInfo(this, "Transaction saved successfully");
} else {
Utils::showError(this, "Failed to save transaction to file");
}
}
void TxConfAdvDialog::signedSaveFile() {
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)");
if(fn.isEmpty()) return;
if (fn.isEmpty()) {
return;
}
m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully")
: QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file.");
bool success = m_tx->saveToFile(fn);
if (success) {
Utils::showInfo(this, "Transaction saved successfully");
} else {
Utils::showError(this, "Failed to save transaction to file");
}
}
void TxConfAdvDialog::unsignedQrCode() {
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;
}
@ -209,7 +230,7 @@ void TxConfAdvDialog::signedCopy() {
void TxConfAdvDialog::txKeyCopy() {
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;
}

View file

@ -4,8 +4,6 @@
#include "TxConfDialog.h"
#include "ui_TxConfDialog.h"
#include <QMessageBox>
#include "constants.h"
#include "TxConfAdvDialog.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_note->hide();
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString();
QString preferredCur = conf()->get(Config::preferredFiatCurrency).toString();
auto convert = [preferredCur](double amount){
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();
if (m_wallet->haveTransaction(txid)) {
QMessageBox::warning(this, "Warning", "This transaction already exists in the wallet. "
"If you can't find it in your history, "
Utils::showWarning(this, "Transaction already exists in wallet", "If you can't find it in your history, "
"check if it belongs to a different account (Wallet -> Account)");
return;
}
if (m_wallet->importTransaction(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;
}
QMessageBox::information(this, "Import transaction", "Transaction imported successfully.");
Utils::showInfo(this, "Transaction imported successfully", "");
} else {
QMessageBox::warning(this, "Import transaction", "Transaction import failed.");
Utils::showError(this, "Failed to import transaction", "");
}
m_wallet->refreshModels();
}

View file

@ -116,7 +116,7 @@ void TxInfoDialog::setData(TransactionInfo *tx) {
ui->label_status->setText("Status: Unconfirmed (in mempool)");
}
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 statusText = QString("Status: Included in block %1 (%2 confirmations) on %3").arg(blockHeight, QString::number(tx->confirmations()), date);
ui->label_status->setText(statusText);
@ -160,14 +160,14 @@ void TxInfoDialog::copyTxID() {
void TxInfoDialog::copyTxKey() {
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;
}
m_wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){
QString txKey = map.value("tx_key").toString();
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 {
Utils::copyToClipboard(txKey);
QMessageBox::information(this, "Transaction key copied", "Transaction key copied to clipboard.");
@ -181,7 +181,7 @@ void TxInfoDialog::createTxProof() {
}
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;

View file

@ -156,7 +156,7 @@ void TxProofDialog::getFormattedProof() {
TxProof proof = this->getProof();
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;
}
@ -216,7 +216,7 @@ void TxProofDialog::getSignature() {
TxProof proof = this->getProof();
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;
}

View file

@ -179,8 +179,12 @@ void VerifyProofDialog::proofStatus(bool success, const QString &message) {
}
else {
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);
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"
"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. "
"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();
}

View file

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

View file

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

View file

@ -176,7 +176,7 @@ bool TransactionHistory::writeCSV(const QString &path) {
// calc historical fiat price
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"));
double fiat_price = usd_price * amount;

View file

@ -74,12 +74,10 @@ Wallet::Wallet(Monero::Wallet *wallet, QObject *parent)
this->history()->refresh(this->currentSubaddressAccount());
});
connect(this, &Wallet::createTransactionError, this, &Wallet::onCreateTransactionError);
connect(this, &Wallet::refreshed, this, &Wallet::onRefreshed);
connect(this, &Wallet::newBlock, this, &Wallet::onNewBlock);
connect(this, &Wallet::updated, this, &Wallet::onUpdated);
connect(this, &Wallet::heightsRefreshed, this, &Wallet::onHeightsRefreshed);
connect(this, &Wallet::transactionCreated, this, &Wallet::onTransactionCreated);
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) {
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";
m_scheduler.run([this, all, address, amount] {
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);
QVector<QString> addresses{address};
emit transactionCreated(ptImpl, addresses);
this->onTransactionCreated(ptImpl, addresses);
});
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) {
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";
m_scheduler.run([this, addresses, amounts] {
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),
currentSubaddressAccount(), subaddr_indices, m_selectedInputs);
emit transactionCreated(ptImpl, addresses);
this->onTransactionCreated(ptImpl, addresses);
});
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));
QVector<QString> addresses {address};
emit transactionCreated(ptImpl, addresses);
this->onTransactionCreated(ptImpl, addresses);
});
emit initiateTransaction();
@ -759,11 +732,6 @@ void Wallet::sweepOutputs(const QVector<QString> &keyImages, QString address, bo
// 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) {
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.
emit createTransactionSuccess(tx, address);
emit transactionCreated(tx, address);
}
// 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
// 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
emit multiBroadcast(txHexMap);
}

View file

@ -300,7 +300,6 @@ public:
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 onCreateTransactionError(const QString &msg);
void commitTransaction(PendingTransaction *tx, const QString &description="");
void onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid, const QMap<QString, QString> &txHexMap);
@ -416,9 +415,6 @@ signals:
void transactionProofVerified(TxProofResult 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 currentSubaddressAccountChanged() const;
@ -428,13 +424,12 @@ signals:
void balanceUpdated(quint64 balance, quint64 spendable);
void keysCorrupted();
void endTransaction();
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void transactionCreated(PendingTransaction *tx, const QVector<QString> &address);
void donationSent();
void walletRefreshed();
void initiateTransaction();
void createTransactionError(QString message);
void selectedInputsChanged(const QStringList &selectedInputs);

View file

@ -42,7 +42,7 @@ void signal_handler(int signum) {
std::cout << keyStream.str();
// 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"};
std::ofstream out(crashLogPath.toStdString());
out << QString("Version: %1-%2\n").arg(FEATHER_VERSION, FEATHER_COMMIT).toStdString();
@ -165,12 +165,12 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
bool logLevelFromEnv;
int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv);
if (logLevelFromEnv) {
config()->set(Config::logLevel, logLevel);
conf()->set(Config::logLevel, logLevel);
} 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";
WalletManager::instance()->setLogLevel(-1);
}
@ -179,23 +179,23 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
}
// Setup wallet directory
QString walletDir = config()->get(Config::walletDirectory).toString();
QString walletDir = conf()->get(Config::walletDirectory).toString();
if (walletDir.isEmpty() || Utils::isPortableMode()) {
walletDir = Utils::defaultWalletDir();
config()->set(Config::walletDirectory, walletDir);
conf()->set(Config::walletDirectory, walletDir);
}
if (!QDir().mkpath(walletDir))
qCritical() << "Unable to create dir: " << walletDir;
// Prestium initial config
if (config()->get(Config::firstRun).toBool() && Prestium::detect()) {
config()->set(Config::proxy, Config::Proxy::i2p);
config()->set(Config::socks5Port, Prestium::i2pPort());
config()->set(Config::hideUpdateNotifications, true);
if (conf()->get(Config::firstRun).toBool() && Prestium::detect()) {
conf()->set(Config::proxy, Config::Proxy::i2p);
conf()->set(Config::socks5Port, Prestium::i2pPort());
conf()->set(Config::hideUpdateNotifications, true);
}
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
@ -239,10 +239,11 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
pool->setMaxThreadCount(8);
}
WindowManager windowManager(QCoreApplication::instance(), &filter);
auto wm = windowManager();
wm->setEventFilter(&filter);
QObject::connect(&app, &SingleApplication::instanceStarted, [&windowManager]() {
windowManager.raise();
QObject::connect(&app, &SingleApplication::instanceStarted, [&wm]() {
wm->raise();
});
return QApplication::exec();

View file

@ -60,7 +60,7 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const {
else if (role == Qt::DecorationRole) {
switch (index.column()) {
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();

View file

@ -148,8 +148,8 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
if (role == Qt::UserRole) {
return row;
}
return tInfo.timestamp().toString(QString("%1 %2 ").arg(config()->get(Config::dateFormat).toString(),
config()->get(Config::timeFormat).toString()));
return tInfo.timestamp().toString(QString("%1 %2 ").arg(conf()->get(Config::dateFormat).toString(),
conf()->get(Config::timeFormat).toString()));
}
case Column::Description:
return tInfo.description();
@ -174,7 +174,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
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") {
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 {
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 {

View file

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

View file

@ -70,7 +70,7 @@ void BountiesWidget::showContextMenu(const QPoint &pos) {
}
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);
}

View file

@ -107,11 +107,11 @@ QString LocalMoneroApi::getBuySellUrl(bool buy, const QString &currencyCode, con
}
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";
}
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";
}

View file

@ -41,7 +41,7 @@ void LocalMoneroInfoDialog::setLabelText(QLabel *label, LocalMoneroModel::Column
void LocalMoneroInfoDialog::onGoToOffer() {
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());
Utils::externalLinkWarning(this, offerUrl);
}

View file

@ -22,7 +22,7 @@ LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, Wallet *wallet)
QPixmap logo(":/assets/images/localMonero_logo.png");
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_api = new LocalMoneroApi(this, m_network);
@ -74,7 +74,7 @@ void LocalMoneroWidget::onSearchClicked() {
}
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);
}
@ -82,7 +82,7 @@ void LocalMoneroWidget::onApiResponse(const LocalMoneroApi::LocalMoneroResponse
ui->btn_search->setEnabled(true);
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;
}
@ -162,7 +162,7 @@ void LocalMoneroWidget::openOfferUrl() {
}
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());

View file

@ -71,7 +71,7 @@ void RedditWidget::showContextMenu(const QPoint &pos) {
}
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);
}

View file

@ -23,6 +23,8 @@ RevuoWidget::RevuoWidget(QWidget *parent)
m_contextMenu->addAction("Open link", this, &RevuoWidget::onOpenLink);
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::customContextMenuRequested, this, &RevuoWidget::showContextMenu);
}

View file

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

View file

@ -11,6 +11,8 @@
#include <QImageCapture>
#include <QVideoFrame>
#include "Utils.h"
QrCodeScanDialog::QrCodeScanDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::QrCodeScanDialog)
@ -85,7 +87,7 @@ void QrCodeScanDialog::onDecoded(int type, const QString &data) {
void QrCodeScanDialog::displayCameraError()
{
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)
{
if (config()->get(Config::proxy).toInt() == Config::Proxy::None) {
if (conf()->get(Config::proxy).toInt() == Config::Proxy::None) {
return getNetworkClearnet();
}

View file

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

View file

@ -46,7 +46,7 @@ void TorManager::init() {
m_started = false;
}
featherTorPort = config()->get(Config::torManagedPort).toString().toUShort();
featherTorPort = conf()->get(Config::torManagedPort).toString().toUShort();
}
void TorManager::stop() {
@ -118,13 +118,13 @@ void TorManager::checkConnection() {
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);
}
else if (m_localTor) {
QString host = config()->get(Config::socks5Host).toString();
quint16 port = config()->get(Config::socks5Port).toString().toUShort();
QString host = conf()->get(Config::socks5Host).toString();
quint16 port = conf()->get(Config::socks5Port).toString().toUShort();
this->setConnectionState(Utils::portOpen(host, port));
}
@ -237,8 +237,8 @@ bool TorManager::isStarted() {
}
bool TorManager::shouldStartTorDaemon() {
QString torHost = config()->get(Config::socks5Host).toString();
quint16 torPort = config()->get(Config::socks5Port).toString().toUShort();
QString torHost = conf()->get(Config::socks5Host).toString();
quint16 torPort = conf()->get(Config::socks5Port).toString().toUShort();
QString torHostPort = QString("%1:%2").arg(torHost, QString::number(torPort));
// Don't start a Tor daemon if Feather is run with Torsocks
@ -258,12 +258,12 @@ bool TorManager::shouldStartTorDaemon() {
#endif
// 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;
}
// 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;
}

View file

@ -16,6 +16,7 @@
#include "utils/config.h"
#include "utils/os/tails.h"
#include "utils/os/whonix.h"
#include "WindowManager.h"
namespace Utils {
bool fileExists(const QString &path) {
@ -46,6 +47,17 @@ QByteArray fileOpenQRC(const QString &path) {
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) {
QFile file(path);
if (file.open(QIODevice::WriteOnly)) {
@ -292,7 +304,7 @@ bool xdgDesktopEntryRegister() {
}
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;
}
@ -469,16 +481,15 @@ QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettyp
}
void externalLinkWarning(QWidget *parent, const QString &url){
if (!config()->get(Config::warnOnExternalLink).toBool()) {
if (!conf()->get(Config::warnOnExternalLink).toBool()) {
QDesktopServices::openUrl(QUrl(url));
return;
}
QString body = QString("You are about to open the following link:\n\n%1").arg(url);
QMessageBox linkWarning(parent);
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);
linkWarning.addButton(QMessageBox::Cancel);
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\.))");
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 <QStandardItemModel>
#include <QTextCharFormat>
#include <QMessageBox>
#include "libwalletqt/Wallet.h"
#include "networktype.h"
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);
QByteArray fileOpen(const QString &path);
QByteArray fileOpenQRC(const QString &path);
QString loadQrc(const QString &qrc);
bool fileWrite(const QString &path, const QString &data);
bool pixmapWrite(const QString &path, const QPixmap &pixmap);
QStringList fileFind(const QRegularExpression &pattern, const QString &baseDir, int level, int depth, int maxPerDir);
@ -74,6 +99,14 @@ namespace Utils
QString QtEnumToString (QEnum 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

View file

@ -45,11 +45,11 @@ void WebsocketClient::start() {
return;
}
if (config()->get(Config::offlineMode).toBool()) {
if (conf()->get(Config::offlineMode).toBool()) {
return;
}
if (config()->get(Config::disableWebsocket).toBool()) {
if (conf()->get(Config::disableWebsocket).toBool()) {
return;
}
@ -107,11 +107,11 @@ void WebsocketClient::nextWebsocketUrl() {
}
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
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;
}
else {

View file

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

View file

@ -32,7 +32,7 @@ bool NodeList::addNode(const QString &node, NetworkType::Type networkType, NodeL
netTypeObj[sourceStr] = sourceArray;
obj[networkTypeStr] = netTypeObj;
config()->set(Config::nodes, obj);
conf()->set(Config::nodes, obj);
return true;
}
@ -49,7 +49,7 @@ void NodeList::setNodes(const QStringList &nodes, NetworkType::Type networkType,
netTypeObj[sourceStr] = sourceArray;
obj[networkTypeStr] = netTypeObj;
config()->set(Config::nodes, obj);
conf()->set(Config::nodes, obj);
}
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 obj = config()->get(Config::nodes).toJsonObject();
QJsonObject obj = conf()->get(Config::nodes).toJsonObject();
// Load old config format
if (obj.isEmpty()) {
auto jsonData = config()->get(Config::nodes).toByteArray();
auto jsonData = conf()->get(Config::nodes).toByteArray();
if (Utils::validateJSON(jsonData)) {
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
obj = doc.object();
@ -208,11 +208,11 @@ void Nodes::connectToNode(const FeatherNode &node) {
return;
}
if (config()->get(Config::offlineMode).toBool()) {
if (conf()->get(Config::offlineMode).toBool()) {
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()) {
// We only want to connect to .onion nodes, but local nodes get an exception.
return;
@ -230,11 +230,11 @@ void Nodes::connectToNode(const FeatherNode &node) {
QString proxyAddress;
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));
} else {
proxyAddress = QString("%1:%2").arg(config()->get(Config::socks5Host).toString(),
config()->get(Config::socks5Port).toString());
proxyAddress = QString("%1:%2").arg(conf()->get(Config::socks5Host).toString(),
conf()->get(Config::socks5Port).toString());
}
}
@ -400,7 +400,7 @@ void Nodes::setCustomNodes(const QList<FeatherNode> &nodes) {
}
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)
if (m_connection.isLocal())
return;
@ -414,15 +414,15 @@ void Nodes::onWalletRefreshed() {
}
bool Nodes::useOnionNodes() {
if (config()->get(Config::proxy) != Config::Proxy::Tor) {
if (conf()->get(Config::proxy) != Config::Proxy::Tor) {
return false;
}
if (config()->get(Config::torOnlyAllowOnion).toBool()) {
if (conf()->get(Config::torOnlyAllowOnion).toBool()) {
return true;
}
auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt();
auto privacyLevel = conf()->get(Config::torPrivacyLevel).toInt();
if (privacyLevel == Config::allTor) {
return true;
}
@ -433,7 +433,7 @@ bool Nodes::useOnionNodes() {
}
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];
if (m_wallet && m_wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) {
@ -446,7 +446,7 @@ bool Nodes::useOnionNodes() {
}
bool Nodes::useI2PNodes() {
if (config()->get(Config::proxy) == Config::Proxy::i2p) {
if (conf()->get(Config::proxy) == Config::Proxy::i2p) {
return true;
}
@ -464,7 +464,7 @@ bool Nodes::useSocks5Proxy(const FeatherNode &node) {
return false;
}
if (config()->get(Config::proxy).toInt() == Config::Proxy::None) {
if (conf()->get(Config::proxy).toInt() == Config::Proxy::None) {
return false;
}
@ -477,12 +477,12 @@ bool Nodes::useSocks5Proxy(const FeatherNode &node) {
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.
return this->useOnionNodes();
}
if (config()->get(Config::proxy).toInt() != Config::Proxy::None) {
if (conf()->get(Config::proxy).toInt() != Config::Proxy::None) {
return true;
}
}
@ -556,7 +556,7 @@ FeatherNode Nodes::connection() {
}
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) {

View file

@ -173,10 +173,10 @@ QString Updater::getPlatformTag() {
}
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";
}
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";
}
else {

View file

@ -18,7 +18,7 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
{
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){
this->onProxySettingsChanged();
ui->frame_proxy->setVisible(index != Config::Proxy::None);
@ -27,19 +27,19 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
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_tor->setVisible(proxy == Config::Proxy::Tor);
ui->groupBox_proxySettings->setTitle(QString("%1 settings").arg(ui->comboBox_proxy->currentText()));
// [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);
// [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]")};
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);
// [Tor settings]
@ -49,7 +49,7 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
ui->checkBox_torManaged->setEnabled(false);
ui->checkBox_torManaged->setToolTip("Feather was bundled without Tor");
#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){
this->updatePort();
this->onProxySettingsChanged();
@ -60,15 +60,15 @@ NetworkProxyWidget::NetworkProxyWidget(QWidget *parent)
#endif
// [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);
// [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);
// [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)
ui->frame_torShowLogs->setVisible(false);
#endif
@ -110,12 +110,12 @@ void NetworkProxyWidget::updatePort() {
}
void NetworkProxyWidget::setProxySettings() {
config()->set(Config::proxy, ui->comboBox_proxy->currentIndex());
config()->set(Config::socks5Host, ui->line_host->text());
config()->set(Config::socks5Port, ui->line_port->text());
config()->set(Config::useLocalTor, !ui->checkBox_torManaged->isChecked());
config()->set(Config::torOnlyAllowOnion, ui->checkBox_torOnlyAllowOnion->isChecked());
config()->set(Config::torPrivacyLevel, ui->comboBox_torNodeTraffic->currentIndex());
conf()->set(Config::proxy, ui->comboBox_proxy->currentIndex());
conf()->set(Config::socks5Host, ui->line_host->text());
conf()->set(Config::socks5Port, ui->line_port->text());
conf()->set(Config::useLocalTor, !ui->checkBox_torManaged->isChecked());
conf()->set(Config::torOnlyAllowOnion, ui->checkBox_torOnlyAllowOnion->isChecked());
conf()->set(Config::torPrivacyLevel, ui->comboBox_torNodeTraffic->currentIndex());
m_proxySettingsChanged = false;
}

View file

@ -25,7 +25,7 @@ NodeWidget::NodeWidget(QWidget *parent)
bool custom = (id == 0);
ui->stackedWidget->setCurrentIndex(custom);
ui->frame_addCustomNodes->setVisible(custom);
config()->set(Config::nodeSource, custom);
conf()->set(Config::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_custom, &QTreeView::doubleClicked, this, &NodeWidget::onContextConnect);
int index = config()->get(Config::nodeSource).toInt();
ui->stackedWidget->setCurrentIndex(config()->get(Config::nodeSource).toInt());
int index = conf()->get(Config::nodeSource).toInt();
ui->stackedWidget->setCurrentIndex(conf()->get(Config::nodeSource).toInt());
ui->frame_addCustomNodes->setVisible(index);
this->onWebsocketStatusChanged();
@ -71,7 +71,7 @@ void NodeWidget::onShowCustomContextMenu(const QPoint &pos) {
}
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);
}

View file

@ -72,7 +72,7 @@ BalanceTickerWidget::BalanceTickerWidget(QWidget *parent, Wallet *wallet, bool t
void BalanceTickerWidget::updateDisplay() {
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);
if (balanceFiatAmount < 0)
return;
@ -91,7 +91,7 @@ PriceTickerWidget::PriceTickerWidget(QWidget *parent, Wallet *wallet, QString sy
}
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);
if (price < 0)
return;

View file

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

View file

@ -7,6 +7,8 @@
#include <QFileDialog>
#include "config-feather.h"
PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent)
: QWizardPage(parent)
, ui(new Ui::PageMenu)
@ -16,9 +18,9 @@ PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget
ui->setupUi(this);
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() {

View file

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

View file

@ -87,36 +87,10 @@
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_openSettings">
<property name="text">
<string>Settings</string>
</property>
</widget>
</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">
<widget class="QLabel" name="label_version">
<property name="enabled">
<bool>false</bool>
</property>
@ -125,18 +99,6 @@
</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>
</item>
</layout>

View file

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

View file

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

View file

@ -5,7 +5,6 @@
#include "ui_PageOpenWallet.h"
#include <QFileDialog>
#include <QMessageBox>
#include "constants.h"
#include "WalletWizard.h"
@ -40,7 +39,7 @@ PageOpenWallet::PageOpenWallet(WalletKeysFilesModel *wallets, QWidget *parent)
connect(ui->walletTable, &QTreeView::doubleClicked, this, &PageOpenWallet::nextPage);
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)");
if (m_walletFile.isEmpty())
return;
@ -86,19 +85,19 @@ void PageOpenWallet::nextPage() {
bool PageOpenWallet::validatePage() {
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;
}
QFileInfo infoPath(m_walletFile);
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;
}
// Clear autoOpen if openOnStartup is not checked
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);
return true;

View file

@ -8,7 +8,6 @@
#include "utils/Utils.h"
#include <QFileDialog>
#include <QMessageBox>
PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent)
: QWizardPage(parent)
@ -22,7 +21,7 @@ PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent)
ui->lockIcon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
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);
if (walletDir.isEmpty()) {
return;
@ -39,7 +38,7 @@ PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent)
void PageWalletFile::initializePage() {
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->check_defaultWalletDirectory->setVisible(false);
ui->check_defaultWalletDirectory->setChecked(false);
@ -91,9 +90,9 @@ bool PageWalletFile::validatePage() {
m_fields->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()) {
config()->set(Config::walletDirectory, walletDir);
conf()->set(Config::walletDirectory, walletDir);
}
return true;

View file

@ -9,7 +9,6 @@
#include <QDialogButtonBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QMessageBox>
#include <monero_seed/wordlist.hpp> // tevador 14 word
#include "utils/Seed.h"
@ -152,17 +151,17 @@ bool PageWalletRestoreSeed::validatePage() {
Seed _seed = Seed(m_fields->seedType, seedSplit, constants::networkType);
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;
}
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);
return false;
}
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;

View file

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

View file

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

View file

@ -122,7 +122,7 @@
<item>
<widget class="QLabel" name="label_14">
<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 name="wordWrap">
<bool>true</bool>
@ -222,7 +222,7 @@
<item>
<widget class="QFrame" name="frame_seedDisplay">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>

View file

@ -19,6 +19,7 @@
#include "PageNetworkProxy.h"
#include "PageNetworkWebsocket.h"
#include "constants.h"
#include "WindowManager.h"
#include <QLineEdit>
#include <QVBoxLayout>
@ -64,18 +65,42 @@ WalletWizard::WalletWizard(QWidget *parent)
setPixmap(QWizard::WatermarkPixmap, QPixmap(":/assets/images/banners/3.png"));
setWizardStyle(WizardStyle::ModernStyle);
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](){
emit initialNetworkConfigured();
});
connect(menuPage, &PageMenu::showSettings, this, &WalletWizard::showSettings);
connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet);
connect(openWalletPage, &PageOpenWallet::openWallet, [=](const QString &path){
emit openWallet(path, "");
});
connect(this, &QWizard::helpRequested, this, &WalletWizard::showHelp);
}
void WalletWizard::resetFields() {
@ -134,3 +159,38 @@ void WalletWizard::onCreateWallet() {
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:
void onCreateWallet();
QString helpPage();
void showHelp();
private:
WalletKeysFilesModel *m_walletKeysFilesModel;