transactiontablemodel.cpp raw
1 // Copyright (c) 2011-present 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.
4
5 #include <qt/transactiontablemodel.h>
6
7 #include <qt/addresstablemodel.h>
8 #include <qt/bitcoinunits.h>
9 #include <qt/clientmodel.h>
10 #include <qt/guiconstants.h>
11 #include <qt/guiutil.h>
12 #include <qt/optionsmodel.h>
13 #include <qt/platformstyle.h>
14 #include <qt/transactiondesc.h>
15 #include <qt/transactionrecord.h>
16 #include <qt/walletmodel.h>
17
18 #include <core_io.h>
19 #include <interfaces/handler.h>
20 #include <tinyformat.h>
21 #include <uint256.h>
22
23 #include <algorithm>
24 #include <functional>
25
26 #include <QColor>
27 #include <QDateTime>
28 #include <QDebug>
29 #include <QIcon>
30 #include <QLatin1Char>
31 #include <QLatin1String>
32 #include <QList>
33
34
35 // Amount column is right-aligned it contains numbers
36 static int column_alignments[] = {
37 Qt::AlignLeft|Qt::AlignVCenter, /*status=*/
38 Qt::AlignLeft|Qt::AlignVCenter, /*date=*/
39 Qt::AlignLeft|Qt::AlignVCenter, /*type=*/
40 Qt::AlignLeft|Qt::AlignVCenter, /*address=*/
41 Qt::AlignRight|Qt::AlignVCenter /* amount */
42 };
43
44 // Comparison operator for sort/binary search of model tx list
45 struct TxLessThan
46 {
47 bool operator()(const TransactionRecord &a, const TransactionRecord &b) const
48 {
49 return a.hash < b.hash;
50 }
51 bool operator()(const TransactionRecord &a, const Txid &b) const
52 {
53 return a.hash < b;
54 }
55 bool operator()(const Txid &a, const TransactionRecord &b) const
56 {
57 return a < b.hash;
58 }
59 };
60
61 // queue notifications to show a non freezing progress dialog e.g. for rescan
62 struct TransactionNotification
63 {
64 public:
65 TransactionNotification() = default;
66 TransactionNotification(Txid _hash, ChangeType _status, bool _showTransaction):
67 hash(_hash), status(_status), showTransaction(_showTransaction) {}
68
69 void invoke(QObject *ttm)
70 {
71 QString strHash = QString::fromStdString(hash.GetHex());
72 qDebug() << "NotifyTransactionChanged: " + strHash + " status= " + QString::number(status);
73 bool invoked = QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection,
74 Q_ARG(QString, strHash),
75 Q_ARG(int, status),
76 Q_ARG(bool, showTransaction));
77 assert(invoked);
78 }
79 private:
80 Txid hash;
81 ChangeType status;
82 bool showTransaction;
83 };
84
85 // Private implementation
86 class TransactionTablePriv
87 {
88 public:
89 explicit TransactionTablePriv(TransactionTableModel *_parent) :
90 parent(_parent)
91 {
92 }
93
94 TransactionTableModel *parent;
95
96 //! Local cache of wallet sorted by transaction hash
97 QList<TransactionRecord> cachedWallet;
98
99 /** True when model finishes loading all wallet transactions on start */
100 bool m_loaded = false;
101 /** True when transactions are being notified, for instance when scanning */
102 bool m_loading = false;
103 std::vector< TransactionNotification > vQueueNotifications;
104
105 void NotifyTransactionChanged(const Txid& hash, ChangeType status);
106 void DispatchNotifications();
107
108 /* Query entire wallet anew from core.
109 */
110 void refreshWallet(interfaces::Wallet& wallet)
111 {
112 assert(!m_loaded);
113 {
114 for (const auto& wtx : wallet.getWalletTxs()) {
115 if (TransactionRecord::showTransaction()) {
116 cachedWallet.append(TransactionRecord::decomposeTransaction(wtx));
117 }
118 }
119 }
120 m_loaded = true;
121 DispatchNotifications();
122 }
123
124 /* Update our model of the wallet incrementally, to synchronize our model of the wallet
125 with that of the core.
126
127 Call with transaction that was added, removed or changed.
128 */
129 void updateWallet(interfaces::Wallet& wallet, const Txid& hash, int status, bool showTransaction)
130 {
131 qDebug() << "TransactionTablePriv::updateWallet: " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
132
133 // Find bounds of this transaction in model
134 QList<TransactionRecord>::iterator lower = std::lower_bound(
135 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
136 QList<TransactionRecord>::iterator upper = std::upper_bound(
137 cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
138 int lowerIndex = (lower - cachedWallet.begin());
139 int upperIndex = (upper - cachedWallet.begin());
140 bool inModel = (lower != upper);
141
142 if(status == CT_UPDATED)
143 {
144 if(showTransaction && !inModel)
145 status = CT_NEW; /* Not in model, but want to show, treat as new */
146 if(!showTransaction && inModel)
147 status = CT_DELETED; /* In model, but want to hide, treat as deleted */
148 }
149
150 qDebug() << " inModel=" + QString::number(inModel) +
151 " Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
152 " showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
153
154 switch(status)
155 {
156 case CT_NEW:
157 if(inModel)
158 {
159 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is already in model";
160 break;
161 }
162 if(showTransaction)
163 {
164 // Find transaction in wallet
165 interfaces::WalletTx wtx = wallet.getWalletTx(hash);
166 if(!wtx.tx)
167 {
168 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_NEW, but transaction is not in wallet";
169 break;
170 }
171 // Added -- insert at the right position
172 QList<TransactionRecord> toInsert =
173 TransactionRecord::decomposeTransaction(wtx);
174 if(!toInsert.isEmpty()) /* only if something to insert */
175 {
176 parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
177 int insert_idx = lowerIndex;
178 for (const TransactionRecord &rec : toInsert)
179 {
180 cachedWallet.insert(insert_idx, rec);
181 insert_idx += 1;
182 }
183 parent->endInsertRows();
184 }
185 }
186 break;
187 case CT_DELETED:
188 if(!inModel)
189 {
190 qWarning() << "TransactionTablePriv::updateWallet: Warning: Got CT_DELETED, but transaction is not in model";
191 break;
192 }
193 // Removed -- remove entire transaction from table
194 parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
195 cachedWallet.erase(lower, upper);
196 parent->endRemoveRows();
197 break;
198 case CT_UPDATED:
199 // Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
200 // visible transactions.
201 for (int i = lowerIndex; i < upperIndex; i++) {
202 TransactionRecord *rec = &cachedWallet[i];
203 rec->status.needsUpdate = true;
204 }
205 break;
206 }
207 }
208
209 int size()
210 {
211 return cachedWallet.size();
212 }
213
214 TransactionRecord* index(interfaces::Wallet& wallet, const uint256& cur_block_hash, const int idx)
215 {
216 if (idx >= 0 && idx < cachedWallet.size()) {
217 TransactionRecord *rec = &cachedWallet[idx];
218
219 // If a status update is needed (blocks came in since last check),
220 // try to update the status of this transaction from the wallet.
221 // Otherwise, simply reuse the cached status.
222 interfaces::WalletTxStatus wtx;
223 int numBlocks;
224 int64_t block_time;
225 if (!cur_block_hash.IsNull() && rec->statusUpdateNeeded(cur_block_hash) && wallet.tryGetTxStatus(rec->hash, wtx, numBlocks, block_time)) {
226 rec->updateStatus(wtx, cur_block_hash, numBlocks, block_time);
227 }
228 return rec;
229 }
230 return nullptr;
231 }
232
233 QString describe(interfaces::Node& node, interfaces::Wallet& wallet, TransactionRecord* rec, BitcoinUnit unit)
234 {
235 return TransactionDesc::toHTML(node, wallet, rec, unit);
236 }
237
238 QString getTxHex(interfaces::Wallet& wallet, TransactionRecord *rec)
239 {
240 auto tx = wallet.getTx(rec->hash);
241 if (tx) {
242 std::string strHex = EncodeHexTx(*tx);
243 return QString::fromStdString(strHex);
244 }
245 return QString();
246 }
247 };
248
249 TransactionTableModel::TransactionTableModel(const PlatformStyle *_platformStyle, WalletModel *parent):
250 QAbstractTableModel(parent),
251 walletModel(parent),
252 priv(new TransactionTablePriv(this)),
253 platformStyle(_platformStyle)
254 {
255 subscribeToCoreSignals();
256
257 columns << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
258 priv->refreshWallet(walletModel->wallet());
259
260 connect(walletModel->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &TransactionTableModel::updateDisplayUnit);
261 }
262
263 TransactionTableModel::~TransactionTableModel()
264 {
265 unsubscribeFromCoreSignals();
266 delete priv;
267 }
268
269 /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */
270 void TransactionTableModel::updateAmountColumnTitle()
271 {
272 columns[Amount] = BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
273 Q_EMIT headerDataChanged(Qt::Horizontal,Amount,Amount);
274 }
275
276 void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
277 {
278 Txid updated = Txid::FromHex(hash.toStdString()).value();
279
280 priv->updateWallet(walletModel->wallet(), updated, status, showTransaction);
281 }
282
283 void TransactionTableModel::updateConfirmations()
284 {
285 // Blocks came in since last poll.
286 // Invalidate status (number of confirmations) and (possibly) description
287 // for all rows. Qt is smart enough to only actually request the data for the
288 // visible rows.
289 Q_EMIT dataChanged(index(0, Status), index(priv->size()-1, Status));
290 Q_EMIT dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress));
291 }
292
293 int TransactionTableModel::rowCount(const QModelIndex &parent) const
294 {
295 if (parent.isValid()) {
296 return 0;
297 }
298 return priv->size();
299 }
300
301 int TransactionTableModel::columnCount(const QModelIndex &parent) const
302 {
303 if (parent.isValid()) {
304 return 0;
305 }
306 return columns.length();
307 }
308
309 QString TransactionTableModel::formatTxStatus(const TransactionRecord *wtx) const
310 {
311 QString status;
312
313 switch(wtx->status.status)
314 {
315 case TransactionStatus::Unconfirmed:
316 status = tr("Unconfirmed");
317 break;
318 case TransactionStatus::Abandoned:
319 status = tr("Abandoned");
320 break;
321 case TransactionStatus::Confirming:
322 status = tr("Confirming (%1 of %2 recommended confirmations)").arg(wtx->status.depth).arg(TransactionRecord::RecommendedNumConfirmations);
323 break;
324 case TransactionStatus::Confirmed:
325 status = tr("Confirmed (%1 confirmations)").arg(wtx->status.depth);
326 break;
327 case TransactionStatus::Conflicted:
328 status = tr("Conflicted");
329 break;
330 case TransactionStatus::Immature:
331 status = tr("Immature (%1 confirmations, will be available after %2)").arg(wtx->status.depth).arg(wtx->status.depth + wtx->status.matures_in);
332 break;
333 case TransactionStatus::NotAccepted:
334 status = tr("Generated but not accepted");
335 break;
336 }
337
338 return status;
339 }
340
341 QString TransactionTableModel::formatTxDate(const TransactionRecord *wtx) const
342 {
343 if(wtx->time)
344 {
345 return GUIUtil::dateTimeStr(wtx->time);
346 }
347 return QString();
348 }
349
350 /* Look up address in address book, if found return label (address)
351 otherwise just return (address)
352 */
353 QString TransactionTableModel::lookupAddress(const std::string &address, bool tooltip) const
354 {
355 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(address));
356 QString description;
357 if(!label.isEmpty())
358 {
359 description += label;
360 }
361 if(label.isEmpty() || tooltip)
362 {
363 description += QString(" (") + QString::fromStdString(address) + QString(")");
364 }
365 return description;
366 }
367
368 QString TransactionTableModel::formatTxType(const TransactionRecord *wtx) const
369 {
370 switch(wtx->type)
371 {
372 case TransactionRecord::RecvWithAddress:
373 return tr("Received with");
374 case TransactionRecord::RecvFromOther:
375 return tr("Received from");
376 case TransactionRecord::SendToAddress:
377 case TransactionRecord::SendToOther:
378 return tr("Sent to");
379 case TransactionRecord::Generated:
380 return tr("Mined");
381 default:
382 return QString();
383 }
384 }
385
386 QVariant TransactionTableModel::txAddressDecoration(const TransactionRecord *wtx) const
387 {
388 switch(wtx->type)
389 {
390 case TransactionRecord::Generated:
391 return QIcon(":/icons/tx_mined");
392 case TransactionRecord::RecvWithAddress:
393 case TransactionRecord::RecvFromOther:
394 return QIcon(":/icons/tx_input");
395 case TransactionRecord::SendToAddress:
396 case TransactionRecord::SendToOther:
397 return QIcon(":/icons/tx_output");
398 default:
399 return QIcon(":/icons/tx_inout");
400 }
401 }
402
403 QString TransactionTableModel::formatTxToAddress(const TransactionRecord *wtx, bool tooltip) const
404 {
405 switch(wtx->type)
406 {
407 case TransactionRecord::RecvFromOther:
408 return QString::fromStdString(wtx->address);
409 case TransactionRecord::RecvWithAddress:
410 case TransactionRecord::SendToAddress:
411 case TransactionRecord::Generated:
412 return lookupAddress(wtx->address, tooltip);
413 case TransactionRecord::SendToOther:
414 return QString::fromStdString(wtx->address);
415 default:
416 return tr("(n/a)");
417 }
418 }
419
420 QVariant TransactionTableModel::addressColor(const TransactionRecord *wtx) const
421 {
422 // Show addresses without label in a less visible color
423 switch(wtx->type)
424 {
425 case TransactionRecord::RecvWithAddress:
426 case TransactionRecord::SendToAddress:
427 case TransactionRecord::Generated:
428 {
429 QString label = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(wtx->address));
430 if(label.isEmpty())
431 return COLOR_BAREADDRESS;
432 } break;
433 default:
434 break;
435 }
436 return QVariant();
437 }
438
439 QString TransactionTableModel::formatTxAmount(const TransactionRecord *wtx, bool showUnconfirmed, BitcoinUnits::SeparatorStyle separators) const
440 {
441 QString str = BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), wtx->credit + wtx->debit, false, separators);
442 if(showUnconfirmed)
443 {
444 if(!wtx->status.countsForBalance)
445 {
446 str = QString("[") + str + QString("]");
447 }
448 }
449 return QString(str);
450 }
451
452 QVariant TransactionTableModel::txStatusDecoration(const TransactionRecord *wtx) const
453 {
454 switch(wtx->status.status)
455 {
456 case TransactionStatus::Unconfirmed:
457 return QIcon(":/icons/transaction_0");
458 case TransactionStatus::Abandoned:
459 return QIcon(":/icons/transaction_abandoned");
460 case TransactionStatus::Confirming:
461 switch(wtx->status.depth)
462 {
463 case 1: return QIcon(":/icons/transaction_1");
464 case 2: return QIcon(":/icons/transaction_2");
465 case 3: return QIcon(":/icons/transaction_3");
466 case 4: return QIcon(":/icons/transaction_4");
467 default: return QIcon(":/icons/transaction_5");
468 };
469 case TransactionStatus::Confirmed:
470 return QIcon(":/icons/transaction_confirmed");
471 case TransactionStatus::Conflicted:
472 return QIcon(":/icons/transaction_conflicted");
473 case TransactionStatus::Immature: {
474 int total = wtx->status.depth + wtx->status.matures_in;
475 int part = (wtx->status.depth * 4 / total) + 1;
476 return QIcon(QString(":/icons/transaction_%1").arg(part));
477 }
478 case TransactionStatus::NotAccepted:
479 return QIcon(":/icons/transaction_0");
480 } // no default case, so the compiler can warn about missing cases
481 assert(false);
482 }
483
484 QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
485 {
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)
489 {
490 tooltip += QString(" ") + formatTxToAddress(rec, true);
491 }
492 return tooltip;
493 }
494
495 QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
496 {
497 if(!index.isValid())
498 return QVariant();
499 TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
500
501 const auto column = static_cast<ColumnIndex>(index.column());
502 switch (role) {
503 case RawDecorationRole:
504 switch (column) {
505 case Status:
506 return txStatusDecoration(rec);
507 case Date: return {};
508 case Type: return {};
509 case ToAddress:
510 return txAddressDecoration(rec);
511 case Amount: return {};
512 } // no default case, so the compiler can warn about missing cases
513 assert(false);
514 case Qt::DecorationRole:
515 {
516 QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole));
517 return platformStyle->TextColorIcon(icon);
518 }
519 case Qt::DisplayRole:
520 switch (column) {
521 case Status: return {};
522 case Date:
523 return formatTxDate(rec);
524 case Type:
525 return formatTxType(rec);
526 case ToAddress:
527 return formatTxToAddress(rec, false);
528 case Amount:
529 return formatTxAmount(rec, true, BitcoinUnits::SeparatorStyle::ALWAYS);
530 } // no default case, so the compiler can warn about missing cases
531 assert(false);
532 case Qt::EditRole:
533 // Edit role is used for sorting, so return the unformatted values
534 switch (column) {
535 case Status:
536 return QString::fromStdString(rec->status.sortKey);
537 case Date:
538 return QString::fromStdString(strprintf("%020s-%s", rec->time, rec->status.sortKey));
539 case Type:
540 return formatTxType(rec);
541 case ToAddress:
542 return formatTxToAddress(rec, true);
543 case Amount:
544 return qint64(rec->credit + rec->debit);
545 } // no default case, so the compiler can warn about missing cases
546 assert(false);
547 case Qt::ToolTipRole:
548 return formatTooltip(rec);
549 case Qt::TextAlignmentRole:
550 return column_alignments[index.column()];
551 case Qt::ForegroundRole:
552 // Use the "danger" color for abandoned transactions
553 if(rec->status.status == TransactionStatus::Abandoned)
554 {
555 return COLOR_TX_STATUS_DANGER;
556 }
557 // Non-confirmed (but not immature) as transactions are grey
558 if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
559 {
560 return COLOR_UNCONFIRMED;
561 }
562 if(index.column() == Amount && (rec->credit+rec->debit) < 0)
563 {
564 return COLOR_NEGATIVE;
565 }
566 if(index.column() == ToAddress)
567 {
568 return addressColor(rec);
569 }
570 break;
571 case TypeRole:
572 return rec->type;
573 case DateRole:
574 return QDateTime::fromSecsSinceEpoch(rec->time);
575 case LongDescriptionRole:
576 return priv->describe(walletModel->node(), walletModel->wallet(), rec, walletModel->getOptionsModel()->getDisplayUnit());
577 case AddressRole:
578 return QString::fromStdString(rec->address);
579 case LabelRole:
580 return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
581 case AmountRole:
582 return qint64(rec->credit + rec->debit);
583 case TxHashRole:
584 return rec->getTxHash();
585 case TxHexRole:
586 return priv->getTxHex(walletModel->wallet(), rec);
587 case TxPlainTextRole:
588 {
589 QString details;
590 QDateTime date = QDateTime::fromSecsSinceEpoch(rec->time);
591 QString txLabel = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
592
593 details.append(date.toString("M/d/yy HH:mm"));
594 details.append(" ");
595 details.append(formatTxStatus(rec));
596 details.append(". ");
597 if(!formatTxType(rec).isEmpty()) {
598 details.append(formatTxType(rec));
599 details.append(" ");
600 }
601 if(!rec->address.empty()) {
602 if(txLabel.isEmpty())
603 details.append(tr("(no label)") + " ");
604 else {
605 details.append("(");
606 details.append(txLabel);
607 details.append(") ");
608 }
609 details.append(QString::fromStdString(rec->address));
610 details.append(" ");
611 }
612 details.append(formatTxAmount(rec, false, BitcoinUnits::SeparatorStyle::NEVER));
613 return details;
614 }
615 case ConfirmedRole:
616 return rec->status.status == TransactionStatus::Status::Confirming || rec->status.status == TransactionStatus::Status::Confirmed;
617 case FormattedAmountRole:
618 // Used for copy/export, so don't include separators
619 return formatTxAmount(rec, false, BitcoinUnits::SeparatorStyle::NEVER);
620 case StatusRole:
621 return rec->status.status;
622 }
623 return QVariant();
624 }
625
626 QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
627 {
628 if(orientation == Qt::Horizontal)
629 {
630 if(role == Qt::DisplayRole)
631 {
632 return columns[section];
633 }
634 else if (role == Qt::TextAlignmentRole)
635 {
636 return column_alignments[section];
637 } else if (role == Qt::ToolTipRole)
638 {
639 switch(section)
640 {
641 case Status:
642 return tr("Transaction status. Hover over this field to show number of confirmations.");
643 case Date:
644 return tr("Date and time that the transaction was received.");
645 case Type:
646 return tr("Type of transaction.");
647 case ToAddress:
648 return tr("User-defined intent/purpose of the transaction.");
649 case Amount:
650 return tr("Amount removed from or added to balance.");
651 }
652 }
653 }
654 return QVariant();
655 }
656
657 QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
658 {
659 Q_UNUSED(parent);
660 TransactionRecord* data = priv->index(walletModel->wallet(), walletModel->getLastBlockProcessed(), row);
661 if(data)
662 {
663 return createIndex(row, column, data);
664 }
665 return QModelIndex();
666 }
667
668 void TransactionTableModel::updateDisplayUnit()
669 {
670 // emit dataChanged to update Amount column with the current unit
671 updateAmountColumnTitle();
672 Q_EMIT dataChanged(index(0, Amount), index(priv->size()-1, Amount));
673 }
674
675 void TransactionTablePriv::NotifyTransactionChanged(const Txid& hash, ChangeType status)
676 {
677 // Find transaction in wallet
678 // Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
679 bool showTransaction = TransactionRecord::showTransaction();
680
681 TransactionNotification notification(hash, status, showTransaction);
682
683 if (!m_loaded || m_loading)
684 {
685 vQueueNotifications.push_back(notification);
686 return;
687 }
688 notification.invoke(parent);
689 }
690
691 void TransactionTablePriv::DispatchNotifications()
692 {
693 if (!m_loaded || m_loading) return;
694
695 if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons
696 bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
697 assert(invoked);
698 }
699 for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
700 {
701 if (vQueueNotifications.size() - i <= 10) {
702 bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
703 assert(invoked);
704 }
705
706 vQueueNotifications[i].invoke(parent);
707 }
708 vQueueNotifications.clear();
709 }
710
711 void TransactionTableModel::subscribeToCoreSignals()
712 {
713 // Connect signals to wallet
714 m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged([this](const Txid& hash, ChangeType status) {
715 priv->NotifyTransactionChanged(hash, status);
716 });
717 m_handler_show_progress = walletModel->wallet().handleShowProgress([this](const std::string&, int progress) {
718 priv->m_loading = progress < 100;
719 priv->DispatchNotifications();
720 });
721 }
722
723 void TransactionTableModel::unsubscribeFromCoreSignals()
724 {
725 // Disconnect signals from wallet
726 m_handler_transaction_changed->disconnect();
727 m_handler_show_progress->disconnect();
728 }
729