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"
10 #include "optionsmodel.h"
11 #include "platformstyle.h"
12 #include "transactiondesc.h"
13 #include "transactionrecord.h"
14 #include "walletmodel.h"
20 #include "wallet/wallet.h"
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
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
51 bool operator()(const uint256
&a
, const TransactionRecord
&b
) const
57 // Private implementation
58 class TransactionTablePriv
61 TransactionTablePriv(CWallet
*wallet
, TransactionTableModel
*parent
) :
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.
80 qDebug() << "TransactionTablePriv::refreshWallet";
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
);
127 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is already in model";
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";
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
);
152 parent
->endInsertRows();
159 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_DELETED, but transaction is not in model";
162 // Removed -- remove entire transaction from table
163 parent
->beginRemoveRows(QModelIndex(), lowerIndex
, upperIndex
-1);
164 cachedWallet
.erase(lower
, upper
);
165 parent
->endRemoveRows();
168 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
169 // visible transactions.
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
);
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
);
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
);
225 TransactionTableModel::TransactionTableModel(const PlatformStyle
*platformStyle
, CWallet
* wallet
, WalletModel
*parent
):
226 QAbstractTableModel(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();
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
)
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
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
278 int TransactionTableModel::columnCount(const QModelIndex
&parent
) const
281 return columns
.length();
284 QString
TransactionTableModel::formatTxStatus(const TransactionRecord
*wtx
) const
288 switch(wtx
->status
.status
)
290 case TransactionStatus::OpenUntilBlock
:
291 status
= tr("Open for %n more block(s)","",wtx
->status
.open_for
);
293 case TransactionStatus::OpenUntilDate
:
294 status
= tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx
->status
.open_for
));
296 case TransactionStatus::Offline
:
297 status
= tr("Offline");
299 case TransactionStatus::Unconfirmed
:
300 status
= tr("Unconfirmed");
302 case TransactionStatus::Confirming
:
303 status
= tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx
->status
.depth
).arg(TransactionRecord::RecommendedNumConfirmations
);
305 case TransactionStatus::Confirmed
:
306 status
= tr("Confirmed (%1 confirmations)").arg(wtx
->status
.depth
);
308 case TransactionStatus::Conflicted
:
309 status
= tr("Conflicted");
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
);
314 case TransactionStatus::MaturesWarning
:
315 status
= tr("This block was not received by any other nodes and will probably not be accepted!");
317 case TransactionStatus::NotAccepted
:
318 status
= tr("Generated but not accepted");
325 QString
TransactionTableModel::formatTxDate(const TransactionRecord
*wtx
) const
329 return GUIUtil::dateTimeStr(wtx
->time
);
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
));
343 description
+= label
;
345 if(label
.isEmpty() || tooltip
)
347 description
+= QString(" (") + QString::fromStdString(address
) + QString(")");
352 QString
TransactionTableModel::formatTxType(const TransactionRecord
*wtx
) const
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
:
372 QVariant
TransactionTableModel::txAddressDecoration(const TransactionRecord
*wtx
) const
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");
385 return QIcon(":/icons/tx_inout");
389 QString
TransactionTableModel::formatTxToAddress(const TransactionRecord
*wtx
, bool tooltip
) const
391 QString watchAddress
;
393 // Mark transactions involving watch-only addresses by adding " (watch-only)"
394 watchAddress
= wtx
->involvesWatchAddress
? QString(" (") + tr("watch-only") + QString(")") : "";
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
:
409 return tr("(n/a)") + watchAddress
;
413 QVariant
TransactionTableModel::addressColor(const TransactionRecord
*wtx
) const
415 // Show addresses without label in a less visible color
418 case TransactionRecord::RecvWithAddress
:
419 case TransactionRecord::SendToAddress
:
420 case TransactionRecord::Generated
:
422 QString label
= walletModel
->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx
->address
));
424 return COLOR_BAREADDRESS
;
426 case TransactionRecord::SendToSelf
:
427 return COLOR_BAREADDRESS
;
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
);
439 if(!wtx
->status
.countsForBalance
)
441 str
= QString("[") + str
+ QString("]");
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");
484 QVariant
TransactionTableModel::txWatchonlyDecoration(const TransactionRecord
*wtx
) const
486 if (wtx
->involvesWatchAddress
)
487 return QIcon(":/icons/eye");
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);
503 QVariant
TransactionTableModel::data(const QModelIndex
&index
, int role
) const
507 TransactionRecord
*rec
= static_cast<TransactionRecord
*>(index
.internalPointer());
511 case RawDecorationRole
:
512 switch(index
.column())
515 return txStatusDecoration(rec
);
517 return txWatchonlyDecoration(rec
);
519 return txAddressDecoration(rec
);
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())
531 return formatTxDate(rec
);
533 return formatTxType(rec
);
535 return formatTxToAddress(rec
, false);
537 return formatTxAmount(rec
, true, BitcoinUnits::separatorAlways
);
541 // Edit role is used for sorting, so return the unformatted values
542 switch(index
.column())
545 return QString::fromStdString(rec
->status
.sortKey
);
549 return formatTxType(rec
);
551 return (rec
->involvesWatchAddress
? 1 : 0);
553 return formatTxToAddress(rec
, true);
555 return qint64(rec
->credit
+ rec
->debit
);
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
);
580 return QDateTime::fromTime_t(static_cast<uint
>(rec
->time
));
582 return rec
->involvesWatchAddress
;
583 case WatchonlyDecorationRole
:
584 return txWatchonlyDecoration(rec
);
585 case LongDescriptionRole
:
586 return priv
->describe(rec
, walletModel
->getOptionsModel()->getDisplayUnit());
588 return QString::fromStdString(rec
->address
);
590 return walletModel
->getAddressTableModel()->labelForAddress(QString::fromStdString(rec
->address
));
592 return qint64(rec
->credit
+ rec
->debit
);
594 return rec
->getTxID();
596 return QString::fromStdString(rec
->hash
.ToString());
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
);
603 return rec
->status
.status
;
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
)
624 return tr("Transaction status. Hover over this field to show number of confirmations.");
626 return tr("Date and time that the transaction was received.");
628 return tr("Type of transaction.");
630 return tr("Whether or not a watch-only address is involved in this transaction.");
632 return tr("User-defined intent/purpose of the transaction.");
634 return tr("Amount removed from or added to balance.");
641 QModelIndex
TransactionTableModel::index(int row
, int column
, const QModelIndex
&parent
) const
644 TransactionRecord
*data
= priv
->index(row
);
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
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
),
674 Q_ARG(bool, showTransaction
));
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
);
700 notification
.invoke(ttm
);
703 static void ShowProgress(TransactionTableModel
*ttm
, const std::string
&title
, int nProgress
)
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
));