History: rework

This commit is contained in:
tobtoht 2021-03-08 21:03:20 +01:00
parent 59586f4b6e
commit cc5b3c3c27
No known key found for this signature in database
GPG key ID: 1CADD27F41F45C3C
34 changed files with 1504 additions and 632 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@ feather.cbp
src/tor/* src/tor/*
!src/tor/.gitkeep !src/tor/.gitkeep
src/config-feather.h src/config-feather.h
src/assets/exec/*

View file

@ -19,7 +19,6 @@ Prices *AppContext::prices = nullptr;
WalletKeysFilesModel *AppContext::wallets = nullptr; WalletKeysFilesModel *AppContext::wallets = nullptr;
TxFiatHistory *AppContext::txFiatHistory = nullptr; TxFiatHistory *AppContext::txFiatHistory = nullptr;
double AppContext::balance = 0; double AppContext::balance = 0;
QMap<QString, QString> AppContext::txDescriptionCache;
QMap<QString, QString> AppContext::txCache; QMap<QString, QString> AppContext::txCache;
AppContext::AppContext(QCommandLineParser *cmdargs) { AppContext::AppContext(QCommandLineParser *cmdargs) {
@ -303,6 +302,13 @@ void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) {
} }
} }
void AppContext::onAmountPrecisionChanged(int precision) {
if (!this->currentWallet) return;
auto *model = this->currentWallet->transactionHistoryModel();
if (!model) return;
model->amountPrecision = precision;
}
void AppContext::onWalletOpened(Wallet *wallet) { void AppContext::onWalletOpened(Wallet *wallet) {
auto state = wallet->status(); auto state = wallet->status();
if (state != Wallet::Status_Ok) { if (state != Wallet::Status_Ok) {
@ -770,21 +776,28 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QStr
} }
void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){ void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){
this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount()); if (status) {
this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount()); for (const auto &entry: txid) {
this->currentWallet->setUserNote(entry, this->tmpTxDescription);
}
this->tmpTxDescription = "";
}
// Store wallet immediately so we don't risk losing tx key if wallet crashes // Store wallet immediately so we don't risk losing tx key if wallet crashes
this->currentWallet->store(); this->currentWallet->store();
this->updateBalance(); this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount());
this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount());
emit transactionCommitted(status, tx, txid); this->updateBalance();
// this tx was a donation to Feather, stop our nagging // this tx was a donation to Feather, stop our nagging
if(this->donationSending) { if(this->donationSending) {
this->donationSending = false; this->donationSending = false;
config()->set(Config::donateBeg, -1); config()->set(Config::donateBeg, -1);
} }
emit transactionCommitted(status, tx, txid);
} }
void AppContext::storeWallet() { void AppContext::storeWallet() {

View file

@ -85,7 +85,6 @@ public:
static Prices *prices; static Prices *prices;
static WalletKeysFilesModel *wallets; static WalletKeysFilesModel *wallets;
static double balance; static double balance;
static QMap<QString, QString> txDescriptionCache;
static QMap<QString, QString> txCache; static QMap<QString, QString> txCache;
static TxFiatHistory *txFiatHistory; static TxFiatHistory *txFiatHistory;
@ -119,6 +118,7 @@ public slots:
void onOpenAliasResolve(const QString &openAlias); void onOpenAliasResolve(const QString &openAlias);
void onSetRestoreHeight(quint64 height); void onSetRestoreHeight(quint64 height);
void onPreferredFiatCurrencyChanged(const QString &symbol); void onPreferredFiatCurrencyChanged(const QString &symbol);
void onAmountPrecisionChanged(int precision);
private slots: private slots:
void onWSNodes(const QJsonArray &nodes); void onWSNodes(const QJsonArray &nodes);

View file

@ -0,0 +1,226 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "TxProofDialog.h"
#include "ui_TxProofDialog.h"
#include <QMessageBox>
#include "libwalletqt/Transfer.h"
#include "utils/utils.h"
TxProofDialog::TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *txInfo)
: QDialog(parent)
, ui(new Ui::TxProofDialog)
, m_wallet(wallet)
{
ui->setupUi(this);
m_txid = txInfo->hash();
m_txKey = m_wallet->getTxKey(m_txid);
m_direction = txInfo->direction();
for (auto const &t: txInfo->transfers()) {
m_OutDestinations.push_back(t->address());
}
for (auto const &s: txInfo->subaddrIndex()) {
m_InDestinations.push_back(wallet->address(txInfo->subaddrAccount(), s));
}
// Due to some logic in core we can't create OutProofs
// for churn transactions that sweep from and send to the same address
for (auto const &address : m_InDestinations) {
m_OutDestinations.removeAll(address);
}
connect(ui->radio_SpendProof, &QRadioButton::toggled, this, &TxProofDialog::selectSpendProof);
connect(ui->radio_OutProof, &QRadioButton::toggled, this, &TxProofDialog::selectOutProof);
connect(ui->radio_InProof, &QRadioButton::toggled, this, &TxProofDialog::selectInProof);
connect(ui->btn_getFormattedProof, &QPushButton::pressed, this, &TxProofDialog::getFormattedProof);
connect(ui->btn_getSignature, &QPushButton::pressed, this, &TxProofDialog::getSignature);
ui->radio_SpendProof->setChecked(true);
ui->label_txid->setText(m_txid);
ui->group_summary->hide(); // todo
this->adjustSize();
}
void TxProofDialog::setTxId(const QString &txid) {
ui->label_txid->setText(txid);
}
void TxProofDialog::selectSpendProof() {
m_mode = Mode::SpendProof;
this->resetFrames();
if (m_direction == TransactionInfo::Direction_In) {
this->showWarning("Your wallet did not construct this transaction. Creating a SpendProof is not possible.");
return;
}
ui->frame_message->show();
ui->label_summary->setText("This proof shows you created a transaction with the txid shown above.");
}
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.");
return;
}
if (m_txKey.isEmpty()) {
this->showWarning("No transaction key stored for this transaction. Creating an OutProof is not possible.");
return;
}
this->selectTxProof();
ui->combo_address->addItems(m_OutDestinations);
ui->label_summary->setText("This proof shows you paid x XMR to the address selected above.");
}
void TxProofDialog::selectInProof() {
m_mode = Mode::InProof;
this->resetFrames();
if (m_InDestinations.empty()) {
this->showWarning("Your wallet did not receive any outputs in this transaction.");
return;
}
this->selectTxProof();
ui->combo_address->addItems(m_InDestinations);
ui->label_summary->setText("This proof shows you received x XMR to the address selected above.");
}
void TxProofDialog::selectTxProof() {
ui->frame_txKeyWarning->hide();
ui->frame_message->show();
ui->frame_address->show();
ui->combo_address->clear();
}
void TxProofDialog::resetFrames() {
ui->frame_txKeyWarning->hide();
ui->frame_message->hide();
ui->frame_address->hide();
this->toggleButtons(true);
}
void TxProofDialog::toggleButtons(bool enabled) {
ui->btn_getFormattedProof->setEnabled(enabled);
ui->btn_getSignature->setEnabled(enabled);
}
void TxProofDialog::showWarning(const QString &message) {
this->toggleButtons(false);
ui->frame_txKeyWarning->show();
ui->label_txKeyWarning->setText(message);
}
void TxProofDialog::getFormattedProof() {
QString message = ui->message->toPlainText();
QString address = ui->combo_address->currentText();
QString nettype = Utils::QtEnumToString(m_wallet->nettype()).toLower();
nettype = nettype.replace(0, 1, nettype[0].toUpper()); // Capitalize first letter
TxProof proof = this->getProof();
if (!proof.error.isEmpty()) {
QMessageBox::warning(this, "Get formatted proof", QString("Failed to get proof signature: %1").arg(proof.error));
return;
}
QStringList signatureSplit;
for (int i = 0; i < proof.proof.length(); i += 64) {
signatureSplit.append(proof.proof.mid(i, 64));
}
QString signature = signatureSplit.join('\n');
QString formattedProof = [this, nettype, message, address, signature]{
switch (m_mode) {
case Mode::SpendProof: {
return QString("-----BEGIN SPENDPROOF-----\n"
"Network: Monero %1\n"
"Txid: %2\n"
"\n"
"%3\n"
"-----BEGIN SPENDPROOF SIGNATURE-----\n"
"\n"
"%4\n"
"-----END SPENDPROOF SIGNATURE-----").arg(nettype, m_txid, message, signature);
}
case Mode::OutProof: {
return QString("-----BEGIN OUTPROOF-----\n"
"Network: Monero %1\n"
"Txid: %2\n"
"Address: %3\n"
"\n"
"%4\n"
"-----BEGIN OUTPROOF SIGNATURE-----\n"
"\n"
"%5\n"
"-----END OUTPROOF SIGNATURE-----").arg(nettype, m_txid, address, message, signature);
}
case Mode::InProof: {
return QString("-----BEGIN INPROOF-----\n"
"Network: Monero %1\n"
"Txid: %2\n"
"Address: %3\n"
"\n"
"%4\n"
"-----BEGIN INPROOF SIGNATURE-----\n"
"\n"
"%5\n"
"-----END INPROOF SIGNATURE-----").arg(nettype, m_txid, address, message, signature);
}
default:
return QString("");
}
}();
Utils::copyToClipboard(formattedProof);
QMessageBox::information(this, "Get formatted proof", "Formatted proof copied to clipboard");
}
void TxProofDialog::getSignature() {
TxProof proof = this->getProof();
if (!proof.error.isEmpty()) {
QMessageBox::warning(this, "Get proof signature", QString("Failed to get proof signature: %1").arg(proof.error));
return;
}
Utils::copyToClipboard(proof.proof);
QMessageBox::information(this, "Get proof singature", "Proof signature copied to clipboard");
}
TxProof TxProofDialog::getProof() {
QString message = ui->message->toPlainText();
QString address = ui->combo_address->currentText();
TxProof proof = [this, message, address]{
switch (m_mode) {
case Mode::SpendProof: {
return m_wallet->getSpendProof(m_txid, message);
}
case Mode::OutProof:
case Mode::InProof: { // Todo: split this into separate functions
return m_wallet->getTxProof(m_txid, address, message);
}
}
}();
return proof;
}
TxProofDialog::~TxProofDialog() {
delete ui;
}

View file

@ -0,0 +1,56 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_TXPROOFDIALOG_H
#define FEATHER_TXPROOFDIALOG_H
#include <QDialog>
#include "libwalletqt/Wallet.h"
#include "libwalletqt/TransactionInfo.h"
namespace Ui {
class TxProofDialog;
}
class TxProofDialog : public QDialog
{
Q_OBJECT
public:
explicit TxProofDialog(QWidget *parent, Wallet *wallet, TransactionInfo *txid);
~TxProofDialog() override;
void setTxId(const QString &txid);
private slots:
void selectSpendProof();
void selectOutProof();
void selectInProof();
void selectTxProof();
private:
enum Mode {
SpendProof = 0,
OutProof,
InProof
};
void getFormattedProof();
void getSignature();
TxProof getProof();
void resetFrames();
void toggleButtons(bool enabled);
void showWarning(const QString &message);
QStringList m_OutDestinations;
QStringList m_InDestinations;
QString m_txid;
QString m_txKey;
Mode m_mode;
TransactionInfo::Direction m_direction;
Ui::TxProofDialog *ui;
Wallet *m_wallet;
};
#endif //FEATHER_TXPROOFDIALOG_H

301
src/dialog/TxProofDialog.ui Normal file
View file

@ -0,0 +1,301 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TxProofDialog</class>
<widget class="QDialog" name="TxProofDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>827</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Tx Proof</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="group_proofType">
<property name="title">
<string>Select proof type:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<widget class="QRadioButton" name="radio_SpendProof">
<property name="text">
<string>Prove authorship of a transaction (SpendProof)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_OutProof">
<property name="text">
<string>Prove a payment to an address (OutProof)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_InProof">
<property name="text">
<string>Prove ownership of an output (InProof)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Txid:</string>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<item>
<widget class="QLabel" name="label_txid">
<property name="text">
<string>txid</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QFrame" name="frame_txKeyWarning">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_txKeyWarning">
<property name="text">
<string>No transaction key stored for this transaction. Creating an OutProof is not possible.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_message">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Message: (optional)</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="message"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_address">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>5</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>Address:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_address"/>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="group_summary">
<property name="title">
<string>Summary:</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_summary">
<property name="text">
<string>This proof shows I paid x XMR to ADDR.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_getFormattedProof">
<property name="text">
<string>Get Formatted Proof</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_getSignature">
<property name="text">
<string>Get Signature</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -20,21 +20,18 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
{ {
ui->setupUi(this); ui->setupUi(this);
m_txProofWidget = new TxProofWidget(this, wallet, txInfo); m_txid = txInfo->hash();
ui->label_txid->setText(m_txid);
ui->label_txid->setText(QString(txInfo->hash())); QString txKey = m_wallet->getTxKey(txInfo->hash());
if (txKey.isEmpty()) {
if (txInfo->direction() == TransactionInfo::Direction_In) { ui->btn_CopyTxKey->setEnabled(false);
ui->frameTxKey->hide(); ui->btn_CopyTxKey->setToolTip("Transaction key unknown");
} else {
QString txKey = m_wallet->getTxKey(txInfo->hash());
if (txKey.isEmpty()) {
ui->btn_CopyTxKey->setEnabled(false);
ui->btn_CopyTxKey->setToolTip("Transaction key unknown");
}
m_txKey = txKey;
} }
m_txKey = txKey;
connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TransactionInfoDialog::copyTxKey); connect(ui->btn_CopyTxKey, &QPushButton::pressed, this, &TransactionInfoDialog::copyTxKey);
connect(ui->btn_createTxProof, &QPushButton::pressed, this, &TransactionInfoDialog::createTxProof);
QString blockHeight = QString::number(txInfo->blockHeight()); QString blockHeight = QString::number(txInfo->blockHeight());
if (blockHeight == "0") if (blockHeight == "0")
@ -66,7 +63,7 @@ TransactionInfoDialog::TransactionInfoDialog(Wallet *wallet, TransactionInfo *tx
ui->frameDestinations->hide(); ui->frameDestinations->hide();
} }
ui->txProofWidget->addWidget(m_txProofWidget); m_txProofDialog = new TxProofDialog(this, m_wallet, txInfo);
this->adjustSize(); this->adjustSize();
} }
@ -75,6 +72,10 @@ void TransactionInfoDialog::copyTxKey() {
Utils::copyToClipboard(m_txKey); Utils::copyToClipboard(m_txKey);
} }
void TransactionInfoDialog::createTxProof() {
m_txProofDialog->show();
}
TransactionInfoDialog::~TransactionInfoDialog() { TransactionInfoDialog::~TransactionInfoDialog() {
delete ui; delete ui;
} }

View file

@ -10,7 +10,7 @@
#include "libwalletqt/Coins.h" #include "libwalletqt/Coins.h"
#include "libwalletqt/TransactionInfo.h" #include "libwalletqt/TransactionInfo.h"
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
#include "widgets/txproofwidget.h" #include "dialog/TxProofDialog.h"
namespace Ui { namespace Ui {
class TransactionInfoDialog; class TransactionInfoDialog;
@ -26,13 +26,15 @@ public:
private: private:
void copyTxKey(); void copyTxKey();
void createTxProof();
Ui::TransactionInfoDialog *ui; Ui::TransactionInfoDialog *ui;
TxProofDialog *m_txProofDialog;
TransactionInfo *m_txInfo; TransactionInfo *m_txInfo;
Wallet *m_wallet; Wallet *m_wallet;
TxProofWidget *m_txProofWidget;
QString m_txKey; QString m_txKey;
QString m_txid;
}; };
#endif //FEATHER_TRANSACTIONINFODIALOG_H #endif //FEATHER_TRANSACTIONINFODIALOG_H

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>829</width> <width>829</width>
<height>570</height> <height>493</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -187,16 +187,47 @@
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeType"> <property name="sizeType">
<enum>QSizePolicy::Maximum</enum> <enum>QSizePolicy::Minimum</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>0</width> <width>0</width>
<height>15</height> <height>10</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QPushButton" name="btn_CopyTxKey">
<property name="text">
<string>Copy Transaction Key</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_createTxProof">
<property name="text">
<string>Create Transaction Proof</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item> <item>
<widget class="QFrame" name="frameTxKey"> <widget class="QFrame" name="frameTxKey">
<property name="frameShape"> <property name="frameShape">
@ -218,39 +249,9 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item>
<widget class="QPushButton" name="btn_CopyTxKey">
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction Key</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<layout class="QVBoxLayout" name="txProofWidget"/>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View file

@ -5,6 +5,7 @@
#include "ui_verifyproofdialog.h" #include "ui_verifyproofdialog.h"
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
#include "utils/utils.h"
#include <QMessageBox> #include <QMessageBox>
@ -15,25 +16,34 @@ VerifyProofDialog::VerifyProofDialog(Wallet *wallet, QWidget *parent)
{ {
ui->setupUi(this); ui->setupUi(this);
m_success = QPixmap(":/assets/images/confirmed.png").scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation);
m_failure = QPixmap(":/assets/images/expired.png").scaled(32, 32, Qt::KeepAspectRatio, Qt::SmoothTransformation);
ui->frame_status->hide();
connect(ui->input_formattedProof, &QPlainTextEdit::textChanged, [this]{
ui->frame_status->hide();
});
connect(ui->btn_verify, &QPushButton::clicked, this, &VerifyProofDialog::checkProof); connect(ui->btn_verify, &QPushButton::clicked, this, &VerifyProofDialog::checkProof);
connect(ui->btn_verifyFormattedProof, &QPushButton::clicked, this, &VerifyProofDialog::checkFormattedProof);
connect(ui->btn_clear, &QPushButton::clicked, [this]{ connect(ui->btn_clear, &QPushButton::clicked, [this]{
switch (ui->tabWidget->currentIndex()) { switch (ui->tabWidget->currentIndex()) {
case 0: case 0:
ui->lineEdit_spendTxID->clear(); ui->lineEdit_spendTxID->clear();
ui->lineEdit_spendMessage->clear(); ui->input_SpendMessage->clear();
ui->input_SpendProof->clear(); ui->input_SpendProof->clear();
break; break;
case 1: case 1:
ui->lineEdit_outTxID->clear(); ui->lineEdit_outTxID->clear();
ui->lineEdit_outAddress->clear(); ui->lineEdit_outAddress->clear();
ui->lineEdit_outMessage->clear(); ui->input_OutMessage->clear();
ui->input_OutProof->clear(); ui->input_OutProof->clear();
break; break;
case 2: case 2:
ui->lineEdit_inTxID->clear(); ui->lineEdit_inTxID->clear();
ui->lineEdit_inAddress->clear(); ui->lineEdit_inAddress->clear();
ui->lineEdit_inMessage->clear(); ui->input_inMessage->clear();
ui->input_InProof->clear(); ui->input_InProof->clear();
break; break;
} }
@ -48,7 +58,7 @@ VerifyProofDialog::~VerifyProofDialog()
void VerifyProofDialog::checkProof() { void VerifyProofDialog::checkProof() {
switch (ui->tabWidget->currentIndex()) { switch (ui->tabWidget->currentIndex()) {
case 0: case 0:
this->checkSpendProof(); this->checkSpendProof(ui->lineEdit_spendTxID->text(), ui->input_SpendMessage->toPlainText(), ui->input_SpendProof->toPlainText());
break; break;
case 1: case 1:
this->checkOutProof(); this->checkOutProof();
@ -59,40 +69,105 @@ void VerifyProofDialog::checkProof() {
} }
} }
void VerifyProofDialog::checkSpendProof() {
auto r = m_wallet->checkSpendProof(ui->lineEdit_spendTxID->text(), ui->lineEdit_spendMessage->text(), ui->input_SpendProof->toPlainText());
if (!r.first) {
QMessageBox::information(this, "Information", m_wallet->errorString());
return;
}
r.second ? QMessageBox::information(this, "Information", "Proof is valid")
: QMessageBox::warning(this, "Warning", "Proof is invalid");
}
void VerifyProofDialog::checkOutProof() {
this->checkTxProof(ui->lineEdit_outTxID->text(), ui->lineEdit_outAddress->text(), ui->lineEdit_outMessage->text(), ui->input_OutProof->toPlainText());
}
void VerifyProofDialog::checkInProof() {
this->checkTxProof(ui->lineEdit_inTxID->text(), ui->lineEdit_inAddress->text(), ui->lineEdit_inMessage->text(), ui->input_InProof->toPlainText());
}
void VerifyProofDialog::checkTxProof(const QString &txId, const QString &address, const QString &message, void VerifyProofDialog::checkTxProof(const QString &txId, const QString &address, const QString &message,
const QString &signature) { const QString &signature) {
TxProofResult r = m_wallet->checkTxProof(txId, address, message, signature); TxProofResult r = m_wallet->checkTxProof(txId, address, message, signature);
if (!r.success) { if (!r.success) {
QMessageBox::information(this, "Information", m_wallet->errorString()); this->proofStatus(false, m_wallet->errorString());
return; return;
} }
if (!r.good) { if (!r.good) {
QMessageBox::warning(this, "Warning", "Proof is invalid"); this->proofStatus(false, "Proof is invalid");
return; return;
} }
QString msg = QString("This address received %1 monero, with %2 confirmation(s)").arg(WalletManager::displayAmount(r.received), QString::number(r.confirmations)); 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)));
QMessageBox::information(this, "Information", QString("Proof is valid.\n\n%1").arg(msg)); }
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");
}
void VerifyProofDialog::checkOutProof() {
this->checkTxProof(ui->lineEdit_outTxID->text(), ui->lineEdit_outAddress->text(), ui->input_OutMessage->toPlainText(), ui->input_OutProof->toPlainText());
}
void VerifyProofDialog::checkInProof() {
this->checkTxProof(ui->lineEdit_inTxID->text(), ui->lineEdit_inAddress->text(), ui->input_inMessage->toPlainText(), ui->input_InProof->toPlainText());
}
void VerifyProofDialog::checkFormattedProof() {
QRegularExpression proof("-----BEGIN (?<type>\\w+)-----\\n"
"Network: (?<coin>\\w+) (?<network>\\w+)\\n"
"Txid: (?<txid>[0-9a-f]{64})\\n"
"(Address: (?<address>\\w+)\\n)?"
"\\n?"
"(?<message>.*?)\\n"
"-----BEGIN \\1 SIGNATURE-----\\n"
"\\n?"
"(?<signature>.*?)\\n"
"-----END \\1 SIGNATURE-----",
QRegularExpression::CaseInsensitiveOption | QRegularExpression::DotMatchesEverythingOption);
QString formattedProof = ui->input_formattedProof->toPlainText();
QRegularExpressionMatch match = proof.match(formattedProof);
if (!match.hasMatch()) {
this->proofStatus(false, "Unable to parse proof");
return;
}
QString type = match.captured("type").toLower();
QString coin = match.captured("coin").toLower();
QString network = match.captured("network").toLower();
QString txid = match.captured("txid");
QString address = match.captured("address");
QString message = match.captured("message");
QString signature = match.captured("signature").remove('\n');
QStringList validTypes = {"inproof", "spendproof", "outproof"};
if (!validTypes.contains(type)) {
this->proofStatus(false, QString("Unknown proof type: %1").arg(type));
return;
}
if (coin != "monero") {
this->proofStatus(false, QString("Can't verify proof for coin: %1").arg(coin));
return;
}
QString walletNetwork = Utils::QtEnumToString(m_wallet->nettype()).toLower();
if (network != walletNetwork) {
this->proofStatus(false, QString("Can't verify proof for %1 network when %2 wallet is opened").arg(network, walletNetwork));
return;
}
if (type == "outproof" || type == "inproof") {
this->checkTxProof(txid, address, message, signature);
}
if (type == "spendproof") {
this->checkSpendProof(txid, message, signature);
}
}
void VerifyProofDialog::proofStatus(bool success, const QString &message) {
if (ui->tabWidget_proofFormat->currentIndex() == 0) {
ui->frame_status->show();
ui->label_icon->setPixmap(success ? m_success : m_failure);
ui->label_status->setText(message);
}
else {
success ? QMessageBox::information(this, "Information", message)
: QMessageBox::warning(this, "Warning", message);
}
} }

View file

@ -5,6 +5,7 @@
#define FEATHER_VERIFYPROOFDIALOG_H #define FEATHER_VERIFYPROOFDIALOG_H
#include <QDialog> #include <QDialog>
#include <QIcon>
#include "libwalletqt/Wallet.h" #include "libwalletqt/Wallet.h"
namespace Ui { namespace Ui {
@ -24,9 +25,14 @@ private slots:
private: private:
void checkTxProof(const QString &txId, const QString &address, const QString &message, const QString &signature); void checkTxProof(const QString &txId, const QString &address, const QString &message, const QString &signature);
void checkSpendProof(); void checkSpendProof(const QString &txId, const QString &message, const QString &signature);
void checkOutProof(); void checkOutProof();
void checkInProof(); void checkInProof();
void checkFormattedProof();
void proofStatus(bool success, const QString &message);
QPixmap m_success;
QPixmap m_failure;
Ui::VerifyProofDialog *ui; Ui::VerifyProofDialog *ui;
Wallet *m_wallet; Wallet *m_wallet;

View file

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1123</width> <width>770</width>
<height>413</height> <height>587</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -15,298 +15,411 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="label"> <widget class="QTabWidget" name="tabWidget_proofFormat">
<property name="text">
<string>Select proof to verify:</string>
</property>
</widget>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="SpendProof"> <widget class="QWidget" name="tab_formatted">
<attribute name="title"> <attribute name="title">
<string>SpendProof</string> <string>Formatted</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_6">
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_16">
<property name="text"> <property name="text">
<string>A SpendProof proves authorship of a transaction.</string> <string>Paste formatted proof here:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout_3"> <widget class="QPlainTextEdit" name="input_formattedProof"/>
<item row="1" column="0"> </item>
<widget class="QLabel" name="label_4"> <item>
<widget class="QFrame" name="frame_status">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label_icon">
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_status">
<property name="text">
<string>Proof is valid</string>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_verifyFormattedProof">
<property name="text"> <property name="text">
<string>Message:</string> <string>Verify</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_spendTxID"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_spendMessage">
<property name="placeholderText">
<string>Optional message against which the signature is signed</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>SpendProof:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPlainTextEdit" name="input_SpendProof">
<property name="overwriteMode">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>SpendProof..</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="OutProof"> <widget class="QWidget" name="tab_manual">
<attribute name="title"> <attribute name="title">
<string>OutProof</string> <string>Manual</string>
</attribute> </attribute>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_5">
<item> <item>
<widget class="QLabel" name="label_10"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>An OutProof shows the prover sent an output to an address.</string> <string>Select proof to verify:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout_2"> <widget class="QTabWidget" name="tabWidget">
<item row="4" column="0"> <property name="currentIndex">
<widget class="QLabel" name="label_9"> <number>0</number>
<property name="text">
<string>OutProof:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_outTxID"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Address:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_outAddress">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Address of recipient</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="lineEdit_outMessage">
<property name="placeholderText">
<string>Optional message against which the signature is signed</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPlainTextEdit" name="input_OutProof">
<property name="placeholderText">
<string>OutProof..</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="InProof">
<attribute name="title">
<string>InProof</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>An InProof proves ownership of an output.</string>
</property> </property>
<widget class="QWidget" name="SpendProof">
<attribute name="title">
<string>SpendProof</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>A SpendProof proves authorship of a transaction.</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_spendTxID"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>SpendProof:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPlainTextEdit" name="input_SpendProof">
<property name="overwriteMode">
<bool>false</bool>
</property>
<property name="placeholderText">
<string>SpendProof..</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPlainTextEdit" name="input_SpendMessage">
<property name="placeholderText">
<string>Optional message against which the signature is signed</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="OutProof">
<attribute name="title">
<string>OutProof</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_10">
<property name="text">
<string>An OutProof shows the prover sent an output to an address.</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_2">
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>OutProof:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_outTxID"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Address:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_outAddress">
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>Address of recipient</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPlainTextEdit" name="input_OutProof">
<property name="placeholderText">
<string>OutProof..</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPlainTextEdit" name="input_OutMessage">
<property name="placeholderText">
<string>Optional message against which the signature is signed</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="InProof">
<attribute name="title">
<string>InProof</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_11">
<property name="text">
<string>An InProof proves ownership of an output.</string>
</property>
</widget>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>InProof:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_inAddress">
<property name="placeholderText">
<string>Output owner's address</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPlainTextEdit" name="input_InProof">
<property name="placeholderText">
<string>InProof..</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_inTxID">
<property name="placeholderText">
<string>Transaction that created the output</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPlainTextEdit" name="input_inMessage">
<property name="placeholderText">
<string>Optional message against which the signature is signed</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Address:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item row="3" column="0"> <item>
<widget class="QLabel" name="label_14"> <spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_clear">
<property name="text"> <property name="text">
<string>InProof:</string> <string>Clear</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item>
<widget class="QLabel" name="label_12"> <widget class="QPushButton" name="btn_verify">
<property name="text"> <property name="text">
<string>Transaction ID:</string> <string>Verify</string>
</property> </property>
</widget> <property name="default">
</item> <bool>true</bool>
<item row="1" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Address:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_inAddress">
<property name="placeholderText">
<string>Output owner's address</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_inTxID">
<property name="placeholderText">
<string>Transaction that created the output</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Message:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_inMessage">
<property name="placeholderText">
<string>Optional message against which the signature is signed</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPlainTextEdit" name="input_InProof">
<property name="placeholderText">
<string>InProof..</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_clear">
<property name="text">
<string>Clear</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_verify">
<property name="text">
<string>Verify</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>

View file

@ -4,6 +4,7 @@
#include "historywidget.h" #include "historywidget.h"
#include "ui_historywidget.h" #include "ui_historywidget.h"
#include "dialog/transactioninfodialog.h" #include "dialog/transactioninfodialog.h"
#include "dialog/TxProofDialog.h"
#include <QMessageBox> #include <QMessageBox>
HistoryWidget::HistoryWidget(QWidget *parent) HistoryWidget::HistoryWidget(QWidget *parent)
@ -20,6 +21,7 @@ HistoryWidget::HistoryWidget(QWidget *parent)
// copy menu // copy menu
m_copyMenu->setIcon(QIcon(":/assets/images/copy.png")); m_copyMenu->setIcon(QIcon(":/assets/images/copy.png"));
m_copyMenu->addAction("Transaction ID", this, [this]{copy(copyField::TxID);}); 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("Date", this, [this]{copy(copyField::Date);});
m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);}); m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);});
@ -48,34 +50,29 @@ void HistoryWidget::showContextMenu(const QPoint &point) {
} }
QMenu menu(this); QMenu menu(this);
TransactionInfo::Direction direction;
QString txid;
bool unconfirmed;
m_txHistory->transaction(m_model->mapToSource(index).row(), [&direction, &txid, &unconfirmed](TransactionInfo &tInfo) {
direction = tInfo.direction();
txid = tInfo.hash();
unconfirmed = tInfo.isFailed() || tInfo.isPending();
});
if (AppContext::txCache.contains(txid) && unconfirmed && direction != TransactionInfo::Direction_In) { auto *tx = ui->history->currentEntry();
if (!tx) return;
bool unconfirmed = tx->isFailed() || tx->isPending();
if (AppContext::txCache.contains(tx->hash()) && unconfirmed && tx->direction() != TransactionInfo::Direction_In) {
menu.addAction(QIcon(":/assets/images/info.png"), "Resend transaction", this, &HistoryWidget::onResendTransaction); menu.addAction(QIcon(":/assets/images/info.png"), "Resend transaction", this, &HistoryWidget::onResendTransaction);
} }
menu.addMenu(m_copyMenu); menu.addMenu(m_copyMenu);
menu.addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails); menu.addAction(QIcon(":/assets/images/info.png"), "Show details", this, &HistoryWidget::showTxDetails);
menu.addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer); menu.addAction(QIcon(":/assets/images/network.png"), "View on block explorer", this, &HistoryWidget::onViewOnBlockExplorer);
menu.addAction("Create tx proof", this, &HistoryWidget::createTxProof);
menu.exec(ui->history->viewport()->mapToGlobal(point)); menu.exec(ui->history->viewport()->mapToGlobal(point));
} }
void HistoryWidget::onResendTransaction() { void HistoryWidget::onResendTransaction() {
QModelIndex index = ui->history->currentIndex(); auto *tx = ui->history->currentEntry();
QString txid; if (tx) {
m_txHistory->transaction(m_model->mapToSource(index).row(), [&txid](TransactionInfo &tInfo) { QString txid = tx->hash();
txid = tInfo.hash(); emit resendTransaction(txid);
}); }
emit resendTransaction(txid);
} }
void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet) void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet)
@ -83,39 +80,35 @@ void HistoryWidget::setModel(TransactionHistoryProxyModel *model, Wallet *wallet
m_model = model; m_model = model;
m_wallet = wallet; m_wallet = wallet;
m_txHistory = m_wallet->history(); m_txHistory = m_wallet->history();
ui->history->setModel(m_model); ui->history->setHistoryModel(m_model);
m_wallet->transactionHistoryModel()->amountPrecision = config()->get(Config::amountPrecision).toInt();
ui->history->header()->setSectionResizeMode(QHeaderView::ResizeToContents); // Load view state
ui->history->header()->setSectionResizeMode(TransactionHistoryModel::Description, QHeaderView::Stretch); ui->history->setViewState(QByteArray::fromBase64(config()->get(Config::GUI_HistoryViewState).toByteArray()));
ui->history->hideColumn(TransactionHistoryModel::TxID);
} }
void HistoryWidget::resetModel() void HistoryWidget::resetModel()
{ {
// Save view state
config()->set(Config::GUI_HistoryViewState, ui->history->viewState().toBase64());
config()->sync();
ui->history->setModel(nullptr); ui->history->setModel(nullptr);
} }
void HistoryWidget::showTxDetails() { void HistoryWidget::showTxDetails() {
QModelIndex index = ui->history->currentIndex(); auto *tx = ui->history->currentEntry();
if (!tx) return;
TransactionInfo *i = nullptr; auto *dialog = new TransactionInfoDialog(m_wallet, tx, this);
m_txHistory->transaction(m_model->mapToSource(index).row(), [&i](TransactionInfo &tInfo) { dialog->show();
i = &tInfo;
});
if (i != nullptr) {
auto * dialog = new TransactionInfoDialog(m_wallet, i, this);
dialog->exec();
}
} }
void HistoryWidget::onViewOnBlockExplorer() { void HistoryWidget::onViewOnBlockExplorer() {
QModelIndex index = ui->history->currentIndex(); auto *tx = ui->history->currentEntry();
if (!tx) return;
QString txid; QString txid = tx->hash();
m_txHistory->transaction(m_model->mapToSource(index).row(), [&txid](TransactionInfo &tInfo) {
txid = tInfo.hash();
});
emit viewOnBlockExplorer(txid); emit viewOnBlockExplorer(txid);
} }
@ -124,27 +117,36 @@ void HistoryWidget::setSearchText(const QString &text) {
} }
void HistoryWidget::setSearchFilter(const QString &filter) { void HistoryWidget::setSearchFilter(const QString &filter) {
if(!m_model) return; if (!m_model) return;
m_model->setSearchFilter(filter); m_model->setSearchFilter(filter);
ui->history->setSearchMode(!filter.isEmpty());
}
void HistoryWidget::createTxProof() {
auto *tx = ui->history->currentEntry();
if (!tx) return;
auto *dialog = new TxProofDialog(this, m_wallet, tx);
dialog->exec();
dialog->deleteLater();
} }
void HistoryWidget::copy(copyField field) { void HistoryWidget::copy(copyField field) {
QModelIndex index = ui->history->currentIndex(); auto *tx = ui->history->currentEntry();
if (!tx) return;
QString data; QString data = [field, tx]{
m_txHistory->transaction(m_model->mapToSource(index).row(), [field, &data](TransactionInfo &tInfo) {
switch(field) { switch(field) {
case copyField::TxID: case copyField::TxID:
data = tInfo.hash(); return tx->hash();
break;
case copyField::Date: case copyField::Date:
data = tInfo.timestamp().toString("yyyy-MM-dd HH:mm"); return tx->timestamp().toString("yyyy-MM-dd HH:mm");
break;
case copyField::Amount: case copyField::Amount:
data = tInfo.displayAmount(); return tx->displayAmount();
break; default:
return QString("");
} }
}); }();
Utils::copyToClipboard(data); Utils::copyToClipboard(data);
} }

View file

@ -40,10 +40,12 @@ private slots:
void onViewOnBlockExplorer(); void onViewOnBlockExplorer();
void setSearchFilter(const QString &filter); void setSearchFilter(const QString &filter);
void onResendTransaction(); void onResendTransaction();
void createTxProof();
private: private:
enum copyField { enum copyField {
TxID = 0, TxID = 0,
Description,
Date, Date,
Amount Amount
}; };

View file

@ -101,7 +101,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QTreeView" name="history"> <widget class="HistoryView" name="history">
<property name="rootIsDecorated"> <property name="rootIsDecorated">
<bool>false</bool> <bool>false</bool>
</property> </property>
@ -115,6 +115,13 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>HistoryView</class>
<extends>QTreeView</extends>
<header>model/HistoryView.h</header>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View file

@ -32,6 +32,15 @@ TransactionInfo* TransactionHistory::transaction(const QString &id)
return itr != m_tinfo.end() ? *itr : nullptr; return itr != m_tinfo.end() ? *itr : nullptr;
} }
TransactionInfo* TransactionHistory::transaction(int index)
{
if (index < 0 || index >= m_tinfo.size()) {
return nullptr;
}
return m_tinfo[index];
}
void TransactionHistory::refresh(quint32 accountIndex) void TransactionHistory::refresh(quint32 accountIndex)
{ {
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)

View file

@ -29,6 +29,7 @@ class TransactionHistory : public QObject
public: public:
Q_INVOKABLE bool transaction(int index, std::function<void (TransactionInfo &)> callback); Q_INVOKABLE bool transaction(int index, std::function<void (TransactionInfo &)> callback);
Q_INVOKABLE TransactionInfo * transaction(const QString &id); Q_INVOKABLE TransactionInfo * transaction(const QString &id);
TransactionInfo* transaction(int index);
Q_INVOKABLE void refresh(quint32 accountIndex); Q_INVOKABLE void refresh(quint32 accountIndex);
Q_INVOKABLE void setTxNote(const QString &txid, const QString &note); Q_INVOKABLE void setTxNote(const QString &txid, const QString &note);
Q_INVOKABLE bool writeCSV(const QString &path); Q_INVOKABLE bool writeCSV(const QString &path);

View file

@ -255,6 +255,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceWidget, &TickerWidget::init); connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_balanceWidget, &TickerWidget::init);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx, &AppContext::onPreferredFiatCurrencyChanged); connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, m_ctx, &AppContext::onPreferredFiatCurrencyChanged);
connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, ui->sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged)); connect(m_windowSettings, &Settings::preferredFiatCurrencyChanged, ui->sendWidget, QOverload<>::of(&SendWidget::onPreferredFiatCurrencyChanged));
connect(m_windowSettings, &Settings::amountPrecisionChanged, m_ctx, &AppContext::onAmountPrecisionChanged);
// Skin // Skin
connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged); connect(m_windowSettings, &Settings::skinChanged, this, &MainWindow::skinChanged);
@ -584,9 +585,7 @@ void MainWindow::onWalletOpened() {
m_wizard->hide(); m_wizard->hide();
} }
this->raise(); this->bringToFront();
this->show();
this->activateWindow();
this->setEnabled(true); this->setEnabled(true);
if(!m_ctx->tor->torConnected) if(!m_ctx->tor->torConnected)
this->setStatusText("Wallet opened - Starting Tor (may take a while)"); this->setStatusText("Wallet opened - Starting Tor (may take a while)");
@ -766,17 +765,10 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
} }
void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) { void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
if(status) { // success if (status) { // success
QString body = QString("Successfully sent %1 transaction(s).").arg(txid.count()); QString body = QString("Successfully sent %1 transaction(s).").arg(txid.count());
QMessageBox::information(this, "Transactions sent", body); QMessageBox::information(this, "Transactions sent", body);
ui->sendWidget->clearFields(); ui->sendWidget->clearFields();
for(const auto &entry: txid) {
m_ctx->currentWallet->setUserNote(entry, m_ctx->tmpTxDescription);
AppContext::txDescriptionCache[entry] = m_ctx->tmpTxDescription;
}
m_ctx->tmpTxDescription = "";
} else { } else {
auto err = tx->errorString(); auto err = tx->errorString();
QString body = QString("Error committing transaction: %1").arg(err); QString body = QString("Error committing transaction: %1").arg(err);
@ -1341,6 +1333,14 @@ QString MainWindow::statusDots() {
return QString(".").repeated(m_statusDots); return QString(".").repeated(m_statusDots);
} }
void MainWindow::bringToFront() {
ensurePolished();
setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
show();
raise();
activateWindow();
}
MainWindow::~MainWindow() { MainWindow::~MainWindow() {
delete ui; delete ui;
} }

View file

@ -172,6 +172,7 @@ private:
void setStatusText(const QString &text, bool override = false, int timeout = 1000); void setStatusText(const QString &text, bool override = false, int timeout = 1000);
void showBalanceDialog(); void showBalanceDialog();
QString statusDots(); QString statusDots();
void bringToFront();
WalletWizard *createWizard(WalletWizard::Page startPage); WalletWizard *createWizard(WalletWizard::Page startPage);

188
src/model/HistoryView.cpp Normal file
View file

@ -0,0 +1,188 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "HistoryView.h"
#include "TransactionHistoryProxyModel.h"
#include <QHeaderView>
#include <QMenu>
HistoryView::HistoryView(QWidget *parent)
: QTreeView(parent)
, m_model(nullptr)
, m_headerMenu(new QMenu(this))
{
setUniformRowHeights(true);
setRootIsDecorated(false);
setAlternatingRowColors(true);
setDragEnabled(true);
setSortingEnabled(true);
header()->setStretchLastSection(false);
header()->setContextMenuPolicy(Qt::CustomContextMenu);
connect(header(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(showHeaderMenu(QPoint)));
}
void HistoryView::setHistoryModel(TransactionHistoryProxyModel *model) {
m_model = model;
m_model->setSortCaseSensitivity(Qt::CaseInsensitive);
m_model->setSortRole(Qt::UserRole);
m_model->setDynamicSortFilter(true);
m_model->setSortLocaleAware(true);
QTreeView::setModel(m_model);
resetViewToDefaults();
m_headerMenu->clear();
// Actions to toggle column visibility, each carrying the corresponding
// column index as data
m_columnActions = new QActionGroup(this);
m_columnActions->setExclusive(false);
for (int visualIndex = 1; visualIndex < header()->count(); ++visualIndex) {
int logicalIndex = header()->logicalIndex(visualIndex);
QString caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString();
if (caption.isEmpty()) {
caption = m_model->headerData(logicalIndex, Qt::Horizontal, Qt::ToolTipRole).toString();
}
auto action = m_headerMenu->addAction(caption);
action->setCheckable(true);
action->setData(logicalIndex);
m_columnActions->addAction(action);
}
connect(m_columnActions, &QActionGroup::triggered, this, &HistoryView::toggleColumnVisibility);
m_headerMenu->addSeparator();
m_headerMenu->addAction(tr("Fit to window"), this, &HistoryView::fitColumnsToWindow);
m_headerMenu->addAction(tr("Fit to contents"), this, &HistoryView::fitColumnsToContents);
m_headerMenu->addSeparator();
m_headerMenu->addAction(tr("Reset to defaults"), this, &HistoryView::resetViewToDefaults);
fitColumnsToWindow();
}
TransactionHistoryModel* HistoryView::sourceModel()
{
return dynamic_cast<TransactionHistoryModel *>(m_model->sourceModel());
}
TransactionInfo* HistoryView::currentEntry()
{
QModelIndexList list = selectionModel()->selectedRows();
if (list.size() == 1) {
return this->sourceModel()->entryFromIndex(m_model->mapToSource(list.first()));
} else {
return nullptr;
}
}
void HistoryView::setSearchMode(bool mode) {
m_inSearchMode = mode;
if (mode) {
header()->showSection(TransactionHistoryModel::TxID);
}
}
QByteArray HistoryView::viewState() const
{
return header()->saveState();
}
bool HistoryView::setViewState(const QByteArray& state)
{
// Reset to unsorted first (https://bugreports.qt.io/browse/QTBUG-86694)
header()->setSortIndicator(-1, Qt::AscendingOrder);
bool status = header()->restoreState(state);
m_columnsNeedRelayout = state.isEmpty();
return status;
}
void HistoryView::showHeaderMenu(const QPoint& position)
{
const QList<QAction*> actions = m_columnActions->actions();
for (auto& action : actions) {
Q_ASSERT(static_cast<QMetaType::Type>(action->data().type()) == QMetaType::Int);
if (static_cast<QMetaType::Type>(action->data().type()) != QMetaType::Int) {
continue;
}
int columnIndex = action->data().toInt();
action->setChecked(!isColumnHidden(columnIndex));
}
m_headerMenu->popup(mapToGlobal(position));
}
void HistoryView::toggleColumnVisibility(QAction* action)
{
// Verify action carries a column index as data. Since QVariant.toInt()
// below will accept anything that's interpretable as int, perform a type
// check here to make sure data actually IS int
Q_ASSERT(static_cast<QMetaType::Type>(action->data().type()) == QMetaType::Int);
if (static_cast<QMetaType::Type>(action->data().type()) != QMetaType::Int) {
return;
}
// Toggle column visibility. Visible columns will only be hidden if at
// least one visible column remains, as the table header will disappear
// entirely when all columns are hidden
int columnIndex = action->data().toInt();
if (action->isChecked()) {
header()->showSection(columnIndex);
if (header()->sectionSize(columnIndex) == 0) {
header()->resizeSection(columnIndex, header()->defaultSectionSize());
}
return;
}
if ((header()->count() - header()->hiddenSectionCount()) > 1) {
header()->hideSection(columnIndex);
return;
}
action->setChecked(true);
}
void HistoryView::fitColumnsToWindow()
{
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
header()->setSectionResizeMode(TransactionHistoryModel::Description, QHeaderView::Stretch);
header()->setStretchLastSection(false);
QCoreApplication::processEvents();
}
void HistoryView::fitColumnsToContents()
{
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
QCoreApplication::processEvents();
header()->setSectionResizeMode(QHeaderView::Interactive);
}
void HistoryView::resetViewToDefaults()
{
if (m_inSearchMode) {
header()->showSection(TransactionHistoryModel::TxID);
} else {
header()->hideSection(TransactionHistoryModel::TxID);
}
header()->showSection(TransactionHistoryModel::Date);
header()->showSection(TransactionHistoryModel::Description);
header()->showSection(TransactionHistoryModel::Amount);
header()->showSection(TransactionHistoryModel::FiatAmount);
// Reset column order to logical indices
for (int i = 0; i < header()->count(); ++i) {
header()->moveSection(header()->visualIndex(i), i);
}
m_model->sort(TransactionHistoryModel::Date, Qt::DescendingOrder);
sortByColumn(TransactionHistoryModel::Date, Qt::DescendingOrder);
// The following call only relayouts reliably if the widget has been shown
// already, so only do it if the widget is visible and let showEvent() handle
// the initial default layout.
if (isVisible()) {
fitColumnsToWindow();
}
}

44
src/model/HistoryView.h Normal file
View file

@ -0,0 +1,44 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_HISTORYVIEW_H
#define FEATHER_HISTORYVIEW_H
#include <QTreeView>
#include <QActionGroup>
#include "TransactionHistoryModel.h"
class HistoryView : public QTreeView
{
Q_OBJECT
public:
explicit HistoryView(QWidget* parent = nullptr);
void setHistoryModel(TransactionHistoryProxyModel *model);
TransactionInfo* currentEntry();
void setSearchMode(bool mode);
QByteArray viewState() const;
bool setViewState(const QByteArray& state);
private slots:
void showHeaderMenu(const QPoint& position);
void toggleColumnVisibility(QAction* action);
void fitColumnsToWindow();
void fitColumnsToContents();
void resetViewToDefaults();
private:
TransactionHistoryModel* sourceModel();
TransactionHistoryProxyModel* m_model;
bool m_inSearchMode = false;
bool m_columnsNeedRelayout = true;
QMenu* m_headerMenu;
QActionGroup* m_columnActions;
};
#endif //FEATHER_HISTORYVIEW_H

View file

@ -16,7 +16,7 @@ QString ModelUtils::displayAddress(const QString& address, int sections, const Q
for (int i = 0; i < sections; i += 1) { for (int i = 0; i < sections; i += 1) {
list << address.mid(i*5, 5); list << address.mid(i*5, 5);
} }
list << "..."; list << ""; // utf-8 Horizontal Ellipsis
for (int i = sections; i > 0; i -= 1) { for (int i = sections; i > 0; i -= 1) {
list << address.mid(address.length() - i * 5, 5); list << address.mid(address.length() - i * 5, 5);
} }

View file

@ -6,6 +6,7 @@
#include "TransactionInfo.h" #include "TransactionInfo.h"
#include "globals.h" #include "globals.h"
#include "utils/ColorScheme.h" #include "utils/ColorScheme.h"
#include "ModelUtils.h"
TransactionHistoryModel::TransactionHistoryModel(QObject *parent) TransactionHistoryModel::TransactionHistoryModel(QObject *parent)
: QAbstractTableModel(parent), : QAbstractTableModel(parent),
@ -38,6 +39,11 @@ TransactionHistory *TransactionHistoryModel::transactionHistory() const {
return m_transactionHistory; return m_transactionHistory;
} }
TransactionInfo* TransactionHistoryModel::entryFromIndex(const QModelIndex &index) const {
Q_ASSERT(index.isValid() && index.row() < m_transactionHistory->count());
return m_transactionHistory->transaction(index.row());
}
int TransactionHistoryModel::rowCount(const QModelIndex &parent) const { int TransactionHistoryModel::rowCount(const QModelIndex &parent) const {
if (parent.isValid()) { if (parent.isValid()) {
return 0; return 0;
@ -65,8 +71,8 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
QVariant result; QVariant result;
bool found = m_transactionHistory->transaction(index.row(), [this, &index, &result, &role](const TransactionInfo &tInfo) { bool found = m_transactionHistory->transaction(index.row(), [this, &index, &result, &role](const TransactionInfo &tInfo) {
if(role == Qt::DisplayRole || role == Qt::EditRole) { if(role == Qt::DisplayRole || role == Qt::EditRole || role == Qt::UserRole) {
result = parseTransactionInfo(tInfo, index.column()); result = parseTransactionInfo(tInfo, index.column(), role);
} }
else if (role == Qt::TextAlignmentRole) { else if (role == Qt::TextAlignmentRole) {
switch (index.column()) { switch (index.column()) {
@ -130,31 +136,26 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const
return result; return result;
} }
QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tInfo, int column) const QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tInfo, int column, int role) const
{ {
switch (column) switch (column)
{ {
case Column::Date: case Column::Date:
return tInfo.timestamp().toString("yyyy-MM-dd HH:mm"); return tInfo.timestamp().toString("yyyy-MM-dd HH:mm ");
case Column::Description: { case Column::Description:
// if this tx is still in the pool, then we wont get the
// description. We've cached it inside `AppContext::txDescriptionCache`
// for the time being.
if(tInfo.isPending()) {
auto hash = tInfo.hash();
if (AppContext::txDescriptionCache.contains(hash))
return AppContext::txDescriptionCache[hash];
}
return tInfo.description(); return tInfo.description();
}
case Column::Amount: case Column::Amount:
{ {
QString amount = QString::number(tInfo.balanceDelta() / globals::cdiv, 'f', 4); if (role == Qt::UserRole) {
return tInfo.balanceDelta();
}
QString amount = QString::number(tInfo.balanceDelta() / globals::cdiv, 'f', this->amountPrecision);
amount = (tInfo.direction() == TransactionInfo::Direction_Out) ? "-" + amount : "+" + amount; amount = (tInfo.direction() == TransactionInfo::Direction_Out) ? "-" + amount : "+" + amount;
return amount; return amount;
} }
case Column::TxID: case Column::TxID: {
return tInfo.hash(); return ModelUtils::displayAddress(tInfo.hash(), 1);
}
case Column::FiatAmount: case Column::FiatAmount:
{ {
double usd_price = AppContext::txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd")); double usd_price = AppContext::txFiatHistory->get(tInfo.timestamp().toString("yyyyMMdd"));
@ -164,8 +165,11 @@ QVariant TransactionHistoryModel::parseTransactionInfo(const TransactionInfo &tI
double usd_amount = usd_price * (tInfo.balanceDelta() / globals::cdiv); double usd_amount = usd_price * (tInfo.balanceDelta() / globals::cdiv);
if(this->preferredFiatSymbol != "USD") if(this->preferredFiatSymbol != "USD")
usd_amount = AppContext::prices->convert("USD", this->preferredFiatSymbol, usd_amount); usd_amount = AppContext::prices->convert("USD", this->preferredFiatSymbol, usd_amount);
double fiat_rounded = ceil(Utils::roundSignificant(usd_amount, 3) * 100.0) / 100.0; 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, this->preferredFiatSymbol));
} }
default: default:
@ -224,15 +228,10 @@ bool TransactionHistoryModel::setData(const QModelIndex &index, const QVariant &
} }
Qt::ItemFlags TransactionHistoryModel::flags(const QModelIndex &index) const { Qt::ItemFlags TransactionHistoryModel::flags(const QModelIndex &index) const {
bool isPending;
m_transactionHistory->transaction(index.row(), [this, &isPending](const TransactionInfo &tInfo){
isPending = tInfo.isPending();
});
if (!index.isValid()) if (!index.isValid())
return Qt::ItemIsEnabled; return Qt::ItemIsEnabled;
if (index.column() == Description && !isPending) if (index.column() == Description)
return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; return QAbstractTableModel::flags(index) | Qt::ItemIsEditable;
return QAbstractTableModel::flags(index); return QAbstractTableModel::flags(index);

View file

@ -24,9 +24,9 @@ public:
enum Column enum Column
{ {
Date = 0, Date = 0,
TxID,
Description, Description,
Amount, Amount,
TxID,
FiatAmount, FiatAmount,
COUNT COUNT
}; };
@ -34,8 +34,11 @@ public:
explicit TransactionHistoryModel(QObject * parent = nullptr); explicit TransactionHistoryModel(QObject * parent = nullptr);
void setTransactionHistory(TransactionHistory * th); void setTransactionHistory(TransactionHistory * th);
TransactionHistory * transactionHistory() const; TransactionHistory * transactionHistory() const;
TransactionInfo* entryFromIndex(const QModelIndex& index) const;
QString preferredFiatSymbol = "USD"; QString preferredFiatSymbol = "USD";
int amountPrecision = 4;
int rowCount(const QModelIndex & parent = QModelIndex()) const override; int rowCount(const QModelIndex & parent = QModelIndex()) const override;
int columnCount(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; QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
@ -48,7 +51,7 @@ signals:
void transactionHistoryChanged(); void transactionHistoryChanged();
private: private:
QVariant parseTransactionInfo(const TransactionInfo &tInfo, int column) const; QVariant parseTransactionInfo(const TransactionInfo &tInfo, int column, int role) const;
TransactionHistory * m_transactionHistory; TransactionHistory * m_transactionHistory;
QIcon m_unconfirmedTx; QIcon m_unconfirmedTx;

View file

@ -16,6 +16,10 @@ TransactionHistoryProxyModel::TransactionHistoryProxyModel(Wallet *wallet, QObje
m_history = m_wallet->history(); m_history = m_wallet->history();
} }
TransactionHistory* TransactionHistoryProxyModel::history() {
return m_history;
}
bool TransactionHistoryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const bool TransactionHistoryProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
{ {
QString description, txid, subaddrlabel; QString description, txid, subaddrlabel;

View file

@ -16,6 +16,7 @@ Q_OBJECT
public: public:
explicit TransactionHistoryProxyModel(Wallet *wallet, QObject* parent = nullptr); explicit TransactionHistoryProxyModel(Wallet *wallet, QObject* parent = nullptr);
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const; bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
TransactionHistory* history();
public slots: public slots:
void setSearchFilter(const QString& searchString){ void setSearchFilter(const QString& searchString){

View file

@ -43,9 +43,15 @@ Settings::Settings(QWidget *parent) :
if (m_skins.contains(settingsSkin)) if (m_skins.contains(settingsSkin))
ui->comboBox_skin->setCurrentIndex(m_skins.indexOf(settingsSkin)); ui->comboBox_skin->setCurrentIndex(m_skins.indexOf(settingsSkin));
for (int i = 0; i <= 12; i++) {
ui->comboBox_amountPrecision->addItem(QString::number(i));
}
ui->comboBox_amountPrecision->setCurrentIndex(config()->get(Config::amountPrecision).toInt());
connect(ui->comboBox_skin, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_skinChanged); connect(ui->comboBox_skin, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_skinChanged);
connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_blockExplorerChanged); connect(ui->comboBox_blockExplorer, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_blockExplorerChanged);
connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_redditFrontendChanged); connect(ui->comboBox_redditFrontend, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_redditFrontendChanged);
connect(ui->comboBox_amountPrecision, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &Settings::comboBox_amountPrecisionChanged);
// setup preferred fiat currency combobox // setup preferred fiat currency combobox
QStringList fiatCurrencies; QStringList fiatCurrencies;
@ -100,6 +106,11 @@ void Settings::comboBox_redditFrontendChanged(int pos) {
config()->set(Config::redditFrontend, redditFrontend); config()->set(Config::redditFrontend, redditFrontend);
} }
void Settings::comboBox_amountPrecisionChanged(int pos) {
config()->set(Config::amountPrecision, pos);
emit amountPrecisionChanged(pos);
}
void Settings::copyToClipboard() { void Settings::copyToClipboard() {
ui->textLogs->copy(); ui->textLogs->copy();
} }

View file

@ -29,6 +29,7 @@ signals:
void skinChanged(QString skinName); void skinChanged(QString skinName);
void showHomeCCS(bool); void showHomeCCS(bool);
void blockExplorerChanged(QString blockExplorer); void blockExplorerChanged(QString blockExplorer);
void amountPrecisionChanged(int precision);
public slots: public slots:
void updatePaths(); void updatePaths();
@ -38,6 +39,7 @@ public slots:
void comboBox_skinChanged(int pos); void comboBox_skinChanged(int pos);
void comboBox_blockExplorerChanged(int pos); void comboBox_blockExplorerChanged(int pos);
void comboBox_redditFrontendChanged(int pos); void comboBox_redditFrontendChanged(int pos);
void comboBox_amountPrecisionChanged(int pos);
private: private:
QStringList m_skins{"Native", "QDarkStyle", "Breeze/Dark", "Breeze/Light"}; QStringList m_skins{"Native", "QDarkStyle", "Breeze/Dark", "Breeze/Light"};

View file

@ -161,14 +161,14 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="5" column="0">
<widget class="QCheckBox" name="checkBox_externalLink"> <widget class="QCheckBox" name="checkBox_externalLink">
<property name="text"> <property name="text">
<string>Warn before opening external link</string> <string>Warn before opening external link</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0"> <item row="6" column="0">
<widget class="QCheckBox" name="checkBox_hideBalance"> <widget class="QCheckBox" name="checkBox_hideBalance">
<property name="text"> <property name="text">
<string>Hide balance</string> <string>Hide balance</string>
@ -201,6 +201,16 @@
</item> </item>
</widget> </widget>
</item> </item>
<item row="4" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Amount precision:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="comboBox_amountPrecision"/>
</item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="tab_node"> <widget class="QWidget" name="tab_node">

View file

@ -46,7 +46,9 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
{Config::firstRun,{QS("firstRun"), false}}, {Config::firstRun,{QS("firstRun"), false}},
{Config::hideBalance, {QS("hideBalance"), false}}, {Config::hideBalance, {QS("hideBalance"), false}},
{Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}}, {Config::redditFrontend, {QS("redditFrontend"), "old.reddit.com"}},
{Config::showHistorySyncNotice, {QS("showHistorySyncNotice"), true}} {Config::showHistorySyncNotice, {QS("showHistorySyncNotice"), true}},
{Config::GUI_HistoryViewState, {QS("GUI_HistoryViewState"), {}}},
{Config::amountPrecision, {QS("amountPrecision"), 4}}
}; };

View file

@ -48,7 +48,9 @@ public:
firstRun, firstRun,
hideBalance, hideBalance,
redditFrontend, redditFrontend,
showHistorySyncNotice showHistorySyncNotice,
GUI_HistoryViewState,
amountPrecision
}; };
~Config() override; ~Config() override;

View file

@ -1,68 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#include "txproofwidget.h"
#include "ui_txproofwidget.h"
#include <QMessageBox>
#include "utils/utils.h"
TxProofWidget::TxProofWidget(QWidget *parent, Wallet *wallet, TransactionInfo *txInfo)
: QWidget(parent)
, ui(new Ui::TxProofWidget)
, m_wallet(wallet)
, m_txid(txInfo->hash())
{
ui->setupUi(this);
if (txInfo->direction() == TransactionInfo::Direction_Out) {
for (auto const &d: txInfo->destinations()) {
ui->comboBox_TxProofAddresses->addItem(d);
}
} else {
ui->btn_copySpendProof->setEnabled(false);
for (auto const &s: txInfo->subaddrIndex()) {
ui->comboBox_TxProofAddresses->addItem(wallet->address(txInfo->subaddrAccount(), s));
}
}
if (ui->comboBox_TxProofAddresses->count() == 0) {
ui->btn_copyTxProof->setEnabled(false);
}
connect(ui->btn_copySpendProof, &QPushButton::clicked, this, &TxProofWidget::copySpendProof);
connect(ui->btn_copyTxProof, &QPushButton::clicked, this, &TxProofWidget::copyTxProof);
ui->label_SpendProof->setHelpText("A SpendProof proves authorship of a transaction.\n\n"
"SpendProofs do not prove that a particular amount was sent to an address, for that use OutProofs.");
ui->label_InOutProof->setHelpText("An InProof proves ownership of an output.\n"
"An OutProof shows the prover sent an output to an address.");
}
void TxProofWidget::copySpendProof() {
auto txproof = m_wallet->getSpendProof(m_txid, "");
if (!txproof.error.isEmpty()) {
QMessageBox::warning(this, "Copy SpendProof", QString("Failed to copy SpendProof").arg(txproof.error));
return;
}
Utils::copyToClipboard(txproof.proof);
QMessageBox::information(this, "Copy SpendProof", "SpendProof copied to clipboard");
}
void TxProofWidget::copyTxProof() {
auto txproof = m_wallet->getTxProof(m_txid, ui->comboBox_TxProofAddresses->currentText(), "");
if (!txproof.error.isEmpty()) {
QMessageBox::warning(this, "Copy Transaction Proof", QString("Failed to copy transaction proof: %1").arg(txproof.error));
return;
}
Utils::copyToClipboard(txproof.proof);
QMessageBox::information(this, "Copy Transaction Proof", "Transaction proof copied to clipboard");
}
TxProofWidget::~TxProofWidget() {
delete ui;
}

View file

@ -1,33 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020-2021, The Monero Project.
#ifndef FEATHER_TXPROOFWIDGET_H
#define FEATHER_TXPROOFWIDGET_H
#include <QWidget>
#include "libwalletqt/Wallet.h"
#include "libwalletqt/TransactionInfo.h"
namespace Ui {
class TxProofWidget;
}
class TxProofWidget : public QWidget
{
Q_OBJECT
public:
explicit TxProofWidget(QWidget *parent, Wallet *wallet, TransactionInfo *txid);
~TxProofWidget() override;
private:
void copySpendProof();
void copyTxProof();
Ui::TxProofWidget *ui;
QString m_txid;
Wallet *m_wallet;
};
#endif //FEATHER_TXPROOFWIDGET_H

View file

@ -1,110 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TxProofWidget</class>
<widget class="QWidget" name="TxProofWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>647</width>
<height>79</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="btn_copySpendProof">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item>
<widget class="HelpLabel" name="label_SpendProof">
<property name="text">
<string>SpendProof</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QPushButton" name="btn_copyTxProof">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Copy</string>
</property>
</widget>
</item>
<item>
<widget class="HelpLabel" name="label_InOutProof">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>In/OutProof</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_TxProofAddresses">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>HelpLabel</class>
<extends>QLabel</extends>
<header>components.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>