1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
6 #include "walletmodel.h"
17 #include <QtAlgorithms>
19 // Credit and Debit columns are right-aligned as they contain numbers
20 static int column_alignments[] = {
21 Qt::AlignLeft|Qt::AlignVCenter,
22 Qt::AlignLeft|Qt::AlignVCenter,
23 Qt::AlignLeft|Qt::AlignVCenter,
24 Qt::AlignLeft|Qt::AlignVCenter,
25 Qt::AlignRight|Qt::AlignVCenter
28 // Comparison operator for sort/binary search of model tx list
31 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
33 return a.hash < b.hash;
35 bool operator()(const TransactionRecord &a, const uint256 &b) const
39 bool operator()(const uint256 &a, const TransactionRecord &b) const
45 // Private implementation
46 struct TransactionTablePriv
48 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
54 TransactionTableModel *parent;
56 /* Local cache of wallet.
57 * As it is in the same order as the CWallet, by definition
58 * this is sorted by sha256.
60 QList<TransactionRecord> cachedWallet;
62 /* Query entire wallet anew from core.
66 #ifdef WALLET_UPDATE_DEBUG
67 qDebug() << "refreshWallet";
70 CRITICAL_BLOCK(wallet->cs_mapWallet)
72 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
74 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
79 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
80 with that of the core.
82 Call with list of hashes of transactions that were added, removed or changed.
84 void updateWallet(const QList<uint256> &updated)
86 // Walk through updated transactions, update model as needed.
87 #ifdef WALLET_UPDATE_DEBUG
88 qDebug() << "updateWallet";
90 // Sort update list, and iterate through it in reverse, so that model updates
91 // can be emitted from end to beginning (so that earlier updates will not influence
92 // the indices of latter ones).
93 QList<uint256> updated_sorted = updated;
94 qSort(updated_sorted);
96 CRITICAL_BLOCK(wallet->cs_mapWallet)
98 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
100 const uint256 &hash = updated_sorted.at(update_idx);
101 /* Find transaction in wallet */
102 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
103 bool inWallet = mi != wallet->mapWallet.end();
104 /* Find bounds of this transaction in model */
105 QList<TransactionRecord>::iterator lower = qLowerBound(
106 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
107 QList<TransactionRecord>::iterator upper = qUpperBound(
108 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
109 int lowerIndex = (lower - cachedWallet.begin());
110 int upperIndex = (upper - cachedWallet.begin());
112 // Determine if transaction is in model already
113 bool inModel = false;
119 #ifdef WALLET_UPDATE_DEBUG
120 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
121 << lowerIndex << "-" << upperIndex;
124 if(inWallet && !inModel)
126 // Added -- insert at the right position
127 QList<TransactionRecord> toInsert =
128 TransactionRecord::decomposeTransaction(wallet, mi->second);
129 if(!toInsert.isEmpty()) /* only if something to insert */
131 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
132 int insert_idx = lowerIndex;
133 foreach(const TransactionRecord &rec, toInsert)
135 cachedWallet.insert(insert_idx, rec);
138 parent->endInsertRows();
141 else if(!inWallet && inModel)
143 // Removed -- remove entire transaction from table
144 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
145 cachedWallet.erase(lower, upper);
146 parent->endRemoveRows();
148 else if(inWallet && inModel)
150 // Updated -- nothing to do, status update will take care of this
158 return cachedWallet.size();
161 TransactionRecord *index(int idx)
163 if(idx >= 0 && idx < cachedWallet.size())
165 TransactionRecord *rec = &cachedWallet[idx];
167 // If a status update is needed (blocks came in since last check),
168 // update the status of this transaction from the wallet. Otherwise,
169 // simply re-use the cached status.
170 if(rec->statusUpdateNeeded())
172 CRITICAL_BLOCK(wallet->cs_mapWallet)
174 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
176 if(mi != wallet->mapWallet.end())
178 rec->updateStatus(mi->second);
190 QString describe(TransactionRecord *rec)
192 CRITICAL_BLOCK(wallet->cs_mapWallet)
194 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
195 if(mi != wallet->mapWallet.end())
197 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
205 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
206 QAbstractTableModel(parent),
209 priv(new TransactionTablePriv(wallet, this))
211 columns << tr("Status") << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
213 priv->refreshWallet();
215 QTimer *timer = new QTimer(this);
216 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
217 timer->start(MODEL_UPDATE_DELAY);
220 TransactionTableModel::~TransactionTableModel()
225 void TransactionTableModel::update()
227 QList<uint256> updated;
229 // Check if there are changes to wallet map
230 TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
232 if(!wallet->vWalletUpdated.empty())
234 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
236 updated.append(hash);
238 wallet->vWalletUpdated.clear();
244 priv->updateWallet(updated);
246 // Status (number of confirmations) and (possibly) description
247 // columns changed for all rows.
248 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
249 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
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 %1").arg(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/%2)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
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 QString TransactionTableModel::lookupAddress(const std::string &address) const
309 QString label = walletModel->labelForAddress(QString::fromStdString(address));
313 description = QString::fromStdString(address);
317 description = label + QString(" (") + QString::fromStdString(address.substr(0,12)) + QString("...)");
322 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
328 case TransactionRecord::RecvWithAddress:
329 description = tr("Received with");
331 case TransactionRecord::RecvFromIP:
332 description = tr("Received from IP");
334 case TransactionRecord::SendToAddress:
335 description = tr("Sent to");
337 case TransactionRecord::SendToIP:
338 description = tr("Sent to IP");
340 case TransactionRecord::SendToSelf:
341 description = tr("Payment to yourself");
343 case TransactionRecord::Generated:
344 description = tr("Generated");
347 return QVariant(description);
350 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
356 case TransactionRecord::RecvWithAddress:
357 description = lookupAddress(wtx->address);
359 case TransactionRecord::RecvFromIP:
360 description = QString::fromStdString(wtx->address);
362 case TransactionRecord::SendToAddress:
363 description = lookupAddress(wtx->address);
365 case TransactionRecord::SendToIP:
366 description = QString::fromStdString(wtx->address);
368 case TransactionRecord::SendToSelf:
369 description = QString();
371 case TransactionRecord::Generated:
372 switch(wtx->status.maturity)
374 case TransactionStatus::Immature:
375 description = tr("(matures in %n more blocks)", "",
376 wtx->status.matures_in);
378 case TransactionStatus::Mature:
379 description = QString();
381 case TransactionStatus::MaturesWarning:
382 description = tr("(Warning: This block was not received by any other nodes and will probably not be accepted!)");
384 case TransactionStatus::NotAccepted:
385 description = tr("(not accepted)");
390 return QVariant(description);
393 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
395 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
396 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
398 str = QString("[") + str + QString("]");
400 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 switch(wtx->status.depth)
416 case 0: return QIcon(":/icons/transaction_0");
417 case 1: return QIcon(":/icons/transaction_1");
418 case 2: return QIcon(":/icons/transaction_2");
419 case 3: return QIcon(":/icons/transaction_3");
420 case 4: return QIcon(":/icons/transaction_4");
421 default: return QIcon(":/icons/transaction_5");
423 case TransactionStatus::HaveConfirmations:
424 return QIcon(":/icons/transaction_confirmed");
426 return QColor(0,0,0);
429 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
433 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
435 if(role == Qt::DecorationRole)
437 if(index.column() == Status)
439 return formatTxDecoration(rec);
442 else if(role == Qt::DisplayRole)
444 // Delegate to specific column handlers
445 switch(index.column())
448 return formatTxDate(rec);
450 return formatTxType(rec);
452 return formatTxToAddress(rec);
454 return formatTxAmount(rec);
457 else if(role == Qt::EditRole)
459 // Edit role is used for sorting so return the real values
460 switch(index.column())
463 return QString::fromStdString(rec->status.sortKey);
467 return formatTxType(rec);
469 return formatTxToAddress(rec);
471 return rec->credit + rec->debit;
474 else if (role == Qt::ToolTipRole)
476 if(index.column() == Status)
478 return formatTxStatus(rec);
481 else if (role == Qt::TextAlignmentRole)
483 return column_alignments[index.column()];
485 else if (role == Qt::ForegroundRole)
487 /* Non-confirmed transactions are grey */
488 if(!rec->status.confirmed)
490 return QColor(128, 128, 128);
492 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
494 return QColor(255, 0, 0);
497 else if (role == TypeRole)
501 else if (role == DateRole)
503 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
505 else if (role == LongDescriptionRole)
507 return priv->describe(rec);
509 else if (role == AddressRole)
511 return QString::fromStdString(rec->address);
513 else if (role == LabelRole)
515 return walletModel->labelForAddress(QString::fromStdString(rec->address));
517 else if (role == AbsoluteAmountRole)
519 return llabs(rec->credit + rec->debit);
524 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
526 if(orientation == Qt::Horizontal)
528 if(role == Qt::DisplayRole)
530 return columns[section];
532 else if (role == Qt::TextAlignmentRole)
534 return column_alignments[section];
535 } else if (role == Qt::ToolTipRole)
540 return tr("Transaction status. Hover over this field to show number of confirmations.");
542 return tr("Date and time that the transaction was received.");
544 return tr("Type of transaction.");
546 return tr("Destination address of transaction.");
548 return tr("Amount removed from or added to balance.");
555 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
557 return QAbstractTableModel::flags(index);
560 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
563 TransactionRecord *data = priv->index(row);
566 return createIndex(row, column, priv->index(row));
570 return QModelIndex();