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