1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
6 #include "walletmodel.h"
7 #include "optionsmodel.h"
8 #include "addresstablemodel.h"
9 #include "bitcoinunits.h"
20 #include <QtAlgorithms>
22 // Credit and Debit columns are right-aligned as they contain numbers
23 static int column_alignments[] = {
24 Qt::AlignLeft|Qt::AlignVCenter,
25 Qt::AlignLeft|Qt::AlignVCenter,
26 Qt::AlignLeft|Qt::AlignVCenter,
27 Qt::AlignLeft|Qt::AlignVCenter,
28 Qt::AlignRight|Qt::AlignVCenter
31 // Comparison operator for sort/binary search of model tx list
34 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
36 return a.hash < b.hash;
38 bool operator()(const TransactionRecord &a, const uint256 &b) const
42 bool operator()(const uint256 &a, const TransactionRecord &b) const
48 // Private implementation
49 struct TransactionTablePriv
51 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
57 TransactionTableModel *parent;
59 /* Local cache of wallet.
60 * As it is in the same order as the CWallet, by definition
61 * this is sorted by sha256.
63 QList<TransactionRecord> cachedWallet;
65 /* Query entire wallet anew from core.
69 #ifdef WALLET_UPDATE_DEBUG
70 qDebug() << "refreshWallet";
73 CRITICAL_BLOCK(wallet->cs_mapWallet)
75 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
77 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
82 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
83 with that of the core.
85 Call with list of hashes of transactions that were added, removed or changed.
87 void updateWallet(const QList<uint256> &updated)
89 // Walk through updated transactions, update model as needed.
90 #ifdef WALLET_UPDATE_DEBUG
91 qDebug() << "updateWallet";
93 // Sort update list, and iterate through it in reverse, so that model updates
94 // can be emitted from end to beginning (so that earlier updates will not influence
95 // the indices of latter ones).
96 QList<uint256> updated_sorted = updated;
97 qSort(updated_sorted);
99 CRITICAL_BLOCK(wallet->cs_mapWallet)
101 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
103 const uint256 &hash = updated_sorted.at(update_idx);
104 /* Find transaction in wallet */
105 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
106 bool inWallet = mi != wallet->mapWallet.end();
107 /* Find bounds of this transaction in model */
108 QList<TransactionRecord>::iterator lower = qLowerBound(
109 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
110 QList<TransactionRecord>::iterator upper = qUpperBound(
111 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
112 int lowerIndex = (lower - cachedWallet.begin());
113 int upperIndex = (upper - cachedWallet.begin());
115 // Determine if transaction is in model already
116 bool inModel = false;
122 #ifdef WALLET_UPDATE_DEBUG
123 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
124 << lowerIndex << "-" << upperIndex;
127 if(inWallet && !inModel)
129 // Added -- insert at the right position
130 QList<TransactionRecord> toInsert =
131 TransactionRecord::decomposeTransaction(wallet, mi->second);
132 if(!toInsert.isEmpty()) /* only if something to insert */
134 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
135 int insert_idx = lowerIndex;
136 foreach(const TransactionRecord &rec, toInsert)
138 cachedWallet.insert(insert_idx, rec);
141 parent->endInsertRows();
144 else if(!inWallet && inModel)
146 // Removed -- remove entire transaction from table
147 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
148 cachedWallet.erase(lower, upper);
149 parent->endRemoveRows();
151 else if(inWallet && inModel)
153 // Updated -- nothing to do, status update will take care of this
161 return cachedWallet.size();
164 TransactionRecord *index(int idx)
166 if(idx >= 0 && idx < cachedWallet.size())
168 TransactionRecord *rec = &cachedWallet[idx];
170 // If a status update is needed (blocks came in since last check),
171 // update the status of this transaction from the wallet. Otherwise,
172 // simply re-use the cached status.
173 if(rec->statusUpdateNeeded())
175 CRITICAL_BLOCK(wallet->cs_mapWallet)
177 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
179 if(mi != wallet->mapWallet.end())
181 rec->updateStatus(mi->second);
193 QString describe(TransactionRecord *rec)
195 CRITICAL_BLOCK(wallet->cs_mapWallet)
197 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
198 if(mi != wallet->mapWallet.end())
200 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
208 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
209 QAbstractTableModel(parent),
212 priv(new TransactionTablePriv(wallet, this))
214 columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
216 priv->refreshWallet();
218 QTimer *timer = new QTimer(this);
219 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
220 timer->start(MODEL_UPDATE_DELAY);
223 TransactionTableModel::~TransactionTableModel()
228 void TransactionTableModel::update()
230 QList<uint256> updated;
232 // Check if there are changes to wallet map
233 TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
235 if(!wallet->vWalletUpdated.empty())
237 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
239 updated.append(hash);
241 wallet->vWalletUpdated.clear();
247 priv->updateWallet(updated);
249 // Status (number of confirmations) and (possibly) description
250 // columns changed for all rows.
251 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
252 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
256 int TransactionTableModel::rowCount(const QModelIndex &parent) const
262 int TransactionTableModel::columnCount(const QModelIndex &parent) const
265 return columns.length();
268 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
272 switch(wtx->status.status)
274 case TransactionStatus::OpenUntilBlock:
275 status = tr("Open for %n block(s)","",wtx->status.open_for);
277 case TransactionStatus::OpenUntilDate:
278 status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
280 case TransactionStatus::Offline:
281 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
283 case TransactionStatus::Unconfirmed:
284 status = tr("Unconfirmed (%1 of %2 confirmations required)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
286 case TransactionStatus::HaveConfirmations:
287 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
290 if(wtx->type == TransactionRecord::Generated)
293 switch(wtx->status.maturity)
295 case TransactionStatus::Immature:
296 status += tr("Mined balance will be available in %n more blocks", "",
297 wtx->status.matures_in);
299 case TransactionStatus::Mature:
301 case TransactionStatus::MaturesWarning:
302 status += tr("This block was not received by any other nodes and will probably not be accepted!");
304 case TransactionStatus::NotAccepted:
305 status += tr("Generated but not accepted");
313 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
317 return GUIUtil::DateTimeStr(wtx->time);
325 /* Look up address in address book, if found return label (address)
326 otherwise just return (address)
328 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
330 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
334 description += label + QString(" ");
336 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
338 description += QString("(") + QString::fromStdString(address) + QString(")");
343 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
347 case TransactionRecord::RecvWithAddress:
348 return tr("Received with");
349 case TransactionRecord::RecvFromIP:
350 return tr("Received from IP");
351 case TransactionRecord::SendToAddress:
352 return tr("Sent to");
353 case TransactionRecord::SendToIP:
354 return tr("Sent to IP");
355 case TransactionRecord::SendToSelf:
356 return tr("Payment to yourself");
357 case TransactionRecord::Generated:
364 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
368 case TransactionRecord::Generated:
369 return QIcon(":/icons/tx_mined");
370 case TransactionRecord::RecvWithAddress:
371 case TransactionRecord::RecvFromIP:
372 return QIcon(":/icons/tx_input");
373 case TransactionRecord::SendToAddress:
374 case TransactionRecord::SendToIP:
375 return QIcon(":/icons/tx_output");
377 return QIcon(":/icons/tx_inout");
382 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
386 case TransactionRecord::RecvFromIP:
387 return QString::fromStdString(wtx->address);
388 case TransactionRecord::RecvWithAddress:
389 case TransactionRecord::SendToAddress:
390 return lookupAddress(wtx->address, tooltip);
391 case TransactionRecord::SendToIP:
392 return QString::fromStdString(wtx->address);
393 case TransactionRecord::SendToSelf:
394 case TransactionRecord::Generated:
400 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
402 // Show addresses without label in a less visible color
405 case TransactionRecord::RecvWithAddress:
406 case TransactionRecord::SendToAddress:
408 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
410 return COLOR_BAREADDRESS;
412 case TransactionRecord::SendToSelf:
413 case TransactionRecord::Generated:
414 return COLOR_BAREADDRESS;
421 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
423 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
426 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
428 str = QString("[") + str + QString("]");
434 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
436 if(wtx->type == TransactionRecord::Generated)
438 switch(wtx->status.maturity)
440 case TransactionStatus::Immature: {
441 int total = wtx->status.depth + wtx->status.matures_in;
442 int part = (wtx->status.depth * 4 / total) + 1;
443 return QIcon(QString(":/icons/transaction_%1").arg(part));
445 case TransactionStatus::Mature:
446 return QIcon(":/icons/transaction_confirmed");
447 case TransactionStatus::MaturesWarning:
448 case TransactionStatus::NotAccepted:
449 return QIcon(":/icons/transaction_0");
454 switch(wtx->status.status)
456 case TransactionStatus::OpenUntilBlock:
457 case TransactionStatus::OpenUntilDate:
458 return QColor(64,64,255);
460 case TransactionStatus::Offline:
461 return QColor(192,192,192);
462 case TransactionStatus::Unconfirmed:
463 switch(wtx->status.depth)
465 case 0: return QIcon(":/icons/transaction_0");
466 case 1: return QIcon(":/icons/transaction_1");
467 case 2: return QIcon(":/icons/transaction_2");
468 case 3: return QIcon(":/icons/transaction_3");
469 case 4: return QIcon(":/icons/transaction_4");
470 default: return QIcon(":/icons/transaction_5");
472 case TransactionStatus::HaveConfirmations:
473 return QIcon(":/icons/transaction_confirmed");
476 return QColor(0,0,0);
479 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
481 QString tooltip = formatTxType(rec);
482 if(rec->type==TransactionRecord::RecvFromIP || rec->type==TransactionRecord::SendToIP ||
483 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
485 tooltip += QString(" ") + formatTxToAddress(rec, true);
487 tooltip += QString("\n") + formatTxStatus(rec);
491 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
495 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
497 if(role == Qt::DecorationRole)
499 switch(index.column())
502 return txStatusDecoration(rec);
504 return txAddressDecoration(rec);
507 else if(role == Qt::DisplayRole)
509 // Delegate to specific column handlers
510 switch(index.column())
513 return formatTxDate(rec);
515 return formatTxType(rec);
517 return formatTxToAddress(rec, false);
519 return formatTxAmount(rec);
522 else if(role == Qt::EditRole)
524 // Edit role is used for sorting so return the real values
525 switch(index.column())
528 return QString::fromStdString(rec->status.sortKey);
532 return formatTxType(rec);
534 return formatTxToAddress(rec, true);
536 return rec->credit + rec->debit;
539 else if (role == Qt::ToolTipRole)
541 switch(index.column())
544 return formatTxStatus(rec);
546 return formatTooltip(rec);
549 else if (role == Qt::TextAlignmentRole)
551 return column_alignments[index.column()];
553 else if (role == Qt::ForegroundRole)
555 /* Non-confirmed transactions are grey */
556 if(!rec->status.confirmed)
558 return COLOR_UNCONFIRMED;
560 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
562 return COLOR_NEGATIVE;
564 if(index.column() == ToAddress)
566 return addressColor(rec);
569 else if (role == TypeRole)
573 else if (role == DateRole)
575 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
577 else if (role == LongDescriptionRole)
579 return priv->describe(rec);
581 else if (role == AddressRole)
583 return QString::fromStdString(rec->address);
585 else if (role == LabelRole)
587 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
589 else if (role == AmountRole)
591 return rec->credit + rec->debit;
593 else if (role == TxIDRole)
595 return QString::fromStdString(rec->getTxID());
597 else if (role == ConfirmedRole)
599 // Return True if transaction counts for balance
600 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
601 rec->status.maturity != TransactionStatus::Mature);
603 else if (role == FormattedAmountRole)
605 return formatTxAmount(rec, false);
610 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
612 if(orientation == Qt::Horizontal)
614 if(role == Qt::DisplayRole)
616 return columns[section];
618 else if (role == Qt::TextAlignmentRole)
620 return column_alignments[section];
621 } else if (role == Qt::ToolTipRole)
626 return tr("Transaction status. Hover over this field to show number of confirmations.");
628 return tr("Date and time that the transaction was received.");
630 return tr("Type of transaction.");
632 return tr("Destination address of transaction.");
634 return tr("Amount removed from or added to balance.");
641 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
643 return QAbstractTableModel::flags(index);
646 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
649 TransactionRecord *data = priv->index(row);
652 return createIndex(row, column, priv->index(row));
656 return QModelIndex();