diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 967f601b39bf..260df471c178 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -67,6 +67,7 @@ QT_MOC_CPP = \ qt/moc_coincontroldialog.cpp \ qt/moc_coincontroltreewidget.cpp \ qt/moc_csvmodelwriter.cpp \ + qt/moc_dash.cpp \ qt/moc_editaddressdialog.cpp \ qt/moc_guiutil.cpp \ qt/moc_intro.cpp \ @@ -99,6 +100,7 @@ QT_MOC_CPP = \ 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 @@ -109,7 +111,6 @@ BITCOIN_MM = \ qt/macos_appnap.mm QT_MOC = \ - qt/dash.moc \ qt/bitcoinamountfield.moc \ qt/callback.moc \ qt/intro.moc \ @@ -146,6 +147,7 @@ BITCOIN_QT_H = \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ qt/csvmodelwriter.h \ + qt/dash.h \ qt/editaddressdialog.h \ qt/guiconstants.h \ qt/guiutil.h \ @@ -184,6 +186,7 @@ BITCOIN_QT_H = \ qt/transactiontablemodel.h \ qt/transactionview.h \ qt/utilitydialog.h \ + qt/walletcontroller.h \ qt/walletframe.h \ qt/walletmodel.h \ qt/walletmodeltransaction.h \ @@ -225,6 +228,7 @@ RES_ICONS = \ BITCOIN_QT_BASE_CPP = \ qt/appearancewidget.cpp \ qt/bantablemodel.cpp \ + qt/dash.cpp \ qt/bitcoinaddressvalidator.cpp \ qt/bitcoinamountfield.cpp \ qt/bitcoingui.cpp \ @@ -273,6 +277,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/transactionrecord.cpp \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ + qt/walletcontroller.cpp \ qt/walletframe.cpp \ qt/walletmodel.cpp \ qt/walletmodeltransaction.cpp \ @@ -382,6 +387,9 @@ qt_libdashqt_a_OBJCXXFLAGS = $(AM_OBJCXXFLAGS) $(QT_PIE_FLAGS) qt_libdashqt_a_SOURCES = $(BITCOIN_QT_CPP) $(BITCOIN_QT_H) $(QT_FORMS_UI) \ $(QT_QRC) $(QT_QRC_LOCALE) $(QT_TS) $(PROTOBUF_PROTO) $(RES_ICONS) $(RES_IMAGES) $(RES_CSS) $(RES_FONTS) $(RES_MOVIES) +if TARGET_DARWIN + qt_libdashqt_a_SOURCES += $(BITCOIN_MM) +endif nodist_qt_libdashqt_a_SOURCES = $(QT_MOC_CPP) $(QT_MOC) $(PROTOBUF_CC) \ $(PROTOBUF_H) $(QT_QRC_CPP) $(QT_QRC_LOCALE_CPP) @@ -404,10 +412,7 @@ qt_dash_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_INCLUDES) $(QT_INCLUDES) $(PROTOBUF_CFLAGS) $(QR_CFLAGS) qt_dash_qt_CXXFLAGS = $(AM_CXXFLAGS) $(QT_PIE_FLAGS) -qt_dash_qt_SOURCES = qt/dash.cpp -if TARGET_DARWIN - qt_dash_qt_SOURCES += $(BITCOIN_MM) -endif +qt_dash_qt_SOURCES = qt/main.cpp if TARGET_WINDOWS qt_dash_qt_SOURCES += $(BITCOIN_RC) endif diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index 61a2bf8be7aa..302df388eac8 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -7,6 +7,7 @@ bin_PROGRAMS += qt/test/test_dash-qt TESTS += qt/test/test_dash-qt TEST_QT_MOC_CPP = \ + qt/test/moc_apptests.cpp \ qt/test/moc_compattests.cpp \ qt/test/moc_rpcnestedtests.cpp \ qt/test/moc_trafficgraphdatatests.cpp \ @@ -24,6 +25,7 @@ endif # ENABLE_WALLET TEST_QT_H = \ qt/test/addressbooktests.h \ + qt/test/apptests.h \ qt/test/compattests.h \ qt/test/rpcnestedtests.h \ qt/test/uritests.h \ @@ -42,6 +44,7 @@ qt_test_test_dash_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_ $(QT_INCLUDES) $(QT_TEST_INCLUDES) $(PROTOBUF_CFLAGS) qt_test_test_dash_qt_SOURCES = \ + qt/test/apptests.cpp \ qt/test/compattests.cpp \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index aa4b4bbf30c6..aa584b18ec10 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -19,6 +19,7 @@ #include #ifdef ENABLE_WALLET +#include #include #include #include @@ -106,6 +107,7 @@ BitcoinGUI::BitcoinGUI(interfaces::Node& node, const NetworkStyle* networkStyle, * the central widget is the rpc console. */ setCentralWidget(rpcConsole); + Q_EMIT consoleShown(rpcConsole); } // Accept D&D of URIs @@ -407,6 +409,7 @@ void BitcoinGUI::createActions() // initially disable the debug window menu items openInfoAction->setEnabled(false); openRPCConsoleAction->setEnabled(false); + openRPCConsoleAction->setObjectName("openRPCConsoleAction"); openGraphAction->setEnabled(false); openPeersAction->setEnabled(false); openRepairAction->setEnabled(false); @@ -783,17 +786,32 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) } #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_action->setVisible(true); } - rpcConsole->addWallet(walletModel); - walletFrame->addWallet(walletModel); } void BitcoinGUI::removeWallet(WalletModel* walletModel) @@ -815,13 +833,19 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model) { if (!walletFrame) 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() @@ -869,9 +893,11 @@ void BitcoinGUI::createTrayIcon() { assert(QSystemTrayIcon::isSystemTrayAvailable()); - trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); - QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + m_network_style->getTitleAddText(); - trayIcon->setToolTip(toolTip); + if (QSystemTrayIcon::isSystemTrayAvailable()) { + trayIcon = new QSystemTrayIcon(m_network_style->getTrayAndWindowIcon(), this); + QString toolTip = tr("%1 client").arg(tr(PACKAGE_NAME)) + " " + m_network_style->getTitleAddText(); + trayIcon->setToolTip(toolTip); + } } void BitcoinGUI::createIconMenu(QMenu *pmenu) @@ -951,6 +977,7 @@ void BitcoinGUI::aboutClicked() void BitcoinGUI::showDebugWindow() { GUIUtil::bringToFront(rpcConsole); + Q_EMIT consoleShown(rpcConsole); } void BitcoinGUI::showInfo() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 7fd749ad7f74..f741a7f43a78 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -32,6 +32,7 @@ class OptionsModel; class RPCConsole; class SendCoinsRecipient; class UnitDisplayStatusBarControl; +class WalletController; class WalletFrame; class WalletModel; class HelpMessageDialog; @@ -74,6 +75,9 @@ class BitcoinGUI : public QMainWindow The client model represents the part of the core that communicates with the P2P network, and is wallet-agnostic. */ void setClientModel(ClientModel *clientModel); +#ifdef ENABLE_WALLET + void setWalletController(WalletController* wallet_controller); +#endif #ifdef ENABLE_WALLET /** Set the wallet model. @@ -101,6 +105,7 @@ class BitcoinGUI : public QMainWindow 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; @@ -228,6 +233,8 @@ class BitcoinGUI : public QMainWindow Q_SIGNALS: /** Signal raised when a URI was entered or dragged to the GUI */ void receivedURI(const QString &uri); + /** Signal raised when RPC console shown */ + void consoleShown(RPCConsole* console); /** Restart handling */ void requestedRestart(QStringList args); diff --git a/src/qt/dash.cpp b/src/qt/dash.cpp index 9e0082cc43e1..79c426c43c48 100644 --- a/src/qt/dash.cpp +++ b/src/qt/dash.cpp @@ -7,6 +7,7 @@ #include #endif +#include #include #include @@ -24,7 +25,7 @@ #ifdef ENABLE_WALLET #include -#include +#include #endif #include @@ -71,11 +72,6 @@ Q_DECLARE_METATYPE(bool*) Q_DECLARE_METATYPE(CAmount) Q_DECLARE_METATYPE(uint256) -/** Translate string to current locale using Qt. */ -const std::function G_TRANSLATION_FUN = [](const char* psz) { - return QCoreApplication::translate("dash-core", psz).toStdString(); -}; - static QString GetLangTerritory() { QSettings settings; @@ -140,99 +136,6 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } } -/** Class encapsulating Dash Core startup and shutdown. - * Allows running startup and shutdown in a different thread from the UI thread. - */ -class BitcoinCore: public QObject -{ - Q_OBJECT -public: - explicit BitcoinCore(interfaces::Node& node); - -public Q_SLOTS: - void initialize(); - void shutdown(); - void restart(QStringList args); - -Q_SIGNALS: - void initializeResult(bool success); - void shutdownResult(); - void runawayException(const QString &message); - -private: - /// Pass fatal exception message to UI thread - void handleRunawayException(const std::exception_ptr e); - - interfaces::Node& m_node; -}; - -/** Main Dash application object */ -class BitcoinApplication: public QApplication -{ - Q_OBJECT -public: - explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); - ~BitcoinApplication(); - -#ifdef ENABLE_WALLET - /// Create payment server - void createPaymentServer(); -#endif - /// parameter interaction/setup based on rules - void parameterSetup(); - /// Create options model - void createOptionsModel(bool resetSettings); - /// Create main window - void createWindow(const NetworkStyle *networkStyle); - /// Create splash screen - void createSplashScreen(const NetworkStyle *networkStyle); - - /// Request core initialization - void requestInitialize(); - /// Request core shutdown - void requestShutdown(); - - /// Get process return value - int getReturnValue() const { return returnValue; } - - /// Get window identifier of QMainWindow (BitcoinGUI) - WId getMainWinId() const; - -public Q_SLOTS: - void initializeResult(bool success); - void shutdownResult(); - /// 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(); - void requestedRestart(QStringList args); - void requestedShutdown(); - void stopThread(); - void splashFinished(); - -private: - QThread *coreThread; - interfaces::Node& m_node; - OptionsModel *optionsModel; - ClientModel *clientModel; - BitcoinGUI *window; - QTimer *pollShutdownTimer; -#ifdef ENABLE_WALLET - PaymentServer* paymentServer; - std::vector m_wallet_models; - std::unique_ptr m_handler_load_wallet; -#endif - int returnValue; - std::unique_ptr shutdownWindow; - - void startThread(); -}; - -#include - BitcoinCore::BitcoinCore(interfaces::Node& node) : QObject(), m_node(node) { @@ -300,10 +203,6 @@ BitcoinApplication::BitcoinApplication(interfaces::Node& node, int &argc, char * clientModel(nullptr), window(nullptr), pollShutdownTimer(nullptr), -#ifdef ENABLE_WALLET - paymentServer(nullptr), - m_wallet_models(), -#endif returnValue(0) { setQuitOnLastWindowClosed(false); @@ -364,6 +263,11 @@ void BitcoinApplication::createSplashScreen(const NetworkStyle *networkStyle) connect(this, &BitcoinApplication::requestedShutdown, splash, &QWidget::close); } +bool BitcoinApplication::baseInitialize() +{ + return m_node.baseInitialize(); +} + void BitcoinApplication::startThread() { if(coreThread) @@ -417,11 +321,8 @@ void BitcoinApplication::requestShutdown() 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 = nullptr; @@ -432,35 +333,6 @@ void BitcoinApplication::requestShutdown() 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; @@ -471,26 +343,22 @@ void BitcoinApplication::initializeResult(bool success) // Log this only after AppInitMain finishes, as then logging setup is guaranteed complete qWarning() << "Platform customization:" << gArgs.GetArg("-uiplatform", BitcoinGUI::DEFAULT_UIPLATFORM).c_str(); #ifdef ENABLE_WALLET + m_wallet_controller = new WalletController(m_node, optionsModel, this); #ifdef ENABLE_BIP70 PaymentServer::LoadRootCAs(); #endif - paymentServer->setOptionsModel(optionsModel); + 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, 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, optionsModel)); - } + window->setWalletController(m_wallet_controller); #endif // If -min option passed, start window minimized (iconified) or minimized to tray @@ -502,6 +370,7 @@ void BitcoinApplication::initializeResult(bool success) window->showMinimized(); } Q_EMIT splashFinished(); + Q_EMIT windowShown(window); // Let the users setup their preferred appearance if there are no settings for it defined yet. GUIUtil::setupAppearance(window, clientModel->getOptionsModel()); @@ -509,12 +378,14 @@ void BitcoinApplication::initializeResult(bool success) #ifdef ENABLE_WALLET // Now that initialization/startup is done, process any command-line // dash: URIs or payment requests: - connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest); - connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile); - connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) { - window->message(title, message, style); - }); - QTimer::singleShot(100, paymentServer, &PaymentServer::uiReady); + if (paymentServer) { + connect(paymentServer, &PaymentServer::receivedPaymentRequest, window, &BitcoinGUI::handlePaymentRequest); + connect(window, &BitcoinGUI::receivedURI, paymentServer, &PaymentServer::handleURIOrFile); + connect(paymentServer, &PaymentServer::message, [this](const QString& title, const QString& message, unsigned int style) { + window->message(title, message, style); + }); + QTimer::singleShot(100, paymentServer, &PaymentServer::uiReady); + } #endif pollShutdownTimer->start(200); } else { @@ -564,7 +435,7 @@ static void SetupUIArgs() } #ifndef BITCOIN_QT_TEST -int main(int argc, char *argv[]) +int GuiMain(int argc, char* argv[]) { RegisterPrettyTerminateHander(); RegisterPrettySignalHandlers(); @@ -606,9 +477,6 @@ int main(int argc, char *argv[]) // IMPORTANT if it is no longer a typedef use the normal variant above qRegisterMetaType< CAmount >("CAmount"); qRegisterMetaType< std::function >("std::function"); -#ifdef ENABLE_WALLET - qRegisterMetaType("WalletModel*"); -#endif /// 2. Parse command-line options. We do this after qt in order to show an error if there are problems parsing these // Command-line options take precedence: @@ -821,7 +689,7 @@ int main(int argc, char *argv[]) // Perform base initialization before spinning up initialization/shutdown thread // This is acceptable because this function only contains steps that are quick to execute, // so the GUI thread won't be held up. - if (node->baseInitialize()) { + if (app.baseInitialize()) { app.requestInitialize(); #if defined(Q_OS_WIN) WinShutdownMonitor::registerShutdownBlockReason(QObject::tr("%1 didn't yet exit safely...").arg(QObject::tr(PACKAGE_NAME)), (HWND)app.getMainWinId()); diff --git a/src/qt/dash.h b/src/qt/dash.h new file mode 100644 index 000000000000..a8d098ab92cc --- /dev/null +++ b/src/qt/dash.h @@ -0,0 +1,122 @@ +// Copyright (c) 2011-2016 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_DASH_H +#define BITCOIN_QT_DASH_H + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include +#include + +class BitcoinGUI; +class ClientModel; +class NetworkStyle; +class OptionsModel; +class PaymentServer; +class WalletController; +class WalletModel; + +namespace interfaces { +class Handler; +class Node; +} // namespace interfaces + +/** Class encapsulating Bitcoin Core startup and shutdown. + * Allows running startup and shutdown in a different thread from the UI thread. + */ +class BitcoinCore: public QObject +{ + Q_OBJECT +public: + explicit BitcoinCore(interfaces::Node& node); + +public Q_SLOTS: + void initialize(); + void shutdown(); + void restart(QStringList args); + +Q_SIGNALS: + void initializeResult(bool success); + void shutdownResult(); + void runawayException(const QString &message); + +private: + /// Pass fatal exception message to UI thread + void handleRunawayException(const std::exception_ptr e); + + interfaces::Node& m_node; +}; + +/** Main Bitcoin application object */ +class BitcoinApplication: public QApplication +{ + Q_OBJECT +public: + explicit BitcoinApplication(interfaces::Node& node, int &argc, char **argv); + ~BitcoinApplication(); + +#ifdef ENABLE_WALLET + /// Create payment server + void createPaymentServer(); +#endif + /// parameter interaction/setup based on rules + void parameterSetup(); + /// Create options model + void createOptionsModel(bool resetSettings); + /// Create main window + void createWindow(const NetworkStyle *networkStyle); + /// Create splash screen + void createSplashScreen(const NetworkStyle *networkStyle); + /// Basic initialization, before starting initialization/shutdown thread. Return true on success. + bool baseInitialize(); + + /// Request core initialization + void requestInitialize(); + /// Request core shutdown + void requestShutdown(); + + /// Get process return value + int getReturnValue() const { return returnValue; } + + /// Get window identifier of QMainWindow (BitcoinGUI) + WId getMainWinId() const; + +public Q_SLOTS: + void initializeResult(bool success); + void shutdownResult(); + /// Handle runaway exceptions. Shows a message box with the problem and quits the program. + void handleRunawayException(const QString &message); + +Q_SIGNALS: + void requestedInitialize(); + void requestedRestart(QStringList args); + void requestedShutdown(); + void stopThread(); + void splashFinished(); + void windowShown(BitcoinGUI* window); + +private: + QThread *coreThread; + interfaces::Node& m_node; + OptionsModel *optionsModel; + ClientModel *clientModel; + BitcoinGUI *window; + QTimer *pollShutdownTimer; +#ifdef ENABLE_WALLET + PaymentServer* paymentServer{nullptr}; + WalletController* m_wallet_controller{nullptr}; +#endif + int returnValue; + std::unique_ptr shutdownWindow; + + void startThread(); +}; + +int GuiMain(int argc, char* argv[]); + +#endif // BITCOIN_QT_DASH_H diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index 8fc5e3c26135..71521907f812 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -1234,6 +1234,7 @@ bool loadFonts() // Fail if an added id is -1 which means QFontDatabase::addApplicationFont failed. if (std::find(vecFontIds.begin(), vecFontIds.end(), -1) != vecFontIds.end()) { + osDefaultFont = nullptr; return false; } diff --git a/src/qt/main.cpp b/src/qt/main.cpp new file mode 100644 index 000000000000..a7213004b6d9 --- /dev/null +++ b/src/qt/main.cpp @@ -0,0 +1,17 @@ +// Copyright (c) 2018 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 + +/** Translate string to current locale using Qt. */ +extern const std::function G_TRANSLATION_FUN = [](const char* psz) { + return QCoreApplication::translate("dash-core", psz).toStdString(); +}; + +int main(int argc, char* argv[]) { return GuiMain(argc, argv); } diff --git a/src/qt/test/apptests.cpp b/src/qt/test/apptests.cpp new file mode 100644 index 000000000000..64a965700afa --- /dev/null +++ b/src/qt/test/apptests.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2018 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 +#include +#include +#include + +#if defined(HAVE_CONFIG_H) +#include +#endif +#ifdef ENABLE_WALLET +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +//! Call getblockchaininfo RPC and check first field of JSON output. +void TestRpcCommand(RPCConsole* console) +{ + QEventLoop loop; + QTextEdit* messagesWidget = console->findChild("messagesWidget"); + QObject::connect(messagesWidget, &QTextEdit::textChanged, &loop, &QEventLoop::quit); + QLineEdit* lineEdit = console->findChild("lineEdit"); + QTest::keyClicks(lineEdit, "getblockchaininfo"); + QTest::keyClick(lineEdit, Qt::Key_Return); + loop.exec(); + QString output = messagesWidget->toPlainText(); + UniValue value; + value.read(output.right(output.size() - output.indexOf("{")).toStdString()); + QCOMPARE(value["chain"].get_str(), std::string("regtest")); +} +} // namespace + +//! Entry point for BitcoinApplication tests. +void AppTests::appTests() +{ +#ifdef Q_OS_MAC + if (QApplication::platformName() == "minimal") { + // Disable for mac on "minimal" platform to avoid crashes inside the Qt + // framework when it tries to look up unimplemented cocoa functions, + // and fails to handle returned nulls + // (https://bugreports.qt.io/browse/QTBUG-49686). + QWARN("Skipping AppTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke " + "with 'test_dash-qt -platform cocoa' on mac, or else use a linux or windows build."); + return; + } +#endif + + m_app.parameterSetup(); + GUIUtil::loadFonts(); + m_app.createOptionsModel(true /* reset settings */); + QScopedPointer style( + NetworkStyle::instantiate(QString::fromStdString(Params().NetworkIDString()))); + m_app.createWindow(style.data()); + connect(&m_app, &BitcoinApplication::windowShown, this, &AppTests::guiTests); + expectCallback("guiTests"); + QSettings().setValue("fAppearanceSetupDone", true); // skip appearance setup + m_app.baseInitialize(); + m_app.requestInitialize(); + m_app.exec(); + m_app.requestShutdown(); + m_app.exec(); + + // Reset global state to avoid interfering with later tests. + AbortShutdown(); + UnloadBlockIndex(); +} + +//! Entry point for BitcoinGUI tests. +void AppTests::guiTests(BitcoinGUI* window) +{ + HandleCallback callback{"guiTests", *this}; + connect(window, &BitcoinGUI::consoleShown, this, &AppTests::consoleTests); + expectCallback("consoleTests"); + QAction* action = window->findChild("openRPCConsoleAction"); + action->activate(QAction::Trigger); +} + +//! Entry point for RPCConsole tests. +void AppTests::consoleTests(RPCConsole* console) +{ + HandleCallback callback{"consoleTests", *this}; + TestRpcCommand(console); +} + +//! Destructor to shut down after the last expected callback completes. +AppTests::HandleCallback::~HandleCallback() +{ + auto& callbacks = m_app_tests.m_callbacks; + auto it = callbacks.find(m_callback); + assert(it != callbacks.end()); + callbacks.erase(it); + if (callbacks.empty()) { + m_app_tests.m_app.quit(); + } +} diff --git a/src/qt/test/apptests.h b/src/qt/test/apptests.h new file mode 100644 index 000000000000..83bf56f1e464 --- /dev/null +++ b/src/qt/test/apptests.h @@ -0,0 +1,50 @@ +// Copyright (c) 2018 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_TEST_APPTESTS_H +#define BITCOIN_QT_TEST_APPTESTS_H + +#include +#include +#include +#include + +class BitcoinApplication; +class BitcoinGUI; +class RPCConsole; + +class AppTests : public QObject +{ + Q_OBJECT +public: + explicit AppTests(BitcoinApplication& app) : m_app(app) {} + +private Q_SLOTS: + void appTests(); + void guiTests(BitcoinGUI* window); + void consoleTests(RPCConsole* console); + +private: + //! Add expected callback name to list of pending callbacks. + void expectCallback(std::string callback) { m_callbacks.emplace(std::move(callback)); } + + //! RAII helper to remove no-longer-pending callback. + struct HandleCallback + { + std::string m_callback; + AppTests& m_app_tests; + ~HandleCallback(); + }; + + //! Bitcoin application. + BitcoinApplication& m_app; + + //! Set of pending callback names. Used to track expected callbacks and shut + //! down the app after the last callback has been handled and all tests have + //! either run or thrown exceptions. This could be a simple int counter + //! instead of a set of names, but the names might be useful for debugging. + std::multiset m_callbacks; +}; + +#endif // BITCOIN_QT_TEST_APPTESTS_H diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp index 388c5dfa4349..13a3bb3ec3dc 100644 --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -43,7 +43,7 @@ void RPCNestedTests::rpcNestedTests() TestingSetup test; - SetRPCWarmupFinished(); + if (RPCIsInWarmup(nullptr)) SetRPCWarmupFinished(); std::string result; std::string result2; diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index 216ff160ee7e..e4dbc09e560d 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -8,6 +8,9 @@ #endif #include +#include +#include +#include #include #include #include @@ -49,12 +52,13 @@ int main(int argc, char *argv[]) { SetupEnvironment(); SetupNetworking(); - SelectParams(CBaseChainParams::MAIN); + SelectParams(CBaseChainParams::REGTEST); noui_connect(); ClearDatadirCache(); fs::path pathTemp = fs::temp_directory_path() / strprintf("test_dash-qt_%lu_%i", (unsigned long)GetTime(), (int)GetRand(1 << 30)); fs::create_directories(pathTemp); gArgs.ForceSetArg("-datadir", pathTemp.string()); + auto node = interfaces::MakeNode(); bool fInvalid = false; @@ -69,11 +73,15 @@ int main(int argc, char *argv[]) // Don't remove this, it's needed to access // QApplication:: and QCoreApplication:: in the tests - QApplication app(argc, argv); + BitcoinApplication app(*node, argc, argv); app.setApplicationName("Dash-Qt-test"); SSL_library_init(); + AppTests app_tests(app); + if (QTest::qExec(&app_tests) != 0) { + fInvalid = true; + } URITests test1; if (QTest::qExec(&test1) != 0) { fInvalid = true; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp new file mode 100644 index 000000000000..0c7509973b8e --- /dev/null +++ b/src/qt/walletcontroller.cpp @@ -0,0 +1,94 @@ +// 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, OptionsModel* options_model, QObject* parent) + : QObject(parent) + , m_node(node) + , 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_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/walletcontroller.h b/src/qt/walletcontroller.h new file mode 100644 index 000000000000..9e19c4264f7f --- /dev/null +++ b/src/qt/walletcontroller.h @@ -0,0 +1,58 @@ +// 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, 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; + 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/walletmodel.cpp b/src/qt/walletmodel.cpp index 32e761324d1a..7465d0d9df49 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -440,7 +440,7 @@ int64_t WalletModel::getKeysLeftSinceAutoBackup() const static void NotifyUnload(WalletModel* walletModel) { qDebug() << "NotifyUnload"; - QMetaObject::invokeMethod(walletModel, "unload", Qt::QueuedConnection); + QMetaObject::invokeMethod(walletModel, "unload"); } static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)