[gitian] Use vm-builder_0.12.4+bzr494 on Debian
[bitcoinplatinum.git] / src / qt / transactiontablemodel.cpp
blob98ad1a44b68fac46ac109e223a49d9dbc23bafe2
1 // Copyright (c) 2011-2014 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 #include "transactiontablemodel.h"
7 #include "addresstablemodel.h"
8 #include "guiconstants.h"
9 #include "guiutil.h"
10 #include "optionsmodel.h"
11 #include "platformstyle.h"
12 #include "transactiondesc.h"
13 #include "transactionrecord.h"
14 #include "walletmodel.h"
16 #include "main.h"
17 #include "sync.h"
18 #include "uint256.h"
19 #include "util.h"
20 #include "wallet/wallet.h"
22 #include <QColor>
23 #include <QDateTime>
24 #include <QDebug>
25 #include <QIcon>
26 #include <QList>
28 #include <boost/foreach.hpp>
30 // Amount column is right-aligned it contains numbers
31 static int column_alignments[] = {
32 Qt::AlignLeft|Qt::AlignVCenter, /* status */
33 Qt::AlignLeft|Qt::AlignVCenter, /* watchonly */
34 Qt::AlignLeft|Qt::AlignVCenter, /* date */
35 Qt::AlignLeft|Qt::AlignVCenter, /* type */
36 Qt::AlignLeft|Qt::AlignVCenter, /* address */
37 Qt::AlignRight|Qt::AlignVCenter /* amount */
40 // Comparison operator for sort/binary search of model tx list
41 struct TxLessThan
43 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
45 return a.hash < b.hash;
47 bool operator()(const TransactionRecord &a, const uint256 &b) const
49 return a.hash < b;
51 bool operator()(const uint256 &a, const TransactionRecord &b) const
53 return a < b.hash;
57 // Private implementation
58 class TransactionTablePriv
60 public:
61 TransactionTablePriv(CWallet *wallet, TransactionTableModel *parent) :
62 wallet(wallet),
63 parent(parent)
67 CWallet *wallet;
68 TransactionTableModel *parent;
70 /* Local cache of wallet.
71 * As it is in the same order as the CWallet, by definition
72 * this is sorted by sha256.
74 QList<TransactionRecord> cachedWallet;
76 /* Query entire wallet anew from core.
78 void refreshWallet()
80 qDebug() << "TransactionTablePriv::refreshWallet";
81 cachedWallet.clear();
83 LOCK2(cs_main, wallet->cs_wallet);
84 for(std::map<uint256, CWalletTx>::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it)
86 if(TransactionRecord::showTransaction(it->second))
87 cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second));
92 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
93 with that of the core.
95 Call with transaction that was added, removed or changed.
97 void updateWallet(const uint256 &hash, int status, bool showTransaction)
99 qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
101 // Find bounds of this transaction in model
102 QList<TransactionRecord>::iterator lower = qLowerBound(
103 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
104 QList<TransactionRecord>::iterator upper = qUpperBound(
105 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
106 int lowerIndex = (lower - cachedWallet.begin());
107 int upperIndex = (upper - cachedWallet.begin());
108 bool inModel = (lower != upper);
110 if(status == CT_UPDATED)
112 if(showTransaction && !inModel)
113 status = CT_NEW; /* Not in model, but want to show, treat as new */
114 if(!showTransaction && inModel)
115 status = CT_DELETED; /* In model, but want to hide, treat as deleted */
118 qDebug() << " inModel=" + QString::number(inModel) +
119 " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
120 " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
122 switch(status)
124 case CT_NEW:
125 if(inModel)
127 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is already in model";
128 break;
130 if(showTransaction)
132 LOCK2(cs_main, wallet->cs_wallet);
133 // Find transaction in wallet
134 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
135 if(mi == wallet->mapWallet.end())
137 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is not in wallet";
138 break;
140 // Added -- insert at the right position
141 QList<TransactionRecord> toInsert =
142 TransactionRecord::decomposeTransaction(wallet, mi->second);
143 if(!toInsert.isEmpty()) /* only if something to insert */
145 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
146 int insert_idx = lowerIndex;
147 Q_FOREACH(const TransactionRecord &rec, toInsert)
149 cachedWallet.insert(insert_idx, rec);
150 insert_idx += 1;
152 parent->endInsertRows();
155 break;
156 case CT_DELETED:
157 if(!inModel)
159 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_DELETED, but transaction is not in model";
160 break;
162 // Removed -- remove entire transaction from table
163 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
164 cachedWallet.erase(lower, upper);
165 parent->endRemoveRows();
166 break;
167 case CT_UPDATED:
168 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
169 // visible transactions.
170 break;
174 int size()
176 return cachedWallet.size();
179 TransactionRecord *index(int idx)
181 if(idx >= 0 && idx < cachedWallet.size())
183 TransactionRecord *rec = &cachedWallet[idx];
185 // Get required locks upfront. This avoids the GUI from getting
186 // stuck if the core is holding the locks for a longer time - for
187 // example, during a wallet rescan.
189 // If a status update is needed (blocks came in since last check),
190 // update the status of this transaction from the wallet. Otherwise,
191 // simply re-use the cached status.
192 TRY_LOCK(cs_main, lockMain);
193 if(lockMain)
195 TRY_LOCK(wallet->cs_wallet, lockWallet);
196 if(lockWallet && rec->statusUpdateNeeded())
198 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
200 if(mi != wallet->mapWallet.end())
202 rec->updateStatus(mi->second);
206 return rec;
208 return 0;
211 QString describe(TransactionRecord *rec, int unit)
214 LOCK2(cs_main, wallet->cs_wallet);
215 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(rec->hash);
216 if(mi != wallet->mapWallet.end())
218 return TransactionDesc::toHTML(wallet, mi->second, rec, unit);
221 return QString();
225 TransactionTableModel::TransactionTableModel(const PlatformStyle *platformStyle, CWallet* wallet, WalletModel *parent):
226 QAbstractTableModel(parent),
227 wallet(wallet),
228 walletModel(parent),
229 priv(new TransactionTablePriv(wallet, this)),
230 fProcessingQueuedTransactions(false),
231 platformStyle(platformStyle)
233 columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
234 priv->refreshWallet();
236 connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
238 subscribeToCoreSignals();
241 TransactionTableModel::~TransactionTableModel()
243 unsubscribeFromCoreSignals();
244 delete priv;
247 /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
248 void TransactionTableModel::updateAmountColumnTitle()
250 columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
251 Q_EMIT headerDataChanged(Qt::Horizontal,Amount,Amount);
254 void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
256 uint256 updated;
257 updated.SetHex(hash.toStdString());
259 priv->updateWallet(updated, status, showTransaction);
262 void TransactionTableModel::updateConfirmations()
264 // Blocks came in since last poll.
265 // Invalidate status (number of confirmations) and (possibly) description
266 // for all rows. Qt is smart enough to only actually request the data for the
267 // visible rows.
268 Q_EMIT dataChanged(index(0, Status), index(priv->size()-1, Status));
269 Q_EMIT dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
272 int TransactionTableModel::rowCount(const QModelIndex &parent) const
274 Q_UNUSED(parent);
275 return priv->size();
278 int TransactionTableModel::columnCount(const QModelIndex &parent) const
280 Q_UNUSED(parent);
281 return columns.length();
284 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
286 QString status;
288 switch(wtx->status.status)
290 case TransactionStatus::OpenUntilBlock:
291 status = tr("Open for %n more block(s)","",wtx->status.open_for);
292 break;
293 case TransactionStatus::OpenUntilDate:
294 status = tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx->status.open_for));
295 break;
296 case TransactionStatus::Offline:
297 status = tr("Offline");
298 break;
299 case TransactionStatus::Unconfirmed:
300 status = tr("Unconfirmed");
301 break;
302 case TransactionStatus::Confirming:
303 status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations);
304 break;
305 case TransactionStatus::Confirmed:
306 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
307 break;
308 case TransactionStatus::Conflicted:
309 status = tr("Conflicted");
310 break;
311 case TransactionStatus::Immature:
312 status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in);
313 break;
314 case TransactionStatus::MaturesWarning:
315 status = tr("This block was not received by any other nodes and will probably not be accepted!");
316 break;
317 case TransactionStatus::NotAccepted:
318 status = tr("Generated but not accepted");
319 break;
322 return status;
325 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
327 if(wtx->time)
329 return GUIUtil::dateTimeStr(wtx->time);
331 return QString();
334 /* Look up address in address book, if found return label (address)
335 otherwise just return (address)
337 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
339 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
340 QString description;
341 if(!label.isEmpty())
343 description += label;
345 if(label.isEmpty() || tooltip)
347 description += QString(" (") + QString::fromStdString(address) + QString(")");
349 return description;
352 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
354 switch(wtx->type)
356 case TransactionRecord::RecvWithAddress:
357 return tr("Received with");
358 case TransactionRecord::RecvFromOther:
359 return tr("Received from");
360 case TransactionRecord::SendToAddress:
361 case TransactionRecord::SendToOther:
362 return tr("Sent to");
363 case TransactionRecord::SendToSelf:
364 return tr("Payment to yourself");
365 case TransactionRecord::Generated:
366 return tr("Mined");
367 default:
368 return QString();
372 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
374 switch(wtx->type)
376 case TransactionRecord::Generated:
377 return QIcon(":/icons/tx_mined");
378 case TransactionRecord::RecvWithAddress:
379 case TransactionRecord::RecvFromOther:
380 return QIcon(":/icons/tx_input");
381 case TransactionRecord::SendToAddress:
382 case TransactionRecord::SendToOther:
383 return QIcon(":/icons/tx_output");
384 default:
385 return QIcon(":/icons/tx_inout");
389 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
391 QString watchAddress;
392 if (tooltip) {
393 // Mark transactions involving watch-only addresses by adding " (watch-only)"
394 watchAddress = wtx->involvesWatchAddress ? QString(" (") + tr("watch-only") + QString(")") : "";
397 switch(wtx->type)
399 case TransactionRecord::RecvFromOther:
400 return QString::fromStdString(wtx->address) + watchAddress;
401 case TransactionRecord::RecvWithAddress:
402 case TransactionRecord::SendToAddress:
403 case TransactionRecord::Generated:
404 return lookupAddress(wtx->address, tooltip) + watchAddress;
405 case TransactionRecord::SendToOther:
406 return QString::fromStdString(wtx->address) + watchAddress;
407 case TransactionRecord::SendToSelf:
408 default:
409 return tr("(n/a)") + watchAddress;
413 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
415 // Show addresses without label in a less visible color
416 switch(wtx->type)
418 case TransactionRecord::RecvWithAddress:
419 case TransactionRecord::SendToAddress:
420 case TransactionRecord::Generated:
422 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
423 if(label.isEmpty())
424 return COLOR_BAREADDRESS;
425 } break;
426 case TransactionRecord::SendToSelf:
427 return COLOR_BAREADDRESS;
428 default:
429 break;
431 return QVariant();
434 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const
436 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators);
437 if(showUnconfirmed)
439 if(!wtx->status.countsForBalance)
441 str = QString("[") + str + QString("]");
444 return QString(str);
447 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
449 switch(wtx->status.status)
451 case TransactionStatus::OpenUntilBlock:
452 case TransactionStatus::OpenUntilDate:
453 return COLOR_TX_STATUS_OPENUNTILDATE;
454 case TransactionStatus::Offline:
455 return COLOR_TX_STATUS_OFFLINE;
456 case TransactionStatus::Unconfirmed:
457 return QIcon(":/icons/transaction_0");
458 case TransactionStatus::Confirming:
459 switch(wtx->status.depth)
461 case 1: return QIcon(":/icons/transaction_1");
462 case 2: return QIcon(":/icons/transaction_2");
463 case 3: return QIcon(":/icons/transaction_3");
464 case 4: return QIcon(":/icons/transaction_4");
465 default: return QIcon(":/icons/transaction_5");
467 case TransactionStatus::Confirmed:
468 return QIcon(":/icons/transaction_confirmed");
469 case TransactionStatus::Conflicted:
470 return QIcon(":/icons/transaction_conflicted");
471 case TransactionStatus::Immature: {
472 int total = wtx->status.depth + wtx->status.matures_in;
473 int part = (wtx->status.depth * 4 / total) + 1;
474 return QIcon(QString(":/icons/transaction_%1").arg(part));
476 case TransactionStatus::MaturesWarning:
477 case TransactionStatus::NotAccepted:
478 return QIcon(":/icons/transaction_0");
479 default:
480 return COLOR_BLACK;
484 QVariant TransactionTableModel::txWatchonlyDecoration(const TransactionRecord *wtx) const
486 if (wtx->involvesWatchAddress)
487 return QIcon(":/icons/eye");
488 else
489 return QVariant();
492 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
494 QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
495 if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
496 rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
498 tooltip += QString(" ") + formatTxToAddress(rec, true);
500 return tooltip;
503 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
505 if(!index.isValid())
506 return QVariant();
507 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
509 switch(role)
511 case RawDecorationRole:
512 switch(index.column())
514 case Status:
515 return txStatusDecoration(rec);
516 case Watchonly:
517 return txWatchonlyDecoration(rec);
518 case ToAddress:
519 return txAddressDecoration(rec);
521 break;
522 case Qt::DecorationRole:
524 QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole));
525 return platformStyle->TextColorIcon(icon);
527 case Qt::DisplayRole:
528 switch(index.column())
530 case Date:
531 return formatTxDate(rec);
532 case Type:
533 return formatTxType(rec);
534 case ToAddress:
535 return formatTxToAddress(rec, false);
536 case Amount:
537 return formatTxAmount(rec, true, BitcoinUnits::separatorAlways);
539 break;
540 case Qt::EditRole:
541 // Edit role is used for sorting, so return the unformatted values
542 switch(index.column())
544 case Status:
545 return QString::fromStdString(rec->status.sortKey);
546 case Date:
547 return rec->time;
548 case Type:
549 return formatTxType(rec);
550 case Watchonly:
551 return (rec->involvesWatchAddress ? 1 : 0);
552 case ToAddress:
553 return formatTxToAddress(rec, true);
554 case Amount:
555 return qint64(rec->credit + rec->debit);
557 break;
558 case Qt::ToolTipRole:
559 return formatTooltip(rec);
560 case Qt::TextAlignmentRole:
561 return column_alignments[index.column()];
562 case Qt::ForegroundRole:
563 // Non-confirmed (but not immature) as transactions are grey
564 if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
566 return COLOR_UNCONFIRMED;
568 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
570 return COLOR_NEGATIVE;
572 if(index.column() == ToAddress)
574 return addressColor(rec);
576 break;
577 case TypeRole:
578 return rec->type;
579 case DateRole:
580 return QDateTime::fromTime_t(static_cast<uint>(rec->time));
581 case WatchonlyRole:
582 return rec->involvesWatchAddress;
583 case WatchonlyDecorationRole:
584 return txWatchonlyDecoration(rec);
585 case LongDescriptionRole:
586 return priv->describe(rec, walletModel->getOptionsModel()->getDisplayUnit());
587 case AddressRole:
588 return QString::fromStdString(rec->address);
589 case LabelRole:
590 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
591 case AmountRole:
592 return qint64(rec->credit + rec->debit);
593 case TxIDRole:
594 return rec->getTxID();
595 case TxHashRole:
596 return QString::fromStdString(rec->hash.ToString());
597 case ConfirmedRole:
598 return rec->status.countsForBalance;
599 case FormattedAmountRole:
600 // Used for copy/export, so don't include separators
601 return formatTxAmount(rec, false, BitcoinUnits::separatorNever);
602 case StatusRole:
603 return rec->status.status;
605 return QVariant();
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)
621 switch(section)
623 case Status:
624 return tr("Transaction status. Hover over this field to show number of confirmations.");
625 case Date:
626 return tr("Date and time that the transaction was received.");
627 case Type:
628 return tr("Type of transaction.");
629 case Watchonly:
630 return tr("Whether or not a watch-only address is involved in this transaction.");
631 case ToAddress:
632 return tr("User-defined intent/purpose of the transaction.");
633 case Amount:
634 return tr("Amount removed from or added to balance.");
638 return QVariant();
641 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
643 Q_UNUSED(parent);
644 TransactionRecord *data = priv->index(row);
645 if(data)
647 return createIndex(row, column, priv->index(row));
649 return QModelIndex();
652 void TransactionTableModel::updateDisplayUnit()
654 // emit dataChanged to update Amount column with the current unit
655 updateAmountColumnTitle();
656 Q_EMIT dataChanged(index(0, Amount), index(priv->size()-1, Amount));
659 // queue notifications to show a non freezing progress dialog e.g. for rescan
660 struct TransactionNotification
662 public:
663 TransactionNotification() {}
664 TransactionNotification(uint256 hash, ChangeType status, bool showTransaction):
665 hash(hash), status(status), showTransaction(showTransaction) {}
667 void invoke(QObject *ttm)
669 QString strHash = QString::fromStdString(hash.GetHex());
670 qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status);
671 QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection,
672 Q_ARG(QString, strHash),
673 Q_ARG(int, status),
674 Q_ARG(bool, showTransaction));
676 private:
677 uint256 hash;
678 ChangeType status;
679 bool showTransaction;
682 static bool fQueueNotifications = false;
683 static std::vector< TransactionNotification > vQueueNotifications;
685 static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet, const uint256 &hash, ChangeType status)
687 // Find transaction in wallet
688 std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
689 // Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
690 bool inWallet = mi != wallet->mapWallet.end();
691 bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
693 TransactionNotification notification(hash, status, showTransaction);
695 if (fQueueNotifications)
697 vQueueNotifications.push_back(notification);
698 return;
700 notification.invoke(ttm);
703 static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress)
705 if (nProgress == 0)
706 fQueueNotifications = true;
708 if (nProgress == 100)
710 fQueueNotifications = false;
711 if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons
712 QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
713 for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
715 if (vQueueNotifications.size() - i <= 10)
716 QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
718 vQueueNotifications[i].invoke(ttm);
720 std::vector<TransactionNotification >().swap(vQueueNotifications); // clear
724 void TransactionTableModel::subscribeToCoreSignals()
726 // Connect signals to wallet
727 wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
728 wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2));
731 void TransactionTableModel::unsubscribeFromCoreSignals()
733 // Disconnect signals from wallet
734 wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
735 wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2));