diff options
author | Wladimir J. van der Laan <laanwj@gmail.com> | 2012-05-20 01:53:24 -0700 |
---|---|---|
committer | Wladimir J. van der Laan <laanwj@gmail.com> | 2012-05-20 01:53:24 -0700 |
commit | 5a8398e55a5f2bc0ce95c1e5da06c11365af0764 (patch) | |
tree | 4ad01e70d94768977f25a0129255ec3c84635f2e | |
parent | 563f3efda3d3230bc512b35f15598d5e4e3a5071 (diff) | |
parent | 239c11d0dd4287e74286c40fb338aea85f4b1996 (diff) |
Merge pull request #1205 from laanwj/2012_05_granular_ui_notifications
Finer-grained UI updates, move UI interface to boost::signals
32 files changed, 694 insertions, 393 deletions
diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 334425a9a9..6c51bad6db 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -435,7 +435,7 @@ Value stop(const Array& params, bool fHelp) "stop\n" "Stop Bitcoin server."); // Shutdown will take long enough that the response should get back - QueueShutdown(); + uiInterface.QueueShutdown(); return "Bitcoin server stopping"; } @@ -1928,7 +1928,7 @@ Value encryptwallet(const Array& params, bool fHelp) // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: - QueueShutdown(); + uiInterface.QueueShutdown(); return "wallet encrypted; Bitcoin server stopping, restart to run with encrypted wallet"; } @@ -2620,7 +2620,7 @@ void ThreadRPCServer2(void* parg) strWhatAmI = strprintf(_("To use the %s option"), "\"-server\""); else if (mapArgs.count("-daemon")) strWhatAmI = strprintf(_("To use the %s option"), "\"-daemon\""); - ThreadSafeMessageBox(strprintf( + uiInterface.ThreadSafeMessageBox(strprintf( _("%s, you must set a rpcpassword in the configuration file:\n %s\n" "It is recommended you use the following random password:\n" "rpcuser=bitcoinrpc\n" @@ -2630,8 +2630,8 @@ void ThreadRPCServer2(void* parg) strWhatAmI.c_str(), GetConfigFile().string().c_str(), EncodeBase58(&rand_pwd[0],&rand_pwd[0]+32).c_str()), - _("Error"), wxOK | wxMODAL); - QueueShutdown(); + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return; } @@ -2650,9 +2650,9 @@ void ThreadRPCServer2(void* parg) } catch(boost::system::system_error &e) { - ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), - _("Error"), wxOK | wxMODAL); - QueueShutdown(); + uiInterface.ThreadSafeMessageBox(strprintf(_("An error occured while setting up the RPC port %i for listening: %s"), endpoint.port(), e.what()), + _("Error"), CClientUIInterface::OK | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return; } diff --git a/src/init.cpp b/src/init.cpp index 47b6f92324..e0c0c893aa 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -22,6 +22,7 @@ using namespace std; using namespace boost; CWallet* pwalletMain; +CClientUIInterface uiInterface; ////////////////////////////////////////////////////////////////////////////// // @@ -90,17 +91,6 @@ void HandleSIGTERM(int) // Start // #if !defined(QT_GUI) -int main(int argc, char* argv[]) -{ - bool fRet = false; - fRet = AppInit(argc, argv); - - if (fRet && fDaemon) - return 0; - - return 1; -} - bool AppInit(int argc, char* argv[]) { bool fRet = false; @@ -156,17 +146,33 @@ bool AppInit(int argc, char* argv[]) Shutdown(NULL); return fRet; } + +extern void noui_connect(); +int main(int argc, char* argv[]) +{ + bool fRet = false; + + // Connect bitcoind signal handlers + noui_connect(); + + fRet = AppInit(argc, argv); + + if (fRet && fDaemon) + return 0; + + return 1; +} #endif bool static InitError(const std::string &str) { - ThreadSafeMessageBox(str, _("Bitcoin"), wxOK | wxMODAL); + uiInterface.ThreadSafeMessageBox(str, _("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::MODAL); return false; } bool static InitWarning(const std::string &str) { - ThreadSafeMessageBox(str, _("Bitcoin"), wxOK | wxICON_EXCLAMATION | wxMODAL); + uiInterface.ThreadSafeMessageBox(str, _("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); return true; } @@ -367,7 +373,7 @@ bool AppInit2() fprintf(stdout, "Bitcoin server starting\n"); int64 nStart; - InitMessage(_("Loading addresses...")); + uiInterface.InitMessage(_("Loading addresses...")); printf("Loading addresses...\n"); nStart = GetTimeMillis(); @@ -380,7 +386,7 @@ bool AppInit2() printf("Loaded %i addresses from peers.dat %"PRI64d"ms\n", addrman.size(), GetTimeMillis() - nStart); - InitMessage(_("Loading block index...")); + uiInterface.InitMessage(_("Loading block index...")); printf("Loading block index...\n"); nStart = GetTimeMillis(); if (!LoadBlockIndex()) @@ -406,7 +412,7 @@ bool AppInit2() } } - InitMessage(_("Loading wallet...")); + uiInterface.InitMessage(_("Loading wallet...")); printf("Loading wallet...\n"); nStart = GetTimeMillis(); bool fFirstRun; @@ -474,14 +480,14 @@ bool AppInit2() } if (pindexBest != pindexRescan) { - InitMessage(_("Rescanning...")); + uiInterface.InitMessage(_("Rescanning...")); printf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); pwalletMain->ScanForWalletTransactions(pindexRescan, true); printf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart); } - InitMessage(_("Done loading")); + uiInterface.InitMessage(_("Done loading")); printf("Done loading\n"); //// debug print diff --git a/src/init.h b/src/init.h index 9a8f98cce9..970527c7ce 100644 --- a/src/init.h +++ b/src/init.h @@ -10,7 +10,6 @@ extern CWallet* pwalletMain; void Shutdown(void* parg); -bool AppInit(int argc, char* argv[]); bool AppInit2(); std::string HelpMessage(); diff --git a/src/keystore.cpp b/src/keystore.cpp index 313518711b..bdeae30135 100644 --- a/src/keystore.cpp +++ b/src/keystore.cpp @@ -73,6 +73,20 @@ bool CCryptoKeyStore::SetCrypted() return true; } +bool CCryptoKeyStore::Lock() +{ + if (!SetCrypted()) + return false; + + { + LOCK(cs_KeyStore); + vMasterKey.clear(); + } + + NotifyStatusChanged(this); + return true; +} + bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) { { @@ -99,6 +113,7 @@ bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn) } vMasterKey = vMasterKeyIn; } + NotifyStatusChanged(this); return true; } diff --git a/src/keystore.h b/src/keystore.h index 52889b184e..6b5d01cfa5 100644 --- a/src/keystore.h +++ b/src/keystore.h @@ -8,6 +8,7 @@ #include "crypter.h" #include "sync.h" #include "base58.h" +#include <boost/signals2/signal.hpp> class CScript; @@ -143,18 +144,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<unsigned char> &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret); bool AddKey(const CKey& key); @@ -185,6 +175,11 @@ public: mi++; } } + + /* Wallet status (encrypted, locked) changed. + * Note: Called without locks held. + */ + boost::signals2::signal<void (CCryptoKeyStore* wallet)> NotifyStatusChanged; }; #endif diff --git a/src/main.cpp b/src/main.cpp index 8ff6d6fec0..e62331debf 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(); + uiInterface.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(); + uiInterface.NotifyBlocksChanged(); return true; } @@ -1858,8 +1858,8 @@ bool CheckDiskSpace(uint64 nAdditionalBytes) string strMessage = _("Warning: Disk space is low"); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage, "Bitcoin", wxOK | wxICON_EXCLAMATION | wxMODAL); - QueueShutdown(); + uiInterface.ThreadSafeMessageBox(strMessage, "Bitcoin", CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + uiInterface.QueueShutdown(); return false; } return true; @@ -2176,6 +2176,18 @@ string GetWarnings(string strFor) return "error"; } +CAlert CAlert::getAlertByHash(const uint256 &hash) +{ + CAlert retval; + { + LOCK(cs_mapAlerts); + map<uint256, CAlert>::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); + uiInterface.NotifyAlertChanged((*mi).first, CT_DELETED); mapAlerts.erase(mi++); } else if (!alert.IsInEffect()) { printf("expiring alert %d\n", alert.nID); + uiInterface.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()) + uiInterface.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 9700909f8b..f4a9759d76 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 8d19387602..8eff9cbe78 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(); + uiInterface.NotifyNumConnectionsChanged(vNodes.size()); } diff --git a/src/noui.cpp b/src/noui.cpp index 08a08b439a..57f53b1c0d 100644 --- a/src/noui.cpp +++ b/src/noui.cpp @@ -3,42 +3,33 @@ // Distributed under the MIT/X11 software license, see the accompanying // file license.txt or http://www.opensource.org/licenses/mit-license.php. #include "ui_interface.h" +#include "init.h" +#include "bitcoinrpc.h" #include <string> -#include "init.h" -int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) +static int noui_ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { printf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); return 4; } -bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) +static bool noui_ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) { return true; } -void MainFrameRepaint() -{ -} - -void AddressBookRepaint() -{ -} - -void InitMessage(const std::string &message) -{ -} - -std::string _(const char* psz) -{ - return psz; -} - -void QueueShutdown() +static void noui_QueueShutdown() { // Without UI, Shutdown can simply be started in a new thread CreateThread(Shutdown, NULL); } +void noui_connect() +{ + // Connect bitcoind signal handlers + uiInterface.ThreadSafeMessageBox.connect(noui_ThreadSafeMessageBox); + uiInterface.ThreadSafeAskFee.connect(noui_ThreadSafeAskFee); + uiInterface.QueueShutdown.connect(noui_QueueShutdown); +} diff --git a/src/qt/addressbookpage.cpp b/src/qt/addressbookpage.cpp index d314e62b5a..c207987561 100644 --- a/src/qt/addressbookpage.cpp +++ b/src/qt/addressbookpage.cpp @@ -132,6 +132,10 @@ void AddressBookPage::setModel(AddressTableModel *model) connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged())); + // Select row for newly created address + connect(model, SIGNAL(rowsInserted(QModelIndex,int,int)), + this, SLOT(selectNewAddress(QModelIndex,int,int))); + if(mode == ForSending) { // Auto-select first row when in sending mode @@ -193,20 +197,11 @@ void AddressBookPage::on_newAddressButton_clicked() EditAddressDialog dlg( tab == SendingTab ? EditAddressDialog::NewSendingAddress : - EditAddressDialog::NewReceivingAddress); + EditAddressDialog::NewReceivingAddress, this); dlg.setModel(model); if(dlg.exec()) { - // Select row for newly created address - QString address = dlg.getAddress(); - QModelIndexList lst = proxyModel->match(proxyModel->index(0, - AddressTableModel::Address, QModelIndex()), - Qt::EditRole, address, 1, Qt::MatchExactly); - if(!lst.isEmpty()) - { - ui->tableView->setFocus(); - ui->tableView->selectRow(lst.at(0).row()); - } + newAddressToSelect = dlg.getAddress(); } } @@ -338,3 +333,15 @@ void AddressBookPage::contextualMenu(const QPoint &point) contextMenu->exec(QCursor::pos()); } } + +void AddressBookPage::selectNewAddress(const QModelIndex &parent, int begin, int end) +{ + QModelIndex idx = proxyModel->mapFromSource(model->index(begin, AddressTableModel::Address, parent)); + if(idx.isValid() && (idx.data(Qt::EditRole).toString() == newAddressToSelect)) + { + // Select row of newly created address, once + ui->tableView->setFocus(); + ui->tableView->selectRow(idx.row()); + newAddressToSelect.clear(); + } +} diff --git a/src/qt/addressbookpage.h b/src/qt/addressbookpage.h index b2cf2db979..b2e91c7cb2 100644 --- a/src/qt/addressbookpage.h +++ b/src/qt/addressbookpage.h @@ -13,6 +13,7 @@ class QTableView; class QItemSelection; class QSortFilterProxyModel; class QMenu; +class QModelIndex; QT_END_NAMESPACE /** Widget that shows a list of sending or receiving addresses. @@ -51,6 +52,7 @@ private: QSortFilterProxyModel *proxyModel; QMenu *contextMenu; QAction *deleteAction; + QString newAddressToSelect; private slots: void on_deleteButton_clicked(); @@ -67,6 +69,9 @@ private slots: void onCopyLabelAction(); /** Edit currently selected address entry */ void onEditAction(); + + /** New entry/entries were added to address table */ + void selectNewAddress(const QModelIndex &parent, int begin, int end); }; #endif // ADDRESSBOOKDIALOG_H diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 7b95f51c04..75ea2c12c5 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -26,20 +26,36 @@ struct AddressTableEntry type(type), label(label), address(address) {} }; +struct AddressTableEntryLessThan +{ + bool operator()(const AddressTableEntry &a, const AddressTableEntry &b) const + { + return a.address < b.address; + } + bool operator()(const AddressTableEntry &a, const QString &b) const + { + return a.address < b; + } + bool operator()(const QString &a, const AddressTableEntry &b) const + { + return a < b.address; + } +}; + // Private implementation class AddressTablePriv { public: CWallet *wallet; QList<AddressTableEntry> cachedAddressTable; + AddressTableModel *parent; - AddressTablePriv(CWallet *wallet): - wallet(wallet) {} + AddressTablePriv(CWallet *wallet, AddressTableModel *parent): + wallet(wallet), parent(parent) {} void refreshAddressTable() { cachedAddressTable.clear(); - { LOCK(wallet->cs_wallet); BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, std::string)& item, wallet->mapAddressBook) @@ -54,6 +70,53 @@ public: } } + void updateEntry(const QString &address, const QString &label, bool isMine, int status) + { + // Find address / label in model + QList<AddressTableEntry>::iterator lower = qLowerBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + QList<AddressTableEntry>::iterator upper = qUpperBound( + cachedAddressTable.begin(), cachedAddressTable.end(), address, AddressTableEntryLessThan()); + int lowerIndex = (lower - cachedAddressTable.begin()); + int upperIndex = (upper - cachedAddressTable.begin()); + bool inModel = (lower != upper); + AddressTableEntry::Type newEntryType = isMine ? AddressTableEntry::Receiving : AddressTableEntry::Sending; + + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_NOW, but entry is already in model\n"); + break; + } + parent->beginInsertRows(QModelIndex(), lowerIndex, lowerIndex); + cachedAddressTable.insert(lowerIndex, AddressTableEntry(newEntryType, label, address)); + parent->endInsertRows(); + break; + case CT_UPDATED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_UPDATED, but entry is not in model\n"); + break; + } + lower->type = newEntryType; + lower->label = label; + parent->emitDataChanged(lowerIndex); + break; + case CT_DELETED: + if(!inModel) + { + OutputDebugStringF("Warning: AddressTablePriv::updateEntry: Got CT_DELETED, but entry is not in model\n"); + break; + } + parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); + cachedAddressTable.erase(lower, upper); + parent->endRemoveRows(); + break; + } + } + int size() { return cachedAddressTable.size(); @@ -76,7 +139,7 @@ AddressTableModel::AddressTableModel(CWallet *wallet, WalletModel *parent) : QAbstractTableModel(parent),walletModel(parent),wallet(wallet),priv(0) { columns << tr("Label") << tr("Address"); - priv = new AddressTablePriv(wallet); + priv = new AddressTablePriv(wallet, this); priv->refreshAddressTable(); } @@ -158,7 +221,6 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu { case Label: wallet->SetAddressBookName(rec->address.toStdString(), value.toString().toStdString()); - rec->label = value.toString(); break; case Address: // Refuse to set invalid address, set error status and return false @@ -177,12 +239,9 @@ bool AddressTableModel::setData(const QModelIndex & index, const QVariant & valu // Add new entry with new address wallet->SetAddressBookName(value.toString().toStdString(), rec->label.toStdString()); } - - rec->address = value.toString(); } break; } - emit dataChanged(index, index); return true; } @@ -232,12 +291,10 @@ QModelIndex AddressTableModel::index(int row, int column, const QModelIndex & pa } } -void AddressTableModel::update() +void AddressTableModel::updateEntry(const QString &address, const QString &label, bool isMine, int status) { // Update address book model from Bitcoin core - beginResetModel(); - priv->refreshAddressTable(); - endResetModel(); + priv->updateEntry(address, label, isMine, status); } QString AddressTableModel::addRow(const QString &type, const QString &label, const QString &address) @@ -341,3 +398,7 @@ int AddressTableModel::lookupAddress(const QString &address) const } } +void AddressTableModel::emitDataChanged(int idx) +{ + emit dataChanged(index(idx, 0, QModelIndex()), index(idx, columns.length()-1, QModelIndex())); +} diff --git a/src/qt/addresstablemodel.h b/src/qt/addresstablemodel.h index 7fd07cfb81..42974e3e1f 100644 --- a/src/qt/addresstablemodel.h +++ b/src/qt/addresstablemodel.h @@ -74,13 +74,18 @@ private: QStringList columns; EditStatus editStatus; + /** Notify listeners that data changed. */ + void emitDataChanged(int index); + 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, bool isMine, int status); + + friend class AddressTablePriv; }; #endif // ADDRESSTABLEMODEL_H diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 6a0a32fbd9..07bdee0886 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -36,15 +36,13 @@ Q_IMPORT_PLUGIN(qtaccessiblewidgets) // Need a global reference for the notifications to find the GUI static BitcoinGUI *guiref; static QSplashScreen *splashref; -static WalletModel *walletmodel; -static ClientModel *clientmodel; -int ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) +static void ThreadSafeMessageBox(const std::string& message, const std::string& caption, int style) { // Message from network thread if(guiref) { - bool modal = (style & wxMODAL); + bool modal = (style & CClientUIInterface::MODAL); // in case of modal message, use blocking connection to wait for user to click OK QMetaObject::invokeMethod(guiref, "error", modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, @@ -57,10 +55,9 @@ int ThreadSafeMessageBox(const std::string& message, const std::string& caption, printf("%s: %s\n", caption.c_str(), message.c_str()); fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); } - return 4; } -bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) +static bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) { if(!guiref) return false; @@ -75,7 +72,7 @@ bool ThreadSafeAskFee(int64 nFeeRequired, const std::string& strCaption) return payFee; } -void ThreadSafeHandleURI(const std::string& strURI) +static void ThreadSafeHandleURI(const std::string& strURI) { if(!guiref) return; @@ -84,21 +81,7 @@ 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) +static void InitMessage(const std::string &message) { if(splashref) { @@ -107,7 +90,7 @@ void InitMessage(const std::string &message) } } -void QueueShutdown() +static void QueueShutdown() { QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); } @@ -115,7 +98,7 @@ void QueueShutdown() /* Translate string to current locale using Qt. */ -std::string _(const char* psz) +static std::string Translate(const char* psz) { return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } @@ -266,6 +249,14 @@ int main(int argc, char *argv[]) if (translator.load(lang_territory, ":/translations/")) app.installTranslator(&translator); + // Subscribe to global signals from core + uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); + uiInterface.ThreadSafeAskFee.connect(ThreadSafeAskFee); + uiInterface.ThreadSafeHandleURI.connect(ThreadSafeHandleURI); + uiInterface.InitMessage.connect(InitMessage); + uiInterface.QueueShutdown.connect(QueueShutdown); + uiInterface.Translate.connect(Translate); + // Show help message immediately after parsing command-line options (for "-lang") and setting locale, // but before showing splash screen. if (mapArgs.count("-?") || mapArgs.count("--help")) @@ -307,9 +298,7 @@ int main(int argc, char *argv[]) splash.finish(&window); ClientModel clientModel(&optionsModel); - clientmodel = &clientModel; WalletModel walletModel(pwalletMain, &optionsModel); - walletmodel = &walletModel; window.setClientModel(&clientModel); window.setWalletModel(&walletModel); @@ -351,8 +340,6 @@ int main(int argc, char *argv[]) window.setClientModel(0); window.setWalletModel(0); guiref = 0; - clientmodel = 0; - walletmodel = 0; } Shutdown(NULL); } diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 7070e59444..9deaa4b6d3 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 88e6d064d7..8a7f6e541b 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 85ab03612d..64fd2a9450 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -5,15 +5,30 @@ #include "transactiontablemodel.h" #include "main.h" -static const int64 nClientStartupTime = GetTime(); +#include "ui_interface.h" #include <QDateTime> +#include <QTimer> + +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())); + + subscribeToCoreSignals(); +} + +ClientModel::~ClientModel() +{ + unsubscribeFromCoreSignals(); } int ClientModel::getNumConnections() const @@ -37,27 +52,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 @@ -104,3 +134,41 @@ QDateTime ClientModel::formatClientStartupTime() const { return QDateTime::fromTime_t(nClientStartupTime); } + +// Handlers for core signals +static void NotifyBlocksChanged(ClientModel *clientmodel) +{ + // This notification is too frequent. Don't trigger a signal. + // Don't remove it, though, as it might be useful later. +} + +static void NotifyNumConnectionsChanged(ClientModel *clientmodel, int newNumConnections) +{ + // Too noisy: OutputDebugStringF("NotifyNumConnectionsChanged %i\n", newNumConnections); + QMetaObject::invokeMethod(clientmodel, "updateNumConnections", Qt::QueuedConnection, + Q_ARG(int, newNumConnections)); +} + +static void NotifyAlertChanged(ClientModel *clientmodel, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyAlertChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(clientmodel, "updateAlert", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void ClientModel::subscribeToCoreSignals() +{ + // Connect signals to client + uiInterface.NotifyBlocksChanged.connect(boost::bind(NotifyBlocksChanged, this)); + uiInterface.NotifyNumConnectionsChanged.connect(boost::bind(NotifyNumConnectionsChanged, this, _1)); + uiInterface.NotifyAlertChanged.connect(boost::bind(NotifyAlertChanged, this, _1, _2)); +} + +void ClientModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client + uiInterface.NotifyBlocksChanged.disconnect(boost::bind(NotifyBlocksChanged, this)); + uiInterface.NotifyNumConnectionsChanged.disconnect(boost::bind(NotifyNumConnectionsChanged, this, _1)); + uiInterface.NotifyAlertChanged.disconnect(boost::bind(NotifyAlertChanged, this, _1, _2)); +} diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 67835db727..0349c389c5 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. */ @@ -18,6 +19,7 @@ class ClientModel : public QObject Q_OBJECT public: explicit ClientModel(OptionsModel *optionsModel, QObject *parent = 0); + ~ClientModel(); OptionsModel *getOptionsModel(); @@ -44,23 +46,26 @@ public: private: OptionsModel *optionsModel; - int cachedNumConnections; int cachedNumBlocks; - QString cachedStatusBar; + int cachedNumBlocksOfPeers; int numBlocksAtStartup; + QTimer *pollTimer; + + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); 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/qtipcserver.cpp b/src/qt/qtipcserver.cpp index 06ada5aaca..3d7d90e902 100644 --- a/src/qt/qtipcserver.cpp +++ b/src/qt/qtipcserver.cpp @@ -31,7 +31,7 @@ void ipcThread(void* parg) ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(100); if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d)) { - ThreadSafeHandleURI(std::string(strBuf, nSize)); + uiInterface.ThreadSafeHandleURI(std::string(strBuf, nSize)); Sleep(1000); } if (fShutdown) @@ -69,7 +69,7 @@ void ipcInit() ptime d = boost::posix_time::microsec_clock::universal_time() + millisec(1); if(mq->timed_receive(&strBuf, sizeof(strBuf), nSize, nPriority, d)) { - ThreadSafeHandleURI(std::string(strBuf, nSize)); + uiInterface.ThreadSafeHandleURI(std::string(strBuf, nSize)); } else break; diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 0c77b05c56..7029ee33bc 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 27f41817ea..4b71cdb988 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 53cfb409ff..017244ffd0 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -40,114 +40,111 @@ QList<TransactionRecord> TransactionRecord::decomposeTransaction(const CWallet * uint256 hash = wtx.GetHash(); std::map<std::string, std::string> mapValue = wtx.mapValue; - if (showTransaction(wtx)) + if (nNet > 0 || wtx.IsCoinBase()) { - if (nNet > 0 || wtx.IsCoinBase()) + // + // Credit + // + BOOST_FOREACH(const CTxOut& txout, wtx.vout) { - // - // Credit - // - BOOST_FOREACH(const CTxOut& txout, wtx.vout) + if(wallet->IsMine(txout)) { - if(wallet->IsMine(txout)) + TransactionRecord sub(hash, nTime); + CBitcoinAddress address; + sub.idx = parts.size(); // sequence number + sub.credit = txout.nValue; + if (wtx.IsCoinBase()) { - 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); + // 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 + } + 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) { - bool fAllFromMe = true; - BOOST_FOREACH(const CTxIn& txin, wtx.vin) - fAllFromMe = fAllFromMe && wallet->IsMine(txin); + // Payment to self + int64 nChange = wtx.GetChange(); - bool fAllToMe = true; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - fAllToMe = fAllToMe && wallet->IsMine(txout); + parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", + -(nDebit - nChange), nCredit - nChange)); + } + else if (fAllFromMe) + { + // + // Debit + // + int64 nTxFee = nDebit - wtx.GetValueOut(); - if (fAllFromMe && fAllToMe) + for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) { - // Payment to self - int64 nChange = wtx.GetChange(); + const CTxOut& txout = wtx.vout[nOut]; + TransactionRecord sub(hash, nTime); + sub.idx = parts.size(); - parts.append(TransactionRecord(hash, nTime, TransactionRecord::SendToSelf, "", - -(nDebit - nChange), nCredit - nChange)); - } - else if (fAllFromMe) - { - // - // Debit - // - int64 nTxFee = nDebit - wtx.GetValueOut(); + 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; + } - for (unsigned int nOut = 0; nOut < wtx.vout.size(); nOut++) + CBitcoinAddress address; + if (ExtractAddress(txout.scriptPubKey, address)) { - 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); + // 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)); - } + } + else + { + // + // 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 5f505f444e..d36bb495a0 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 <QLocale> #include <QList> @@ -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<uint256, CWalletTx>::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<uint256> &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<uint256> 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<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); + bool inWallet = mi != wallet->mapWallet.end(); + + // 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) { - const uint256 &hash = updated_sorted.at(update_idx); - // Find transaction in wallet - std::map<uint256, CWalletTx>::iterator mi = wallet->mapWallet.find(hash); - bool inWallet = mi != wallet->mapWallet.end(); - // 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()); - - // Determine if transaction is in model already - bool inModel = false; - if(lower != upper) - { - inModel = true; - } + 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 */ + } -#ifdef WALLET_UPDATE_DEBUG - qDebug() << " " << QString::fromStdString(hash.ToString()) << inWallet << " " << inModel - << lowerIndex << "-" << upperIndex; -#endif + OutputDebugStringF(" inWallet=%i inModel=%i Index=%i-%i showTransaction=%i derivedStatus=%i\n", + inWallet, inModel, lowerIndex, upperIndex, showTransaction, status); - if(inWallet && !inModel) + switch(status) + { + case CT_NEW: + if(inModel) + { + OutputDebugStringF("Warning: updateWallet: Got CT_NEW, but transaction is already in model\n"); + break; + } + 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<TransactionRecord> toInsert = @@ -141,17 +147,22 @@ public: parent->endInsertRows(); } } - else if(!inWallet && inModel) - { - // Removed -- remove entire transaction from table - parent->beginRemoveRows(QModelIndex(), lowerIndex, upperIndex-1); - cachedWallet.erase(lower, upper); - parent->endRemoveRows(); - } - else if(inWallet && inModel) + break; + case CT_DELETED: + if(!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<uint256> updated; + uint256 updated; + updated.SetHex(hash.toStdString()); - // Check if there are changes to wallet map - { - TRY_LOCK(wallet->cs_wallet, lockWallet); - if (lockWallet && !wallet->vWalletUpdated.empty()) - { - BOOST_FOREACH(uint256 hash, wallet->vWalletUpdated) - { - updated.append(hash); - } - wallet->vWalletUpdated.clear(); - } - } + priv->updateWallet(updated, status); +} - if(!updated.empty()) +void TransactionTableModel::updateConfirmations() +{ + if(nBestHeight != cachedNumBlocks) { - 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 db88a0604f..0aafa70915 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 b9ccb06c09..b89c3dba33 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -18,6 +18,13 @@ WalletModel::WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *p { addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(wallet, this); + + subscribeToCoreSignals(); +} + +WalletModel::~WalletModel() +{ + unsubscribeFromCoreSignals(); } qint64 WalletModel::getBalance() const @@ -40,30 +47,38 @@ int WalletModel::getNumTransactions() const return numTransactions; } -void WalletModel::update() +void WalletModel::updateStatus() +{ + EncryptionStatus newEncryptionStatus = getEncryptionStatus(); + + 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(); - EncryptionStatus newEncryptionStatus = getEncryptionStatus(); if(cachedBalance != newBalance || cachedUnconfirmedBalance != newUnconfirmedBalance) emit balanceChanged(newBalance, newUnconfirmedBalance); - if(cachedNumTransactions != newNumTransactions) emit numTransactionsChanged(newNumTransactions); - if(cachedEncryptionStatus != newEncryptionStatus) - emit encryptionStatusChanged(newEncryptionStatus); - cachedBalance = newBalance; cachedUnconfirmedBalance = newUnconfirmedBalance; cachedNumTransactions = newNumTransactions; } -void WalletModel::updateAddressList() +void WalletModel::updateAddressBook(const QString &address, const QString &label, bool isMine, int status) { - addressTableModel->update(); + if(addressTableModel) + addressTableModel->updateEntry(address, label, isMine, status); } bool WalletModel::validateAddress(const QString &address) @@ -139,7 +154,7 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie } return TransactionCreationFailed; } - if(!ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString())) + if(!uiInterface.ThreadSafeAskFee(nFeeRequired, tr("Sending...").toStdString())) { return Aborted; } @@ -246,6 +261,47 @@ bool WalletModel::backupWallet(const QString &filename) return BackupWallet(*wallet, filename.toLocal8Bit().data()); } +// Handlers for core signals +static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel, CCryptoKeyStore *wallet) +{ + OutputDebugStringF("NotifyKeyStoreStatusChanged\n"); + QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection); +} + +static void NotifyAddressBookChanged(WalletModel *walletmodel, CWallet *wallet, const std::string &address, const std::string &label, bool isMine, ChangeType status) +{ + OutputDebugStringF("NotifyAddressBookChanged %s %s isMine=%i status=%i\n", address.c_str(), label.c_str(), isMine, status); + QMetaObject::invokeMethod(walletmodel, "updateAddressBook", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(address)), + Q_ARG(QString, QString::fromStdString(label)), + Q_ARG(bool, isMine), + Q_ARG(int, status)); +} + +static void NotifyTransactionChanged(WalletModel *walletmodel, CWallet *wallet, const uint256 &hash, ChangeType status) +{ + OutputDebugStringF("NotifyTransactionChanged %s status=%i\n", hash.GetHex().c_str(), status); + QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(hash.GetHex())), + Q_ARG(int, status)); +} + +void WalletModel::subscribeToCoreSignals() +{ + // Connect signals to wallet + wallet->NotifyStatusChanged.connect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.connect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.connect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + +void WalletModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from wallet + wallet->NotifyStatusChanged.disconnect(boost::bind(&NotifyKeyStoreStatusChanged, this, _1)); + wallet->NotifyAddressBookChanged.disconnect(boost::bind(NotifyAddressBookChanged, this, _1, _2, _3, _4, _5)); + wallet->NotifyTransactionChanged.disconnect(boost::bind(NotifyTransactionChanged, this, _1, _2, _3)); +} + // WalletModel::UnlockContext implementation WalletModel::UnlockContext WalletModel::requestUnlock() { diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 6c47f61bef..8b615ffe8e 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -24,6 +24,7 @@ class WalletModel : public QObject Q_OBJECT public: explicit WalletModel(CWallet *wallet, OptionsModel *optionsModel, QObject *parent = 0); + ~WalletModel(); enum StatusCode // Returned by sendCoins { @@ -118,6 +119,8 @@ private: qint64 cachedNumTransactions; EncryptionStatus cachedEncryptionStatus; + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); signals: // Signal that balance in wallet changed void balanceChanged(qint64 balance, qint64 unconfirmedBalance); @@ -137,8 +140,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, bool isMine, int status); }; diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 1119aeaf75..9f5693a8bd 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/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 7ff7545ab4..bf597c9b73 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -5,11 +5,15 @@ #include "wallet.h" CWallet* pwalletMain; +CClientUIInterface uiInterface; extern bool fPrintToConsole; +extern void noui_connect(); + struct TestingSetup { TestingSetup() { fPrintToConsole = true; // don't want to write to debug.log file + noui_connect(); pwalletMain = new CWallet(); RegisterWallet(pwalletMain); } diff --git a/src/ui_interface.h b/src/ui_interface.h index 514768086d..711a56d5ec 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -6,45 +6,99 @@ #include <string> #include "util.h" // for int64 +#include <boost/signals2/signal.hpp> +#include <boost/signals2/last_value.hpp> -#define wxYES 0x00000002 -#define wxOK 0x00000004 -#define wxNO 0x00000008 -#define wxYES_NO (wxYES|wxNO) -#define wxCANCEL 0x00000010 -#define wxAPPLY 0x00000020 -#define wxCLOSE 0x00000040 -#define wxOK_DEFAULT 0x00000000 -#define wxYES_DEFAULT 0x00000000 -#define wxNO_DEFAULT 0x00000080 -#define wxCANCEL_DEFAULT 0x80000000 -#define wxICON_EXCLAMATION 0x00000100 -#define wxICON_HAND 0x00000200 -#define wxICON_WARNING wxICON_EXCLAMATION -#define wxICON_ERROR wxICON_HAND -#define wxICON_QUESTION 0x00000400 -#define wxICON_INFORMATION 0x00000800 -#define wxICON_STOP wxICON_HAND -#define wxICON_ASTERISK wxICON_INFORMATION -#define wxICON_MASK (0x00000100|0x00000200|0x00000400|0x00000800) -#define wxFORWARD 0x00001000 -#define wxBACKWARD 0x00002000 -#define wxRESET 0x00004000 -#define wxHELP 0x00008000 -#define wxMORE 0x00010000 -#define wxSETUP 0x00020000 -// Force blocking, modal message box dialog (not just notification) -#define wxMODAL 0x00040000 - -/* 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); +class CBasicKeyStore; +class CWallet; +class uint256; + +/** General change type (added, updated, removed). */ +enum ChangeType +{ + CT_NEW, + CT_UPDATED, + CT_DELETED +}; + +/** Signals for UI communication. */ +class CClientUIInterface +{ +public: + /** Flags for CClientUIInterface::ThreadSafeMessageBox */ + enum MessageBoxFlags + { + YES = 0x00000002, + OK = 0x00000004, + NO = 0x00000008, + YES_NO = (YES|NO), + CANCEL = 0x00000010, + APPLY = 0x00000020, + CLOSE = 0x00000040, + OK_DEFAULT = 0x00000000, + YES_DEFAULT = 0x00000000, + NO_DEFAULT = 0x00000080, + CANCEL_DEFAULT = 0x80000000, + ICON_EXCLAMATION = 0x00000100, + ICON_HAND = 0x00000200, + ICON_WARNING = ICON_EXCLAMATION, + ICON_ERROR = ICON_HAND, + ICON_QUESTION = 0x00000400, + ICON_INFORMATION = 0x00000800, + ICON_STOP = ICON_HAND, + ICON_ASTERISK = ICON_INFORMATION, + ICON_MASK = (0x00000100|0x00000200|0x00000400|0x00000800), + FORWARD = 0x00001000, + BACKWARD = 0x00002000, + RESET = 0x00004000, + HELP = 0x00008000, + MORE = 0x00010000, + SETUP = 0x00020000, + // Force blocking, modal message box dialog (not just OS notification) + MODAL = 0x00040000 + }; + + /** Show message box. */ + boost::signals2::signal<void (const std::string& message, const std::string& caption, int style)> ThreadSafeMessageBox; + + /** Ask the user whether he want to pay a fee or not. */ + boost::signals2::signal<bool (int64 nFeeRequired, const std::string& strCaption), boost::signals2::last_value<bool> > ThreadSafeAskFee; + + /** Handle an URL passed on the command line. */ + boost::signals2::signal<void (const std::string& strURI)> ThreadSafeHandleURI; + + /** Progress message during initialization. */ + boost::signals2::signal<void (const std::string &message)> InitMessage; + + /** Initiate client shutdown. */ + boost::signals2::signal<void ()> QueueShutdown; + + /** Translate a message to the native language of the user. */ + boost::signals2::signal<std::string (const char* psz)> Translate; + + /** Block chain changed. */ + boost::signals2::signal<void ()> NotifyBlocksChanged; + + /** Number of network connections changed. */ + boost::signals2::signal<void (int newNumConnections)> NotifyNumConnectionsChanged; + + /** + * New, updated or cancelled alert. + * @note called with lock cs_mapAlerts held. + */ + boost::signals2::signal<void (const uint256 &hash, ChangeType status)> NotifyAlertChanged; +}; + +extern CClientUIInterface uiInterface; + +/** + * Translation function: Call Translate signal on UI interface, which returns a boost::optional result. + * If no translation slot is registered, nothing is returned, and simply return the input. + */ +inline std::string _(const char* psz) +{ + boost::optional<std::string> rv = uiInterface.Translate(psz); + return rv ? (*rv) : psz; +} #endif diff --git a/src/util.cpp b/src/util.cpp index c778e51e3b..108f33b78e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1000,7 +1000,7 @@ void AddTimeData(const CNetAddr& ip, int64 nTime) string strMessage = _("Warning: Please check that your computer's date and time are correct. If your clock is wrong Bitcoin will not work properly."); strMiscWarning = strMessage; printf("*** %s\n", strMessage.c_str()); - ThreadSafeMessageBox(strMessage+" ", string("Bitcoin"), wxOK | wxICON_EXCLAMATION); + uiInterface.ThreadSafeMessageBox(strMessage+" ", string("Bitcoin"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION); } } } diff --git a/src/wallet.cpp b/src/wallet.cpp index 768f9f85ea..d67cdf4b71 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); + } + NotifyStatusChanged(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; } @@ -1231,13 +1229,12 @@ string CWallet::SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew, return strError; } - if (fAskFee && !ThreadSafeAskFee(nFeeRequired, _("Sending..."))) + if (fAskFee && !uiInterface.ThreadSafeAskFee(nFeeRequired, _("Sending..."))) return "ABORTED"; 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<CBitcoinAddress, std::string>::iterator mi = mapAddressBook.find(address); mapAddressBook[address] = strName; - AddressBookRepaint(); + NotifyAddressBookChanged(this, address.ToString(), strName, HaveKey(address), (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(), "", HaveKey(address), CT_DELETED); if (!fFileBacked) return false; return CWalletDB(strWalletFile).EraseName(address.ToString()); @@ -1558,3 +1556,14 @@ void CWallet::GetAllReserveAddresses(set<CBitcoinAddress>& setAddress) setAddress.insert(address); } } + +void CWallet::UpdatedTransaction(const uint256 &hashTx) +{ + { + LOCK(cs_wallet); + // Only notify UI if this transaction is in this wallet + map<uint256, CWalletTx>::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 44c11e2ec4..55deffdde2 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -9,6 +9,7 @@ #include "key.h" #include "keystore.h" #include "script.h" +#include "ui_interface.h" class CWalletTx; class CReserveKey; @@ -102,8 +103,6 @@ public: } std::map<uint256, CWalletTx> mapWallet; - std::vector<uint256> vWalletUpdated; - std::map<uint256, int> mapRequestCount; std::map<CBitcoinAddress, std::string> mapAddressBook; @@ -232,13 +231,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); @@ -269,6 +262,16 @@ public: // get the current wallet format (the oldest client version guaranteed to understand this wallet) int GetVersion() { return nWalletVersion; } + + /** Address book entry changed. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal<void (CWallet *wallet, const std::string &address, const std::string &label, bool isMine, ChangeType status)> NotifyAddressBookChanged; + + /** Wallet transaction added, removed or updated. + * @note called with lock cs_wallet held. + */ + boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx, ChangeType status)> NotifyTransactionChanged; }; /** A key allocated from the key pool. */ |