Skip to content

Commit d46dbc0

Browse files
committed
qml: refactoring to allow for custom datadir and android support
1 parent c1067d1 commit d46dbc0

File tree

1 file changed

+214
-91
lines changed

1 file changed

+214
-91
lines changed

src/qml/bitcoin.cpp

Lines changed: 214 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <qml/appmode.h>
1818
#ifdef __ANDROID__
1919
#include <qml/androidnotifier.h>
20+
#include <qml/androidcustomdatadir.h>
2021
#endif
2122
#include <qml/components/blockclockdial.h>
2223
#include <qml/controls/linegraph.h>
@@ -47,6 +48,7 @@
4748
#include <QQmlApplicationEngine>
4849
#include <QQmlContext>
4950
#include <QQuickWindow>
51+
#include <QSettings>
5052
#include <QString>
5153
#include <QStyleHints>
5254
#include <QUrl>
@@ -160,6 +162,194 @@ void setupChainQSettings(QGuiApplication* app, QString chain)
160162
app->setApplicationName(QAPP_APP_NAME_REGTEST);
161163
}
162164
}
165+
166+
bool setCustomDataDir(QString strDataDir)
167+
{
168+
if(fs::exists(GUIUtil::QStringToPath(strDataDir))){
169+
gArgs.SoftSetArg("-datadir", fs::PathToString(GUIUtil::QStringToPath(strDataDir)));
170+
gArgs.ClearPathCache();
171+
return true;
172+
} else {
173+
return false;
174+
}
175+
}
176+
177+
QGuiApplication* m_app;
178+
QQmlApplicationEngine* m_engine;
179+
boost::signals2::connection m_handler_message_box;
180+
std::unique_ptr<interfaces::Init> m_init;
181+
std::unique_ptr<interfaces::Node> m_node;
182+
std::unique_ptr<interfaces::Chain> m_chain;
183+
NodeModel* m_node_model{nullptr};
184+
InitExecutor* m_executor{nullptr};
185+
ChainModel* m_chain_model{nullptr};
186+
OptionsQmlModel* m_options_model{nullptr};
187+
int m_argc;
188+
char** m_argv;
189+
NetworkTrafficTower* m_network_traffic_tower;
190+
PeerTableModel* m_peer_model;
191+
PeerListSortProxy* m_peer_model_sort_proxy;
192+
bool m_isOnboarded;
193+
WalletController *m_wallet_controller;
194+
WalletListModel *m_wallet_list_model;
195+
196+
bool createNode(QGuiApplication& app, QQmlApplicationEngine& engine, int& argc, char* argv[], ArgsManager& gArgs)
197+
{
198+
m_engine = &engine;
199+
200+
InitLogging(gArgs);
201+
InitParameterInteraction(gArgs);
202+
203+
m_init = interfaces::MakeGuiInit(argc, argv);
204+
205+
m_node = m_init->makeNode();
206+
m_chain = m_init->makeChain();
207+
208+
if (!m_node->baseInitialize()) {
209+
// A dialog with detailed error will have been shown by InitError().
210+
return EXIT_FAILURE;
211+
}
212+
213+
m_handler_message_box.disconnect();
214+
215+
m_node_model = new NodeModel{*m_node};
216+
m_executor = new InitExecutor{*m_node};
217+
QObject::connect(m_node_model, &NodeModel::requestedInitialize, m_executor, &InitExecutor::initialize);
218+
QObject::connect(m_node_model, &NodeModel::requestedShutdown, m_executor, &InitExecutor::shutdown);
219+
QObject::connect(m_executor, &InitExecutor::initializeResult, m_node_model, &NodeModel::initializeResult);
220+
QObject::connect(m_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection);
221+
222+
m_network_traffic_tower = new NetworkTrafficTower{*m_node_model};
223+
#ifdef __ANDROID__
224+
AndroidNotifier android_notifier{*m_node_model};
225+
#endif
226+
227+
m_chain_model = new ChainModel{*m_chain};
228+
m_chain_model->setCurrentNetworkName(QString::fromStdString(ChainTypeToString(gArgs.GetChainType())));
229+
setupChainQSettings(m_app, m_chain_model->currentNetworkName());
230+
231+
QObject::connect(m_node_model, &NodeModel::setTimeRatioList, m_chain_model, &ChainModel::setTimeRatioList);
232+
QObject::connect(m_node_model, &NodeModel::setTimeRatioListInitial, m_chain_model, &ChainModel::setTimeRatioListInitial);
233+
234+
qGuiApp->setQuitOnLastWindowClosed(false);
235+
QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] {
236+
m_node->startShutdown();
237+
});
238+
239+
m_peer_model = new PeerTableModel{*m_node, nullptr};
240+
m_peer_model_sort_proxy = new PeerListSortProxy{nullptr};
241+
m_peer_model_sort_proxy->setSourceModel(m_peer_model);
242+
243+
m_wallet_controller = new WalletController{*m_node};
244+
245+
m_wallet_list_model = new WalletListModel{*m_node, nullptr};
246+
247+
m_engine->rootContext()->setContextProperty("networkTrafficTower", m_network_traffic_tower);
248+
m_engine->rootContext()->setContextProperty("nodeModel", m_node_model);
249+
m_engine->rootContext()->setContextProperty("chainModel", m_chain_model);
250+
m_engine->rootContext()->setContextProperty("peerTableModel", m_peer_model);
251+
m_engine->rootContext()->setContextProperty("peerListModelProxy", m_peer_model_sort_proxy);
252+
m_engine->rootContext()->setContextProperty("walletController", m_wallet_controller);
253+
m_engine->rootContext()->setContextProperty("walletListModel", m_wallet_list_model);
254+
255+
m_options_model->setNode(&(*m_node), m_isOnboarded);
256+
257+
QObject::connect(m_options_model, &OptionsQmlModel::requestedShutdown, m_executor, &InitExecutor::shutdown);
258+
259+
m_engine->rootContext()->setContextProperty("optionsModel", m_options_model);
260+
261+
m_node_model->startShutdownPolling();
262+
263+
return true;
264+
}
265+
266+
void startNodeAndTransitionSlot() { createNode(*m_app, *m_engine, m_argc, m_argv, gArgs); }
267+
268+
int initializeAndRunApplication(QGuiApplication* app, QQmlApplicationEngine* m_engine) {
269+
AppMode app_mode = SetupAppMode();
270+
271+
// Register the singleton instance for AppMode with the QML engine
272+
qmlRegisterSingletonInstance<AppMode>("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode);
273+
274+
// Register custom QML types
275+
qmlRegisterType<BlockClockDial>("org.bitcoincore.qt", 1, 0, "BlockClockDial");
276+
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
277+
278+
// Load the main QML file
279+
m_engine->load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
280+
281+
// Check if the QML engine failed to load the main QML file
282+
if (m_engine->rootObjects().isEmpty()) {
283+
return EXIT_FAILURE;
284+
}
285+
286+
// Get the first root object as a QQuickWindow
287+
auto window = qobject_cast<QQuickWindow*>(m_engine->rootObjects().first());
288+
if (!window) {
289+
return EXIT_FAILURE;
290+
}
291+
292+
// Install the custom message handler for qDebug()
293+
qInstallMessageHandler(DebugMessageHandler);
294+
295+
// Log the graphics API in use
296+
qInfo() << "Graphics API in use:" << QmlUtil::GraphicsApi(window);
297+
298+
// Execute the application
299+
return qGuiApp->exec();
300+
}
301+
302+
bool startNode(QGuiApplication& app, QQmlApplicationEngine& engine, int& argc, char* argv[])
303+
{
304+
m_engine = &engine;
305+
QScopedPointer<const NetworkStyle> network_style{NetworkStyle::instantiate(Params().GetChainType())};
306+
assert(!network_style.isNull());
307+
m_engine->addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()});
308+
309+
m_isOnboarded = true;
310+
311+
m_options_model = new OptionsQmlModel{nullptr, m_isOnboarded};
312+
m_engine->rootContext()->setContextProperty("optionsModel", m_options_model);
313+
314+
// moved this so that the settings.json file is read and parsed before creating the node
315+
std::string error;
316+
/// Read and parse settings.json file.
317+
std::vector<std::string> errors;
318+
if (!gArgs.ReadSettingsFile(&errors)) {
319+
error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors));
320+
InitError(Untranslated(error));
321+
return EXIT_FAILURE;
322+
}
323+
324+
createNode(*m_app, *m_engine, argc, argv, gArgs);
325+
326+
initializeAndRunApplication(&app, m_engine);
327+
return true;
328+
}
329+
330+
bool startOnboarding(QGuiApplication& app, QQmlApplicationEngine& engine, ArgsManager& gArgs)
331+
{
332+
m_engine = &engine;
333+
QScopedPointer<const NetworkStyle> network_style{NetworkStyle::instantiate(Params().GetChainType())};
334+
assert(!network_style.isNull());
335+
m_engine->addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()});
336+
337+
m_isOnboarded = false;
338+
339+
m_options_model = new OptionsQmlModel{nullptr, m_isOnboarded};
340+
341+
if (gArgs.IsArgSet("-resetguisettings")) {
342+
m_options_model->defaultReset();
343+
}
344+
345+
m_engine->rootContext()->setContextProperty("optionsModel", m_options_model);
346+
347+
QObject::connect(m_options_model, &OptionsQmlModel::onboardingFinished, startNodeAndTransitionSlot);
348+
349+
initializeAndRunApplication(&app, m_engine);
350+
351+
return true;
352+
}
163353
} // namespace
164354

