Add sent and received shielded Txs in the transactions tab.
This commit is contained in:
@@ -21,6 +21,7 @@ struct ToFields {
|
|||||||
struct Tx {
|
struct Tx {
|
||||||
QString fromAddr;
|
QString fromAddr;
|
||||||
QList<ToFields> toAddrs;
|
QList<ToFields> toAddrs;
|
||||||
|
double fee;
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
|
|||||||
@@ -35,6 +35,8 @@
|
|||||||
#include <QtNetwork/QNetworkAccessManager>
|
#include <QtNetwork/QNetworkAccessManager>
|
||||||
#include <QtNetwork/QNetworkReply>
|
#include <QtNetwork/QNetworkReply>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
#include <QAbstractTableModel>
|
#include <QAbstractTableModel>
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|||||||
82
src/rpc.cpp
82
src/rpc.cpp
@@ -1,7 +1,7 @@
|
|||||||
#include "rpc.h"
|
#include "rpc.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "transactionitem.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
|
#include "senttxstore.h"
|
||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
|
|||||||
// Set up the timer to watch for tx status
|
// Set up the timer to watch for tx status
|
||||||
txTimer = new QTimer(main);
|
txTimer = new QTimer(main);
|
||||||
QObject::connect(txTimer, &QTimer::timeout, [=]() {
|
QObject::connect(txTimer, &QTimer::timeout, [=]() {
|
||||||
refreshTxStatus();
|
watchTxStatus();
|
||||||
});
|
});
|
||||||
// Start at every 10s. When an operation is pending, this will change to every second
|
// Start at every 10s. When an operation is pending, this will change to every second
|
||||||
txTimer->start(Utils::updateSpeed);
|
txTimer->start(Utils::updateSpeed);
|
||||||
@@ -45,7 +45,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
|
|||||||
QObject::connect(priceTimer, &QTimer::timeout, [=]() {
|
QObject::connect(priceTimer, &QTimer::timeout, [=]() {
|
||||||
refreshZECPrice();
|
refreshZECPrice();
|
||||||
});
|
});
|
||||||
priceTimer->start(60 * 60 * 1000); // Every hour
|
priceTimer->start(Utils::priceRefreshSpeed); // Every hour
|
||||||
}
|
}
|
||||||
|
|
||||||
RPC::~RPC() {
|
RPC::~RPC() {
|
||||||
@@ -389,7 +389,7 @@ void RPC::getReceivedZTrans(QList<QString> zaddrs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionsTableModel->addNewData(txdata);
|
transactionsTableModel->addZRecvData(txdata);
|
||||||
|
|
||||||
// Cleanup both responses;
|
// Cleanup both responses;
|
||||||
delete zaddrTxids;
|
delete zaddrTxids;
|
||||||
@@ -417,7 +417,7 @@ void RPC::getInfoThenRefresh() {
|
|||||||
|
|
||||||
doRPC(payload, [=] (const json& reply) {
|
doRPC(payload, [=] (const json& reply) {
|
||||||
// Testnet?
|
// Testnet?
|
||||||
if (reply.find("testnet") != reply.end()) {
|
if (!reply["testnet"].is_null()) {
|
||||||
Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>());
|
Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>());
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -425,13 +425,11 @@ void RPC::getInfoThenRefresh() {
|
|||||||
QIcon i(":/icons/res/connected.png");
|
QIcon i(":/icons/res/connected.png");
|
||||||
main->statusIcon->setPixmap(i.pixmap(16, 16));
|
main->statusIcon->setPixmap(i.pixmap(16, 16));
|
||||||
|
|
||||||
// Expect 2 data additions, then automatically refresh the table
|
|
||||||
transactionsTableModel->prepNewData(2);
|
|
||||||
|
|
||||||
// Refresh everything.
|
// Refresh everything.
|
||||||
refreshBalances();
|
refreshBalances();
|
||||||
refreshAddresses();
|
refreshAddresses();
|
||||||
refreshTransactions();
|
refreshTransactions();
|
||||||
|
refreshZSentTransactions();
|
||||||
|
|
||||||
// Call to see if the blockchain is syncing.
|
// Call to see if the blockchain is syncing.
|
||||||
json payload = {
|
json payload = {
|
||||||
@@ -559,14 +557,14 @@ void RPC::refreshTransactions() {
|
|||||||
|
|
||||||
for (auto& it : reply.get<json::array_t>()) {
|
for (auto& it : reply.get<json::array_t>()) {
|
||||||
double fee = 0;
|
double fee = 0;
|
||||||
if (it.find("fee") != it.end()) {
|
if (!it["fee"].is_null()) {
|
||||||
fee = it["fee"].get<json::number_float_t>();
|
fee = it["fee"].get<json::number_float_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionItem tx{
|
TransactionItem tx{
|
||||||
QString::fromStdString(it["category"]),
|
QString::fromStdString(it["category"]),
|
||||||
it["time"].get<json::number_unsigned_t>(),
|
it["time"].get<json::number_unsigned_t>(),
|
||||||
(it["address"].is_null() ? "(shielded)" : QString::fromStdString(it["address"])),
|
(it["address"].is_null() ? "" : QString::fromStdString(it["address"])),
|
||||||
QString::fromStdString(it["txid"]),
|
QString::fromStdString(it["txid"]),
|
||||||
it["amount"].get<json::number_float_t>() + fee,
|
it["amount"].get<json::number_float_t>() + fee,
|
||||||
it["confirmations"].get<json::number_unsigned_t>()
|
it["confirmations"].get<json::number_unsigned_t>()
|
||||||
@@ -576,15 +574,55 @@ void RPC::refreshTransactions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update model data, which updates the table view
|
// Update model data, which updates the table view
|
||||||
transactionsTableModel->addNewData(txdata);
|
transactionsTableModel->addTData(txdata);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void RPC::refreshTxStatus(const QString& newOpid) {
|
// Read sent Z transactions from the file.
|
||||||
if (!newOpid.isEmpty()) {
|
void RPC::refreshZSentTransactions() {
|
||||||
watchingOps.insert(newOpid);
|
auto sentZTxs = SentTxStore::readSentTxFile();
|
||||||
|
QList<QString> txids;
|
||||||
|
|
||||||
|
for (auto sentTx: sentZTxs) {
|
||||||
|
txids.push_back(sentTx.txid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Look up all the txids to get the confirmation count for them.
|
||||||
|
getBatchRPC(txids,
|
||||||
|
[=] (QString txid) {
|
||||||
|
json payload = {
|
||||||
|
{"jsonrpc", "1.0"},
|
||||||
|
{"id", "senttxid"},
|
||||||
|
{"method", "gettransaction"},
|
||||||
|
{"params", {txid.toStdString()}}
|
||||||
|
};
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
},
|
||||||
|
[=] (QMap<QString, json>* txidList) {
|
||||||
|
auto newSentZTxs = sentZTxs;
|
||||||
|
// Update the original sent list with the confirmation count
|
||||||
|
// TODO: This whole thing is kinda inefficient. We should probably just update the file
|
||||||
|
// with the confirmed block number, so we don't have to keep calling gettransaction for the
|
||||||
|
// sent items.
|
||||||
|
for (TransactionItem& sentTx: newSentZTxs) {
|
||||||
|
auto error = txidList->value(sentTx.txid)["confirmations"].is_null();
|
||||||
|
if (!error)
|
||||||
|
sentTx.confirmations = txidList->value(sentTx.txid)["confirmations"].get<json::number_unsigned_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionsTableModel->addZSentData(newSentZTxs);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RPC::addNewTxToWatch(Tx tx, const QString& newOpid) {
|
||||||
|
watchingOps.insert(newOpid, tx);
|
||||||
|
|
||||||
|
watchTxStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RPC::watchTxStatus() {
|
||||||
// Make an RPC to load pending operation statues
|
// Make an RPC to load pending operation statues
|
||||||
json payload = {
|
json payload = {
|
||||||
{"jsonrpc", "1.0"},
|
{"jsonrpc", "1.0"},
|
||||||
@@ -602,12 +640,13 @@ void RPC::refreshTxStatus(const QString& newOpid) {
|
|||||||
QString status = QString::fromStdString(it["status"]);
|
QString status = QString::fromStdString(it["status"]);
|
||||||
if (status == "success") {
|
if (status == "success") {
|
||||||
auto txid = QString::fromStdString(it["result"]["txid"]);
|
auto txid = QString::fromStdString(it["result"]["txid"]);
|
||||||
qDebug() << "Tx completed: " << txid;
|
|
||||||
|
SentTxStore::addToSentTx(watchingOps.value(id), txid);
|
||||||
|
|
||||||
main->ui->statusBar->showMessage(Utils::txidStatusMessage + " " + txid);
|
main->ui->statusBar->showMessage(Utils::txidStatusMessage + " " + txid);
|
||||||
main->loadingLabel->setVisible(false);
|
main->loadingLabel->setVisible(false);
|
||||||
|
|
||||||
watchingOps.remove(id);
|
watchingOps.remove(id);
|
||||||
txTimer->start(Utils::updateSpeed);
|
|
||||||
|
|
||||||
// Refresh balances to show unconfirmed balances
|
// Refresh balances to show unconfirmed balances
|
||||||
refresh();
|
refresh();
|
||||||
@@ -623,17 +662,19 @@ void RPC::refreshTxStatus(const QString& newOpid) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
watchingOps.remove(id);
|
watchingOps.remove(id);
|
||||||
txTimer->start(Utils::updateSpeed);
|
|
||||||
|
|
||||||
main->ui->statusBar->showMessage(" Tx " % id % " failed", 15 * 1000);
|
main->ui->statusBar->showMessage(" Tx " % id % " failed", 15 * 1000);
|
||||||
main->loadingLabel->setVisible(false);
|
main->loadingLabel->setVisible(false);
|
||||||
|
|
||||||
msg.exec();
|
msg.exec();
|
||||||
} else if (status == "executing") {
|
|
||||||
// If the operation is executing, then watch every second.
|
|
||||||
txTimer->start(Utils::quickUpdateSpeed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (watchingOps.isEmpty()) {
|
||||||
|
txTimer->start(Utils::updateSpeed);
|
||||||
|
} else {
|
||||||
|
txTimer->start(Utils::quickUpdateSpeed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is some op that we are watching, then show the loading bar, otherwise hide it
|
// If there is some op that we are watching, then show the loading bar, otherwise hide it
|
||||||
@@ -646,6 +687,7 @@ void RPC::refreshTxStatus(const QString& newOpid) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the ZEC->USD price from coinmarketcap using their API
|
||||||
void RPC::refreshZECPrice() {
|
void RPC::refreshZECPrice() {
|
||||||
QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/");
|
QUrl cmcURL("https://api.coinmarketcap.com/v1/ticker/");
|
||||||
|
|
||||||
|
|||||||
16
src/rpc.h
16
src/rpc.h
@@ -11,6 +11,16 @@
|
|||||||
|
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
|
||||||
|
struct TransactionItem {
|
||||||
|
QString type;
|
||||||
|
unsigned long datetime;
|
||||||
|
QString address;
|
||||||
|
QString txid;
|
||||||
|
double amount;
|
||||||
|
unsigned long confirmations;
|
||||||
|
};
|
||||||
|
|
||||||
class RPC
|
class RPC
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -18,11 +28,12 @@ public:
|
|||||||
~RPC();
|
~RPC();
|
||||||
|
|
||||||
void refresh(); // Refresh all transactions
|
void refresh(); // Refresh all transactions
|
||||||
void refreshTxStatus(const QString& newOpid = QString()); // Refresh the status of all pending txs.
|
|
||||||
void refreshAddresses(); // Refresh wallet Z-addrs
|
void refreshAddresses(); // Refresh wallet Z-addrs
|
||||||
void refreshZECPrice();
|
void refreshZECPrice();
|
||||||
|
|
||||||
void sendZTransaction (json params, const std::function<void(json)>& cb);
|
void sendZTransaction (json params, const std::function<void(json)>& cb);
|
||||||
|
void watchTxStatus();
|
||||||
|
void addNewTxToWatch(Tx tx, const QString& newOpid);
|
||||||
|
|
||||||
BalancesTableModel* getBalancesModel() { return balancesTableModel; }
|
BalancesTableModel* getBalancesModel() { return balancesTableModel; }
|
||||||
const QList<QString>* getAllZAddresses() { return zaddresses; }
|
const QList<QString>* getAllZAddresses() { return zaddresses; }
|
||||||
@@ -40,6 +51,7 @@ private:
|
|||||||
|
|
||||||
void refreshBalances();
|
void refreshBalances();
|
||||||
void refreshTransactions();
|
void refreshTransactions();
|
||||||
|
void refreshZSentTransactions();
|
||||||
|
|
||||||
bool processUnspent (const json& reply);
|
bool processUnspent (const json& reply);
|
||||||
void updateUI (bool anyUnconfirmed);
|
void updateUI (bool anyUnconfirmed);
|
||||||
@@ -69,7 +81,7 @@ private:
|
|||||||
QMap<QString, double>* allBalances = nullptr;
|
QMap<QString, double>* allBalances = nullptr;
|
||||||
QList<QString>* zaddresses = nullptr;
|
QList<QString>* zaddresses = nullptr;
|
||||||
|
|
||||||
QSet<QString> watchingOps;
|
QMap<QString, Tx> watchingOps;
|
||||||
|
|
||||||
TxTableModel* transactionsTableModel = nullptr;
|
TxTableModel* transactionsTableModel = nullptr;
|
||||||
BalancesTableModel* balancesTableModel = nullptr;
|
BalancesTableModel* balancesTableModel = nullptr;
|
||||||
|
|||||||
@@ -319,6 +319,7 @@ Tx MainWindow::createTxFromSendPage() {
|
|||||||
tx.toAddrs.push_back( ToFields{addr, amt, memo, memo.toUtf8().toHex()} );
|
tx.toAddrs.push_back( ToFields{addr, amt, memo, memo.toUtf8().toHex()} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.fee = Utils::getMinerFee();
|
||||||
return tx;
|
return tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -416,7 +417,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
|
|||||||
minerFee->setObjectName(QStringLiteral("minerFee"));
|
minerFee->setObjectName(QStringLiteral("minerFee"));
|
||||||
minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
|
minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
|
||||||
confirm.gridLayout->addWidget(minerFee, i, 1, 1, 1);
|
confirm.gridLayout->addWidget(minerFee, i, 1, 1, 1);
|
||||||
minerFee->setText(Settings::getInstance()->getZECDisplayFormat(Utils::getMinerFee()));
|
minerFee->setText(Settings::getInstance()->getZECDisplayFormat(tx.fee));
|
||||||
|
|
||||||
auto minerFeeUSD = new QLabel(confirm.sendToAddrs);
|
auto minerFeeUSD = new QLabel(confirm.sendToAddrs);
|
||||||
QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred);
|
||||||
@@ -424,7 +425,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
|
|||||||
minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD"));
|
minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD"));
|
||||||
minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
|
minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
|
||||||
confirm.gridLayout->addWidget(minerFeeUSD, i, 2, 1, 1);
|
confirm.gridLayout->addWidget(minerFeeUSD, i, 2, 1, 1);
|
||||||
minerFeeUSD->setText(Settings::getInstance()->getUSDFormat(Utils::getMinerFee()));
|
minerFeeUSD->setText(Settings::getInstance()->getUSDFormat(tx.fee));
|
||||||
|
|
||||||
if (!devFee.addr.isEmpty()) {
|
if (!devFee.addr.isEmpty()) {
|
||||||
auto labelDevFee = new QLabel(confirm.sendToAddrs);
|
auto labelDevFee = new QLabel(confirm.sendToAddrs);
|
||||||
@@ -517,7 +518,7 @@ void MainWindow::sendButton() {
|
|||||||
ui->statusBar->showMessage("Computing Tx: " % opid);
|
ui->statusBar->showMessage("Computing Tx: " % opid);
|
||||||
|
|
||||||
// And then start monitoring the transaction
|
// And then start monitoring the transaction
|
||||||
rpc->refreshTxStatus(opid);
|
rpc->addNewTxToWatch(tx, opid);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
86
src/senttxstore.cpp
Normal file
86
src/senttxstore.cpp
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
#include "senttxstore.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
/// Get the location of the app data file to be written.
|
||||||
|
QString SentTxStore::writeableFile() {
|
||||||
|
auto filename = QStringLiteral("senttxstore.dat");
|
||||||
|
|
||||||
|
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
|
||||||
|
if (!dir.exists())
|
||||||
|
QDir().mkpath(dir.absolutePath());
|
||||||
|
|
||||||
|
if (Settings::getInstance()->isTestnet()) {
|
||||||
|
return dir.filePath("testnet-" % filename);
|
||||||
|
} else {
|
||||||
|
return dir.filePath(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<TransactionItem> SentTxStore::readSentTxFile() {
|
||||||
|
QFile data(writeableFile());
|
||||||
|
if (!data.exists()) {
|
||||||
|
return QList<TransactionItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc;
|
||||||
|
|
||||||
|
data.open(QFile::ReadOnly);
|
||||||
|
jsonDoc = QJsonDocument().fromJson(data.readAll());
|
||||||
|
data.close();
|
||||||
|
|
||||||
|
QList<TransactionItem> items;
|
||||||
|
|
||||||
|
for (auto i : jsonDoc.array()) {
|
||||||
|
auto sentTx = i.toObject();
|
||||||
|
TransactionItem t{"sent", (unsigned long)sentTx["datetime"].toInt(),
|
||||||
|
sentTx["address"].toString(),
|
||||||
|
sentTx["txid"].toString(), sentTx["amount"].toDouble(), 0};
|
||||||
|
items.push_back(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SentTxStore::addToSentTx(Tx tx, QString txid) {
|
||||||
|
QFile data(writeableFile());
|
||||||
|
QJsonDocument jsonDoc;
|
||||||
|
|
||||||
|
// If data doesn't exist, then create a blank one
|
||||||
|
if (!data.exists()) {
|
||||||
|
QJsonArray a;
|
||||||
|
jsonDoc.setArray(a);
|
||||||
|
|
||||||
|
QFile newFile(writeableFile());
|
||||||
|
newFile.open(QFile::WriteOnly);
|
||||||
|
newFile.write(jsonDoc.toJson());
|
||||||
|
newFile.close();
|
||||||
|
} else {
|
||||||
|
data.open(QFile::ReadOnly);
|
||||||
|
jsonDoc = QJsonDocument().fromJson(data.readAll());
|
||||||
|
data.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total amount in this tx
|
||||||
|
double totalAmount = 0;
|
||||||
|
for (auto i : tx.toAddrs) {
|
||||||
|
totalAmount += i.amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto list = jsonDoc.array();
|
||||||
|
QJsonObject txItem;
|
||||||
|
txItem["type"] = "sent";
|
||||||
|
txItem["datetime"] = QDateTime().currentSecsSinceEpoch();
|
||||||
|
txItem["address"] = QString(); // The sent address is blank, to be consistent with t-Addr sent behaviour
|
||||||
|
txItem["txid"] = txid;
|
||||||
|
txItem["amount"] = -totalAmount;
|
||||||
|
txItem["fee"] = -tx.fee;
|
||||||
|
list.append(txItem);
|
||||||
|
|
||||||
|
jsonDoc.setArray(list);
|
||||||
|
|
||||||
|
QFile writer(writeableFile());
|
||||||
|
if (writer.open(QFile::WriteOnly | QFile::Truncate)) {
|
||||||
|
writer.write(jsonDoc.toJson());
|
||||||
|
}
|
||||||
|
writer.close();
|
||||||
|
}
|
||||||
18
src/senttxstore.h
Normal file
18
src/senttxstore.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef SENTTXSTORE_H
|
||||||
|
#define SENTTXSTORE_H
|
||||||
|
|
||||||
|
#include "precompiled.h"
|
||||||
|
#include "mainwindow.h"
|
||||||
|
#include "rpc.h"
|
||||||
|
|
||||||
|
class SentTxStore {
|
||||||
|
public:
|
||||||
|
static QList<TransactionItem> readSentTxFile();
|
||||||
|
static void addToSentTx(Tx tx, QString txid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QString writeableFile();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SENTTXSTORE_H
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
#ifndef TRANSACTIONITEM_H
|
|
||||||
#define TRANSACTIONITEM_H
|
|
||||||
|
|
||||||
#include "precompiled.h"
|
|
||||||
|
|
||||||
struct TransactionItem {
|
|
||||||
QString type;
|
|
||||||
unsigned long datetime;
|
|
||||||
QString address;
|
|
||||||
QString txid;
|
|
||||||
double amount;
|
|
||||||
unsigned long confirmations;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // TRANSACTIONITEM_H
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "txtablemodel.h"
|
#include "txtablemodel.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "rpc.h"
|
||||||
|
|
||||||
TxTableModel::TxTableModel(QObject *parent)
|
TxTableModel::TxTableModel(QObject *parent)
|
||||||
: QAbstractTableModel(parent) {
|
: QAbstractTableModel(parent) {
|
||||||
@@ -11,33 +12,46 @@ TxTableModel::~TxTableModel() {
|
|||||||
delete modeldata;
|
delete modeldata;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxTableModel::prepNewData(int expect) {
|
void TxTableModel::addZSentData(const QList<TransactionItem>& data) {
|
||||||
newmodeldata = new QList<TransactionItem>();
|
delete zsTrans;
|
||||||
expectedData = expect;
|
zsTrans = new QList<TransactionItem>();
|
||||||
|
std::copy(data.begin(), data.end(), std::back_inserter(*zsTrans));
|
||||||
|
|
||||||
|
updateAllData();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxTableModel::addNewData(const QList<TransactionItem>& data) {
|
void TxTableModel::addZRecvData(const QList<TransactionItem>& data) {
|
||||||
// Make sure we're expecting some data.
|
delete zrTrans;
|
||||||
Q_ASSERT(expectedData > 0);
|
zrTrans = new QList<TransactionItem>();
|
||||||
|
std::copy(data.begin(), data.end(), std::back_inserter(*zrTrans));
|
||||||
|
|
||||||
// Add all
|
updateAllData();
|
||||||
std::copy(data.begin(), data.end(), std::back_inserter(*newmodeldata));
|
}
|
||||||
expectedData--;
|
|
||||||
|
|
||||||
if (expectedData == 0) {
|
|
||||||
delete modeldata;
|
|
||||||
|
|
||||||
modeldata = newmodeldata;
|
void TxTableModel::addTData(const QList<TransactionItem>& data) {
|
||||||
newmodeldata = nullptr;
|
delete tTrans;
|
||||||
|
tTrans = new QList<TransactionItem>();
|
||||||
|
std::copy(data.begin(), data.end(), std::back_inserter(*tTrans));
|
||||||
|
|
||||||
// Sort by reverse time
|
updateAllData();
|
||||||
std::sort(modeldata->begin(), modeldata->end(), [=] (auto a, auto b) {
|
}
|
||||||
return a.datetime > b.datetime; // reverse sort
|
|
||||||
});
|
|
||||||
|
|
||||||
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
|
void TxTableModel::updateAllData() {
|
||||||
layoutChanged();
|
delete modeldata;
|
||||||
}
|
modeldata = new QList<TransactionItem>();
|
||||||
|
|
||||||
|
if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*modeldata));
|
||||||
|
if (zsTrans != nullptr) std::copy(zsTrans->begin(), zsTrans->end(), std::back_inserter(*modeldata));
|
||||||
|
if (zrTrans != nullptr) std::copy(zrTrans->begin(), zrTrans->end(), std::back_inserter(*modeldata));
|
||||||
|
|
||||||
|
// Sort by reverse time
|
||||||
|
std::sort(modeldata->begin(), modeldata->end(), [=] (auto a, auto b) {
|
||||||
|
return a.datetime > b.datetime; // reverse sort
|
||||||
|
});
|
||||||
|
|
||||||
|
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
|
||||||
|
layoutChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
int TxTableModel::rowCount(const QModelIndex&) const
|
int TxTableModel::rowCount(const QModelIndex&) const
|
||||||
@@ -73,7 +87,13 @@ void TxTableModel::addNewData(const QList<TransactionItem>& data) {
|
|||||||
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
|
if (role == Qt::DisplayRole || role == Qt::ToolTipRole) {
|
||||||
switch (index.column()) {
|
switch (index.column()) {
|
||||||
case 0: return modeldata->at(index.row()).type;
|
case 0: return modeldata->at(index.row()).type;
|
||||||
case 1: return modeldata->at(index.row()).address;
|
case 1: {
|
||||||
|
auto addr = modeldata->at(index.row()).address;
|
||||||
|
if (addr.trimmed().isEmpty())
|
||||||
|
return "(Shielded)";
|
||||||
|
else
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
case 2: return QDateTime::fromSecsSinceEpoch(modeldata->at(index.row()).datetime).toLocalTime().toString();
|
case 2: return QDateTime::fromSecsSinceEpoch(modeldata->at(index.row()).datetime).toLocalTime().toString();
|
||||||
case 3: {
|
case 3: {
|
||||||
if (role == Qt::DisplayRole)
|
if (role == Qt::DisplayRole)
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
#ifndef STRINGSTABLEMODEL_H
|
#ifndef STRINGSTABLEMODEL_H
|
||||||
#define STRINGSTABLEMODEL_H
|
#define STRINGSTABLEMODEL_H
|
||||||
|
|
||||||
#include "transactionitem.h"
|
|
||||||
#include "precompiled.h"
|
#include "precompiled.h"
|
||||||
|
|
||||||
|
struct TransactionItem;
|
||||||
|
|
||||||
class TxTableModel: public QAbstractTableModel
|
class TxTableModel: public QAbstractTableModel
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
TxTableModel(QObject* parent);
|
TxTableModel(QObject* parent);
|
||||||
~TxTableModel();
|
~TxTableModel();
|
||||||
|
|
||||||
void prepNewData (int expectedData);
|
void addTData (const QList<TransactionItem>& data);
|
||||||
void addNewData (const QList<TransactionItem>& data);
|
void addZSentData(const QList<TransactionItem>& data);
|
||||||
|
void addZRecvData(const QList<TransactionItem>& data);
|
||||||
|
|
||||||
QString getTxId(int row);
|
QString getTxId(int row);
|
||||||
|
|
||||||
@@ -21,9 +23,13 @@ public:
|
|||||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<TransactionItem>* modeldata = nullptr;
|
void updateAllData();
|
||||||
QList<TransactionItem>* newmodeldata = nullptr;
|
|
||||||
int expectedData;
|
QList<TransactionItem>* tTrans = nullptr;
|
||||||
|
QList<TransactionItem>* zrTrans = nullptr; // Z received
|
||||||
|
QList<TransactionItem>* zsTrans = nullptr; // Z sent
|
||||||
|
|
||||||
|
QList<TransactionItem>* modeldata = nullptr;
|
||||||
|
|
||||||
QList<QString> headers;
|
QList<QString> headers;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ public:
|
|||||||
static double getDevFee();
|
static double getDevFee();
|
||||||
static double getTotalFee();
|
static double getTotalFee();
|
||||||
|
|
||||||
static const int updateSpeed = 20 * 1000; // 20 sec
|
static const int updateSpeed = 20 * 1000; // 20 sec
|
||||||
static const int quickUpdateSpeed = 5 * 1000; // 5 sec
|
static const int quickUpdateSpeed = 5 * 1000; // 5 sec
|
||||||
|
static const int priceRefreshSpeed = 60 * 60 * 1000; // 1 hr
|
||||||
private:
|
private:
|
||||||
Utils() = delete;
|
Utils() = delete;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ SOURCES += \
|
|||||||
src/3rdparty/qrcode/QrSegment.cpp \
|
src/3rdparty/qrcode/QrSegment.cpp \
|
||||||
src/settings.cpp \
|
src/settings.cpp \
|
||||||
src/sendtab.cpp \
|
src/sendtab.cpp \
|
||||||
|
src/senttxstore.cpp \
|
||||||
src/txtablemodel.cpp \
|
src/txtablemodel.cpp \
|
||||||
src/utils.cpp
|
src/utils.cpp
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ HEADERS += \
|
|||||||
src/3rdparty/json/json.hpp \
|
src/3rdparty/json/json.hpp \
|
||||||
src/settings.h \
|
src/settings.h \
|
||||||
src/txtablemodel.h \
|
src/txtablemodel.h \
|
||||||
src/transactionitem.h \
|
src/senttxstore.h \
|
||||||
src/utils.h
|
src/utils.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
|
|||||||
Reference in New Issue
Block a user