Merge pull request 'Beta-7' (#359) from tobtoht/feather:fix_comp_warn into master

Reviewed-on: https://git.featherwallet.org/feather/feather/pulls/359
This commit is contained in:
tobtoht 2021-05-26 20:11:49 +00:00
commit 7f70fa908d
180 changed files with 3845 additions and 3147 deletions

View file

@ -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

View file

@ -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)

View file

@ -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 && \

View file

@ -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

View file

@ -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"

451
src/WindowManager.cpp Normal file
View file

@ -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 <QMessageBox>
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"
"<a>https://support.ledger.com/hc/en-us/articles/115005165269-Fix-connection-issues</a>";
#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);
}

79
src/WindowManager.h Normal file
View file

@ -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 <QObject>
#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<MainWindow*> m_windows;
WalletManager *m_walletManager;
WalletWizard *m_wizard = nullptr;
SplashDialog *m_splashDialog = nullptr;
QSystemTrayIcon *m_tray;
QMap<QString, QString> m_skins;
bool m_openWalletTriedOnce = false;
bool m_openingWallet = false;
};
#endif //FEATHER_WINDOWMANAGER_H

View file

@ -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;

View file

@ -45,8 +45,8 @@ private slots:
private:
QString getBuySellUrl(bool buy, const QString &currencyCode, const QString &countryCode="", const QString &paymentMethod="", const QString &amount = "", int page = 0);
QString m_baseUrl;
UtilsNetworking *m_network;
QString m_baseUrl;
};

View file

@ -5,7 +5,7 @@
#include <QMessageBox>
#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<QString, QString> 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<QString> &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<QString> &addresses, const QVector<quint64> &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<QString> &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 QVector<QStr
qDebug() << Q_FUNC_INFO;
for (auto &addr : address) {
if (addr == globals::donationAddress) {
if (addr == constants::donationAddress) {
this->donationSending = true;
}
}
@ -647,21 +350,21 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QStr
void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){
if (status) {
for (const auto &entry: txid) {
this->currentWallet->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
}

View file

@ -7,22 +7,15 @@
#include <QObject>
#include <QTimer>
#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> 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<QString, QString> txCache;
static void createConfigDirectory(const QString &dir);
QMap<QString, QString> 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<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &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<QString> &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<QString> &address, double amount);
void createTransactionCancelled(const QVector<QString> &address, quint64 amount);
void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &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

View file

@ -19,6 +19,7 @@
<file>assets/images/bitcoin.png</file>
<file>assets/images/camera_dark.png</file>
<file>assets/images/camera_white.png</file>
<file>assets/images/change_account.png</file>
<file>assets/images/clock1.png</file>
<file>assets/images/clock2.png</file>
<file>assets/images/clock3.png</file>

View file

@ -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-<current_year>, The Monero Project
All rights reserved.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -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<int>::of(&QComboBox::currentIndexChanged), [this]{this->convert(false);});
connect(ui->comboCalcTo, QOverload<int>::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<QString> cryptoKeys = appData()->prices.markets.keys();
QList<QString> 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<QString> marketsKeys = appData()->prices.markets.keys();
QList<QString> ratesKeys = appData()->prices.rates.keys();
if(marketsKeys.count() <= 0 || ratesKeys.count() <= 0) return;
QList<QString> cryptoKeys = appData()->prices.markets.keys();
QList<QString> 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;
}

View file

@ -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 <QWidget>
#include <QComboBox>
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

View file

