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