dash/src/qt/transactiontablemodel.cpp

592 lines
17 KiB
C++
Raw Normal View History

2011-05-12 14:49:42 +02:00
#include "transactiontablemodel.h"
2011-05-27 18:38:30 +02:00
#include "guiutil.h"
#include "transactionrecord.h"
2011-05-28 20:32:19 +02:00
#include "guiconstants.h"
#include "transactiondesc.h"
2011-05-08 16:30:10 +02:00
#include "headers.h"
2011-05-10 19:03:10 +02:00
#include <QLocale>
2011-05-27 08:20:23 +02:00
#include <QDebug>
#include <QList>
#include <QColor>
2011-05-28 20:32:19 +02:00
#include <QTimer>
2011-06-15 20:07:21 +02:00
#include <QIcon>
#include <QDateTime>
2011-06-03 20:48:03 +02:00
#include <QtAlgorithms>
2011-05-10 19:03:10 +02:00
const QString TransactionTableModel::Sent = "s";
const QString TransactionTableModel::Received = "r";
2011-05-27 18:38:30 +02:00
const QString TransactionTableModel::Other = "o";
// Credit and Debit columns are right-aligned as they contain numbers
static int column_alignments[] = {
Qt::AlignLeft|Qt::AlignVCenter,
Qt::AlignLeft|Qt::AlignVCenter,
Qt::AlignLeft|Qt::AlignVCenter,
Qt::AlignLeft|Qt::AlignVCenter,
Qt::AlignRight|Qt::AlignVCenter
};
// Comparison operator for sort/binary search of model tx list
2011-06-03 20:48:03 +02:00
struct TxLessThan
{
bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
{
return a.hash < b.hash;
}
bool operator()(const TransactionRecord &a, const uint256 &b) const
{
return a.hash < b;
}
bool operator()(const uint256 &a, const TransactionRecord &b) const
{
return a < b.hash;
}
};
// Private implementation
2011-06-01 15:33:33 +02:00
struct TransactionTablePriv
2011-05-27 08:20:23 +02:00
{
TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
wallet(wallet),
2011-06-03 20:48:03 +02:00
parent(parent)
{
}
CWallet *wallet;
2011-06-03 20:48:03 +02:00
TransactionTableModel *parent;
2011-05-28 20:32:19 +02:00
/* Local cache of wallet.
* As it is in the same order as the CWallet, by definition
* this is sorted by sha256.
*/
2011-05-27 08:20:23 +02:00
QList<TransactionRecord> cachedWallet;
/* Query entire wallet anew from core.
*/
2011-05-28 20:32:19 +02:00
void refreshWallet()
2011-05-27 08:20:23 +02:00
{
2011-06-12 11:22:44 +02:00
#ifdef WALLET_UPDATE_DEBUG
2011-05-28 20:32:19 +02:00
qDebug() << "refreshWallet";
2011-06-12 11:22:44 +02:00
#endif
cachedWallet.clear();
CRITICAL_BLOCK(wallet->cs_mapWallet)
2011-05-27 08:20:23 +02:00
{
for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
2011-05-27 08:20:23 +02:00
{
cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
2011-05-27 08:20:23 +02:00
}
}
}
/* Update our model of the wallet incrementally, to synchronize our model of the wallet
with that of the core.
2011-05-28 20:32:19 +02:00
Call with list of hashes of transactions that were added, removed or changed.
*/
void updateWallet(const QList<uint256> &updated)
{
// Walk through updated transactions, update model as needed.
2011-06-12 11:22:44 +02:00
#ifdef WALLET_UPDATE_DEBUG
2011-05-28 20:32:19 +02:00
qDebug() << "updateWallet";
2011-06-12 11:22:44 +02:00
#endif
// Sort update list, and iterate through it in reverse, so that model updates
// can be emitted from end to beginning (so that earlier updates will not influence
// the indices of latter ones).
2011-06-03 20:48:03 +02:00
QList<uint256> updated_sorted = updated;
qSort(updated_sorted);
CRITICAL_BLOCK(wallet->cs_mapWallet)
2011-05-28 20:32:19 +02:00
{
2011-06-03 20:48:03 +02:00
for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
{
const uint256 &hash = updated_sorted.at(update_idx);
/* Find transaction in wallet */
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
bool inWallet = mi != wallet->mapWallet.end();
2011-06-03 20:48:03 +02:00
/* Find bounds of this transaction in model */
QList<TransactionRecord>::iterator lower = qLowerBound(
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
QList<TransactionRecord>::iterator upper = qUpperBound(
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
int lowerIndex = (lower - cachedWallet.begin());
int upperIndex = (upper - cachedWallet.begin());
// Determine if transaction is in model already
2011-06-03 20:48:03 +02:00
bool inModel = false;
if(lower != upper)
{
inModel = true;
}
2011-06-12 11:22:44 +02:00
#ifdef WALLET_UPDATE_DEBUG
2011-06-03 20:48:03 +02:00
qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
<< lowerIndex << "-" << upperIndex;
2011-06-12 11:22:44 +02:00
#endif
2011-06-03 20:48:03 +02:00
if(inWallet && !inModel)
{
// Added -- insert at the right position
2011-06-03 20:48:03 +02:00
QList<TransactionRecord> toInsert =
TransactionRecord::decomposeTransaction(wallet, mi->second);
2011-06-03 20:48:03 +02:00
if(!toInsert.isEmpty()) /* only if something to insert */
{
parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
int insert_idx = lowerIndex;
foreach(const TransactionRecord &rec, toInsert)
{
cachedWallet.insert(insert_idx, rec);
insert_idx += 1;
}
parent->endInsertRows();
}
2011-06-07 18:59:01 +02:00
}
else if(!inWallet && inModel)
2011-06-03 20:48:03 +02:00
{
// Removed -- remove entire transaction from table
2011-06-03 20:48:03 +02:00
parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
cachedWallet.erase(lower, upper);
parent->endRemoveRows();
2011-06-07 18:59:01 +02:00
}
else if(inWallet && inModel)
2011-06-03 20:48:03 +02:00
{
// Updated -- nothing to do, status update will take care of this
2011-06-03 20:48:03 +02:00
}
}
2011-05-28 20:32:19 +02:00
}
}
2011-05-27 08:20:23 +02:00
int size()
{
return cachedWallet.size();
}
TransactionRecord *index(int idx)
{
if(idx >= 0 && idx < cachedWallet.size())
{
TransactionRecord *rec = &cachedWallet[idx];
// If a status update is needed (blocks came in since last check),
// update the status of this transaction from the wallet. Otherwise,
// simply re-use the cached status.
if(rec->statusUpdateNeeded())
{
CRITICAL_BLOCK(wallet->cs_mapWallet)
{
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
if(mi != wallet->mapWallet.end())
{
rec->updateStatus(mi->second);
}
}
}
return rec;
2011-06-07 18:59:01 +02:00
}
else
{
2011-05-27 08:20:23 +02:00
return 0;
}
}
2011-05-28 20:32:19 +02:00
QString describe(TransactionRecord *rec)
{
CRITICAL_BLOCK(wallet->cs_mapWallet)
{
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
if(mi != wallet->mapWallet.end())
{
return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
}
}
return QString("");
}
2011-05-27 08:20:23 +02:00
};
TransactionTableModel::TransactionTableModel(CWallet* wallet, QObject *parent):
2011-05-27 08:20:23 +02:00
QAbstractTableModel(parent),
wallet(wallet),
priv(new TransactionTablePriv(wallet, this))
2011-05-08 16:30:10 +02:00
{
columns << tr("Status") << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
2011-05-27 08:20:23 +02:00
2011-06-01 15:33:33 +02:00
priv->refreshWallet();
2011-05-28 20:32:19 +02:00
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(MODEL_UPDATE_DELAY);
2011-05-27 08:20:23 +02:00
}
TransactionTableModel::~TransactionTableModel()
{
2011-06-01 15:33:33 +02:00
delete priv;
2011-05-08 16:30:10 +02:00
}
2011-05-28 20:32:19 +02:00
void TransactionTableModel::update()
{
2011-05-28 20:32:19 +02:00
QList<uint256> updated;
// Check if there are changes to wallet map
TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
2011-05-28 20:32:19 +02:00
{
if(!wallet->vWalletUpdated.empty())
2011-05-28 20:32:19 +02:00
{
BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
2011-05-28 20:32:19 +02:00
{
updated.append(hash);
}
wallet->vWalletUpdated.clear();
2011-05-28 20:32:19 +02:00
}
}
if(!updated.empty())
{
2011-06-01 15:33:33 +02:00
priv->updateWallet(updated);
// Status (number of confirmations) and (possibly) description
// columns changed for all rows.
emit dataChanged(index(0, Status), index(priv->size()-1, Status));
emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
2011-05-28 20:32:19 +02:00
}
}
2011-05-27 08:20:23 +02:00
2011-05-08 16:30:10 +02:00
int TransactionTableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
2011-06-01 15:33:33 +02:00
return priv->size();
2011-05-08 16:30:10 +02:00
}
int TransactionTableModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return columns.length();
}
2011-05-27 08:20:23 +02:00
QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
{
2011-05-27 18:38:30 +02:00
QString status;
2011-05-27 20:36:58 +02:00
2011-05-27 18:38:30 +02:00
switch(wtx->status.status)
2011-05-27 08:20:23 +02:00
{
2011-05-27 18:38:30 +02:00
case TransactionStatus::OpenUntilBlock:
status = tr("Open for %n block(s)","",wtx->status.open_for);
break;
case TransactionStatus::OpenUntilDate:
2011-06-24 21:45:33 +02:00
status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
2011-05-27 18:38:30 +02:00
break;
case TransactionStatus::Offline:
2011-06-13 09:05:48 +02:00
status = tr("Offline (%1)").arg(wtx->status.depth);
2011-05-27 18:38:30 +02:00
break;
case TransactionStatus::Unconfirmed:
status = tr("Unconfirmed (%1/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
2011-05-27 18:38:30 +02:00
break;
case TransactionStatus::HaveConfirmations:
2011-06-13 09:05:48 +02:00
status = tr("Confirmed (%1)").arg(wtx->status.depth);
2011-05-27 18:38:30 +02:00
break;
2011-05-27 08:20:23 +02:00
}
2011-05-27 18:38:30 +02:00
return QVariant(status);
2011-05-27 08:20:23 +02:00
}
QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
{
2011-05-27 18:38:30 +02:00
if(wtx->time)
{
return QVariant(GUIUtil::DateTimeStr(wtx->time));
2011-06-07 18:59:01 +02:00
}
else
{
2011-05-27 18:38:30 +02:00
return QVariant();
}
2011-05-27 08:20:23 +02:00
}
/* Look up label for address in address book, if not found return empty string.
This should really move to the wallet class.
2011-05-27 20:36:58 +02:00
*/
QString TransactionTableModel::labelForAddress(const std::string &address) const
2011-05-27 20:36:58 +02:00
{
CRITICAL_BLOCK(wallet->cs_mapAddressBook)
2011-05-27 20:36:58 +02:00
{
std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
if (mi != wallet->mapAddressBook.end())
2011-06-07 18:59:01 +02:00
{
return QString::fromStdString(mi->second);
2011-06-07 18:59:01 +02:00
}
2011-05-27 20:36:58 +02:00
}
return QString();
}
/* Look up address in address book, if found return
address[0:12]... (label)
otherwise just return address
*/
QString TransactionTableModel::lookupAddress(const std::string &address) const
{
QString label = labelForAddress(address);
QString description;
if(label.isEmpty())
{
description = QString::fromStdString(address);
}
else
{
description = QString::fromStdString(address.substr(0,12)) + QString("... (") + label + QString(")");
}
2011-05-27 20:36:58 +02:00
return description;
}
QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
2011-05-27 08:20:23 +02:00
{
QString description;
switch(wtx->type)
{
case TransactionRecord::RecvWithAddress:
description = tr("Received with");
break;
case TransactionRecord::RecvFromIP:
description = tr("Received from IP");
break;
case TransactionRecord::SendToAddress:
description = tr("Sent to");
break;
case TransactionRecord::SendToIP:
description = tr("Sent to IP");
break;
case TransactionRecord::SendToSelf:
description = tr("Payment to yourself");
break;
case TransactionRecord::Generated:
description = tr("Generated");
break;
}
return QVariant(description);
}
QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
{
QString description;
switch(wtx->type)
{
case TransactionRecord::RecvWithAddress:
description = lookupAddress(wtx->address);
break;
case TransactionRecord::RecvFromIP:
description = QString::fromStdString(wtx->address);
break;
case TransactionRecord::SendToAddress:
description = lookupAddress(wtx->address);
break;
case TransactionRecord::SendToIP:
description = QString::fromStdString(wtx->address);
break;
case TransactionRecord::SendToSelf:
description = QString();
break;
case TransactionRecord::Generated:
2011-05-27 22:06:30 +02:00
switch(wtx->status.maturity)
{
case TransactionStatus::Immature:
description = tr("(matures in %n more blocks)", "",
2011-05-27 22:06:30 +02:00
wtx->status.matures_in);
break;
case TransactionStatus::Mature:
description = QString();
2011-05-27 22:06:30 +02:00
break;
case TransactionStatus::MaturesWarning:
description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
2011-05-27 22:06:30 +02:00
break;
case TransactionStatus::NotAccepted:
description = tr("(not accepted)");
2011-05-27 22:06:30 +02:00
break;
}
break;
}
return QVariant(description);
2011-05-27 08:20:23 +02:00
}
QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
2011-05-27 08:20:23 +02:00
{
QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
2011-05-27 18:38:30 +02:00
{
str = QString("[") + str + QString("]");
2011-05-27 18:38:30 +02:00
}
return QVariant(str);
2011-05-27 08:20:23 +02:00
}
2011-06-13 09:05:48 +02:00
QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
{
switch(wtx->status.status)
{
case TransactionStatus::OpenUntilBlock:
case TransactionStatus::OpenUntilDate:
return QColor(64,64,255);
break;
case TransactionStatus::Offline:
return QColor(192,192,192);
case TransactionStatus::Unconfirmed:
2011-06-17 17:47:40 +02:00
switch(wtx->status.depth)
2011-06-13 09:05:48 +02:00
{
2011-06-17 17:47:40 +02:00
case 0: return QIcon(":/icons/transaction_0");
case 1: return QIcon(":/icons/transaction_1");
case 2: return QIcon(":/icons/transaction_2");
case 3: return QIcon(":/icons/transaction_3");
case 4: return QIcon(":/icons/transaction_4");
default: return QIcon(":/icons/transaction_5");
};
2011-06-13 09:05:48 +02:00
case TransactionStatus::HaveConfirmations:
2011-06-17 17:47:40 +02:00
return QIcon(":/icons/transaction_confirmed");
2011-06-13 09:05:48 +02:00
}
return QColor(0,0,0);
}
2011-05-08 16:30:10 +02:00
QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid())
return QVariant();
2011-05-27 08:20:23 +02:00
TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
2011-05-08 16:30:10 +02:00
2011-06-13 09:05:48 +02:00
if(role == Qt::DecorationRole)
{
if(index.column() == Status)
{
return formatTxDecoration(rec);
}
}
else if(role == Qt::DisplayRole)
2011-05-08 16:30:10 +02:00
{
// Delegate to specific column handlers
2011-05-27 08:20:23 +02:00
switch(index.column())
{
case Date:
return formatTxDate(rec);
case Type:
return formatTxType(rec);
case ToAddress:
return formatTxToAddress(rec);
case Amount:
return formatTxAmount(rec);
2011-05-27 08:20:23 +02:00
}
2011-06-07 18:59:01 +02:00
}
else if(role == Qt::EditRole)
2011-05-28 20:32:19 +02:00
{
// Edit role is used for sorting so return the real values
2011-05-28 20:32:19 +02:00
switch(index.column())
{
case Status:
return QString::fromStdString(rec->status.sortKey);
case Date:
return rec->time;
case Type:
return formatTxType(rec);
case ToAddress:
return formatTxToAddress(rec);
case Amount:
return rec->credit + rec->debit;
2011-05-28 20:32:19 +02:00
}
2011-06-07 18:59:01 +02:00
}
else if (role == Qt::ToolTipRole)
{
if(index.column() == Status)
{
return formatTxStatus(rec);
}
}
2011-06-07 18:59:01 +02:00
else if (role == Qt::TextAlignmentRole)
2011-05-08 22:23:31 +02:00
{
return column_alignments[index.column()];
2011-06-07 18:59:01 +02:00
}
else if (role == Qt::ForegroundRole)
{
2011-05-28 16:09:23 +02:00
/* Non-confirmed transactions are grey */
2011-06-17 22:44:15 +02:00
if(!rec->status.confirmed)
2011-06-07 18:59:01 +02:00
{
return QColor(128, 128, 128);
}
if(index.column() == Amount && (rec->credit+rec->debit) < 0)
{
return QColor(255, 0, 0);
}
2011-06-07 18:59:01 +02:00
}
else if (role == TypeRole)
2011-05-10 19:03:10 +02:00
{
return rec->type;
}
else if (role == DateRole)
{
return QDateTime::fromTime_t(static_cast<uint>(rec->time));
2011-05-08 16:30:10 +02:00
}
else if (role == LongDescriptionRole)
{
return priv->describe(rec);
}
else if (role == AddressRole)
{
return QString::fromStdString(rec->address);
}
else if (role == LabelRole)
{
return labelForAddress(rec->address);
}
else if (role == AbsoluteAmountRole)
{
return llabs(rec->credit + rec->debit);
}
2011-05-08 16:30:10 +02:00
return QVariant();
}
QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{
2011-05-28 16:09:23 +02:00
if(orientation == Qt::Horizontal)
2011-05-08 22:23:31 +02:00
{
2011-05-28 16:09:23 +02:00
if(role == Qt::DisplayRole)
2011-05-08 22:23:31 +02:00
{
return columns[section];
2011-06-07 18:59:01 +02:00
}
else if (role == Qt::TextAlignmentRole)
2011-05-28 16:09:23 +02:00
{
return column_alignments[section];
} else if (role == Qt::ToolTipRole)
{
switch(section)
{
case Status:
2011-06-24 21:45:33 +02:00
return tr("Transaction status. Hover over this field to show number of confirmations.");
case Date:
return tr("Date and time that the transaction was received.");
case Type:
return tr("Type of transaction.");
case ToAddress:
return tr("Destination address of transaction.");
case Amount:
return tr("Amount removed from or added to balance.");
}
2011-05-08 22:23:31 +02:00
}
2011-05-08 16:30:10 +02:00
}
return QVariant();
}
Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
{
return QAbstractTableModel::flags(index);
}
2011-05-27 08:20:23 +02:00
2011-06-01 15:33:33 +02:00
QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
2011-05-27 08:20:23 +02:00
{
Q_UNUSED(parent);
2011-06-01 15:33:33 +02:00
TransactionRecord *data = priv->index(row);
2011-05-27 08:20:23 +02:00
if(data)
{
2011-06-01 15:33:33 +02:00
return createIndex(row, column, priv->index(row));
2011-06-07 18:59:01 +02:00
}
else
{
2011-05-27 08:20:23 +02:00
return QModelIndex();
}
}