@ -6,33 +6,16 @@
<rect>
<x>0</x>
<y>0</y>
<width>737</width>
<height>153</height>
<width>800</width>
<height>132</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>9</number>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
@ -40,6 +23,28 @@
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_configure">
<property name="text">
<string>Configure</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
@ -47,8 +52,6 @@
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<layout class="QGridLayout" name="gridLayout_2">
<property name="horizontalSpacing">
@ -118,8 +121,6 @@
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="labelWarning">
<property name="text">
@ -127,36 +128,6 @@
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>170</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
@ -167,72 +138,7 @@
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>lineFrom</sender>
<signal>textChanged(QString)</signal>
<receiver>CalcWidget</receiver>
<slot>fromChanged(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>119</x>
<y>77</y>
</hint>
<hint type="destinationlabel">
<x>427</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>lineTo</sender>
<signal>textChanged(QString)</signal>
<receiver>CalcWidget</receiver>
<slot>toChanged(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>463</x>
<y>86</y>
</hint>
<hint type="destinationlabel">
<x>640</x>
<y>-4</y>
</hint>
</hints>
</connection>
<connection>
<sender>comboCalcFrom</sender>
<signal>currentIndexChanged(QString)</signal>
<receiver>CalcWidget</receiver>
<slot>fromChanged(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>196</x>
<y>77</y>
</hint>
<hint type="destinationlabel">
<x>330</x>
<y>-9</y>
</hint>
</hints>
</connection>
<connection>
<sender>comboCalcTo</sender>
<signal>currentIndexChanged(QString)</signal>
<receiver>CalcWidget</receiver>
<slot>toComboChanged(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>574</x>
<y>82</y>
</hint>
<hint type="destinationlabel">
<x>265</x>
<y>-5</y>
</hint>
</hints>
</connection>
</connections>
<connections/>
<slots>
<slot>fromChanged(QString)</slot>
<slot>toChanged(QString)</slot>

View file

@ -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) {

View file

@ -21,10 +21,6 @@ public:
signals:
void closed();
public slots:
void initFiat();
void initCrypto();
private:
void closeEvent(QCloseEvent *bar) override;

View file

@ -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;
}
else if (mode == CLIMode::BruteforcePassword)
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 == 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> 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();
}

View file

@ -7,37 +7,28 @@
#include <QtCore>
#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

View file

@ -11,14 +11,14 @@
#include <QClipboard>
#include <QMessageBox>
CoinsWidget::CoinsWidget(QWidget *parent)
CoinsWidget::CoinsWidget(QSharedPointer<AppContext> 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<int>& 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<int> &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();
}

View file

@ -22,13 +22,10 @@ class CoinsWidget : public QWidget
Q_OBJECT
public:
explicit CoinsWidget(QWidget *parent = nullptr);
explicit CoinsWidget(QSharedPointer<AppContext> 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<AppContext> 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);

View file

@ -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 <QtGlobal>
#include <QUrl>
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

View file

@ -11,12 +11,22 @@
#include <QMessageBox>
ContactsWidget::ContactsWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ContactsWidget)
ContactsWidget::ContactsWidget(QSharedPointer<AppContext> 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; i<num_addresses; i++) {
m_ctx->currentWallet->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()

View file

@ -20,10 +20,12 @@ class ContactsWidget : public QWidget
Q_OBJECT
public:
explicit ContactsWidget(QWidget *parent = nullptr);
void setModel(AddressBookModel * model);
explicit ContactsWidget(QSharedPointer<AppContext> 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<AppContext> m_ctx;
QAction *m_showFullAddressesAction;
QMenu *m_rowMenu;

View file

@ -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 <QMenu>
AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer<AppContext> 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;
}

View file

@ -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 <QDialog>
#include "appcontext.h"
#include "model/SubaddressAccountModel.h"
namespace Ui {
class AccountSwitcherDialog;
}
class AccountSwitcherDialog : public QDialog
{
Q_OBJECT
public:
explicit AccountSwitcherDialog(QSharedPointer<AppContext> 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<AppContext> m_ctx;
SubaddressAccountModel *m_model;
SubaddressAccountProxyModel *m_proxyModel;
};
#endif //FEATHER_ACCOUNTSWITCHERDIALOG_H

View file

@ -0,0 +1,115 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AccountSwitcherDialog</class>
<widget class="QDialog" name="AccountSwitcherDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>521</width>
<height>347</height>
</rect>
</property>
<property name="windowTitle">
<string>Accounts</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeView" name="accounts">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<attribute name="headerStretchLastSection">
<bool>false</bool>
</attribute>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Total balance:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_totalBalance">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>TextLabel</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_newAccount">
<property name="text">
<string>New account</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>AccountSwitcherDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>AccountSwitcherDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -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;
}

View file

@ -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 <QDialog>
#include <QListWidget>
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

View file

@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CalcConfigDialog</class>
<widget class="QDialog" name="CalcConfigDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>509</width>
<height>574</height>
</rect>
</property>
<property name="windowTitle">
<string>Calc config</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Fiat</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="list_fiat"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Crypto</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QListWidget" name="list_crypto"/>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_selectAll">
<property name="text">
<string>Select all</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_deselectAll">
<property name="text">
<string>Deselect all</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>CalcConfigDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>CalcConfigDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "RestoreHeightDialog.h"
#include <QLabel>
#include <QVBoxLayout>
#include <QDialogButtonBox>
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();
}

View file

@ -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 <QDialog>
#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

View file

@ -8,16 +8,17 @@
#include "libwalletqt/Transfer.h"
#include "utils/utils.h"
#include "utils/Icons.h"
TxProofDialog::TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *txInfo)
TxProofDialog::TxProofDialog(QWidget *parent, QSharedPointer<AppContext> ctx, TransactionInfo *txInfo)
: QDialog(parent)
, ui(new Ui::TxProofDialog)
, m_wallet(wallet)
, 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);
}
}
}();

