1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
15 #include <QtAlgorithms>
17 const QString TransactionTableModel::Sent = "s";
18 const QString TransactionTableModel::Received = "r";
19 const QString TransactionTableModel::Other = "o";
21 // Credit and Debit columns are right-aligned as they contain 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 QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
207 TransactionTableModel::TransactionTableModel(CWallet* wallet, QObject *parent):
208 QAbstractTableModel(parent),
210 priv(new TransactionTablePriv(wallet, this))
212 columns << tr("Status") << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
214 priv->refreshWallet();
216 QTimer *timer = new QTimer(this);
217 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
218 timer->start(MODEL_UPDATE_DELAY);
221 TransactionTableModel::~TransactionTableModel()
226 void TransactionTableModel::update()
228 QList<uint256> updated;
230 // Check if there are changes to wallet map
231 TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
233 if(!wallet->vWalletUpdated.empty())
235 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
237 updated.append(hash);
239 wallet->vWalletUpdated.clear();
245 priv->updateWallet(updated);
247 // Status (number of confirmations) and (possibly) description
248 // columns changed for all rows.
249 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
250 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
254 int TransactionTableModel::rowCount(const QModelIndex &parent) const
260 int TransactionTableModel::columnCount(const QModelIndex &parent) const
263 return columns.length();
266 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
270 switch(wtx->status.status)
272 case TransactionStatus::OpenUntilBlock:
273 status = tr("Open for %n block(s)","",wtx->status.open_for);
275 case TransactionStatus::OpenUntilDate:
276 status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
278 case TransactionStatus::Offline:
279 status = tr("Offline (%1)").arg(wtx->status.depth);
281 case TransactionStatus::Unconfirmed:
282 status = tr("Unconfirmed (%1/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
284 case TransactionStatus::HaveConfirmations:
285 status = tr("Confirmed (%1)").arg(wtx->status.depth);
289 return QVariant(status);
292 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
296 return QVariant(GUIUtil::DateTimeStr(wtx->time));
304 /* Look up address in address book, if found return
305 address[0:12]... (label)
306 otherwise just return address
308 std::string TransactionTableModel::lookupAddress(const std::string &address) const
310 std::string description;
311 CRITICAL_BLOCK(wallet->cs_mapAddressBook)
313 std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
314 if (mi != wallet->mapAddressBook.end() && !(*mi).second.empty())
316 std::string label = (*mi).second;
317 description += address.substr(0,12) + "... ";
318 description += "(" + label + ")";
322 description += address;
328 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
334 case TransactionRecord::RecvWithAddress:
335 description = tr("Received with");
337 case TransactionRecord::RecvFromIP:
338 description = tr("Received from IP");
340 case TransactionRecord::SendToAddress:
341 description = tr("Sent to");
343 case TransactionRecord::SendToIP:
344 description = tr("Sent to IP");
346 case TransactionRecord::SendToSelf:
347 description = tr("Payment to yourself");
349 case TransactionRecord::Generated:
350 description = tr("Generated");
353 return QVariant(description);
356 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
362 case TransactionRecord::RecvWithAddress:
363 description = QString::fromStdString(lookupAddress(wtx->address));
365 case TransactionRecord::RecvFromIP:
366 description = QString::fromStdString(wtx->address);
368 case TransactionRecord::SendToAddress:
369 description = QString::fromStdString(lookupAddress(wtx->address));
371 case TransactionRecord::SendToIP:
372 description = QString::fromStdString(wtx->address);
374 case TransactionRecord::SendToSelf:
375 description = QString();
377 case TransactionRecord::Generated:
378 switch(wtx->status.maturity)
380 case TransactionStatus::Immature:
381 description = tr("(matures in %n more blocks)", "",
382 wtx->status.matures_in);
384 case TransactionStatus::Mature:
385 description = QString();
387 case TransactionStatus::MaturesWarning:
388 description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
390 case TransactionStatus::NotAccepted:
391 description = tr("(not accepted)");
396 return QVariant(description);
399 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
401 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
402 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
404 str = QString("[") + str + QString("]");
406 return QVariant(str);
409 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
411 switch(wtx->status.status)
413 case TransactionStatus::OpenUntilBlock:
414 case TransactionStatus::OpenUntilDate:
415 return QColor(64,64,255);
417 case TransactionStatus::Offline:
418 return QColor(192,192,192);
419 case TransactionStatus::Unconfirmed:
420 switch(wtx->status.depth)
422 case 0: return QIcon(":/icons/transaction_0");
423 case 1: return QIcon(":/icons/transaction_1");
424 case 2: return QIcon(":/icons/transaction_2");
425 case 3: return QIcon(":/icons/transaction_3");
426 case 4: return QIcon(":/icons/transaction_4");
427 default: return QIcon(":/icons/transaction_5");
429 case TransactionStatus::HaveConfirmations:
430 return QIcon(":/icons/transaction_confirmed");
432 return QColor(0,0,0);
435 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
439 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
441 if(role == Qt::DecorationRole)
443 if(index.column() == Status)
445 return formatTxDecoration(rec);
448 else if(role == Qt::DisplayRole)
450 // Delegate to specific column handlers
451 switch(index.column())
454 return formatTxDate(rec);
456 return formatTxType(rec);
458 return formatTxToAddress(rec);
460 return formatTxAmount(rec);
463 else if(role == Qt::EditRole)
465 // Edit role is used for sorting so return the real values
466 switch(index.column())
469 return QString::fromStdString(rec->status.sortKey);
473 return formatTxType(rec);
475 return formatTxToAddress(rec);
477 return rec->credit + rec->debit;
480 else if (role == Qt::ToolTipRole)
482 if(index.column() == Status)
484 return formatTxStatus(rec);
487 else if (role == Qt::TextAlignmentRole)
489 return column_alignments[index.column()];
491 else if (role == Qt::ForegroundRole)
493 /* Non-confirmed transactions are grey */
494 if(!rec->status.confirmed)
496 return QColor(128, 128, 128);
498 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
500 return QColor(255, 0, 0);
503 else if (role == TypeRole)
505 /* Role for filtering tabs by type */
508 case TransactionRecord::RecvWithAddress:
509 case TransactionRecord::RecvFromIP:
510 return TransactionTableModel::Received;
511 case TransactionRecord::SendToAddress:
512 case TransactionRecord::SendToIP:
513 case TransactionRecord::SendToSelf:
514 return TransactionTableModel::Sent;
516 return TransactionTableModel::Other;
519 else if (role == LongDescriptionRole)
521 return priv->describe(rec);
526 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
528 if(orientation == Qt::Horizontal)
530 if(role == Qt::DisplayRole)
532 return columns[section];
534 else if (role == Qt::TextAlignmentRole)
536 return column_alignments[section];
537 } else if (role == Qt::ToolTipRole)
542 return tr("Transaction status. Hover over this field to show number of confirmations.");
544 return tr("Date and time that the transaction was received.");
546 return tr("Type of transaction.");
548 return tr("Destination address of transaction.");
550 return tr("Amount removed from or added to balance.");
557 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
559 return QAbstractTableModel::flags(index);
562 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
565 TransactionRecord *data = priv->index(row);
568 return createIndex(row, column, priv->index(row));
572 return QModelIndex();