1 #include "transactiontablemodel.h"
3 #include "transactionrecord.h"
4 #include "guiconstants.h"
5 #include "transactiondesc.h"
6 #include "walletmodel.h"
7 #include "optionsmodel.h"
8 #include "addresstablemodel.h"
9 #include "bitcoinunits.h"
12 #include "ui_interface.h"
20 #include <QtAlgorithms>
22 // Amount column is right-aligned it contains 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 class TransactionTablePriv
52 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent):
58 TransactionTableModel *parent;
60 /* Local cache of wallet.
61 * As it is in the same order as the CWallet, by definition
62 * this is sorted by sha256.
64 QList<TransactionRecord> cachedWallet;
66 /* Query entire wallet anew from core.
70 OutputDebugStringF("refreshWallet\n");
73 LOCK(wallet->cs_wallet);
74 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
76 if(TransactionRecord::showTransaction(it->second))
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 transaction that was added, removed or changed.
87 void updateWallet(const uint256 &hash, int status)
89 OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status);
91 LOCK(wallet->cs_wallet);
93 // Find transaction in wallet
94 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
95 bool inWallet = mi != wallet->mapWallet.end();
97 // Find bounds of this transaction in model
98 QList<TransactionRecord>::iterator lower = qLowerBound(
99 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
100 QList<TransactionRecord>::iterator upper = qUpperBound(
101 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
102 int lowerIndex = (lower - cachedWallet.begin());
103 int upperIndex = (upper - cachedWallet.begin());
104 bool inModel = (lower != upper);
106 // Determine whether to show transaction or not
107 bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
109 if(status == CT_UPDATED)
111 if(showTransaction && !inModel)
112 status = CT_NEW; /* Not in model, but want to show, treat as new */
113 if(!showTransaction && inModel)
114 status = CT_DELETED; /* In model, but want to hide, treat as deleted */
117 OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n",
118 inWallet, inModel, lowerIndex, upperIndex, showTransaction, status);
125 OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n");
130 OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n");
135 // Added -- insert at the right position
136 QList<TransactionRecord> toInsert =
137 TransactionRecord::decomposeTransaction(wallet, mi->second);
138 if(!toInsert.isEmpty()) /* only if something to insert */
140 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
141 int insert_idx = lowerIndex;
142 foreach(const TransactionRecord &rec, toInsert)
144 cachedWallet.insert(insert_idx, rec);
147 parent->endInsertRows();
154 OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n");
157 // Removed -- remove entire transaction from table
158 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
159 cachedWallet.erase(lower, upper);
160 parent->endRemoveRows();
163 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
164 // visible transactions.
172 return cachedWallet.size();
175 TransactionRecord *index(int idx)
177 if(idx >= 0 && idx < cachedWallet.size())
179 TransactionRecord *rec = &cachedWallet[idx];
181 // If a status update is needed (blocks came in since last check),
182 // update the status of this transaction from the wallet. Otherwise,
183 // simply re-use the cached status.
184 if(rec->statusUpdateNeeded())
187 LOCK(wallet->cs_wallet);
188 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
190 if(mi != wallet->mapWallet.end())
192 rec->updateStatus(mi->second);
204 QString describe(TransactionRecord *rec)
207 LOCK(wallet->cs_wallet);
208 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
209 if(mi != wallet->mapWallet.end())
211 return TransactionDesc::toHTML(wallet, mi->second);
219 TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *parent):
220 QAbstractTableModel(parent),
223 priv(new TransactionTablePriv(wallet, this)),
226 columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount");
228 priv->refreshWallet();
230 QTimer *timer = new QTimer(this);
231 connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations()));
232 timer->start(MODEL_UPDATE_DELAY);
234 connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
237 TransactionTableModel::~TransactionTableModel()
242 void TransactionTableModel::updateTransaction(const QString &hash, int status)
245 updated.SetHex(hash.toStdString());
247 priv->updateWallet(updated, status);
250 void TransactionTableModel::updateConfirmations()
252 if(nBestHeight != cachedNumBlocks)
254 cachedNumBlocks = nBestHeight;
255 // Blocks came in since last poll.
256 // Invalidate status (number of confirmations) and (possibly) description
257 // for all rows. Qt is smart enough to only actually request the data for the
259 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
260 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
264 int TransactionTableModel::rowCount(const QModelIndex &parent) const
270 int TransactionTableModel::columnCount(const QModelIndex &parent) const
273 return columns.length();
276 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
280 switch(wtx->status.status)
282 case TransactionStatus::OpenUntilBlock:
283 status = tr("Open for %n block(s)","",wtx->status.open_for);
285 case TransactionStatus::OpenUntilDate:
286 status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
288 case TransactionStatus::Offline:
289 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
291 case TransactionStatus::Unconfirmed:
292 status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
294 case TransactionStatus::HaveConfirmations:
295 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
297 case TransactionStatus::Conflicted:
298 status = tr("Conflicted");
302 if(wtx->type == TransactionRecord::Generated)
304 switch(wtx->status.maturity)
306 case TransactionStatus::Immature:
307 status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
309 case TransactionStatus::Mature:
311 case TransactionStatus::MaturesWarning:
312 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
314 case TransactionStatus::NotAccepted:
315 status += "\n" + tr("Generated but not accepted");
323 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
327 return GUIUtil::dateTimeStr(wtx->time);
335 /* Look up address in address book, if found return label (address)
336 otherwise just return (address)
338 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
340 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
344 description += label + QString(" ");
346 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
348 description += QString("(") + QString::fromStdString(address) + QString(")");
353 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
357 case TransactionRecord::RecvWithAddress:
358 return tr("Received with");
359 case TransactionRecord::RecvFromOther:
360 return tr("Received from");
361 case TransactionRecord::SendToAddress:
362 case TransactionRecord::SendToOther:
363 return tr("Sent to");
364 case TransactionRecord::SendToSelf:
365 return tr("Payment to yourself");
366 case TransactionRecord::Generated:
373 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
377 case TransactionRecord::Generated:
378 return QIcon(":/icons/tx_mined");
379 case TransactionRecord::RecvWithAddress:
380 case TransactionRecord::RecvFromOther:
381 return QIcon(":/icons/tx_input");
382 case TransactionRecord::SendToAddress:
383 case TransactionRecord::SendToOther:
384 return QIcon(":/icons/tx_output");
386 return QIcon(":/icons/tx_inout");
391 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
395 case TransactionRecord::RecvFromOther:
396 return QString::fromStdString(wtx->address);
397 case TransactionRecord::RecvWithAddress:
398 case TransactionRecord::SendToAddress:
399 case TransactionRecord::Generated:
400 return lookupAddress(wtx->address, tooltip);
401 case TransactionRecord::SendToOther:
402 return QString::fromStdString(wtx->address);
403 case TransactionRecord::SendToSelf:
409 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
411 // Show addresses without label in a less visible color
414 case TransactionRecord::RecvWithAddress:
415 case TransactionRecord::SendToAddress:
416 case TransactionRecord::Generated:
418 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
420 return COLOR_BAREADDRESS;
422 case TransactionRecord::SendToSelf:
423 return COLOR_BAREADDRESS;
430 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
432 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
435 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
437 str = QString("[") + str + QString("]");
443 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
445 if(wtx->type == TransactionRecord::Generated)
447 switch(wtx->status.maturity)
449 case TransactionStatus::Immature: {
450 int total = wtx->status.depth + wtx->status.matures_in;
451 int part = (wtx->status.depth * 4 / total) + 1;
452 return QIcon(QString(":/icons/transaction_%1").arg(part));
454 case TransactionStatus::Mature:
455 return QIcon(":/icons/transaction_confirmed");
456 case TransactionStatus::MaturesWarning:
457 case TransactionStatus::NotAccepted:
458 return QIcon(":/icons/transaction_0");
463 switch(wtx->status.status)
465 case TransactionStatus::OpenUntilBlock:
466 case TransactionStatus::OpenUntilDate:
467 return QColor(64,64,255);
469 case TransactionStatus::Offline:
470 return QColor(192,192,192);
471 case TransactionStatus::Unconfirmed:
472 switch(wtx->status.depth)
474 case 0: return QIcon(":/icons/transaction_0");
475 case 1: return QIcon(":/icons/transaction_1");
476 case 2: return QIcon(":/icons/transaction_2");
477 case 3: return QIcon(":/icons/transaction_3");
478 case 4: return QIcon(":/icons/transaction_4");
479 default: return QIcon(":/icons/transaction_5");
481 case TransactionStatus::HaveConfirmations:
482 return QIcon(":/icons/transaction_confirmed");
483 case TransactionStatus::Conflicted:
484 return QIcon(":/icons/transaction_conflicted");
487 return QColor(0,0,0);
490 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
492 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
493 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
494 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
496 tooltip += QString(" ") + formatTxToAddress(rec, true);
501 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
505 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
509 case Qt::DecorationRole:
510 switch(index.column())
513 return txStatusDecoration(rec);
515 return txAddressDecoration(rec);
518 case Qt::DisplayRole:
519 switch(index.column())
522 return formatTxDate(rec);
524 return formatTxType(rec);
526 return formatTxToAddress(rec, false);
528 return formatTxAmount(rec);
532 // Edit role is used for sorting, so return the unformatted values
533 switch(index.column())
536 return QString::fromStdString(rec->status.sortKey);
540 return formatTxType(rec);
542 return formatTxToAddress(rec, true);
544 return rec->credit + rec->debit;
547 case Qt::ToolTipRole:
548 return formatTooltip(rec);
549 case Qt::TextAlignmentRole:
550 return column_alignments[index.column()];
551 case Qt::ForegroundRole:
552 // Non-confirmed transactions are grey
553 if(!rec->status.confirmed)
555 return COLOR_UNCONFIRMED;
557 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
559 return COLOR_NEGATIVE;
561 if(index.column() == ToAddress)
563 return addressColor(rec);
569 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
570 case LongDescriptionRole:
571 return priv->describe(rec);
573 return QString::fromStdString(rec->address);
575 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
577 return rec->credit + rec->debit;
579 return QString::fromStdString(rec->getTxID());
581 // Return True if transaction counts for balance
582 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
583 case FormattedAmountRole:
584 return formatTxAmount(rec, false);
586 return rec->status.status;
591 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
593 if(orientation == Qt::Horizontal)
595 if(role == Qt::DisplayRole)
597 return columns[section];
599 else if (role == Qt::TextAlignmentRole)
601 return column_alignments[section];
602 } else if (role == Qt::ToolTipRole)
607 return tr("Transaction status. Hover over this field to show number of confirmations.");
609 return tr("Date and time that the transaction was received.");
611 return tr("Type of transaction.");
613 return tr("Destination address of transaction.");
615 return tr("Amount removed from or added to balance.");
622 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
625 TransactionRecord *data = priv->index(row);
628 return createIndex(row, column, priv->index(row));
632 return QModelIndex();
636 void TransactionTableModel::updateDisplayUnit()
638 // emit dataChanged to update Amount column with the current unit
639 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));