1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
6 #include "transactiondesc.h"
14 #include <QtAlgorithms>
16 const QString TransactionTableModel::Sent = "s";
17 const QString TransactionTableModel::Received = "r";
18 const QString TransactionTableModel::Other = "o";
20 /* Comparison operator for sort/binary search of model tx list */
23 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
25 return a.hash < b.hash;
27 bool operator()(const TransactionRecord &a, const uint256 &b) const
31 bool operator()(const uint256 &a, const TransactionRecord &b) const
37 /* Private implementation */
38 struct TransactionTablePriv
40 TransactionTablePriv(TransactionTableModel *parent):
45 TransactionTableModel *parent;
47 /* Local cache of wallet.
48 * As it is in the same order as the CWallet, by definition
49 * this is sorted by sha256.
51 QList<TransactionRecord> cachedWallet;
55 #ifdef WALLET_UPDATE_DEBUG
56 qDebug() << "refreshWallet";
58 /* Query entire wallet from core.
61 CRITICAL_BLOCK(cs_mapWallet)
63 for(std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
65 cachedWallet.append(TransactionRecord::decomposeTransaction(it->second));
70 /* Update our model of the wallet incrementally.
71 Call with list of hashes of transactions that were added, removed or changed.
73 void updateWallet(const QList<uint256> &updated)
75 /* Walk through updated transactions, update model as needed.
77 #ifdef WALLET_UPDATE_DEBUG
78 qDebug() << "updateWallet";
80 /* Sort update list, and iterate through it in reverse, so that model updates
81 can be emitted from end to beginning (so that earlier updates will not influence
82 the indices of latter ones).
84 QList<uint256> updated_sorted = updated;
85 qSort(updated_sorted);
87 CRITICAL_BLOCK(cs_mapWallet)
89 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
91 const uint256 &hash = updated_sorted.at(update_idx);
92 /* Find transaction in wallet */
93 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
94 bool inWallet = mi != mapWallet.end();
95 /* Find bounds of this transaction in model */
96 QList<TransactionRecord>::iterator lower = qLowerBound(
97 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
98 QList<TransactionRecord>::iterator upper = qUpperBound(
99 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
100 int lowerIndex = (lower - cachedWallet.begin());
101 int upperIndex = (upper - cachedWallet.begin());
103 bool inModel = false;
109 #ifdef WALLET_UPDATE_DEBUG
110 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
111 << lowerIndex << "-" << upperIndex;
114 if(inWallet && !inModel)
116 /* Added -- insert at the right position */
117 QList<TransactionRecord> toInsert =
118 TransactionRecord::decomposeTransaction(mi->second);
119 if(!toInsert.isEmpty()) /* only if something to insert */
121 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
122 int insert_idx = lowerIndex;
123 foreach(const TransactionRecord &rec, toInsert)
125 cachedWallet.insert(insert_idx, rec);
128 parent->endInsertRows();
131 else if(!inWallet && inModel)
133 /* Removed -- remove entire transaction from table */
134 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
135 cachedWallet.erase(lower, upper);
136 parent->endRemoveRows();
138 else if(inWallet && inModel)
140 /* Updated -- nothing to do, status update will take care of this */
148 return cachedWallet.size();
151 TransactionRecord *index(int idx)
153 if(idx >= 0 && idx < cachedWallet.size())
155 TransactionRecord *rec = &cachedWallet[idx];
157 /* If a status update is needed (blocks came in since last check),
158 update the status of this transaction from the wallet. Otherwise,
159 simply re-use the cached status.
161 if(rec->statusUpdateNeeded())
163 CRITICAL_BLOCK(cs_mapWallet)
165 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(rec->hash);
167 if(mi != mapWallet.end())
169 rec->updateStatus(mi->second);
181 QString describe(TransactionRecord *rec)
183 CRITICAL_BLOCK(cs_mapWallet)
185 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(rec->hash);
186 if(mi != mapWallet.end())
188 return QString::fromStdString(TransactionDesc::toHTML(mi->second));
196 /* Credit and Debit columns are right-aligned as they contain numbers */
197 static int column_alignments[] = {
198 Qt::AlignLeft|Qt::AlignVCenter,
199 Qt::AlignLeft|Qt::AlignVCenter,
200 Qt::AlignLeft|Qt::AlignVCenter,
201 Qt::AlignRight|Qt::AlignVCenter,
202 Qt::AlignRight|Qt::AlignVCenter,
203 Qt::AlignLeft|Qt::AlignVCenter
206 TransactionTableModel::TransactionTableModel(QObject *parent):
207 QAbstractTableModel(parent),
208 priv(new TransactionTablePriv(this))
210 columns << tr("Status") << tr("Date") << tr("Description") << tr("Debit") << tr("Credit");
212 priv->refreshWallet();
214 QTimer *timer = new QTimer(this);
215 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
216 timer->start(MODEL_UPDATE_DELAY);
219 TransactionTableModel::~TransactionTableModel()
224 void TransactionTableModel::update()
226 QList<uint256> updated;
228 /* Check if there are changes to wallet map */
229 TRY_CRITICAL_BLOCK(cs_mapWallet)
231 if(!vWalletUpdated.empty())
233 BOOST_FOREACH(uint256 hash, vWalletUpdated)
235 updated.append(hash);
237 vWalletUpdated.clear();
243 priv->updateWallet(updated);
245 /* Status (number of confirmations) and (possibly) description
246 columns changed for all rows.
248 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
249 emit dataChanged(index(0, Description), index(priv->size()-1, Description));
253 int TransactionTableModel::rowCount(const QModelIndex &parent) const
259 int TransactionTableModel::columnCount(const QModelIndex &parent) const
262 return columns.length();
265 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
269 switch(wtx->status.status)
271 case TransactionStatus::OpenUntilBlock:
272 status = tr("Open for %n block(s)","",wtx->status.open_for);
274 case TransactionStatus::OpenUntilDate:
275 status = tr("Open until ") + GUIUtil::DateTimeStr(wtx->status.open_for);
277 case TransactionStatus::Offline:
278 status = tr("Offline (%1)").arg(wtx->status.depth);
280 case TransactionStatus::Unconfirmed:
281 status = tr("Unconfirmed (%1)").arg(wtx->status.depth);
283 case TransactionStatus::HaveConfirmations:
284 status = tr("Confirmed (%1)").arg(wtx->status.depth);
288 return QVariant(status);
291 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
295 return QVariant(GUIUtil::DateTimeStr(wtx->time));
303 /* Look up address in address book, if found return
304 address[0:12]... (label)
305 otherwise just return address
307 std::string lookupAddress(const std::string &address)
309 std::string description;
310 CRITICAL_BLOCK(cs_mapAddressBook)
312 std::map<std::string, std::string>::iterator mi = mapAddressBook.find(address);
313 if (mi != mapAddressBook.end() && !(*mi).second.empty())
315 std::string label = (*mi).second;
316 description += address.substr(0,12) + "... ";
317 description += "(" + label + ")";
321 description += address;
327 QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
333 case TransactionRecord::RecvWithAddress:
334 description = tr("Received with: ") + QString::fromStdString(lookupAddress(wtx->address));
336 case TransactionRecord::RecvFromIP:
337 description = tr("Received from IP: ") + QString::fromStdString(wtx->address);
339 case TransactionRecord::SendToAddress:
340 description = tr("Sent to: ") + QString::fromStdString(lookupAddress(wtx->address));
342 case TransactionRecord::SendToIP:
343 description = tr("Sent to IP: ") + QString::fromStdString(wtx->address);
345 case TransactionRecord::SendToSelf:
346 description = tr("Payment to yourself");
348 case TransactionRecord::Generated:
349 switch(wtx->status.maturity)
351 case TransactionStatus::Immature:
352 description = tr("Generated (matures in %n more blocks)", "",
353 wtx->status.matures_in);
355 case TransactionStatus::Mature:
356 description = tr("Generated");
358 case TransactionStatus::MaturesWarning:
359 description = tr("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
361 case TransactionStatus::NotAccepted:
362 description = tr("Generated (not accepted)");
367 return QVariant(description);
370 QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
374 QString str = QString::fromStdString(FormatMoney(wtx->debit));
375 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
377 str = QString("[") + str + QString("]");
379 return QVariant(str);
387 QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
391 QString str = QString::fromStdString(FormatMoney(wtx->credit));
392 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
394 str = QString("[") + str + QString("]");
396 return QVariant(str);
404 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
406 switch(wtx->status.status)
408 case TransactionStatus::OpenUntilBlock:
409 case TransactionStatus::OpenUntilDate:
410 return QColor(64,64,255);
412 case TransactionStatus::Offline:
413 return QColor(192,192,192);
414 case TransactionStatus::Unconfirmed:
415 if(wtx->status.depth)
417 return QIcon(":/icons/transaction1");
421 return QIcon(":/icons/transaction0");
423 case TransactionStatus::HaveConfirmations:
424 return QIcon(":/icons/transaction2");
426 return QColor(0,0,0);
429 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
433 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
435 if(role == Qt::DecorationRole)
437 if(index.column() == Status)
439 return formatTxDecoration(rec);
442 else if(role == Qt::DisplayRole)
444 /* Delegate to specific column handlers */
445 switch(index.column())
448 // return formatTxStatus(rec);
450 return formatTxDate(rec);
452 return formatTxDescription(rec);
454 return formatTxDebit(rec);
456 return formatTxCredit(rec);
459 else if(role == Qt::EditRole)
461 /* Edit role is used for sorting so return the real values */
462 switch(index.column())
465 return QString::fromStdString(rec->status.sortKey);
469 return formatTxDescription(rec);
476 else if (role == Qt::ToolTipRole)
478 if(index.column() == Status)
480 return formatTxStatus(rec);
483 else if (role == Qt::TextAlignmentRole)
485 return column_alignments[index.column()];
487 else if (role == Qt::ForegroundRole)
489 /* Non-confirmed transactions are grey */
490 if(rec->status.confirmed)
492 return QColor(0, 0, 0);
496 return QColor(128, 128, 128);
499 else if (role == TypeRole)
501 /* Role for filtering tabs by type */
504 case TransactionRecord::RecvWithAddress:
505 case TransactionRecord::RecvFromIP:
506 return TransactionTableModel::Received;
507 case TransactionRecord::SendToAddress:
508 case TransactionRecord::SendToIP:
509 case TransactionRecord::SendToSelf:
510 return TransactionTableModel::Sent;
512 return TransactionTableModel::Other;
515 else if (role == LongDescriptionRole)
517 return priv->describe(rec);
522 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
524 if(orientation == Qt::Horizontal)
526 if(role == Qt::DisplayRole)
528 return columns[section];
530 else if (role == Qt::TextAlignmentRole)
532 return column_alignments[section];
533 } else if (role == Qt::ToolTipRole)
538 return tr("Transaction status. Hover over this field to show number of transactions.");
540 return tr("Date and time that the transaction was received.");
542 return tr("Short description of the transaction.");
544 return tr("Amount removed from balance.");
546 return tr("Amount added to balance.");
553 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
555 return QAbstractTableModel::flags(index);
558 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
561 TransactionRecord *data = priv->index(row);
564 return createIndex(row, column, priv->index(row));
568 return QModelIndex();