/ src / qt / transactiontablemodel.cpp
transactiontablemodel.cpp
  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      default:
481          return COLOR_BLACK;
482      }
483  }
484  
485  QString TransactionTableModel::formatTooltip(const TransactionRecord *rec) const
486  {
487      QString tooltip = formatTxStatus(rec) + QString("\n") + formatTxType(rec);
488      if(rec->type==TransactionRecord::RecvFromOther || rec->type==TransactionRecord::SendToOther ||
489         rec->type==TransactionRecord::SendToAddress || rec->type==TransactionRecord::RecvWithAddress)
490      {
491          tooltip += QString(" ") + formatTxToAddress(rec, true);
492      }
493      return tooltip;
494  }
495  
496  QVariant TransactionTableModel::data(const QModelIndex &index, int role) const
497  {
498      if(!index.isValid())
499          return QVariant();
500      TransactionRecord *rec = static_cast<TransactionRecord*>(index.internalPointer());
501  
502      const auto column = static_cast<ColumnIndex>(index.column());
503      switch (role) {
504      case RawDecorationRole:
505          switch (column) {
506          case Status:
507              return txStatusDecoration(rec);
508          case Date: return {};
509          case Type: return {};
510          case ToAddress:
511              return txAddressDecoration(rec);
512          case Amount: return {};
513          } // no default case, so the compiler can warn about missing cases
514          assert(false);
515      case Qt::DecorationRole:
516      {
517          QIcon icon = qvariant_cast<QIcon>(index.data(RawDecorationRole));
518          return platformStyle->TextColorIcon(icon);
519      }
520      case Qt::DisplayRole:
521          switch (column) {
522          case Status: return {};
523          case Date:
524              return formatTxDate(rec);
525          case Type:
526              return formatTxType(rec);
527          case ToAddress:
528              return formatTxToAddress(rec, false);
529          case Amount:
530              return formatTxAmount(rec, true, BitcoinUnits::SeparatorStyle::ALWAYS);
531          } // no default case, so the compiler can warn about missing cases
532          assert(false);
533      case Qt::EditRole:
534          // Edit role is used for sorting, so return the unformatted values
535          switch (column) {
536          case Status:
537              return QString::fromStdString(rec->status.sortKey);
538          case Date:
539              return QString::fromStdString(strprintf("%020s-%s", rec->time, rec->status.sortKey));
540          case Type:
541              return formatTxType(rec);
542          case ToAddress:
543              return formatTxToAddress(rec, true);
544          case Amount:
545              return qint64(rec->credit + rec->debit);
546          } // no default case, so the compiler can warn about missing cases
547          assert(false);
548      case Qt::ToolTipRole:
549          return formatTooltip(rec);
550      case Qt::TextAlignmentRole:
551          return column_alignments[index.column()];
552      case Qt::ForegroundRole:
553          // Use the "danger" color for abandoned transactions
554          if(rec->status.status == TransactionStatus::Abandoned)
555          {
556              return COLOR_TX_STATUS_DANGER;
557          }
558          // Non-confirmed (but not immature) as transactions are grey
559          if(!rec->status.countsForBalance && rec->status.status != TransactionStatus::Immature)
560          {
561              return COLOR_UNCONFIRMED;
562          }
563          if(index.column() == Amount && (rec->credit+rec->debit) < 0)
564          {
565              return COLOR_NEGATIVE;
566          }
567          if(index.column() == ToAddress)
568          {
569              return addressColor(rec);
570          }
571          break;
572      case TypeRole:
573          return rec->type;
574      case DateRole:
575          return QDateTime::fromSecsSinceEpoch(rec->time);
576      case LongDescriptionRole:
577          return priv->describe(walletModel->node(), walletModel->wallet(), rec, walletModel->getOptionsModel()->getDisplayUnit());
578      case AddressRole:
579          return QString::fromStdString(rec->address);
580      case LabelRole:
581          return walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
582      case AmountRole:
583          return qint64(rec->credit + rec->debit);
584      case TxHashRole:
585          return rec->getTxHash();
586      case TxHexRole:
587          return priv->getTxHex(walletModel->wallet(), rec);
588      case TxPlainTextRole:
589          {
590              QString details;
591              QDateTime date = QDateTime::fromSecsSinceEpoch(rec->time);
592              QString txLabel = walletModel->getAddressTableModel()->labelForAddress(QString::fromStdString(rec->address));
593  
594              details.append(date.toString("M/d/yy HH:mm"));
595              details.append(" ");
596              details.append(formatTxStatus(rec));
597              details.append(". ");
598              if(!formatTxType(rec).isEmpty()) {
599                  details.append(formatTxType(rec));
600                  details.append(" ");
601              }
602              if(!rec->address.empty()) {
603                  if(txLabel.isEmpty())
604                      details.append(tr("(no label)") + " ");
605                  else {
606                      details.append("(");
607                      details.append(txLabel);
608                      details.append(") ");
609                  }
610                  details.append(QString::fromStdString(rec->address));
611                  details.append(" ");
612              }
613              details.append(formatTxAmount(rec, false, BitcoinUnits::SeparatorStyle::NEVER));
614              return details;
615          }
616      case ConfirmedRole:
617          return rec->status.status == TransactionStatus::Status::Confirming || rec->status.status == TransactionStatus::Status::Confirmed;
618      case FormattedAmountRole:
619          // Used for copy/export, so don't include separators
620          return formatTxAmount(rec, false, BitcoinUnits::SeparatorStyle::NEVER);
621      case StatusRole:
622          return rec->status.status;
623      }
624      return QVariant();
625  }
626  
627  QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientation, int role) const
628  {
629      if(orientation == Qt::Horizontal)
630      {
631          if(role == Qt::DisplayRole)
632          {
633              return columns[section];
634          }
635          else if (role == Qt::TextAlignmentRole)
636          {
637              return column_alignments[section];
638          } else if (role == Qt::ToolTipRole)
639          {
640              switch(section)
641              {
642              case Status:
643                  return tr("Transaction status. Hover over this field to show number of confirmations.");
644              case Date:
645                  return tr("Date and time that the transaction was received.");
646              case Type:
647                  return tr("Type of transaction.");
648              case ToAddress:
649                  return tr("User-defined intent/purpose of the transaction.");
650              case Amount:
651                  return tr("Amount removed from or added to balance.");
652              }
653          }
654      }
655      return QVariant();
656  }
657  
658  QModelIndex TransactionTableModel::index(int row, int column, const QModelIndex &parent) const
659  {
660      Q_UNUSED(parent);
661      TransactionRecord* data = priv->index(walletModel->wallet(), walletModel->getLastBlockProcessed(), row);
662      if(data)
663      {
664          return createIndex(row, column, data);
665      }
666      return QModelIndex();
667  }
668  
669  void TransactionTableModel::updateDisplayUnit()
670  {
671      // emit dataChanged to update Amount column with the current unit
672      updateAmountColumnTitle();
673      Q_EMIT dataChanged(index(0, Amount), index(priv->size()-1, Amount));
674  }
675  
676  void TransactionTablePriv::NotifyTransactionChanged(const Txid& hash, ChangeType status)
677  {
678      // Find transaction in wallet
679      // Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
680      bool showTransaction = TransactionRecord::showTransaction();
681  
682      TransactionNotification notification(hash, status, showTransaction);
683  
684      if (!m_loaded || m_loading)
685      {
686          vQueueNotifications.push_back(notification);
687          return;
688      }
689      notification.invoke(parent);
690  }
691  
692  void TransactionTablePriv::DispatchNotifications()
693  {
694      if (!m_loaded || m_loading) return;
695  
696      if (vQueueNotifications.size() > 10) { // prevent balloon spam, show maximum 10 balloons
697          bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
698          assert(invoked);
699      }
700      for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
701      {
702          if (vQueueNotifications.size() - i <= 10) {
703              bool invoked = QMetaObject::invokeMethod(parent, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
704              assert(invoked);
705          }
706  
707          vQueueNotifications[i].invoke(parent);
708      }
709      vQueueNotifications.clear();
710  }
711  
712  void TransactionTableModel::subscribeToCoreSignals()
713  {
714      // Connect signals to wallet
715      m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged([this](const Txid& hash, ChangeType status) {
716          priv->NotifyTransactionChanged(hash, status);
717      });
718      m_handler_show_progress = walletModel->wallet().handleShowProgress([this](const std::string&, int progress) {
719          priv->m_loading = progress < 100;
720          priv->DispatchNotifications();
721      });
722  }
723  
724  void TransactionTableModel::unsubscribeFromCoreSignals()
725  {
726      // Disconnect signals from wallet
727      m_handler_transaction_changed->disconnect();
728      m_handler_show_progress->disconnect();
729  }