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 // Comparison operator for sort/binary search of model tx list
24 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
26 return a.hash < b.hash;
28 bool operator()(const TransactionRecord &a, const uint256 &b) const
32 bool operator()(const uint256 &a, const TransactionRecord &b) const
38 // Private implementation
39 struct TransactionTablePriv
41 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
47 TransactionTableModel *parent;
49 /* Local cache of wallet.
50 * As it is in the same order as the CWallet, by definition
51 * this is sorted by sha256.
53 QList<TransactionRecord> cachedWallet;
55 /* Query entire wallet anew from core.
59 #ifdef WALLET_UPDATE_DEBUG
60 qDebug() << "refreshWallet";
63 CRITICAL_BLOCK(wallet->cs_mapWallet)
65 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
67 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
72 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
73 with that of the core.
75 Call with list of hashes of transactions that were added, removed or changed.
77 void updateWallet(const QList<uint256> &updated)
79 // Walk through updated transactions, update model as needed.
80 #ifdef WALLET_UPDATE_DEBUG
81 qDebug() << "updateWallet";
83 // Sort update list, and iterate through it in reverse, so that model updates
84 // can be emitted from end to beginning (so that earlier updates will not influence
85 // the indices of latter ones).
86 QList<uint256> updated_sorted = updated;
87 qSort(updated_sorted);
89 CRITICAL_BLOCK(wallet->cs_mapWallet)
91 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
93 const uint256 &hash = updated_sorted.at(update_idx);
94 /* Find transaction in wallet */
95 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
96 bool inWallet = mi != wallet->mapWallet.end();
97 /* Find bounds of this transaction in model */
98 QList<TransactionRecord>::iterator lower = qLowerBound(
99 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
100 QList<TransactionRecord>::iterator upper = qUpperBound(
101 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
102 int lowerIndex = (lower - cachedWallet.begin());
103 int upperIndex = (upper - cachedWallet.begin());
105 // Determine if transaction is in model already
106 bool inModel = false;
112 #ifdef WALLET_UPDATE_DEBUG
113 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
114 << lowerIndex << "-" << upperIndex;
117 if(inWallet && !inModel)
119 // Added -- insert at the right position
120 QList<TransactionRecord> toInsert =
121 TransactionRecord::decomposeTransaction(wallet, mi->second);
122 if(!toInsert.isEmpty()) /* only if something to insert */
124 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
125 int insert_idx = lowerIndex;
126 foreach(const TransactionRecord &rec, toInsert)
128 cachedWallet.insert(insert_idx, rec);
131 parent->endInsertRows();
134 else if(!inWallet && inModel)
136 // Removed -- remove entire transaction from table
137 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
138 cachedWallet.erase(lower, upper);
139 parent->endRemoveRows();
141 else if(inWallet && inModel)
143 // Updated -- nothing to do, status update will take care of this
151 return cachedWallet.size();
154 TransactionRecord *index(int idx)
156 if(idx >= 0 && idx < cachedWallet.size())
158 TransactionRecord *rec = &cachedWallet[idx];
160 // If a status update is needed (blocks came in since last check),
161 // update the status of this transaction from the wallet. Otherwise,
162 // simply re-use the cached status.
163 if(rec->statusUpdateNeeded())
165 CRITICAL_BLOCK(wallet->cs_mapWallet)
167 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
169 if(mi != wallet->mapWallet.end())
171 rec->updateStatus(mi->second);
183 QString describe(TransactionRecord *rec)
185 CRITICAL_BLOCK(wallet->cs_mapWallet)
187 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
188 if(mi != wallet->mapWallet.end())
190 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
198 // Credit and Debit columns are right-aligned as they contain numbers
199 static int column_alignments[] = {
200 Qt::AlignLeft|Qt::AlignVCenter,
201 Qt::AlignLeft|Qt::AlignVCenter,
202 Qt::AlignLeft|Qt::AlignVCenter,
203 Qt::AlignRight|Qt::AlignVCenter,
204 Qt::AlignRight|Qt::AlignVCenter,
205 Qt::AlignLeft|Qt::AlignVCenter
208 TransactionTableModel::TransactionTableModel(CWallet* wallet, QObject *parent):
209 QAbstractTableModel(parent),
211 priv(new TransactionTablePriv(wallet, this))
213 columns << tr("Status") << tr("Date") << tr("Description") << tr("Debit") << tr("Credit");
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, Description), index(priv->size()-1, Description));
255 int TransactionTableModel::rowCount(const QModelIndex &parent) const
261 int TransactionTableModel::columnCount(const QModelIndex &parent) const
264 return columns.length();
267 QVariant 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)").arg(wtx->status.depth);
282 case TransactionStatus::Unconfirmed:
283 status = tr("Unconfirmed (%1/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
285 case TransactionStatus::HaveConfirmations:
286 status = tr("Confirmed (%1)").arg(wtx->status.depth);
290 return QVariant(status);
293 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
297 return QVariant(GUIUtil::DateTimeStr(wtx->time));
305 /* Look up address in address book, if found return
306 address[0:12]... (label)
307 otherwise just return address
309 std::string TransactionTableModel::lookupAddress(const std::string &address) const
311 std::string description;
312 CRITICAL_BLOCK(wallet->cs_mapAddressBook)
314 std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
315 if (mi != wallet->mapAddressBook.end() && !(*mi).second.empty())
317 std::string label = (*mi).second;
318 description += address.substr(0,12) + "... ";
319 description += "(" + label + ")";
323 description += address;
329 QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
335 case TransactionRecord::RecvWithAddress:
336 description = tr("Received with: ") + QString::fromStdString(lookupAddress(wtx->address));
338 case TransactionRecord::RecvFromIP:
339 description = tr("Received from IP: ") + QString::fromStdString(wtx->address);
341 case TransactionRecord::SendToAddress:
342 description = tr("Sent to: ") + QString::fromStdString(lookupAddress(wtx->address));
344 case TransactionRecord::SendToIP:
345 description = tr("Sent to IP: ") + QString::fromStdString(wtx->address);
347 case TransactionRecord::SendToSelf:
348 description = tr("Payment to yourself");
350 case TransactionRecord::Generated:
351 switch(wtx->status.maturity)
353 case TransactionStatus::Immature:
354 description = tr("Generated (matures in %n more blocks)", "",
355 wtx->status.matures_in);
357 case TransactionStatus::Mature:
358 description = tr("Generated");
360 case TransactionStatus::MaturesWarning:
361 description = tr("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
363 case TransactionStatus::NotAccepted:
364 description = tr("Generated (not accepted)");
369 return QVariant(description);
372 QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
376 QString str = QString::fromStdString(FormatMoney(wtx->debit));
377 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
379 str = QString("[") + str + QString("]");
381 return QVariant(str);
389 QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
393 QString str = QString::fromStdString(FormatMoney(wtx->credit));
394 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
396 str = QString("[") + str + QString("]");
398 return QVariant(str);
406 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
408 switch(wtx->status.status)
410 case TransactionStatus::OpenUntilBlock:
411 case TransactionStatus::OpenUntilDate:
412 return QColor(64,64,255);
414 case TransactionStatus::Offline:
415 return QColor(192,192,192);
416 case TransactionStatus::Unconfirmed:
417 switch(wtx->status.depth)
419 case 0: return QIcon(":/icons/transaction_0");
420 case 1: return QIcon(":/icons/transaction_1");
421 case 2: return QIcon(":/icons/transaction_2");
422 case 3: return QIcon(":/icons/transaction_3");
423 case 4: return QIcon(":/icons/transaction_4");
424 default: return QIcon(":/icons/transaction_5");
426 case TransactionStatus::HaveConfirmations:
427 return QIcon(":/icons/transaction_confirmed");
429 return QColor(0,0,0);
432 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
436 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
438 if(role == Qt::DecorationRole)
440 if(index.column() == Status)
442 return formatTxDecoration(rec);
445 else if(role == Qt::DisplayRole)
447 // Delegate to specific column handlers
448 switch(index.column())
451 return formatTxDate(rec);
453 return formatTxDescription(rec);
455 return formatTxDebit(rec);
457 return formatTxCredit(rec);
460 else if(role == Qt::EditRole)
462 // Edit role is used for sorting so return the real values
463 switch(index.column())
466 return QString::fromStdString(rec->status.sortKey);
470 return formatTxDescription(rec);
477 else if (role == Qt::ToolTipRole)
479 if(index.column() == Status)
481 return formatTxStatus(rec);
484 else if (role == Qt::TextAlignmentRole)
486 return column_alignments[index.column()];
488 else if (role == Qt::ForegroundRole)
490 /* Non-confirmed transactions are grey */
491 if(!rec->status.confirmed)
493 return QColor(128, 128, 128);
496 else if (role == TypeRole)
498 /* Role for filtering tabs by type */
501 case TransactionRecord::RecvWithAddress:
502 case TransactionRecord::RecvFromIP:
503 return TransactionTableModel::Received;
504 case TransactionRecord::SendToAddress:
505 case TransactionRecord::SendToIP:
506 case TransactionRecord::SendToSelf:
507 return TransactionTableModel::Sent;
509 return TransactionTableModel::Other;
512 else if (role == LongDescriptionRole)
514 return priv->describe(rec);
519 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
521 if(orientation == Qt::Horizontal)
523 if(role == Qt::DisplayRole)
525 return columns[section];
527 else if (role == Qt::TextAlignmentRole)
529 return column_alignments[section];
530 } else if (role == Qt::ToolTipRole)
535 return tr("Transaction status. Hover over this field to show number of confirmations.");
537 return tr("Date and time that the transaction was received.");
539 return tr("Short description of the transaction.");
541 return tr("Amount removed from balance.");
543 return tr("Amount added to balance.");
550 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
552 return QAbstractTableModel::flags(index);
555 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
558 TransactionRecord *data = priv->index(row);
561 return createIndex(row, column, priv->index(row));
565 return QModelIndex();