1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
6 #include "transactiondesc.h"
13 #include <QtAlgorithms>
15 const QString TransactionTableModel::Sent = "s";
16 const QString TransactionTableModel::Received = "r";
17 const QString TransactionTableModel::Other = "o";
19 /* Comparison operator for sort/binary search of model tx list */
22 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
24 return a.hash < b.hash;
26 bool operator()(const TransactionRecord &a, const uint256 &b) const
30 bool operator()(const uint256 &a, const TransactionRecord &b) const
36 /* Private implementation */
37 struct TransactionTablePriv
39 TransactionTablePriv(TransactionTableModel *parent):
44 TransactionTableModel *parent;
46 /* Local cache of wallet.
47 * As it is in the same order as the CWallet, by definition
48 * this is sorted by sha256.
50 QList<TransactionRecord> cachedWallet;
54 #ifdef WALLET_UPDATE_DEBUG
55 qDebug() << "refreshWallet";
57 /* Query entire wallet from core.
60 CRITICAL_BLOCK(cs_mapWallet)
62 for(std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
64 cachedWallet.append(TransactionRecord::decomposeTransaction(it->second));
69 /* Update our model of the wallet incrementally.
70 Call with list of hashes of transactions that were added, removed or changed.
72 void updateWallet(const QList<uint256> &updated)
74 /* Walk through updated transactions, update model as needed.
76 #ifdef WALLET_UPDATE_DEBUG
77 qDebug() << "updateWallet";
79 /* Sort update list, and iterate through it in reverse, so that model updates
80 can be emitted from end to beginning (so that earlier updates will not influence
81 the indices of latter ones).
83 QList<uint256> updated_sorted = updated;
84 qSort(updated_sorted);
86 CRITICAL_BLOCK(cs_mapWallet)
88 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
90 const uint256 &hash = updated_sorted.at(update_idx);
91 /* Find transaction in wallet */
92 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(hash);
93 bool inWallet = mi != mapWallet.end();
94 /* Find bounds of this transaction in model */
95 QList<TransactionRecord>::iterator lower = qLowerBound(
96 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
97 QList<TransactionRecord>::iterator upper = qUpperBound(
98 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
99 int lowerIndex = (lower - cachedWallet.begin());
100 int upperIndex = (upper - cachedWallet.begin());
102 bool inModel = false;
108 #ifdef WALLET_UPDATE_DEBUG
109 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
110 << lowerIndex << "-" << upperIndex;
113 if(inWallet && !inModel)
116 QList<TransactionRecord> toInsert =
117 TransactionRecord::decomposeTransaction(mi->second);
118 if(!toInsert.isEmpty()) /* only if something to insert */
120 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
121 int insert_idx = lowerIndex;
122 foreach(const TransactionRecord &rec, toInsert)
124 cachedWallet.insert(insert_idx, rec);
127 parent->endInsertRows();
130 else if(!inWallet && inModel)
133 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
134 cachedWallet.erase(lower, upper);
135 parent->endRemoveRows();
137 else if(inWallet && inModel)
139 /* Updated -- nothing to do, status update will take care of this */
147 return cachedWallet.size();
150 TransactionRecord *index(int idx)
152 if(idx >= 0 && idx < cachedWallet.size())
154 TransactionRecord *rec = &cachedWallet[idx];
156 /* If a status update is needed (blocks came in since last check),
157 update the status of this transaction from the wallet. Otherwise,
158 simply re-use the cached status.
160 if(rec->statusUpdateNeeded())
162 CRITICAL_BLOCK(cs_mapWallet)
164 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(rec->hash);
166 if(mi != mapWallet.end())
168 rec->updateStatus(mi->second);
180 QString describe(TransactionRecord *rec)
182 CRITICAL_BLOCK(cs_mapWallet)
184 std::map<uint256, CWalletTx>::iterator mi = mapWallet.find(rec->hash);
185 if(mi != mapWallet.end())
187 return QString::fromStdString(TransactionDesc::toHTML(mi->second));
195 /* Credit and Debit columns are right-aligned as they contain numbers */
196 static int column_alignments[] = {
197 Qt::AlignLeft|Qt::AlignVCenter,
198 Qt::AlignLeft|Qt::AlignVCenter,
199 Qt::AlignLeft|Qt::AlignVCenter,
200 Qt::AlignRight|Qt::AlignVCenter,
201 Qt::AlignRight|Qt::AlignVCenter,
202 Qt::AlignLeft|Qt::AlignVCenter
205 TransactionTableModel::TransactionTableModel(QObject *parent):
206 QAbstractTableModel(parent),
207 priv(new TransactionTablePriv(this))
209 columns << tr("Status") << tr("Date") << tr("Description") << tr("Debit") << tr("Credit");
211 priv->refreshWallet();
213 QTimer *timer = new QTimer(this);
214 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
215 timer->start(MODEL_UPDATE_DELAY);
218 TransactionTableModel::~TransactionTableModel()
223 void TransactionTableModel::update()
225 QList<uint256> updated;
227 /* Check if there are changes to wallet map */
228 TRY_CRITICAL_BLOCK(cs_mapWallet)
230 if(!vWalletUpdated.empty())
232 BOOST_FOREACH(uint256 hash, vWalletUpdated)
234 updated.append(hash);
236 vWalletUpdated.clear();
242 priv->updateWallet(updated);
244 /* Status (number of confirmations) and (possibly) description
245 columns changed for all rows.
247 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
248 emit dataChanged(index(0, Description), index(priv->size()-1, Description));
252 int TransactionTableModel::rowCount(const QModelIndex &parent) const
258 int TransactionTableModel::columnCount(const QModelIndex &parent) const
261 return columns.length();
264 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
268 switch(wtx->status.status)
270 case TransactionStatus::OpenUntilBlock:
271 status = tr("Open for %n block(s)","",wtx->status.open_for);
273 case TransactionStatus::OpenUntilDate:
274 status = tr("Open until ") + GUIUtil::DateTimeStr(wtx->status.open_for);
276 case TransactionStatus::Offline:
277 status = tr("Offline (%1)").arg(wtx->status.depth);
279 case TransactionStatus::Unconfirmed:
280 status = tr("Unconfirmed (%1)").arg(wtx->status.depth);
282 case TransactionStatus::HaveConfirmations:
283 status = tr("Confirmed (%1)").arg(wtx->status.depth);
287 return QVariant(status);
290 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
294 return QVariant(GUIUtil::DateTimeStr(wtx->time));
302 /* Look up address in address book, if found return
303 address[0:12]... (label)
304 otherwise just return address
306 std::string lookupAddress(const std::string &address)
308 std::string description;
309 CRITICAL_BLOCK(cs_mapAddressBook)
311 std::map<std::string, std::string>::iterator mi = mapAddressBook.find(address);
312 if (mi != mapAddressBook.end() && !(*mi).second.empty())
314 std::string label = (*mi).second;
315 description += address.substr(0,12) + "... ";
316 description += "(" + label + ")";
320 description += address;
326 QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
332 case TransactionRecord::RecvWithAddress:
333 description = tr("Received with: ") + QString::fromStdString(lookupAddress(wtx->address));
335 case TransactionRecord::RecvFromIP:
336 description = tr("Received from IP: ") + QString::fromStdString(wtx->address);
338 case TransactionRecord::SendToAddress:
339 description = tr("Sent to: ") + QString::fromStdString(lookupAddress(wtx->address));
341 case TransactionRecord::SendToIP:
342 description = tr("Sent to IP: ") + QString::fromStdString(wtx->address);
344 case TransactionRecord::SendToSelf:
345 description = tr("Payment to yourself");
347 case TransactionRecord::Generated:
348 switch(wtx->status.maturity)
350 case TransactionStatus::Immature:
351 description = tr("Generated (matures in %n more blocks)", "",
352 wtx->status.matures_in);
354 case TransactionStatus::Mature:
355 description = tr("Generated");
357 case TransactionStatus::MaturesWarning:
358 description = tr("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
360 case TransactionStatus::NotAccepted:
361 description = tr("Generated (not accepted)");
366 return QVariant(description);
369 QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
373 QString str = QString::fromStdString(FormatMoney(wtx->debit));
374 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
376 str = QString("[") + str + QString("]");
378 return QVariant(str);
386 QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
390 QString str = QString::fromStdString(FormatMoney(wtx->credit));
391 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
393 str = QString("[") + str + QString("]");
395 return QVariant(str);
403 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
405 switch(wtx->status.status)
407 case TransactionStatus::OpenUntilBlock:
408 case TransactionStatus::OpenUntilDate:
409 return QColor(64,64,255);
411 case TransactionStatus::Offline:
412 return QColor(192,192,192);
413 case TransactionStatus::Unconfirmed:
414 if(wtx->status.depth)
416 return QColor(255,0,0);
420 return QColor(192,192,192);
422 case TransactionStatus::HaveConfirmations:
423 return QColor(0,255,0);
425 return QColor(0,0,0);
428 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
432 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
434 if(role == Qt::DecorationRole)
436 if(index.column() == Status)
438 return formatTxDecoration(rec);
441 else if(role == Qt::DisplayRole)
443 /* Delegate to specific column handlers */
444 switch(index.column())
447 // return formatTxStatus(rec);
449 return formatTxDate(rec);
451 return formatTxDescription(rec);
453 return formatTxDebit(rec);
455 return formatTxCredit(rec);
458 else if(role == Qt::EditRole)
460 /* Edit role is used for sorting so return the real values */
461 switch(index.column())
464 return QString::fromStdString(rec->status.sortKey);
468 return formatTxDescription(rec);
475 else if (role == Qt::ToolTipRole)
477 if(index.column() == Status)
479 return formatTxStatus(rec);
482 else if (role == Qt::TextAlignmentRole)
484 return column_alignments[index.column()];
486 else if (role == Qt::ForegroundRole)
488 /* Non-confirmed transactions are grey */
489 if(rec->status.confirmed)
491 return QColor(0, 0, 0);
495 return QColor(128, 128, 128);
498 else if (role == TypeRole)
500 /* Role for filtering tabs by type */
503 case TransactionRecord::RecvWithAddress:
504 case TransactionRecord::RecvFromIP:
505 return TransactionTableModel::Received;
506 case TransactionRecord::SendToAddress:
507 case TransactionRecord::SendToIP:
508 case TransactionRecord::SendToSelf:
509 return TransactionTableModel::Sent;
511 return TransactionTableModel::Other;
514 else if (role == LongDescriptionRole)
516 return priv->describe(rec);
521 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
523 if(orientation == Qt::Horizontal)
525 if(role == Qt::DisplayRole)
527 return columns[section];
529 else if (role == Qt::TextAlignmentRole)
531 return column_alignments[section];
532 } else if (role == Qt::ToolTipRole)
537 return tr("Transaction status. Hover over this field to show number of transactions.");
539 return tr("Date and time that the transaction was received.");
541 return tr("Short description of the transaction.");
543 return tr("Amount removed from balance.");
545 return tr("Amount added to balance.");
552 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
554 return QAbstractTableModel::flags(index);
557 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
560 TransactionRecord *data = priv->index(row);
563 return createIndex(row, column, priv->index(row));
567 return QModelIndex();