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