View file

@ -6,8 +6,8 @@
#include <QDialog>
#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<AppContext> 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<AppContext> m_ctx;
};
#endif //FEATHER_TXPROOFDIALOG_H

View file

@ -30,7 +30,7 @@
<item>
<widget class="QRadioButton" name="radio_OutProof">
<property name="text">
<string>Prove a payment to an address (OutProof)</string>
<string>Prove payment to an address (OutProof)</string>
</property>
</widget>
</item>
@ -226,7 +226,31 @@
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_address"/>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QComboBox" name="combo_address">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_copyAddress">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View file

@ -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

View file

@ -7,81 +7,81 @@
#include <QRadioButton>
WalletCacheDebugDialog::WalletCacheDebugDialog(AppContext *ctx, QWidget *parent)
WalletCacheDebugDialog::WalletCacheDebugDialog(QSharedPointer<AppContext> 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();

View file

@ -16,13 +16,13 @@ class WalletCacheDebugDialog : public QDialog
Q_OBJECT
public:
explicit WalletCacheDebugDialog(AppContext *ctx, QWidget *parent = nullptr);
explicit WalletCacheDebugDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
~WalletCacheDebugDialog() override;
private:
void setOutput(const QString &output);
Ui::WalletCacheDebugDialog *ui;
AppContext *m_ctx;
QSharedPointer<AppContext> m_ctx;
};

View file

@ -21,8 +21,8 @@ public:
~AboutDialog() override;
private:
QStringListModel *m_model;
Ui::AboutDialog *ui;
QStringListModel *m_model;
};
#endif // ABOUT_H

View file

@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Balance</string>
<string>Account Balance</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>

View file

@ -7,14 +7,14 @@
#include <QMessageBox>
BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx, const QString &transactionHex)
BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, QSharedPointer<AppContext> 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);
}

View file

@ -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<AppContext> ctx, const QString &transactionHex = "");
~BroadcastTxDialog() override;
private slots:
@ -26,8 +26,8 @@ private slots:
private:
Ui::BroadcastTxDialog *ui;
QSharedPointer<AppContext> m_ctx;
UtilsNetworking *m_network;
AppContext *m_ctx;
DaemonRpc *m_rpc;
};

View file

@ -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<AppContext> 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);

View file

@ -17,7 +17,7 @@ class DebugInfoDialog : public QDialog
Q_OBJECT
public:
explicit DebugInfoDialog(AppContext *ctx, QWidget *parent = nullptr);
explicit DebugInfoDialog(QSharedPointer<AppContext> 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<AppContext> m_ctx;
QTimer m_updateTimer;
};
#endif //FEATHER_DEBUGINFODIALOG_H

View file

@ -4,21 +4,20 @@
#include "keysdialog.h"
#include "ui_keysdialog.h"
KeysDialog::KeysDialog(AppContext *ctx, QWidget *parent)
KeysDialog::KeysDialog(QSharedPointer<AppContext> 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();
}

View file

@ -16,7 +16,7 @@ class KeysDialog : public QDialog
Q_OBJECT
public:
explicit KeysDialog(AppContext *ctx, QWidget *parent = nullptr);
explicit KeysDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
~KeysDialog() override;
private:

