diff --git a/src/keystore.cpp b/src/keystore.cpp index 313518711..55fba4c37 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -5,6 +5,7 @@ #include "keystore.h" #include "script.h" +#include "ui_interface.h" bool CKeyStore::GetPubKey(const CBitcoinAddress &address, std::vector &vchPubKeyOut) const { @@ -73,6 +74,20 @@ bool CCryptoKeyStore::SetCrypted() return true; } +bool CCryptoKeyStore::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyKeyStoreStatusChanged(this); + return true; +} + bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) { { @@ -99,6 +114,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) } vMasterKey = vMasterKeyIn; } + NotifyKeyStoreStatusChanged(this); return true; } diff --git a/src/keystore.h b/src/keystore.h index 52889b184..dd7110db5 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -143,18 +143,7 @@ public: return result; } - bool Lock() - { - if (!SetCrypted()) - return false; - - { - LOCK(cs_KeyStore); - vMasterKey.clear(); - } - - return true; - } + bool Lock(); virtual bool AddCryptedKey(const std::vector &vchPubKey, const std::vector &vchCryptedSecret); bool AddKey(const CKey& key); diff --git a/src/main.cpp b/src/main.cpp index 8ff6d6fec..7e7a0badf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -946,7 +946,7 @@ void static InvalidChainFound(CBlockIndex* pindexNew) { bnBestInvalidWork = pindexNew->bnChainWork; CTxDB().WriteBestInvalidWork(bnBestInvalidWork); - MainFrameRepaint(); + NotifyBlocksChanged(); } printf("InvalidChainFound: invalid block=%s height=%d work=%s\n", pindexNew->GetBlockHash().ToString().substr(0,20).c_str(), pindexNew->nHeight, pindexNew->bnChainWork.ToString().c_str()); printf("InvalidChainFound: current best=%s height=%d work=%s\n", hashBestChain.ToString().substr(0,20).c_str(), nBestHeight, bnBestChainWork.ToString().c_str()); @@ -1647,7 +1647,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) hashPrevBestCoinBase = vtx[0].GetHash(); } - MainFrameRepaint(); + NotifyBlocksChanged(); return true; } @@ -2176,6 +2176,18 @@ string GetWarnings(string strFor) return "error"; } +CAlert CAlert::getAlertByHash(const uint256 &hash) +{ + CAlert retval; + { + LOCK(cs_mapAlerts); + map::iterator mi = mapAlerts.find(hash); + if(mi != mapAlerts.end()) + retval = mi->second; + } + return retval; +} + bool CAlert::ProcessAlert() { if (!CheckSignature()) @@ -2192,11 +2204,13 @@ bool CAlert::ProcessAlert() if (Cancels(alert)) { printf("cancelling alert %d\n", alert.nID); + NotifyAlertChanged((*mi).first, CT_DELETED); mapAlerts.erase(mi++); } else if (!alert.IsInEffect()) { printf("expiring alert %d\n", alert.nID); + NotifyAlertChanged((*mi).first, CT_DELETED); mapAlerts.erase(mi++); } else @@ -2216,10 +2230,12 @@ bool CAlert::ProcessAlert() // Add to mapAlerts mapAlerts.insert(make_pair(GetHash(), *this)); + // Notify UI if it applies to me + if(AppliesToMe()) + NotifyAlertChanged(GetHash(), CT_NEW); } printf("accepted alert %d, AppliesToMe()=%d\n", nID, AppliesToMe()); - MainFrameRepaint(); return true; } diff --git a/src/main.h b/src/main.h index 9700909f8..f4a9759d7 100644 --- a/src/main.h +++ b/src/main.h @@ -1574,6 +1574,11 @@ public: } bool ProcessAlert(); + + /* + * Get copy of (active) alert object by hash. Returns a null alert if it is not found. + */ + static CAlert getAlertByHash(const uint256 &hash); }; class CTxMemPool diff --git a/src/net.cpp b/src/net.cpp index 8d1938760..32875b0f0 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -705,7 +705,7 @@ void ThreadSocketHandler2(void* parg) if (vNodes.size() != nPrevNodeCount) { nPrevNodeCount = vNodes.size(); - MainFrameRepaint(); + NotifyNumConnectionsChanged(vNodes.size()); } diff --git a/src/noui.cpp b/src/noui.cpp index 08a08b439..c7b74bea7 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -19,14 +19,6 @@ bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) return true; } -void MainFrameRepaint() -{ -} - -void AddressBookRepaint() -{ -} - void InitMessage(const std::string &message) { } @@ -42,3 +34,26 @@ void QueueShutdown() CreateThread(Shutdown, NULL); } +void NotifyBlocksChanged() +{ +} + +void NotifyKeyStoreStatusChanged(CBasicKeyStore *wallet) +{ +} + +void NotifyAddressBookChanged(CWallet *wallet, const std::string &address, const std::string &label, EntryStatus status) +{ +} + +void NotifyTransactionChanged(CWallet *wallet, const uint256 &hashTx, EntryStatus status) +{ +} + +void NotifyNumConnectionsChanged(int newNumConnections) +{ +} + +void NotifyAlertChanged(const uint256 &hash, EntryStatus status) +{ +} diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 7b95f51c0..4af4c3ac1 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -232,9 +232,10 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex & pa } } -void AddressTableModel::update() +void AddressTableModel::updateEntry(const QString &address, const QString &label, int status) { // Update address book model from Bitcoin core + // TODO: use address, label, status to update only the specified entry (like in WalletModel) beginResetModel(); priv->refreshAddressTable(); endResetModel(); diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 7fd07cfb8..bd73c6d14 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -78,9 +78,9 @@ signals: void defaultAddressChanged(const QString &address); public slots: - /* Update address list from core. Invalidates any indices. + /* Update address list from core. */ - void update(); + void updateEntry(const QString &address, const QString &label, int status); }; #endif // ADDRESSTABLEMODEL_H diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 6a0a32fbd..a50443021 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -84,20 +84,6 @@ void ThreadSafeHandleURI(const std::string& strURI) Q_ARG(QString, QString::fromStdString(strURI))); } -void MainFrameRepaint() -{ - if(clientmodel) - QMetaObject::invokeMethod(clientmodel, "update", Qt::QueuedConnection); - if(walletmodel) - QMetaObject::invokeMethod(walletmodel, "update", Qt::QueuedConnection); -} - -void AddressBookRepaint() -{ - if(walletmodel) - QMetaObject::invokeMethod(walletmodel, "updateAddressList", Qt::QueuedConnection); -} - void InitMessage(const std::string &message) { if(splashref) @@ -120,6 +106,61 @@ std::string _(const char* psz) return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } +void NotifyBlocksChanged() +{ + // This notification is too frequent. Don't trigger a signal. + // Don't remove it, though, as it might be useful later. +} + +void NotifyKeyStoreStatusChanged(CBasicKeyStore *wallet) +{ + // This currently ignores the wallet argument. When multiple wallet support is implemented, this + // parameter should be mapped to a specific WalletModel for that wallet. + OutputDebugStringF("NotifyKeyStoreStatusChanged\n"); + if(walletmodel) + QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); +} + +void NotifyAddressBookChanged(CWallet *wallet, const std::string &address, const std::string &label, ChangeType status) +{ + // This currently ignores the wallet argument. When multiple wallet support is implemented, this + // parameter should be mapped to a specific WalletModel for that wallet. + OutputDebugStringF("NotifyAddressBookChanged %s %s status=%i\n", address.c_str(), label.c_str(), status); + if(walletmodel) + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(address)), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(int, status)); +} + +void NotifyTransactionChanged(CWallet *wallet, const uint256 &hash, ChangeType status) +{ + // This currently ignores the wallet argument. When multiple wallet support is implemented, this + // parameter should be mapped to a specific WalletModel for that wallet. + OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status); + if(walletmodel) + QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void NotifyNumConnectionsChanged(int newNumConnections) +{ + // Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections); + if(clientmodel) + QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, + Q_ARG(int, newNumConnections)); +} + +void NotifyAlertChanged(const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status); + if(clientmodel) + QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + /* Handle runaway exceptions. Shows a message box with the problem and quits the program. */ static void handleRunawayException(std::exception *e) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 7070e5944..9deaa4b6d 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -350,11 +350,11 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) setNumConnections(clientModel->getNumConnections()); connect(clientModel, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); - setNumBlocks(clientModel->getNumBlocks()); - connect(clientModel, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int))); + setNumBlocks(clientModel->getNumBlocks(), clientModel->getNumBlocksOfPeers()); + connect(clientModel, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); // Report errors from network/worker thread - connect(clientModel, SIGNAL(error(QString,QString, bool)), this, SLOT(error(QString,QString,bool))); + connect(clientModel, SIGNAL(error(QString,QString,bool)), this, SLOT(error(QString,QString,bool))); rpcConsole->setClientModel(clientModel); } @@ -493,7 +493,7 @@ void BitcoinGUI::setNumConnections(int count) labelConnectionsIcon->setToolTip(tr("%n active connection(s) to Bitcoin network", "", count)); } -void BitcoinGUI::setNumBlocks(int count) +void BitcoinGUI::setNumBlocks(int count, int nTotalBlocks) { // don't show / hide progressBar and it's label if we have no connection(s) to the network if (!clientModel || clientModel->getNumConnections() == 0) @@ -504,7 +504,6 @@ void BitcoinGUI::setNumBlocks(int count) return; } - int nTotalBlocks = clientModel->getNumBlocksOfPeers(); QString tooltip; if(count < nTotalBlocks) diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 88e6d064d..8a7f6e541 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -111,7 +111,7 @@ public slots: /** Set number of connections shown in the UI */ void setNumConnections(int count); /** Set number of blocks shown in the UI */ - void setNumBlocks(int count); + void setNumBlocks(int count, int countOfPeers); /** Set the encryption status as shown in the UI. @param[in] status current encryption status @see WalletModel::EncryptionStatus diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 85ab03612..1914ef90b 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -5,15 +5,23 @@ #include "transactiontablemodel.h" #include "main.h" -static const int64 nClientStartupTime = GetTime(); +#include "ui_interface.h" #include +#include + +static const int64 nClientStartupTime = GetTime(); ClientModel::ClientModel(OptionsModel *optionsModel, QObject *parent) : QObject(parent), optionsModel(optionsModel), - cachedNumConnections(0), cachedNumBlocks(0) + cachedNumBlocks(0), cachedNumBlocksOfPeers(0), pollTimer(0) { numBlocksAtStartup = -1; + + pollTimer = new QTimer(); + pollTimer->setInterval(MODEL_UPDATE_DELAY); + pollTimer->start(); + connect(pollTimer, SIGNAL(timeout()), this, SLOT(updateTimer())); } int ClientModel::getNumConnections() const @@ -37,27 +45,42 @@ QDateTime ClientModel::getLastBlockDate() const return QDateTime::fromTime_t(pindexBest->GetBlockTime()); } -void ClientModel::update() +void ClientModel::updateTimer() { - int newNumConnections = getNumConnections(); + // Some quantities (such as number of blocks) change so fast that we don't want to be notified for each change. + // Periodically check and update with a timer. int newNumBlocks = getNumBlocks(); - QString newStatusBar = getStatusBarWarnings(); + int newNumBlocksOfPeers = getNumBlocksOfPeers(); - if(cachedNumConnections != newNumConnections) - emit numConnectionsChanged(newNumConnections); - if(cachedNumBlocks != newNumBlocks || cachedStatusBar != newStatusBar) + if(cachedNumBlocks != newNumBlocks || cachedNumBlocksOfPeers != newNumBlocksOfPeers) + emit numBlocksChanged(newNumBlocks, newNumBlocksOfPeers); + + cachedNumBlocks = newNumBlocks; + cachedNumBlocksOfPeers = newNumBlocksOfPeers; +} + +void ClientModel::updateNumConnections(int numConnections) +{ + emit numConnectionsChanged(numConnections); +} + +void ClientModel::updateAlert(const QString &hash, int status) +{ + // Show error message notification for new alert + if(status == CT_NEW) { - // Simply emit a numBlocksChanged for now in case the status message changes, - // so that the view updates the status bar. - // TODO: It should send a notification. - // (However, this might generate looped notifications and needs to be thought through and tested carefully) - // error(tr("Network Alert"), newStatusBar); - emit numBlocksChanged(newNumBlocks); + uint256 hash_256; + hash_256.SetHex(hash.toStdString()); + CAlert alert = CAlert::getAlertByHash(hash_256); + if(!alert.IsNull()) + { + emit error(tr("Network Alert"), QString::fromStdString(alert.strStatusBar), false); + } } - cachedNumConnections = newNumConnections; - cachedNumBlocks = newNumBlocks; - cachedStatusBar = newStatusBar; + // Emit a numBlocksChanged when the status message changes, + // so that the view recomputes and updates the status bar. + emit numBlocksChanged(getNumBlocks(), getNumBlocksOfPeers()); } bool ClientModel::isTestNet() const diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 67835db72..bf2cd84a1 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -10,6 +10,7 @@ class CWallet; QT_BEGIN_NAMESPACE class QDateTime; +class QTimer; QT_END_NAMESPACE /** Model for Bitcoin network client. */ @@ -44,23 +45,24 @@ public: private: OptionsModel *optionsModel; - int cachedNumConnections; int cachedNumBlocks; - QString cachedStatusBar; + int cachedNumBlocksOfPeers; int numBlocksAtStartup; + QTimer *pollTimer; + signals: void numConnectionsChanged(int count); - void numBlocksChanged(int count); + void numBlocksChanged(int count, int countOfPeers); //! Asynchronous error notification void error(const QString &title, const QString &message, bool modal); public slots: - -private slots: - void update(); + void updateTimer(); + void updateNumConnections(int numConnections); + void updateAlert(const QString &hash, int status); }; #endif // CLIENTMODEL_H diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 0c77b05c5..7029ee33b 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -157,7 +157,7 @@ void RPCConsole::setClientModel(ClientModel *model) { // Subscribe to information, replies, messages, errors connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); - connect(model, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int))); + connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); @@ -168,7 +168,7 @@ void RPCConsole::setClientModel(ClientModel *model) setNumConnections(model->getNumConnections()); ui->isTestNet->setChecked(model->isTestNet()); - setNumBlocks(model->getNumBlocks()); + setNumBlocks(model->getNumBlocks(), model->getNumBlocksOfPeers()); } } @@ -235,9 +235,10 @@ void RPCConsole::setNumConnections(int count) ui->numberOfConnections->setText(QString::number(count)); } -void RPCConsole::setNumBlocks(int count) +void RPCConsole::setNumBlocks(int count, int countOfPeers) { ui->numberOfBlocks->setText(QString::number(count)); + ui->totalBlocks->setText(QString::number(countOfPeers)); if(clientModel) { // If there is no current number available display N/A instead of 0, which can't ever be true diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 27f41817e..4b71cdb98 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -42,7 +42,7 @@ public slots: /** Set number of connections shown in the UI */ void setNumConnections(int count); /** Set number of blocks shown in the UI */ - void setNumBlocks(int count); + void setNumBlocks(int count, int countOfPeers); /** Go forward or back in history */ void browseHistory(int offset); /** Scroll console view to end */ diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 53cfb409f..017244ffd 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -40,114 +40,111 @@ QList TransactionRecord::decomposeTransaction(const CWallet * uint256 hash = wtx.GetHash(); std::map mapValue = wtx.mapValue; - if (showTransaction(wtx)) + if (nNet > 0 || wtx.IsCoinBase()) { - if (nNet > 0 || wtx.IsCoinBase()) + // + // Credit + // + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + { + if(wallet->IsMine(txout)) + { + TransactionRecord sub(hash, nTime); + CBitcoinAddress address; + sub.idx = parts.size(); // sequence number + sub.credit = txout.nValue; + if (wtx.IsCoinBase()) + { + // Generated + sub.type = TransactionRecord::Generated; + } + else if (ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address)) + { + // Received by Bitcoin Address + sub.type = TransactionRecord::RecvWithAddress; + sub.address = address.ToString(); + } + else + { + // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction + sub.type = TransactionRecord::RecvFromOther; + sub.address = mapValue["from"]; + } + + parts.append(sub); + } + } + } + else + { + bool fAllFromMe = true; + BOOST_FOREACH(const CTxIn& txin, wtx.vin) + fAllFromMe = fAllFromMe && wallet->IsMine(txin); + + bool fAllToMe = true; + BOOST_FOREACH(const CTxOut& txout, wtx.vout) + fAllToMe = fAllToMe && wallet->IsMine(txout); + + if (fAllFromMe && fAllToMe) + { + // Payment to self + int64 nChange = wtx.GetChange(); + + parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", + -(nDebit - nChange), nCredit - nChange)); + } + else if (fAllFromMe) { // - // Credit + // Debit // - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + int64 nTxFee = nDebit - wtx.GetValueOut(); + + for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { + const CTxOut& txout = wtx.vout[nOut]; + TransactionRecord sub(hash, nTime); + sub.idx = parts.size(); + if(wallet->IsMine(txout)) { - TransactionRecord sub(hash, nTime); - CBitcoinAddress address; - sub.idx = parts.size(); // sequence number - sub.credit = txout.nValue; - if (wtx.IsCoinBase()) - { - // Generated - sub.type = TransactionRecord::Generated; - } - else if (ExtractAddress(txout.scriptPubKey, address) && wallet->HaveKey(address)) - { - // Received by Bitcoin Address - sub.type = TransactionRecord::RecvWithAddress; - sub.address = address.ToString(); - } - else - { - // Received by IP connection (deprecated features), or a multisignature or other non-simple transaction - sub.type = TransactionRecord::RecvFromOther; - sub.address = mapValue["from"]; - } - - parts.append(sub); + // Ignore parts sent to self, as this is usually the change + // from a transaction sent back to our own address. + continue; } + + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, address)) + { + // Sent to Bitcoin Address + sub.type = TransactionRecord::SendToAddress; + sub.address = address.ToString(); + } + else + { + // Sent to IP, or other non-address transaction like OP_EVAL + sub.type = TransactionRecord::SendToOther; + sub.address = mapValue["to"]; + } + + int64 nValue = txout.nValue; + /* Add fee to first output */ + if (nTxFee > 0) + { + nValue += nTxFee; + nTxFee = 0; + } + sub.debit = -nValue; + + parts.append(sub); } } else { - bool fAllFromMe = true; - BOOST_FOREACH(const CTxIn& txin, wtx.vin) - fAllFromMe = fAllFromMe && wallet->IsMine(txin); - - bool fAllToMe = true; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - fAllToMe = fAllToMe && wallet->IsMine(txout); - - if (fAllFromMe && fAllToMe) - { - // Payment to self - int64 nChange = wtx.GetChange(); - - parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", - -(nDebit - nChange), nCredit - nChange)); - } - else if (fAllFromMe) - { - // - // Debit - // - int64 nTxFee = nDebit - wtx.GetValueOut(); - - for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) - { - const CTxOut& txout = wtx.vout[nOut]; - TransactionRecord sub(hash, nTime); - sub.idx = parts.size(); - - if(wallet->IsMine(txout)) - { - // Ignore parts sent to self, as this is usually the change - // from a transaction sent back to our own address. - continue; - } - - CBitcoinAddress address; - if (ExtractAddress(txout.scriptPubKey, address)) - { - // Sent to Bitcoin Address - sub.type = TransactionRecord::SendToAddress; - sub.address = address.ToString(); - } - else - { - // Sent to IP, or other non-address transaction like OP_EVAL - sub.type = TransactionRecord::SendToOther; - sub.address = mapValue["to"]; - } - - int64 nValue = txout.nValue; - /* Add fee to first output */ - if (nTxFee > 0) - { - nValue += nTxFee; - nTxFee = 0; - } - sub.debit = -nValue; - - parts.append(sub); - } - } - else - { - // - // Mixed debit transaction, can't break down payees - // - parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); - } + // + // Mixed debit transaction, can't break down payees + // + parts.append(TransactionRecord(hash, nTime, TransactionRecord::Other, "", nNet, 0)); } } diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 5f505f444..d36bb495a 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -9,6 +9,7 @@ #include "bitcoinunits.h" #include "wallet.h" +#include "ui_interface.h" #include #include @@ -66,15 +67,14 @@ public: */ void refreshWallet() { -#ifdef WALLET_UPDATE_DEBUG - qDebug() << "refreshWallet"; -#endif + OutputDebugStringF("refreshWallet\n"); cachedWallet.clear(); { LOCK(wallet->cs_wallet); for(std::map::iterator it = wallet->mapWallet.begin(); it != wallet->mapWallet.end(); ++it) { - cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); + if(TransactionRecord::showTransaction(it->second)) + cachedWallet.append(TransactionRecord::decomposeTransaction(wallet, it->second)); } } } @@ -82,49 +82,55 @@ public: /* Update our model of the wallet incrementally, to synchronize our model of the wallet with that of the core. - Call with list of hashes of transactions that were added, removed or changed. + Call with transaction that was added, removed or changed. */ - void updateWallet(const QList &updated) + void updateWallet(const uint256 &hash, int status) { - // Walk through updated transactions, update model as needed. -#ifdef WALLET_UPDATE_DEBUG - qDebug() << "updateWallet"; -#endif - // Sort update list, and iterate through it in reverse, so that model updates - // can be emitted from end to beginning (so that earlier updates will not influence - // the indices of latter ones). - QList updated_sorted = updated; - qSort(updated_sorted); - + OutputDebugStringF("updateWallet %s %i\n", hash.ToString().c_str(), status); { LOCK(wallet->cs_wallet); - for(int update_idx = updated_sorted.size()-1; update_idx >= 0; --update_idx) + + // Find transaction in wallet + std::map::iterator mi = wallet->mapWallet.find(hash); + bool inWallet = mi != wallet->mapWallet.end(); + + // Find bounds of this transaction in model + QList::iterator lower = qLowerBound( + cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); + QList::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) { - const uint256 &hash = updated_sorted.at(update_idx); - // Find transaction in wallet - std::map::iterator mi = wallet->mapWallet.find(hash); - bool inWallet = mi != wallet->mapWallet.end(); - // Find bounds of this transaction in model - QList::iterator lower = qLowerBound( - cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - QList::iterator upper = qUpperBound( - cachedWallet.begin(), cachedWallet.end(), hash, TxLessThan()); - int lowerIndex = (lower - cachedWallet.begin()); - int upperIndex = (upper - cachedWallet.begin()); + 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 */ + } - // Determine if transaction is in model already - bool inModel = false; - if(lower != upper) + OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n", + inWallet, inModel, lowerIndex, upperIndex, showTransaction, status); + + switch(status) + { + case CT_NEW: + if(inModel) { - inModel = true; + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n"); + break; } - -#ifdef WALLET_UPDATE_DEBUG - qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel - << lowerIndex << "-" << upperIndex; -#endif - - if(inWallet && !inModel) + if(!inWallet) + { + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is not in wallet\n"); + break; + } + if(showTransaction) { // Added -- insert at the right position QList toInsert = @@ -141,17 +147,22 @@ public: parent->endInsertRows(); } } - else if(!inWallet && inModel) + break; + case CT_DELETED: + if(!inModel) { - // Removed -- remove entire transaction from table - parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); - cachedWallet.erase(lower, upper); - parent->endRemoveRows(); - } - else if(inWallet && inModel) - { - // Updated -- nothing to do, status update will take care of this + OutputDebugStringF("Warning: updateWallet: Got CT_DELETED, but transaction is not in model\n"); + 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; } } } @@ -209,14 +220,15 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren QAbstractTableModel(parent), wallet(wallet), walletModel(parent), - priv(new TransactionTablePriv(wallet, this)) + priv(new TransactionTablePriv(wallet, this)), + cachedNumBlocks(0) { columns << QString() << tr("Date") << tr("Type") << tr("Address") << tr("Amount"); priv->refreshWallet(); QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(update())); + connect(timer, SIGNAL(timeout()), this, SLOT(updateConfirmations())); timer->start(MODEL_UPDATE_DELAY); } @@ -225,29 +237,23 @@ TransactionTableModel::~TransactionTableModel() delete priv; } -void TransactionTableModel::update() +void TransactionTableModel::updateTransaction(const QString &hash, int status) { - QList updated; + uint256 updated; + updated.SetHex(hash.toStdString()); - // Check if there are changes to wallet map + priv->updateWallet(updated, status); +} + +void TransactionTableModel::updateConfirmations() +{ + if(nBestHeight != cachedNumBlocks) { - TRY_LOCK(wallet->cs_wallet, lockWallet); - if (lockWallet && !wallet->vWalletUpdated.empty()) - { - BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated) - { - updated.append(hash); - } - wallet->vWalletUpdated.clear(); - } - } - - if(!updated.empty()) - { - priv->updateWallet(updated); - - // Status (number of confirmations) and (possibly) description - // columns changed for all rows. + cachedNumBlocks = nBestHeight; + // Blocks came in since last poll. + // Invalidate status (number of confirmations) and (possibly) description + // for all rows. Qt is smart enough to only actually request the data for the + // visible rows. emit dataChanged(index(0, Status), index(priv->size()-1, Status)); emit dataChanged(index(0, ToAddress), index(priv->size()-1, ToAddress)); } diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index db88a0604..0aafa7091 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -60,6 +60,7 @@ private: WalletModel *walletModel; QStringList columns; TransactionTablePriv *priv; + int cachedNumBlocks; QString lookupAddress(const std::string &address, bool tooltip) const; QVariant addressColor(const TransactionRecord *wtx) const; @@ -72,8 +73,9 @@ private: QVariant txStatusDecoration(const TransactionRecord *wtx) const; QVariant txAddressDecoration(const TransactionRecord *wtx) const; -private slots: - void update(); +public slots: + void updateTransaction(const QString &hash, int status); + void updateConfirmations(); friend class TransactionTablePriv; }; diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index b9ccb06c0..4e082a8ab 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -40,30 +40,38 @@ int WalletModel::getNumTransactions() const return numTransactions; } -void WalletModel::update() +void WalletModel::updateStatus() { - qint64 newBalance = getBalance(); - qint64 newUnconfirmedBalance = getUnconfirmedBalance(); - int newNumTransactions = getNumTransactions(); EncryptionStatus newEncryptionStatus = getEncryptionStatus(); - if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance) - emit balanceChanged(newBalance, newUnconfirmedBalance); - - if(cachedNumTransactions != newNumTransactions) - emit numTransactionsChanged(newNumTransactions); - if(cachedEncryptionStatus != newEncryptionStatus) emit encryptionStatusChanged(newEncryptionStatus); +} + +void WalletModel::updateTransaction(const QString &hash, int status) +{ + if(transactionTableModel) + transactionTableModel->updateTransaction(hash, status); + + // Balance and number of transactions might have changed + qint64 newBalance = getBalance(); + qint64 newUnconfirmedBalance = getUnconfirmedBalance(); + int newNumTransactions = getNumTransactions(); + + if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance) + emit balanceChanged(newBalance, newUnconfirmedBalance); + if(cachedNumTransactions != newNumTransactions) + emit numTransactionsChanged(newNumTransactions); cachedBalance = newBalance; cachedUnconfirmedBalance = newUnconfirmedBalance; cachedNumTransactions = newNumTransactions; } -void WalletModel::updateAddressList() +void WalletModel::updateAddressBook(const QString &address, const QString &label, int status) { - addressTableModel->update(); + if(addressTableModel) + addressTableModel->updateEntry(address, label, status); } bool WalletModel::validateAddress(const QString &address) diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 6c47f61be..8250794f2 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -137,8 +137,12 @@ signals: void error(const QString &title, const QString &message, bool modal); public slots: - void update(); - void updateAddressList(); + /* Wallet status might have changed */ + void updateStatus(); + /* New transaction, or transaction changed status */ + void updateTransaction(const QString &hash, int status); + /* New, updated or removed address book entry */ + void updateAddressBook(const QString &address, const QString &label, int status); }; diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 1119aeaf7..9f5693a8b 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -73,8 +73,6 @@ Value importprivkey(const Array& params, bool fHelp) pwalletMain->ReacceptWalletTransactions(); } - MainFrameRepaint(); - return Value::null; } diff --git a/src/ui_interface.h b/src/ui_interface.h index 514768086..c1ed265a0 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -7,6 +7,10 @@ #include #include "util.h" // for int64 +class CBasicKeyStore; +class CWallet; +class uint256; + #define wxYES 0x00000002 #define wxOK 0x00000004 #define wxNO 0x00000008 @@ -36,15 +40,46 @@ // Force blocking, modal message box dialog (not just notification) #define wxMODAL 0x00040000 +enum ChangeType +{ + CT_NEW, + CT_UPDATED, + CT_DELETED +}; + /* These UI communication functions are implemented in bitcoin.cpp (for ui) and noui.cpp (no ui) */ extern int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style=wxOK); extern bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption); extern void ThreadSafeHandleURI(const std::string& strURI); -extern void MainFrameRepaint(); -extern void AddressBookRepaint(); extern void QueueShutdown(); extern void InitMessage(const std::string &message); extern std::string _(const char* psz); +/* Block chain changed. */ +extern void NotifyBlocksChanged(); + +/* Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ +extern void NotifyKeyStoreStatusChanged(CBasicKeyStore *wallet); + +/* Address book entry changed. + * Note: called with lock cs_wallet held. + */ +extern void NotifyAddressBookChanged(CWallet *wallet, const std::string &address, const std::string &label, ChangeType status); + +/* Wallet transaction added, removed or updated. + * Note: called with lock cs_wallet held. + */ +extern void NotifyTransactionChanged(CWallet *wallet, const uint256 &hashTx, ChangeType status); + +/* Number of connections changed. */ +extern void NotifyNumConnectionsChanged(int newNumConnections); + +/* New, updated or cancelled alert. + * Note: called with lock cs_mapAlerts held. + */ +extern void NotifyAlertChanged(const uint256 &hash, ChangeType status); + #endif diff --git a/src/wallet.cpp b/src/wallet.cpp index 768f9f85e..132f68bee 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -274,7 +274,9 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) // Need to completely rewrite the wallet file; if we don't, bdb might keep // bits of the unencrypted private key in slack space in the database file. CDB::Rewrite(strWalletFile); + } + NotifyKeyStoreStatusChanged(this); return true; } @@ -297,7 +299,7 @@ void CWallet::WalletUpdateSpent(const CTransaction &tx) printf("WalletUpdateSpent found spent coin %sbc %s\n", FormatMoney(wtx.GetCredit()).c_str(), wtx.GetHash().ToString().c_str()); wtx.MarkSpent(txin.prevout.n); wtx.WriteToDisk(); - vWalletUpdated.push_back(txin.prevout.hash); + NotifyTransactionChanged(this, txin.prevout.hash, CT_UPDATED); } } } @@ -373,15 +375,12 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn) } } #endif - // Notify UI - vWalletUpdated.push_back(hash); - // since AddToWallet is called directly for self-originating transactions, check for consumption of own coins WalletUpdateSpent(wtx); - } - // Refresh UI - MainFrameRepaint(); + // Notify UI of new or updated transaction + NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED); + } return true; } @@ -1183,7 +1182,7 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) coin.BindWallet(this); coin.MarkSpent(txin.prevout.n); coin.WriteToDisk(); - vWalletUpdated.push_back(coin.GetHash()); + NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED); } if (fFileBacked) @@ -1202,7 +1201,6 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey) } wtxNew.RelayWalletTransaction(); } - MainFrameRepaint(); return true; } @@ -1237,7 +1235,6 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, if (!CommitTransaction(wtxNew, reservekey)) return _("Error: The transaction was rejected. This might happen if some of the coins in your wallet were already spent, such as if you used a copy of wallet.dat and coins were spent in the copy but not marked as spent here."); - MainFrameRepaint(); return ""; } @@ -1290,8 +1287,9 @@ int CWallet::LoadWallet(bool& fFirstRunRet) bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& strName) { + std::map::iterator mi = mapAddressBook.find(address); mapAddressBook[address] = strName; - AddressBookRepaint(); + NotifyAddressBookChanged(this, address.ToString(), strName, (mi == mapAddressBook.end()) ? CT_NEW : CT_UPDATED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).WriteName(address.ToString(), strName); @@ -1300,7 +1298,7 @@ bool CWallet::SetAddressBookName(const CBitcoinAddress& address, const string& s bool CWallet::DelAddressBookName(const CBitcoinAddress& address) { mapAddressBook.erase(address); - AddressBookRepaint(); + NotifyAddressBookChanged(this, address.ToString(), "", CT_DELETED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).EraseName(address.ToString()); @@ -1558,3 +1556,14 @@ void CWallet::GetAllReserveAddresses(set& setAddress) setAddress.insert(address); } } + +void CWallet::UpdatedTransaction(const uint256 &hashTx) +{ + { + LOCK(cs_wallet); + // Only notify UI if this transaction is in this wallet + map::const_iterator mi = mapWallet.find(hashTx); + if (mi != mapWallet.end()) + NotifyTransactionChanged(this, hashTx, CT_UPDATED); + } +} diff --git a/src/wallet.h b/src/wallet.h index 44c11e2ec..f84e6a329 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -102,8 +102,6 @@ public: } std::map mapWallet; - std::vector vWalletUpdated; - std::map mapRequestCount; std::map mapAddressBook; @@ -232,13 +230,7 @@ public: bool DelAddressBookName(const CBitcoinAddress& address); - void UpdatedTransaction(const uint256 &hashTx) - { - { - LOCK(cs_wallet); - vWalletUpdated.push_back(hashTx); - } - } + void UpdatedTransaction(const uint256 &hashTx); void PrintWallet(const CBlock& block);