aboutsummaryrefslogtreecommitdiff
path: root/gui
diff options
context:
space:
mode:
authorWladimir J. van der Laan <laanwj@gmail.com>2011-05-27 18:38:30 +0200
committerWladimir J. van der Laan <laanwj@gmail.com>2011-05-27 18:38:30 +0200
commit0856c1a03e4c663b09fa50237198c4af382dc18d (patch)
treef692ad3247b1df0c891e8637cd18f601eedecc84 /gui
parent213f7636301a3311cfcc47421910e326157501c2 (diff)
downloadbitcoin-0856c1a03e4c663b09fa50237198c4af382dc18d.tar.xz
work on transaction list model
Diffstat (limited to 'gui')
-rw-r--r--gui/include/guiutil.h8
-rw-r--r--gui/include/transactiontablemodel.h3
-rw-r--r--gui/src/guiutil.cpp9
-rw-r--r--gui/src/transactiontablemodel.cpp423
4 files changed, 366 insertions, 77 deletions
diff --git a/gui/include/guiutil.h b/gui/include/guiutil.h
new file mode 100644
index 0000000000..a73eadc02a
--- /dev/null
+++ b/gui/include/guiutil.h
@@ -0,0 +1,8 @@
+#ifndef GUIUTIL_H
+#define GUIUTIL_H
+
+#include <QString>
+
+QString DateTimeStr(qint64 nTime);
+
+#endif // GUIUTIL_H
diff --git a/gui/include/transactiontablemodel.h b/gui/include/transactiontablemodel.h
index 684a9470dd..2d56299574 100644
--- a/gui/include/transactiontablemodel.h
+++ b/gui/include/transactiontablemodel.h
@@ -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;
diff --git a/gui/src/guiutil.cpp b/gui/src/guiutil.cpp
new file mode 100644
index 0000000000..3a5b5ac344
--- /dev/null
+++ b/gui/src/guiutil.cpp
@@ -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");
+}
diff --git a/gui/src/transactiontablemodel.cpp b/gui/src/transactiontablemodel.cpp
index 454a10d93e..af907474fa 100644
--- a/gui/src/transactiontablemodel.cpp
+++ b/gui/src/transactiontablemodel.cpp
@@ -1,4 +1,5 @@
#include "transactiontablemodel.h"
+#include "guiutil.h"
#include "main.h"
#include <QLocale>
@@ -8,66 +9,319 @@
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;
-
- /* Properties that change based on changes in block chain that come in
- over the network.
- */
- bool confirmed;
- int depthInMainChain;
- bool final;
- int requestCount;
+ int64 credit;
+
+ /* Status: can change with block chain update */
+ TransactionStatus status;
+};
- TransactionRecord(const CWalletTx &tx)
+/* Return positive answer if transaction should be shown in list.
+ */
+bool showTransaction(const CWalletTx &wtx)
+{
+ if (wtx.IsCoinBase())
{
- /* 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);
+ // 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();
- update(tx);
+ 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;
+ }
}
- void update(const CWalletTx &tx)
+ if (showTransaction(wtx))
{
- confirmed = tx.IsConfirmed();
- depthInMainChain = tx.GetDepthInMainChain();
- final = tx.IsFinal();
- requestCount = tx.GetRequestCount();
+
+ 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());
- }
- 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);
+ 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;
}
-#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