diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 2f2d69847b..7da0c2504c 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -35,6 +35,7 @@ QT_FORMS_UI = \ qt/forms/transactiondescdialog.ui QT_MOC_CPP = \ + qml/moc_engine.cpp \ qml/moc_nodemodel.cpp \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ @@ -108,6 +109,7 @@ QT_QRC_LOCALE = qt/bitcoin_locale.qrc BITCOIN_QT_H = \ qml/bitcoin.h \ + qml/engine.h \ qml/imageprovider.h \ qml/nodemodel.h \ qml/util.h \ @@ -286,6 +288,7 @@ BITCOIN_QT_WALLET_CPP = \ BITCOIN_QML_BASE_CPP = \ qml/bitcoin.cpp \ + qml/engine.cpp \ qml/imageprovider.cpp \ qml/nodemodel.cpp \ qml/util.cpp @@ -444,7 +447,7 @@ ui_%.h: %.ui $(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(MOC) $(DEFAULT_INCLUDES) $(QT_INCLUDES_UNSUPPRESSED) $(MOC_DEFS) $< > $@ moc_%.cpp: %.h - $(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(MOC) $(DEFAULT_INCLUDES) $(QT_INCLUDES_UNSUPPRESSED) $(MOC_DEFS) $< > $@ + $(AM_V_GEN) QT_SELECT=$(QT_SELECT) $(MOC) $(DEFAULT_INCLUDES) $(QT_INCLUDES_UNSUPPRESSED) $(QT_QUICK_CFLAGS) $(MOC_DEFS) $< > $@ %.qm: %.ts @test -f $(LRELEASE) diff --git a/src/qml/bitcoin.cpp b/src/qml/bitcoin.cpp index 5e035677dd..249e74887a 100644 --- a/src/qml/bitcoin.cpp +++ b/src/qml/bitcoin.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,6 @@ #include #include -#include #include #include #include @@ -157,30 +157,26 @@ int QmlGuiMain(int argc, char* argv[]) handler_message_box.disconnect(); - NodeModel node_model{*node}; InitExecutor init_executor{*node}; - QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &InitExecutor::initialize); - QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &InitExecutor::shutdown); - QObject::connect(&init_executor, &InitExecutor::initializeResult, &node_model, &NodeModel::initializeResult); - QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection); - // QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException); qGuiApp->setQuitOnLastWindowClosed(false); QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] { node->startShutdown(); - node_model.startNodeShutdown(); + init_executor.shutdown(); }); GUIUtil::LoadFont(":/fonts/inter/regular"); GUIUtil::LoadFont(":/fonts/inter/semibold"); - QQmlApplicationEngine engine; + qmlRegisterType("BitcoinCore", 1, 0, "NodeModel"); + + Engine engine(*node); QScopedPointer network_style{NetworkStyle::instantiate(Params().NetworkIDString())}; assert(!network_style.isNull()); engine.addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()}); - engine.rootContext()->setContextProperty("nodeModel", &node_model); + engine.rootContext()->setContextProperty("initExecutor", &init_executor); engine.load(QUrl(QStringLiteral("qrc:///qml/pages/stub.qml"))); if (engine.rootObjects().isEmpty()) { diff --git a/src/qml/engine.cpp b/src/qml/engine.cpp new file mode 100644 index 0000000000..f8bd591cec --- /dev/null +++ b/src/qml/engine.cpp @@ -0,0 +1,26 @@ +// Copyright (c) 2021 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 + +Engine::Engine(interfaces::Node& node) + : m_node(node) +{ +} + +Engine::~Engine() +{ +} + +interfaces::Node& Engine::node(QObject* object) +{ + auto context = Assert(QQmlEngine::contextForObject(object)); + auto engine = Assert(context->engine()); + return Assert(qobject_cast(engine))->node(); +} diff --git a/src/qml/engine.h b/src/qml/engine.h new file mode 100644 index 0000000000..e8ebf31728 --- /dev/null +++ b/src/qml/engine.h @@ -0,0 +1,30 @@ +// Copyright (c) 2021 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_QML_ENGINE_H +#define BITCOIN_QML_ENGINE_H + +#include + +namespace interfaces { +class Node; +} + +class Engine : public QQmlApplicationEngine +{ + Q_OBJECT + +public: + explicit Engine(interfaces::Node& node); + ~Engine(); + + interfaces::Node& node() const { return m_node; } + + static interfaces::Node& node(QObject* object); + +private: + interfaces::Node& m_node; +}; + +#endif // BITCOIN_QML_ENGINE_H diff --git a/src/qml/nodemodel.cpp b/src/qml/nodemodel.cpp index 45601791df..07fab41898 100644 --- a/src/qml/nodemodel.cpp +++ b/src/qml/nodemodel.cpp @@ -5,14 +5,15 @@ #include #include +#include +#include #include #include -NodeModel::NodeModel(interfaces::Node& node) - : m_node{node} +NodeModel::NodeModel(QObject* parent) + : QObject(parent) { - ConnectToBlockTipSignal(); } void NodeModel::setBlockTipHeight(int new_height) @@ -23,27 +24,19 @@ void NodeModel::setBlockTipHeight(int new_height) } } -void NodeModel::startNodeInitializionThread() +void NodeModel::classBegin() { - Q_EMIT requestedInitialize(); } -void NodeModel::startNodeShutdown() -{ - Q_EMIT requestedShutdown(); -} - -void NodeModel::initializeResult([[maybe_unused]] bool success, interfaces::BlockAndHeaderTipInfo tip_info) -{ - // TODO: Handle the `success` parameter, - setBlockTipHeight(tip_info.block_height); -} - -void NodeModel::ConnectToBlockTipSignal() +void NodeModel::componentComplete() { + auto& node = Engine::node(this); + setBlockTipHeight(node.getNumBlocks()); assert(!m_handler_notify_block_tip); - m_handler_notify_block_tip = m_node.handleNotifyBlockTip( + m_handler_notify_block_tip = node.handleNotifyBlockTip( [this](SynchronizationState state, interfaces::BlockTip tip, double verification_progress) { - setBlockTipHeight(tip.block_height); + GUIUtil::ObjectInvoke(this, [=] { + setBlockTipHeight(tip.block_height); + }); }); } diff --git a/src/qml/nodemodel.h b/src/qml/nodemodel.h index b3190a0199..d704235a00 100644 --- a/src/qml/nodemodel.h +++ b/src/qml/nodemodel.h @@ -6,47 +6,41 @@ #define BITCOIN_QML_NODEMODEL_H #include -#include #include #include +#include namespace interfaces { class Node; } /** Model for Bitcoin network client. */ -class NodeModel : public QObject +class NodeModel : public QObject, public QQmlParserStatus { Q_OBJECT + Q_INTERFACES(QQmlParserStatus) Q_PROPERTY(int blockTipHeight READ blockTipHeight NOTIFY blockTipHeightChanged) public: - explicit NodeModel(interfaces::Node& node); + explicit NodeModel(QObject* parent = nullptr); int blockTipHeight() const { return m_block_tip_height; } void setBlockTipHeight(int new_height); - Q_INVOKABLE void startNodeInitializionThread(); - void startNodeShutdown(); - -public Q_SLOTS: - void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info); + // QQmlParserStatus + void classBegin() override; + void componentComplete() override; Q_SIGNALS: void blockTipHeightChanged(); - void requestedInitialize(); - void requestedShutdown(); private: // Properties that are exposed to QML. int m_block_tip_height{0}; - interfaces::Node& m_node; std::unique_ptr m_handler_notify_block_tip; - - void ConnectToBlockTipSignal(); }; #endif // BITCOIN_QML_NODEMODEL_H diff --git a/src/qml/pages/stub.qml b/src/qml/pages/stub.qml index d7c6fdbdfa..d6ddb2e8b7 100644 --- a/src/qml/pages/stub.qml +++ b/src/qml/pages/stub.qml @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +import BitcoinCore 1.0 import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.11 @@ -16,33 +17,51 @@ ApplicationWindow { color: "black" visible: true - Component.onCompleted: nodeModel.startNodeInitializionThread(); + Component.onCompleted: initExecutor.initialize() - ColumnLayout { + Connections { + target: initExecutor + onInitializeResult: nodeModel.initializeResult(success, tip_info) + } + + BusyIndicator { + running: !initExecutor.ready + visible: running anchors.centerIn: parent - spacing: 15 - width: 400 - Image { - Layout.alignment: Qt.AlignCenter - source: "image://images/app" - sourceSize.width: 64 - sourceSize.height: 64 - } - BlockCounter { - Layout.alignment: Qt.AlignCenter - blockHeight: nodeModel.blockTipHeight - } - ProgressIndicator { - id: indicator - Layout.fillWidth: true - progress: 0.666 - background: MouseArea { - onClicked: indicator.progress = mouseX / width + } + + Loader { + id: loader + active: initExecutor.ready + anchors.centerIn: parent + sourceComponent: ColumnLayout { + spacing: 15 + width: 400 + NodeModel { + id: nodeModel + } + Image { + Layout.alignment: Qt.AlignCenter + source: "image://images/app" + sourceSize.width: 64 + sourceSize.height: 64 + } + BlockCounter { + Layout.alignment: Qt.AlignCenter + blockHeight: nodeModel.blockTipHeight + } + ProgressIndicator { + id: indicator + Layout.fillWidth: true + progress: 0.666 + background: MouseArea { + onClicked: indicator.progress = mouseX / width + } + } + ConnectionOptions { + Layout.preferredWidth: 400 + focus: true } - } - ConnectionOptions { - Layout.preferredWidth: 400 - focus: true } } } diff --git a/src/qt/initexecutor.cpp b/src/qt/initexecutor.cpp index 24ae7ba73d..bdb6b24791 100644 --- a/src/qt/initexecutor.cpp +++ b/src/qt/initexecutor.cpp @@ -21,6 +21,11 @@ InitExecutor::InitExecutor(interfaces::Node& node) { m_context.moveToThread(&m_thread); m_thread.start(); + + connect(this, &InitExecutor::initializeResult, this, [=](bool success, interfaces::BlockAndHeaderTipInfo tip_info) { + m_ready = success; + Q_EMIT readyChanged(); + }); } InitExecutor::~InitExecutor() diff --git a/src/qt/initexecutor.h b/src/qt/initexecutor.h index 410c44fa2d..8017bc48c2 100644 --- a/src/qt/initexecutor.h +++ b/src/qt/initexecutor.h @@ -22,15 +22,19 @@ QT_END_NAMESPACE class InitExecutor : public QObject { Q_OBJECT + Q_PROPERTY(bool ready READ ready NOTIFY readyChanged) public: explicit InitExecutor(interfaces::Node& node); ~InitExecutor(); + bool ready() const { return m_ready; } + public Q_SLOTS: void initialize(); void shutdown(); Q_SIGNALS: + void readyChanged(); void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info); void shutdownResult(); void runawayException(const QString& message); @@ -42,6 +46,7 @@ public Q_SLOTS: interfaces::Node& m_node; QObject m_context; QThread m_thread; + bool m_ready{false}; }; #endif // BITCOIN_QT_INITEXECUTOR_H