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 void TransactionTableModel::refresh()
266 priv->refreshWallet();
267 emit dataChanged(index(0, 0), index(priv->size() - 1, Amount));
270 int TransactionTableModel::rowCount(const QModelIndex &parent) const
276 int TransactionTableModel::columnCount(const QModelIndex &parent) const
279 return columns.length();
282 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
286 switch(wtx->status.status)
288 case TransactionStatus::OpenUntilBlock:
289 status = tr("Open for %n block(s)","",wtx->status.open_for);
291 case TransactionStatus::OpenUntilDate:
292 status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
294 case TransactionStatus::Offline:
295 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
297 case TransactionStatus::Unconfirmed:
298 status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
300 case TransactionStatus::HaveConfirmations:
301 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
305 if(wtx->type == TransactionRecord::Generated)
307 switch(wtx->status.maturity)
309 case TransactionStatus::Immature:
310 status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
312 case TransactionStatus::Mature:
314 case TransactionStatus::MaturesWarning:
315 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
317 case TransactionStatus::NotAccepted:
318 status += "\n" + tr("Generated but not accepted");
326 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
330 return GUIUtil::dateTimeStr(wtx->time);
338 /* Look up address in address book, if found return label (address)
339 otherwise just return (address)
341 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
343 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
347 description += label + QString(" ");
349 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
351 description += QString("(") + QString::fromStdString(address) + QString(")");
356 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
360 case TransactionRecord::RecvWithAddress:
361 return tr("Received with");
362 case TransactionRecord::RecvFromOther:
363 return tr("Received from");
364 case TransactionRecord::SendToAddress:
365 case TransactionRecord::SendToOther:
366 return tr("Sent to");
367 case TransactionRecord::SendToSelf:
368 return tr("Payment to yourself");
369 case TransactionRecord::Generated:
376 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
380 case TransactionRecord::Generated:
381 return QIcon(":/icons/tx_mined");
382 case TransactionRecord::RecvWithAddress:
383 case TransactionRecord::RecvFromOther:
384 return QIcon(":/icons/tx_input");
385 case TransactionRecord::SendToAddress:
386 case TransactionRecord::SendToOther:
387 return QIcon(":/icons/tx_output");
389 return QIcon(":/icons/tx_inout");
394 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
398 case TransactionRecord::RecvFromOther:
399 return QString::fromStdString(wtx->address);
400 case TransactionRecord::RecvWithAddress:
401 case TransactionRecord::SendToAddress:
402 case TransactionRecord::Generated:
403 return lookupAddress(wtx->address, tooltip);
404 case TransactionRecord::SendToOther:
405 return QString::fromStdString(wtx->address);
406 case TransactionRecord::SendToSelf:
412 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
414 // Show addresses without label in a less visible color
417 case TransactionRecord::RecvWithAddress:
418 case TransactionRecord::SendToAddress:
419 case TransactionRecord::Generated:
421 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
423 return COLOR_BAREADDRESS;
425 case TransactionRecord::SendToSelf:
426 return COLOR_BAREADDRESS;
433 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
435 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
438 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
440 str = QString("[") + str + QString("]");
446 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
448 if(wtx->type == TransactionRecord::Generated)
450 switch(wtx->status.maturity)
452 case TransactionStatus::Immature: {
453 int total = wtx->status.depth + wtx->status.matures_in;
454 int part = (wtx->status.depth * 4 / total) + 1;
455 return QIcon(QString(":/icons/transaction_%1").arg(part));
457 case TransactionStatus::Mature:
458 return QIcon(":/icons/transaction_confirmed");
459 case TransactionStatus::MaturesWarning:
460 case TransactionStatus::NotAccepted:
461 return QIcon(":/icons/transaction_0");
466 switch(wtx->status.status)
468 case TransactionStatus::OpenUntilBlock:
469 case TransactionStatus::OpenUntilDate:
470 return QColor(64,64,255);
472 case TransactionStatus::Offline:
473 return QColor(192,192,192);
474 case TransactionStatus::Unconfirmed:
475 switch(wtx->status.depth)
477 case 0: return QIcon(":/icons/transaction_0");
478 case 1: return QIcon(":/icons/transaction_1");
479 case 2: return QIcon(":/icons/transaction_2");
480 case 3: return QIcon(":/icons/transaction_3");
481 case 4: return QIcon(":/icons/transaction_4");
482 default: return QIcon(":/icons/transaction_5");
484 case TransactionStatus::HaveConfirmations:
485 return QIcon(":/icons/transaction_confirmed");
488 return QColor(0,0,0);
491 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
493 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
494 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
495 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
497 tooltip += QString(" ") + formatTxToAddress(rec, true);
502 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
506 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
510 case Qt::DecorationRole:
511 switch(index.column())
514 return txStatusDecoration(rec);
516 return txAddressDecoration(rec);
519 case Qt::DisplayRole:
520 switch(index.column())
523 return formatTxDate(rec);
525 return formatTxType(rec);
527 return formatTxToAddress(rec, false);
529 return formatTxAmount(rec);
533 // Edit role is used for sorting, so return the unformatted values
534 switch(index.column())
537 return QString::fromStdString(rec->status.sortKey);
541 return formatTxType(rec);
543 return formatTxToAddress(rec, true);
545 return rec->credit + rec->debit;
548 case Qt::ToolTipRole:
549 return formatTooltip(rec);
550 case Qt::TextAlignmentRole:
551 return column_alignments[index.column()];
552 case Qt::ForegroundRole:
553 // Non-confirmed transactions are grey
554 if(!rec->status.confirmed)
556 return COLOR_UNCONFIRMED;
558 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
560 return COLOR_NEGATIVE;
562 if(index.column() == ToAddress)
564 return addressColor(rec);
570 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
571 case LongDescriptionRole:
572 return priv->describe(rec);
574 return QString::fromStdString(rec->address);
576 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
578 return rec->credit + rec->debit;
580 return QString::fromStdString(rec->getTxID());
582 return QString::fromStdString(rec->hash.ToString());
584 // Return True if transaction counts for balance
585 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
586 case FormattedAmountRole:
587 return formatTxAmount(rec, false);
592 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
594 if(orientation == Qt::Horizontal)
596 if(role == Qt::DisplayRole)
598 return columns[section];
600 else if (role == Qt::TextAlignmentRole)
602 return column_alignments[section];
603 } else if (role == Qt::ToolTipRole)
608 return tr("Transaction status. Hover over this field to show number of confirmations.");
610 return tr("Date and time that the transaction was received.");
612 return tr("Type of transaction.");
614 return tr("Destination address of transaction.");
616 return tr("Amount removed from or added to balance.");
623 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
626 TransactionRecord *data = priv->index(row);
629 return createIndex(row, column, priv->index(row));
633 return QModelIndex();
637 void TransactionTableModel::updateDisplayUnit()
639 // emit dataChanged to update Amount column with the current unit
640 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));