2020-10-07 10:36:04 +00:00
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-12-26 19:56:06 +00:00
|
|
|
// Copyright (c) 2020-2021, The Monero Project.
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-06-28 17:48:23 +00:00
|
|
|
#include "SendWidget.h"
|
|
|
|
#include "ui_SendWidget.h"
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
#include <QMessageBox>
|
2021-06-28 17:48:23 +00:00
|
|
|
|
|
|
|
#include "ColorScheme.h"
|
2021-05-18 15:59:18 +00:00
|
|
|
#include "constants.h"
|
2021-05-02 18:22:38 +00:00
|
|
|
#include "utils/AppData.h"
|
2021-06-25 14:14:49 +00:00
|
|
|
#include "Icons.h"
|
|
|
|
|
|
|
|
#ifdef WITH_SCANNER
|
|
|
|
#include "qrcode_scanner/QrCodeScanDialog.h"
|
|
|
|
#endif
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-05-18 15:59:18 +00:00
|
|
|
SendWidget::SendWidget(QSharedPointer<AppContext> ctx, QWidget *parent)
|
2021-05-02 18:22:38 +00:00
|
|
|
: QWidget(parent)
|
|
|
|
, ui(new Ui::SendWidget)
|
2021-05-18 15:59:18 +00:00
|
|
|
, m_ctx(std::move(ctx))
|
2020-10-07 10:36:04 +00:00
|
|
|
{
|
|
|
|
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->lineAmount->setValidator(validator);
|
|
|
|
|
2021-05-18 15:59:18 +00:00
|
|
|
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);
|
2021-05-02 18:22:38 +00:00
|
|
|
|
2021-06-25 14:14:49 +00:00
|
|
|
connect(ui->btnScan, &QPushButton::clicked, this, &SendWidget::scanClicked);
|
2020-10-07 10:36:04 +00:00
|
|
|
connect(ui->btnSend, &QPushButton::clicked, this, &SendWidget::sendClicked);
|
|
|
|
connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked);
|
|
|
|
connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked);
|
|
|
|
connect(ui->comboCurrencySelection, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SendWidget::currencyComboChanged);
|
2021-01-26 23:55:27 +00:00
|
|
|
connect(ui->lineAmount, &QLineEdit::textChanged, this, &SendWidget::amountEdited);
|
|
|
|
connect(ui->lineAddress, &QPlainTextEdit::textChanged, this, &SendWidget::addressEdited);
|
2020-10-07 10:36:04 +00:00
|
|
|
connect(ui->btn_openAlias, &QPushButton::clicked, this, &SendWidget::aliasClicked);
|
2021-07-07 22:48:17 +00:00
|
|
|
connect(ui->lineAddress, &PayToEdit::dataPasted, this, &SendWidget::onDataPasted);
|
2020-10-15 02:17:57 +00:00
|
|
|
ui->label_conversionAmount->setText("");
|
|
|
|
ui->label_conversionAmount->hide();
|
2020-10-07 10:36:04 +00:00
|
|
|
ui->btn_openAlias->hide();
|
2020-11-24 21:16:24 +00:00
|
|
|
|
2020-12-16 15:28:13 +00:00
|
|
|
ui->label_PayTo->setHelpText("Recipient of the funds.\n\n"
|
|
|
|
"You may enter a Monero address, or an alias (email-like address that forwards to a Monero address)");
|
|
|
|
ui->label_Description->setHelpText("Description of the transaction (optional).\n\n"
|
|
|
|
"The description is not sent to the recipient of the funds. It is stored in your wallet cache, "
|
|
|
|
"and displayed in the 'History' tab.");
|
|
|
|
ui->label_Amount->setHelpText("Amount to be sent.\n\nThis is the exact amount the recipient will receive. "
|
|
|
|
"In addition to this amount a transaction fee will be subtracted from your balance. "
|
|
|
|
"You will be able to review the transaction fee before the transaction is broadcast.\n\n"
|
|
|
|
"To send all your balance, click the Max button to the right.");
|
|
|
|
|
2021-05-18 15:59:18 +00:00
|
|
|
ui->lineAddress->setNetType(constants::networkType);
|
2020-11-24 21:16:24 +00:00
|
|
|
this->setupComboBox();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::currencyComboChanged(int index) {
|
|
|
|
QString amount = ui->lineAmount->text();
|
|
|
|
if(amount.isEmpty()) return;
|
|
|
|
this->amountEdited(amount);
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:55:27 +00:00
|
|
|
void SendWidget::addressEdited() {
|
|
|
|
QVector<PartialTxOutput> outputs = ui->lineAddress->getOutputs();
|
|
|
|
|
2021-05-25 13:20:10 +00:00
|
|
|
bool freezeAmounts = !outputs.empty();
|
2021-01-26 23:55:27 +00:00
|
|
|
|
|
|
|
ui->lineAmount->setReadOnly(freezeAmounts);
|
|
|
|
ui->lineAmount->setFrame(!freezeAmounts);
|
|
|
|
ui->btnMax->setDisabled(freezeAmounts);
|
2021-02-03 18:42:31 +00:00
|
|
|
ui->comboCurrencySelection->setDisabled(freezeAmounts);
|
2021-01-26 23:55:27 +00:00
|
|
|
|
2021-05-25 13:20:10 +00:00
|
|
|
if (!outputs.empty()) {
|
|
|
|
ui->lineAmount->setText(WalletManager::displayAmount(ui->lineAddress->getTotal(), false));
|
2021-02-03 18:42:31 +00:00
|
|
|
ui->comboCurrencySelection->setCurrentIndex(0);
|
2021-01-26 23:55:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ui->btn_openAlias->setVisible(ui->lineAddress->isOpenAlias());
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::amountEdited(const QString &text) {
|
|
|
|
this->updateConversionLabel();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::fill(double amount) {
|
|
|
|
ui->lineAmount->setText(QString::number(amount));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::fill(const QString &address, const QString &description, double amount) {
|
|
|
|
ui->lineAddress->setText(address);
|
2021-01-26 23:55:27 +00:00
|
|
|
ui->lineAddress->moveCursor(QTextCursor::Start);
|
|
|
|
|
2021-07-02 16:01:11 +00:00
|
|
|
ui->lineDescription->setText(description);
|
|
|
|
|
2020-12-14 00:59:32 +00:00
|
|
|
if (amount > 0)
|
|
|
|
ui->lineAmount->setText(QString::number(amount));
|
2021-07-02 16:01:11 +00:00
|
|
|
ui->lineAmount->setFocus();
|
|
|
|
|
2020-12-14 00:59:32 +00:00
|
|
|
this->updateConversionLabel();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::fillAddress(const QString &address) {
|
|
|
|
ui->lineAddress->setText(address);
|
2021-01-26 23:55:27 +00:00
|
|
|
ui->lineAddress->moveCursor(QTextCursor::Start);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2021-06-25 14:14:49 +00:00
|
|
|
void SendWidget::scanClicked() {
|
|
|
|
#ifdef WITH_SCANNER
|
|
|
|
auto *dialog = new QrCodeScanDialog(this);
|
|
|
|
dialog->exec();
|
|
|
|
ui->lineAddress->setText(dialog->decodedString);
|
|
|
|
dialog->deleteLater();
|
|
|
|
#else
|
|
|
|
QMessageBox::warning(this, "QR scanner", "Feather was built without webcam QR scanner support.");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
void SendWidget::sendClicked() {
|
2021-05-18 15:59:18 +00:00
|
|
|
if (!m_ctx->wallet->isConnected()) {
|
2020-12-22 23:46:42 +00:00
|
|
|
QMessageBox::warning(this, "Error", "Unable to create transaction:\n\n"
|
|
|
|
"Wallet is not connected to a node.\n"
|
|
|
|
"Go to File -> Settings -> Node to manually connect to a node.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
QString currency = ui->comboCurrencySelection->currentText();
|
2020-10-30 21:13:56 +00:00
|
|
|
QString recipient = ui->lineAddress->text().simplified().remove(' ');
|
2020-10-07 10:36:04 +00:00
|
|
|
QString description = ui->lineDescription->text();
|
|
|
|
if(recipient.isEmpty()) {
|
|
|
|
QMessageBox::warning(this, "Malformed recipient", "The recipient address was not correct");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:55:27 +00:00
|
|
|
QVector<PartialTxOutput> outputs = ui->lineAddress->getOutputs();
|
|
|
|
QVector<PayToLineError> errors = ui->lineAddress->getErrors();
|
|
|
|
if (errors.size() > 0 && ui->lineAddress->isMultiline()) {
|
|
|
|
QString errorText;
|
|
|
|
for (auto &error: errors) {
|
|
|
|
errorText += QString("Line #%1:\n%2\n").arg(QString::number(error.idx + 1), error.error);
|
|
|
|
}
|
|
|
|
|
|
|
|
QMessageBox::warning(this, "Warning", QString("Invalid lines found:\n\n%1").arg(errorText));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (outputs.size() > 0) { // multi destination transaction
|
|
|
|
if (outputs.size() > 16) {
|
|
|
|
QMessageBox::warning(this, "Warning", "Maximum number of outputs (16) exceeded.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QVector<QString> addresses;
|
|
|
|
QVector<quint64> amounts;
|
|
|
|
for (auto &output : outputs) {
|
|
|
|
addresses.push_back(output.address);
|
|
|
|
amounts.push_back(output.amount);
|
|
|
|
}
|
|
|
|
|
2021-05-02 18:22:38 +00:00
|
|
|
m_ctx->onCreateTransactionMultiDest(addresses, amounts, description);
|
2021-01-26 23:55:27 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-31 03:26:03 +00:00
|
|
|
quint64 amount;
|
|
|
|
if (currency == "XMR") {
|
|
|
|
amount = this->amount();
|
|
|
|
bool sendAll = (ui->lineAmount->text() == "all");
|
|
|
|
if (amount == 0 && !sendAll) {
|
|
|
|
QMessageBox::warning(this, "Amount error", "Invalid amount specified.");
|
|
|
|
return;
|
|
|
|
}
|
2021-05-02 18:22:38 +00:00
|
|
|
m_ctx->onCreateTransaction(recipient, amount, description, sendAll);
|
2020-12-31 03:26:03 +00:00
|
|
|
} else {
|
|
|
|
amount = WalletManager::amountFromDouble(this->conversionAmount());
|
|
|
|
if (amount == 0) {
|
2020-10-07 10:36:04 +00:00
|
|
|
QMessageBox::warning(this, "Fiat conversion error", "Could not create transaction.");
|
|
|
|
return;
|
|
|
|
}
|
2021-05-02 18:22:38 +00:00
|
|
|
m_ctx->onCreateTransaction(recipient, amount, description, false);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::aliasClicked() {
|
|
|
|
auto address = ui->lineAddress->text();
|
2021-05-02 18:22:38 +00:00
|
|
|
m_ctx->onOpenAliasResolve(address);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::clearClicked() {
|
|
|
|
ui->lineAddress->clear();
|
|
|
|
ui->lineAmount->clear();
|
|
|
|
ui->lineDescription->clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::btnMaxClicked() {
|
|
|
|
ui->lineAmount->setText("all");
|
2020-12-31 03:26:03 +00:00
|
|
|
this->updateConversionLabel();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::updateConversionLabel() {
|
2020-12-31 03:26:03 +00:00
|
|
|
auto amount = this->amountDouble();
|
|
|
|
|
2020-10-15 02:17:57 +00:00
|
|
|
ui->label_conversionAmount->setText("");
|
2020-12-31 03:26:03 +00:00
|
|
|
if (amount <= 0) {
|
2020-10-15 02:17:57 +00:00
|
|
|
ui->label_conversionAmount->hide();
|
2020-10-07 10:36:04 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-15 02:17:57 +00:00
|
|
|
QString conversionAmountStr = [this]{
|
|
|
|
QString currency = ui->comboCurrencySelection->currentText();
|
|
|
|
if (currency != "XMR") {
|
|
|
|
return QString("~%1 XMR").arg(QString::number(this->conversionAmount(), 'f'));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
auto preferredFiatCurrency = config()->get(Config::preferredFiatCurrency).toString();
|
2021-05-02 18:22:38 +00:00
|
|
|
double conversionAmount = appData()->prices.convert("XMR", preferredFiatCurrency, this->amountDouble());
|
2020-10-15 02:17:57 +00:00
|
|
|
return QString("~%1 %2").arg(QString::number(conversionAmount, 'f', 2), preferredFiatCurrency);
|
2020-12-28 04:39:20 +00:00
|
|
|
}
|
2020-10-15 02:17:57 +00:00
|
|
|
}();
|
|
|
|
|
|
|
|
ui->label_conversionAmount->setText(conversionAmountStr);
|
|
|
|
ui->label_conversionAmount->show();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
double SendWidget::conversionAmount() {
|
|
|
|
QString currency = ui->comboCurrencySelection->currentText();
|
2021-05-02 18:22:38 +00:00
|
|
|
return appData()->prices.convert(currency, "XMR", this->amountDouble());
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2020-12-31 03:26:03 +00:00
|
|
|
quint64 SendWidget::amount() {
|
2020-10-07 10:36:04 +00:00
|
|
|
// grab amount from "amount" text box
|
|
|
|
QString amount = ui->lineAmount->text();
|
2020-12-31 03:26:03 +00:00
|
|
|
if (amount == "all") return 0;
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
amount.replace(',', '.');
|
2020-12-31 03:26:03 +00:00
|
|
|
if (amount.isEmpty()) return 0;
|
|
|
|
|
|
|
|
return WalletManager::amountFromString(amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
double SendWidget::amountDouble() {
|
|
|
|
quint64 amount = this->amount();
|
2021-05-18 15:59:18 +00:00
|
|
|
return amount / constants::cdiv;
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::onOpenAliasResolved(const QString &address, const QString &openAlias) {
|
|
|
|
this->fill(address, openAlias);
|
|
|
|
ui->btn_openAlias->hide();
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::onOpenAliasResolveError(const QString &msg) {
|
|
|
|
QMessageBox::warning(this, "OpenAlias error", msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::clearFields() {
|
|
|
|
ui->lineAddress->clear();
|
|
|
|
ui->lineAmount->clear();
|
|
|
|
ui->lineDescription->clear();
|
2020-10-15 02:17:57 +00:00
|
|
|
ui->label_conversionAmount->clear();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2021-01-26 23:55:27 +00:00
|
|
|
void SendWidget::payToMany() {
|
|
|
|
ui->lineAddress->payToMany();
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
void SendWidget::onInitiateTransaction() {
|
|
|
|
ui->btnSend->setEnabled(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void SendWidget::onEndTransaction() {
|
|
|
|
ui->btnSend->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
2021-07-07 22:48:17 +00:00
|
|
|
void SendWidget::onDataPasted(const QString &data) {
|
|
|
|
if (!data.isEmpty()) {
|
|
|
|
QVariantMap uriData = m_ctx->wallet->parse_uri_to_object(data);
|
|
|
|
if (!uriData.contains("error")) {
|
2021-07-09 21:43:59 +00:00
|
|
|
ui->lineAddress->setText(uriData.value("address").toString());
|
|
|
|
ui->lineDescription->setText(uriData.value("tx_description").toString());
|
|
|
|
ui->lineAmount->setText(uriData.value("amount").toString());
|
2021-07-07 22:48:17 +00:00
|
|
|
} else {
|
|
|
|
ui->lineAddress->setText(data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
QMessageBox::warning(this, "Error", "No Qr Code found.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-24 21:16:24 +00:00
|
|
|
void SendWidget::setupComboBox() {
|
|
|
|
ui->comboCurrencySelection->clear();
|
|
|
|
|
|
|
|
QStringList defaultCurrencies = {"XMR", "USD", "EUR", "CNY", "JPY", "GBP"};
|
|
|
|
QString preferredCurrency = config()->get(Config::preferredFiatCurrency).toString();
|
|
|
|
|
|
|
|
if (defaultCurrencies.contains(preferredCurrency)) {
|
|
|
|
defaultCurrencies.removeOne(preferredCurrency);
|
|
|
|
}
|
|
|
|
|
|
|
|
ui->comboCurrencySelection->insertItems(0, defaultCurrencies);
|
|
|
|
ui->comboCurrencySelection->insertItem(1, preferredCurrency);
|
|
|
|
}
|
|
|
|
|
2020-10-15 02:17:57 +00:00
|
|
|
void SendWidget::onPreferredFiatCurrencyChanged() {
|
|
|
|
this->updateConversionLabel();
|
2020-11-24 21:16:24 +00:00
|
|
|
this->setupComboBox();
|
2020-10-15 02:17:57 +00:00
|
|
|
}
|
|
|
|
|
2021-06-25 14:14:49 +00:00
|
|
|
void SendWidget::skinChanged() {
|
|
|
|
if (ColorScheme::hasDarkBackground(this)) {
|
|
|
|
ui->btnScan->setIcon(icons()->icon("camera_white.png"));
|
|
|
|
} else {
|
|
|
|
ui->btnScan->setIcon(icons()->icon("camera_dark.png"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-27 12:13:05 +00:00
|
|
|
SendWidget::~SendWidget() = default;
|