1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
6 #include "walletmodel.h"
7 #include "addresstablemodel.h"
8 #include "bitcoinunits.h"
19 #include <QtAlgorithms>
21 // Credit and Debit columns are right-aligned as they contain numbers
22 static int column_alignments[] = {
23 Qt::AlignLeft|Qt::AlignVCenter,
24 Qt::AlignLeft|Qt::AlignVCenter,
25 Qt::AlignLeft|Qt::AlignVCenter,
26 Qt::AlignLeft|Qt::AlignVCenter,
27 Qt::AlignRight|Qt::AlignVCenter
30 // Comparison operator for sort/binary search of model tx list
33 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
35 return a.hash < b.hash;
37 bool operator()(const TransactionRecord &a, const uint256 &b) const
41 bool operator()(const uint256 &a, const TransactionRecord &b) const
47 // Private implementation
48 struct TransactionTablePriv
50 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
56 TransactionTableModel *parent;
58 /* Local cache of wallet.
59 * As it is in the same order as the CWallet, by definition
60 * this is sorted by sha256.
62 QList<TransactionRecord> cachedWallet;
64 /* Query entire wallet anew from core.
68 #ifdef WALLET_UPDATE_DEBUG
69 qDebug() << "refreshWallet";
72 CRITICAL_BLOCK(wallet->cs_mapWallet)
74 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
76 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
81 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
82 with that of the core.
84 Call with list of hashes of transactions that were added, removed or changed.
86 void updateWallet(const QList<uint256> &updated)
88 // Walk through updated transactions, update model as needed.
89 #ifdef WALLET_UPDATE_DEBUG
90 qDebug() << "updateWallet";
92 // Sort update list, and iterate through it in reverse, so that model updates
93 // can be emitted from end to beginning (so that earlier updates will not influence
94 // the indices of latter ones).
95 QList<uint256> updated_sorted = updated;
96 qSort(updated_sorted);
98 CRITICAL_BLOCK(wallet->cs_mapWallet)
100 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
102 const uint256 &hash = updated_sorted.at(update_idx);
103 /* Find transaction in wallet */
104 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
105 bool inWallet = mi != wallet->mapWallet.end();
106 /* Find bounds of this transaction in model */
107 QList<TransactionRecord>::iterator lower = qLowerBound(
108 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
109 QList<TransactionRecord>::iterator upper = qUpperBound(
110 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
111 int lowerIndex = (lower - cachedWallet.begin());
112 int upperIndex = (upper - cachedWallet.begin());
114 // Determine if transaction is in model already
115 bool inModel = false;
121 #ifdef WALLET_UPDATE_DEBUG
122 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
123 << lowerIndex << "-" << upperIndex;
126 if(inWallet && !inModel)
128 // Added -- insert at the right position
129 QList<TransactionRecord> toInsert =
130 TransactionRecord::decomposeTransaction(wallet, mi->second);
131 if(!toInsert.isEmpty()) /* only if something to insert */
133 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
134 int insert_idx = lowerIndex;
135 foreach(const TransactionRecord &rec, toInsert)
137 cachedWallet.insert(insert_idx, rec);
140 parent->endInsertRows();
143 else if(!inWallet && inModel)
145 // Removed -- remove entire transaction from table
146 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
147 cachedWallet.erase(lower, upper);
148 parent->endRemoveRows();
150 else if(inWallet && inModel)
152 // Updated -- nothing to do, status update will take care of this
160 return cachedWallet.size();
163 TransactionRecord *index(int idx)
165 if(idx >= 0 && idx < cachedWallet.size())
167 TransactionRecord *rec = &cachedWallet[idx];
169 // If a status update is needed (blocks came in since last check),
170 // update the status of this transaction from the wallet. Otherwise,
171 // simply re-use the cached status.
172 if(rec->statusUpdateNeeded())
174 CRITICAL_BLOCK(wallet->cs_mapWallet)
176 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
178 if(mi != wallet->mapWallet.end())
180 rec->updateStatus(mi->second);
192 QString describe(TransactionRecord *rec)
194 CRITICAL_BLOCK(wallet->cs_mapWallet)
196 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
197 if(mi != wallet->mapWallet.end())
199 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
207 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
208 QAbstractTableModel(parent),
211 priv(new TransactionTablePriv(wallet, this))
213 columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
215 priv->refreshWallet();
217 QTimer *timer = new QTimer(this);
218 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
219 timer->start(MODEL_UPDATE_DELAY);
222 TransactionTableModel::~TransactionTableModel()
227 void TransactionTableModel::update()
229 QList<uint256> updated;
231 // Check if there are changes to wallet map
232 TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
234 if(!wallet->vWalletUpdated.empty())
236 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
238 updated.append(hash);
240 wallet->vWalletUpdated.clear();
246 priv->updateWallet(updated);
248 // Status (number of confirmations) and (possibly) description
249 // columns changed for all rows.
250 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
251 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
255 int TransactionTableModel::rowCount(const QModelIndex &parent) const
261 int TransactionTableModel::columnCount(const QModelIndex &parent) const
264 return columns.length();
267 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
271 switch(wtx->status.status)
273 case TransactionStatus::OpenUntilBlock:
274 status = tr("Open for %n block(s)","",wtx->status.open_for);
276 case TransactionStatus::OpenUntilDate:
277 status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
279 case TransactionStatus::Offline:
280 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
282 case TransactionStatus::Unconfirmed:
283 status = tr("Unconfirmed (%1 of %2 confirmations required)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
285 case TransactionStatus::HaveConfirmations:
286 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
289 if(wtx->type == TransactionRecord::Generated)
292 switch(wtx->status.maturity)
294 case TransactionStatus::Immature:
295 status += tr("Mined balance will be available in %n more blocks", "",
296 wtx->status.matures_in);
298 case TransactionStatus::Mature:
300 case TransactionStatus::MaturesWarning:
301 status += tr("This block was not received by any other nodes and will probably not be accepted!");
303 case TransactionStatus::NotAccepted:
304 status += tr("Generated but not accepted");
309 return QVariant(status);
312 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
316 return QVariant(GUIUtil::DateTimeStr(wtx->time));
324 /* Look up address in address book, if found return
326 otherwise just return address
328 QString TransactionTableModel::lookupAddress(const std::string &address) const
330 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
334 description = QString::fromStdString(address);
338 description = label + QString(" (") + QString::fromStdString(address) + QString(")");
343 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
349 case TransactionRecord::RecvWithAddress:
350 description = tr("Received with");
352 case TransactionRecord::RecvFromIP:
353 description = tr("Received from IP");
355 case TransactionRecord::SendToAddress:
356 description = tr("Sent to");
358 case TransactionRecord::SendToIP:
359 description = tr("Sent to IP");
361 case TransactionRecord::SendToSelf:
362 description = tr("Payment to yourself");
364 case TransactionRecord::Generated:
365 description = tr("Mined");
368 return QVariant(description);
371 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
377 case TransactionRecord::RecvWithAddress:
378 description = lookupAddress(wtx->address);
380 case TransactionRecord::RecvFromIP:
381 description = QString::fromStdString(wtx->address);
383 case TransactionRecord::SendToAddress:
384 description = lookupAddress(wtx->address);
386 case TransactionRecord::SendToIP:
387 description = QString::fromStdString(wtx->address);
389 case TransactionRecord::SendToSelf:
390 description = QString();
392 case TransactionRecord::Generated:
393 description = QString();
396 return QVariant(description);
399 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
401 QString str = BitcoinUnits::format(BitcoinUnits::BTC, wtx->credit + wtx->debit);
404 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
406 str = QString("[") + str + QString("]");
409 return QVariant(str);
412 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
414 if(wtx->type == TransactionRecord::Generated)
416 switch(wtx->status.maturity)
418 case TransactionStatus::Immature: {
419 int total = wtx->status.depth + wtx->status.matures_in;
420 int part = (wtx->status.depth * 4 / total) + 1;
421 return QIcon(QString(":/icons/transaction_%1").arg(part));
423 case TransactionStatus::Mature:
424 return QIcon(":/icons/transaction_confirmed");
425 case TransactionStatus::MaturesWarning:
426 case TransactionStatus::NotAccepted:
427 return QIcon(":/icons/transaction_0");
432 switch(wtx->status.status)
434 case TransactionStatus::OpenUntilBlock:
435 case TransactionStatus::OpenUntilDate:
436 return QColor(64,64,255);
438 case TransactionStatus::Offline:
439 return QColor(192,192,192);
440 case TransactionStatus::Unconfirmed:
441 switch(wtx->status.depth)
443 case 0: return QIcon(":/icons/transaction_0");
444 case 1: return QIcon(":/icons/transaction_1");
445 case 2: return QIcon(":/icons/transaction_2");
446 case 3: return QIcon(":/icons/transaction_3");
447 case 4: return QIcon(":/icons/transaction_4");
448 default: return QIcon(":/icons/transaction_5");
450 case TransactionStatus::HaveConfirmations:
451 return QIcon(":/icons/transaction_confirmed");
454 return QColor(0,0,0);
457 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
461 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
463 if(role == Qt::DecorationRole)
465 if(index.column() == Status)
467 return formatTxDecoration(rec);
470 else if(role == Qt::DisplayRole)
472 // Delegate to specific column handlers
473 switch(index.column())
476 return formatTxDate(rec);
478 return formatTxType(rec);
480 return formatTxToAddress(rec);
482 return formatTxAmount(rec);
485 else if(role == Qt::EditRole)
487 // Edit role is used for sorting so return the real values
488 switch(index.column())
491 return QString::fromStdString(rec->status.sortKey);
495 return formatTxType(rec);
497 return formatTxToAddress(rec);
499 return rec->credit + rec->debit;
502 else if (role == Qt::ToolTipRole)
504 if(index.column() == Status)
506 return formatTxStatus(rec);
509 else if (role == Qt::TextAlignmentRole)
511 return column_alignments[index.column()];
513 else if (role == Qt::ForegroundRole)
515 /* Non-confirmed transactions are grey */
516 if(!rec->status.confirmed)
518 return COLOR_UNCONFIRMED;
520 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
522 return COLOR_NEGATIVE;
525 else if (role == TypeRole)
529 else if (role == DateRole)
531 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
533 else if (role == LongDescriptionRole)
535 return priv->describe(rec);
537 else if (role == AddressRole)
539 return QString::fromStdString(rec->address);
541 else if (role == LabelRole)
543 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
545 else if (role == AbsoluteAmountRole)
547 return llabs(rec->credit + rec->debit);
549 else if (role == TxIDRole)
551 return QString::fromStdString(rec->getTxID());
553 else if (role == ConfirmedRole)
555 return rec->status.status == TransactionStatus::HaveConfirmations;
557 else if (role == FormattedAmountRole)
559 return formatTxAmount(rec, false);
564 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
566 if(orientation == Qt::Horizontal)
568 if(role == Qt::DisplayRole)
570 return columns[section];
572 else if (role == Qt::TextAlignmentRole)
574 return column_alignments[section];
575 } else if (role == Qt::ToolTipRole)
580 return tr("Transaction status. Hover over this field to show number of confirmations.");
582 return tr("Date and time that the transaction was received.");
584 return tr("Type of transaction.");
586 return tr("Destination address of transaction.");
588 return tr("Amount removed from or added to balance.");
595 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
597 return QAbstractTableModel::flags(index);
600 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
603 TransactionRecord *data = priv->index(row);
606 return createIndex(row, column, priv->index(row));
610 return QModelIndex();