work on transaction list model

This commit is contained in:
Wladimir J. van der Laan 2011-05-27 18:38:30 +02:00
parent 213f763630
commit 0856c1a03e
5 changed files with 375 additions and 84 deletions

View file

@ -54,7 +54,8 @@ HEADERS += gui/include/bitcoingui.h \
json/include/json/json_spirit.h \ json/include/json/json_spirit.h \
core/include/rpc.h \ core/include/rpc.h \
gui/src/clientmodel.h \ gui/src/clientmodel.h \
gui/include/clientmodel.h gui/include/clientmodel.h \
gui/include/guiutil.h
SOURCES += gui/src/bitcoin.cpp gui/src/bitcoingui.cpp \ SOURCES += gui/src/bitcoin.cpp gui/src/bitcoingui.cpp \
gui/src/transactiontablemodel.cpp \ gui/src/transactiontablemodel.cpp \
gui/src/addresstablemodel.cpp \ gui/src/addresstablemodel.cpp \
@ -78,7 +79,8 @@ SOURCES += gui/src/bitcoin.cpp gui/src/bitcoingui.cpp \
json/src/json_spirit_writer.cpp \ json/src/json_spirit_writer.cpp \
json/src/json_spirit_value.cpp \ json/src/json_spirit_value.cpp \
json/src/json_spirit_reader.cpp \ json/src/json_spirit_reader.cpp \
gui/src/clientmodel.cpp gui/src/clientmodel.cpp \
gui/src/guiutil.cpp
RESOURCES += \ RESOURCES += \
gui/bitcoin.qrc gui/bitcoin.qrc

8
gui/include/guiutil.h Normal file
View file

@ -0,0 +1,8 @@
#ifndef GUIUTIL_H
#define GUIUTIL_H
#include <QString>
QString DateTimeStr(qint64 nTime);
#endif // GUIUTIL_H

View file

@ -26,10 +26,11 @@ public:
TypeRole = Qt::UserRole TypeRole = Qt::UserRole
} RoleIndex; } RoleIndex;
/* Transaction type */ /* TypeRole values */
static const QString Sent; static const QString Sent;
static const QString Received; static const QString Received;
static const QString Generated; static const QString Generated;
static const QString Other;
int rowCount(const QModelIndex &parent) const; int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const;

9
gui/src/guiutil.cpp Normal file
View file

@ -0,0 +1,9 @@
#include "guiutil.h"
#include <QDateTime>
QString DateTimeStr(qint64 nTime)
{
QDateTime date = QDateTime::fromMSecsSinceEpoch(nTime*1000);
return date.toString(Qt::DefaultLocaleShortDate) + QString(" ") + date.toString("hh:mm");
}

View file

