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