diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index abd8a9f11..99fdecb03 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -108,7 +108,7 @@ QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \ BITCOIN_MM = macdockiconhandler.mm macnotificationhandler.mm -QT_MOC = intro.moc overviewpage.moc rpcconsole.moc +QT_MOC = intro.moc overviewpage.moc rpcconsole.moc bitcoin.moc QT_QRC_CPP = qrc_bitcoin.cpp QT_QRC = bitcoin.qrc diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 794d12f69..45551657d 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include #if defined(QT_STATICPLUGIN) #include @@ -52,40 +55,8 @@ Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); // Declare meta types used for QMetaObject::invokeMethod Q_DECLARE_METATYPE(bool*) -// Need a global reference for the notifications to find the GUI -static BitcoinGUI *guiref; -static SplashScreen *splashref; - -static bool ThreadSafeMessageBox(const std::string& message, const std::string& caption, unsigned int style) -{ - if(guiref) - { - bool modal = (style & CClientUIInterface::MODAL); - bool ret = false; - // In case of modal message, use blocking connection to wait for user to click a button - QMetaObject::invokeMethod(guiref, "message", - modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, - Q_ARG(QString, QString::fromStdString(caption)), - Q_ARG(QString, QString::fromStdString(message)), - Q_ARG(unsigned int, style), - Q_ARG(bool*, &ret)); - return ret; - } - else - { - LogPrintf("%s: %s\n", caption.c_str(), message.c_str()); - fprintf(stderr, "%s: %s\n", caption.c_str(), message.c_str()); - return false; - } -} - static void InitMessage(const std::string &message) { - if(splashref) - { - splashref->showMessage(QString::fromStdString(message), Qt::AlignBottom|Qt::AlignHCenter, QColor(55,55,55)); - qApp->processEvents(); - } LogPrintf("init message: %s\n", message.c_str()); } @@ -97,15 +68,6 @@ static std::string Translate(const char* psz) return QCoreApplication::translate("bitcoin-core", psz).toStdString(); } -/* Handle runaway exceptions. Shows a message box with the problem and quits the program. - */ -static void handleRunawayException(std::exception *e) -{ - PrintExceptionContinue(e, "Runaway exception"); - QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + QString::fromStdString(strMiscWarning)); - exit(1); -} - /** Set up translations */ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTranslator, QTranslator &translatorBase, QTranslator &translator) { @@ -162,6 +124,300 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons } #endif +/** 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(); + +public slots: + void initialize(); + void shutdown(); + +signals: + void initializeResult(int retval); + void shutdownResult(int retval); + void runawayException(const QString &message); + +private: + boost::thread_group threadGroup; + + /// Pass fatal exception message to UI thread + void handleRunawayException(std::exception *e); +}; + +/** Main Bitcoin application object */ +class BitcoinApplication: public QApplication +{ + Q_OBJECT +public: + explicit BitcoinApplication(int &argc, char **argv); + ~BitcoinApplication(); + + /// Create payment server + void createPaymentServer(); + /// Create options model + void createOptionsModel(); + /// Create main window + void createWindow(bool isaTestNet); + /// Create splash screen + void createSplashScreen(bool isaTestNet); + + /// Request core initialization + void requestInitialize(); + /// Request core shutdown + void requestShutdown(); + + /// Get process return value + int getReturnValue() { return returnValue; } + +public slots: + void initializeResult(int retval); + void shutdownResult(int retval); + /// Handle runaway exceptions. Shows a message box with the problem and quits the program. + void handleRunawayException(const QString &message); + +signals: + void requestedInitialize(); + void requestedShutdown(); + void stopThread(); + void splashFinished(QWidget *window); + +private: + QThread *coreThread; + PaymentServer* paymentServer; + OptionsModel *optionsModel; + ClientModel *clientModel; + BitcoinGUI *window; + WalletModel *walletModel; + QTimer *pollShutdownTimer; + int returnValue; + + void startThread(); +}; + +#include "bitcoin.moc" + +BitcoinCore::BitcoinCore(): + QObject() +{ +} + +void BitcoinCore::handleRunawayException(std::exception *e) +{ + PrintExceptionContinue(e, "Runaway exception"); + emit runawayException(QString::fromStdString(strMiscWarning)); +} + +void BitcoinCore::initialize() +{ + try + { + LogPrintf("Running AppInit2 in thread\n"); + int rv = AppInit2(threadGroup); + emit initializeResult(rv); + } catch (std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(NULL); + } +} + +void BitcoinCore::shutdown() +{ + try + { + LogPrintf("Running Shutdown in thread\n"); + threadGroup.interrupt_all(); + threadGroup.join_all(); + Shutdown(); + LogPrintf("Shutdown finished\n"); + emit shutdownResult(1); + } catch (std::exception& e) { + handleRunawayException(&e); + } catch (...) { + handleRunawayException(NULL); + } +} + +BitcoinApplication::BitcoinApplication(int &argc, char **argv): + QApplication(argc, argv), + coreThread(0), + paymentServer(0), + optionsModel(0), + clientModel(0), + window(0), + walletModel(0), + pollShutdownTimer(0), + returnValue(0) +{ + setQuitOnLastWindowClosed(false); + startThread(); +} + +BitcoinApplication::~BitcoinApplication() +{ + LogPrintf("Stopping thread\n"); + emit stopThread(); + coreThread->wait(); + LogPrintf("Stopped thread\n"); + + delete window; + delete paymentServer; + delete optionsModel; +} + +void BitcoinApplication::createPaymentServer() +{ + paymentServer = new PaymentServer(this); +} + +void BitcoinApplication::createOptionsModel() +{ + optionsModel = new OptionsModel(); +} + +void BitcoinApplication::createWindow(bool isaTestNet) +{ + window = new BitcoinGUI(isaTestNet, 0); + + pollShutdownTimer = new QTimer(window); + connect(pollShutdownTimer, SIGNAL(timeout()), window, SLOT(detectShutdown())); + pollShutdownTimer->start(200); +} + +void BitcoinApplication::createSplashScreen(bool isaTestNet) +{ + SplashScreen *splash = new SplashScreen(QPixmap(), 0, isaTestNet); + splash->setAttribute(Qt::WA_DeleteOnClose); + splash->show(); + connect(this, SIGNAL(splashFinished(QWidget*)), splash, SLOT(slotFinish(QWidget*))); +} + +void BitcoinApplication::startThread() +{ + coreThread = new QThread(this); + BitcoinCore *executor = new BitcoinCore(); + executor->moveToThread(coreThread); + + /* communication to and from thread */ + connect(executor, SIGNAL(initializeResult(int)), this, SLOT(initializeResult(int))); + connect(executor, SIGNAL(shutdownResult(int)), this, SLOT(shutdownResult(int))); + connect(executor, SIGNAL(runawayException(QString)), this, SLOT(handleRunawayException(QString))); + connect(this, SIGNAL(requestedInitialize()), executor, SLOT(initialize())); + connect(this, SIGNAL(requestedShutdown()), executor, SLOT(shutdown())); + /* make sure executor object is deleted in its own thread */ + connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater())); + connect(this, SIGNAL(stopThread()), coreThread, SLOT(quit())); + + coreThread->start(); +} + +void BitcoinApplication::requestInitialize() +{ + LogPrintf("Requesting initialize\n"); + emit requestedInitialize(); +} + +void BitcoinApplication::requestShutdown() +{ + LogPrintf("Requesting shutdown\n"); + window->hide(); + window->setClientModel(0); + window->removeAllWallets(); + pollShutdownTimer->stop(); + + delete walletModel; + walletModel = 0; + delete clientModel; + clientModel = 0; + + // Show a simple window indicating shutdown status + QWidget *shutdownWindow = new QWidget(); + QVBoxLayout *layout = new QVBoxLayout(); + layout->addWidget(new QLabel( + tr("Bitcoin Core is shutting down...\n") + + tr("Do not shut down the computer until this window disappears."))); + shutdownWindow->setLayout(layout); + + // Center shutdown window at where main window was + const QPoint global = window->mapToGlobal(window->rect().center()); + shutdownWindow->move(global.x() - shutdownWindow->width() / 2, global.y() - shutdownWindow->height() / 2); + shutdownWindow->show(); + + // Request shutdown from core thread + emit requestedShutdown(); +} + +void BitcoinApplication::initializeResult(int retval) +{ + LogPrintf("Initialization result: %i\n", retval); + // Set exit result: 0 if successful, 1 if failure + returnValue = retval ? 0 : 1; + if(retval) + { + // Miscellaneous initialization after core is initialized + optionsModel->Upgrade(); // Must be done after AppInit2 + + PaymentServer::LoadRootCAs(); + paymentServer->setOptionsModel(optionsModel); + + emit splashFinished(window); + + clientModel = new ClientModel(optionsModel); + window->setClientModel(clientModel); + + if(pwalletMain) + { + walletModel = new WalletModel(pwalletMain, optionsModel); + + window->addWallet("~Default", walletModel); + window->setCurrentWallet("~Default"); + + connect(walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), + paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); + } + + // If -min option passed, start window minimized. + if(GetBoolArg("-min", false)) + { + window->showMinimized(); + } + else + { + window->show(); + } + + // Now that initialization/startup is done, process any command-line + // bitcoin: URIs or payment requests: + connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), + window, SLOT(handlePaymentRequest(SendCoinsRecipient))); + connect(window, SIGNAL(receivedURI(QString)), + paymentServer, SLOT(handleURIOrFile(QString))); + connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), + window, SLOT(message(QString,QString,unsigned int))); + QTimer::singleShot(100, paymentServer, SLOT(uiReady())); + + } else { + quit(); // Exit main loop + } +} + +void BitcoinApplication::shutdownResult(int retval) +{ + LogPrintf("Shutdown result: %i\n", retval); + quit(); // Exit main loop after shutdown finished +} + +void BitcoinApplication::handleRunawayException(const QString &message) +{ + QMessageBox::critical(0, "Runaway exception", BitcoinGUI::tr("A fatal error occurred. Bitcoin can no longer continue safely and will quit.") + QString("\n\n") + message); + ::exit(1); +} + #ifndef BITCOIN_QT_TEST int main(int argc, char *argv[]) { @@ -189,7 +445,7 @@ int main(int argc, char *argv[]) #endif Q_INIT_RESOURCE(bitcoin); - QApplication app(argc, argv); + BitcoinApplication app(argc, argv); #if QT_VERSION > 0x050100 // Generate high-dpi pixmaps QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); @@ -215,6 +471,7 @@ int main(int argc, char *argv[]) // Now that QSettings are accessible, initialize translations QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; initTranslations(qtTranslatorBase, qtTranslator, translatorBase, translator); + uiInterface.Translate.connect(Translate); // Show help message immediately after parsing command-line options (for "-lang") and setting locale, // but before showing splash screen. @@ -254,7 +511,7 @@ int main(int argc, char *argv[]) // Start up the payment server early, too, so impatient users that click on // bitcoin: links repeatedly have their payment requests routed to this process: - PaymentServer* paymentServer = new PaymentServer(&app); + app.createPaymentServer(); /// 8. Main GUI initialization // Install global event filter that makes sure that long tooltips can be word-wrapped @@ -266,24 +523,22 @@ int main(int argc, char *argv[]) qInstallMessageHandler(DebugMessageHandler); #endif // Load GUI settings from QSettings - OptionsModel optionsModel; + app.createOptionsModel(); // Subscribe to global signals from core - uiInterface.ThreadSafeMessageBox.connect(ThreadSafeMessageBox); uiInterface.InitMessage.connect(InitMessage); - uiInterface.Translate.connect(Translate); - // Show splash screen if appropriate - SplashScreen splash(QPixmap(), 0, isaTestNet); - if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) + // 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")) { - splash.show(); - splash.setAutoFillBackground(true); - splashref = &splash; + GUIUtil::HelpMessageBox help; + help.showOrPrint(); + return 1; } - app.processEvents(); - app.setQuitOnLastWindowClosed(false); + if (GetBoolArg("-splash", true) && !GetBoolArg("-min", false)) + app.createSplashScreen(isaTestNet); try { @@ -293,93 +548,18 @@ int main(int argc, char *argv[]) if (GUIUtil::GetStartOnSystemStartup()) GUIUtil::SetStartOnSystemStartup(true); #endif - - boost::thread_group threadGroup; - - BitcoinGUI window(isaTestNet, 0); - guiref = &window; - - QTimer* pollShutdownTimer = new QTimer(guiref); - QObject::connect(pollShutdownTimer, SIGNAL(timeout()), guiref, SLOT(detectShutdown())); - pollShutdownTimer->start(200); - - if(AppInit2(threadGroup)) - { - { - // Put this in a block, so that the Model objects are cleaned up before - // calling Shutdown(). - - optionsModel.Upgrade(); // Must be done after AppInit2 - - PaymentServer::LoadRootCAs(); - paymentServer->setOptionsModel(&optionsModel); - - if (splashref) - splash.finish(&window); - - ClientModel clientModel(&optionsModel); - window.setClientModel(&clientModel); - - WalletModel *walletModel = 0; - if(pwalletMain) - walletModel = new WalletModel(pwalletMain, &optionsModel); - - if(walletModel) - { - window.addWallet("~Default", walletModel); - window.setCurrentWallet("~Default"); - } - - // If -min option passed, start window minimized. - if(GetBoolArg("-min", false)) - { - window.showMinimized(); - } - else - { - window.show(); - } - - // Now that initialization/startup is done, process any command-line - // bitcoin: URIs or payment requests: - QObject::connect(paymentServer, SIGNAL(receivedPaymentRequest(SendCoinsRecipient)), - &window, SLOT(handlePaymentRequest(SendCoinsRecipient))); - QObject::connect(&window, SIGNAL(receivedURI(QString)), - paymentServer, SLOT(handleURIOrFile(QString))); - if(walletModel) - { - QObject::connect(walletModel, SIGNAL(coinsSent(CWallet*,SendCoinsRecipient,QByteArray)), - paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); - } - QObject::connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), - guiref, SLOT(message(QString,QString,unsigned int))); - QTimer::singleShot(100, paymentServer, SLOT(uiReady())); - - app.exec(); - - window.hide(); - window.setClientModel(0); - window.removeAllWallets(); - guiref = 0; - delete walletModel; - } - // Shutdown the core and its threads, but don't exit the GUI here - threadGroup.interrupt_all(); - threadGroup.join_all(); - Shutdown(); - } - else - { - threadGroup.interrupt_all(); - threadGroup.join_all(); - Shutdown(); - return 1; - } + app.createWindow(isaTestNet); + app.requestInitialize(); + app.exec(); + app.requestShutdown(); + app.exec(); } catch (std::exception& e) { - handleRunawayException(&e); + PrintExceptionContinue(&e, "Runaway exception"); + app.handleRunawayException(QString::fromStdString(strMiscWarning)); } catch (...) { - handleRunawayException(NULL); + PrintExceptionContinue(NULL, "Runaway exception"); + app.handleRunawayException(QString::fromStdString(strMiscWarning)); } - return 0; + return app.getReturnValue(); } #endif // BITCOIN_QT_TEST diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 6be5a6401..ee27fe90a 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -170,10 +170,16 @@ BitcoinGUI::BitcoinGUI(bool fIsTestnet, QWidget *parent) : // Initially wallet actions should be disabled setWalletActionsEnabled(false); + + // Subscribe to notifications from core + subscribeToCoreSignals(); } BitcoinGUI::~BitcoinGUI() { + // Unsubscribe from notifications from core + unsubscribeFromCoreSignals(); + GUIUtil::saveWindowGeometry("nWindow", this); if(trayIcon) // Hide tray icon, as deleting will let it linger until quit (on Ubuntu) trayIcon->hide(); @@ -849,5 +855,35 @@ void BitcoinGUI::toggleHidden() void BitcoinGUI::detectShutdown() { if (ShutdownRequested()) - QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); + { + if(rpcConsole) + rpcConsole->hide(); + qApp->quit(); + } +} + +static bool ThreadSafeMessageBox(BitcoinGUI *gui, const std::string& message, const std::string& caption, unsigned int style) +{ + bool modal = (style & CClientUIInterface::MODAL); + bool ret = false; + // In case of modal message, use blocking connection to wait for user to click a button + QMetaObject::invokeMethod(gui, "message", + modal ? GUIUtil::blockingGUIThreadConnection() : Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(caption)), + Q_ARG(QString, QString::fromStdString(message)), + Q_ARG(unsigned int, style), + Q_ARG(bool*, &ret)); + return ret; +} + +void BitcoinGUI::subscribeToCoreSignals() +{ + // Connect signals to client + uiInterface.ThreadSafeMessageBox.connect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); +} + +void BitcoinGUI::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client + uiInterface.ThreadSafeMessageBox.disconnect(boost::bind(ThreadSafeMessageBox, this, _1, _2, _3)); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 75c61d2a8..f5d5bb976 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -111,6 +111,11 @@ private: /** Enable or disable all wallet-related actions */ void setWalletActionsEnabled(bool enabled); + /** Connect core signals to GUI client */ + void subscribeToCoreSignals(); + /** Disconnect core signals from GUI client */ + void unsubscribeFromCoreSignals(); + signals: /** Signal raised when a URI was entered or dragged to the GUI */ void receivedURI(const QString &uri); diff --git a/src/qt/splashscreen.cpp b/src/qt/splashscreen.cpp index 6fb834c04..8b16496c1 100644 --- a/src/qt/splashscreen.cpp +++ b/src/qt/splashscreen.cpp @@ -6,6 +6,7 @@ #include "clientversion.h" #include "util.h" +#include "ui_interface.h" #include #include @@ -13,6 +14,8 @@ SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f, bool isTestNet) : QSplashScreen(pixmap, f) { + setAutoFillBackground(true); + // set reference point, paddings int paddingRight = 50; int paddingTop = 50; @@ -83,4 +86,37 @@ SplashScreen::SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f, bool isTest pixPaint.end(); this->setPixmap(newPixmap); + + subscribeToCoreSignals(); +} + +SplashScreen::~SplashScreen() +{ + unsubscribeFromCoreSignals(); +} + +void SplashScreen::slotFinish(QWidget *mainWin) +{ + finish(mainWin); +} + +static void InitMessage(SplashScreen *splash, const std::string &message) +{ + QMetaObject::invokeMethod(splash, "showMessage", + Qt::QueuedConnection, + Q_ARG(QString, QString::fromStdString(message)), + Q_ARG(int, Qt::AlignBottom|Qt::AlignHCenter), + Q_ARG(QColor, QColor(55,55,55))); +} + +void SplashScreen::subscribeToCoreSignals() +{ + // Connect signals to client + uiInterface.InitMessage.connect(boost::bind(InitMessage, this, _1)); +} + +void SplashScreen::unsubscribeFromCoreSignals() +{ + // Disconnect signals from client + uiInterface.InitMessage.disconnect(boost::bind(InitMessage, this, _1)); } diff --git a/src/qt/splashscreen.h b/src/qt/splashscreen.h index 070e376c9..6bc10e60a 100644 --- a/src/qt/splashscreen.h +++ b/src/qt/splashscreen.h @@ -15,6 +15,17 @@ class SplashScreen : public QSplashScreen public: explicit SplashScreen(const QPixmap &pixmap, Qt::WindowFlags f, bool isTestNet); + ~SplashScreen(); + +public slots: + /** Slot to call finish() method as it's not defined as slot */ + void slotFinish(QWidget *mainWin); + +private: + /** Connect core signals to splash screen */ + void subscribeToCoreSignals(); + /** Disconnect core signals to splash screen */ + void unsubscribeFromCoreSignals(); }; #endif // SPLASHSCREEN_H