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 || wtx->type == TransactionRecord::StakeMint)
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::StakeMint:
363 case TransactionRecord::Generated:
370 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
374 case TransactionRecord::Generated:
375 case TransactionRecord::StakeMint:
376 return QIcon(":/icons/tx_mined");
377 case TransactionRecord::RecvWithAddress:
378 case TransactionRecord::RecvFromOther:
379 return QIcon(":/icons/tx_input");
380 case TransactionRecord::SendToAddress:
381 case TransactionRecord::SendToOther:
382 return QIcon(":/icons/tx_output");
384 return QIcon(":/icons/tx_inout");
389 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
393 case TransactionRecord::RecvFromOther:
394 return QString::fromStdString(wtx->address);
395 case TransactionRecord::RecvWithAddress:
396 case TransactionRecord::SendToAddress:
397 case TransactionRecord::Generated:
398 return lookupAddress(wtx->address, tooltip);
399 case TransactionRecord::SendToOther:
400 return QString::fromStdString(wtx->address);
401 case TransactionRecord::SendToSelf:
407 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
409 // Show addresses without label in a less visible color
412 case TransactionRecord::RecvWithAddress:
413 case TransactionRecord::SendToAddress:
414 case TransactionRecord::Generated:
416 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
418 return COLOR_BAREADDRESS;
420 case TransactionRecord::SendToSelf:
421 return COLOR_BAREADDRESS;
428 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed) const
430 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit);
433 if(!wtx->status.confirmed || wtx->status.maturity != TransactionStatus::Mature)
435 str = QString("[") + str + QString("]");
441 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
443 if(wtx->type == TransactionRecord::Generated || wtx->type == TransactionRecord::StakeMint)
445 switch(wtx->status.maturity)
447 case TransactionStatus::Immature: {
448 int total = wtx->status.depth + wtx->status.matures_in;
449 int part = (wtx->status.depth * 4 / total) + 1;
450 return QIcon(QString(":/icons/transaction_%1").arg(part));
452 case TransactionStatus::Mature:
453 return QIcon(":/icons/transaction_confirmed");
454 case TransactionStatus::MaturesWarning:
455 case TransactionStatus::NotAccepted:
456 return QIcon(":/icons/transaction_0");
461 switch(wtx->status.status)
463 case TransactionStatus::OpenUntilBlock:
464 case TransactionStatus::OpenUntilDate:
465 return QColor(64,64,255);
467 case TransactionStatus::Offline:
468 return QColor(192,192,192);
469 case TransactionStatus::Unconfirmed:
470 switch(wtx->status.depth)
472 case 0: return QIcon(":/icons/transaction_0");
473 case 1: return QIcon(":/icons/transaction_1");
474 case 2: return QIcon(":/icons/transaction_2");
475 case 3: return QIcon(":/icons/transaction_3");
476 case 4: return QIcon(":/icons/transaction_4");
477 default: return QIcon(":/icons/transaction_5");
479 case TransactionStatus::HaveConfirmations:
480 return QIcon(":/icons/transaction_confirmed");
483 return QColor(0,0,0);
486 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
488 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
489 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
490 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
492 tooltip += QString(" ") + formatTxToAddress(rec, true);
497 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
501 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
505 case Qt::DecorationRole:
506 switch(index.column())
509 return txStatusDecoration(rec);
511 return txAddressDecoration(rec);
514 case Qt::DisplayRole:
515 switch(index.column())
518 return formatTxDate(rec);
520 return formatTxType(rec);
522 return formatTxToAddress(rec, false);
524 return formatTxAmount(rec);
528 // Edit role is used for sorting, so return the unformatted values
529 switch(index.column())
532 return QString::fromStdString(rec->status.sortKey);
536 return formatTxType(rec);
538 return formatTxToAddress(rec, true);
540 return rec->credit + rec->debit;
543 case Qt::ToolTipRole:
544 return formatTooltip(rec);
545 case Qt::TextAlignmentRole:
546 return column_alignments[index.column()];
547 case Qt::ForegroundRole:
548 // Non-confirmed transactions are grey
549 if(!rec->status.confirmed)
551 return COLOR_UNCONFIRMED;
553 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
555 return COLOR_NEGATIVE;
557 if(index.column() == ToAddress)
559 return addressColor(rec);
565 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
566 case LongDescriptionRole:
567 return priv->describe(rec);
569 return QString::fromStdString(rec->address);
571 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
573 return rec->credit + rec->debit;
575 return QString::fromStdString(rec->getTxID());
577 // Return True if transaction counts for balance
578 return rec->status.confirmed && !((rec->type == TransactionRecord::Generated || rec->type == TransactionRecord::StakeMint) &&
579 rec->status.maturity != TransactionStatus::Mature);
580 case FormattedAmountRole:
581 return formatTxAmount(rec, false);
586 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
588 if(orientation == Qt::Horizontal)
590 if(role == Qt::DisplayRole)
592 return columns[section];
594 else if (role == Qt::TextAlignmentRole)
596 return column_alignments[section];
597 } else if (role == Qt::ToolTipRole)
602 return tr("Transaction status. Hover over this field to show number of confirmations.");
604 return tr("Date and time that the transaction was received.");
606 return tr("Type of transaction.");
608 return tr("Destination address of transaction.");
610 return tr("Amount removed from or added to balance.");
617 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
620 TransactionRecord *data = priv->index(row);
623 return createIndex(row, column, priv->index(row));
627 return QModelIndex();
631 void TransactionTableModel::updateDisplayUnit()
633 // emit dataChanged to update Amount column with the current unit
634 emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));