1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
16 #include <QtAlgorithms>
18 const QString TransactionTableModel::Sent = "s";
19 const QString TransactionTableModel::Received = "r";
20 const QString TransactionTableModel::Other = "o";
22 // Credit and Debit columns are right-aligned as they contain numbers
23 static int column_alignments[] = {
24 Qt::AlignLeft|Qt::AlignVCenter,
25 Qt::AlignLeft|Qt::AlignVCenter,
26 Qt::AlignLeft|Qt::AlignVCenter,
27 Qt::AlignLeft|Qt::AlignVCenter,
28 Qt::AlignRight|Qt::AlignVCenter
31 // Comparison operator for sort/binary search of model tx list
34 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
36 return a.hash < b.hash;
38 bool operator()(const TransactionRecord &a, const uint256 &b) const
42 bool operator()(const uint256 &a, const TransactionRecord &b) const
48 // Private implementation
49 struct TransactionTablePriv
51 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
57 TransactionTableModel *parent;
59 /* Local cache of wallet.
60 * As it is in the same order as the CWallet, by definition
61 * this is sorted by sha256.
63 QList<TransactionRecord> cachedWallet;
65 /* Query entire wallet anew from core.
69 #ifdef WALLET_UPDATE_DEBUG
70 qDebug() << "refreshWallet";
73 CRITICAL_BLOCK(wallet->cs_mapWallet)
75 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
77 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
82 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
83 with that of the core.
85 Call with list of hashes of transactions that were added, removed or changed.
87 void updateWallet(const QList<uint256> &updated)
89 // Walk through updated transactions, update model as needed.
90 #ifdef WALLET_UPDATE_DEBUG
91 qDebug() << "updateWallet";
93 // Sort update list, and iterate through it in reverse, so that model updates
94 // can be emitted from end to beginning (so that earlier updates will not influence
95 // the indices of latter ones).
96 QList<uint256> updated_sorted = updated;
97 qSort(updated_sorted);
99 CRITICAL_BLOCK(wallet->cs_mapWallet)
101 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
103 const uint256 &hash = updated_sorted.at(update_idx);
104 /* Find transaction in wallet */
105 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
106 bool inWallet = mi != wallet->mapWallet.end();
107 /* Find bounds of this transaction in model */
108 QList<TransactionRecord>::iterator lower = qLowerBound(
109 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
110 QList<TransactionRecord>::iterator upper = qUpperBound(
111 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
112 int lowerIndex = (lower - cachedWallet.begin());
113 int upperIndex = (upper - cachedWallet.begin());
115 // Determine if transaction is in model already
116 bool inModel = false;
122 #ifdef WALLET_UPDATE_DEBUG
123 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
124 << lowerIndex << "-" << upperIndex;
127 if(inWallet && !inModel)
129 // Added -- insert at the right position
130 QList<TransactionRecord> toInsert =
131 TransactionRecord::decomposeTransaction(wallet, mi->second);
132 if(!toInsert.isEmpty()) /* only if something to insert */
134 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
135 int insert_idx = lowerIndex;
136 foreach(const TransactionRecord &rec, toInsert)
138 cachedWallet.insert(insert_idx, rec);
141 parent->endInsertRows();
144 else if(!inWallet && inModel)
146 // Removed -- remove entire transaction from table
147 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
148 cachedWallet.erase(lower, upper);
149 parent->endRemoveRows();
151 else if(inWallet && inModel)
153 // Updated -- nothing to do, status update will take care of this
161 return cachedWallet.size();
164 TransactionRecord *index(int idx)
166 if(idx >= 0 && idx < cachedWallet.size())
168 TransactionRecord *rec = &cachedWallet[idx];
170 // If a status update is needed (blocks came in since last check),
171 // update the status of this transaction from the wallet. Otherwise,
172 // simply re-use the cached status.
173 if(rec->statusUpdateNeeded())
175 CRITICAL_BLOCK(wallet->cs_mapWallet)
177 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
179 if(mi != wallet->mapWallet.end())
181 rec->updateStatus(mi->second);
193 QString describe(TransactionRecord *rec)
195 CRITICAL_BLOCK(wallet->cs_mapWallet)
197 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
198 if(mi != wallet->mapWallet.end())
200 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
208 TransactionTableModel::TransactionTableModel(CWallet* wallet, QObject *parent):
209 QAbstractTableModel(parent),
211 priv(new TransactionTablePriv(wallet, this))
213 columns << tr("Status") << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
215 priv->refreshWallet();
217 QTimer *timer = new QTimer(this);
218 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
219 timer->start(MODEL_UPDATE_DELAY);
222 TransactionTableModel::~TransactionTableModel()
227 void TransactionTableModel::update()
229 QList<uint256> updated;
231 // Check if there are changes to wallet map
232 TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
234 if(!wallet->vWalletUpdated.empty())
236 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
238 updated.append(hash);
240 wallet->vWalletUpdated.clear();
246 priv->updateWallet(updated);
248 // Status (number of confirmations) and (possibly) description
249 // columns changed for all rows.
250 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
251 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
255 int TransactionTableModel::rowCount(const QModelIndex &parent) const
261 int TransactionTableModel::columnCount(const QModelIndex &parent) const
264 return columns.length();
267 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
271 switch(wtx->status.status)
273 case TransactionStatus::OpenUntilBlock:
274 status = tr("Open for %n block(s)","",wtx->status.open_for);
276 case TransactionStatus::OpenUntilDate:
277 status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
279 case TransactionStatus::Offline:
280 status = tr("Offline (%1)").arg(wtx->status.depth);
282 case TransactionStatus::Unconfirmed:
283 status = tr("Unconfirmed (%1/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
285 case TransactionStatus::HaveConfirmations:
286 status = tr("Confirmed (%1)").arg(wtx->status.depth);
290 return QVariant(status);
293 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
297 return QVariant(GUIUtil::DateTimeStr(wtx->time));
305 /* Look up label for address in address book, if not found return empty string.
306 This should really move to the wallet class.
308 QString TransactionTableModel::labelForAddress(const std::string &address) const
310 CRITICAL_BLOCK(wallet->cs_mapAddressBook)
312 std::map<std::string, std::string>::iterator mi = wallet->mapAddressBook.find(address);
313 if (mi != wallet->mapAddressBook.end())
315 return QString::fromStdString(mi->second);
321 /* Look up address in address book, if found return
322 address[0:12]... (label)
323 otherwise just return address
325 QString TransactionTableModel::lookupAddress(const std::string &address) const
327 QString label = labelForAddress(address);
331 description = QString::fromStdString(address);
335 description = QString::fromStdString(address.substr(0,12)) + QString("... (") + label + QString(")");
340 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
346 case TransactionRecord::RecvWithAddress:
347 description = tr("Received with");
349 case TransactionRecord::RecvFromIP:
350 description = tr("Received from IP");
352 case TransactionRecord::SendToAddress:
353 description = tr("Sent to");
355 case TransactionRecord::SendToIP:
356 description = tr("Sent to IP");
358 case TransactionRecord::SendToSelf:
359 description = tr("Payment to yourself");
361 case TransactionRecord::Generated:
362 description = tr("Generated");
365 return QVariant(description);
368 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
374 case TransactionRecord::RecvWithAddress:
375 description = lookupAddress(wtx->address);
377 case TransactionRecord::RecvFromIP:
378 description = QString::fromStdString(wtx->address);
380 case TransactionRecord::SendToAddress:
381 description = lookupAddress(wtx->address);
383 case TransactionRecord::SendToIP:
384 description = QString::fromStdString(wtx->address);
386 case TransactionRecord::SendToSelf:
387 description = QString();
389 case TransactionRecord::Generated:
390 switch(wtx->status.maturity)
392 case TransactionStatus::Immature:
393 description = tr("(matures in %n more blocks)", "",
394 wtx->status.matures_in);
396 case TransactionStatus::Mature:
397 description = QString();
399 case TransactionStatus::MaturesWarning:
400 description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
402 case TransactionStatus::NotAccepted:
403 description = tr("(not accepted)");
408 return QVariant(description);
411 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
413 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
414 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
416 str = QString("[") + str + QString("]");
418 return QVariant(str);
421 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
423 switch(wtx->status.status)
425 case TransactionStatus::OpenUntilBlock:
426 case TransactionStatus::OpenUntilDate:
427 return QColor(64,64,255);
429 case TransactionStatus::Offline:
430 return QColor(192,192,192);
431 case TransactionStatus::Unconfirmed:
432 switch(wtx->status.depth)
434 case 0: return QIcon(":/icons/transaction_0");
435 case 1: return QIcon(":/icons/transaction_1");
436 case 2: return QIcon(":/icons/transaction_2");
437 case 3: return QIcon(":/icons/transaction_3");
438 case 4: return QIcon(":/icons/transaction_4");
439 default: return QIcon(":/icons/transaction_5");
441 case TransactionStatus::HaveConfirmations:
442 return QIcon(":/icons/transaction_confirmed");
444 return QColor(0,0,0);
447 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
451 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
453 if(role == Qt::DecorationRole)
455 if(index.column() == Status)
457 return formatTxDecoration(rec);
460 else if(role == Qt::DisplayRole)
462 // Delegate to specific column handlers
463 switch(index.column())
466 return formatTxDate(rec);
468 return formatTxType(rec);
470 return formatTxToAddress(rec);
472 return formatTxAmount(rec);
475 else if(role == Qt::EditRole)
477 // Edit role is used for sorting so return the real values
478 switch(index.column())
481 return QString::fromStdString(rec->status.sortKey);
485 return formatTxType(rec);
487 return formatTxToAddress(rec);
489 return rec->credit + rec->debit;
492 else if (role == Qt::ToolTipRole)
494 if(index.column() == Status)
496 return formatTxStatus(rec);
499 else if (role == Qt::TextAlignmentRole)
501 return column_alignments[index.column()];
503 else if (role == Qt::ForegroundRole)
505 /* Non-confirmed transactions are grey */
506 if(!rec->status.confirmed)
508 return QColor(128, 128, 128);
510 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
512 return QColor(255, 0, 0);
515 else if (role == TypeRole)
519 else if (role == DateRole)
521 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
523 else if (role == LongDescriptionRole)
525 return priv->describe(rec);
527 else if (role == AddressRole)
529 return QString::fromStdString(rec->address);
531 else if (role == LabelRole)
533 return labelForAddress(rec->address);
535 else if (role == AbsoluteAmountRole)
537 return llabs(rec->credit + rec->debit);
542 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
544 if(orientation == Qt::Horizontal)
546 if(role == Qt::DisplayRole)
548 return columns[section];
550 else if (role == Qt::TextAlignmentRole)
552 return column_alignments[section];
553 } else if (role == Qt::ToolTipRole)
558 return tr("Transaction status. Hover over this field to show number of confirmations.");
560 return tr("Date and time that the transaction was received.");
562 return tr("Type of transaction.");
564 return tr("Destination address of transaction.");
566 return tr("Amount removed from or added to balance.");
573 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
575 return QAbstractTableModel::flags(index);
578 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
581 TransactionRecord *data = priv->index(row);
584 return createIndex(row, column, priv->index(row));
588 return QModelIndex();