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");
406 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
410 case TransactionRecord::RecvFromOther:
411 return QString::fromStdString(wtx->address);
412 case TransactionRecord::RecvWithAddress:
413 case TransactionRecord::SendToAddress:
414 case TransactionRecord::Generated:
415 return lookupAddress(wtx->address, tooltip);
416 case TransactionRecord::SendToOther:
417 return QString::fromStdString(wtx->address);
418 case TransactionRecord::SendToSelf:
424 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
426 // Show addresses without label in a less visible color
429 case TransactionRecord::RecvWithAddress:
430 case TransactionRecord::SendToAddress:
431 case TransactionRecord::Generated:
433 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
435 return COLOR_BAREADDRESS;
437 case TransactionRecord::SendToSelf:
438 return COLOR_BAREADDRESS;
445 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
447 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
450 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
452 str = QString("[") + str + QString("]");
458 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
460 if(wtx->type == TransactionRecord::Generated)
462 switch(wtx->status.maturity)
464 case TransactionStatus::Immature: {
465 int total = wtx->status.depth + wtx->status.matures_in;
466 int part = (wtx->status.depth * 4 / total) + 1;
467 return QIcon(QString(":/icons/transaction_%1").arg(part));
469 case TransactionStatus::Mature:
470 return QIcon(":/icons/transaction_confirmed");
471 case TransactionStatus::MaturesWarning:
472 case TransactionStatus::NotAccepted:
473 return QIcon(":/icons/transaction_0");
478 switch(wtx->status.status)
480 case TransactionStatus::OpenUntilBlock:
481 case TransactionStatus::OpenUntilDate:
482 return QColor(64,64,255);
484 case TransactionStatus::Offline:
485 return QColor(192,192,192);
486 case TransactionStatus::Unconfirmed:
487 switch(wtx->status.depth)
489 case 0: return QIcon(":/icons/transaction_0");
490 case 1: return QIcon(":/icons/transaction_1");
491 case 2: return QIcon(":/icons/transaction_2");
492 case 3: return QIcon(":/icons/transaction_3");
493 case 4: return QIcon(":/icons/transaction_4");
494 default: return QIcon(":/icons/transaction_5");
496 case TransactionStatus::HaveConfirmations:
497 return QIcon(":/icons/transaction_confirmed");
500 return QColor(0,0,0);
503 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
505 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
506 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
507 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
509 tooltip += QString(" ") + formatTxToAddress(rec, true);
514 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
518 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
522 case Qt::DecorationRole:
523 switch(index.column())
526 return txStatusDecoration(rec);
528 return txAddressDecoration(rec);
531 case Qt::DisplayRole:
532 switch(index.column())
535 return formatTxDate(rec);
537 return formatTxType(rec);
539 return formatTxToAddress(rec, false);
541 return formatTxAmount(rec);
545 // Edit role is used for sorting, so return the unformatted values
546 switch(index.column())
549 return QString::fromStdString(rec->status.sortKey);
551 // We need cast here to prevent ambigious conversion error
552 return static_cast<qlonglong>(rec->time);
554 return formatTxType(rec);
556 return formatTxToAddress(rec, true);
559 return static_cast<qlonglong>(rec->credit + rec->debit);
562 case Qt::ToolTipRole:
563 return formatTooltip(rec);
564 case Qt::TextAlignmentRole:
565 return column_alignments[index.column()];
566 case Qt::ForegroundRole:
567 // Non-confirmed transactions are grey
568 if(!rec->status.confirmed)
570 return COLOR_UNCONFIRMED;
572 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
574 return COLOR_NEGATIVE;
576 if(index.column() == ToAddress)
578 return addressColor(rec);
584 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
585 case LongDescriptionRole:
586 return priv->describe(rec);
588 return QString::fromStdString(rec->address);
590 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
593 return static_cast<qlonglong>(rec->credit + rec->debit);
595 return QString::fromStdString(rec->getTxID());
597 return QString::fromStdString(rec->hash.ToString());
599 // Return True if transaction counts for balance
600 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
601 case FormattedAmountRole:
602 return formatTxAmount(rec, false);
607 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
609 if(orientation == Qt::Horizontal)
611 if(role == Qt::DisplayRole)
613 return columns[section];
615 else if (role == Qt::TextAlignmentRole)
617 return column_alignments[section];
618 } else if (role == Qt::ToolTipRole)
623 return tr("Transaction status. Hover over this field to show number of confirmations.");
625 return tr("Date and time that the transaction was received.");
627 return tr("Type of transaction.");
629 return tr("Destination address of transaction.");
631 return tr("Amount removed from or added to balance.");
638 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
641 TransactionRecord *data = priv->index(row);
644 return createIndex(row, column, priv->index(row));
648 return QModelIndex();
652 void TransactionTableModel::updateDisplayUnit()
654 updateAmountColumnTitle();
655 // emit dataChanged to update Amount column with the current unit
656 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));