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 << QString() << 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
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) + 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, bool showUnconfirmed) const
399 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
402 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
404 str = QString("[") + str + QString("]");
407 return QVariant(str);
410 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
412 if(wtx->type == TransactionRecord::Generated)
414 switch(wtx->status.maturity)
416 case TransactionStatus::Immature: {
417 int total = wtx->status.depth + wtx->status.matures_in;
418 int part = (wtx->status.depth * 4 / total) + 1;
419 return QIcon(QString(":/icons/transaction_%1").arg(part));
421 case TransactionStatus::Mature:
422 return QIcon(":/icons/transaction_confirmed");
423 case TransactionStatus::MaturesWarning:
424 case TransactionStatus::NotAccepted:
425 return QIcon(":/icons/transaction_0");
430 switch(wtx->status.status)
432 case TransactionStatus::OpenUntilBlock:
433 case TransactionStatus::OpenUntilDate:
434 return QColor(64,64,255);
436 case TransactionStatus::Offline:
437 return QColor(192,192,192);
438 case TransactionStatus::Unconfirmed:
439 switch(wtx->status.depth)
441 case 0: return QIcon(":/icons/transaction_0");
442 case 1: return QIcon(":/icons/transaction_1");
443 case 2: return QIcon(":/icons/transaction_2");
444 case 3: return QIcon(":/icons/transaction_3");
445 case 4: return QIcon(":/icons/transaction_4");
446 default: return QIcon(":/icons/transaction_5");
448 case TransactionStatus::HaveConfirmations:
449 return QIcon(":/icons/transaction_confirmed");
452 return QColor(0,0,0);
455 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
459 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
461 if(role == Qt::DecorationRole)
463 if(index.column() == Status)
465 return formatTxDecoration(rec);
468 else if(role == Qt::DisplayRole)
470 // Delegate to specific column handlers
471 switch(index.column())
474 return formatTxDate(rec);
476 return formatTxType(rec);
478 return formatTxToAddress(rec);
480 return formatTxAmount(rec);
483 else if(role == Qt::EditRole)
485 // Edit role is used for sorting so return the real values
486 switch(index.column())
489 return QString::fromStdString(rec->status.sortKey);
493 return formatTxType(rec);
495 return formatTxToAddress(rec);
497 return rec->credit + rec->debit;
500 else if (role == Qt::ToolTipRole)
502 if(index.column() == Status)
504 return formatTxStatus(rec);
507 else if (role == Qt::TextAlignmentRole)
509 return column_alignments[index.column()];
511 else if (role == Qt::ForegroundRole)
513 /* Non-confirmed transactions are grey */
514 if(!rec->status.confirmed)
516 return QColor(128, 128, 128);
518 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
520 return QColor(255, 0, 0);
523 else if (role == TypeRole)
527 else if (role == DateRole)
529 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
531 else if (role == LongDescriptionRole)
533 return priv->describe(rec);
535 else if (role == AddressRole)
537 return QString::fromStdString(rec->address);
539 else if (role == LabelRole)
541 return walletModel->labelForAddress(QString::fromStdString(rec->address));
543 else if (role == AbsoluteAmountRole)
545 return llabs(rec->credit + rec->debit);
547 else if (role == TxIDRole)
549 return QString::fromStdString(rec->getTxID());
551 else if (role == ConfirmedRole)
553 return rec->status.status == TransactionStatus::HaveConfirmations;
555 else if (role == FormattedAmountRole)
557 return formatTxAmount(rec, false);
562 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
564 if(orientation == Qt::Horizontal)
566 if(role == Qt::DisplayRole)
568 return columns[section];
570 else if (role == Qt::TextAlignmentRole)
572 return column_alignments[section];
573 } else if (role == Qt::ToolTipRole)
578 return tr("Transaction status. Hover over this field to show number of confirmations.");
580 return tr("Date and time that the transaction was received.");
582 return tr("Type of transaction.");
584 return tr("Destination address of transaction.");
586 return tr("Amount removed from or added to balance.");
593 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
595 return QAbstractTableModel::flags(index);
598 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
601 TransactionRecord *data = priv->index(row);
604 return createIndex(row, column, priv->index(row));
608 return QModelIndex();