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") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
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 /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
243 void TransactionTableModel::updateAmountColumnTitle()
245 columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
246 emit headerDataChanged(Qt::Horizontal,Amount,Amount);
249 void TransactionTableModel::updateTransaction(const QString &hash, int status)
252 updated.SetHex(hash.toStdString());
254 priv->updateWallet(updated, status);
257 void TransactionTableModel::updateConfirmations()
259 if(nBestHeight != cachedNumBlocks)
261 cachedNumBlocks = nBestHeight;
262 // Blocks came in since last poll.
263 // Invalidate status (number of confirmations) and (possibly) description
264 // for all rows. Qt is smart enough to only actually request the data for the
266 emit dataChanged(index(0, Status), index(priv->size()-1, Status));
267 emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
271 void TransactionTableModel::refresh()
273 priv->refreshWallet();
274 emit dataChanged(index(0, 0), index(priv->size() - 1, Amount));
277 int TransactionTableModel::rowCount(const QModelIndex &parent) const
283 int TransactionTableModel::columnCount(const QModelIndex &parent) const
286 return columns.length();
289 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
292 int nNumConf = TransactionRecord::NumConfirmations;
294 if (wtx->type == TransactionRecord::Generated)
296 nNumConf = nCoinbaseMaturity + 20;
299 switch(wtx->status.status)
301 case TransactionStatus::OpenUntilBlock:
302 status = tr("Open for %n block(s)","",wtx->status.open_for);
304 case TransactionStatus::OpenUntilDate:
305 status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
307 case TransactionStatus::Offline:
308 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
310 case TransactionStatus::Unconfirmed:
311 status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(nNumConf);
313 case TransactionStatus::HaveConfirmations:
314 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
318 if(wtx->type == TransactionRecord::Generated)
320 switch(wtx->status.maturity)
322 case TransactionStatus::Immature:
323 status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
325 case TransactionStatus::Mature:
327 case TransactionStatus::MaturesWarning:
328 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
330 case TransactionStatus::NotAccepted:
331 status += "\n" + tr("Generated but not accepted");
339 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
343 return GUIUtil::dateTimeStr(wtx->time);
351 /* Look up address in address book, if found return label (address)
352 otherwise just return (address)
354 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
356 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
360 description += label + QString(" ");
362 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
364 description += QString("(") + QString::fromStdString(address) + QString(")");
369 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
373 case TransactionRecord::RecvWithAddress:
374 return tr("Received with");
375 case TransactionRecord::RecvFromOther:
376 return tr("Received from");
377 case TransactionRecord::SendToAddress:
378 case TransactionRecord::SendToOther:
379 return tr("Sent to");
380 case TransactionRecord::SendToSelf:
381 return tr("Payment to yourself");
382 case TransactionRecord::Generated:
389 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
393 case TransactionRecord::Generated:
394 return QIcon(":/icons/tx_mined");
395 case TransactionRecord::RecvWithAddress:
396 case TransactionRecord::RecvFromOther:
397 return QIcon(":/icons/tx_input");
398 case TransactionRecord::SendToAddress:
399 case TransactionRecord::SendToOther:
400 return QIcon(":/icons/tx_output");
402 return QIcon(":/icons/tx_inout");
407 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
411 case TransactionRecord::RecvFromOther:
412 return QString::fromStdString(wtx->address);
413 case TransactionRecord::RecvWithAddress:
414 case TransactionRecord::SendToAddress:
415 case TransactionRecord::Generated:
416 return lookupAddress(wtx->address, tooltip);
417 case TransactionRecord::SendToOther:
418 return QString::fromStdString(wtx->address);
419 case TransactionRecord::SendToSelf:
425 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
427 // Show addresses without label in a less visible color
430 case TransactionRecord::RecvWithAddress:
431 case TransactionRecord::SendToAddress:
432 case TransactionRecord::Generated:
434 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
436 return COLOR_BAREADDRESS;
438 case TransactionRecord::SendToSelf:
439 return COLOR_BAREADDRESS;
446 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
448 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
451 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
453 str = QString("[") + str + QString("]");
459 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
461 if(wtx->type == TransactionRecord::Generated)
463 switch(wtx->status.maturity)
465 case TransactionStatus::Immature: {
466 int total = wtx->status.depth + wtx->status.matures_in;
467 int part = (wtx->status.depth * 4 / total) + 1;
468 return QIcon(QString(":/icons/transaction_%1").arg(part));
470 case TransactionStatus::Mature:
471 return QIcon(":/icons/transaction_confirmed");
472 case TransactionStatus::MaturesWarning:
473 case TransactionStatus::NotAccepted:
474 return QIcon(":/icons/transaction_0");
479 switch(wtx->status.status)
481 case TransactionStatus::OpenUntilBlock:
482 case TransactionStatus::OpenUntilDate:
483 return QColor(64,64,255);
485 case TransactionStatus::Offline:
486 return QColor(192,192,192);
487 case TransactionStatus::Unconfirmed:
488 switch(wtx->status.depth)
490 case 0: return QIcon(":/icons/transaction_0");
491 case 1: return QIcon(":/icons/transaction_1");
492 case 2: return QIcon(":/icons/transaction_2");
493 case 3: return QIcon(":/icons/transaction_3");
494 case 4: return QIcon(":/icons/transaction_4");
495 default: return QIcon(":/icons/transaction_5");
497 case TransactionStatus::HaveConfirmations:
498 return QIcon(":/icons/transaction_confirmed");
501 return QColor(0,0,0);
504 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
506 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
507 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
508 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
510 tooltip += QString(" ") + formatTxToAddress(rec, true);
515 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
519 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
523 case Qt::DecorationRole:
524 switch(index.column())
527 return txStatusDecoration(rec);
529 return txAddressDecoration(rec);
532 case Qt::DisplayRole:
533 switch(index.column())
536 return formatTxDate(rec);
538 return formatTxType(rec);
540 return formatTxToAddress(rec, false);
542 return formatTxAmount(rec);
546 // Edit role is used for sorting, so return the unformatted values
547 switch(index.column())
550 return QString::fromStdString(rec->status.sortKey);
552 // We need cast here to prevent ambigious conversion error
553 return static_cast<qlonglong>(rec->time);
555 return formatTxType(rec);
557 return formatTxToAddress(rec, true);
560 return static_cast<qlonglong>(rec->credit + rec->debit);
563 case Qt::ToolTipRole:
564 return formatTooltip(rec);
565 case Qt::TextAlignmentRole:
566 return column_alignments[index.column()];
567 case Qt::ForegroundRole:
568 // Non-confirmed transactions are grey
569 if(!rec->status.confirmed)
571 return COLOR_UNCONFIRMED;
573 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
575 return COLOR_NEGATIVE;
577 if(index.column() == ToAddress)
579 return addressColor(rec);
585 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
586 case LongDescriptionRole:
587 return priv->describe(rec);
589 return QString::fromStdString(rec->address);
591 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
594 return static_cast<qlonglong>(rec->credit + rec->debit);
596 return QString::fromStdString(rec->getTxID());
598 return QString::fromStdString(rec->hash.ToString());
600 // Return True if transaction counts for balance
601 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
602 case FormattedAmountRole:
603 return formatTxAmount(rec, false);
608 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
610 if(orientation == Qt::Horizontal)
612 if(role == Qt::DisplayRole)
614 return columns[section];
616 else if (role == Qt::TextAlignmentRole)
618 return column_alignments[section];
619 } else if (role == Qt::ToolTipRole)
624 return tr("Transaction status. Hover over this field to show number of confirmations.");
626 return tr("Date and time that the transaction was received.");
628 return tr("Type of transaction.");
630 return tr("Destination address of transaction.");
632 return tr("Amount removed from or added to balance.");
639 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
642 TransactionRecord *data = priv->index(row);
645 return createIndex(row, column, priv->index(row));
649 return QModelIndex();
653 void TransactionTableModel::updateDisplayUnit()
655 updateAmountColumnTitle();
656 // emit dataChanged to update Amount column with the current unit
657 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));