1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
6 #include "walletmodel.h"
7 #include "addresstablemodel.h"
18 #include <QtAlgorithms>
20 // Credit and Debit columns are right-aligned as they contain numbers
21 static int column_alignments[] = {
22 Qt::AlignLeft|Qt::AlignVCenter,
23 Qt::AlignLeft|Qt::AlignVCenter,
24 Qt::AlignLeft|Qt::AlignVCenter,
25 Qt::AlignLeft|Qt::AlignVCenter,
26 Qt::AlignRight|Qt::AlignVCenter
29 // Comparison operator for sort/binary search of model tx list
32 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
34 return a.hash < b.hash;
36 bool operator()(const TransactionRecord &a, const uint256 &b) const
40 bool operator()(const uint256 &a, const TransactionRecord &b) const
46 // Private implementation
47 struct TransactionTablePriv
49 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
55 TransactionTableModel *parent;
57 /* Local cache of wallet.
58 * As it is in the same order as the CWallet, by definition
59 * this is sorted by sha256.
61 QList<TransactionRecord> cachedWallet;
63 /* Query entire wallet anew from core.
67 #ifdef WALLET_UPDATE_DEBUG
68 qDebug() << "refreshWallet";
71 CRITICAL_BLOCK(wallet->cs_mapWallet)
73 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
75 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
80 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
81 with that of the core.
83 Call with list of hashes of transactions that were added, removed or changed.
85 void updateWallet(const QList<uint256> &updated)
87 // Walk through updated transactions, update model as needed.
88 #ifdef WALLET_UPDATE_DEBUG
89 qDebug() << "updateWallet";
91 // Sort update list, and iterate through it in reverse, so that model updates
92 // can be emitted from end to beginning (so that earlier updates will not influence
93 // the indices of latter ones).
94 QList<uint256> updated_sorted = updated;
95 qSort(updated_sorted);
97 CRITICAL_BLOCK(wallet->cs_mapWallet)
99 for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx)
101 const uint256 &hash = updated_sorted.at(update_idx);
102 /* Find transaction in wallet */
103 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
104 bool inWallet = mi != wallet->mapWallet.end();
105 /* Find bounds of this transaction in model */
106 QList<TransactionRecord>::iterator lower = qLowerBound(
107 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
108 QList<TransactionRecord>::iterator upper = qUpperBound(
109 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
110 int lowerIndex = (lower - cachedWallet.begin());
111 int upperIndex = (upper - cachedWallet.begin());
113 // Determine if transaction is in model already
114 bool inModel = false;
120 #ifdef WALLET_UPDATE_DEBUG
121 qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel
122 << lowerIndex << "-" << upperIndex;
125 if(inWallet && !inModel)
127 // Added -- insert at the right position
128 QList<TransactionRecord> toInsert =
129 TransactionRecord::decomposeTransaction(wallet, mi->second);
130 if(!toInsert.isEmpty()) /* only if something to insert */
132 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
133 int insert_idx = lowerIndex;
134 foreach(const TransactionRecord &rec, toInsert)
136 cachedWallet.insert(insert_idx, rec);
139 parent->endInsertRows();
142 else if(!inWallet && inModel)
144 // Removed -- remove entire transaction from table
145 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
146 cachedWallet.erase(lower, upper);
147 parent->endRemoveRows();
149 else if(inWallet && inModel)
151 // Updated -- nothing to do, status update will take care of this
159 return cachedWallet.size();
162 TransactionRecord *index(int idx)
164 if(idx >= 0 && idx < cachedWallet.size())
166 TransactionRecord *rec = &cachedWallet[idx];
168 // If a status update is needed (blocks came in since last check),
169 // update the status of this transaction from the wallet. Otherwise,
170 // simply re-use the cached status.
171 if(rec->statusUpdateNeeded())
173 CRITICAL_BLOCK(wallet->cs_mapWallet)
175 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
177 if(mi != wallet->mapWallet.end())
179 rec->updateStatus(mi->second);
191 QString describe(TransactionRecord *rec)
193 CRITICAL_BLOCK(wallet->cs_mapWallet)
195 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
196 if(mi != wallet->mapWallet.end())
198 return QString::fromStdString(TransactionDesc::toHTML(wallet, mi->second));
206 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
207 QAbstractTableModel(parent),
210 priv(new TransactionTablePriv(wallet, this))
212 columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
214 priv->refreshWallet();
216 QTimer *timer = new QTimer(this);
217 connect(timer, SIGNAL(timeout()), this, SLOT(update()));
218 timer->start(MODEL_UPDATE_DELAY);
221 TransactionTableModel::~TransactionTableModel()
226 void TransactionTableModel::update()
228 QList<uint256> updated;
230 // Check if there are changes to wallet map
231 TRY_CRITICAL_BLOCK(wallet->cs_mapWallet)
233 if(!wallet->vWalletUpdated.empty())
235 BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated)
237 updated.append(hash);
239 wallet->vWalletUpdated.clear();
245 priv->updateWallet(updated);
247 // Status (number of confirmations) and (possibly) description
248 // columns changed for all rows.
249 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
250 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
254 int TransactionTableModel::rowCount(const QModelIndex &parent) const
260 int TransactionTableModel::columnCount(const QModelIndex &parent) const
263 return columns.length();
266 QVariant TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
270 switch(wtx->status.status)
272 case TransactionStatus::OpenUntilBlock:
273 status = tr("Open for %n block(s)","",wtx->status.open_for);
275 case TransactionStatus::OpenUntilDate:
276 status = tr("Open until %1").arg(GUIUtil::DateTimeStr(wtx->status.open_for));
278 case TransactionStatus::Offline:
279 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
281 case TransactionStatus::Unconfirmed:
282 status = tr("Unconfirmed (%1 of %2 confirmations required)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
284 case TransactionStatus::HaveConfirmations:
285 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
288 if(wtx->type == TransactionRecord::Generated)
291 switch(wtx->status.maturity)
293 case TransactionStatus::Immature:
294 status += tr("Mined balance will be available in %n more blocks", "",
295 wtx->status.matures_in);
297 case TransactionStatus::Mature:
299 case TransactionStatus::MaturesWarning:
300 status += tr("This block was not received by any other nodes and will probably not be accepted!");
302 case TransactionStatus::NotAccepted:
303 status += tr("Generated but not accepted");
308 return QVariant(status);
311 QVariant TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
315 return QVariant(GUIUtil::DateTimeStr(wtx->time));
323 /* Look up address in address book, if found return
325 otherwise just return address
327 QString TransactionTableModel::lookupAddress(const std::string &address) const
329 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
333 description = QString::fromStdString(address);
337 description = label + QString(" (") + QString::fromStdString(address) + QString(")");
342 QVariant TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
348 case TransactionRecord::RecvWithAddress:
349 description = tr("Received with");
351 case TransactionRecord::RecvFromIP:
352 description = tr("Received from IP");
354 case TransactionRecord::SendToAddress:
355 description = tr("Sent to");
357 case TransactionRecord::SendToIP:
358 description = tr("Sent to IP");
360 case TransactionRecord::SendToSelf:
361 description = tr("Payment to yourself");
363 case TransactionRecord::Generated:
364 description = tr("Mined");
367 return QVariant(description);
370 QVariant TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx) const
376 case TransactionRecord::RecvWithAddress:
377 description = lookupAddress(wtx->address);
379 case TransactionRecord::RecvFromIP:
380 description = QString::fromStdString(wtx->address);
382 case TransactionRecord::SendToAddress:
383 description = lookupAddress(wtx->address);
385 case TransactionRecord::SendToIP:
386 description = QString::fromStdString(wtx->address);
388 case TransactionRecord::SendToSelf:
389 description = QString();
391 case TransactionRecord::Generated:
392 description = QString();
395 return QVariant(description);
398 QVariant TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
400 QString str = QString::fromStdString(FormatMoney(wtx->credit + wtx->debit));
403 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
405 str = QString("[") + str + QString("]");
408 return QVariant(str);
411 QVariant TransactionTableModel::formatTxDecoration(const TransactionRecord *wtx) const
413 if(wtx->type == TransactionRecord::Generated)
415 switch(wtx->status.maturity)
417 case TransactionStatus::Immature: {
418 int total = wtx->status.depth + wtx->status.matures_in;
419 int part = (wtx->status.depth * 4 / total) + 1;
420 return QIcon(QString(":/icons/transaction_%1").arg(part));
422 case TransactionStatus::Mature:
423 return QIcon(":/icons/transaction_confirmed");
424 case TransactionStatus::MaturesWarning:
425 case TransactionStatus::NotAccepted:
426 return QIcon(":/icons/transaction_0");
431 switch(wtx->status.status)
433 case TransactionStatus::OpenUntilBlock:
434 case TransactionStatus::OpenUntilDate:
435 return QColor(64,64,255);
437 case TransactionStatus::Offline:
438 return QColor(192,192,192);
439 case TransactionStatus::Unconfirmed:
440 switch(wtx->status.depth)
442 case 0: return QIcon(":/icons/transaction_0");
443 case 1: return QIcon(":/icons/transaction_1");
444 case 2: return QIcon(":/icons/transaction_2");
445 case 3: return QIcon(":/icons/transaction_3");
446 case 4: return QIcon(":/icons/transaction_4");
447 default: return QIcon(":/icons/transaction_5");
449 case TransactionStatus::HaveConfirmations:
450 return QIcon(":/icons/transaction_confirmed");
453 return QColor(0,0,0);
456 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
460 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
462 if(role == Qt::DecorationRole)
464 if(index.column() == Status)
466 return formatTxDecoration(rec);
469 else if(role == Qt::DisplayRole)
471 // Delegate to specific column handlers
472 switch(index.column())
475 return formatTxDate(rec);
477 return formatTxType(rec);
479 return formatTxToAddress(rec);
481 return formatTxAmount(rec);
484 else if(role == Qt::EditRole)
486 // Edit role is used for sorting so return the real values
487 switch(index.column())
490 return QString::fromStdString(rec->status.sortKey);
494 return formatTxType(rec);
496 return formatTxToAddress(rec);
498 return rec->credit + rec->debit;
501 else if (role == Qt::ToolTipRole)
503 if(index.column() == Status)
505 return formatTxStatus(rec);
508 else if (role == Qt::TextAlignmentRole)
510 return column_alignments[index.column()];
512 else if (role == Qt::ForegroundRole)
514 /* Non-confirmed transactions are grey */
515 if(!rec->status.confirmed)
517 return QColor(128, 128, 128);
519 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
521 return QColor(255, 0, 0);
524 else if (role == TypeRole)
528 else if (role == DateRole)
530 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
532 else if (role == LongDescriptionRole)
534 return priv->describe(rec);
536 else if (role == AddressRole)
538 return QString::fromStdString(rec->address);
540 else if (role == LabelRole)
542 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
544 else if (role == AbsoluteAmountRole)
546 return llabs(rec->credit + rec->debit);
548 else if (role == TxIDRole)
550 return QString::fromStdString(rec->getTxID());
552 else if (role == ConfirmedRole)
554 return rec->status.status == TransactionStatus::HaveConfirmations;
556 else if (role == FormattedAmountRole)
558 return formatTxAmount(rec, false);
563 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
565 if(orientation == Qt::Horizontal)
567 if(role == Qt::DisplayRole)
569 return columns[section];
571 else if (role == Qt::TextAlignmentRole)
573 return column_alignments[section];
574 } else if (role == Qt::ToolTipRole)
579 return tr("Transaction status. Hover over this field to show number of confirmations.");
581 return tr("Date and time that the transaction was received.");
583 return tr("Type of transaction.");
585 return tr("Destination address of transaction.");
587 return tr("Amount removed from or added to balance.");
594 Qt::ItemFlags TransactionTableModel::flags(const QModelIndex &index) const
596 return QAbstractTableModel::flags(index);
599 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
602 TransactionRecord *data = priv->index(row);
605 return createIndex(row, column, priv->index(row));
609 return QModelIndex();