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