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
293 switch(wtx->status.status)
295 case TransactionStatus::OpenUntilBlock:
296 status = tr("Open for %n block(s)","",wtx->status.open_for);
298 case TransactionStatus::OpenUntilDate:
299 status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
301 case TransactionStatus::Offline:
302 status = tr("Offline (%1 confirmations)").arg(wtx->status.depth);
304 case TransactionStatus::Unconfirmed:
305 status = tr("Unconfirmed (%1 of %2 confirmations)").arg(wtx->status.depth).arg(TransactionRecord::NumConfirmations);
307 case TransactionStatus::HaveConfirmations:
308 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
312 if(wtx->type == TransactionRecord::Generated)
314 switch(wtx->status.maturity)
316 case TransactionStatus::Immature:
317 status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
319 case TransactionStatus::Mature:
321 case TransactionStatus::MaturesWarning:
322 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
324 case TransactionStatus::NotAccepted:
325 status += "\n" + tr("Generated but not accepted");
333 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
337 return GUIUtil::dateTimeStr(wtx->time);
345 /* Look up address in address book, if found return label (address)
346 otherwise just return (address)
348 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
350 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
354 description += label + QString(" ");
356 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
358 description += QString("(") + QString::fromStdString(address) + QString(")");
363 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
367 case TransactionRecord::RecvWithAddress:
368 return tr("Received with");
369 case TransactionRecord::RecvFromOther:
370 return tr("Received from");
371 case TransactionRecord::SendToAddress:
372 case TransactionRecord::SendToOther:
373 return tr("Sent to");
374 case TransactionRecord::SendToSelf:
375 return tr("Payment to yourself");
376 case TransactionRecord::Generated:
383 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
387 case TransactionRecord::Generated:
388 return QIcon(":/icons/tx_mined");
389 case TransactionRecord::RecvWithAddress:
390 case TransactionRecord::RecvFromOther:
391 return QIcon(":/icons/tx_input");
392 case TransactionRecord::SendToAddress:
393 case TransactionRecord::SendToOther:
394 return QIcon(":/icons/tx_output");
396 return QIcon(":/icons/tx_inout");
401 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
405 case TransactionRecord::RecvFromOther:
406 return QString::fromStdString(wtx->address);
407 case TransactionRecord::RecvWithAddress:
408 case TransactionRecord::SendToAddress:
409 case TransactionRecord::Generated:
410 return lookupAddress(wtx->address, tooltip);
411 case TransactionRecord::SendToOther:
412 return QString::fromStdString(wtx->address);
413 case TransactionRecord::SendToSelf:
419 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
421 // Show addresses without label in a less visible color
424 case TransactionRecord::RecvWithAddress:
425 case TransactionRecord::SendToAddress:
426 case TransactionRecord::Generated:
428 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
430 return COLOR_BAREADDRESS;
432 case TransactionRecord::SendToSelf:
433 return COLOR_BAREADDRESS;
440 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
442 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
445 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
447 str = QString("[") + str + QString("]");
453 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
455 if(wtx->type == TransactionRecord::Generated)
457 switch(wtx->status.maturity)
459 case TransactionStatus::Immature: {
460 int total = wtx->status.depth + wtx->status.matures_in;
461 int part = (wtx->status.depth * 4 / total) + 1;
462 return QIcon(QString(":/icons/transaction_%1").arg(part));
464 case TransactionStatus::Mature:
465 return QIcon(":/icons/transaction_confirmed");
466 case TransactionStatus::MaturesWarning:
467 case TransactionStatus::NotAccepted:
468 return QIcon(":/icons/transaction_0");
473 switch(wtx->status.status)
475 case TransactionStatus::OpenUntilBlock:
476 case TransactionStatus::OpenUntilDate:
477 return QColor(64,64,255);
479 case TransactionStatus::Offline:
480 return QColor(192,192,192);
481 case TransactionStatus::Unconfirmed:
482 switch(wtx->status.depth)
484 case 0: return QIcon(":/icons/transaction_0");
485 case 1: return QIcon(":/icons/transaction_1");
486 case 2: return QIcon(":/icons/transaction_2");
487 case 3: return QIcon(":/icons/transaction_3");
488 case 4: return QIcon(":/icons/transaction_4");
489 default: return QIcon(":/icons/transaction_5");
491 case TransactionStatus::HaveConfirmations:
492 return QIcon(":/icons/transaction_confirmed");
495 return QColor(0,0,0);
498 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
500 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
501 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
502 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
504 tooltip += QString(" ") + formatTxToAddress(rec, true);
509 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
513 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
517 case Qt::DecorationRole:
518 switch(index.column())
521 return txStatusDecoration(rec);
523 return txAddressDecoration(rec);
526 case Qt::DisplayRole:
527 switch(index.column())
530 return formatTxDate(rec);
532 return formatTxType(rec);
534 return formatTxToAddress(rec, false);
536 return formatTxAmount(rec);
540 // Edit role is used for sorting, so return the unformatted values
541 switch(index.column())
544 return QString::fromStdString(rec->status.sortKey);
546 // We need cast here to prevent ambigious conversion error
547 return static_cast<qlonglong>(rec->time);
549 return formatTxType(rec);
551 return formatTxToAddress(rec, true);
554 return static_cast<qlonglong>(rec->credit + rec->debit);
557 case Qt::ToolTipRole:
558 return formatTooltip(rec);
559 case Qt::TextAlignmentRole:
560 return column_alignments[index.column()];
561 case Qt::ForegroundRole:
562 // Non-confirmed transactions are grey
563 if(!rec->status.confirmed)
565 return COLOR_UNCONFIRMED;
567 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
569 return COLOR_NEGATIVE;
571 if(index.column() == ToAddress)
573 return addressColor(rec);
579 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
580 case LongDescriptionRole:
581 return priv->describe(rec);
583 return QString::fromStdString(rec->address);
585 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
588 return static_cast<qlonglong>(rec->credit + rec->debit);
590 return QString::fromStdString(rec->getTxID());
592 return QString::fromStdString(rec->hash.ToString());
594 // Return True if transaction counts for balance
595 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated && rec->status.maturity != TransactionStatus::Mature);
596 case FormattedAmountRole:
597 return formatTxAmount(rec, false);
602 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
604 if(orientation == Qt::Horizontal)
606 if(role == Qt::DisplayRole)
608 return columns[section];
610 else if (role == Qt::TextAlignmentRole)
612 return column_alignments[section];
613 } else if (role == Qt::ToolTipRole)
618 return tr("Transaction status. Hover over this field to show number of confirmations.");
620 return tr("Date and time that the transaction was received.");
622 return tr("Type of transaction.");
624 return tr("Destination address of transaction.");
626 return tr("Amount removed from or added to balance.");
633 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
636 TransactionRecord *data = priv->index(row);
639 return createIndex(row, column, priv->index(row));
643 return QModelIndex();
647 void TransactionTableModel::updateDisplayUnit()
649 updateAmountColumnTitle();
650 // emit dataChanged to update Amount column with the current unit
651 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));