diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -157,6 +157,7 @@ qt/moc_transactiontablemodel.cpp \ qt/moc_transactionview.cpp \ qt/moc_utilitydialog.cpp \ + qt/moc_walletcontroller.cpp \ qt/moc_walletframe.cpp \ qt/moc_walletmodel.cpp \ qt/moc_walletview.cpp @@ -235,6 +236,7 @@ qt/transactiontablemodel.h \ qt/transactionview.h \ qt/utilitydialog.h \ + qt/walletcontroller.h \ qt/walletframe.h \ qt/walletmodel.h \ qt/walletmodeltransaction.h \ @@ -348,6 +350,7 @@ qt/transactionrecord.cpp \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ + qt/walletcontroller.cpp \ qt/walletframe.cpp \ qt/walletmodel.cpp \ qt/walletmodeltransaction.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -271,6 +271,7 @@ transactionrecord.cpp transactiontablemodel.cpp transactionview.cpp + walletcontroller.cpp walletframe.cpp walletmodel.cpp walletmodeltransaction.cpp diff --git a/src/qt/bitcoin.h b/src/qt/bitcoin.h --- a/src/qt/bitcoin.h +++ b/src/qt/bitcoin.h @@ -23,6 +23,7 @@ class PaymentServer; class PlatformStyle; class RPCServer; +class WalletController; class WalletModel; namespace interfaces { @@ -100,8 +101,6 @@ /// Handle runaway exceptions. Shows a message box with the problem and /// quits the program. void handleRunawayException(const QString &message); - void addWallet(WalletModel *walletModel); - void removeWallet(); Q_SIGNALS: void requestedInitialize(Config *config, RPCServer *rpcServer, @@ -119,9 +118,8 @@ BitcoinGUI *window; QTimer *pollShutdownTimer; #ifdef ENABLE_WALLET - PaymentServer *paymentServer; - std::vector m_wallet_models; - std::unique_ptr m_handler_load_wallet; + PaymentServer *paymentServer{nullptr}; + WalletController *m_wallet_controller{nullptr}; #endif int returnValue; const PlatformStyle *platformStyle; diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -34,7 +34,7 @@ #ifdef ENABLE_WALLET #include -#include +#include #endif #include @@ -189,11 +189,8 @@ BitcoinApplication::BitcoinApplication(interfaces::Node &node, int &argc, char **argv) : QApplication(argc, argv), coreThread(0), m_node(node), optionsModel(0), - clientModel(0), window(0), pollShutdownTimer(0), -#ifdef ENABLE_WALLET - paymentServer(0), m_wallet_models(), -#endif - returnValue(0), platformStyle(0) { + clientModel(0), window(0), pollShutdownTimer(0), returnValue(0), + platformStyle(0) { setQuitOnLastWindowClosed(false); } @@ -342,11 +339,8 @@ pollShutdownTimer->stop(); #ifdef ENABLE_WALLET - window->removeAllWallets(); - for (const WalletModel *walletModel : m_wallet_models) { - delete walletModel; - } - m_wallet_models.clear(); + delete m_wallet_controller; + m_wallet_controller = nullptr; #endif delete clientModel; clientModel = 0; @@ -357,35 +351,6 @@ Q_EMIT requestedShutdown(); } -void BitcoinApplication::addWallet(WalletModel *walletModel) { -#ifdef ENABLE_WALLET - window->addWallet(walletModel); - - if (m_wallet_models.empty()) { - window->setCurrentWallet(walletModel); - } - -#ifdef ENABLE_BIP70 - connect(walletModel, &WalletModel::coinsSent, paymentServer, - &PaymentServer::fetchPaymentACK); -#endif - connect(walletModel, &WalletModel::unload, this, - &BitcoinApplication::removeWallet); - - m_wallet_models.push_back(walletModel); -#endif -} - -void BitcoinApplication::removeWallet() { -#ifdef ENABLE_WALLET - WalletModel *walletModel = static_cast(sender()); - m_wallet_models.erase( - std::find(m_wallet_models.begin(), m_wallet_models.end(), walletModel)); - window->removeWallet(walletModel); - walletModel->deleteLater(); -#endif -} - void BitcoinApplication::initializeResult(bool success) { qDebug() << __func__ << ": Initialization result: " << success; returnValue = success ? EXIT_SUCCESS : EXIT_FAILURE; @@ -400,33 +365,24 @@ // guaranteed complete. qWarning() << "Platform customization:" << platformStyle->getName(); #ifdef ENABLE_WALLET + m_wallet_controller = + new WalletController(m_node, platformStyle, optionsModel, this); #ifdef ENABLE_BIP70 PaymentServer::LoadRootCAs(); #endif if (paymentServer) { paymentServer->setOptionsModel(optionsModel); +#ifdef ENABLE_BIP70 + connect(m_wallet_controller, &WalletController::coinsSent, + paymentServer, &PaymentServer::fetchPaymentACK); +#endif } #endif clientModel = new ClientModel(m_node, optionsModel); window->setClientModel(clientModel); - #ifdef ENABLE_WALLET - m_handler_load_wallet = m_node.handleLoadWallet( - [this](std::unique_ptr wallet) { - WalletModel *wallet_model = - new WalletModel(std::move(wallet), m_node, platformStyle, - optionsModel, nullptr); - // Fix wallet model thread affinity. - wallet_model->moveToThread(thread()); - QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, - Q_ARG(WalletModel *, wallet_model)); - }); - - for (auto &wallet : m_node.getWallets()) { - addWallet(new WalletModel(std::move(wallet), m_node, platformStyle, - optionsModel)); - } + window->setWalletController(m_wallet_controller); #endif // If -min option passed, start window minimized. @@ -587,9 +543,6 @@ // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType("Amount"); qRegisterMetaType>("std::function"); -#ifdef ENABLE_WALLET - qRegisterMetaType("WalletModel*"); -#endif // Need to register any types Qt doesn't know about if you intend // to use them with the signal/slot mechanism Qt provides. Even pointers. diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -30,6 +30,7 @@ class RPCConsole; class SendCoinsRecipient; class UnitDisplayStatusBarControl; +class WalletController; class WalletFrame; class WalletModel; class HelpMessageDialog; @@ -76,6 +77,9 @@ * the P2P network, and is wallet-agnostic. */ void setClientModel(ClientModel *clientModel); +#ifdef ENABLE_WALLET + void setWalletController(WalletController *wallet_controller); +#endif #ifdef ENABLE_WALLET /** @@ -99,6 +103,7 @@ private: interfaces::Node &m_node; + WalletController *m_wallet_controller{nullptr}; std::unique_ptr m_handler_message_box; std::unique_ptr m_handler_question; ClientModel *clientModel = nullptr; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -30,6 +30,7 @@ #include #include #ifdef ENABLE_WALLET +#include #include #include #include @@ -604,19 +605,35 @@ } #ifdef ENABLE_WALLET +void BitcoinGUI::setWalletController(WalletController *wallet_controller) { + assert(!m_wallet_controller); + assert(wallet_controller); + + m_wallet_controller = wallet_controller; + + connect(wallet_controller, &WalletController::walletAdded, this, + &BitcoinGUI::addWallet); + connect(wallet_controller, &WalletController::walletRemoved, this, + &BitcoinGUI::removeWallet); + + for (WalletModel *wallet_model : m_wallet_controller->getWallets()) { + addWallet(wallet_model); + } +} + void BitcoinGUI::addWallet(WalletModel *walletModel) { if (!walletFrame) { return; } const QString display_name = walletModel->getDisplayName(); setWalletActionsEnabled(true); + rpcConsole->addWallet(walletModel); + walletFrame->addWallet(walletModel); m_wallet_selector->addItem(display_name, QVariant::fromValue(walletModel)); if (m_wallet_selector->count() == 2) { m_wallet_selector_label_action->setVisible(true); m_wallet_selector_action->setVisible(true); } - rpcConsole->addWallet(walletModel); - walletFrame->addWallet(walletModel); } void BitcoinGUI::removeWallet(WalletModel *walletModel) { @@ -641,13 +658,22 @@ return; } walletFrame->setCurrentWallet(wallet_model); + for (int index = 0; index < m_wallet_selector->count(); ++index) { + if (m_wallet_selector->itemData(index).value() == + wallet_model) { + m_wallet_selector->setCurrentIndex(index); + break; + } + } updateWindowTitle(); } void BitcoinGUI::setCurrentWalletBySelectorIndex(int index) { WalletModel *wallet_model = m_wallet_selector->itemData(index).value(); - setCurrentWallet(wallet_model); + if (wallet_model) { + setCurrentWallet(wallet_model); + } } void BitcoinGUI::removeAllWallets() { diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h new file mode 100644 --- /dev/null +++ b/src/qt/walletcontroller.h @@ -0,0 +1,61 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_WALLETCONTROLLER_H +#define BITCOIN_QT_WALLETCONTROLLER_H + +#include +#include + +#include + +#include +#include +#include + +class OptionsModel; +class PlatformStyle; + +namespace interfaces { +class Handler; +class Node; +} // namespace interfaces + +/** + * Controller between interfaces::Node, WalletModel instances and the GUI. + */ +class WalletController : public QObject { + Q_OBJECT + + WalletModel *getOrCreateWallet(std::unique_ptr wallet); + void removeAndDeleteWallet(WalletModel *wallet_model); + +public: + WalletController(interfaces::Node &node, + const PlatformStyle *platform_style, + OptionsModel *options_model, QObject *parent); + ~WalletController(); + + std::vector getWallets() const; + +private Q_SLOTS: + void addWallet(WalletModel *wallet_model); + +Q_SIGNALS: + void walletAdded(WalletModel *wallet_model); + void walletRemoved(WalletModel *wallet_model); + + void coinsSent(WalletModel *wallet_model, SendCoinsRecipient recipient, + QByteArray transaction); + +private: + interfaces::Node &m_node; + const PlatformStyle *const m_platform_style; + OptionsModel *const m_options_model; + mutable QMutex m_mutex; + std::vector m_wallets; + std::unique_ptr m_handler_load_wallet; +}; + +#endif // BITCOIN_QT_WALLETCONTROLLER_H diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp new file mode 100644 --- /dev/null +++ b/src/qt/walletcontroller.cpp @@ -0,0 +1,96 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include + +#include +#include + +#include + +WalletController::WalletController(interfaces::Node &node, + const PlatformStyle *platform_style, + OptionsModel *options_model, QObject *parent) + : QObject(parent), m_node(node), m_platform_style(platform_style), + m_options_model(options_model) { + m_handler_load_wallet = m_node.handleLoadWallet( + [this](std::unique_ptr wallet) { + getOrCreateWallet(std::move(wallet)); + }); + + for (std::unique_ptr &wallet : m_node.getWallets()) { + getOrCreateWallet(std::move(wallet)); + } +} + +// Not using the default destructor because not all member types definitions are +// available in the header, just forward declared. +WalletController::~WalletController() {} + +std::vector WalletController::getWallets() const { + QMutexLocker locker(&m_mutex); + return m_wallets; +} + +WalletModel *WalletController::getOrCreateWallet( + std::unique_ptr wallet) { + QMutexLocker locker(&m_mutex); + + // Return model instance if exists. + if (!m_wallets.empty()) { + std::string name = wallet->getWalletName(); + for (WalletModel *wallet_model : m_wallets) { + if (wallet_model->wallet().getWalletName() == name) { + return wallet_model; + } + } + } + + // Instantiate model and register it. + WalletModel *wallet_model = new WalletModel( + std::move(wallet), m_node, m_platform_style, m_options_model, nullptr); + m_wallets.push_back(wallet_model); + + connect(wallet_model, &WalletModel::unload, + [this, wallet_model] { removeAndDeleteWallet(wallet_model); }); + + // Re-emit coinsSent signal from wallet model. + connect(wallet_model, &WalletModel::coinsSent, this, + &WalletController::coinsSent); + + // Notify walletAdded signal on the GUI thread. + if (QThread::currentThread() == thread()) { + addWallet(wallet_model); + } else { + // Handler callback runs in a different thread so fix wallet model + // thread affinity. + wallet_model->moveToThread(thread()); + QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, + Q_ARG(WalletModel *, wallet_model)); + } + + return wallet_model; +} + +void WalletController::addWallet(WalletModel *wallet_model) { + // Take ownership of the wallet model and register it. + wallet_model->setParent(this); + Q_EMIT walletAdded(wallet_model); +} + +void WalletController::removeAndDeleteWallet(WalletModel *wallet_model) { + // Unregister wallet model. + { + QMutexLocker locker(&m_mutex); + m_wallets.erase( + std::remove(m_wallets.begin(), m_wallets.end(), wallet_model)); + } + Q_EMIT walletRemoved(wallet_model); + // Currently this can trigger the unload since the model can hold the last + // CWallet shared pointer. + delete wallet_model; +} diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -356,7 +356,7 @@ // Handlers for core signals static void NotifyUnload(WalletModel *walletModel) { qDebug() << "NotifyUnload"; - QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection); + QMetaObject::invokeMethod(walletModel, "unload"); } static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel) {