View file

@ -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);

View file

@ -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 = "";

View file

@ -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;
}

View file

@ -1,39 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef RESTOREDIALOG_H
#define RESTOREDIALOG_H
#include <QPushButton>
#include <QDialogButtonBox>
#include <QDialog>
#include <QStandardItemModel>
#include <QAbstractButton>
#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

View file

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RestoreDialog</class>
<widget class="QDialog" name="RestoreDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>584</width>
<height>99</height>
</rect>
</property>
<property name="windowTitle">
<string>specify restore height</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="RestoreHeightWidget" name="restoreHeightWidget" native="true"/>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>RestoreHeightWidget</class>
<extends>QWidget</extends>
<header>widgets/restoreheightwidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -3,19 +3,26 @@
#include "ui_seeddialog.h"
#include "seeddialog.h"
#include "constants.h"
SeedDialog::SeedDialog(Wallet *wallet, QWidget *parent)
SeedDialog::SeedDialog(QSharedPointer<AppContext> 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();

View file

@ -5,7 +5,7 @@
#define FEATHER_SEEDDIALOG_H
#include <QDialog>
#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<AppContext> ctx, QWidget *parent = nullptr);
~SeedDialog() override;
private:
void setSeed(const QString &seed);
Ui::SeedDialog *ui;
QSharedPointer<AppContext> m_ctx;
};

View file

@ -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);

View file

@ -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));

View file

@ -7,13 +7,16 @@
#include <QPushButton>
#include <QDesktopServices>
#include <QMessageBox>
#include <QInputDialog>
#include "utils/TorManager.h"
#include "utils/os/tails.h"
#include "utils/Icons.h"
TorInfoDialog::TorInfoDialog(QWidget *parent, AppContext *ctx)
TorInfoDialog::TorInfoDialog(QSharedPointer<AppContext> 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;
}

View file

@ -18,7 +18,7 @@ class TorInfoDialog : public QDialog
Q_OBJECT
public:
explicit TorInfoDialog(QWidget *parent, AppContext *ctx);
explicit TorInfoDialog(QSharedPointer<AppContext> 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<AppContext> m_ctx;
};

View file

@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Tor information</string>
<string>Tor settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
@ -157,6 +157,19 @@
</attribute>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_configureInitSync">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
<item>

View file

@ -17,10 +17,10 @@
#include <QMessageBox>
#include <QScrollBar>
TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *txInfo, QWidget *parent)
TransactionInfoDialog::TransactionInfoDialog(QSharedPointer<AppContext> ctx, TransactionInfo *txInfo, QWidget *parent)
: QDialog(parent)
, ui(new Ui::TransactionInfoDialog)
, m_wallet(wallet)
, m_ctx(std::move(ctx))
, m_txInfo(txInfo)
{
ui->setupUi(this);
@ -28,7 +28,7 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
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);
}

View file

@ -7,9 +7,7 @@
#include <QDialog>
#include <QTextCharFormat>
#include <QtSvg/QSvgWidget>
#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<AppContext> ctx, TransactionInfo *txInfo, QWidget *parent = nullptr);
~TransactionInfoDialog() override;
signals:
@ -35,9 +33,9 @@ private:
Ui::TransactionInfoDialog *ui;
TxProofDialog *m_txProofDialog;
QSharedPointer<AppContext> m_ctx;
TransactionInfo *m_txInfo;
Wallet *m_wallet;
TxProofDialog *m_txProofDialog;
QString m_txKey;
QString m_txid;
QTimer m_updateTimer;

View file

@ -12,10 +12,10 @@
#include <QFileDialog>
#include <QMessageBox>
TxConfAdvDialog::TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent)
TxConfAdvDialog::TxConfAdvDialog(QSharedPointer<AppContext> ctx, const QString &description, QWidget *parent)
: QDialog(parent)
, ui(new Ui::TxConfAdvDialog)
, m_ctx(ctx)
, m_ctx(std::move(ctx))
, m_exportUnsignedMenu(new QMenu(this))
, m_exportSignedMenu(new QMenu(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();
}

View file

@ -22,7 +22,7 @@ class TxConfAdvDialog : public QDialog
Q_OBJECT
public:
explicit TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent = nullptr);
explicit TxConfAdvDialog(QSharedPointer<AppContext> 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<AppContext> m_ctx;
PendingTransaction *m_tx = nullptr;
UnsignedTransaction *m_utx = nullptr;
QMenu *m_exportUnsignedMenu;

View file

@ -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 <QMessageBox>
TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent)
TxConfDialog::TxConfDialog(QSharedPointer<AppContext> 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<QString> 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();
}

