1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
16 #include <QtAlgorithms>
18 // Credit and Debit columns are right-aligned as they contain numbers
19 static int column_alignments[] = {
20 Qt::AlignLeft|Qt::AlignVCenter,
21 Qt::AlignLeft|Qt::AlignVCenter,
22 Qt::AlignLeft|Qt::AlignVCenter,
23 Qt::AlignLeft|Qt::AlignVCenter,
24 Qt::AlignRight|Qt::AlignVCenter
27 // Comparison operator for sort/binary search of model tx list
30 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
32 return a.hash < b.hash;
34 bool operator()(const TransactionRecord &a, const uint256 &b) const
38 bool operator()(const uint256 &a, const TransactionRecord &b) const
44 // Private implementation
45 struct TransactionTablePriv
47 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
53 TransactionTableModel *parent;
55 /* Local cache of wallet.
56 * As it is in the same order as the CWallet, by definition
57 * this is sorted by sha256.
59 QList<TransactionRecord> cachedWallet;
61 /* Query entire wallet anew from core.
65 #ifdef WALLET_UPDATE_DEBUG
66 qDebug() << "refreshWallet";
69 CRITICAL_BLOCK(wallet->cs_mapWallet)
71 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
73 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
78 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
79 with that of the core.
81 Call with list of hashes of transactions that were added, removed or changed.
83 void updateWallet(const QList<uint256> &updated)
85 // Walk through updated transactions, update model as needed.
86 #ifdef WALLET_UPDATE_DEBUG
87 qDebug() << "updateWallet";
89 // Sort update list, and iterate through it in reverse, so that model updates
90 // can be emitted from end to beginning (so that earlier updates will not influence
91 // the indices of latter ones).
92 QList<uint256> updated_sorted = updated;
93 qSort(updated_sorted);
95 CRITICAL_BLOCK(wallet->cs_mapWallet)
97 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
99 const uint256 &hash = updated_sorted.at(update_idx);
100 /* Find transaction in wallet */
101 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
102 bool inWallet = mi != wallet->mapWallet.end();
103 /* Find bounds of this transaction in model */
104 QList<TransactionRecord>::iterator lower = qLowerBound(
105 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
106 QList<TransactionRecord>::iterator upper = qUpperBound(
107 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
108 int lowerIndex = (lower - cachedWallet.begin());
109 int upperIndex = (upper - cachedWallet.begin());
111 // Determine if transaction is in model already
112 bool inModel = false;
118 #ifdef WALLET_UPDATE_DEBUG
119 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
120 << lowerIndex << "-" << upperIndex;
123 if(inWallet && !inModel)
125 // Added -- insert at the right position
126 QList<TransactionRecord> toInsert =
127 TransactionRecord::decomposeTransaction(wallet, mi->second);
128 if(!toInsert.isEmpty()) /* only if something to insert */
130 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
131 int insert_idx = lowerIndex;
132 foreach(const TransactionRecord &rec, toInsert)
134 cachedWallet.insert(insert_idx, rec);
137 parent->endInsertRows();
140 else if(!inWallet && inModel)
142 // Removed -- remove entire transaction from table
143 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
144 cachedWallet.erase(lower, upper);
145 parent->endRemoveRows();
147 else if(inWallet && inModel)
149 // Updated -- nothing to do, status update will take care of this
157 return cachedWallet.size();
160 TransactionRecord *index(int idx)
162 if(idx >= 0 && idx < cachedWallet.size())
164 TransactionRecord *rec = &cachedWallet[idx];
166 // If a status update is needed (blocks came in since last check),
167 // update the status of this transaction from the wallet. Otherwise,
168 // simply re-use the cached status.
169 if(rec->statusUpdateNeeded())
171 CRITICAL_BLOCK(wallet->cs_mapWallet)
173 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
175 if(mi != wallet->mapWallet.end())
177 rec->updateStatus(mi->second);
189 QString describe(TransactionRecord *rec)
191 CRITICAL_BLOCK(wallet->cs_mapWallet)
193 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
194 if(mi != wallet->mapWallet.end())
196 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
204 TransactionTableModel::TransactionTableModel(CWallet* wallet, QObject *parent):
205 QAbstractTableModel(parent),
207 priv(new TransactionTablePriv(wallet, this))
209 columns << tr("Status") << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
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(wallet->cs_mapWallet)
230 if(!wallet->vWalletUpdated.empty())
232 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
234 updated.append(hash);
236 wallet->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, ToAddress), index(priv->size()-1, ToAddress));
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 %1").arg(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/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
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 label for address in address book, if not found return empty string.
302 This should really move to the wallet class.
304 QString TransactionTableModel::labelForAddress(const std::string &address) const
306 CRITICAL_BLOCK(wallet->cs_mapAddressBook)
308 std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
309 if (mi != wallet->mapAddressBook.end())
311 return QString::fromStdString(mi->second);
317 /* Look up address in address book, if found return
318 address[0:12]... (label)
319 otherwise just return address
321 QString TransactionTableModel::lookupAddress(const std::string &address) const
323 QString label = labelForAddress(address);
327 description = QString::fromStdString(address);
331 description = label + QString(" (") + QString::fromStdString(address.substr(0,12)) + QString("...)");
336 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
342 case TransactionRecord::RecvWithAddress:
343 description = tr("Received with");
345 case TransactionRecord::RecvFromIP:
346 description = tr("Received from IP");
348 case TransactionRecord::SendToAddress:
349 description = tr("Sent to");
351 case TransactionRecord::SendToIP:
352 description = tr("Sent to IP");
354 case TransactionRecord::SendToSelf:
355 description = tr("Payment to yourself");
357 case TransactionRecord::Generated:
358 description = tr("Generated");
361 return QVariant(description);
364 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
370 case TransactionRecord::RecvWithAddress:
371 description = lookupAddress(wtx->address);
373 case TransactionRecord::RecvFromIP:
374 description = QString::fromStdString(wtx->address);
376 case TransactionRecord::SendToAddress:
377 description = lookupAddress(wtx->address);
379 case TransactionRecord::SendToIP:
380 description = QString::fromStdString(wtx->address);
382 case TransactionRecord::SendToSelf:
383 description = QString();
385 case TransactionRecord::Generated:
386 switch(wtx->status.maturity)
388 case TransactionStatus::Immature:
389 description = tr("(matures in %n more blocks)", "",
390 wtx->status.matures_in);
392 case TransactionStatus::Mature:
393 description = QString();
395 case TransactionStatus::MaturesWarning:
396 description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
398 case TransactionStatus::NotAccepted:
399 description = tr("(not accepted)");
404 return QVariant(description);
407 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
409 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
410 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
412 str = QString("[") + str + QString("]");
414 return QVariant(str);
417 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
419 switch(wtx->status.status)
421 case TransactionStatus::OpenUntilBlock:
422 case TransactionStatus::OpenUntilDate:
423 return QColor(64,64,255);
425 case TransactionStatus::Offline:
426 return QColor(192,192,192);
427 case TransactionStatus::Unconfirmed:
428 switch(wtx->status.depth)
430 case 0: return QIcon(":/icons/transaction_0");
431 case 1: return QIcon(":/icons/transaction_1");
432 case 2: return QIcon(":/icons/transaction_2");
433 case 3: return QIcon(":/icons/transaction_3");
434 case 4: return QIcon(":/icons/transaction_4");
435 default: return QIcon(":/icons/transaction_5");
437 case TransactionStatus::HaveConfirmations:
438 return QIcon(":/icons/transaction_confirmed");
440 return QColor(0,0,0);
443 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
447 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
449 if(role == Qt::DecorationRole)
451 if(index.column() == Status)
453 return formatTxDecoration(rec);
456 else if(role == Qt::DisplayRole)
458 // Delegate to specific column handlers
459 switch(index.column())
462 return formatTxDate(rec);
464 return formatTxType(rec);
466 return formatTxToAddress(rec);
468 return formatTxAmount(rec);
471 else if(role == Qt::EditRole)
473 // Edit role is used for sorting so return the real values
474 switch(index.column())
477 return QString::fromStdString(rec->status.sortKey);
481 return formatTxType(rec);
483 return formatTxToAddress(rec);
485 return rec->credit + rec->debit;
488 else if (role == Qt::ToolTipRole)
490 if(index.column() == Status)
492 return formatTxStatus(rec);
495 else if (role == Qt::TextAlignmentRole)
497 return column_alignments[index.column()];
499 else if (role == Qt::ForegroundRole)
501 /* Non-confirmed transactions are grey */
502 if(!rec->status.confirmed)
504 return QColor(128, 128, 128);
506 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
508 return QColor(255, 0, 0);
511 else if (role == TypeRole)
515 else if (role == DateRole)
517 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
519 else if (role == LongDescriptionRole)
521 return priv->describe(rec);
523 else if (role == AddressRole)
525 return QString::fromStdString(rec->address);
527 else if (role == LabelRole)
529 return labelForAddress(rec->address);
531 else if (role == AbsoluteAmountRole)
533 return llabs(rec->credit + rec->debit);
538 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
540 if(orientation == Qt::Horizontal)
542 if(role == Qt::DisplayRole)
544 return columns[section];
546 else if (role == Qt::TextAlignmentRole)
548 return column_alignments[section];
549 } else if (role == Qt::ToolTipRole)
554 return tr("Transaction status. Hover over this field to show number of confirmations.");
556 return tr("Date and time that the transaction was received.");
558 return tr("Type of transaction.");
560 return tr("Destination address of transaction.");
562 return tr("Amount removed from or added to balance.");
569 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
571 return QAbstractTableModel::flags(index);
574 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
577 TransactionRecord *data = priv->index(row);
580 return createIndex(row, column, priv->index(row));
584 return QModelIndex();