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