@ -1,4 +1,5 @@
#include "transactiontablemodel.h" #include "transactiontablemodel.h"
#include "guiutil.h"
#include "main.h" #include "main.h"
#include <QLocale> #include <QLocale>
@ -8,66 +9,319 @@
const QString TransactionTableModel::Sent = "s"; const QString TransactionTableModel::Sent = "s";
const QString TransactionTableModel::Received = "r"; const QString TransactionTableModel::Received = "r";
const QString TransactionTableModel::Generated = "g"; const QString TransactionTableModel::Generated = "g";
const QString TransactionTableModel::Other = "o";
/* Separate transaction record format from core. /* TODO: look up address in address book
* When the GUI is going to communicate with the core through the network, when showing.
* we'll need our own internal formats anyway. Color based on confirmation status.
(fConfirmed ? wxColour(0,0,0) : wxColour(128,128,128))
*/ */
class TransactionStatus
{
public:
TransactionStatus():
confirmed(false), sortKey(""), maturity(Mature),
matures_in(0), status(Offline), depth(0), open_for(0)
{ }
enum Maturity
{
Immature,
Mature,
MaturesIn,
MaturesWarning, /* Will probably not mature because no nodes have confirmed */
NotAccepted
};
enum Status {
OpenUntilDate,
OpenUntilBlock,
Offline,
Unconfirmed,
HaveConfirmations
};
bool confirmed;
std::string sortKey;
/* For "Generated" transactions */
Maturity maturity;
int matures_in;
/* Reported status */
Status status;
int64 depth;
int64 open_for; /* Timestamp if status==OpenUntilDate, otherwise number of blocks */
};
class TransactionRecord class TransactionRecord
{ {
public: public:
/* Information that never changes for the life of the transaction enum Type
*/ {
Other,
Generated,
SendToAddress,
SendToIP,
RecvFromAddress,
RecvFromIP,
SendToSelf
};
TransactionRecord():
hash(), time(0), type(Other), address(""), debit(0), credit(0)
{
}
TransactionRecord(uint256 hash, int64 time, const TransactionStatus &status):
hash(hash), time(time), type(Other), address(""), debit(0),
credit(0), status(status)
{
}
TransactionRecord(uint256 hash, int64 time, const TransactionStatus &status,
Type type, const std::string &address,
int64 debit, int64 credit):
hash(hash), time(time), type(type), address(address), debit(debit), credit(credit),
status(status)
{
}
/* Fixed */
uint256 hash; uint256 hash;
int64 time; int64 time;
int64 credit; Type type;
std::string address;
int64 debit; int64 debit;
int64 change; int64 credit;
int64 lockTime;
int64 timeReceived;
bool isCoinBase;
int blockIndex;
/* Properties that change based on changes in block chain that come in /* Status: can change with block chain update */
over the network. TransactionStatus status;
};
/* Return positive answer if transaction should be shown in list.
*/ */
bool confirmed; bool showTransaction(const CWalletTx &wtx)
int depthInMainChain;
bool final;
int requestCount;
TransactionRecord(const CWalletTx &tx)
{ {
/* Copy immutable properties. if (wtx.IsCoinBase())
*/ {
hash = tx.GetHash(); // Don't show generated coin until confirmed by at least one block after it
time = tx.GetTxTime(); // so we don't get the user's hopes up until it looks like it's probably accepted.
credit = tx.GetCredit(true); //
debit = tx.GetDebit(); // It is not an error when generated blocks are not accepted. By design,
change = tx.GetChange(); // some percentage of blocks, like 10% or more, will end up not accepted.
isCoinBase = tx.IsCoinBase(); // This is the normal mechanism by which the network copes with latency.
lockTime = tx.nLockTime; //
timeReceived = tx.nTimeReceived; // We display regular transactions right away before any confirmation
// because they can always get into some block eventually. Generated coins
// are special because if their block is not accepted, they are not valid.
//
if (wtx.GetDepthInMainChain() < 2)
{
return false;
}
}
return true;
}
/* Find the block the tx is in, store the index /* Decompose CWallet transaction to model transaction records.
*/ */
QList<TransactionRecord> decomposeTransaction(const CWalletTx &wtx)
{
QList<TransactionRecord> parts;
int64 nTime = wtx.nTimeDisplayed = wtx.GetTxTime();
int64 nCredit = wtx.GetCredit(true);
int64 nDebit = wtx.GetDebit();
int64 nNet = nCredit - nDebit;
uint256 hash = wtx.GetHash();
std::map<std::string, std::string> mapValue = wtx.mapValue;
// Find the block the tx is in
CBlockIndex* pindex = NULL; CBlockIndex* pindex = NULL;
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(tx.hashBlock); std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock);
if (mi != mapBlockIndex.end()) if (mi != mapBlockIndex.end())
pindex = (*mi).second; pindex = (*mi).second;
blockIndex = (pindex ? pindex->nHeight : INT_MAX);
update(tx); // Determine transaction status
} TransactionStatus status;
// Sort order, unrecorded transactions sort to the top
status.sortKey = strprintf("%010d-%01d-%010u",
(pindex ? pindex->nHeight : INT_MAX),
(wtx.IsCoinBase() ? 1 : 0),
wtx.nTimeReceived);
status.confirmed = wtx.IsConfirmed();
status.depth = wtx.GetDepthInMainChain();
void update(const CWalletTx &tx) if (!wtx.IsFinal())
{ {
confirmed = tx.IsConfirmed(); if (wtx.nLockTime < 500000000)
depthInMainChain = tx.GetDepthInMainChain(); {
final = tx.IsFinal(); status.status = TransactionStatus::OpenUntilBlock;
requestCount = tx.GetRequestCount(); status.open_for = nBestHeight - wtx.nLockTime;
} else {
status.status = TransactionStatus::OpenUntilDate;
status.open_for = wtx.nLockTime;
}
}
else
{
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
{
status.status = TransactionStatus::Offline;
} else if (status.depth < 6)
{
status.status = TransactionStatus::Unconfirmed;
} else
{
status.status = TransactionStatus::HaveConfirmations;
}
}
if (showTransaction(wtx))
{
if (nNet > 0 || wtx.IsCoinBase())
{
//
// Credit
//
TransactionRecord sub(hash, nTime, status);
sub.credit = nNet;
if (wtx.IsCoinBase())
{
// Generated
sub.type = TransactionRecord::Generated;
if (nCredit == 0)
{
sub.status.maturity = TransactionStatus::Immature;
int64 nUnmatured = 0;
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
nUnmatured += txout.GetCredit();
sub.credit = nUnmatured;
if (wtx.IsInMainChain())
{
sub.status.maturity = TransactionStatus::MaturesIn;
sub.status.matures_in = wtx.GetBlocksToMaturity();
// Check if the block was requested by anyone
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
sub.status.maturity = TransactionStatus::MaturesWarning;
}
else
{
sub.status.maturity = TransactionStatus::NotAccepted;
}
}
}
else if (!mapValue["from"].empty() || !mapValue["message"].empty())
{
// Received by IP connection
sub.type = TransactionRecord::RecvFromIP;
if (!mapValue["from"].empty())
sub.address = mapValue["from"];
}
else
{
// Received by Bitcoin Address
sub.type = TransactionRecord::RecvFromAddress;
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
{
if (txout.IsMine())
{
std::vector<unsigned char> vchPubKey;
if (ExtractPubKey(txout.scriptPubKey, true, vchPubKey))
{
sub.address = PubKeyToAddress(vchPubKey);
}
break;
}
}
}
parts.append(sub);
}
else
{
bool fAllFromMe = true;
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
fAllFromMe = fAllFromMe && txin.IsMine();
bool fAllToMe = true;
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
fAllToMe = fAllToMe && txout.IsMine();
if (fAllFromMe && fAllToMe)
{
// Payment to self
int64 nChange = wtx.GetChange();
parts.append(TransactionRecord(hash, nTime, status, TransactionRecord::SendToSelf, "",
-(nDebit - nChange), nCredit - nChange));
}
else if (fAllFromMe)
{
//
// Debit
//
int64 nTxFee = nDebit - wtx.GetValueOut();
for (int nOut = 0; nOut < wtx.vout.size(); nOut++)
{
const CTxOut& txout = wtx.vout[nOut];
TransactionRecord sub(hash, nTime, status);
if (txout.IsMine())
{
// Sent to self
sub.type = TransactionRecord::SendToSelf;
sub.credit = txout.nValue;
} else if (!mapValue["to"].empty())
{
// Sent to IP
sub.type = TransactionRecord::SendToIP;
sub.address = mapValue["to"];
} else {
// Sent to Bitcoin Address
sub.type = TransactionRecord::SendToAddress;
uint160 hash160;
if (ExtractHash160(txout.scriptPubKey, hash160))
sub.address = Hash160ToAddress(hash160);
}
int64 nValue = txout.nValue;
/* Add fee to first output */
if (nTxFee > 0)
{
nValue += nTxFee;
nTxFee = 0;
}
sub.debit = nValue;
sub.status.sortKey += strprintf("-%d", nOut);
parts.append(sub);
}
} else {
//
// Mixed debit transaction, can't break down payees
//
bool fAllMine = true;
BOOST_FOREACH(const CTxOut& txout, wtx.vout)
fAllMine = fAllMine && txout.IsMine();
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
fAllMine = fAllMine && txin.IsMine();
parts.append(TransactionRecord(hash, nTime, status, TransactionRecord::Other, "", nNet, 0));
}
}
}
return parts;
} }
};
/* Internal implementation */ /* Internal implementation */
class TransactionTableImpl class TransactionTableImpl
@ -93,7 +347,7 @@ public:
/* TODO: Make note of new and removed transactions */ /* TODO: Make note of new and removed transactions */
/* insertedIndices */ /* insertedIndices */
/* removedIndices */ /* removedIndices */
cachedWallet.append(TransactionRecord(it->second)); cachedWallet.append(decomposeTransaction(it->second));
} }
} }
/* beginInsertRows(QModelIndex(), first, last) */ /* beginInsertRows(QModelIndex(), first, last) */
@ -146,14 +400,6 @@ TransactionTableModel::~TransactionTableModel()
int TransactionTableModel::rowCount(const QModelIndex &parent) const int TransactionTableModel::rowCount(const QModelIndex &parent) const
{ {
Q_UNUSED(parent); Q_UNUSED(parent);
/*
int retval = 0;
CRITICAL_BLOCK(cs_mapWallet)
{
retval = mapWallet.size();
}
return retval;
*/
return impl->size(); return impl->size();
} }
@ -165,33 +411,38 @@ int TransactionTableModel::columnCount(const QModelIndex &parent) const
QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
{ {
return QVariant(QString("Test")); QString status;
#if 0 switch(wtx->status.status)
// Status
if (!wtx.IsFinal())
{ {
if (wtx.nLockTime < 500000000) case TransactionStatus::OpenUntilBlock:
return strprintf(_("Open for %d blocks"), nBestHeight - wtx.nLockTime); status = tr("Open for %n block(s)","",wtx->status.open_for);
else break;
return strprintf(_("Open until %s"), DateTimeStr(wtx.nLockTime).c_str()); case TransactionStatus::OpenUntilDate:
status = tr("Open until ") + DateTimeStr(wtx->status.open_for);
break;
case TransactionStatus::Offline:
status = tr("%1/offline").arg(wtx->status.depth);
break;
case TransactionStatus::Unconfirmed:
status = tr("%1/unconfirmed").arg(wtx->status.depth);
break;
case TransactionStatus::HaveConfirmations:
status = tr("%1 confirmations").arg(wtx->status.depth);
break;
} }
else
{ return QVariant(status);
int nDepth = wtx.GetDepthInMainChain();
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
return strprintf(_("%d/offline?"), nDepth);
else if (nDepth < 6)
return strprintf(_("%d/unconfirmed"), nDepth);
else
return strprintf(_("%d confirmations"), nDepth);
}
#endif
} }
QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
{ {
if(wtx->time)
{
return QVariant(DateTimeStr(wtx->time));
} else {
return QVariant(); return QVariant();
} }
}
QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
{ {
@ -200,13 +451,33 @@ QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx
QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
{ {
if(wtx->debit)
{
QString str = QString::fromStdString(FormatMoney(wtx->debit));
if(!wtx->status.confirmed)
{
str = QString("[") + str + QString("]");
}
return QVariant(str);
} else {
return QVariant(); return QVariant();
} }
}
QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
{ {
if(wtx->credit)
{
QString str = QString::fromStdString(FormatMoney(wtx->credit));
if(!wtx->status.confirmed)
{
str = QString("[") + str + QString("]");
}
return QVariant(str);
} else {
return QVariant(); return QVariant();
} }
}
QVariant TransactionTableModel::data(const QModelIndex &index, int role) const QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
{ {