View file

@ -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<AppContext> 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<AppContext> m_ctx;
PendingTransaction *m_tx;
QString m_address;
QString m_description;

View file

@ -7,17 +7,17 @@
#include <QMessageBox>
TxImportDialog::TxImportDialog(QWidget *parent, AppContext *ctx)
TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer<AppContext> 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.");

View file

@ -17,7 +17,7 @@ class TxImportDialog : public QDialog
Q_OBJECT
public:
explicit TxImportDialog(QWidget *parent, AppContext *ctx);
explicit TxImportDialog(QWidget *parent, QSharedPointer<AppContext> ctx);
~TxImportDialog() override;
private slots:
@ -27,8 +27,9 @@ private slots:
private:
Ui::TxImportDialog *ui;
QSharedPointer<AppContext> m_ctx;
UtilsNetworking *m_network;
AppContext *m_ctx;
DaemonRpc *m_rpc;
QTimer *m_loadTimer;

View file

@ -6,13 +6,14 @@
#include "libwalletqt/WalletManager.h"
#include "utils/utils.h"
#include "model/ModelUtils.h"
#include <QMessageBox>
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()

View file

@ -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

View file

@ -8,20 +8,21 @@
#include "viewonlydialog.h"
#include "ui_viewonlydialog.h"
ViewOnlyDialog::ViewOnlyDialog(AppContext *ctx, QWidget *parent)
ViewOnlyDialog::ViewOnlyDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent)
, ui(new Ui::ViewOnlyDialog), m_ctx(ctx)
, 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.");
}

View file

@ -16,7 +16,7 @@ class ViewOnlyDialog : public QDialog
Q_OBJECT
public:
explicit ViewOnlyDialog(AppContext *ctx, QWidget *parent = nullptr);
explicit ViewOnlyDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
~ViewOnlyDialog() override;
private slots:
@ -24,7 +24,7 @@ private slots:
private:
Ui::ViewOnlyDialog *ui;
AppContext *m_ctx = nullptr;
QSharedPointer<AppContext> m_ctx;
void copyToClipboad();
};

View file

@ -6,21 +6,20 @@
#include <QDesktopServices>
WalletInfoDialog::WalletInfoDialog(AppContext *ctx, QWidget *parent)
WalletInfoDialog::WalletInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent)
: QDialog(parent)
, ui(new Ui::WalletInfoDialog)
, m_ctx(ctx)
, 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));
}

View file

@ -17,14 +17,14 @@ class WalletInfoDialog : public QDialog
Q_OBJECT
public:
explicit WalletInfoDialog(AppContext *ctx, QWidget *parent = nullptr);
explicit WalletInfoDialog(QSharedPointer<AppContext> ctx, QWidget *parent = nullptr);
~WalletInfoDialog() override;
private:
void openWalletDir();
Ui::WalletInfoDialog *ui;
AppContext *m_ctx;
QSharedPointer<AppContext> m_ctx;
};
#endif //FEATHER_WALLETINFODIALOG_H

View file

@ -11,11 +11,13 @@
#include <QMessageBox>
HistoryWidget::HistoryWidget(QWidget *parent)
HistoryWidget::HistoryWidget(QSharedPointer<AppContext> 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();
}

View file

@ -8,6 +8,7 @@
#include "model/TransactionHistoryProxyModel.h"
#include "libwalletqt/Coins.h"
#include "libwalletqt/Wallet.h"
#include "appcontext.h"
#include <QWidget>
#include <QMenu>
@ -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<AppContext> 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<AppContext> m_ctx;
QMenu *m_contextMenu;
QMenu *m_copyMenu;
TransactionHistory *m_txHistory;
TransactionHistoryProxyModel *m_model;
Wallet *m_wallet = nullptr;
};
#endif //FEATHER_HISTORYWIDGET_H

