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 switch(wtx->status.depth)
417 case 0: return QIcon(":/icons/transaction_0");
418 case 1: return QIcon(":/icons/transaction_1");
419 case 2: return QIcon(":/icons/transaction_2");
420 case 3: return QIcon(":/icons/transaction_3");
421 case 4: return QIcon(":/icons/transaction_4");
422 default: return QIcon(":/icons/transaction_5");
424 case TransactionStatus::HaveConfirmations:
425 return QIcon(":/icons/transaction_confirmed");
427 return QColor(0,0,0);
430 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
434 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
436 if(role == Qt::DecorationRole)
438 if(index.column() == Status)
440 return formatTxDecoration(rec);
443 else if(role == Qt::DisplayRole)
445 /* Delegate to specific column handlers */
446 switch(index.column())
449 // return formatTxStatus(rec);
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 transactions.");
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();