Changeset View
Changeset View
Standalone View
Standalone View
src/qt/transactiontablemodel.cpp
// Copyright (c) 2011-2016 The Bitcoin Core developers | // Copyright (c) 2011-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#include "transactiontablemodel.h" | #include <qt/transactiontablemodel.h> | ||||
#include "addresstablemodel.h" | #include <core_io.h> | ||||
#include "guiconstants.h" | #include <interfaces/handler.h> | ||||
#include "guiutil.h" | #include <interfaces/node.h> | ||||
#include "optionsmodel.h" | #include <qt/addresstablemodel.h> | ||||
#include "platformstyle.h" | #include <qt/guiconstants.h> | ||||
#include "transactiondesc.h" | #include <qt/guiutil.h> | ||||
#include "transactionrecord.h" | #include <qt/optionsmodel.h> | ||||
#include "walletmodel.h" | #include <qt/platformstyle.h> | ||||
#include <qt/transactiondesc.h> | |||||
#include "core_io.h" | #include <qt/transactionrecord.h> | ||||
#include "sync.h" | #include <qt/walletmodel.h> | ||||
#include "uint256.h" | #include <sync.h> | ||||
#include "util.h" | #include <uint256.h> | ||||
#include "validation.h" | #include <util.h> | ||||
#include "wallet/wallet.h" | #include <validation.h> | ||||
#include <QColor> | #include <QColor> | ||||
#include <QDateTime> | #include <QDateTime> | ||||
#include <QDebug> | #include <QDebug> | ||||
#include <QIcon> | #include <QIcon> | ||||
#include <QList> | #include <QList> | ||||
// Amount column is right-aligned it contains numbers | // Amount column is right-aligned it contains numbers | ||||
Show All 18 Lines | struct TxLessThan { | ||||
bool operator()(const TxId &a, const TransactionRecord &b) const { | bool operator()(const TxId &a, const TransactionRecord &b) const { | ||||
return a < b.txid; | return a < b.txid; | ||||
} | } | ||||
}; | }; | ||||
// Private implementation | // Private implementation | ||||
class TransactionTablePriv { | class TransactionTablePriv { | ||||
public: | public: | ||||
TransactionTablePriv(CWallet *_wallet, TransactionTableModel *_parent) | TransactionTablePriv(TransactionTableModel *_parent) : parent(_parent) {} | ||||
: wallet(_wallet), parent(_parent) {} | |||||
CWallet *wallet; | |||||
TransactionTableModel *parent; | TransactionTableModel *parent; | ||||
/* Local cache of wallet. | /* Local cache of wallet. | ||||
* As it is in the same order as the CWallet, by definition this is sorted | * As it is in the same order as the CWallet, by definition this is sorted | ||||
* by sha256. | * by sha256. | ||||
*/ | */ | ||||
QList<TransactionRecord> cachedWallet; | QList<TransactionRecord> cachedWallet; | ||||
/** | /** | ||||
* Query entire wallet anew from core. | * Query entire wallet anew from core. | ||||
*/ | */ | ||||
void refreshWallet() { | void refreshWallet(interfaces::Wallet &wallet) { | ||||
qDebug() << "TransactionTablePriv::refreshWallet"; | qDebug() << "TransactionTablePriv::refreshWallet"; | ||||
cachedWallet.clear(); | cachedWallet.clear(); | ||||
for (const auto &wtx : wallet.getWalletTxs()) { | |||||
LOCK2(cs_main, wallet->cs_wallet); | if (TransactionRecord::showTransaction()) { | ||||
for (const auto &entry : wallet->mapWallet) { | cachedWallet.append( | ||||
if (TransactionRecord::showTransaction(entry.second)) { | TransactionRecord::decomposeTransaction(wtx)); | ||||
cachedWallet.append(TransactionRecord::decomposeTransaction( | |||||
wallet, entry.second)); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
/** | /** | ||||
* Update our model of the wallet incrementally, to synchronize our model of | * Update our model of the wallet incrementally, to synchronize our model of | ||||
* the wallet with that of the core. | * the wallet with that of the core. | ||||
* Call with transaction that was added, removed or changed. | * Call with transaction that was added, removed or changed. | ||||
*/ | */ | ||||
void updateWallet(const TxId &txid, int status, bool showTransaction) { | void updateWallet(interfaces::Wallet &wallet, const TxId &txid, int status, | ||||
bool showTransaction) { | |||||
qDebug() << "TransactionTablePriv::updateWallet: " + | qDebug() << "TransactionTablePriv::updateWallet: " + | ||||
QString::fromStdString(txid.ToString()) + " " + | QString::fromStdString(txid.ToString()) + " " + | ||||
QString::number(status); | QString::number(status); | ||||
// Find bounds of this transaction in model | // Find bounds of this transaction in model | ||||
QList<TransactionRecord>::iterator lower = qLowerBound( | QList<TransactionRecord>::iterator lower = qLowerBound( | ||||
cachedWallet.begin(), cachedWallet.end(), txid, TxLessThan()); | cachedWallet.begin(), cachedWallet.end(), txid, TxLessThan()); | ||||
QList<TransactionRecord>::iterator upper = qUpperBound( | QList<TransactionRecord>::iterator upper = qUpperBound( | ||||
Show All 23 Lines | void updateWallet(interfaces::Wallet &wallet, const TxId &txid, int status, | ||||
case CT_NEW: | case CT_NEW: | ||||
if (inModel) { | if (inModel) { | ||||
qWarning() << "TransactionTablePriv::updateWallet: " | qWarning() << "TransactionTablePriv::updateWallet: " | ||||
"Warning: Got CT_NEW, but transaction is " | "Warning: Got CT_NEW, but transaction is " | ||||
"already in model"; | "already in model"; | ||||
break; | break; | ||||
} | } | ||||
if (showTransaction) { | if (showTransaction) { | ||||
LOCK2(cs_main, wallet->cs_wallet); | |||||
// Find transaction in wallet | // Find transaction in wallet | ||||
std::map<TxId, CWalletTx>::iterator mi = | interfaces::WalletTx wtx = wallet.getWalletTx(txid); | ||||
wallet->mapWallet.find(txid); | if (!wtx.tx) { | ||||
if (mi == wallet->mapWallet.end()) { | |||||
qWarning() << "TransactionTablePriv::updateWallet: " | qWarning() << "TransactionTablePriv::updateWallet: " | ||||
"Warning: Got CT_NEW, but transaction is " | "Warning: Got CT_NEW, but transaction is " | ||||
"not in wallet"; | "not in wallet"; | ||||
break; | break; | ||||
} | } | ||||
// Added -- insert at the right position | // Added -- insert at the right position | ||||
QList<TransactionRecord> toInsert = | QList<TransactionRecord> toInsert = | ||||
TransactionRecord::decomposeTransaction(wallet, | TransactionRecord::decomposeTransaction(wtx); | ||||
mi->second); | |||||
/* only if something to insert */ | /* only if something to insert */ | ||||
if (!toInsert.isEmpty()) { | if (!toInsert.isEmpty()) { | ||||
parent->beginInsertRows(QModelIndex(), lowerIndex, | parent->beginInsertRows(QModelIndex(), lowerIndex, | ||||
lowerIndex + toInsert.size() - | lowerIndex + toInsert.size() - | ||||
1); | 1); | ||||
int insert_idx = lowerIndex; | int insert_idx = lowerIndex; | ||||
for (const TransactionRecord &rec : toInsert) { | for (const TransactionRecord &rec : toInsert) { | ||||
cachedWallet.insert(insert_idx, rec); | cachedWallet.insert(insert_idx, rec); | ||||
Show All 21 Lines | void updateWallet(interfaces::Wallet &wallet, const TxId &txid, int status, | ||||
// take care of this, and is only computed for visible | // take care of this, and is only computed for visible | ||||
// transactions. | // transactions. | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
int size() { return cachedWallet.size(); } | int size() { return cachedWallet.size(); } | ||||
TransactionRecord *index(int idx) { | TransactionRecord *index(interfaces::Wallet &wallet, int idx) { | ||||
if (idx >= 0 && idx < cachedWallet.size()) { | if (idx >= 0 && idx < cachedWallet.size()) { | ||||
TransactionRecord *rec = &cachedWallet[idx]; | TransactionRecord *rec = &cachedWallet[idx]; | ||||
// Get required locks upfront. This avoids the GUI from getting | // Get required locks upfront. This avoids the GUI from getting | ||||
// stuck if the core is holding the locks for a longer time - for | // stuck if the core is holding the locks for a longer time - for | ||||
// example, during a wallet rescan. | // example, during a wallet rescan. | ||||
// | // | ||||
// If a status update is needed (blocks came in since last check), | // If a status update is needed (blocks came in since last check), | ||||
// update the status of this transaction from the wallet. Otherwise, | // update the status of this transaction from the wallet. Otherwise, | ||||
// simply re-use the cached status. | // simply re-use the cached status. | ||||
TRY_LOCK(cs_main, lockMain); | interfaces::WalletTxStatus wtx; | ||||
if (lockMain) { | int numBlocks; | ||||
TRY_LOCK(wallet->cs_wallet, lockWallet); | int64_t adjustedTime; | ||||
if (lockWallet && rec->statusUpdateNeeded()) { | if (wallet.tryGetTxStatus(rec->txid, wtx, numBlocks, | ||||
std::map<TxId, CWalletTx>::iterator mi = | adjustedTime) && | ||||
wallet->mapWallet.find(rec->txid); | rec->statusUpdateNeeded(numBlocks)) { | ||||
rec->updateStatus(wtx, numBlocks, adjustedTime); | |||||
if (mi != wallet->mapWallet.end()) { | |||||
rec->updateStatus(mi->second); | |||||
} | |||||
} | |||||
} | } | ||||
return rec; | return rec; | ||||
} | } | ||||
return 0; | return 0; | ||||
} | } | ||||
QString describe(TransactionRecord *rec, int unit) { | QString describe(interfaces::Node &node, interfaces::Wallet &wallet, | ||||
LOCK2(cs_main, wallet->cs_wallet); | TransactionRecord *rec, int unit) { | ||||
std::map<TxId, CWalletTx>::iterator mi = | return TransactionDesc::toHTML(node, wallet, rec, unit); | ||||
wallet->mapWallet.find(rec->txid); | |||||
if (mi != wallet->mapWallet.end()) { | |||||
return TransactionDesc::toHTML(wallet, mi->second, rec, unit); | |||||
} | } | ||||
return QString(); | QString getTxHex(interfaces::Wallet &wallet, TransactionRecord *rec) { | ||||
} | auto tx = wallet.getTx(rec->txid); | ||||
if (tx) { | |||||
QString getTxHex(TransactionRecord *rec) { | std::string strHex = EncodeHexTx(*tx); | ||||
LOCK2(cs_main, wallet->cs_wallet); | |||||
std::map<TxId, CWalletTx>::iterator mi = | |||||
wallet->mapWallet.find(rec->txid); | |||||
if (mi != wallet->mapWallet.end()) { | |||||
std::string strHex = EncodeHexTx(*mi->second.tx); | |||||
return QString::fromStdString(strHex); | return QString::fromStdString(strHex); | ||||
} | } | ||||
return QString(); | return QString(); | ||||
} | } | ||||
}; | }; | ||||
TransactionTableModel::TransactionTableModel( | TransactionTableModel::TransactionTableModel( | ||||
const PlatformStyle *_platformStyle, CWallet *_wallet, WalletModel *parent) | const PlatformStyle *_platformStyle, WalletModel *parent) | ||||
: QAbstractTableModel(parent), wallet(_wallet), walletModel(parent), | : QAbstractTableModel(parent), walletModel(parent), | ||||
priv(new TransactionTablePriv(_wallet, this)), | priv(new TransactionTablePriv(this)), | ||||
fProcessingQueuedTransactions(false), platformStyle(_platformStyle) { | fProcessingQueuedTransactions(false), platformStyle(_platformStyle) { | ||||
columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") | columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") | ||||
<< BitcoinUnits::getAmountColumnTitle( | << BitcoinUnits::getAmountColumnTitle( | ||||
walletModel->getOptionsModel()->getDisplayUnit()); | walletModel->getOptionsModel()->getDisplayUnit()); | ||||
priv->refreshWallet(); | priv->refreshWallet(walletModel->wallet()); | ||||
connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), | connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), | ||||
this, SLOT(updateDisplayUnit())); | this, SLOT(updateDisplayUnit())); | ||||
subscribeToCoreSignals(); | subscribeToCoreSignals(); | ||||
} | } | ||||
TransactionTableModel::~TransactionTableModel() { | TransactionTableModel::~TransactionTableModel() { | ||||
Show All 9 Lines | void TransactionTableModel::updateAmountColumnTitle() { | ||||
Q_EMIT headerDataChanged(Qt::Horizontal, Amount, Amount); | Q_EMIT headerDataChanged(Qt::Horizontal, Amount, Amount); | ||||
} | } | ||||
void TransactionTableModel::updateTransaction(const QString &hash, int status, | void TransactionTableModel::updateTransaction(const QString &hash, int status, | ||||
bool showTransaction) { | bool showTransaction) { | ||||
TxId updated; | TxId updated; | ||||
updated.SetHex(hash.toStdString()); | updated.SetHex(hash.toStdString()); | ||||
priv->updateWallet(updated, status, showTransaction); | priv->updateWallet(walletModel->wallet(), updated, status, showTransaction); | ||||
} | } | ||||
void TransactionTableModel::updateConfirmations() { | void TransactionTableModel::updateConfirmations() { | ||||
// Blocks came in since last poll. | // Blocks came in since last poll. | ||||
// Invalidate status (number of confirmations) and (possibly) description | // Invalidate status (number of confirmations) and (possibly) description | ||||
// for all rows. Qt is smart enough to only actually request the data for | // for all rows. Qt is smart enough to only actually request the data for | ||||
// the visible rows. | // the visible rows. | ||||
Q_EMIT dataChanged(index(0, Status), index(priv->size() - 1, Status)); | Q_EMIT dataChanged(index(0, Status), index(priv->size() - 1, Status)); | ||||
▲ Show 20 Lines • Show All 325 Lines • ▼ Show 20 Lines | switch (role) { | ||||
case DateRole: | case DateRole: | ||||
return QDateTime::fromTime_t(static_cast<uint>(rec->time)); | return QDateTime::fromTime_t(static_cast<uint>(rec->time)); | ||||
case WatchonlyRole: | case WatchonlyRole: | ||||
return rec->involvesWatchAddress; | return rec->involvesWatchAddress; | ||||
case WatchonlyDecorationRole: | case WatchonlyDecorationRole: | ||||
return txWatchonlyDecoration(rec); | return txWatchonlyDecoration(rec); | ||||
case LongDescriptionRole: | case LongDescriptionRole: | ||||
return priv->describe( | return priv->describe( | ||||
rec, walletModel->getOptionsModel()->getDisplayUnit()); | walletModel->node(), walletModel->wallet(), rec, | ||||
walletModel->getOptionsModel()->getDisplayUnit()); | |||||
case AddressRole: | case AddressRole: | ||||
return QString::fromStdString(rec->address); | return QString::fromStdString(rec->address); | ||||
case LabelRole: | case LabelRole: | ||||
return walletModel->getAddressTableModel()->labelForAddress( | return walletModel->getAddressTableModel()->labelForAddress( | ||||
QString::fromStdString(rec->address)); | QString::fromStdString(rec->address)); | ||||
case AmountRole: | case AmountRole: | ||||
return qint64((rec->credit + rec->debit) / SATOSHI); | return qint64((rec->credit + rec->debit) / SATOSHI); | ||||
case TxIDRole: | case TxIDRole: | ||||
return rec->getTxID(); | return rec->getTxID(); | ||||
case TxHashRole: | case TxHashRole: | ||||
return QString::fromStdString(rec->txid.ToString()); | return QString::fromStdString(rec->txid.ToString()); | ||||
case TxHexRole: | case TxHexRole: | ||||
return priv->getTxHex(rec); | return priv->getTxHex(walletModel->wallet(), rec); | ||||
case TxPlainTextRole: { | case TxPlainTextRole: { | ||||
QString details; | QString details; | ||||
QDateTime date = | QDateTime date = | ||||
QDateTime::fromTime_t(static_cast<uint>(rec->time)); | QDateTime::fromTime_t(static_cast<uint>(rec->time)); | ||||
QString txLabel = | QString txLabel = | ||||
walletModel->getAddressTableModel()->labelForAddress( | walletModel->getAddressTableModel()->labelForAddress( | ||||
QString::fromStdString(rec->address)); | QString::fromStdString(rec->address)); | ||||
▲ Show 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | if (orientation == Qt::Horizontal) { | ||||
} | } | ||||
} | } | ||||
return QVariant(); | return QVariant(); | ||||
} | } | ||||
QModelIndex TransactionTableModel::index(int row, int column, | QModelIndex TransactionTableModel::index(int row, int column, | ||||
const QModelIndex &parent) const { | const QModelIndex &parent) const { | ||||
Q_UNUSED(parent); | Q_UNUSED(parent); | ||||
TransactionRecord *data = priv->index(row); | TransactionRecord *data = priv->index(walletModel->wallet(), row); | ||||
if (data) { | if (data) { | ||||
return createIndex(row, column, priv->index(row)); | return createIndex(row, column, | ||||
priv->index(walletModel->wallet(), row)); | |||||
} | } | ||||
return QModelIndex(); | return QModelIndex(); | ||||
} | } | ||||
void TransactionTableModel::updateDisplayUnit() { | void TransactionTableModel::updateDisplayUnit() { | ||||
// emit dataChanged to update Amount column with the current unit | // emit dataChanged to update Amount column with the current unit | ||||
updateAmountColumnTitle(); | updateAmountColumnTitle(); | ||||
Q_EMIT dataChanged(index(0, Amount), index(priv->size() - 1, Amount)); | Q_EMIT dataChanged(index(0, Amount), index(priv->size() - 1, Amount)); | ||||
Show All 22 Lines | private: | ||||
ChangeType status; | ChangeType status; | ||||
bool showTransaction; | bool showTransaction; | ||||
}; | }; | ||||
static bool fQueueNotifications = false; | static bool fQueueNotifications = false; | ||||
static std::vector<TransactionNotification> vQueueNotifications; | static std::vector<TransactionNotification> vQueueNotifications; | ||||
static void NotifyTransactionChanged(TransactionTableModel *ttm, | static void NotifyTransactionChanged(TransactionTableModel *ttm, | ||||
CWallet *wallet, const TxId &txid, | const TxId &txid, ChangeType status) { | ||||
ChangeType status) { | |||||
// Find transaction in wallet | // Find transaction in wallet | ||||
std::map<TxId, CWalletTx>::iterator mi = wallet->mapWallet.find(txid); | |||||
// Determine whether to show transaction or not (determine this here so that | // Determine whether to show transaction or not (determine this here so that | ||||
// no relocking is needed in GUI thread) | // no relocking is needed in GUI thread) | ||||
bool inWallet = mi != wallet->mapWallet.end(); | bool showTransaction = TransactionRecord::showTransaction(); | ||||
bool showTransaction = | |||||
(inWallet && TransactionRecord::showTransaction(mi->second)); | |||||
TransactionNotification notification(txid, status, showTransaction); | TransactionNotification notification(txid, status, showTransaction); | ||||
if (fQueueNotifications) { | if (fQueueNotifications) { | ||||
vQueueNotifications.push_back(notification); | vQueueNotifications.push_back(notification); | ||||
return; | return; | ||||
} | } | ||||
notification.invoke(ttm); | notification.invoke(ttm); | ||||
Show All 25 Lines | if (nProgress == 100) { | ||||
// clear | // clear | ||||
std::vector<TransactionNotification>().swap(vQueueNotifications); | std::vector<TransactionNotification>().swap(vQueueNotifications); | ||||
} | } | ||||
} | } | ||||
void TransactionTableModel::subscribeToCoreSignals() { | void TransactionTableModel::subscribeToCoreSignals() { | ||||
// Connect signals to wallet | // Connect signals to wallet | ||||
wallet->NotifyTransactionChanged.connect( | m_handler_transaction_changed = | ||||
boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); | walletModel->wallet().handleTransactionChanged( | ||||
wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2)); | boost::bind(NotifyTransactionChanged, this, _1, _2)); | ||||
m_handler_show_progress = walletModel->wallet().handleShowProgress( | |||||
boost::bind(ShowProgress, this, _1, _2)); | |||||
} | } | ||||
void TransactionTableModel::unsubscribeFromCoreSignals() { | void TransactionTableModel::unsubscribeFromCoreSignals() { | ||||
// Disconnect signals from wallet | // Disconnect signals from wallet | ||||
wallet->NotifyTransactionChanged.disconnect( | m_handler_transaction_changed->disconnect(); | ||||
boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); | m_handler_show_progress->disconnect(); | ||||
wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2)); | |||||
} | } |