1 // Copyright (c) 2011-2016 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"
17 #include "validation.h"
21 #include "wallet/wallet.h"
29 // Amount column is right-aligned it contains numbers
30 static int column_alignments
[] = {
31 Qt::AlignLeft
|Qt::AlignVCenter
, /* status */
32 Qt::AlignLeft
|Qt::AlignVCenter
, /* watchonly */
33 Qt::AlignLeft
|Qt::AlignVCenter
, /* date */
34 Qt::AlignLeft
|Qt::AlignVCenter
, /* type */
35 Qt::AlignLeft
|Qt::AlignVCenter
, /* address */
36 Qt::AlignRight
|Qt::AlignVCenter
/* amount */
39 // Comparison operator for sort/binary search of model tx list
42 bool operator()(const TransactionRecord
&a
, const TransactionRecord
&b
) const
44 return a
.hash
< b
.hash
;
46 bool operator()(const TransactionRecord
&a
, const uint256
&b
) const
50 bool operator()(const uint256
&a
, const TransactionRecord
&b
) const
56 // Private implementation
57 class TransactionTablePriv
60 TransactionTablePriv(CWallet
*_wallet
, TransactionTableModel
*_parent
) :
67 TransactionTableModel
*parent
;
69 /* Local cache of wallet.
70 * As it is in the same order as the CWallet, by definition
71 * this is sorted by sha256.
73 QList
<TransactionRecord
> cachedWallet
;
75 /* Query entire wallet anew from core.
79 qDebug() << "TransactionTablePriv::refreshWallet";
82 LOCK2(cs_main
, wallet
->cs_wallet
);
83 for(std::map
<uint256
, CWalletTx
>::iterator it
= wallet
->mapWallet
.begin(); it
!= wallet
->mapWallet
.end(); ++it
)
85 if(TransactionRecord::showTransaction(it
->second
))
86 cachedWallet
.append(TransactionRecord::decomposeTransaction(wallet
, it
->second
));
91 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
92 with that of the core.
94 Call with transaction that was added, removed or changed.
96 void updateWallet(const uint256
&hash
, int status
, bool showTransaction
)
98 qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash
.ToString()) + " " + QString::number(status
);
100 // Find bounds of this transaction in model
101 QList
<TransactionRecord
>::iterator lower
= qLowerBound(
102 cachedWallet
.begin(), cachedWallet
.end(), hash
, TxLessThan());
103 QList
<TransactionRecord
>::iterator upper
= qUpperBound(
104 cachedWallet
.begin(), cachedWallet
.end(), hash
, TxLessThan());
105 int lowerIndex
= (lower
- cachedWallet
.begin());
106 int upperIndex
= (upper
- cachedWallet
.begin());
107 bool inModel
= (lower
!= upper
);
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 qDebug() << " inModel=" + QString::number(inModel
) +
118 " Index=" + QString::number(lowerIndex
) + "-" + QString::number(upperIndex
) +
119 " showTransaction=" + QString::number(showTransaction
) + " derivedStatus=" + QString::number(status
);
126 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is already in model";
131 LOCK2(cs_main
, wallet
->cs_wallet
);
132 // Find transaction in wallet
133 std::map
<uint256
, CWalletTx
>::iterator mi
= wallet
->mapWallet
.find(hash
);
134 if(mi
== wallet
->mapWallet
.end())
136 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is not in wallet";
139 // Added -- insert at the right position
140 QList
<TransactionRecord
> toInsert
=
141 TransactionRecord::decomposeTransaction(wallet
, mi
->second
);
142 if(!toInsert
.isEmpty()) /* only if something to insert */
144 parent
->beginInsertRows(QModelIndex(), lowerIndex
, lowerIndex
+toInsert
.size()-1);
145 int insert_idx
= lowerIndex
;
146 for (const TransactionRecord
&rec
: toInsert
)
148 cachedWallet
.insert(insert_idx
, rec
);
151 parent
->endInsertRows();
158 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_DELETED, but transaction is not in model";
161 // Removed -- remove entire transaction from table
162 parent
->beginRemoveRows(QModelIndex(), lowerIndex
, upperIndex
-1);
163 cachedWallet
.erase(lower
, upper
);
164 parent
->endRemoveRows();
167 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
168 // visible transactions.
169 for (int i
= lowerIndex
; i
< upperIndex
; i
++) {
170 TransactionRecord
*rec
= &cachedWallet
[i
];
171 rec
->status
.needsUpdate
= true;
179 return cachedWallet
.size();
182 TransactionRecord
*index(int idx
)
184 if(idx
>= 0 && idx
< cachedWallet
.size())
186 TransactionRecord
*rec
= &cachedWallet
[idx
];
188 // Get required locks upfront. This avoids the GUI from getting
189 // stuck if the core is holding the locks for a longer time - for
190 // example, during a wallet rescan.
192 // If a status update is needed (blocks came in since last check),
193 // update the status of this transaction from the wallet. Otherwise,
194 // simply re-use the cached status.
195 TRY_LOCK(cs_main
, lockMain
);
198 TRY_LOCK(wallet
->cs_wallet
, lockWallet
);
199 if(lockWallet
&& rec
->statusUpdateNeeded())
201 std::map
<uint256
, CWalletTx
>::iterator mi
= wallet
->mapWallet
.find(rec
->hash
);
203 if(mi
!= wallet
->mapWallet
.end())
205 rec
->updateStatus(mi
->second
);
214 QString
describe(TransactionRecord
*rec
, int unit
)
217 LOCK2(cs_main
, wallet
->cs_wallet
);
218 std::map
<uint256
, CWalletTx
>::iterator mi
= wallet
->mapWallet
.find(rec
->hash
);
219 if(mi
!= wallet
->mapWallet
.end())
221 return TransactionDesc::toHTML(wallet
, mi
->second
, rec
, unit
);
227 QString
getTxHex(TransactionRecord
*rec
)
229 LOCK2(cs_main
, wallet
->cs_wallet
);
230 std::map
<uint256
, CWalletTx
>::iterator mi
= wallet
->mapWallet
.find(rec
->hash
);
231 if(mi
!= wallet
->mapWallet
.end())
233 std::string strHex
= EncodeHexTx(static_cast<CTransaction
>(mi
->second
));
234 return QString::fromStdString(strHex
);
240 TransactionTableModel::TransactionTableModel(const PlatformStyle
*_platformStyle
, CWallet
* _wallet
, WalletModel
*parent
):
241 QAbstractTableModel(parent
),
244 priv(new TransactionTablePriv(_wallet
, this)),
245 fProcessingQueuedTransactions(false),
246 platformStyle(_platformStyle
)
248 columns
<< QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle(walletModel
->getOptionsModel()->getDisplayUnit());
249 priv
->refreshWallet();
251 connect(walletModel
->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
253 subscribeToCoreSignals();
256 TransactionTableModel::~TransactionTableModel()
258 unsubscribeFromCoreSignals();
262 /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
263 void TransactionTableModel::updateAmountColumnTitle()
265 columns
[Amount
] = BitcoinUnits::getAmountColumnTitle(walletModel
->getOptionsModel()->getDisplayUnit());
266 Q_EMIT
headerDataChanged(Qt::Horizontal
,Amount
,Amount
);
269 void TransactionTableModel::updateTransaction(const QString
&hash
, int status
, bool showTransaction
)
272 updated
.SetHex(hash
.toStdString());
274 priv
->updateWallet(updated
, status
, showTransaction
);
277 void TransactionTableModel::updateConfirmations()
279 // Blocks came in since last poll.
280 // Invalidate status (number of confirmations) and (possibly) description
281 // for all rows. Qt is smart enough to only actually request the data for the
283 Q_EMIT
dataChanged(index(0, Status
), index(priv
->size()-1, Status
));
284 Q_EMIT
dataChanged(index(0, ToAddress
), index(priv
->size()-1, ToAddress
));
287 int TransactionTableModel::rowCount(const QModelIndex
&parent
) const
293 int TransactionTableModel::columnCount(const QModelIndex
&parent
) const
296 return columns
.length();
299 QString
TransactionTableModel::formatTxStatus(const TransactionRecord
*wtx
) const
303 switch(wtx
->status
.status
)
305 case TransactionStatus::OpenUntilBlock
:
306 status
= tr("Open for %n more block(s)","",wtx
->status
.open_for
);
308 case TransactionStatus::OpenUntilDate
:
309 status
= tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx
->status
.open_for
));
311 case TransactionStatus::Offline
:
312 status
= tr("Offline");
314 case TransactionStatus::Unconfirmed
:
315 status
= tr("Unconfirmed");
317 case TransactionStatus::Abandoned
:
318 status
= tr("Abandoned");
320 case TransactionStatus::Confirming
:
321 status
= tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx
->status
.depth
).arg(TransactionRecord::RecommendedNumConfirmations
);
323 case TransactionStatus::Confirmed
:
324 status
= tr("Confirmed (%1 confirmations)").arg(wtx
->status
.depth
);
326 case TransactionStatus::Conflicted
:
327 status
= tr("Conflicted");
329 case TransactionStatus::Immature
:
330 status
= tr("Immature (%1 confirmations, will be available after %2)").arg(wtx
->status
.depth
).arg(wtx
->status
.depth
+ wtx
->status
.matures_in
);
332 case TransactionStatus::MaturesWarning
:
333 status
= tr("This block was not received by any other nodes and will probably not be accepted!");
335 case TransactionStatus::NotAccepted
:
336 status
= tr("Generated but not accepted");
343 QString
TransactionTableModel::formatTxDate(const TransactionRecord
*wtx
) const
347 return GUIUtil::dateTimeStr(wtx
->time
);
352 /* Look up address in address book, if found return label (address)
353 otherwise just return (address)
355 QString
TransactionTableModel::lookupAddress(const std::string
&address
, bool tooltip
) const
357 QString label
= walletModel
->getAddressTableModel()->labelForAddress(QString::fromStdString(address
));
361 description
+= label
;
363 if(label
.isEmpty() || tooltip
)
365 description
+= QString(" (") + QString::fromStdString(address
) + QString(")");
370 QString
TransactionTableModel::formatTxType(const TransactionRecord
*wtx
) const
374 case TransactionRecord::RecvWithAddress
:
375 return tr("Received with");
376 case TransactionRecord::RecvFromOther
:
377 return tr("Received from");
378 case TransactionRecord::SendToAddress
:
379 case TransactionRecord::SendToOther
:
380 return tr("Sent to");
381 case TransactionRecord::SendToSelf
:
382 return tr("Payment to yourself");
383 case TransactionRecord::Generated
:
390 QVariant
TransactionTableModel::txAddressDecoration(const TransactionRecord
*wtx
) const
394 case TransactionRecord::Generated
:
395 return QIcon(":/icons/tx_mined");
396 case TransactionRecord::RecvWithAddress
:
397 case TransactionRecord::RecvFromOther
:
398 return QIcon(":/icons/tx_input");
399 case TransactionRecord::SendToAddress
:
400 case TransactionRecord::SendToOther
:
401 return QIcon(":/icons/tx_output");
403 return QIcon(":/icons/tx_inout");
407 QString
TransactionTableModel::formatTxToAddress(const TransactionRecord
*wtx
, bool tooltip
) const
409 QString watchAddress
;
411 // Mark transactions involving watch-only addresses by adding " (watch-only)"
412 watchAddress
= wtx
->involvesWatchAddress
? QString(" (") + tr("watch-only") + QString(")") : "";
417 case TransactionRecord::RecvFromOther
:
418 return QString::fromStdString(wtx
->address
) + watchAddress
;
419 case TransactionRecord::RecvWithAddress
:
420 case TransactionRecord::SendToAddress
:
421 case TransactionRecord::Generated
:
422 return lookupAddress(wtx
->address
, tooltip
) + watchAddress
;
423 case TransactionRecord::SendToOther
:
424 return QString::fromStdString(wtx
->address
) + watchAddress
;
425 case TransactionRecord::SendToSelf
:
427 return tr("(n/a)") + watchAddress
;
431 QVariant
TransactionTableModel::addressColor(const TransactionRecord
*wtx
) const
433 // Show addresses without label in a less visible color
436 case TransactionRecord::RecvWithAddress
:
437 case TransactionRecord::SendToAddress
:
438 case TransactionRecord::Generated
:
440 QString label
= walletModel
->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx
->address
));
442 return COLOR_BAREADDRESS
;
444 case TransactionRecord::SendToSelf
:
445 return COLOR_BAREADDRESS
;
452 QString
TransactionTableModel::formatTxAmount(const TransactionRecord
*wtx
, bool showUnconfirmed
, BitcoinUnits::SeparatorStyle separators
) const
454 QString str
= BitcoinUnits::format(walletModel
->getOptionsModel()->getDisplayUnit(), wtx
->credit
+ wtx
->debit
, false, separators
);
457 if(!wtx
->status
.countsForBalance
)
459 str
= QString("[") + str
+ QString("]");
465 QVariant
TransactionTableModel::txStatusDecoration(const TransactionRecord
*wtx
) const
467 switch(wtx
->status
.status
)
469 case TransactionStatus::OpenUntilBlock
:
470 case TransactionStatus::OpenUntilDate
:
471 return COLOR_TX_STATUS_OPENUNTILDATE
;
472 case TransactionStatus::Offline
:
473 return COLOR_TX_STATUS_OFFLINE
;
474 case TransactionStatus::Unconfirmed
:
475 return QIcon(":/icons/transaction_0");
476 case TransactionStatus::Abandoned
:
477 return QIcon(":/icons/transaction_abandoned");
478 case TransactionStatus::Confirming
:
479 switch(wtx
->status
.depth
)
481 case 1: return QIcon(":/icons/transaction_1");
482 case 2: return QIcon(":/icons/transaction_2");
483 case 3: return QIcon(":/icons/transaction_3");
484 case 4: return QIcon(":/icons/transaction_4");
485 default: return QIcon(":/icons/transaction_5");
487 case TransactionStatus::Confirmed
:
488 return QIcon(":/icons/transaction_confirmed");
489 case TransactionStatus::Conflicted
:
490 return QIcon(":/icons/transaction_conflicted");
491 case TransactionStatus::Immature
: {
492 int total
= wtx
->status
.depth
+ wtx
->status
.matures_in
;
493 int part
= (wtx
->status
.depth
* 4 / total
) + 1;
494 return QIcon(QString(":/icons/transaction_%1").arg(part
));
496 case TransactionStatus::MaturesWarning
:
497 case TransactionStatus::NotAccepted
:
498 return QIcon(":/icons/transaction_0");
504 QVariant
TransactionTableModel::txWatchonlyDecoration(const TransactionRecord
*wtx
) const
506 if (wtx
->involvesWatchAddress
)
507 return QIcon(":/icons/eye");
512 QString
TransactionTableModel::formatTooltip(const TransactionRecord
*rec
) const
514 QString tooltip
= formatTxStatus(rec
) + QString("\n") + formatTxType(rec
);
515 if(rec
->type
==TransactionRecord::RecvFromOther
|| rec
->type
==TransactionRecord::SendToOther
||
516 rec
->type
==TransactionRecord::SendToAddress
|| rec
->type
==TransactionRecord::RecvWithAddress
)
518 tooltip
+= QString(" ") + formatTxToAddress(rec
, true);
523 QVariant
TransactionTableModel::data(const QModelIndex
&index
, int role
) const
527 TransactionRecord
*rec
= static_cast<TransactionRecord
*>(index
.internalPointer());
531 case RawDecorationRole
:
532 switch(index
.column())
535 return txStatusDecoration(rec
);
537 return txWatchonlyDecoration(rec
);
539 return txAddressDecoration(rec
);
542 case Qt::DecorationRole
:
544 QIcon icon
= qvariant_cast
<QIcon
>(index
.data(RawDecorationRole
));
545 return platformStyle
->TextColorIcon(icon
);
547 case Qt::DisplayRole
:
548 switch(index
.column())
551 return formatTxDate(rec
);
553 return formatTxType(rec
);
555 return formatTxToAddress(rec
, false);
557 return formatTxAmount(rec
, true, BitcoinUnits::separatorAlways
);
561 // Edit role is used for sorting, so return the unformatted values
562 switch(index
.column())
565 return QString::fromStdString(rec
->status
.sortKey
);
569 return formatTxType(rec
);
571 return (rec
->involvesWatchAddress
? 1 : 0);
573 return formatTxToAddress(rec
, true);
575 return qint64(rec
->credit
+ rec
->debit
);
578 case Qt::ToolTipRole
:
579 return formatTooltip(rec
);
580 case Qt::TextAlignmentRole
:
581 return column_alignments
[index
.column()];
582 case Qt::ForegroundRole
:
583 // Use the "danger" color for abandoned transactions
584 if(rec
->status
.status
== TransactionStatus::Abandoned
)
586 return COLOR_TX_STATUS_DANGER
;
588 // Non-confirmed (but not immature) as transactions are grey
589 if(!rec
->status
.countsForBalance
&& rec
->status
.status
!= TransactionStatus::Immature
)
591 return COLOR_UNCONFIRMED
;
593 if(index
.column() == Amount
&& (rec
->credit
+rec
->debit
) < 0)
595 return COLOR_NEGATIVE
;
597 if(index
.column() == ToAddress
)
599 return addressColor(rec
);
605 return QDateTime::fromTime_t(static_cast<uint
>(rec
->time
));
607 return rec
->involvesWatchAddress
;
608 case WatchonlyDecorationRole
:
609 return txWatchonlyDecoration(rec
);
610 case LongDescriptionRole
:
611 return priv
->describe(rec
, walletModel
->getOptionsModel()->getDisplayUnit());
613 return QString::fromStdString(rec
->address
);
615 return walletModel
->getAddressTableModel()->labelForAddress(QString::fromStdString(rec
->address
));
617 return qint64(rec
->credit
+ rec
->debit
);
619 return rec
->getTxID();
621 return QString::fromStdString(rec
->hash
.ToString());
623 return priv
->getTxHex(rec
);
624 case TxPlainTextRole
:
627 QDateTime date
= QDateTime::fromTime_t(static_cast<uint
>(rec
->time
));
628 QString txLabel
= walletModel
->getAddressTableModel()->labelForAddress(QString::fromStdString(rec
->address
));
630 details
.append(date
.toString("M/d/yy HH:mm"));
632 details
.append(formatTxStatus(rec
));
633 details
.append(". ");
634 if(!formatTxType(rec
).isEmpty()) {
635 details
.append(formatTxType(rec
));
638 if(!rec
->address
.empty()) {
639 if(txLabel
.isEmpty())
640 details
.append(tr("(no label)") + " ");
643 details
.append(txLabel
);
644 details
.append(") ");
646 details
.append(QString::fromStdString(rec
->address
));
649 details
.append(formatTxAmount(rec
, false, BitcoinUnits::separatorNever
));
653 return rec
->status
.countsForBalance
;
654 case FormattedAmountRole
:
655 // Used for copy/export, so don't include separators
656 return formatTxAmount(rec
, false, BitcoinUnits::separatorNever
);
658 return rec
->status
.status
;
663 QVariant
TransactionTableModel::headerData(int section
, Qt::Orientation orientation
, int role
) const
665 if(orientation
== Qt::Horizontal
)
667 if(role
== Qt::DisplayRole
)
669 return columns
[section
];
671 else if (role
== Qt::TextAlignmentRole
)
673 return column_alignments
[section
];
674 } else if (role
== Qt::ToolTipRole
)
679 return tr("Transaction status. Hover over this field to show number of confirmations.");
681 return tr("Date and time that the transaction was received.");
683 return tr("Type of transaction.");
685 return tr("Whether or not a watch-only address is involved in this transaction.");
687 return tr("User-defined intent/purpose of the transaction.");
689 return tr("Amount removed from or added to balance.");
696 QModelIndex
TransactionTableModel::index(int row
, int column
, const QModelIndex
&parent
) const
699 TransactionRecord
*data
= priv
->index(row
);
702 return createIndex(row
, column
, priv
->index(row
));
704 return QModelIndex();
707 void TransactionTableModel::updateDisplayUnit()
709 // emit dataChanged to update Amount column with the current unit
710 updateAmountColumnTitle();
711 Q_EMIT
dataChanged(index(0, Amount
), index(priv
->size()-1, Amount
));
714 // queue notifications to show a non freezing progress dialog e.g. for rescan
715 struct TransactionNotification
718 TransactionNotification() {}
719 TransactionNotification(uint256 _hash
, ChangeType _status
, bool _showTransaction
):
720 hash(_hash
), status(_status
), showTransaction(_showTransaction
) {}
722 void invoke(QObject
*ttm
)
724 QString strHash
= QString::fromStdString(hash
.GetHex());
725 qDebug() << "NotifyTransactionChanged: " + strHash
+ " status= " + QString::number(status
);
726 QMetaObject::invokeMethod(ttm
, "updateTransaction", Qt::QueuedConnection
,
727 Q_ARG(QString
, strHash
),
729 Q_ARG(bool, showTransaction
));
734 bool showTransaction
;
737 static bool fQueueNotifications
= false;
738 static std::vector
< TransactionNotification
> vQueueNotifications
;
740 static void NotifyTransactionChanged(TransactionTableModel
*ttm
, CWallet
*wallet
, const uint256
&hash
, ChangeType status
)
742 // Find transaction in wallet
743 std::map
<uint256
, CWalletTx
>::iterator mi
= wallet
->mapWallet
.find(hash
);
744 // Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
745 bool inWallet
= mi
!= wallet
->mapWallet
.end();
746 bool showTransaction
= (inWallet
&& TransactionRecord::showTransaction(mi
->second
));
748 TransactionNotification
notification(hash
, status
, showTransaction
);
750 if (fQueueNotifications
)
752 vQueueNotifications
.push_back(notification
);
755 notification
.invoke(ttm
);
758 static void ShowProgress(TransactionTableModel
*ttm
, const std::string
&title
, int nProgress
)
761 fQueueNotifications
= true;
763 if (nProgress
== 100)
765 fQueueNotifications
= false;
766 if (vQueueNotifications
.size() > 10) // prevent balloon spam, show maximum 10 balloons
767 QMetaObject::invokeMethod(ttm
, "setProcessingQueuedTransactions", Qt::QueuedConnection
, Q_ARG(bool, true));
768 for (unsigned int i
= 0; i
< vQueueNotifications
.size(); ++i
)
770 if (vQueueNotifications
.size() - i
<= 10)
771 QMetaObject::invokeMethod(ttm
, "setProcessingQueuedTransactions", Qt::QueuedConnection
, Q_ARG(bool, false));
773 vQueueNotifications
[i
].invoke(ttm
);
775 std::vector
<TransactionNotification
>().swap(vQueueNotifications
); // clear
779 void TransactionTableModel::subscribeToCoreSignals()
781 // Connect signals to wallet
782 wallet
->NotifyTransactionChanged
.connect(boost::bind(NotifyTransactionChanged
, this, _1
, _2
, _3
));
783 wallet
->ShowProgress
.connect(boost::bind(ShowProgress
, this, _1
, _2
));
786 void TransactionTableModel::unsubscribeFromCoreSignals()
788 // Disconnect signals from wallet
789 wallet
->NotifyTransactionChanged
.disconnect(boost::bind(NotifyTransactionChanged
, this, _1
, _2
, _3
));
790 wallet
->ShowProgress
.disconnect(boost::bind(ShowProgress
, this, _1
, _2
));