mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-11-11 06:28:31 +01:00
qt: Move transaction notification to transaction table model
Move transaction new/update notification to TransactionTableModel. This moves the concerns to where they're actually handled. No need to bounce this through wallet model. - Do wallet transaction preprocessing on signal handler side; avoids locking cs_main/cs_wallet on notification in GUI thread (except for new transactions)
This commit is contained in:
@@ -91,87 +91,80 @@ public:
|
||||
|
||||
Call with transaction that was added, removed or changed.
|
||||
*/
|
||||
void updateWallet(const uint256 &hash, int status)
|
||||
void updateWallet(const uint256 &hash, int status, bool showTransaction)
|
||||
{
|
||||
qDebug() << "TransactionTablePriv::updateWallet : " + QString::fromStdString(hash.ToString()) + " " + QString::number(status);
|
||||
|
||||
// Find bounds of this transaction in model
|
||||
QList<TransactionRecord>::iterator lower = qLowerBound(
|
||||
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
|
||||
QList<TransactionRecord>::iterator upper = qUpperBound(
|
||||
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
|
||||
int lowerIndex = (lower - cachedWallet.begin());
|
||||
int upperIndex = (upper - cachedWallet.begin());
|
||||
bool inModel = (lower != upper);
|
||||
|
||||
if(status == CT_UPDATED)
|
||||
{
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
if(showTransaction && !inModel)
|
||||
status = CT_NEW; /* Not in model, but want to show, treat as new */
|
||||
if(!showTransaction && inModel)
|
||||
status = CT_DELETED; /* In model, but want to hide, treat as deleted */
|
||||
}
|
||||
|
||||
// Find transaction in wallet
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
||||
bool inWallet = mi != wallet->mapWallet.end();
|
||||
qDebug() << " inModel=" + QString::number(inModel) +
|
||||
" Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
|
||||
" showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
|
||||
|
||||
// Find bounds of this transaction in model
|
||||
QList<TransactionRecord>::iterator lower = qLowerBound(
|
||||
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
|
||||
QList<TransactionRecord>::iterator upper = qUpperBound(
|
||||
cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan());
|
||||
int lowerIndex = (lower - cachedWallet.begin());
|
||||
int upperIndex = (upper - cachedWallet.begin());
|
||||
bool inModel = (lower != upper);
|
||||
|
||||
// Determine whether to show transaction or not
|
||||
bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
|
||||
|
||||
if(status == CT_UPDATED)
|
||||
switch(status)
|
||||
{
|
||||
case CT_NEW:
|
||||
if(inModel)
|
||||
{
|
||||
if(showTransaction && !inModel)
|
||||
status = CT_NEW; /* Not in model, but want to show, treat as new */
|
||||
if(!showTransaction && inModel)
|
||||
status = CT_DELETED; /* In model, but want to hide, treat as deleted */
|
||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is already in model";
|
||||
break;
|
||||
}
|
||||
|
||||
qDebug() << " inWallet=" + QString::number(inWallet) + " inModel=" + QString::number(inModel) +
|
||||
" Index=" + QString::number(lowerIndex) + "-" + QString::number(upperIndex) +
|
||||
" showTransaction=" + QString::number(showTransaction) + " derivedStatus=" + QString::number(status);
|
||||
|
||||
switch(status)
|
||||
if(showTransaction)
|
||||
{
|
||||
case CT_NEW:
|
||||
if(inModel)
|
||||
{
|
||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is already in model";
|
||||
break;
|
||||
}
|
||||
if(!inWallet)
|
||||
LOCK2(cs_main, wallet->cs_wallet);
|
||||
// Find transaction in wallet
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
||||
if(mi == wallet->mapWallet.end())
|
||||
{
|
||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_NEW, but transaction is not in wallet";
|
||||
break;
|
||||
}
|
||||
if(showTransaction)
|
||||
// Added -- insert at the right position
|
||||
QList<TransactionRecord> toInsert =
|
||||
TransactionRecord::decomposeTransaction(wallet, mi->second);
|
||||
if(!toInsert.isEmpty()) /* only if something to insert */
|
||||
{
|
||||
// Added -- insert at the right position
|
||||
QList<TransactionRecord> toInsert =
|
||||
TransactionRecord::decomposeTransaction(wallet, mi->second);
|
||||
if(!toInsert.isEmpty()) /* only if something to insert */
|
||||
parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
|
||||
int insert_idx = lowerIndex;
|
||||
foreach(const TransactionRecord &rec, toInsert)
|
||||
{
|
||||
parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex+toInsert.size()-1);
|
||||
int insert_idx = lowerIndex;
|
||||
foreach(const TransactionRecord &rec, toInsert)
|
||||
{
|
||||
cachedWallet.insert(insert_idx, rec);
|
||||
insert_idx += 1;
|
||||
}
|
||||
parent->endInsertRows();
|
||||
cachedWallet.insert(insert_idx, rec);
|
||||
insert_idx += 1;
|
||||
}
|
||||
parent->endInsertRows();
|
||||
}
|
||||
break;
|
||||
case CT_DELETED:
|
||||
if(!inModel)
|
||||
{
|
||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_DELETED, but transaction is not in model";
|
||||
break;
|
||||
}
|
||||
// Removed -- remove entire transaction from table
|
||||
parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
|
||||
cachedWallet.erase(lower, upper);
|
||||
parent->endRemoveRows();
|
||||
break;
|
||||
case CT_UPDATED:
|
||||
// Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
|
||||
// visible transactions.
|
||||
}
|
||||
break;
|
||||
case CT_DELETED:
|
||||
if(!inModel)
|
||||
{
|
||||
qWarning() << "TransactionTablePriv::updateWallet : Warning: Got CT_DELETED, but transaction is not in model";
|
||||
break;
|
||||
}
|
||||
// Removed -- remove entire transaction from table
|
||||
parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1);
|
||||
cachedWallet.erase(lower, upper);
|
||||
parent->endRemoveRows();
|
||||
break;
|
||||
case CT_UPDATED:
|
||||
// Miscellaneous updates -- nothing to do, status update will take care of this, and is only computed for
|
||||
// visible transactions.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,16 +223,20 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren
|
||||
QAbstractTableModel(parent),
|
||||
wallet(wallet),
|
||||
walletModel(parent),
|
||||
priv(new TransactionTablePriv(wallet, this))
|
||||
priv(new TransactionTablePriv(wallet, this)),
|
||||
fProcessingQueuedTransactions(false)
|
||||
{
|
||||
columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
|
||||
priv->refreshWallet();
|
||||
|
||||
connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
|
||||
|
||||
subscribeToCoreSignals();
|
||||
}
|
||||
|
||||
TransactionTableModel::~TransactionTableModel()
|
||||
{
|
||||
unsubscribeFromCoreSignals();
|
||||
delete priv;
|
||||
}
|
||||
|
||||
@@ -250,12 +247,12 @@ void TransactionTableModel::updateAmountColumnTitle()
|
||||
emit headerDataChanged(Qt::Horizontal,Amount,Amount);
|
||||
}
|
||||
|
||||
void TransactionTableModel::updateTransaction(const QString &hash, int status)
|
||||
void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
|
||||
{
|
||||
uint256 updated;
|
||||
updated.SetHex(hash.toStdString());
|
||||
|
||||
priv->updateWallet(updated, status);
|
||||
priv->updateWallet(updated, status, showTransaction);
|
||||
}
|
||||
|
||||
void TransactionTableModel::updateConfirmations()
|
||||
@@ -649,3 +646,82 @@ void TransactionTableModel::updateDisplayUnit()
|
||||
updateAmountColumnTitle();
|
||||
emit dataChanged(index(0, Amount), index(priv->size()-1, Amount));
|
||||
}
|
||||
|
||||
// queue notifications to show a non freezing progress dialog e.g. for rescan
|
||||
struct TransactionNotification
|
||||
{
|
||||
public:
|
||||
TransactionNotification() {}
|
||||
TransactionNotification(uint256 hash, ChangeType status, bool showTransaction):
|
||||
hash(hash), status(status), showTransaction(showTransaction) {}
|
||||
|
||||
void invoke(QObject *ttm)
|
||||
{
|
||||
QString strHash = QString::fromStdString(hash.GetHex());
|
||||
qDebug() << "NotifyTransactionChanged : " + strHash + " status= " + QString::number(status);
|
||||
QMetaObject::invokeMethod(ttm, "updateTransaction", Qt::QueuedConnection,
|
||||
Q_ARG(QString, strHash),
|
||||
Q_ARG(int, status),
|
||||
Q_ARG(bool, showTransaction));
|
||||
}
|
||||
private:
|
||||
uint256 hash;
|
||||
ChangeType status;
|
||||
bool showTransaction;
|
||||
};
|
||||
|
||||
static bool fQueueNotifications = false;
|
||||
static std::vector< TransactionNotification > vQueueNotifications;
|
||||
|
||||
static void NotifyTransactionChanged(TransactionTableModel *ttm, CWallet *wallet, const uint256 &hash, ChangeType status)
|
||||
{
|
||||
// Find transaction in wallet
|
||||
std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash);
|
||||
// Determine whether to show transaction or not (determine this here so that no relocking is needed in GUI thread)
|
||||
bool inWallet = mi != wallet->mapWallet.end();
|
||||
bool showTransaction = (inWallet && TransactionRecord::showTransaction(mi->second));
|
||||
|
||||
TransactionNotification notification(hash, status, showTransaction);
|
||||
|
||||
if (fQueueNotifications)
|
||||
{
|
||||
vQueueNotifications.push_back(notification);
|
||||
return;
|
||||
}
|
||||
notification.invoke(ttm);
|
||||
}
|
||||
|
||||
static void ShowProgress(TransactionTableModel *ttm, const std::string &title, int nProgress)
|
||||
{
|
||||
if (nProgress == 0)
|
||||
fQueueNotifications = true;
|
||||
|
||||
if (nProgress == 100)
|
||||
{
|
||||
fQueueNotifications = false;
|
||||
if (vQueueNotifications.size() > 10) // prevent balloon spam, show maximum 10 balloons
|
||||
QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, true));
|
||||
for (unsigned int i = 0; i < vQueueNotifications.size(); ++i)
|
||||
{
|
||||
if (vQueueNotifications.size() - i <= 10)
|
||||
QMetaObject::invokeMethod(ttm, "setProcessingQueuedTransactions", Qt::QueuedConnection, Q_ARG(bool, false));
|
||||
|
||||
vQueueNotifications[i].invoke(ttm);
|
||||
}
|
||||
std::vector<TransactionNotification >().swap(vQueueNotifications); // clear
|
||||
}
|
||||
}
|
||||
|
||||
void TransactionTableModel::subscribeToCoreSignals()
|
||||
{
|
||||
// Connect signals to wallet
|
||||
wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||
wallet->ShowProgress.connect(boost::bind(ShowProgress, this, _1, _2));
|
||||
}
|
||||
|
||||
void TransactionTableModel::unsubscribeFromCoreSignals()
|
||||
{
|
||||
// Disconnect signals from wallet
|
||||
wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3));
|
||||
wallet->ShowProgress.disconnect(boost::bind(ShowProgress, this, _1, _2));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user