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 QVariant 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");
310 return QVariant(status);
313 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
317 return QVariant(GUIUtil::DateTimeStr(wtx->time));
325 /* Look up address in address book, if found return
327 otherwise just return address
329 QString TransactionTableModel::lookupAddress(const std::string &address) const
331 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
335 description = QString::fromStdString(address);
339 description = label + QString(" (") + QString::fromStdString(address) + QString(")");
344 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
350 case TransactionRecord::RecvWithAddress:
351 description = tr("Received with");
353 case TransactionRecord::RecvFromIP:
354 description = tr("Received from IP");
356 case TransactionRecord::SendToAddress:
357 description = tr("Sent to");
359 case TransactionRecord::SendToIP:
360 description = tr("Sent to IP");
362 case TransactionRecord::SendToSelf:
363 description = tr("Payment to yourself");
365 case TransactionRecord::Generated:
366 description = tr("Mined");
369 return QVariant(description);
372 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
378 case TransactionRecord::RecvWithAddress:
379 description = lookupAddress(wtx->address);
381 case TransactionRecord::RecvFromIP:
382 description = QString::fromStdString(wtx->address);
384 case TransactionRecord::SendToAddress:
385 description = lookupAddress(wtx->address);
387 case TransactionRecord::SendToIP:
388 description = QString::fromStdString(wtx->address);
390 case TransactionRecord::SendToSelf:
391 description = QString();
393 case TransactionRecord::Generated:
394 description = QString();
397 return QVariant(description);
400 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
402 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
405 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
407 str = QString("[") + str + QString("]");
410 return QVariant(str);
413 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
415 if(wtx->type == TransactionRecord::Generated)
417 switch(wtx->status.maturity)
419 case TransactionStatus::Immature: {
420 int total = wtx->status.depth + wtx->status.matures_in;
421 int part = (wtx->status.depth * 4 / total) + 1;
422 return QIcon(QString(":/icons/transaction_%1").arg(part));
424 case TransactionStatus::Mature:
425 return QIcon(":/icons/transaction_confirmed");
426 case TransactionStatus::MaturesWarning:
427 case TransactionStatus::NotAccepted:
428 return QIcon(":/icons/transaction_0");
433 switch(wtx->status.status)
435 case TransactionStatus::OpenUntilBlock:
436 case TransactionStatus::OpenUntilDate:
437 return QColor(64,64,255);
439 case TransactionStatus::Offline:
440 return QColor(192,192,192);
441 case TransactionStatus::Unconfirmed:
442 switch(wtx->status.depth)
444 case 0: return QIcon(":/icons/transaction_0");
445 case 1: return QIcon(":/icons/transaction_1");
446 case 2: return QIcon(":/icons/transaction_2");
447 case 3: return QIcon(":/icons/transaction_3");
448 case 4: return QIcon(":/icons/transaction_4");
449 default: return QIcon(":/icons/transaction_5");
451 case TransactionStatus::HaveConfirmations:
452 return QIcon(":/icons/transaction_confirmed");
455 return QColor(0,0,0);
458 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
462 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
464 if(role == Qt::DecorationRole)
466 if(index.column() == Status)
468 return formatTxDecoration(rec);
471 else if(role == Qt::DisplayRole)
473 // Delegate to specific column handlers
474 switch(index.column())
477 return formatTxDate(rec);
479 return formatTxType(rec);
481 return formatTxToAddress(rec);
483 return formatTxAmount(rec);
486 else if(role == Qt::EditRole)
488 // Edit role is used for sorting so return the real values
489 switch(index.column())
492 return QString::fromStdString(rec->status.sortKey);
496 return formatTxType(rec);
498 return formatTxToAddress(rec);
500 return rec->credit + rec->debit;
503 else if (role == Qt::ToolTipRole)
505 if(index.column() == Status)
507 return formatTxStatus(rec);
510 else if (role == Qt::TextAlignmentRole)
512 return column_alignments[index.column()];
514 else if (role == Qt::ForegroundRole)
516 /* Non-confirmed transactions are grey */
517 if(!rec->status.confirmed)
519 return COLOR_UNCONFIRMED;
521 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
523 return COLOR_NEGATIVE;
526 else if (role == TypeRole)
530 else if (role == DateRole)
532 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
534 else if (role == LongDescriptionRole)
536 return priv->describe(rec);
538 else if (role == AddressRole)
540 return QString::fromStdString(rec->address);
542 else if (role == LabelRole)
544 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
546 else if (role == AbsoluteAmountRole)
548 return llabs(rec->credit + rec->debit);
550 else if (role == TxIDRole)
552 return QString::fromStdString(rec->getTxID());
554 else if (role == ConfirmedRole)
556 return rec->status.status == TransactionStatus::HaveConfirmations;
558 else if (role == FormattedAmountRole)
560 return formatTxAmount(rec, false);
565 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
567 if(orientation == Qt::Horizontal)
569 if(role == Qt::DisplayRole)
571 return columns[section];
573 else if (role == Qt::TextAlignmentRole)
575 return column_alignments[section];
576 } else if (role == Qt::ToolTipRole)
581 return tr("Transaction status. Hover over this field to show number of confirmations.");
583 return tr("Date and time that the transaction was received.");
585 return tr("Type of transaction.");
587 return tr("Destination address of transaction.");
589 return tr("Amount removed from or added to balance.");
596 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
598 return QAbstractTableModel::flags(index);
601 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
604 TransactionRecord *data = priv->index(row);
607 return createIndex(row, column, priv->index(row));
611 return QModelIndex();