diff --git a/BUILDING.md b/BUILDING.md index 68e130d7..8959837e 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -22,7 +22,7 @@ git clone --branch master --recursive https://git.featherwallet.org/feather/feat cd feather ``` -Replace `master` with the desired version tag (e.g. `beta-6`) to build the release binary. +Replace `master` with the desired version tag (e.g. `beta-7`) to build the release binary. #### 2. Base image @@ -49,9 +49,11 @@ The resulting binary can be found in `build/bin/feather`. First create the standalone binary using the Docker command in the previous step. ```bash -docker run --rm -it -v $PWD:/feather -w /feather feather:linux contrib/build-appimage.sh +docker run --rm -it -v $PWD:/feather -w /feather/build feather:linux ../contrib/build-appimage.sh ``` +The resulting AppImage will be located in `./build`. + ### Windows (reproducible) #### 1. Clone @@ -61,7 +63,7 @@ git clone --branch master --recursive https://git.featherwallet.org/feather/feat cd feather ``` -Replace `master` with the desired version tag (e.g. `beta-4`) to build the release binary. +Replace `master` with the desired version tag (e.g. `beta-7`) to build the release binary. #### 2. Base image diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a77d0d8..d2da3361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,17 +7,19 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) set(VERSION_MAJOR "0") set(VERSION_MINOR "1") set(VERSION_REVISION "0") -set(VERSION "beta-6") +set(VERSION "beta-7") + +option(STATIC "Link libraries statically, requires static Qt") option(FETCH_DEPS "Download dependencies if they are not found" ON) + option(LOCALMONERO "Include LocalMonero module" ON) option(XMRIG "Include XMRig module" ON) option(TOR_BIN "Path to Tor binary to embed inside Feather" OFF) option(CHECK_UPDATES "Enable checking for application updates" OFF) - -option(STATIC "Link libraries statically, requires static Qt") option(USE_DEVICE_TREZOR "Trezor support compilation" OFF) option(DONATE_BEG "Prompt donation window every once in a while" ON) + list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake") include(CheckCCompilerFlag) include(CheckCXXCompilerFlag) diff --git a/Dockerfile b/Dockerfile index 52f8bacb..d7b0eee8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,7 +69,7 @@ RUN git clone -b tor-0.4.5.7 --depth 1 https://git.torproject.org/tor.git && \ FROM ubuntu:16.04 ARG THREADS=1 -ARG QT_VERSION=5.15.2 +ARG QT_VERSION=v5.15.2 ENV CFLAGS="-fPIC" ENV CPPFLAGS="-fPIC" @@ -88,8 +88,6 @@ RUN apt-get update && \ # build tools software-properties-common automake pkg-config python \ libtool-bin wget zip \ -# dependencies - libusb-1.0-0-dev \ # Qt libgl1-mesa-dev libglib2.0-dev mesa-common-dev \ # libusb @@ -112,6 +110,11 @@ RUN add-apt-repository ppa:git-core/ppa && \ apt-get install -y git && \ rm -rf /var/lib/apt/lists/* +RUN wget http://archive.ubuntu.com/ubuntu/pool/main/s/systemd/libudev-dev_229-4ubuntu21.31_amd64.deb && \ + echo "f1f72bd814d1e8ca2828ac004924fba0b54a3299e60f089c6d818262b11750f7 libudev-dev_229-4ubuntu21.31_amd64.deb" | sha256sum -c && \ + apt install ./libudev-dev_229-4ubuntu21.31_amd64.deb && \ + rm libudev-dev_229-4ubuntu21.31_amd64.deb + RUN mkdir appimagetool && \ cd appimagetool && \ wget https://github.com/AppImage/AppImageKit/releases/download/12/appimagetool-x86_64.AppImage && \ diff --git a/contrib/build-appimage.sh b/contrib/build-appimage.sh index c68b5039..3ba81573 100755 --- a/contrib/build-appimage.sh +++ b/contrib/build-appimage.sh @@ -4,13 +4,14 @@ set -e unset SOURCE_DATE_EPOCH APPDIR="$PWD/feather.AppDir" +rm -rf $APPDIR mkdir -p "$APPDIR" mkdir -p "$APPDIR/usr/share/applications/" mkdir -p "$APPDIR/usr/bin" -cp "$PWD/src/assets/feather.desktop" "$APPDIR/usr/share/applications/feather.desktop" -cp "$PWD/src/assets/images/appicons/64x64.png" "$APPDIR/feather.png" -cp "$PWD/build/bin/feather" "$APPDIR/usr/bin/feather" +cp "$PWD/../src/assets/feather.desktop" "$APPDIR/usr/share/applications/feather.desktop" +cp "$PWD/../src/assets/images/appicons/64x64.png" "$APPDIR/feather.png" +cp "$PWD/bin/feather" "$APPDIR/usr/bin/feather" LD_LIBRARY_PATH=/usr/local/lib /linuxdeployqt/squashfs-root/AppRun feather.AppDir/usr/share/applications/feather.desktop -bundle-non-qt-libs diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 94b89688..bdac1b53 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,8 @@ file(GLOB SOURCE_FILES "api/*.cpp" "utils/*.h" "utils/*.cpp" + "utils/os/*.h" + "utils/os/*.cpp" "libwalletqt/*.h" "libwalletqt/*.cpp" "daemon/*.h" diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp new file mode 100644 index 00000000..107f188c --- /dev/null +++ b/src/WindowManager.cpp @@ -0,0 +1,451 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "WindowManager.h" +#include "constants.h" +#include "dialog/passworddialog.h" +#include "dialog/splashdialog.h" +#include "utils/WebsocketNotifier.h" +#include "utils/os/tails.h" +#include "utils/Icons.h" +#include "utils/NetworkManager.h" +#include "utils/TorManager.h" + +#include + +WindowManager::WindowManager() { + m_walletManager = WalletManager::instance(); + m_splashDialog = new SplashDialog; + + connect(m_walletManager, &WalletManager::walletOpened, this, &WindowManager::onWalletOpened); + connect(m_walletManager, &WalletManager::walletCreated, this, &WindowManager::onWalletCreated); + connect(m_walletManager, &WalletManager::deviceButtonRequest, this, &WindowManager::onDeviceButtonRequest); + connect(m_walletManager, &WalletManager::deviceError, this, &WindowManager::onDeviceError); + + connect(qApp, &QGuiApplication::lastWindowClosed, this, &WindowManager::quitAfterLastWindow); + + m_tray = new QSystemTrayIcon(icons()->icon("appicons/64x64.png")); + m_tray->setToolTip("Feather Wallet"); + this->buildTrayMenu(); + m_tray->show(); + + this->initSkins(); + + if (!config()->get(Config::firstRun).toBool()) { + this->onInitialNetworkConfigured(); + } + + if (!this->autoOpenWallet()) { + this->initWizard(); + } +} + +// ######################## APPLICATION LIFECYCLE ######################## + +void WindowManager::quitAfterLastWindow() { + if (m_windows.length() > 0 || m_openingWallet) { + return; + } + + qDebug() << "No wizards in progress and no wallets open, quitting application."; + this->close(); +} + +void WindowManager::close() { + qDebug() << Q_FUNC_INFO; + for (const auto &window: m_windows) { + window->close(); + } + + torManager()->stop(); + m_tray->hide(); + + QApplication::quit(); +} + +void WindowManager::closeWindow(MainWindow *window) { + m_windows.removeOne(window); +} + +void WindowManager::restartApplication(const QString &binaryFilename) { + QProcess::startDetached(binaryFilename, qApp->arguments()); + this->close(); +} + +// ######################## WALLET OPEN ######################## + +void WindowManager::tryOpenWallet(const QString &path, const QString &password) { + // Path : path to .keys file + + QString absolutePath = path; + if (absolutePath.startsWith("~")) { + absolutePath.replace(0, 1, QDir::homePath()); + } + + // If the wallet is already open, just bring window to front + for (const auto &window : m_windows) { + if (absolutePath == window->walletKeysPath() || absolutePath == window->walletCachePath()) { + window->bringToFront(); + return; + } + } + + if (!Utils::fileExists(path)) { + this->handleWalletError(QString("Wallet not found: %1").arg(path)); + return; + } + + m_openingWallet = true; + m_walletManager->openWalletAsync(path, password, constants::networkType, 1); +} + +void WindowManager::onWalletOpened(Wallet *wallet) { + if (wallet->status() != Wallet::Status_Ok) { + QString errMsg = wallet->errorString(); + if (wallet->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, wallet->cachePath()); + } + else if (errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) { + qCritical() << errMsg; + WalletManager::clearWalletCache(wallet->cachePath()); // TODO: check this + errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg); + this->handleWalletError(errMsg); + } else { + this->handleWalletError(errMsg); + } + return; + } + + // Create new mainwindow with wallet + + m_splashDialog->hide(); + m_openWalletTriedOnce = false; + auto *window = new MainWindow(this, wallet); + m_windows.append(window); + this->buildTrayMenu(); + m_openingWallet = false; +} + +void WindowManager::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) { + QFileInfo fileInfo(path); + + PasswordDialog dialog{fileInfo.fileName(), invalidPassword}; + switch (dialog.exec()) { + case QDialog::Rejected: + { + m_openWalletTriedOnce = false; + this->showWizard(WalletWizard::Page_OpenWallet); + return; + } + } + + this->tryOpenWallet(path, dialog.password); +} + +bool WindowManager::autoOpenWallet() { + QString autoPath = config()->get(Config::autoOpenWalletPath).toString(); + if (!autoPath.isEmpty() && autoPath.startsWith(QString::number(constants::networkType))) { + autoPath.remove(0, 1); + } + if (!autoPath.isEmpty() && Utils::fileExists(autoPath)) { + this->tryOpenWallet(autoPath, ""); // TODO: get password from --password + return true; + } + return false; +} + +// ######################## WALLET CREATION ######################## + +void WindowManager::tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password, + const QString &seedOffset) { + if(Utils::fileExists(path)) { + auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); + this->handleWalletError(err); + return; + } + + if (seed.mnemonic.isEmpty()) { + this->handleWalletError("Mnemonic seed error. Failed to write wallet."); + return; + } + + Wallet *wallet = nullptr; + if (seed.seedType == SeedType::TEVADOR) { + wallet = m_walletManager->createDeterministicWalletFromSpendKey(path, password, seed.language, constants::networkType, seed.spendKey, seed.restoreHeight, constants::kdfRounds, seedOffset); + wallet->setCacheAttribute("feather.seed", seed.mnemonic.join(" ")); + wallet->setCacheAttribute("feather.seedoffset", seedOffset); + } + if (seed.seedType == SeedType::MONERO) { + wallet = m_walletManager->recoveryWallet(path, password, seed.mnemonic.join(" "), seedOffset, constants::networkType, seed.restoreHeight, constants::kdfRounds); + } + + if (!wallet) { + this->handleWalletError("Failed to write wallet"); + return; + } + + this->onWalletOpened(wallet); +} + +void WindowManager::tryCreateWalletFromDevice(const QString &path, const QString &password, int restoreHeight) +{ + if (Utils::fileExists(path)) { + auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); + this->handleWalletError(err); + return; + } + + m_openingWallet = true; + m_walletManager->createWalletFromDeviceAsync(path, password, constants::networkType, "Ledger", restoreHeight); +} + +void WindowManager::tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address, + const QString &viewkey, const QString &spendkey, quint64 restoreHeight) { + if (Utils::fileExists(path)) { + auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); + this->handleWalletError(err); + return; + } + + if (!WalletManager::addressValid(address, constants::networkType)) { + auto err = QString("Failed to create wallet. Invalid address provided.").arg(path); + this->handleWalletError(err); + return; + } + + if (!WalletManager::keyValid(viewkey, address, true, constants::networkType)) { + auto err = QString("Failed to create wallet. Invalid viewkey provided.").arg(path); + this->handleWalletError(err); + return; + } + + 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); + return; + } + + Wallet *wallet = m_walletManager->createWalletFromKeys(path, password, constants::seedLanguage, constants::networkType, address, viewkey, spendkey, restoreHeight); + m_openingWallet = true; + m_walletManager->walletOpened(wallet); +} + +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(); + this->showWizard(WalletWizard::Page_Menu); + return; + } + + this->onWalletOpened(wallet); +} + +// ######################## ERROR HANDLING ######################## + +void WindowManager::handleWalletError(const QString &message) { + qCritical() << message; + this->displayWalletErrorMessage(message); + this->initWizard(); +} + +void WindowManager::displayWalletErrorMessage(const QString &message) { + QString errMsg = message; + if (message.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 (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"; +#endif + } + + if (message.contains("SW_CLIENT_NOT_SUPPORTED")) { + errMsg += "\n\nIncompatible version: you may need to upgrade the Monero app on the Ledger device to the latest version."; + } + else if (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); + msgBox.exec(); +} + +// ######################## DEVICE ######################## + +void WindowManager::onDeviceButtonRequest(quint64 code) { + m_splashDialog->setMessage("Action required on device: Export the view key to open the wallet."); + m_splashDialog->setIcon(QPixmap(":/assets/images/key.png")); + m_splashDialog->show(); + m_splashDialog->setEnabled(true); +} + +void WindowManager::onDeviceError(const QString &errorMessage) { + // TODO: when does this get called? + qCritical() << Q_FUNC_INFO << errorMessage; +} + +// ######################## TRAY ######################## + +void WindowManager::buildTrayMenu() { + QMenu *menu; + if (!m_tray->contextMenu()) { + menu = new QMenu(); + m_tray->setContextMenu(menu); + } else { + menu = m_tray->contextMenu(); + menu->clear(); + } + + for (const auto &window : m_windows) { + QString name = window->walletName(); + QMenu *submenu = menu->addMenu(name); + submenu->addAction("Show/Hide", window, &MainWindow::showOrHide); + submenu->addAction("Close", window, &MainWindow::close); + } + menu->addSeparator(); + menu->addAction("Exit Feather", this, &WindowManager::close); +} + +// ######################## NETWORKING ######################## + +void WindowManager::onInitialNetworkConfigured() { + this->initTor(); + this->initWS(); +} + +void WindowManager::initTor() { + torManager()->init(); + torManager()->start(); + + connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect); + + this->onTorSettingsChanged(); +} + +void WindowManager::onTorSettingsChanged() { + if (Utils::isTorsocks()) { + return; + } + + // use local tor -> bundled tor + QString host = config()->get(Config::socks5Host).toString(); + quint16 port = config()->get(Config::socks5Port).toString().toUShort(); + if (!torManager()->isLocalTor()) { + host = torManager()->featherTorHost; + port = torManager()->featherTorPort; + } + + QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port}; + getNetworkTor()->setProxy(proxy); + websocketNotifier()->websocketClient.webSocket.setProxy(proxy); + + emit torSettingsChanged(); +} + +void WindowManager::initWS() { + websocketNotifier()->websocketClient.start(); +} + +// ######################## WIZARD ######################## + +WalletWizard* WindowManager::createWizard(WalletWizard::Page startPage) const { + auto *wizard = new WalletWizard; + connect(wizard, &WalletWizard::initialNetworkConfigured, this, &WindowManager::onInitialNetworkConfigured); + connect(wizard, &WalletWizard::skinChanged, this, &WindowManager::changeSkin); + connect(wizard, &WalletWizard::openWallet, this, &WindowManager::tryOpenWallet); + connect(wizard, &WalletWizard::createWallet, this, &WindowManager::tryCreateWallet); + connect(wizard, &WalletWizard::createWalletFromKeys, this, &WindowManager::tryCreateWalletFromKeys); + connect(wizard, &WalletWizard::createWalletFromDevice, this, &WindowManager::tryCreateWalletFromDevice); + return wizard; +} + +void WindowManager::initWizard() { + auto startPage = WalletWizard::Page_Menu; + if (config()->get(Config::firstRun).toBool() && !(TailsOS::detect() || WhonixOS::detect())) { + startPage = WalletWizard::Page_Network; + } + + this->showWizard(startPage); +} + +void WindowManager::showWizard(WalletWizard::Page startPage) { + if (!m_wizard) { + m_wizard = this->createWizard(startPage); + } + + m_wizard->setStartId(startPage); + m_wizard->restart(); + m_wizard->setEnabled(true); + m_wizard->show(); +} + +void WindowManager::wizardOpenWallet() { + this->showWizard(WalletWizard::Page_OpenWallet); +} + +// ######################## SKINS ######################## + +void WindowManager::initSkins() { + m_skins.insert("Native", ""); + + QString qdarkstyle = this->loadStylesheet(":qdarkstyle/style.qss"); + if (!qdarkstyle.isEmpty()) + m_skins.insert("QDarkStyle", qdarkstyle); + + QString breeze_dark = this->loadStylesheet(":/dark.qss"); + if (!breeze_dark.isEmpty()) + m_skins.insert("Breeze/Dark", breeze_dark); + + QString breeze_light = this->loadStylesheet(":/light.qss"); + if (!breeze_light.isEmpty()) + m_skins.insert("Breeze/Light", breeze_light); + + QString skin = config()->get(Config::skin).toString(); + qApp->setStyleSheet(m_skins[skin]); +} + +QString WindowManager::loadStylesheet(const QString &resource) { + QFile f(resource); + if (!f.exists()) { + printf("Unable to set stylesheet, file not found\n"); + f.close(); + return ""; + } + + f.open(QFile::ReadOnly | QFile::Text); + QTextStream ts(&f); + QString data = ts.readAll(); + f.close(); + + return data; +} + +void WindowManager::changeSkin(const QString &skinName) { + if (!m_skins.contains(skinName)) { + qWarning() << QString("No such skin %1").arg(skinName); + return; + } + + config()->set(Config::skin, skinName); + qApp->setStyleSheet(m_skins[skinName]); + qDebug() << QString("Skin changed to %1").arg(skinName); +} diff --git a/src/WindowManager.h b/src/WindowManager.h new file mode 100644 index 00000000..3d99c6d9 --- /dev/null +++ b/src/WindowManager.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_WINDOWMANAGER_H +#define FEATHER_WINDOWMANAGER_H + +#include +#include "libwalletqt/WalletManager.h" +#include "libwalletqt/Wallet.h" +#include "wizard/WalletWizard.h" +#include "dialog/torinfodialog.h" +#include "mainwindow.h" + +class MainWindow; +class WindowManager : public QObject { +Q_OBJECT + +public: + explicit WindowManager(); + + void wizardOpenWallet(); + void close(); + void closeWindow(MainWindow *window); + void showWizard(WalletWizard::Page startPage); + void changeSkin(const QString &skinName); + void restartApplication(const QString &binaryFilename); + +signals: + void torSettingsChanged(); + +public slots: + void onTorSettingsChanged(); + void tryOpenWallet(const QString &path, const QString &password); + +private slots: + void onWalletOpened(Wallet *wallet); + void onWalletCreated(Wallet *wallet); + void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path); + void onInitialNetworkConfigured(); + void onDeviceButtonRequest(quint64 code); + void onDeviceError(const QString &errorMessage); + +private: + void tryCreateWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset); + void tryCreateWalletFromDevice(const QString &path, const QString &password, int restoreHeight); + void tryCreateWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight); + + bool autoOpenWallet(); + + void initWizard(); + WalletWizard* createWizard(WalletWizard::Page startPage) const; + + void handleWalletError(const QString &message); + void displayWalletErrorMessage(const QString &message); + + void initTor(); + void initWS(); + void initSkins(); + QString loadStylesheet(const QString &resource); + void buildTrayMenu(); + + void quitAfterLastWindow(); + + QVector m_windows; + + WalletManager *m_walletManager; + WalletWizard *m_wizard = nullptr; + SplashDialog *m_splashDialog = nullptr; + + QSystemTrayIcon *m_tray; + + QMap m_skins; + + bool m_openWalletTriedOnce = false; + bool m_openingWallet = false; +}; + + +#endif //FEATHER_WINDOWMANAGER_H diff --git a/src/api/LocalMoneroApi.cpp b/src/api/LocalMoneroApi.cpp index a047a87f..1e494d89 100644 --- a/src/api/LocalMoneroApi.cpp +++ b/src/api/LocalMoneroApi.cpp @@ -60,21 +60,6 @@ void LocalMoneroApi::onResponse(QNetworkReply *reply, LocalMoneroApi::Endpoint e const QString err = reply->errorString(); QByteArray data = reply->readAll(); - - qDebug() << "Response"; - qDebug() << data; - for (const auto header : reply->rawHeaderList()) { - qDebug() << header << ": " << reply->rawHeader(header); - } - - qDebug() << reply->rawHeaderPairs(); - - qDebug() << "Request"; - for (const auto header : reply->request().rawHeaderList()) { - qDebug() << "header: " << header << ": " << reply->request().rawHeader(header); - } - qDebug() << reply->request().url(); - reply->deleteLater(); QJsonObject obj; diff --git a/src/api/LocalMoneroApi.h b/src/api/LocalMoneroApi.h index 14b6c82b..60145b89 100644 --- a/src/api/LocalMoneroApi.h +++ b/src/api/LocalMoneroApi.h @@ -45,8 +45,8 @@ private slots: private: QString getBuySellUrl(bool buy, const QString ¤cyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0); - QString m_baseUrl; UtilsNetworking *m_network; + QString m_baseUrl; }; diff --git a/src/appcontext.cpp b/src/appcontext.cpp index de5c2e16..96b45635 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -5,7 +5,7 @@ #include #include "appcontext.h" -#include "globals.h" +#include "constants.h" // libwalletqt #include "libwalletqt/TransactionHistory.h" @@ -17,44 +17,35 @@ #include "utils/WebsocketClient.h" #include "utils/WebsocketNotifier.h" -WalletKeysFilesModel *AppContext::wallets = nullptr; -QMap AppContext::txCache; +// This class serves as a business logic layer between MainWindow and libwalletqt. +// This way we don't clutter the GUI with wallet logic, +// and keep libwalletqt (mostly) clean of Feather specific implementation details -AppContext::AppContext(QCommandLineParser *cmdargs) { - this->cmdargs = cmdargs; +AppContext::AppContext(Wallet *wallet) + : wallet(wallet) + , nodes(new Nodes(this, this)) + , networkType(constants::networkType) + , m_rpc(new DaemonRpc{this, getNetworkTor(), ""}) +{ + connect(this->wallet.get(), &Wallet::moneySpent, this, &AppContext::onMoneySpent); + connect(this->wallet.get(), &Wallet::moneyReceived, this, &AppContext::onMoneyReceived); + connect(this->wallet.get(), &Wallet::unconfirmedMoneyReceived, this, &AppContext::onUnconfirmedMoneyReceived); + connect(this->wallet.get(), &Wallet::newBlock, this, &AppContext::onWalletNewBlock); + connect(this->wallet.get(), &Wallet::updated, this, &AppContext::onWalletUpdate); + connect(this->wallet.get(), &Wallet::refreshed, this, &AppContext::onWalletRefreshed); + connect(this->wallet.get(), &Wallet::transactionCommitted, this, &AppContext::onTransactionCommitted); + connect(this->wallet.get(), &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed); + connect(this->wallet.get(), &Wallet::transactionCreated, this, &AppContext::onTransactionCreated); + connect(this->wallet.get(), &Wallet::deviceError, this, &AppContext::onDeviceError); + connect(this->wallet.get(), &Wallet::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest); + connect(this->wallet.get(), &Wallet::connectionStatusChanged, [this]{ + this->nodes->autoConnect(); + }); + connect(this->wallet.get(), &Wallet::currentSubaddressAccountChanged, [this]{ + this->updateBalance(); + }); - this->isTails = TailsOS::detect(); - this->isWhonix = WhonixOS::detect(); - - // ----------------- Setup Paths ----------------- - - QString configDir = Config::defaultConfigDir().path(); - createConfigDirectory(configDir); - - QString walletDir = config()->get(Config::walletDirectory).toString(); - if (walletDir.isEmpty()) { - walletDir = Utils::defaultWalletDir(); - } - this->defaultWalletDir = walletDir; - if (!QDir().mkpath(defaultWalletDir)) - qCritical() << "Unable to create dir: " << defaultWalletDir; - - // ----------------- Network Type ----------------- - - if (this->cmdargs->isSet("stagenet")) { - this->networkType = NetworkType::STAGENET; - config()->set(Config::networkType, NetworkType::STAGENET); - } - else if (this->cmdargs->isSet("testnet")) { - this->networkType = NetworkType::TESTNET; - config()->set(Config::networkType, NetworkType::TESTNET); - } - else { - this->networkType = NetworkType::MAINNET; - config()->set(Config::networkType, NetworkType::MAINNET); - } - - this->nodes = new Nodes(this, this); + connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError); // Store the wallet every 2 minutes m_storeTimer.start(2 * 60 * 1000); @@ -62,104 +53,40 @@ AppContext::AppContext(QCommandLineParser *cmdargs) { this->storeWallet(); }); - this->walletManager = WalletManager::instance(); - QString logPath = QString("%1/daemon.log").arg(configDir); - Monero::Utils::onStartup(); - Monero::Wallet::init("", "feather", logPath.toStdString(), true); + this->updateBalance(); - bool logLevelFromEnv; - int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv); - if (this->cmdargs->isSet("quiet")) - this->walletManager->setLogLevel(-1); - else if (logLevelFromEnv && logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max) - Monero::WalletManagerFactory::setLogLevel(logLevel); + // force trigger preferredFiat signal for history model + this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString()); - connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError); - - // libwallet connects - connect(this->walletManager, &WalletManager::walletOpened, this, &AppContext::onWalletOpened); - connect(this->walletManager, &WalletManager::walletCreated, this, &AppContext::onWalletCreated); - connect(this->walletManager, &WalletManager::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest); - connect(this->walletManager, &WalletManager::deviceError, this, &AppContext::onDeviceError); - - // TODO: move me - connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this->nodes, &Nodes::onWSNodesReceived); - - m_rpc = new DaemonRpc{this, getNetworkTor(), ""}; + connect(this->wallet->history(), &TransactionHistory::txNoteChanged, [this]{ + this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); + }); } -void AppContext::initTor() { - if (this->cmdargs->isSet("tor-host")) - config()->set(Config::socks5Host, this->cmdargs->value("tor-host")); - if (this->cmdargs->isSet("tor-port")) - config()->set(Config::socks5Port, this->cmdargs->value("tor-port")); - if (this->cmdargs->isSet("use-local-tor")) - config()->set(Config::useLocalTor, true); - - torManager()->init(); - torManager()->start(); - - connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect); - - this->onTorSettingsChanged(); -} - -void AppContext::initWS() { - websocketNotifier()->websocketClient.start(); -} - -void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector &address) { - // tx cancelled by user - double amount = tx->amount() / globals::cdiv; - emit createTransactionCancelled(address, amount); - this->currentWallet->disposeTransaction(tx); -} - -void AppContext::onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) { - if(this->currentWallet == nullptr){ - qCritical() << "Cannot create transaction; no wallet loaded"; - return; - } - - if (churn) { - address = this->currentWallet->address(0, 0); // primary address - } - - qCritical() << "Creating transaction"; - this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority); - - emit initiateTransaction(); -} +// ################## Transaction creation ################## void AppContext::onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all) { - // tx creation this->tmpTxDescription = description; - if(this->currentWallet == nullptr) { - emit createTransactionError("Cannot create transaction; no wallet loaded"); - return; - } - if (!all && amount == 0) { emit createTransactionError("Cannot send nothing"); return; } - auto balance = this->currentWallet->balance(); - auto unlocked_balance = this->currentWallet->unlockedBalance(); - if(!all && amount > unlocked_balance) { + quint64 unlocked_balance = this->wallet->unlockedBalance(); + if (!all && amount > unlocked_balance) { emit createTransactionError("Not enough money to spend"); return; - } else if(unlocked_balance == 0) { + } else if (unlocked_balance == 0) { emit createTransactionError("No money to spend"); return; } - qDebug() << "creating tx"; + qInfo() << "Creating transaction"; if (all) - this->currentWallet->createTransactionAllAsync(address, "", globals::mixin, this->tx_priority); + this->wallet->createTransactionAllAsync(address, "", constants::mixin, this->tx_priority); else - this->currentWallet->createTransactionAsync(address, "", amount, globals::mixin, this->tx_priority); + this->wallet->createTransactionAsync(address, "", amount, constants::mixin, this->tx_priority); emit initiateTransaction(); } @@ -167,23 +94,29 @@ void AppContext::onCreateTransaction(const QString &address, quint64 amount, con void AppContext::onCreateTransactionMultiDest(const QVector &addresses, const QVector &amounts, const QString &description) { this->tmpTxDescription = description; - if (this->currentWallet == nullptr) { - emit createTransactionError("Cannot create transaction; no wallet loaded"); - return; - } - quint64 total_amount = 0; for (auto &amount : amounts) { total_amount += amount; } - auto unlocked_balance = this->currentWallet->unlockedBalance(); + auto unlocked_balance = this->wallet->unlockedBalance(); if (total_amount > unlocked_balance) { emit createTransactionError("Not enough money to spend"); } - qDebug() << "Creating tx"; - this->currentWallet->createTransactionMultiDestAsync(addresses, amounts, this->tx_priority); + qInfo() << "Creating transaction"; + this->wallet->createTransactionMultiDestAsync(addresses, amounts, this->tx_priority); + + emit initiateTransaction(); +} + +void AppContext::onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) { + if (churn) { + address = this->wallet->address(0, 0); // primary address + } + + qInfo() << "Creating transaction"; + this->wallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority); emit initiateTransaction(); } @@ -193,70 +126,10 @@ void AppContext::onCreateTransactionError(const QString &msg) { emit endTransaction(); } -void AppContext::closeWallet(bool emitClosedSignal, bool storeWallet) { - if (this->currentWallet == nullptr) - return; - - emit walletAboutToClose(); - - if (storeWallet) { - this->storeWallet(); - } - - this->currentWallet->disconnect(); - this->walletManager->closeWallet(); - this->currentWallet = nullptr; - - if (emitClosedSignal) - emit walletClosed(); -} - -void AppContext::onOpenWallet(const QString &path, const QString &password){ - if(this->currentWallet != nullptr){ - emit walletOpenedError("There is an active wallet opened."); - return; - } - - if(!Utils::fileExists(path)) { - emit walletOpenedError(QString("Wallet not found: %1").arg(path)); - return; - } - - if (password.isEmpty()) { - this->walletPassword = ""; - } - - config()->set(Config::firstRun, false); - - this->walletPath = path; - this->walletManager->openWalletAsync(path, password, this->networkType, 1); -} - -void AppContext::onWalletCreated(Wallet * wallet) { - // Currently only called when a wallet is created from device. - auto state = wallet->status(); - if (state != Wallet::Status_Ok) { - emit walletCreatedError(wallet->errorString()); - return; - } - - this->onWalletOpened(wallet); -} - -void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) { - if(this->currentWallet) { - auto *model = this->currentWallet->transactionHistoryModel(); - if(model != nullptr) { - model->preferredFiatSymbol = symbol; - } - } -} - -void AppContext::onAmountPrecisionChanged(int precision) { - if (!this->currentWallet) return; - auto *model = this->currentWallet->transactionHistoryModel(); - if (!model) return; - model->amountPrecision = precision; +void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector &address) { + // tx cancelled by user + emit createTransactionCancelled(address, tx->amount()); + this->wallet->disposeTransaction(tx); } void AppContext::commitTransaction(PendingTransaction *tx) { @@ -266,12 +139,12 @@ void AppContext::commitTransaction(PendingTransaction *tx) { this->onMultiBroadcast(tx); } - this->currentWallet->commitTransactionAsync(tx); + this->wallet->commitTransactionAsync(tx); } void AppContext::onMultiBroadcast(PendingTransaction *tx) { - int count = tx->txCount(); - for (int i = 0; i < count; i++) { + quint64 count = tx->txCount(); + for (quint64 i = 0; i < count; i++) { QString txData = tx->signedTxToHex(i); for (const auto& node: this->nodes->websocketNodes()) { @@ -285,6 +158,23 @@ void AppContext::onMultiBroadcast(PendingTransaction *tx) { } } +// ################## Models ################## + +void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) { + auto *model = this->wallet->transactionHistoryModel(); + if (model != nullptr) { + model->preferredFiatSymbol = symbol; + } +} + +void AppContext::onAmountPrecisionChanged(int precision) { + auto *model = this->wallet->transactionHistoryModel(); + if (!model) return; + model->amountPrecision = precision; +} + +// ################## Device ################## + void AppContext::onDeviceButtonRequest(quint64 code) { emit deviceButtonRequest(code); } @@ -294,206 +184,40 @@ void AppContext::onDeviceError(const QString &message) { emit deviceError(message); } +// ################## Misc ################## + void AppContext::onTorSettingsChanged() { - if (WhonixOS::detect() || Utils::isTorsocks()) { + if (Utils::isTorsocks()) { return; } - // use local tor -> bundled tor - QString host = config()->get(Config::socks5Host).toString(); - quint16 port = config()->get(Config::socks5Port).toString().toUShort(); - if (!torManager()->isLocalTor()) { - host = torManager()->featherTorHost; - port = torManager()->featherTorPort; - } - - QNetworkProxy proxy{QNetworkProxy::Socks5Proxy, host, port}; - getNetworkTor()->setProxy(proxy); - websocketNotifier()->websocketClient.webSocket.setProxy(proxy); - this->nodes->connectToNode(); auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt(); qDebug() << "Changed privacyLevel to " << privacyLevel; } -void AppContext::onInitialNetworkConfigured() { - this->initTor(); - this->initWS(); -} - -void AppContext::onWalletOpened(Wallet *wallet) { - auto state = wallet->status(); - if (state != Wallet::Status_Ok) { - auto errMsg = wallet->errorString(); - if (state == Wallet::Status_BadPassword) { - this->closeWallet(false); - // Don't show incorrect password when we try with empty password for the first time - bool showIncorrectPassword = m_openWalletTriedOnce; - m_openWalletTriedOnce = true; - emit walletOpenPasswordNeeded(showIncorrectPassword, wallet->path()); - } - else if (errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) { - qCritical() << errMsg; - this->walletManager->clearWalletCache(this->walletPath); - errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart Feather.").arg(errMsg); - this->closeWallet(false); - emit walletOpenedError(errMsg); - } else { - this->closeWallet(false); - emit walletOpenedError(errMsg); - } - - return; - } - - m_openWalletTriedOnce = false; - this->refreshed = false; - this->currentWallet = wallet; - this->walletPath = this->currentWallet->path() + ".keys"; - this->walletPassword = this->currentWallet->getPassword(); - config()->set(Config::walletPath, this->walletPath); - - connect(this->currentWallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent); - connect(this->currentWallet, &Wallet::moneyReceived, this, &AppContext::onMoneyReceived); - connect(this->currentWallet, &Wallet::unconfirmedMoneyReceived, this, &AppContext::onUnconfirmedMoneyReceived); - connect(this->currentWallet, &Wallet::newBlock, this, &AppContext::onWalletNewBlock); - connect(this->currentWallet, &Wallet::updated, this, &AppContext::onWalletUpdate); - connect(this->currentWallet, &Wallet::refreshed, this, &AppContext::onWalletRefreshed); - connect(this->currentWallet, &Wallet::transactionCommitted, this, &AppContext::onTransactionCommitted); - connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed); - connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated); - connect(this->currentWallet, &Wallet::deviceError, this, &AppContext::onDeviceError); - connect(this->currentWallet, &Wallet::deviceButtonRequest, this, &AppContext::onDeviceButtonRequest); - - emit walletOpened(); - - connect(this->currentWallet, &Wallet::connectionStatusChanged, [this]{ - this->nodes->autoConnect(); - }); - this->nodes->connectToNode(); - this->updateBalance(); - -#ifdef DONATE_BEG - this->donateBeg(); -#endif - - // force trigger preferredFiat signal for history model - this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString()); -} - -void AppContext::createConfigDirectory(const QString &dir) { - QString config_dir_tor = QString("%1/%2").arg(dir).arg("tor"); - QString config_dir_tordata = QString("%1/%2").arg(dir).arg("tor/data"); - - QStringList createDirs({dir, config_dir_tor, config_dir_tordata}); - for(const auto &d: createDirs) { - if(!Utils::dirExists(d)) { - qDebug() << QString("Creating directory: %1").arg(d); - if (!QDir().mkpath(d)) { - qCritical() << "Could not create directory " << d; - } - } - } -} - -void AppContext::createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset) { - if(Utils::fileExists(path)) { - auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); - qCritical() << err; - emit walletCreatedError(err); - return; - } - - if(seed.mnemonic.isEmpty()) { - emit walletCreatedError("Mnemonic seed error. Failed to write wallet."); - return; - } - - Wallet *wallet = nullptr; - if (seed.seedType == SeedType::TEVADOR) { - wallet = this->walletManager->createDeterministicWalletFromSpendKey(path, password, seed.language, this->networkType, seed.spendKey, seed.restoreHeight, globals::kdfRounds, seedOffset); - wallet->setCacheAttribute("feather.seed", seed.mnemonic.join(" ")); - wallet->setCacheAttribute("feather.seedoffset", seedOffset); - } - if (seed.seedType == SeedType::MONERO) { - wallet = this->walletManager->recoveryWallet(path, password, seed.mnemonic.join(" "), seedOffset, this->networkType, seed.restoreHeight, globals::kdfRounds); - } - - this->currentWallet = wallet; - if(this->currentWallet == nullptr) { - emit walletCreatedError("Failed to write wallet"); - return; - } - - this->onWalletOpened(wallet); -} - -void AppContext::createWalletFromDevice(const QString &path, const QString &password, int restoreHeight) { - if(Utils::fileExists(path)) { - auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); - qCritical() << err; - emit walletCreatedError(err); - return; - } - - this->walletManager->createWalletFromDeviceAsync(path, password, this->networkType, "Ledger", restoreHeight); -} - -void AppContext::createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic) { - if(Utils::fileExists(path)) { - auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path); - qCritical() << err; - emit walletCreatedError(err); - return; - } - - if(!WalletManager::addressValid(address, this->networkType)) { - auto err = QString("Failed to create wallet. Invalid address provided.").arg(path); - qCritical() << err; - emit walletCreatedError(err); - return; - } - - if(!this->walletManager->keyValid(viewkey, address, true, this->networkType)) { - auto err = QString("Failed to create wallet. Invalid viewkey provided.").arg(path); - qCritical() << err; - emit walletCreatedError(err); - return; - } - - if(!spendkey.isEmpty() && !this->walletManager->keyValid(spendkey, address, false, this->networkType)) { - auto err = QString("Failed to create wallet. Invalid spendkey provided.").arg(path); - qCritical() << err; - emit walletCreatedError(err); - return; - } - - Wallet *wallet = this->walletManager->createWalletFromKeys(path, password, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight); - this->walletManager->walletOpened(wallet); -} - void AppContext::onSetRestoreHeight(quint64 height){ - auto seed = this->currentWallet->getCacheAttribute("feather.seed"); + auto seed = this->wallet->getCacheAttribute("feather.seed"); if(!seed.isEmpty()) { const auto msg = "This wallet has a 14 word mnemonic seed which has the restore height embedded."; emit setRestoreHeightError(msg); return; } - this->currentWallet->setWalletCreationHeight(height); - this->currentWallet->setPassword(this->currentWallet->getPassword()); // trigger .keys write + this->wallet->setWalletCreationHeight(height); + this->wallet->setPassword(this->wallet->getPassword()); // trigger .keys write // nuke wallet cache - const auto fn = this->currentWallet->path(); - this->walletManager->clearWalletCache(fn); + const auto fn = this->wallet->cachePath(); + WalletManager::clearWalletCache(fn); emit customRestoreHeightSet(height); } void AppContext::onOpenAliasResolve(const QString &openAlias) { // @TODO: calling this freezes for about 1-2 seconds :/ - const auto result = this->walletManager->resolveOpenAlias(openAlias); + const auto result = WalletManager::instance()->resolveOpenAlias(openAlias);; // TODO: async call const auto spl = result.split("|"); auto msg = QString(""); if(spl.count() != 2) { @@ -504,7 +228,7 @@ void AppContext::onOpenAliasResolve(const QString &openAlias) { const auto &status = spl.at(0); const auto &address = spl.at(1); - const auto valid = this->walletManager->addressValid(address, this->networkType); + const auto valid = WalletManager::addressValid(address, constants::networkType); if(status == "false"){ if(valid){ msg = "Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed"; @@ -533,50 +257,30 @@ void AppContext::onOpenAliasResolve(const QString &openAlias) { emit openAliasResolveError(msg); } -void AppContext::donateBeg() { - if (this->currentWallet == nullptr) return; - if (this->networkType != NetworkType::Type::MAINNET) return; - if (this->currentWallet->viewOnly()) return; - - auto donationCounter = config()->get(Config::donateBeg).toInt(); - if (donationCounter == -1) - return; // previously donated - - donationCounter += 1; - if (donationCounter % globals::donationBoundary == 0) { - emit donationNag(); - } - config()->set(Config::donateBeg, donationCounter); -} - -AppContext::~AppContext() {} - -// ############################################## LIBWALLET QT ######################################################### +// ########################################## LIBWALLET QT SIGNALS #################################################### void AppContext::onMoneySpent(const QString &txId, quint64 amount) { - auto amount_num = amount / globals::cdiv; - qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num); + // Outgoing tx included in a block + qDebug() << Q_FUNC_INFO << txId << " " << WalletManager::displayAmount(amount); } void AppContext::onMoneyReceived(const QString &txId, quint64 amount) { // Incoming tx included in a block. - auto amount_num = amount / globals::cdiv; - qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num); + qDebug() << Q_FUNC_INFO << txId << " " << WalletManager::displayAmount(amount); } void AppContext::onUnconfirmedMoneyReceived(const QString &txId, quint64 amount) { - // Incoming transaction in pool - auto amount_num = amount / globals::cdiv; - qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num); + // Incoming tx in pool + qDebug() << Q_FUNC_INFO << txId << " " << WalletManager::displayAmount(amount); - if(this->currentWallet->synchronized()) { - auto notify = QString("%1 XMR (pending)").arg(amount_num); + if (this->wallet->synchronized()) { + auto notify = QString("%1 XMR (pending)").arg(WalletManager::displayAmount(amount, false)); Utils::desktopNotify("Payment received", notify, 5000); } } void AppContext::onWalletUpdate() { - if (this->currentWallet->synchronized()) { + if (this->wallet->synchronized()) { this->refreshModels(); this->storeWallet(); } @@ -596,7 +300,7 @@ void AppContext::onWalletRefreshed(bool success, const QString &message) { this->refreshed = true; emit walletRefreshed(); // store wallet immediately upon finishing synchronization - this->currentWallet->store(); + this->wallet->store(); } qDebug() << "Wallet refresh status: " << success; @@ -606,10 +310,9 @@ void AppContext::onWalletNewBlock(quint64 blockheight, quint64 targetHeight) { // Called whenever a new block gets scanned by the wallet this->syncStatusUpdated(blockheight, targetHeight); - if (!this->currentWallet) return; - if (this->currentWallet->isSynchronized()) { - this->currentWallet->coins()->refreshUnlocked(); - this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount()); + if (this->wallet->isSynchronized()) { + this->wallet->coins()->refreshUnlocked(); + this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); // Todo: only refresh tx confirmations } } @@ -617,7 +320,7 @@ void AppContext::onWalletNewBlock(quint64 blockheight, quint64 targetHeight) { void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) { qDebug() << Q_FUNC_INFO << walletHeight << daemonHeight << targetHeight; - if (this->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) + if (this->wallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) return; if (daemonHeight < targetHeight) { @@ -632,7 +335,7 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVectordonationSending = true; } } @@ -647,21 +350,21 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVectorcurrentWallet->setUserNote(entry, this->tmpTxDescription); + this->wallet->setUserNote(entry, this->tmpTxDescription); } this->tmpTxDescription = ""; } // Store wallet immediately so we don't risk losing tx key if wallet crashes - this->currentWallet->store(); + this->wallet->store(); - this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount()); - this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount()); + this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); + this->wallet->coins()->refresh(this->wallet->currentSubaddressAccount()); this->updateBalance(); // this tx was a donation to Feather, stop our nagging - if(this->donationSending) { + if (this->donationSending) { this->donationSending = false; config()->set(Config::donateBeg, -1); } @@ -671,19 +374,16 @@ void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, con void AppContext::storeWallet() { // Do not store a synchronizing wallet: store() is NOT thread safe and may crash the wallet - if (this->currentWallet == nullptr || !this->currentWallet->isSynchronized()) + if (!this->wallet->isSynchronized()) return; qDebug() << "Storing wallet"; - this->currentWallet->store(); + this->wallet->store(); } void AppContext::updateBalance() { - if (!this->currentWallet) - return; - - quint64 balance = this->currentWallet->balance(); - quint64 spendable = this->currentWallet->unlockedBalance(); + quint64 balance = this->wallet->balance(); + quint64 spendable = this->wallet->unlockedBalance(); emit balanceUpdated(balance, spendable); } @@ -699,11 +399,8 @@ void AppContext::syncStatusUpdated(quint64 height, quint64 target) { } void AppContext::refreshModels() { - if (!this->currentWallet) - return; - - this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount()); - this->currentWallet->subaddress()->refresh(this->currentWallet->currentSubaddressAccount()); - this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount()); + this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); + this->wallet->subaddress()->refresh(this->wallet->currentSubaddressAccount()); + this->wallet->coins()->refresh(this->wallet->currentSubaddressAccount()); // Todo: set timer for refreshes } diff --git a/src/appcontext.h b/src/appcontext.h index 35435b7c..ebf482b8 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -7,22 +7,15 @@ #include #include -#include "utils/tails.h" -#include "utils/whonix.h" -#include "utils/prices.h" +#include "utils/os/whonix.h" #include "utils/networking.h" -#include "utils/TorManager.h" #include "utils/wsclient.h" -#include "utils/txfiathistory.h" #include "utils/FeatherSeed.h" #include "utils/daemonrpc.h" -#include "widgets/RedditPost.h" -#include "widgets/CCSEntry.h" #include "utils/RestoreHeightLookup.h" #include "utils/nodes.h" #include "libwalletqt/WalletManager.h" -#include "utils/keysfiles.h" #include "PendingTransaction.h" class AppContext : public QObject @@ -30,55 +23,30 @@ class AppContext : public QObject Q_OBJECT public: - explicit AppContext(QCommandLineParser *cmdargs); - ~AppContext() override; + explicit AppContext(Wallet *wallet); - QCommandLineParser *cmdargs; - - bool isTails = false; - bool isWhonix = false; + QScopedPointer wallet; + Nodes *nodes; bool donationSending = false; - QString defaultWalletDir; - QString tmpTxDescription; + QString tmpTxDescription; // TODO: remove the need for this var - QString walletPath; - QString walletPassword = ""; NetworkType::Type networkType; - PendingTransaction::Priority tx_priority = PendingTransaction::Priority::Priority_Low; - QString seedLanguage = "English"; // 14 word `monero-seed` only has English - - Nodes *nodes; // TODO: move this to mainwindow (?) - - static WalletKeysFilesModel *wallets; - static QMap txCache; - - static void createConfigDirectory(const QString &dir); + QMap txCache; // libwalletqt bool refreshed = false; - WalletManager *walletManager; - Wallet *currentWallet = nullptr; - void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = ""); - void createWalletFromDevice(const QString &path, const QString &password, int restoreHeight); - void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false); + void commitTransaction(PendingTransaction *tx); void syncStatusUpdated(quint64 height, quint64 target); void updateBalance(); - void initTor(); - void initWS(); - void donateBeg(); void refreshModels(); - // Closes the currently opened wallet - void closeWallet(bool emitClosedSignal = true, bool storeWallet = false); void storeWallet(); public slots: - void onOpenWallet(const QString& path, const QString &password); - void onWalletCreated(Wallet * wallet); void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all); void onCreateTransactionMultiDest(const QVector &addresses, const QVector &amounts, const QString &description); void onCancelTransaction(PendingTransaction *tx, const QVector &address); @@ -90,59 +58,44 @@ public slots: void onAmountPrecisionChanged(int precision); void onMultiBroadcast(PendingTransaction *tx); void onDeviceButtonRequest(quint64 code); - void onTorSettingsChanged(); - void onInitialNetworkConfigured(); void onDeviceError(const QString &message); + void onTorSettingsChanged(); // should not be here + private slots: void onMoneySpent(const QString &txId, quint64 amount); void onMoneyReceived(const QString &txId, quint64 amount); void onUnconfirmedMoneyReceived(const QString &txId, quint64 amount); void onWalletUpdate(); void onWalletRefreshed(bool success, const QString &message); - void onWalletOpened(Wallet *wallet); + void onWalletNewBlock(quint64 blockheight, quint64 targetHeight); void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight); void onTransactionCreated(PendingTransaction *tx, const QVector &address); void onTransactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); signals: - // Emitted just before the wallet is closed - void walletAboutToClose(); - - // Emitted after a wallet has been closed - void walletClosed(); - void balanceUpdated(quint64 balance, quint64 spendable); void blockchainSync(int height, int target); void refreshSync(int height, int target); void synchronized(); void walletRefreshed(); - void walletOpened(); - void walletCreatedError(const QString &msg); - void walletCreated(Wallet *wallet); - void walletOpenedError(QString msg); - void walletOpenPasswordNeeded(bool invalidPassword, QString path); void transactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void createTransactionError(QString message); - void createTransactionCancelled(const QVector &address, double amount); + void createTransactionCancelled(const QVector &address, quint64 amount); void createTransactionSuccess(PendingTransaction *tx, const QVector &address); void openAliasResolveError(const QString &msg); void openAliasResolved(const QString &address, const QString &openAlias); void setRestoreHeightError(const QString &msg); void customRestoreHeightSet(int height); - void closeApplication(); - void donationNag(); void initiateTransaction(); void endTransaction(); void deviceButtonRequest(quint64 code); - void updatesAvailable(const QJsonObject &updates); void deviceError(const QString &message); private: DaemonRpc *m_rpc; QTimer m_storeTimer; - bool m_openWalletTriedOnce = false; }; #endif //FEATHER_APPCONTEXT_H diff --git a/src/assets.qrc b/src/assets.qrc index 8f7488aa..9956d8be 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -19,6 +19,7 @@ assets/images/bitcoin.png assets/images/camera_dark.png assets/images/camera_white.png + assets/images/change_account.png assets/images/clock1.png assets/images/clock2.png assets/images/clock3.png diff --git a/src/assets/about.txt b/src/assets/about.txt index 06b7ab9e..84af5c19 100644 --- a/src/assets/about.txt +++ b/src/assets/about.txt @@ -5,6 +5,8 @@ E-mail: dev@featherwallet.org Created by dsc, tobtoht, and contributors. +Uses icons from the Icons8 icon pack (icons8.com). + Copyright (c) 2020-, The Monero Project All rights reserved. diff --git a/src/assets/images/change_account.png b/src/assets/images/change_account.png new file mode 100644 index 00000000..f24a4ba2 Binary files /dev/null and b/src/assets/images/change_account.png differ diff --git a/src/calcwidget.cpp b/src/calcwidget.cpp index 48e85744..11616a29 100644 --- a/src/calcwidget.cpp +++ b/src/calcwidget.cpp @@ -8,10 +8,11 @@ #include "utils/ColorScheme.h" #include "utils/AppData.h" #include "utils/config.h" +#include "dialog/CalcConfigDialog.h" -CalcWidget::CalcWidget(QWidget *parent) : - QWidget(parent), - ui(new Ui::CalcWidget) +CalcWidget::CalcWidget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::CalcWidget) { ui->setupUi(this); @@ -29,115 +30,120 @@ CalcWidget::CalcWidget(QWidget *parent) : ui->lineFrom->setValidator(dv); ui->lineTo->setValidator(dv); - connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::initFiat); - connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::initCrypto);; + connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::onPricesReceived); + connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::onPricesReceived); + + connect(ui->lineFrom, &QLineEdit::textEdited, this, [this]{this->convert(false);}); + connect(ui->lineTo, &QLineEdit::textEdited, this, [this]{this->convert(true);}); + + connect(ui->comboCalcFrom, QOverload::of(&QComboBox::currentIndexChanged), [this]{this->convert(false);}); + connect(ui->comboCalcTo, QOverload::of(&QComboBox::currentIndexChanged), [this]{this->convert(false);}); + + connect(ui->btn_configure, &QPushButton::clicked, this, &CalcWidget::showCalcConfigureDialog); + + QTimer::singleShot(1, [this]{ + this->skinChanged(); + }); } -void CalcWidget::fromChanged(const QString &data) { - if(!this->m_comboBoxInit) return; - if(this->m_changing){ - this->m_changing = false; +void CalcWidget::convert(bool reverse) { + if (!m_comboBoxInit) return; + + auto lineFrom = reverse ? ui->lineTo : ui->lineFrom; + auto lineTo = reverse ? ui->lineFrom : ui->lineTo; + + auto comboFrom = reverse ? ui->comboCalcTo : ui->comboCalcFrom; + auto comboTo = reverse ? ui->comboCalcFrom : ui->comboCalcTo; + + QString symbolFrom = comboFrom->itemText(comboFrom->currentIndex()); + QString symbolTo = comboTo->itemText(comboTo->currentIndex()); + + if (symbolFrom == symbolTo) { + lineTo->setText(lineFrom->text()); } - QString symbolFrom = ui->comboCalcFrom->itemText(ui->comboCalcFrom->currentIndex()); - QString symbolTo = ui->comboCalcTo->itemText(ui->comboCalcTo->currentIndex()); - - if(symbolFrom == symbolTo){ - ui->lineTo->setText(data); - return; - } - - QString amount_str = ui->lineFrom->text(); - if(amount_str.startsWith('.')){ - ui->lineFrom->setText(ui->lineTo->text()); - return; - } - - double amount = amount_str.toDouble(); + QString amountStr = lineFrom->text(); + double amount = amountStr.toDouble(); double result = appData()->prices.convert(symbolFrom, symbolTo, amount); - this->m_changing = true; - int precision = 10; if (appData()->prices.rates.contains(symbolTo)) precision = 2; - ui->lineTo->setText(QString::number(result, 'f', precision)); + lineTo->setText(QString::number(result, 'f', precision)); } -void CalcWidget::toChanged(const QString &data) { - if(!this->m_comboBoxInit) return; - if(this->m_changing){ - this->m_changing = false; +void CalcWidget::onPricesReceived() { + if (m_comboBoxInit) return; - } - QString symbolFrom = ui->comboCalcFrom->itemText( - ui->comboCalcFrom->currentIndex()); - QString symbolTo = ui->comboCalcTo->itemText( - ui->comboCalcTo->currentIndex()); - - if(symbolFrom == symbolTo){ - ui->lineTo->setText(ui->lineFrom->text()); + QList cryptoKeys = appData()->prices.markets.keys(); + QList fiatKeys = appData()->prices.rates.keys(); + if (cryptoKeys.empty() || fiatKeys.empty()) return; - } - QString amount_str = ui->lineTo->text(); - if(amount_str.startsWith('.')){ - ui->lineTo->setText(""); - return; - } - - double amount = amount_str.toDouble(); - double result = appData()->prices.convert(symbolTo, symbolFrom, amount); - - this->m_changing = true; - - int precision = 10; - if(appData()->prices.rates.contains(symbolFrom)) - precision = 2; - - ui->lineFrom->setText(QString::number(result, 'f', precision)); -} - -void CalcWidget::toComboChanged(const QString &data) { - this->fromChanged(data); -} - -void CalcWidget::initCrypto() { - this->initComboBox(); -} - -void CalcWidget::initFiat() { + ui->btn_configure->setEnabled(true); this->initComboBox(); + m_comboBoxInit = true; } void CalcWidget::initComboBox() { - if(m_comboBoxInit) return; - QList marketsKeys = appData()->prices.markets.keys(); - QList ratesKeys = appData()->prices.rates.keys(); - if(marketsKeys.count() <= 0 || ratesKeys.count() <= 0) return; + QList cryptoKeys = appData()->prices.markets.keys(); + QList fiatKeys = appData()->prices.rates.keys(); - ui->comboCalcFrom->addItems(marketsKeys); - ui->comboCalcFrom->insertSeparator(marketsKeys.count()); - ui->comboCalcFrom->addItems(ratesKeys); - ui->comboCalcFrom->setCurrentIndex(marketsKeys.indexOf("XMR")); - - ui->comboCalcTo->addItems(marketsKeys); - ui->comboCalcTo->insertSeparator(marketsKeys.count()); - ui->comboCalcTo->addItems(ratesKeys); + QStringList enabledCrypto = config()->get(Config::cryptoSymbols).toStringList(); + QStringList filteredCryptoKeys; + for (const auto& symbol : cryptoKeys) { + if (enabledCrypto.contains(symbol)) { + filteredCryptoKeys.append(symbol); + } + } + QStringList enabledFiat = config()->get(Config::fiatSymbols).toStringList(); auto preferredFiat = config()->get(Config::preferredFiatCurrency).toString(); - ui->comboCalcTo->setCurrentText(preferredFiat); + if (!enabledFiat.contains(preferredFiat) && fiatKeys.contains(preferredFiat)) { + enabledFiat.append(preferredFiat); + config()->set(Config::fiatSymbols, enabledFiat); + } + QStringList filteredFiatKeys; + for (const auto &symbol : fiatKeys) { + if (enabledFiat.contains(symbol)) { + filteredFiatKeys.append(symbol); + } + } - this->m_comboBoxInit = true; + this->setupComboBox(ui->comboCalcFrom, filteredCryptoKeys, filteredFiatKeys); + this->setupComboBox(ui->comboCalcTo, filteredCryptoKeys, filteredFiatKeys); + + ui->comboCalcFrom->setCurrentIndex(ui->comboCalcFrom->findText("XMR")); + + if (!preferredFiat.isEmpty()) { + ui->comboCalcTo->setCurrentIndex(ui->comboCalcTo->findText(preferredFiat)); + } else { + ui->comboCalcTo->setCurrentIndex(ui->comboCalcTo->findText("USD")); + } } void CalcWidget::skinChanged() { ui->imageExchange->setMode(ColorScheme::hasDarkBackground(this)); } +void CalcWidget::showCalcConfigureDialog() { + CalcConfigDialog dialog{this}; + + if (dialog.exec() == QDialog::Accepted) { + this->initComboBox(); + } +} + +void CalcWidget::setupComboBox(QComboBox *comboBox, const QStringList &crypto, const QStringList &fiat) { + comboBox->clear(); + comboBox->addItems(crypto); + comboBox->insertSeparator(comboBox->count()); + comboBox->addItems(fiat); +} + CalcWidget::~CalcWidget() { delete ui; } diff --git a/src/calcwidget.h b/src/calcwidget.h index 9ad2134c..f5c4af12 100644 --- a/src/calcwidget.h +++ b/src/calcwidget.h @@ -1,10 +1,11 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2021, The Monero Project. -#ifndef CALC_H -#define CALC_H +#ifndef FEATHER_CALCWIDGET_H +#define FEATHER_CALCWIDGET_H #include +#include namespace Ui { class CalcWidget; @@ -18,23 +19,20 @@ public: explicit CalcWidget(QWidget *parent = nullptr); ~CalcWidget() override; -signals: - void closed(); - public slots: - void fromChanged(const QString& data); - void toChanged(const QString& data); - void toComboChanged(const QString& data); - void initFiat(); - void initCrypto(); void skinChanged(); -private: - Ui::CalcWidget *ui; - - bool m_comboBoxInit = false; +private slots: void initComboBox(); - bool m_changing = false; + void showCalcConfigureDialog(); + void onPricesReceived(); + +private: + void convert(bool reverse); + void setupComboBox(QComboBox *comboBox, const QStringList &crypto, const QStringList &fiat); + + Ui::CalcWidget *ui; + bool m_comboBoxInit = false; }; -#endif // CALC_H +#endif // FEATHER_CALCWIDGET_H diff --git a/src/calcwidget.ui b/src/calcwidget.ui index dbd49bbd..3fa1db2b 100644 --- a/src/calcwidget.ui +++ b/src/calcwidget.ui @@ -6,141 +6,22 @@ 0 0 - 737 - 153 + 800 + 132 MainWindow - - - 0 - - - 0 - - - 0 - - - 0 - - - + + + - - - 9 + + + Crypto/fiat and fiat/fiat calculator. - - - - Crypto/fiat and fiat/fiat calculator. - - - - - - - Qt::Horizontal - - - - - - - - - 18 - - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - true - - - From... - - - - - - - - - - - - exchange image - - - - - - - 0 - - - - - - 0 - 0 - - - - false - - - To... - - - - - - - - - - - - - - - - Exchange rates are updated every 2 minutes. - - - - - - - Qt::Vertical - - - - 20 - 170 - - - - - + @@ -155,8 +36,98 @@ + + + + Configure + + + + + + + Qt::Horizontal + + + + + + + 18 + + + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + From... + + + + + + + + + + + + exchange image + + + + + + + 0 + + + + + + 0 + 0 + + + + false + + + To... + + + + + + + + + + + + + + Exchange rates are updated every 2 minutes. + + + @@ -167,72 +138,7 @@ - - - lineFrom - textChanged(QString) - CalcWidget - fromChanged(QString) - - - 119 - 77 - - - 427 - -1 - - - - - lineTo - textChanged(QString) - CalcWidget - toChanged(QString) - - - 463 - 86 - - - 640 - -4 - - - - - comboCalcFrom - currentIndexChanged(QString) - CalcWidget - fromChanged(QString) - - - 196 - 77 - - - 330 - -9 - - - - - comboCalcTo - currentIndexChanged(QString) - CalcWidget - toComboChanged(QString) - - - 574 - 82 - - - 265 - -5 - - - - + fromChanged(QString) toChanged(QString) diff --git a/src/calcwindow.cpp b/src/calcwindow.cpp index f4ccbfc0..1b72fc33 100644 --- a/src/calcwindow.cpp +++ b/src/calcwindow.cpp @@ -8,26 +8,15 @@ #include "ui_calcwindow.h" -CalcWindow::CalcWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::CalcWindow) +CalcWindow::CalcWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::CalcWindow) { Qt::WindowFlags flags = this->windowFlags(); this->setWindowFlags(flags|Qt::WindowStaysOnTopHint); // on top ui->setupUi(this); this->setWindowIcon(icons()->icon("gnome-calc.png")); - - connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWindow::initFiat); - connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWindow::initCrypto); -} - -void CalcWindow::initFiat() { - this->ui->calcWidget->initFiat(); -} - -void CalcWindow::initCrypto() { - this->ui->calcWidget->initCrypto(); } void CalcWindow::closeEvent(QCloseEvent *foo) { diff --git a/src/calcwindow.h b/src/calcwindow.h index 8d6b6ed0..886a9fb8 100644 --- a/src/calcwindow.h +++ b/src/calcwindow.h @@ -21,10 +21,6 @@ public: signals: void closed(); -public slots: - void initFiat(); - void initCrypto(); - private: void closeEvent(QCloseEvent *bar) override; diff --git a/src/cli.cpp b/src/cli.cpp index 6f236dc7..a0e7a63d 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -9,48 +9,59 @@ #include "model/AddressBookModel.h" #include "model/TransactionHistoryModel.h" #include "utils/brute.h" +#include "constants.h" -CLI::CLI(AppContext *ctx, QObject *parent) : - QObject(parent), - ctx(ctx) { - connect(this->ctx, &AppContext::walletOpened, this, &CLI::onWalletOpened); - connect(this->ctx, &AppContext::walletOpenedError, this, &CLI::onWalletOpenedError); - connect(this->ctx, &AppContext::walletOpenPasswordNeeded, this, &CLI::onWalletOpenPasswordRequired); -} +CLI::CLI(Mode mode, QCommandLineParser *cmdargs, QObject *parent) + : QObject(parent) + , m_mode(mode) + , m_cmdargs(cmdargs) +{ + m_walletManager = WalletManager::instance(); + connect(m_walletManager, &WalletManager::walletOpened, this, &CLI::onWalletOpened); -void CLI::run() { - if (mode == CLIMode::ExportContacts || mode == CLIMode::ExportTxHistory) + if (m_mode == Mode::ExportContacts || m_mode == Mode::ExportTxHistory) { - if(!ctx->cmdargs->isSet("wallet-file")) - return this->finishedError("--wallet-file argument missing"); - if(!ctx->cmdargs->isSet("password")) - return this->finishedError("--password argument missing"); - ctx->onOpenWallet(ctx->cmdargs->value("wallet-file"), ctx->cmdargs->value("password")); + if (!cmdargs->isSet("wallet-file")) { + this->finished("--wallet-file argument missing"); + return; + } + if (!cmdargs->isSet("password")) { + this->finished("--password argument missing"); + return; + } + + QString walletFile = cmdargs->value("wallet-file"); + QString password = cmdargs->value("password"); + + + m_walletManager->openWalletAsync(walletFile, password, constants::networkType); } - else if (mode == CLIMode::BruteforcePassword) + else if (mode == Mode::BruteforcePassword) { - QString keys_file = ctx->cmdargs->value("bruteforce-password"); + QString keys_file = m_cmdargs->value("bruteforce-password"); if (!keys_file.endsWith(".keys")) { - return this->finishedError("Wallet file does not end with .keys"); + this->finished("Wallet file does not end with .keys"); + return; } QStringList words; - if (ctx->cmdargs->isSet("bruteforce-dict")) { - QString data = Utils::barrayToString(Utils::fileOpen(ctx->cmdargs->value("bruteforce-dict"))); + if (m_cmdargs->isSet("bruteforce-dict")) { + QString data = Utils::barrayToString(Utils::fileOpen(m_cmdargs->value("bruteforce-dict"))); words = data.split("\n"); } - if (!ctx->cmdargs->isSet("bruteforce-chars")) { - return this->finishedError("--bruteforce-chars argument missing"); + if (!m_cmdargs->isSet("bruteforce-chars")) { + this->finished("--bruteforce-chars argument missing"); + return; } - QString chars = ctx->cmdargs->value("bruteforce-chars"); + QString chars = m_cmdargs->value("bruteforce-chars"); brute b(chars.toStdString()); if (words.isEmpty()) { qDebug() << "No dictionairy specified, bruteforcing all chars"; while (true) { QString pass = QString::fromStdString(b.next()); - if (ctx->walletManager->verifyWalletPassword(keys_file, pass, false)) { + if (m_walletManager->verifyWalletPassword(keys_file, pass, false)) { this->finished(QString("Found password: %1").arg(pass)); break; } @@ -60,7 +71,7 @@ void CLI::run() { else { bruteword bb(chars.toStdString()); bool foundPass = false; - for (auto word: words) { + for (const auto& word: words) { if (word.isEmpty()) { continue; } @@ -71,7 +82,7 @@ void CLI::run() { if (pass == "") { break; } - if (ctx->walletManager->verifyWalletPassword(keys_file, pass, false)) { + if (m_walletManager->verifyWalletPassword(keys_file, pass, false)) { this->finished(QString("Found password: %1").arg(pass)); foundPass = true; break; @@ -88,50 +99,39 @@ void CLI::run() { } } } -} - -void CLI::onWalletOpened() { - if(mode == CLIMode::ExportContacts){ - auto *model = ctx->currentWallet->addressBookModel(); - auto fn = ctx->cmdargs->value("export-contacts"); - if(model->writeCSV(fn)) - this->finished(QString("Address book exported to %1").arg(fn)); - else - this->finishedError("Address book export failure"); - } else if(mode == ExportTxHistory) { - ctx->currentWallet->history()->refresh(ctx->currentWallet->currentSubaddressAccount()); - auto *model = ctx->currentWallet->history(); - auto fn = ctx->cmdargs->value("export-txhistory"); - if(model->writeCSV(fn)) - this->finished(QString("Transaction history exported to %1").arg(fn)); - else - this->finishedError("Transaction history export failure"); + else { + this->finished("Invalid mode"); } } -void CLI::onWalletOpenedError(const QString &err) { - if(mode == CLIMode::ExportContacts || - mode == CLIMode::ExportTxHistory) - return this->finishedError(err); +void CLI::onWalletOpened(Wallet *w) { + QScopedPointer wallet{w}; + + if (wallet->status() != Wallet::Status_Ok) { + this->finished(wallet->errorString()); + return; + } + + if (m_mode == Mode::ExportContacts) { + auto *model = wallet->addressBookModel(); + QString fileName = m_cmdargs->value("export-contacts"); + if (model->writeCSV(fileName)) + this->finished(QString("Contacts exported to %1").arg(fileName)); + else + this->finished("Failed to export contacts"); + } + else if (m_mode == Mode::ExportTxHistory) { + wallet->history()->refresh(wallet->currentSubaddressAccount()); + auto *model = wallet->history(); + QString fileName = m_cmdargs->value("export-txhistory"); + if (model->writeCSV(fileName)) + this->finished(QString("Transaction history exported to %1").arg(fileName)); + else + this->finished("Failed to export transaction history"); + } } -void CLI::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) { - if(mode == CLIMode::ExportContacts || - mode == CLIMode::ExportTxHistory) - return this->finishedError("invalid password"); -} - -void CLI::finished(const QString &msg){ - qInfo() << msg; - emit closeApplication(); -} - -void CLI::finishedError(const QString &err) { - qCritical() << err; - emit closeApplication(); -} - -CLI::~CLI() { - ctx->disconnect(); - delete ctx; +void CLI::finished(const QString &message) { + qInfo() << message; + QApplication::quit(); } diff --git a/src/cli.h b/src/cli.h index 38271cdc..dc04a9ae 100644 --- a/src/cli.h +++ b/src/cli.h @@ -7,37 +7,28 @@ #include #include "appcontext.h" -enum CLIMode { - ExportContacts, - ExportTxHistory, - BruteforcePassword -}; - class CLI : public QObject { Q_OBJECT + public: - CLIMode mode; - explicit CLI(AppContext *ctx, QObject *parent = nullptr); - ~CLI() override; + enum Mode { + ExportContacts, + ExportTxHistory, + BruteforcePassword + }; -public slots: - void run(); - - //libwalletqt - void onWalletOpened(); - void onWalletOpenedError(const QString& err); - void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path); - -private: - AppContext *ctx; + explicit CLI(Mode mode, QCommandLineParser *cmdargs, QObject *parent = nullptr); private slots: - void finished(const QString &msg); - void finishedError(const QString &err); + void onWalletOpened(Wallet *wallet); -signals: - void closeApplication(); +private: + void finished(const QString &message); + + Mode m_mode; + QCommandLineParser *m_cmdargs; + WalletManager *m_walletManager; }; #endif //FEATHER_CLI_H diff --git a/src/coinswidget.cpp b/src/coinswidget.cpp index 507da8b6..60228c13 100644 --- a/src/coinswidget.cpp +++ b/src/coinswidget.cpp @@ -11,14 +11,14 @@ #include #include -CoinsWidget::CoinsWidget(QWidget *parent) - : QWidget(parent) - , ui(new Ui::CoinsWidget) - , m_headerMenu(new QMenu(this)) - , m_copyMenu(new QMenu("Copy",this)) +CoinsWidget::CoinsWidget(QSharedPointer ctx, QWidget *parent) + : QWidget(parent) + , ui(new Ui::CoinsWidget) + , m_ctx(std::move(ctx)) + , m_headerMenu(new QMenu(this)) + , m_copyMenu(new QMenu("Copy",this)) { ui->setupUi(this); - m_ctx = MainWindow::getContext(); // header context menu ui->coins->header()->setContextMenuPolicy(Qt::CustomContextMenu); @@ -69,7 +69,7 @@ void CoinsWidget::setModel(CoinsModel * model, Coins * coins) { ui->coins->setColumnHidden(CoinsModel::SpentHeight, true); ui->coins->setColumnHidden(CoinsModel::Frozen, true); - if (!m_ctx->currentWallet->viewOnly()) { + if (!m_ctx->wallet->viewOnly()) { ui->coins->setColumnHidden(CoinsModel::KeyImageKnown, true); } else { ui->coins->setColumnHidden(CoinsModel::KeyImageKnown, false); @@ -186,10 +186,6 @@ void CoinsWidget::onSweepOutput() { dialog->deleteLater(); } -void CoinsWidget::resetModel() { - ui->coins->setModel(nullptr); -} - void CoinsWidget::copy(copyField field) { CoinsInfo* c = this->currentEntry(); if (!c) return; @@ -233,17 +229,17 @@ CoinsInfo* CoinsWidget::currentEntry() { void CoinsWidget::freezeCoins(const QVector& indexes) { for (int i : indexes) { - m_ctx->currentWallet->coins()->freeze(i); + m_ctx->wallet->coins()->freeze(i); } - m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount()); + m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->updateBalance(); } void CoinsWidget::thawCoins(const QVector &indexes) { for (int i : indexes) { - m_ctx->currentWallet->coins()->thaw(i); + m_ctx->wallet->coins()->thaw(i); } - m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount()); + m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->updateBalance(); } diff --git a/src/coinswidget.h b/src/coinswidget.h index a2f3e5b1..91ffba96 100644 --- a/src/coinswidget.h +++ b/src/coinswidget.h @@ -22,13 +22,10 @@ class CoinsWidget : public QWidget Q_OBJECT public: - explicit CoinsWidget(QWidget *parent = nullptr); + explicit CoinsWidget(QSharedPointer ctx, QWidget *parent = nullptr); void setModel(CoinsModel * model, Coins * coins); ~CoinsWidget() override; -public slots: - void resetModel(); - private slots: void showHeaderMenu(const QPoint& position); void setShowSpent(bool show); @@ -54,11 +51,12 @@ private: }; Ui::CoinsWidget *ui; + QSharedPointer m_ctx; QMenu *m_contextMenu; + QMenu *m_headerMenu; QMenu *m_copyMenu; QAction *m_showSpentAction; - QMenu *m_headerMenu; QAction *m_freezeOutputAction; QAction *m_freezeAllSelectedAction; QAction *m_thawOutputAction; @@ -68,7 +66,6 @@ private: Coins *m_coins; CoinsModel * m_model; CoinsProxyModel * m_proxyModel; - AppContext *m_ctx; void showContextMenu(const QPoint & point); void copy(copyField field); diff --git a/src/globals.h b/src/constants.h similarity index 67% rename from src/globals.h rename to src/constants.h index fd9b67e4..76c79e1a 100644 --- a/src/globals.h +++ b/src/constants.h @@ -1,24 +1,31 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2021, The Monero Project. -#ifndef FEATHER_GLOBALS_H -#define FEATHER_GLOBALS_H +#ifndef FEATHER_CONSTANTS_H +#define FEATHER_CONSTANTS_H #include #include -namespace globals +#include "networktype.h" + +namespace constants { + // application constants + static NetworkType::Type networkType; // TODO: compiler moans, also not really a const + // coin constants const std::string coinName = "monero"; const qreal cdiv = 1e12; const quint32 mixin = 10; const quint64 kdfRounds = 1; + const QString seedLanguage = "English"; // todo: move me + // donation constants const QString donationAddress = "47ntfT2Z5384zku39pTM6hGcnLnvpRYW2Azm87GiAAH2bcTidtq278TL6HmwyL8yjMeERqGEBs3cqC8vvHPJd1cWQrGC65f"; const int donationAmount = 25; // euro - const int donationBoundary = 15; + const int donationBoundary = 25; // websocket constants const QUrl websocketUrl = QUrl(QStringLiteral("ws://7e6egbawekbkxzkv4244pqeqgoo4axko2imgjbedwnn6s5yb6b7oliqd.onion/ws")); @@ -27,4 +34,4 @@ namespace globals const QString websiteUrl = "https://featherwallet.org"; } -#endif //FEATHER_GLOBALS_H +#endif //FEATHER_CONSTANTS_H diff --git a/src/contactswidget.cpp b/src/contactswidget.cpp index a7a7bc3d..62b0908f 100644 --- a/src/contactswidget.cpp +++ b/src/contactswidget.cpp @@ -11,12 +11,22 @@ #include -ContactsWidget::ContactsWidget(QWidget *parent) : - QWidget(parent), - ui(new Ui::ContactsWidget) +ContactsWidget::ContactsWidget(QSharedPointer ctx, QWidget *parent) + : QWidget(parent) + , ui(new Ui::ContactsWidget) + , m_ctx(std::move(ctx)) { ui->setupUi(this); - m_ctx = MainWindow::getContext(); + + m_model = m_ctx->wallet->addressBookModel(); + m_proxyModel = new AddressBookProxyModel; + m_proxyModel->setSourceModel(m_model); + ui->contacts->setModel(m_proxyModel); + + ui->contacts->setSortingEnabled(true); + ui->contacts->header()->setSectionResizeMode(AddressBookModel::Address, QHeaderView::Stretch); + ui->contacts->header()->setSectionResizeMode(AddressBookModel::Description, QHeaderView::ResizeToContents); + ui->contacts->header()->setMinimumSectionSize(200); // header context menu ui->contacts->header()->setContextMenuPolicy(Qt::CustomContextMenu); @@ -53,6 +63,15 @@ ContactsWidget::ContactsWidget(QWidget *parent) : connect(ui->search, &QLineEdit::textChanged, this, &ContactsWidget::setSearchFilter); } +void ContactsWidget::setSearchbarVisible(bool visible) { + ui->search->setVisible(visible); +} + +void ContactsWidget::focusSearchbar() { + ui->search->setFocusPolicy(Qt::StrongFocus); + ui->search->setFocus(); +} + void ContactsWidget::copyAddress() { QModelIndex index = ui->contacts->currentIndex(); ModelUtils::copyColumn(&index, AddressBookModel::Address); @@ -69,19 +88,6 @@ void ContactsWidget::payTo() { emit fillAddress(address); } -void ContactsWidget::setModel(AddressBookModel *model) -{ - m_model = model; - m_proxyModel = new AddressBookProxyModel; - m_proxyModel->setSourceModel(m_model); - ui->contacts->setModel(m_proxyModel); - - ui->contacts->setSortingEnabled(true); - ui->contacts->header()->setSectionResizeMode(AddressBookModel::Address, QHeaderView::Stretch); - ui->contacts->header()->setSectionResizeMode(AddressBookModel::Description, QHeaderView::ResizeToContents); - ui->contacts->header()->setMinimumSectionSize(200); -} - void ContactsWidget::setShowFullAddresses(bool show) { m_model->setShowFullAddresses(show); } @@ -91,11 +97,6 @@ void ContactsWidget::setSearchFilter(const QString &filter) { m_proxyModel->setSearchFilter(filter); } -void ContactsWidget::resetModel() -{ - ui->contacts->setModel(nullptr); -} - void ContactsWidget::showHeaderMenu(const QPoint& position) { m_showFullAddressesAction->setChecked(m_model->isShowFullAddresses()); @@ -104,24 +105,26 @@ void ContactsWidget::showHeaderMenu(const QPoint& position) void ContactsWidget::newContact(QString address, QString name) { - auto * dialog = new ContactsDialog(this, address, name); - int ret = dialog->exec(); - if (!ret) return; + ContactsDialog dialog{this, address, name}; + int ret = dialog.exec(); + if (ret != QDialog::Accepted) { + return; + } - address = dialog->getAddress(); - name = dialog->getName(); + address = dialog.getAddress(); + name = dialog.getName(); - bool addressValid = WalletManager::addressValid(address, m_ctx->currentWallet->nettype()); + bool addressValid = WalletManager::addressValid(address, m_ctx->wallet->nettype()); if (!addressValid) { QMessageBox::warning(this, "Invalid address", "Invalid address"); return; } - int num_addresses = m_ctx->currentWallet->addressBook()->count(); + int num_addresses = m_ctx->wallet->addressBook()->count(); QString address_entry; QString name_entry; for (int i=0; icurrentWallet->addressBook()->getRow(i, [&address_entry, &name_entry](const AddressBookInfo &entry){ + m_ctx->wallet->addressBook()->getRow(i, [&address_entry, &name_entry](const AddressBookInfo &entry){ address_entry = entry.address(); name_entry = entry.description(); }); @@ -138,7 +141,7 @@ void ContactsWidget::newContact(QString address, QString name) } } - m_ctx->currentWallet->addressBook()->addRow(address, "", name); + m_ctx->wallet->addressBook()->addRow(address, "", name); } void ContactsWidget::deleteContact() diff --git a/src/contactswidget.h b/src/contactswidget.h index 40357735..5c2a819c 100644 --- a/src/contactswidget.h +++ b/src/contactswidget.h @@ -20,10 +20,12 @@ class ContactsWidget : public QWidget Q_OBJECT public: - explicit ContactsWidget(QWidget *parent = nullptr); - void setModel(AddressBookModel * model); + explicit ContactsWidget(QSharedPointer ctx, QWidget *parent = nullptr); ~ContactsWidget() override; + void setSearchbarVisible(bool visible); + void focusSearchbar(); + public slots: void copyAddress(); void copyName(); @@ -32,7 +34,6 @@ public slots: void deleteContact(); void setShowFullAddresses(bool show); void setSearchFilter(const QString &filter); - void resetModel(); signals: void fillAddress(QString &address); @@ -42,7 +43,7 @@ private slots: private: Ui::ContactsWidget *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; QAction *m_showFullAddressesAction; QMenu *m_rowMenu; diff --git a/src/dialog/AccountSwitcherDialog.cpp b/src/dialog/AccountSwitcherDialog.cpp new file mode 100644 index 00000000..50416572 --- /dev/null +++ b/src/dialog/AccountSwitcherDialog.cpp @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "AccountSwitcherDialog.h" +#include "ui_AccountSwitcherDialog.h" + +#include "libwalletqt/SubaddressAccount.h" +#include "utils/Icons.h" +#include "model/ModelUtils.h" +#include + +AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer ctx, QWidget *parent) + : QDialog(parent) + , ui(new Ui::AccountSwitcherDialog) + , m_ctx(std::move(ctx)) + , m_model(m_ctx->wallet->subaddressAccountModel()) + , m_proxyModel(new SubaddressAccountProxyModel(this)) +{ + ui->setupUi(this); + + m_ctx->wallet->subaddressAccount()->refresh(); + m_proxyModel->setSourceModel(m_model); + + ui->label_totalBalance->setFont(ModelUtils::getMonospaceFont()); + ui->label_totalBalance->setText(WalletManager::displayAmount(m_ctx->wallet->balanceAll())); + + ui->accounts->setModel(m_proxyModel); + ui->accounts->setContextMenuPolicy(Qt::CustomContextMenu); + ui->accounts->setSelectionMode(QAbstractItemView::SingleSelection); + ui->accounts->setSelectionBehavior(QAbstractItemView::SelectRows); + ui->accounts->setSortingEnabled(true); + ui->accounts->sortByColumn(SubaddressAccountModel::Column::Number, Qt::AscendingOrder); + ui->accounts->hideColumn(SubaddressAccountModel::Column::Address); + ui->accounts->hideColumn(SubaddressAccountModel::Column::UnlockedBalance); + ui->accounts->setColumnWidth(SubaddressAccountModel::Column::Label, 200); + ui->accounts->header()->setSectionResizeMode(QHeaderView::ResizeToContents); + ui->accounts->header()->setSectionResizeMode(SubaddressAccountModel::Label, QHeaderView::Stretch); + + connect(ui->accounts->selectionModel(), &QItemSelectionModel::currentChanged, this, &AccountSwitcherDialog::switchAccount); + connect(ui->accounts, &QTreeView::customContextMenuRequested, this, &AccountSwitcherDialog::showContextMenu); + + connect(ui->btn_newAccount, &QPushButton::clicked, [this]{ + m_ctx->wallet->addSubaddressAccount("New account"); + m_ctx->wallet->subaddressAccount()->refresh(); + }); + + connect(m_ctx->wallet.get(), &Wallet::currentSubaddressAccountChanged, this, &AccountSwitcherDialog::updateSelection); + connect(m_ctx->wallet->subaddressAccount(), &SubaddressAccount::refreshFinished, this, &AccountSwitcherDialog::updateSelection); + + this->updateSelection(); +} + +void AccountSwitcherDialog::switchAccount() { + QModelIndex index = ui->accounts->currentIndex(); + m_ctx->wallet->switchSubaddressAccount(index.row()); +} + +void AccountSwitcherDialog::copyLabel() { + auto row = this->currentEntry(); + if (!row) + return; + + Utils::copyToClipboard(QString::fromStdString(row->getLabel())); +} + +void AccountSwitcherDialog::copyBalance() { + auto row = this->currentEntry(); + if (!row) + return; + + Utils::copyToClipboard(QString::fromStdString(row->getBalance())); +} + +void AccountSwitcherDialog::editLabel() { + QModelIndex index = ui->accounts->currentIndex().siblingAtColumn(m_ctx->wallet->subaddressAccountModel()->Column::Label); + ui->accounts->setCurrentIndex(index); + ui->accounts->edit(index); +} + +void AccountSwitcherDialog::updateSelection() { + qDebug() << "test"; + QModelIndex index = m_model->index(m_ctx->wallet->currentSubaddressAccount(), 0); + ui->accounts->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); +} + +void AccountSwitcherDialog::showContextMenu(const QPoint &point) { + QModelIndex index = ui->accounts->currentIndex(); + if (!index.isValid()) { + return; + } + + auto *menu = new QMenu(ui->accounts); + + menu->addAction("Copy label", this, &AccountSwitcherDialog::copyLabel); + menu->addAction("Copy balance", this, &AccountSwitcherDialog::copyBalance); + menu->addAction("Edit label", this, &AccountSwitcherDialog::editLabel); + + menu->popup(ui->accounts->viewport()->mapToGlobal(point)); +} + +Monero::SubaddressAccountRow* AccountSwitcherDialog::currentEntry() { + QModelIndex index = ui->accounts->currentIndex(); + return m_ctx->wallet->subaddressAccountModel()->entryFromIndex(index); +} + +AccountSwitcherDialog::~AccountSwitcherDialog() { + delete ui; +} diff --git a/src/dialog/AccountSwitcherDialog.h b/src/dialog/AccountSwitcherDialog.h new file mode 100644 index 00000000..077b3c1f --- /dev/null +++ b/src/dialog/AccountSwitcherDialog.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_ACCOUNTSWITCHERDIALOG_H +#define FEATHER_ACCOUNTSWITCHERDIALOG_H + +#include +#include "appcontext.h" + +#include "model/SubaddressAccountModel.h" + +namespace Ui { + class AccountSwitcherDialog; +} + +class AccountSwitcherDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AccountSwitcherDialog(QSharedPointer ctx, QWidget *parent = nullptr); + ~AccountSwitcherDialog() override; + +private slots: + void showContextMenu(const QPoint& point); + void updateSelection(); + +private: + void switchAccount(); + void copyLabel(); + void copyBalance(); + void editLabel(); + + Monero::SubaddressAccountRow* currentEntry(); + + Ui::AccountSwitcherDialog *ui; + QSharedPointer m_ctx; + SubaddressAccountModel *m_model; + SubaddressAccountProxyModel *m_proxyModel; +}; + +#endif //FEATHER_ACCOUNTSWITCHERDIALOG_H diff --git a/src/dialog/AccountSwitcherDialog.ui b/src/dialog/AccountSwitcherDialog.ui new file mode 100644 index 00000000..1b203cfd --- /dev/null +++ b/src/dialog/AccountSwitcherDialog.ui @@ -0,0 +1,115 @@ + + + AccountSwitcherDialog + + + + 0 + 0 + 521 + 347 + + + + Accounts + + + + + + Qt::NoFocus + + + false + + + false + + + + + + + + + Total balance: + + + + + + + + 0 + 0 + + + + TextLabel + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + + New account + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AccountSwitcherDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AccountSwitcherDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialog/CalcConfigDialog.cpp b/src/dialog/CalcConfigDialog.cpp new file mode 100644 index 00000000..319d376b --- /dev/null +++ b/src/dialog/CalcConfigDialog.cpp @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "CalcConfigDialog.h" +#include "ui_CalcConfigDialog.h" +#include "AppData.h" +#include "utils/config.h" + +CalcConfigDialog::CalcConfigDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::CalcConfigDialog) +{ + ui->setupUi(this); + + this->fillListWidgets(); + + connect(ui->btn_selectAll, &QPushButton::clicked, this, &CalcConfigDialog::selectAll); + 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()); + this->accept(); + }); + + this->adjustSize(); +} + +QStringList CalcConfigDialog::checkedFiat() { + return this->getChecked(ui->list_fiat); +} + +QStringList CalcConfigDialog::checkedCrypto() { + return this->getChecked(ui->list_crypto); +} + +void CalcConfigDialog::selectAll() { + this->setCheckState(this->getVisibleListWidget(), Qt::Checked); +} + +void CalcConfigDialog::deselectAll() { + this->setCheckState(this->getVisibleListWidget(), Qt::Unchecked); +} + +void CalcConfigDialog::setCheckState(QListWidget *widget, Qt::CheckState checkState) { + QListWidgetItem *item; + for (int i=0; i < widget->count(); i++) { + item = widget->item(i); + item->setCheckState(checkState); + } +} + +QStringList CalcConfigDialog::getChecked(QListWidget *widget) { + QStringList checked; + QListWidgetItem *item; + for (int i=0; i < widget->count(); i++) { + item = widget->item(i); + if (item->checkState() == Qt::Checked) { + checked.append(item->text()); + } + } + return checked; +} + +QListWidget* CalcConfigDialog::getVisibleListWidget() { + if (ui->tabWidget->currentIndex() == 0) { + return ui->list_fiat; + } else { + return ui->list_crypto; + } +} + +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(); + + ui->list_crypto->addItems(cryptoCurrencies); + ui->list_fiat->addItems(fiatCurrencies); + + auto setChecked = [](QListWidget *widget, const QStringList &checked){ + QListWidgetItem *item; + for (int i=0; i < widget->count(); i++) { + item = widget->item(i); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + if (checked.contains(item->text())) { + item->setCheckState(Qt::Checked); + } else { + item->setCheckState(Qt::Unchecked); + } + } + }; + + setChecked(ui->list_crypto, checkedCryptoCurrencies); + setChecked(ui->list_fiat, checkedFiatCurrencies); +} + +CalcConfigDialog::~CalcConfigDialog() { + delete ui; +} diff --git a/src/dialog/CalcConfigDialog.h b/src/dialog/CalcConfigDialog.h new file mode 100644 index 00000000..aceed432 --- /dev/null +++ b/src/dialog/CalcConfigDialog.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_CALCCONFIGDIALOG_H +#define FEATHER_CALCCONFIGDIALOG_H + +#include +#include + +namespace Ui { + class CalcConfigDialog; +} + +class CalcConfigDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CalcConfigDialog(QWidget *parent = nullptr); + ~CalcConfigDialog() override; + + QStringList checkedFiat(); + QStringList checkedCrypto(); + +private slots: + void selectAll(); + void deselectAll(); + +private: + void setCheckState(QListWidget *widget, Qt::CheckState checkState); + QStringList getChecked(QListWidget *widget); + void fillListWidgets(); + QListWidget* getVisibleListWidget(); + + Ui::CalcConfigDialog *ui; +}; + + +#endif //FEATHER_CALCCONFIGDIALOG_H diff --git a/src/dialog/CalcConfigDialog.ui b/src/dialog/CalcConfigDialog.ui new file mode 100644 index 00000000..d88daeeb --- /dev/null +++ b/src/dialog/CalcConfigDialog.ui @@ -0,0 +1,153 @@ + + + CalcConfigDialog + + + + 0 + 0 + 509 + 574 + + + + Calc config + + + + + + 0 + + + + Fiat + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Crypto + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + Qt::Vertical + + + + + + + + + Select all + + + + + + + Deselect all + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + CalcConfigDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CalcConfigDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/dialog/RestoreHeightDialog.cpp b/src/dialog/RestoreHeightDialog.cpp new file mode 100644 index 00000000..ca7cfda8 --- /dev/null +++ b/src/dialog/RestoreHeightDialog.cpp @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "RestoreHeightDialog.h" + +#include +#include +#include + +RestoreHeightDialog::RestoreHeightDialog(QWidget *parent, quint64 currentRestoreHeight) + : QDialog(parent) + , m_restoreHeightWidget(new RestoreHeightWidget(this)) +{ + auto *layout = new QVBoxLayout(this); + + auto *label = new QLabel(this); + label->setText("Enter a wallet creation date or restore height."); + + auto *buttonBox = new QDialogButtonBox(this); + buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); + + layout->addWidget(label); + layout->addWidget(m_restoreHeightWidget); + layout->addWidget(buttonBox); + + this->setLayout(layout); + + if (currentRestoreHeight) { + m_restoreHeightWidget->setHeight(currentRestoreHeight); + } + + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + this->adjustSize(); +} + +int RestoreHeightDialog::getHeight() { + return m_restoreHeightWidget->getHeight(); +} \ No newline at end of file diff --git a/src/dialog/RestoreHeightDialog.h b/src/dialog/RestoreHeightDialog.h new file mode 100644 index 00000000..16159c9d --- /dev/null +++ b/src/dialog/RestoreHeightDialog.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_RESTOREHEIGHTDIALOG_H +#define FEATHER_RESTOREHEIGHTDIALOG_H + +#include +#include "widgets/RestoreHeightWidget.h" + +class RestoreHeightDialog : public QDialog +{ +Q_OBJECT + +public: + explicit RestoreHeightDialog(QWidget *parent = nullptr, quint64 currentRestoreHeight = 0); + int getHeight(); + +private: + RestoreHeightWidget *m_restoreHeightWidget; +}; + + +#endif //FEATHER_RESTOREHEIGHTDIALOG_H diff --git a/src/dialog/TxProofDialog.cpp b/src/dialog/TxProofDialog.cpp index 811ddbc9..428a6970 100644 --- a/src/dialog/TxProofDialog.cpp +++ b/src/dialog/TxProofDialog.cpp @@ -8,16 +8,17 @@ #include "libwalletqt/Transfer.h" #include "utils/utils.h" +#include "utils/Icons.h" -TxProofDialog::TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *txInfo) - : QDialog(parent) - , ui(new Ui::TxProofDialog) - , m_wallet(wallet) +TxProofDialog::TxProofDialog(QWidget *parent, QSharedPointer ctx, TransactionInfo *txInfo) + : QDialog(parent) + , ui(new Ui::TxProofDialog) + , m_ctx(std::move(ctx)) { ui->setupUi(this); m_txid = txInfo->hash(); - m_txKey = m_wallet->getTxKey(m_txid); + m_txKey = m_ctx->wallet->getTxKey(m_txid); m_direction = txInfo->direction(); for (auto const &t: txInfo->transfers()) { @@ -25,7 +26,7 @@ TxProofDialog::TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *t } for (auto const &s: txInfo->subaddrIndex()) { - m_InDestinations.push_back(wallet->address(txInfo->subaddrAccount(), s)); + m_InDestinations.push_back(m_ctx->wallet->address(txInfo->subaddrAccount(), s)); } // Due to some logic in core we can't create OutProofs @@ -44,6 +45,10 @@ TxProofDialog::TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *t ui->radio_SpendProof->setChecked(true); ui->label_txid->setText(m_txid); + ui->btn_copyAddress->setIcon(icons()->icon("copy.png")); + connect(ui->btn_copyAddress, &QPushButton::clicked, [this]{ + Utils::copyToClipboard(ui->combo_address->currentText()); + }); ui->group_summary->hide(); // todo this->adjustSize(); @@ -127,7 +132,7 @@ void TxProofDialog::showWarning(const QString &message) { void TxProofDialog::getFormattedProof() { QString message = ui->message->toPlainText(); QString address = ui->combo_address->currentText(); - QString nettype = Utils::QtEnumToString(m_wallet->nettype()).toLower(); + QString nettype = Utils::QtEnumToString(m_ctx->wallet->nettype()).toLower(); nettype = nettype.replace(0, 1, nettype[0].toUpper()); // Capitalize first letter TxProof proof = this->getProof(); @@ -208,12 +213,12 @@ TxProof TxProofDialog::getProof() { TxProof proof = [this, message, address]{ switch (m_mode) { case Mode::SpendProof: { - return m_wallet->getSpendProof(m_txid, message); + return m_ctx->wallet->getSpendProof(m_txid, message); } case Mode::OutProof: case Mode::InProof: default: { // Todo: split this into separate functions - return m_wallet->getTxProof(m_txid, address, message); + return m_ctx->wallet->getTxProof(m_txid, address, message); } } }(); diff --git a/src/dialog/TxProofDialog.h b/src/dialog/TxProofDialog.h index 1ad53ce0..d0102480 100644 --- a/src/dialog/TxProofDialog.h +++ b/src/dialog/TxProofDialog.h @@ -6,8 +6,8 @@ #include -#include "libwalletqt/Wallet.h" #include "libwalletqt/TransactionInfo.h" +#include "appcontext.h" namespace Ui { class TxProofDialog; @@ -18,7 +18,7 @@ class TxProofDialog : public QDialog Q_OBJECT public: - explicit TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *txid); + explicit TxProofDialog(QWidget *parent, QSharedPointer ctx, TransactionInfo *txid); ~TxProofDialog() override; void setTxId(const QString &txid); @@ -50,7 +50,7 @@ private: TransactionInfo::Direction m_direction; Ui::TxProofDialog *ui; - Wallet *m_wallet; + QSharedPointer m_ctx; }; #endif //FEATHER_TXPROOFDIALOG_H diff --git a/src/dialog/TxProofDialog.ui b/src/dialog/TxProofDialog.ui index bd599b68..f71486aa 100644 --- a/src/dialog/TxProofDialog.ui +++ b/src/dialog/TxProofDialog.ui @@ -30,7 +30,7 @@ - Prove a payment to an address (OutProof) + Prove payment to an address (OutProof) @@ -226,7 +226,31 @@ - + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + Copy + + + + diff --git a/src/dialog/UpdateDialog.h b/src/dialog/UpdateDialog.h index 70fe3134..86d959a6 100644 --- a/src/dialog/UpdateDialog.h +++ b/src/dialog/UpdateDialog.h @@ -34,6 +34,8 @@ signals: private: void setStatus(const QString &msg, bool success = false); + Ui::UpdateDialog *ui; + QString m_version; QString m_downloadUrl; QString m_hash; @@ -44,8 +46,6 @@ private: std::string m_updateZipArchive; QNetworkReply *m_reply = nullptr; - - Ui::UpdateDialog *ui; }; #endif //FEATHER_UPDATEDIALOG_H diff --git a/src/dialog/WalletCacheDebugDialog.cpp b/src/dialog/WalletCacheDebugDialog.cpp index b3b9dc87..3f1d2d7c 100644 --- a/src/dialog/WalletCacheDebugDialog.cpp +++ b/src/dialog/WalletCacheDebugDialog.cpp @@ -7,81 +7,81 @@ #include -WalletCacheDebugDialog::WalletCacheDebugDialog(AppContext *ctx, QWidget *parent) +WalletCacheDebugDialog::WalletCacheDebugDialog(QSharedPointer ctx, QWidget *parent) : QDialog(parent) - , m_ctx(ctx) , ui(new Ui::WalletCacheDebugDialog) + , m_ctx(std::move(ctx)) { ui->setupUi(this); ui->output->setFont(ModelUtils::getMonospaceFont()); connect(ui->m_blockchain, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printBlockchain()); + this->setOutput(m_ctx->wallet->printBlockchain()); }); connect(ui->m_transfers, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printTransfers()); + this->setOutput(m_ctx->wallet->printTransfers()); }); connect(ui->m_unconfirmed_payments, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printUnconfirmedPayments()); + this->setOutput(m_ctx->wallet->printUnconfirmedPayments()); }); connect(ui->m_confirmed_txs, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printConfirmedTransferDetails()); + this->setOutput(m_ctx->wallet->printConfirmedTransferDetails()); }); connect(ui->m_unconfirmed_txs, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printUnconfirmedTransferDetails()); + this->setOutput(m_ctx->wallet->printUnconfirmedTransferDetails()); }); connect(ui->m_payments, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printPayments()); + this->setOutput(m_ctx->wallet->printPayments()); }); connect(ui->m_pub_keys, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printPubKeys()); + this->setOutput(m_ctx->wallet->printPubKeys()); }); connect(ui->m_tx_notes, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printTxNotes()); + this->setOutput(m_ctx->wallet->printTxNotes()); }); connect(ui->m_subaddresses, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printSubaddresses()); + this->setOutput(m_ctx->wallet->printSubaddresses()); }); connect(ui->m_subaddress_labels, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printSubaddressLabels()); + this->setOutput(m_ctx->wallet->printSubaddressLabels()); }); connect(ui->m_additional_tx_keys, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printAdditionalTxKeys()); + this->setOutput(m_ctx->wallet->printAdditionalTxKeys()); }); connect(ui->m_attributes, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printAttributes()); + this->setOutput(m_ctx->wallet->printAttributes()); }); connect(ui->m_key_images, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printKeyImages()); + this->setOutput(m_ctx->wallet->printKeyImages()); }); connect(ui->m_account_tags, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printAccountTags()); + this->setOutput(m_ctx->wallet->printAccountTags()); }); connect(ui->m_tx_keys, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printTxKeys()); + this->setOutput(m_ctx->wallet->printTxKeys()); }); connect(ui->m_address_book, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printAddressBook()); + this->setOutput(m_ctx->wallet->printAddressBook()); }); connect(ui->m_scanned_pool_txs, &QRadioButton::pressed, [this]{ - this->setOutput(m_ctx->currentWallet->printScannedPoolTxs()); + this->setOutput(m_ctx->wallet->printScannedPoolTxs()); }); this->adjustSize(); diff --git a/src/dialog/WalletCacheDebugDialog.h b/src/dialog/WalletCacheDebugDialog.h index 54a5f67b..e2ba3c72 100644 --- a/src/dialog/WalletCacheDebugDialog.h +++ b/src/dialog/WalletCacheDebugDialog.h @@ -16,13 +16,13 @@ class WalletCacheDebugDialog : public QDialog Q_OBJECT public: - explicit WalletCacheDebugDialog(AppContext *ctx, QWidget *parent = nullptr); + explicit WalletCacheDebugDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~WalletCacheDebugDialog() override; private: void setOutput(const QString &output); Ui::WalletCacheDebugDialog *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; }; diff --git a/src/dialog/aboutdialog.h b/src/dialog/aboutdialog.h index e7ce12cb..5f082906 100644 --- a/src/dialog/aboutdialog.h +++ b/src/dialog/aboutdialog.h @@ -21,8 +21,8 @@ public: ~AboutDialog() override; private: - QStringListModel *m_model; Ui::AboutDialog *ui; + QStringListModel *m_model; }; #endif // ABOUT_H diff --git a/src/dialog/balancedialog.ui b/src/dialog/balancedialog.ui index c267fea0..b09ed90f 100644 --- a/src/dialog/balancedialog.ui +++ b/src/dialog/balancedialog.ui @@ -11,7 +11,7 @@ - Balance + Account Balance diff --git a/src/dialog/broadcasttxdialog.cpp b/src/dialog/broadcasttxdialog.cpp index 9d1ed711..42b5d59a 100644 --- a/src/dialog/broadcasttxdialog.cpp +++ b/src/dialog/broadcasttxdialog.cpp @@ -7,14 +7,14 @@ #include -BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QString &transactionHex) +BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, QSharedPointer ctx, const QString &transactionHex) : QDialog(parent) - , m_ctx(ctx) , ui(new Ui::BroadcastTxDialog) + , m_ctx(std::move(ctx)) { ui->setupUi(this); - auto node = ctx->nodes->connection(); + auto node = m_ctx->nodes->connection(); m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress()); connect(ui->btn_Broadcast, &QPushButton::clicked, this, &BroadcastTxDialog::broadcastTx); @@ -32,19 +32,15 @@ BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QSt void BroadcastTxDialog::broadcastTx() { QString tx = ui->transaction->toPlainText(); - QString node = [this]{ - QString node; - if (ui->radio_useCustom->isChecked()) - node = ui->customNode->text(); - else if (ui->radio_useDefault->isChecked()) - node = m_ctx->nodes->connection().toAddress(); + FeatherNode node = ui->radio_useCustom->isChecked() ? FeatherNode(ui->customNode->text()) : m_ctx->nodes->connection(); - if (!node.startsWith("http://")) - node = QString("http://%1").arg(node); - return node; - }(); + if (node.isLocal()) { + m_rpc->setNetwork(getNetworkClearnet()); + } else { + m_rpc->setNetwork(getNetworkTor()); + } - m_rpc->setDaemonAddress(node); + m_rpc->setDaemonAddress(node.toURL()); m_rpc->sendRawTransaction(tx); } diff --git a/src/dialog/broadcasttxdialog.h b/src/dialog/broadcasttxdialog.h index bc2f5e38..0f32c7df 100644 --- a/src/dialog/broadcasttxdialog.h +++ b/src/dialog/broadcasttxdialog.h @@ -17,7 +17,7 @@ class BroadcastTxDialog : public QDialog Q_OBJECT public: - explicit BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QString &transactionHex = ""); + explicit BroadcastTxDialog(QWidget *parent, QSharedPointer ctx, const QString &transactionHex = ""); ~BroadcastTxDialog() override; private slots: @@ -26,8 +26,8 @@ private slots: private: Ui::BroadcastTxDialog *ui; + QSharedPointer m_ctx; UtilsNetworking *m_network; - AppContext *m_ctx; DaemonRpc *m_rpc; }; diff --git a/src/dialog/debuginfodialog.cpp b/src/dialog/debuginfodialog.cpp index 28544df1..f6371ad4 100644 --- a/src/dialog/debuginfodialog.cpp +++ b/src/dialog/debuginfodialog.cpp @@ -7,11 +7,12 @@ #include "utils/WebsocketClient.h" #include "utils/TorManager.h" #include "utils/WebsocketNotifier.h" +#include "utils/os/tails.h" -DebugInfoDialog::DebugInfoDialog(AppContext *ctx, QWidget *parent) +DebugInfoDialog::DebugInfoDialog(QSharedPointer ctx, QWidget *parent) : QDialog(parent) , ui(new Ui::DebugInfoDialog) - , m_ctx(ctx) + , m_ctx(std::move(ctx)) { ui->setupUi(this); @@ -28,7 +29,7 @@ void DebugInfoDialog::updateInfo() { QString torStatus; // Special case for Tails because we know the status of the daemon by polling tails-tor-has-bootstrapped.target - if(m_ctx->isTails) { + if (TailsOS::detect()) { if(torManager()->torConnected) torStatus = "Connected"; else @@ -46,32 +47,33 @@ void DebugInfoDialog::updateInfo() { ui->label_featherVersion->setText(QString("%1-%2").arg(FEATHER_VERSION, FEATHER_BRANCH)); ui->label_moneroVersion->setText(QString("%1-%2").arg(MONERO_VERSION, MONERO_BRANCH)); - ui->label_walletHeight->setText(QString::number(m_ctx->currentWallet->blockChainHeight())); - ui->label_daemonHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainHeight())); - ui->label_targetHeight->setText(QString::number(m_ctx->currentWallet->daemonBlockChainTargetHeight())); - ui->label_restoreHeight->setText(QString::number(m_ctx->currentWallet->getWalletCreationHeight())); - ui->label_synchronized->setText(m_ctx->currentWallet->isSynchronized() ? "True" : "False"); + ui->label_walletHeight->setText(QString::number(m_ctx->wallet->blockChainHeight())); + ui->label_daemonHeight->setText(QString::number(m_ctx->wallet->daemonBlockChainHeight())); + ui->label_targetHeight->setText(QString::number(m_ctx->wallet->daemonBlockChainTargetHeight())); + QDateTime restoreDate = appData()->restoreHeights[constants::networkType]->heightToDate(m_ctx->wallet->getWalletCreationHeight()); + ui->label_restoreHeight->setText(QString("%1 (%2)").arg(QString::number(m_ctx->wallet->getWalletCreationHeight()), restoreDate.toString("yyyy-MM-dd"))); + ui->label_synchronized->setText(m_ctx->wallet->isSynchronized() ? "True" : "False"); auto node = m_ctx->nodes->connection(); ui->label_remoteNode->setText(node.toAddress()); - ui->label_walletStatus->setText(this->statusToString(m_ctx->currentWallet->connectionStatus())); + ui->label_walletStatus->setText(this->statusToString(m_ctx->wallet->connectionStatus())); ui->label_torStatus->setText(torStatus); ui->label_websocketStatus->setText(Utils::QtEnumToString(websocketNotifier()->websocketClient.webSocket.state()).remove("State")); QString seedType = [this](){ - if (m_ctx->currentWallet->isHwBacked()) + if (m_ctx->wallet->isHwBacked()) return "Hardware"; - if (m_ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty()) + if (m_ctx->wallet->getCacheAttribute("feather.seed").isEmpty()) return "25 word"; else return "14 word"; }(); QString deviceType = [this](){ - if (m_ctx->currentWallet->isHwBacked()) { - if (m_ctx->currentWallet->isLedger()) + if (m_ctx->wallet->isHwBacked()) { + if (m_ctx->wallet->isLedger()) return "Ledger"; - else if (m_ctx->currentWallet->isTrezor()) + else if (m_ctx->wallet->isTrezor()) return "Trezor"; else return "Unknown"; @@ -81,17 +83,17 @@ void DebugInfoDialog::updateInfo() { } }(); - ui->label_netType->setText(Utils::QtEnumToString(m_ctx->currentWallet->nettype())); + ui->label_netType->setText(Utils::QtEnumToString(m_ctx->wallet->nettype())); ui->label_seedType->setText(seedType); ui->label_deviceType->setText(deviceType); - ui->label_viewOnly->setText(m_ctx->currentWallet->viewOnly() ? "True" : "False"); - ui->label_primaryOnly->setText(m_ctx->currentWallet->balance(0) == m_ctx->currentWallet->balanceAll() ? "True" : "False"); + ui->label_viewOnly->setText(m_ctx->wallet->viewOnly() ? "True" : "False"); + ui->label_primaryOnly->setText(m_ctx->wallet->balance(0) == m_ctx->wallet->balanceAll() ? "True" : "False"); QString os = QSysInfo::prettyProductName(); - if (m_ctx->isTails) { + if (TailsOS::detect()) { os = QString("Tails %1").arg(TailsOS::version()); } - if (m_ctx->isWhonix) { + if (WhonixOS::detect()) { os = QString("Whonix %1").arg(WhonixOS::version()); } ui->label_OS->setText(os); diff --git a/src/dialog/debuginfodialog.h b/src/dialog/debuginfodialog.h index 1f9821be..80a53918 100644 --- a/src/dialog/debuginfodialog.h +++ b/src/dialog/debuginfodialog.h @@ -17,7 +17,7 @@ class DebugInfoDialog : public QDialog Q_OBJECT public: - explicit DebugInfoDialog(AppContext *ctx, QWidget *parent = nullptr); + explicit DebugInfoDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~DebugInfoDialog() override; private: @@ -25,10 +25,10 @@ private: void copyToClipboad(); void updateInfo(); - QTimer m_updateTimer; - AppContext *m_ctx; - Ui::DebugInfoDialog *ui; + QSharedPointer m_ctx; + + QTimer m_updateTimer; }; #endif //FEATHER_DEBUGINFODIALOG_H diff --git a/src/dialog/keysdialog.cpp b/src/dialog/keysdialog.cpp index 73533495..56184814 100644 --- a/src/dialog/keysdialog.cpp +++ b/src/dialog/keysdialog.cpp @@ -4,21 +4,20 @@ #include "keysdialog.h" #include "ui_keysdialog.h" -KeysDialog::KeysDialog(AppContext *ctx, QWidget *parent) - : QDialog(parent) - , ui(new Ui::KeysDialog) +KeysDialog::KeysDialog(QSharedPointer ctx, QWidget *parent) + : QDialog(parent) + , ui(new Ui::KeysDialog) { ui->setupUi(this); - auto w = ctx->currentWallet; QString unavailable = "Unavailable: Key is stored on hardware device"; - ui->label_restoreHeight->setText(QString::number(w->getWalletCreationHeight())); - ui->label_primaryAddress->setText(w->address(0, 0)); - ui->label_secretSpendKey->setText(w->isHwBacked() ? unavailable : w->getSecretSpendKey()); - ui->label_secretViewKey->setText(w->getSecretViewKey()); - ui->label_publicSpendKey->setText(w->getPublicSpendKey()); - ui->label_publicViewKey->setText(w->getPublicViewKey()); + ui->label_restoreHeight->setText(QString::number(ctx->wallet->getWalletCreationHeight())); + ui->label_primaryAddress->setText(ctx->wallet->address(0, 0)); + ui->label_secretSpendKey->setText(ctx->wallet->isHwBacked() ? unavailable : ctx->wallet->getSecretSpendKey()); + ui->label_secretViewKey->setText(ctx->wallet->getSecretViewKey()); + ui->label_publicSpendKey->setText(ctx->wallet->getPublicSpendKey()); + ui->label_publicViewKey->setText(ctx->wallet->getPublicViewKey()); this->adjustSize(); } diff --git a/src/dialog/keysdialog.h b/src/dialog/keysdialog.h index e0fe0c0e..84df75d5 100644 --- a/src/dialog/keysdialog.h +++ b/src/dialog/keysdialog.h @@ -16,7 +16,7 @@ class KeysDialog : public QDialog Q_OBJECT public: - explicit KeysDialog(AppContext *ctx, QWidget *parent = nullptr); + explicit KeysDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~KeysDialog() override; private: diff --git a/src/dialog/passworddialog.cpp b/src/dialog/passworddialog.cpp index 7f7d0f73..284c47f2 100644 --- a/src/dialog/passworddialog.cpp +++ b/src/dialog/passworddialog.cpp @@ -3,13 +3,15 @@ #include "passworddialog.h" #include "ui_passworddialog.h" +#include "utils/Icons.h" -PasswordDialog::PasswordDialog(QWidget *parent, const QString &walletName, bool incorrectPassword) +PasswordDialog::PasswordDialog(const QString &walletName, bool incorrectPassword, QWidget *parent) : QDialog(parent) , ui(new Ui::PasswordDialog) { ui->setupUi(this); + this->setWindowIcon(icons()->icon("appicons/64x64.png")); ui->label_wallet->setText(QString("Please enter password for wallet: %1").arg(walletName)); ui->label_incorrectPassword->setVisible(incorrectPassword); diff --git a/src/dialog/passworddialog.h b/src/dialog/passworddialog.h index 060de72a..a204cea1 100644 --- a/src/dialog/passworddialog.h +++ b/src/dialog/passworddialog.h @@ -15,7 +15,7 @@ class PasswordDialog : public QDialog Q_OBJECT public: - explicit PasswordDialog(QWidget *parent, const QString &walletName, bool incorrectPassword); + explicit PasswordDialog(const QString &walletName, bool incorrectPassword, QWidget *parent = nullptr); ~PasswordDialog() override; QString password = ""; diff --git a/src/dialog/restoredialog.cpp b/src/dialog/restoredialog.cpp deleted file mode 100644 index 49edce02..00000000 --- a/src/dialog/restoredialog.cpp +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include "restoredialog.h" -#include "ui_restoredialog.h" - -RestoreDialog::RestoreDialog(AppContext *ctx, QWidget *parent) - : QDialog(parent) - , ui(new Ui::RestoreDialog) - , m_ctx(ctx) -{ - ui->setupUi(this); - this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png")); - connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &RestoreDialog::accepted); - connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &RestoreDialog::rejected); - - if(m_ctx->networkType == NetworkType::Type::TESTNET) { - ui->restoreHeightWidget->hideSlider(); - } else { - // load restoreHeight lookup db - ui->restoreHeightWidget->initRestoreHeights(appData()->restoreHeights[m_ctx->networkType]); - } -} - -int RestoreDialog::getHeight() { - return ui->restoreHeightWidget->getHeight(); -} - -void RestoreDialog::initRestoreHeights(RestoreHeightLookup *lookup) { - ui->restoreHeightWidget->initRestoreHeights(lookup); -} - -RestoreDialog::~RestoreDialog() { - delete ui; -} - diff --git a/src/dialog/restoredialog.h b/src/dialog/restoredialog.h deleted file mode 100644 index 065cc907..00000000 --- a/src/dialog/restoredialog.h +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#ifndef RESTOREDIALOG_H -#define RESTOREDIALOG_H - -#include -#include -#include -#include -#include - -#include "utils/RestoreHeightLookup.h" -#include "appcontext.h" - -namespace Ui { - class RestoreDialog; -} - -class RestoreDialog : public QDialog -{ -Q_OBJECT - -public: - explicit RestoreDialog(AppContext *ctx, QWidget *parent = nullptr); - void initRestoreHeights(RestoreHeightLookup *lookup); - int getHeight(); - ~RestoreDialog() override; - -signals: - void accepted(); - void rejected(); - -private: - AppContext *m_ctx; - Ui::RestoreDialog *ui; -}; - -#endif // RESTOREDIALOG_H diff --git a/src/dialog/restoredialog.ui b/src/dialog/restoredialog.ui deleted file mode 100644 index 5c914bb6..00000000 --- a/src/dialog/restoredialog.ui +++ /dev/null @@ -1,56 +0,0 @@ - - - RestoreDialog - - - - 0 - 0 - 584 - 99 - - - - specify restore height - - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - RestoreHeightWidget - QWidget -
widgets/restoreheightwidget.h
- 1 -
-
- - -
diff --git a/src/dialog/seeddialog.cpp b/src/dialog/seeddialog.cpp index 63f46a26..ca8405d2 100644 --- a/src/dialog/seeddialog.cpp +++ b/src/dialog/seeddialog.cpp @@ -3,19 +3,26 @@ #include "ui_seeddialog.h" #include "seeddialog.h" +#include "constants.h" -SeedDialog::SeedDialog(Wallet *wallet, QWidget *parent) - : QDialog(parent) - , ui(new Ui::SeedDialog) +SeedDialog::SeedDialog(QSharedPointer ctx, QWidget *parent) + : QDialog(parent) + , ui(new Ui::SeedDialog) + , m_ctx(std::move(ctx)) { ui->setupUi(this); ui->label_seedIcon->setPixmap(QPixmap(":/assets/images/seed.png").scaledToWidth(64, Qt::SmoothTransformation)); - ui->label_restoreHeight->setText(QString::number(wallet->getWalletCreationHeight())); + ui->label_restoreHeight->setText(QString::number(m_ctx->wallet->getWalletCreationHeight())); - QString seedOffset = wallet->getCacheAttribute("feather.seedoffset"); - QString seed_14_words = wallet->getCacheAttribute("feather.seed"); - QString seed_25_words = wallet->getSeed(seedOffset); + if (m_ctx->wallet->getSeedLanguage().isEmpty()) { + qDebug() << "No seed language set, using default"; + m_ctx->wallet->setSeedLanguage(constants::seedLanguage); + } + + QString seedOffset = m_ctx->wallet->getCacheAttribute("feather.seedoffset"); + QString seed_14_words = m_ctx->wallet->getCacheAttribute("feather.seed"); + QString seed_25_words = m_ctx->wallet->getSeed(seedOffset); if (seed_14_words.isEmpty()) { ui->check_toggleSeedType->hide(); diff --git a/src/dialog/seeddialog.h b/src/dialog/seeddialog.h index 52793e46..b1e211b3 100644 --- a/src/dialog/seeddialog.h +++ b/src/dialog/seeddialog.h @@ -5,7 +5,7 @@ #define FEATHER_SEEDDIALOG_H #include -#include "libwalletqt/Wallet.h" +#include "appcontext.h" namespace Ui { class SeedDialog; @@ -16,13 +16,14 @@ class SeedDialog : public QDialog Q_OBJECT public: - explicit SeedDialog(Wallet *wallet, QWidget *parent = nullptr); + explicit SeedDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~SeedDialog() override; private: void setSeed(const QString &seed); Ui::SeedDialog *ui; + QSharedPointer m_ctx; }; diff --git a/src/dialog/signverifydialog.cpp b/src/dialog/signverifydialog.cpp index f60dbdb8..283c33b4 100644 --- a/src/dialog/signverifydialog.cpp +++ b/src/dialog/signverifydialog.cpp @@ -9,8 +9,8 @@ SignVerifyDialog::SignVerifyDialog(Wallet *wallet, QWidget *parent) : QDialog(parent) - , m_wallet(wallet) , ui(new Ui::SignVerifyDialog) + , m_wallet(wallet) { ui->setupUi(this); diff --git a/src/dialog/splashdialog.cpp b/src/dialog/splashdialog.cpp index ded389f3..06b2481e 100644 --- a/src/dialog/splashdialog.cpp +++ b/src/dialog/splashdialog.cpp @@ -3,6 +3,7 @@ #include "splashdialog.h" #include "ui_splashdialog.h" +#include "utils/Icons.h" SplashDialog::SplashDialog(QWidget *parent) : QDialog(parent) @@ -10,6 +11,8 @@ SplashDialog::SplashDialog(QWidget *parent) { ui->setupUi(this); + this->setWindowIcon(icons()->icon("appicon/64x64")); + QPixmap pixmap = QPixmap(":/assets/images/key.png"); ui->icon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation)); diff --git a/src/dialog/torinfodialog.cpp b/src/dialog/torinfodialog.cpp index 48528765..59ebbf56 100644 --- a/src/dialog/torinfodialog.cpp +++ b/src/dialog/torinfodialog.cpp @@ -7,13 +7,16 @@ #include #include #include +#include #include "utils/TorManager.h" +#include "utils/os/tails.h" +#include "utils/Icons.h" -TorInfoDialog::TorInfoDialog(QWidget *parent, AppContext *ctx) +TorInfoDialog::TorInfoDialog(QSharedPointer ctx, QWidget *parent) : QDialog(parent) , ui(new Ui::TorInfoDialog) - , m_ctx(ctx) + , m_ctx(std::move(ctx)) { ui->setupUi(this); @@ -45,6 +48,9 @@ TorInfoDialog::TorInfoDialog(QWidget *parent, AppContext *ctx) ui->label_changes->hide(); + ui->btn_configureInitSync->setIcon(icons()->icon("preferences.svg")); + connect(ui->btn_configureInitSync, &QPushButton::clicked, this, &TorInfoDialog::onShowInitSyncConfigDialog); + #ifndef HAS_TOR_BIN ui->check_useLocalTor->setChecked(true); ui->check_useLocalTor->setEnabled(false); @@ -118,7 +124,7 @@ void TorInfoDialog::initPrivacyLevel() { } if (m_ctx->nodes->connection().isLocal()) { - ui->label_notice->setText("You are connected to a local node. Traffic is not routed over Tor."); + ui->label_notice->setText("You are connected to a local node. Traffic to node is not routed over Tor."); } else if (Utils::isTorsocks()) { ui->label_notice->setText("Feather was started with torsocks, all traffic is routed over Tor"); @@ -145,6 +151,20 @@ void TorInfoDialog::onStopTor() { torManager()->stop(); } +void TorInfoDialog::onShowInitSyncConfigDialog() { + + int threshold = config()->get(Config::initSyncThreshold).toInt(); + + bool ok; + int newThreshold = QInputDialog::getInt(this, "Sync threshold", + "Synchronize over clearnet if wallet is behind more than x blocks: ", + threshold, 0, 10000, 10, &ok); + + if (ok) { + config()->set(Config::initSyncThreshold, newThreshold); + } +} + TorInfoDialog::~TorInfoDialog() { delete ui; } diff --git a/src/dialog/torinfodialog.h b/src/dialog/torinfodialog.h index 60b5b864..3b3847ed 100644 --- a/src/dialog/torinfodialog.h +++ b/src/dialog/torinfodialog.h @@ -18,7 +18,7 @@ class TorInfoDialog : public QDialog Q_OBJECT public: - explicit TorInfoDialog(QWidget *parent, AppContext *ctx); + explicit TorInfoDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~TorInfoDialog() override; public slots: @@ -29,6 +29,7 @@ private slots: void onApplySettings(); void onSettingsChanged(); void onStopTor(); + void onShowInitSyncConfigDialog(); signals: void torSettingsChanged(); @@ -38,7 +39,7 @@ private: void initPrivacyLevel(); Ui::TorInfoDialog *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; }; diff --git a/src/dialog/torinfodialog.ui b/src/dialog/torinfodialog.ui index a71c1093..ffce23df 100644 --- a/src/dialog/torinfodialog.ui +++ b/src/dialog/torinfodialog.ui @@ -11,7 +11,7 @@ - Tor information + Tor settings @@ -157,6 +157,19 @@ + + + + + 0 + 0 + + + + + + +
diff --git a/src/dialog/transactioninfodialog.cpp b/src/dialog/transactioninfodialog.cpp index 048b3fbf..b4d0adcb 100644 --- a/src/dialog/transactioninfodialog.cpp +++ b/src/dialog/transactioninfodialog.cpp @@ -17,18 +17,18 @@ #include #include -TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent) - : QDialog(parent) - , ui(new Ui::TransactionInfoDialog) - , m_wallet(wallet) - , m_txInfo(txInfo) +TransactionInfoDialog::TransactionInfoDialog(QSharedPointer ctx, TransactionInfo *txInfo, QWidget *parent) + : QDialog(parent) + , ui(new Ui::TransactionInfoDialog) + , m_ctx(std::move(ctx)) + , m_txInfo(txInfo) { ui->setupUi(this); m_txid = txInfo->hash(); ui->label_txid->setText(m_txid); - m_txKey = m_wallet->getTxKey(txInfo->hash()); + m_txKey = m_ctx->wallet->getTxKey(txInfo->hash()); if (m_txKey.isEmpty()) { ui->btn_CopyTxKey->setEnabled(false); ui->btn_CopyTxKey->setToolTip("Transaction key unknown"); @@ -37,11 +37,11 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TransactionInfoDialog::copyTxKey); connect(ui->btn_createTxProof, &QPushButton::pressed, this, &TransactionInfoDialog::createTxProof); - connect(m_wallet, &Wallet::newBlock, this, &TransactionInfoDialog::updateData); + connect(m_ctx->wallet.get(), &Wallet::newBlock, this, &TransactionInfoDialog::updateData); this->setData(txInfo); - if (AppContext::txCache.contains(txInfo->hash()) && (txInfo->isFailed() || txInfo->isPending()) && txInfo->direction() != TransactionInfo::Direction_In) { + if (m_ctx->txCache.contains(txInfo->hash()) && (txInfo->isFailed() || txInfo->isPending()) && txInfo->direction() != TransactionInfo::Direction_In) { connect(ui->btn_rebroadcastTx, &QPushButton::pressed, [this]{ emit resendTranscation(m_txid); }); @@ -53,7 +53,7 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx for (const auto& transfer : txInfo->transfers()) { auto address = transfer->address(); auto amount = WalletManager::displayAmount(transfer->amount()); - auto index = m_wallet->subaddressIndex(address); + auto index = m_ctx->wallet->subaddressIndex(address); cursor.insertText(address, Utils::addressTextFormat(index)); cursor.insertText(QString(" %1").arg(amount), QTextCharFormat()); cursor.insertBlock(); @@ -63,7 +63,7 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx ui->frameDestinations->hide(); } - m_txProofDialog = new TxProofDialog(this, m_wallet, txInfo); + m_txProofDialog = new TxProofDialog(this, m_ctx, txInfo); QCoreApplication::processEvents(); @@ -104,7 +104,7 @@ void TransactionInfoDialog::setData(TransactionInfo* tx) { } QString direction = tx->direction() == TransactionInfo::Direction_In ? "received" : "sent"; - ui->label_amount->setText(QString("Amount %1: %2").arg(direction, tx->displayAmount())); + ui->label_amount->setText(QString("Amount %1: %2 XMR").arg(direction, tx->displayAmount())); QString fee; if (tx->isCoinbase()) @@ -121,8 +121,7 @@ void TransactionInfoDialog::setData(TransactionInfo* tx) { } void TransactionInfoDialog::updateData() { - if (!m_wallet) return; - TransactionInfo* tx = m_wallet->history()->transaction(m_txid); + TransactionInfo* tx = m_ctx->wallet->history()->transaction(m_txid); if (!tx) return; this->setData(tx); } diff --git a/src/dialog/transactioninfodialog.h b/src/dialog/transactioninfodialog.h index 02908dc6..6cb2960d 100644 --- a/src/dialog/transactioninfodialog.h +++ b/src/dialog/transactioninfodialog.h @@ -7,9 +7,7 @@ #include #include #include -#include "libwalletqt/Coins.h" -#include "libwalletqt/TransactionInfo.h" -#include "libwalletqt/Wallet.h" +#include "appcontext.h" #include "dialog/TxProofDialog.h" namespace Ui { @@ -21,7 +19,7 @@ class TransactionInfoDialog : public QDialog Q_OBJECT public: - explicit TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent = nullptr); + explicit TransactionInfoDialog(QSharedPointer ctx, TransactionInfo *txInfo, QWidget *parent = nullptr); ~TransactionInfoDialog() override; signals: @@ -35,9 +33,9 @@ private: Ui::TransactionInfoDialog *ui; - TxProofDialog *m_txProofDialog; + QSharedPointer m_ctx; TransactionInfo *m_txInfo; - Wallet *m_wallet; + TxProofDialog *m_txProofDialog; QString m_txKey; QString m_txid; QTimer m_updateTimer; diff --git a/src/dialog/txconfadvdialog.cpp b/src/dialog/txconfadvdialog.cpp index 98fbc8d6..2f27c557 100644 --- a/src/dialog/txconfadvdialog.cpp +++ b/src/dialog/txconfadvdialog.cpp @@ -12,12 +12,12 @@ #include #include -TxConfAdvDialog::TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent) - : QDialog(parent) - , ui(new Ui::TxConfAdvDialog) - , m_ctx(ctx) - , m_exportUnsignedMenu(new QMenu(this)) - , m_exportSignedMenu(new QMenu(this)) +TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString &description, QWidget *parent) + : QDialog(parent) + , ui(new Ui::TxConfAdvDialog) + , m_ctx(std::move(ctx)) + , m_exportUnsignedMenu(new QMenu(this)) + , m_exportSignedMenu(new QMenu(this)) { ui->setupUi(this); @@ -30,7 +30,7 @@ TxConfAdvDialog::TxConfAdvDialog(AppContext *ctx, const QString &description, QW m_exportSignedMenu->addAction("Save to file", this, &TxConfAdvDialog::signedSaveFile); ui->btn_exportSigned->setMenu(m_exportSignedMenu); - if (m_ctx->currentWallet->viewOnly()) { + if (m_ctx->wallet->viewOnly()) { ui->btn_exportSigned->hide(); ui->btn_send->hide(); } @@ -61,7 +61,7 @@ void TxConfAdvDialog::setTransaction(PendingTransaction *tx) { ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee())); auto size_str = [this]{ - if (m_ctx->currentWallet->viewOnly()) { + if (m_ctx->wallet->viewOnly()) { return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); } else { auto size = m_tx->signedTxToHex(0).size() / 2; @@ -108,7 +108,7 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { for (const auto& o: outputs) { auto address = o->address(); auto amount = WalletManager::displayAmount(o->amount()); - auto index = m_ctx->currentWallet->subaddressIndex(address); + auto index = m_ctx->wallet->subaddressIndex(address); cursor.insertText(address, Utils::addressTextFormat(index)); cursor.insertText(QString(" %1").arg(amount), QTextCharFormat()); cursor.insertBlock(); @@ -177,9 +177,9 @@ void TxConfAdvDialog::broadcastTransaction() { void TxConfAdvDialog::closeDialog() { if (m_tx != nullptr) - m_ctx->currentWallet->disposeTransaction(m_tx); + m_ctx->wallet->disposeTransaction(m_tx); if (m_utx != nullptr) - m_ctx->currentWallet->disposeTransaction(m_utx); + m_ctx->wallet->disposeTransaction(m_utx); QDialog::reject(); } diff --git a/src/dialog/txconfadvdialog.h b/src/dialog/txconfadvdialog.h index bd687972..e59a7666 100644 --- a/src/dialog/txconfadvdialog.h +++ b/src/dialog/txconfadvdialog.h @@ -22,7 +22,7 @@ class TxConfAdvDialog : public QDialog Q_OBJECT public: - explicit TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent = nullptr); + explicit TxConfAdvDialog(QSharedPointer ctx, const QString &description, QWidget *parent = nullptr); ~TxConfAdvDialog() override; void setTransaction(PendingTransaction *tx); @@ -43,7 +43,7 @@ private: void signedSaveFile(); Ui::TxConfAdvDialog *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; PendingTransaction *m_tx = nullptr; UnsignedTransaction *m_utx = nullptr; QMenu *m_exportUnsignedMenu; diff --git a/src/dialog/txconfdialog.cpp b/src/dialog/txconfdialog.cpp index 47d959a2..dbf9f236 100644 --- a/src/dialog/txconfdialog.cpp +++ b/src/dialog/txconfdialog.cpp @@ -5,16 +5,16 @@ #include "ui_txconfdialog.h" #include "model/ModelUtils.h" #include "txconfadvdialog.h" -#include "globals.h" +#include "constants.h" #include "utils/AppData.h" #include "utils/ColorScheme.h" #include -TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent) +TxConfDialog::TxConfDialog(QSharedPointer ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent) : QDialog(parent) , ui(new Ui::TxConfDialog) - , m_ctx(ctx) + , m_ctx(std::move(ctx)) , m_tx(tx) , m_address(address) , m_description(description) @@ -37,9 +37,9 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin int maxLength = Utils::maxLength(amounts); std::for_each(amounts.begin(), amounts.end(), [maxLength](QString& amount){amount = amount.rightJustified(maxLength, ' ');}); - QString amount_fiat = convert(tx->amount() / globals::cdiv); - QString fee_fiat = convert(tx->fee() / globals::cdiv); - QString total_fiat = convert((tx->amount() + tx->fee()) / globals::cdiv); + QString amount_fiat = convert(tx->amount() / constants::cdiv); + QString fee_fiat = convert(tx->fee() / constants::cdiv); + QString total_fiat = convert((tx->amount() + tx->fee()) / constants::cdiv); QVector amounts_fiat = {amount_fiat, fee_fiat, total_fiat}; int maxLengthFiat = Utils::maxLength(amounts_fiat); std::for_each(amounts_fiat.begin(), amounts_fiat.end(), [maxLengthFiat](QString& amount){amount = amount.rightJustified(maxLengthFiat, ' ');}); @@ -52,7 +52,7 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin ui->label_fee->setText(QString("%1 (%2 %3)").arg(amounts[1], amounts_fiat[1], preferredCur)); ui->label_total->setText(QString("%1 (%2 %3)").arg(amounts[2], amounts_fiat[2], preferredCur)); - auto subaddressIndex = m_ctx->currentWallet->subaddressIndex(address); + auto subaddressIndex = m_ctx->wallet->subaddressIndex(address); QString addressExtra; ui->label_address->setText(ModelUtils::displayAddress(address, 2)); @@ -76,7 +76,7 @@ TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QStrin connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::setShowAdvanced); - AppContext::txCache[tx->txid()[0]] = tx->signedTxToHex(0); + m_ctx->txCache[tx->txid()[0]] = tx->signedTxToHex(0); this->adjustSize(); } diff --git a/src/dialog/txconfdialog.h b/src/dialog/txconfdialog.h index 0fdd95a1..1057df88 100644 --- a/src/dialog/txconfdialog.h +++ b/src/dialog/txconfdialog.h @@ -18,7 +18,7 @@ class TxConfDialog : public QDialog Q_OBJECT public: - explicit TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent = nullptr); + explicit TxConfDialog(QSharedPointer ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent = nullptr); ~TxConfDialog() override; bool showAdvanced = false; @@ -27,7 +27,7 @@ private: void setShowAdvanced(); Ui::TxConfDialog *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; PendingTransaction *m_tx; QString m_address; QString m_description; diff --git a/src/dialog/tximportdialog.cpp b/src/dialog/tximportdialog.cpp index 2dcc5f97..a2562484 100644 --- a/src/dialog/tximportdialog.cpp +++ b/src/dialog/tximportdialog.cpp @@ -7,17 +7,17 @@ #include -TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx) +TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer ctx) : QDialog(parent) - , m_ctx(ctx) - , m_loadTimer(new QTimer(this)) , ui(new Ui::TxImportDialog) + , m_ctx(std::move(ctx)) + , m_loadTimer(new QTimer(this)) { ui->setupUi(this); ui->resp->hide(); ui->label_loading->hide(); - auto node = ctx->nodes->connection(); + auto node = m_ctx->nodes->connection(); m_rpc = new DaemonRpc(this, getNetworkTor(), node.toAddress()); connect(ui->btn_load, &QPushButton::clicked, this, &TxImportDialog::loadTx); @@ -34,12 +34,16 @@ TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx) void TxImportDialog::loadTx() { QString txid = ui->line_txid->text(); - QString node = m_ctx->nodes->connection().toAddress(); + FeatherNode node = m_ctx->nodes->connection(); - if (!node.startsWith("http://")) - node = QString("http://%1").arg(node); + if (node.isLocal()) { + m_rpc->setNetwork(getNetworkClearnet()); + } else { + m_rpc->setNetwork(getNetworkTor()); + } - m_rpc->setDaemonAddress(node); + qDebug() << node.toURL(); + m_rpc->setDaemonAddress(node.toURL()); m_rpc->getTransactions(QStringList() << txid, false, true); ui->label_loading->setText("Loading transaction"); @@ -88,7 +92,7 @@ void TxImportDialog::onImport() { bool pool = tx.value("in_pool").toBool(); bool double_spend_seen = tx.value("double_spend_seen").toBool(); - if (m_ctx->currentWallet->importTransaction(tx.value("tx_hash").toString(), output_indices, height, timestamp, false, pool, double_spend_seen)) { + if (m_ctx->wallet->importTransaction(tx.value("tx_hash").toString(), output_indices, height, timestamp, false, pool, double_spend_seen)) { QMessageBox::information(this, "Import transaction", "Transaction imported successfully."); } else { QMessageBox::warning(this, "Import transaction", "Transaction import failed."); diff --git a/src/dialog/tximportdialog.h b/src/dialog/tximportdialog.h index 13294f40..6b6992c9 100644 --- a/src/dialog/tximportdialog.h +++ b/src/dialog/tximportdialog.h @@ -17,7 +17,7 @@ class TxImportDialog : public QDialog Q_OBJECT public: - explicit TxImportDialog(QWidget *parent, AppContext *ctx); + explicit TxImportDialog(QWidget *parent, QSharedPointer ctx); ~TxImportDialog() override; private slots: @@ -27,8 +27,9 @@ private slots: private: Ui::TxImportDialog *ui; + QSharedPointer m_ctx; + UtilsNetworking *m_network; - AppContext *m_ctx; DaemonRpc *m_rpc; QTimer *m_loadTimer; diff --git a/src/dialog/verifyproofdialog.cpp b/src/dialog/verifyproofdialog.cpp index 77493ed8..ecabd65e 100644 --- a/src/dialog/verifyproofdialog.cpp +++ b/src/dialog/verifyproofdialog.cpp @@ -6,13 +6,14 @@ #include "libwalletqt/WalletManager.h" #include "utils/utils.h" +#include "model/ModelUtils.h" #include VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) : QDialog(parent) - , m_wallet(wallet) , ui(new Ui::VerifyProofDialog) + , m_wallet(wallet) { ui->setupUi(this); @@ -48,6 +49,8 @@ VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) break; } }); + + ui->input_formattedProof->setFont(ModelUtils::getMonospaceFont()); } VerifyProofDialog::~VerifyProofDialog() diff --git a/src/dialog/verifyproofdialog.h b/src/dialog/verifyproofdialog.h index 89e44e33..9159decb 100644 --- a/src/dialog/verifyproofdialog.h +++ b/src/dialog/verifyproofdialog.h @@ -31,11 +31,11 @@ private: void checkFormattedProof(); void proofStatus(bool success, const QString &message); - QPixmap m_success; - QPixmap m_failure; - Ui::VerifyProofDialog *ui; Wallet *m_wallet; + + QPixmap m_success; + QPixmap m_failure; }; #endif //FEATHER_VERIFYPROOFDIALOG_H diff --git a/src/dialog/viewonlydialog.cpp b/src/dialog/viewonlydialog.cpp index 3a551ff0..8aead695 100644 --- a/src/dialog/viewonlydialog.cpp +++ b/src/dialog/viewonlydialog.cpp @@ -8,20 +8,21 @@ #include "viewonlydialog.h" #include "ui_viewonlydialog.h" -ViewOnlyDialog::ViewOnlyDialog(AppContext *ctx, QWidget *parent) - : QDialog(parent) - , ui(new Ui::ViewOnlyDialog), m_ctx(ctx) +ViewOnlyDialog::ViewOnlyDialog(QSharedPointer ctx, QWidget *parent) + : QDialog(parent) + , ui(new Ui::ViewOnlyDialog) + , m_ctx(std::move(ctx)) { ui->setupUi(this); - ui->label_restoreHeight->setText(QString::number(ctx->currentWallet->getWalletCreationHeight())); - ui->label_primaryAddress->setText(ctx->currentWallet->address(0, 0)); - ui->label_secretViewKey->setText(ctx->currentWallet->getSecretViewKey()); + ui->label_restoreHeight->setText(QString::number(m_ctx->wallet->getWalletCreationHeight())); + ui->label_primaryAddress->setText(m_ctx->wallet->address(0, 0)); + ui->label_secretViewKey->setText(m_ctx->wallet->getSecretViewKey()); connect(ui->btn_Copy, &QPushButton::clicked, this, &ViewOnlyDialog::copyToClipboad); connect(ui->btn_Save, &QPushButton::clicked, this, &ViewOnlyDialog::onWriteViewOnlyWallet); - ui->btn_Save->setEnabled(!m_ctx->currentWallet->viewOnly()); + ui->btn_Save->setEnabled(!m_ctx->wallet->viewOnly()); this->adjustSize(); } @@ -40,7 +41,7 @@ void ViewOnlyDialog::onWriteViewOnlyWallet(){ if((bool)passwordDialog.exec()) passwd = passwordDialog.textValue(); - m_ctx->currentWallet->createViewOnly(fn, passwd); + m_ctx->wallet->createViewOnly(fn, passwd); QMessageBox::information(this, "Information", "View-only wallet successfully written to disk."); } diff --git a/src/dialog/viewonlydialog.h b/src/dialog/viewonlydialog.h index ccc9c2e2..50caa387 100644 --- a/src/dialog/viewonlydialog.h +++ b/src/dialog/viewonlydialog.h @@ -16,7 +16,7 @@ class ViewOnlyDialog : public QDialog Q_OBJECT public: - explicit ViewOnlyDialog(AppContext *ctx, QWidget *parent = nullptr); + explicit ViewOnlyDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~ViewOnlyDialog() override; private slots: @@ -24,7 +24,7 @@ private slots: private: Ui::ViewOnlyDialog *ui; - AppContext *m_ctx = nullptr; + QSharedPointer m_ctx; void copyToClipboad(); }; diff --git a/src/dialog/walletinfodialog.cpp b/src/dialog/walletinfodialog.cpp index 991cce7a..57ae5c46 100644 --- a/src/dialog/walletinfodialog.cpp +++ b/src/dialog/walletinfodialog.cpp @@ -6,21 +6,20 @@ #include -WalletInfoDialog::WalletInfoDialog(AppContext *ctx, QWidget *parent) - : QDialog(parent) - , ui(new Ui::WalletInfoDialog) - , m_ctx(ctx) +WalletInfoDialog::WalletInfoDialog(QSharedPointer ctx, QWidget *parent) + : QDialog(parent) + , ui(new Ui::WalletInfoDialog) + , m_ctx(std::move(ctx)) { ui->setupUi(this); - QFileInfo keys(ctx->walletPath); - QFileInfo cache(ctx->currentWallet->path()); + QFileInfo cache(m_ctx->wallet->cachePath()); - ui->label_walletName->setText(keys.fileName().replace(".keys", "")); - ui->label_netType->setText(Utils::QtEnumToString(ctx->currentWallet->nettype())); - ui->label_seedType->setText(ctx->currentWallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word"); - ui->label_viewOnly->setText(ctx->currentWallet->viewOnly() ? "True" : "False"); - ui->label_path->setText(ctx->walletPath); + ui->label_walletName->setText(QFileInfo(m_ctx->wallet->cachePath()).fileName()); + ui->label_netType->setText(Utils::QtEnumToString(m_ctx->wallet->nettype())); + ui->label_seedType->setText(m_ctx->wallet->getCacheAttribute("feather.seed").isEmpty() ? "25 word" : "14 word"); + ui->label_viewOnly->setText(m_ctx->wallet->viewOnly() ? "True" : "False"); + ui->label_path->setText(m_ctx->wallet->keysPath()); ui->label_cacheSize->setText(QString("%1 MB").arg(QString::number(cache.size() / 1e6, 'f', 2))); connect(ui->btn_openWalletDir, &QPushButton::clicked, this, &WalletInfoDialog::openWalletDir); @@ -28,12 +27,12 @@ WalletInfoDialog::WalletInfoDialog(AppContext *ctx, QWidget *parent) this->adjustSize(); } -void WalletInfoDialog::openWalletDir() { - QFileInfo file(m_ctx->walletPath); - - QDesktopServices::openUrl(QUrl(QString("file://%1").arg(file.absolutePath()), QUrl::TolerantMode)); -} - WalletInfoDialog::~WalletInfoDialog() { delete ui; } + +void WalletInfoDialog::openWalletDir() { + QFileInfo file(m_ctx->wallet->keysPath()); + + QDesktopServices::openUrl(QUrl(QString("file://%1").arg(file.absolutePath()), QUrl::TolerantMode)); +} \ No newline at end of file diff --git a/src/dialog/walletinfodialog.h b/src/dialog/walletinfodialog.h index 9697475b..37647323 100644 --- a/src/dialog/walletinfodialog.h +++ b/src/dialog/walletinfodialog.h @@ -17,14 +17,14 @@ class WalletInfoDialog : public QDialog Q_OBJECT public: - explicit WalletInfoDialog(AppContext *ctx, QWidget *parent = nullptr); + explicit WalletInfoDialog(QSharedPointer ctx, QWidget *parent = nullptr); ~WalletInfoDialog() override; private: void openWalletDir(); Ui::WalletInfoDialog *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; }; #endif //FEATHER_WALLETINFODIALOG_H diff --git a/src/historywidget.cpp b/src/historywidget.cpp index c74f8bbc..a0e86028 100644 --- a/src/historywidget.cpp +++ b/src/historywidget.cpp @@ -11,11 +11,13 @@ #include -HistoryWidget::HistoryWidget(QWidget *parent) - : QWidget(parent) - , ui(new Ui::HistoryWidget) - , m_contextMenu(new QMenu(this)) - , m_copyMenu(new QMenu("Copy", this)) +HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) + : QWidget(parent) + , ui(new Ui::HistoryWidget) + , m_ctx(std::move(ctx)) + , m_contextMenu(new QMenu(this)) + , m_copyMenu(new QMenu("Copy", this)) + , m_model(m_ctx->wallet->historyModel()) { ui->setupUi(this); m_contextMenu->addMenu(m_copyMenu); @@ -45,6 +47,27 @@ HistoryWidget::HistoryWidget(QWidget *parent) config()->set(Config::showHistorySyncNotice, false); ui->syncNotice->hide(); }); + + connect(m_ctx.get(), &AppContext::walletRefreshed, this, &HistoryWidget::onWalletRefreshed); + + ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool()); + ui->history->setHistoryModel(m_model); + m_ctx->wallet->transactionHistoryModel()->amountPrecision = config()->get(Config::amountPrecision).toInt(); + + // Load view state + QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()); + if (!historyViewState.isEmpty()) { + ui->history->setViewState(historyViewState); + } +} + +void HistoryWidget::setSearchbarVisible(bool visible) { + ui->search->setVisible(visible); +} + +void HistoryWidget::focusSearchbar() { + ui->search->setFocusPolicy(Qt::StrongFocus); + ui->search->setFocus(); } void HistoryWidget::showContextMenu(const QPoint &point) { @@ -59,7 +82,7 @@ void HistoryWidget::showContextMenu(const QPoint &point) { if (!tx) return; bool unconfirmed = tx->isFailed() || tx->isPending(); - if (AppContext::txCache.contains(tx->hash()) && unconfirmed && tx->direction() != TransactionInfo::Direction_In) { + if (m_ctx->txCache.contains(tx->hash()) && unconfirmed && tx->direction() != TransactionInfo::Direction_In) { menu.addAction(icons()->icon("info2.svg"), "Resend transaction", this, &HistoryWidget::onResendTransaction); } @@ -79,22 +102,6 @@ void HistoryWidget::onResendTransaction() { } } -void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet) -{ - m_model = model; - m_wallet = wallet; - m_txHistory = m_wallet->history(); - ui->history->setHistoryModel(m_model); - m_wallet->transactionHistoryModel()->amountPrecision = config()->get(Config::amountPrecision).toInt(); - - // Load view state - QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()); - - if (!historyViewState.isEmpty()) { - ui->history->setViewState(historyViewState); - } -} - void HistoryWidget::resetModel() { // Save view state @@ -108,7 +115,7 @@ void HistoryWidget::showTxDetails() { auto *tx = ui->history->currentEntry(); if (!tx) return; - auto *dialog = new TransactionInfoDialog(m_wallet, tx, this); + auto *dialog = new TransactionInfoDialog(m_ctx, tx, this); connect(dialog, &TransactionInfoDialog::resendTranscation, [this](const QString &txid){ emit resendTransaction(txid); }); @@ -128,7 +135,6 @@ void HistoryWidget::setSearchText(const QString &text) { } void HistoryWidget::setSearchFilter(const QString &filter) { - if (!m_model) return; m_model->setSearchFilter(filter); ui->history->setSearchMode(!filter.isEmpty()); } @@ -137,7 +143,7 @@ void HistoryWidget::createTxProof() { auto *tx = ui->history->currentEntry(); if (!tx) return; - auto *dialog = new TxProofDialog(this, m_wallet, tx); + auto *dialog = new TxProofDialog(this, m_ctx, tx); dialog->exec(); dialog->deleteLater(); } @@ -162,10 +168,6 @@ void HistoryWidget::copy(copyField field) { Utils::copyToClipboard(data); } -void HistoryWidget::onWalletOpened() { - ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool()); -} - void HistoryWidget::onWalletRefreshed() { ui->syncNotice->hide(); } diff --git a/src/historywidget.h b/src/historywidget.h index 197d0531..a41a262b 100644 --- a/src/historywidget.h +++ b/src/historywidget.h @@ -8,6 +8,7 @@ #include "model/TransactionHistoryProxyModel.h" #include "libwalletqt/Coins.h" #include "libwalletqt/Wallet.h" +#include "appcontext.h" #include #include @@ -21,15 +22,16 @@ class HistoryWidget : public QWidget Q_OBJECT public: - explicit HistoryWidget(QWidget *parent = nullptr); - void setModel(TransactionHistoryProxyModel *model, Wallet *wallet); + explicit HistoryWidget(QSharedPointer ctx, QWidget *parent = nullptr); ~HistoryWidget() override; + void setSearchbarVisible(bool visible); + void focusSearchbar(); + public slots: void setSearchText(const QString &text); void resetModel(); void onWalletRefreshed(); - void onWalletOpened(); signals: void viewOnBlockExplorer(QString txid); @@ -55,11 +57,10 @@ private: void showSyncNoticeMsg(); Ui::HistoryWidget *ui; + QSharedPointer m_ctx; QMenu *m_contextMenu; QMenu *m_copyMenu; - TransactionHistory *m_txHistory; TransactionHistoryProxyModel *m_model; - Wallet *m_wallet = nullptr; }; #endif //FEATHER_HISTORYWIDGET_H diff --git a/src/libwalletqt/AddressBook.cpp b/src/libwalletqt/AddressBook.cpp index 381d763c..3f4fd85f 100644 --- a/src/libwalletqt/AddressBook.cpp +++ b/src/libwalletqt/AddressBook.cpp @@ -116,13 +116,6 @@ quint64 AddressBook::count() const return m_rows.size(); } -int AddressBook::lookupPaymentID(const QString &payment_id) const -{ - QReadLocker locker(&m_lock); - - return m_addressBookImpl->lookupPaymentID(payment_id.toStdString()); -} - QString AddressBook::getDescription(const QString &address) const { QReadLocker locker(&m_lock); diff --git a/src/libwalletqt/AddressBook.h b/src/libwalletqt/AddressBook.h index 56d78279..6b88704f 100644 --- a/src/libwalletqt/AddressBook.h +++ b/src/libwalletqt/AddressBook.h @@ -28,7 +28,6 @@ public: quint64 count() const; Q_INVOKABLE QString errorString() const; Q_INVOKABLE int errorCode() const; - Q_INVOKABLE int lookupPaymentID(const QString &payment_id) const; Q_INVOKABLE QString getDescription(const QString &address) const; enum ErrorCode { diff --git a/src/libwalletqt/Coins.cpp b/src/libwalletqt/Coins.cpp index c275cab4..d0440db4 100644 --- a/src/libwalletqt/Coins.cpp +++ b/src/libwalletqt/Coins.cpp @@ -41,6 +41,10 @@ void Coins::refresh(quint32 accountIndex) m_pimpl->refresh(); for (const auto i : m_pimpl->getAll()) { + if (i->subaddrAccount() != accountIndex) { + continue; + } + m_tinfo.append(new CoinsInfo(i, this)); } } diff --git a/src/libwalletqt/Subaddress.cpp b/src/libwalletqt/Subaddress.cpp index 308eb4aa..dc683577 100644 --- a/src/libwalletqt/Subaddress.cpp +++ b/src/libwalletqt/Subaddress.cpp @@ -5,7 +5,9 @@ #include Subaddress::Subaddress(Monero::Subaddress *subaddressImpl, QObject *parent) - : QObject(parent), m_subaddressImpl(subaddressImpl), m_unusedLookahead(0) + : QObject(parent) + , m_subaddressImpl(subaddressImpl) + , m_unusedLookahead(0) { getAll(); } diff --git a/src/libwalletqt/SubaddressAccount.cpp b/src/libwalletqt/SubaddressAccount.cpp index db885e8b..5aa304a2 100644 --- a/src/libwalletqt/SubaddressAccount.cpp +++ b/src/libwalletqt/SubaddressAccount.cpp @@ -62,3 +62,8 @@ quint64 SubaddressAccount::count() const return m_rows.size(); } + +Monero::SubaddressAccountRow* SubaddressAccount::row(int index) const +{ + return m_rows.value(index); +} \ No newline at end of file diff --git a/src/libwalletqt/SubaddressAccount.h b/src/libwalletqt/SubaddressAccount.h index e2db11e3..7cc380e0 100644 --- a/src/libwalletqt/SubaddressAccount.h +++ b/src/libwalletqt/SubaddressAccount.h @@ -22,6 +22,7 @@ public: Q_INVOKABLE void setLabel(quint32 accountIndex, const QString &label) const; Q_INVOKABLE void refresh() const; quint64 count() const; + Monero::SubaddressAccountRow* row(int index) const; signals: void refreshStarted() const; diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp index 42d2349c..e355275c 100644 --- a/src/libwalletqt/TransactionHistory.cpp +++ b/src/libwalletqt/TransactionHistory.cpp @@ -104,7 +104,6 @@ void TransactionHistory::refresh(quint32 accountIndex) void TransactionHistory::setTxNote(const QString &txid, const QString ¬e) { m_pimpl->setTxNote(txid.toStdString(), note.toStdString()); - this->refresh(0); // todo: get actual account index emit txNoteChanged(); } diff --git a/src/libwalletqt/TransactionHistory.h b/src/libwalletqt/TransactionHistory.h index c00a3872..15b0ba20 100644 --- a/src/libwalletqt/TransactionHistory.h +++ b/src/libwalletqt/TransactionHistory.h @@ -60,6 +60,7 @@ private: // history contains locked transfers mutable bool m_locked; + quint32 lastAccountIndex = 0; }; #endif // TRANSACTIONHISTORY_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 2079519e..003fd364 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -3,6 +3,9 @@ #include "Wallet.h" +#include +#include + #include "TransactionHistory.h" #include "AddressBook.h" #include "Subaddress.h" @@ -174,9 +177,14 @@ SubaddressIndex Wallet::subaddressIndex(const QString &address) const return SubaddressIndex(i.first, i.second); } -QString Wallet::path() const +QString Wallet::cachePath() const { - return QDir::toNativeSeparators(QString::fromStdString(m_walletImpl->path())); + return QDir::toNativeSeparators(QString::fromStdString(m_walletImpl->filename())); +} + +QString Wallet::keysPath() const +{ + return QDir::toNativeSeparators(QString::fromStdString(m_walletImpl->keysFilename()));; } //void Wallet::storeAsync(const QJSValue &callback, const QString &path /* = "" */) @@ -367,6 +375,8 @@ void Wallet::switchSubaddressAccount(quint32 accountIndex) } m_subaddress->refresh(m_currentSubaddressAccount); m_history->refresh(m_currentSubaddressAccount); + m_coins->refresh(m_currentSubaddressAccount); + this->subaddressModel()->setCurrentSubaddressAcount(m_currentSubaddressAccount); emit currentSubaddressAccountChanged(); } } @@ -1257,18 +1267,18 @@ void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device Wallet::Wallet(Monero::Wallet *w, QObject *parent) : QObject(parent) , m_walletImpl(w) - , m_history(nullptr) + , m_history(new TransactionHistory(m_walletImpl->history(), this)) , m_historyModel(nullptr) - , m_addressBook(nullptr) + , m_addressBook(new AddressBook(m_walletImpl->addressBook(), this)) , m_addressBookModel(nullptr) , m_daemonBlockChainHeight(0) , m_daemonBlockChainTargetHeight(0) , m_connectionStatus(Wallet::ConnectionStatus_Disconnected) , m_disconnected(true) , m_currentSubaddressAccount(0) - , m_subaddress(nullptr) + , m_subaddress(new Subaddress(m_walletImpl->subaddress(), this)) , m_subaddressModel(nullptr) - , m_subaddressAccount(nullptr) + , m_subaddressAccount(new SubaddressAccount(m_walletImpl->subaddressAccount(), this)) , m_subaddressAccountModel(nullptr) , m_coinsModel(nullptr) , m_refreshNow(false) @@ -1276,12 +1286,8 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent) , m_refreshing(false) , m_scheduler(this) , m_useSSL(true) + , m_coins(new Coins(m_walletImpl->coins(), this)) { - m_history = new TransactionHistory(m_walletImpl->history(), this); - m_addressBook = new AddressBook(m_walletImpl->addressBook(), this); - m_subaddress = new Subaddress(m_walletImpl->subaddress(), this); - m_subaddressAccount = new SubaddressAccount(m_walletImpl->subaddressAccount(), this); - m_coins = new Coins(m_walletImpl->coins(), this); m_walletListener = new WalletListenerImpl(this); m_walletImpl->setListener(m_walletListener); m_currentSubaddressAccount = getCacheAttribute(ATTRIBUTE_SUBADDRESS_ACCOUNT).toUInt(); @@ -1304,19 +1310,6 @@ Wallet::~Wallet() m_scheduler.shutdownWaitForFinished(); - delete m_addressBook; - m_addressBook = NULL; - - delete m_history; - m_history = NULL; - delete m_addressBook; - m_addressBook = NULL; - delete m_subaddress; - m_subaddress = NULL; - delete m_subaddressAccount; - m_subaddressAccount = NULL; - delete m_coins; - m_coins = NULL; //Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl); if(status() == Status_Critical || status() == Status_BadPassword) qDebug("Not storing wallet cache"); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index a3b96121..94dcc25a 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -76,6 +76,9 @@ class Wallet : public QObject, public PassprasePrompter Q_OBJECT public: + Wallet(QObject * parent = nullptr); + Wallet(Monero::Wallet *w, QObject * parent = nullptr); + ~Wallet(); enum Status { Status_Ok = Monero::Wallet::Status_Ok, @@ -138,8 +141,11 @@ public: //! returns the subaddress index of the address SubaddressIndex subaddressIndex(const QString &address) const; - //! returns wallet file's path - QString path() const; + //! returns wallet cache file path + QString cachePath() const; + + //! returns wallet keys file path + QString keysPath() const; //! saves wallet to the file by given path //! empty path stores in current location @@ -423,7 +429,7 @@ public: // Passphrase entry for hardware wallets void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false); - virtual void onWalletPassphraseNeeded(bool on_device) override; + void onWalletPassphraseNeeded(bool on_device) override; quint64 getBytesReceived() const; quint64 getBytesSent() const; @@ -465,10 +471,6 @@ signals: void refreshingChanged() const; private: - Wallet(QObject * parent = nullptr); - Wallet(Monero::Wallet *w, QObject * parent = 0); - ~Wallet(); - //! initializes wallet bool init( const QString &daemonAddress, diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index d1feb23d..aab885f6 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -68,14 +68,10 @@ Wallet *WalletManager::createWallet(const QString &path, const QString &password const QString &language, NetworkType::Type nettype, quint64 kdfRounds) { QMutexLocker locker(&m_mutex); - if (m_currentWallet) { - qDebug() << "Closing open m_currentWallet" << m_currentWallet; - delete m_currentWallet; - } + Monero::Wallet * w = m_pimpl->createWallet(path.toStdString(), password.toStdString(), language.toStdString(), static_cast(nettype), kdfRounds); - m_currentWallet = new Wallet(w); - return m_currentWallet; + return new Wallet(w); } Wallet *WalletManager::openWallet(const QString &path, const QString &password, NetworkType::Type nettype, quint64 kdfRounds) @@ -90,24 +86,20 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, this->m_passphraseReceiver = nullptr; }); - if (m_currentWallet) { - qDebug() << "Closing open m_currentWallet" << m_currentWallet; - delete m_currentWallet; - } qDebug() << QString("%1: opening wallet at %2, nettype = %3 ").arg(__PRETTY_FUNCTION__).arg(qPrintable(path)).arg(nettype); Monero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), static_cast(nettype), kdfRounds, &tmpListener); w->setListener(nullptr); qDebug() << QString("%1: opened wallet: %2, status: %3").arg(__PRETTY_FUNCTION__).arg(w->address(0, 0).c_str()).arg(w->status()); - m_currentWallet = new Wallet(w); + auto wallet = new Wallet(w); // move wallet to the GUI thread. Otherwise it wont be emitting signals - if (m_currentWallet->thread() != qApp->thread()) { - m_currentWallet->moveToThread(qApp->thread()); + if (wallet->thread() != qApp->thread()) { + wallet->moveToThread(qApp->thread()); } - return m_currentWallet; + return wallet; } void WalletManager::openWalletAsync(const QString &path, const QString &password, NetworkType::Type nettype, quint64 kdfRounds) @@ -121,13 +113,9 @@ void WalletManager::openWalletAsync(const QString &path, const QString &password Wallet *WalletManager::recoveryWallet(const QString &path, const QString &password, const QString &seed, const QString &seed_offset, NetworkType::Type nettype, quint64 restoreHeight, quint64 kdfRounds) { QMutexLocker locker(&m_mutex); - if (m_currentWallet) { - qDebug() << "Closing open m_currentWallet" << m_currentWallet; - delete m_currentWallet; - } + Monero::Wallet * w = m_pimpl->recoveryWallet(path.toStdString(), password.toStdString(), seed.toStdString(), static_cast(nettype), restoreHeight, kdfRounds, seed_offset.toStdString()); - m_currentWallet = new Wallet(w); - return m_currentWallet; + return new Wallet(w); } Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &password, const QString &language, @@ -135,30 +123,18 @@ Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString & const QString &spendkey, quint64 restoreHeight, quint64 kdfRounds) { QMutexLocker locker(&m_mutex); - if (m_currentWallet) { - qDebug() << "Closing open m_currentWallet" << m_currentWallet; - delete m_currentWallet; - m_currentWallet = nullptr; - } Monero::Wallet * w = m_pimpl->createWalletFromKeys(path.toStdString(), password.toStdString(), language.toStdString(), static_cast(nettype), restoreHeight, address.toStdString(), viewkey.toStdString(), spendkey.toStdString(), kdfRounds); - m_currentWallet = new Wallet(w); - return m_currentWallet; + return new Wallet(w); } Wallet *WalletManager::createDeterministicWalletFromSpendKey(const QString &path, const QString &password, const QString &language, NetworkType::Type nettype, const QString &spendkey, quint64 restoreHeight, quint64 kdfRounds, const QString &offset_passphrase) { QMutexLocker locker(&m_mutex); - if (m_currentWallet) { - qDebug() << "Closing open m_currentWallet" << m_currentWallet; - delete m_currentWallet; - m_currentWallet = nullptr; - } Monero::Wallet * w = m_pimpl->createDeterministicWalletFromSpendKey(path.toStdString(), password.toStdString(), language.toStdString(), static_cast(nettype), restoreHeight, spendkey.toStdString(), kdfRounds, offset_passphrase.toStdString()); - m_currentWallet = new Wallet(w); - return m_currentWallet; + return new Wallet(w); } Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString &password, NetworkType::Type nettype, @@ -174,26 +150,20 @@ Wallet *WalletManager::createWalletFromDevice(const QString &path, const QString this->m_passphraseReceiver = nullptr; }); - if (m_currentWallet) { - qDebug() << "Closing open m_currentWallet" << m_currentWallet; - delete m_currentWallet; - m_currentWallet = nullptr; - } Monero::Wallet * w = m_pimpl->createWalletFromDevice(path.toStdString(), password.toStdString(), static_cast(nettype), deviceName.toStdString(), restoreHeight, subaddressLookahead.toStdString(), 1, &tmpListener); w->setListener(nullptr); - m_currentWallet = new Wallet(w); + auto wallet = new Wallet(w); // move wallet to the GUI thread. Otherwise it wont be emitting signals - if (m_currentWallet->thread() != qApp->thread()) { - m_currentWallet->moveToThread(qApp->thread()); + if (wallet->thread() != qApp->thread()) { + wallet->moveToThread(qApp->thread()); } - return m_currentWallet; + return wallet; } - void WalletManager::createWalletFromDeviceAsync(const QString &path, const QString &password, NetworkType::Type nettype, const QString &deviceName, quint64 restoreHeight, const QString &subaddressLookahead) { @@ -203,30 +173,6 @@ void WalletManager::createWalletFromDeviceAsync(const QString &path, const QStri }); } -QString WalletManager::closeWallet() -{ - QMutexLocker locker(&m_mutex); - qDebug() << Q_FUNC_INFO ; - QString result; - if (m_currentWallet) { - result = m_currentWallet->address(0, 0); - delete m_currentWallet; - m_currentWallet = nullptr; - } else { - qCritical() << "Trying to close non existing wallet " << m_currentWallet; - result = "0"; - } - return result; -} - -// @TODO: fix -//void WalletManager::closeWalletAsync(const QJSValue& callback) -//{ -// m_scheduler.run([this] { -// return QJSValueList({closeWallet()}); -// }, callback); -//} - bool WalletManager::walletExists(const QString &path) const { return m_pimpl->walletExists(path.toStdString()); @@ -262,9 +208,21 @@ QString WalletManager::maximumAllowedAmountAsString() const return WalletManager::displayAmount(WalletManager::maximumAllowedAmount()); } -QString WalletManager::displayAmount(quint64 amount) +QString WalletManager::displayAmount(quint64 amount, bool trailing_zeroes, int decimals) { - return QString::fromStdString(Monero::Wallet::displayAmount(amount)); + auto amountStr = QString::fromStdString(Monero::Wallet::displayAmount(amount)); + + if (decimals < 12) { + int i = amountStr.indexOf("."); + amountStr.remove(i+decimals+1, 12); + } + + if (!trailing_zeroes) { + amountStr.remove(QRegExp("0+$")); + amountStr.remove(QRegExp("\\.$")); + } + + return amountStr; } quint64 WalletManager::amountFromString(const QString &amount) @@ -287,7 +245,7 @@ bool WalletManager::addressValid(const QString &address, NetworkType::Type netty return Monero::Wallet::addressValid(address.toStdString(), static_cast(nettype)); } -bool WalletManager::keyValid(const QString &key, const QString &address, bool isViewKey, NetworkType::Type nettype) const +bool WalletManager::keyValid(const QString &key, const QString &address, bool isViewKey, NetworkType::Type nettype) { std::string error; if(!Monero::Wallet::keyValid(key.toStdString(), address.toStdString(), isViewKey, static_cast(nettype), error)){ @@ -329,43 +287,6 @@ quint64 WalletManager::blockchainTargetHeight() const return m_pimpl->blockchainTargetHeight(); } -double WalletManager::miningHashRate() const -{ - return m_pimpl->miningHashRate(); -} - -bool WalletManager::isMining() const -{ - { - QMutexLocker locker(&m_mutex); - if (m_currentWallet == nullptr || !m_currentWallet->connectionStatus()) - { - return false; - } - } - - return m_pimpl->isMining(); -} - -void WalletManager::miningStatusAsync() -{ - m_scheduler.run([this] { - emit miningStatus(isMining()); - }); -} - -bool WalletManager::startMining(const QString &address, quint32 threads, bool backgroundMining, bool ignoreBattery) -{ - if(threads == 0) - threads = 1; - return m_pimpl->startMining(address.toStdString(), threads, backgroundMining, ignoreBattery); -} - -bool WalletManager::stopMining() -{ - return m_pimpl->stopMining(); -} - bool WalletManager::localDaemonSynced() const { return blockchainHeight() > 1 && blockchainHeight() >= blockchainTargetHeight(); @@ -386,8 +307,9 @@ QString WalletManager::resolveOpenAlias(const QString &address) const bool WalletManager::parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector &unknown_parameters, QString &error) const { QMutexLocker locker(&m_mutex); - if (m_currentWallet) - return m_currentWallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); + // TODO: FIXME +// if (m_currentWallet) +// return m_currentWallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); return false; } @@ -435,9 +357,8 @@ QUrl WalletManager::localPathToUrl(const QString &path) const return QUrl::fromLocalFile(path); } -bool WalletManager::clearWalletCache(const QString &wallet_path) const +bool WalletManager::clearWalletCache(const QString &wallet_path) { - QString fileName = wallet_path; // Make sure wallet file is not .keys fileName.replace(".keys",""); @@ -457,7 +378,6 @@ WalletManager::WalletManager(QObject *parent) : QObject(parent) , m_passphraseReceiver(nullptr) , m_scheduler(this) - , m_currentWallet(nullptr) { m_pimpl = Monero::WalletManagerFactory::getWalletManager(); } diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 0f4e58e2..b43f83de 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -93,16 +93,7 @@ public: const QString &deviceName, quint64 restoreHeight = 0, const QString &subaddressLookahead = ""); - /*! - * \brief closeWallet - closes current open wallet and frees memory - * \return wallet address - */ - Q_INVOKABLE QString closeWallet(); - /*! - * \brief closeWalletAsync - asynchronous version of "closeWallet" - */ - //Q_INVOKABLE void closeWalletAsync(const QJSValue& callback); //! checks is given filename is a wallet; Q_INVOKABLE bool walletExists(const QString &path) const; @@ -117,7 +108,7 @@ public: Q_INVOKABLE QString errorString() const; //! since we can't call static method from QML, move it to this class - Q_INVOKABLE static QString displayAmount(quint64 amount); + Q_INVOKABLE static QString displayAmount(quint64 amount, bool trailing_zeroes = true, int decimals = 12); Q_INVOKABLE static quint64 amountFromString(const QString &amount); Q_INVOKABLE static quint64 amountFromDouble(double amount); Q_INVOKABLE quint64 maximumAllowedAmount() const; @@ -127,7 +118,7 @@ public: Q_INVOKABLE bool paymentIdValid(const QString &payment_id) const; Q_INVOKABLE static bool addressValid(const QString &address, NetworkType::Type nettype); - Q_INVOKABLE bool keyValid(const QString &key, const QString &address, bool isViewKey, NetworkType::Type nettype) const; + Q_INVOKABLE static bool keyValid(const QString &key, const QString &address, bool isViewKey, NetworkType::Type nettype); Q_INVOKABLE QString paymentIdFromAddress(const QString &address, NetworkType::Type nettype) const; @@ -136,14 +127,9 @@ public: Q_INVOKABLE quint64 networkDifficulty() const; Q_INVOKABLE quint64 blockchainHeight() const; Q_INVOKABLE quint64 blockchainTargetHeight() const; - Q_INVOKABLE double miningHashRate() const; Q_INVOKABLE bool localDaemonSynced() const; Q_INVOKABLE bool isDaemonLocal(const QString &daemon_address) const; - Q_INVOKABLE void miningStatusAsync(); - Q_INVOKABLE bool startMining(const QString &address, quint32 threads, bool backgroundMining, bool ignoreBattery); - Q_INVOKABLE bool stopMining(); - // QML missing such functionality, implementing these helpers here Q_INVOKABLE QString urlToLocalPath(const QUrl &url) const; Q_INVOKABLE QUrl localPathToUrl(const QString &path) const; @@ -159,10 +145,9 @@ public: Q_INVOKABLE QString resolveOpenAlias(const QString &address) const; Q_INVOKABLE bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector &unknown_parameters, QString &error) const; Q_INVOKABLE QVariantMap parse_uri_to_object(const QString &uri) const; -// Q_INVOKABLE bool saveQrCode(const QString &, const QString &) const; // clear/rename wallet cache - Q_INVOKABLE bool clearWalletCache(const QString &fileName) const; + Q_INVOKABLE static bool clearWalletCache(const QString &fileName); Q_INVOKABLE void onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort=false); virtual void onWalletPassphraseNeeded(bool on_device) override; @@ -171,7 +156,6 @@ public: void setProxyAddress(QString address); signals: - void walletOpened(Wallet * wallet); void walletCreated(Wallet * wallet); void walletPassphraseNeeded(bool onDevice); @@ -185,16 +169,13 @@ public slots: private: friend class WalletPassphraseListenerImpl; - explicit WalletManager(QObject *parent = 0); - ~WalletManager(); + explicit WalletManager(QObject *parent = nullptr); + ~WalletManager() override; - bool isMining() const; - - static WalletManager * m_instance; - Monero::WalletManager * m_pimpl; + static WalletManager *m_instance; + Monero::WalletManager *m_pimpl; mutable QMutex m_mutex; - QPointer m_currentWallet; - PassphraseReceiver * m_passphraseReceiver; + PassphraseReceiver *m_passphraseReceiver; QMutex m_mutex_passphraseReceiver; QString m_proxyAddress; mutable QMutex m_proxyMutex; diff --git a/src/main.cpp b/src/main.cpp index 97dc1914..a62ae807 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,8 +7,10 @@ #include #include "config-feather.h" +#include "constants.h" #include "mainwindow.h" #include "cli.h" +#include "WindowManager.h" #if defined(Q_OS_WIN) #include @@ -87,14 +89,13 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { QCommandLineOption bruteforceDictionairy(QStringList() << "bruteforce-dict", "Bruteforce dictionairy", "file"); parser.addOption(bruteforceDictionairy); - auto parsed = parser.parse(argv_); - if(!parsed) { + bool parsed = parser.parse(argv_); + if (!parsed) { qCritical() << parser.errorText(); exit(1); } const QStringList args = parser.positionalArguments(); - bool localTor = parser.isSet(useLocalTorOption); bool stagenet = parser.isSet(stagenetOption); bool testnet = parser.isSet(testnetOption); bool quiet = parser.isSet(quietModeOption); @@ -103,33 +104,15 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { bool bruteforcePassword = parser.isSet(bruteforcePasswordOption); bool cliMode = exportContacts || exportTxHistory || bruteforcePassword; - if(cliMode) { - QCoreApplication cli_app(argc, argv); - QCoreApplication::setApplicationName("FeatherWallet"); - - auto *ctx = new AppContext(&parser); - - auto *cli = new CLI(ctx, &cli_app); - QObject::connect(cli, &CLI::closeApplication, &cli_app, &QCoreApplication::quit); - - if(exportContacts) { - if(!quiet) - qInfo() << "CLI mode: Address book export"; - cli->mode = CLIMode::ExportContacts; - QTimer::singleShot(0, cli, &CLI::run); - } else if(exportTxHistory) { - if(!quiet) - qInfo() << "CLI mode: Transaction history export"; - cli->mode = CLIMode::ExportTxHistory; - QTimer::singleShot(0, cli, &CLI::run); - } else if(bruteforcePassword) { - cli->mode = CLIMode::BruteforcePassword; - QTimer::singleShot(0, cli, &CLI::run); - } - - return QCoreApplication::exec(); - } + // Setup networkType + if (stagenet) + constants::networkType = NetworkType::STAGENET; + else if (testnet) + constants::networkType = NetworkType::TESTNET; + else + constants::networkType = NetworkType::MAINNET; + // Setup QApplication QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); QApplication::setDesktopSettingsAware(true); // use system font @@ -137,11 +120,70 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { QApplication app(argc, argv); + QApplication::setQuitOnLastWindowClosed(false); QApplication::setApplicationName("FeatherWallet"); + // Setup config directories + QString configDir = Config::defaultConfigDir().path(); + QString config_dir_tor = QString("%1/%2").arg(configDir).arg("tor"); + QString config_dir_tordata = QString("%1/%2").arg(configDir).arg("tor/data"); + QStringList createDirs({configDir, config_dir_tor, config_dir_tordata}); + for (const auto &d: createDirs) { + if (!Utils::dirExists(d)) { + qDebug() << QString("Creating directory: %1").arg(d); + if (!QDir().mkpath(d)) { + qCritical() << "Could not create directory " << d; + } + } + } + + // Setup logging + QString logPath = QString("%1/daemon.log").arg(configDir); + Monero::Utils::onStartup(); + Monero::Wallet::init("", "feather", logPath.toStdString(), true); + + bool logLevelFromEnv; + int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv); + + if (parser.isSet("quiet")) + WalletManager::instance()->setLogLevel(-1); + else if (logLevelFromEnv && logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max) + Monero::WalletManagerFactory::setLogLevel(logLevel); + + // Setup wallet directory + QString walletDir = config()->get(Config::walletDirectory).toString(); + if (walletDir.isEmpty()) { + walletDir = Utils::defaultWalletDir(); + config()->set(Config::walletDirectory, walletDir); + } + if (!QDir().mkpath(walletDir)) + qCritical() << "Unable to create dir: " << walletDir; + + // Setup Tor config + if (parser.isSet("tor-host")) + config()->set(Config::socks5Host, parser.value("tor-host")); + if (parser.isSet("tor-port")) + config()->set(Config::socks5Port, parser.value("tor-port")); + if (parser.isSet("use-local-tor")) + config()->set(Config::useLocalTor, true); + + if (cliMode) { + CLI::Mode mode = [&]{ + if (exportContacts) + return CLI::Mode::ExportContacts; + if (exportTxHistory) + return CLI::Mode::ExportTxHistory; + if (bruteforcePassword) + return CLI::Mode::BruteforcePassword; + }(); + + CLI cli{mode, &parser, &app}; + return QCoreApplication::exec(); + } + parser.process(app); // Parse again for --help and --version - if(!quiet) { + if (!quiet) { QMap info; info["Qt"] = QT_VERSION_STR; info["Feather"] = FEATHER_VERSION; @@ -154,8 +196,6 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { qWarning().nospace().noquote() << QString("%1: %2").arg(k).arg(info[k]); } - auto *ctx = new AppContext(&parser); - #if defined(Q_OS_MAC) // For some odd reason, if we don't do this, QPushButton's // need to be clicked *twice* in order to fire ?! @@ -167,6 +207,7 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { qInstallMessageHandler(Utils::applicationLogHandler); qRegisterMetaType>(); - auto *mainWindow = new MainWindow(ctx); + WindowManager windowManager; + return QApplication::exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 228c4bcd..f81df981 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -2,7 +2,6 @@ // Copyright (c) 2020-2021, The Monero Project. #include -#include #include #include "mainwindow.h" @@ -21,7 +20,8 @@ #include "dialog/balancedialog.h" #include "dialog/WalletCacheDebugDialog.h" #include "dialog/UpdateDialog.h" -#include "globals.h" +#include "dialog/AccountSwitcherDialog.h" +#include "constants.h" #include "libwalletqt/AddressBook.h" #include "utils/AsyncTask.h" #include "utils/AppData.h" @@ -31,31 +31,31 @@ #include "utils/Icons.h" #include "utils/WebsocketNotifier.h" #include "utils/Updater.h" +#include "utils/os/tails.h" +#include "utils/TorManager.h" -MainWindow * MainWindow::pMainWindow = nullptr; - -MainWindow::MainWindow(AppContext *ctx, QWidget *parent) +MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) - , m_ctx(ctx) + , m_windowManager(windowManager) + , m_ctx(new AppContext(wallet)) { - pMainWindow = this; ui->setupUi(this); - m_windowSettings = new Settings(this); + // Ensure the destructor is called after closeEvent() + setAttribute(Qt::WA_DeleteOnClose); + + m_windowSettings = new Settings(m_ctx, this); m_windowCalc = new CalcWindow(this); m_splashDialog = new SplashDialog(this); this->restoreGeo(); this->startupWarning(); - this->initSkins(); this->initStatusBar(); this->initWidgets(); this->initMenu(); - this->initTray(); this->initHome(); - this->initTouchBar(); this->initWalletContext(); // Websocket notifier @@ -65,64 +65,34 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) #ifdef HAS_XMRIG connect(websocketNotifier(), &WebsocketNotifier::XMRigDownloadsReceived, m_xmrig, &XMRigWidget::onDownloads); #endif + websocketNotifier()->emitCache(); // Get cached data // Settings - for (auto tickerWidget: m_tickerWidgets) - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, tickerWidget, &TickerWidget::init); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceWidget, &TickerWidget::init); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx, &AppContext::onPreferredFiatCurrencyChanged); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, ui->sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); - connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx, &AppContext::onAmountPrecisionChanged); + for (const auto &widget: m_priceTickerWidgets) + connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay); + connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay); + connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx.get(), &AppContext::onPreferredFiatCurrencyChanged); + connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); + connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx.get(), &AppContext::onAmountPrecisionChanged); connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged); - // Wizard - connect(this, &MainWindow::closed, [=]{ - if (m_wizard) - m_wizard->close(); - }); + connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged); + connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); + this->onTorConnectionStateChanged(torManager()->torConnected); - // History - // TODO: move this - connect(m_ctx, &AppContext::walletRefreshed, ui->historyWidget, &HistoryWidget::onWalletRefreshed); - connect(m_ctx, &AppContext::walletOpened, ui->historyWidget, &HistoryWidget::onWalletOpened); - - if (!config()->get(Config::firstRun).toBool() || TailsOS::detect() || WhonixOS::detect()) { - this->onInitialNetworkConfigured(); - } - - this->setEnabled(true); - this->show(); ColorScheme::updateFromWidget(this); - if (!this->autoOpenWallet()) { - this->initWizard(); - } - // Timers connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats); connect(&m_txTimer, &QTimer::timeout, [this]{ m_statusLabelStatus->setText("Constructing transaction" + this->statusDots()); }); -} -void MainWindow::initSkins() { - m_skins.insert("Native", ""); + this->onWalletOpened(); - QString qdarkstyle = this->loadStylesheet(":qdarkstyle/style.qss"); - if (!qdarkstyle.isEmpty()) - m_skins.insert("QDarkStyle", qdarkstyle); - - QString breeze_dark = this->loadStylesheet(":/dark.qss"); - if (!breeze_dark.isEmpty()) - m_skins.insert("Breeze/Dark", breeze_dark); - - QString breeze_light = this->loadStylesheet(":/light.qss"); - if (!breeze_light.isEmpty()) - m_skins.insert("Breeze/Light", breeze_light); - - QString skin = config()->get(Config::skin).toString(); - qApp->setStyleSheet(m_skins[skin]); - ColorScheme::updateFromWidget(this); +#ifdef DONATE_BEG + this->donationNag(); +#endif } void MainWindow::initStatusBar() { @@ -159,6 +129,10 @@ void MainWindow::initStatusBar() { connect(m_statusBtnConnectionStatusIndicator, &StatusBarButton::clicked, this, &MainWindow::showConnectionStatusDialog); this->statusBar()->addPermanentWidget(m_statusBtnConnectionStatusIndicator); + m_statusAccountSwitcher = new StatusBarButton(icons()->icon("change_account.png"), "Account switcher", this); + connect(m_statusAccountSwitcher, &StatusBarButton::clicked, this, &MainWindow::showAccountSwitcherDialog); + this->statusBar()->addPermanentWidget(m_statusAccountSwitcher); + m_statusBtnPassword = new StatusBarButton(icons()->icon("lock.svg"), "Password", this); connect(m_statusBtnPassword, &StatusBarButton::clicked, this, &MainWindow::showPasswordDialog); this->statusBar()->addPermanentWidget(m_statusBtnPassword); @@ -171,7 +145,7 @@ void MainWindow::initStatusBar() { connect(m_statusBtnSeed, &StatusBarButton::clicked, this, &MainWindow::showSeedDialog); this->statusBar()->addPermanentWidget(m_statusBtnSeed); - m_statusBtnTor = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Tor", this); + m_statusBtnTor = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Tor settings", this); connect(m_statusBtnTor, &StatusBarButton::clicked, this, &MainWindow::menuTorClicked); this->statusBar()->addPermanentWidget(m_statusBtnTor); @@ -189,16 +163,30 @@ void MainWindow::initWidgets() { }); // [History] - connect(ui->historyWidget, &HistoryWidget::viewOnBlockExplorer, this, &MainWindow::onViewOnBlockExplorer); - connect(ui->historyWidget, &HistoryWidget::resendTransaction, this, &MainWindow::onResendTransaction); + m_historyWidget = new HistoryWidget(m_ctx, this); + ui->historyWidgetLayout->addWidget(m_historyWidget); + connect(m_historyWidget, &HistoryWidget::viewOnBlockExplorer, this, &MainWindow::onViewOnBlockExplorer); + connect(m_historyWidget, &HistoryWidget::resendTransaction, this, &MainWindow::onResendTransaction); + + // [Send] + m_sendWidget = new SendWidget(m_ctx, this); + ui->sendWidgetLayout->addWidget(m_sendWidget); + // -------------- + m_contactsWidget = new ContactsWidget(m_ctx, this); + ui->contactsWidgetLayout->addWidget(m_contactsWidget); // [Receive] - connect(ui->receiveWidget, &ReceiveWidget::showTransactions, [this](const QString &text) { - ui->historyWidget->setSearchText(text); + m_receiveWidget = new ReceiveWidget(m_ctx, this); + ui->receiveWidgetLayout->addWidget(m_receiveWidget); + connect(m_receiveWidget, &ReceiveWidget::showTransactions, [this](const QString &text) { + m_historyWidget->setSearchText(text); ui->tabWidget->setCurrentIndex(Tabs::HISTORY); }); - connect(ui->contactWidget, &ContactsWidget::fillAddress, ui->sendWidget, &SendWidget::fillAddress); + connect(m_contactsWidget, &ContactsWidget::fillAddress, m_sendWidget, &SendWidget::fillAddress); + // [Coins] + m_coinsWidget = new CoinsWidget(m_ctx, this); + ui->coinsWidgetLayout->addWidget(m_coinsWidget); #ifdef HAS_LOCALMONERO m_localMoneroWidget = new LocalMoneroWidget(this, m_ctx); @@ -211,8 +199,8 @@ void MainWindow::initWidgets() { m_xmrig = new XMRigWidget(m_ctx, this); ui->xmrRigLayout->addWidget(m_xmrig); - connect(m_xmrig, &XMRigWidget::miningStarted, [this]{ this->setTitle(true); }); - connect(m_xmrig, &XMRigWidget::miningEnded, [this]{ this->setTitle(false); }); + connect(m_xmrig, &XMRigWidget::miningStarted, [this]{ this->updateTitle(); }); + connect(m_xmrig, &XMRigWidget::miningEnded, [this]{ this->updateTitle(); }); #else ui->tabWidget->setTabVisible(Tabs::XMRIG, false); #endif @@ -221,19 +209,22 @@ void MainWindow::initWidgets() { void MainWindow::initMenu() { // TODO: Rename actions to follow style // [File] - connect(ui->actionClose, &QAction::triggered, this, &MainWindow::menuWalletCloseClicked); // Close current wallet - connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::menuQuitClicked); // Quit application - connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::menuSettingsClicked); + connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::menuOpenClicked); + connect(ui->actionNew_Restore, &QAction::triggered, this, &MainWindow::menuNewRestoreClicked); + connect(ui->actionClose, &QAction::triggered, this, &MainWindow::menuWalletCloseClicked); // Close current wallet + connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::menuQuitClicked); // Quit application + connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::menuSettingsClicked); // [Wallet] connect(ui->actionInformation, &QAction::triggered, this, &MainWindow::showWalletInfoDialog); + connect(ui->actionAccount, &QAction::triggered, this, &MainWindow::showAccountSwitcherDialog); connect(ui->actionPassword, &QAction::triggered, this, &MainWindow::showPasswordDialog); connect(ui->actionSeed, &QAction::triggered, this, &MainWindow::showSeedDialog); connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog); connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog); // [Wallet] -> [Advanced] - connect(ui->actionStore_wallet, &QAction::triggered, [this]{m_ctx->currentWallet->store();}); + connect(ui->actionStore_wallet, &QAction::triggered, [this]{m_ctx->wallet->store();}); connect(ui->actionUpdate_balance, &QAction::triggered, [this]{m_ctx->updateBalance();}); connect(ui->actionRefresh_tabs, &QAction::triggered, [this]{m_ctx->refreshModels();}); connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent); @@ -257,6 +248,8 @@ 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()); // Show/Hide Home connect(ui->actionShow_Home, &QAction::triggered, m_tabShowHideSignalMapper, QOverload<>::of(&QSignalMapper::map)); @@ -328,117 +321,62 @@ void MainWindow::initMenu() { // Setup shortcuts ui->actionStore_wallet->setShortcut(QKeySequence("Ctrl+S")); ui->actionRefresh_tabs->setShortcut(QKeySequence("Ctrl+R")); + ui->actionOpen->setShortcut(QKeySequence("Ctrl+O")); + ui->actionNew_Restore->setShortcut(QKeySequence("Ctrl+N")); ui->actionClose->setShortcut(QKeySequence("Ctrl+W")); ui->actionShow_debug_info->setShortcut(QKeySequence("Ctrl+D")); ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S")); ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U")); -} - -void MainWindow::initTray() { - // TODO: Add tray support on Windows and Mac - -#if defined(Q_OS_LINUX) - // system tray - m_trayIcon = new QSystemTrayIcon(QIcon(":/assets/images/appicons/64x64.png")); - m_trayIcon->show(); - - m_trayActionCalc = new QAction("Calc", this); - m_trayActionCalc->setStatusTip("Calculator"); - - m_trayActionSend = new QAction("Send", this); - m_trayActionSend->setStatusTip("Send XMR payment"); - - m_trayActionHistory = new QAction("History", this); - m_trayActionHistory->setStatusTip("View incoming transfers"); - - m_trayActionExit = new QAction("Quit", this); - m_trayActionExit->setStatusTip("Exit application"); - - m_trayMenu.addAction(m_trayActionSend); - m_trayMenu.addAction(m_trayActionHistory); - m_trayMenu.addAction(m_trayActionCalc); - m_trayMenu.addAction(m_trayActionExit); - m_trayIcon->setContextMenu(&m_trayMenu); - - connect(m_trayActionCalc, &QAction::triggered, this, &MainWindow::showCalcWindow); - connect(m_trayActionSend, &QAction::triggered, this, &MainWindow::showSendTab); - connect(m_trayActionHistory, &QAction::triggered, this, &MainWindow::showHistoryTab); - connect(m_trayActionExit, &QAction::triggered, this, &QMainWindow::close); -#endif + ui->actionShow_Searchbar->setShortcut(QKeySequence("Ctrl+F")); } void MainWindow::initHome() { // Ticker widgets - m_tickerWidgets.append(new TickerWidget(this, m_ctx, "XMR")); - m_tickerWidgets.append(new TickerWidget(this, m_ctx, "BTC")); - for (auto tickerWidget: m_tickerWidgets) { - ui->tickerLayout->addWidget(tickerWidget); + m_priceTickerWidgets.append(new PriceTickerWidget(this, m_ctx, "XMR")); + m_priceTickerWidgets.append(new PriceTickerWidget(this, m_ctx, "BTC")); + for (const auto &widget : m_priceTickerWidgets) { + ui->tickerLayout->addWidget(widget); } - m_balanceWidget = new TickerWidget(this, m_ctx, "XMR", "Balance", true, true); - ui->fiatTickerLayout->addWidget(m_balanceWidget); + + m_balanceTickerWidget = new BalanceTickerWidget(this, m_ctx, false); + ui->fiatTickerLayout->addWidget(m_balanceTickerWidget); connect(ui->ccsWidget, &CCSWidget::selected, this, &MainWindow::showSendScreen); connect(ui->redditWidget, &RedditWidget::setStatusText, this, &MainWindow::setStatusText); } -void MainWindow::initTouchBar() { -#ifdef Q_OS_MAC - m_touchbar = new KDMacTouchBar(this); - m_touchbarActionWelcome = new QAction(QIcon(":/assets/images/feather.png"), "Welcome to Feather!"); - m_touchbarWalletItems = {ui->actionSettings, ui->actionCalculator, ui->actionKeys, ui->actionDonate_to_Feather}; - m_touchbarWizardItems = {m_touchbarActionWelcome}; -#endif -} - void MainWindow::initWalletContext() { - connect(m_ctx, &AppContext::walletClosed, [this](){this->onWalletClosed();}); - connect(m_ctx, &AppContext::balanceUpdated, this, &MainWindow::onBalanceUpdated); - connect(m_ctx, &AppContext::walletOpened, this, &MainWindow::onWalletOpened); - connect(m_ctx, &AppContext::walletOpenedError, this, &MainWindow::onWalletOpenedError); - connect(m_ctx, &AppContext::walletCreatedError, this, &MainWindow::onWalletCreatedError); - connect(m_ctx, &AppContext::synchronized, this, &MainWindow::onSynchronized); - connect(m_ctx, &AppContext::blockchainSync, this, &MainWindow::onBlockchainSync); - connect(m_ctx, &AppContext::refreshSync, this, &MainWindow::onRefreshSync); - connect(m_ctx, &AppContext::createTransactionError, this, &MainWindow::onCreateTransactionError); - connect(m_ctx, &AppContext::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess); - connect(m_ctx, &AppContext::transactionCommitted, this, &MainWindow::onTransactionCommitted); - connect(m_ctx, &AppContext::walletOpenPasswordNeeded, this, &MainWindow::onWalletOpenPasswordRequired); - connect(m_ctx, &AppContext::deviceButtonRequest, this, &MainWindow::onDeviceButtonRequest); - connect(m_ctx, &AppContext::deviceError, this, &MainWindow::onDeviceError); - connect(m_ctx, &AppContext::donationNag, this, &MainWindow::onShowDonationNag); - connect(m_ctx, &AppContext::initiateTransaction, this, &MainWindow::onInitiateTransaction); - connect(m_ctx, &AppContext::endTransaction, this, &MainWindow::onEndTransaction); - connect(m_ctx, &AppContext::customRestoreHeightSet, this, &MainWindow::onCustomRestoreHeightSet); - connect(m_ctx, &AppContext::walletAboutToClose, this, &MainWindow::onWalletAboutToClose); + connect(m_ctx.get(), &AppContext::balanceUpdated, this, &MainWindow::onBalanceUpdated); + connect(m_ctx.get(), &AppContext::synchronized, this, &MainWindow::onSynchronized); + connect(m_ctx.get(), &AppContext::blockchainSync, this, &MainWindow::onBlockchainSync); + connect(m_ctx.get(), &AppContext::refreshSync, this, &MainWindow::onRefreshSync); + connect(m_ctx.get(), &AppContext::createTransactionError, this, &MainWindow::onCreateTransactionError); + connect(m_ctx.get(), &AppContext::createTransactionSuccess, this, &MainWindow::onCreateTransactionSuccess); + connect(m_ctx.get(), &AppContext::transactionCommitted, this, &MainWindow::onTransactionCommitted); + connect(m_ctx.get(), &AppContext::deviceError, this, &MainWindow::onDeviceError); + connect(m_ctx.get(), &AppContext::initiateTransaction, this, &MainWindow::onInitiateTransaction); + connect(m_ctx.get(), &AppContext::endTransaction, this, &MainWindow::onEndTransaction); + connect(m_ctx.get(), &AppContext::customRestoreHeightSet, this, &MainWindow::onCustomRestoreHeightSet); // Nodes - connect(m_ctx->nodes, &Nodes::updateStatus, [=](const QString &msg){this->setStatusText(msg);}); + connect(m_ctx->nodes, &Nodes::updateStatus, this, &MainWindow::onSetStatusText); connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage); connect(m_ctx->nodes, &Nodes::WSNodeExhausted, this, &MainWindow::showWSNodeExhaustedMessage); -} -void MainWindow::initWizard() { - this->setEnabled(false); - auto startPage = WalletWizard::Page_Menu; - if (config()->get(Config::firstRun).toBool() && !(TailsOS::detect() || WhonixOS::detect())) { - startPage = WalletWizard::Page_Network; - } - - m_wizard = this->createWizard(startPage); - m_wizard->show(); - m_wizard->setEnabled(true); - this->touchbarShowWizard(); + // Wallet + connect(m_ctx->wallet.get(), &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged); + connect(m_ctx->wallet.get(), &Wallet::currentSubaddressAccountChanged, this, &MainWindow::updateTitle); } void MainWindow::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 (m_ctx->networkType == NetworkType::STAGENET && config()->get(Config::warnOnStagenet).toBool()) { + if (constants::networkType == NetworkType::STAGENET && config()->get(Config::warnOnStagenet).toBool()) { QMessageBox::warning(this, "Warning", worthlessWarning.arg("stagenet")); config()->set(Config::warnOnStagenet, false); } - else if (m_ctx->networkType == NetworkType::TESTNET && config()->get(Config::warnOnTestnet).toBool()){ + else if (constants::networkType == NetworkType::TESTNET && config()->get(Config::warnOnTestnet).toBool()){ QMessageBox::warning(this, "Warning", worthlessWarning.arg("testnet")); config()->set(Config::warnOnTestnet, false); } @@ -452,18 +390,6 @@ void MainWindow::startupWarning() { } } -bool MainWindow::autoOpenWallet() { - QString autoPath = config()->get(Config::autoOpenWalletPath).toString(); - if (!autoPath.isEmpty() && autoPath.startsWith(QString::number(m_ctx->networkType))) { - autoPath.remove(0, 1); - } - if (!autoPath.isEmpty() && Utils::fileExists(autoPath)) { - m_ctx->onOpenWallet(autoPath, m_ctx->walletPassword); - return true; - } - return false; -} - void MainWindow::menuToggleTabVisible(const QString &key){ const auto toggleTab = m_tabShowHideMapper[key]; bool show = config()->get(toggleTab->configKey).toBool(); @@ -473,91 +399,16 @@ void MainWindow::menuToggleTabVisible(const QString &key){ toggleTab->menuAction->setText((show ? QString("Hide ") : QString("Show ")) + toggleTab->name); } -WalletWizard *MainWindow::createWizard(WalletWizard::Page startPage){ - auto *wizard = new WalletWizard(m_ctx, startPage, this); - connect(wizard, &WalletWizard::initialNetworkConfigured, this, &MainWindow::onInitialNetworkConfigured); - connect(wizard, &WalletWizard::skinChanged, this, &MainWindow::skinChanged); - connect(wizard, &WalletWizard::openWallet, m_ctx, &AppContext::onOpenWallet); - connect(wizard, &WalletWizard::defaultWalletDirChanged, m_windowSettings, &Settings::updatePaths); - connect(wizard, &WalletWizard::rejected, [this]{ - this->cleanupBeforeClose(); - QCoreApplication::quit(); - }); - return wizard; +QString MainWindow::walletName() { + return QFileInfo(m_ctx->wallet->cachePath()).fileName(); } -void MainWindow::showWizard(WalletWizard::Page startPage) { - this->setEnabled(false); - if (m_wizard == nullptr) - m_wizard = this->createWizard(startPage); - m_wizard->setStartId(startPage); - m_wizard->restart(); - m_wizard->setEnabled(true); - m_wizard->show(); +QString MainWindow::walletCachePath() { + return m_ctx->wallet->cachePath(); } -void MainWindow::onWalletClosed(WalletWizard::Page page) { - m_statusLabelBalance->clear(); - m_statusLabelStatus->clear(); - this->showWizard(page); -} - -void MainWindow::touchbarShowWizard() { -#ifdef Q_OS_MAC - m_touchbar->clear(); - for(auto* action: m_touchbarWizardItems) m_touchbar->addAction(action); -#endif -} - -void MainWindow::touchbarShowWallet() { -#ifdef Q_OS_MAC - m_touchbar->clear(); - for(auto* action: m_touchbarWalletItems) m_touchbar->addAction(action); -#endif -} - -void MainWindow::onWalletCreatedError(const QString &err) { - this->displayWalletErrorMsg(err); - m_splashDialog->hide(); - this->showWizard(WalletWizard::Page_Menu); -} - -void MainWindow::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) { - QFileInfo fileInfo(path); - - auto dialog = new PasswordDialog(this, fileInfo.fileName(), invalidPassword); - switch (dialog->exec()) { - case QDialog::Rejected: - { - this->showWizard(WalletWizard::Page_OpenWallet); - return; - } - } - - m_ctx->walletPassword = dialog->password; - m_ctx->onOpenWallet(m_ctx->walletPath, m_ctx->walletPassword); - - dialog->deleteLater(); -} - -void MainWindow::onDeviceButtonRequest(quint64 code) { - if (m_wizard) { - m_wizard->hide(); - } - - m_splashDialog->setMessage("Action required on device: Export the view key to open the wallet."); - m_splashDialog->setIcon(QPixmap(":/assets/images/key.png")); - m_splashDialog->show(); - m_splashDialog->setEnabled(true); -} - -void MainWindow::onWalletOpenedError(const QString &err) { - qDebug() << Q_FUNC_INFO << QString("Wallet open error: %1").arg(err); - m_splashDialog->hide(); - this->displayWalletErrorMsg(err); - this->setWindowTitle("Feather"); - this->showWizard(WalletWizard::Page_OpenWallet); - this->touchbarShowWizard(); +QString MainWindow::walletKeysPath() { + return m_ctx->wallet->keysPath(); } void MainWindow::displayWalletErrorMsg(const QString &err) { @@ -587,74 +438,70 @@ void MainWindow::onWalletOpened() { qDebug() << Q_FUNC_INFO; m_splashDialog->hide(); - if (m_wizard) { - m_wizard->hide(); - } - - if (m_ctx->currentWallet->isHwBacked()) { + if (m_ctx->wallet->isHwBacked()) { m_statusBtnHwDevice->show(); } this->bringToFront(); this->setEnabled(true); - if(!torManager()->torConnected) + if (!torManager()->torConnected) this->setStatusText("Wallet opened - Starting Tor (may take a while)"); else this->setStatusText("Wallet opened - Searching for node"); - connect(m_ctx->currentWallet, &Wallet::connectionStatusChanged, this, &MainWindow::onConnectionStatusChanged); - // receive page - m_ctx->currentWallet->subaddress()->refresh( m_ctx->currentWallet->currentSubaddressAccount()); - ui->receiveWidget->setModel( m_ctx->currentWallet->subaddressModel(), m_ctx->currentWallet); - if (m_ctx->currentWallet->subaddress()->count() == 1) { + m_ctx->wallet->subaddress()->refresh(m_ctx->wallet->currentSubaddressAccount()); + if (m_ctx->wallet->subaddress()->count() == 1) { for (int i = 0; i < 10; i++) { - m_ctx->currentWallet->subaddress()->addRow(m_ctx->currentWallet->currentSubaddressAccount(), ""); + m_ctx->wallet->subaddress()->addRow(m_ctx->wallet->currentSubaddressAccount(), ""); } } + m_ctx->wallet->subaddressModel()->setCurrentSubaddressAcount(m_ctx->wallet->currentSubaddressAccount()); // history page - m_ctx->currentWallet->history()->refresh(m_ctx->currentWallet->currentSubaddressAccount()); - ui->historyWidget->setModel(m_ctx->currentWallet->historyModel(), m_ctx->currentWallet); - - // contacts widget - ui->contactWidget->setModel(m_ctx->currentWallet->addressBookModel()); + m_ctx->wallet->history()->refresh(m_ctx->wallet->currentSubaddressAccount()); // coins page - m_ctx->currentWallet->coins()->refresh(m_ctx->currentWallet->currentSubaddressAccount()); - ui->coinsWidget->setModel(m_ctx->currentWallet->coinsModel(), m_ctx->currentWallet->coins()); + m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); + m_coinsWidget->setModel(m_ctx->wallet->coinsModel(), m_ctx->wallet->coins()); - this->touchbarShowWallet(); this->updatePasswordIcon(); - - this->setTitle(false); - + this->updateTitle(); + m_ctx->nodes->connectToNode(); m_updateBytes.start(250); + + this->updateRecentlyOpened(m_ctx->wallet->cachePath()); } void MainWindow::onBalanceUpdated(quint64 balance, quint64 spendable) { qDebug() << Q_FUNC_INFO; + bool hide = config()->get(Config::hideBalance).toBool(); + int displaySetting = config()->get(Config::balanceDisplay).toInt(); + int decimals = config()->get(Config::amountPrecision).toInt(); - int amountPrecision = config()->get(Config::amountPrecision).toInt(); + QString balance_str = "Balance: "; + if (hide) { + balance_str += "HIDDEN"; + } + else if (displaySetting == Config::totalBalance) { + balance_str += QString("%1 XMR").arg(WalletManager::displayAmount(balance, false, decimals)); + } + else if (displaySetting == Config::spendable || displaySetting == Config::spendablePlusUnconfirmed) { + balance_str += QString("%1 XMR").arg(WalletManager::displayAmount(spendable, false, decimals)); - QString balance_str = WalletManager::displayAmount(spendable); - balance_str.remove(QRegExp("0+$")); - - QString label_str = QString("Balance: %1 XMR").arg(balance_str); - if (balance > spendable) { - QString unconfirmed_str = WalletManager::displayAmount(spendable); - unconfirmed_str.remove(QRegExp("0+$")); - label_str += QString(" (+%1 XMR unconfirmed)").arg(Utils::balanceFormat(balance - spendable)); + if (displaySetting == Config::spendablePlusUnconfirmed && balance > spendable) { + balance_str += QString(" (+%1 XMR unconfirmed)").arg(WalletManager::displayAmount(balance - spendable, false, decimals)); + } } - - if (hide) - label_str = "Balance: HIDDEN"; - m_statusLabelBalance->setToolTip("Click for details"); - m_statusLabelBalance->setText(label_str); - m_balanceWidget->setHidden(hide); + m_statusLabelBalance->setText(balance_str); + m_balanceTickerWidget->setHidden(hide); +} + +void MainWindow::onSetStatusText(const QString &text) { + this->setStatusText(text); } void MainWindow::setStatusText(const QString &text, bool override, int timeout) { @@ -728,42 +575,51 @@ void MainWindow::onConnectionStatusChanged(int status) void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector &address) { auto tx_status = tx->status(); - auto err = QString("Can't create transaction: "); + QString err{"Can't create transaction: "}; - if(tx_status != PendingTransaction::Status_Ok){ - auto tx_err = tx->errorString(); + if (tx_status != PendingTransaction::Status_Ok){ + QString tx_err = tx->errorString(); qCritical() << tx_err; - if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion) + if (m_ctx->wallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion) err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err); else err = QString("%1 %2").arg(err).arg(tx_err); + if (tx_err.contains("Daemon response did not include the requested real output")) { + QString currentNode = m_ctx->nodes->connection().toAddress(); + + err += QString("\nYou are currently connected to: %1\n\n" + "This node may be acting maliciously. You are strongly recommended to disconnect from this node." + "Please report this incident to dev@featherwallet.org, #feather on OFTC or /r/FeatherWallet.").arg(currentNode); + } + qDebug() << Q_FUNC_INFO << err; this->displayWalletErrorMsg(err); - m_ctx->currentWallet->disposeTransaction(tx); - } else if (tx->txCount() == 0) { + m_ctx->wallet->disposeTransaction(tx); + } + else if (tx->txCount() == 0) { err = QString("%1 %2").arg(err).arg("No unmixable outputs to sweep."); qDebug() << Q_FUNC_INFO << err; this->displayWalletErrorMsg(err); - m_ctx->currentWallet->disposeTransaction(tx); - } else { + m_ctx->wallet->disposeTransaction(tx); + } + else { const auto &description = m_ctx->tmpTxDescription; // Show advanced dialog on multi-destination transactions if (address.size() > 1) { - auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this); - dialog_adv->setTransaction(tx); - dialog_adv->exec(); - dialog_adv->deleteLater(); + TxConfAdvDialog dialog_adv{m_ctx, description, this}; + dialog_adv.setTransaction(tx); + dialog_adv.exec(); return; } - auto *dialog = new TxConfDialog(m_ctx, tx, address[0], description, this); - switch (dialog->exec()) { + TxConfDialog dialog{m_ctx, tx, address[0], description, this}; + switch (dialog.exec()) { case QDialog::Rejected: { - if (!dialog->showAdvanced) + if (!dialog.showAdvanced) m_ctx->onCancelTransaction(tx, address); break; } @@ -772,13 +628,11 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto break; } - if (dialog->showAdvanced) { - auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this); - dialog_adv->setTransaction(tx); - dialog_adv->exec(); - dialog_adv->deleteLater(); + if (dialog.showAdvanced) { + TxConfAdvDialog dialog_adv{m_ctx, description, this}; + dialog_adv.setTransaction(tx); + dialog_adv.exec(); } - dialog->deleteLater(); } } @@ -786,7 +640,7 @@ void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, con if (status) { // success QString body = QString("Successfully sent %1 transaction(s).").arg(txid.count()); QMessageBox::information(this, "Transactions sent", body); - ui->sendWidget->clearFields(); + m_sendWidget->clearFields(); } else { auto err = tx->errorString(); QString body = QString("Error committing transaction: %1").arg(err); @@ -805,35 +659,33 @@ void MainWindow::onCreateTransactionError(const QString &message) { } void MainWindow::showWalletInfoDialog() { - auto *dialog = new WalletInfoDialog(m_ctx, this); - dialog->exec(); - dialog->deleteLater(); + WalletInfoDialog dialog{m_ctx, this}; + dialog.exec(); } void MainWindow::showSeedDialog() { - if (m_ctx->currentWallet->isHwBacked()) { + if (m_ctx->wallet->isHwBacked()) { QMessageBox::information(this, "Information", "Seed unavailable: Wallet keys are stored on hardware device."); return; } - if (m_ctx->currentWallet->viewOnly()) { + if (m_ctx->wallet->viewOnly()) { QMessageBox::information(this, "Information", "Wallet is view-only and has no seed.\n\nTo obtain wallet keys go to Wallet -> View-Only"); return; } - if (!m_ctx->currentWallet->isDeterministic()) { + if (!m_ctx->wallet->isDeterministic()) { QMessageBox::information(this, "Information", "Wallet is non-deterministic and has no seed.\n\nTo obtain wallet keys go to Wallet -> Keys"); return; } - auto *dialog = new SeedDialog(m_ctx->currentWallet, this); - dialog->exec(); - dialog->deleteLater(); + SeedDialog dialog{m_ctx, this}; + dialog.exec(); } void MainWindow::showConnectionStatusDialog() { - auto status = m_ctx->currentWallet->connectionStatus(); - bool synchronized = m_ctx->currentWallet->isSynchronized(); + auto status = m_ctx->wallet->connectionStatus(); + bool synchronized = m_ctx->wallet->isSynchronized(); QString statusMsg; switch(status){ @@ -864,64 +716,52 @@ void MainWindow::showConnectionStatusDialog() { statusMsg = "Unknown connection status (this should never happen)."; } - statusMsg += QString("\n\nTx: %1, Rx: %2").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesSent()), - Utils::formatBytes(m_ctx->currentWallet->getBytesReceived())); + statusMsg += QString("\n\nTx: %1, Rx: %2").arg(Utils::formatBytes(m_ctx->wallet->getBytesSent()), + Utils::formatBytes(m_ctx->wallet->getBytesReceived())); QMessageBox::information(this, "Connection Status", statusMsg); } void MainWindow::showPasswordDialog() { - auto *pdialog = new PasswordChangeDialog(this, m_ctx->currentWallet); - pdialog->exec(); - pdialog->deleteLater(); + PasswordChangeDialog dialog{this, m_ctx->wallet.get()}; + dialog.exec(); this->updatePasswordIcon(); } void MainWindow::updatePasswordIcon() { - QIcon icon = m_ctx->currentWallet->getPassword().isEmpty() ? icons()->icon("unlock.svg") : icons()->icon("lock.svg"); + QIcon icon = m_ctx->wallet->getPassword().isEmpty() ? icons()->icon("unlock.svg") : icons()->icon("lock.svg"); m_statusBtnPassword->setIcon(icon); } void MainWindow::showRestoreHeightDialog() { // settings custom restore height is only available for 25 word seeds - auto seed = m_ctx->currentWallet->getCacheAttribute("feather.seed"); - if(!seed.isEmpty()) { + auto seed = m_ctx->wallet->getCacheAttribute("feather.seed"); + if(!seed.isEmpty()) { // TODO: update this warning (import tx, delete cache, restore from seed) const auto msg = "This wallet has a 14 word mnemonic seed which has the restore height embedded."; QMessageBox::warning(this, "Cannot set custom restore height", msg); return; } - m_restoreDialog = new RestoreDialog(m_ctx, this); - m_restoreDialog->show(); - - connect(m_restoreDialog, &RestoreDialog::accepted, [=]{ - auto height = m_restoreDialog->getHeight(); - m_restoreDialog->disconnect(); - m_restoreDialog->deleteLater(); - m_ctx->onSetRestoreHeight(height); - }); - - connect(m_restoreDialog, &RestoreDialog::rejected, [=]{ - m_restoreDialog->disconnect(); - m_restoreDialog->deleteLater(); - }); + RestoreHeightDialog dialog{this, m_ctx->wallet->getWalletCreationHeight()}; + if (dialog.exec() == QDialog::Accepted) { + int restoreHeight = dialog.getHeight(); + m_ctx->onSetRestoreHeight(restoreHeight); + } } void MainWindow::showKeysDialog() { - auto *dialog = new KeysDialog(m_ctx, this); - dialog->exec(); - dialog->deleteLater(); + KeysDialog dialog{m_ctx, this}; + dialog.exec(); } void MainWindow::showViewOnlyDialog() { - auto *dialog = new ViewOnlyDialog(m_ctx, this); - dialog->exec(); - dialog->deleteLater(); + ViewOnlyDialog dialog{m_ctx, this}; + dialog.exec(); } void MainWindow::menuTorClicked() { - auto *dialog = new TorInfoDialog(this, m_ctx); - connect(dialog, &TorInfoDialog::torSettingsChanged, m_ctx, &AppContext::onTorSettingsChanged); + auto *dialog = new TorInfoDialog(m_ctx, this); + connect(dialog, &TorInfoDialog::torSettingsChanged, m_windowManager, &WindowManager::onTorSettingsChanged); dialog->exec(); dialog->deleteLater(); } @@ -930,20 +770,21 @@ void MainWindow::menuHwDeviceClicked() { QMessageBox::information(this, "Hardware Device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice())); } +void MainWindow::menuOpenClicked() { + m_windowManager->wizardOpenWallet(); +} + void MainWindow::menuNewRestoreClicked() { - // TODO: implement later + m_windowManager->showWizard(WalletWizard::Page_Menu); } void MainWindow::menuQuitClicked() { - cleanupBeforeClose(); - QCoreApplication::quit(); + this->close(); } void MainWindow::menuWalletCloseClicked() { - if (m_ctx->currentWallet == nullptr) - return; - - m_ctx->closeWallet(true, true); + m_windowManager->showWizard(WalletWizard::Page_Menu); + this->close(); } void MainWindow::menuAboutClicked() { @@ -958,24 +799,17 @@ void MainWindow::menuSettingsClicked() { } void MainWindow::menuSignVerifyClicked() { - SignVerifyDialog dialog{m_ctx->currentWallet, this}; + SignVerifyDialog dialog{m_ctx->wallet.get(), this}; dialog.exec(); } void MainWindow::menuVerifyTxProof() { - VerifyProofDialog dialog{m_ctx->currentWallet, this}; + VerifyProofDialog dialog{m_ctx->wallet.get(), this}; dialog.exec(); } void MainWindow::skinChanged(const QString &skinName) { - if (!m_skins.contains(skinName)) { - qWarning() << QString("No such skin %1").arg(skinName); - return; - } - - config()->set(Config::skin, skinName); - qApp->setStyleSheet(m_skins[skinName]); - qDebug() << QString("Skin changed to %1").arg(skinName); + m_windowManager->changeSkin(skinName); ColorScheme::updateFromWidget(this); #ifdef HAS_LOCALMONERO @@ -986,17 +820,29 @@ void MainWindow::skinChanged(const QString &skinName) { } void MainWindow::closeEvent(QCloseEvent *event) { - cleanupBeforeClose(); + qDebug() << Q_FUNC_INFO; - QWidget::closeEvent(event); + if (!this->cleanedUp) { + this->cleanedUp = true; + + m_historyWidget->resetModel(); + + m_updateBytes.stop(); + m_txTimer.stop(); + + this->saveGeo(); + m_windowManager->closeWindow(this); + } + + event->accept(); } void MainWindow::donateButtonClicked() { - double donation = appData()->prices.convert("EUR", "XMR", globals::donationAmount); + double donation = appData()->prices.convert("EUR", "XMR", constants::donationAmount); if (donation <= 0) donation = 0.1337; - ui->sendWidget->fill(globals::donationAddress, "Donation to the Feather development team", donation); + m_sendWidget->fill(constants::donationAddress, "Donation to the Feather development team", donation); ui->tabWidget->setCurrentIndex(Tabs::SEND); } @@ -1016,7 +862,7 @@ void MainWindow::showCalcWindow() { void MainWindow::payToMany() { ui->tabWidget->setCurrentIndex(Tabs::SEND); - ui->sendWidget->payToMany(); + m_sendWidget->payToMany(); QMessageBox::information(this, "Pay to many", "Enter a list of outputs in the 'Pay to' field.\n" "One output per line.\n" "Format: address, amount\n" @@ -1024,17 +870,17 @@ void MainWindow::payToMany() { } void MainWindow::showSendScreen(const CCSEntry &entry) { - ui->sendWidget->fill(entry); + m_sendWidget->fill(entry); ui->tabWidget->setCurrentIndex(Tabs::SEND); } void MainWindow::onViewOnBlockExplorer(const QString &txid) { - QString blockExplorerLink = Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), m_ctx->networkType, txid); + QString blockExplorerLink = Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, txid); Utils::externalLinkWarning(this, blockExplorerLink); } void MainWindow::onResendTransaction(const QString &txid) { - if (!AppContext::txCache.contains(txid)) { + if (!m_ctx->txCache.contains(txid)) { QMessageBox::warning(this, "Unable to resend transaction", "Transaction was not found in transaction cache. Unable to resend."); return; } @@ -1042,23 +888,22 @@ void MainWindow::onResendTransaction(const QString &txid) { // Connect to a different node so chances of successful relay are higher m_ctx->nodes->autoConnect(true); - auto dialog = new BroadcastTxDialog(this, m_ctx, AppContext::txCache[txid]); - dialog->exec(); - dialog->deleteLater(); + BroadcastTxDialog dialog{this, m_ctx, m_ctx->txCache[txid]}; + dialog.exec(); } void MainWindow::importContacts() { const QString targetFile = QFileDialog::getOpenFileName(this, "Import CSV file", QDir::homePath(), "CSV Files (*.csv)"); if(targetFile.isEmpty()) return; - auto *model = m_ctx->currentWallet->addressBookModel(); + auto *model = m_ctx->wallet->addressBookModel(); QMapIterator i(model->readCSV(targetFile)); int inserts = 0; while (i.hasNext()) { i.next(); - bool addressValid = WalletManager::addressValid(i.value(), m_ctx->currentWallet->nettype()); + bool addressValid = WalletManager::addressValid(i.value(), m_ctx->wallet->nettype()); if(addressValid) { - m_ctx->currentWallet->addressBook()->addRow(i.value(), "", i.key()); + m_ctx->wallet->addressBook()->addRow(i.value(), "", i.key()); inserts++; } } @@ -1066,30 +911,6 @@ void MainWindow::importContacts() { QMessageBox::information(this, "Contacts imported", QString("Total contacts imported: %1").arg(inserts)); } -MainWindow *MainWindow::getInstance() { - return pMainWindow; -} - -AppContext *MainWindow::getContext(){ - return pMainWindow->m_ctx; -} - -QString MainWindow::loadStylesheet(const QString &resource) { - QFile f(resource); - if (!f.exists()) { - printf("Unable to set stylesheet, file not found\n"); - f.close(); - return ""; - } - - f.open(QFile::ReadOnly | QFile::Text); - QTextStream ts(&f); - QString data = ts.readAll(); - f.close(); - - return data; -} - void MainWindow::saveGeo() { config()->set(Config::geometry, QString(saveGeometry().toBase64())); config()->set(Config::windowState, QString(saveState().toBase64())); @@ -1102,15 +923,18 @@ void MainWindow::restoreGeo() { } void MainWindow::showDebugInfo() { - auto *dialog = new DebugInfoDialog(m_ctx, this); - dialog->exec(); - dialog->deleteLater(); + DebugInfoDialog dialog{m_ctx, this}; + dialog.exec(); } void MainWindow::showWalletCacheDebugDialog() { - auto *dialog = new WalletCacheDebugDialog(m_ctx, this); - dialog->exec(); - dialog->deleteLater(); + WalletCacheDebugDialog dialog{m_ctx, this}; + dialog.exec(); +} + +void MainWindow::showAccountSwitcherDialog() { + AccountSwitcherDialog dialog{m_ctx, this}; + dialog.exec(); } void MainWindow::showNodeExhaustedMessage() { @@ -1133,8 +957,8 @@ void MainWindow::exportKeyImages() { QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QDir::homePath(), "Key Images (*_keyImages)"); if (fn.isEmpty()) return; if (!fn.endsWith("_keyImages")) fn += "_keyImages"; - m_ctx->currentWallet->exportKeyImages(fn, true); - auto err = m_ctx->currentWallet->errorString(); + m_ctx->wallet->exportKeyImages(fn, true); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Key image export", QString("Failed to export key images.\nReason: %1").arg(err)); } else { @@ -1145,8 +969,8 @@ void MainWindow::exportKeyImages() { void MainWindow::importKeyImages() { QString fn = QFileDialog::getOpenFileName(this, "Import key image file", QDir::homePath(), "Key Images (*_keyImages)"); if (fn.isEmpty()) return; - m_ctx->currentWallet->importKeyImages(fn); - auto err = m_ctx->currentWallet->errorString(); + m_ctx->wallet->importKeyImages(fn); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Key image import", QString("Failed to import key images.\n\n%1").arg(err)); } else { @@ -1159,8 +983,8 @@ void MainWindow::exportOutputs() { QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QDir::homePath(), "Outputs (*_outputs)"); if (fn.isEmpty()) return; if (!fn.endsWith("_outputs")) fn += "_outputs"; - m_ctx->currentWallet->exportOutputs(fn, true); - auto err = m_ctx->currentWallet->errorString(); + m_ctx->wallet->exportOutputs(fn, true); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Outputs export", QString("Failed to export outputs.\nReason: %1").arg(err)); } else { @@ -1171,8 +995,8 @@ void MainWindow::exportOutputs() { void MainWindow::importOutputs() { QString fn = QFileDialog::getOpenFileName(this, "Import outputs file", QDir::homePath(), "Outputs (*_outputs)"); if (fn.isEmpty()) return; - m_ctx->currentWallet->importOutputs(fn); - auto err = m_ctx->currentWallet->errorString(); + m_ctx->wallet->importOutputs(fn); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Outputs import", QString("Failed to import outputs.\n\n%1").arg(err)); } else { @@ -1181,17 +1005,11 @@ void MainWindow::importOutputs() { } } -void MainWindow::cleanupBeforeClose() { - m_ctx->closeWallet(false, true); - torManager()->stop(); - this->saveGeo(); -} - void MainWindow::loadUnsignedTx() { QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*unsigned_monero_tx)"); if (fn.isEmpty()) return; - UnsignedTransaction *tx = m_ctx->currentWallet->loadTxFile(fn); - auto err = m_ctx->currentWallet->errorString(); + UnsignedTransaction *tx = m_ctx->wallet->loadTxFile(fn); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Load transaction from file", QString("Failed to load transaction.\n\n%1").arg(err)); return; @@ -1206,8 +1024,8 @@ void MainWindow::loadUnsignedTxFromClipboard() { QMessageBox::warning(this, "Load unsigned transaction from clipboard", "Clipboard is empty"); return; } - UnsignedTransaction *tx = m_ctx->currentWallet->loadTxFromBase64Str(unsigned_tx); - auto err = m_ctx->currentWallet->errorString(); + UnsignedTransaction *tx = m_ctx->wallet->loadTxFromBase64Str(unsigned_tx); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Load unsigned transaction from clipboard", QString("Failed to load transaction.\n\n%1").arg(err)); return; @@ -1219,34 +1037,30 @@ void MainWindow::loadUnsignedTxFromClipboard() { void MainWindow::loadSignedTx() { QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx)"); if (fn.isEmpty()) return; - PendingTransaction *tx = m_ctx->currentWallet->loadSignedTxFile(fn); - auto err = m_ctx->currentWallet->errorString(); + PendingTransaction *tx = m_ctx->wallet->loadSignedTxFile(fn); + auto err = m_ctx->wallet->errorString(); if (!err.isEmpty()) { QMessageBox::warning(this, "Load signed transaction from file", err); return; } - auto *dialog = new TxConfAdvDialog(m_ctx, "", this); - dialog->setTransaction(tx); - dialog->exec(); - dialog->deleteLater(); + TxConfAdvDialog dialog{m_ctx, "", this}; + dialog.setTransaction(tx); + dialog.exec(); } void MainWindow::loadSignedTxFromText() { - auto dialog = new BroadcastTxDialog(this, m_ctx); - dialog->exec(); - dialog->deleteLater(); + BroadcastTxDialog dialog{this, m_ctx}; + dialog.exec(); } void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) { - auto *dialog = new TxConfAdvDialog(m_ctx, "", this); - dialog->setUnsignedTransaction(tx); - dialog->exec(); - dialog->deleteLater(); + TxConfAdvDialog dialog{m_ctx, "", this}; + dialog.setUnsignedTransaction(tx); + dialog.exec(); } void MainWindow::importTransaction() { - if (config()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptNode) { // TODO: don't show if connected to local node @@ -1259,9 +1073,8 @@ void MainWindow::importTransaction() { } } - auto *dialog = new TxImportDialog(this, m_ctx); - dialog->exec(); - dialog->deleteLater(); + TxImportDialog dialog(this, m_ctx); + dialog.exec(); } void MainWindow::onDeviceError(const QString &error) { @@ -1273,56 +1086,51 @@ void MainWindow::onDeviceError(const QString &error) { m_showDeviceError = true; auto result = QMessageBox::question(this, "Hardware device", "Lost connection to hardware device. Attempt to reconnect?"); if (result == QMessageBox::Yes) { - bool r = m_ctx->currentWallet->reconnectDevice(); + bool r = m_ctx->wallet->reconnectDevice(); if (r) { break; } } - if (result == QMessageBox::No){ - m_ctx->closeWallet(true); + if (result == QMessageBox::No) { + this->menuWalletCloseClicked(); return; } } m_statusBtnHwDevice->setIcon(icons()->icon("ledger.png")); - m_ctx->currentWallet->startRefresh(); + m_ctx->wallet->startRefresh(); m_showDeviceError = false; } void MainWindow::updateNetStats() { - if (m_ctx->currentWallet == nullptr) { + if (m_ctx->wallet == nullptr) { + m_statusLabelNetStats->setText(""); + return; + } + if (m_ctx->wallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) { m_statusLabelNetStats->setText(""); return; } - if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected) { - m_statusLabelNetStats->setText(""); - return; - } - - if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Synchronized) { + if (m_ctx->wallet->connectionStatus() == Wallet::ConnectionStatus_Synchronized) { m_statusLabelNetStats->setText(""); return; } - m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_ctx->currentWallet->getBytesReceived()))); + m_statusLabelNetStats->setText(QString("(D: %1)").arg(Utils::formatBytes(m_ctx->wallet->getBytesReceived()))); } void MainWindow::rescanSpent() { - if (!m_ctx->currentWallet->rescanSpent()) { - QMessageBox::warning(this, "Rescan spent", m_ctx->currentWallet->errorString()); + if (!m_ctx->wallet->rescanSpent()) { + QMessageBox::warning(this, "Rescan spent", m_ctx->wallet->errorString()); } else { QMessageBox::information(this, "Rescan spent", "Successfully rescanned spent outputs."); } } void MainWindow::showBalanceDialog() { - if (!m_ctx->currentWallet) { - return; - } - auto *dialog = new BalanceDialog(this, m_ctx->currentWallet); - dialog->exec(); - dialog->deleteLater(); + BalanceDialog dialog{this, m_ctx->wallet.get()}; + dialog.exec(); } QString MainWindow::statusDots() { @@ -1331,6 +1139,13 @@ QString MainWindow::statusDots() { return QString(".").repeated(m_statusDots); } +void MainWindow::showOrHide() { + if (this->isHidden()) + this->bringToFront(); + else + this->hide(); +} + void MainWindow::bringToFront() { ensurePolished(); setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive); @@ -1339,12 +1154,11 @@ void MainWindow::bringToFront() { activateWindow(); } -void MainWindow::onInitialNetworkConfigured() { - m_ctx->onInitialNetworkConfigured(); - - connect(torManager(), &TorManager::connectionStateChanged, [this](bool connected){ - connected ? m_statusBtnTor->setIcon(icons()->icon("tor_logo.png")) - : m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png"));}); +void MainWindow::onTorConnectionStateChanged(bool connected) { + if (connected) + m_statusBtnTor->setIcon(icons()->icon("tor_logo.png")); + else + m_statusBtnTor->setIcon(icons()->icon("tor_logo_disabled.png")); } void MainWindow::onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, @@ -1367,7 +1181,7 @@ void MainWindow::onShowUpdateCheck(const QString &version, const QString &binary QString downloadUrl = QString("https://featherwallet.org/files/releases/%1/%2").arg(this->getPlatformTag(), binaryFilename); UpdateDialog updateDialog{this, version, downloadUrl, hash, signer}; - connect(&updateDialog, &UpdateDialog::restartWallet, this, &MainWindow::onRestartApplication); + connect(&updateDialog, &UpdateDialog::restartWallet, m_windowManager, &WindowManager::restartApplication); updateDialog.exec(); } @@ -1395,7 +1209,7 @@ void MainWindow::onUpdatesAvailable(const QJsonObject &updates) { // Hooray! New update available - QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(globals::websiteUrl, newVersion); + QString hashesUrl = QString("%1/files/releases/hashes-%2-plain.txt").arg(constants::websiteUrl, newVersion); UtilsNetworking network{getNetworkTor()}; QNetworkReply *reply = network.get(hashesUrl); @@ -1446,7 +1260,7 @@ void MainWindow::onInitiateTransaction() { m_constructingTransaction = true; m_txTimer.start(1000); - if (m_ctx->currentWallet->isHwBacked()) { + if (m_ctx->wallet->isHwBacked()) { QString message = "Constructing transaction: action may be required on device."; m_splashDialog->setMessage(message); m_splashDialog->setIcon(QPixmap(":/assets/images/unconfirmed.png")); @@ -1461,7 +1275,7 @@ void MainWindow::onEndTransaction() { m_txTimer.stop(); this->setStatusText(m_statusText); - if (m_ctx->currentWallet->isHwBacked()) { + if (m_ctx->wallet->isHwBacked()) { m_splashDialog->hide(); } } @@ -1473,34 +1287,21 @@ void MainWindow::onCustomRestoreHeightSet(int height) { this->menuQuitClicked(); } -void MainWindow::onWalletAboutToClose() { - if (!config()->get(Config::showTabHome).toBool()) - ui->tabWidget->setCurrentIndex(Tabs::HISTORY); - else - ui->tabWidget->setCurrentIndex(Tabs::HOME); - - // Clear all tables when wallet is closed - ui->historyWidget->resetModel(); - ui->contactWidget->resetModel(); - ui->receiveWidget->resetModel(); - ui->coinsWidget->resetModel(); -} - void MainWindow::onExportHistoryCSV(bool checked) { - if (m_ctx->currentWallet == nullptr) + if (m_ctx->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_ctx->currentWallet->history()->writeCSV(fn); + m_ctx->wallet->history()->writeCSV(fn); QMessageBox::information(this, "CSV export", QString("Transaction history exported to %1").arg(fn)); } void MainWindow::onExportContactsCSV(bool checked) { - if (m_ctx->currentWallet == nullptr) return; - auto *model = m_ctx->currentWallet->addressBookModel(); + if (m_ctx->wallet == nullptr) return; + auto *model = m_ctx->wallet->addressBookModel(); if (model->rowCount() <= 0){ QMessageBox::warning(this, "Error", "Addressbook empty"); return; @@ -1530,13 +1331,6 @@ void MainWindow::onReportBug(bool checked) { "Try to explain not only what the bug is, but how it occurs."); } -void MainWindow::onRestartApplication(const QString &binaryFilename) { - QProcess::startDetached(binaryFilename, qApp->arguments()); - - this->cleanupBeforeClose(); - QCoreApplication::quit(); -} - QString MainWindow::getPlatformTag() { #ifdef Q_OS_MACOS return "mac"; @@ -1554,26 +1348,96 @@ QString MainWindow::getPlatformTag() { } QString MainWindow::getHardwareDevice() { - if (!m_ctx->currentWallet->isHwBacked()) + if (!m_ctx->wallet->isHwBacked()) return ""; - if (m_ctx->currentWallet->isTrezor()) + if (m_ctx->wallet->isTrezor()) return "Trezor"; - if (m_ctx->currentWallet->isLedger()) + if (m_ctx->wallet->isLedger()) return "Ledger"; return "Unknown"; } -void MainWindow::setTitle(bool mining) { - QFileInfo fileInfo(m_ctx->walletPath); - auto title = QString("Feather - [%1]").arg(fileInfo.fileName()); - if (m_ctx->currentWallet && m_ctx->currentWallet->viewOnly()) +void MainWindow::updateTitle() { + QString title = QString("%1 (#%2)").arg(this->walletName(), QString::number(m_ctx->wallet->currentSubaddressAccount())); + + if (m_ctx->wallet->viewOnly()) title += " [view-only]"; - if (mining) + if (m_xmrig->isMining()) title += " [mining]"; + title += " - Feather"; + this->setWindowTitle(title); } +void MainWindow::donationNag() { + if (m_ctx->networkType != NetworkType::Type::MAINNET) + return; + + if (m_ctx->wallet->viewOnly()) + return; + + auto donationCounter = config()->get(Config::donateBeg).toInt(); + if (donationCounter == -1) + return; + + donationCounter++; + if (donationCounter % constants::donationBoundary == 0) { + auto msg = "Feather is a 100% community-sponsored endeavor. Please consider supporting " + "the project financially. Get rid of this message by donating any amount."; + int ret = QMessageBox::information(this, "Donate to Feather", msg, QMessageBox::Yes, QMessageBox::No); + if (ret == QMessageBox::Yes) { + this->donateButtonClicked(); + } + } + config()->set(Config::donateBeg, donationCounter); +} + +void MainWindow::updateRecentlyOpened(const QString &keysFile) { + auto recent = config()->get(Config::recentlyOpenedWallets).toList(); + + if (recent.contains(keysFile)) { + recent.removeOne(keysFile); + } + recent.insert(0, keysFile); + + QList recent_; + int count = 0; + for (const auto &file : recent) { + if (Utils::fileExists(file.toString())) { + recent_.append(file); + count++; + } + if (count >= 5) { + break; + } + } + + config()->set(Config::recentlyOpenedWallets, recent_); + ui->menuRecently_open->clear(); + for (const auto &var : recent_) { + QString path = var.toString(); + QFileInfo fileInfo{path}; + ui->menuRecently_open->addAction(fileInfo.fileName(), m_windowManager, std::bind(&WindowManager::tryOpenWallet, m_windowManager, path, "")); + } +} + +void MainWindow::toggleSearchbar(bool visible) { + config()->set(Config::showSearchbar, visible); + + m_historyWidget->setSearchbarVisible(visible); + m_receiveWidget->setSearchbarVisible(visible); + m_contactsWidget->setSearchbarVisible(visible); + + int currentTab = ui->tabWidget->currentIndex(); + if (currentTab == Tabs::HISTORY) + m_historyWidget->focusSearchbar(); + else if (currentTab == Tabs::SEND) + m_contactsWidget->focusSearchbar(); + else if (currentTab == Tabs::RECEIVE) + m_receiveWidget->focusSearchbar(); +} + MainWindow::~MainWindow() { delete ui; } diff --git a/src/mainwindow.h b/src/mainwindow.h index 9faa7d3e..56f2dc09 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -21,7 +21,7 @@ #include "dialog/passwordchangedialog.h" #include "dialog/keysdialog.h" #include "dialog/aboutdialog.h" -#include "dialog/restoredialog.h" +#include "dialog/RestoreHeightDialog.h" #include "dialog/splashdialog.h" #include "libwalletqt/Wallet.h" #include "model/SubaddressModel.h" @@ -33,9 +33,17 @@ #include "utils/config.h" #include "widgets/ccswidget.h" #include "widgets/redditwidget.h" -#include "widgets/tickerwidget.h" +#include "widgets/TickerWidget.h" #include "wizard/WalletWizard.h" +#include "contactswidget.h" +#include "historywidget.h" +#include "sendwidget.h" +#include "receivewidget.h" +#include "coinswidget.h" + +#include "WindowManager.h" + #ifdef HAS_LOCALMONERO #include "widgets/LocalMoneroWidget.h" #endif @@ -44,10 +52,6 @@ #include "widgets/xmrigwidget.h" #endif -#ifdef Q_OS_MAC -#include "src/kdmactouchbar.h" -#endif - namespace Ui { class MainWindow; } @@ -62,16 +66,19 @@ struct ToggleTab { Config::ConfigKey configKey; }; +class WindowManager; class MainWindow : public QMainWindow { Q_OBJECT public: - explicit MainWindow(AppContext *ctx, QWidget *parent = nullptr); - static MainWindow *getInstance(); - static AppContext *getContext(); + explicit MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *parent = nullptr); ~MainWindow() override; + QString walletName(); + QString walletCachePath(); + QString walletKeysPath(); + enum Tabs { HOME = 0, HISTORY, @@ -88,44 +95,29 @@ public: REDDIT }; -public slots: - void showWizard(WalletWizard::Page startPage); + void showOrHide(); + void bringToFront(); + +signals: + void closed(); + +private slots: + // TODO: use a consistent naming convention for slots + // Menu + void menuOpenClicked(); void menuNewRestoreClicked(); void menuQuitClicked(); void menuSettingsClicked(); void menuAboutClicked(); void menuSignVerifyClicked(); void menuVerifyTxProof(); - void showWalletInfoDialog(); - void showSeedDialog(); - void showConnectionStatusDialog(); - void showPasswordDialog(); - void showKeysDialog(); - void showViewOnlyDialog(); - void donateButtonClicked(); - void showCalcWindow(); - void payToMany(); - void showWalletCacheDebugDialog(); - void showSendTab(); - void showHistoryTab(); - void showSendScreen(const CCSEntry &entry); - void skinChanged(const QString &skinName); - void menuTorClicked(); - void onBlockchainSync(int height, int target); - void onRefreshSync(int height, int target); - void onWalletOpenedError(const QString &err); - void onWalletCreatedError(const QString &err); void menuWalletCloseClicked(); - void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path); - void onDeviceButtonRequest(quint64 code); - void onViewOnBlockExplorer(const QString &txid); - void onResendTransaction(const QString &txid); - void importContacts(); - void showRestoreHeightDialog(); - void importTransaction(); - void onDeviceError(const QString &error); - void menuHwDeviceClicked(); - void onUpdatesAvailable(const QJsonObject &updates); + void menuTorClicked(); + void menuToggleTabVisible(const QString &key); + void onExportHistoryCSV(bool checked); + void onExportContactsCSV(bool checked); + void onCreateDesktopEntry(bool checked); + void onReportBug(bool checked); // offline tx signing void exportKeyImages(); @@ -137,104 +129,110 @@ public slots: void loadSignedTx(); void loadSignedTxFromText(); - // libwalletqt - void onBalanceUpdated(quint64 balance, quint64 spendable); - void onSynchronized(); - void onWalletOpened(); - void onWalletClosed(WalletWizard::Page page = WalletWizard::Page_Menu); - void onConnectionStatusChanged(int status); - void onCreateTransactionError(const QString &message); - void onCreateTransactionSuccess(PendingTransaction *tx, const QVector &address); - void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); - -signals: - void closed(); - -private slots: - void onInitialNetworkConfigured(); + void onTorConnectionStateChanged(bool connected); void onCheckUpdatesComplete(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer); void onShowUpdateCheck(const QString &version, const QString &binaryFilename, const QString &hash, const QString &signer); - void onRestartApplication(const QString &binaryFilename); void onSignedHashesReceived(QNetworkReply *reply, const QString &platformTag, const QString &version); void onShowDonationNag(); void onInitiateTransaction(); void onEndTransaction(); void onCustomRestoreHeightSet(int height); - void onWalletAboutToClose(); - // Menu - void onExportHistoryCSV(bool checked); - void onExportContactsCSV(bool checked); - void onCreateDesktopEntry(bool checked); - void onReportBug(bool checked); + // libwalletqt + void onBalanceUpdated(quint64 balance, quint64 spendable); + void onSynchronized(); + void onWalletOpened(); + void onConnectionStatusChanged(int status); + void onCreateTransactionError(const QString &message); + void onCreateTransactionSuccess(PendingTransaction *tx, const QVector &address); + void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); + + // Dialogs + void showWalletInfoDialog(); + void showSeedDialog(); + void showConnectionStatusDialog(); + void showPasswordDialog(); + void showKeysDialog(); + void showViewOnlyDialog(); + void showWalletCacheDebugDialog(); + void showAccountSwitcherDialog(); + + void donateButtonClicked(); + void showCalcWindow(); + void payToMany(); + void showSendTab(); + void showHistoryTab(); + void showSendScreen(const CCSEntry &entry); + void skinChanged(const QString &skinName); + void onBlockchainSync(int height, int target); + void onRefreshSync(int height, int target); + void onViewOnBlockExplorer(const QString &txid); + void onResendTransaction(const QString &txid); + void importContacts(); + void showRestoreHeightDialog(); + void importTransaction(); + void onDeviceError(const QString &error); + void menuHwDeviceClicked(); + void onUpdatesAvailable(const QJsonObject &updates); + void toggleSearchbar(bool enabled); + void onSetStatusText(const QString &text); private: - void initSkins(); void initStatusBar(); void initWidgets(); void initMenu(); - void initTray(); void initHome(); - void initTouchBar(); void initWalletContext(); - void initWizard(); void startupWarning(); - bool autoOpenWallet(); - AppContext *m_ctx; - - static MainWindow * pMainWindow; void closeEvent(QCloseEvent *event) override; - void cleanupBeforeClose(); - QString loadStylesheet(const QString &resource); + void saveGeo(); void restoreGeo(); void showDebugInfo(); void showNodeExhaustedMessage(); void showWSNodeExhaustedMessage(); void createUnsignedTxDialog(UnsignedTransaction *tx); - void touchbarShowWizard(); - void touchbarShowWallet(); void updatePasswordIcon(); void updateNetStats(); void rescanSpent(); void setStatusText(const QString &text, bool override = false, int timeout = 1000); void showBalanceDialog(); QString statusDots(); - void bringToFront(); QString getPlatformTag(); void displayWalletErrorMsg(const QString &err); QString getHardwareDevice(); - void setTitle(bool mining); - - WalletWizard *createWizard(WalletWizard::Page startPage); + void updateTitle(); + void donationNag(); + void updateRecentlyOpened(const QString &filename); Ui::MainWindow *ui; + WindowManager *m_windowManager; + QSharedPointer m_ctx; + Settings *m_windowSettings = nullptr; CalcWindow *m_windowCalc = nullptr; - RestoreDialog *m_restoreDialog = nullptr; - XMRigWidget *m_xmrig = nullptr; SplashDialog *m_splashDialog = nullptr; + XMRigWidget *m_xmrig = nullptr; + ContactsWidget *m_contactsWidget = nullptr; + HistoryWidget *m_historyWidget = nullptr; + SendWidget *m_sendWidget = nullptr; + ReceiveWidget *m_receiveWidget = nullptr; + CoinsWidget *m_coinsWidget = nullptr; #ifdef HAS_LOCALMONERO LocalMoneroWidget *m_localMoneroWidget = nullptr; #endif - QSystemTrayIcon *m_trayIcon; - QMenu m_trayMenu; - QAction *m_trayActionCalc; - QAction *m_trayActionExit; - QAction *m_trayActionSend; - QAction *m_trayActionHistory; - - QList m_tickerWidgets; - TickerWidget *m_balanceWidget; + QList m_priceTickerWidgets; + BalanceTickerWidget *m_balanceTickerWidget; // lower status bar QPushButton *m_statusUpdateAvailable; ClickableLabel *m_statusLabelBalance; QLabel *m_statusLabelStatus; QLabel *m_statusLabelNetStats; + StatusBarButton *m_statusAccountSwitcher; StatusBarButton *m_statusBtnConnectionStatusIndicator; StatusBarButton *m_statusBtnPassword; StatusBarButton *m_statusBtnPreferences; @@ -242,18 +240,8 @@ private: StatusBarButton *m_statusBtnTor; StatusBarButton *m_statusBtnHwDevice; -#ifdef Q_OS_MAC - QAction *m_touchbarActionWelcome; - KDMacTouchBar *m_touchbar; - QList m_touchbarWalletItems; - QList m_touchbarWizardItems; -#endif - QSignalMapper *m_tabShowHideSignalMapper; QMap m_tabShowHideMapper; - WalletWizard *m_wizard = nullptr; - - QMap m_skins; QTimer m_updateBytes; @@ -264,8 +252,7 @@ private: bool m_showDeviceError = false; QTimer m_txTimer; -private slots: - void menuToggleTabVisible(const QString &key); + bool cleanedUp = false; }; #endif // MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index be1b3814..fc0c81d6 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -161,8 +161,8 @@ 9 - - + +
@@ -188,29 +188,11 @@ 11 - - - - 0 - 0 - + + + 0 - - - 0 - 100 - - - - - 16777215 - 16777215 - - - - - - + @@ -226,7 +208,7 @@ - + @@ -240,7 +222,7 @@ - + @@ -254,7 +236,7 @@ - + @@ -266,9 +248,26 @@ Calc - - - + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + @@ -360,6 +359,14 @@ File + + + Recently open + + + + + @@ -413,6 +420,7 @@ + @@ -470,6 +478,8 @@ + + @@ -559,7 +569,7 @@ - Close current wallet + Close wallet @@ -732,38 +742,64 @@ Pay to many + + + Open wallet + + + + + New/Restore + + + + + Recently open + + + + + t + + + + + d + + + + + true + + + true + + + false + + + Primary account + + + + + Account + + + + + true + + + true + + + Show Searchbar + + - - SendWidget - QWidget -
sendwidget.h
- 1 -
- - ContactsWidget - QWidget -
contactswidget.h
- 1 -
- - ReceiveWidget - QWidget -
receivewidget.h
- 1 -
- - CoinsWidget - QWidget -
coinswidget.h
- 1 -
- - HistoryWidget - QWidget -
historywidget.h
-
CalcWidget QWidget diff --git a/src/model/AddressBookModel.cpp b/src/model/AddressBookModel.cpp index 92004b15..9f96a8f3 100644 --- a/src/model/AddressBookModel.cpp +++ b/src/model/AddressBookModel.cpp @@ -8,9 +8,9 @@ #include "utils/Icons.h" AddressBookModel::AddressBookModel(QObject *parent, AddressBook *addressBook) - : QAbstractTableModel(parent), - m_addressBook(addressBook), - m_showFullAddresses(false) + : QAbstractTableModel(parent) + , m_addressBook(addressBook) + , m_showFullAddresses(false) { connect(m_addressBook, &AddressBook::refreshStarted, this, &AddressBookModel::startReset); connect(m_addressBook, &AddressBook::refreshFinished, this, &AddressBookModel::endReset); @@ -150,11 +150,6 @@ bool AddressBookModel::deleteRow(int row) return m_addressBook->deleteRow(row); } -int AddressBookModel::lookupPaymentID(const QString &payment_id) const -{ - return m_addressBook->lookupPaymentID(payment_id); -} - bool AddressBookModel::writeCSV(const QString &path) { QString csv = ""; for(int i = 0; i < this->rowCount(); i++) { diff --git a/src/model/AddressBookModel.h b/src/model/AddressBookModel.h index 93854280..7157c6d1 100644 --- a/src/model/AddressBookModel.h +++ b/src/model/AddressBookModel.h @@ -31,7 +31,6 @@ public: bool setData(const QModelIndex &index, const QVariant &value, int role) override; Q_INVOKABLE bool deleteRow(int row); - Q_INVOKABLE int lookupPaymentID(const QString &payment_id) const; bool isShowFullAddresses() const; void setShowFullAddresses(bool show); diff --git a/src/model/CoinsModel.cpp b/src/model/CoinsModel.cpp index 83c4471e..ca297fbb 100644 --- a/src/model/CoinsModel.cpp +++ b/src/model/CoinsModel.cpp @@ -5,9 +5,10 @@ #include "CoinsInfo.h" #include "Coins.h" #include "ModelUtils.h" -#include "globals.h" +#include "constants.h" #include "utils/ColorScheme.h" #include "utils/Icons.h" +#include "libwalletqt/WalletManager.h" #include @@ -182,9 +183,9 @@ QVariant CoinsModel::parseTransactionInfo(const CoinsInfo &cInfo, int column, in case Amount: { if (role == Qt::UserRole) { - return cInfo.amount() / globals::cdiv; + return cInfo.amount(); } - return QString::number(cInfo.amount() / globals::cdiv, 'f', 12); + return cInfo.displayAmount(); } case Frozen: return cInfo.frozen(); diff --git a/src/model/NodeModel.cpp b/src/model/NodeModel.cpp index 6224d0b6..040d4ce9 100644 --- a/src/model/NodeModel.cpp +++ b/src/model/NodeModel.cpp @@ -69,7 +69,15 @@ QVariant NodeModel::data(const QModelIndex &index, int role) const { } } } - else if(role == Qt::BackgroundRole) { + else if (role == Qt::ToolTipRole) { + switch (index.column()) { + case NodeModel::URL: { + if (node.isActive) + return QString("Feather is connected to this node."); + } + } + } + else if (role == Qt::BackgroundRole) { if (node.isConnecting) return QBrush(ColorScheme::YELLOW.asColor(true)); else if (node.isActive) diff --git a/src/model/NodeModel.h b/src/model/NodeModel.h index 5b79a5ea..f7509eab 100644 --- a/src/model/NodeModel.h +++ b/src/model/NodeModel.h @@ -32,9 +32,9 @@ public: private: QList m_nodes; + int m_nodeSource; QIcon m_offline; QIcon m_online; - int m_nodeSource; }; #endif //FEATHER_NODEMODEL_H diff --git a/src/model/SubaddressAccountModel.cpp b/src/model/SubaddressAccountModel.cpp index 8a1f75fa..a15ba838 100644 --- a/src/model/SubaddressAccountModel.cpp +++ b/src/model/SubaddressAccountModel.cpp @@ -5,9 +5,12 @@ #include "SubaddressAccount.h" #include +#include +#include "ModelUtils.h" SubaddressAccountModel::SubaddressAccountModel(QObject *parent, SubaddressAccount *subaddressAccount) - : QAbstractListModel(parent), m_subaddressAccount(subaddressAccount) + : QAbstractTableModel(parent) + , m_subaddressAccount(subaddressAccount) { connect(m_subaddressAccount, &SubaddressAccount::refreshStarted, this, &SubaddressAccountModel::startReset); connect(m_subaddressAccount, &SubaddressAccount::refreshFinished, this, &SubaddressAccountModel::endReset); @@ -16,13 +19,26 @@ SubaddressAccountModel::SubaddressAccountModel(QObject *parent, SubaddressAccoun void SubaddressAccountModel::startReset(){ beginResetModel(); } + void SubaddressAccountModel::endReset(){ endResetModel(); } -int SubaddressAccountModel::rowCount(const QModelIndex &) const +int SubaddressAccountModel::rowCount(const QModelIndex &parent) const { - return m_subaddressAccount->count(); + if (parent.isValid()) { + return 0; + } else { + return m_subaddressAccount->count(); + } +} + +int SubaddressAccountModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return Column::COUNT; } QVariant SubaddressAccountModel::data(const QModelIndex &index, int role) const @@ -32,24 +48,17 @@ QVariant SubaddressAccountModel::data(const QModelIndex &index, int role) const QVariant result; - bool found = m_subaddressAccount->getRow(index.row(), [&result, &role](const Monero::SubaddressAccountRow &row) { - switch (role) { - case SubaddressAccountAddressRole: - result = QString::fromStdString(row.getAddress()); - break; - case SubaddressAccountLabelRole: - result = QString::fromStdString(row.getLabel()); - break; - case SubaddressAccountBalanceRole: - result = QString::fromStdString(row.getBalance()); - break; - case SubaddressAccountUnlockedBalanceRole: - result = QString::fromStdString(row.getUnlockedBalance()); - break; - default: - qCritical() << "Unimplemented role" << role; + bool found = m_subaddressAccount->getRow(index.row(), [this, &index, &result, &role](const Monero::SubaddressAccountRow &row) { + if (role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) { + result = parseSubaddressAccountRow(row, index, role); + } + else if (role == Qt::FontRole) { + if (index.column() == Column::Balance || index.column() == Column::UnlockedBalance) { + result = ModelUtils::getMonospaceFont(); + } } }); + if (!found) { qCritical("%s: internal error: invalid index %d", __FUNCTION__, index.row()); } @@ -57,15 +66,85 @@ QVariant SubaddressAccountModel::data(const QModelIndex &index, int role) const return result; } -QHash SubaddressAccountModel::roleNames() const +QVariant SubaddressAccountModel::parseSubaddressAccountRow(const Monero::SubaddressAccountRow &row, + const QModelIndex &index, int role) const { - static QHash roleNames; - if (roleNames.empty()) - { - roleNames.insert(SubaddressAccountAddressRole, "address"); - roleNames.insert(SubaddressAccountLabelRole, "label"); - roleNames.insert(SubaddressAccountBalanceRole, "balance"); - roleNames.insert(SubaddressAccountUnlockedBalanceRole, "unlockedBalance"); + switch (index.column()) { + case Number: + return QString("#%1").arg(QString::number(index.row())); + case Address: + return QString::fromStdString(row.getAddress()); + case Label: + return QString::fromStdString(row.getLabel()); + case Balance: + return QString::fromStdString(row.getBalance()); + case UnlockedBalance: + return QString::fromStdString(row.getUnlockedBalance()); + default: + return QVariant(); } - return roleNames; } + +QVariant SubaddressAccountModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) { + return QVariant(); + } + if (orientation == Qt::Horizontal) + { + switch (section) { + case Address: + return QString("Address"); + case Label: + return QString("Label"); + case Balance: + return QString("Balance"); + case UnlockedBalance: + return QString("Spendable balance"); + default: + return QVariant(); + } + } + return QVariant(); +} + +bool SubaddressAccountModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) { + const int row = index.row(); + + switch (index.column()) { + case Label: + m_subaddressAccount->setLabel(row, value.toString()); + break; + default: + return false; + } + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole}); + + return true; + } + return false; +} + + +Qt::ItemFlags SubaddressAccountModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled; + + if (index.column() == Label) + return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; + + return QAbstractTableModel::flags(index); +} + +Monero::SubaddressAccountRow* SubaddressAccountModel::entryFromIndex(const QModelIndex &index) const { + Q_ASSERT(index.isValid() && index.row() < m_subaddressAccount->count()); + return m_subaddressAccount->row(index.row()); +} + +SubaddressAccountProxyModel::SubaddressAccountProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} \ No newline at end of file diff --git a/src/model/SubaddressAccountModel.h b/src/model/SubaddressAccountModel.h index cbc7019b..1c145580 100644 --- a/src/model/SubaddressAccountModel.h +++ b/src/model/SubaddressAccountModel.h @@ -4,36 +4,54 @@ #ifndef SUBADDRESSACCOUNTMODEL_H #define SUBADDRESSACCOUNTMODEL_H -#include +#include "Subaddress.h" +#include +#include class SubaddressAccount; -class SubaddressAccountModel : public QAbstractListModel +class SubaddressAccountModel : public QAbstractTableModel { Q_OBJECT public: - enum SubaddressAccountRowRole { - SubaddressAccountRole = Qt::UserRole + 1, // for the SubaddressAccountRow object; - SubaddressAccountAddressRole, - SubaddressAccountLabelRole, - SubaddressAccountBalanceRole, - SubaddressAccountUnlockedBalanceRole, + enum Column { + Number, + Address, + Label, + Balance, + UnlockedBalance, + COUNT }; - Q_ENUM(SubaddressAccountRowRole) SubaddressAccountModel(QObject *parent, SubaddressAccount *subaddressAccount); int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QHash roleNames() const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + + Monero::SubaddressAccountRow* entryFromIndex(const QModelIndex &index) const; public slots: void startReset(); void endReset(); private: + QVariant parseSubaddressAccountRow(const Monero::SubaddressAccountRow &row, const QModelIndex &index, int role) const; + SubaddressAccount *m_subaddressAccount; }; +class SubaddressAccountProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit SubaddressAccountProxyModel(QObject *parent = nullptr); +}; + #endif // SUBADDRESSACCOUNTMODEL_H diff --git a/src/model/SubaddressModel.cpp b/src/model/SubaddressModel.cpp index 7b170b33..bad56e35 100644 --- a/src/model/SubaddressModel.cpp +++ b/src/model/SubaddressModel.cpp @@ -11,9 +11,9 @@ #include SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress) - : QAbstractTableModel(parent), - m_subaddress(subaddress), - m_showFullAddresses(false) + : QAbstractTableModel(parent) + , m_subaddress(subaddress) + , m_showFullAddresses(false) { connect(m_subaddress, &Subaddress::refreshStarted, this, &SubaddressModel::startReset); connect(m_subaddress, &Subaddress::refreshFinished, this, &SubaddressModel::endReset); @@ -143,7 +143,7 @@ bool SubaddressModel::setData(const QModelIndex &index, const QVariant &value, i switch (index.column()) { case Label: - m_subaddress->setLabel(0, row, value.toString()); // Todo: get actual account index + m_subaddress->setLabel(m_currentSubaddressAcount, row, value.toString()); break; default: return false; @@ -180,6 +180,10 @@ int SubaddressModel::unusedLookahead() const { return m_subaddress->unusedLookahead(); } +void SubaddressModel::setCurrentSubaddressAcount(quint32 accountIndex) { + m_currentSubaddressAcount = accountIndex; +} + Monero::SubaddressRow* SubaddressModel::entryFromIndex(const QModelIndex &index) const { Q_ASSERT(index.isValid() && index.row() < m_subaddress->count()); return m_subaddress->row(index.row()); diff --git a/src/model/SubaddressModel.h b/src/model/SubaddressModel.h index 0b8eb0cf..3056c028 100644 --- a/src/model/SubaddressModel.h +++ b/src/model/SubaddressModel.h @@ -40,6 +40,7 @@ public: Monero::SubaddressRow* entryFromIndex(const QModelIndex &index) const; + void setCurrentSubaddressAcount(quint32 accountIndex); int unusedLookahead() const; public slots: @@ -51,6 +52,7 @@ private: QVariant parseSubaddressRow(const Monero::SubaddressRow &subaddress, const QModelIndex &index, int role) const; bool m_showFullAddresses; + quint32 m_currentSubaddressAcount; }; #endif // SUBADDRESSMODEL_H diff --git a/src/model/SubaddressProxyModel.cpp b/src/model/SubaddressProxyModel.cpp index a984e398..b14802e6 100644 --- a/src/model/SubaddressProxyModel.cpp +++ b/src/model/SubaddressProxyModel.cpp @@ -4,11 +4,11 @@ #include "SubaddressProxyModel.h" SubaddressProxyModel::SubaddressProxyModel(QObject *parent, Subaddress *subaddress, bool hidePrimary) - : QSortFilterProxyModel(parent), - m_searchRegExp(""), - m_searchCaseSensitiveRegExp(""), - m_subaddress(subaddress), - m_hidePrimary(hidePrimary) + : QSortFilterProxyModel(parent) + , m_subaddress(subaddress) + , m_searchRegExp("") + , m_searchCaseSensitiveRegExp("") + , m_hidePrimary(hidePrimary) { m_searchRegExp.setCaseSensitivity(Qt::CaseInsensitive); m_searchRegExp.setPatternSyntax(QRegExp::FixedString); diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp index db0d7993..0374cf17 100644 --- a/src/model/TransactionHistoryModel.cpp +++ b/src/model/TransactionHistoryModel.cpp @@ -4,7 +4,7 @@ #include "TransactionHistoryModel.h" #include "TransactionHistory.h" #include "TransactionInfo.h" -#include "globals.h" +#include "constants.h" #include "utils/config.h" #include "utils/ColorScheme.h" #include "utils/Icons.h" @@ -137,6 +137,9 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI { case Column::Date: { + if (role == Qt::UserRole) { + return tInfo.timestamp(); + } return tInfo.timestamp().toString(QString("%1 %2 ").arg(config()->get(Config::dateFormat).toString(), config()->get(Config::timeFormat).toString())); } @@ -147,7 +150,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI if (role == Qt::UserRole) { return tInfo.balanceDelta(); } - QString amount = QString::number(tInfo.balanceDelta() / globals::cdiv, 'f', this->amountPrecision); + QString amount = QString::number(tInfo.balanceDelta() / constants::cdiv, 'f', this->amountPrecision); amount = (tInfo.direction() == TransactionInfo::Direction_Out) ? "-" + amount : "+" + amount; return amount; } @@ -160,7 +163,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI if (usd_price == 0.0) return QVariant("?"); - double usd_amount = usd_price * (tInfo.balanceDelta() / globals::cdiv); + double usd_amount = usd_price * (tInfo.balanceDelta() / constants::cdiv); if(this->preferredFiatSymbol != "USD") usd_amount = appData()->prices.convert("USD", this->preferredFiatSymbol, usd_amount); if (role == Qt::UserRole) { diff --git a/src/utils/keysfiles.cpp b/src/model/WalletKeysFilesModel.cpp similarity index 69% rename from src/utils/keysfiles.cpp rename to src/model/WalletKeysFilesModel.cpp index 4c78d103..37fd03d5 100644 --- a/src/utils/keysfiles.cpp +++ b/src/model/WalletKeysFilesModel.cpp @@ -1,58 +1,41 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2014-2021, The Monero Project. -#include -#include -#include -#include -#include -#include +#include "WalletKeysFilesModel.h" -#include "appcontext.h" +#include "utils/utils.h" +#include +#include +#include "config.h" using namespace std::chrono; - -WalletKeysFiles::WalletKeysFiles(const QFileInfo &info, int networkType, QString address) : - m_fileName(info.fileName()), - m_modified(info.lastModified().toSecsSinceEpoch()), - m_path(QDir::toNativeSeparators(info.absoluteFilePath())), - m_networkType(networkType), - m_address(std::move(address)) +WalletKeysFile::WalletKeysFile(const QFileInfo &info, int networkType, QString address) + : m_fileName(info.fileName()) + , m_modified(getModified(info)) + , m_path(QDir::toNativeSeparators(info.absoluteFilePath())) + , m_networkType(networkType) + , m_address(std::move(address)) { +} + +qint64 WalletKeysFile::getModified(const QFileInfo &info) { + qint64 m = info.lastModified().toSecsSinceEpoch(); + QFileInfo cacheFile = QFileInfo(info.absoluteFilePath().replace(QRegExp(".keys$"), "")); qint64 cacheLastModified = cacheFile.lastModified().toSecsSinceEpoch(); - if (cacheFile.exists()) { - m_modified = (cacheLastModified > m_modified) ? cacheLastModified : m_modified; + if (cacheFile.exists() && cacheLastModified > m) { + m = cacheLastModified; } + return m; } -QString WalletKeysFiles::fileName() const { - return m_fileName; -} +// Model -qint64 WalletKeysFiles::modified() const { - return m_modified; -} - -QString WalletKeysFiles::address() const { - return m_address; -} - -QString WalletKeysFiles::path() const { - return m_path; -} - -int WalletKeysFiles::networkType() const { - return m_networkType; -} - -WalletKeysFilesModel::WalletKeysFilesModel(AppContext *ctx, QObject *parent) - : QAbstractTableModel(parent) - , m_ctx(ctx) +WalletKeysFilesModel::WalletKeysFilesModel(QObject *parent) + : QAbstractTableModel(parent) { this->updateDirectories(); - this->m_walletKeysFilesItemModel = qobject_cast(this); } void WalletKeysFilesModel::clear() { @@ -68,21 +51,22 @@ void WalletKeysFilesModel::refresh() { } void WalletKeysFilesModel::updateDirectories() { - this->walletDirectories.clear(); + m_walletDirectories.clear(); + QDir defaultWalletDir = QDir(Utils::defaultWalletDir()); QString walletDir = defaultWalletDir.path(); defaultWalletDir.cdUp(); QString walletDirRoot = defaultWalletDir.path(); - this->walletDirectories << walletDir; - this->walletDirectories << walletDirRoot; - auto walletPath = config()->get(Config::walletPath).toString(); - if(!walletPath.isEmpty() && Utils::fileExists(walletPath)) { - QDir d = QFileInfo(walletPath).absoluteDir(); - this->walletDirectories << d.absolutePath(); - } - this->walletDirectories << QDir::homePath(); - this->walletDirectories.removeDuplicates(); + m_walletDirectories << walletDir; + m_walletDirectories << walletDirRoot; + m_walletDirectories << QDir::homePath(); + + QString walletDirectory = config()->get(Config::walletDirectory).toString(); + if (!walletDirectory.isEmpty()) + m_walletDirectories << walletDirectory; + + m_walletDirectories.removeDuplicates(); } void WalletKeysFilesModel::findWallets() { @@ -93,9 +77,9 @@ void WalletKeysFilesModel::findWallets() { rx.setPatternSyntax(QRegExp::Wildcard); QStringList walletPaths; - for(auto i = 0; i != this->walletDirectories.length(); i++) { + for(auto i = 0; i != m_walletDirectories.length(); i++) { // Scan default wallet dir (~/Monero/) - walletPaths << Utils::fileFind(rx, this->walletDirectories[i], 0, i == 0 ? 2 : 0, 200); + walletPaths << Utils::fileFind(rx, m_walletDirectories[i], 0, i == 0 ? 2 : 0, 200); } walletPaths.removeDuplicates(); @@ -127,16 +111,16 @@ void WalletKeysFilesModel::findWallets() { file.close(); } - this->addWalletKeysFile(WalletKeysFiles(fileInfo, networkType, std::move(addr))); + this->addWalletKeysFile(WalletKeysFile(fileInfo, networkType, std::move(addr))); } - auto duration = duration_cast(high_resolution_clock::now() - now).count(); + auto duration = duration_cast(high_resolution_clock::now() - now).count(); qDebug() << QString("wallet .keys search completed in %1 ms").arg(duration); } -void WalletKeysFilesModel::addWalletKeysFile(const WalletKeysFiles &walletKeysFile) { +void WalletKeysFilesModel::addWalletKeysFile(const WalletKeysFile &walletKeysFile) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); - m_walletKeyFiles << walletKeysFile; + m_walletKeyFiles.append(walletKeysFile); endInsertRows(); } @@ -145,7 +129,7 @@ int WalletKeysFilesModel::rowCount(const QModelIndex & parent) const { } int WalletKeysFilesModel::columnCount(const QModelIndex &) const { - return 4; + return Column::COUNT; } QVariant WalletKeysFilesModel::data(const QModelIndex &index, int role) const { @@ -156,7 +140,7 @@ QVariant WalletKeysFilesModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { switch(index.column()) { - case ModelColumns::NetworkType: { + case Column::NetworkType: { auto c = static_cast(walletKeyFile.networkType()); if (c == NetworkType::Type::STAGENET) return QString("stage"); @@ -164,9 +148,10 @@ QVariant WalletKeysFilesModel::data(const QModelIndex &index, int role) const { return QString("test"); return QString("main"); } - case ModelColumns::FileName: + case Column::FileName: { return walletKeyFile.fileName().replace(".keys", ""); - case ModelColumns::Path: { + } + case Column::Path: { auto fp = walletKeyFile.path(); #if defined(Q_OS_MAC) || defined(Q_OS_LINUX) if (fp.startsWith(QDir::homePath())) { @@ -175,20 +160,20 @@ QVariant WalletKeysFilesModel::data(const QModelIndex &index, int role) const { #endif return fp; } - case ModelColumns::Modified: + case Column::Modified: return walletKeyFile.modified(); default: break; } } else if(role == Qt::UserRole) { switch(index.column()) { - case ModelColumns::NetworkType: + case Column::NetworkType: return static_cast(walletKeyFile.networkType()); - case ModelColumns::FileName: + case Column::FileName: return walletKeyFile.fileName(); - case ModelColumns::Modified: + case Column::Modified: return (int)walletKeyFile.modified(); - case ModelColumns::Path: + case Column::Path: return walletKeyFile.path(); default: break; @@ -216,6 +201,8 @@ QVariant WalletKeysFilesModel::headerData(int section, Qt::Orientation orientati return QVariant(); } +// ProxyModel + WalletKeysFilesProxyModel::WalletKeysFilesProxyModel(QObject *parent, NetworkType::Type nettype) : QSortFilterProxyModel(parent) , m_nettype(nettype) diff --git a/src/utils/keysfiles.h b/src/model/WalletKeysFilesModel.h similarity index 56% rename from src/utils/keysfiles.h rename to src/model/WalletKeysFilesModel.h index 4c01edd2..cdabe08b 100644 --- a/src/utils/keysfiles.h +++ b/src/model/WalletKeysFilesModel.h @@ -1,25 +1,30 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2014-2021, The Monero Project. -#ifndef KEYSFILES_H -#define KEYSFILES_H +#ifndef FEATHER_WALLETKEYSFILESMODEL_H +#define FEATHER_WALLETKEYSFILESMODEL_H + +#include +#include +#include +#include -#include "libwalletqt/WalletManager.h" #include "utils/networktype.h" -#include "utils/utils.h" -class WalletKeysFiles +class WalletKeysFile { public: - WalletKeysFiles(const QFileInfo &info, int networkType, QString address); + WalletKeysFile(const QFileInfo &info, int networkType, QString address); - QString fileName() const; - qint64 modified() const; - QString path() const; - int networkType() const; - QString address() const; + QString fileName() const {return m_fileName;}; + qint64 modified() const {return m_modified;}; + QString path() const {return m_path;}; + int networkType() const {return m_networkType;}; + QString address() const {return m_address;}; private: + static qint64 getModified(const QFileInfo &info); + QString m_fileName; qint64 m_modified; QString m_path; @@ -29,41 +34,42 @@ private: class WalletKeysFilesModel : public QAbstractTableModel { - Q_OBJECT +Q_OBJECT + public: - enum ModelColumns { + enum Column { NetworkType = 0, FileName, Path, - Modified + Modified, + COUNT }; - explicit WalletKeysFilesModel(AppContext *ctx, QObject *parent = nullptr); + explicit WalletKeysFilesModel(QObject *parent = nullptr); Q_INVOKABLE void refresh(); Q_INVOKABLE void clear(); void findWallets(); - void addWalletKeysFile(const WalletKeysFiles &walletKeysFile); + void addWalletKeysFile(const WalletKeysFile &walletKeysFile); int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - QStringList walletDirectories; private: void updateDirectories(); - AppContext *m_ctx; - QList m_walletKeyFiles; - QAbstractItemModel *m_walletKeysFilesItemModel; - QSortFilterProxyModel m_walletKeysFilesModelProxy; + QStringList m_walletDirectories; + + QList m_walletKeyFiles; }; class WalletKeysFilesProxyModel : public QSortFilterProxyModel { Q_OBJECT + public: explicit WalletKeysFilesProxyModel(QObject *parent, NetworkType::Type nettype); bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; @@ -72,4 +78,4 @@ private: NetworkType::Type m_nettype; }; -#endif // KEYSFILES_H +#endif //FEATHER_WALLETKEYSFILESMODEL_H diff --git a/src/receivewidget.cpp b/src/receivewidget.cpp index b86517cf..e5a74e6b 100644 --- a/src/receivewidget.cpp +++ b/src/receivewidget.cpp @@ -10,12 +10,33 @@ #include #include -ReceiveWidget::ReceiveWidget(QWidget *parent) : - QWidget(parent), - ui(new Ui::ReceiveWidget) +ReceiveWidget::ReceiveWidget(QSharedPointer ctx, QWidget *parent) + : QWidget(parent) + , ui(new Ui::ReceiveWidget) + , m_ctx(std::move(ctx)) { ui->setupUi(this); + m_model = m_ctx->wallet->subaddressModel(); + m_proxyModel = new SubaddressProxyModel(this, m_ctx->wallet->subaddress()); + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setHiddenAddresses(this->getHiddenAddresses()); + + ui->addresses->setModel(m_proxyModel); + ui->addresses->setColumnHidden(SubaddressModel::isUsed, true); + ui->addresses->header()->setSectionResizeMode(SubaddressModel::Address, QHeaderView::Stretch); + ui->addresses->header()->setSectionResizeMode(SubaddressModel::Label, QHeaderView::ResizeToContents); + ui->addresses->header()->setMinimumSectionSize(200); + + connect(ui->addresses->selectionModel(), &QItemSelectionModel::currentChanged, [=](QModelIndex current, QModelIndex prev){ + this->updateQrCode(); + }); + connect(m_model, &SubaddressModel::modelReset, [this](){ + this->updateQrCode(); + }); + + ui->qrCode->setCursor(Qt::PointingHandCursor); + // header context menu ui->addresses->header()->setContextMenuPolicy(Qt::CustomContextMenu); m_headerMenu = new QMenu(this); @@ -34,33 +55,19 @@ ReceiveWidget::ReceiveWidget(QWidget *parent) : connect(ui->btn_generateSubaddress, &QPushButton::clicked, this, &ReceiveWidget::generateSubaddress); connect(ui->qrCode, &ClickableLabel::clicked, this, &ReceiveWidget::showQrCodeDialog); - connect(ui->label_addressSearch, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter); + connect(ui->search, &QLineEdit::textChanged, this, &ReceiveWidget::setSearchFilter); connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses); connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses); } -void ReceiveWidget::setModel(SubaddressModel * model, Wallet * wallet) { - m_wallet = wallet; - m_subaddress = wallet->subaddress(); - m_model = model; - m_proxyModel = new SubaddressProxyModel(this, m_subaddress); - m_proxyModel->setSourceModel(m_model); - m_proxyModel->setHiddenAddresses(this->getHiddenAddresses()); +void ReceiveWidget::setSearchbarVisible(bool visible) { + ui->search->setVisible(visible); +} - ui->addresses->setModel(m_proxyModel); - - ui->addresses->setColumnHidden(SubaddressModel::isUsed, true); - ui->addresses->header()->setSectionResizeMode(SubaddressModel::Address, QHeaderView::Stretch); - ui->addresses->header()->setSectionResizeMode(SubaddressModel::Label, QHeaderView::ResizeToContents); - ui->addresses->header()->setMinimumSectionSize(200); - - connect(ui->addresses->selectionModel(), &QItemSelectionModel::currentChanged, [=](QModelIndex current, QModelIndex prev){ - this->updateQrCode(); - }); - connect(m_model, &SubaddressModel::modelReset, [this](){ - this->updateQrCode(); - }); +void ReceiveWidget::focusSearchbar() { + ui->search->setFocusPolicy(Qt::StrongFocus); + ui->search->setFocus(); } void ReceiveWidget::copyAddress() { @@ -103,7 +110,7 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { menu->addAction("Hide address", this, &ReceiveWidget::hideAddress); } - if (m_wallet->isHwBacked()) { + if (m_ctx->wallet->isHwBacked()) { menu->addAction("Show on device", this, &ReceiveWidget::showOnDevice); } @@ -120,10 +127,6 @@ void ReceiveWidget::onShowTransactions() { emit showTransactions(address); } -void ReceiveWidget::resetModel() { - ui->addresses->setModel(nullptr); -} - void ReceiveWidget::setShowFullAddresses(bool show) { if (!m_model) return; m_model->setShowFullAddresses(show); @@ -146,6 +149,7 @@ void ReceiveWidget::setSearchFilter(const QString &filter) { void ReceiveWidget::showHeaderMenu(const QPoint& position) { + Q_UNUSED(position); m_showFullAddressesAction->setChecked(m_model->isShowFullAddresses()); m_headerMenu->exec(QCursor::pos()); } @@ -171,15 +175,13 @@ void ReceiveWidget::showAddress() void ReceiveWidget::showOnDevice() { Monero::SubaddressRow* row = this->currentEntry(); if (!row) return; - m_wallet->deviceShowAddressAsync(m_wallet->currentSubaddressAccount(), row->getRowId(), ""); + m_ctx->wallet->deviceShowAddressAsync(m_ctx->wallet->currentSubaddressAccount(), row->getRowId(), ""); } void ReceiveWidget::generateSubaddress() { - if (!m_wallet) return; - - bool r = m_wallet->subaddress()->addRow(m_wallet->currentSubaddressAccount(), ""); + bool r = m_ctx->wallet->subaddress()->addRow(m_ctx->wallet->currentSubaddressAccount(), ""); if (!r) { - QMessageBox::warning(this, "Warning", QString("Failed to generate subaddress:\n\n%1").arg(m_wallet->subaddress()->errorString())); + QMessageBox::warning(this, "Warning", QString("Failed to generate subaddress:\n\n%1").arg(m_ctx->wallet->subaddress()->errorString())); } } @@ -212,7 +214,7 @@ void ReceiveWidget::showQrCodeDialog() { } QStringList ReceiveWidget::getHiddenAddresses() { - QString data = m_wallet->getCacheAttribute("feather.hiddenaddresses"); + QString data = m_ctx->wallet->getCacheAttribute("feather.hiddenaddresses"); return data.split(","); } @@ -222,14 +224,14 @@ void ReceiveWidget::addHiddenAddress(const QString& address) { hiddenAddresses.append(address); } QString data = hiddenAddresses.join(","); - m_wallet->setCacheAttribute("feather.hiddenaddresses", data); + m_ctx->wallet->setCacheAttribute("feather.hiddenaddresses", data); } void ReceiveWidget::removeHiddenAddress(const QString &address) { QStringList hiddenAddresses = this->getHiddenAddresses(); hiddenAddresses.removeAll(address); QString data = hiddenAddresses.join(","); - m_wallet->setCacheAttribute("feather.hiddenaddresses", data); + m_ctx->wallet->setCacheAttribute("feather.hiddenaddresses", data); } Monero::SubaddressRow* ReceiveWidget::currentEntry() { diff --git a/src/receivewidget.h b/src/receivewidget.h index da6285a1..8fa16e88 100644 --- a/src/receivewidget.h +++ b/src/receivewidget.h @@ -22,10 +22,11 @@ class ReceiveWidget : public QWidget Q_OBJECT public: - explicit ReceiveWidget(QWidget *parent = nullptr); - void setModel(SubaddressModel * model, Wallet * wallet); + explicit ReceiveWidget(QSharedPointer ctx, QWidget *parent = nullptr); ~ReceiveWidget() override; + void setSearchbarVisible(bool visible); + void focusSearchbar(); public slots: void copyAddress(); @@ -37,7 +38,6 @@ public slots: void setShowHiddenAddresses(bool show); void setSearchFilter(const QString &filter); void onShowTransactions(); - void resetModel(); signals: void showTransactions(const QString& address); @@ -51,14 +51,13 @@ private slots: private: Ui::ReceiveWidget *ui; + QSharedPointer m_ctx; QMenu *m_headerMenu; QAction *m_showFullAddressesAction; QAction *m_showUsedAddressesAction; QAction *m_showTransactionsAction; - Subaddress * m_subaddress; SubaddressModel * m_model; SubaddressProxyModel * m_proxyModel; - Wallet * m_wallet; void updateQrCode(); void showQrCodeDialog(); diff --git a/src/receivewidget.ui b/src/receivewidget.ui index 52c8e74c..494b2a57 100644 --- a/src/receivewidget.ui +++ b/src/receivewidget.ui @@ -27,7 +27,7 @@ 0 - + true diff --git a/src/sendwidget.cpp b/src/sendwidget.cpp index cc09f9cf..2e1cd29b 100644 --- a/src/sendwidget.cpp +++ b/src/sendwidget.cpp @@ -5,15 +5,15 @@ #include "sendwidget.h" #include "mainwindow.h" #include "ui_sendwidget.h" -#include "globals.h" +#include "constants.h" #include "utils/AppData.h" -SendWidget::SendWidget(QWidget *parent) +SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) : QWidget(parent) , ui(new Ui::SendWidget) + , m_ctx(std::move(ctx)) { ui->setupUi(this); - m_ctx = MainWindow::getContext(); QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)"; QRegExp rx; @@ -21,11 +21,10 @@ SendWidget::SendWidget(QWidget *parent) QValidator *validator = new QRegExpValidator(rx, this); ui->lineAmount->setValidator(validator); - connect(m_ctx, &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction); - connect(m_ctx, &AppContext::endTransaction, this, &SendWidget::onEndTransaction); - connect(m_ctx, &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved); - connect(m_ctx, &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError); - connect(m_ctx, &AppContext::walletClosed, this, &SendWidget::onWalletClosed); + connect(m_ctx.get(), &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction); + connect(m_ctx.get(), &AppContext::endTransaction, this, &SendWidget::onEndTransaction); + connect(m_ctx.get(), &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved); + connect(m_ctx.get(), &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError); connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked); connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked); @@ -48,7 +47,7 @@ SendWidget::SendWidget(QWidget *parent) "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."); - ui->lineAddress->setNetType(m_ctx->networkType); + ui->lineAddress->setNetType(constants::networkType); this->setupComboBox(); } @@ -61,18 +60,16 @@ void SendWidget::currencyComboChanged(int index) { void SendWidget::addressEdited() { QVector outputs = ui->lineAddress->getOutputs(); - bool freezeAmounts = outputs.size() > 0; + bool freezeAmounts = !outputs.empty(); ui->lineAmount->setReadOnly(freezeAmounts); ui->lineAmount->setFrame(!freezeAmounts); ui->btnMax->setDisabled(freezeAmounts); ui->comboCurrencySelection->setDisabled(freezeAmounts); - if (outputs.size() > 0) { - ui->lineAmount->setText(WalletManager::displayAmount(ui->lineAddress->getTotal())); + if (!outputs.empty()) { + ui->lineAmount->setText(WalletManager::displayAmount(ui->lineAddress->getTotal(), false)); ui->comboCurrencySelection->setCurrentIndex(0); - } else { - ui->lineAmount->setText(""); } ui->btn_openAlias->setVisible(ui->lineAddress->isOpenAlias()); @@ -107,7 +104,7 @@ void SendWidget::fillAddress(const QString &address) { } void SendWidget::sendClicked() { - if (!m_ctx->currentWallet->isConnected()) { + if (!m_ctx->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."); @@ -229,7 +226,7 @@ quint64 SendWidget::amount() { double SendWidget::amountDouble() { quint64 amount = this->amount(); - return amount / globals::cdiv; + return amount / constants::cdiv; } void SendWidget::onOpenAliasResolved(const QString &address, const QString &openAlias) { @@ -252,11 +249,6 @@ void SendWidget::payToMany() { ui->lineAddress->payToMany(); } -void SendWidget::onWalletClosed() { - this->clearFields(); - ui->btnSend->setEnabled(true); -} - void SendWidget::onInitiateTransaction() { ui->btnSend->setEnabled(false); } diff --git a/src/sendwidget.h b/src/sendwidget.h index 056fb404..f4666db7 100644 --- a/src/sendwidget.h +++ b/src/sendwidget.h @@ -17,7 +17,7 @@ class SendWidget : public QWidget Q_OBJECT public: - explicit SendWidget(QWidget *parent = nullptr); + explicit SendWidget(QSharedPointer ctx, QWidget *parent = nullptr); void fill(const CCSEntry &entry); void fill(const QString &address, const QString& description, double amount = 0); void fill(double amount); @@ -37,7 +37,6 @@ public slots: void updateConversionLabel(); void onOpenAliasResolveError(const QString &err); void onOpenAliasResolved(const QString &address, const QString &openAlias); - void onWalletClosed(); void onPreferredFiatCurrencyChanged(); void onInitiateTransaction(); @@ -48,7 +47,7 @@ private: double amountDouble(); Ui::SendWidget *ui; - AppContext *m_ctx; + QSharedPointer m_ctx; quint64 amount(); double conversionAmount(); }; diff --git a/src/sendwidget.ui b/src/sendwidget.ui index 566459da..5de09348 100644 --- a/src/sendwidget.ui +++ b/src/sendwidget.ui @@ -10,6 +10,12 @@ 231 + + + 0 + 0 + + Form diff --git a/src/settings.cpp b/src/settings.cpp index bf73847a..fe3eb2c2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -7,12 +7,12 @@ #include -Settings::Settings(QWidget *parent) : - QDialog(parent), - ui(new Ui::Settings) +Settings::Settings(QSharedPointer ctx, QWidget *parent) + : QDialog(parent) + , ui(new Ui::Settings) + , m_ctx(std::move(ctx)) { ui->setupUi(this); - m_ctx = MainWindow::getContext(); this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png")); @@ -74,7 +74,11 @@ Settings::Settings(QWidget *parent) : connect(ui->comboBox_dateFormat, QOverload::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_dateFormatChanged); connect(ui->comboBox_timeFormat, QOverload::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_timeFormatChanged); - // setup preferred fiat currency combobox + // Balance display combobox + ui->comboBox_balanceDisplay->setCurrentIndex(config()->get(Config::balanceDisplay).toInt()); + connect(ui->comboBox_balanceDisplay, QOverload::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_balanceDisplayChanged); + + // Preferred fiat currency combobox QStringList fiatCurrencies; for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index); @@ -89,11 +93,12 @@ Settings::Settings(QWidget *parent) : // setup paths tab this->updatePaths(); connect(ui->btn_browseDefaultWalletDir, &QPushButton::clicked, [this]{ - QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", m_ctx->defaultWalletDir, QFileDialog::ShowDirsOnly); - if (walletDir.isEmpty()) return; - m_ctx->defaultWalletDir = walletDir; + QString walletDirOld = config()->get(Config::walletDirectory).toString(); + QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", walletDirOld, QFileDialog::ShowDirsOnly); + if (walletDir.isEmpty()) + return; config()->set(Config::walletDirectory, walletDir); - ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir); + ui->lineEdit_defaultWalletDir->setText(walletDir); }); // Links tab @@ -110,7 +115,7 @@ Settings::Settings(QWidget *parent) : } void Settings::updatePaths() { - ui->lineEdit_defaultWalletDir->setText(m_ctx->defaultWalletDir); + ui->lineEdit_defaultWalletDir->setText(config()->get(Config::walletDirectory).toString()); ui->lineEdit_configDir->setText(Config::defaultConfigDir().path()); ui->lineEdit_applicationDir->setText(QCoreApplication::applicationDirPath()); } @@ -143,6 +148,7 @@ void Settings::comboBox_localMoneroFrontendChanged(int pos) { void Settings::comboBox_amountPrecisionChanged(int pos) { config()->set(Config::amountPrecision, pos); + m_ctx->updateBalance(); emit amountPrecisionChanged(pos); } @@ -154,6 +160,11 @@ void Settings::comboBox_timeFormatChanged(int pos) { config()->set(Config::timeFormat, m_timeFormats.at(pos)); } +void Settings::comboBox_balanceDisplayChanged(int pos) { + config()->set(Config::balanceDisplay, pos); + m_ctx->updateBalance(); +} + void Settings::copyToClipboard() { ui->textLogs->copy(); } diff --git a/src/settings.h b/src/settings.h index 65b9078f..24d01db3 100644 --- a/src/settings.h +++ b/src/settings.h @@ -20,7 +20,7 @@ class Settings : public QDialog Q_OBJECT public: - explicit Settings(QWidget *parent = nullptr); + explicit Settings(QSharedPointer ctx, QWidget *parent = nullptr); ~Settings() override; signals: @@ -40,6 +40,7 @@ public slots: void comboBox_amountPrecisionChanged(int pos); void comboBox_dateFormatChanged(int pos); void comboBox_timeFormatChanged(int pos); + void comboBox_balanceDisplayChanged(int pos); void comboBox_blockExplorerChanged(int pos); void comboBox_redditFrontendChanged(int pos); @@ -49,8 +50,8 @@ private: void setupSkinCombobox(); void setupLocalMoneroFrontendCombobox(); - AppContext *m_ctx; Ui::Settings *ui; + QSharedPointer m_ctx; QStringList m_skins{"Native", "QDarkStyle", "Breeze/Dark", "Breeze/Light"}; QStringList m_dateFormats{"yyyy-MM-dd", "MM-dd-yyyy", "dd-MM-yyyy"}; diff --git a/src/settings.ui b/src/settings.ui index 71787f33..533afdf8 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -162,6 +162,32 @@ + + + + Balance display: + + + + + + + + Total balance + + + + + Spendable balance (+ unconfirmed balance) + + + + + Spendable balance + + + +
diff --git a/src/ui/qdarkstyle/style.qss b/src/ui/qdarkstyle/style.qss index 9b1368d4..a85365fd 100644 --- a/src/ui/qdarkstyle/style.qss +++ b/src/ui/qdarkstyle/style.qss @@ -986,8 +986,6 @@ QPushButton { border-radius: 4px; padding: 3px; outline: none; - /* Issue #194 - Special case of QPushButton inside dialogs, for better UI */ - min-width: 80px; } QPushButton:disabled { @@ -2152,10 +2150,6 @@ QComboBox::item:checked { max-height: 30px; } -QPushButton { - min-width: default; -} - QPushButton:flat { background-color: transparent; border: none; diff --git a/src/utils/FeatherSeed.h b/src/utils/FeatherSeed.h index bf501019..3232e67f 100644 --- a/src/utils/FeatherSeed.h +++ b/src/utils/FeatherSeed.h @@ -16,6 +16,8 @@ enum SeedType { struct FeatherSeed { // TODO: this is spaghetti, needs refactor + NetworkType::Type netType; + QString coin; QString language; SeedType seedType; @@ -24,8 +26,6 @@ struct FeatherSeed { QString spendKey; QString correction; - NetworkType::Type netType; - time_t time; int restoreHeight = 0; @@ -93,13 +93,13 @@ struct FeatherSeed { if (this->time == 0) this->restoreHeight = 1; - this->restoreHeight = appData()->restoreHeights[netType]->dateToRestoreHeight(this->time); + this->restoreHeight = appData()->restoreHeights[netType]->dateToHeight(this->time); } int setRestoreHeight(int height) { auto now = std::time(nullptr); auto nowClearance = 3600 * 24; - auto currentBlockHeight = appData()->restoreHeights[netType]->dateToRestoreHeight(now - nowClearance); + auto currentBlockHeight = appData()->restoreHeights[netType]->dateToHeight(now - nowClearance); if (height >= currentBlockHeight + nowClearance) { qCritical() << "unrealistic restore height detected, setting to current blockheight instead: " << currentBlockHeight; this->restoreHeight = currentBlockHeight; diff --git a/src/utils/RestoreHeightLookup.h b/src/utils/RestoreHeightLookup.h index 8ae4292b..b0499f03 100644 --- a/src/utils/RestoreHeightLookup.h +++ b/src/utils/RestoreHeightLookup.h @@ -17,7 +17,7 @@ struct RestoreHeightLookup { QMap data; explicit RestoreHeightLookup(NetworkType::Type type) : type(type) {} - int dateToRestoreHeight(int date) { + int dateToHeight(int date) { // restore height based on a given timestamp using a lookup // table. If it cannot find the date in the lookup table, it // will calculate the blockheight based off the last known @@ -31,9 +31,9 @@ struct RestoreHeightLookup { int blocksPerDay = 86400 / blockTime; int blockCalcClearance = blocksPerDay * 5; QList values = this->data.keys(); - if(date <= values.at(0)) + if (date <= values.at(0)) return this->data[values.at(0)]; - for(int i = 0; i != values.count(); i++) { + for (int i = 0; i != values.count(); i++) { if(values[i] > date) { return i - 1 < 0 ? this->data[values[i]] : this->data[values[i-1]] - blockCalcClearance; } @@ -49,7 +49,7 @@ struct RestoreHeightLookup { return blockHeight; } - int restoreHeightToDate(int height) { + int heightToTimestamp(int height) { // @TODO: most likely inefficient, refactor QMap::iterator i; int timestamp = 0; @@ -70,6 +70,10 @@ struct RestoreHeightLookup { return timestamp; } + QDateTime heightToDate(int height) { + return QDateTime::fromSecsSinceEpoch(this->heightToTimestamp(height)); + } + static RestoreHeightLookup *fromFile(const QString &fn, NetworkType::Type type) { // initialize this class using a lookup table, e.g `:/assets/restore_heights_monero_mainnet.txt`/ auto rtn = new RestoreHeightLookup(type); diff --git a/src/utils/TorManager.cpp b/src/utils/TorManager.cpp index 252417b0..3a9c7bb7 100644 --- a/src/utils/TorManager.cpp +++ b/src/utils/TorManager.cpp @@ -9,6 +9,7 @@ #include #include "utils/utils.h" +#include "utils/os/tails.h" #include "appcontext.h" #include "config-feather.h" diff --git a/src/utils/WebsocketClient.h b/src/utils/WebsocketClient.h index 805e6524..c9e098db 100644 --- a/src/utils/WebsocketClient.h +++ b/src/utils/WebsocketClient.h @@ -8,7 +8,7 @@ #include #include #include -#include "globals.h" +#include "constants.h" class WebsocketClient : public QObject { Q_OBJECT @@ -36,7 +36,7 @@ private slots: private: bool m_connect = false; - QUrl m_url = globals::websocketUrl; + QUrl m_url = constants::websocketUrl; QTimer m_connectionTimer; QTimer m_pingTimer; }; diff --git a/src/utils/WebsocketNotifier.cpp b/src/utils/WebsocketNotifier.cpp index 1d509a64..39a7c450 100644 --- a/src/utils/WebsocketNotifier.cpp +++ b/src/utils/WebsocketNotifier.cpp @@ -3,8 +3,8 @@ #include "WebsocketNotifier.h" #include "utils.h" -#include "tails.h" -#include "whonix.h" +#include "utils/os/tails.h" +#include "utils/os/whonix.h" #include @@ -20,6 +20,8 @@ QPointer WebsocketNotifier::m_instance(nullptr); void WebsocketNotifier::onWSMessage(const QJsonObject &msg) { QString cmd = msg.value("cmd").toString(); + m_cache[cmd] = msg; + if (cmd == "blockheights") { QJsonObject data = msg.value("data").toObject(); int mainnet = data.value("mainnet").toInt(); @@ -84,6 +86,12 @@ void WebsocketNotifier::onWSMessage(const QJsonObject &msg) { #endif } +void WebsocketNotifier::emitCache() { + for (const auto &msg : m_cache) { + this->onWSMessage(msg); + } +} + void WebsocketNotifier::onWSNodes(const QJsonArray &nodes) { // TODO: Refactor, should be filtered client side @@ -93,7 +101,7 @@ void WebsocketNotifier::onWSNodes(const QJsonArray &nodes) { auto nettype = obj.value("nettype"); auto type = obj.value("type"); - auto networkType = config()->get(Config::networkType).toInt(); + auto networkType = constants::networkType; // filter remote node network types if(nettype == "mainnet" && networkType != NetworkType::MAINNET) diff --git a/src/utils/WebsocketNotifier.h b/src/utils/WebsocketNotifier.h index 3fa17c5c..96096034 100644 --- a/src/utils/WebsocketNotifier.h +++ b/src/utils/WebsocketNotifier.h @@ -22,10 +22,10 @@ public: explicit WebsocketNotifier(QObject *parent); QMap heights; - WebsocketClient websocketClient; static WebsocketNotifier* instance(); + void emitCache(); signals: void BlockHeightsReceived(int mainnet, int stagenet); @@ -52,6 +52,8 @@ private slots: private: static QPointer m_instance; + + QHash m_cache; }; inline WebsocketNotifier* websocketNotifier() diff --git a/src/utils/brute.h b/src/utils/brute.h index dddab97d..16801b6b 100644 --- a/src/utils/brute.h +++ b/src/utils/brute.h @@ -40,8 +40,8 @@ private: std::string m_word; strategy m_currentStrategy; - int iword = 0; - int ichar = 0; + size_t iword = 0; + size_t ichar = 0; }; #endif //FEATHER_BRUTE_H diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 883a871d..2e4baf96 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -5,7 +5,7 @@ #include "config.h" #include "utils/utils.h" -#include "utils/tails.h" +#include "utils/os/tails.h" #define QS QStringLiteral @@ -17,49 +17,68 @@ struct ConfigDirective static const QHash configStrings = { // General - {Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}}, - {Config::checkForUpdates,{QS("checkForUpdates"), true}}, + {Config::firstRun, {QS("firstRun"), true}}, {Config::warnOnStagenet,{QS("warnOnStagenet"), true}}, {Config::warnOnTestnet,{QS("warnOnTestnet"), true}}, {Config::warnOnAlpha,{QS("warnOnAlpha"), true}}, + {Config::homeWidget,{QS("homeWidget"), "ccs"}}, {Config::donateBeg,{QS("donateBeg"), 1}}, - {Config::skin,{QS("skin"), "light"}}, - {Config::preferredFiatCurrency,{QS("preferredFiatCurrency"), "USD"}}, - {Config::blockExplorer,{QS("blockExplorer"), "exploremonero.com"}}, + {Config::showHistorySyncNotice, {QS("showHistorySyncNotice"), true}}, + + {Config::geometry, {QS("geometry"), {}}}, + {Config::windowState, {QS("windowState"), {}}}, + {Config::GUI_HistoryViewState, {QS("GUI_HistoryViewState"), {}}}, + + // Wallets {Config::walletDirectory,{QS("walletDirectory"), ""}}, {Config::autoOpenWalletPath,{QS("autoOpenWalletPath"), ""}}, - {Config::walletPath,{QS("walletPath"), ""}}, - {Config::xmrigPath,{QS("xmrigPath"), ""}}, - {Config::xmrigPool,{QS("xmrigPool"), "pool.xmr.pt:9000"}}, + {Config::recentlyOpenedWallets, {QS("recentlyOpenedWallets"), {}}}, + + // Nodes {Config::nodes,{QS("nodes"), "{}"}}, - {Config::websocketEnabled,{QS("websocketEnabled"), true}}, {Config::nodeSource,{QS("nodeSource"), 0}}, {Config::useOnionNodes,{QS("useOnionNodes"), false}}, + + // Tabs {Config::showTabHome,{QS("showTabHome"), true}}, {Config::showTabCoins,{QS("showTabCoins"), false}}, {Config::showTabExchange, {QS("showTabExchange"), false}}, {Config::showTabXMRig,{QS("showTabXMRig"), false}}, {Config::showTabCalc,{QS("showTabCalc"), true}}, - {Config::geometry, {QS("geometry"), {}}}, - {Config::windowState, {QS("windowState"), {}}}, - {Config::firstRun, {QS("firstRun"), true}}, - {Config::hideBalance, {QS("hideBalance"), false}}, - {Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}}, - {Config::showHistorySyncNotice, {QS("showHistorySyncNotice"), true}}, - {Config::GUI_HistoryViewState, {QS("GUI_HistoryViewState"), {}}}, + {Config::showSearchbar,{QS("showSearchbar"), true}}, + + // Mining + {Config::xmrigPath,{QS("xmrigPath"), ""}}, + {Config::xmrigPool,{QS("xmrigPool"), "pool.xmr.pt:9000"}}, + + // Settings + {Config::preferredFiatCurrency,{QS("preferredFiatCurrency"), "USD"}}, + {Config::skin,{QS("skin"), "light"}}, {Config::amountPrecision, {QS("amountPrecision"), 12}}, {Config::dateFormat, {QS("dateFormat"), "yyyy-MM-dd"}}, {Config::timeFormat, {QS("timeFormat"), "HH:mm"}}, + {Config::balanceDisplay, {QS("balanceDisplay"), Config::BalanceDisplay::spendablePlusUnconfirmed}}, + {Config::multiBroadcast, {QS("multiBroadcast"), true}}, + {Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}}, + {Config::hideBalance, {QS("hideBalance"), false}}, + + {Config::blockExplorer,{QS("blockExplorer"), "exploremonero.com"}}, + {Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}}, + {Config::localMoneroFrontend, {QS("localMoneroFrontend"), "https://localmonero.co"}}, + + {Config::fiatSymbols, {QS("fiatSymbols"), QStringList{"USD", "EUR", "GBP", "CAD", "AUD", "RUB"}}}, + {Config::cryptoSymbols, {QS("cryptoSymbols"), QStringList{"BTC", "ETH", "LTC", "XMR", "ZEC"}}}, + + // Tor {Config::torPrivacyLevel, {QS("torPrivacyLevel"), 1}}, {Config::socks5Host, {QS("socks5Host"), "127.0.0.1"}}, {Config::socks5Port, {QS("socks5Port"), "9050"}}, - {Config::socks5User, {QS("socks5User"), ""}}, - {Config::socks5Pass, {QS("socks5Pass"), ""}}, + {Config::socks5User, {QS("socks5User"), ""}}, // Unused + {Config::socks5Pass, {QS("socks5Pass"), ""}}, // Unused {Config::useLocalTor, {QS("useLocalTor"), false}}, - {Config::networkType, {QS("networkType"), NetworkType::Type::MAINNET}}, - {Config::localMoneroFrontend, {QS("localMoneroFrontend"), "https://localmonero.co"}} + {Config::initSyncThreshold, {QS("initSyncThreshold"), 360}} }; diff --git a/src/utils/config.h b/src/utils/config.h index 91268975..96fdb345 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -20,50 +20,69 @@ public: enum ConfigKey { - warnOnExternalLink, - checkForUpdates, + // General + firstRun, warnOnStagenet, warnOnTestnet, warnOnAlpha, + homeWidget, donateBeg, + showHistorySyncNotice, + + geometry, + windowState, + GUI_HistoryViewState, + + // Wallets + walletDirectory, // Directory where wallet files are stored autoOpenWalletPath, - skin, - preferredFiatCurrency, - blockExplorer, - walletDirectory, - walletPath, - xmrigPath, - xmrigPool, + recentlyOpenedWallets, + + // Nodes nodes, - websocketEnabled, nodeSource, useOnionNodes, + + // Tabs showTabHome, showTabCoins, showTabExchange, showTabCalc, showTabXMRig, - geometry, - windowState, - firstRun, - hideBalance, - redditFrontend, - showHistorySyncNotice, - GUI_HistoryViewState, + showSearchbar, + + // Mining + xmrigPath, + xmrigPool, + + // Settings + preferredFiatCurrency, + skin, amountPrecision, - portableMode, dateFormat, timeFormat, + balanceDisplay, + multiBroadcast, + warnOnExternalLink, + hideBalance, + + blockExplorer, + redditFrontend, + localMoneroFrontend, + + fiatSymbols, + cryptoSymbols, + + // Tor torPrivacyLevel, socks5Host, socks5Port, socks5User, socks5Pass, useLocalTor, // Prevents Feather from starting bundled Tor daemon - networkType, - localMoneroFrontend + initSyncThreshold }; enum PrivacyLevel { @@ -72,6 +91,12 @@ public: allTor }; + enum BalanceDisplay { + totalBalance = 0, + spendablePlusUnconfirmed, + spendable + }; + ~Config() override; QVariant get(ConfigKey key); QString getFileName(); diff --git a/src/utils/daemonrpc.cpp b/src/utils/daemonrpc.cpp index 58c57d9f..48faafd5 100644 --- a/src/utils/daemonrpc.cpp +++ b/src/utils/daemonrpc.cpp @@ -93,3 +93,7 @@ QString DaemonRpc::onSendRawTransactionFailed(const QJsonObject &obj) { void DaemonRpc::setDaemonAddress(const QString &daemonAddress) { m_daemonAddress = daemonAddress; } + +void DaemonRpc::setNetwork(QNetworkAccessManager *network) { + m_network = new UtilsNetworking(network, this); +} \ No newline at end of file diff --git a/src/utils/daemonrpc.h b/src/utils/daemonrpc.h index 403e8939..0bd1af8e 100644 --- a/src/utils/daemonrpc.h +++ b/src/utils/daemonrpc.h @@ -33,6 +33,7 @@ public: void getTransactions(const QStringList &txs_hashes, bool decode_as_json = false, bool prune = false); void setDaemonAddress(const QString &daemonAddress); + void setNetwork(QNetworkAccessManager *network); signals: void ApiResponse(DaemonResponse resp); diff --git a/src/utils/nodes.cpp b/src/utils/nodes.cpp index 2d7b08c4..a16f5c06 100644 --- a/src/utils/nodes.cpp +++ b/src/utils/nodes.cpp @@ -5,63 +5,128 @@ #include "nodes.h" #include "utils/utils.h" -#include "utils/WebsocketClient.h" +#include "utils/os/tails.h" #include "appcontext.h" +#include "constants.h" +#include "utils/WebsocketNotifier.h" +#include "utils/TorManager.h" +bool NodeList::addNode(const QString &node, NetworkType::Type networkType, NodeList::Type source) { + // We can't obtain references to QJsonObjects... + QJsonObject obj = this->getConfigData(); + this->ensureStructure(obj, networkType); + + QString networkTypeStr = QString::number(networkType); + QJsonObject netTypeObj = obj.value(networkTypeStr).toObject(); + + QString sourceStr = Utils::QtEnumToString(source); + QJsonArray sourceArray = netTypeObj.value(sourceStr).toArray(); + + if (sourceArray.contains(node)) { + return false; + } + + sourceArray.append(node); + + netTypeObj[sourceStr] = sourceArray; + obj[networkTypeStr] = netTypeObj; + + config()->set(Config::nodes, obj); + return true; +} + +void NodeList::setNodes(const QStringList &nodes, NetworkType::Type networkType, NodeList::Type source) { + QJsonObject obj = this->getConfigData(); + this->ensureStructure(obj, networkType); + + QString networkTypeStr = QString::number(networkType); + QJsonObject netTypeObj = obj.value(networkTypeStr).toObject(); + + QString sourceStr = Utils::QtEnumToString(source); + QJsonArray sourceArray = QJsonArray::fromStringList(nodes); + + netTypeObj[sourceStr] = sourceArray; + obj[networkTypeStr] = netTypeObj; + + config()->set(Config::nodes, obj); +} + +QStringList NodeList::getNodes(NetworkType::Type networkType, NodeList::Type source) { + QJsonObject obj = this->getConfigData(); + this->ensureStructure(obj, networkType); + + QString networkTypeStr = QString::number(networkType); + QJsonObject netTypeObj = obj.value(networkTypeStr).toObject(); + + QString sourceStr = Utils::QtEnumToString(source); + QJsonArray sourceArray = netTypeObj.value(sourceStr).toArray(); + + QStringList nodes; + for (const auto &node : sourceArray) { + nodes << node.toString(); + } + return nodes; +} + +QJsonObject NodeList::getConfigData() { + QJsonObject obj = config()->get(Config::nodes).toJsonObject(); + + // Load old config format + if (obj.isEmpty()) { + auto jsonData = config()->get(Config::nodes).toByteArray(); + if (Utils::validateJSON(jsonData)) { + QJsonDocument doc = QJsonDocument::fromJson(jsonData); + obj = doc.object(); + } + } + + return obj; +} + +void NodeList::ensureStructure(QJsonObject &obj, NetworkType::Type networkType) { + QString networkTypeStr = QString::number(networkType); + QJsonObject netTypeObj = obj.value(networkTypeStr).toObject(); + if (!netTypeObj.contains("ws")) + netTypeObj["ws"] = QJsonArray(); + if (!netTypeObj.contains("custom")) + netTypeObj["custom"] = QJsonArray(); + + obj[networkTypeStr] = netTypeObj; +} Nodes::Nodes(AppContext *ctx, QObject *parent) : QObject(parent) - , m_ctx(ctx) - , m_connection(FeatherNode()) , modelWebsocket(new NodeModel(NodeSource::websocket, this)) , modelCustom(new NodeModel(NodeSource::custom, this)) + , m_ctx(ctx) + , m_connection(FeatherNode()) { this->loadConfig(); connect(m_ctx, &AppContext::walletRefreshed, this, &Nodes::onWalletRefreshed); + connect(websocketNotifier(), &WebsocketNotifier::NodesReceived, this, &Nodes::onWSNodesReceived); } void Nodes::loadConfig() { - auto configNodes = config()->get(Config::nodes).toByteArray(); - auto key = QString::number(m_ctx->networkType); - if (!Utils::validateJSON(configNodes)) { - m_configJson[key] = QJsonObject(); - qCritical() << "Fixed malformed config key \"nodes\""; - } - - QJsonDocument doc = QJsonDocument::fromJson(configNodes); - m_configJson = doc.object(); - - if (!m_configJson.contains(key)) - m_configJson[key] = QJsonObject(); - - auto obj = m_configJson.value(key).toObject(); - if (!obj.contains("custom")) - obj["custom"] = QJsonArray(); - if (!obj.contains("ws")) - obj["ws"] = QJsonArray(); - - // load custom nodes - auto nodes = obj.value("custom").toArray(); - for (auto value: nodes) { - auto customNode = FeatherNode(value.toString()); + QStringList customNodes = m_nodes.getNodes(constants::networkType, NodeList::custom); + for (const auto &node : customNodes) { + FeatherNode customNode{node}; customNode.custom = true; - if(m_connection == customNode) { - if(m_connection.isActive) + if (m_connection == customNode) { + if (m_connection.isActive) customNode.isActive = true; - else if(m_connection.isConnecting) + else if (m_connection.isConnecting) customNode.isConnecting = true; } m_customNodes.append(customNode); } - // load cached websocket nodes - auto ws = obj.value("ws").toArray(); - for (auto value: ws) { - auto wsNode = FeatherNode(value.toString()); + QStringList websocketNodes = m_nodes.getNodes(constants::networkType, NodeList::ws); + for (const auto &node : websocketNodes) { + FeatherNode wsNode{node}; wsNode.custom = false; - wsNode.online = true; // assume online + wsNode.online = true; // assume online if (m_connection == wsNode) { if (m_connection.isActive) @@ -88,9 +153,9 @@ void Nodes::loadConfig() { QJsonObject nodes_obj = nodes_json.object(); QString netKey; - if (m_ctx->networkType == NetworkType::MAINNET) { + if (constants::networkType == NetworkType::MAINNET) { netKey = "mainnet"; - } else if (m_ctx->networkType == NetworkType::STAGENET) { + } else if (constants::networkType == NetworkType::STAGENET) { netKey = "stagenet"; } @@ -98,30 +163,22 @@ void Nodes::loadConfig() { QJsonArray nodes_list; nodes_list = nodes_json[netKey].toObject()["tor"].toArray(); nodes_list.append(nodes_list = nodes_json[netKey].toObject()["clearnet"].toArray()); + for (auto node: nodes_list) { - auto wsNode = FeatherNode(node.toString()); + FeatherNode wsNode(node.toString()); wsNode.custom = false; wsNode.online = true; m_websocketNodes.append(wsNode); + m_nodes.addNode(node.toString(), constants::networkType, NodeList::Type::ws); } } qDebug() << QString("Loaded %1 nodes from hardcoded list").arg(m_websocketNodes.count()); } - m_configJson[key] = obj; - this->writeConfig(); this->updateModels(); } -void Nodes::writeConfig() { - QJsonDocument doc(m_configJson); - QString output(doc.toJson(QJsonDocument::Compact)); - config()->set(Config::nodes, output); - - qDebug() << "Saved node config."; -} - void Nodes::connectToNode() { // auto connect m_wsExhaustedWarningEmitted = false; @@ -137,10 +194,10 @@ void Nodes::connectToNode(const FeatherNode &node) { qInfo() << QString("Attempting to connect to %1 (%2)").arg(node.toAddress()).arg(node.custom ? "custom" : "ws"); if (!node.url.userName().isEmpty() && !node.url.password().isEmpty()) - m_ctx->currentWallet->setDaemonLogin(node.url.userName(), node.url.password()); + m_ctx->wallet->setDaemonLogin(node.url.userName(), node.url.password()); // Don't use SSL over Tor - m_ctx->currentWallet->setUseSSL(!node.isOnion()); + m_ctx->wallet->setUseSSL(!node.isOnion()); QString proxyAddress; if (useTorProxy(node)) { @@ -152,7 +209,7 @@ void Nodes::connectToNode(const FeatherNode &node) { } } - m_ctx->currentWallet->initAsync(node.toAddress(), true, 0, false, false, 0, proxyAddress); + m_ctx->wallet->initAsync(node.toAddress(), true, 0, false, false, 0, proxyAddress); m_connection = node; m_connection.isActive = false; @@ -164,11 +221,11 @@ void Nodes::connectToNode(const FeatherNode &node) { void Nodes::autoConnect(bool forceReconnect) { // this function is responsible for automatically connecting to a daemon. - if (m_ctx->currentWallet == nullptr || !m_enableAutoconnect) { + if (m_ctx->wallet == nullptr || !m_enableAutoconnect) { return; } - Wallet::ConnectionStatus status = m_ctx->currentWallet->connectionStatus(); + Wallet::ConnectionStatus status = m_ctx->wallet->connectionStatus(); bool wsMode = (this->source() == NodeSource::websocket); if (wsMode && !m_wsNodesReceived && websocketNodes().count() == 0) { @@ -183,7 +240,7 @@ void Nodes::autoConnect(bool forceReconnect) { } // try a connect - auto node = this->pickEligibleNode(); + FeatherNode node = this->pickEligibleNode(); this->connectToNode(node); return; } @@ -278,15 +335,12 @@ void Nodes::onWSNodesReceived(QList &nodes) { } // cache into config - auto key = QString::number(m_ctx->networkType); - auto obj = m_configJson.value(key).toObject(); - auto ws = QJsonArray(); - for (auto const &node: m_websocketNodes) - ws.push_back(node.toAddress()); + QStringList wsNodeList; + for (const auto &node : m_websocketNodes) { + wsNodeList << node.toAddress(); + } + m_nodes.setNodes(wsNodeList, constants::networkType, NodeList::ws); - obj["ws"] = ws; - m_configJson[key] = obj; - this->writeConfig(); this->resetLocalState(); this->updateModels(); } @@ -299,30 +353,35 @@ void Nodes::onNodeSourceChanged(NodeSource nodeSource) { void Nodes::setCustomNodes(const QList &nodes) { m_customNodes.clear(); - auto key = QString::number(m_ctx->networkType); - auto obj = m_configJson.value(key).toObject(); QStringList nodesList; for (auto const &node: nodes) { - if (nodesList.contains(node.toAddress())) continue; + if (nodesList.contains(node.toAddress())) // skip duplicates + continue; nodesList.append(node.toAddress()); m_customNodes.append(node); } - auto arr = QJsonArray::fromStringList(nodesList); - obj["custom"] = arr; - m_configJson[key] = obj; - this->writeConfig(); + m_nodes.setNodes(nodesList, constants::networkType, NodeList::Type::custom); + this->resetLocalState(); this->updateModels(); } void Nodes::onWalletRefreshed() { if (config()->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; + + // Don't reconnect if we're already connected to an .onion node + if (m_connection.isOnion()) + return; + + // Don't reconnect on Tails or Whonix (all traffic is already routed through Tor) if (TailsOS::detect() || WhonixOS::detect()) return; + this->autoConnect(true); } } @@ -332,9 +391,24 @@ bool Nodes::useOnionNodes() { return true; } auto privacyLevel = config()->get(Config::torPrivacyLevel).toInt(); - if (privacyLevel == Config::allTor || (privacyLevel == Config::allTorExceptInitSync && m_ctx->refreshed)) { + if (privacyLevel == Config::allTor) { return true; } + + if (privacyLevel == Config::allTorExceptInitSync) { + if (m_ctx->refreshed) + return true; + + if (appData()->heights.contains(constants::networkType)) { + int initSyncThreshold = config()->get(Config::initSyncThreshold).toInt(); + int networkHeight = appData()->heights[constants::networkType]; + + if (m_ctx->wallet->blockChainHeight() > (networkHeight - initSyncThreshold)) { + return true; + } + } + } + return false; } diff --git a/src/utils/nodes.h b/src/utils/nodes.h index a70881df..ce60584c 100644 --- a/src/utils/nodes.h +++ b/src/utils/nodes.h @@ -20,6 +20,25 @@ enum NodeSource { custom }; +class NodeList : public QObject { +Q_OBJECT + +public: + enum Type { + ws = 0, + custom + }; + Q_ENUM(Type) + + bool addNode(const QString &node, NetworkType::Type networkType, NodeList::Type source); + void setNodes(const QStringList &nodes, NetworkType::Type networkType, NodeList::Type source); + QStringList getNodes(NetworkType::Type networkType, NodeList::Type source); + +private: + void ensureStructure(QJsonObject &obj, NetworkType::Type networkType); + QJsonObject getConfigData(); +}; + struct FeatherNode { explicit FeatherNode(QString address = "", int height = 0, int target_height = 0, bool online = false) : height(height) @@ -56,7 +75,8 @@ struct FeatherNode { } bool isLocal() const { - return (url.host() == "127.0.0.1" || url.host() == "localhost"); + 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"); } bool isOnion() const { @@ -92,7 +112,6 @@ class Nodes : public QObject { public: explicit Nodes(AppContext *ctx, QObject *parent = nullptr); void loadConfig(); - void writeConfig(); NodeSource source(); FeatherNode connection(); @@ -122,9 +141,11 @@ private slots: void onWalletRefreshed(); private: - AppContext *m_ctx = nullptr; + AppContext *m_ctx; QJsonObject m_configJson; + NodeList m_nodes; + QStringList m_recentFailures; QList m_customNodes; diff --git a/src/utils/tails.cpp b/src/utils/os/tails.cpp similarity index 100% rename from src/utils/tails.cpp rename to src/utils/os/tails.cpp diff --git a/src/utils/tails.h b/src/utils/os/tails.h similarity index 100% rename from src/utils/tails.h rename to src/utils/os/tails.h diff --git a/src/utils/whonix.cpp b/src/utils/os/whonix.cpp similarity index 100% rename from src/utils/whonix.cpp rename to src/utils/os/whonix.cpp diff --git a/src/utils/whonix.h b/src/utils/os/whonix.h similarity index 100% rename from src/utils/whonix.h rename to src/utils/os/whonix.h diff --git a/src/utils/prices.cpp b/src/utils/prices.cpp index 1ff50853..ea1d03c5 100644 --- a/src/utils/prices.cpp +++ b/src/utils/prices.cpp @@ -1,93 +1,73 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2021, The Monero Project. -#include -#include -#include - #include "utils/prices.h" -Prices::Prices(QObject *parent) : QObject(parent) { - this->rates = QMap(); - this->markets = QMap(); - this->fiat = QMap(); - fiat["USD"] = "$"; - fiat["EUR"] = "€"; - fiat["JPY"] = "¥"; - fiat["KRW"] = "₩"; - fiat["MXN"] = "$"; - fiat["RUB"] = "\u20BD"; - fiat["CAD"] = "$"; - fiat["GBP"] = "£"; - fiat["SEK"] = "kr"; - fiat["ZAR"] = "R"; - fiat["THB"] = "฿"; - fiat["TRY"] = "₺"; - fiat["CHF"] = "Fr"; - fiat["CNY"] = "¥"; - fiat["CZK"] = "Kč"; - fiat["AUD"] = "$"; - fiat["NZD"] = "$"; +Prices::Prices(QObject *parent) + : QObject(parent) +{ } void Prices::cryptoPricesReceived(const QJsonArray &data) { - QStringList filter = QStringList() << "XMR" << "ZEC" << "BTC" << "ETH" << "BCH" << "LTC"; - filter << "EOS" << "ADA" << "XLM" << "TRX" << "DASH" << "DCR" << "VET" << "DOGE" << "XRP" << "WOW"; + this->markets.clear(); - QMap msMap; - for(auto &&entry: data) { - marketStruct ms; + for (const auto &entry : data) { QJsonObject obj = entry.toObject(); + marketStruct ms; ms.symbol = obj.value("symbol").toString(); ms.image = obj.value("image").toString(); ms.name = obj.value("name").toString(); ms.price_usd = obj.value("current_price").toDouble(); ms.price_usd_change_pct_24h = obj.value("price_change_percentage_24h").toDouble(); + if (ms.price_usd <= 0) + continue; - if(ms.price_usd <= 0) continue; - if(filter.contains(ms.symbol.toUpper())) - msMap.insert(ms.symbol.toUpper(), ms); + this->markets.insert(ms.symbol.toUpper(), ms); } - if(msMap.count() > 0) - this->markets = msMap; - emit cryptoPricesUpdated(); } -double Prices::convert(const QString &symbolFrom, const QString &symbolTo, double amount) { - if(symbolFrom == symbolTo) return amount; - if(amount <= 0.0) return 0.0; +void Prices::fiatPricesReceived(const QJsonObject &data) { + QJsonObject ratesData = data.value("rates").toObject(); + for (const auto ¤cy : ratesData.keys()) { + this->rates.insert(currency, ratesData.value(currency).toDouble()); + } + emit fiatPricesUpdated(); +} - double usd_from; - QString from = symbolFrom.toUpper(); - QString to = symbolTo.toUpper(); - - if(this->markets.contains(from)) - usd_from = this->markets[from].price_usd * amount; - else if(this->rates.contains(from)) { - if(from == "USD") - usd_from = amount; - else - usd_from = amount / this->rates[from]; - } else +double Prices::convert(QString symbolFrom, QString symbolTo, double amount) { + if (symbolFrom == symbolTo) + return amount; + if (amount <= 0.0) return 0.0; - if(to == "USD") - return usd_from; + symbolFrom = symbolFrom.toUpper(); + symbolTo = symbolTo.toUpper(); - if(this->markets.contains(to)) - return usd_from / this->markets[to].price_usd; - else if(this->rates.contains(to)) - return usd_from * this->rates[to]; + double usdPrice; + if (this->markets.contains(symbolFrom)) { + usdPrice = this->markets[symbolFrom].price_usd * amount; + } + else if (this->rates.contains(symbolFrom)) { + if (symbolFrom == "USD") { + usdPrice = amount; + } else { + usdPrice = amount / this->rates[symbolFrom]; + } + } + else { + return 0.0; + } + + if (symbolTo == "USD") + return usdPrice; + + if (this->markets.contains(symbolTo)) + return usdPrice / this->markets[symbolTo].price_usd; + else if (this->rates.contains(symbolTo)) + return usdPrice * this->rates[symbolTo]; return 0.0; } -void Prices::fiatPricesReceived(const QJsonObject &data) { - QJsonObject rates = data.value("rates").toObject(); - for(const auto ¤cy: fiat.keys()) - if(rates.contains(currency)) - this->rates.insert(currency, rates.value(currency).toDouble()); - emit fiatPricesUpdated(); -} diff --git a/src/utils/prices.h b/src/utils/prices.h index 3f0df189..03fa06be 100644 --- a/src/utils/prices.h +++ b/src/utils/prices.h @@ -5,8 +5,6 @@ #define FEATHER_PRICES_H #include -#include -#include #include "utils/utils.h" @@ -26,13 +24,12 @@ public: explicit Prices(QObject *parent = nullptr); QMap rates; QMap markets; - QMap fiat; // symbol:sign public slots: void cryptoPricesReceived(const QJsonArray &data); void fiatPricesReceived(const QJsonObject &data); - double convert(const QString &symbolFrom, const QString &symbolTo, double amount); + double convert(QString symbolFrom, QString symbolTo, double amount); signals: void fiatPricesUpdated(); diff --git a/src/utils/txfiathistory.cpp b/src/utils/txfiathistory.cpp index 6a4eaab7..b0f9fb83 100644 --- a/src/utils/txfiathistory.cpp +++ b/src/utils/txfiathistory.cpp @@ -7,10 +7,11 @@ #include "txfiathistory.h" #include "utils/utils.h" -TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) : - QObject(parent), - m_genesis_timestamp(genesis_timestamp), - m_configDirectory(configDirectory) { +TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) + : QObject(parent) + , m_genesis_timestamp(genesis_timestamp) + , m_configDirectory(configDirectory) +{ m_databasePath = QString("%1/fiatHistory.db").arg(configDirectory); this->loadDatabase(); } @@ -70,7 +71,7 @@ void TxFiatHistory::onUpdateDatabase() { } // keep local fiatTxHistory database up to date, loop for missing dates - for(year; year != yearCurrent + 1; year += 1){ + for(; year != yearCurrent + 1; year += 1){ for(int month = 1; month != 13; month++) { if(year == yearCurrent && month == now.month() && now.day() == 1) break; QDateTime _now; diff --git a/src/utils/txfiathistory.h b/src/utils/txfiathistory.h index 5587b7f9..cbb7d80d 100644 --- a/src/utils/txfiathistory.h +++ b/src/utils/txfiathistory.h @@ -23,11 +23,11 @@ signals: private: void loadDatabase(); void writeDatabase(); + int m_genesis_timestamp; QString m_databasePath; QString m_configDirectory; bool m_initialized = false; QMap m_database; - int m_genesis_timestamp; }; #endif //FEATHER_TXFIATHISTORY_H diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 97e38c9e..78fc8306 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -12,10 +12,10 @@ #include "utils.h" #include "utils/config.h" -#include "utils/tails.h" -#include "utils/whonix.h" +#include "utils/os/tails.h" +#include "utils/os/whonix.h" #include "utils/ColorScheme.h" -#include "globals.h" +#include "constants.h" QByteArray Utils::fileGetContents(const QString &path) { @@ -142,7 +142,7 @@ void Utils::desktopNotify(const QString &title, const QString &message, int dura QString stderr = process.readAllStandardError(); #elif defined(Q_OS_MACOS) QProcess process; - // @TODO: need to escape special chars with \ + // @TODO: need to escape special chars with "\" process.start("osascript", macos); process.waitForFinished(-1); QString stdout = process.readAllStandardOutput(); @@ -453,15 +453,6 @@ int Utils::maxLength(const QVector &array) { return maxLength; } -QString Utils::balanceFormat(quint64 balance) { - QString str = QString::number(balance / globals::cdiv, 'f', 4); - - str.remove(QRegExp("0+$")); - str.remove(QRegExp("\\.$")); - - return str; -} - QTextCharFormat Utils::addressTextFormat(const SubaddressIndex &index) { if (index.isPrimary()) { QTextCharFormat rec; diff --git a/src/utils/utils.h b/src/utils/utils.h index e72a8e73..7b88b8d8 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -75,7 +75,6 @@ public: static QString amountToCurrencyString(double amount, const QString ¤cyCode); static int maxLength(const QVector &array); static QMap localeCache; - static QString balanceFormat(quint64 balance); static QTextCharFormat addressTextFormat(const SubaddressIndex &index); static bool isTorsocks(); static QString defaultWalletDir(); diff --git a/src/utils/xmrig.cpp b/src/utils/xmrig.cpp index 6d0d9c45..f9f937f4 100644 --- a/src/utils/xmrig.cpp +++ b/src/utils/xmrig.cpp @@ -7,6 +7,7 @@ #include "utils/utils.h" #include "utils/xmrig.h" +#include "utils/TorManager.h" #include "appcontext.h" XmRig::XmRig(const QString &configDir, QObject *parent) diff --git a/src/widgets/LocalMoneroWidget.cpp b/src/widgets/LocalMoneroWidget.cpp index e8565c07..db3c31b5 100644 --- a/src/widgets/LocalMoneroWidget.cpp +++ b/src/widgets/LocalMoneroWidget.cpp @@ -12,15 +12,13 @@ #include #include -LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, AppContext *ctx) +LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, QSharedPointer ctx) : QWidget(parent) , ui(new Ui::LocalMoneroWidget) - , m_ctx(ctx) + , m_ctx(std::move(ctx)) { ui->setupUi(this); -// this->adjustSize(); - QPixmap logo(":/assets/images/localMonero_logo.png"); ui->logo->setPixmap(logo.scaled(100, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -52,7 +50,9 @@ LocalMoneroWidget::LocalMoneroWidget(QWidget *parent, AppContext *ctx) ui->frame_loadMore->hide(); - this->skinChanged(); + QTimer::singleShot(1, [this]{ + this->skinChanged(); + }); } void LocalMoneroWidget::skinChanged() { @@ -68,6 +68,7 @@ void LocalMoneroWidget::skinChanged() { void LocalMoneroWidget::onSearchClicked() { m_model->clearData(); m_currentPage = 0; + ui->btn_search->setEnabled(false); this->searchOffers(); } @@ -78,7 +79,7 @@ void LocalMoneroWidget::onSignUpClicked() { } void LocalMoneroWidget::onApiResponse(const LocalMoneroApi::LocalMoneroResponse &resp) { - qDebug() << "We got a response"; + ui->btn_search->setEnabled(true); if (!resp.ok) { QMessageBox::warning(this, "LocalMonero error", QString("Request failed:\n\n%1").arg(resp.message)); diff --git a/src/widgets/LocalMoneroWidget.h b/src/widgets/LocalMoneroWidget.h index 1bace38f..4c08878d 100644 --- a/src/widgets/LocalMoneroWidget.h +++ b/src/widgets/LocalMoneroWidget.h @@ -19,7 +19,7 @@ class LocalMoneroWidget : public QWidget Q_OBJECT public: - explicit LocalMoneroWidget(QWidget *parent, AppContext *ctx); + explicit LocalMoneroWidget(QWidget *parent, QSharedPointer ctx); ~LocalMoneroWidget() override; public slots: @@ -41,11 +41,11 @@ private: void viewOfferDetails(); void updatePaymentMethods(); + Ui::LocalMoneroWidget *ui; + QSharedPointer m_ctx; + int m_currentPage = 0; - Ui::LocalMoneroWidget *ui; - - AppContext *m_ctx; LocalMoneroApi *m_api; LocalMoneroModel *m_model; UtilsNetworking *m_network; diff --git a/src/widgets/RestoreHeightWidget.cpp b/src/widgets/RestoreHeightWidget.cpp new file mode 100644 index 00000000..b3250f17 --- /dev/null +++ b/src/widgets/RestoreHeightWidget.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "RestoreHeightWidget.h" +#include "ui_RestoreHeightWidget.h" + +#include + +#include "AppData.h" +#include "constants.h" + +RestoreHeightWidget::RestoreHeightWidget(QWidget *parent) + : QWidget(parent) + , ui(new Ui::RestoreHeightWidget) +{ + ui->setupUi(this); + + QRegExp yearRe(R"(\d{2,4}-\d{1,2}-\d{1,2})"); + QValidator *yearValidator = new QRegExpValidator(yearRe, this); + ui->line_creationDate->setValidator(yearValidator); + + QRegExp heightRe(R"(\d{7})"); + QValidator *heightValidator = new QRegExpValidator(heightRe, this); + ui->line_restoreHeight->setValidator(heightValidator); + + connect(ui->line_creationDate, &QLineEdit::textEdited, this, &RestoreHeightWidget::onCreationDateChanged); + connect(ui->line_restoreHeight, &QLineEdit::textEdited, this, &RestoreHeightWidget::onRestoreHeightChanged); +} + +void RestoreHeightWidget::setHeight(quint64 restoreHeight) { + ui->line_restoreHeight->setText(QString::number(restoreHeight)); + this->onRestoreHeightChanged(); +} + +int RestoreHeightWidget::getHeight() { + return ui->line_restoreHeight->text().toInt(); +} + +void RestoreHeightWidget::onCreationDateChanged() { + auto curDate = QDateTime::currentDateTime().addDays(-7); + auto date = QDateTime::fromString(ui->line_creationDate->text(), "yyyy-MM-dd"); + if (!date.isValid()) { + return; + } + + QDateTime restoreDate = date > curDate ? curDate : date; + qint64 timestamp = restoreDate.toSecsSinceEpoch(); + + QString restoreHeight = QString::number(appData()->restoreHeights[constants::networkType]->dateToHeight(timestamp)); + ui->line_restoreHeight->setText(restoreHeight); +} + +void RestoreHeightWidget::onRestoreHeightChanged() { + int restoreHeight = ui->line_restoreHeight->text().toInt(); + QDateTime date = appData()->restoreHeights[constants::networkType]->heightToDate(restoreHeight); + ui->line_creationDate->setText(date.toString("yyyy-MM-dd")); +} + +RestoreHeightWidget::~RestoreHeightWidget() { + delete ui; +} \ No newline at end of file diff --git a/src/widgets/restoreheightwidget.h b/src/widgets/RestoreHeightWidget.h similarity index 53% rename from src/widgets/restoreheightwidget.h rename to src/widgets/RestoreHeightWidget.h index a1418c35..a7f466bd 100644 --- a/src/widgets/restoreheightwidget.h +++ b/src/widgets/RestoreHeightWidget.h @@ -1,13 +1,10 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2021, The Monero Project. -#ifndef RESTOREHEIGHTWIDGET_H -#define RESTOREHEIGHTWIDGET_H +#ifndef FEATHER_RESTOREHEIGHTWIDGET_H +#define FEATHER_RESTOREHEIGHTWIDGET_H #include -#include - -#include "appcontext.h" namespace Ui { class RestoreHeightWidget; @@ -19,17 +16,18 @@ class RestoreHeightWidget : public QWidget public: explicit RestoreHeightWidget(QWidget *parent = nullptr); - void initRestoreHeights(RestoreHeightLookup *lookup); - void setHeight(int height); - int getHeight(); - void hideSlider(); ~RestoreHeightWidget() override; -private: - void updateTimestamp(int date); + void setHeight(quint64 restoreHeight); + int getHeight(); - RestoreHeightLookup *m_restoreHeightLookup = nullptr; +private slots: + void onCreationDateChanged(); + void onRestoreHeightChanged(); + +private: Ui::RestoreHeightWidget *ui; }; -#endif // RESTOREHEIGHTWIDGET_H + +#endif //FEATHER_RESTOREHEIGHTWIDGET_H diff --git a/src/widgets/RestoreHeightWidget.ui b/src/widgets/RestoreHeightWidget.ui new file mode 100644 index 00000000..491c9fdf --- /dev/null +++ b/src/widgets/RestoreHeightWidget.ui @@ -0,0 +1,61 @@ + + + RestoreHeightWidget + + + + 0 + 0 + 622 + 88 + + + + Form + + + + + + Wallet creation date: + + + + + + + + + + (YYYY-MM-DD) + + + + + + + Restore height: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + diff --git a/src/widgets/TickerWidget.cpp b/src/widgets/TickerWidget.cpp new file mode 100644 index 00000000..d8458ff4 --- /dev/null +++ b/src/widgets/TickerWidget.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "TickerWidget.h" +#include "ui_TickerWidget.h" + +#include "constants.h" +#include "utils/AppData.h" + +TickerWidgetBase::TickerWidgetBase(QWidget *parent, QSharedPointer ctx) + : QWidget(parent) + , ui(new Ui::TickerWidget) + , m_ctx(std::move(ctx)) +{ + ui->setupUi(this); + + ui->tickerPct->setFont(Utils::relativeFont(-2)); + ui->tickerFiat->setFont(Utils::relativeFont(0)); + + this->setPercentageText("0.0", true); + ui->tickerFiat->setText("..."); +} + +TickerWidgetBase::~TickerWidgetBase() { + delete ui; +} + +void TickerWidgetBase::setTitle(const QString &title) { + ui->tickerBox->setTitle(title); +} + +void TickerWidgetBase::setPercentageVisible(bool visible) { + ui->tickerPct->setVisible(visible); +} + +void TickerWidgetBase::setPercentageText(const QString &text, bool positive) { + QString pctText = "

"; + if(positive) { + pctText = pctText.replace("red", "green"); + pctText += QString("+%1%").arg(text); + } else + pctText += QString("%1%").arg(text); + pctText += "

"; + + ui->tickerPct->setText(pctText); +} + +void TickerWidgetBase::setFiatText(double amount, const QString &fiatCurrency) { + QString conversionText = Utils::amountToCurrencyString(amount, fiatCurrency); + ui->tickerFiat->setText(conversionText); +} + +// BalanceTickerWidget +BalanceTickerWidget::BalanceTickerWidget(QWidget *parent, QSharedPointer ctx, bool totalBalance) + : TickerWidgetBase(parent, std::move(ctx)) + , m_totalBalance(totalBalance) +{ + if (totalBalance) + this->setTitle("Total balance"); + else + this->setTitle("Balance"); + + this->setPercentageVisible(false); + + connect(m_ctx.get(), &AppContext::balanceUpdated, this, &BalanceTickerWidget::updateDisplay); + connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &BalanceTickerWidget::updateDisplay); + connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &BalanceTickerWidget::updateDisplay); +} + +void BalanceTickerWidget::updateDisplay() { + double balance = (m_totalBalance ? m_ctx->wallet->balanceAll() : m_ctx->wallet->balance()) / constants::cdiv; + QString fiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); + double balanceFiatAmount = appData()->prices.convert("XMR", fiatCurrency, balance); + if (balanceFiatAmount < 0) + return; + this->setFiatText(balanceFiatAmount, fiatCurrency); +} + +// PriceTickerWidget +PriceTickerWidget::PriceTickerWidget(QWidget *parent, QSharedPointer ctx, QString symbol) + : TickerWidgetBase(parent, std::move(ctx)) + , m_symbol(std::move(symbol)) +{ + this->setTitle(m_symbol); + + connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &PriceTickerWidget::updateDisplay); + connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &PriceTickerWidget::updateDisplay); +} + +void PriceTickerWidget::updateDisplay() { + QString fiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); + double price = appData()->prices.convert(m_symbol, fiatCurrency, 1.0); + if (price < 0) + return; + + auto markets = appData()->prices.markets; + if (!markets.contains(m_symbol)) + return; + + double percentChange24h = markets[m_symbol].price_usd_change_pct_24h; + QString percentChange24hStr = QString::number(percentChange24h, 'f', 2); + this->setPercentageText(percentChange24hStr, percentChange24h >= 0.0); + + this->setFiatText(price, fiatCurrency); +} diff --git a/src/widgets/TickerWidget.h b/src/widgets/TickerWidget.h new file mode 100644 index 00000000..cd586bf0 --- /dev/null +++ b/src/widgets/TickerWidget.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_TICKERWIDGET_H +#define FEATHER_TICKERWIDGET_H + +#include + +#include "appcontext.h" + +namespace Ui { + class TickerWidget; +} + +class TickerWidgetBase : public QWidget +{ + Q_OBJECT + +public: + explicit TickerWidgetBase(QWidget *parent, QSharedPointer ctx); + ~TickerWidgetBase() override; + + void setTitle(const QString &title); + void setPercentageVisible(bool visible); + + void setPercentageText(const QString &text, bool positive); + void setFiatText(double amount, const QString &fiatCurrency); + +public slots: + virtual void updateDisplay() = 0; + +private: + Ui::TickerWidget *ui; + +protected: + QSharedPointer m_ctx; +}; + +class BalanceTickerWidget : public TickerWidgetBase +{ + Q_OBJECT + +public: + explicit BalanceTickerWidget(QWidget *parent, QSharedPointer ctx, bool totalBalance); + +public slots: + void updateDisplay() override; + +private: + bool m_totalBalance; +}; + +class PriceTickerWidget : public TickerWidgetBase +{ + Q_OBJECT + +public: + explicit PriceTickerWidget(QWidget *parent, QSharedPointer ctx, QString symbol); + +public slots: + void updateDisplay() override; + +private: + QString m_symbol; +}; + +#endif // FEATHER_TICKERWIDGET_H diff --git a/src/widgets/tickerwidget.ui b/src/widgets/TickerWidget.ui similarity index 100% rename from src/widgets/tickerwidget.ui rename to src/widgets/TickerWidget.ui diff --git a/src/widgets/ccsprogressdelegate.cpp b/src/widgets/ccsprogressdelegate.cpp index 51044acf..18c39b36 100644 --- a/src/widgets/ccsprogressdelegate.cpp +++ b/src/widgets/ccsprogressdelegate.cpp @@ -6,10 +6,9 @@ #include CCSProgressDelegate::CCSProgressDelegate(CCSModel *model, QWidget *parent) - : m_model(model) - , QStyledItemDelegate(parent) + : QStyledItemDelegate(parent) + , m_model(model) { - } void CCSProgressDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, diff --git a/src/widgets/nodewidget.cpp b/src/widgets/nodewidget.cpp index d082f93a..94b857c4 100644 --- a/src/widgets/nodewidget.cpp +++ b/src/widgets/nodewidget.cpp @@ -156,7 +156,7 @@ void NodeWidget::onCustomAddClicked(){ m_ctx->nodes->setCustomNodes(nodesList); } -void NodeWidget::setupUI(AppContext *ctx) { +void NodeWidget::setupUI(QSharedPointer ctx) { m_ctx = ctx; auto nodeSource = m_ctx->nodes->source(); diff --git a/src/widgets/nodewidget.h b/src/widgets/nodewidget.h index be987f43..e7fa10d6 100644 --- a/src/widgets/nodewidget.h +++ b/src/widgets/nodewidget.h @@ -25,7 +25,7 @@ public: ~NodeWidget(); void setWSModel(NodeModel *model); void setCustomModel(NodeModel *model); - void setupUI(AppContext *ctx); + void setupUI(QSharedPointer ctx); NodeModel* model(); public slots: @@ -44,7 +44,7 @@ signals: void nodeSourceChanged(NodeSource nodeSource); private: - AppContext *m_ctx; + QSharedPointer m_ctx; Ui::NodeWidget *ui; NodeModel* m_customModel; NodeModel* m_wsModel; diff --git a/src/widgets/restoreheightwidget.cpp b/src/widgets/restoreheightwidget.cpp deleted file mode 100644 index b0ecb2cb..00000000 --- a/src/widgets/restoreheightwidget.cpp +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include -#include -#include - -#include "restoreheightwidget.h" -#include "ui_restoreheightwidget.h" - -RestoreHeightWidget::RestoreHeightWidget(QWidget *parent) : - QWidget(parent), - ui(new Ui::RestoreHeightWidget) -{ - ui->setupUi(this); - ui->lineEdit_restoreHeight->setValidator(new QIntValidator(0, 2147483647, this)); - - connect(ui->lineEdit_restoreHeight, &QLineEdit::textEdited, [this](const QString &val){ - if (val.isEmpty()) return; - this->setHeight(val.toInt()); - }); -} - -void RestoreHeightWidget::hideSlider(){ - ui->restoreGrid->hide(); -} - -void RestoreHeightWidget::setHeight(int height) { - if (height < 0) - height = 0; - - // Update lineEdit - ui->lineEdit_restoreHeight->setText(QString::number(height)); - - // Update slider - int date = m_restoreHeightLookup->restoreHeightToDate(height); - ui->restoreSlider->setValue(date); - - this->updateTimestamp(date); -} - -void RestoreHeightWidget::initRestoreHeights(RestoreHeightLookup *lookup) { - // init slider - m_restoreHeightLookup = lookup; - int now = std::time(nullptr); - QList blockDates = m_restoreHeightLookup->data.keys(); - ui->restoreSlider->setMinimum(blockDates[0]); - ui->restoreSlider->setMaximum(now); - - connect(ui->restoreSlider, &QSlider::sliderMoved, [this](int date){ - // Update lineEdit - int blockHeight = m_restoreHeightLookup->dateToRestoreHeight(date); - ui->lineEdit_restoreHeight->setText(QString::number(blockHeight)); - - this->updateTimestamp(date); - }); -} - -void RestoreHeightWidget::updateTimestamp(int date) { - QDateTime timestamp; - timestamp.setTime_t(date); - ui->label_restoreHeightDate->setText(timestamp.toString("yyyy-MM-dd")); -} - -int RestoreHeightWidget::getHeight() { - return ui->lineEdit_restoreHeight->text().toInt(); -} - -RestoreHeightWidget::~RestoreHeightWidget() { - delete ui; -} diff --git a/src/widgets/restoreheightwidget.ui b/src/widgets/restoreheightwidget.ui deleted file mode 100644 index 00cb7d37..00000000 --- a/src/widgets/restoreheightwidget.ui +++ /dev/null @@ -1,112 +0,0 @@ - - - RestoreHeightWidget - - - - 0 - 0 - 360 - 89 - - - - Form - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - QFrame::NoFrame - - - QFrame::Plain - - - 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 2014-04-18 - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Restore height - - - - - - - Qt::Horizontal - - - - - - - - - - - - - - 1 - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - diff --git a/src/widgets/tickerwidget.cpp b/src/widgets/tickerwidget.cpp deleted file mode 100644 index 9fa98ca5..00000000 --- a/src/widgets/tickerwidget.cpp +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include "tickerwidget.h" -#include "ui_tickerwidget.h" - -#include "globals.h" -#include "utils/AppData.h" - -TickerWidget::TickerWidget(QWidget *parent, AppContext *ctx, QString symbol, QString title, bool convertBalance, bool hidePercent) : - QWidget(parent), - ui(new Ui::TickerWidget), - m_ctx(ctx), - m_symbol(std::move(symbol)), - m_convertBalance(convertBalance), - m_hidePercent(hidePercent) -{ - ui->setupUi(this); - - // default values before API data - if (title == "") title = m_symbol; - this->ui->tickerBox->setTitle(title); - QString defaultPct = "0.0"; - QString defaultFiat = "..."; - - this->setFontSizes(); - this->setPctText(defaultPct, true); - this->setFiatText(defaultFiat, 0.0); - - ui->tickerPct->setHidden(hidePercent); - - connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &TickerWidget::init); - connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &TickerWidget::init); - if (convertBalance) - connect(m_ctx, &AppContext::balanceUpdated, this, &TickerWidget::init); -} - -void TickerWidget::init() { - if(!appData()->prices.markets.count() || !appData()->prices.rates.count()) - return; - - QString fiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); - - if(!appData()->prices.rates.contains(fiatCurrency)){ - config()->set(Config::preferredFiatCurrency, "USD"); - return; - } - - double walletBalance = m_ctx->currentWallet ? (m_ctx->currentWallet->balance() / globals::cdiv) : 0; - - double amount = m_convertBalance ? walletBalance : 1.0; - double conversion = appData()->prices.convert(m_symbol, fiatCurrency, amount); - if (conversion < 0) return; - - auto markets = appData()->prices.markets; - if(!markets.contains(m_symbol)) return; - - bool hidePercent = (conversion == 0 || m_hidePercent); - if (hidePercent) { - ui->tickerPct->hide(); - } else { - auto pct24h = markets[m_symbol].price_usd_change_pct_24h; - auto pct24hText = QString::number(pct24h, 'f', 2); - this->setPctText(pct24hText, pct24h >= 0.0); - } - - this->setFiatText(fiatCurrency, conversion); -} - -void TickerWidget::setFiatText(QString &fiatCurrency, double amount) { - QString conversionText = Utils::amountToCurrencyString(amount, fiatCurrency); - ui->tickerFiat->setText(conversionText); -} - -void TickerWidget::setPctText(QString &text, bool positive) { - QString pctText = "

"; - if(positive) { - pctText = pctText.replace("red", "green"); - pctText += QString("+%1%").arg(text); - } else - pctText += QString("%1%").arg(text); - - pctText += "

"; - ui->tickerPct->setText(pctText); -} - -void TickerWidget::setFontSizes() { - ui->tickerPct->setFont(Utils::relativeFont(-2)); - ui->tickerFiat->setFont(Utils::relativeFont(0)); -} - -TickerWidget::~TickerWidget() { - delete ui; -} diff --git a/src/widgets/tickerwidget.h b/src/widgets/tickerwidget.h deleted file mode 100644 index e6464ebb..00000000 --- a/src/widgets/tickerwidget.h +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#ifndef TICKERWIDGET_H -#define TICKERWIDGET_H - -#include - -#include "appcontext.h" - -namespace Ui { - class TickerWidget; -} - -class TickerWidget : public QWidget -{ - Q_OBJECT - -public: - explicit TickerWidget(QWidget *parent, AppContext *ctx, QString symbol, QString title = "", bool convertBalance = false, bool hidePercent = false); - void setFiatText(QString &fiatCurrency, double amount); - void setPctText(QString &text, bool positive); - void setFontSizes(); - ~TickerWidget() override; - - -public slots: - void init(); - -private: - Ui::TickerWidget *ui; - QString m_symbol; - bool m_convertBalance; - bool m_hidePercent; - AppContext *m_ctx; -}; - -#endif // TICKERWIDGET_H diff --git a/src/widgets/xmrigwidget.cpp b/src/widgets/xmrigwidget.cpp index 77f53a96..aef8e7cc 100644 --- a/src/widgets/xmrigwidget.cpp +++ b/src/widgets/xmrigwidget.cpp @@ -12,10 +12,10 @@ #include "ui_xmrigwidget.h" #include "utils/Icons.h" -XMRigWidget::XMRigWidget(AppContext *ctx, QWidget *parent) +XMRigWidget::XMRigWidget(QSharedPointer ctx, QWidget *parent) : QWidget(parent) , ui(new Ui::XMRigWidget) - , m_ctx(ctx) + , m_ctx(std::move(ctx)) , m_XMRig(new XmRig(Config::defaultConfigDir().path())) , m_model(new QStandardItemModel(this)) , m_contextMenu(new QMenu(this)) @@ -29,9 +29,6 @@ XMRigWidget::XMRigWidget(AppContext *ctx, QWidget *parent) connect(m_XMRig, &XmRig::error, this, &XMRigWidget::onProcessError); connect(m_XMRig, &XmRig::hashrate, this, &XMRigWidget::onHashrate); - connect(m_ctx, &AppContext::walletClosed, this, &XMRigWidget::onWalletClosed); - connect(m_ctx, &AppContext::walletOpened, this, &XMRigWidget::onWalletOpened); - // table ui->tableView->setModel(this->m_model); m_contextMenu->addAction(icons()->icon("network.png"), "Download file", this, &XMRigWidget::linkClicked); @@ -92,16 +89,34 @@ XMRigWidget::XMRigWidget(AppContext *ctx, QWidget *parent) // username/password connect(ui->lineEdit_password, &QLineEdit::editingFinished, [=]() { - m_ctx->currentWallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); + m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); m_ctx->storeWallet(); }); connect(ui->lineEdit_address, &QLineEdit::editingFinished, [=]() { - m_ctx->currentWallet->setCacheAttribute("feather.xmrig_username", ui->lineEdit_address->text()); + m_ctx->wallet->setCacheAttribute("feather.xmrig_username", ui->lineEdit_address->text()); m_ctx->storeWallet(); }); // checkbox connects connect(ui->check_solo, &QCheckBox::stateChanged, this, &XMRigWidget::onSoloChecked); + + // Xmrig username + auto username = m_ctx->wallet->getCacheAttribute("feather.xmrig_username"); + if(!username.isEmpty()) + ui->lineEdit_address->setText(username); + + // Xmrig passwd + auto password = m_ctx->wallet->getCacheAttribute("feather.xmrig_password"); + if(!password.isEmpty()) { + ui->lineEdit_password->setText(password); + } else { + ui->lineEdit_password->setText("featherwallet"); + m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); + } +} + +bool XMRigWidget::isMining() { + return m_isMining; } void XMRigWidget::onWalletClosed() { @@ -111,22 +126,6 @@ void XMRigWidget::onWalletClosed() { ui->lineEdit_address->setText(""); } -void XMRigWidget::onWalletOpened(){ - // Xmrig username - auto username = m_ctx->currentWallet->getCacheAttribute("feather.xmrig_username"); - if(!username.isEmpty()) - ui->lineEdit_address->setText(username); - - // Xmrig passwd - auto password = m_ctx->currentWallet->getCacheAttribute("feather.xmrig_password"); - if(!password.isEmpty()) { - ui->lineEdit_password->setText(password); - } else { - ui->lineEdit_password->setText("featherwallet"); - m_ctx->currentWallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); - } -} - void XMRigWidget::onThreadsValueChanged(int threads) { m_threads = threads; ui->label_threads->setText(QString("CPU threads: %1").arg(m_threads)); @@ -154,8 +153,8 @@ void XMRigWidget::onStartClicked() { xmrigPath = config()->get(Config::xmrigPath).toString(); // username is receiving address usually - auto username = m_ctx->currentWallet->getCacheAttribute("feather.xmrig_username"); - auto password = m_ctx->currentWallet->getCacheAttribute("feather.xmrig_password"); + auto username = m_ctx->wallet->getCacheAttribute("feather.xmrig_username"); + auto password = m_ctx->wallet->getCacheAttribute("feather.xmrig_password"); if(username.isEmpty()) { QString err = "Please specify a receiving address on the Settings screen"; @@ -172,12 +171,13 @@ void XMRigWidget::onStartClicked() { if(address.contains("cryptonote.social") && !username.contains(".")) { // cryptonote social requires ., we'll just grab a few chars from primary addy - username = QString("%1.%2").arg(username, m_ctx->currentWallet->address(0, 0).mid(0, 6)); + username = QString("%1.%2").arg(username, m_ctx->wallet->address(0, 0).mid(0, 6)); } m_XMRig->start(xmrigPath, m_threads, address, username, password, ui->relayTor->isChecked(), ui->check_tls->isChecked()); ui->btn_start->setEnabled(false); ui->btn_stop->setEnabled(true); + m_isMining = true; emit miningStarted(); } @@ -186,6 +186,7 @@ void XMRigWidget::onStopClicked() { ui->btn_start->setEnabled(true); ui->btn_stop->setEnabled(false); ui->label_status->hide(); + m_isMining = false; emit miningEnded(); } @@ -205,6 +206,7 @@ void XMRigWidget::onProcessError(const QString &msg) { ui->btn_start->setEnabled(true); ui->btn_stop->setEnabled(false); ui->label_status->hide(); + m_isMining = false; emit miningEnded(); } diff --git a/src/widgets/xmrigwidget.h b/src/widgets/xmrigwidget.h index aa5d7e7d..fd800a47 100644 --- a/src/widgets/xmrigwidget.h +++ b/src/widgets/xmrigwidget.h @@ -21,13 +21,14 @@ class XMRigWidget : public QWidget Q_OBJECT public: - explicit XMRigWidget(AppContext *ctx, QWidget *parent = nullptr); + explicit XMRigWidget(QSharedPointer ctx, QWidget *parent = nullptr); ~XMRigWidget() override; QStandardItemModel *model(); + bool isMining(); + public slots: void onWalletClosed(); - void onWalletOpened(); void onStartClicked(); void onStopClicked(); void onClearClicked(); @@ -50,12 +51,13 @@ signals: private: void showContextMenu(const QPoint &pos); - AppContext *m_ctx; Ui::XMRigWidget *ui; + QSharedPointer m_ctx; + XmRig * m_XMRig; QStandardItemModel *m_model; QMenu *m_contextMenu; - XmRig * m_XMRig; + bool m_isMining = false; int m_threads; QStringList m_urls; QStringList m_pools{"pool.xmr.pt:9000", "pool.supportxmr.com:9000", "mine.xmrpool.net:443", "xmrpool.eu:9999", "xmr-eu1.nanopool.org:14433", "pool.minexmr.com:6666", "us-west.minexmr.com:6666", "monerohash.com:9999", "cryptonote.social:5555", "cryptonote.social:5556"}; diff --git a/src/wizard/PageHardwareDevice.cpp b/src/wizard/PageHardwareDevice.cpp index 03e0a413..6bdb48fd 100644 --- a/src/wizard/PageHardwareDevice.cpp +++ b/src/wizard/PageHardwareDevice.cpp @@ -7,10 +7,9 @@ #include -PageHardwareDevice::PageHardwareDevice(AppContext *ctx, WizardFields *fields, QWidget *parent) +PageHardwareDevice::PageHardwareDevice(WizardFields *fields, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageHardwareDevice) - , m_ctx(ctx) , m_fields(fields) { ui->setupUi(this); diff --git a/src/wizard/PageHardwareDevice.h b/src/wizard/PageHardwareDevice.h index 9267c354..689bd29c 100644 --- a/src/wizard/PageHardwareDevice.h +++ b/src/wizard/PageHardwareDevice.h @@ -21,14 +21,13 @@ class PageHardwareDevice : public QWizardPage Q_OBJECT public: - explicit PageHardwareDevice(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageHardwareDevice(WizardFields *fields, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; bool isComplete() const override; private: - AppContext *m_ctx; Ui::PageHardwareDevice *ui; WizardFields *m_fields; }; diff --git a/src/wizard/PageMenu.cpp b/src/wizard/PageMenu.cpp index 2ca886b3..da8c2896 100644 --- a/src/wizard/PageMenu.cpp +++ b/src/wizard/PageMenu.cpp @@ -7,10 +7,9 @@ #include -PageMenu::PageMenu(AppContext *ctx, WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent) +PageMenu::PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageMenu) - , m_ctx(ctx) , m_walletKeysFilesModel(wallets) , m_fields(fields) { diff --git a/src/wizard/PageMenu.h b/src/wizard/PageMenu.h index c755a98d..ba20662a 100644 --- a/src/wizard/PageMenu.h +++ b/src/wizard/PageMenu.h @@ -19,7 +19,7 @@ class PageMenu : public QWizardPage Q_OBJECT public: - explicit PageMenu(AppContext *ctx, WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent = nullptr); + explicit PageMenu(WizardFields *fields, WalletKeysFilesModel *wallets, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -28,9 +28,8 @@ signals: void enableDarkMode(bool enable); private: - AppContext *m_ctx; - WalletKeysFilesModel *m_walletKeysFilesModel; Ui::PageMenu *ui; + WalletKeysFilesModel *m_walletKeysFilesModel; WizardFields *m_fields; }; diff --git a/src/wizard/PageNetwork.cpp b/src/wizard/PageNetwork.cpp index f6d39ec3..b359c887 100644 --- a/src/wizard/PageNetwork.cpp +++ b/src/wizard/PageNetwork.cpp @@ -4,11 +4,11 @@ #include "PageNetwork.h" #include "ui_PageNetwork.h" #include "WalletWizard.h" +#include "constants.h" -PageNetwork::PageNetwork(AppContext *ctx, QWidget *parent) +PageNetwork::PageNetwork(QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageNetwork) - , m_ctx(ctx) { ui->setupUi(this); this->setTitle("Welcome to Feather"); @@ -35,10 +35,8 @@ bool PageNetwork::validatePage() { config()->set(Config::nodeSource, id); if (id == 1) { - QList nodes; - FeatherNode node{ui->line_customNode->text()}; - nodes.append(node); - m_ctx->nodes->setCustomNodes(nodes); + NodeList nodeList; + nodeList.addNode(ui->line_customNode->text(), constants::networkType, NodeList::Type::custom); } return true; diff --git a/src/wizard/PageNetwork.h b/src/wizard/PageNetwork.h index 502f3497..4e68bb9a 100644 --- a/src/wizard/PageNetwork.h +++ b/src/wizard/PageNetwork.h @@ -20,13 +20,12 @@ class PageNetwork : public QWizardPage Q_OBJECT public: - explicit PageNetwork(AppContext *ctx, QWidget *parent = nullptr); + explicit PageNetwork(QWidget *parent = nullptr); bool validatePage() override; int nextId() const override; bool isComplete() const override; private: - AppContext *m_ctx; Ui::PageNetwork *ui; }; diff --git a/src/wizard/PageNetworkTor.cpp b/src/wizard/PageNetworkTor.cpp index cade2ee7..e3649e4c 100644 --- a/src/wizard/PageNetworkTor.cpp +++ b/src/wizard/PageNetworkTor.cpp @@ -5,10 +5,9 @@ #include "ui_PageNetworkTor.h" #include "WalletWizard.h" -PageNetworkTor::PageNetworkTor(AppContext *ctx, QWidget *parent) +PageNetworkTor::PageNetworkTor(QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageNetworkTor) - , m_ctx(ctx) { ui->setupUi(this); diff --git a/src/wizard/PageNetworkTor.h b/src/wizard/PageNetworkTor.h index 9863b58b..5447f079 100644 --- a/src/wizard/PageNetworkTor.h +++ b/src/wizard/PageNetworkTor.h @@ -17,7 +17,7 @@ class PageNetworkTor : public QWizardPage Q_OBJECT public: - explicit PageNetworkTor(AppContext *ctx, QWidget *parent = nullptr); + explicit PageNetworkTor(QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -26,7 +26,6 @@ signals: void initialNetworkConfigured(); private: - AppContext *m_ctx; Ui::PageNetworkTor *ui; }; diff --git a/src/wizard/PageOpenWallet.cpp b/src/wizard/PageOpenWallet.cpp index cb76f278..4c0e1883 100644 --- a/src/wizard/PageOpenWallet.cpp +++ b/src/wizard/PageOpenWallet.cpp @@ -7,21 +7,21 @@ #include #include -PageOpenWallet::PageOpenWallet(AppContext *ctx, WalletKeysFilesModel *wallets, QWidget *parent) +#include "constants.h" +#include "WalletWizard.h" + +PageOpenWallet::PageOpenWallet(WalletKeysFilesModel *wallets, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageOpenWallet) - , m_ctx(ctx) , m_walletKeysFilesModel(wallets) { ui->setupUi(this); - connect(ui->btnBrowse, &QPushButton::clicked, [=]{ - // manually browsing for wallet - auto walletPath = config()->get(Config::walletPath).toString(); - if (walletPath.isEmpty()) - walletPath = m_ctx->defaultWalletDir; - QString path = QFileDialog::getOpenFileName(this, "Select your wallet file", walletPath, "Wallet file (*.keys)"); - if(path.isEmpty()) return; + connect(ui->btnBrowse, &QPushButton::clicked, [this]{ + QString walletDir = config()->get(Config::walletDirectory).toString(); + QString path = QFileDialog::getOpenFileName(this, "Select your wallet file", walletDir, "Wallet file (*.keys)"); + if (path.isEmpty()) + return; QFileInfo infoPath(path); if(!infoPath.isReadable()) { @@ -30,7 +30,7 @@ PageOpenWallet::PageOpenWallet(AppContext *ctx, WalletKeysFilesModel *wallets, Q } if (ui->openOnStartup->isChecked()) - config()->set(Config::autoOpenWalletPath, QString("%1%2").arg(m_ctx->networkType).arg(path)); + config()->set(Config::autoOpenWalletPath, QString("%1%2").arg(constants::networkType).arg(path)); emit openWallet(path); }); @@ -41,7 +41,7 @@ PageOpenWallet::PageOpenWallet(AppContext *ctx, WalletKeysFilesModel *wallets, Q ui->walletTable->setSelectionBehavior(QAbstractItemView::SelectRows); ui->walletTable->setContextMenuPolicy(Qt::CustomContextMenu); - m_keysProxy = new WalletKeysFilesProxyModel(this, m_ctx->networkType); + m_keysProxy = new WalletKeysFilesProxyModel(this, constants::networkType); m_keysProxy->setSourceModel(m_walletKeysFilesModel); m_keysProxy->setSortRole(Qt::UserRole); @@ -57,7 +57,13 @@ PageOpenWallet::PageOpenWallet(AppContext *ctx, WalletKeysFilesModel *wallets, Q connect(ui->walletTable->selectionModel(), &QItemSelectionModel::currentRowChanged, [this](QModelIndex current, QModelIndex prev){ this->updatePath(); }); - connect(ui->walletTable, &QTreeView::doubleClicked, this, &PageOpenWallet::validatePage); + connect(ui->walletTable, &QTreeView::doubleClicked, [this]{ + // Simulate next button click + QWizard *wizard = this->wizard(); + if (wizard) { + wizard->button(QWizard::FinishButton)->click(); + } + }); } void PageOpenWallet::initializePage() { @@ -85,9 +91,9 @@ bool PageOpenWallet::validatePage() { QMessageBox::warning(this, "Wallet not selected", "Please select a wallet from the list."); return false; } - QString walletPath = index.model()->data(index.siblingAtColumn(WalletKeysFilesModel::ModelColumns::Path), Qt::UserRole).toString(); + QString walletPath = index.model()->data(index.siblingAtColumn(WalletKeysFilesModel::Column::Path), Qt::UserRole).toString(); - auto autoWallet = ui->openOnStartup->isChecked() ? QString("%1%2").arg(m_ctx->networkType).arg(walletPath) : ""; + auto autoWallet = ui->openOnStartup->isChecked() ? QString("%1%2").arg(constants::networkType).arg(walletPath) : ""; config()->set(Config::autoOpenWalletPath, autoWallet); emit openWallet(walletPath); diff --git a/src/wizard/PageOpenWallet.h b/src/wizard/PageOpenWallet.h index 73309d6a..1385203d 100644 --- a/src/wizard/PageOpenWallet.h +++ b/src/wizard/PageOpenWallet.h @@ -9,7 +9,7 @@ #include #include "appcontext.h" -#include "utils/keysfiles.h" +#include "model/WalletKeysFilesModel.h" namespace Ui { class PageOpenWallet; @@ -20,7 +20,7 @@ class PageOpenWallet : public QWizardPage Q_OBJECT public: - explicit PageOpenWallet(AppContext *ctx, WalletKeysFilesModel *wallets, QWidget *parent = nullptr); + explicit PageOpenWallet(WalletKeysFilesModel *wallets, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -31,10 +31,9 @@ signals: private: void updatePath(); - AppContext *m_ctx; + Ui::PageOpenWallet *ui; WalletKeysFilesModel *m_walletKeysFilesModel; WalletKeysFilesProxyModel *m_keysProxy; - Ui::PageOpenWallet *ui; QStandardItemModel *m_model; }; diff --git a/src/wizard/PageSetPassword.cpp b/src/wizard/PageSetPassword.cpp index f4b6c93f..11366c4b 100644 --- a/src/wizard/PageSetPassword.cpp +++ b/src/wizard/PageSetPassword.cpp @@ -5,10 +5,9 @@ #include "ui_PageSetPassword.h" #include "WalletWizard.h" -PageSetPassword::PageSetPassword(AppContext *ctx, WizardFields *fields, QWidget *parent) +PageSetPassword::PageSetPassword(WizardFields *fields, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageSetPassword) - , m_ctx(ctx) , m_fields(fields) { ui->setupUi(this); diff --git a/src/wizard/PageSetPassword.h b/src/wizard/PageSetPassword.h index aa89c5c7..2ba604de 100644 --- a/src/wizard/PageSetPassword.h +++ b/src/wizard/PageSetPassword.h @@ -19,7 +19,7 @@ class PageSetPassword : public QWizardPage Q_OBJECT public: - explicit PageSetPassword(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageSetPassword(WizardFields *fields, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -29,9 +29,9 @@ signals: void createWallet(); private: - AppContext *m_ctx; - WizardFields *m_fields; Ui::PageSetPassword *ui; + + WizardFields *m_fields; }; diff --git a/src/wizard/PageSetRestoreHeight.cpp b/src/wizard/PageSetRestoreHeight.cpp index 12e00a01..7bd13fac 100644 --- a/src/wizard/PageSetRestoreHeight.cpp +++ b/src/wizard/PageSetRestoreHeight.cpp @@ -5,11 +5,11 @@ #include "PageSetRestoreHeight.h" #include "ui_PageSetRestoreHeight.h" #include "WalletWizard.h" +#include "constants.h" -PageSetRestoreHeight::PageSetRestoreHeight(AppContext *ctx, WizardFields *fields, QWidget *parent) +PageSetRestoreHeight::PageSetRestoreHeight(WizardFields *fields, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageSetRestoreHeight) - , m_ctx(ctx) , m_fields(fields) { ui->setupUi(this); @@ -61,7 +61,7 @@ void PageSetRestoreHeight::onCreationDateEdited() { QDateTime restoreDate = date > curDate ? curDate : date; int timestamp = restoreDate.toSecsSinceEpoch(); - QString restoreHeight = QString::number(appData()->restoreHeights[m_ctx->networkType]->dateToRestoreHeight(timestamp)); + QString restoreHeight = QString::number(appData()->restoreHeights[constants::networkType]->dateToHeight(timestamp)); ui->line_restoreHeight->setText(restoreHeight); this->showScanWarning(restoreDate); @@ -77,8 +77,7 @@ void PageSetRestoreHeight::onRestoreHeightEdited() { return; } - int timestamp = appData()->restoreHeights[m_ctx->networkType]->restoreHeightToDate(restoreHeight); - auto date = QDateTime::fromSecsSinceEpoch(timestamp); + QDateTime date = appData()->restoreHeights[constants::networkType]->heightToDate(restoreHeight); ui->line_creationDate->setText(date.toString("yyyy-MM-dd")); this->showScanWarning(date); @@ -86,7 +85,7 @@ void PageSetRestoreHeight::onRestoreHeightEdited() { } void PageSetRestoreHeight::showScanWarning(const QDateTime &date) { - QString dateString = date.toString("yyyy-MM-dd"); + QString dateString = date.toString("MMMM dd, yyyy"); ui->label_scanWarning->setText(QString("Wallet will not scan for transactions before %1").arg(dateString)); ui->frame_scanWarning->show(); } diff --git a/src/wizard/PageSetRestoreHeight.h b/src/wizard/PageSetRestoreHeight.h index 155406bb..6230a1e2 100644 --- a/src/wizard/PageSetRestoreHeight.h +++ b/src/wizard/PageSetRestoreHeight.h @@ -19,7 +19,7 @@ class PageSetRestoreHeight : public QWizardPage Q_OBJECT public: - explicit PageSetRestoreHeight(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageSetRestoreHeight(WizardFields *fields, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -33,9 +33,8 @@ private: void showScanWarning(const QDateTime &date); void showWalletAgeWarning(const QDateTime &date); - AppContext *m_ctx; - WizardFields *m_fields; Ui::PageSetRestoreHeight *ui; + WizardFields *m_fields; }; #endif //FEATHER_PAGESETRESTOREHEIGHT_H diff --git a/src/wizard/PageWalletFile.cpp b/src/wizard/PageWalletFile.cpp index cfde928b..4171ae45 100644 --- a/src/wizard/PageWalletFile.cpp +++ b/src/wizard/PageWalletFile.cpp @@ -10,10 +10,9 @@ #include #include -PageWalletFile::PageWalletFile(AppContext *ctx, WizardFields *fields, QWidget *parent) +PageWalletFile::PageWalletFile(WizardFields *fields, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageWalletFile) - , m_ctx(ctx) , m_fields(fields) { ui->setupUi(this); @@ -23,9 +22,9 @@ PageWalletFile::PageWalletFile(AppContext *ctx, WizardFields *fields, QWidget *p ui->lockIcon->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation)); connect(ui->btnChange, &QPushButton::clicked, [=] { - QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", m_ctx->defaultWalletDir, QFileDialog::ShowDirsOnly); + QString currentWalletDir = config()->get(Config::walletDirectory).toString(); + QString walletDir = QFileDialog::getExistingDirectory(this, "Select wallet directory ", currentWalletDir, QFileDialog::ShowDirsOnly); if(walletDir.isEmpty()) return; - m_ctx->defaultWalletDir = walletDir; ui->line_walletDir->setText(walletDir); config()->set(Config::walletDirectory, walletDir); emit defaultWalletDirChanged(walletDir); @@ -37,7 +36,7 @@ PageWalletFile::PageWalletFile(AppContext *ctx, WizardFields *fields, QWidget *p void PageWalletFile::initializePage() { this->setTitle(m_fields->modeText); - ui->line_walletDir->setText(m_ctx->defaultWalletDir); + ui->line_walletDir->setText(config()->get(Config::walletDirectory).toString()); ui->line_walletName->setText(this->defaultWalletName()); } diff --git a/src/wizard/PageWalletFile.h b/src/wizard/PageWalletFile.h index 3d2b495a..36e4f66e 100644 --- a/src/wizard/PageWalletFile.h +++ b/src/wizard/PageWalletFile.h @@ -20,7 +20,7 @@ class PageWalletFile : public QWizardPage Q_OBJECT public: - explicit PageWalletFile(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageWalletFile(WizardFields *fields, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -34,7 +34,6 @@ private: bool walletPathExists(const QString &walletName); bool validateWidgets(); - AppContext *m_ctx; Ui::PageWalletFile *ui; WizardFields *m_fields; bool m_validated; diff --git a/src/wizard/PageWalletRestoreKeys.cpp b/src/wizard/PageWalletRestoreKeys.cpp index 364213fa..390a3179 100644 --- a/src/wizard/PageWalletRestoreKeys.cpp +++ b/src/wizard/PageWalletRestoreKeys.cpp @@ -1,16 +1,17 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020-2021, The Monero Project. -#include "WalletWizard.h" #include "PageWalletRestoreKeys.h" #include "ui_PageWalletRestoreKeys.h" #include -PageWalletRestoreKeys::PageWalletRestoreKeys(AppContext *ctx, WizardFields *fields, QWidget *parent) +#include "WalletWizard.h" +#include "constants.h" + +PageWalletRestoreKeys::PageWalletRestoreKeys(WizardFields *fields, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageWalletRestoreKeys) - , m_ctx(ctx) , m_fields(fields) { ui->setupUi(this); @@ -25,9 +26,9 @@ PageWalletRestoreKeys::PageWalletRestoreKeys(AppContext *ctx, WizardFields *fiel QGuiApplication::restoreOverrideCursor(); #endif - if (m_ctx->networkType == NetworkType::Type::MAINNET) { + if (constants::networkType == NetworkType::Type::MAINNET) { ui->line_address->setPlaceholderText("4..."); - } else if (m_ctx->networkType == NetworkType::Type::STAGENET) { + } else if (constants::networkType == NetworkType::Type::STAGENET) { ui->line_address->setPlaceholderText("5..."); } } @@ -53,21 +54,21 @@ bool PageWalletRestoreKeys::validatePage() { QString viewkey = ui->line_viewkey->text().trimmed(); QString spendkey = ui->line_spendkey->text().trimmed(); - if(!m_ctx->walletManager->addressValid(address, m_ctx->networkType)){ + if(!WalletManager::addressValid(address, constants::networkType)){ ui->label_errorString->show(); ui->label_errorString->setText("Invalid address."); ui->line_address->setStyleSheet(errStyle); return false; } - if(!m_ctx->walletManager->keyValid(viewkey, address, true, m_ctx->networkType)) { + if(!WalletManager::keyValid(viewkey, address, true, constants::networkType)) { ui->label_errorString->show(); ui->label_errorString->setText("Invalid key."); ui->line_viewkey->setStyleSheet(errStyle); return false; } - if(!spendkey.isEmpty() && !m_ctx->walletManager->keyValid(spendkey, address, false, m_ctx->networkType)) { + if(!spendkey.isEmpty() && !WalletManager::keyValid(spendkey, address, false, constants::networkType)) { ui->label_errorString->show(); ui->label_errorString->setText("Invalid key."); ui->line_viewkey->setStyleSheet(errStyle); diff --git a/src/wizard/PageWalletRestoreKeys.h b/src/wizard/PageWalletRestoreKeys.h index ff20301f..9aeb0fa0 100644 --- a/src/wizard/PageWalletRestoreKeys.h +++ b/src/wizard/PageWalletRestoreKeys.h @@ -11,6 +11,7 @@ #include #include "appcontext.h" +#include "WalletWizard.h" namespace Ui { class PageWalletRestoreKeys; @@ -21,15 +22,14 @@ class PageWalletRestoreKeys : public QWizardPage Q_OBJECT public: - explicit PageWalletRestoreKeys(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageWalletRestoreKeys(WizardFields *fields, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; private: - AppContext *m_ctx; - WizardFields *m_fields; Ui::PageWalletRestoreKeys *ui; + WizardFields *m_fields; }; #endif diff --git a/src/wizard/PageWalletRestoreSeed.cpp b/src/wizard/PageWalletRestoreSeed.cpp index a7d44212..f9128560 100644 --- a/src/wizard/PageWalletRestoreSeed.cpp +++ b/src/wizard/PageWalletRestoreSeed.cpp @@ -10,12 +10,11 @@ #include // tevador 14 word #include "utils/FeatherSeed.h" -#include "globals.h" +#include "constants.h" -PageWalletRestoreSeed::PageWalletRestoreSeed(AppContext *ctx, WizardFields *fields, QWidget *parent) +PageWalletRestoreSeed::PageWalletRestoreSeed(WizardFields *fields, QWidget *parent) : QWizardPage(parent) , ui(new Ui::PageWalletRestoreSeed) - , m_ctx(ctx) , m_fields(fields) { ui->setupUi(this); @@ -110,7 +109,7 @@ bool PageWalletRestoreSeed::validatePage() { } } - auto _seed = FeatherSeed(m_ctx->networkType, QString::fromStdString(globals::coinName), m_ctx->seedLanguage, seedSplit); + auto _seed = FeatherSeed(constants::networkType, QString::fromStdString(constants::coinName), constants::seedLanguage, seedSplit); if (!_seed.errorString.isEmpty()) { QMessageBox::warning(this, "Invalid seed", QString("Invalid seed:\n\n%1").arg(_seed.errorString)); ui->seedEdit->setStyleSheet(errStyle); diff --git a/src/wizard/PageWalletRestoreSeed.h b/src/wizard/PageWalletRestoreSeed.h index d29c7b23..f2d89717 100644 --- a/src/wizard/PageWalletRestoreSeed.h +++ b/src/wizard/PageWalletRestoreSeed.h @@ -22,7 +22,7 @@ class PageWalletRestoreSeed : public QWizardPage Q_OBJECT public: - explicit PageWalletRestoreSeed(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageWalletRestoreSeed(WizardFields *fields, QWidget *parent = nullptr); bool validatePage() override; void initializePage() override; int nextId() const override; @@ -50,7 +50,6 @@ private: void onSeedTypeToggled(); - AppContext *m_ctx; Ui::PageWalletRestoreSeed *ui; WizardFields *m_fields; diff --git a/src/wizard/PageWalletSeed.cpp b/src/wizard/PageWalletSeed.cpp index 241c8c96..5030208a 100644 --- a/src/wizard/PageWalletSeed.cpp +++ b/src/wizard/PageWalletSeed.cpp @@ -4,13 +4,12 @@ #include "WalletWizard.h" #include "PageWalletSeed.h" #include "ui_PageWalletSeed.h" -#include "globals.h" +#include "constants.h" #include -PageWalletSeed::PageWalletSeed(AppContext *ctx, WizardFields *fields, QWidget *parent) +PageWalletSeed::PageWalletSeed(WizardFields *fields, QWidget *parent) : QWizardPage(parent) - , m_ctx(ctx) , ui(new Ui::PageWalletSeed) , m_fields(fields) { @@ -50,7 +49,7 @@ void PageWalletSeed::seedRoulette(int count) { void PageWalletSeed::generateSeed() { do { - FeatherSeed seed = FeatherSeed(m_ctx->networkType, QString::fromStdString(globals::coinName), m_ctx->seedLanguage); + FeatherSeed seed = FeatherSeed(constants::networkType, QString::fromStdString(constants::coinName), constants::seedLanguage); m_mnemonic = seed.mnemonic.join(" "); m_restoreHeight = seed.restoreHeight; } while (m_mnemonic.split(" ").length() != 14); // https://github.com/tevador/monero-seed/issues/2 diff --git a/src/wizard/PageWalletSeed.h b/src/wizard/PageWalletSeed.h index dcb4f8da..5ed406d3 100644 --- a/src/wizard/PageWalletSeed.h +++ b/src/wizard/PageWalletSeed.h @@ -9,7 +9,6 @@ #include #include "utils/utils.h" -#include "appcontext.h" namespace Ui { class PageWalletSeed; @@ -20,7 +19,7 @@ class PageWalletSeed : public QWizardPage Q_OBJECT public: - explicit PageWalletSeed(AppContext *ctx, WizardFields *fields, QWidget *parent = nullptr); + explicit PageWalletSeed(WizardFields *fields, QWidget *parent = nullptr); void initializePage() override; bool validatePage() override; int nextId() const override; @@ -36,7 +35,6 @@ signals: void createWallet(); private: - AppContext *m_ctx; Ui::PageWalletSeed *ui; WizardFields *m_fields; diff --git a/src/wizard/WalletWizard.cpp b/src/wizard/WalletWizard.cpp index 78198c5f..5501b6d9 100644 --- a/src/wizard/WalletWizard.cpp +++ b/src/wizard/WalletWizard.cpp @@ -15,29 +15,28 @@ #include "PageSetRestoreHeight.h" #include "PageHardwareDevice.h" #include "PageNetworkTor.h" -#include "globals.h" +#include "constants.h" #include #include #include -WalletWizard::WalletWizard(AppContext *ctx, WalletWizard::Page startPage, QWidget *parent) - : QWizard(parent) - , m_ctx(ctx) +WalletWizard::WalletWizard(QWidget *parent) + : QWizard(parent) { this->setWindowTitle("Welcome to Feather Wallet"); this->setWindowIcon(QIcon(":/assets/images/appicons/64x64.png")); - m_walletKeysFilesModel = new WalletKeysFilesModel(m_ctx, this); + m_walletKeysFilesModel = new WalletKeysFilesModel(this); m_walletKeysFilesModel->refresh(); - auto networkPage = new PageNetwork(m_ctx, this); - auto networkTorPage = new PageNetworkTor(m_ctx, this); - auto menuPage = new PageMenu(m_ctx, &m_wizardFields, m_walletKeysFilesModel, this); - auto openWalletPage = new PageOpenWallet(m_ctx, m_walletKeysFilesModel, this); - auto createWallet = new PageWalletFile(m_ctx, &m_wizardFields , this); - auto createWalletSeed = new PageWalletSeed(m_ctx, &m_wizardFields, this); - auto walletSetPasswordPage = new PageSetPassword(m_ctx, &m_wizardFields, this); + auto networkPage = new PageNetwork(this); + auto networkTorPage = new PageNetworkTor(this); + auto menuPage = new PageMenu(&m_wizardFields, m_walletKeysFilesModel, this); + auto openWalletPage = new PageOpenWallet(m_walletKeysFilesModel, this); + auto createWallet = new PageWalletFile(&m_wizardFields , this); + auto createWalletSeed = new PageWalletSeed(&m_wizardFields, this); + auto walletSetPasswordPage = new PageSetPassword(&m_wizardFields, this); setPage(Page_Menu, menuPage); setPage(Page_WalletFile, createWallet); setPage(Page_OpenWallet, openWalletPage); @@ -45,22 +44,18 @@ WalletWizard::WalletWizard(AppContext *ctx, WalletWizard::Page startPage, QWidge setPage(Page_SetPasswordPage, walletSetPasswordPage); setPage(Page_Network, networkPage); setPage(Page_NetworkTor, networkTorPage); - setPage(Page_WalletRestoreSeed, new PageWalletRestoreSeed(m_ctx, &m_wizardFields, this)); - setPage(Page_WalletRestoreKeys, new PageWalletRestoreKeys(m_ctx, &m_wizardFields, this)); - setPage(Page_SetRestoreHeight, new PageSetRestoreHeight(m_ctx, &m_wizardFields, this)); - setPage(Page_HardwareDevice, new PageHardwareDevice(m_ctx, &m_wizardFields, this)); + setPage(Page_WalletRestoreSeed, new PageWalletRestoreSeed(&m_wizardFields, this)); + setPage(Page_WalletRestoreKeys, new PageWalletRestoreKeys(&m_wizardFields, this)); + setPage(Page_SetRestoreHeight, new PageSetRestoreHeight(&m_wizardFields, this)); + setPage(Page_HardwareDevice, new PageHardwareDevice(&m_wizardFields, this)); - setStartId(startPage); + setStartId(Page_Menu); setButtonText(QWizard::CancelButton, "Close"); setPixmap(QWizard::WatermarkPixmap, QPixmap(":/assets/images/banners/3.png")); setWizardStyle(WizardStyle::ModernStyle); setOption(QWizard::NoBackButtonOnStartPage); - connect(this, &QWizard::rejected, [=]{ - return QApplication::exit(1); - }); - connect(networkTorPage, &PageNetworkTor::initialNetworkConfigured, [this](){ emit initialNetworkConfigured(); }); @@ -72,7 +67,9 @@ WalletWizard::WalletWizard(AppContext *ctx, WalletWizard::Page startPage, QWidge emit skinChanged("Native"); }); - connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::createWallet); + connect(walletSetPasswordPage, &PageSetPassword::createWallet, this, &WalletWizard::onCreateWallet); + + connect(createWallet, &PageWalletFile::defaultWalletDirChanged, [this](const QString &walletDir){ emit defaultWalletDirChanged(walletDir); }); @@ -82,12 +79,12 @@ WalletWizard::WalletWizard(AppContext *ctx, WalletWizard::Page startPage, QWidge }); } -void WalletWizard::createWallet() { +void WalletWizard::onCreateWallet() { auto walletPath = QString("%1/%2").arg(m_wizardFields.walletDir, m_wizardFields.walletName); int currentBlockHeight = 0; - if (appData()->heights.contains(m_ctx->networkType)) { - currentBlockHeight = appData()->heights[m_ctx->networkType]; + if (appData()->heights.contains(constants::networkType)) { + currentBlockHeight = appData()->heights[constants::networkType]; } if (m_wizardFields.mode == WizardMode::CreateWalletFromDevice) { @@ -96,23 +93,23 @@ void WalletWizard::createWallet() { restoreHeight = m_wizardFields.restoreHeight; } - m_ctx->createWalletFromDevice(walletPath, m_wizardFields.password, restoreHeight); + emit createWalletFromDevice(walletPath, m_wizardFields.password, restoreHeight); return; } if (m_wizardFields.mode == WizardMode::RestoreFromKeys) { - m_ctx->createWalletFromKeys(walletPath, - m_wizardFields.password, - m_wizardFields.address, - m_wizardFields.secretViewKey, - m_wizardFields.secretSpendKey, - m_wizardFields.restoreHeight); + emit createWalletFromKeys(walletPath, + m_wizardFields.password, + m_wizardFields.address, + m_wizardFields.secretViewKey, + m_wizardFields.secretSpendKey, + m_wizardFields.restoreHeight); return; } - auto seed = FeatherSeed(m_ctx->networkType, QString::fromStdString(globals::coinName), m_ctx->seedLanguage, m_wizardFields.seed.split(" ")); + auto seed = FeatherSeed(constants::networkType, QString::fromStdString(constants::coinName), constants::seedLanguage, m_wizardFields.seed.split(" ")); - if (m_wizardFields.mode == WizardMode::CreateWallet) { + if (m_wizardFields.mode == WizardMode::CreateWallet && currentBlockHeight > 0) { qInfo() << "New wallet, setting restore height to latest blockheight: " << currentBlockHeight; seed.setRestoreHeight(currentBlockHeight); } @@ -120,5 +117,5 @@ void WalletWizard::createWallet() { if (m_wizardFields.mode == WizardMode::RestoreFromSeed && m_wizardFields.seedType == SeedType::MONERO) seed.setRestoreHeight(m_wizardFields.restoreHeight); - m_ctx->createWallet(seed, walletPath, m_wizardFields.password, m_wizardFields.seedOffsetPassphrase); + emit createWallet(seed, walletPath, m_wizardFields.password, m_wizardFields.seedOffsetPassphrase); } diff --git a/src/wizard/WalletWizard.h b/src/wizard/WalletWizard.h index 19d0178a..2c2db39c 100644 --- a/src/wizard/WalletWizard.h +++ b/src/wizard/WalletWizard.h @@ -9,6 +9,7 @@ #include #include "appcontext.h" +#include "model/WalletKeysFilesModel.h" #include "utils/RestoreHeightLookup.h" #include "utils/config.h" @@ -54,7 +55,7 @@ public: Page_NetworkTor }; - explicit WalletWizard(AppContext *ctx, WalletWizard::Page startPage = WalletWizard::Page::Page_Menu, QWidget *parent = nullptr); + explicit WalletWizard(QWidget *parent = nullptr); signals: void initialNetworkConfigured(); @@ -62,12 +63,16 @@ signals: void openWallet(QString path, QString password); void defaultWalletDirChanged(QString walletDir); + void createWalletFromDevice(const QString &path, const QString &password, int restoreHeight); + void createWalletFromKeys(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight, bool deterministic = false); + void createWallet(FeatherSeed seed, const QString &path, const QString &password, const QString &seedOffset = ""); + +private slots: + void onCreateWallet(); + private: - AppContext *m_ctx; WalletKeysFilesModel *m_walletKeysFilesModel; WizardFields m_wizardFields; - - void createWallet(); }; #endif // WALLETWIZARD_H