165355

@@ -177,9 +367,7 @@ int QmlGuiMain(int argc, char* argv[])
177367
QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusAllControls);
178368
QGuiApplication app(argc, argv);
179369

180-
auto handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox);
181-
182-
std::unique_ptr<interfaces::Init> init = interfaces::MakeGuiInit(argc, argv);
370+
auto m_handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox);
183371

184372
SetupEnvironment();
185373
util::ThreadSetInternalName("main");
@@ -191,6 +379,10 @@ int QmlGuiMain(int argc, char* argv[])
191379
app.setOrganizationDomain(QAPP_ORG_DOMAIN);
192380
app.setApplicationName(QAPP_APP_NAME_DEFAULT);
193381

382+
QSettings settings;
383+
QString dataDir;
384+
dataDir = settings.value("strDataDir", dataDir).toString();
385+
194386
/// Parse command-line options. We do this after qt in order to show an error if there are problems parsing these.
195387
SetupServerArgs(gArgs);
196388
SetupUIArgs(gArgs);
@@ -220,20 +412,24 @@ int QmlGuiMain(int argc, char* argv[])
220412
return EXIT_FAILURE;
221413
}
222414

223-
/// Read and parse settings.json file.
224-
std::vector<std::string> errors;
225-
if (!gArgs.ReadSettingsFile(&errors)) {
226-
error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors));
227-
InitError(Untranslated(error));
228-
return EXIT_FAILURE;
229-
}
230-
231415
QVariant need_onboarding(true);
232-
if (gArgs.IsArgSet("-datadir") && !gArgs.GetPathArg("-datadir").empty()) {
416+
#ifdef __ANDROID__
417+
AndroidCustomDataDir custom_data_dir;
418+
QString storePath = custom_data_dir.readCustomDataDir();
419+
if (!storePath.isEmpty()) {
420+
custom_data_dir.setDataDir(storePath);
233421
need_onboarding.setValue(false);
234422
} else if (ConfigurationFileExists(gArgs)) {
235423
need_onboarding.setValue(false);
236424
}
425+
#else
426+
if ((gArgs.IsArgSet("-datadir") && !gArgs.GetPathArg("-datadir").empty()) || fs::exists(GUIUtil::QStringToPath(dataDir)) ) {
427+
setCustomDataDir(dataDir);
428+
need_onboarding.setValue(false);
429+
} else if (ConfigurationFileExists(gArgs)) {
430+
need_onboarding.setValue(false);
431+
}
432+
#endif // __ANDROID__
237433

238434
if (gArgs.IsArgSet("-resetguisettings")) {
239435
need_onboarding.setValue(true);
@@ -242,95 +438,22 @@ int QmlGuiMain(int argc, char* argv[])
242438
// Default printtoconsole to false for the GUI. GUI programs should not
243439
// print to the console unnecessarily.
244440
gArgs.SoftSetBoolArg("-printtoconsole", false);
245-
InitLogging(gArgs);
246-
InitParameterInteraction(gArgs);
247441

248442
GUIUtil::LogQtInfo();
249-
250-
std::unique_ptr<interfaces::Node> node = init->makeNode();
251-
std::unique_ptr<interfaces::Chain> chain = init->makeChain();
252-
if (!node->baseInitialize()) {
253-
// A dialog with detailed error will have been shown by InitError().
254-
return EXIT_FAILURE;
255-
}
256-
257-
handler_message_box.disconnect();
258-
259-
NodeModel node_model{*node};
260-
InitExecutor init_executor{*node};
261-
QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &InitExecutor::initialize);
262-
QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &InitExecutor::shutdown);
263-
QObject::connect(&init_executor, &InitExecutor::initializeResult, &node_model, &NodeModel::initializeResult);
264-
QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection);
265-
// QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);
266-
267-
NetworkTrafficTower network_traffic_tower{node_model};
268-
#ifdef __ANDROID__
269-
AndroidNotifier android_notifier{node_model};
270-
#endif
271-
272-
ChainModel chain_model{*chain};
273-
chain_model.setCurrentNetworkName(QString::fromStdString(ChainTypeToString(gArgs.GetChainType())));
274-
setupChainQSettings(&app, chain_model.currentNetworkName());
275-
276-
QObject::connect(&node_model, &NodeModel::setTimeRatioList, &chain_model, &ChainModel::setTimeRatioList);
277-
QObject::connect(&node_model, &NodeModel::setTimeRatioListInitial, &chain_model, &ChainModel::setTimeRatioListInitial);
278-
279-
qGuiApp->setQuitOnLastWindowClosed(false);
280-
QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] {
281-
node->startShutdown();
282-
});
283-
284-
PeerTableModel peer_model{*node, nullptr};
285-
PeerListSortProxy peer_model_sort_proxy{nullptr};
286-
peer_model_sort_proxy.setSourceModel(&peer_model);
287-
288443
GUIUtil::LoadFont(":/fonts/inter/regular");
289444
GUIUtil::LoadFont(":/fonts/inter/semibold");
290445

