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)
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::RecvFromOther:
349 return tr("Received from");
350 case TransactionRecord::SendToAddress:
351 case TransactionRecord::SendToOther:
352 return tr("Sent to");
353 case TransactionRecord::SendToSelf:
354 return tr("Payment to yourself");
355 case TransactionRecord::Generated:
362 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
366 case TransactionRecord::Generated:
367 return QIcon(":/icons/tx_mined");
368 case TransactionRecord::RecvWithAddress:
369 case TransactionRecord::RecvFromOther:
370 return QIcon(":/icons/tx_input");
371 case TransactionRecord::SendToAddress:
372 case TransactionRecord::SendToOther:
373 return QIcon(":/icons/tx_output");
375 return QIcon(":/icons/tx_inout");
380 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
384 case TransactionRecord::RecvFromOther:
385 return QString::fromStdString(wtx->address);
386 case TransactionRecord::RecvWithAddress:
387 case TransactionRecord::SendToAddress:
388 return lookupAddress(wtx->address, tooltip);
389 case TransactionRecord::SendToOther:
390 return QString::fromStdString(wtx->address);
391 case TransactionRecord::SendToSelf:
392 case TransactionRecord::Generated:
398 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
400 // Show addresses without label in a less visible color
403 case TransactionRecord::RecvWithAddress:
404 case TransactionRecord::SendToAddress:
406 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
408 return COLOR_BAREADDRESS;
410 case TransactionRecord::SendToSelf:
411 case TransactionRecord::Generated:
412 return COLOR_BAREADDRESS;
419 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
421 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
424 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
426 str = QString("[") + str + QString("]");
432 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
434 if(wtx->type == TransactionRecord::Generated)
436 switch(wtx->status.maturity)
438 case TransactionStatus::Immature: {
439 int total = wtx->status.depth + wtx->status.matures_in;
440 int part = (wtx->status.depth * 4 / total) + 1;
441 return QIcon(QString(":/icons/transaction_%1").arg(part));
443 case TransactionStatus::Mature:
444 return QIcon(":/icons/transaction_confirmed");
445 case TransactionStatus::MaturesWarning:
446 case TransactionStatus::NotAccepted:
447 return QIcon(":/icons/transaction_0");
452 switch(wtx->status.status)
454 case TransactionStatus::OpenUntilBlock:
455 case TransactionStatus::OpenUntilDate:
456 return QColor(64,64,255);
458 case TransactionStatus::Offline:
459 return QColor(192,192,192);
460 case TransactionStatus::Unconfirmed:
461 switch(wtx->status.depth)
463 case 0: return QIcon(":/icons/transaction_0");
464 case 1: return QIcon(":/icons/transaction_1");
465 case 2: return QIcon(":/icons/transaction_2");
466 case 3: return QIcon(":/icons/transaction_3");
467 case 4: return QIcon(":/icons/transaction_4");
468 default: return QIcon(":/icons/transaction_5");
470 case TransactionStatus::HaveConfirmations:
471 return QIcon(":/icons/transaction_confirmed");
474 return QColor(0,0,0);
477 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
479 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
480 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
481 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
483 tooltip += QString(" ") + formatTxToAddress(rec, true);
488 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
492 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
496 case Qt::DecorationRole:
497 switch(index.column())
500 return txStatusDecoration(rec);
502 return txAddressDecoration(rec);
505 case Qt::DisplayRole:
506 switch(index.column())
509 return formatTxDate(rec);
511 return formatTxType(rec);
513 return formatTxToAddress(rec, false);
515 return formatTxAmount(rec);
519 // Edit role is used for sorting, so return the unformatted values
520 switch(index.column())
523 return QString::fromStdString(rec->status.sortKey);
527 return formatTxType(rec);
529 return formatTxToAddress(rec, true);
531 return rec->credit + rec->debit;
534 case Qt::ToolTipRole:
535 return formatTooltip(rec);
536 case Qt::TextAlignmentRole:
537 return column_alignments[index.column()];
538 case Qt::ForegroundRole:
539 // Non-confirmed transactions are grey
540 if(!rec->status.confirmed)
542 return COLOR_UNCONFIRMED;
544 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
546 return COLOR_NEGATIVE;
548 if(index.column() == ToAddress)
550 return addressColor(rec);
556 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
557 case LongDescriptionRole:
558 return priv->describe(rec);
560 return QString::fromStdString(rec->address);
562 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
564 return rec->credit + rec->debit;
566 return QString::fromStdString(rec->getTxID());
568 // Return True if transaction counts for balance
569 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
570 rec->status.maturity != TransactionStatus::Mature);
571 case FormattedAmountRole:
572 return formatTxAmount(rec, false);
577 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
579 if(orientation == Qt::Horizontal)
581 if(role == Qt::DisplayRole)
583 return columns[section];
585 else if (role == Qt::TextAlignmentRole)
587 return column_alignments[section];
588 } else if (role == Qt::ToolTipRole)
593 return tr("Transaction status. Hover over this field to show number of confirmations.");
595 return tr("Date and time that the transaction was received.");
597 return tr("Type of transaction.");
599 return tr("Destination address of transaction.");
601 return tr("Amount removed from or added to balance.");
608 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
611 TransactionRecord *data = priv->index(row);
614 return createIndex(row, column, priv->index(row));
618 return QModelIndex();