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;
53 /* Query entire wallet anew from core.
57 #ifdef WALLET_UPDATE_DEBUG
58 qDebug() << "refreshWallet";
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, to synchronize our model of the wallet
71 with that of the core.
73 Call with list of hashes of transactions that were added, removed or changed.
75 void updateWallet(const QList<uint256> &updated)
77 // Walk through updated transactions, update model as needed.
78 #ifdef WALLET_UPDATE_DEBUG
79 qDebug() << "updateWallet";
81 // Sort update list, and iterate through it in reverse, so that model updates
82 // can be emitted from end to beginning (so that earlier updates will not influence
83 // 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.
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.
246 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
247 emit dataChanged(index(0, Description), index(priv->size()-1, Description));
251 int TransactionTableModel::rowCount(const QModelIndex &parent) const
257 int TransactionTableModel::columnCount(const QModelIndex &parent) const
260 return columns.length();
263 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
267 switch(wtx->status.status)
269 case TransactionStatus::OpenUntilBlock:
270 status = tr("Open for %n block(s)","",wtx->status.open_for);
272 case TransactionStatus::OpenUntilDate:
273 status = tr("Open until ") + GUIUtil::DateTimeStr(wtx->status.open_for);
275 case TransactionStatus::Offline:
276 status = tr("Offline (%1)").arg(wtx->status.depth);
278 case TransactionStatus::Unconfirmed:
279 status = tr("Unconfirmed (%1)").arg(wtx->status.depth);
281 case TransactionStatus::HaveConfirmations:
282 status = tr("Confirmed (%1)").arg(wtx->status.depth);
286 return QVariant(status);
289 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
293 return QVariant(GUIUtil::DateTimeStr(wtx->time));
301 /* Look up address in address book, if found return
302 address[0:12]... (label)
303 otherwise just return address
305 std::string lookupAddress(const std::string &address)
307 std::string description;
308 CRITICAL_BLOCK(cs_mapAddressBook)
310 std::map<std::string, std::string>::iterator mi = mapAddressBook.find(address);
311 if (mi != mapAddressBook.end() && !(*mi).second.empty())
313 std::string label = (*mi).second;
314 description += address.substr(0,12) + "... ";
315 description += "(" + label + ")";
319 description += address;
325 QVariant TransactionTableModel::formatTxDescription(const TransactionRecord *wtx) const
331 case TransactionRecord::RecvWithAddress:
332 description = tr("Received with: ") + QString::fromStdString(lookupAddress(wtx->address));
334 case TransactionRecord::RecvFromIP:
335 description = tr("Received from IP: ") + QString::fromStdString(wtx->address);
337 case TransactionRecord::SendToAddress:
338 description = tr("Sent to: ") + QString::fromStdString(lookupAddress(wtx->address));
340 case TransactionRecord::SendToIP:
341 description = tr("Sent to IP: ") + QString::fromStdString(wtx->address);
343 case TransactionRecord::SendToSelf:
344 description = tr("Payment to yourself");
346 case TransactionRecord::Generated:
347 switch(wtx->status.maturity)
349 case TransactionStatus::Immature:
350 description = tr("Generated (matures in %n more blocks)", "",
351 wtx->status.matures_in);
353 case TransactionStatus::Mature:
354 description = tr("Generated");
356 case TransactionStatus::MaturesWarning:
357 description = tr("Generated - Warning: This block was not received by any other nodes and will probably not be accepted!");
359 case TransactionStatus::NotAccepted:
360 description = tr("Generated (not accepted)");
365 return QVariant(description);
368 QVariant TransactionTableModel::formatTxDebit(const TransactionRecord *wtx) const
372 QString str = QString::fromStdString(FormatMoney(wtx->debit));
373 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
375 str = QString("[") + str + QString("]");
377 return QVariant(str);
385 QVariant TransactionTableModel::formatTxCredit(const TransactionRecord *wtx) const
389 QString str = QString::fromStdString(FormatMoney(wtx->credit));
390 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
392 str = QString("[") + str + QString("]");
394 return QVariant(str);
402 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
404 switch(wtx->status.status)
406 case TransactionStatus::OpenUntilBlock:
407 case TransactionStatus::OpenUntilDate:
408 return QColor(64,64,255);
410 case TransactionStatus::Offline:
411 return QColor(192,192,192);
412 case TransactionStatus::Unconfirmed:
413 switch(wtx->status.depth)
415 case 0: return QIcon(":/icons/transaction_0");
416 case 1: return QIcon(":/icons/transaction_1");
417 case 2: return QIcon(":/icons/transaction_2");
418 case 3: return QIcon(":/icons/transaction_3");
419 case 4: return QIcon(":/icons/transaction_4");
420 default: return QIcon(":/icons/transaction_5");
422 case TransactionStatus::HaveConfirmations:
423 return QIcon(":/icons/transaction_confirmed");
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 formatTxDate(rec);
449 return formatTxDescription(rec);
451 return formatTxDebit(rec);
453 return formatTxCredit(rec);
456 else if(role == Qt::EditRole)
458 // Edit role is used for sorting so return the real values
459 switch(index.column())
462 return QString::fromStdString(rec->status.sortKey);
466 return formatTxDescription(rec);
473 else if (role == Qt::ToolTipRole)
475 if(index.column() == Status)
477 return formatTxStatus(rec);
480 else if (role == Qt::TextAlignmentRole)
482 return column_alignments[index.column()];
484 else if (role == Qt::ForegroundRole)
486 /* Non-confirmed transactions are grey */
487 if(!rec->status.confirmed)
489 return QColor(128, 128, 128);
492 else if (role == TypeRole)
494 /* Role for filtering tabs by type */
497 case TransactionRecord::RecvWithAddress:
498 case TransactionRecord::RecvFromIP:
499 return TransactionTableModel::Received;
500 case TransactionRecord::SendToAddress:
501 case TransactionRecord::SendToIP:
502 case TransactionRecord::SendToSelf:
503 return TransactionTableModel::Sent;
505 return TransactionTableModel::Other;
508 else if (role == LongDescriptionRole)
510 return priv->describe(rec);
515 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
517 if(orientation == Qt::Horizontal)
519 if(role == Qt::DisplayRole)
521 return columns[section];
523 else if (role == Qt::TextAlignmentRole)
525 return column_alignments[section];
526 } else if (role == Qt::ToolTipRole)
531 return tr("Transaction status. Hover over this field to show number of transactions.");
533 return tr("Date and time that the transaction was received.");
535 return tr("Short description of the transaction.");
537 return tr("Amount removed from balance.");
539 return tr("Amount added to balance.");
546 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
548 return QAbstractTableModel::flags(index);
551 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
554 TransactionRecord *data = priv->index(row);
557 return createIndex(row, column, priv->index(row));
561 return QModelIndex();