291-
WalletController wallet_controller(*node);
446+
m_app = &app;
292447

293448
QQmlApplicationEngine engine;
294449

295-
QScopedPointer<const NetworkStyle> network_style{NetworkStyle::instantiate(Params().GetChainType())};
296-
assert(!network_style.isNull());
297-
engine.addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()});
298-
299-
WalletListModel wallet_list_model{*node, nullptr};
300-
301-
engine.rootContext()->setContextProperty("networkTrafficTower", &network_traffic_tower);
302-
engine.rootContext()->setContextProperty("nodeModel", &node_model);
303-
engine.rootContext()->setContextProperty("chainModel", &chain_model);
304-
engine.rootContext()->setContextProperty("peerTableModel", &peer_model);
305-
engine.rootContext()->setContextProperty("peerListModelProxy", &peer_model_sort_proxy);
306-
engine.rootContext()->setContextProperty("walletController", &wallet_controller);
307-
engine.rootContext()->setContextProperty("walletListModel", &wallet_list_model);
308-
309-
OptionsQmlModel options_model(*node, !need_onboarding.toBool());
310-
engine.rootContext()->setContextProperty("optionsModel", &options_model);
311450
engine.rootContext()->setContextProperty("needOnboarding", need_onboarding);
312451

313-
AppMode app_mode = SetupAppMode();
314-
315-
qmlRegisterSingletonInstance<AppMode>("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode);
316-
qmlRegisterType<BlockClockDial>("org.bitcoincore.qt", 1, 0, "BlockClockDial");
317-
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
318-
319-
engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
320-
if (engine.rootObjects().isEmpty()) {
321-
return EXIT_FAILURE;
322-
}
323-
324-
auto window = qobject_cast<QQuickWindow*>(engine.rootObjects().first());
325-
if (!window) {
326-
return EXIT_FAILURE;
452+
if(need_onboarding.toBool()) {
453+
startOnboarding(*m_app, engine, gArgs);
454+
} else {
455+
startNode(*m_app, engine, argc, argv);
327456
}
328457

329-
// Install qDebug() message handler to route to debug.log
330-
qInstallMessageHandler(DebugMessageHandler);
331-
332-
qInfo() << "Graphics API in use:" << QmlUtil::GraphicsApi(window);
333-
334-
node_model.startShutdownPolling();
335-
return qGuiApp->exec();
458+
return 0;
336459
}

0 commit comments

Comments
 (0)