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 \
core/include/rpc.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 \
gui/src/transactiontablemodel.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_value.cpp \
json/src/json_spirit_reader.cpp \
gui/src/clientmodel.cpp
gui/src/clientmodel.cpp \
gui/src/guiutil.cpp
RESOURCES += \
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
} RoleIndex;
/* Transaction type */
/* TypeRole values */
static const QString Sent;
static const QString Received;
static const QString Generated;
static const QString Other;
int rowCount(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 "guiutil.h"
#include "main.h"
#include <QLocale>
@ -8,67 +9,320 @@
const QString TransactionTableModel::Sent = "s";
const QString TransactionTableModel::Received = "r";
const QString TransactionTableModel::Generated = "g";
const QString TransactionTableModel::Other = "o";
/* Separate transaction record format from core.
* When the GUI is going to communicate with the core through the network,
* we'll need our own internal formats anyway.
/* TODO: look up address in address book
when showing.
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
{
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;
int64 time;
int64 credit;
Type type;
std::string address;
int64 debit;
int64 change;
int64 lockTime;
int64 timeReceived;
bool isCoinBase;
int blockIndex;
int64 credit;
/* Properties that change based on changes in block chain that come in
over the network.
*/
bool confirmed;
int depthInMainChain;
bool final;
int requestCount;
TransactionRecord(const CWalletTx &tx)
{
/* Copy immutable properties.
*/
hash = tx.GetHash();
time = tx.GetTxTime();
credit = tx.GetCredit(true);
debit = tx.GetDebit();
change = tx.GetChange();
isCoinBase = tx.IsCoinBase();
lockTime = tx.nLockTime;
timeReceived = tx.nTimeReceived;
/* Find the block the tx is in, store the index
*/
CBlockIndex* pindex = NULL;
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(tx.hashBlock);
if (mi != mapBlockIndex.end())
pindex = (*mi).second;
blockIndex = (pindex ? pindex->nHeight : INT_MAX);
update(tx);
}
void update(const CWalletTx &tx)
{
confirmed = tx.IsConfirmed();
depthInMainChain = tx.GetDepthInMainChain();
final = tx.IsFinal();
requestCount = tx.GetRequestCount();
}
/* Status: can change with block chain update */
TransactionStatus status;
};
/* Return positive answer if transaction should be shown in list.
*/
bool showTransaction(const CWalletTx &wtx)
{
if (wtx.IsCoinBase())
{
// Don't show generated coin until confirmed by at least one block after it
// so we don't get the user's hopes up until it looks like it's probably accepted.
//
// It is not an error when generated blocks are not accepted. By design,
// some percentage of blocks, like 10% or more, will end up not accepted.
// This is the normal mechanism by which the network copes with latency.
//
// 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;
}
/* 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;
std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(wtx.hashBlock);
if (mi != mapBlockIndex.end())
pindex = (*mi).second;
// 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();
if (!wtx.IsFinal())
{
if (wtx.nLockTime < 500000000)
{
status.status = TransactionStatus::OpenUntilBlock;
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 */
class TransactionTableImpl
{
@ -93,7 +347,7 @@ public:
/* TODO: Make note of new and removed transactions */
/* insertedIndices */
/* removedIndices */
cachedWallet.append(TransactionRecord(it->second));
cachedWallet.append(decomposeTransaction(it->second));
}
}
/* beginInsertRows(QModelIndex(), first, last) */
@ -146,14 +400,6 @@ TransactionTableModel::~TransactionTableModel()
int TransactionTableModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
/*
int retval = 0;
CRITICAL_BLOCK(cs_mapWallet)
{
retval = mapWallet.size();
}
return retval;
*/
return impl->size();
}
@ -165,32 +411,37 @@ int TransactionTableModel::columnCount(const QModelIndex &parent) const
QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
{
return QVariant(QString("Test"));
#if 0
// Status
if (!wtx.IsFinal())
QString status;
switch(wtx->status.status)
{
if (wtx.nLockTime < 500000000)
return strprintf(_("Open for %d blocks"), nBestHeight - wtx.nLockTime);
else
return strprintf(_("Open until %s"), DateTimeStr(wtx.nLockTime).c_str());
case TransactionStatus::OpenUntilBlock:
status = tr("Open for %n block(s)","",wtx->status.open_for);
break;
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
{
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
return QVariant(status);
}
QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
{
return QVariant();
if(wtx->time)
{
return QVariant(DateTimeStr(wtx->time));
} else {
return QVariant();
}
}
QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
@ -200,12 +451,32 @@ QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx
QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
{
return QVariant();
if(wtx->debit)
{
QString str = QString::fromStdString(FormatMoney(wtx->debit));
if(!wtx->status.confirmed)
{
str = QString("[") + str + QString("]");
}
return QVariant(str);
} else {
return QVariant();
}
}
QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
{
return QVariant();
if(wtx->credit)
{
QString str = QString::fromStdString(FormatMoney(wtx->credit));
if(!wtx->status.confirmed)
{
str = QString("[") + str + QString("]");
}
return QVariant(str);
} else {
return QVariant();
}
}
QVariant TransactionTableModel::data(const QModelIndex &index, int role) const