View file

@ -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);

View file

@ -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 {

View file

@ -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));
}
}

View file

@ -5,7 +5,9 @@
#include <QDebug>
Subaddress::Subaddress(Monero::Subaddress *subaddressImpl, QObject *parent)
: QObject(parent), m_subaddressImpl(subaddressImpl), m_unusedLookahead(0)
: QObject(parent)
, m_subaddressImpl(subaddressImpl)
, m_unusedLookahead(0)
{
getAll();
}

View file

@ -62,3 +62,8 @@ quint64 SubaddressAccount::count() const
return m_rows.size();
}
Monero::SubaddressAccountRow* SubaddressAccount::row(int index) const
{
return m_rows.value(index);
}

View file

@ -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;

View file

@ -104,7 +104,6 @@ void TransactionHistory::refresh(quint32 accountIndex)
void TransactionHistory::setTxNote(const QString &txid, const QString &note)
{
m_pimpl->setTxNote(txid.toStdString(), note.toStdString());
this->refresh(0); // todo: get actual account index
emit txNoteChanged();
}

View file

@ -60,6 +60,7 @@ private:
// history contains locked transfers
mutable bool m_locked;
quint32 lastAccountIndex = 0;
};
#endif // TRANSACTIONHISTORY_H

View file

@ -3,6 +3,9 @@
#include "Wallet.h"
#include <chrono>
#include <thread>
#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");

View file

@ -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,

View file

