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_wallet)
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_wallet)
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_wallet)
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_wallet)
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_wallet)
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)
291 switch(wtx->status.maturity)
293 case TransactionStatus::Immature:
294 status += "\n" + tr("Mined balance will be available in %n more blocks", "",
295 wtx->status.matures_in);
297 case TransactionStatus::Mature:
299 case TransactionStatus::MaturesWarning:
300 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
302 case TransactionStatus::NotAccepted:
303 status += "\n" + tr("Generated but not accepted");
311 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
315 return GUIUtil::dateTimeStr(wtx->time);
323 /* Look up address in address book, if found return label (address)
324 otherwise just return (address)
326 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
328 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
332 description += label + QString(" ");
334 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
336 description += QString("(") + QString::fromStdString(address) + QString(")");
341 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
345 case TransactionRecord::RecvWithAddress:
346 return tr("Received with");
347 case TransactionRecord::RecvFromOther:
348 return tr("Received from");
349 case TransactionRecord::SendToAddress:
350 case TransactionRecord::SendToOther:
351 return tr("Sent to");
352 case TransactionRecord::SendToSelf:
353 return tr("Payment to yourself");
354 case TransactionRecord::Generated:
361 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
365 case TransactionRecord::Generated:
366 return QIcon(":/icons/tx_mined");
367 case TransactionRecord::RecvWithAddress:
368 case TransactionRecord::RecvFromOther:
369 return QIcon(":/icons/tx_input");
370 case TransactionRecord::SendToAddress:
371 case TransactionRecord::SendToOther:
372 return QIcon(":/icons/tx_output");
374 return QIcon(":/icons/tx_inout");
379 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
383 case TransactionRecord::RecvFromOther:
384 return QString::fromStdString(wtx->address);
385 case TransactionRecord::RecvWithAddress:
386 case TransactionRecord::SendToAddress:
387 return lookupAddress(wtx->address, tooltip);
388 case TransactionRecord::SendToOther:
389 return QString::fromStdString(wtx->address);
390 case TransactionRecord::SendToSelf:
391 case TransactionRecord::Generated:
397 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
399 // Show addresses without label in a less visible color
402 case TransactionRecord::RecvWithAddress:
403 case TransactionRecord::SendToAddress:
405 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
407 return COLOR_BAREADDRESS;
409 case TransactionRecord::SendToSelf:
410 case TransactionRecord::Generated:
411 return COLOR_BAREADDRESS;
418 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
420 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
423 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
425 str = QString("[") + str + QString("]");
431 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
433 if(wtx->type == TransactionRecord::Generated)
435 switch(wtx->status.maturity)
437 case TransactionStatus::Immature: {
438 int total = wtx->status.depth + wtx->status.matures_in;
439 int part = (wtx->status.depth * 4 / total) + 1;
440 return QIcon(QString(":/icons/transaction_%1").arg(part));
442 case TransactionStatus::Mature:
443 return QIcon(":/icons/transaction_confirmed");
444 case TransactionStatus::MaturesWarning:
445 case TransactionStatus::NotAccepted:
446 return QIcon(":/icons/transaction_0");
451 switch(wtx->status.status)
453 case TransactionStatus::OpenUntilBlock:
454 case TransactionStatus::OpenUntilDate:
455 return QColor(64,64,255);
457 case TransactionStatus::Offline:
458 return QColor(192,192,192);
459 case TransactionStatus::Unconfirmed:
460 switch(wtx->status.depth)
462 case 0: return QIcon(":/icons/transaction_0");
463 case 1: return QIcon(":/icons/transaction_1");
464 case 2: return QIcon(":/icons/transaction_2");
465 case 3: return QIcon(":/icons/transaction_3");
466 case 4: return QIcon(":/icons/transaction_4");
467 default: return QIcon(":/icons/transaction_5");
469 case TransactionStatus::HaveConfirmations:
470 return QIcon(":/icons/transaction_confirmed");
473 return QColor(0,0,0);
476 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
478 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
479 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
480 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
482 tooltip += QString(" ") + formatTxToAddress(rec, true);
487 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
491 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
495 case Qt::DecorationRole:
496 switch(index.column())
499 return txStatusDecoration(rec);
501 return txAddressDecoration(rec);
504 case Qt::DisplayRole:
505 switch(index.column())
508 return formatTxDate(rec);
510 return formatTxType(rec);
512 return formatTxToAddress(rec, false);
514 return formatTxAmount(rec);
518 // Edit role is used for sorting, so return the unformatted values
519 switch(index.column())
522 return QString::fromStdString(rec->status.sortKey);
526 return formatTxType(rec);
528 return formatTxToAddress(rec, true);
530 return rec->credit + rec->debit;
533 case Qt::ToolTipRole:
534 return formatTooltip(rec);
535 case Qt::TextAlignmentRole:
536 return column_alignments[index.column()];
537 case Qt::ForegroundRole:
538 // Non-confirmed transactions are grey
539 if(!rec->status.confirmed)
541 return COLOR_UNCONFIRMED;
543 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
545 return COLOR_NEGATIVE;
547 if(index.column() == ToAddress)
549 return addressColor(rec);
555 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
556 case LongDescriptionRole:
557 return priv->describe(rec);
559 return QString::fromStdString(rec->address);
561 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
563 return rec->credit + rec->debit;
565 return QString::fromStdString(rec->getTxID());
567 // Return True if transaction counts for balance
568 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
569 rec->status.maturity != TransactionStatus::Mature);
570 case FormattedAmountRole:
571 return formatTxAmount(rec, false);
576 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
578 if(orientation == Qt::Horizontal)
580 if(role == Qt::DisplayRole)
582 return columns[section];
584 else if (role == Qt::TextAlignmentRole)
586 return column_alignments[section];
587 } else if (role == Qt::ToolTipRole)
592 return tr("Transaction status. Hover over this field to show number of confirmations.");
594 return tr("Date and time that the transaction was received.");
596 return tr("Type of transaction.");
598 return tr("Destination address of transaction.");
600 return tr("Amount removed from or added to balance.");
607 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
610 TransactionRecord *data = priv->index(row);
613 return createIndex(row, column, priv->index(row));
617 return QModelIndex();