diff --git a/CMakeLists.txt b/CMakeLists.txt index 69ee54611a..2c86b4acbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -254,7 +254,7 @@ if(WITH_USDT) endif() if(BUILD_GUI) - set(qt_components Core Gui Widgets LinguistTools) + set(qt_components Core Gui Widgets Qml Quick QuickControls2 LinguistTools) if(ENABLE_WALLET) list(APPEND qt_components Network) endif() diff --git a/cmake/module/Maintenance.cmake b/cmake/module/Maintenance.cmake index 15fbf2bea8..20d2ff0f09 100644 --- a/cmake/module/Maintenance.cmake +++ b/cmake/module/Maintenance.cmake @@ -36,7 +36,7 @@ function(add_windows_deploy_target) OUTPUT ${PROJECT_BINARY_DIR}/bitcoin-win64-setup.exe COMMAND ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/release COMMAND ${CMAKE_STRIP} $ -o ${PROJECT_BINARY_DIR}/release/$ - COMMAND ${CMAKE_STRIP} $ -o ${PROJECT_BINARY_DIR}/release/$ + COMMAND ${CMAKE_STRIP} $ -o ${PROJECT_BINARY_DIR}/release/$ COMMAND ${CMAKE_STRIP} $ -o ${PROJECT_BINARY_DIR}/release/$ COMMAND ${CMAKE_STRIP} $ -o ${PROJECT_BINARY_DIR}/release/$ COMMAND ${CMAKE_STRIP} $ -o ${PROJECT_BINARY_DIR}/release/$ @@ -65,8 +65,8 @@ function(add_macos_deploy_target) add_custom_command( OUTPUT ${PROJECT_BINARY_DIR}/${macos_app}/Contents/MacOS/Bitcoin-Qt - COMMAND ${CMAKE_COMMAND} --install ${PROJECT_BINARY_DIR} --config $ --component bitcoin-qt --prefix ${macos_app}/Contents/MacOS --strip - COMMAND ${CMAKE_COMMAND} -E rename ${macos_app}/Contents/MacOS/bin/$ ${macos_app}/Contents/MacOS/Bitcoin-Qt + COMMAND ${CMAKE_COMMAND} --install ${PROJECT_BINARY_DIR} --config $ --component bitcoin-qml --prefix ${macos_app}/Contents/MacOS --strip + COMMAND ${CMAKE_COMMAND} -E rename ${macos_app}/Contents/MacOS/bin/$ ${macos_app}/Contents/MacOS/Bitcoin-Qt COMMAND ${CMAKE_COMMAND} -E rm -rf ${macos_app}/Contents/MacOS/bin COMMAND ${CMAKE_COMMAND} -E rm -rf ${macos_app}/Contents/MacOS/share VERBATIM diff --git a/contrib/guix/symbol-check.py b/contrib/guix/symbol-check.py index 86b7965277..65f3f5f599 100755 --- a/contrib/guix/symbol-check.py +++ b/contrib/guix/symbol-check.py @@ -130,6 +130,7 @@ 'Metal', # 3D graphics 'QuartzCore', # animation 'Security', # access control and authentication +'SystemConfiguration', # network configuration 'UniformTypeIdentifiers', # collection of types that map to MIME and file types } @@ -158,6 +159,7 @@ 'NETAPI32.dll', # network management 'ole32.dll', # component object model 'OLEAUT32.dll', # OLE Automation API +'Secur32.dll', # SSPI (Security Support Provider Interface) 'SHLWAPI.dll', # light weight shell API 'USER32.dll', # user interface 'USERENV.dll', # user management diff --git a/depends/packages/native_qt.mk b/depends/packages/native_qt.mk index 2bf088c10a..6d2aec3734 100644 --- a/depends/packages/native_qt.mk +++ b/depends/packages/native_qt.mk @@ -11,13 +11,21 @@ $(package)_patches += rcc_hardcode_timestamp.patch $(package)_patches += qttools_skip_dependencies.patch $(package)_patches += fix-macos26-qyield.patch +$(package)_qtshadertools_file_name=$(qt_details_qtshadertools_file_name) +$(package)_qtshadertools_sha256_hash=$(qt_details_qtshadertools_sha256_hash) + +$(package)_qtdeclarative_file_name=$(qt_details_qtdeclarative_file_name) +$(package)_qtdeclarative_sha256_hash=$(qt_details_qtdeclarative_sha256_hash) + $(package)_qttranslations_file_name=$(qt_details_qttranslations_file_name) $(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash) $(package)_qttools_file_name=$(qt_details_qttools_file_name) $(package)_qttools_sha256_hash=$(qt_details_qttools_sha256_hash) -$(package)_extra_sources := $($(package)_qttranslations_file_name) +$(package)_extra_sources := $($(package)_qtshadertools_file_name) +$(package)_extra_sources += $($(package)_qtdeclarative_file_name) +$(package)_extra_sources += $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) $(package)_top_download_path=$(qt_details_top_download_path) @@ -51,14 +59,13 @@ $(package)_config_opts += -no-feature-concurrent $(package)_config_opts += -no-feature-network $(package)_config_opts += -no-feature-printsupport $(package)_config_opts += -no-feature-sql -$(package)_config_opts += -no-feature-testlib $(package)_config_opts += -no-feature-xml -$(package)_config_opts += -no-gui $(package)_config_opts += -no-widgets $(package)_config_opts += -no-glib $(package)_config_opts += -no-icu $(package)_config_opts += -no-libudev +$(package)_config_opts += -no-opengl $(package)_config_opts += -no-openssl $(package)_config_opts += -no-zstd $(package)_config_opts += -qt-pcre @@ -67,6 +74,7 @@ $(package)_config_opts += -no-feature-backtrace $(package)_config_opts += -no-feature-permissions $(package)_config_opts += -no-feature-process $(package)_config_opts += -no-feature-settings +$(package)_config_opts += -no-feature-vulkan # Core tools. $(package)_config_opts += -no-feature-androiddeployqt @@ -106,6 +114,8 @@ endef define $(package)_fetch_cmds $(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \ +$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtshadertools_file_name),$($(package)_qtshadertools_file_name),$($(package)_qtshadertools_sha256_hash)) && \ +$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtdeclarative_file_name),$($(package)_qtdeclarative_file_name),$($(package)_qtdeclarative_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttranslations_file_name),$($(package)_qttranslations_file_name),$($(package)_qttranslations_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttools_file_name),$($(package)_qttools_file_name),$($(package)_qttools_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_top_download_path),$($(package)_top_cmakelists_download_file),$($(package)_top_cmakelists_file_name)-$($(package)_version),$($(package)_top_cmakelists_sha256_hash)) && \ @@ -116,6 +126,8 @@ endef define $(package)_extract_cmds mkdir -p $($(package)_extract_dir) && \ echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtshadertools_sha256_hash) $($(package)_source_dir)/$($(package)_qtshadertools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtdeclarative_sha256_hash) $($(package)_source_dir)/$($(package)_qtdeclarative_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_qttranslations_sha256_hash) $($(package)_source_dir)/$($(package)_qttranslations_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_qttools_sha256_hash) $($(package)_source_dir)/$($(package)_qttools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ @@ -124,6 +136,10 @@ define $(package)_extract_cmds $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ mkdir -p qtbase && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source) -C qtbase && \ + mkdir -p qtshadertools && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtshadertools_file_name) -C qtshadertools && \ + mkdir -p qtdeclarative && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtdeclarative_file_name) -C qtdeclarative && \ mkdir -p qttranslations && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttranslations_file_name) -C qttranslations && \ mkdir -p qttools && \ diff --git a/depends/packages/qt.mk b/depends/packages/qt.mk index 9b7cec341d..b0f51fe437 100644 --- a/depends/packages/qt.mk +++ b/depends/packages/qt.mk @@ -27,13 +27,21 @@ $(package)_patches += fix-gcc16-sfinae-qanystringview.patch $(package)_patches += fix-macos26-qyield.patch $(package)_patches += fix-qbytearray-include.patch +$(package)_qtshadertools_file_name=$(qt_details_qtshadertools_file_name) +$(package)_qtshadertools_sha256_hash=$(qt_details_qtshadertools_sha256_hash) + +$(package)_qtdeclarative_file_name=$(qt_details_qtdeclarative_file_name) +$(package)_qtdeclarative_sha256_hash=$(qt_details_qtdeclarative_sha256_hash) + $(package)_qttranslations_file_name=$(qt_details_qttranslations_file_name) $(package)_qttranslations_sha256_hash=$(qt_details_qttranslations_sha256_hash) $(package)_qttools_file_name=$(qt_details_qttools_file_name) $(package)_qttools_sha256_hash=$(qt_details_qttools_sha256_hash) -$(package)_extra_sources := $($(package)_qttranslations_file_name) +$(package)_extra_sources := $($(package)_qtshadertools_file_name) +$(package)_extra_sources += $($(package)_qtdeclarative_file_name) +$(package)_extra_sources += $($(package)_qttranslations_file_name) $(package)_extra_sources += $($(package)_qttools_file_name) $(package)_top_download_path=$(qt_details_top_download_path) @@ -127,6 +135,9 @@ $(package)_config_opts += -no-feature-macdeployqt $(package)_config_opts += -no-feature-qmake $(package)_config_opts += -no-feature-windeployqt +# QML features. +$(package)_config_opts += -no-feature-qml-profiler + ifeq ($(host),$(build)) # Qt Tools module. $(package)_config_opts += -feature-linguist @@ -224,6 +235,8 @@ endef define $(package)_fetch_cmds $(call fetch_file,$(package),$($(package)_download_path),$($(package)_download_file),$($(package)_file_name),$($(package)_sha256_hash)) && \ +$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtshadertools_file_name),$($(package)_qtshadertools_file_name),$($(package)_qtshadertools_sha256_hash)) && \ +$(call fetch_file,$(package),$($(package)_download_path),$($(package)_qtdeclarative_file_name),$($(package)_qtdeclarative_file_name),$($(package)_qtdeclarative_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttranslations_file_name),$($(package)_qttranslations_file_name),$($(package)_qttranslations_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_download_path),$($(package)_qttools_file_name),$($(package)_qttools_file_name),$($(package)_qttools_sha256_hash)) && \ $(call fetch_file,$(package),$($(package)_top_download_path),$($(package)_top_cmakelists_download_file),$($(package)_top_cmakelists_file_name)-$($(package)_version),$($(package)_top_cmakelists_sha256_hash)) && \ @@ -235,6 +248,8 @@ ifeq ($(host),$(build)) define $(package)_extract_cmds mkdir -p $($(package)_extract_dir) && \ echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtshadertools_sha256_hash) $($(package)_source_dir)/$($(package)_qtshadertools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtdeclarative_sha256_hash) $($(package)_source_dir)/$($(package)_qtdeclarative_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_qttranslations_sha256_hash) $($(package)_source_dir)/$($(package)_qttranslations_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_qttools_sha256_hash) $($(package)_source_dir)/$($(package)_qttools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ @@ -243,6 +258,10 @@ define $(package)_extract_cmds $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ mkdir -p qtbase && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source) -C qtbase && \ + mkdir -p qtshadertools && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtshadertools_file_name) -C qtshadertools && \ + mkdir -p qtdeclarative && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtdeclarative_file_name) -C qtdeclarative && \ mkdir -p qttranslations && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qttranslations_file_name) -C qttranslations && \ mkdir -p qttools && \ @@ -256,12 +275,18 @@ else define $(package)_extract_cmds mkdir -p $($(package)_extract_dir) && \ echo "$($(package)_sha256_hash) $($(package)_source)" > $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtshadertools_sha256_hash) $($(package)_source_dir)/$($(package)_qtshadertools_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ + echo "$($(package)_qtdeclarative_sha256_hash) $($(package)_source_dir)/$($(package)_qtdeclarative_file_name)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmakelists_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmake_ecmoptionaladdsubdirectory_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ echo "$($(package)_top_cmake_qttoplevelhelpers_sha256_hash) $($(package)_source_dir)/$($(package)_top_cmake_qttoplevelhelpers_file_name)-$($(package)_version)" >> $($(package)_extract_dir)/.$($(package)_file_name).hash && \ $(build_SHA256SUM) -c $($(package)_extract_dir)/.$($(package)_file_name).hash && \ mkdir -p qtbase && \ $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source) -C qtbase && \ + mkdir -p qtshadertools && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtshadertools_file_name) -C qtshadertools && \ + mkdir qtdeclarative && \ + $(build_TAR) --no-same-owner --strip-components=1 -xf $($(package)_source_dir)/$($(package)_qtdeclarative_file_name) -C qtdeclarative && \ cp $($(package)_source_dir)/$($(package)_top_cmakelists_file_name)-$($(package)_version) ./$($(package)_top_cmakelists_file_name) && \ mkdir -p cmake && \ cp $($(package)_source_dir)/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name)-$($(package)_version) cmake/$($(package)_top_cmake_ecmoptionaladdsubdirectory_file_name) && \ diff --git a/depends/packages/qt_details.mk b/depends/packages/qt_details.mk index e49ac6a287..1e7264ab1a 100644 --- a/depends/packages/qt_details.mk +++ b/depends/packages/qt_details.mk @@ -5,6 +5,12 @@ qt_details_suffix := everywhere-src-$(qt_details_version).tar.xz qt_details_qtbase_file_name := qtbase-$(qt_details_suffix) qt_details_qtbase_sha256_hash := 56001b905601bb9023d399f3ba780d7fa940f3e4861e496a7c490331f49e0b80 +qt_details_qtshadertools_file_name = qtshadertools-$(qt_details_suffix) +qt_details_qtshadertools_sha256_hash = f6ec88bf42deba84d8f6b5d0914636ceed4749ccb51d1945b2f79b322b7ecf47 + +qt_details_qtdeclarative_file_name = qtdeclarative-$(qt_details_suffix) +qt_details_qtdeclarative_sha256_hash = 1f03a2b8f5588b4face7da87926e9b2c1372b3a32157c52df07a75067a9db1af + qt_details_qttranslations_file_name := qttranslations-$(qt_details_suffix) qt_details_qttranslations_sha256_hash := c3c61d79c3d8fe316a20b3617c64673ce5b5519b2e45535f49bee313152fa531 diff --git a/depends/patches/qt/qtbase_skip_tools.patch b/depends/patches/qt/qtbase_skip_tools.patch index eef65425d4..d5377c3f45 100644 --- a/depends/patches/qt/qtbase_skip_tools.patch +++ b/depends/patches/qt/qtbase_skip_tools.patch @@ -3,8 +3,7 @@ Skip building/installing unneeded tools: 1. Wrapper CMake scripts. 2. CI support files. 3. tracepointgen and tracegen tools. -4. Qt Look Ahead LR Parser Generator (qlalr). -5. Qt Vulkan Header Generator (qvkgen). +4. Qt Vulkan Header Generator (qvkgen). --- a/qtbase/cmake/QtBaseGlobalTargets.cmake @@ -47,11 +46,10 @@ Skip building/installing unneeded tools: --- a/qtbase/src/tools/CMakeLists.txt +++ b/qtbase/src/tools/CMakeLists.txt -@@ -11,8 +11,6 @@ if (QT_FEATURE_dbus) - add_subdirectory(qdbuscpp2xml) +@@ -12,7 +12,6 @@ if (QT_FEATURE_dbus) add_subdirectory(qdbusxml2cpp) endif() --add_subdirectory(qlalr) + add_subdirectory(qlalr) -add_subdirectory(qvkgen) if (QT_FEATURE_commandlineparser) add_subdirectory(qtpaths) diff --git a/depends/patches/qt/static_fixes.patch b/depends/patches/qt/static_fixes.patch index 1ae3f5774a..317dd3608d 100644 --- a/depends/patches/qt/static_fixes.patch +++ b/depends/patches/qt/static_fixes.patch @@ -78,3 +78,13 @@ index e8fb442dd43..e964138115c 100644 CODE "// xkb.h is using a variable called 'explicit', which is a reserved keyword in C++ #define explicit dont_use_cxx_explicit +--- a/qtbase/src/plugins/platforms/xcb/CMakeLists.txt ++++ b/qtbase/src/plugins/platforms/xcb/CMakeLists.txt +@@ -65,6 +65,7 @@ qt_internal_add_module(XcbQpaPrivate + XCB::XFIXES + XCB::XKB + XKB::XKB ++ $ + NO_UNITY_BUILD # X11 define clashes + ) + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 524c28165c..be9dd7069a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -398,6 +398,7 @@ endif() if(BUILD_GUI) add_subdirectory(qt) + add_subdirectory(qml) endif() diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 20369fd2e2..898dc0ce14 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -127,6 +127,9 @@ class Chain //! Get block hash. Height must be valid or this function will abort. virtual uint256 getBlockHash(int height) = 0; + //! Get block time, Height must be valid or this function will abort. + virtual int64_t getBlockTime(int height) = 0; + //! Check that the block is available on disk (i.e. has not been //! pruned), and contains transactions. virtual bool haveBlockOnDisk(int height) = 0; diff --git a/src/logging/timer.h b/src/logging/timer.h index 2b183822e9..1706c9e71b 100644 --- a/src/logging/timer.h +++ b/src/logging/timer.h @@ -99,13 +99,13 @@ class Timer #define LOG_TIME_MICROS_WITH_CATEGORY(end_msg, log_category) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY(end_msg, log_category) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category) #define LOG_TIME_MILLIS_WITH_CATEGORY_MSG_ONCE(end_msg, log_category) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category, /* msg_on_completion=*/false) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg, log_category, /* msg_on_completion=*/false) #define LOG_TIME_SECONDS(end_msg) \ - BCLog::Timer UNIQUE_NAME(logging_timer)(__func__, end_msg) + BCLog::Timer BITCOIN_UNIQUE_NAME(logging_timer)(__func__, end_msg) #endif // BITCOIN_LOGGING_TIMER_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 2f68f414f0..d7b0d8ecd8 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -569,6 +569,11 @@ class ChainImpl : public Chain LOCK(::cs_main); return Assert(chainman().ActiveChain()[height])->GetBlockHash(); } + int64_t getBlockTime(int height) override + { + LOCK(::cs_main); + return Assert(chainman().ActiveChain()[height])->GetBlockTime(); + } bool haveBlockOnDisk(int height) override { LOCK(::cs_main); diff --git a/src/qml/BitcoinApp/CMakeLists.txt b/src/qml/BitcoinApp/CMakeLists.txt new file mode 100644 index 0000000000..0c24a902f5 --- /dev/null +++ b/src/qml/BitcoinApp/CMakeLists.txt @@ -0,0 +1,70 @@ +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +qt6_add_qml_module(bitcoin_qml + URI BitcoinApp + VERSION 1.0 + STATIC + SOURCES + appmode.h + bitcoin.cpp + chainmodel.cpp + imageprovider.cpp + nodemodel.cpp + options_model.cpp + util.cpp + RESOURCE_PREFIX /qt/qml/ + QML_FILES + initerrormessage.qml + main.qml + NodeRunner.qml + NodeSettings.qml + OnboardingBlockclock.qml + OnboardingConnection.qml + OnboardingCover.qml + OnboardingStorageAmount.qml + OnboardingStorageLocation.qml + OnboardingStrengthen.qml + SettingsAbout.qml + SettingsConnection.qml + SettingsDeveloper.qml + SettingsStorage.qml + RESOURCES + res/fonts/Inter-Regular.otf + res/fonts/Inter-SemiBold.otf + res/icons/arrow-down.png + res/icons/arrow-up.png + res/icons/bitcoin-circle.png + res/icons/blocktime-dark.png + res/icons/blocktime-light.png + res/icons/caret-left.png + res/icons/caret-right.png + res/icons/check.png + res/icons/cross.png + res/icons/export.png + res/icons/gear.png + res/icons/info.png + res/icons/network-dark.png + res/icons/network-light.png + res/icons/storage-dark.png + res/icons/storage-light.png +) + +add_subdirectory(Components) +add_subdirectory(Controls) + +target_link_libraries(bitcoin_qml + INTERFACE + bitcoin_qmlplugin + bitcoin_qml_componentsplugin + bitcoin_qml_controlsplugin + PUBLIC + Qt6::Widgets + Qt6::Quick + PRIVATE + univalue + Boost::headers +) + +qt6_import_qml_plugins(bitcoin_qml) diff --git a/src/qml/BitcoinApp/Components/AboutOptions.qml b/src/qml/BitcoinApp/Components/AboutOptions.qml new file mode 100644 index 0000000000..8c8de755e2 --- /dev/null +++ b/src/qml/BitcoinApp/Components/AboutOptions.qml @@ -0,0 +1,63 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + spacing: 20 + Setting { + Layout.fillWidth: true + header: qsTr("Website") + actionItem: ExternalLink { + description: "bitcoincore.org" + link: "https://bitcoincore.org" + iconSource: "image://images/caret-right" + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Source code") + actionItem: ExternalLink { + description: "github.com/bitcoin/bitcoin" + link: "https://github.com/bitcoin/bitcoin" + iconSource: "image://images/caret-right" + } + } + Setting { + Layout.fillWidth: true + header: qsTr("License") + actionItem: ExternalLink { + description: "MIT" + link: "https://opensource.org/licenses/MIT" + iconSource: "image://images/caret-right" + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Version") + actionItem: ExternalLink { + description: "v22.99.0-1e7564eca8a6" + link: "https://bitcoin.org/en/download" + iconSource: "image://images/caret-right" + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Developer options") + description: qsTr("Only use these if you have development experience") + actionItem: Button { + icon.source: "image://images/caret-right" + icon.color: Theme.color.neutral9 + icon.height: 18 + icon.width: 18 + background: null + onClicked: { + aboutSwipe.incrementCurrentIndex() + } + } + } +} diff --git a/src/qml/BitcoinApp/Components/BlockClock.qml b/src/qml/BitcoinApp/Components/BlockClock.qml new file mode 100644 index 0000000000..6489898f7f --- /dev/null +++ b/src/qml/BitcoinApp/Components/BlockClock.qml @@ -0,0 +1,146 @@ +// Copyright (c) 2023 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import org.bitcoincore.qt + +Item { + id: root + + Layout.alignment: Qt.AlignCenter + implicitWidth: 200 + implicitHeight: 200 + + property alias header: mainText.text + property alias headerSize: mainText.font.pixelSize + property alias subText: subText.text + property bool synced: nodeModel.verificationProgress > 0.999 + property bool paused: false + property bool conns: true + + BlockClockDial { + id: dial + anchors.fill: parent + timeRatioList: chainModel.timeRatioList + verificationProgress: nodeModel.verificationProgress + paused: root.paused + synced: nodeModel.verificationProgress > 0.999 + backgroundColor: Theme.color.neutral2 + timeTickColor: Theme.color.neutral5 + } + + Button { + id: bitcoinIcon + background: null + icon.source: "image://images/bitcoin-circle" + icon.color: Theme.color.neutral9 + icon.width: 40 + icon.height: 40 + anchors.bottom: mainText.top + anchors.horizontalCenter: root.horizontalCenter + } + + Label { + id: mainText + anchors.centerIn: parent + font.family: "Inter" + font.styleName: "Semi Bold" + font.pixelSize: 32 + color: Theme.color.neutral9 + } + + Label { + id: subText + anchors.top: mainText.bottom + anchors.horizontalCenter: root.horizontalCenter + font.family: "Inter" + font.styleName: "Semi Bold" + font.pixelSize: 18 + color: Theme.color.neutral4 + } + + RowLayout { + id: peersIndicator + anchors.top: subText.bottom + anchors.topMargin: 20 + anchors.horizontalCenter: root.horizontalCenter + spacing: 5 + Repeater { + model: 5 + Rectangle { + width: 3 + height: width + radius: width/2 + color: Theme.color.neutral9 + } + } + } + + MouseArea { + anchors.fill: dial + onClicked: { + root.paused = !root.paused + nodeModel.pause = root.paused + } + } + + states: [ + State { + name: "intialBlockDownload"; when: !synced && !paused && conns + PropertyChanges { + target: root + header: Math.round(nodeModel.verificationProgress * 100) + "%" + subText: Math.round(nodeModel.remainingSyncTime/60000) > 0 ? Math.round(nodeModel.remainingSyncTime/60000) + "mins" : Math.round(nodeModel.remainingSyncTime/1000) + "secs" + } + }, + + State { + name: "blockClock"; when: synced && !paused && conns + PropertyChanges { + target: root + header: Number(nodeModel.blockTipHeight).toLocaleString(Qt.locale(), 'f', 0) + subText: "Blocktime" + } + }, + + State { + name: "Manual Pause"; when: paused + PropertyChanges { + target: root + header: "Paused" + headerSize: 24 + subText: "Tap to resume" + } + PropertyChanges { + target: bitcoinIcon + anchors.bottomMargin: 5 + } + PropertyChanges { + target: subText + anchors.topMargin: 4 + } + }, + + State { + name: "Connecting"; when: !paused && !conns + PropertyChanges { + target: root + header: "Connecting" + headerSize: 24 + subText: "Please Wait" + } + PropertyChanges { + target: bitcoinIcon + anchors.bottomMargin: 5 + } + PropertyChanges { + target: subText + anchors.topMargin: 4 + } + } + ] +} diff --git a/src/qml/BitcoinApp/Components/BlockCounter.qml b/src/qml/BitcoinApp/Components/BlockCounter.qml new file mode 100644 index 0000000000..cae7830499 --- /dev/null +++ b/src/qml/BitcoinApp/Components/BlockCounter.qml @@ -0,0 +1,24 @@ +// 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. + +// The BlockCounter component. + +import QtQuick +import QtQuick.Controls +import BitcoinApp.Controls + +Label { + property int blockHeight: 0 + background: Rectangle { + color: Theme.color.background + } + color: Theme.color.orange + padding: 16 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.family: "Inter" + font.styleName: "Semi Bold" + font.pixelSize: 20 + text: blockHeight +} diff --git a/src/qml/BitcoinApp/Components/CMakeLists.txt b/src/qml/BitcoinApp/Components/CMakeLists.txt new file mode 100644 index 0000000000..6fa055cc10 --- /dev/null +++ b/src/qml/BitcoinApp/Components/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +qt6_add_qml_module(bitcoin_qml_components + URI BitcoinApp.Components + VERSION 1.0 + STATIC + SOURCES + blockclockdial.cpp + RESOURCE_PREFIX /qt/qml/ + QML_FILES + AboutOptions.qml + BlockClock.qml + BlockCounter.qml + ConnectionOptions.qml + ConnectionSettings.qml + DeveloperOptions.qml + StorageLocations.qml + StorageOptions.qml + StorageSettings.qml +) + +target_link_libraries(bitcoin_qml_components + PRIVATE + Qt6::Quick +) diff --git a/src/qml/BitcoinApp/Components/ConnectionOptions.qml b/src/qml/BitcoinApp/Components/ConnectionOptions.qml new file mode 100644 index 0000000000..05bef0a2f8 --- /dev/null +++ b/src/qml/BitcoinApp/Components/ConnectionOptions.qml @@ -0,0 +1,35 @@ +// 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. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + ButtonGroup { + id: group + } + spacing: 15 + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Fast always on") + description: qsTr("Loads quickly at all times and uses as much cellular data as needed.") + recommended: true + } + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + checked: true + text: qsTr("Slow always on") + description: qsTr("Loads at all times with reduced cellular data usage.") + } + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Only when on Wi-Fi") + description: qsTr("Loads quickly when on wi-fi and pauses when on cellular data.") + } +} diff --git a/src/qml/BitcoinApp/Components/ConnectionSettings.qml b/src/qml/BitcoinApp/Components/ConnectionSettings.qml new file mode 100644 index 0000000000..1f945c2130 --- /dev/null +++ b/src/qml/BitcoinApp/Components/ConnectionSettings.qml @@ -0,0 +1,49 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + spacing: 20 + Setting { + Layout.fillWidth: true + header: qsTr("Enable listening") + description: qsTr("Allows incoming connections") + actionItem: OptionSwitch { + checked: optionsModel.listen + onToggled: optionsModel.listen = checked + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Map port using PCP or NAT-PMP") + actionItem: OptionSwitch { + checked: optionsModel.natpmp + onToggled: optionsModel.natpmp = checked + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Enable RPC server") + actionItem: OptionSwitch { + checked: optionsModel.server + onToggled: optionsModel.server = checked + } + } + Setting { + last: true + Layout.fillWidth: true + header: qsTr("Proxy settings") + actionItem: Button { + icon.source: "image://images/caret-right" + icon.color: Theme.color.neutral9 + icon.height: 18 + icon.width: 18 + background: null + } + } +} diff --git a/src/qml/BitcoinApp/Components/DeveloperOptions.qml b/src/qml/BitcoinApp/Components/DeveloperOptions.qml new file mode 100644 index 0000000000..f28ab2658c --- /dev/null +++ b/src/qml/BitcoinApp/Components/DeveloperOptions.qml @@ -0,0 +1,44 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + spacing: 20 + Setting { + Layout.fillWidth: true + header: qsTr("Developer documentation") + actionItem: ExternalLink { + iconSource: ":/qt/qml/BitcoinApp/res/icons/export" + iconWidth: 30 + iconHeight: 30 + link: "https://bitcoin.org/en/bitcoin-core/contribute/documentation" + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Database cache size") + actionItem: ValueInput { + description: ("450 MiB") + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Script verification threads") + actionItem: ValueInput { + description: ("0") + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Dark Mode") + actionItem: OptionSwitch { + checked: Theme.dark + onToggled: Theme.toggleDark() + } + } +} diff --git a/src/qml/BitcoinApp/Components/StorageLocations.qml b/src/qml/BitcoinApp/Components/StorageLocations.qml new file mode 100644 index 0000000000..9c41f8d36b --- /dev/null +++ b/src/qml/BitcoinApp/Components/StorageLocations.qml @@ -0,0 +1,29 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + ButtonGroup { + id: group + } + spacing: 15 + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Default") + description: qsTr("Your application directory.") + recommended: true + checked: true + } + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Custom") + description: qsTr("Choose the directory and storage device.") + } +} diff --git a/src/qml/BitcoinApp/Components/StorageOptions.qml b/src/qml/BitcoinApp/Components/StorageOptions.qml new file mode 100644 index 0000000000..ba133fbf7b --- /dev/null +++ b/src/qml/BitcoinApp/Components/StorageOptions.qml @@ -0,0 +1,36 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + ButtonGroup { + id: group + } + spacing: 15 + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Reduce storage") + description: qsTr("Uses about 2GB. For simple wallet use.") + recommended: true + checked: true + onClicked: { + optionsModel.prune = true + optionsModel.pruneSizeGB = 2 + } + } + OptionButton { + Layout.fillWidth: true + ButtonGroup.group: group + text: qsTr("Store all data") + description: qsTr("Uses about 550GB. Support the network.") + onClicked: { + optionsModel.prune = false + } + } +} diff --git a/src/qml/BitcoinApp/Components/StorageSettings.qml b/src/qml/BitcoinApp/Components/StorageSettings.qml new file mode 100644 index 0000000000..09418f8199 --- /dev/null +++ b/src/qml/BitcoinApp/Components/StorageSettings.qml @@ -0,0 +1,28 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ColumnLayout { + spacing: 20 + Setting { + Layout.fillWidth: true + header: qsTr("Store recent blocks only") + actionItem: OptionSwitch { + checked: optionsModel.prune + onToggled: optionsModel.prune = checked + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Storage limit (GB)") + actionItem: ValueInput { + description: optionsModel.pruneSizeGB + onEditingFinished: optionsModel.pruneSizeGB = parseInt(text) + } + } +} diff --git a/src/qml/BitcoinApp/Components/blockclockdial.cpp b/src/qml/BitcoinApp/Components/blockclockdial.cpp new file mode 100644 index 0000000000..772e7d957d --- /dev/null +++ b/src/qml/BitcoinApp/Components/blockclockdial.cpp @@ -0,0 +1,196 @@ +// Copyright (c) 2023 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 + +BlockClockDial::BlockClockDial(QQuickItem *parent) +: QQuickPaintedItem(parent) +, m_time_ratio_list{0.0} +, m_background_color{QColor("#2D2D2D")} +, m_time_tick_color{QColor("#000000")} +{ +} + +void BlockClockDial::setTimeRatioList(QVariantList new_list) +{ + m_time_ratio_list = new_list; + update(); +} + +void BlockClockDial::setVerificationProgress(double progress) +{ + m_verification_progress = progress; + update(); +} + +void BlockClockDial::setSynced(bool synced) +{ + m_is_synced = synced; + update(); +} + +void BlockClockDial::setPaused(bool paused) +{ + m_is_paused = paused; + update(); +} + +void BlockClockDial::setBackgroundColor(QColor color) +{ + m_background_color = color; + update(); +} + +void BlockClockDial::setTimeTickColor(QColor color) +{ + m_time_tick_color = color; + update(); +} + +QRectF BlockClockDial::getBoundsForPen(const QPen & pen) +{ + const QRectF bounds = boundingRect(); + const qreal smallest = qMin(bounds.width(), bounds.height()); + QRectF rect = QRectF(pen.widthF() / 2.0 + 1, pen.widthF() / 2.0 + 1, smallest - pen.widthF() - 2, smallest - pen.widthF() - 2); + rect.moveCenter(bounds.center()); + + // Make sure the arc is aligned to whole pixels. + if (rect.x() - int(rect.x()) > 0) + rect.setX(qCeil(rect.x())); + if (rect.y() - int(rect.y()) > 0) + rect.setY(qCeil(rect.y())); + if (rect.width() - int(rect.width()) > 0) + rect.setWidth(qFloor(rect.width())); + if (rect.height() - int(rect.height()) > 0) + rect.setHeight(qFloor(rect.height())); + + return rect; +} + +void BlockClockDial::paintBlocks(QPainter * painter) +{ + int numberOfBlocks = m_time_ratio_list.length(); + if (numberOfBlocks < 2) { + return; + } + + QPen pen(QColor("#F1D54A")); + pen.setWidth(4); + pen.setCapStyle(Qt::FlatCap); + const QRectF bounds = getBoundsForPen(pen); + painter->setPen(pen); + + QColor confirmationColors[] = { + QColor("#FF1C1C"), // red + QColor("#ED6E46"), + QColor("#EE8847"), + QColor("#EFA148"), + QColor("#F0BB49"), + QColor("#F1D54A"), // yellow + }; + + // The gap is calculated here and is used to create a + // one pixel spacing between each block + double gap = degreesPerPixel(); + + // Paint blocks + for (int i = 1; i < numberOfBlocks; i++) { + if (numberOfBlocks - i <= 6) { + QPen pen(confirmationColors[numberOfBlocks - i - 1]); + pen.setWidth(4); + pen.setCapStyle(Qt::FlatCap); + painter->setPen(pen); + } + + const qreal startAngle = 90 + (-360 * m_time_ratio_list[i].toDouble()); + qreal nextAngle; + if (i == numberOfBlocks - 1) { + nextAngle = 90 + (-360 * m_time_ratio_list[0].toDouble()); + } else { + nextAngle = 90 + (-360 * m_time_ratio_list[i+1].toDouble()); + } + const qreal spanAngle = -1 * (startAngle - nextAngle) + gap; + QPainterPath path; + path.arcMoveTo(bounds, startAngle); + path.arcTo(bounds, startAngle, spanAngle); + painter->drawPath(path); + } +} + +void BlockClockDial::paintProgress(QPainter * painter) +{ + QPen pen(QColor("#F1D54A")); + pen.setWidthF(4); + pen.setCapStyle(Qt::RoundCap); + const QRectF bounds = getBoundsForPen(pen); + painter->setPen(pen); + + // QPainter::drawArc uses positive values for counter clockwise - the opposite of our API - + // so we must reverse the angles with * -1. Also, our angle origin is at 12 o'clock, whereas + // QPainter's is 3 o'clock, hence - 90. + const qreal startAngle = 90; + const qreal spanAngle = verificationProgress() * -360; + + // QPainter::drawArc parameters are 1/16 of a degree + painter->drawArc(bounds, startAngle * 16, spanAngle * 16); +} + +void BlockClockDial::paintBackground(QPainter * painter) +{ + QPen pen(m_background_color); + pen.setWidthF(4); + const QRectF bounds = getBoundsForPen(pen); + painter->setPen(pen); + + painter->drawEllipse(bounds); +} + +double BlockClockDial::degreesPerPixel() +{ + double circumference = width() * 3.1415926; + return 360 / circumference; +} + +void BlockClockDial::paintTimeTicks(QPainter * painter) +{ + QPen pen(m_time_tick_color); + pen.setWidthF(4); + // Calculate bound based on width of default pen + const QRectF bounds = getBoundsForPen(pen); + + QPen time_tick_pen = QPen(m_time_tick_color); + time_tick_pen.setWidth(2); + time_tick_pen.setCapStyle(Qt::RoundCap); + painter->setPen(time_tick_pen); + for (double angle = 0; angle < 360; angle += 30) { + QPainterPath path; + path.arcMoveTo(bounds, angle); + path.arcTo(bounds, angle, degreesPerPixel()); + painter->drawPath(path); + } +} + +void BlockClockDial::paint(QPainter * painter) +{ + if (width() <= 0 || height() <= 0) { + return; + } + painter->setRenderHint(QPainter::Antialiasing); + + paintBackground(painter); + paintTimeTicks(painter); + + if (paused()) return; + + if (synced()) { + paintBlocks(painter); + } else { + paintProgress(painter); + } +} diff --git a/src/qml/BitcoinApp/Components/blockclockdial.h b/src/qml/BitcoinApp/Components/blockclockdial.h new file mode 100644 index 0000000000..70ab7082ef --- /dev/null +++ b/src/qml/BitcoinApp/Components/blockclockdial.h @@ -0,0 +1,56 @@ +// Copyright (c) 2022 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_COMPONENTS_BLOCKCLOCKDIAL_H +#define BITCOIN_QML_COMPONENTS_BLOCKCLOCKDIAL_H + +#include +#include + +class BlockClockDial : public QQuickPaintedItem +{ + Q_OBJECT + Q_PROPERTY(QVariantList timeRatioList READ timeRatioList WRITE setTimeRatioList) + Q_PROPERTY(double verificationProgress READ verificationProgress WRITE setVerificationProgress) + Q_PROPERTY(bool synced READ synced WRITE setSynced) + Q_PROPERTY(bool paused READ paused WRITE setPaused) + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_PROPERTY(QColor timeTickColor READ timeTickColor WRITE setTimeTickColor) + +public: + explicit BlockClockDial(QQuickItem * parent = nullptr); + void paint(QPainter * painter) override; + + QVariantList timeRatioList() const { return m_time_ratio_list; }; + double verificationProgress() const { return m_verification_progress; }; + bool synced() const { return m_is_synced; }; + bool paused() const { return m_is_paused; }; + QColor backgroundColor() const { return m_background_color; }; + QColor timeTickColor() const { return m_time_tick_color; }; + +public Q_SLOTS: + void setTimeRatioList(QVariantList new_time); + void setVerificationProgress(double progress); + void setSynced(bool synced); + void setPaused(bool paused); + void setBackgroundColor(QColor color); + void setTimeTickColor(QColor color); + +private: + void paintProgress(QPainter * painter); + void paintBlocks(QPainter * painter); + void paintBackground(QPainter * painter); + void paintTimeTicks(QPainter * painter); + QRectF getBoundsForPen(const QPen & pen); + double degreesPerPixel(); + + QVariantList m_time_ratio_list; + double m_verification_progress; + bool m_is_synced; + bool m_is_paused; + QColor m_background_color; + QColor m_time_tick_color; +}; + +#endif // BITCOIN_QML_COMPONENTS_BLOCKCLOCKDIAL_H diff --git a/src/qml/BitcoinApp/Controls/CMakeLists.txt b/src/qml/BitcoinApp/Controls/CMakeLists.txt new file mode 100644 index 0000000000..cf0455f7d5 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/CMakeLists.txt @@ -0,0 +1,31 @@ +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +set_source_files_properties( + Theme.qml + PROPERTIES QT_QML_SINGLETON_TYPE TRUE +) + +qt6_add_qml_module(bitcoin_qml_controls + URI BitcoinApp.Controls + VERSION 1.0 + STATIC + RESOURCE_PREFIX /qt/qml/ + QML_FILES + ContinueButton.qml + ExternalLink.qml + Header.qml + InformationPage.qml + NavButton.qml + NavigationBar.qml + OptionButton.qml + OptionSwitch.qml + OutlineButton.qml + PageIndicator.qml + ProgressIndicator.qml + Setting.qml + TextButton.qml + Theme.qml + ValueInput.qml +) diff --git a/src/qml/BitcoinApp/Controls/ContinueButton.qml b/src/qml/BitcoinApp/Controls/ContinueButton.qml new file mode 100644 index 0000000000..ef95a2f214 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/ContinueButton.qml @@ -0,0 +1,25 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls + +Button { + font.family: "Inter" + font.styleName: "Semi Bold" + font.pixelSize: 18 + contentItem: Text { + text: parent.text + font: parent.font + color: Theme.color.white + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + background: Rectangle { + implicitHeight: 46 + implicitWidth: 300 + color: Theme.color.orange + radius: 5 + } +} diff --git a/src/qml/BitcoinApp/Controls/ExternalLink.qml b/src/qml/BitcoinApp/Controls/ExternalLink.qml new file mode 100644 index 0000000000..7a26f5afea --- /dev/null +++ b/src/qml/BitcoinApp/Controls/ExternalLink.qml @@ -0,0 +1,49 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Control { + id: root + required property string link + property string description: "" + property int descriptionSize: 18 + property url iconSource: "" + property int iconWidth: 18 + property int iconHeight: 18 + + contentItem: RowLayout { + spacing: 0 + width: parent.width + Loader { + Layout.fillWidth: true + active: root.description.length > 0 + visible: active + sourceComponent: Text { + font.family: "Inter" + font.styleName: "Regular" + font.pixelSize: root.descriptionSize + color: Theme.color.neutral7 + textFormat: Text.RichText + text: "" + "" + root.description + "" + onLinkActivated: Qt.openUrlExternally(link) + } + } + Loader { + Layout.fillWidth: true + active: root.iconSource.toString().length > 0 + visible: active + sourceComponent: Button { + icon.source: root.iconSource + icon.color: Theme.color.neutral9 + icon.height: root.iconHeight + icon.width: root.iconWidth + background: null + onClicked: Qt.openUrlExternally(link) + } + } + } +} diff --git a/src/qml/BitcoinApp/Controls/Header.qml b/src/qml/BitcoinApp/Controls/Header.qml new file mode 100644 index 0000000000..f66975b59c --- /dev/null +++ b/src/qml/BitcoinApp/Controls/Header.qml @@ -0,0 +1,68 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ColumnLayout { + id: root + property bool center: true + required property string header + property int headerMargin + property int headerSize: 28 + property bool headerBold: false + property string description: "" + property int descriptionMargin: 10 + property int descriptionSize: 18 + property string descriptionColor: Theme.color.neutral8 + property bool descriptionBold: false + property string subtext: "" + property int subtextMargin + property int subtextSize: 15 + property bool wrap: true + + spacing: 0 + Label { + Layout.fillWidth: true + topPadding: root.headerMargin + font.family: "Inter" + font.styleName: root.headerBold ? "Semi Bold" : "Regular" + font.pixelSize: root.headerSize + color: Theme.color.neutral9 + text: root.header + horizontalAlignment: center ? Text.AlignHCenter : Text.AlignLeft + wrapMode: wrap ? Text.WordWrap : Text.NoWrap + } + Loader { + Layout.fillWidth: true + active: root.description.length > 0 + visible: active + sourceComponent: Label { + topPadding: root.descriptionMargin + font.family: "Inter" + font.styleName: root.descriptionBold ? "Semi Bold" : "Regular" + font.pixelSize: root.descriptionSize + color: root.descriptionColor + text: root.description + horizontalAlignment: root.center ? Text.AlignHCenter : Text.AlignLeft + wrapMode: wrap ? Text.WordWrap : Text.NoWrap + } + } + Loader { + Layout.fillWidth: true + active: root.subtext.length > 0 + visible: active + sourceComponent: Label { + topPadding: root.subtextMargin + font.family: "Inter" + font.styleName: "Regular" + font.pixelSize: root.subtextSize + color: Theme.color.neutral9 + text: root.subtext + horizontalAlignment: root.center ? Text.AlignHCenter : Text.AlignLeft + wrapMode: wrap ? Text.WordWrap : Text.NoWrap + } + } +} diff --git a/src/qml/BitcoinApp/Controls/InformationPage.qml b/src/qml/BitcoinApp/Controls/InformationPage.qml new file mode 100644 index 0000000000..e4d891c859 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/InformationPage.qml @@ -0,0 +1,145 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import org.bitcoincore.qt + +Page { + id: root + implicitHeight: parent.height + property alias bannerItem: banner_loader.sourceComponent + property alias detailItem: detail_loader.sourceComponent + property alias navLeftDetail: navbar.leftDetail + property alias navMiddleDetail: navbar.middleDetail + property alias navRightDetail: navbar.rightDetail + property string buttonText: "" + property bool bannerActive: true + property bool detailActive: false + property bool lastPage: false + property bool bold: false + property bool center: true + property int bannerMargin: 20 + required property string headerText + property int headerMargin: 30 + property int headerSize: 28 + property string description: "" + property int descriptionMargin: 20 + property int descriptionSize: 18 + property string subtext: "" + property int subtextMargin: 30 + property int subtextSize: 15 + property real maximumWidth: 600 + property real detailMaximumWidth: 450 + + background: null + clip: true + + header: NavigationBar { + id: navbar + } + + ScrollView { + id: scrollView + width: parent.width + height: parent.height + clip: true + contentWidth: width + + ColumnLayout { + id: information + width: Math.min(parent.width, 600) + anchors.horizontalCenter: parent.horizontalCenter + spacing: 0 + Loader { + id: banner_loader + active: root.bannerActive + visible: active + Layout.alignment: Qt.AlignCenter + Layout.topMargin: root.bannerMargin + sourceComponent: root.bannerItem + } + Header { + Layout.fillWidth: true + Layout.leftMargin: 20 + Layout.rightMargin: 20 + headerBold: root.bold + center: root.center + header: root.headerText + headerMargin: root.headerMargin + headerSize: root.headerSize + description: root.description + descriptionMargin: root.descriptionMargin + descriptionSize: root.descriptionSize + subtext: root.subtext + subtextMargin: root.subtextMargin + subtextSize: root.subtextSize + } + Loader { + id: detail_loader + active: root.detailActive + visible: active + Layout.fillWidth: true + Layout.alignment: Qt.AlignCenter + Layout.topMargin: 30 + Layout.leftMargin: 20 + Layout.rightMargin: 20 + Layout.maximumWidth: detailMaximumWidth + sourceComponent: root.detailItem + } + } + ContinueButton { + id: continueButton + visible: root.buttonText.length > 0 + enabled: visible + width: Math.min(300, parent.width - 2 * anchors.leftMargin) + anchors.topMargin: 40 + anchors.bottomMargin: 60 + anchors.leftMargin: 20 + anchors.rightMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + text: root.buttonText + onClicked: root.lastPage ? swipeView.finished = true : swipeView.incrementCurrentIndex() + } + } + + state: AppMode.state + + states: [ + State { + name: "MOBILE" + AnchorChanges { + target: continueButton + anchors.top: undefined + anchors.bottom: continueButton.parent.bottom + } + PropertyChanges { + target: scrollView + contentHeight: { + var combinedHeight = information.height + continueButton.height + + continueButton.anchors.topMargin + continueButton.anchors.bottomMargin + if (scrollView.height < combinedHeight) { + return combinedHeight + } else { + return scrollView.height + } + } + } + }, + State { + name: "DESKTOP" + AnchorChanges { + target: continueButton + anchors.top: information.bottom + anchors.bottom: undefined + } + PropertyChanges { + target: scrollView + contentHeight: information.height + continueButton.height + + continueButton.anchors.topMargin + continueButton.anchors.bottomMargin + } + } + ] +} diff --git a/src/qml/BitcoinApp/Controls/NavButton.qml b/src/qml/BitcoinApp/Controls/NavButton.qml new file mode 100644 index 0000000000..a478a8fe55 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/NavButton.qml @@ -0,0 +1,59 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +AbstractButton { + id: root + property int iconHeight: 30 + property int iconWidth: 30 + property int textSize: 18 + property url iconSource: "" + + padding: 0 + background: null + contentItem: RowLayout { + anchors.fill: parent + spacing: 0 + Loader { + id: button_background + active: root.iconSource.toString().length > 0 + visible: active + sourceComponent: Button { + id: icon_button + padding: 0 + display: AbstractButton.IconOnly + height: root.iconHeight + width: root.iconWidth + icon.source: root.iconSource + icon.color: Theme.color.neutral9 + icon.height: root.iconHeight + icon.width: root.iconWidth + background: null + onClicked: root.clicked() + } + } + Loader { + active: root.text.length > 0 + visible: active + sourceComponent: AbstractButton { + id: container + padding: 0 + font.family: "Inter" + font.styleName: "Semi Bold" + font.pixelSize: root.textSize + background: null + contentItem: Text { + anchors.verticalCenter: parent.verticalCenter + font: container.font + color: Theme.color.neutral9 + text: root.text + } + onClicked: root.clicked() + } + } + } +} diff --git a/src/qml/BitcoinApp/Controls/NavigationBar.qml b/src/qml/BitcoinApp/Controls/NavigationBar.qml new file mode 100644 index 0000000000..a7c9131e14 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/NavigationBar.qml @@ -0,0 +1,49 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +RowLayout { + id: root + property alias leftDetail: left_detail.sourceComponent + property alias middleDetail: middle_detail.sourceComponent + property alias rightDetail: right_detail.sourceComponent + + height: 46 + spacing: 0 + Layout.fillWidth: true + RowLayout { + Layout.preferredWidth: parent.width / 3 + Loader { + Layout.alignment: Qt.AlignLeft + id: left_detail + active: true + visible: active + sourceComponent: root.leftDetail + } + } + RowLayout { + Layout.preferredWidth: parent.width / 3 + Loader { + Layout.alignment: Qt.AlignHCenter + id: middle_detail + active: true + visible: active + sourceComponent: root.middleDetail + } + } + RowLayout { + Layout.preferredWidth: parent.width / 3 + Loader { + id: right_detail + Layout.rightMargin: 10 + Layout.alignment: Qt.AlignRight + active: true + visible: active + sourceComponent: root.rightDetail + } + } +} diff --git a/src/qml/BitcoinApp/Controls/OptionButton.qml b/src/qml/BitcoinApp/Controls/OptionButton.qml new file mode 100644 index 0000000000..1f07fdb2b3 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/OptionButton.qml @@ -0,0 +1,80 @@ +// 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. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +Button { + property string description + property bool recommended: false + id: button + padding: 15 + checkable: true + implicitWidth: 450 + background: Rectangle { + border.width: 1 + border.color: button.checked ? Theme.color.orange : button.hovered ? Theme.color.neutral9 : Theme.color.neutral5 + radius: 10 + color: "transparent" + Rectangle { + visible: button.visualFocus + anchors.fill: parent + anchors.margins: -4 + border.width: 2 + border.color: Theme.color.orange + radius: 14 + color: "transparent" + opacity: 0.4 + } + } + contentItem: RowLayout { + spacing: 3 + ColumnLayout { + spacing: 3 + Header { + Layout.fillWidth: true + Layout.preferredWidth: 0 + center: false + header: button.text + headerSize: 18 + headerMargin: 0 + description: button.description + descriptionSize: 15 + descriptionMargin: 0 + } + Loader { + Layout.topMargin: 2 + active: button.recommended + visible: active + sourceComponent: Label { + background: Rectangle { + color: Theme.color.neutral9 + radius: 3 + } + font.styleName: "Regular" + font.pixelSize: 13 + topPadding: 3 + rightPadding: 7 + bottomPadding: 4 + leftPadding: 7 + color: Theme.color.neutral0 + text: qsTr("Recommended") + } + } + } + Loader { + id: detail_loader + visible: button.checked + active: true + sourceComponent: Button { + icon.source: "image://images/check" + icon.color: Theme.color.neutral9 + icon.height: 24 + icon.width: 24 + background: null + } + } + } +} diff --git a/src/qml/BitcoinApp/Controls/OptionSwitch.qml b/src/qml/BitcoinApp/Controls/OptionSwitch.qml new file mode 100644 index 0000000000..a8c95d3a19 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/OptionSwitch.qml @@ -0,0 +1,29 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls + +Switch { + id: root + implicitWidth: 45 + implicitHeight: 28 + background: Rectangle { + radius: Math.floor(height / 2) + color: root.checked ? Theme.color.orange : Theme.color.neutral4 + } + indicator: Rectangle { + property real _margin: Math.round((parent.height - height) / 2) + y: Math.round(parent.height - height) / 2 + x: root.checked ? parent.width - width - _margin : _margin + radius: 10 + width: 20 + height: 20 + color: Theme.color.white + Behavior on x { + SmoothedAnimation { + } + } + } +} diff --git a/src/qml/BitcoinApp/Controls/OutlineButton.qml b/src/qml/BitcoinApp/Controls/OutlineButton.qml new file mode 100644 index 0000000000..b714ceea3a --- /dev/null +++ b/src/qml/BitcoinApp/Controls/OutlineButton.qml @@ -0,0 +1,29 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls + +Button { + font.family: "Inter" + font.styleName: "Semi Bold" + font.pixelSize: 18 + contentItem: Text { + text: parent.text + font: parent.font + color: Theme.color.neutral9 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + background: Rectangle { + implicitHeight: 46 + implicitWidth: 340 + color: Theme.color.background + radius: 5 + border { + width: 1 + color: Theme.color.neutral4 + } + } +} diff --git a/src/qml/BitcoinApp/Controls/PageIndicator.qml b/src/qml/BitcoinApp/Controls/PageIndicator.qml new file mode 100644 index 0000000000..1fd3c74d1e --- /dev/null +++ b/src/qml/BitcoinApp/Controls/PageIndicator.qml @@ -0,0 +1,22 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls + +PageIndicator { + id: root + delegate: Rectangle { + implicitWidth: 10 + implicitHeight: 10 + radius: Math.round(width / 2) + color: Theme.color.neutral9 + opacity: index === root.currentIndex ? 0.95 : pressed ? 0.7 : 0.45 + Behavior on opacity { + OpacityAnimator { + duration: 100 + } + } + } +} diff --git a/src/qml/BitcoinApp/Controls/ProgressIndicator.qml b/src/qml/BitcoinApp/Controls/ProgressIndicator.qml new file mode 100644 index 0000000000..113b32034d --- /dev/null +++ b/src/qml/BitcoinApp/Controls/ProgressIndicator.qml @@ -0,0 +1,33 @@ +// 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. + +import QtQuick +import QtQuick.Controls + +Control { + property real progress: 0 + Behavior on progress { + NumberAnimation { + easing.type: Easing.Bezier + easing.bezierCurve: [0.5, 0.0, 0.2, 1, 1, 1] + duration: 250 + } + } + contentItem: Rectangle { + implicitHeight: 6 + radius: Math.floor(height / 2) + color: Theme.color.neutral3 + Item { + width: Math.round(progress * contentItem.width) + height: parent.height + clip: true + Rectangle { + width: contentItem.width + height: contentItem.height + radius: contentItem.radius + color: Theme.color.orange + } + } + } +} diff --git a/src/qml/BitcoinApp/Controls/Setting.qml b/src/qml/BitcoinApp/Controls/Setting.qml new file mode 100644 index 0000000000..88c70cb526 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/Setting.qml @@ -0,0 +1,44 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +ColumnLayout { + id: root + property bool last: parent && root === parent.children[parent.children.length - 1] + required property string header + property alias actionItem: action_loader.sourceComponent + property string description + + spacing: 20 + RowLayout { + Header { + Layout.fillWidth: true + center: false + header: root.header + headerSize: 18 + description: root.description + descriptionSize: 15 + descriptionMargin: 0 + } + Loader { + id: action_loader + active: true + visible: active + sourceComponent: root.actionItem + } + } + Loader { + Layout.fillWidth: true + Layout.columnSpan: 2 + active: !last + visible: active + sourceComponent: Rectangle { + height: 1 + color: Theme.color.neutral5 + } + } +} diff --git a/src/qml/BitcoinApp/Controls/TextButton.qml b/src/qml/BitcoinApp/Controls/TextButton.qml new file mode 100644 index 0000000000..30c6954f0a --- /dev/null +++ b/src/qml/BitcoinApp/Controls/TextButton.qml @@ -0,0 +1,25 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls + +Button { + id: root + property int textSize: 18 + property string textColor: Theme.color.orange + property bool bold: true + property bool rightalign: false + font.family: "Inter" + font.styleName: bold ? "Semi Bold" : "Regular" + font.pixelSize: root.textSize + contentItem: Text { + text: root.text + font: root.font + color: root.textColor + horizontalAlignment: rightalign ? Text.AlignRight : Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + background: null +} diff --git a/src/qml/BitcoinApp/Controls/Theme.qml b/src/qml/BitcoinApp/Controls/Theme.qml new file mode 100644 index 0000000000..73e30c2bb0 --- /dev/null +++ b/src/qml/BitcoinApp/Controls/Theme.qml @@ -0,0 +1,95 @@ +pragma Singleton +import QtQuick +import QtQuick.Controls + +Control { + property bool dark: true + readonly property ColorSet color: dark ? darkColorSet : lightColorSet + readonly property ImageSet image: dark ? darkImageSet : lightImageSet + + component ColorSet: QtObject { + required property color white + required property color background + required property color orange + required property color red + required property color green + required property color blue + required property color purple + required property color neutral0 + required property color neutral1 + required property color neutral2 + required property color neutral3 + required property color neutral4 + required property color neutral5 + required property color neutral6 + required property color neutral7 + required property color neutral8 + required property color neutral9 + } + + component ImageSet: QtObject { + required property url blocktime + required property url network + required property url storage + } + + ColorSet { + id: darkColorSet + white: "#FFFFFF" + background: "black" + orange: "#F89B2A" + red: "#EC6363" + green: "#36B46B" + blue: "#3CA3DE" + purple: "#C075DC" + neutral0: "#000000" + neutral1: "#1A1A1A" + neutral2: "#2D2D2D" + neutral3: "#444444" + neutral4: "#5C5C5C" + neutral5: "#787878" + neutral6: "#949494" + neutral7: "#B0B0B0" + neutral8: "#CCCCCC" + neutral9: "#FFFFFF" + } + + ColorSet { + id: lightColorSet + white: "#FFFFFF" + background: "white" + orange: "#F7931A" + red: "#EB5757" + green: "#27AE60" + blue: "#2D9CDB" + purple: "#BB6BD9" + neutral0: "#FFFFFF" + neutral1: "#F8F8F8" + neutral2: "#F4F4F4" + neutral3: "#EDEDED" + neutral4: "#DEDEDE" + neutral5: "#BBBBBB" + neutral6: "#999999" + neutral7: "#777777" + neutral8: "#404040" + neutral9: "#000000" + } + + ImageSet { + id: darkImageSet + blocktime: "image://images/blocktime-dark" + network: "image://images/network-dark" + storage: "image://images/storage-dark" + } + + ImageSet { + id: lightImageSet + blocktime: "image://images/blocktime-light" + network: "image://images/network-light" + storage: "image://images/storage-light" + } + + function toggleDark() { + dark = !dark + } +} diff --git a/src/qml/BitcoinApp/Controls/ValueInput.qml b/src/qml/BitcoinApp/Controls/ValueInput.qml new file mode 100644 index 0000000000..2034691d8d --- /dev/null +++ b/src/qml/BitcoinApp/Controls/ValueInput.qml @@ -0,0 +1,20 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls + +TextEdit { + id: root + property string description: "" + property int descriptionSize: 18 + + font.family: "Inter" + font.styleName: "Regular" + font.pixelSize: root.descriptionSize + color: Theme.color.neutral8 + text: description + horizontalAlignment: Text.AlignRight + wrapMode: Text.WordWrap +} diff --git a/src/qml/BitcoinApp/NodeRunner.qml b/src/qml/BitcoinApp/NodeRunner.qml new file mode 100644 index 0000000000..e54de0554f --- /dev/null +++ b/src/qml/BitcoinApp/NodeRunner.qml @@ -0,0 +1,24 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +Page { + background: null + clip: true + property alias navRightDetail: navbar.rightDetail + header: NavigationBar { + id: navbar + } + + Component.onCompleted: nodeModel.startNodeInitializionThread(); + + BlockClock { + anchors.centerIn: parent + } +} diff --git a/src/qml/BitcoinApp/NodeSettings.qml b/src/qml/BitcoinApp/NodeSettings.qml new file mode 100644 index 0000000000..1b2313e151 --- /dev/null +++ b/src/qml/BitcoinApp/NodeSettings.qml @@ -0,0 +1,119 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +Item { + id: nodeSettings + property alias navMiddleDetail: nodeSettingsView.navMiddleDetail + property alias navRightDetail: nodeSettingsView.navRightDetail + + StackView { + id: nodeSettingsView + property alias navMiddleDetail: node_settings.navMiddleDetail + property alias navRightDetail: node_settings.navRightDetail + anchors.fill: parent + + initialItem: Page { + id: node_settings + property alias navMiddleDetail: navbar.middleDetail + property alias navRightDetail: navbar.rightDetail + background: null + implicitWidth: 450 + leftPadding: 20 + rightPadding: 20 + topPadding: 30 + + header: NavigationBar { + id: navbar + } + ColumnLayout { + spacing: 20 + width: Math.min(parent.width, 450) + anchors.horizontalCenter: parent.horizontalCenter + Setting { + Layout.fillWidth: true + header: qsTr("Dark Mode") + actionItem: OptionSwitch { + checked: Theme.dark + onToggled: Theme.toggleDark() + } + } + Setting { + Layout.fillWidth: true + header: qsTr("About") + actionItem: NavButton { + iconSource: "image://images/caret-right" + background: null + onClicked: { + nodeSettingsView.push(about_page) + } + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Storage") + actionItem: NavButton { + iconSource: "image://images/caret-right" + background: null + onClicked: { + nodeSettingsView.push(storage_page) + } + } + } + Setting { + Layout.fillWidth: true + header: qsTr("Connection") + actionItem: NavButton { + iconSource: "image://images/caret-right" + background: null + onClicked: { + nodeSettingsView.push(connection_page) + } + } + } + } + } + } + Component { + id: about_page + SettingsAbout { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + nodeSettingsView.pop() + } + } + } + } + Component { + id: storage_page + SettingsStorage { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + nodeSettingsView.pop() + } + } + } + } + Component { + id: connection_page + SettingsConnection { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + nodeSettingsView.pop() + } + } + } + } +} diff --git a/src/qml/BitcoinApp/OnboardingBlockclock.qml b/src/qml/BitcoinApp/OnboardingBlockclock.qml new file mode 100644 index 0000000000..7a44cb722b --- /dev/null +++ b/src/qml/BitcoinApp/OnboardingBlockclock.qml @@ -0,0 +1,25 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +InformationPage { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: swipeView.decrementCurrentIndex() + } + bannerItem: Image { + source: Theme.image.blocktime + sourceSize.width: 200 + sourceSize.height: 200 + } + bold: true + headerText: qsTr("The block clock") + description: qsTr("The Bitcoin network targets a new block every\n10 minutes. Sometimes it's faster and sometimes slower.\n\nThe block clock indicates each block on a dial\nthat represents the current day.") + buttonText: qsTr("Next") +} diff --git a/src/qml/BitcoinApp/OnboardingConnection.qml b/src/qml/BitcoinApp/OnboardingConnection.qml new file mode 100644 index 0000000000..27dae48f38 --- /dev/null +++ b/src/qml/BitcoinApp/OnboardingConnection.qml @@ -0,0 +1,54 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +Page { + background: null + clip: true + SwipeView { + id: connections + anchors.fill: parent + interactive: false + orientation: Qt.Vertical + InformationPage { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: swipeView.decrementCurrentIndex() + } + bannerItem: Image { + Layout.topMargin: 20 + Layout.alignment: Qt.AlignCenter + source: Theme.image.storage + sourceSize.width: 200 + sourceSize.height: 200 + } + bold: true + headerText: qsTr("Starting initial download") + headerMargin: 30 + description: qsTr("The application will connect to the Bitcoin network and start downloading and verifying transactions.\n\nThis may take several hours, or even days, based on your connection.") + descriptionMargin: 20 + detailActive: true + detailItem: TextButton { + text: qsTr("Connection settings") + onClicked: connections.incrementCurrentIndex() + } + lastPage: true + buttonText: qsTr("Next") + } + SettingsConnection { + navRightDetail: NavButton { + text: qsTr("Done") + onClicked: { + connections.decrementCurrentIndex() + } + } + } + } +} diff --git a/src/qml/BitcoinApp/OnboardingCover.qml b/src/qml/BitcoinApp/OnboardingCover.qml new file mode 100644 index 0000000000..c32a9dfaad --- /dev/null +++ b/src/qml/BitcoinApp/OnboardingCover.qml @@ -0,0 +1,55 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +Page { + background: null + clip: true + SwipeView { + id: introductions + anchors.fill: parent + interactive: false + orientation: Qt.Horizontal + InformationPage { + navRightDetail: NavButton { + iconSource: "image://images/info" + iconHeight: 24 + onClicked: { + introductions.incrementCurrentIndex() + } + } + bannerItem: Image { + Layout.fillWidth: true + Layout.alignment: Qt.AlignCenter + source: "image://images/app" + // Bitcoin icon has ~11% padding + sourceSize.width: 112 + sourceSize.height: 112 + } + bannerMargin: 0 + bold: true + headerText: qsTr("Bitcoin Core App") + headerSize: 36 + description: qsTr("Be part of the Bitcoin network.") + descriptionMargin: 10 + descriptionSize: 24 + subtext: qsTr("100% open-source & open-design") + buttonText: qsTr("Start") + } + SettingsAbout { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + introductions.decrementCurrentIndex() + } + } + } + } +} diff --git a/src/qml/BitcoinApp/OnboardingStorageAmount.qml b/src/qml/BitcoinApp/OnboardingStorageAmount.qml new file mode 100644 index 0000000000..106fdc58c0 --- /dev/null +++ b/src/qml/BitcoinApp/OnboardingStorageAmount.qml @@ -0,0 +1,56 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +Page { + background: null + clip: true + SwipeView { + id: storages + anchors.fill: parent + interactive: false + orientation: Qt.Vertical + InformationPage { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: swipeView.decrementCurrentIndex() + } + bannerActive: false + bold: true + headerText: qsTr("Storage") + headerMargin: 0 + description: qsTr("Data retrieved from the Bitcoin network is stored on your device.\nYou have 500GB of storage available.") + descriptionMargin: 10 + detailActive: true + detailItem: ColumnLayout { + spacing: 0 + StorageOptions { + Layout.maximumWidth: 450 + Layout.alignment: Qt.AlignCenter + } + TextButton { + Layout.topMargin: 30 + Layout.fillWidth: true + text: qsTr("Detailed settings") + onClicked: storages.incrementCurrentIndex() + } + } + buttonText: qsTr("Next") + } + SettingsStorage { + navRightDetail: NavButton { + text: qsTr("Done") + onClicked: { + storages.decrementCurrentIndex() + } + } + } + } +} diff --git a/src/qml/BitcoinApp/OnboardingStorageLocation.qml b/src/qml/BitcoinApp/OnboardingStorageLocation.qml new file mode 100644 index 0000000000..d03919513b --- /dev/null +++ b/src/qml/BitcoinApp/OnboardingStorageLocation.qml @@ -0,0 +1,27 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components +import org.bitcoincore.qt + +InformationPage { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: swipeView.decrementCurrentIndex() + } + bannerActive: false + bold: true + headerText: qsTr("Storage location") + headerMargin: 0 + description: qsTr("Where do you want to store the downloaded block data?\nYou need a minimum of 1GB of storage.") + descriptionMargin: 20 + detailActive: true + detailItem: StorageLocations {} + buttonText: qsTr("Next") +} diff --git a/src/qml/BitcoinApp/OnboardingStrengthen.qml b/src/qml/BitcoinApp/OnboardingStrengthen.qml new file mode 100644 index 0000000000..6e2057aff1 --- /dev/null +++ b/src/qml/BitcoinApp/OnboardingStrengthen.qml @@ -0,0 +1,25 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +InformationPage { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: swipeView.decrementCurrentIndex() + } + bannerItem: Image { + source: Theme.image.network + sourceSize.width: 200 + sourceSize.height: 200 + } + bold: true + headerText: qsTr("Strengthen bitcoin") + description: qsTr("Bitcoin Core runs a full Bitcoin node which verifies the rules of the network are being followed.\n\nUsers running nodes is what makes bitcoin\nso resilient and trustworthy.") + buttonText: qsTr("Next") +} diff --git a/src/qml/BitcoinApp/SettingsAbout.qml b/src/qml/BitcoinApp/SettingsAbout.qml new file mode 100644 index 0000000000..6305c4ee66 --- /dev/null +++ b/src/qml/BitcoinApp/SettingsAbout.qml @@ -0,0 +1,41 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +Item { + property alias navLeftDetail: aboutSwipe.navLeftDetail + SwipeView { + id: aboutSwipe + property alias navLeftDetail: about_settings.navLeftDetail + anchors.fill: parent + interactive: false + orientation: Qt.Horizontal + InformationPage { + id: about_settings + bannerActive: false + bannerMargin: 0 + bold: true + headerText: qsTr("About") + headerMargin: 0 + description: qsTr("Bitcoin Core is an open source project.\nIf you find it useful, please contribute.\n\n This is experimental software.") + descriptionMargin: 20 + detailActive: true + detailItem: AboutOptions {} + } + SettingsDeveloper { + navLeftDetail: NavButton { + iconSource: "image://images/caret-left" + text: qsTr("Back") + onClicked: { + aboutSwipe.decrementCurrentIndex() + } + } + } + } +} diff --git a/src/qml/BitcoinApp/SettingsConnection.qml b/src/qml/BitcoinApp/SettingsConnection.qml new file mode 100644 index 0000000000..160e363bef --- /dev/null +++ b/src/qml/BitcoinApp/SettingsConnection.qml @@ -0,0 +1,20 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +InformationPage { + background: null + clip: true + bannerActive: false + bold: true + headerText: qsTr("Connection settings") + headerMargin: 0 + detailActive: true + detailItem: ConnectionSettings {} +} diff --git a/src/qml/BitcoinApp/SettingsDeveloper.qml b/src/qml/BitcoinApp/SettingsDeveloper.qml new file mode 100644 index 0000000000..172d7929de --- /dev/null +++ b/src/qml/BitcoinApp/SettingsDeveloper.qml @@ -0,0 +1,18 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +InformationPage { + bannerActive: false + bold: true + headerText: qsTr("Developer options") + headerMargin: 0 + detailActive: true + detailItem: DeveloperOptions {} +} diff --git a/src/qml/BitcoinApp/SettingsStorage.qml b/src/qml/BitcoinApp/SettingsStorage.qml new file mode 100644 index 0000000000..ddb32f190a --- /dev/null +++ b/src/qml/BitcoinApp/SettingsStorage.qml @@ -0,0 +1,24 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls +import BitcoinApp.Components + +InformationPage { + bannerActive: false + bold: true + headerText: qsTr("Storage settings") + headerMargin: 0 + detailActive: true + detailItem: StorageSettings { + // Set default prune values + Component.onCompleted: { + optionsModel.prune = true + optionsModel.pruneSizeGB = 2 + } + } +} diff --git a/src/qml/BitcoinApp/appmode.h b/src/qml/BitcoinApp/appmode.h new file mode 100644 index 0000000000..8265984103 --- /dev/null +++ b/src/qml/BitcoinApp/appmode.h @@ -0,0 +1,49 @@ +// Copyright (c) 2022 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_APPMODE_H +#define BITCOIN_QML_APPMODE_H + +#include + +class AppMode : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool isDesktop READ isDesktop NOTIFY modeChanged) + Q_PROPERTY(bool isMobile READ isMobile NOTIFY modeChanged) + Q_PROPERTY(QString state READ state NOTIFY modeChanged) + +public: + enum Mode { + DESKTOP, + MOBILE + }; + + explicit AppMode(Mode mode) : m_mode(mode) + { + } + + bool isMobile() { return m_mode == MOBILE; } + bool isDesktop() { return m_mode == DESKTOP; } + QString state() + { + switch (m_mode) { + case MOBILE: + return "MOBILE"; + case DESKTOP: + return "DESKTOP"; + default: + return "DESKTOP"; + } + } + Mode mode() const { return m_mode; } + +Q_SIGNALS: + void modeChanged(); + +private: + const Mode m_mode; +}; + +#endif // BITCOIN_QML_APPMODE_H diff --git a/src/qml/BitcoinApp/bitcoin.cpp b/src/qml/BitcoinApp/bitcoin.cpp new file mode 100644 index 0000000000..0f7eb45a41 --- /dev/null +++ b/src/qml/BitcoinApp/bitcoin.cpp @@ -0,0 +1,199 @@ +// Copyright (c) 2021-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE +class QMessageLogContext; +QT_END_NAMESPACE + +namespace { +void SetupUIArgs(ArgsManager& argsman) +{ + argsman.AddArg("-lang=", "Set language, for example \"de_DE\" (default: system locale)", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + argsman.AddArg("-min", "Start minimized", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + argsman.AddArg("-resetguisettings", "Reset all settings changed in the GUI", ArgsManager::ALLOW_ANY, OptionsCategory::GUI); + argsman.AddArg("-splash", strprintf("Show splash screen on startup (default: %u)", DEFAULT_SPLASHSCREEN), ArgsManager::ALLOW_ANY, OptionsCategory::GUI); +} + +bool InitErrorMessageBox( + const bilingual_str& message, + [[maybe_unused]] unsigned int style) +{ + QQmlApplicationEngine engine; + engine.addImportPath(QStringLiteral(":/qt/qml")); + engine.rootContext()->setContextProperty("message", QString::fromStdString(message.translated)); + engine.load(QUrl(QStringLiteral("qrc:/qt/qml/BitcoinApp/initerrormessage.qml"))); + if (engine.rootObjects().isEmpty()) { + return EXIT_FAILURE; + } + qGuiApp->exec(); + return false; +} + +/* qDebug() message handler --> debug.log */ +void BitcoinQmlDebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg) +{ + Q_UNUSED(context); + if (type == QtDebugMsg) { + LogDebug(BCLog::QT, "GUI: %s\n", msg.toStdString()); + } else { + LogInfo("GUI: %s\n", msg.toStdString()); + } +} +} // namespace + + +int QmlGuiMain(int argc, char* argv[]) +{ + QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusAllControls); + QGuiApplication app(argc, argv); + + auto handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox); + + std::unique_ptr init = interfaces::MakeGuiInit(argc, argv); + + /// Parse command-line options. We do this after qt in order to show an error if there are problems parsing these. + SetupServerArgs(gArgs); + SetupUIArgs(gArgs); + std::string error; + if (!gArgs.ParseParameters(argc, argv, error)) { + InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error))); + return EXIT_FAILURE; + } + + /// Determine availability of data directory. + if (!CheckDataDirOption(gArgs)) { + InitError(Untranslated(strprintf("Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")))); + return EXIT_FAILURE; + } + + /// Read and parse bitcoin.conf file. + if (!gArgs.ReadConfigFiles(error, true)) { + InitError(Untranslated(strprintf("Cannot parse configuration file: %s\n", error))); + return EXIT_FAILURE; + } + + /// Check for chain settings (Params() calls are only valid after this clause). + try { + SelectParams(gArgs.GetChainType()); + } catch(std::exception &e) { + InitError(Untranslated(strprintf("%s\n", e.what()))); + return EXIT_FAILURE; + } + + // Default printtoconsole to false for the GUI. GUI programs should not + // print to the console unnecessarily. + gArgs.SoftSetBoolArg("-printtoconsole", false); + InitLogging(gArgs); + InitParameterInteraction(gArgs); + + std::unique_ptr node = init->makeNode(); + std::unique_ptr chain = init->makeChain(); + if (!node->baseInitialize()) { + // A dialog with detailed error will have been shown by InitError(). + return EXIT_FAILURE; + } + + 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); + + ChainModel chain_model{*chain}; + + QObject::connect(&node_model, &NodeModel::setTimeRatioList, &chain_model, &ChainModel::setTimeRatioList); + QObject::connect(&node_model, &NodeModel::setTimeRatioListInitial, &chain_model, &ChainModel::setTimeRatioListInitial); + + qGuiApp->setQuitOnLastWindowClosed(false); + QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] { + node->startShutdown(); + }); + + GUIUtil::LoadFont(":/qt/qml/BitcoinApp/res/fonts/Inter-Regular.otf"); + GUIUtil::LoadFont(":/qt/qml/BitcoinApp/res/fonts/Inter-SemiBold.otf"); + + QQmlApplicationEngine engine; + engine.addImportPath(QStringLiteral(":/qt/qml")); + + QScopedPointer network_style{NetworkStyle::instantiate(Params().GetChainType())}; + assert(!network_style.isNull()); + engine.addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()}); + + engine.rootContext()->setContextProperty("nodeModel", &node_model); + engine.rootContext()->setContextProperty("chainModel", &chain_model); + + OptionsQmlModel options_model{*node}; + engine.rootContext()->setContextProperty("optionsModel", &options_model); + +#ifdef __ANDROID__ + AppMode app_mode(AppMode::MOBILE); +#else + AppMode app_mode(AppMode::DESKTOP); +#endif // __ANDROID__ + + qmlRegisterSingletonInstance("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode); + qmlRegisterType("org.bitcoincore.qt", 1, 0, "BlockClockDial"); + + engine.load(QUrl(QStringLiteral("qrc:/qt/qml/BitcoinApp/main.qml"))); + if (engine.rootObjects().isEmpty()) { + return EXIT_FAILURE; + } + + auto window = qobject_cast(engine.rootObjects().first()); + if (!window) { + return EXIT_FAILURE; + } + + // Install qDebug() message handler to route to debug.log + qInstallMessageHandler(BitcoinQmlDebugMessageHandler); + + qInfo() << "Graphics API in use:" << QmlUtil::GraphicsApi(window); + + node_model.startShutdownPolling(); + return qGuiApp->exec(); +} diff --git a/src/qml/BitcoinApp/bitcoin.h b/src/qml/BitcoinApp/bitcoin.h new file mode 100644 index 0000000000..9fa0043b54 --- /dev/null +++ b/src/qml/BitcoinApp/bitcoin.h @@ -0,0 +1,10 @@ +// Copyright (c) 2021-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +#ifndef BITCOIN_QML_BITCOIN_H +#define BITCOIN_QML_BITCOIN_H + +int QmlGuiMain(int argc, char* argv[]); + +#endif // BITCOIN_QML_BITCOIN_H diff --git a/src/qml/BitcoinApp/chainmodel.cpp b/src/qml/BitcoinApp/chainmodel.cpp new file mode 100644 index 0000000000..58277f84bb --- /dev/null +++ b/src/qml/BitcoinApp/chainmodel.cpp @@ -0,0 +1,91 @@ +// Copyright (c) 2022 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 + +ChainModel::ChainModel(interfaces::Chain& chain) + : m_chain{chain} +{ + QTimer* timer = new QTimer(); + connect(timer, &QTimer::timeout, this, &ChainModel::setCurrentTimeRatio); + timer->start(1000); + + QThread* timer_thread = new QThread; + timer->moveToThread(timer_thread); + timer_thread->start(); +} + +void ChainModel::setTimeRatioList(int new_time) +{ + if (m_time_ratio_list.isEmpty()) { + setTimeRatioListInitial(); + } + int time_at_meridian = timestampAtMeridian(); + + if (new_time < time_at_meridian) { + return; + } + m_time_ratio_list.push_back(double(new_time - time_at_meridian) / SECS_IN_12_HOURS); + + Q_EMIT timeRatioListChanged(); +} + +int ChainModel::timestampAtMeridian() +{ + int secs_since_meridian = (QTime::currentTime().msecsSinceStartOfDay() / 1000) % SECS_IN_12_HOURS; + int current_timestamp = QDateTime::currentSecsSinceEpoch(); + + return current_timestamp - secs_since_meridian; +} + +void ChainModel::setTimeRatioListInitial() +{ + int time_at_meridian = timestampAtMeridian(); + m_time_ratio_list.clear(); + /* m_time_ratio_list[0] = current_time_ratio + * m_time_ratio_list[1] = 0 + * These two positions remain fixed for these + * values in m_time_ratio_list */ + m_time_ratio_list.push_back(double(QDateTime::currentSecsSinceEpoch() - time_at_meridian) / SECS_IN_12_HOURS); + m_time_ratio_list.push_back(0); + + int first_block_height; + int active_chain_height = m_chain.getHeight().value(); + bool success = m_chain.findFirstBlockWithTimeAndHeight(/*min_time=*/time_at_meridian, /*min_height=*/0, interfaces::FoundBlock().height(first_block_height)); + + if (!success) { + Q_EMIT timeRatioListChanged(); + return; + } + + for (int height = first_block_height; height < active_chain_height + 1; height++) { + m_time_ratio_list.push_back(double(m_chain.getBlockTime(height) - time_at_meridian) / SECS_IN_12_HOURS); + } + + Q_EMIT timeRatioListChanged(); +} + +void ChainModel::setCurrentTimeRatio() +{ + int secs_since_meridian = (QTime::currentTime().msecsSinceStartOfDay() / 1000) % SECS_IN_12_HOURS; + double current_time_ratio = double(secs_since_meridian) / SECS_IN_12_HOURS; + + if (current_time_ratio < m_time_ratio_list[0].toDouble()) { // That means time has crossed a meridian + m_time_ratio_list.clear(); + } + + if (m_time_ratio_list.isEmpty()) { + m_time_ratio_list.push_back(current_time_ratio); + m_time_ratio_list.push_back(0); + } else { + m_time_ratio_list[0] = current_time_ratio; + } + + Q_EMIT timeRatioListChanged(); +} diff --git a/src/qml/BitcoinApp/chainmodel.h b/src/qml/BitcoinApp/chainmodel.h new file mode 100644 index 0000000000..e8919acea1 --- /dev/null +++ b/src/qml/BitcoinApp/chainmodel.h @@ -0,0 +1,54 @@ +// Copyright (c) 2022 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_CHAINMODEL_H +#define BITCOIN_QML_CHAINMODEL_H + +#include + +#include +#include +#include + +namespace interfaces { +class FoundBlock; +class Chain; +} // namespace interfaces + +static const int SECS_IN_12_HOURS = 43200; + +class ChainModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariantList timeRatioList READ timeRatioList NOTIFY timeRatioListChanged) + +public: + explicit ChainModel(interfaces::Chain& chain); + + QVariantList timeRatioList() const { return m_time_ratio_list; }; + + int timestampAtMeridian(); + + void setCurrentTimeRatio(); + +public Q_SLOTS: + void setTimeRatioList(int new_time); + void setTimeRatioListInitial(); + +Q_SIGNALS: + void timeRatioListChanged(); + +private: + /* time_ratio: Ratio between the time at which an event + * happened and 12 hours. So, for example, if a block is + * found at 4 am or pm, the time_ratio would be 0.3. + * The m_time_ratio_list stores the time ratio value for + * the current_time and the time at which the blocks in + * the last 12 hours were mined. */ + QVariantList m_time_ratio_list{0.0}; + + interfaces::Chain& m_chain; +}; + +#endif // BITCOIN_QML_CHAINMODEL_H diff --git a/src/qml/BitcoinApp/imageprovider.cpp b/src/qml/BitcoinApp/imageprovider.cpp new file mode 100644 index 0000000000..a33fd7043f --- /dev/null +++ b/src/qml/BitcoinApp/imageprovider.cpp @@ -0,0 +1,111 @@ +// 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 +#include +#include +#include + +ImageProvider::ImageProvider(const NetworkStyle* network_style) + : QQuickImageProvider{QQuickImageProvider::Pixmap}, + m_network_style{network_style} {} + +QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requested_size) +{ + if (!size || !requested_size.isValid()) { + return {}; + } + + if (id == "arrow-down") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/arrow-down.png").pixmap(requested_size); + } + + if (id == "arrow-up") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/arrow-up.png").pixmap(requested_size); + } + + if (id == "bitcoin-circle") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/bitcoin-circle.png").pixmap(requested_size); + } + + if (id == "blocktime-dark") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/blocktime-dark.png").pixmap(requested_size); + } + + if (id == "blocktime-light") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/blocktime-light.png").pixmap(requested_size); + } + + if (id == "app") { + *size = requested_size; + return m_network_style->getAppIcon().pixmap(requested_size); + } + + if (id == "caret-left") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/caret-left.png").pixmap(requested_size); + } + + if (id == "caret-right") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/caret-right.png").pixmap(requested_size); + } + + if (id == "check") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/check.png").pixmap(requested_size); + } + + if (id == "cross") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/cross.png").pixmap(requested_size); + } + + if (id == "export") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/export.png").pixmap(requested_size); + } + + if (id == "gear") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/gear.png").pixmap(requested_size); + } + + if (id == "info") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/info.png").pixmap(requested_size); + } + + if (id == "network-dark") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/network-dark.png").pixmap(requested_size); + } + + if (id == "network-light") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/network-light.png").pixmap(requested_size); + } + + if (id == "storage-dark") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/storage-dark.png").pixmap(requested_size); + } + + if (id == "storage-light") { + *size = requested_size; + return QIcon(":/qt/qml/BitcoinApp/res/icons/storage-light.png").pixmap(requested_size); + } + + return {}; +} diff --git a/src/qml/BitcoinApp/imageprovider.h b/src/qml/BitcoinApp/imageprovider.h new file mode 100644 index 0000000000..ba602d7184 --- /dev/null +++ b/src/qml/BitcoinApp/imageprovider.h @@ -0,0 +1,29 @@ +// 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_IMAGEPROVIDER_H +#define BITCOIN_QML_IMAGEPROVIDER_H + +#include +#include + +class NetworkStyle; + +QT_BEGIN_NAMESPACE +class QSize; +class QString; +QT_END_NAMESPACE + +class ImageProvider : public QQuickImageProvider +{ +public: + explicit ImageProvider(const NetworkStyle* network_style); + + QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requested_size) override; + +private: + const NetworkStyle* m_network_style; +}; + +#endif // BITCOIN_QML_IMAGEPROVIDER_H diff --git a/src/qml/BitcoinApp/initerrormessage.qml b/src/qml/BitcoinApp/initerrormessage.qml new file mode 100644 index 0000000000..8a0380bb3d --- /dev/null +++ b/src/qml/BitcoinApp/initerrormessage.qml @@ -0,0 +1,38 @@ +// Copyright (c) 2021-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Controls + +ApplicationWindow { + id: root + visible: true + title: "Bitcoin Core App" + minimumWidth: 500 + minimumHeight: 250 + color: Theme.color.background + ColumnLayout { + spacing: 30 + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + width: 340 + + Header { + Layout.topMargin: 30 + Layout.fillWidth: true + bold: true + header: qsTr("There was an issue starting up.") + headerSize: 21 + description: message + descriptionMargin: 3 + } + OutlineButton { + Layout.alignment: Qt.AlignCenter + text: qsTr("Close") + onClicked: Qt.quit() + } + } +} diff --git a/src/qml/BitcoinApp/main.qml b/src/qml/BitcoinApp/main.qml new file mode 100644 index 0000000000..a2922e1736 --- /dev/null +++ b/src/qml/BitcoinApp/main.qml @@ -0,0 +1,70 @@ +// Copyright (c) 2021-present The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://opensource.org/license/mit/. + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import BitcoinApp.Components +import BitcoinApp.Controls +import org.bitcoincore.qt + +ApplicationWindow { + id: appWindow + title: "Bitcoin Core App" + minimumWidth: 640 + minimumHeight: 665 + color: Theme.color.background + visible: true + + StackView { + id: main + initialItem: onboardingWizard + anchors.fill: parent + } + + Component { + id: onboardingWizard + SwipeView { + id: swipeView + property bool finished: false + interactive: false + + OnboardingCover {} + OnboardingStrengthen {} + OnboardingBlockclock {} + OnboardingStorageLocation {} + OnboardingStorageAmount {} + OnboardingConnection {} + + onFinishedChanged: main.push(node) + } + } + + Component { + id: node + SwipeView { + id: node_swipe + interactive: false + orientation: Qt.Vertical + NodeRunner { + navRightDetail: NavButton { + iconSource: "image://images/gear" + iconHeight: 24 + onClicked: node_swipe.incrementCurrentIndex() + } + } + NodeSettings { + navMiddleDetail: Header { + headerBold: true + headerSize: 18 + header: "Settings" + } + navRightDetail: NavButton { + text: qsTr("Done") + onClicked: node_swipe.decrementCurrentIndex() + } + } + } + } +} diff --git a/src/qml/BitcoinApp/nodemodel.cpp b/src/qml/BitcoinApp/nodemodel.cpp new file mode 100644 index 0000000000..c68edaa4e2 --- /dev/null +++ b/src/qml/BitcoinApp/nodemodel.cpp @@ -0,0 +1,131 @@ +// 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 +#include + +#include +#include +#include + +NodeModel::NodeModel(interfaces::Node& node) + : m_node{node} +{ + ConnectToBlockTipSignal(); +} + +void NodeModel::setBlockTipHeight(int new_height) +{ + if (new_height != m_block_tip_height) { + m_block_tip_height = new_height; + Q_EMIT blockTipHeightChanged(); + } +} + +void NodeModel::setRemainingSyncTime(double new_progress) +{ + int currentTime = QDateTime::currentDateTime().toMSecsSinceEpoch(); + + // keep a vector of samples of verification progress at height + m_block_process_time.push_front(qMakePair(currentTime, new_progress)); + + // show progress speed if we have more than one sample + if (m_block_process_time.size() >= 2) { + double progressDelta = 0; + int timeDelta = 0; + int remainingMSecs = 0; + double remainingProgress = 1.0 - new_progress; + for (int i = 1; i < m_block_process_time.size(); i++) { + QPair sample = m_block_process_time[i]; + + // take first sample after 500 seconds or last available one + if (sample.first < (currentTime - 500 * 1000) || i == m_block_process_time.size() - 1) { + progressDelta = m_block_process_time[0].second - sample.second; + timeDelta = m_block_process_time[0].first - sample.first; + remainingMSecs = (progressDelta > 0) ? remainingProgress / progressDelta * timeDelta : -1; + break; + } + } + if (remainingMSecs > 0 && m_block_process_time.count() % 1000 == 0) { + m_remaining_sync_time = remainingMSecs; + + Q_EMIT remainingSyncTimeChanged(); + } + static const int MAX_SAMPLES = 5000; + if (m_block_process_time.count() > MAX_SAMPLES) { + m_block_process_time.remove(1, m_block_process_time.count() - 1); + } + } +} +void NodeModel::setVerificationProgress(double new_progress) +{ + if (new_progress != m_verification_progress) { + setRemainingSyncTime(new_progress); + + m_verification_progress = new_progress; + Q_EMIT verificationProgressChanged(); + } +} + +void NodeModel::setPause(bool new_pause) +{ + if(m_pause != new_pause) { + m_pause = new_pause; + m_node.setNetworkActive(!new_pause); + Q_EMIT pauseChanged(new_pause); + } +} + +void NodeModel::startNodeInitializionThread() +{ + Q_EMIT requestedInitialize(); +} + +void NodeModel::initializeResult([[maybe_unused]] bool success, interfaces::BlockAndHeaderTipInfo tip_info) +{ + // TODO: Handle the `success` parameter, + setBlockTipHeight(tip_info.block_height); + setVerificationProgress(tip_info.verification_progress); + + Q_EMIT setTimeRatioListInitial(); +} + +void NodeModel::startShutdownPolling() +{ + m_shutdown_polling_timer_id = startTimer(200ms); +} + +void NodeModel::stopShutdownPolling() +{ + killTimer(m_shutdown_polling_timer_id); +} + +void NodeModel::timerEvent(QTimerEvent* event) +{ + Q_UNUSED(event) + if (m_node.shutdownRequested()) { + stopShutdownPolling(); + Q_EMIT requestedShutdown(); + } +} + +void NodeModel::ConnectToBlockTipSignal() +{ + assert(!m_handler_notify_block_tip); + + m_handler_notify_block_tip = m_node.handleNotifyBlockTip( + [this](SynchronizationState state, interfaces::BlockTip tip, double verification_progress) { + QMetaObject::invokeMethod(this, [this, tip, verification_progress] { + setBlockTipHeight(tip.block_height); + setVerificationProgress(verification_progress); + + Q_EMIT setTimeRatioList(tip.block_time); + }); + }); +} diff --git a/src/qml/BitcoinApp/nodemodel.h b/src/qml/BitcoinApp/nodemodel.h new file mode 100644 index 0000000000..2da453f152 --- /dev/null +++ b/src/qml/BitcoinApp/nodemodel.h @@ -0,0 +1,83 @@ +// 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_NODEMODEL_H +#define BITCOIN_QML_NODEMODEL_H + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE +class QTimerEvent; +QT_END_NAMESPACE + +namespace interfaces { +class Node; +} + +/** Model for Bitcoin network client. */ +class NodeModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(int blockTipHeight READ blockTipHeight NOTIFY blockTipHeightChanged) + Q_PROPERTY(int remainingSyncTime READ remainingSyncTime NOTIFY remainingSyncTimeChanged) + Q_PROPERTY(double verificationProgress READ verificationProgress NOTIFY verificationProgressChanged) + Q_PROPERTY(bool pause READ pause WRITE setPause NOTIFY pauseChanged) + +public: + explicit NodeModel(interfaces::Node& node); + + int blockTipHeight() const { return m_block_tip_height; } + void setBlockTipHeight(int new_height); + int remainingSyncTime() const { return m_remaining_sync_time; } + void setRemainingSyncTime(double new_progress); + double verificationProgress() const { return m_verification_progress; } + void setVerificationProgress(double new_progress); + bool pause() const { return m_pause; } + void setPause(bool new_pause); + + Q_INVOKABLE void startNodeInitializionThread(); + + void startShutdownPolling(); + void stopShutdownPolling(); + +public Q_SLOTS: + void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info); + +Q_SIGNALS: + void blockTipHeightChanged(); + void remainingSyncTimeChanged(); + void requestedInitialize(); + void requestedShutdown(); + void verificationProgressChanged(); + void pauseChanged(bool new_pause); + + void setTimeRatioList(int new_time); + void setTimeRatioListInitial(); + +protected: + void timerEvent(QTimerEvent* event) override; + +private: + // Properties that are exposed to QML. + int m_block_tip_height{0}; + int m_remaining_sync_time{0}; + double m_verification_progress{0.0}; + bool m_pause{false}; + + int m_shutdown_polling_timer_id{0}; + + QVector> m_block_process_time; + + interfaces::Node& m_node; + std::unique_ptr m_handler_notify_block_tip; + + void ConnectToBlockTipSignal(); +}; + +#endif // BITCOIN_QML_NODEMODEL_H diff --git a/src/qml/BitcoinApp/options_model.cpp b/src/qml/BitcoinApp/options_model.cpp new file mode 100644 index 0000000000..1706dbf925 --- /dev/null +++ b/src/qml/BitcoinApp/options_model.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2022 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 + +OptionsQmlModel::OptionsQmlModel(interfaces::Node& node) + : m_node{node} +{ + int64_t prune_value{SettingTo(m_node.getPersistentSetting("prune"), 0)}; + m_prune = (prune_value > 1); + m_prune_size_gb = m_prune ? PruneMiBtoGB(prune_value) : DEFAULT_PRUNE_TARGET_GB; +} + +void OptionsQmlModel::setListen(bool new_listen) +{ + if (new_listen != m_listen) { + m_listen = new_listen; + m_node.updateRwSetting("listen", new_listen); + Q_EMIT listenChanged(new_listen); + } +} + +void OptionsQmlModel::setNatpmp(bool new_natpmp) +{ + if (new_natpmp != m_natpmp) { + m_natpmp = new_natpmp; + m_node.updateRwSetting("natpmp", new_natpmp); + Q_EMIT natpmpChanged(new_natpmp); + } +} + +void OptionsQmlModel::setPrune(bool new_prune) +{ + if (new_prune != m_prune) { + m_prune = new_prune; + m_node.updateRwSetting("prune", pruneSetting()); + Q_EMIT pruneChanged(new_prune); + } +} + +void OptionsQmlModel::setPruneSizeGB(int new_prune_size_gb) +{ + if (new_prune_size_gb != m_prune_size_gb) { + m_prune_size_gb = new_prune_size_gb; + m_node.updateRwSetting("prune", pruneSetting()); + Q_EMIT pruneSizeGBChanged(new_prune_size_gb); + } +} + +void OptionsQmlModel::setServer(bool new_server) +{ + if (new_server != m_server) { + m_server = new_server; + m_node.updateRwSetting("server", new_server); + Q_EMIT serverChanged(new_server); + } +} + +common::SettingsValue OptionsQmlModel::pruneSetting() const +{ + assert(!m_prune || m_prune_size_gb >= 1); + return m_prune ? PruneGBtoMiB(m_prune_size_gb) : 0; +} diff --git a/src/qml/BitcoinApp/options_model.h b/src/qml/BitcoinApp/options_model.h new file mode 100644 index 0000000000..a9213d2e5f --- /dev/null +++ b/src/qml/BitcoinApp/options_model.h @@ -0,0 +1,60 @@ +// Copyright (c) 2022 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_OPTIONS_MODEL_H +#define BITCOIN_QML_OPTIONS_MODEL_H + +#include + +#include + +namespace interfaces { +class Node; +} + +/** Model for Bitcoin client options. */ +class OptionsQmlModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool listen READ listen WRITE setListen NOTIFY listenChanged) + Q_PROPERTY(bool natpmp READ natpmp WRITE setNatpmp NOTIFY natpmpChanged) + Q_PROPERTY(bool prune READ prune WRITE setPrune NOTIFY pruneChanged) + Q_PROPERTY(int pruneSizeGB READ pruneSizeGB WRITE setPruneSizeGB NOTIFY pruneSizeGBChanged) + Q_PROPERTY(bool server READ server WRITE setServer NOTIFY serverChanged) + +public: + explicit OptionsQmlModel(interfaces::Node& node); + + bool listen() const { return m_listen; } + void setListen(bool new_listen); + bool natpmp() const { return m_natpmp; } + void setNatpmp(bool new_natpmp); + bool prune() const { return m_prune; } + void setPrune(bool new_prune); + int pruneSizeGB() const { return m_prune_size_gb; } + void setPruneSizeGB(int new_prune_size); + bool server() const { return m_server; } + void setServer(bool new_server); + +Q_SIGNALS: + void listenChanged(bool new_listen); + void natpmpChanged(bool new_natpmp); + void pruneChanged(bool new_prune); + void pruneSizeGBChanged(int new_prune_size_gb); + void serverChanged(bool new_server); + +private: + interfaces::Node& m_node; + + // Properties that are exposed to QML. + bool m_listen; + bool m_natpmp; + bool m_prune; + int m_prune_size_gb; + bool m_server; + + common::SettingsValue pruneSetting() const; +}; + +#endif // BITCOIN_QML_OPTIONS_MODEL_H diff --git a/src/qml/BitcoinApp/res/fonts/Inter-Regular.otf b/src/qml/BitcoinApp/res/fonts/Inter-Regular.otf new file mode 100644 index 0000000000..84e6a61c3c Binary files /dev/null and b/src/qml/BitcoinApp/res/fonts/Inter-Regular.otf differ diff --git a/src/qml/BitcoinApp/res/fonts/Inter-SemiBold.otf b/src/qml/BitcoinApp/res/fonts/Inter-SemiBold.otf new file mode 100644 index 0000000000..daf4c4413f Binary files /dev/null and b/src/qml/BitcoinApp/res/fonts/Inter-SemiBold.otf differ diff --git a/src/qml/BitcoinApp/res/icons/arrow-down.png b/src/qml/BitcoinApp/res/icons/arrow-down.png new file mode 100644 index 0000000000..10b46c4a17 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/arrow-down.png differ diff --git a/src/qml/BitcoinApp/res/icons/arrow-up.png b/src/qml/BitcoinApp/res/icons/arrow-up.png new file mode 100644 index 0000000000..6481eff063 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/arrow-up.png differ diff --git a/src/qml/BitcoinApp/res/icons/bitcoin-circle.png b/src/qml/BitcoinApp/res/icons/bitcoin-circle.png new file mode 100644 index 0000000000..dac55c3610 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/bitcoin-circle.png differ diff --git a/src/qml/BitcoinApp/res/icons/blocktime-dark.png b/src/qml/BitcoinApp/res/icons/blocktime-dark.png new file mode 100644 index 0000000000..2e34d95b4f Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/blocktime-dark.png differ diff --git a/src/qml/BitcoinApp/res/icons/blocktime-light.png b/src/qml/BitcoinApp/res/icons/blocktime-light.png new file mode 100644 index 0000000000..ead9b4dd75 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/blocktime-light.png differ diff --git a/src/qml/BitcoinApp/res/icons/caret-left.png b/src/qml/BitcoinApp/res/icons/caret-left.png new file mode 100644 index 0000000000..7a8ad06309 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/caret-left.png differ diff --git a/src/qml/BitcoinApp/res/icons/caret-right.png b/src/qml/BitcoinApp/res/icons/caret-right.png new file mode 100644 index 0000000000..88d1a5489f Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/caret-right.png differ diff --git a/src/qml/BitcoinApp/res/icons/check.png b/src/qml/BitcoinApp/res/icons/check.png new file mode 100644 index 0000000000..ef408cb502 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/check.png differ diff --git a/src/qml/BitcoinApp/res/icons/cross.png b/src/qml/BitcoinApp/res/icons/cross.png new file mode 100644 index 0000000000..16d43af533 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/cross.png differ diff --git a/src/qml/BitcoinApp/res/icons/export.png b/src/qml/BitcoinApp/res/icons/export.png new file mode 100644 index 0000000000..636e8221b3 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/export.png differ diff --git a/src/qml/BitcoinApp/res/icons/gear.png b/src/qml/BitcoinApp/res/icons/gear.png new file mode 100644 index 0000000000..da93278122 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/gear.png differ diff --git a/src/qml/BitcoinApp/res/icons/info.png b/src/qml/BitcoinApp/res/icons/info.png new file mode 100644 index 0000000000..03c3997d2d Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/info.png differ diff --git a/src/qml/BitcoinApp/res/icons/network-dark.png b/src/qml/BitcoinApp/res/icons/network-dark.png new file mode 100644 index 0000000000..5f9279247d Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/network-dark.png differ diff --git a/src/qml/BitcoinApp/res/icons/network-light.png b/src/qml/BitcoinApp/res/icons/network-light.png new file mode 100644 index 0000000000..2f50522bfb Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/network-light.png differ diff --git a/src/qml/BitcoinApp/res/icons/storage-dark.png b/src/qml/BitcoinApp/res/icons/storage-dark.png new file mode 100644 index 0000000000..8490d579ad Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/storage-dark.png differ diff --git a/src/qml/BitcoinApp/res/icons/storage-light.png b/src/qml/BitcoinApp/res/icons/storage-light.png new file mode 100644 index 0000000000..ab5700bad1 Binary files /dev/null and b/src/qml/BitcoinApp/res/icons/storage-light.png differ diff --git a/src/qml/BitcoinApp/res/src/arrow-down.svg b/src/qml/BitcoinApp/res/src/arrow-down.svg new file mode 100644 index 0000000000..4cab9899b7 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/qml/BitcoinApp/res/src/arrow-up.svg b/src/qml/BitcoinApp/res/src/arrow-up.svg new file mode 100644 index 0000000000..5b1233b30e --- /dev/null +++ b/src/qml/BitcoinApp/res/src/arrow-up.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/qml/BitcoinApp/res/src/bitcoin-circle.svg b/src/qml/BitcoinApp/res/src/bitcoin-circle.svg new file mode 100644 index 0000000000..408d66db75 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/bitcoin-circle.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qml/BitcoinApp/res/src/blocktime-dark.svg b/src/qml/BitcoinApp/res/src/blocktime-dark.svg new file mode 100644 index 0000000000..dad783be78 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/blocktime-dark.svg @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/BitcoinApp/res/src/blocktime-light.svg b/src/qml/BitcoinApp/res/src/blocktime-light.svg new file mode 100644 index 0000000000..13de838d92 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/blocktime-light.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/BitcoinApp/res/src/caret-left.svg b/src/qml/BitcoinApp/res/src/caret-left.svg new file mode 100644 index 0000000000..3af3b06243 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/caret-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/qml/BitcoinApp/res/src/caret-right.svg b/src/qml/BitcoinApp/res/src/caret-right.svg new file mode 100644 index 0000000000..5ec4eefb8b --- /dev/null +++ b/src/qml/BitcoinApp/res/src/caret-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/qml/BitcoinApp/res/src/check.svg b/src/qml/BitcoinApp/res/src/check.svg new file mode 100644 index 0000000000..a393deb9b4 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/qml/BitcoinApp/res/src/cross.svg b/src/qml/BitcoinApp/res/src/cross.svg new file mode 100644 index 0000000000..1fcfaf2d03 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/cross.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qml/BitcoinApp/res/src/export.svg b/src/qml/BitcoinApp/res/src/export.svg new file mode 100644 index 0000000000..6bcb7e4a94 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/export.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/qml/BitcoinApp/res/src/gear.svg b/src/qml/BitcoinApp/res/src/gear.svg new file mode 100644 index 0000000000..5098ccb101 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/gear.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/qml/BitcoinApp/res/src/info.svg b/src/qml/BitcoinApp/res/src/info.svg new file mode 100644 index 0000000000..236b78c2fd --- /dev/null +++ b/src/qml/BitcoinApp/res/src/info.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qml/BitcoinApp/res/src/network-dark.svg b/src/qml/BitcoinApp/res/src/network-dark.svg new file mode 100644 index 0000000000..cee25614f8 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/network-dark.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/BitcoinApp/res/src/network-light.svg b/src/qml/BitcoinApp/res/src/network-light.svg new file mode 100644 index 0000000000..f798b6cb1d --- /dev/null +++ b/src/qml/BitcoinApp/res/src/network-light.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/BitcoinApp/res/src/storage-dark.svg b/src/qml/BitcoinApp/res/src/storage-dark.svg new file mode 100644 index 0000000000..7dbfaaebc7 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/storage-dark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/BitcoinApp/res/src/storage-light.svg b/src/qml/BitcoinApp/res/src/storage-light.svg new file mode 100644 index 0000000000..63f2e5a769 --- /dev/null +++ b/src/qml/BitcoinApp/res/src/storage-light.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qml/BitcoinApp/util.cpp b/src/qml/BitcoinApp/util.cpp new file mode 100644 index 0000000000..606f9dc95a --- /dev/null +++ b/src/qml/BitcoinApp/util.cpp @@ -0,0 +1,34 @@ +// 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 +#include +#include + +namespace QmlUtil { + +QString GraphicsApi(QQuickWindow* window) +{ + switch (window->rendererInterface()->graphicsApi()) { + case QSGRendererInterface::Unknown: return "Unknown"; + case QSGRendererInterface::Software: return "The Qt Quick 2D Renderer"; + case QSGRendererInterface::OpenVG: return "OpenVG via EGL"; + case QSGRendererInterface::OpenGL: return "OpenGL ES 2.0 or higher via a graphics abstraction layer"; + case QSGRendererInterface::Direct3D11: return "Direct3D 11 via a graphics abstraction layer"; +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + case QSGRendererInterface::Direct3D12: return "Direct3D 12 via a graphics abstraction layer"; +#endif + case QSGRendererInterface::Vulkan: return "Vulkan 1.0 via a graphics abstraction layer"; + case QSGRendererInterface::Metal: return "Metal via a graphics abstraction layer"; + case QSGRendererInterface::Null: return "Null (no output) via a graphics abstraction layer"; + } // no default case, so the compiler can warn about missing cases + assert(false); +} + +} // namespace QmlUtil diff --git a/src/qml/BitcoinApp/util.h b/src/qml/BitcoinApp/util.h new file mode 100644 index 0000000000..6df5d2c2b5 --- /dev/null +++ b/src/qml/BitcoinApp/util.h @@ -0,0 +1,27 @@ +// 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_UTIL_H +#define BITCOIN_QML_UTIL_H + +#include +#include + +QT_BEGIN_NAMESPACE +class QQuickWindow; +QT_END_NAMESPACE + +/** + * Utility functions used by the QML-based Bitcoin Core GUI. + */ +namespace QmlUtil { +/** +* Returns a human-readable description of the graphics API +* that is in use by the Qt Quick scene graph renderer. +*/ +QString GraphicsApi(QQuickWindow* window); + +} // namespace QmlUtil + +#endif // BITCOIN_QML_UTIL_H diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt new file mode 100644 index 0000000000..34144378b3 --- /dev/null +++ b/src/qml/CMakeLists.txt @@ -0,0 +1,41 @@ +# Copyright (c) 2025-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +set(CMAKE_AUTOMOC ON) + +add_compile_definitions( + QT_NO_KEYWORDS + QT_USE_QSTRINGBUILDER +) + +add_subdirectory(BitcoinApp) + +add_executable(bitcoin-qml WIN32 + main.cpp + ${PROJECT_SOURCE_DIR}/src/init/bitcoin-qt.cpp +) + +# TODO: Switch to own resource file. +add_windows_resources(bitcoin-qml ${PROJECT_SOURCE_DIR}/src/qt/res/bitcoin-qt-res.rc) +add_windows_application_manifest(bitcoin-qml) + +target_link_libraries(bitcoin-qml + PRIVATE + core_interface + bitcoin_qml + bitcoinqt + bitcoin_node +) + +install_binary_component(bitcoin-qml) + +# Use the `bitcoin-qml` target as a drop-in replacement +# for the legacy QtWidgets-based `bitcoin-qt`. +set_target_properties(bitcoin-qt PROPERTIES + OUTPUT_NAME "bitcoin-qt-legacy" + EXCLUDE_FROM_ALL TRUE +) +set_target_properties(bitcoin-qml PROPERTIES + OUTPUT_NAME "bitcoin-qt" +) diff --git a/src/qml/README.md b/src/qml/README.md new file mode 100644 index 0000000000..29dcbe810b --- /dev/null +++ b/src/qml/README.md @@ -0,0 +1,84 @@ +# Bitcoin Core QML GUI + +**WARNING: THIS IS EXPERIMENTAL, DO NOT USE BUILDS FROM THIS REPO FOR REAL TRANSACTIONS!** + +This directory contains the source code for an experimental Bitcoin Core graphical user interface (GUI) built using the [Qt Quick](https://doc.qt.io/qt-6/qtquick-index.html) framework. + +## Goals and Limitations + +The current Bitcoin Core GUI has gathered enough technical debt and hacked on features; it is time to begin anew. +This project will start from a clean slate to produce a feature-rich GUI with intuitive user flows and first-class design. + +The primary goals of the project can be summed up as follows: + +- Implement UX/UI best-practices as documented in the [Bitcoin Design Guide](https://bitcoin.design/guide/) +- Engage with the Bitcoin Design community to implement well-designed features +- Work alongside the Bitcoin Design community to develop an aesthetic GUI +- Develop a mobile-optimized GUI + +We must avoid conflicts with the Bitcoin Core repo. +As such, this project will aim to make very few changes outside of the qml directory. +Pull requests must be focused on developing the GUI itself, adding build support, +or improving relevant documentation. + +This project will **not** accept pull requests making any significant changes unrelated to the GUI. + +## Development Process + +This repo is synced with the [Bitcoin Core repo](https://github.com/bitcoin/bitcoin) on a weekly basis, or as needed to resolve conflicts. + +Contributions are welcome from all, developers and designers. If you are a new contributor, please read [CONTRIBUTING.md](../../CONTRIBUTING.md). + +### Policies + +This project has custom policies for development, see: +- [Icon Policy](./doc/icon-policy.md) +- [Translator Comments Policy](./doc/translator-comments.md) + +## Compile and Run + +The master branch is only guaranteed to work and build on Debian-based systems, Fedora, and macOS. +Support for more systems will be confirmed and documented as the project matures. + +### Dependencies +No additional dependencies, besides those in [build-osx.md](../../doc/build-osx.md), are needed for macOS. + +Aside from the dependencies listed in [build-unix.md](../../doc/build-unix.md), the following additional dependencies are required to compile: + +#### Debian-based systems: + +``` +sudo apt install qt6-declarative-dev +``` + +The following runtime dependencies are also required for dynamic builds; +they are not needed for static builds: + +``` +sudo apt install qml6-module-qtqml-models qml6-module-qtqml-workerscript qml6-module-qtquick qml6-module-qtquick-controls qml6-module-qtquick-dialogs qml6-module-qtquick-layouts qml6-module-qtquick-templates qml6-module-qtquick-window +``` + +#### Fedora: + +``` +sudo dnf install qt6-qtdeclarative-devel +``` + +### Build + +For instructions on how to build and compile Bitcoin Core, refer to your respective system's build doc. + +As long as the required dependencies are installed, the QML GUI will be built. +To ensure that you are in fact building the QML GUI, you can configure with the following option: + +``` +cmake -B build -DBUILD_GUI=ON +cmake --build build -t bitcoin-qml +``` + +### Run + +To run the QML GUI: +``` +./build/bin/bitcoin-qml +``` diff --git a/src/qml/doc/icon-policy.md b/src/qml/doc/icon-policy.md new file mode 100644 index 0000000000..a07ba6a12b --- /dev/null +++ b/src/qml/doc/icon-policy.md @@ -0,0 +1,39 @@ +# Icon policy +This document provides guidelines for contributing icons to Bitcoin Core. + +## Preparing Icons +Both an icon source file, in Scalable Vector Graphics (SVG) format, +and an optimized production file, in Portable Network Graphics (PNG) format, +are required for each icon. + +#### SVG Source File +SVGs are used as source files because they can scale while retaining image quality. +They are not used in production due to limited application support. +If different-sized production (PNG) icons are required, +they can be generated from the associated SVG source file in any vector-based tool. + +#### PNG Production File +PNGs are used in production due to wide application support, transparency support, +and better image quality compared to competing file types such as JPEG. + +#### Optimizing Production Files (PNG) +Production (PNG) files must be processed by the [optimize-pngs.py](https://github.com/bitcoin-core/bitcoin-maintainer-tools/blob/master/optimize-pngs.py) script before their inclusion in Bitcoin Core. +PNG optimization removes various unnecessary color profiles, ancillary data, +and text chunks, resulting in a lossless file size reduction. + +## Contributing +Bitcoin Core primarily uses icons from the [Bitcoin Icon set](https://github.com/BitcoinDesign/Bitcoin-Icons), +an open source icon set made for Bitcoin applications. +If a proposed feature requires an icon to be designed, +an issue should be opened in the [Bitcoin Icons repo](https://github.com/BitcoinDesign/Bitcoin-Icons/issues) +with the request. + +Icons are not to be added to Bitcoin Core prior to a production use case. +If an icon is not being used, it should be removed. + +SVGs are to be included under the `src/qml/res/src` directory. +Optimized PNGs are to be included under the `src/qml/res/icons` directory. + +## Attribution +Icon additions must include appropriate attribution to the author, license information, and any comments relevant to the icon documented under the +[contrib/debian/copyright](../../../contrib/debian/copyright) file. diff --git a/src/qml/doc/translator-comments.md b/src/qml/doc/translator-comments.md new file mode 100644 index 0000000000..3558dabb6d --- /dev/null +++ b/src/qml/doc/translator-comments.md @@ -0,0 +1,86 @@ +# Translator Comment Policy + +Translator comments are required for all user-facing strings. A PR introducing +new user-facing strings must also submit the accompanying translator comment. +To prevent this requirement from halting development, the PR author can specify +that they would like the introduction of the comment to be picked up in a +follow-up PR. + +## Formulating Translator Comments + +Translator comments only apply to strings that are marked for translation. +For more information, see: +[Internationalization and Localization with Qt Quick](https://doc.qt.io/qt-5/qtquick-internationalization.html) + +Below, the values for the `text` and `description` properties are examples of +new user-facing strings marked for translation without translator comments: + +```qml +OptionButton { + ButtonGroup.group: group + Layout.fillWidth: true + text: qsTr("Only when on Wi-Fi") + description: qsTr("Loads quickly when on wi-fi and pauses when on cellular data.") +} +``` + +Translator comments are needed to provide a translator with the appropriate +context needed to aid them with translating a certain string. +The first step in formulating a translator comment is to think about what +information a translator needs to perform their job effectively. + +**Necessary context is**: +- Where is this string shown? + - e.g. Shown in the Create Wallet dialog, Shown in file menu options ... +- Explain what the string is meant to convey + - This is an explanatory text shown after X that aims to inform the user that ... +- What action is associated with this string? + - If the string is associated with a button, what is the outcome of pressing the button? + +**Optional context is**: +- Alternatives to complicated/technical words + - Some words may not have a direct one-to-one translation in certain languages. + It is nice to provide a replacement for such a word. For example, + some languages may not have a 1-1 translation for the word "subnet", + an appropriate substitute for this word is "IP address". + +## Writing Translator Comments + +Begin by writing down the text for the comment, guided by the necessary and +optional context specified in the previous section. Ensure that your text ends +in a full stop. + +For the value of the `text` property, you may write: + +> Main text for button displayed when choosing the network mode the client will +run in. Choosing this option means that the node will only sync when the device +is connected to a wireless internet connection. + +For the value of the `description` property, you may write: + +> Explanatory text for button which allows user to set the client to only load +when connected to wifi. This text intends to make it clear to the user that, +under this mode, their node will not sync if connected to cellular data. +If there is no clear translation for "cellular data", use the translation for +"mobile data". + +Now take these texts, split them into separate sentences, and append `//:` +to them. Place the text above its respective translatable string: + +```qml +OptionButton { + ButtonGroup.group: group + Layout.fillWidth: true + //: Main text for button displayed when choosing the network mode the client will run in. + //: Choosing this option means that the node will only sync when the device is connected to a wireless internet connection. + text: qsTr("Only when on Wi-Fi") + //: Explanatory text for button which allows user to set the client to only load when connected to wifi. + //: This text intends to make it clear to the user that, under this mode, their node will not sync if connected to cellular data. + //: If there is no clear translation for "cellular data", use the translation for "mobile data". + description: qsTr("Loads quickly when on wi-fi and pauses when on cellular data.") +} +``` + +The special character `//:` lets Qt's translator engine know that the proceeding +text is a translator comment. Each line will get concatenated together to be +presented to the translator as one paragraph. diff --git a/src/qml/main.cpp b/src/qml/main.cpp new file mode 100644 index 0000000000..2943765c89 --- /dev/null +++ b/src/qml/main.cpp @@ -0,0 +1,25 @@ +// Copyright (c) 2018-2022 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 + +/** Translate string to current locale using Qt. */ +extern const TranslateFn G_TRANSLATION_FUN = [](const char* psz) { + return QCoreApplication::translate("bitcoin-core", psz).toStdString(); +}; + +const std::function G_TEST_GET_FULL_NAME{}; + +MAIN_FUNCTION +{ + return QmlGuiMain(argc, argv); +} diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 58fa08b56a..12b9e37d19 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -270,7 +270,7 @@ target_link_libraries(bitcoin-qt ) import_plugins(bitcoin-qt) -install_binary_component(bitcoin-qt HAS_MANPAGE) +# install_binary_component(bitcoin-qt HAS_MANPAGE) if(WIN32) set_target_properties(bitcoin-qt PROPERTIES WIN32_EXECUTABLE TRUE) endif() diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index bf42f78da0..5dab7ee1e3 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -184,7 +184,7 @@ static void ErrorSettingsWrite(const bilingual_str& error, const std::vector debug.log */ -void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg) +void BitcoinQtDebugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &msg) { Q_UNUSED(context); if (type == QtDebugMsg) { @@ -638,7 +638,7 @@ int GuiMain(int argc, char* argv[]) qApp->installNativeEventFilter(new WinShutdownMonitor([&app] { app.node().startShutdown(); })); #endif // Install qDebug() message handler to route to debug.log - qInstallMessageHandler(DebugMessageHandler); + qInstallMessageHandler(BitcoinQtDebugMessageHandler); // Allow parameter interaction before we create the options model app.parameterSetup(); GUIUtil::LogQtInfo(); diff --git a/src/sync.h b/src/sync.h index 123184d44f..28bc78e911 100644 --- a/src/sync.h +++ b/src/sync.h @@ -251,7 +251,7 @@ class SCOPED_LOCKABLE UniqueLock : public MutexType::unique_lock // it is not possible to use the lock's copy of the mutex for that purpose. // Instead, the original mutex needs to be passed back to the reverse_lock for // the sake of thread-safety analysis, but it is not actually used otherwise. -#define REVERSE_LOCK(g, cs) typename std::decay::type::reverse_lock UNIQUE_NAME(revlock)(g, cs, #cs, __FILE__, __LINE__) +#define REVERSE_LOCK(g, cs) typename std::decay::type::reverse_lock BITCOIN_UNIQUE_NAME(revlock)(g, cs, #cs, __FILE__, __LINE__) // When locking a Mutex, require negative capability to ensure the lock // is not already held @@ -265,7 +265,7 @@ inline MutexType& MaybeCheckNotHeld(MutexType& m) LOCKS_EXCLUDED(m) LOCK_RETURNE template inline MutexType* MaybeCheckNotHeld(MutexType* m) LOCKS_EXCLUDED(m) LOCK_RETURNED(m) { return m; } -#define LOCK(cs) UniqueLock UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) +#define LOCK(cs) UniqueLock BITCOIN_UNIQUE_NAME(criticalblock)(MaybeCheckNotHeld(cs), #cs, __FILE__, __LINE__) #define LOCK2(cs1, cs2) \ UniqueLock criticalblock1(MaybeCheckNotHeld(cs1), #cs1, __FILE__, __LINE__); \ UniqueLock criticalblock2(MaybeCheckNotHeld(cs2), #cs2, __FILE__, __LINE__) diff --git a/src/test/util/logging.h b/src/test/util/logging.h index ed3ee19e1b..6df208d841 100644 --- a/src/test/util/logging.h +++ b/src/test/util/logging.h @@ -39,6 +39,6 @@ class DebugLogHelper MatchFn m_match; }; -#define ASSERT_DEBUG_LOG(message) DebugLogHelper UNIQUE_NAME(debugloghelper)(message) +#define ASSERT_DEBUG_LOG(message) DebugLogHelper BITCOIN_UNIQUE_NAME(debugloghelper)(message) #endif // BITCOIN_TEST_UTIL_LOGGING_H diff --git a/src/util/macros.h b/src/util/macros.h index 9ad2fa6ef4..eedd23c692 100644 --- a/src/util/macros.h +++ b/src/util/macros.h @@ -8,7 +8,7 @@ #define PASTE(x, y) x ## y #define PASTE2(x, y) PASTE(x, y) -#define UNIQUE_NAME(name) PASTE2(name, __COUNTER__) +#define BITCOIN_UNIQUE_NAME(name) PASTE2(name, __COUNTER__) /** * Converts the parameter X to a string after macro replacement on X has been performed. diff --git a/src/util/stdmutex.h b/src/util/stdmutex.h index ab89a98768..d2ac1ba195 100644 --- a/src/util/stdmutex.h +++ b/src/util/stdmutex.h @@ -38,6 +38,6 @@ class LOCKABLE StdMutex : public std::mutex }; // Provide STDLOCK(..) wrapper around StdMutex::Guard that checks the lock is not already held -#define STDLOCK(cs) StdMutex::Guard UNIQUE_NAME(criticalblock){StdMutex::CheckNotHeld(cs)} +#define STDLOCK(cs) StdMutex::Guard BITCOIN_UNIQUE_NAME(criticalblock){StdMutex::CheckNotHeld(cs)} #endif // BITCOIN_UTIL_STDMUTEX_H