@ -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<Monero::NetworkType>(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<Monero::NetworkType>(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<Monero::NetworkType>(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<Monero::NetworkType>(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<Monero::NetworkType>(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<Monero::NetworkType>(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<Monero::NetworkType>(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<Monero::NetworkType>(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<QString> &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();
}

View file

@ -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<QString> &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<Wallet> m_currentWallet;
PassphraseReceiver * m_passphraseReceiver;
PassphraseReceiver *m_passphraseReceiver;
QMutex m_mutex_passphraseReceiver;
QString m_proxyAddress;
mutable QMutex m_proxyMutex;

View file

@ -7,8 +7,10 @@
#include <QtGui>
#include "config-feather.h"
#include "constants.h"
#include "mainwindow.h"
#include "cli.h"
#include "WindowManager.h"
#if defined(Q_OS_WIN)
#include <windows.h>
@ -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<QString, QString> 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<QVector<QString>>();
auto *mainWindow = new MainWindow(ctx);
WindowManager windowManager;
return QApplication::exec();
}

File diff suppressed because it is too large Load diff

View file

@ -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<QString> &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<QString> &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<AppContext> 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<TickerWidget*> m_tickerWidgets;
TickerWidget *m_balanceWidget;
QList<PriceTickerWidget*> 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<QAction *> m_touchbarWalletItems;
QList<QAction *> m_touchbarWizardItems;
#endif
QSignalMapper *m_tabShowHideSignalMapper;
QMap<QString, ToggleTab*> m_tabShowHideMapper;
WalletWizard *m_wizard = nullptr;
QMap<QString, QString> 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

View file

@ -161,8 +161,8 @@
<property name="verticalSpacing">
<number>9</number>
</property>
<item row="1" column="0">
<widget class="HistoryWidget" name="historyWidget" native="true"/>
<item row="0" column="0">
<layout class="QVBoxLayout" name="historyWidgetLayout"/>
</item>
</layout>
</widget>
@ -188,29 +188,11 @@
<number>11</number>
</property>
<item>
<widget class="SendWidget" name="sendWidget" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<layout class="QVBoxLayout" name="sendWidgetLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
</layout>
</item>
<item>
<widget class="Line" name="line_3">
@ -226,7 +208,7 @@
</widget>
</item>
<item>
<widget class="ContactsWidget" name="contactWidget" native="true"/>
<layout class="QVBoxLayout" name="contactsWidgetLayout"/>
</item>
</layout>
</widget>
@ -240,7 +222,7 @@
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="ReceiveWidget" name="receiveWidget" native="true"/>
<layout class="QVBoxLayout" name="receiveWidgetLayout"/>
</item>
</layout>
</widget>
@ -254,7 +236,7 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="CoinsWidget" name="coinsWidget" native="true"/>
<layout class="QVBoxLayout" name="coinsWidgetLayout"/>
</item>
</layout>
</widget>
@ -266,11 +248,28 @@
<attribute name="title">
<string>Calc</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="CalcWidget" name="conversionWidget" native="true"/>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabExchange">
<attribute name="icon">
@ -360,6 +359,14 @@
<property name="title">
<string>File</string>
</property>
<widget class="QMenu" name="menuRecently_open">
<property name="title">
<string>Recently open</string>
</property>
</widget>
<addaction name="menuRecently_open"/>
<addaction name="actionOpen"/>
<addaction name="actionNew_Restore"/>
<addaction name="actionClose"/>
<addaction name="actionQuit"/>
<addaction name="separator"/>
@ -413,6 +420,7 @@
<addaction name="actionInformation"/>
<addaction name="menuAdvanced"/>
<addaction name="separator"/>
<addaction name="actionAccount"/>
<addaction name="actionPassword"/>
<addaction name="actionSeed"/>
<addaction name="actionKeys"/>
@ -470,6 +478,8 @@
<addaction name="actionShow_calc"/>
<addaction name="actionShow_Exchange"/>
<addaction name="actionShow_XMRig"/>
<addaction name="separator"/>
<addaction name="actionShow_Searchbar"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuWallet"/>
@ -559,7 +569,7 @@
</action>
<action name="actionClose">
<property name="text">
<string>Close current wallet</string>
<string>Close wallet</string>
</property>
</action>
<action name="actionVerifyTxProof">
@ -732,38 +742,64 @@
<string>Pay to many</string>
</property>
</action>
<action name="actionOpen">
<property name="text">
<string>Open wallet</string>
</property>
</action>
<action name="actionNew_Restore">
<property name="text">
<string>New/Restore</string>
</property>
</action>
<action name="actionRecently_open">
<property name="text">
<string>Recently open</string>
</property>
</action>
<action name="actiont">
<property name="text">
<string>t</string>
</property>
</action>
<action name="actiond">
<property name="text">
<string>d</string>
</property>
</action>
<action name="actionPrimary_account">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Primary account</string>
</property>
</action>
<action name="actionAccount">
<property name="text">
<string>Account</string>
</property>
</action>
<action name="actionShow_Searchbar">
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="text">
<string>Show Searchbar</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>SendWidget</class>
<extends>QWidget</extends>
<header>sendwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ContactsWidget</class>
<extends>QWidget</extends>
<header>contactswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ReceiveWidget</class>
<extends>QWidget</extends>
<header>receivewidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>CoinsWidget</class>
<extends>QWidget</extends>
<header>coinswidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>HistoryWidget</class>
<extends>QWidget</extends>
<header>historywidget.h</header>
</customwidget>
<customwidget>
<class>CalcWidget</class>
<extends>QWidget</extends>

View file

@ -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++) {

View file

@ -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);

View file

@ -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 <QBrush>
@ -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();

View file

@ -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)

View file

@ -32,9 +32,9 @@ public:
private:
QList<FeatherNode> m_nodes;
int m_nodeSource;
QIcon m_offline;
QIcon m_online;
int m_nodeSource;
};
#endif //FEATHER_NODEMODEL_H

View file

@ -5,9 +5,12 @@
#include "SubaddressAccount.h"
#include <QDebug>
#include <QFont>
#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
{
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<int, QByteArray> SubaddressAccountModel::roleNames() const
QVariant SubaddressAccountModel::parseSubaddressAccountRow(const Monero::SubaddressAccountRow &row,
const QModelIndex &index, int role) const
{
static QHash<int, QByteArray> 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)
{
}

View file

@ -4,36 +4,54 @@
#ifndef SUBADDRESSACCOUNTMODEL_H
#define SUBADDRESSACCOUNTMODEL_H
#include <QAbstractListModel>
#include "Subaddress.h"
#include <QAbstractTableModel>
#include <QSortFilterProxyModel>
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<int, QByteArray> 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

View file

@ -11,9 +11,9 @@
#include <QBrush>
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());

Some files were not shown because too many files have changed in this diff Show more