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"
19 #include <QtAlgorithms>
21 // Amount column is right-aligned it contains 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 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 QString 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)").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");
312 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
316 return GUIUtil::dateTimeStr(wtx->time);
324 /* Look up address in address book, if found return label (address)
325 otherwise just return (address)
327 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
329 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
333 description += label + QString(" ");
335 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
337 description += QString("(") + QString::fromStdString(address) + QString(")");
342 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
346 case TransactionRecord::RecvWithAddress:
347 return tr("Received with");
348 case TransactionRecord::RecvFromIP:
349 return tr("Received from IP");
350 case TransactionRecord::SendToAddress:
351 return tr("Sent to");
352 case TransactionRecord::SendToIP:
353 return tr("Sent to IP");
354 case TransactionRecord::SendToSelf:
355 return tr("Payment to yourself");
356 case TransactionRecord::Generated:
363 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
367 case TransactionRecord::Generated:
368 return QIcon(":/icons/tx_mined");
369 case TransactionRecord::RecvWithAddress:
370 case TransactionRecord::RecvFromIP:
371 return QIcon(":/icons/tx_input");
372 case TransactionRecord::SendToAddress:
373 case TransactionRecord::SendToIP:
374 return QIcon(":/icons/tx_output");
376 return QIcon(":/icons/tx_inout");
381 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
385 case TransactionRecord::RecvFromIP:
386 return QString::fromStdString(wtx->address);
387 case TransactionRecord::RecvWithAddress:
388 case TransactionRecord::SendToAddress:
389 return lookupAddress(wtx->address, tooltip);
390 case TransactionRecord::SendToIP:
391 return QString::fromStdString(wtx->address);
392 case TransactionRecord::SendToSelf:
393 case TransactionRecord::Generated:
399 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
401 // Show addresses without label in a less visible color
404 case TransactionRecord::RecvWithAddress:
405 case TransactionRecord::SendToAddress:
407 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
409 return COLOR_BAREADDRESS;
411 case TransactionRecord::SendToSelf:
412 case TransactionRecord::Generated:
413 return COLOR_BAREADDRESS;
420 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
422 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
425 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
427 str = QString("[") + str + QString("]");
433 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
435 if(wtx->type == TransactionRecord::Generated)
437 switch(wtx->status.maturity)
439 case TransactionStatus::Immature: {
440 int total = wtx->status.depth + wtx->status.matures_in;
441 int part = (wtx->status.depth * 4 / total) + 1;
442 return QIcon(QString(":/icons/transaction_%1").arg(part));
444 case TransactionStatus::Mature:
445 return QIcon(":/icons/transaction_confirmed");
446 case TransactionStatus::MaturesWarning:
447 case TransactionStatus::NotAccepted:
448 return QIcon(":/icons/transaction_0");
453 switch(wtx->status.status)
455 case TransactionStatus::OpenUntilBlock:
456 case TransactionStatus::OpenUntilDate:
457 return QColor(64,64,255);
459 case TransactionStatus::Offline:
460 return QColor(192,192,192);
461 case TransactionStatus::Unconfirmed:
462 switch(wtx->status.depth)
464 case 0: return QIcon(":/icons/transaction_0");
465 case 1: return QIcon(":/icons/transaction_1");
466 case 2: return QIcon(":/icons/transaction_2");
467 case 3: return QIcon(":/icons/transaction_3");
468 case 4: return QIcon(":/icons/transaction_4");
469 default: return QIcon(":/icons/transaction_5");
471 case TransactionStatus::HaveConfirmations:
472 return QIcon(":/icons/transaction_confirmed");
475 return QColor(0,0,0);
478 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
480 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
481 if(rec->type==TransactionRecord::RecvFromIP || rec->type==TransactionRecord::SendToIP ||
482 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
484 tooltip += QString(" ") + formatTxToAddress(rec, true);
489 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
493 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
497 case Qt::DecorationRole:
498 switch(index.column())
501 return txStatusDecoration(rec);
503 return txAddressDecoration(rec);
506 case Qt::DisplayRole:
507 switch(index.column())
510 return formatTxDate(rec);
512 return formatTxType(rec);
514 return formatTxToAddress(rec, false);
516 return formatTxAmount(rec);
520 // Edit role is used for sorting, so return the unformatted values
521 switch(index.column())
524 return QString::fromStdString(rec->status.sortKey);
528 return formatTxType(rec);
530 return formatTxToAddress(rec, true);
532 return rec->credit + rec->debit;
535 case Qt::ToolTipRole:
536 return formatTooltip(rec);
537 case Qt::TextAlignmentRole:
538 return column_alignments[index.column()];
539 case Qt::ForegroundRole:
540 // Non-confirmed transactions are grey
541 if(!rec->status.confirmed)
543 return COLOR_UNCONFIRMED;
545 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
547 return COLOR_NEGATIVE;
549 if(index.column() == ToAddress)
551 return addressColor(rec);
557 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
558 case LongDescriptionRole:
559 return priv->describe(rec);
561 return QString::fromStdString(rec->address);
563 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
565 return rec->credit + rec->debit;
567 return QString::fromStdString(rec->getTxID());
569 // Return True if transaction counts for balance
570 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
571 rec->status.maturity != TransactionStatus::Mature);
572 case FormattedAmountRole:
573 return formatTxAmount(rec, false);
578 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
580 if(orientation == Qt::Horizontal)
582 if(role == Qt::DisplayRole)
584 return columns[section];
586 else if (role == Qt::TextAlignmentRole)
588 return column_alignments[section];
589 } else if (role == Qt::ToolTipRole)
594 return tr("Transaction status. Hover over this field to show number of confirmations.");
596 return tr("Date and time that the transaction was received.");
598 return tr("Type of transaction.");
600 return tr("Destination address of transaction.");
602 return tr("Amount removed from or added to balance.");
609 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
612 TransactionRecord *data = priv->index(row);
615 return createIndex(row, column, priv->index(row));
619 return QModelIndex();