From 3767e736c22e1ba1efc908c5c17d7262a4a46e0d Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 1 Oct 2021 15:37:20 +0200 Subject: [PATCH 01/56] Coins: freeze/thaw based on pubkey --- CMakeLists.txt | 2 +- src/CoinsWidget.cpp | 52 +++++++++++++++------------------------ src/CoinsWidget.h | 7 +++--- src/libwalletqt/Coins.cpp | 8 +++--- src/libwalletqt/Coins.h | 4 +-- 5 files changed, 30 insertions(+), 43 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e6f30be6..b5d465b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ if(DEBUG) set(CMAKE_VERBOSE_MAKEFILE ON) endif() -set(MONERO_HEAD "d4257af2e7503fc6dc09fc704606230d353a0a02") +set(MONERO_HEAD "ed8859318349a5f01e9fd90e898603d4142b0951") set(BUILD_GUI_DEPS ON) option(ARCH "Target architecture" "x86-64") set(BUILD_64 ON) diff --git a/src/CoinsWidget.cpp b/src/CoinsWidget.cpp index 2a972943..767c4bc5 100644 --- a/src/CoinsWidget.cpp +++ b/src/CoinsWidget.cpp @@ -51,8 +51,8 @@ CoinsWidget::CoinsWidget(QSharedPointer ctx, QWidget *parent) m_sweepOutputAction = new QAction("Sweep output", this); m_sweepOutputsAction = new QAction("Sweep selected outputs", this); - connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeOutput); - connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawOutput); + connect(m_freezeOutputAction, &QAction::triggered, this, &CoinsWidget::freezeAllSelected); + connect(m_thawOutputAction, &QAction::triggered, this, &CoinsWidget::thawAllSelected); connect(m_viewOutputAction, &QAction::triggered, this, &CoinsWidget::viewOutput); connect(m_sweepOutputAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs); connect(m_sweepOutputsAction, &QAction::triggered, this, &CoinsWidget::onSweepOutputs); @@ -155,36 +155,24 @@ void CoinsWidget::setSearchFilter(const QString &filter) { m_proxyModel->setSearchFilter(filter); } -void CoinsWidget::freezeOutput() { - QModelIndex index = ui->coins->currentIndex(); - QVector indexes = {m_proxyModel->mapToSource(index).row()}; - this->freezeCoins(indexes); +QStringList CoinsWidget::selectedPubkeys() { + QModelIndexList list = ui->coins->selectionModel()->selectedRows(); + + QStringList pubkeys; + for (QModelIndex index: list) { + pubkeys << m_model->entryFromIndex(m_proxyModel->mapToSource(index))->pubKey(); + } + return pubkeys; } void CoinsWidget::freezeAllSelected() { - QModelIndexList list = ui->coins->selectionModel()->selectedRows(); - - QVector indexes; - for (QModelIndex index: list) { - indexes.push_back(m_proxyModel->mapToSource(index).row()); // todo: will segfault if index get invalidated - } - this->freezeCoins(indexes); -} - -void CoinsWidget::thawOutput() { - QModelIndex index = ui->coins->currentIndex(); - QVector indexes = {m_proxyModel->mapToSource(index).row()}; - this->thawCoins(indexes); + QStringList pubkeys = this->selectedPubkeys(); + this->freezeCoins(pubkeys); } void CoinsWidget::thawAllSelected() { - QModelIndexList list = ui->coins->selectionModel()->selectedRows(); - - QVector indexes; - for (QModelIndex index: list) { - indexes.push_back(m_proxyModel->mapToSource(index).row()); - } - this->thawCoins(indexes); + QStringList pubkeys = this->selectedPubkeys(); + this->thawCoins(pubkeys); } void CoinsWidget::viewOutput() { @@ -291,17 +279,17 @@ QVector CoinsWidget::currentEntries() { return selectedCoins; } -void CoinsWidget::freezeCoins(const QVector& indexes) { - for (int i : indexes) { - m_ctx->wallet->coins()->freeze(i); +void CoinsWidget::freezeCoins(QStringList &pubkeys) { + for (auto &pubkey : pubkeys) { + m_ctx->wallet->coins()->freeze(pubkey); } m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->updateBalance(); } -void CoinsWidget::thawCoins(const QVector &indexes) { - for (int i : indexes) { - m_ctx->wallet->coins()->thaw(i); +void CoinsWidget::thawCoins(QStringList &pubkeys) { + for (auto &pubkey : pubkeys) { + m_ctx->wallet->coins()->thaw(pubkey); } m_ctx->wallet->coins()->refresh(m_ctx->wallet->currentSubaddressAccount()); m_ctx->updateBalance(); diff --git a/src/CoinsWidget.h b/src/CoinsWidget.h index 90cbb534..6c48e24c 100644 --- a/src/CoinsWidget.h +++ b/src/CoinsWidget.h @@ -33,9 +33,7 @@ public slots: private slots: void showHeaderMenu(const QPoint& position); void setShowSpent(bool show); - void freezeOutput(); void freezeAllSelected(); - void thawOutput(); void thawAllSelected(); void viewOutput(); void onSweepOutputs(); @@ -43,8 +41,8 @@ private slots: void editLabel(); private: - void freezeCoins(const QVector& indexes); - void thawCoins(const QVector& indexes); + void freezeCoins(QStringList &pubkeys); + void thawCoins(QStringList &pubkeys); enum copyField { PubKey = 0, @@ -79,6 +77,7 @@ private: void copy(copyField field); CoinsInfo* currentEntry(); QVector currentEntries(); + QStringList selectedPubkeys(); }; diff --git a/src/libwalletqt/Coins.cpp b/src/libwalletqt/Coins.cpp index ac49d272..b95d4f7c 100644 --- a/src/libwalletqt/Coins.cpp +++ b/src/libwalletqt/Coins.cpp @@ -71,15 +71,15 @@ quint64 Coins::count() const return m_tinfo.count(); } -void Coins::freeze(int index) const +void Coins::freeze(QString &publicKey) const { - m_pimpl->setFrozen(index); + m_pimpl->setFrozen(publicKey.toStdString()); emit coinFrozen(); } -void Coins::thaw(int index) const +void Coins::thaw(QString &publicKey) const { - m_pimpl->thaw(index); + m_pimpl->thaw(publicKey.toStdString()); emit coinThawed(); } diff --git a/src/libwalletqt/Coins.h b/src/libwalletqt/Coins.h index 1813cb05..7c73af35 100644 --- a/src/libwalletqt/Coins.h +++ b/src/libwalletqt/Coins.h @@ -28,8 +28,8 @@ public: Q_INVOKABLE CoinsInfo * coin(int index); Q_INVOKABLE void refresh(quint32 accountIndex); Q_INVOKABLE void refreshUnlocked(); - Q_INVOKABLE void freeze(int index) const; - Q_INVOKABLE void thaw(int index) const; + Q_INVOKABLE void freeze(QString &publicKey) const; + Q_INVOKABLE void thaw(QString &publicKey) const; Q_INVOKABLE QVector coins_from_txid(const QString &txid); Q_INVOKABLE void setDescription(int index, quint32 accountIndex, const QString &description); From e512e539b6074fcb861eb1c72bc19cd029b05e1b Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 1 Oct 2021 19:10:46 +0200 Subject: [PATCH 02/56] PaymentRequestDialog --- src/ReceiveWidget.cpp | 17 +++ src/ReceiveWidget.h | 1 + src/ReceiveWidget.ui | 7 + src/SendWidget.cpp | 2 +- src/dialog/PaymentRequestDialog.cpp | 82 +++++++++++ src/dialog/PaymentRequestDialog.h | 37 +++++ src/dialog/PaymentRequestDialog.ui | 203 ++++++++++++++++++++++++++++ src/libwalletqt/Wallet.cpp | 8 ++ src/libwalletqt/Wallet.h | 2 + src/widgets/QrCodeWidget.cpp | 8 ++ src/widgets/QrCodeWidget.h | 2 + 11 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 src/dialog/PaymentRequestDialog.cpp create mode 100644 src/dialog/PaymentRequestDialog.h create mode 100644 src/dialog/PaymentRequestDialog.ui diff --git a/src/ReceiveWidget.cpp b/src/ReceiveWidget.cpp index 477e51cf..f583af43 100644 --- a/src/ReceiveWidget.cpp +++ b/src/ReceiveWidget.cpp @@ -7,6 +7,7 @@ #include #include +#include "dialog/PaymentRequestDialog.h" #include "dialog/QrCodeDialog.h" #include "model/ModelUtils.h" #include "utils/Icons.h" @@ -60,6 +61,8 @@ ReceiveWidget::ReceiveWidget(QSharedPointer ctx, QWidget *parent) connect(ui->check_showUsed, &QCheckBox::clicked, this, &ReceiveWidget::setShowUsedAddresses); connect(ui->check_showHidden, &QCheckBox::clicked, this, &ReceiveWidget::setShowHiddenAddresses); + + connect(ui->btn_createPaymentRequest, &QPushButton::clicked, this, &ReceiveWidget::createPaymentRequest); } void ReceiveWidget::setSearchbarVisible(bool visible) { @@ -118,6 +121,18 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { menu->popup(ui->addresses->viewport()->mapToGlobal(point)); } +void ReceiveWidget::createPaymentRequest() { + QModelIndex index = ui->addresses->currentIndex(); + if (!index.isValid()) { + return; + } + + QString address = index.model()->data(index.siblingAtColumn(SubaddressModel::Address), Qt::UserRole).toString(); + + PaymentRequestDialog dialog{this, m_ctx, address}; + dialog.exec(); +} + void ReceiveWidget::onShowTransactions() { QModelIndex index = ui->addresses->currentIndex(); if (!index.isValid()) { @@ -190,6 +205,7 @@ void ReceiveWidget::updateQrCode(){ QModelIndex index = ui->addresses->currentIndex(); if (!index.isValid()) { ui->qrCode->clear(); + ui->btn_createPaymentRequest->hide(); return; } @@ -199,6 +215,7 @@ void ReceiveWidget::updateQrCode(){ int width = ui->qrCode->width() - 4; if (qrc.isValid()) { ui->qrCode->setPixmap(qrc.toPixmap(1).scaled(width, width, Qt::KeepAspectRatio)); + ui->btn_createPaymentRequest->show(); } } diff --git a/src/ReceiveWidget.h b/src/ReceiveWidget.h index 23ee78b1..e01f93fa 100644 --- a/src/ReceiveWidget.h +++ b/src/ReceiveWidget.h @@ -39,6 +39,7 @@ public slots: void setShowHiddenAddresses(bool show); void setSearchFilter(const QString &filter); void onShowTransactions(); + void createPaymentRequest(); signals: void showTransactions(const QString& address); diff --git a/src/ReceiveWidget.ui b/src/ReceiveWidget.ui index 0d420817..b84ed585 100644 --- a/src/ReceiveWidget.ui +++ b/src/ReceiveWidget.ui @@ -69,6 +69,13 @@ + + + + Payment Request + + + diff --git a/src/SendWidget.cpp b/src/SendWidget.cpp index b460a083..92f9a34c 100644 --- a/src/SendWidget.cpp +++ b/src/SendWidget.cpp @@ -26,7 +26,7 @@ SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) QString amount_rx = R"(^\d{0,8}[\.,]\d{0,12}|(all)$)"; QRegExp rx; rx.setPattern(amount_rx); - QValidator *validator = new QRegExpValidator(rx, this); + QValidator *validator = new QRegExpValidator(rx, this); ui->lineAmount->setValidator(validator); connect(m_ctx.get(), &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction); diff --git a/src/dialog/PaymentRequestDialog.cpp b/src/dialog/PaymentRequestDialog.cpp new file mode 100644 index 00000000..4cf38eee --- /dev/null +++ b/src/dialog/PaymentRequestDialog.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "PaymentRequestDialog.h" +#include "ui_PaymentRequestDialog.h" + +#include +#include +#include +#include + +#include "WalletManager.h" + +PaymentRequestDialog::PaymentRequestDialog(QWidget *parent, QSharedPointer ctx, QString address) + : QDialog(parent) + , ui(new Ui::PaymentRequestDialog) + , m_ctx(std::move(ctx)) + , m_address(std::move(address)) +{ + ui->setupUi(this); + + QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}|(all)$)"; + QRegExp rx; + rx.setPattern(amount_rx); + QValidator *validator = new QRegExpValidator(rx, this); + ui->line_amountXMR->setValidator(validator); + + connect(ui->line_amountXMR, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest); + connect(ui->line_description, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest); + connect(ui->line_recipient, &QLineEdit::textChanged, this, &PaymentRequestDialog::updatePaymentRequest); + + connect(ui->btn_copyLink, &QPushButton::clicked, this, &PaymentRequestDialog::copyLink); + connect(ui->btn_copyImage, &QPushButton::clicked, this, &PaymentRequestDialog::copyImage); + connect(ui->btn_saveImage, &QPushButton::clicked, this, &PaymentRequestDialog::saveImage); + + this->updatePaymentRequest(); + + ui->line_amountXMR->setFocus(); + + this->adjustSize(); +} + +void PaymentRequestDialog::updatePaymentRequest() { + QString description = ui->line_description->text(); + QString recipient = ui->line_recipient->text(); + quint64 amount = WalletManager::amountFromString(ui->line_amountXMR->text()); + + QString uri = m_ctx->wallet->make_uri(m_address, amount, description, recipient); + + ui->line_paymentRequestUri->setText(uri); + ui->line_paymentRequestUri->setCursorPosition(0); + + // TODO: memory leak, cba to refactor now + m_qrCode = new QrCode(uri, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM); + if (m_qrCode->isValid()) { + ui->qrWidget->setQrCode(m_qrCode); + } +} + +void PaymentRequestDialog::copyLink() { + Utils::copyToClipboard(ui->line_paymentRequestUri->text()); + QMessageBox::information(this, "Information", "Payment request link copied to clipboard."); +} + +void PaymentRequestDialog::copyImage() { + QApplication::clipboard()->setPixmap(m_qrCode->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio)); + QMessageBox::information(this, "Information", "QR code copied to clipboard."); +} + +void PaymentRequestDialog::saveImage() { + QString filename = QFileDialog::getSaveFileName(this, "Select where to save file", QDir::current().filePath("qrcode.png")); + if (filename.isEmpty()) { + return; + } + + QFile file(filename); + file.open(QIODevice::WriteOnly); + m_qrCode->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio).save(&file, "PNG"); + QMessageBox::information(this, "Information", "QR code saved to file"); +} + +PaymentRequestDialog::~PaymentRequestDialog() = default; \ No newline at end of file diff --git a/src/dialog/PaymentRequestDialog.h b/src/dialog/PaymentRequestDialog.h new file mode 100644 index 00000000..c109d0a3 --- /dev/null +++ b/src/dialog/PaymentRequestDialog.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#ifndef FEATHER_PAYMENTREQUESTDIALOG_H +#define FEATHER_PAYMENTREQUESTDIALOG_H + +#include + +#include "appcontext.h" +#include "qrcode/QrCode.h" + +namespace Ui { + class PaymentRequestDialog; +} + +class PaymentRequestDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PaymentRequestDialog(QWidget *parent, QSharedPointer ctx, QString address); + ~PaymentRequestDialog() override; + +private slots: + void updatePaymentRequest(); + void copyLink(); + void copyImage(); + void saveImage(); + +private: + QScopedPointer ui; + QSharedPointer m_ctx; + QString m_address; + QrCode *m_qrCode; +}; + +#endif //FEATHER_PAYMENTREQUESTDIALOG_H diff --git a/src/dialog/PaymentRequestDialog.ui b/src/dialog/PaymentRequestDialog.ui new file mode 100644 index 00000000..5b4dac23 --- /dev/null +++ b/src/dialog/PaymentRequestDialog.ui @@ -0,0 +1,203 @@ + + + PaymentRequestDialog + + + + 0 + 0 + 658 + 761 + + + + Create Payment Request + + + + + + + 0 + 0 + + + + + + + + true + + + + + + + Qt::Horizontal + + + + + + + + + Amount: + + + + + + + + + + + + 0 + 0 + + + + + + + + XMR + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Description: + + + + + + + + + + Your name: + + + + + + + + + + + + + + Copy Link + + + + + + + Copy Image + + + + + + + Save Image + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + + QrCodeWidget + QWidget +
widgets/QrCodeWidget.h
+ 1 +
+
+ + + + buttonBox + accepted() + PaymentRequestDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PaymentRequestDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index f0dd9f38..ea1d844d 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -1150,6 +1150,14 @@ QVariantMap Wallet::parse_uri_to_object(const QString &uri) const return result; } +QString Wallet::make_uri(const QString &address, quint64 &amount, const QString &description, + const QString &recipient) const +{ + std::string error; + std::string uri = m_walletImpl->make_uri(address.toStdString(), "", amount, description.toStdString(), recipient.toStdString(), error); + return QString::fromStdString(uri); +} + bool Wallet::rescanSpent() { QMutexLocker locker(&m_asyncMutex); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 1e419450..f54e8c77 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -390,6 +390,8 @@ public: bool parse_uri(const QString &uri, QString &address, QString &payment_id, uint64_t &amount, QString &tx_description, QString &recipient_name, QVector &unknown_parameters, QString &error) const; QVariantMap parse_uri_to_object(const QString &uri) const; + QString make_uri(const QString &address, quint64 &amount, const QString &description, const QString &recipient) const; + //! Namespace your cacheAttribute keys to avoid collisions bool setCacheAttribute(const QString &key, const QString &val); QString getCacheAttribute(const QString &key) const; diff --git a/src/widgets/QrCodeWidget.cpp b/src/widgets/QrCodeWidget.cpp index e82902b3..ffc17af6 100644 --- a/src/widgets/QrCodeWidget.cpp +++ b/src/widgets/QrCodeWidget.cpp @@ -57,4 +57,12 @@ void QrCodeWidget::paintEvent(QPaintEvent *event) { } } } +} + +bool QrCodeWidget::hasHeightForWidth() const { + return true; +} + +int QrCodeWidget::heightForWidth(int w) const { + return w; } \ No newline at end of file diff --git a/src/widgets/QrCodeWidget.h b/src/widgets/QrCodeWidget.h index 855936e3..f86c81dd 100644 --- a/src/widgets/QrCodeWidget.h +++ b/src/widgets/QrCodeWidget.h @@ -18,6 +18,8 @@ public: protected: void paintEvent(QPaintEvent *event) override; + int heightForWidth(int w) const override; + bool hasHeightForWidth() const override; private: QrCode *m_qrcode = nullptr; From 3721665ea62a6b076793a1079d0f254442987b1e Mon Sep 17 00:00:00 2001 From: tobtoht Date: Mon, 4 Oct 2021 18:33:58 +0200 Subject: [PATCH 03/56] TransactionInfo: fix balance delta for incoming txs --- src/libwalletqt/TransactionInfo.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/libwalletqt/TransactionInfo.cpp b/src/libwalletqt/TransactionInfo.cpp index f7ad540e..2a6bce4e 100644 --- a/src/libwalletqt/TransactionInfo.cpp +++ b/src/libwalletqt/TransactionInfo.cpp @@ -28,7 +28,13 @@ bool TransactionInfo::isCoinbase() const quint64 TransactionInfo::balanceDelta() const { - return m_amount + m_fee; + if (m_direction == Direction_In) { + return m_amount; + } + else if (m_direction == Direction_Out) { + return m_amount + m_fee; + } + return m_amount; } double TransactionInfo::amount() const From 0eb4592f872541652f9504a0a2bc78def3233ad7 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Tue, 5 Oct 2021 14:30:23 +0200 Subject: [PATCH 04/56] Improve transaction history CSV export format --- src/libwalletqt/TransactionHistory.cpp | 60 ++++++++++++++------------ 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp index 50a76f06..deacae13 100644 --- a/src/libwalletqt/TransactionHistory.cpp +++ b/src/libwalletqt/TransactionHistory.cpp @@ -6,6 +6,8 @@ #include "utils/Utils.h" #include "utils/AppData.h" #include "utils/config.h" +#include "constants.h" +#include "WalletManager.h" bool TransactionHistory::transaction(int index, std::function callback) { @@ -147,13 +149,16 @@ TransactionHistory::TransactionHistory(Monero::TransactionHistory *pimpl, QObjec } bool TransactionHistory::writeCSV(const QString &path) { - auto data = QString(""); + QString data; QReadLocker locker(&m_lock); - for (const auto &tx : m_pimpl->getAll()) { - if (tx->subaddrAccount() != 0) { // only account 0 - continue; - } + auto transactions = m_pimpl->getAll(); + + std::sort(transactions.begin(), transactions.end(), [](const Monero::TransactionInfo *info1, const Monero::TransactionInfo *info2){ + return info1->blockHeight() < info2->blockHeight(); + }); + + for (const auto &tx : transactions) { TransactionInfo info(tx, this); // collect column data @@ -166,47 +171,46 @@ bool TransactionHistory::writeCSV(const QString &path) { const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd")); double fiat_price = usd_price * amount; - if(preferredFiatSymbol != "USD") + if (preferredFiatSymbol != "USD") fiat_price = appData()->prices.convert("USD", preferredFiatSymbol, fiat_price); double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0; - if(fiat_price != 0) - fiatAmount = QString("%1 %2").arg(QString::number(fiat_rounded)).arg(preferredFiatSymbol); + if (usd_price != 0) + fiatAmount = QString::number(fiat_rounded); + else + fiatAmount = "\"?\""; - // collect some more column data - quint64 atomicAmount = info.atomicAmount(); - quint32 subaddrAccount = info.subaddrAccount(); - QString fee = info.fee(); QString direction = QString(""); - TransactionInfo::Direction _direction = info.direction(); - if(_direction == TransactionInfo::Direction_In) + if (info.direction() == TransactionInfo::Direction_In) direction = QString("in"); - else if(_direction == TransactionInfo::Direction_Out) + else if (info.direction() == TransactionInfo::Direction_Out) direction = QString("out"); else continue; // skip TransactionInfo::Direction_Both - QString label = info.label(); - label.remove(QChar('"')); // reserved - quint64 blockHeight = info.blockHeight(); - QString date = info.date() + " " + info.time(); - uint epoch = timeStamp.toTime_t(); QString displayAmount = info.displayAmount(); QString paymentId = info.paymentId(); - if(paymentId == "0000000000000000") + if (paymentId == "0000000000000000") { paymentId = ""; + } + + QString date = QString("%1T%2Z").arg(info.date(), info.time()); // ISO 8601 + + QString balanceDelta = WalletManager::displayAmount(info.balanceDelta()); + if (info.direction() == TransactionInfo::Direction_Out) { + balanceDelta = "-" + balanceDelta; + } // format and write - QString line = QString("%1,%2,%3,%4,%5,\"%6\",%7,%8,%9,\"%10\",%11,%12\n") - .arg(QString::number(blockHeight), QString::number(epoch), date) - .arg(direction, QString::number(amount), fiatAmount, QString::number(atomicAmount)) - .arg(info.fee(), info.hash(), label, QString::number(subaddrAccount)) - .arg(paymentId); + QString line = QString("\n%1,%2,\"%3\",%4,\"%5\",%6,%7,%8,\"%9\",\"%10\",\"%11\",%12,\"%13\"") + .arg(QString::number(info.blockHeight()), QString::number(timeStamp.toTime_t()), date, + QString::number(info.subaddrAccount()), direction, balanceDelta, info.displayAmount(), + info.fee(), info.hash(), info.description(), paymentId, fiatAmount, preferredFiatSymbol); data += line; } - if(data.isEmpty()) + if (data.isEmpty()) return false; - data = QString("blockHeight,epoch,date,direction,amount,fiat,atomicAmount,fee,txid,label,subaddrAccount,paymentId\n%1").arg(data); + data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data); return Utils::fileWrite(path, data); } From 3fec15e4aa87224208eb612875c5b61673fc9748 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Tue, 12 Oct 2021 14:45:06 +0200 Subject: [PATCH 05/56] Rework Mining UI --- src/utils/config.cpp | 5 + src/utils/config.h | 10 + src/utils/xmrig.cpp | 51 ++-- src/utils/xmrig.h | 10 +- src/widgets/XMRigWidget.cpp | 272 +++++++++++------- src/widgets/XMRigWidget.h | 14 +- src/widgets/XMRigWidget.ui | 539 ++++++++++++++++++------------------ 7 files changed, 503 insertions(+), 398 deletions(-) diff --git a/src/utils/config.cpp b/src/utils/config.cpp index 51f17ad2..dd0b838b 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -49,8 +49,13 @@ static const QHash configStrings = { {Config::showSearchbar,{QS("showSearchbar"), true}}, // Mining + {Config::miningMode,{QS("miningMode"), Config::MiningMode::Pool}}, {Config::xmrigPath,{QS("xmrigPath"), ""}}, + {Config::xmrigElevated,{QS("xmrigElevated"), false}}, + {Config::xmrigThreads,{QS("xmrigThreads"), 1}}, {Config::xmrigPool,{QS("xmrigPool"), "pool.xmr.pt:9000"}}, + {Config::xmrigNetworkTLS,{QS("xmrigNetworkTLS"), true}}, + {Config::xmrigNetworkTor,{QS("xmrigNetworkTor"), false}}, {Config::pools,{QS("pools"), {}}}, // Settings diff --git a/src/utils/config.h b/src/utils/config.h index 1dce5303..679b6913 100644 --- a/src/utils/config.h +++ b/src/utils/config.h @@ -53,8 +53,13 @@ public: showSearchbar, // Mining + miningMode, xmrigPath, + xmrigElevated, + xmrigThreads, xmrigPool, + xmrigNetworkTLS, + xmrigNetworkTor, pools, // Settings @@ -98,6 +103,11 @@ public: spendable }; + enum MiningMode { + Pool = 0, + Solo + }; + ~Config() override; QVariant get(ConfigKey key); QString getFileName(); diff --git a/src/utils/xmrig.cpp b/src/utils/xmrig.cpp index 2f8b613b..c80597d3 100644 --- a/src/utils/xmrig.cpp +++ b/src/utils/xmrig.cpp @@ -13,49 +13,58 @@ XmRig::XmRig(const QString &configDir, QObject *parent) : QObject(parent) { - this->rigDir = QDir(configDir).filePath("xmrig"); - m_process.setProcessChannelMode(QProcess::MergedChannels); connect(&m_process, &QProcess::readyReadStandardOutput, this, &XmRig::handleProcessOutput); connect(&m_process, &QProcess::errorOccurred, this, &XmRig::handleProcessError); - connect(&m_process, &QProcess::stateChanged, this, &XmRig::stateChanged); + connect(&m_process, &QProcess::stateChanged, this, &XmRig::onStateChanged); } void XmRig::stop() { - if(m_process.state() == QProcess::Running) { + qDebug() << m_process.processId(); + if (m_process.state() == QProcess::Running) { #if defined(Q_OS_WIN) m_process.kill(); // https://doc.qt.io/qt-5/qprocess.html#terminate -#else - m_process.terminate(); +#elif defined(Q_OS_LINUX) + if (m_elevated) { + m_killProcess.start("pkexec", QStringList() << "kill" << QString::number(m_process.processId())); + return; + } #endif + m_process.terminate(); } } void XmRig::start(const QString &path, int threads, const QString &address, const QString &username, - const QString &password, bool tor, bool tls) + const QString &password, bool tor, bool tls, bool elevated) { + m_elevated = elevated; + auto state = m_process.state(); if (state == QProcess::ProcessState::Running || state == QProcess::ProcessState::Starting) { emit error("Can't start XMRig, already running or starting"); return; } - if(path.isEmpty()) { + if (path.isEmpty()) { emit error("XmRig->Start path parameter missing."); return; } - if(!Utils::fileExists(path)) { + if (!Utils::fileExists(path)) { emit error(QString("Path to XMRig binary invalid; file does not exist: %1").arg(path)); return; } QStringList arguments; + if (m_elevated) { + arguments << path; + } arguments << "-o" << address; arguments << "-a" << "rx/0"; arguments << "-u" << username; - if(!password.isEmpty()) + if (!password.isEmpty()) { arguments << "-p" << password; + } arguments << "--no-color"; arguments << "-t" << QString::number(threads); if (tor) { @@ -67,20 +76,30 @@ void XmRig::start(const QString &path, int threads, const QString &address, cons } arguments << "-x" << QString("%1:%2").arg(host, port); } - - if(tls) + if (tls) { arguments << "--tls"; + } arguments << "--donate-level" << "1"; QString cmd = QString("%1 %2").arg(path, arguments.join(" ")); emit output(cmd.toUtf8()); - m_process.start(path, arguments); + + if (m_elevated) { + m_process.start("pkexec", arguments); + } else { + m_process.start(path, arguments); + } } -void XmRig::stateChanged(QProcess::ProcessState state) { - if(state == QProcess::ProcessState::Running) +void XmRig::onStateChanged(QProcess::ProcessState state) { + emit stateChanged(state); + + if (state == QProcess::ProcessState::Running) { emit output("XMRig started"); - else if (state == QProcess::ProcessState::NotRunning) + } + + else if (state == QProcess::ProcessState::NotRunning) { emit output("XMRig stopped"); + } } void XmRig::handleProcessOutput() { diff --git a/src/utils/xmrig.h b/src/utils/xmrig.h index 2d89abc5..6b48c8f3 100644 --- a/src/utils/xmrig.h +++ b/src/utils/xmrig.h @@ -21,24 +21,24 @@ Q_OBJECT public: explicit XmRig(const QString &configDir, QObject *parent = nullptr); - void start(const QString &path, int threads, const QString &address, const QString &username, const QString &password, bool tor = false, bool tls = true); + void start(const QString &path, int threads, const QString &address, const QString &username, const QString &password, bool tor = false, bool tls = true, bool elevated = false); void stop(); - QString rigDir; - QString rigPath; - signals: void error(const QString &msg); void output(const QByteArray &data); void hashrate(const QString &rate); + void stateChanged(QProcess::ProcessState state); private slots: - void stateChanged(QProcess::ProcessState); + void onStateChanged(QProcess::ProcessState); void handleProcessOutput(); void handleProcessError(QProcess::ProcessError error); private: ChildProcess m_process; + QProcess m_killProcess; + bool m_elevated; }; #endif //FEATHER_XMRIG_H diff --git a/src/widgets/XMRigWidget.cpp b/src/widgets/XMRigWidget.cpp index 39cc22bd..7aa6e700 100644 --- a/src/widgets/XMRigWidget.cpp +++ b/src/widgets/XMRigWidget.cpp @@ -24,52 +24,54 @@ XMRigWidget::XMRigWidget(QSharedPointer ctx, QWidget *parent) { ui->setupUi(this); - QPixmap p(":assets/images/xmrig.svg"); - ui->lbl_logo->setPixmap(p.scaled(64, 64, Qt::KeepAspectRatio, Qt::SmoothTransformation)); - + connect(m_XMRig, &XmRig::stateChanged, this, &XMRigWidget::onXMRigStateChanged); connect(m_XMRig, &XmRig::output, this, &XMRigWidget::onProcessOutput); connect(m_XMRig, &XmRig::error, this, &XMRigWidget::onProcessError); connect(m_XMRig, &XmRig::hashrate, this, &XMRigWidget::onHashrate); - // table - ui->tableView->setModel(this->m_model); + // [Downloads] tab + ui->tableView->setModel(m_model); m_contextMenu->addAction(icons()->icon("network.png"), "Download file", this, &XMRigWidget::linkClicked); connect(ui->tableView, &QHeaderView::customContextMenuRequested, this, &XMRigWidget::showContextMenu); connect(ui->tableView, &QTableView::doubleClicked, this, &XMRigWidget::linkClicked); - // threads + // [Settings] tab + ui->poolFrame->show(); + ui->soloFrame->hide(); + + // XMRig executable + connect(ui->btn_browse, &QPushButton::clicked, this, &XMRigWidget::onBrowseClicked); + ui->lineEdit_path->setText(config()->get(Config::xmrigPath).toString()); + + // Run as admin/root + bool elevated = config()->get(Config::xmrigElevated).toBool(); + if (elevated) { + ui->radio_elevateYes->setChecked(true); + } else { + ui->radio_elevateNo->setChecked(true); + } + connect(ui->radio_elevateYes, &QRadioButton::toggled, this, &XMRigWidget::onXMRigElevationChanged); +#if defined(Q_OS_WIN) + ui->radio_elevateYes->setToolTip("Not supported on Windows, yet."); + ui->radio_elevateYes->setEnabled(false); + ui->radio_elevateNo->setChecked(true); +#endif + + // CPU threads ui->threadSlider->setMinimum(1); - int threads = QThread::idealThreadCount(); - m_threads = threads / 2; - ui->threadSlider->setMaximum(threads); - ui->threadSlider->setValue(m_threads); - ui->label_threads->setText(QString("CPU threads: %1").arg(m_threads)); + ui->threadSlider->setMaximum(QThread::idealThreadCount()); + + int threads = config()->get(Config::xmrigThreads).toInt(); + ui->threadSlider->setValue(threads); + ui->label_threads->setText(QString("CPU threads: %1").arg(threads)); + connect(ui->threadSlider, &QSlider::valueChanged, this, &XMRigWidget::onThreadsValueChanged); - // buttons - connect(ui->btn_start, &QPushButton::clicked, this, &XMRigWidget::onStartClicked); - connect(ui->btn_stop, &QPushButton::clicked, this, &XMRigWidget::onStopClicked); - connect(ui->btn_browse, &QPushButton::clicked, this, &XMRigWidget::onBrowseClicked); - connect(ui->btn_clear, &QPushButton::clicked, this, &XMRigWidget::onClearClicked); + // Mining mode + connect(ui->combo_miningMode, QOverload::of(&QComboBox::currentIndexChanged), this, &XMRigWidget::onMiningModeChanged); + ui->combo_miningMode->setCurrentIndex(config()->get(Config::miningMode).toInt()); - // defaults - ui->btn_stop->setEnabled(false); - ui->check_autoscroll->setChecked(true); - ui->relayTor->setChecked(false); - ui->check_tls->setChecked(true); - ui->label_status->setTextInteractionFlags(Qt::TextSelectableByMouse); - ui->label_status->hide(); - ui->soloFrame->hide(); - ui->poolFrame->hide(); - - // XMRig binary - auto path = config()->get(Config::xmrigPath).toString(); - if(!path.isEmpty()) { - ui->lineEdit_path->setText(path); - } - - // pools - ui->poolFrame->show(); + // Pool/node address this->updatePools(); connect(ui->combo_pools, &QComboBox::currentTextChanged, this, &XMRigWidget::onPoolChanged); @@ -87,41 +89,45 @@ XMRigWidget::XMRigWidget(QSharedPointer ctx, QWidget *parent) this->updatePools(); }); - // info - ui->console->appendPlainText(QString("Detected %1 CPU threads.").arg(threads)); - if(!path.isEmpty() && !Utils::fileExists(path)) - ui->console->appendPlainText("Invalid path to XMRig binary detected. Please reconfigure on the Settings tab."); - else - ui->console->appendPlainText(QString("XMRig path set to %1").arg(path)); + // Network settings + connect(ui->check_tls, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTLSToggled); + connect(ui->relayTor, &QCheckBox::toggled, this, &XMRigWidget::onNetworkTorToggled); + ui->check_tls->setChecked(config()->get(Config::xmrigNetworkTLS).toBool()); + ui->relayTor->setChecked(config()->get(Config::xmrigNetworkTor).toBool()); - ui->console->appendPlainText("Ready to mine."); - - // username/password - connect(ui->lineEdit_password, &QLineEdit::editingFinished, [=]() { - m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); - m_ctx->storeWallet(); - }); - connect(ui->lineEdit_address, &QLineEdit::editingFinished, [=]() { - m_ctx->wallet->setCacheAttribute("feather.xmrig_username", ui->lineEdit_address->text()); - m_ctx->storeWallet(); - }); - - // checkbox connects - connect(ui->check_solo, &QCheckBox::stateChanged, this, &XMRigWidget::onSoloChecked); - - // Xmrig username + // Receiving address auto username = m_ctx->wallet->getCacheAttribute("feather.xmrig_username"); - if(!username.isEmpty()) + if (!username.isEmpty()) { ui->lineEdit_address->setText(username); + } + connect(ui->lineEdit_address, &QLineEdit::textChanged, [=]() { + m_ctx->wallet->setCacheAttribute("feather.xmrig_username", ui->lineEdit_address->text()); + }); + connect(ui->btn_fillPrimaryAddress, &QPushButton::clicked, this, &XMRigWidget::onUsePrimaryAddressClicked); - // Xmrig passwd + // Password auto password = m_ctx->wallet->getCacheAttribute("feather.xmrig_password"); - if(!password.isEmpty()) { + if (!password.isEmpty()) { ui->lineEdit_password->setText(password); } else { ui->lineEdit_password->setText("featherwallet"); m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); } + connect(ui->lineEdit_password, &QLineEdit::textChanged, [=]() { + m_ctx->wallet->setCacheAttribute("feather.xmrig_password", ui->lineEdit_password->text()); + }); + + // [Status] tab + connect(ui->btn_start, &QPushButton::clicked, this, &XMRigWidget::onStartClicked); + connect(ui->btn_stop, &QPushButton::clicked, this, &XMRigWidget::onStopClicked); + connect(ui->btn_clear, &QPushButton::clicked, this, &XMRigWidget::onClearClicked); + + ui->btn_stop->setEnabled(false); + ui->check_autoscroll->setChecked(true); + ui->label_status->setTextInteractionFlags(Qt::TextSelectableByMouse); + ui->label_status->hide(); + + this->printConsoleInfo(); } bool XMRigWidget::isMining() { @@ -130,14 +136,11 @@ bool XMRigWidget::isMining() { void XMRigWidget::onWalletClosed() { this->onStopClicked(); - this->onClearClicked(); - ui->lineEdit_password->setText(""); - ui->lineEdit_address->setText(""); } void XMRigWidget::onThreadsValueChanged(int threads) { - m_threads = threads; - ui->label_threads->setText(QString("CPU threads: %1").arg(m_threads)); + config()->set(Config::xmrigThreads, threads); + ui->label_threads->setText(QString("CPU threads: %1").arg(threads)); } void XMRigWidget::onPoolChanged(const QString &pool) { @@ -146,10 +149,15 @@ void XMRigWidget::onPoolChanged(const QString &pool) { } } +void XMRigWidget::onXMRigElevationChanged(bool elevated) { + config()->set(Config::xmrigElevated, elevated); +} + void XMRigWidget::onBrowseClicked() { - QString fileName = QFileDialog::getOpenFileName( - this, "Path to XMRig executable", QDir::homePath()); - if (fileName.isEmpty()) return; + QString fileName = QFileDialog::getOpenFileName(this, "Path to XMRig executable", QDir::homePath()); + if (fileName.isEmpty()) { + return; + } config()->set(Config::xmrigPath, fileName); ui->lineEdit_path->setText(fileName); } @@ -158,47 +166,50 @@ void XMRigWidget::onClearClicked() { ui->console->clear(); } +void XMRigWidget::onUsePrimaryAddressClicked() { + ui->lineEdit_address->setText(m_ctx->wallet->address(0, 0)); +} + void XMRigWidget::onStartClicked() { - QString xmrigPath; - bool solo = ui->check_solo->isChecked(); - xmrigPath = config()->get(Config::xmrigPath).toString(); + QString xmrigPath = config()->get(Config::xmrigPath).toString(); + if (!this->checkXMRigPath()) { + return; + } + + QString address = [this](){ + if (ui->combo_miningMode->currentIndex() == Config::MiningMode::Pool) { + return config()->get(Config::xmrigPool).toString(); + } else { + return ui->lineEdit_solo->text().trimmed(); + } + }(); + if (address.isEmpty()) { + ui->console->appendPlainText("No pool or node address set. Please configure on the Settings tab."); + return; + } // username is receiving address usually auto username = m_ctx->wallet->getCacheAttribute("feather.xmrig_username"); auto password = m_ctx->wallet->getCacheAttribute("feather.xmrig_password"); - if(username.isEmpty()) { - QString err = "Please specify a receiving address on the Settings screen"; - ui->console->appendPlainText(err); - QMessageBox::warning(this, "Error", err); + if (username.isEmpty()) { + ui->console->appendPlainText("Please specify a receiving address on the Settings screen."); return; } - QString address; - if(solo) - address = ui->lineEdit_solo->text().trimmed(); - else - address = config()->get(Config::xmrigPool).toString(); - - if(address.contains("cryptonote.social") && !username.contains(".")) { + if (address.contains("cryptonote.social") && !username.contains(".")) { // cryptonote social requires ., we'll just grab a few chars from primary addy username = QString("%1.%2").arg(username, m_ctx->wallet->address(0, 0).mid(0, 6)); } - m_XMRig->start(xmrigPath, m_threads, address, username, password, ui->relayTor->isChecked(), ui->check_tls->isChecked()); - ui->btn_start->setEnabled(false); - ui->btn_stop->setEnabled(true); - m_isMining = true; - emit miningStarted(); + int threads = ui->threadSlider->value(); + + m_XMRig->start(xmrigPath, threads, address, username, password, ui->relayTor->isChecked(), ui->check_tls->isChecked(), + ui->radio_elevateYes->isChecked()); } void XMRigWidget::onStopClicked() { m_XMRig->stop(); - ui->btn_start->setEnabled(true); - ui->btn_stop->setEnabled(false); - ui->label_status->hide(); - m_isMining = false; - emit miningEnded(); } void XMRigWidget::onProcessOutput(const QByteArray &data) { @@ -216,9 +227,7 @@ void XMRigWidget::onProcessError(const QString &msg) { ui->console->appendPlainText("\n" + msg); ui->btn_start->setEnabled(true); ui->btn_stop->setEnabled(false); - ui->label_status->hide(); - m_isMining = false; - emit miningEnded(); + this->setMiningStopped(); } void XMRigWidget::onHashrate(const QString &hashrate) { @@ -307,15 +316,78 @@ void XMRigWidget::updatePools() { } } -void XMRigWidget::onSoloChecked(int state) { - if(state == 2) { - ui->poolFrame->hide(); - ui->soloFrame->show(); - ui->check_tls->setChecked(false); +void XMRigWidget::printConsoleInfo() { + ui->console->appendPlainText(QString("Detected %1 CPU threads.").arg(QThread::idealThreadCount())); + if (this->checkXMRigPath()) { + QString path = config()->get(Config::xmrigPath).toString(); + ui->console->appendPlainText(QString("XMRig path set to %1").arg(path)); } - else { +} + +void XMRigWidget::onMiningModeChanged(int mode) { + config()->set(Config::miningMode, mode); + + if (mode == Config::MiningMode::Pool) { ui->poolFrame->show(); ui->soloFrame->hide(); + ui->label_poolNodeAddress->setText("Pool address:"); + ui->check_tls->setChecked(true); + } else { // Solo mining + ui->poolFrame->hide(); + ui->soloFrame->show(); + ui->label_poolNodeAddress->setText("Node address:"); + ui->check_tls->setChecked(false); + } +} + +void XMRigWidget::onNetworkTLSToggled(bool checked) { + config()->set(Config::xmrigNetworkTLS, checked); +} + +void XMRigWidget::onNetworkTorToggled(bool checked) { + config()->set(Config::xmrigNetworkTor, checked); +} + +void XMRigWidget::onXMRigStateChanged(QProcess::ProcessState state) { + if (state == QProcess::ProcessState::Starting) { + ui->btn_start->setEnabled(false); + ui->btn_stop->setEnabled(false); + this->setMiningStarted(); + } + else if (state == QProcess::ProcessState::Running) { + ui->btn_start->setEnabled(false); + ui->btn_stop->setEnabled(true); + this->setMiningStarted(); + } + else if (state == QProcess::ProcessState::NotRunning) { + ui->btn_start->setEnabled(true); // todo + ui->btn_stop->setEnabled(false); + ui->label_status->hide(); + this->setMiningStopped(); + } +} + +void XMRigWidget::setMiningStopped() { + m_isMining = false; + emit miningEnded(); +} + +void XMRigWidget::setMiningStarted() { + m_isMining = true; + emit miningStarted(); +} + +bool XMRigWidget::checkXMRigPath() { + QString path = config()->get(Config::xmrigPath).toString(); + + if (path.isEmpty()) { + ui->console->appendPlainText("No XMRig executable is set. Please configure on the Settings tab."); + return false; + } else if (!Utils::fileExists(path)) { + ui->console->appendPlainText("Invalid path to XMRig executable detected. Please reconfigure on the Settings tab."); + return false; + } else { + return true; } } diff --git a/src/widgets/XMRigWidget.h b/src/widgets/XMRigWidget.h index d4b9fffc..bbe349f6 100644 --- a/src/widgets/XMRigWidget.h +++ b/src/widgets/XMRigWidget.h @@ -32,17 +32,22 @@ public slots: void onStartClicked(); void onStopClicked(); void onClearClicked(); + void onUsePrimaryAddressClicked(); void onDownloads(const QJsonObject &data); void linkClicked(); void onProcessError(const QString &msg); void onProcessOutput(const QByteArray &msg); void onHashrate(const QString &hashrate); - void onSoloChecked(int state); + void onMiningModeChanged(int mode); + void onNetworkTLSToggled(bool checked); + void onNetworkTorToggled(bool checked); + void onXMRigStateChanged(QProcess::ProcessState state); private slots: void onBrowseClicked(); void onThreadsValueChanged(int date); void onPoolChanged(const QString &pool); + void onXMRigElevationChanged(bool elevated); signals: void miningStarted(); @@ -51,15 +56,18 @@ signals: private: void showContextMenu(const QPoint &pos); void updatePools(); + void printConsoleInfo(); + void setMiningStopped(); + void setMiningStarted(); + bool checkXMRigPath(); QScopedPointer ui; QSharedPointer m_ctx; - XmRig * m_XMRig; + XmRig *m_XMRig; QStandardItemModel *m_model; QMenu *m_contextMenu; bool m_isMining = false; - int m_threads; QStringList m_urls; QStringList m_defaultPools{"pool.xmr.pt:9000", "pool.supportxmr.com:9000", "mine.xmrpool.net:443", "xmrpool.eu:9999", "xmr-eu1.nanopool.org:14433", "pool.minexmr.com:6666", "us-west.minexmr.com:6666", "monerohash.com:9999", "cryptonote.social:5555", "cryptonote.social:5556"}; }; diff --git a/src/widgets/XMRigWidget.ui b/src/widgets/XMRigWidget.ui index 8b1cb9f1..512a0ca3 100644 --- a/src/widgets/XMRigWidget.ui +++ b/src/widgets/XMRigWidget.ui @@ -33,7 +33,7 @@
- Mining + Status @@ -130,247 +130,16 @@ - - - - - - - - Threads: - - - - - - - - - Qt::Horizontal - - - - - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - TLS - - - - - - - Tor - - - - - - - Solo mine - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - - - Pool - - - - - - - - 0 - 0 - - - - - - - - Configure - - - - - - - Qt::Horizontal - - - - 414 - 20 - - - - - - - - - - - QFrame::NoFrame - - - QFrame::Plain - - - - 0 - - - 0 - - - 0 - - - - - Node address - - - - - - - 127.0.0.1:18081 - - - - - - - Qt::Horizontal - - - - 173 - 20 - - - - - - - - - - + - Receiving address + XMRig executable: - - - - - - Password (optional) - - - - - - - - - - XMRig executable - - - - @@ -388,50 +157,272 @@ + + + + Run as admin/root: + + + + + + + + + Yes + + + + + + + No + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + CPU threads: + + + + + + + Qt::Horizontal + + + + + + + Mining mode: + + + + + + + + + + Pool mining + + + + + Solo mining + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Pool/node address: + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + + Configure + + + + + + + Qt::Horizontal + + + + 414 + 20 + + + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + + 0 + + + 0 + + + 0 + + + + + 127.0.0.1:18081 + + + + + + + Qt::Horizontal + + + + 173 + 20 + + + + + + + + + + + + + Network settings: + + + + + + + + + Secure connection (TLS) + + + + + + + Connect via Tor + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Receiving address: + + + + + + + + + + + + Use primary address + + + + + + + + + Password (optional): + + + + + + - - - - Qt::Horizontal - - - QSizePolicy::Maximum - - - - 24 - 20 - - - - - - - - - - logoimg - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - From 3deaa1bc1d234bfb08809a7e879782a09b9a452c Mon Sep 17 00:00:00 2001 From: tobtoht Date: Tue, 12 Oct 2021 14:58:54 +0200 Subject: [PATCH 06/56] TxInfoDialog: add copy Tx ID button --- src/dialog/TxInfoDialog.cpp | 9 +++++++-- src/dialog/TxInfoDialog.h | 1 + src/dialog/TxInfoDialog.ui | 11 +++++++++-- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/dialog/TxInfoDialog.cpp b/src/dialog/TxInfoDialog.cpp index 69576e74..76a7ef43 100644 --- a/src/dialog/TxInfoDialog.cpp +++ b/src/dialog/TxInfoDialog.cpp @@ -29,8 +29,9 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn m_txid = txInfo->hash(); ui->label_txid->setText(m_txid); - connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TxInfoDialog::copyTxKey); - connect(ui->btn_createTxProof, &QPushButton::pressed, this, &TxInfoDialog::createTxProof); + connect(ui->btn_copyTxID, &QPushButton::clicked, this, &TxInfoDialog::copyTxID); + connect(ui->btn_CopyTxKey, &QPushButton::clicked, this, &TxInfoDialog::copyTxKey); + connect(ui->btn_createTxProof, &QPushButton::clicked, this, &TxInfoDialog::createTxProof); connect(m_ctx->wallet, &Wallet::newBlock, this, &TxInfoDialog::updateData); @@ -140,6 +141,10 @@ void TxInfoDialog::updateData() { this->setData(tx); } +void TxInfoDialog::copyTxID() { + Utils::copyToClipboard(m_txid); +} + void TxInfoDialog::copyTxKey() { m_ctx->wallet->getTxKeyAsync(m_txid, [this](QVariantMap map){ QString txKey = map.value("tx_key").toString(); diff --git a/src/dialog/TxInfoDialog.h b/src/dialog/TxInfoDialog.h index 0041471a..0de4a374 100644 --- a/src/dialog/TxInfoDialog.h +++ b/src/dialog/TxInfoDialog.h @@ -28,6 +28,7 @@ signals: void resendTranscation(const QString &txid); private: + void copyTxID(); void copyTxKey(); void createTxProof(); void setData(TransactionInfo *tx); diff --git a/src/dialog/TxInfoDialog.ui b/src/dialog/TxInfoDialog.ui index 02b18c82..f9258058 100644 --- a/src/dialog/TxInfoDialog.ui +++ b/src/dialog/TxInfoDialog.ui @@ -7,7 +7,7 @@ 0 0 929 - 631 + 715 @@ -205,10 +205,17 @@ + + + + Copy Tx ID/hash + + + - Copy Transaction Key + Copy Tx Secret Key From ca641d6530231a6344d44d035553340d65935b38 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 13 Oct 2021 15:55:07 +0200 Subject: [PATCH 07/56] Contacts: add contact button --- src/ContactsWidget.cpp | 5 +++++ src/ContactsWidget.h | 2 ++ src/ContactsWidget.ui | 24 +++++++++++++++++------- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/ContactsWidget.cpp b/src/ContactsWidget.cpp index c43a4b62..04426615 100644 --- a/src/ContactsWidget.cpp +++ b/src/ContactsWidget.cpp @@ -18,6 +18,11 @@ ContactsWidget::ContactsWidget(QSharedPointer ctx, QWidget *parent) { ui->setupUi(this); + m_btn_addContact = new QPushButton(this); + m_btn_addContact->setIcon(icons()->icon("localMonero_register.svg")); + ui->searchLayout->addWidget(m_btn_addContact, 0, Qt::AlignRight); + connect(m_btn_addContact, &QPushButton::clicked, [this]{this->newContact();}); + m_model = m_ctx->wallet->addressBookModel(); m_proxyModel = new AddressBookProxyModel; m_proxyModel->setSourceModel(m_model); diff --git a/src/ContactsWidget.h b/src/ContactsWidget.h index e800a0ea..9a71fa92 100644 --- a/src/ContactsWidget.h +++ b/src/ContactsWidget.h @@ -4,6 +4,7 @@ #ifndef FEATHER_CONTACTSWIDGET_H #define FEATHER_CONTACTSWIDGET_H +#include #include #include @@ -51,6 +52,7 @@ private: QMenu *m_headerMenu; AddressBookModel * m_model; AddressBookProxyModel * m_proxyModel; + QPushButton *m_btn_addContact; }; #endif // FEATHER_CONTACTSWIDGET_H diff --git a/src/ContactsWidget.ui b/src/ContactsWidget.ui index 69151e64..8281279d 100644 --- a/src/ContactsWidget.ui +++ b/src/ContactsWidget.ui @@ -6,8 +6,8 @@ 0 0 - 589 - 416 + 914 + 763 @@ -30,14 +30,24 @@ 0 - - - Search contacts... - - + + + + + Search contacts... + + + + + + + 0 + 0 + + false From aa1c805c83de70ed77b3a91d49e5f64acc3a4073 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 13 Oct 2021 22:30:49 +0200 Subject: [PATCH 08/56] History: make sure tx info dialog is closed on exit --- src/HistoryWidget.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index 4499a5d5..62d18d18 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -121,6 +121,7 @@ void HistoryWidget::showTxDetails() { emit resendTransaction(txid); }); dialog->show(); + dialog->setAttribute(Qt::WA_DeleteOnClose); } void HistoryWidget::onViewOnBlockExplorer() { From 0fc5c70830ec2ced98bf4c19fd2a7a1ebc3f97c8 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 13 Oct 2021 22:31:47 +0200 Subject: [PATCH 09/56] OfflineTxSigning: set default file names --- src/MainWindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index df001c38..64c61c7e 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -626,7 +626,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto m_ctx->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0)); // Show advanced dialog on multi-destination transactions - if (address.size() > 1) { + if (address.size() > 1 || m_ctx->wallet->viewOnly()) { TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this}; dialog_adv.setTransaction(tx); dialog_adv.exec(); @@ -1042,7 +1042,7 @@ void MainWindow::showWSNodeExhaustedMessage() { } void MainWindow::exportKeyImages() { - QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QDir::homePath(), "Key Images (*_keyImages)"); + QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Key Images (*_keyImages)"); if (fn.isEmpty()) return; if (!fn.endsWith("_keyImages")) fn += "_keyImages"; m_ctx->wallet->exportKeyImages(fn, true); @@ -1068,7 +1068,7 @@ void MainWindow::importKeyImages() { } void MainWindow::exportOutputs() { - QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QDir::homePath(), "Outputs (*_outputs)"); + QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Outputs (*_outputs)"); if (fn.isEmpty()) return; if (!fn.endsWith("_outputs")) fn += "_outputs"; m_ctx->wallet->exportOutputs(fn, true); From a390bebf08000f6300ea3f9e8f73bcb220967659 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 13 Oct 2021 23:02:21 +0200 Subject: [PATCH 10/56] OfflineTxSigning: UI improvements --- src/MainWindow.cpp | 2 +- src/dialog/TxConfAdvDialog.cpp | 26 ++++++++++++++++---------- src/dialog/TxConfAdvDialog.h | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 64c61c7e..3f830a32 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -628,7 +628,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto // Show advanced dialog on multi-destination transactions if (address.size() > 1 || m_ctx->wallet->viewOnly()) { TxConfAdvDialog dialog_adv{m_ctx, m_ctx->tmpTxDescription, this}; - dialog_adv.setTransaction(tx); + dialog_adv.setTransaction(tx, !m_ctx->wallet->viewOnly()); dialog_adv.exec(); return; } diff --git a/src/dialog/TxConfAdvDialog.cpp b/src/dialog/TxConfAdvDialog.cpp index 8261849f..3ddb412f 100644 --- a/src/dialog/TxConfAdvDialog.cpp +++ b/src/dialog/TxConfAdvDialog.cpp @@ -35,11 +35,6 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString & m_exportTxKeyMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::txKeyCopy); ui->btn_exportTxKey->setMenu(m_exportTxKeyMenu); - if (m_ctx->wallet->viewOnly()) { - ui->btn_exportSigned->hide(); - ui->btn_send->hide(); - } - ui->label_description->setText(QString("Description: %1").arg(description)); connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction); @@ -52,25 +47,36 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString & this->adjustSize(); } -void TxConfAdvDialog::setTransaction(PendingTransaction *tx) { +void TxConfAdvDialog::setTransaction(PendingTransaction *tx, bool isSigned) { ui->btn_sign->hide(); + if (!isSigned) { + ui->btn_exportSigned->hide(); + ui->btn_send->hide(); + } + m_tx = tx; m_tx->refresh(); PendingTransactionInfo *ptx = m_tx->transaction(0); //Todo: support split transactions + // TODO: implement hasTxKey() + if (!m_ctx->wallet->isHwBacked() && m_tx->transaction(0)->txKey() == "0100000000000000000000000000000000000000000000000000000000000000") { + ui->btn_exportTxKey->hide(); + } + ui->txid->setText(tx->txid().first()); ui->amount->setText(WalletManager::displayAmount(tx->amount())); ui->fee->setText(WalletManager::displayAmount(ptx->fee())); ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee())); - auto size_str = [this]{ - if (m_ctx->wallet->viewOnly()) { - return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); - } else { + auto size_str = [this, isSigned]{ + if (isSigned) { auto size = m_tx->signedTxToHex(0).size() / 2; return QString("Size: %1 bytes (%2 bytes unsigned)").arg(QString::number(size), QString::number(m_tx->unsignedTxToBin().size())); + } else { + + return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); } }(); ui->label_size->setText(size_str); diff --git a/src/dialog/TxConfAdvDialog.h b/src/dialog/TxConfAdvDialog.h index 9122261b..c4df7161 100644 --- a/src/dialog/TxConfAdvDialog.h +++ b/src/dialog/TxConfAdvDialog.h @@ -24,7 +24,7 @@ public: explicit TxConfAdvDialog(QSharedPointer ctx, const QString &description, QWidget *parent = nullptr); ~TxConfAdvDialog() override; - void setTransaction(PendingTransaction *tx); + void setTransaction(PendingTransaction *tx, bool isSigned = true); // #TODO: have libwallet return a UnsignedTransaction, this is just dumb void setUnsignedTransaction(UnsignedTransaction *utx); private: From 3082a295286c05cf66549ca9ff1f04533e71c868 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 13 Oct 2021 23:02:48 +0200 Subject: [PATCH 11/56] PendingTransaction: remove commented code --- src/libwalletqt/PendingTransaction.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index 754bf4cc..247b0142 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -11,11 +11,6 @@ #include #include "PendingTransactionInfo.h" - -//namespace Monero { -//class PendingTransaction; -//} - class PendingTransaction : public QObject { Q_OBJECT From f1380e581b81ff0c6cbf13826fe72ff60235a754 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 14 Oct 2021 14:29:06 +0200 Subject: [PATCH 12/56] TxFiatHistory: simplify logic --- src/utils/AppData.cpp | 6 +- src/utils/AppData.h | 2 +- src/utils/TxFiatHistory.cpp | 100 +++++++++++++++ .../{txfiathistory.h => TxFiatHistory.h} | 8 +- src/utils/WebsocketNotifier.h | 2 +- src/utils/txfiathistory.cpp | 117 ------------------ 6 files changed, 109 insertions(+), 126 deletions(-) create mode 100644 src/utils/TxFiatHistory.cpp rename src/utils/{txfiathistory.h => TxFiatHistory.h} (88%) delete mode 100644 src/utils/txfiathistory.cpp diff --git a/src/utils/AppData.cpp b/src/utils/AppData.cpp index 7db6e7b3..09c0ddd6 100644 --- a/src/utils/AppData.cpp +++ b/src/utils/AppData.cpp @@ -11,17 +11,13 @@ AppData::AppData(QObject *parent) this->initRestoreHeights(); auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey(); - this->txFiatHistory = new TxFiatHistory(genesis_timestamp, Config::defaultConfigDir().path()); + this->txFiatHistory = new TxFiatHistory(genesis_timestamp, Config::defaultConfigDir().path(), this); connect(&websocketNotifier()->websocketClient, &WebsocketClient::connectionEstablished, this->txFiatHistory, &TxFiatHistory::onUpdateDatabase); connect(this->txFiatHistory, &TxFiatHistory::requestYear, [](int year){ QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8(); websocketNotifier()->websocketClient.sendMsg(data); }); - connect(this->txFiatHistory, &TxFiatHistory::requestYearMonth, [](int year, int month){ - QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8(); - websocketNotifier()->websocketClient.sendMsg(data); - }); connect(websocketNotifier(), &WebsocketNotifier::CryptoRatesReceived, &this->prices, &Prices::cryptoPricesReceived); connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, &this->prices, &Prices::fiatPricesReceived); diff --git a/src/utils/AppData.h b/src/utils/AppData.h index a5415f36..2560b1ba 100644 --- a/src/utils/AppData.h +++ b/src/utils/AppData.h @@ -9,7 +9,7 @@ #include #include "prices.h" -#include "txfiathistory.h" +#include "TxFiatHistory.h" #include "RestoreHeightLookup.h" class AppData : public QObject { diff --git a/src/utils/TxFiatHistory.cpp b/src/utils/TxFiatHistory.cpp new file mode 100644 index 00000000..a573463b --- /dev/null +++ b/src/utils/TxFiatHistory.cpp @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. + +#include "TxFiatHistory.h" +#include "utils/Utils.h" + +TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) + : QObject(parent) + , m_genesis_timestamp(genesis_timestamp) + , m_databasePath(QString("%1/fiatHistory.db").arg(configDirectory)) +{ + this->loadDatabase(); +} + +double TxFiatHistory::get(int timestamp) { + QDateTime ts; + ts.setTime_t(timestamp); + auto key = ts.toString("yyyyMMdd"); + return this->get(key); // USD +} + +double TxFiatHistory::get(const QString &date) { + if (m_database.contains(date)) { + return m_database[date]; // USD + } + return 0.0; +} + +void TxFiatHistory::loadDatabase() { + if (!Utils::fileExists(m_databasePath)) { + return; + } + + m_database.clear(); + + QString contents = Utils::barrayToString(Utils::fileOpen(m_databasePath)); + for (auto &line: contents.split("\n")) { + line = line.trimmed(); + if (line.isEmpty()) { + continue; + } + QStringList spl = line.split(":"); + if (spl.length() == 2) { + m_database[spl.at(0)] = spl.at(1).toDouble(); + } + } +} + +void TxFiatHistory::writeDatabase() { + QString data; + for (const auto &line: m_database.toStdMap()) { + data += QString("%1:%2\n").arg(line.first).arg(QString::number(line.second)); + } + Utils::fileWrite(m_databasePath, data); +} + +void TxFiatHistory::onUpdateDatabase() { + // update local txFiatHistory database + if (m_initialized) { + return; + } + + QDateTime genesis; + genesis.setSecsSinceEpoch(m_genesis_timestamp); + QDate genesis_date = genesis.date(); + + QDate now = QDate::currentDate(); + + QSet missingYears; + for (QDate date = genesis_date; date <= now;) { + + if (!m_database.contains(this->dateToKey(date))) { + qInfo() << "TxFiatHistory: Can't find value for date: " << this->dateToKey(date); + missingYears << date.year(); + date.setDate(date.year()+1, 1, 1); + continue; + } + + date = date.addDays(1); + } + + for (const int year : missingYears) { + emit requestYear(year); + } + + m_initialized = true; +} + +QString TxFiatHistory::dateToKey(const QDate &date) { + return date.toString("yyyyMMdd"); +} + +void TxFiatHistory::onWSData(const QJsonObject &data) { + foreach(const QString &key, data.keys()) { + QJsonValue value = data.value(key); + m_database[key] = value.toDouble(); + } + + this->writeDatabase(); +} diff --git a/src/utils/txfiathistory.h b/src/utils/TxFiatHistory.h similarity index 88% rename from src/utils/txfiathistory.h rename to src/utils/TxFiatHistory.h index cbb7d80d..87cd14fd 100644 --- a/src/utils/txfiathistory.h +++ b/src/utils/TxFiatHistory.h @@ -4,6 +4,10 @@ #ifndef FEATHER_TXFIATHISTORY_H #define FEATHER_TXFIATHISTORY_H +#include +#include +#include + class TxFiatHistory : public QObject { Q_OBJECT @@ -18,14 +22,14 @@ public slots: signals: void requestYear(int year); - void requestYearMonth(int year, int month); private: void loadDatabase(); void writeDatabase(); + QString dateToKey(const QDate &date); + int m_genesis_timestamp; QString m_databasePath; - QString m_configDirectory; bool m_initialized = false; QMap m_database; }; diff --git a/src/utils/WebsocketNotifier.h b/src/utils/WebsocketNotifier.h index 96096034..c159e41c 100644 --- a/src/utils/WebsocketNotifier.h +++ b/src/utils/WebsocketNotifier.h @@ -13,7 +13,7 @@ #include "prices.h" #include "widgets/RedditPost.h" #include "widgets/CCSEntry.h" -#include "txfiathistory.h" +#include "TxFiatHistory.h" class WebsocketNotifier : public QObject { Q_OBJECT diff --git a/src/utils/txfiathistory.cpp b/src/utils/txfiathistory.cpp deleted file mode 100644 index 73cd4717..00000000 --- a/src/utils/txfiathistory.cpp +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include -#include - -#include "txfiathistory.h" -#include "utils/Utils.h" - -TxFiatHistory::TxFiatHistory(int genesis_timestamp, const QString &configDirectory, QObject *parent) - : QObject(parent) - , m_genesis_timestamp(genesis_timestamp) - , m_configDirectory(configDirectory) -{ - m_databasePath = QString("%1/fiatHistory.db").arg(configDirectory); - this->loadDatabase(); -} - -double TxFiatHistory::get(int timestamp) { - QDateTime ts; - ts.setTime_t(timestamp); - auto key = ts.toString("yyyyMMdd"); - return this->get(key); // USD -} - -double TxFiatHistory::get(const QString &date) { - if(m_database.contains(date)) - return m_database[date]; // USD - return 0.0; -} - -void TxFiatHistory::loadDatabase() { - if(!Utils::fileExists(m_databasePath)) - return; - - m_database.clear(); - QString contents = Utils::barrayToString(Utils::fileOpen(m_databasePath)); - for(auto &line: contents.split("\n")){ - line = line.trimmed(); - if(line.isEmpty()) continue; - auto spl = line.split(":"); - m_database[spl.at(0)] = spl.at(1).toDouble(); - } -} - -void TxFiatHistory::writeDatabase() { - QString data; - for(const auto &line: m_database.toStdMap()) - data += QString("%1:%2\n").arg(line.first).arg(QString::number(line.second)); - Utils::fileWrite(m_databasePath, data); -} - -void TxFiatHistory::onUpdateDatabase() { - // update local txFiatHistory database - if(m_initialized) return; - - QDateTime genesis; - genesis.setTime_t(m_genesis_timestamp); - auto genesis_date = genesis.date(); - - auto now = QDate::currentDate(); - auto nowKey = now.toString("yyyyMMdd"); - int year = genesis.toString("yyyy").toInt(); - auto yearCurrent = now.year(); - - // if current year is genesis year we'll refresh regardless. - if(yearCurrent == genesis_date.year()) { - emit requestYear(year); - m_initialized = true; - return; - } - - // keep local fiatTxHistory database up to date, loop for missing dates - for(; year != yearCurrent + 1; year += 1){ - for(int month = 1; month != 13; month++) { - if(year == yearCurrent && month == now.month() && now.day() == 1) break; - QDateTime _now; - _now.setDate(QDate(year, month, 1)); - if(_now.toSecsSinceEpoch() < m_genesis_timestamp) continue; - if(_now.toSecsSinceEpoch() > std::time(nullptr) - 86400) continue; - QString key = ""; - - // genesis year we'll only fetch once - if(year == genesis_date.year()){ - key = QString("%1%2%3").arg(year).arg(12).arg("31"); - if(!m_database.contains(key)) - emit requestYear(year); - break; - } - - auto _month = QString::number(month); - if(_month.length() == 1) - _month = QString("0%1").arg(_month); // how2fill - - key = QString("%1%2%3").arg(year).arg(_month).arg("01"); - if(!m_database.contains(key)){ - if(year != yearCurrent) { - emit requestYear(year); - break; - } else - emit requestYearMonth(year, month); - } else if (year == yearCurrent && month == now.month() && !m_database.contains(nowKey)) - emit requestYearMonth(year, month); - } - } - - m_initialized = true; -} - -void TxFiatHistory::onWSData(const QJsonObject &data) { - foreach(const QString &key, data.keys()) { - QJsonValue value = data.value(key); - m_database[key] = value.toDouble(); - } - - this->writeDatabase(); -} From baf4fdc67d04213bbfa607af889cb64f5edf3301 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 14 Oct 2021 14:42:43 +0200 Subject: [PATCH 13/56] Remove duplicate code --- src/appcontext.h | 1 - src/utils/wsclient.cpp | 96 ------------------------------------------ src/utils/wsclient.h | 42 ------------------ 3 files changed, 139 deletions(-) delete mode 100644 src/utils/wsclient.cpp delete mode 100644 src/utils/wsclient.h diff --git a/src/appcontext.h b/src/appcontext.h index 46fd215c..ac4db6fd 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -9,7 +9,6 @@ #include "utils/os/whonix.h" #include "utils/networking.h" -#include "utils/wsclient.h" #include "utils/FeatherSeed.h" #include "utils/daemonrpc.h" #include "utils/RestoreHeightLookup.h" diff --git a/src/utils/wsclient.cpp b/src/utils/wsclient.cpp deleted file mode 100644 index d9977f3c..00000000 --- a/src/utils/wsclient.cpp +++ /dev/null @@ -1,96 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#include -#include -#include "wsclient.h" -#include "utils/Utils.h" - -WSClient::WSClient(QUrl url, QObject *parent) - : QObject(parent) - , m_url(std::move(url)) -{ - connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &WSClient::onbinaryMessageReceived); - connect(&webSocket, &QWebSocket::connected, this, &WSClient::onConnected); - connect(&webSocket, &QWebSocket::disconnected, this, &WSClient::closed); - connect(&webSocket, QOverload::of(&QWebSocket::error), this, &WSClient::onError); - connect(&m_connectionTimer, &QTimer::timeout, this, &WSClient::checkConnection); - - // Keep websocket connection alive - connect(&m_pingTimer, &QTimer::timeout, [this]{ - if (webSocket.state() == QAbstractSocket::ConnectedState) - webSocket.ping(); - }); - m_pingTimer.setInterval(30 * 1000); - m_pingTimer.start(); -} - -void WSClient::sendMsg(const QByteArray &data) { - if (webSocket.state() == QAbstractSocket::ConnectedState) - webSocket.sendBinaryMessage(data); -} - -void WSClient::onToggleConnect(bool connect) { - m_connect = connect; - if (m_connect) - checkConnection(); -} - -void WSClient::start() { - // connect & reconnect on errors/close -#ifdef QT_DEBUG - qDebug() << "WebSocket connect:" << m_url.url(); -#endif - - if (m_connect) - webSocket.open(m_url); - - if (!m_connectionTimer.isActive()) { - m_connectionTimer.start(2000); - } -} - -void WSClient::checkConnection() { - if (!m_connect) - return; - - if (webSocket.state() == QAbstractSocket::UnconnectedState) { -#ifdef QT_DEBUG - qDebug() << "WebSocket reconnect"; -#endif - this->start(); - } -} - -void WSClient::onConnected() { -#ifdef QT_DEBUG - qDebug() << "WebSocket connected"; -#endif - emit connectionEstablished(); -} - -void WSClient::onError(QAbstractSocket::SocketError error) { - qCritical() << "WebSocket error: " << error; - auto state = webSocket.state(); - if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState) - webSocket.abort(); -} - -void WSClient::onbinaryMessageReceived(const QByteArray &message) { -#ifdef QT_DEBUG - qDebug() << "WebSocket received:" << message; -#endif - if (!Utils::validateJSON(message)) { - qCritical() << "Could not interpret WebSocket message as JSON"; - return; - } - - QJsonDocument doc = QJsonDocument::fromJson(message); - QJsonObject object = doc.object(); - if(!object.contains("cmd") || !object.contains("data")) { - qCritical() << "Invalid WebSocket message received"; - return; - } - - emit WSMessage(object); -} diff --git a/src/utils/wsclient.h b/src/utils/wsclient.h deleted file mode 100644 index 29a7955a..00000000 --- a/src/utils/wsclient.h +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright (c) 2020-2021, The Monero Project. - -#ifndef FEATHER_WSCLIENT_H -#define FEATHER_WSCLIENT_H - -#include -#include -#include - -class WSClient : public QObject -{ - Q_OBJECT - -public: - explicit WSClient(QUrl url, QObject *parent = nullptr); - void start(); - void sendMsg(const QByteArray &data); - QWebSocket webSocket; - -public slots: - void onToggleConnect(bool connect); - -signals: - void closed(); - void connectionEstablished(); - void WSMessage(QJsonObject message); - -private slots: - void onConnected(); - void onbinaryMessageReceived(const QByteArray &message); - void checkConnection(); - void onError(QAbstractSocket::SocketError error); - -private: - bool m_connect = false; - QUrl m_url; - QTimer m_connectionTimer; - QTimer m_pingTimer; -}; - -#endif // FEATHER_WSCLIENT_H \ No newline at end of file From 3ef10e9b534b44f2437f3f030a2936046c34afa7 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 14 Oct 2021 15:41:14 +0200 Subject: [PATCH 14/56] Websocket: fix unnecessary reconnect --- src/WindowManager.cpp | 2 -- src/utils/WebsocketClient.cpp | 51 ++++++++++++----------------------- src/utils/WebsocketClient.h | 8 +----- 3 files changed, 18 insertions(+), 43 deletions(-) diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp index c4c6025e..a20ece0a 100644 --- a/src/WindowManager.cpp +++ b/src/WindowManager.cpp @@ -460,8 +460,6 @@ void WindowManager::initTor() { torManager()->init(); torManager()->start(); - connect(torManager(), &TorManager::connectionStateChanged, &websocketNotifier()->websocketClient, &WebsocketClient::onToggleConnect); - this->onTorSettingsChanged(); } diff --git a/src/utils/WebsocketClient.cpp b/src/utils/WebsocketClient.cpp index 5855f95d..7087a174 100644 --- a/src/utils/WebsocketClient.cpp +++ b/src/utils/WebsocketClient.cpp @@ -9,76 +9,59 @@ WebsocketClient::WebsocketClient(QObject *parent) : QObject(parent) { - connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &WebsocketClient::onbinaryMessageReceived); connect(&webSocket, &QWebSocket::connected, this, &WebsocketClient::onConnected); - connect(&webSocket, &QWebSocket::disconnected, this, &WebsocketClient::closed); + connect(&webSocket, &QWebSocket::disconnected, this, &WebsocketClient::onDisconnected); connect(&webSocket, QOverload::of(&QWebSocket::error), this, &WebsocketClient::onError); - connect(&m_connectionTimer, &QTimer::timeout, this, &WebsocketClient::checkConnection); + + connect(&webSocket, &QWebSocket::binaryMessageReceived, this, &WebsocketClient::onbinaryMessageReceived); // Keep websocket connection alive connect(&m_pingTimer, &QTimer::timeout, [this]{ - if (webSocket.state() == QAbstractSocket::ConnectedState) + if (webSocket.state() == QAbstractSocket::ConnectedState) { webSocket.ping(); + } }); m_pingTimer.setInterval(30 * 1000); m_pingTimer.start(); } void WebsocketClient::sendMsg(const QByteArray &data) { - if (webSocket.state() == QAbstractSocket::ConnectedState) + if (webSocket.state() == QAbstractSocket::ConnectedState) { webSocket.sendBinaryMessage(data); -} - -void WebsocketClient::onToggleConnect(bool connect) { - m_connect = connect; - if (m_connect) - checkConnection(); + } } void WebsocketClient::start() { // connect & reconnect on errors/close -#ifdef QT_DEBUG qDebug() << "WebSocket connect:" << m_url.url(); -#endif - if (m_connect) + auto state = webSocket.state(); + if (state != QAbstractSocket::ConnectedState && state != QAbstractSocket::ConnectingState) { webSocket.open(m_url); - - if (!m_connectionTimer.isActive()) { - m_connectionTimer.start(2000); - } -} - -void WebsocketClient::checkConnection() { - if (!m_connect) - return; - - if (webSocket.state() == QAbstractSocket::UnconnectedState) { -#ifdef QT_DEBUG - qDebug() << "WebSocket reconnect"; -#endif - this->start(); } } void WebsocketClient::onConnected() { -#ifdef QT_DEBUG qDebug() << "WebSocket connected"; -#endif emit connectionEstablished(); } +void WebsocketClient::onDisconnected() { + qDebug() << "WebSocket disconnected"; + QTimer::singleShot(1000, [this]{this->start();}); +} + void WebsocketClient::onError(QAbstractSocket::SocketError error) { qCritical() << "WebSocket error: " << error; auto state = webSocket.state(); - if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState) + if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState) { webSocket.abort(); + } } void WebsocketClient::onbinaryMessageReceived(const QByteArray &message) { -#ifdef QT_DEBUG qDebug() << "WebSocket received:" << message; -#endif + if (!Utils::validateJSON(message)) { qCritical() << "Could not interpret WebSocket message as JSON"; return; diff --git a/src/utils/WebsocketClient.h b/src/utils/WebsocketClient.h index c9e098db..f9aaefc9 100644 --- a/src/utils/WebsocketClient.h +++ b/src/utils/WebsocketClient.h @@ -20,24 +20,18 @@ public: QWebSocket webSocket; -public slots: - void onToggleConnect(bool connect); - signals: - void closed(); void connectionEstablished(); void WSMessage(QJsonObject message); private slots: void onConnected(); + void onDisconnected(); void onbinaryMessageReceived(const QByteArray &message); - void checkConnection(); void onError(QAbstractSocket::SocketError error); private: - bool m_connect = false; QUrl m_url = constants::websocketUrl; - QTimer m_connectionTimer; QTimer m_pingTimer; }; From 0cdc7e57f28a6055cce2868449b4e3fea6c45526 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Mon, 30 Aug 2021 02:01:07 +0200 Subject: [PATCH 15/56] Bump version to 1.0.0 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5d465b9..602e8894 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,8 +3,8 @@ project(feather) message(STATUS "Initiating compile using CMake ${CMAKE_VERSION}") -set(VERSION_MAJOR "0") -set(VERSION_MINOR "1") +set(VERSION_MAJOR "1") +set(VERSION_MINOR "0") set(VERSION_REVISION "0") set(VERSION "beta-9") From 466909ecea07b881060d0bf2c82ab86fb6578834 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Mon, 30 Aug 2021 01:58:37 +0200 Subject: [PATCH 16/56] Move to semantic versioning --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 602e8894..45010e0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ message(STATUS "Initiating compile using CMake ${CMAKE_VERSION}") set(VERSION_MAJOR "1") set(VERSION_MINOR "0") set(VERSION_REVISION "0") -set(VERSION "beta-9") +set(VERSION "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_REVISION}") option(STATIC "Link libraries statically, requires static Qt") From 1ea74525560cbabad1ca7ba586429e99f0e7d007 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 18 Aug 2021 17:50:04 +0200 Subject: [PATCH 17/56] Remove Beta warning --- src/WindowManager.cpp | 8 -------- src/utils/config.cpp | 1 - 2 files changed, 9 deletions(-) diff --git a/src/WindowManager.cpp b/src/WindowManager.cpp index a20ece0a..717e1d8b 100644 --- a/src/WindowManager.cpp +++ b/src/WindowManager.cpp @@ -102,14 +102,6 @@ void WindowManager::startupWarning() { this->showWarningMessageBox("Warning", worthlessWarning.arg("testnet")); config()->set(Config::warnOnTestnet, false); } - - // Beta - if (config()->get(Config::warnOnAlpha).toBool()) { - QString warning = "Feather Wallet is currently in beta.\n\nPlease report any bugs " - "you encounter on our Git repository, IRC or on /r/FeatherWallet."; - this->showWarningMessageBox("Beta warning", warning); - config()->set(Config::warnOnAlpha, false); - } } void WindowManager::showWarningMessageBox(const QString &title, const QString &message) { diff --git a/src/utils/config.cpp b/src/utils/config.cpp index dd0b838b..f66269eb 100644 --- a/src/utils/config.cpp +++ b/src/utils/config.cpp @@ -20,7 +20,6 @@ static const QHash configStrings = { {Config::firstRun, {QS("firstRun"), true}}, {Config::warnOnStagenet,{QS("warnOnStagenet"), true}}, {Config::warnOnTestnet,{QS("warnOnTestnet"), true}}, - {Config::warnOnAlpha,{QS("warnOnAlpha"), true}}, {Config::homeWidget,{QS("homeWidget"), "ccs"}}, {Config::donateBeg,{QS("donateBeg"), 1}}, From 2c07bd8a3ab517cdd9bbd48d885502bb43306dcf Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 18 Aug 2021 17:48:30 +0200 Subject: [PATCH 18/56] Windows installer --- contrib/installers/windows/LICENSE.txt | 28 +++++++++++ contrib/installers/windows/appicon.ico | Bin 0 -> 105517 bytes contrib/installers/windows/feather.iss | 67 +++++++++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 contrib/installers/windows/LICENSE.txt create mode 100644 contrib/installers/windows/appicon.ico create mode 100644 contrib/installers/windows/feather.iss diff --git a/contrib/installers/windows/LICENSE.txt b/contrib/installers/windows/LICENSE.txt new file mode 100644 index 00000000..1c64b26a --- /dev/null +++ b/contrib/installers/windows/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright (c) 2020-2021, The Monero Project + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/contrib/installers/windows/appicon.ico b/contrib/installers/windows/appicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a2092a856230ae3209dcbdd18fa80cc18348709f GIT binary patch literal 105517 zcmeI51zZ(P7l$t(A}DqvZKD#Rq?CXt1_;unlu~vpV0U15cMD<%>ZjsszqTl1Ct{-@ z%=bUbE?y86U$HO#9%tv?jT3Wb=g!W|ibPVPk|O$(5~+y}R27MWupJ$pldoS&N>rzs zNMvEb=dDDdcB)b$9i5!~nmQs;-_AHvjn9`CiIm4kiOM6T^K~sD5*?W>B^u|@zLlav zO$D4b;ny$yAJ5n2vt~ z7A;Iaxl3gIZGW#l0_wESIFj@~)3h67_W7nN)m_FOxMWI??fPbmsJzWY+35OR8i}kc zgzm1?gQY$c>6E`eW>D0lDTl+-pE@ZIUHR&2#s%%Mo7>h{Z59wPMP@*Q#QW(ghmKr~ zJUn(&-A94jPKh2ids#vAiR{#JvF@S=6Sv3NzO5KN_hMx4v77o0DWNvLdTEj4|J+1l zHTu;UD*9J0W#i)#$z#rB)VJ52+o4Tg_uKZZxA$6m*ziR=+wFdLAEe)WwzyN{7Lz@W zrL~y&@}C1kt)C>_s~o*h6lu`*)&7601@-P`zHZZ={oa9ki`SpNKdI9C_SR`xI!kO7 z&C8{j9DNZL6c8*`RkZB#p;n@0W4DTH*mR6^+5trw(fGcK3zejwzu%G?w%c1dYOl`N zSKWrzOIdX!ZouJ_m5#2D-)`Rwo<-^m8ky0*A&kpHxC zt)vyU%UtQGnvv?Vxbr5xrLmL$DcNXuhGo>+fF8PWE#p%gXYF07R6b@yOtMtUq=Ro3 zOFh58tj93;qU)v zXd~ZvnsspGm~#=OQ}%4|)X^)exvKv23cTa|rPRi|EjNqSc%2btqu;>S+hS$M11o&1 z3_ostret!LvbJG;Wz@Hxj*$%*5z=7LBpJ~eQTDy6+QYk^3Ux-HdF|1Et|Fsxs!Bqfdj{L4ACzzCwn)QawNnX$kxSFF{f{1ul8cs&-ZUtWXOBu- zRNiX4FnXwHdrfzzki#RMj43sHYxKsoW=GZ@QknTi+iZ$eU~J>=(POV~bTZ$;-u9@k zdlaQ;$F*LL*5QY`qxg{)xh3V&l1dJPmR^#Xn(X3J-*@4$S)-aZH1|2%wEwGn8oe%N z#2X)RRo{3*(Z28e{_Ip&!j@j6BBTLtM-DPg$p`JbJA0Bu^e&;Uz<+1Nq$h~Ua z!TXZ?{^(^}rF*X5-7BNOnn{aADw%4|Rrz=S z^z$60c6$HYM@?>?zPj4^ZNRa1hVyT@%?|G~JURB6P9tZZu*ZEX)=R9rJ2~}Pn)BSa zYUYhK-0HuXY4F6}Hp$#PJ1Og?eC*>%Y{-7;Wz9z3M2M0jbrC&W;A}W&mry2pK}=(l z2XCTx4ST7tU{-7F!xvVO>#1n;mmV*wIMM4_V(lx}SFTp^RE}&H5&inW+v-=l*KH!- zU%jTxb5*SuDiyugx+*CwsChMZeYlLv=H;=`C3^S0y5Yp_A*#=Fd~iiM{{d&%wLbMIOE1l@gc3Q8huMyy-}Th&@mBBCB@3Y|+{{Wof3U>VngQ^y=L8 z&XOO0wZyp4tYHUgXvAI}Td%`2<-NLP%+GAOUum7$e7t~fl=iFqpoXMrY##=Mr0ViEi0*?cV)-1c}fFS zV?q6%rXKKqX04O%Z5=5!wZn!>qg=dIW?FYY*?UF0V#C2v&t$JpZF@1WY^N2mA3P_F z)19Hyx}xFbfbcijO*FHzQ(CU}aeF(Vj>2;#zFy2Vdb67H?p)tthqY!%MH3bglH5RO8udt^ZjUe*DoWD{U8(7X#uW+nqNvnDFHM z?Ack0+l({s_PSqv!jS8BajV~!%{EM2wLQ)J_+(2DmmsMtgA*5*j?3C{qQS#k2kTbY zch>fHtzJjXhAea+DnF|2aQ(IBW_^cyEn9t|d&5WjWVM~+=9H>hxo522`4`$x4oB>{ zn7Cg)TD6}?&phFO>2JeiAxvCtA4q_YWVGFm{aSsn5pw9?y-Oho644 zwM{h@?+c~JM%Iq93iR$J@4i;G^TP!#r#&p28Thb7*!W6oYq!6!<(O6TtR?3@*ro5^ z+BAHZj>qM9iel9;SAor1Ug>#r&FnJK{Y$!+wdi0Hu}(ow_4>6VAyRi{Dy~a?r{$~69sKb>2u(? z{cB!rx+XQv?)AF7p>)qoBns3VT-D;ro#v(d&WGISs?q80!pKI+vl^`lyB6GI*>(5X zed|rQ665Ubd3jajFx&Gno%`Q?ruTBu+RT1y*B-vu=FRG(FZQP&tQ5XW+rztYwdg0R zW~pybBn6cO8Qp(t+T4t-DL+^R@|-dLj1nQ8T4AyxylJwyyqvENLl_^ z+S)8Upq+L?xA0RJ*HvpXbdJL4QXQO|4%oE8wMur62R&!j={RiL*)g-~c6qv?c4F=0 zRTjOt=yPJI$;kzYH}A$iTB^2aw%nrqJD*mwSU>*L8ZUfd^qv0Uk7arcol~z{MxDNn z8fY$@TF}2v)q4vz2050kT4mvyyShIAo~SVv@w54aPlBz1>b6(y$|;>*)joVuT@5?e zC5M|%uNyL;$Glao{{7#m3FB2pq_b7e*s#@$+}}=Ew%vX5F43ud4)fYeoeh;U?0>Fn z@`$>n6WY|T*}LRAm4`i*8%D)mSQ)GQNI%0{$LiYhN4-L`6k>a=FLxrkP4o_#QpZ~K zmwuP5;9X*4#XJQ&)KgUy(&1ngO*Sm1isRt>UTs8;n8VJxfh&e5TX&Fq*62Z}ep}~_ zDCg4pVUNJDHm~Q6bUC)f!u-{ajXSo#?P8>1J6!&#amMm`Hs%7vk&bbh-P6de6JmnQ*Ml!gZ;~GTX1&VB%zZIzBl5mj1=8bywSt zUlHH=*c7P&x`~mc(#%zx&yZU*Xl-MIHgzLq#2OP^zOb~5X3OC<- zt(wx~4nb!ZUf3*mc42^H+t*D(rko6Ox$0SZ_Le#uRn_P84(y|pG$;O~!Cdb}p;8xa zx=p(=d`Mc~$17Eaq-Py{appve%qC6PsgRZ}TV8tj@8g3b;*C5Ra-2qvZbF=tWWECsv1|j zZrrc)CV6?%Oy@oGdkvZtZKFM4sk3>V16r*`{#q|5J=*(^$JLo#W$Q$rQF^~;(#cG@ zw83VHtL`lt8ocG?g5GJmx0fG2eE3$Ewb=>7WbV~%ziZQm`O-Q&+1+j;U&U9`2g}{8 zhNji!@|8uQF^Zxdr;~>_Y8!g&&iE@Uvb@*) zjcGkLDAu`jpt{?u?A}+)ObeLHn%8z$TOT#&V7!6nM*Fi9CS_~2Svh5-;=MajH#=(I zPfScaP@ojdp9-cTnO)dN!bnpjtM*N`7DS4Fl*?ZkdETeffA9U8CQ{I2yG z+f@6iJ?t&h+FqFZ@Ya#wf80$y*QqwUlG^Y}%(?U9Zn`aSzFM=o-2w~N^Re|j6#UUo zZ2bZPR#dVuOdq7x=a`P@Wc9gYvctS)Mm5^}kM!An-NPE4T=guenM{f?S5_|RBoEhG zp7Pr6!n8B%!iH>>J0mCS*RrGKrFRBHnyzxb@5+v;KRG;0_Uy&GFKSpcKE6dhp|Ne! zk&+L3Y<|HuE4bckSHZD2HB`8Qzdp9yl40Ghc)CPgPK*$pRShT``aG*)_0Td;6HlwF zzOEJ^Rr_dM*7;Y;4U?Lv+?bmYId-D6W7`c26SPNkUpv_G?1xP4v&+4Di<;LA-#Du3 zdDm-~_sMR&@!{IG``MP8XFMC~zuH0RwtMj7{R;<8GrK!o6jA!niAN(t=4U%=*YDLz zZ?*Bu)3aY0n%!J~EplMT&F2TMUVNnQu8c$fPM_t__H}5pQ-c$BI9pU4dv|`fhx_`k z$(r^&%6f%d!jp;j&6~w)L@!c{i)`$cp64FM{bD`WN4ojH`?`O4(z*G-w9|)6*-jgm zm?U*Qc){(g6GPiwy1iVZOOue0QxA4r>(^R#&u;BC+tb!O-oH_`L;qX#WwIUiC0rygHf-}m;TY8BnWH=ejW zc>eKe%zK^YIj4y?ujo!vQg{=kbbq~k-G2t@KRxGuLp%LlSo>7NZR#7lxcZdyRM8(T zXR`HKWwX{^aV_O1ob^0>&u_~8IOCEJL?cVr@tUbAbQ{tIL9*x2 zNu#Tp8cDN)G^B$g+Fe>x?Ldq4dBc*s_i%E#nLVVLW&hSuZj0xfTVJt*ORf9s-xzFZ z>}eadY|72#HJiO}9;l|iIlTX=slPUI_#vYzAD^){<8>{(C! zy)Q%U-yN(f3Z8U-@Wc%s8(j{U4(+TvGCuDwISPsGB{S#WxosZbs=I5QUIx(ya*u4U zOz_y=YMz`z3*)qN6UJ+}iaIVoocwN}|Naef>66+uYVR{g>++i>DvKL6jtm^q@7m7T z-nPx<_gzaHC^|N2`(mBgo2tsnLjy)7A8$75llH_!O)1x#b7Hb&qLbBb=ldtB=%Nzxw+O)w=a_ym7ydM#%7k zN4pFf;;);Pw(@G6(>;yXk&`U$Nc-)pXP~s}&51)LMV=$ticGt2^S&$_o#&^}6}c^u zC#4Jn@7{D6XWxBz;?doWrl(nL3f&Vj|5X3=jarAC`C#vztRWjHx)qmIE4s98QoTbd zSN?bV!q#y|c0Vy56uDI{q1n+F<~p&;XOjlw7uuDsCfx&SRFJi+h|Y&&Vq&Hr zeNk<1`mNWuWc|nMstgS1D_1L4w4u_=1gC0#L!#F$zMiFdvEP%^{iSwpZ}RlvK;`k? zsygj&OuA>+rmN1~`R2&nUhg_5l}riG=)o-eGL6MjJ%*Mymq}TF zG~Q!o>J3q{Qh<`^y5F{}c`?e;<*Sa46D`uewkf2Z#wU%HiBF`gPn!=&8{0mxQuJgs zuaWWVHl>^Ew$B{?A-US)L3_uH8BKVoY-8^JbL9lJv`V0usa zV-uh1zt+ETN)(}ct$mHFQPS1R#m+r<&M`E2$*MAnibkRkvu(Xk4`5p=3`!j?9kN`$ z<)s%{XO$;6cp6_?#UOU|zI1K5t9OUV$X)0YR91HKg6pqFo!?l?Tu-k{_~rN-7CXz` zju#~dZp#`SGs$xExU-Y4mA`TJ!Njm3efspNb>d;l!4aJcGMPfZ(Mj9~MbHpW5@o4wuYH-Ht-Ze{>>kEW@w7 z3=M*V?ypNcy(_)Ou{4W~rL+V0HAh+KlKW$w>so9%Y;7G)P0dSV;~qbJ_;7c!ZoR4> zN{V$|a5sxu@^%|zS0%>EOdb36e;TjN7Z|V33Qka8g^p=aYn%4go2>kX*T;4=eSltT z>ekNrlM-n(5pnPzND_$!Bo>fZKw<%j1tb=bSU_R{i3KDUkXS%s0f_}97LZs#VgZQ- zBo>fZKw<%j1tb=bSU_R{i3R>D3$$z3PMU}2e@OoRG7G@>GCb0Mnd%aCVF8K%|Gr|- zwryKkP>O*6FZ1j|8p)%JRDlqzgKaZF`w-oLB_VwOBU5{OdwHNpKn7Q@L_3CfxvmGEE0o2EYAg`_jr~fC5B^v8y-xMm;4pQ0 zZ@N$LeAbl5r0aavn#T&M84)loRh{tczgZJyf$CVI4e;QJK}w7RRPPhMz=>xWQerr0 z$@AHJ0orYHK!L#ibYCH;jv2^9*IUFh0x3atQ#T%0q{98FIkpKc#C|oNQopIYid}5@ zz8#Mn{2jsLkJOt8p2r&TC<_;d4J(0K1njz%X9LpZJl6QG3W59~9{MhTM_o8qK`HP1 za!AWzf1!RS|HkuB{eG8c7E)p`t|!#*)v+y26m)*cXCEyf@&89#{E1t2!?UFb`%z4- z?P%sr_|ImW5Zjk{7}BEf4*Naf*@kp3fx4j%;Tk%P>gYZ^tC1cg5Q9|-Y&(KP9;(MF zPYCVDp4i^Za~`SCew&SLg5tX+Pqj8}+DH+9qR)y`CyD=y)0)4>xf=Fj6@e0=)tlK7 zdi_~vqrq%C(S95|-f1$sOZd%XjOa3(v7mW5;GR%WeUOQGf5~$S=^ma?q@kV1p6}-D zHz0`U;OII9@hY?hI$)cix?w-hJfvfJMj|D~fJr^J5qCyD>U%Od>V z3^)+*{Y9QTNExBmkFn+ha&Q>O7~wLRG29n8s2sqjMddB_CH8?fgmaf6-tbp24|gBH zHuVdyzqEhNmXle$qtYT1vht%mNbs7iO71?{oNCgFswcf#E!xkRIWA zfs}X)xcG0}hcWHGIT#FLj4&F)m|0W~tw%G~0d)f5Gd+jSxJRI!$AK8nts8>pWPBjd zM{w6?*okd~lkWiPhug{{#Q$#O*Yi++fbjeM7Pg5+i2WKo^!!GO`13qgyc)sxO$qqc z9~|a+BKZ2hq{b)G+?r{@H{ zd6JP5Gm6(Tf5+J*{{L(IToS&nL?EUkzycnwPX8}){x!$w^m@mPdOqQ3+v5=H)O{0& zgF70Tz{!6+^BAyOV5G;uJ+_449mF^F1)A|tU!KsuKa6c+BIBif*=U~;r!5J=Wr*)ez>p^z=?b3h zNQu#C`_<)96ps7bQ~2JOs2%!da^3KEsYJbqjQ91sXEFk1AO|W3#Z(6LBA&5kVfQ&~ zJmxXN)@>TI>okJH+;0an2|C9SJU5eZ%=?i0n*hHL2B8nhfDrr#zm^5`3{DHa9uMgE z0I`5)5YlEm6@}ye_Eh5kzoP`G2fru@DiH9wD;UW057K`$y1lxp8S?r)Q9I;K7GMPK z3m*wfXLSJT2v#>Dhx*+!P)`tVJ^K!gmX^@?Onh z8gPUW#z7}ovu>9-^dlZH{hrS`y3WgH9P^R#{Q|-BvWEoD`Cv>4-b38W10@16J`o(@ z5!!(?w#=I6uN$8OzsrM~1pGZ5Z0BJ}i$=Fs4CREOj4<3UIJmNa@_=>&Uk)-pmI2&5 zbi1c<7<9eJtUZ@;v~r7Kb}nN%47(*ULO<+5?){|+`i^wahfD}Q7f<2;p#ifqO$jmo z(|A1^H$d%xN#GwI;rU)9@-%Lbs39Epx1 zc*&EAv}pJIFYH$@=n*3t2Hapee#bZry>_z}o^c#z9t#;^;=7GC3%$+Jq{o}j#(y)! zzwzK4#A05&F6e{LwFx19`S@2xsthL&193cakP<_HEzcj_{)2BFfzZA$7C#qOUUa)$ zVuX?R4rbSJAcv*v0{DCrN4;*(8Bss%^F9~94;Tz&tOcGC5rz1_JDwHQBLx4$ukxTK z4~^%Tz$3K(2Ow|2^GE;wm-wF>&+}LLA3oOrfjpa#{>xJ=@t@PaOGEoEjcIrJmo@YK zABP2EooL~{ni+WS<7T9hixB@=s0%W9yb$|k39L2OeU zKxlv_JVHNk6!J}ZDhS8@DJgv26wKkdfwWlsE}Vz_UkB}bqPfpu)a@HN>^erF&W~c| zZi`qG{~H{2gP!DGTj)okegbhHe{Q>w`u_ubpU>KZ{8bU-BRPEsP1H`60SIcp2%R z^#?ym{9k}e|8)P?^1VANhkLt}{q8eDrSk*ukVDn~G0WizQww;SLtrXX3w*|*9{il; z@FF*6g_x&QEATc)Gv5Tn`A`l^x23u5d%7PGnmCX6al&?PKOy>yKNs`j-_h@Wi^v=h z??M~!EVg^{)Gff(1%3qo|Bq)ZejAkMku7lN-|$0;|3B}vKhgj2c}-A{XBSd%@2s-l zJ;Xmj^}quTl`g0UcvQPQVjNtZ@C5Y&K%GFS1rk&*q_Wxp4_Li`yBy7Y_AtW8YYVe< zoyTG5y^HDlCUQ{RbD(`7#JqSa#6LYB5RdWCZ6p3OEF)Pufp|a2GY_dfPm%lY;s235 zUWk2~*G=Fhe{Y z=RYINhB7Y#v0shnKmBHd|G9ZSNWb`7vFN4h$L|Th*97ee`2ByNSo|)Wr;hg}*4%A3 zhn3R=X6JxOeV~5l8yt20?-tqT;$`A1{Qm&H-^tSs<58;-Ki<;6<`pIW|26&cy``$@ zol={q<$I?aV)YeIrVzK^ni@E-XN0NKB4*QZBu5jE15Cr`21hOYo*-2GAN|Mp&lxj@ zF{R?||18A$2cCgQ3CHhk%ir<*694~>Ui&tgYkJ?21~P<_&mC_9aUKKGc|Lqg+-rDW zWp%tRaF{wRM(i(UCeBNkk;`(9`d%lPn$Jy+-1tY|0mhJVh~MA8v`ynh3B*6eJIxU# z&Vao9|LK&d+b3PaUF6a0CF8sdVg?^n`sPjj02z9=y&#lj%i+iq!=LRZ3 zJ*epYKea>OCghMlwm~meoky}h;%5qx#Q&c$&L8?!6}RN7z>DvUi|xy@t-Q*7m$rOC!T>*Jfo5N6Nr5o9?4p31r$g4zBB>&UwMwmLyV6{Cjb9}Ae#mfL~({BKwF91Hy;yH=58;@oIs!Bd8zycEg7hsgX z=p)2<7oO=zDej3(@Ky0&-TgXKcDu~c)Oi&%bDYG{)OjN#v|RQvC6DVlc;Djs4!)-U zK)epXc(P249eYn8-l?u9Xzqa~Pno~Sh2Nwq{5+Xw+t>PBeE%BmNleY{0*6UQ%&+gz zouj_%2}WqRoy#px)Q|AB{YT>dM_=%Jl`fCo67aV@kKQ*K;x9c|;{U(T@p3KW)qyTi z$@yXyQQ756ws<^$?RFiPgRG^)XbwZC)vReJjN1YQok!?Tq&`LQxc|J!Z&JD;J1=^85#K$NxrUp7&$?Xm3g9ZoW$g5w!hvEw<8%AGDT4wuWU zit9Cws%|%O<67PIG$R^y+QF>p9Hp#cnS#v@j&f~}GNOEke^@2w^BhzT1iuq`9pyLhVj z<%EQIKjL~wg4#=$sTC8QVK+b$q4i=jk)eA&rn>bdpRSs4T!%*iB zW5yk4qrN}LQ47BVzZCbv_tXXyo-Lz3FQ`2@37->zi2bTOa>ZwvzvE2syDIP@;P>2m zAKSeDi=I|CUCe5kE#$CkAIb=0$0_LhxxztxL4tq3lnU(t>Juo){}^}f#$)n#c60cQxrahedzMNLZ?FJ=`?=W*y+4@T^V za;TWiU_^DxWlXX40gj4J|DvBDi9?9*BBZa;4!l92EkONyZHVt{mA~ig@Vh;@MZo88 zit2Y^Kk|JO%LyDNwgZ@@T?B{ly^Jw_KbWlKDBJc3`Ux&_6v6Mpz2XM;ox_v6uIk_8 zq3^4y#Q)#dzQ6j};s4g)8iAO7l^eHT`2D9~yo3?8%%(7NyD$!&mcv+miwPWg&VxFD z>VZ<0)PJ~(ql(*gMu_(r=c^MgB2O#<&3XRl^)Y`&S%KeMflCB@_9BnpK2ru1ao;D@ zEygiBdvA`0mZO-G>0FM2&LeBOhLtp1&mpt}xH3@im=YRmOboB&nk3638LL;!g+${2 zVm9b+b~^O`Sc6jp%EGgP;BY*T9Z1L-EiLre!p>XLe8UIDHV5Al<=P+bjdACC#BXcR-%N0c z|9>-w6}PyCOHD+EOSck;|9geSDSfVPK8a~tjo>h{8Ni78mZQ12r`Rv?nYttYd2ua9 z$e653lCz4NPAJ&!F-7cGAc|W}$@vPgfZ=ozt5M95u0;;T#nAgzRV93;EB5{3~1|l|bx=^RzCaq>|mfhXpYH6TbH*;1}ux z$Z41JeywIUotd@j!=c|2ZGX%090mBc&~~73PWU?saD5FTgCz<)k}>g>r^Nq7 zwbY-vpW)I9h<$wmak@u{)yk&x8ByC}JhN^c%%O>Ay%jOuhxk(bV=l>io|}mI^*r5> z+7XC-86L?q-Je;q43?rE#ytSQ_uB;j3vGTaJkxL0#*d?>`9!XdrzrhA@bO!oGbmfb zcy!?NQXD^1XUVlcT0r9ekGA;Bw@_b?1V@P$wtew@uP>hK4?%lx97jR*KGhe9^K1e! zo&c6|7%WvsokB?FWd0`QO5*?D#8KZ#O!(g)%plBK^-L%9t%tlZpI92ECCl>1KNN={qc7BF0@2H)urmUi9l6Iv~YJ}Y6gB%6SbAK|;3 zJQI)--9T*~$@*Hw?UNsQK8gQ-q_uy&YZ)#rjW{k(Aokl4h<^vfekTI)-yYw!;c0@D z&;(_8e(U#_{(27i`4UMPED-@Io}Vv}M1mh`0g3;AsJSHfZKw<%j1tb=bSU_R{i3KDUkXS%s0f_}97LZs#VgZQ-Bo>fZKw<%j1tb=bSU_R{ zi3N&m0rrRfWvk{@>`VD~q68f9PDVKD)4WKPy%XhrpDp@q9$$-O-U0r6*`n%PUO0We zJnhc?NJ8dBK;$_qlmqocwv=@`8Bz^0~Y7-+w7D9M1UtbMw4<`SVQp zeF>n5b2#HVJwd1cdamzr9ORJ#>>y54r;H;CZYOC@1iIThwuM3-o{vlfgmgP!;JUumm`Q zj)2O=L2wVu2EN#@aho=6ewli-VJ}t@G}G$MCTsU)@rHxhC7a>wh1(Ry{AMy1G&heO zGNF4KxB<3w8Gog(OVH?G=rszA1uMZ0upev(RK6)bsP51MYS6zN^cOE8KV=&_SAfpC zz#WVScfi}meHhd0$C&kS#@dc$%y|-Hp3@lfpPknh!Sg=64~TiP#-sNdD)SQo`8gKE zgMR?|JRJI}qx}Ce{uYc4{hLDH>EJS8h0(M>V^-0OxuZOEnZ+2&Jr@^2v(uU1=!?%? zBNp9(o|k~2KgD=&kPLplqXmVLLT5$jX$ck-rtjxI*YEX`S@pQgI(6T~9Nc>`*NA1z zW5Di%9s~E-(q*7;1KQ77c?*1h}XXwuk!(<^8l3v z>hk(2f6%`I^llHf1NI5sUNOy(*Q|Ev8z$JK0qj9`(Cwba%;0ky_qoi@Wh^u7mc+Ds zykb;8>Y#pv-U9mrW@ifiSA@O}fa*WG-vxr7^6pgit}pGNZ#;PUiQWa-0-tFHr?E!< zNzBx9J8Rvs4>R|RW6eTu=ZO=YzKj_S%GI50um^BDqWo8ezAoSxApL{D57i&KDgj4u z2;2vm;8Wk{`>avGO=jR7&rID{u@)X%nO^5(OgH2Og8%okhJkmvvVyvjs~5%Z1qJAz0sU9{&tjjnZ=m-&;11M(g#LIp_bg?< z``4BI9x#>851Fd}W2P4H1f(*xz-LT7_&M}?ksAkEfw!5N+hS(jX$ouM8OPaz%7b7# zsuStHkY9g2w1+GrbN#;r`r8+zAN2koK$Xz8bV1)2*JseXCg=dJgIBeD?=fXR;yzRE zOg!LF>GF^f8lCSjEx*gm!gVGy@mPV&>6O#ckaDDF4FNa$_@ zY80dkbhiODq5pSVn@7p#ZUyLU2UY;~B{aN~n3iWEGj!X)%w49lrik+fe0dlAE~JI% zpN9B9YZ=LwL2qqPz98M{Gqn3;zIVGH`s+jIabMC~_`b16A~Wr@jM+K#X7xMYV6}su z{j+|ESMvdZ*NeUYkI=px@_G1LyV3!YPIsxh!KE zJ~vn`#0b?3g1-4vv=asWv!U|`FcAE-wwz$sn%;M$fh=^L4X%L?pVM2z`zq7+I1JrU z-rZtZeXo;D#rHNBE2;rc`Fiow_Ceho{a-=nV_*pB4u1MOQLDP&tN`?Ddf!e_^11T{ zFrpwEXn0>^jXjQ_%&&moSF(Cur*d_t{Z;555may)fd3!k{=XAC4?ubU>3$ddk9SmR zgNfBVZnEm0DXfOqEe2Z@sDERRW6Z>54YTPuimCaeFlFDoc2dUuKs7M6z!<0<`Zl3X z*gJ;?y$Y}61etzRiuZDALHB4N{_ft~?HrrjgiTVIp4$PG`N_<<(_E(RlU$&E=sv0H z|0uuyNvI2Es0BVR0X}Kh{3u=XUkC4|lY`!Uz|q3=r_Yt#ud&*0XPITkiL4oX-w@?r zi1&hGgxUn^Xa_>)Y_Jy$0J@|*$S=lE%R@HT z4>A=m)b*cf8=!rZts40B19Ya}OWlF?PgC+U7lyu*!PSEF7CuvUyUZGQie+{ky0iMQ zLoK%}TpdwZ{YZ8}JMn``mq%}*w>>DZE%zC*zWY~lE#u1rUA(6^tBPwyhKdWF( zXdjS7@BJuYyn@NLSj$RTZegWucQeJ-39Lf9BdoeZBI11xGjLkL8g|-~M|Yu|(0wwW z4GuwXAN*cY{;o=V!^xq)Io7$J@ws=Nlr~wyYMCu$cI`u%R;O)D$?aN!`coMYZztfl zgB$o|btkhy_mSX}cLR#sqq^A?X4N``X<1BSHSKq>N>2Y4sJ{>gCQN%D_25zZdKLFeSd>LePB% zcqHy`VVm9oSI=@RYmWCsV6FI^_W~I&$GhFPuyXB=v8rx2^4s7s%D{AB^9>^X&_kj7 z6mVD2Sv*xVUd;4udNX=ArjqI0-1j1tFj>h;nytruAt}EN5}*9LJ z87*PujTfTM3uX=Qj-<+_a|+a7w#6E%3op8f~=hmr2!r;oGvn&JTcBcXqucSDvn zS->>$?q>_T5Zw1Bvr?aV4{RyR*ehl1_D{xdrytS>{WZE3XL?xX^drW8YVZ8G7HPDflJE9u!&M{5#@(Akcpl z=}+&2)@~Wen%fM*J7K1MQs-fx58yWT-wVCR;azWX;J2#p@~e;c*i?Z|A*4UO7thER z1D34&73h2D{1&mk2Dtx8@3})+wgTLH!Sq`WXKHvKkWe4sb9y%ny%%OFY(?+<`mWE~ zKJm{F_yGE;gDw`W!k?*|Pk66rGCv*p#~>Dr1Rl`8Ij6h9((m@H?FXn@@FjBI4Wfp3 zg3PUEHf4p3(URrRcP5~B2h%&`3w;Jx@Ckq4Plfkk{Jt7UB$rq~VgZQ-Bo>fZKw<%j z1q!#oe_03N4u6o4CQM-m>7xXh4^l|7MN$buS`PUXT%FPcq}`RrFGG~h-D_<`JYB5F(4iGVpIe-aD)6boQHFOBj4Dc3HJi$Zpp-%);y zp{BW2a*+K57z${f5zV1q4eUV)P@swUW2N|gN+C~kb={Hn2eJhoRE!^D9)tc=nr6J?MJh~z`5+aFkch$(_E!&@CYmooc>I=y@NB&)7%X?Y9rkPXub|X zbN8-+N%+nZx_tM3MaV6Uv9_g=IwC#Ws1Hjw7|605#_{uzG4B+<5OlyiZjl(ZiI~Q~^$ZY_*X}s_okOjE8BQ#&G3FeHo9LAW( zRI&N9v)*KR4Lx)R^8X7t69CPZq4gv#f-;c1pvx5NXSG9K%VOT6I%J(M$Xj@tGbhrx z$35oMJ(f9o52m@V=^lgjL0qn0HAgxN?1AjHDF%vFSpH;~(E5*&SqhESxe6)%+_rxGYvY!f@Z&Ei0Aa6xtSJ_R~l$T_BcRu zcMZUId3Hpy0~N@>2D}F}?iS;3UonktALnAy+*Hy@FCvv$d9PuuTqj`;CdRY(de3zF zW(jjI(*x&xu!iikMu!q)rZpI50JYuyMLML-twDqWC10KD9v&K<~^SNDLKc|^}9XAeBh7sU`T$`kMsZDqJE`vL-#6WFal5v7=dCdKaiK^F;4|=K>qRSx%s-m zPce5jmf1RuW;$IiVNTmKM)UZ@V>+Mwsj$ByWTkc;=};1^0Q%sY<;OTP5umv$i!shP z^W(g{r+Mb&X?A^%d5Bk;Eo^Se&&3n;qspCgd(d zvV%OIe{BZx%K(}e__8qBl_7gGw>`|(aUg5Z`6}i=(;UgX<%{NliOEm%pmtymbX~}e zc3ocCNk+QYe#`w9bDKnjlFu!g+xGNxvKu3;kg zbO(9L%h%fVOZ!*%yd?z`A@?az;M^<~%*AQ$8qX}9rZP>;&#J}G%PD9s66RrLLgve8 zU)YiC;7cNW)AtxFRuT-uIMJ+v<`(6Yncd8~<2cN#$vd}#=2HoF7gEe4+FQS8R$a*d z-P@2D-zEdvLjH7+XZ$BO{@Lp$(|6s)S~?D6G&d}N*`oPCVr7Wx+G+eepl`BiQ4fa9 z)xp0Y>l69iZ?J|gN13$)#yonQ{bVkepbKAyoyR6n}qqwgE9U?gmExsz&@HEQ4;eb6l`{2p{}E> zq7&wUKpz9=4VWJ>m(_63nJXgrM|?g2a@Wz;)x5Bs{%y-F| z>j8a~%+Y4=(2J?L=AF|*I#5hQUJl&rJV8n5kpB|jHIK1brNBYRp7l}wobh^c7_(+> z7sl#aOl1{29M4mx1RtOry@3urzN?U*IXPsP2bUpxwz&MV7+2fKay&C{)sxXSjVG?y z@sqg~@1VyF+$Vn8xGP@%Gx_ab4&#Jdwhmw_EoSoL#g?;@<{NUx#ecjv?5W~@qawx! zf5t-J_bZfrd2m!*{t_7PM`PRdTSjxX7x#<2^>)-xNpyd>hI<`kPyVKM?zd_O*-L># zg8U?VRg-zlx=j~W0drC)FD^gosBFbH2Om{-xoS?bgKrh#+m1I}Du*#-%OPVX$ti2R zgc)LtsSd^&Q@IzHowiZl-b)#;2$5^Kz2vuDxR{PMTv`(HkArL(WNTHknVjuud~|-< zk5Av+HPqOj*(OS&( z9Ff-G#>l~M`@`4+6Fl#*^6bBv#(vN^j&Xp-qzng~40!HAV{|%#7(mbRU4a&^PgTuV z|Dn&kBG#Mi+nTEk<&OwbY6EBDnG%Xa{t zGgyI5fLjyY2+urQk7leh)`Z9PS-`Z^yH36XXuWe9`?3ry!gb~Io#v~vcwVNDbq5#X zy5i5BxNTaC+92c-vkN`IJo;{7J|oUXmX%;=V zqcxE+wt#yM-!$wFv%nhA#z9zLD&hfbkc~Be9`OKbM71)r?d z+YoDW8hY(!CcfKp*Wzt9Ab0&ydiR0?u00G40bji?Bd$qn=57Lcp54$heC;m(VoluD zOuO?rRxg};j>h=`<8r7B(Au{&R!9cW7^o;v;cN6qzbDo%y?2(@Dy4PExHVe?pR(Ek z515hH7G~(ZD|cN|TCZCu6Ig$l)}5UPa@VWEwY^}AB0pn7Uoov6x=`pp7E)Rx)EH|- zYWpT~Yr+b45xagG##fENwQ-mG`1dvAU1$wQTrURXc|J&MHJUgt!5WPxV2`}((qY|p zdiHt-H2-+j|F(s`LkslTz7g;Lr{`hD{2Ga53tBriXU*K4a)DUb*swQi^xv-d@hDuo zAvgzepQY6Dz_mLqVGTTw<@V>33~D)R6F!3vM&lXQH;+dkDR5m{113|rKCKzo)Oi)t za@og}yl>^XKFTD;Bdw`i=A#DxeXE4&8mn?`k7ZYIJcBiQE_2TV8sV9ep7U1D7UIt{ zp#Oc~{@<^b_ek{RR6(Ck96ei+HC>H$ayGNdHgQ-pY&O=3yO8S(dge>E2Z4yqVp}f- z{bM$`ewxs)R|?NY^stUWb<1UVPL=1m!fVJdIIr~Ip2PK9(|1%Q92luM|rFh zBK~Y<3Cc#rZ@*&RGr<1Vkl`6!kJfN$Zas*V7hAuhbgMntRa~zfMt}G>-$V0Wna>$6 zHNo|{wWQR|C$ajLqx1I7o2*Jwu-#)?;IYN=VNM_E`EqzBU|`c9eT;dY3%r7TS{KJ< z3O$G6(hAUjPi>3wSV!aInnCFIyoqafL!WH1)GvizA79{F{#dIDbNCm$gDj`ew>J`f zli%D{``k72e22O8S}lhh zNp0qsP-c$q26ObEn4|B+{GCYJ{JE%PR0`&$rSM-zQ|}D=Gq|1<>P0~x28TIPA`0Nl zBfuFT{zv^3)L>Hsx&0I<{XgTq!)U8QivRW(JU2tFy`w9=VqdngkM@Ti+8?jz{RQ=XFECxd zMDF(qy^{g&pGm^;oUSa*e%yEH>3WV9a0*BO(fC{x1+WkG1 Date: Thu, 21 Oct 2021 19:55:55 +0200 Subject: [PATCH 19/56] TxInfoDialog: disable get tx key for incoming txs --- src/dialog/TxInfoDialog.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/dialog/TxInfoDialog.cpp b/src/dialog/TxInfoDialog.cpp index 76a7ef43..14920e5a 100644 --- a/src/dialog/TxInfoDialog.cpp +++ b/src/dialog/TxInfoDialog.cpp @@ -44,6 +44,12 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn } else { ui->btn_rebroadcastTx->hide(); } + + if (txInfo->direction() == TransactionInfo::Direction_In) { + ui->btn_CopyTxKey->setDisabled(true); + ui->btn_CopyTxKey->setToolTip("No tx secret key available for incoming transactions."); + } + // // if (txInfo->direction() == TransactionInfo::Direction_Out) { // // TODO: this will not properly represent coinjoin-like transactions. From ddb78db5b5fbf0396a753a1ddb19c6505882a391 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 19:56:40 +0200 Subject: [PATCH 20/56] TxInfoDialog: ensure it is closed --- src/MainWindow.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 3f830a32..20b1f8ae 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -666,9 +666,10 @@ void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, con if (msgBox.clickedButton() == showDetailsButton) { this->showHistoryTab(); TransactionInfo *txInfo = m_ctx->wallet->history()->transaction(txid.first()); - TxInfoDialog dialog{m_ctx, txInfo, this}; - connect(&dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction); - dialog.exec(); + auto *dialog = new TxInfoDialog(m_ctx, txInfo, this); + connect(dialog, &TxInfoDialog::resendTranscation, this, &MainWindow::onResendTransaction); + dialog->show(); + dialog->setAttribute(Qt::WA_DeleteOnClose); } m_sendWidget->clearFields(); From 9b22aa25a5687f389abf8fb5e54e544573a62902 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 21:08:33 +0200 Subject: [PATCH 21/56] Calc: allow pasting full precision Monero amounts --- src/CalcWidget.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/CalcWidget.cpp b/src/CalcWidget.cpp index f7737167..8bc654df 100644 --- a/src/CalcWidget.cpp +++ b/src/CalcWidget.cpp @@ -23,13 +23,12 @@ CalcWidget::CalcWidget(QWidget *parent) ui->imageExchange->setFixedSize(26, 26); // validator/locale for input - QLocale lo(QLocale::C); - lo.setNumberOptions(QLocale::RejectGroupSeparator); - auto dv = new QDoubleValidator(0.0, 2147483647, 10, this); // [0, 32bit max], 10 decimals of precision - dv->setNotation(QDoubleValidator::StandardNotation); - dv->setLocale(lo); - ui->lineFrom->setValidator(dv); - ui->lineTo->setValidator(dv); + QString amount_rx = R"(^\d{0,8}[\.]\d{0,12}$)"; + QRegExp rx; + rx.setPattern(amount_rx); + QValidator *validator = new QRegExpValidator(rx, this); + ui->lineFrom->setValidator(validator); + ui->lineTo->setValidator(validator); connect(&appData()->prices, &Prices::fiatPricesUpdated, this, &CalcWidget::onPricesReceived); connect(&appData()->prices, &Prices::cryptoPricesUpdated, this, &CalcWidget::onPricesReceived); From 130733ad98ba703cb47794b6b1877583933f7fbc Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 21:38:39 +0200 Subject: [PATCH 22/56] History: set correct fiat currency --- src/HistoryWidget.cpp | 1 - src/MainWindow.cpp | 2 -- src/SettingsDialog.cpp | 3 ++- src/appcontext.cpp | 18 ------------------ src/appcontext.h | 2 -- src/model/TransactionHistoryModel.cpp | 18 +++++++++++------- src/model/TransactionHistoryModel.h | 3 --- 7 files changed, 13 insertions(+), 34 deletions(-) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index 62d18d18..e6e66d56 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -53,7 +53,6 @@ HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) 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()); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 20b1f8ae..14eab457 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -72,9 +72,7 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa for (const auto &widget: m_priceTickerWidgets) connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay); connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx.get(), &AppContext::onPreferredFiatCurrencyChanged); connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); - connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx.get(), &AppContext::onAmountPrecisionChanged); connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged); QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index d948f96e..5084db53 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -79,8 +79,9 @@ Settings::Settings(QSharedPointer ctx, QWidget *parent) // Preferred fiat currency combobox QStringList fiatCurrencies; - for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) + for (int index = 0; index < ui->comboBox_fiatCurrency->count(); index++) { fiatCurrencies << ui->comboBox_fiatCurrency->itemText(index); + } auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); if(!preferredFiatCurrency.isEmpty()) diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 2714b125..cadf15dc 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -55,9 +55,6 @@ AppContext::AppContext(Wallet *wallet) this->updateBalance(); - // force trigger preferredFiat signal for history model - this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString()); - connect(this->wallet->history(), &TransactionHistory::txNoteChanged, [this]{ this->wallet->history()->refresh(this->wallet->currentSubaddressAccount()); }); @@ -167,21 +164,6 @@ QString AppContext::getCacheTransaction(const QString &txid) const { return txHex; } -// ################## 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) { diff --git a/src/appcontext.h b/src/appcontext.h index ac4db6fd..d3e4d239 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -57,8 +57,6 @@ public slots: void onCreateTransactionError(const QString &msg); void onOpenAliasResolve(const QString &openAlias); void onSetRestoreHeight(quint64 height); - void onPreferredFiatCurrencyChanged(const QString &symbol); - void onAmountPrecisionChanged(int precision); void onMultiBroadcast(PendingTransaction *tx); void onDeviceButtonRequest(quint64 code); void onDeviceButtonPressed(); diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp index 2265a3e9..3bec012f 100644 --- a/src/model/TransactionHistoryModel.cpp +++ b/src/model/TransactionHistoryModel.cpp @@ -158,7 +158,7 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI if (role == Qt::UserRole) { return tInfo.balanceDelta(); } - QString amount = QString::number(tInfo.balanceDelta() / constants::cdiv, 'f', this->amountPrecision); + QString amount = QString::number(tInfo.balanceDelta() / constants::cdiv, 'f', config()->get(Config::amountPrecision).toInt()); amount = (tInfo.direction() == TransactionInfo::Direction_Out) ? "-" + amount : "+" + amount; return amount; } @@ -168,23 +168,27 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI case Column::FiatAmount: { double usd_price = appData()->txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd")); - if (usd_price == 0.0) - return QVariant("?"); + if (usd_price == 0.0) { + return QString("?"); + } double usd_amount = usd_price * (tInfo.balanceDelta() / constants::cdiv); - if(this->preferredFiatSymbol != "USD") - usd_amount = appData()->prices.convert("USD", this->preferredFiatSymbol, usd_amount); + + QString preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString(); + if (preferredFiatCurrency != "USD") { + usd_amount = appData()->prices.convert("USD", preferredFiatCurrency, usd_amount); + } if (role == Qt::UserRole) { return usd_amount; } double fiat_rounded = ceil(Utils::roundSignificant(usd_amount, 3) * 100.0) / 100.0; - return QString("%1").arg(Utils::amountToCurrencyString(fiat_rounded, this->preferredFiatSymbol)); + return QString("%1").arg(Utils::amountToCurrencyString(fiat_rounded, preferredFiatCurrency)); } default: { qCritical() << "Unimplemented role"; - return QVariant(); + return {}; } } } diff --git a/src/model/TransactionHistoryModel.h b/src/model/TransactionHistoryModel.h index 4df7dbd4..d61da796 100644 --- a/src/model/TransactionHistoryModel.h +++ b/src/model/TransactionHistoryModel.h @@ -35,9 +35,6 @@ public: TransactionHistory * transactionHistory() const; TransactionInfo* entryFromIndex(const QModelIndex& index) const; - QString preferredFiatSymbol = "USD"; - int amountPrecision = 4; - 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; From e2a21868e99beb111910a0a26697c58ed97a9fff Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 21:43:46 +0200 Subject: [PATCH 23/56] History: copy amount now copies value shown in table --- src/HistoryWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index e6e66d56..17744704 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -160,7 +160,7 @@ void HistoryWidget::copy(copyField field) { case copyField::Date: return tx->timestamp().toString("yyyy-MM-dd HH:mm"); case copyField::Amount: - return tx->displayAmount(); + return WalletManager::displayAmount(tx->balanceDelta()); default: return QString(""); } From b35642e861363942b650fec7b483867c7951ae09 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 21:49:01 +0200 Subject: [PATCH 24/56] History: fix copy description --- src/HistoryWidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index 17744704..f8ce40f4 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -157,6 +157,8 @@ void HistoryWidget::copy(copyField field) { switch(field) { case copyField::TxID: return tx->hash(); + case copyField::Description: + return tx->description(); case copyField::Date: return tx->timestamp().toString("yyyy-MM-dd HH:mm"); case copyField::Amount: From 9eae7737390d1e0f304d0459314e245ca56a3654 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 22:05:30 +0200 Subject: [PATCH 25/56] History: context menu: remove icons --- src/HistoryWidget.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index f8ce40f4..a0852308 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -26,7 +26,6 @@ HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) m_contextMenu->addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); // copy menu - m_copyMenu->setIcon(icons()->icon("copy.png")); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);}); m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);}); @@ -87,8 +86,8 @@ void HistoryWidget::showContextMenu(const QPoint &point) { } menu.addMenu(m_copyMenu); - menu.addAction(icons()->icon("info2.svg"), "Show details", this, &HistoryWidget::showTxDetails); - menu.addAction(icons()->icon("network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); + menu.addAction("Show details", this, &HistoryWidget::showTxDetails); + menu.addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof); menu.exec(ui->history->viewport()->mapToGlobal(point)); From 516a73fc2cb072acf48942f94a13045feff30920 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 22:47:26 +0200 Subject: [PATCH 26/56] About: update UI, remove contributors, update acknowledgements --- src/assets/about.txt | 7 ------- src/assets/ack.txt | 35 ++++++++++++++++++++++++++++------- src/assets/contributors.txt | 5 ----- src/dialog/AboutDialog.cpp | 11 +---------- src/dialog/AboutDialog.h | 1 - src/dialog/AboutDialog.ui | 19 +------------------ 6 files changed, 30 insertions(+), 48 deletions(-) delete mode 100644 src/assets/contributors.txt diff --git a/src/assets/about.txt b/src/assets/about.txt index 84af5c19..db366e94 100644 --- a/src/assets/about.txt +++ b/src/assets/about.txt @@ -1,12 +1,5 @@ Feather () -Website: https://featherwallet.org -E-mail: dev@featherwallet.org - -Created by dsc, tobtoht, and contributors. - -Uses icons from the Icons8 icon pack (icons8.com). - Copyright (c) 2020-, The Monero Project All rights reserved. diff --git a/src/assets/ack.txt b/src/assets/ack.txt index c0259860..07e7329b 100644 --- a/src/assets/ack.txt +++ b/src/assets/ack.txt @@ -1,12 +1,33 @@ -The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin. +• The wallet UI is heavily inspired by Electrum. We would like to recognize Thomas Voegtlin for his pioneering work on Bitcoin. -Feather uses monero-seed written by Tevador, for 14 word mnemonic seeds. +• Feather uses the monero-seed libary written by Tevador for 14 word mnemonic seeds. -Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art). +• Wizard banner art was adapted from a paper wallet design by the themonera (themonera.art). -Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon. +• Uses icons from the Icons8 icon pack (icons8.com). -Huge thanks to nioc, fluffypony, wowario, thrmo for help during development. +• Initial CMake support for the Monero GUI was coded by TheCharlatan/xiphon. -Some more shoutouts for people for hosting nodes and/or having good ideas: -dnale0r, dEBRUYNE, binaryFate, lza_menace, jwinterm, kico, wowario +Shoutouts: + +binaryFate +blasty +dEBRUYNE +dnale0r +fluffypony +geonic +jwinterm +kico +lozbek +lyza +lza_menace +nioc +rbrunner +rehrar +samsunggalaxyplayer +selsta +sethsimmons +spoontechtips +tevador +thrmo +wowario \ No newline at end of file diff --git a/src/assets/contributors.txt b/src/assets/contributors.txt deleted file mode 100644 index 92c91224..00000000 --- a/src/assets/contributors.txt +++ /dev/null @@ -1,5 +0,0 @@ -dsc -tobtoht -selsta -Diego Salazar -Matt Smith \ No newline at end of file diff --git a/src/dialog/AboutDialog.cpp b/src/dialog/AboutDialog.cpp index 6825400b..5e8d701b 100644 --- a/src/dialog/AboutDialog.cpp +++ b/src/dialog/AboutDialog.cpp @@ -10,10 +10,9 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent) , ui(new Ui::AboutDialog) - , m_model(new QStringListModel(this)) { ui->setupUi(this); - this->setWindowIcon(QIcon("://assets/images/appicons/64x64.png")); + // cute fox (c) Diego "rehrar" Salazar :-D QPixmap p(":assets/images/cutexmrfox.png"); ui->aboutImage->setPixmap(p.scaled(128, 128, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -28,14 +27,6 @@ AboutDialog::AboutDialog(QWidget *parent) auto ack_text = Utils::barrayToString(ack); ui->ackText->setText(ack_text); - QString contributors = Utils::barrayToString(Utils::fileOpenQRC(":assets/contributors.txt")); - QStringList contributor_list = contributors.split("\n"); - m_model->setStringList(contributor_list); - - ui->authorView->setHeaderHidden(true); - ui->authorView->setModel(this->m_model); - ui->authorView->header()->setSectionResizeMode(QHeaderView::Stretch); - this->adjustSize(); } diff --git a/src/dialog/AboutDialog.h b/src/dialog/AboutDialog.h index 4853e4fb..2f2e404b 100644 --- a/src/dialog/AboutDialog.h +++ b/src/dialog/AboutDialog.h @@ -21,7 +21,6 @@ public: private: QScopedPointer ui; - QStringListModel *m_model; }; #endif // FEATHER_ABOUT_H diff --git a/src/dialog/AboutDialog.ui b/src/dialog/AboutDialog.ui index 84f2a76c..8956d86f 100644 --- a/src/dialog/AboutDialog.ui +++ b/src/dialog/AboutDialog.ui @@ -43,7 +43,7 @@ - a free, open-source Monero wallet + <html><head/><body><p><span style=" font-weight:600;">Feather</span>: a free, open-source Monero wallet</p><p>Created by dsc, tobtoht and contributors</p></body></html> Qt::AlignCenter @@ -71,23 +71,6 @@ - - - Authors - - - - - - QAbstractItemView::NoEditTriggers - - - false - - - - - Acknowledgements From 96657b0d4566e1f5d6a21241cebb3a26aa272cdb Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 22:58:05 +0200 Subject: [PATCH 27/56] About: add Info tab --- src/assets.qrc | 1 - src/dialog/AboutDialog.ui | 143 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/assets.qrc b/src/assets.qrc index c2512404..09e28c31 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -2,7 +2,6 @@ assets/about.txt assets/ack.txt - assets/contributors.txt assets/feather.desktop assets/nodes.json assets/gpg_keys/featherwallet.asc diff --git a/src/dialog/AboutDialog.ui b/src/dialog/AboutDialog.ui index 8956d86f..3436c18e 100644 --- a/src/dialog/AboutDialog.ui +++ b/src/dialog/AboutDialog.ui @@ -71,6 +71,149 @@
+ + + Info + + + + + + Website: + + + + + + + Docs: + + + + + + + Git: + + + + + + + Reddit: + + + + + + + IRC: + + + + + + + Matrix: + + + + + + + Email: + + + + + + + featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + docs.featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + git.featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + /r/FeatherWallet + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + #feather on OFTC + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + #_oftc_#feather:matrix.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + dev@featherwallet.org + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Twitter: + + + + + + + @featherwallet + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + Acknowledgements From a958a5492726a0d519f42fc75dac2171a9392874 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 21 Oct 2021 23:13:25 +0200 Subject: [PATCH 28/56] Menu: add documentation action, update report bug action --- src/MainWindow.cpp | 14 +++++++------- src/MainWindow.h | 1 + src/MainWindow.ui | 6 ++++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 14eab457..bdbdbb2d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -325,6 +325,7 @@ void MainWindow::initMenu() { connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked); connect(ui->actionOfficialWebsite, &QAction::triggered, [this](){Utils::externalLinkWarning(this, "https://featherwallet.org");}); connect(ui->actionDonate_to_Feather, &QAction::triggered, this, &MainWindow::donateButtonClicked); + connect(ui->actionDocumentation, &QAction::triggered, this, &MainWindow::onShowDocumentaton); connect(ui->actionReport_bug, &QAction::triggered, this, &MainWindow::onReportBug); connect(ui->actionShow_debug_info, &QAction::triggered, this, &MainWindow::showDebugInfo); @@ -339,6 +340,7 @@ void MainWindow::initMenu() { ui->actionSettings->setShortcut(QKeySequence("Ctrl+Alt+S")); ui->actionUpdate_balance->setShortcut(QKeySequence("Ctrl+U")); ui->actionShow_Searchbar->setShortcut(QKeySequence("Ctrl+F")); + ui->actionDocumentation->setShortcut(QKeySequence("F1")); } void MainWindow::initHome() { @@ -1463,14 +1465,12 @@ void MainWindow::onCreateDesktopEntry(bool checked) { QMessageBox::information(this, "Desktop entry", msg); } +void MainWindow::onShowDocumentaton() { + Utils::externalLinkWarning(this, "https://docs.featherwallet.org"); +} + void MainWindow::onReportBug(bool checked) { - QMessageBox::information(this, "Reporting Bugs", - "Please report any bugs as issues on our git repo:
\n" - "https://git.featherwallet.org/feather/feather/issues

" - "\n" - "Before reporting a bug, upgrade to the most recent version of Feather " - "(latest release or git HEAD), and include the version number in your report. " - "Try to explain not only what the bug is, but how it occurs."); + Utils::externalLinkWarning(this, "https://docs.featherwallet.org/guides/report-an-issue"); } QString MainWindow::getPlatformTag() { diff --git a/src/MainWindow.h b/src/MainWindow.h index 883329d0..a007aa47 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -117,6 +117,7 @@ private slots: void onExportHistoryCSV(bool checked); void onExportContactsCSV(bool checked); void onCreateDesktopEntry(bool checked); + void onShowDocumentaton(); void onReportBug(bool checked); // offline tx signing diff --git a/src/MainWindow.ui b/src/MainWindow.ui index 6b886813..a80c4604 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -467,6 +467,7 @@ +
@@ -803,6 +804,11 @@ Address checker + + + Documentation + + From 6d00d444e1ea529ca1a0344eba577929829382d3 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 13:05:40 +0200 Subject: [PATCH 29/56] Settings: make dialog modal --- src/MainWindow.cpp | 21 +++++++++------------ src/MainWindow.h | 1 - src/MainWindow.ui | 3 ++- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index bdbdbb2d..4f083529 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -47,7 +47,6 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa // Ensure the destructor is called after closeEvent() setAttribute(Qt::WA_DeleteOnClose); - m_windowSettings = new Settings(m_ctx, this); m_windowCalc = new CalcWindow(this); m_splashDialog = new SplashDialog(this); @@ -68,19 +67,12 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa #endif websocketNotifier()->emitCache(); // Get cached data - // Settings - for (const auto &widget: m_priceTickerWidgets) - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay); - connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); - connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged); - QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); - connect(m_windowManager, &WindowManager::torSettingsChanged, m_ctx.get(), &AppContext::onTorSettingsChanged); connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged); this->onTorConnectionStateChanged(torManager()->torConnected); ColorScheme::updateFromWidget(this); + QTimer::singleShot(1, [this]{this->updateWidgetIcons();}); // Timers connect(&m_updateBytes, &QTimer::timeout, this, &MainWindow::updateNetStats); @@ -833,9 +825,14 @@ void MainWindow::menuAboutClicked() { } void MainWindow::menuSettingsClicked() { - m_windowSettings->raise(); - m_windowSettings->show(); - m_windowSettings->activateWindow(); + Settings settings{m_ctx, this}; + for (const auto &widget: m_priceTickerWidgets) { + connect(&settings, &Settings::preferredFiatCurrencyChanged, widget, &PriceTickerWidget::updateDisplay); + } + connect(&settings, &Settings::preferredFiatCurrencyChanged, m_balanceTickerWidget, &BalanceTickerWidget::updateDisplay); + connect(&settings, &Settings::preferredFiatCurrencyChanged, m_sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); + connect(&settings, &Settings::skinChanged, this, &MainWindow::skinChanged); + settings.exec(); } void MainWindow::menuSignVerifyClicked() { diff --git a/src/MainWindow.h b/src/MainWindow.h index a007aa47..fc863e15 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -223,7 +223,6 @@ private: WindowManager *m_windowManager; QSharedPointer m_ctx; - Settings *m_windowSettings = nullptr; CalcWindow *m_windowCalc = nullptr; SplashDialog *m_splashDialog = nullptr; diff --git a/src/MainWindow.ui b/src/MainWindow.ui index a80c4604..3d82bf17 100644 --- a/src/MainWindow.ui +++ b/src/MainWindow.ui @@ -465,11 +465,12 @@ - + + From ecfef8c6cf37c552cad2a4afd8ac9615ef6e7306 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 13:06:09 +0200 Subject: [PATCH 30/56] DebugInfoDialog: fix spacing --- src/dialog/DebugInfoDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dialog/DebugInfoDialog.cpp b/src/dialog/DebugInfoDialog.cpp index bbc1b025..07fcc8b5 100644 --- a/src/dialog/DebugInfoDialog.cpp +++ b/src/dialog/DebugInfoDialog.cpp @@ -139,7 +139,7 @@ void DebugInfoDialog::copyToClipboad() { text += QString("Seed type: %1 \n").arg(ui->label_seedType->text()); text += QString("Device type: %1 \n").arg(ui->label_deviceType->text()); text += QString("View only: %1 \n").arg(ui->label_viewOnly->text()); - text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text()); + text += QString("Primary only: %1 \n").arg(ui->label_primaryOnly->text()); text += QString("Operating system: %1 \n").arg(ui->label_OS->text()); text += QString("Timestamp: %1 \n").arg(ui->label_timestamp->text()); From d1ae7b77e8d130f811e09ffb91cebcabf7ccc117 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 14:07:06 +0200 Subject: [PATCH 31/56] History: change context menu order --- src/HistoryWidget.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index a0852308..beddccbd 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -27,8 +27,8 @@ HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) // copy menu m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); - m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);}); m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);}); + m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);}); m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);}); ui->history->setContextMenuPolicy(Qt::CustomContextMenu); @@ -88,7 +88,7 @@ void HistoryWidget::showContextMenu(const QPoint &point) { menu.addMenu(m_copyMenu); menu.addAction("Show details", this, &HistoryWidget::showTxDetails); menu.addAction("View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); - menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof); + menu.addAction("Create Tx Proof", this, &HistoryWidget::createTxProof); menu.exec(ui->history->viewport()->mapToGlobal(point)); } From 1ebb408c2c8912f6aef87f85ff746d4546cd5066 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 14:07:31 +0200 Subject: [PATCH 32/56] About: more acknowledgements --- src/assets/ack.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/assets/ack.txt b/src/assets/ack.txt index 07e7329b..eb8b0d98 100644 --- a/src/assets/ack.txt +++ b/src/assets/ack.txt @@ -11,6 +11,8 @@ Shoutouts: binaryFate +bignastyhammer +boogerlad blasty dEBRUYNE dnale0r @@ -27,7 +29,9 @@ rehrar samsunggalaxyplayer selsta sethsimmons +SmashTR spoontechtips +rottenwheel tevador thrmo wowario \ No newline at end of file From ba127b4251570dffc327e09a331242a04749a325 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 14:08:08 +0200 Subject: [PATCH 33/56] TxInfoDialog: improve strings --- src/dialog/TxInfoDialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dialog/TxInfoDialog.ui b/src/dialog/TxInfoDialog.ui index f9258058..5545e78f 100644 --- a/src/dialog/TxInfoDialog.ui +++ b/src/dialog/TxInfoDialog.ui @@ -208,7 +208,7 @@ - Copy Tx ID/hash + Copy Tx ID @@ -222,7 +222,7 @@ - Create Transaction Proof + Create Tx Proof From 27f3e9668638a157798635f47719bccc5f6bd1f3 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 17:11:50 +0200 Subject: [PATCH 34/56] History: fix txid column behavior --- src/model/HistoryView.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/model/HistoryView.cpp b/src/model/HistoryView.cpp index be1da45b..8b1ce165 100644 --- a/src/model/HistoryView.cpp +++ b/src/model/HistoryView.cpp @@ -82,11 +82,16 @@ TransactionInfo* HistoryView::currentEntry() } void HistoryView::setSearchMode(bool mode) { + if (!m_inSearchMode) { + m_showTxidColumn = !header()->isSectionHidden(TransactionHistoryModel::TxID); + } + m_inSearchMode = mode; if (mode) { header()->showSection(TransactionHistoryModel::TxID); - } else { + } + else if (!m_showTxidColumn) { header()->hideSection(TransactionHistoryModel::TxID); } } From b8dbf128d2bc6060d492ccaf3ee84e11b577ff4e Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 17:49:46 +0200 Subject: [PATCH 35/56] History: copy date as shown --- src/HistoryWidget.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index beddccbd..eef0c867 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -159,7 +159,8 @@ void HistoryWidget::copy(copyField field) { case copyField::Description: return tx->description(); case copyField::Date: - return tx->timestamp().toString("yyyy-MM-dd HH:mm"); + return tx->timestamp().toString(QString("%1 %2").arg(config()->get(Config::dateFormat).toString(), + config()->get(Config::timeFormat).toString())); case copyField::Amount: return WalletManager::displayAmount(tx->balanceDelta()); default: From 19e8ed1b19bd3640955fada6a1d5e364d0f5cf39 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 17:55:24 +0200 Subject: [PATCH 36/56] History: fiat return ? if not usd before websocket connection --- src/model/TransactionHistoryModel.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp index 3bec012f..c7425b0f 100644 --- a/src/model/TransactionHistoryModel.cpp +++ b/src/model/TransactionHistoryModel.cpp @@ -181,6 +181,9 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI if (role == Qt::UserRole) { return usd_amount; } + if (usd_amount == 0.0) { + return QString("?"); + } double fiat_rounded = ceil(Utils::roundSignificant(usd_amount, 3) * 100.0) / 100.0; return QString("%1").arg(Utils::amountToCurrencyString(fiat_rounded, preferredFiatCurrency)); From 484f61759e41b822e66a93c1dd2e814dea11bbe8 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 18:11:02 +0200 Subject: [PATCH 37/56] TxInfoDialog: added a clickable icon to view transaction on block explorer --- src/assets.qrc | 1 + src/dialog/TxInfoDialog.cpp | 13 +++++++++++++ src/dialog/TxInfoDialog.h | 1 + src/dialog/TxInfoDialog.ui | 15 ++++++++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/assets.qrc b/src/assets.qrc index 09e28c31..bf99e011 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -34,6 +34,7 @@ assets/images/copy.png assets/images/cutexmrfox.png assets/images/edit.png + assets/images/external-link.svg assets/images/exchange.png assets/images/exchange_white.png assets/images/expired.png diff --git a/src/dialog/TxInfoDialog.cpp b/src/dialog/TxInfoDialog.cpp index 14920e5a..eb0bfeff 100644 --- a/src/dialog/TxInfoDialog.cpp +++ b/src/dialog/TxInfoDialog.cpp @@ -9,6 +9,7 @@ #include "appcontext.h" #include "config.h" +#include "constants.h" #include "libwalletqt/Coins.h" #include "libwalletqt/CoinsInfo.h" #include "libwalletqt/TransactionHistory.h" @@ -16,6 +17,7 @@ #include "libwalletqt/WalletManager.h" #include "model/ModelUtils.h" #include "Utils.h" +#include "utils/Icons.h" TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txInfo, QWidget *parent) : QDialog(parent) @@ -26,6 +28,10 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn { ui->setupUi(this); + ui->btn_viewOnBlockExplorer->setIcon(icons()->icon("external-link.svg")); + ui->btn_viewOnBlockExplorer->setToolTip("View on block explorer"); + connect(ui->btn_viewOnBlockExplorer, &QPushButton::clicked, this, &TxInfoDialog::viewOnBlockExplorer); + m_txid = txInfo->hash(); ui->label_txid->setText(m_txid); @@ -85,6 +91,9 @@ TxInfoDialog::TxInfoDialog(QSharedPointer ctx, TransactionInfo *txIn } this->adjustSize(); + + // Don't autofocus any of the buttons. There is probably a better way for this. + ui->label_txid->setFocus(); } void TxInfoDialog::adjustHeight(QTextEdit *textEdit, qreal docHeight) { @@ -168,4 +177,8 @@ void TxInfoDialog::createTxProof() { m_txProofDialog->getTxKey(); } +void TxInfoDialog::viewOnBlockExplorer() { + Utils::externalLinkWarning(this, Utils::blockExplorerLink(config()->get(Config::blockExplorer).toString(), constants::networkType, m_txid)); +} + TxInfoDialog::~TxInfoDialog() = default; \ No newline at end of file diff --git a/src/dialog/TxInfoDialog.h b/src/dialog/TxInfoDialog.h index 0de4a374..36bc46f0 100644 --- a/src/dialog/TxInfoDialog.h +++ b/src/dialog/TxInfoDialog.h @@ -34,6 +34,7 @@ private: void setData(TransactionInfo *tx); void updateData(); void adjustHeight(QTextEdit *textEdit, qreal docHeight); + void viewOnBlockExplorer(); QScopedPointer ui; QSharedPointer m_ctx; diff --git a/src/dialog/TxInfoDialog.ui b/src/dialog/TxInfoDialog.ui index 5545e78f..22405d92 100644 --- a/src/dialog/TxInfoDialog.ui +++ b/src/dialog/TxInfoDialog.ui @@ -19,7 +19,7 @@ Transaction ID: - + @@ -30,6 +30,19 @@ + + + + + 0 + 0 + + + + + + + From 5f4dc89085ba099926255dc8e8d82cd6d66d78f4 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 19:19:56 +0200 Subject: [PATCH 38/56] Use WindowModality for dialogs --- src/components.cpp | 6 ++++++ src/components.h | 7 +++++++ src/dialog/AboutDialog.cpp | 2 +- src/dialog/AboutDialog.h | 4 +++- src/dialog/AccountSwitcherDialog.cpp | 4 +++- src/dialog/AccountSwitcherDialog.h | 3 ++- src/dialog/BalanceDialog.cpp | 2 +- src/dialog/BalanceDialog.h | 3 ++- src/dialog/CalcConfigDialog.cpp | 2 +- src/dialog/CalcConfigDialog.h | 4 +++- src/dialog/ContactsDialog.cpp | 2 +- src/dialog/ContactsDialog.h | 4 +++- src/dialog/DebugInfoDialog.cpp | 2 +- src/dialog/DebugInfoDialog.h | 3 ++- src/dialog/InfoDialog.cpp | 2 +- src/dialog/InfoDialog.h | 4 +++- src/dialog/KeysDialog.cpp | 2 +- src/dialog/KeysDialog.h | 3 ++- src/dialog/LocalMoneroInfoDialog.cpp | 2 +- src/dialog/LocalMoneroInfoDialog.h | 3 ++- src/dialog/OutputInfoDialog.cpp | 2 +- src/dialog/OutputInfoDialog.h | 3 ++- src/dialog/OutputSweepDialog.cpp | 2 +- src/dialog/OutputSweepDialog.h | 3 ++- src/dialog/PasswordChangeDialog.cpp | 2 +- src/dialog/PasswordChangeDialog.h | 3 ++- src/dialog/PasswordDialog.cpp | 2 +- src/dialog/PasswordDialog.h | 4 +++- src/dialog/PaymentRequestDialog.cpp | 2 +- src/dialog/PaymentRequestDialog.h | 3 ++- src/dialog/QrCodeDialog.cpp | 2 +- src/dialog/QrCodeDialog.h | 3 ++- src/dialog/RestoreHeightDialog.cpp | 2 +- src/dialog/RestoreHeightDialog.h | 3 ++- src/dialog/SeedDialog.cpp | 2 +- src/dialog/SeedDialog.h | 3 ++- src/dialog/SignVerifyDialog.cpp | 2 +- src/dialog/SignVerifyDialog.h | 3 ++- src/dialog/SplashDialog.cpp | 2 +- src/dialog/SplashDialog.h | 4 +++- src/dialog/TxBroadcastDialog.cpp | 2 +- src/dialog/TxBroadcastDialog.h | 3 ++- src/dialog/TxConfAdvDialog.cpp | 2 +- src/dialog/TxConfAdvDialog.h | 3 ++- src/dialog/TxConfDialog.cpp | 2 +- src/dialog/TxConfDialog.h | 3 ++- src/dialog/TxImportDialog.cpp | 2 +- src/dialog/TxImportDialog.h | 3 ++- src/dialog/TxProofDialog.cpp | 2 +- src/dialog/TxProofDialog.h | 3 ++- src/dialog/VerifyProofDialog.cpp | 2 +- src/dialog/VerifyProofDialog.h | 3 ++- src/dialog/ViewOnlyDialog.cpp | 2 +- src/dialog/ViewOnlyDialog.h | 3 ++- src/dialog/WalletCacheDebugDialog.cpp | 2 +- src/dialog/WalletCacheDebugDialog.h | 3 ++- src/dialog/WalletInfoDialog.cpp | 2 +- src/dialog/WalletInfoDialog.h | 3 ++- 58 files changed, 105 insertions(+), 56 deletions(-) diff --git a/src/components.cpp b/src/components.cpp index 4e02c65f..810c49f2 100644 --- a/src/components.cpp +++ b/src/components.cpp @@ -113,4 +113,10 @@ ClickableLabel::~ClickableLabel() = default; void ClickableLabel::mousePressEvent(QMouseEvent* event) { emit clicked(); +} + +WindowModalDialog::WindowModalDialog(QWidget *parent) + : QDialog(parent) +{ + this->setWindowModality(Qt::WindowModal); } \ No newline at end of file diff --git a/src/components.h b/src/components.h index eeb38049..6f26014e 100644 --- a/src/components.h +++ b/src/components.h @@ -121,4 +121,11 @@ protected: }; +class WindowModalDialog : public QDialog { + Q_OBJECT + +public: + explicit WindowModalDialog(QWidget *parent); +}; + #endif //FEATHER_COMPONENTS_H diff --git a/src/dialog/AboutDialog.cpp b/src/dialog/AboutDialog.cpp index 5e8d701b..82130e2f 100644 --- a/src/dialog/AboutDialog.cpp +++ b/src/dialog/AboutDialog.cpp @@ -8,7 +8,7 @@ #include "utils/Utils.h" AboutDialog::AboutDialog(QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::AboutDialog) { ui->setupUi(this); diff --git a/src/dialog/AboutDialog.h b/src/dialog/AboutDialog.h index 2f2e404b..4dcc8a2b 100644 --- a/src/dialog/AboutDialog.h +++ b/src/dialog/AboutDialog.h @@ -7,11 +7,13 @@ #include #include +#include "components.h" + namespace Ui { class AboutDialog; } -class AboutDialog : public QDialog +class AboutDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/AccountSwitcherDialog.cpp b/src/dialog/AccountSwitcherDialog.cpp index ee1e10e8..9a006131 100644 --- a/src/dialog/AccountSwitcherDialog.cpp +++ b/src/dialog/AccountSwitcherDialog.cpp @@ -11,7 +11,7 @@ #include "utils/Icons.h" AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::AccountSwitcherDialog) , m_ctx(std::move(ctx)) , m_model(m_ctx->wallet->subaddressAccountModel()) @@ -25,6 +25,8 @@ AccountSwitcherDialog::AccountSwitcherDialog(QSharedPointer ctx, QWi ui->label_totalBalance->setFont(ModelUtils::getMonospaceFont()); ui->label_totalBalance->setText(WalletManager::displayAmount(m_ctx->wallet->balanceAll())); + this->setWindowModality(Qt::WindowModal); + ui->accounts->setModel(m_proxyModel); ui->accounts->setContextMenuPolicy(Qt::CustomContextMenu); ui->accounts->setSelectionMode(QAbstractItemView::SingleSelection); diff --git a/src/dialog/AccountSwitcherDialog.h b/src/dialog/AccountSwitcherDialog.h index 08c15be0..e31f419f 100644 --- a/src/dialog/AccountSwitcherDialog.h +++ b/src/dialog/AccountSwitcherDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "model/SubaddressAccountModel.h" namespace Ui { class AccountSwitcherDialog; } -class AccountSwitcherDialog : public QDialog +class AccountSwitcherDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/BalanceDialog.cpp b/src/dialog/BalanceDialog.cpp index 7c490007..2fab5676 100644 --- a/src/dialog/BalanceDialog.cpp +++ b/src/dialog/BalanceDialog.cpp @@ -8,7 +8,7 @@ #include "model/ModelUtils.h" BalanceDialog::BalanceDialog(QWidget *parent, Wallet *wallet) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::BalanceDialog) { ui->setupUi(this); diff --git a/src/dialog/BalanceDialog.h b/src/dialog/BalanceDialog.h index 1880ae9f..8c32c2e1 100644 --- a/src/dialog/BalanceDialog.h +++ b/src/dialog/BalanceDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class BalanceDialog; } -class BalanceDialog : public QDialog +class BalanceDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/CalcConfigDialog.cpp b/src/dialog/CalcConfigDialog.cpp index caafb941..7bf7a079 100644 --- a/src/dialog/CalcConfigDialog.cpp +++ b/src/dialog/CalcConfigDialog.cpp @@ -8,7 +8,7 @@ #include "utils/config.h" CalcConfigDialog::CalcConfigDialog(QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::CalcConfigDialog) { ui->setupUi(this); diff --git a/src/dialog/CalcConfigDialog.h b/src/dialog/CalcConfigDialog.h index 37f7a2d2..5209bd1c 100644 --- a/src/dialog/CalcConfigDialog.h +++ b/src/dialog/CalcConfigDialog.h @@ -7,11 +7,13 @@ #include #include +#include "components.h" + namespace Ui { class CalcConfigDialog; } -class CalcConfigDialog : public QDialog +class CalcConfigDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/ContactsDialog.cpp b/src/dialog/ContactsDialog.cpp index 439208d6..381f66c7 100644 --- a/src/dialog/ContactsDialog.cpp +++ b/src/dialog/ContactsDialog.cpp @@ -5,7 +5,7 @@ #include "ContactsDialog.h" ContactsDialog::ContactsDialog(QWidget *parent, const QString &address, const QString &name) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::ContactsDialog) { ui->setupUi(this); diff --git a/src/dialog/ContactsDialog.h b/src/dialog/ContactsDialog.h index 03b81c4e..f0a74bc4 100644 --- a/src/dialog/ContactsDialog.h +++ b/src/dialog/ContactsDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class ContactsDialog; } -class ContactsDialog : public QDialog +class ContactsDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/DebugInfoDialog.cpp b/src/dialog/DebugInfoDialog.cpp index 07fcc8b5..c77f2963 100644 --- a/src/dialog/DebugInfoDialog.cpp +++ b/src/dialog/DebugInfoDialog.cpp @@ -11,7 +11,7 @@ #include "utils/WebsocketNotifier.h" DebugInfoDialog::DebugInfoDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::DebugInfoDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/DebugInfoDialog.h b/src/dialog/DebugInfoDialog.h index d2fbc83f..d94493e0 100644 --- a/src/dialog/DebugInfoDialog.h +++ b/src/dialog/DebugInfoDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class DebugInfoDialog; } -class DebugInfoDialog : public QDialog +class DebugInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/InfoDialog.cpp b/src/dialog/InfoDialog.cpp index 976d96cd..2de636db 100644 --- a/src/dialog/InfoDialog.cpp +++ b/src/dialog/InfoDialog.cpp @@ -5,7 +5,7 @@ #include "ui_InfoDialog.h" InfoDialog::InfoDialog(QWidget *parent, const QString &title, const QString &infoData) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::InfoDialog) { ui->setupUi(this); diff --git a/src/dialog/InfoDialog.h b/src/dialog/InfoDialog.h index fa18b5b4..a3348283 100644 --- a/src/dialog/InfoDialog.h +++ b/src/dialog/InfoDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class InfoDialog; } -class InfoDialog : public QDialog +class InfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/KeysDialog.cpp b/src/dialog/KeysDialog.cpp index 7fe362d8..12af0e3a 100644 --- a/src/dialog/KeysDialog.cpp +++ b/src/dialog/KeysDialog.cpp @@ -5,7 +5,7 @@ #include "ui_KeysDialog.h" KeysDialog::KeysDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::KeysDialog) { ui->setupUi(this); diff --git a/src/dialog/KeysDialog.h b/src/dialog/KeysDialog.h index 5684075c..bb08c494 100644 --- a/src/dialog/KeysDialog.h +++ b/src/dialog/KeysDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class KeysDialog; } -class KeysDialog : public QDialog +class KeysDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/LocalMoneroInfoDialog.cpp b/src/dialog/LocalMoneroInfoDialog.cpp index d3df1a7f..f6f1b36e 100644 --- a/src/dialog/LocalMoneroInfoDialog.cpp +++ b/src/dialog/LocalMoneroInfoDialog.cpp @@ -8,7 +8,7 @@ #include "utils/Utils.h" LocalMoneroInfoDialog::LocalMoneroInfoDialog(QWidget *parent, LocalMoneroModel *model, int row) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::LocalMoneroInfoDialog) , m_model(model) , m_row(row) diff --git a/src/dialog/LocalMoneroInfoDialog.h b/src/dialog/LocalMoneroInfoDialog.h index e5f6cd11..273bafc4 100644 --- a/src/dialog/LocalMoneroInfoDialog.h +++ b/src/dialog/LocalMoneroInfoDialog.h @@ -7,13 +7,14 @@ #include #include +#include "components.h" #include "model/LocalMoneroModel.h" namespace Ui { class LocalMoneroInfoDialog; } -class LocalMoneroInfoDialog : public QDialog +class LocalMoneroInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/OutputInfoDialog.cpp b/src/dialog/OutputInfoDialog.cpp index 772d1fb4..4ab953b6 100644 --- a/src/dialog/OutputInfoDialog.cpp +++ b/src/dialog/OutputInfoDialog.cpp @@ -8,7 +8,7 @@ #include "utils/Utils.h" OutputInfoDialog::OutputInfoDialog(CoinsInfo *cInfo, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::OutputInfoDialog) { ui->setupUi(this); diff --git a/src/dialog/OutputInfoDialog.h b/src/dialog/OutputInfoDialog.h index 18dd39cc..c0eb49a1 100644 --- a/src/dialog/OutputInfoDialog.h +++ b/src/dialog/OutputInfoDialog.h @@ -6,6 +6,7 @@ #include +#include "components.h" #include "libwalletqt/Coins.h" #include "libwalletqt/CoinsInfo.h" @@ -13,7 +14,7 @@ namespace Ui { class OutputInfoDialog; } -class OutputInfoDialog : public QDialog +class OutputInfoDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/OutputSweepDialog.cpp b/src/dialog/OutputSweepDialog.cpp index 7c7d2fbc..3883676c 100644 --- a/src/dialog/OutputSweepDialog.cpp +++ b/src/dialog/OutputSweepDialog.cpp @@ -7,7 +7,7 @@ #include "libwalletqt/WalletManager.h" OutputSweepDialog::OutputSweepDialog(QWidget *parent, quint64 amount) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::OutputSweepDialog) , m_amount(amount) { diff --git a/src/dialog/OutputSweepDialog.h b/src/dialog/OutputSweepDialog.h index 4b565fb4..8c5e6b54 100644 --- a/src/dialog/OutputSweepDialog.h +++ b/src/dialog/OutputSweepDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/CoinsInfo.h" namespace Ui { class OutputSweepDialog; } -class OutputSweepDialog : public QDialog +class OutputSweepDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/PasswordChangeDialog.cpp b/src/dialog/PasswordChangeDialog.cpp index 14c5e9d0..d7e1386f 100644 --- a/src/dialog/PasswordChangeDialog.cpp +++ b/src/dialog/PasswordChangeDialog.cpp @@ -7,7 +7,7 @@ #include PasswordChangeDialog::PasswordChangeDialog(QWidget *parent, Wallet *wallet) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::PasswordChangeDialog) , m_wallet(wallet) { diff --git a/src/dialog/PasswordChangeDialog.h b/src/dialog/PasswordChangeDialog.h index 7fc91e03..d39ec6b6 100644 --- a/src/dialog/PasswordChangeDialog.h +++ b/src/dialog/PasswordChangeDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class PasswordChangeDialog; } -class PasswordChangeDialog : public QDialog +class PasswordChangeDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/PasswordDialog.cpp b/src/dialog/PasswordDialog.cpp index d4e0e4af..5a49b59a 100644 --- a/src/dialog/PasswordDialog.cpp +++ b/src/dialog/PasswordDialog.cpp @@ -7,7 +7,7 @@ #include "utils/Icons.h" PasswordDialog::PasswordDialog(const QString &walletName, bool incorrectPassword, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::PasswordDialog) { ui->setupUi(this); diff --git a/src/dialog/PasswordDialog.h b/src/dialog/PasswordDialog.h index 544331f4..7afc5b0c 100644 --- a/src/dialog/PasswordDialog.h +++ b/src/dialog/PasswordDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class PasswordDialog; } -class PasswordDialog : public QDialog +class PasswordDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/PaymentRequestDialog.cpp b/src/dialog/PaymentRequestDialog.cpp index 4cf38eee..dfac97be 100644 --- a/src/dialog/PaymentRequestDialog.cpp +++ b/src/dialog/PaymentRequestDialog.cpp @@ -12,7 +12,7 @@ #include "WalletManager.h" PaymentRequestDialog::PaymentRequestDialog(QWidget *parent, QSharedPointer ctx, QString address) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::PaymentRequestDialog) , m_ctx(std::move(ctx)) , m_address(std::move(address)) diff --git a/src/dialog/PaymentRequestDialog.h b/src/dialog/PaymentRequestDialog.h index c109d0a3..ca8274c7 100644 --- a/src/dialog/PaymentRequestDialog.h +++ b/src/dialog/PaymentRequestDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "qrcode/QrCode.h" namespace Ui { class PaymentRequestDialog; } -class PaymentRequestDialog : public QDialog +class PaymentRequestDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/QrCodeDialog.cpp b/src/dialog/QrCodeDialog.cpp index 4b1c7cf0..41ca49cc 100644 --- a/src/dialog/QrCodeDialog.cpp +++ b/src/dialog/QrCodeDialog.cpp @@ -9,7 +9,7 @@ #include QrCodeDialog::QrCodeDialog(QWidget *parent, QrCode *qrCode, const QString &title) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::QrCodeDialog) { ui->setupUi(this); diff --git a/src/dialog/QrCodeDialog.h b/src/dialog/QrCodeDialog.h index 706b120b..78d7dc30 100644 --- a/src/dialog/QrCodeDialog.h +++ b/src/dialog/QrCodeDialog.h @@ -6,6 +6,7 @@ #include +#include "components.h" #include "qrcode/QrCode.h" #include "widgets/QrCodeWidget.h" @@ -13,7 +14,7 @@ namespace Ui { class QrCodeDialog; } -class QrCodeDialog : public QDialog +class QrCodeDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/RestoreHeightDialog.cpp b/src/dialog/RestoreHeightDialog.cpp index ca7cfda8..5a4045c2 100644 --- a/src/dialog/RestoreHeightDialog.cpp +++ b/src/dialog/RestoreHeightDialog.cpp @@ -8,7 +8,7 @@ #include RestoreHeightDialog::RestoreHeightDialog(QWidget *parent, quint64 currentRestoreHeight) - : QDialog(parent) + : WindowModalDialog(parent) , m_restoreHeightWidget(new RestoreHeightWidget(this)) { auto *layout = new QVBoxLayout(this); diff --git a/src/dialog/RestoreHeightDialog.h b/src/dialog/RestoreHeightDialog.h index 88cad2c8..711882b1 100644 --- a/src/dialog/RestoreHeightDialog.h +++ b/src/dialog/RestoreHeightDialog.h @@ -6,9 +6,10 @@ #include +#include "components.h" #include "widgets/RestoreHeightWidget.h" -class RestoreHeightDialog : public QDialog +class RestoreHeightDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/SeedDialog.cpp b/src/dialog/SeedDialog.cpp index 9153b2a5..cc09e05f 100644 --- a/src/dialog/SeedDialog.cpp +++ b/src/dialog/SeedDialog.cpp @@ -7,7 +7,7 @@ #include "constants.h" SeedDialog::SeedDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::SeedDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/SeedDialog.h b/src/dialog/SeedDialog.h index a0398bd4..7cc656ff 100644 --- a/src/dialog/SeedDialog.h +++ b/src/dialog/SeedDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class SeedDialog; } -class SeedDialog : public QDialog +class SeedDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/SignVerifyDialog.cpp b/src/dialog/SignVerifyDialog.cpp index 490aa566..f65df4c5 100644 --- a/src/dialog/SignVerifyDialog.cpp +++ b/src/dialog/SignVerifyDialog.cpp @@ -9,7 +9,7 @@ #include "utils/Utils.h" SignVerifyDialog::SignVerifyDialog(Wallet *wallet, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::SignVerifyDialog) , m_wallet(wallet) { diff --git a/src/dialog/SignVerifyDialog.h b/src/dialog/SignVerifyDialog.h index d7819f61..18ad34a6 100644 --- a/src/dialog/SignVerifyDialog.h +++ b/src/dialog/SignVerifyDialog.h @@ -6,13 +6,14 @@ #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class SignVerifyDialog; } -class SignVerifyDialog : public QDialog +class SignVerifyDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/SplashDialog.cpp b/src/dialog/SplashDialog.cpp index ba0bc5c2..7f2e9d2c 100644 --- a/src/dialog/SplashDialog.cpp +++ b/src/dialog/SplashDialog.cpp @@ -7,7 +7,7 @@ #include "utils/Icons.h" SplashDialog::SplashDialog(QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::SplashDialog) { ui->setupUi(this); diff --git a/src/dialog/SplashDialog.h b/src/dialog/SplashDialog.h index 7341b7df..8f772a8d 100644 --- a/src/dialog/SplashDialog.h +++ b/src/dialog/SplashDialog.h @@ -6,11 +6,13 @@ #include +#include "components.h" + namespace Ui { class SplashDialog; } -class SplashDialog : public QDialog +class SplashDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxBroadcastDialog.cpp b/src/dialog/TxBroadcastDialog.cpp index 58223db1..12585221 100644 --- a/src/dialog/TxBroadcastDialog.cpp +++ b/src/dialog/TxBroadcastDialog.cpp @@ -9,7 +9,7 @@ #include "utils/NetworkManager.h" TxBroadcastDialog::TxBroadcastDialog(QWidget *parent, QSharedPointer ctx, const QString &transactionHex) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxBroadcastDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/TxBroadcastDialog.h b/src/dialog/TxBroadcastDialog.h index 944d463d..98f4e194 100644 --- a/src/dialog/TxBroadcastDialog.h +++ b/src/dialog/TxBroadcastDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "utils/daemonrpc.h" namespace Ui { class TxBroadcastDialog; } -class TxBroadcastDialog : public QDialog +class TxBroadcastDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxConfAdvDialog.cpp b/src/dialog/TxConfAdvDialog.cpp index 3ddb412f..db3b230b 100644 --- a/src/dialog/TxConfAdvDialog.cpp +++ b/src/dialog/TxConfAdvDialog.cpp @@ -14,7 +14,7 @@ #include "qrcode/QrCode.h" TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString &description, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxConfAdvDialog) , m_ctx(std::move(ctx)) , m_exportUnsignedMenu(new QMenu(this)) diff --git a/src/dialog/TxConfAdvDialog.h b/src/dialog/TxConfAdvDialog.h index c4df7161..a0e50699 100644 --- a/src/dialog/TxConfAdvDialog.h +++ b/src/dialog/TxConfAdvDialog.h @@ -10,13 +10,14 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/PendingTransaction.h" namespace Ui { class TxConfAdvDialog; } -class TxConfAdvDialog : public QDialog +class TxConfAdvDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxConfDialog.cpp b/src/dialog/TxConfDialog.cpp index 5cb27767..9db85092 100644 --- a/src/dialog/TxConfDialog.cpp +++ b/src/dialog/TxConfDialog.cpp @@ -13,7 +13,7 @@ #include "utils/ColorScheme.h" TxConfDialog::TxConfDialog(QSharedPointer ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxConfDialog) , m_ctx(std::move(ctx)) , m_tx(tx) diff --git a/src/dialog/TxConfDialog.h b/src/dialog/TxConfDialog.h index 4d2ab877..0fd5cf4d 100644 --- a/src/dialog/TxConfDialog.h +++ b/src/dialog/TxConfDialog.h @@ -7,6 +7,7 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/PendingTransaction.h" #include "libwalletqt/WalletManager.h" @@ -14,7 +15,7 @@ namespace Ui { class TxConfDialog; } -class TxConfDialog : public QDialog +class TxConfDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxImportDialog.cpp b/src/dialog/TxImportDialog.cpp index 2a00bcc7..56645ad5 100644 --- a/src/dialog/TxImportDialog.cpp +++ b/src/dialog/TxImportDialog.cpp @@ -9,7 +9,7 @@ #include "utils/NetworkManager.h" TxImportDialog::TxImportDialog(QWidget *parent, QSharedPointer ctx) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxImportDialog) , m_ctx(std::move(ctx)) , m_loadTimer(new QTimer(this)) diff --git a/src/dialog/TxImportDialog.h b/src/dialog/TxImportDialog.h index 49192527..4e5f5a85 100644 --- a/src/dialog/TxImportDialog.h +++ b/src/dialog/TxImportDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "utils/daemonrpc.h" namespace Ui { class TxImportDialog; } -class TxImportDialog : public QDialog +class TxImportDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/TxProofDialog.cpp b/src/dialog/TxProofDialog.cpp index 9299c4d5..e3ac4522 100644 --- a/src/dialog/TxProofDialog.cpp +++ b/src/dialog/TxProofDialog.cpp @@ -11,7 +11,7 @@ #include "utils/Utils.h" TxProofDialog::TxProofDialog(QWidget *parent, QSharedPointer ctx, TransactionInfo *txInfo) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::TxProofDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/TxProofDialog.h b/src/dialog/TxProofDialog.h index ba47a57f..7c1a84f5 100644 --- a/src/dialog/TxProofDialog.h +++ b/src/dialog/TxProofDialog.h @@ -7,13 +7,14 @@ #include #include "appcontext.h" +#include "components.h" #include "libwalletqt/TransactionInfo.h" namespace Ui { class TxProofDialog; } -class TxProofDialog : public QDialog +class TxProofDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/VerifyProofDialog.cpp b/src/dialog/VerifyProofDialog.cpp index d696f8f6..f27aa027 100644 --- a/src/dialog/VerifyProofDialog.cpp +++ b/src/dialog/VerifyProofDialog.cpp @@ -11,7 +11,7 @@ #include "utils/Utils.h" VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::VerifyProofDialog) , m_wallet(wallet) { diff --git a/src/dialog/VerifyProofDialog.h b/src/dialog/VerifyProofDialog.h index 8c538eb6..4d9f3ab4 100644 --- a/src/dialog/VerifyProofDialog.h +++ b/src/dialog/VerifyProofDialog.h @@ -7,13 +7,14 @@ #include #include +#include "components.h" #include "libwalletqt/Wallet.h" namespace Ui { class VerifyProofDialog; } -class VerifyProofDialog : public QDialog +class VerifyProofDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/ViewOnlyDialog.cpp b/src/dialog/ViewOnlyDialog.cpp index a031a1a2..d1251cc5 100644 --- a/src/dialog/ViewOnlyDialog.cpp +++ b/src/dialog/ViewOnlyDialog.cpp @@ -9,7 +9,7 @@ #include ViewOnlyDialog::ViewOnlyDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::ViewOnlyDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/ViewOnlyDialog.h b/src/dialog/ViewOnlyDialog.h index 3cb99429..8f137a8f 100644 --- a/src/dialog/ViewOnlyDialog.h +++ b/src/dialog/ViewOnlyDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class ViewOnlyDialog; } -class ViewOnlyDialog : public QDialog +class ViewOnlyDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/WalletCacheDebugDialog.cpp b/src/dialog/WalletCacheDebugDialog.cpp index f2547ee2..2805c1ca 100644 --- a/src/dialog/WalletCacheDebugDialog.cpp +++ b/src/dialog/WalletCacheDebugDialog.cpp @@ -9,7 +9,7 @@ #include "model/ModelUtils.h" WalletCacheDebugDialog::WalletCacheDebugDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::WalletCacheDebugDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/WalletCacheDebugDialog.h b/src/dialog/WalletCacheDebugDialog.h index 0b8f0768..5778814c 100644 --- a/src/dialog/WalletCacheDebugDialog.h +++ b/src/dialog/WalletCacheDebugDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class WalletCacheDebugDialog; } -class WalletCacheDebugDialog : public QDialog +class WalletCacheDebugDialog : public WindowModalDialog { Q_OBJECT diff --git a/src/dialog/WalletInfoDialog.cpp b/src/dialog/WalletInfoDialog.cpp index fac1766b..bcf8f77d 100644 --- a/src/dialog/WalletInfoDialog.cpp +++ b/src/dialog/WalletInfoDialog.cpp @@ -7,7 +7,7 @@ #include WalletInfoDialog::WalletInfoDialog(QSharedPointer ctx, QWidget *parent) - : QDialog(parent) + : WindowModalDialog(parent) , ui(new Ui::WalletInfoDialog) , m_ctx(std::move(ctx)) { diff --git a/src/dialog/WalletInfoDialog.h b/src/dialog/WalletInfoDialog.h index 9ab4c25d..1c77e748 100644 --- a/src/dialog/WalletInfoDialog.h +++ b/src/dialog/WalletInfoDialog.h @@ -7,12 +7,13 @@ #include #include "appcontext.h" +#include "components.h" namespace Ui { class WalletInfoDialog; } -class WalletInfoDialog : public QDialog +class WalletInfoDialog : public WindowModalDialog { Q_OBJECT From dcb22245376969ed37d2b5058c0f24f6e11e43d0 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 20:28:30 +0200 Subject: [PATCH 39/56] TxProofDialog: don't allow creating InProofs for out. txs, fix misleading error msg --- src/dialog/TxProofDialog.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/dialog/TxProofDialog.cpp b/src/dialog/TxProofDialog.cpp index e3ac4522..a2d2a6ac 100644 --- a/src/dialog/TxProofDialog.cpp +++ b/src/dialog/TxProofDialog.cpp @@ -88,13 +88,13 @@ void TxProofDialog::selectOutProof() { m_mode = Mode::OutProof; this->resetFrames(); - if (m_OutDestinations.empty()) { - this->showWarning("This transaction did not spend any outputs owned by this wallet. Creating an OutProof is not possible."); + if (m_txKey.isEmpty()) { + this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible."); return; } - if (m_txKey.isEmpty()) { - this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible."); + if (m_OutDestinations.empty()) { + this->showWarning("This transaction did not send funds to any known addresses. Creating an OutProof is not possible."); return; } @@ -107,6 +107,11 @@ void TxProofDialog::selectInProof() { m_mode = Mode::InProof; this->resetFrames(); + if (m_direction == TransactionInfo::Direction_Out) { + this->showWarning("Can't create InProofs for outgoing transactions."); + return; + } + if (m_InDestinations.empty()) { this->showWarning("Your wallet did not receive any outputs in this transaction."); return; From 6ccc98842082b2e92df2950401e2f59652a970c0 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 21:25:27 +0200 Subject: [PATCH 40/56] VerifyProofDialog: don't hang on verify --- src/dialog/VerifyProofDialog.cpp | 57 ++++++++++++++++++++------------ src/dialog/VerifyProofDialog.h | 2 ++ src/libwalletqt/Wallet.cpp | 15 +++++++++ src/libwalletqt/Wallet.h | 5 +++ src/main.cpp | 2 ++ 5 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/dialog/VerifyProofDialog.cpp b/src/dialog/VerifyProofDialog.cpp index f27aa027..937b6d9b 100644 --- a/src/dialog/VerifyProofDialog.cpp +++ b/src/dialog/VerifyProofDialog.cpp @@ -50,6 +50,9 @@ VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent) } }); + connect(m_wallet, &Wallet::transactionProofVerified, this, &VerifyProofDialog::onTxProofVerified); + connect(m_wallet, &Wallet::spendProofVerified, this, &VerifyProofDialog::onSpendProofVerified); + ui->input_formattedProof->setFont(ModelUtils::getMonospaceFont()); } @@ -69,31 +72,15 @@ void VerifyProofDialog::checkProof() { void VerifyProofDialog::checkTxProof(const QString &txId, const QString &address, const QString &message, const QString &signature) { - TxProofResult r = m_wallet->checkTxProof(txId, address, message, signature); - - if (!r.success) { - this->proofStatus(false, m_wallet->errorString()); - return; - } - - if (!r.good) { - this->proofStatus(false, "Proof is invalid"); - return; - } - - this->proofStatus(true, QString("Proof is valid.\n\nThis address received %1 XMR, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations))); + ui->btn_verifyFormattedProof->setEnabled(false); + ui->btn_verify->setEnabled(false); + m_wallet->checkTxProofAsync(txId, address, message, signature); } void VerifyProofDialog::checkSpendProof(const QString &txId, const QString &message, const QString &signature) { - auto r = m_wallet->checkSpendProof(txId, message, signature); - - if (!r.first) { - this->proofStatus(false, m_wallet->errorString()); - return; - } - - r.second ? this->proofStatus(true, "Proof is valid") - : this->proofStatus(false, "Proof is invalid"); + ui->btn_verifyFormattedProof->setEnabled(false); + ui->btn_verify->setEnabled(false); + m_wallet->checkSpendProofAsync(txId, message, signature); } void VerifyProofDialog::checkOutProof() { @@ -163,11 +150,37 @@ void VerifyProofDialog::proofStatus(bool success, const QString &message) { ui->frame_status->show(); ui->label_icon->setPixmap(success ? m_success : m_failure); ui->label_status->setText(message); + ui->btn_verifyFormattedProof->setEnabled(true); } else { + ui->btn_verify->setEnabled(true); success ? QMessageBox::information(this, "Information", message) : QMessageBox::warning(this, "Warning", message); } } +void VerifyProofDialog::onTxProofVerified(TxProofResult r) { + if (!r.success) { + this->proofStatus(false, m_wallet->errorString()); + return; + } + + if (!r.good) { + this->proofStatus(false, "Proof is invalid"); + return; + } + + this->proofStatus(true, QString("Proof is valid.\n\nThis address received %1 XMR, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations))); +} + +void VerifyProofDialog::onSpendProofVerified(QPair r) { + if (!r.first) { + this->proofStatus(false, m_wallet->errorString()); + return; + } + + r.second ? this->proofStatus(true, "Proof is valid") + : this->proofStatus(false, "Proof is invalid"); +} + VerifyProofDialog::~VerifyProofDialog() = default; \ No newline at end of file diff --git a/src/dialog/VerifyProofDialog.h b/src/dialog/VerifyProofDialog.h index 4d9f3ab4..ca2f641a 100644 --- a/src/dialog/VerifyProofDialog.h +++ b/src/dialog/VerifyProofDialog.h @@ -32,6 +32,8 @@ private: void checkInProof(); void checkFormattedProof(); void proofStatus(bool success, const QString &message); + void onTxProofVerified(TxProofResult result); + void onSpendProofVerified(QPair result); QScopedPointer ui; Wallet *m_wallet; diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index ea1d844d..41225dd2 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -1016,6 +1016,14 @@ TxProofResult Wallet::checkTxProof(const QString &txid, const QString &address, return {success, good, received, in_pool, confirmations}; } +void Wallet::checkTxProofAsync(const QString &txid, const QString &address, const QString &message, + const QString &signature) { + m_scheduler.run([this, txid, address, message, signature] { + auto result = this->checkTxProof(txid, address, message, signature); + emit transactionProofVerified(result); + }); +} + Q_INVOKABLE TxProof Wallet::getSpendProof(const QString &txid, const QString &message) const { std::string result = m_walletImpl->getSpendProof(txid.toStdString(), message.toStdString()); @@ -1036,6 +1044,13 @@ Q_INVOKABLE QPair Wallet::checkSpendProof(const QString &txid, const return {success, good}; } +void Wallet::checkSpendProofAsync(const QString &txid, const QString &message, const QString &signature) { + m_scheduler.run([this, txid, message, signature] { + auto result = this->checkSpendProof(txid, message, signature); + emit spendProofVerified(result); + }); +} + QString Wallet::signMessage(const QString &message, bool filename, const QString &address) const { if (filename) { diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index f54e8c77..9bcdd670 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -61,6 +61,7 @@ class Coins; class CoinsModel; struct TxProofResult { + TxProofResult() {} TxProofResult(bool success, bool good, uint64_t received, bool in_pool, uint64_t confirmations) : success(success), good(good), received(received), in_pool(in_pool), confirmations(confirmations){} @@ -406,9 +407,11 @@ public: // void getTxProofAsync(const QString &txid, const QString &address, const QString &message, const QJSValue &callback); //QString checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature); TxProofResult checkTxProof(const QString &txid, const QString &address, const QString &message, const QString &signature); + void checkTxProofAsync(const QString &txid, const QString &address, const QString &message, const QString &signature); TxProof getSpendProof(const QString &txid, const QString &message) const; // void getSpendProofAsync(const QString &txid, const QString &message, const QJSValue &callback); QPair checkSpendProof(const QString &txid, const QString &message, const QString &signature) const; + void checkSpendProofAsync(const QString &txid, const QString &message, const QString &signature); // Rescan spent outputs bool rescanSpent(); @@ -475,6 +478,8 @@ signals: void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const; void deviceShowAddressShowed(); + void transactionProofVerified(TxProofResult result); + void spendProofVerified(QPair result); // emitted when transaction is created async void transactionCreated(PendingTransaction * transaction, QVector address); diff --git a/src/main.cpp b/src/main.cpp index 68501959..c748615e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -207,6 +207,8 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { qInstallMessageHandler(Utils::applicationLogHandler); qRegisterMetaType>(); + qRegisterMetaType("TxProofResult"); + qRegisterMetaType>(); WindowManager windowManager; From 2e7b23898cfbfb6abebcc5f2d9ffae68e431045a Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 23:26:28 +0200 Subject: [PATCH 41/56] History: update on websocket fiat rates received --- src/HistoryWidget.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/HistoryWidget.cpp b/src/HistoryWidget.cpp index eef0c867..c4465172 100644 --- a/src/HistoryWidget.cpp +++ b/src/HistoryWidget.cpp @@ -11,6 +11,7 @@ #include "dialog/TxProofDialog.h" #include "utils/config.h" #include "utils/Icons.h" +#include "WebsocketNotifier.h" HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) : QWidget(parent) @@ -53,6 +54,10 @@ HistoryWidget::HistoryWidget(QSharedPointer ctx, QWidget *parent) ui->syncNotice->setVisible(config()->get(Config::showHistorySyncNotice).toBool()); ui->history->setHistoryModel(m_model); + connect(websocketNotifier(), &WebsocketNotifier::FiatRatesReceived, [this]{ + ui->history->update(); + }); + // Load view state QByteArray historyViewState = QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()); if (!historyViewState.isEmpty()) { From ab32e0b2a36d53cd172a0f9afcfa52a36f0fb93c Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 22 Oct 2021 23:35:20 +0200 Subject: [PATCH 42/56] Send: fix multidest parse line numbers --- src/widgets/PayToEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/PayToEdit.cpp b/src/widgets/PayToEdit.cpp index 1a7fc813..740f5a48 100644 --- a/src/widgets/PayToEdit.cpp +++ b/src/widgets/PayToEdit.cpp @@ -170,8 +170,9 @@ void PayToEdit::parseAsMultiline(const QStringList &lines) { m_outputs.clear(); m_total = 0; - int i = 0; + int i = -1; for (auto &line : lines) { + i++; PartialTxOutput output = this->parseAddressAndAmount(line); if (output.address.isEmpty() && output.amount == 0) { m_errors.append(PayToLineError(line, "Expected two comma-separated values: (address, amount)", i, true)); @@ -186,6 +187,5 @@ void PayToEdit::parseAsMultiline(const QStringList &lines) { m_outputs.append(output); m_total += output.amount; - i += 1; } } \ No newline at end of file From 779ae278f9fd5b5bdaa6746e5acbc4ae50f1f020 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 00:14:34 +0200 Subject: [PATCH 43/56] Send: resolve alias don't hang UI --- src/SendWidget.cpp | 33 ++++++++++++++++++------ src/SendWidget.h | 2 +- src/appcontext.cpp | 42 ------------------------------- src/appcontext.h | 3 --- src/libwalletqt/WalletManager.cpp | 14 ++++++++--- src/libwalletqt/WalletManager.h | 4 ++- 6 files changed, 39 insertions(+), 59 deletions(-) diff --git a/src/SendWidget.cpp b/src/SendWidget.cpp index 92f9a34c..b124cf24 100644 --- a/src/SendWidget.cpp +++ b/src/SendWidget.cpp @@ -31,8 +31,8 @@ SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) connect(m_ctx.get(), &AppContext::initiateTransaction, this, &SendWidget::onInitiateTransaction); connect(m_ctx.get(), &AppContext::endTransaction, this, &SendWidget::onEndTransaction); - connect(m_ctx.get(), &AppContext::openAliasResolved, this, &SendWidget::onOpenAliasResolved); - connect(m_ctx.get(), &AppContext::openAliasResolveError, this, &SendWidget::onOpenAliasResolveError); + + connect(WalletManager::instance(), &WalletManager::openAliasResolved, this, &SendWidget::onOpenAliasResolved); connect(ui->btnScan, &QPushButton::clicked, this, &SendWidget::scanClicked); connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked); @@ -139,8 +139,8 @@ void SendWidget::sendClicked() { QString currency = ui->comboCurrencySelection->currentText(); QString recipient = ui->lineAddress->text().simplified().remove(' '); QString description = ui->lineDescription->text(); - if(recipient.isEmpty()) { - QMessageBox::warning(this, "Malformed recipient", "The recipient address was not correct"); + if (recipient.isEmpty()) { + QMessageBox::warning(this, "Malformed recipient", "No destination address was entered."); return; } @@ -178,7 +178,7 @@ void SendWidget::sendClicked() { amount = this->amount(); bool sendAll = (ui->lineAmount->text() == "all"); if (amount == 0 && !sendAll) { - QMessageBox::warning(this, "Amount error", "Invalid amount specified."); + QMessageBox::warning(this, "Amount error", "No amount was entered."); return; } m_ctx->onCreateTransaction(recipient, amount, description, sendAll); @@ -193,8 +193,8 @@ void SendWidget::sendClicked() { } void SendWidget::aliasClicked() { - auto address = ui->lineAddress->text(); - m_ctx->onOpenAliasResolve(address); + auto alias = ui->lineAddress->text(); + WalletManager::instance()->resolveOpenAliasAsync(alias); } void SendWidget::clearClicked() { @@ -254,7 +254,24 @@ double SendWidget::amountDouble() { return amount / constants::cdiv; } -void SendWidget::onOpenAliasResolved(const QString &address, const QString &openAlias) { +void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid) { + if (address.isEmpty()) { + this->onOpenAliasResolveError("Could not resolve OpenAlias."); + return; + } + + if (!dnssecValid) { + this->onOpenAliasResolveError("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed."); + return; + } + + bool valid = WalletManager::addressValid(address, constants::networkType); + if (!valid) { + this->onOpenAliasResolveError(QString("Address validation error. Perhaps it is of the wrong network type.\n\n" + "OpenAlias: %1\nAddress: %2").arg(openAlias, address)); + return; + } + this->fill(address, openAlias); ui->btn_openAlias->hide(); } diff --git a/src/SendWidget.h b/src/SendWidget.h index 31c9c0a0..00199203 100644 --- a/src/SendWidget.h +++ b/src/SendWidget.h @@ -38,7 +38,7 @@ public slots: void fillAddress(const QString &address); void updateConversionLabel(); void onOpenAliasResolveError(const QString &err); - void onOpenAliasResolved(const QString &address, const QString &openAlias); + void onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid); void onPreferredFiatCurrencyChanged(); void onInitiateTransaction(); diff --git a/src/appcontext.cpp b/src/appcontext.cpp index cadf15dc..12ba6361 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -210,48 +210,6 @@ void AppContext::onSetRestoreHeight(quint64 height){ emit customRestoreHeightSet(height); } -void AppContext::onOpenAliasResolve(const QString &openAlias) { - // @TODO: calling this freezes for about 1-2 seconds :/ - const auto result = WalletManager::instance()->resolveOpenAlias(openAlias);; // TODO: async call - const auto spl = result.split("|"); - auto msg = QString(""); - if(spl.count() != 2) { - msg = "Internal error"; - emit openAliasResolveError(msg); - return; - } - - const auto &status = spl.at(0); - const auto &address = spl.at(1); - 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"; - emit openAliasResolveError(msg); - return; - } else { - msg = "No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed"; - emit openAliasResolveError(msg); - return; - } - } else if(status != "true") { - msg = "Internal error"; - emit openAliasResolveError(msg); - return; - } - - if(valid){ - emit openAliasResolved(address, openAlias); - return; - } - - msg = QString("Address validation error."); - if(!address.isEmpty()) - msg += QString(" Perhaps it is of the wrong network type." - "\n\nOpenAlias: %1\nAddress: %2").arg(openAlias).arg(address); - emit openAliasResolveError(msg); -} - void AppContext::stopTimers() { m_storeTimer.stop(); } diff --git a/src/appcontext.h b/src/appcontext.h index d3e4d239..d3850e26 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -55,7 +55,6 @@ public slots: void onCancelTransaction(PendingTransaction *tx, const QVector &address); void onSweepOutputs(const QVector &keyImages, QString address, bool churn, int outputs); void onCreateTransactionError(const QString &msg); - void onOpenAliasResolve(const QString &openAlias); void onSetRestoreHeight(quint64 height); void onMultiBroadcast(PendingTransaction *tx); void onDeviceButtonRequest(quint64 code); @@ -86,8 +85,6 @@ signals: void createTransactionError(QString message); void createTransactionCancelled(const QVector &address, quint64 amount); void createTransactionSuccess(PendingTransaction *tx, const QVector &address); - void openAliasResolveError(const QString &msg); - void openAliasResolved(const QString &address, const QString &openAlias); void setRestoreHeightError(const QString &msg); void customRestoreHeightSet(int height); void initiateTransaction(); diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 9c47dd85..bb1ef153 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -297,14 +297,20 @@ bool WalletManager::isDaemonLocal(const QString &daemon_address) const return daemon_address.isEmpty() ? false : Monero::Utils::isAddressLocal(daemon_address.toStdString()); } -QString WalletManager::resolveOpenAlias(const QString &address) const +QString WalletManager::resolveOpenAlias(const QString &address, bool &dnssecValid) const { - bool dnssec_valid = false; - std::string res = m_pimpl->resolveOpenAlias(address.toStdString(), dnssec_valid); - res = std::string(dnssec_valid ? "true" : "false") + "|" + res; + std::string res = m_pimpl->resolveOpenAlias(address.toStdString(), dnssecValid); return QString::fromStdString(res); } +void WalletManager::resolveOpenAliasAsync(const QString &alias) { + m_scheduler.run([this, alias] { + bool dnssecValid; + QString address = this->resolveOpenAlias(alias, dnssecValid); + emit openAliasResolved(alias, address, dnssecValid); + }); +} + void WalletManager::setLogLevel(int logLevel) { Monero::WalletManagerFactory::setLogLevel(logLevel); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index b9086c5d..47e6176b 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -142,7 +142,8 @@ public: Q_INVOKABLE qint64 addi(qint64 x, qint64 y) const { return x + y; } Q_INVOKABLE qint64 subi(qint64 x, qint64 y) const { return x - y; } - Q_INVOKABLE QString resolveOpenAlias(const QString &address) const; + QString resolveOpenAlias(const QString &address, bool &dnssecValid) const; + void resolveOpenAliasAsync(const QString &address); // clear/rename wallet cache Q_INVOKABLE static bool clearWalletCache(const QString &fileName); @@ -162,6 +163,7 @@ signals: void deviceError(const QString &message); void miningStatus(bool isMining) const; void proxyAddressChanged() const; + void openAliasResolved(const QString &alias, const QString &address, bool dnssecValid); public slots: private: From a9147d8da3f4e75147fcf6296ef10c9f59ec3c73 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 01:46:08 +0200 Subject: [PATCH 44/56] TxConfAdv: remove unlock time, add fiat amounts, make description editable --- src/MainWindow.cpp | 2 +- src/appcontext.cpp | 14 +++------- src/appcontext.h | 2 +- src/dialog/TxConfAdvDialog.cpp | 47 ++++++++++++++++++++++++++-------- src/dialog/TxConfAdvDialog.h | 2 ++ src/dialog/TxConfAdvDialog.ui | 46 +++++++++++++-------------------- src/libwalletqt/Wallet.cpp | 14 +++++++--- src/libwalletqt/Wallet.h | 2 +- 8 files changed, 75 insertions(+), 54 deletions(-) diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 4f083529..bced3c9c 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -634,7 +634,7 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto break; } case QDialog::Accepted: - m_ctx->commitTransaction(tx); + m_ctx->commitTransaction(tx, m_ctx->tmpTxDescription); break; } diff --git a/src/appcontext.cpp b/src/appcontext.cpp index 12ba6361..2318557f 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -72,7 +72,8 @@ void AppContext::onCreateTransaction(const QString &address, quint64 amount, con quint64 unlocked_balance = this->wallet->unlockedBalance(); if (!all && amount > unlocked_balance) { - emit createTransactionError("Not enough money to spend"); + emit createTransactionError(QString("Not enough money to spend.\n\n" + "Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance))); return; } else if (unlocked_balance == 0) { emit createTransactionError("No money to spend"); @@ -129,14 +130,14 @@ void AppContext::onCancelTransaction(PendingTransaction *tx, const QVectorwallet->disposeTransaction(tx); } -void AppContext::commitTransaction(PendingTransaction *tx) { +void AppContext::commitTransaction(PendingTransaction *tx, const QString &description) { // Nodes - even well-connected, properly configured ones - consistently fail to relay transactions // To mitigate transactions failing we just send the transaction to every node we know about over Tor if (config()->get(Config::multiBroadcast).toBool()) { this->onMultiBroadcast(tx); } - this->wallet->commitTransactionAsync(tx); + this->wallet->commitTransactionAsync(tx, description); } void AppContext::onMultiBroadcast(PendingTransaction *tx) { @@ -305,13 +306,6 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVectorwallet->setUserNote(entry, this->tmpTxDescription); - } - this->tmpTxDescription = ""; - } - // Store wallet immediately so we don't risk losing tx key if wallet crashes this->wallet->store(); diff --git a/src/appcontext.h b/src/appcontext.h index d3850e26..806bb88a 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -37,7 +37,7 @@ public: // libwalletqt bool refreshed = false; - void commitTransaction(PendingTransaction *tx); + void commitTransaction(PendingTransaction *tx, const QString &description=""); void syncStatusUpdated(quint64 height, quint64 target); void updateBalance(); void refreshModels(); diff --git a/src/dialog/TxConfAdvDialog.cpp b/src/dialog/TxConfAdvDialog.cpp index db3b230b..95db78c0 100644 --- a/src/dialog/TxConfAdvDialog.cpp +++ b/src/dialog/TxConfAdvDialog.cpp @@ -7,6 +7,7 @@ #include #include +#include "constants.h" #include "dialog/QrCodeDialog.h" #include "libwalletqt/Input.h" #include "libwalletqt/Transfer.h" @@ -35,12 +36,16 @@ TxConfAdvDialog::TxConfAdvDialog(QSharedPointer ctx, const QString & m_exportTxKeyMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::txKeyCopy); ui->btn_exportTxKey->setMenu(m_exportTxKeyMenu); - ui->label_description->setText(QString("Description: %1").arg(description)); + ui->line_description->setText(description); connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction); connect(ui->btn_send, &QPushButton::clicked, this, &TxConfAdvDialog::broadcastTransaction); connect(ui->btn_close, &QPushButton::clicked, this, &TxConfAdvDialog::closeDialog); + ui->amount->setFont(ModelUtils::getMonospaceFont()); + ui->fee->setFont(ModelUtils::getMonospaceFont()); + ui->total->setFont(ModelUtils::getMonospaceFont()); + ui->inputs->setFont(ModelUtils::getMonospaceFont()); ui->outputs->setFont(ModelUtils::getMonospaceFont()); @@ -64,11 +69,10 @@ void TxConfAdvDialog::setTransaction(PendingTransaction *tx, bool isSigned) { ui->btn_exportTxKey->hide(); } - ui->txid->setText(tx->txid().first()); + m_txid = tx->txid().first(); + ui->txid->setText(m_txid); - ui->amount->setText(WalletManager::displayAmount(tx->amount())); - ui->fee->setText(WalletManager::displayAmount(ptx->fee())); - ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee())); + this->setAmounts(tx->amount(), tx->fee()); auto size_str = [this, isSigned]{ if (isSigned) { @@ -97,14 +101,38 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) { ui->txid->setText("n/a"); ui->label_size->setText("Size: n/a"); - ui->amount->setText(WalletManager::displayAmount(utx->amount(0))); - ui->fee->setText(WalletManager::displayAmount(utx->fee(0))); - ui->total->setText(WalletManager::displayAmount(utx->amount(0) + utx->fee(0))); + this->setAmounts(utx->amount(0), utx->fee(0)); ConstructionInfo *ci = m_utx->constructionInfo(0); this->setupConstructionData(ci); } +void TxConfAdvDialog::setAmounts(quint64 amount, quint64 fee) { + QString preferredCur = config()->get(Config::preferredFiatCurrency).toString(); + + auto convert = [preferredCur](double amount){ + return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2); + }; + + QString amount_str = WalletManager::displayAmount(amount); + QString fee_str = WalletManager::displayAmount(fee); + QString total = WalletManager::displayAmount(amount + fee); + QVector amounts = {amount_str, fee_str, total}; + int maxLength = Utils::maxLength(amounts); + std::for_each(amounts.begin(), amounts.end(), [maxLength](QString& amount){amount = amount.rightJustified(maxLength, ' ');}); + + QString amount_fiat = convert(amount / constants::cdiv); + QString fee_fiat = convert(fee / constants::cdiv); + QString total_fiat = convert((amount + fee) / constants::cdiv); + QVector amounts_fiat = {amount_fiat, fee_fiat, total_fiat}; + int maxLengthFiat = Utils::maxLength(amounts_fiat); + std::for_each(amounts_fiat.begin(), amounts_fiat.end(), [maxLengthFiat](QString& amount){amount = amount.rightJustified(maxLengthFiat, ' ');}); + + ui->amount->setText(QString("%1 (%2 %3)").arg(amounts[0], amounts_fiat[0], preferredCur)); + ui->fee->setText(QString("%1 (%2 %3)").arg(amounts[1], amounts_fiat[1], preferredCur)); + ui->total->setText(QString("%1 (%2 %3)").arg(amounts[2], amounts_fiat[2], preferredCur)); +} + void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { QString inputs_str; auto inputs = ci->inputs(); @@ -128,7 +156,6 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size()))); ui->label_ringSize->setText(QString("Ring size: %1").arg(QString::number(ci->minMixinCount() + 1))); - ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(QString::number(ci->unlockTime()))); } void TxConfAdvDialog::signTransaction() { @@ -186,7 +213,7 @@ void TxConfAdvDialog::signedQrCode() { void TxConfAdvDialog::broadcastTransaction() { if (m_tx == nullptr) return; - m_ctx->commitTransaction(m_tx); + m_ctx->commitTransaction(m_tx, ui->line_description->text()); QDialog::accept(); } diff --git a/src/dialog/TxConfAdvDialog.h b/src/dialog/TxConfAdvDialog.h index a0e50699..3e2a7f05 100644 --- a/src/dialog/TxConfAdvDialog.h +++ b/src/dialog/TxConfAdvDialog.h @@ -33,6 +33,7 @@ private: void signTransaction(); void broadcastTransaction(); void closeDialog(); + void setAmounts(quint64 amount, quint64 fee); void unsignedCopy(); void unsignedQrCode(); @@ -51,6 +52,7 @@ private: QMenu *m_exportUnsignedMenu; QMenu *m_exportSignedMenu; QMenu *m_exportTxKeyMenu; + QString m_txid; }; #endif //FEATHER_TXCONFADVDIALOG_H diff --git a/src/dialog/TxConfAdvDialog.ui b/src/dialog/TxConfAdvDialog.ui index 7030ee12..2a37803d 100644 --- a/src/dialog/TxConfAdvDialog.ui +++ b/src/dialog/TxConfAdvDialog.ui @@ -21,15 +21,15 @@ - - + + Transaction ID: - + txid @@ -41,13 +41,6 @@ - - - - Qt::Horizontal - - - @@ -122,14 +115,21 @@ - - - Description: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - + + + + + Description: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + @@ -141,16 +141,6 @@ - - - - Unlock time: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 41225dd2..8cfefe33 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -826,11 +826,19 @@ bool Wallet::refresh(bool historyAndSubaddresses /* = true */) } } -void Wallet::commitTransactionAsync(PendingTransaction *t) +void Wallet::commitTransactionAsync(PendingTransaction *t, const QString &description) { - m_scheduler.run([this, t] { + m_scheduler.run([this, t, description] { auto txIdList = t->txid(); // retrieve before commit - emit transactionCommitted(t->commit(), t, txIdList); + bool success = t->commit(); + + if (success && !description.isEmpty()) { + for (const auto &txid : txIdList) { + this->setUserNote(txid, description); + } + } + + emit transactionCommitted(success, t, txIdList); }); } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 9bcdd670..5bf5bc23 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -329,7 +329,7 @@ public: bool submitTxFile(const QString &fileName) const; //! asynchronous transaction commit - void commitTransactionAsync(PendingTransaction * t); + void commitTransactionAsync(PendingTransaction * t, const QString &description=""); //! deletes transaction and frees memory void disposeTransaction(PendingTransaction * t); From 0bc873c5cc2638f0fe91270113eff4e2a41fe00b Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 01:46:38 +0200 Subject: [PATCH 45/56] Send: disable openalias button on resolve --- src/SendWidget.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/SendWidget.cpp b/src/SendWidget.cpp index b124cf24..b1f0f8bf 100644 --- a/src/SendWidget.cpp +++ b/src/SendWidget.cpp @@ -193,6 +193,7 @@ void SendWidget::sendClicked() { } void SendWidget::aliasClicked() { + ui->btn_openAlias->setEnabled(false); auto alias = ui->lineAddress->text(); WalletManager::instance()->resolveOpenAliasAsync(alias); } @@ -255,6 +256,8 @@ double SendWidget::amountDouble() { } void SendWidget::onOpenAliasResolved(const QString &openAlias, const QString &address, bool dnssecValid) { + ui->btn_openAlias->setEnabled(true); + if (address.isEmpty()) { this->onOpenAliasResolveError("Could not resolve OpenAlias."); return; From 6ff9960affb9c04e687a0b20ff4f43479e0c3361 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 01:46:59 +0200 Subject: [PATCH 46/56] Add missing icon --- src/assets/images/external-link.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/assets/images/external-link.svg diff --git a/src/assets/images/external-link.svg b/src/assets/images/external-link.svg new file mode 100644 index 00000000..b0f7a16b --- /dev/null +++ b/src/assets/images/external-link.svg @@ -0,0 +1 @@ + \ No newline at end of file From 678d2511697825a92f883d4c1aea90bdae03f2f1 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 15:06:46 +0200 Subject: [PATCH 47/56] TxConfAdv: improve UI --- src/dialog/TxConfAdvDialog.ui | 96 +++++++++++++++++------------------ 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/dialog/TxConfAdvDialog.ui b/src/dialog/TxConfAdvDialog.ui index 2a37803d..80fbc07b 100644 --- a/src/dialog/TxConfAdvDialog.ui +++ b/src/dialog/TxConfAdvDialog.ui @@ -43,6 +43,54 @@ + + + + + + + + Description: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + + + Size: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Ringsize: + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + Qt::Vertical + + + @@ -105,54 +153,6 @@ - - - - Qt::Vertical - - - - - - - - - - - Description: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - - - - - - Size: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - Ringsize: - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - From 09e4185cabf3f6f33a8473f1f8739e6453b9bae0 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 15:25:29 +0200 Subject: [PATCH 48/56] Receive: clarify strings, remove cm icons --- src/ReceiveWidget.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/ReceiveWidget.cpp b/src/ReceiveWidget.cpp index f583af43..881c0334 100644 --- a/src/ReceiveWidget.cpp +++ b/src/ReceiveWidget.cpp @@ -44,8 +44,6 @@ ReceiveWidget::ReceiveWidget(QSharedPointer ctx, QWidget *parent) m_headerMenu = new QMenu(this); m_showFullAddressesAction = m_headerMenu->addAction("Show full addresses", this, &ReceiveWidget::setShowFullAddresses); m_showFullAddressesAction->setCheckable(true); - m_showUsedAddressesAction = m_headerMenu->addAction("Show used addresses", this, &ReceiveWidget::setShowUsedAddresses); - m_showUsedAddressesAction->setCheckable(true); connect(ui->addresses->header(), &QHeaderView::customContextMenuRequested, this, &ReceiveWidget::showHeaderMenu); // context menu @@ -99,9 +97,9 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { auto *menu = new QMenu(ui->addresses); - menu->addAction(icons()->icon("copy.png"), "Copy address", this, &ReceiveWidget::copyAddress); - menu->addAction(icons()->icon("copy.png"), "Copy label", this, &ReceiveWidget::copyLabel); - menu->addAction(icons()->icon("edit.png"), "Edit label", this, &ReceiveWidget::editLabel); + menu->addAction("Copy address", this, &ReceiveWidget::copyAddress); + menu->addAction("Copy label", this, &ReceiveWidget::copyLabel); + menu->addAction("Edit label", this, &ReceiveWidget::editLabel); if (isUsed) { menu->addAction(m_showTransactionsAction); @@ -109,7 +107,7 @@ void ReceiveWidget::showContextMenu(const QPoint &point) { QStringList hiddenAddresses = this->getHiddenAddresses(); if (hiddenAddresses.contains(address)) { - menu->addAction("Show address", this, &ReceiveWidget::showAddress); + menu->addAction("Unhide address", this, &ReceiveWidget::showAddress); } else { menu->addAction("Hide address", this, &ReceiveWidget::hideAddress); } From 6f5ac0a06e44d87b0f7c03a9f7fa01406d61953e Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 15:27:36 +0200 Subject: [PATCH 49/56] Contacts: remove cm icon --- src/ContactsWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ContactsWidget.cpp b/src/ContactsWidget.cpp index 04426615..bb2380d3 100644 --- a/src/ContactsWidget.cpp +++ b/src/ContactsWidget.cpp @@ -50,7 +50,7 @@ ContactsWidget::ContactsWidget(QSharedPointer ctx, QWidget *parent) // context menu ui->contacts->setContextMenuPolicy(Qt::CustomContextMenu); m_contextMenu = new QMenu(ui->contacts); - m_contextMenu->addAction(icons()->icon("person.svg"), "New contact", [this]{ + m_contextMenu->addAction("New contact", [this]{ this->newContact(); }); From b431638b4dad34fee1ae7a15973cb5f625528a5b Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 20:16:06 +0200 Subject: [PATCH 50/56] more acknowledgements --- src/assets/ack.txt | 53 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/src/assets/ack.txt b/src/assets/ack.txt index eb8b0d98..a226a52d 100644 --- a/src/assets/ack.txt +++ b/src/assets/ack.txt @@ -10,28 +10,73 @@ Shoutouts: +Alex_LocalMonero +anhdres +authentic +BigNastyHammer binaryFate -bignastyhammer -boogerlad +bits-of-change blasty +boldsuck +boogerlad +bruh +btsfav +csd +CzarekNakamoto +deanguss dEBRUYNE dnale0r fluffypony geonic +GhostintheQubes +GriftKilla +jberman +john_r365 jwinterm +kayabaNerve kico +kinghat lozbek -lyza +Lyza lza_menace +Matt Smith +MoneroArbo +moneromooo +mrdeveloper +Nekun +netrik182 +nikg83 nioc +noobmaximus +n-peugnet +onionltd +Paris +phoenix1213 +qvqc +rating89us rbrunner rehrar +rottenwheel samsunggalaxyplayer +samwhiskey +scoobybejesus selsta sethsimmons +Shakybeats SmashTR +SovereignStreetArt spoontechtips +stickyf00t +strace rottenwheel tevador thrmo -wowario \ No newline at end of file +tinkerwithtor +Vespco +viperperidot +witchman05 +wowario +xiphon +xmrscott +xubuntu +yanmaani \ No newline at end of file From 518fa831aedba2b5e1aba794343ece3a53bfaf68 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Sat, 23 Oct 2021 22:42:49 +0200 Subject: [PATCH 51/56] Coins: fix description edit on multi-account wallets --- CMakeLists.txt | 2 +- src/CoinsWidget.cpp | 5 ++--- src/libwalletqt/Coins.cpp | 4 ++-- src/libwalletqt/Coins.h | 2 +- src/model/CoinsModel.cpp | 10 +++++++++- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45010e0a..08f8ed38 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ if(DEBUG) set(CMAKE_VERBOSE_MAKEFILE ON) endif() -set(MONERO_HEAD "ed8859318349a5f01e9fd90e898603d4142b0951") +set(MONERO_HEAD "eba8ce661ce289df99f507225dbb1aaf58125e85") set(BUILD_GUI_DEPS ON) option(ARCH "Target architecture" "x86-64") set(BUILD_64 ON) diff --git a/src/CoinsWidget.cpp b/src/CoinsWidget.cpp index 767c4bc5..1808c70e 100644 --- a/src/CoinsWidget.cpp +++ b/src/CoinsWidget.cpp @@ -26,8 +26,7 @@ CoinsWidget::CoinsWidget(QSharedPointer ctx, QWidget *parent) connect(ui->coins->header(), &QHeaderView::customContextMenuRequested, this, &CoinsWidget::showHeaderMenu); // copy menu - m_copyMenu->setIcon(icons()->icon("copy.png")); - m_copyMenu->addAction("Public key", this, [this]{copy(copyField::PubKey);}); + m_copyMenu->addAction("Public Key", this, [this]{copy(copyField::PubKey);}); m_copyMenu->addAction("Key Image", this, [this]{copy(copyField::KeyImage);}); m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); m_copyMenu->addAction("Address", this, [this]{copy(copyField::Address);}); @@ -47,7 +46,7 @@ CoinsWidget::CoinsWidget(QSharedPointer ctx, QWidget *parent) m_freezeAllSelectedAction = new QAction("Freeze selected", this); m_thawAllSelectedAction = new QAction("Thaw selected", this); - m_viewOutputAction = new QAction(icons()->icon("info2.svg"), "Details", this); + m_viewOutputAction = new QAction("Details", this); m_sweepOutputAction = new QAction("Sweep output", this); m_sweepOutputsAction = new QAction("Sweep selected outputs", this); diff --git a/src/libwalletqt/Coins.cpp b/src/libwalletqt/Coins.cpp index b95d4f7c..5ba518b4 100644 --- a/src/libwalletqt/Coins.cpp +++ b/src/libwalletqt/Coins.cpp @@ -96,9 +96,9 @@ QVector Coins::coins_from_txid(const QString &txid) return coins; } -void Coins::setDescription(int index, quint32 accountIndex, const QString &description) +void Coins::setDescription(const QString &publicKey, quint32 accountIndex, const QString &description) { - m_pimpl->setDescription(index, description.toStdString()); + m_pimpl->setDescription(publicKey.toStdString(), description.toStdString()); this->refresh(accountIndex); emit descriptionChanged(); } diff --git a/src/libwalletqt/Coins.h b/src/libwalletqt/Coins.h index 7c73af35..cda1363d 100644 --- a/src/libwalletqt/Coins.h +++ b/src/libwalletqt/Coins.h @@ -31,7 +31,7 @@ public: Q_INVOKABLE void freeze(QString &publicKey) const; Q_INVOKABLE void thaw(QString &publicKey) const; Q_INVOKABLE QVector coins_from_txid(const QString &txid); - Q_INVOKABLE void setDescription(int index, quint32 accountIndex, const QString &description); + Q_INVOKABLE void setDescription(const QString &publicKey, quint32 accountIndex, const QString &description); quint64 count() const; diff --git a/src/model/CoinsModel.cpp b/src/model/CoinsModel.cpp index a5a409dc..7110c394 100644 --- a/src/model/CoinsModel.cpp +++ b/src/model/CoinsModel.cpp @@ -179,9 +179,17 @@ bool CoinsModel::setData(const QModelIndex &index, const QVariant &value, int ro if (index.isValid() && role == Qt::EditRole) { const int row = index.row(); + QString pubkey; + bool found = m_coins->coin(index.row(), [this, &pubkey](const CoinsInfo &cInfo) { + pubkey = cInfo.pubKey(); + }); + if (!found) { + return false; + } + switch (index.column()) { case Label: - m_coins->setDescription(row, m_currentSubaddressAccount, value.toString()); + m_coins->setDescription(pubkey, m_currentSubaddressAccount, value.toString()); emit descriptionChanged(); break; default: From fc9452b5133a8024ce53fe93b8bbbdbbe822a5ca Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 28 Oct 2021 18:55:47 +0200 Subject: [PATCH 52/56] fix macOS built-in updater --- CMakeLists.txt | 2 +- monero | 2 +- src/dialog/UpdateDialog.cpp | 49 +++++++++++++++++++++++++++++++------ src/dialog/UpdateDialog.h | 1 + src/utils/Utils.cpp | 10 ++++++++ src/utils/Utils.h | 1 + 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 08f8ed38..8e803939 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ if(DEBUG) set(CMAKE_VERBOSE_MAKEFILE ON) endif() -set(MONERO_HEAD "eba8ce661ce289df99f507225dbb1aaf58125e85") +set(MONERO_HEAD "bdd284b35d2e2c9c6ac733b4bc5ce8bd3b1162dd") set(BUILD_GUI_DEPS ON) option(ARCH "Target architecture" "x86-64") set(BUILD_64 ON) diff --git a/monero b/monero index d4257af2..bdd284b3 160000 --- a/monero +++ b/monero @@ -1 +1 @@ -Subproject commit d4257af2e7503fc6dc09fc704606230d353a0a02 +Subproject commit bdd284b35d2e2c9c6ac733b4bc5ce8bd3b1162dd diff --git a/src/dialog/UpdateDialog.cpp b/src/dialog/UpdateDialog.cpp index af673d85..5ed8fa42 100644 --- a/src/dialog/UpdateDialog.cpp +++ b/src/dialog/UpdateDialog.cpp @@ -117,6 +117,11 @@ void UpdateDialog::onInstallUpdate() { ui->btn_installUpdate->hide(); this->setStatus("Unzipping archive..."); +#ifdef Q_OS_MACOS + this->installUpdateMac(); + return; +#endif + zip_error_t err; zip_error_init(&err); @@ -172,14 +177,7 @@ void UpdateDialog::onInstallUpdate() { zip_fclose(zf); zip_close(zip_archive); - QString applicationPath = qgetenv("APPIMAGE"); - if (!applicationPath.isEmpty()) { - applicationPath = QFileInfo(applicationPath).absoluteDir().path(); - } else { - applicationPath = QCoreApplication::applicationDirPath(); - } - - QDir applicationDir(applicationPath); + QDir applicationDir(Utils::applicationPath()); QString filePath = applicationDir.filePath(name); m_updatePath = filePath; @@ -206,6 +204,41 @@ void UpdateDialog::onInstallUpdate() { ui->btn_restart->show(); } +void UpdateDialog::installUpdateMac() { + QString appPath = Utils::applicationPath(); + QDir appDir(appPath); + if (appPath.endsWith("Contents/MacOS")) { + appDir.cd("../../.."); + } + QString appName = QString("feather-%1").arg(m_version); + QString zipName = QString("%1.zip").arg(appName); + QString fPath = appDir.filePath(zipName); + + QFile file(fPath); + qDebug() << "Writing zip file to " << fPath; + if (!file.open(QIODevice::WriteOnly)) + { + this->onInstallError(QString("Error: Could not write to application path: %1").arg(fPath)); + return; + } + + if (static_cast(file.write(&m_updateZipArchive[0], m_updateZipArchive.size())) != m_updateZipArchive.size()) { + this->onInstallError("Error: Unable to write file"); + return; + } + + QProcess unzip; + unzip.start("/usr/bin/unzip", {"-n", fPath, "-d", appDir.path()}); + unzip.waitForFinished(); + + m_updatePath = QString("%1.app/Contents/MacOS/feather").arg(appDir.filePath(appName)); + + file.remove(); + + this->setStatus("Installation successful. Do you want to restart Feather now?"); + ui->btn_restart->show(); +} + void UpdateDialog::onInstallError(const QString &errMsg) { this->setStatus(errMsg); } diff --git a/src/dialog/UpdateDialog.h b/src/dialog/UpdateDialog.h index 27396665..b64332c8 100644 --- a/src/dialog/UpdateDialog.h +++ b/src/dialog/UpdateDialog.h @@ -33,6 +33,7 @@ signals: private: void setStatus(const QString &msg, bool success = false); + void installUpdateMac(); QScopedPointer ui; diff --git a/src/utils/Utils.cpp b/src/utils/Utils.cpp index f754c0fb..9be92c5f 100644 --- a/src/utils/Utils.cpp +++ b/src/utils/Utils.cpp @@ -126,6 +126,16 @@ QString defaultWalletDir() { #endif } +QString applicationPath() { + QString applicationPath = qgetenv("APPIMAGE"); + if (!applicationPath.isEmpty()) { + applicationPath = QFileInfo(applicationPath).absoluteDir().path(); + } else { + applicationPath = QCoreApplication::applicationDirPath(); + } + return applicationPath; +} + bool validateJSON(const QByteArray &blob) { QJsonDocument doc = QJsonDocument::fromJson(blob); QString jsonString = doc.toJson(QJsonDocument::Indented); diff --git a/src/utils/Utils.h b/src/utils/Utils.h index 2df870ff..ea20f7ac 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -23,6 +23,7 @@ namespace Utils bool dirExists(const QString &path); QString defaultWalletDir(); + QString applicationPath(); bool validateJSON(const QByteArray &blob); bool readJsonFile(QIODevice &device, QSettings::SettingsMap &map); From aa53b4e488dbb26503cf59f5cee14606a70f3b5a Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 28 Oct 2021 18:56:02 +0200 Subject: [PATCH 53/56] Update hardcoded nodes --- src/assets/nodes.json | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/assets/nodes.json b/src/assets/nodes.json index e95c3f53..8ed4e562 100644 --- a/src/assets/nodes.json +++ b/src/assets/nodes.json @@ -1,16 +1,22 @@ { "mainnet": { "tor": [ + "sfprpc5klzs5vyitq2mrooicgk2wcs5ho2nm3niqduvzn5o6ylaslaqd.onion:18089", + "sfprpc2fws6ltnq4hyr7lvpul3nank5layd7q7tyc5h4gy4h77gtabad.onion:18089", "mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081", "moneroxmrxw44lku6qniyarpwgznpcwml4drq7vb24ppatlcg4kmxpqd.onion:18089", - "rbpgdckle3h3vi4wwwrh75usqtoc5r3alohy7yyx57isynvay63nacyd.onion:18089", "6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081", "56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089", - "melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081" + "melo7jwjspngus3xhbt5kxeqc4njhokyvh55jfmehplglgmb7a6rb6yd.onion:18081", + "ip4zpbps7unk6xhlanqtw24f75akfbl3upeckfjqjks7ftfnk4i73oid.onion:18081", + "ghziyspoobhmp5oun2xcomrmetqiwbvuaegmte3s47nnqv7hkaa64sid.onion:18089", + "xmrnodesarnt4w35aqmu66aart3o324yw6qbnv6pglpof6uqaydzk5id.onion:18081", + "usexmr2eeexmlwpuvsfe6tyjmdqliplb2b7uxju6yrrziq3n7fksnxyd.onion:18081" ], "clearnet": [ "node.melo.tools:18081", - "node-1.sethsimmons.me:18089", + "node.sethforprivacy.com:18089", + "node2.sethforprivacy.com:18089", "selsta1.featherwallet.net:18081", "selsta2.featherwallet.net:18081", "node.monerooutreach.org:18081", From 17519ef7c0c5021db5c6fe216d134695b23e70d0 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 28 Oct 2021 18:57:53 +0200 Subject: [PATCH 54/56] Don't use WindowModal on macOS (for now) --- src/components.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components.cpp b/src/components.cpp index 810c49f2..c7261ce3 100644 --- a/src/components.cpp +++ b/src/components.cpp @@ -118,5 +118,7 @@ void ClickableLabel::mousePressEvent(QMouseEvent* event) { WindowModalDialog::WindowModalDialog(QWidget *parent) : QDialog(parent) { +#ifndef Q_OS_MACOS this->setWindowModality(Qt::WindowModal); +#endif } \ No newline at end of file From d68496bdd48a11702ebfc1940162fed2446bc5d4 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 28 Oct 2021 19:50:18 +0200 Subject: [PATCH 55/56] macOS: fix statusbar button clickability --- src/MainWindow.cpp | 16 ++++++++++++++++ src/MainWindow.h | 1 + src/assets.qrc | 1 + src/assets/macStylesheet.patch | 31 +++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 src/assets/macStylesheet.patch diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index bced3c9c..0dfcbb0d 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -95,6 +95,10 @@ void MainWindow::initStatusBar() { this->statusBar()->setStyleSheet("QStatusBar::item {border: None;}"); #endif +#if defined(Q_OS_MACOS) + this->patchStylesheetMac(); +#endif + this->statusBar()->setFixedHeight(30); m_statusLabelStatus = new QLabel("Idle", this); @@ -849,6 +853,10 @@ void MainWindow::skinChanged(const QString &skinName) { m_windowManager->changeSkin(skinName); ColorScheme::updateFromWidget(this); this->updateWidgetIcons(); + +#if defined(Q_OS_MACOS) + this->patchStylesheetMac(); +#endif } void MainWindow::updateWidgetIcons() { @@ -1586,6 +1594,14 @@ bool MainWindow::verifyPassword() { return true; } +void MainWindow::patchStylesheetMac() { + auto patch = Utils::fileOpenQRC(":assets/macStylesheet.patch"); + auto patch_text = Utils::barrayToString(patch); + + QString styleSheet = qApp->styleSheet() + patch_text; + qApp->setStyleSheet(styleSheet); +} + void MainWindow::toggleSearchbar(bool visible) { config()->set(Config::showSearchbar, visible); diff --git a/src/MainWindow.h b/src/MainWindow.h index fc863e15..a4fc5903 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -215,6 +215,7 @@ private: void updateRecentlyOpenedMenu(); void updateWidgetIcons(); bool verifyPassword(); + void patchStylesheetMac(); QIcon hardwareDevicePairedIcon(); QIcon hardwareDeviceUnpairedIcon(); diff --git a/src/assets.qrc b/src/assets.qrc index bf99e011..cf9e9abf 100644 --- a/src/assets.qrc +++ b/src/assets.qrc @@ -3,6 +3,7 @@ assets/about.txt assets/ack.txt assets/feather.desktop + assets/macStylesheet.patch assets/nodes.json assets/gpg_keys/featherwallet.asc assets/images/appicons/32x32.png diff --git a/src/assets/macStylesheet.patch b/src/assets/macStylesheet.patch new file mode 100644 index 00000000..955debd9 --- /dev/null +++ b/src/assets/macStylesheet.patch @@ -0,0 +1,31 @@ +/* From electrum/gui/qt/stylesheet_patcher.py */ +StatusBarButton { + background-color: transparent; + border: 1px solid transparent; + border-radius: 4px; + margin: 0px; + padding: 2px; +} + +StatusBarButton:checked { + background-color: transparent; + border: 1px solid #1464A0; +} + +StatusBarButton:checked:disabled { + border: 1px solid #14506E; +} + +StatusBarButton:pressed { + margin: 1px; + background-color: transparent; + border: 1px solid #1464A0; +} + +StatusBarButton:disabled { + border: none; +} + +StatusBarButton:hover { + border: 1px solid #148CD2; +} \ No newline at end of file From 628b9d75d915765c835b484b6662263f6a1f1a9f Mon Sep 17 00:00:00 2001 From: tobtoht Date: Thu, 28 Oct 2021 19:54:12 +0200 Subject: [PATCH 56/56] Fix PKGBUILD --- Makefile | 7 ++++++- PKGBUILD | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c2a7a3ef..5a633726 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,12 @@ CMAKEFLAGS = \ release: mkdir -p build/release && \ cd build/release && \ - cmake -D CMAKE_BUILD_TYPE=Release $(CMAKEFLAGS) ../.. && \ + cmake \ + -DARCH=x86-64 \ + -D BUILD_TAG="linux-x64" \ + -D CMAKE_BUILD_TYPE=Release \ + $(CMAKEFLAGS) \ + ../.. && \ $(MAKE) release-static: diff --git a/PKGBUILD b/PKGBUILD index 11de9ccb..f80c21ca 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -2,7 +2,7 @@ # Contributor: wowario pkgname='monero-feather-git' -pkgver=0.8.0.d3791cbd26 +pkgver=1.0.0 pkgrel=1 pkgdesc='A free Monero desktop wallet' license=('BSD')