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);
298 if(wtx->type == TransactionRecord::Generated)
300 switch(wtx->status.maturity)
302 case TransactionStatus::Immature:
303 status += "\n" + tr("Mined balance will be available when it matures in %n more block(s)", "", wtx->status.matures_in);
305 case TransactionStatus::Mature:
307 case TransactionStatus::MaturesWarning:
308 status += "\n" + tr("This block was not received by any other nodes and will probably not be accepted!");
310 case TransactionStatus::NotAccepted:
311 status += "\n" + tr("Generated but not accepted");
319 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
323 return GUIUtil::dateTimeStr(wtx->time);
331 /* Look up address in address book, if found return label (address)
332 otherwise just return (address)
334 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
336 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
340 description += label + QString(" ");
342 if(label.isEmpty() || walletModel->getOptionsModel()->getDisplayAddresses() || tooltip)
344 description += QString("(") + QString::fromStdString(address) + QString(")");
349 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
353 case TransactionRecord::RecvWithAddress:
354 return tr("Received with");
355 case TransactionRecord::RecvFromOther:
356 return tr("Received from");
357 case TransactionRecord::SendToAddress:
358 case TransactionRecord::SendToOther:
359 return tr("Sent to");
360 case TransactionRecord::SendToSelf:
361 return tr("Payment to yourself");
362 case TransactionRecord::Generated:
369 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
373 case TransactionRecord::Generated:
374 return QIcon(":/icons/tx_mined");
375 case TransactionRecord::RecvWithAddress:
376 case TransactionRecord::RecvFromOther:
377 return QIcon(":/icons/tx_input");
378 case TransactionRecord::SendToAddress:
379 case TransactionRecord::SendToOther:
380 return QIcon(":/icons/tx_output");
382 return QIcon(":/icons/tx_inout");
387 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
391 case TransactionRecord::RecvFromOther:
392 return QString::fromStdString(wtx->address);
393 case TransactionRecord::RecvWithAddress:
394 case TransactionRecord::SendToAddress:
395 case TransactionRecord::Generated:
396 return lookupAddress(wtx->address, tooltip);
397 case TransactionRecord::SendToOther:
398 return QString::fromStdString(wtx->address);
399 case TransactionRecord::SendToSelf:
405 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
407 // Show addresses without label in a less visible color
410 case TransactionRecord::RecvWithAddress:
411 case TransactionRecord::SendToAddress:
412 case TransactionRecord::Generated:
414 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
416 return COLOR_BAREADDRESS;
418 case TransactionRecord::SendToSelf:
419 return COLOR_BAREADDRESS;
426 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
428 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
431 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
433 str = QString("[") + str + QString("]");
439 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
441 if(wtx->type == TransactionRecord::Generated)
443 switch(wtx->status.maturity)
445 case TransactionStatus::Immature: {
446 int total = wtx->status.depth + wtx->status.matures_in;
447 int part = (wtx->status.depth * 4 / total) + 1;
448 return QIcon(QString(":/icons/transaction_%1").arg(part));
450 case TransactionStatus::Mature:
451 return QIcon(":/icons/transaction_confirmed");
452 case TransactionStatus::MaturesWarning:
453 case TransactionStatus::NotAccepted:
454 return QIcon(":/icons/transaction_0");
459 switch(wtx->status.status)
461 case TransactionStatus::OpenUntilBlock:
462 case TransactionStatus::OpenUntilDate:
463 return QColor(64,64,255);
465 case TransactionStatus::Offline:
466 return QColor(192,192,192);
467 case TransactionStatus::Unconfirmed:
468 switch(wtx->status.depth)
470 case 0: return QIcon(":/icons/transaction_0");
471 case 1: return QIcon(":/icons/transaction_1");
472 case 2: return QIcon(":/icons/transaction_2");
473 case 3: return QIcon(":/icons/transaction_3");
474 case 4: return QIcon(":/icons/transaction_4");
475 default: return QIcon(":/icons/transaction_5");
477 case TransactionStatus::HaveConfirmations:
478 return QIcon(":/icons/transaction_confirmed");
481 return QColor(0,0,0);
484 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
486 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
487 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
488 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
490 tooltip += QString(" ") + formatTxToAddress(rec, true);
495 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
499 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
503 case Qt::DecorationRole:
504 switch(index.column())
507 return txStatusDecoration(rec);
509 return txAddressDecoration(rec);
512 case Qt::DisplayRole:
513 switch(index.column())
516 return formatTxDate(rec);
518 return formatTxType(rec);
520 return formatTxToAddress(rec, false);
522 return formatTxAmount(rec);
526 // Edit role is used for sorting, so return the unformatted values
527 switch(index.column())
530 return QString::fromStdString(rec->status.sortKey);
534 return formatTxType(rec);
536 return formatTxToAddress(rec, true);
538 return rec->credit + rec->debit;
541 case Qt::ToolTipRole:
542 return formatTooltip(rec);
543 case Qt::TextAlignmentRole:
544 return column_alignments[index.column()];
545 case Qt::ForegroundRole:
546 // Non-confirmed transactions are grey
547 if(!rec->status.confirmed)
549 return COLOR_UNCONFIRMED;
551 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
553 return COLOR_NEGATIVE;
555 if(index.column() == ToAddress)
557 return addressColor(rec);
563 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
564 case LongDescriptionRole:
565 return priv->describe(rec);
567 return QString::fromStdString(rec->address);
569 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
571 return rec->credit + rec->debit;
573 return QString::fromStdString(rec->getTxID());
575 // Return True if transaction counts for balance
576 return rec->status.confirmed && !(rec->type == TransactionRecord::Generated &&
577 rec->status.maturity != TransactionStatus::Mature);
578 case FormattedAmountRole:
579 return formatTxAmount(rec, false);
584 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
586 if(orientation == Qt::Horizontal)
588 if(role == Qt::DisplayRole)
590 return columns[section];
592 else if (role == Qt::TextAlignmentRole)
594 return column_alignments[section];
595 } else if (role == Qt::ToolTipRole)
600 return tr("Transaction status. Hover over this field to show number of confirmations.");
602 return tr("Date and time that the transaction was received.");
604 return tr("Type of transaction.");
606 return tr("Destination address of transaction.");
608 return tr("Amount removed from or added to balance.");
615 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
618 TransactionRecord *data = priv->index(row);
621 return createIndex(row, column, priv->index(row));
625 return QModelIndex();
629 void TransactionTableModel::updateDisplayUnit()
631 // emit dataChanged to update Amount column with the current unit
632 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));