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 confirmations)").arg(wtx->status.depth);
280 case TransactionStatus::Unconfirmed:
281 status = tr("Unconfirmed (%1/%2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
283 case TransactionStatus::HaveConfirmations:
284 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
287 if(wtx->type == TransactionRecord::Generated)
290 switch(wtx->status.maturity)
292 case TransactionStatus::Immature:
293 status += tr("Mined balance will be available in %n more blocks", "",
294 wtx->status.matures_in);
296 case TransactionStatus::Mature:
298 case TransactionStatus::MaturesWarning:
299 status += tr("This block was not received by any other nodes and will probably not be accepted!");
301 case TransactionStatus::NotAccepted:
302 status += tr("Generated but not accepted");
307 return QVariant(status);
310 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
314 return QVariant(GUIUtil::DateTimeStr(wtx->time));
322 /* Look up address in address book, if found return
323 address[0:12]... (label)
324 otherwise just return address
326 QString TransactionTableModel::lookupAddress(const std::string &address) const
328 QString label = walletModel->labelForAddress(QString::fromStdString(address));
332 description = QString::fromStdString(address);
336 description = label + QString(" (") + QString::fromStdString(address.substr(0,12)) + QString("...)");
341 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
347 case TransactionRecord::RecvWithAddress:
348 description = tr("Received with");
350 case TransactionRecord::RecvFromIP:
351 description = tr("Received from IP");
353 case TransactionRecord::SendToAddress:
354 description = tr("Sent to");
356 case TransactionRecord::SendToIP:
357 description = tr("Sent to IP");
359 case TransactionRecord::SendToSelf:
360 description = tr("Payment to yourself");
362 case TransactionRecord::Generated:
363 description = tr("Mined");
366 return QVariant(description);
369 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
375 case TransactionRecord::RecvWithAddress:
376 description = lookupAddress(wtx->address);
378 case TransactionRecord::RecvFromIP:
379 description = QString::fromStdString(wtx->address);
381 case TransactionRecord::SendToAddress:
382 description = lookupAddress(wtx->address);
384 case TransactionRecord::SendToIP:
385 description = QString::fromStdString(wtx->address);
387 case TransactionRecord::SendToSelf:
388 description = QString();
390 case TransactionRecord::Generated:
391 description = QString();
394 return QVariant(description);
397 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx) const
399 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
400 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
402 str = QString("[") + str + QString("]");
404 return QVariant(str);
407 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
409 if(wtx->type == TransactionRecord::Generated)
411 switch(wtx->status.maturity)
413 case TransactionStatus::Immature: {
414 int total = wtx->status.depth + wtx->status.matures_in;
415 int part = (wtx->status.depth * 4 / total) + 1;
416 return QIcon(QString(":/icons/transaction_%1").arg(part));
418 case TransactionStatus::Mature:
419 return QIcon(":/icons/transaction_confirmed");
420 case TransactionStatus::MaturesWarning:
421 case TransactionStatus::NotAccepted:
422 return QIcon(":/icons/transaction_0");
427 switch(wtx->status.status)
429 case TransactionStatus::OpenUntilBlock:
430 case TransactionStatus::OpenUntilDate:
431 return QColor(64,64,255);
433 case TransactionStatus::Offline:
434 return QColor(192,192,192);
435 case TransactionStatus::Unconfirmed:
436 switch(wtx->status.depth)
438 case 0: return QIcon(":/icons/transaction_0");
439 case 1: return QIcon(":/icons/transaction_1");
440 case 2: return QIcon(":/icons/transaction_2");
441 case 3: return QIcon(":/icons/transaction_3");
442 case 4: return QIcon(":/icons/transaction_4");
443 default: return QIcon(":/icons/transaction_5");
445 case TransactionStatus::HaveConfirmations:
446 return QIcon(":/icons/transaction_confirmed");
449 return QColor(0,0,0);
452 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
456 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
458 if(role == Qt::DecorationRole)
460 if(index.column() == Status)
462 return formatTxDecoration(rec);
465 else if(role == Qt::DisplayRole)
467 // Delegate to specific column handlers
468 switch(index.column())
471 return formatTxDate(rec);
473 return formatTxType(rec);
475 return formatTxToAddress(rec);
477 return formatTxAmount(rec);
480 else if(role == Qt::EditRole)
482 // Edit role is used for sorting so return the real values
483 switch(index.column())
486 return QString::fromStdString(rec->status.sortKey);
490 return formatTxType(rec);
492 return formatTxToAddress(rec);
494 return rec->credit + rec->debit;
497 else if (role == Qt::ToolTipRole)
499 if(index.column() == Status)
501 return formatTxStatus(rec);
504 else if (role == Qt::TextAlignmentRole)
506 return column_alignments[index.column()];
508 else if (role == Qt::ForegroundRole)
510 /* Non-confirmed transactions are grey */
511 if(!rec->status.confirmed)
513 return QColor(128, 128, 128);
515 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
517 return QColor(255, 0, 0);
520 else if (role == TypeRole)
524 else if (role == DateRole)
526 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
528 else if (role == LongDescriptionRole)
530 return priv->describe(rec);
532 else if (role == AddressRole)
534 return QString::fromStdString(rec->address);
536 else if (role == LabelRole)
538 return walletModel->labelForAddress(QString::fromStdString(rec->address));
540 else if (role == AbsoluteAmountRole)
542 return llabs(rec->credit + rec->debit);
547 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
549 if(orientation == Qt::Horizontal)
551 if(role == Qt::DisplayRole)
553 return columns[section];
555 else if (role == Qt::TextAlignmentRole)
557 return column_alignments[section];
558 } else if (role == Qt::ToolTipRole)
563 return tr("Transaction status. Hover over this field to show number of confirmations.");
565 return tr("Date and time that the transaction was received.");
567 return tr("Type of transaction.");
569 return tr("Destination address of transaction.");
571 return tr("Amount removed from or added to balance.");
578 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
580 return QAbstractTableModel::flags(index);
583 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
586 TransactionRecord *data = priv->index(row);
589 return createIndex(row, column, priv->index(row));
593 return QModelIndex();