diff --git a/.github/workflows/artifacts.yml b/.github/workflows/artifacts.yml new file mode 100644 index 0000000000..e43e1fb5c1 --- /dev/null +++ b/.github/workflows/artifacts.yml @@ -0,0 +1,73 @@ +# Copyright (c) 2025 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +name: Artifacts +on: + # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request. + pull_request: + # See: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#push. + push: + branches: + - '**' + tags-ignore: + - '**' + +jobs: + build: + runs-on: ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: [macos-14, ubuntu-22.04] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: MacOS Install Deps + if: contains(matrix.os, 'macos') + run: | + brew install cmake ccache boost pkgconf libevent qt@6 qrencode coreutils + echo "CCACHE_DIR=${{ runner.temp }}/ccache" >> "$GITHUB_ENV" + + - name: Ubuntu Install Deps + if: contains(matrix.os, 'ubuntu') + run: | + sudo apt-get update && sudo apt-get install -y \ + build-essential ccache cmake pkgconf \ + libevent-dev libboost-dev libsqlite3-dev libgl-dev libqrencode-dev \ + qt6-base-dev qt6-tools-dev qt6-l10n-tools qt6-tools-dev-tools \ + qt6-declarative-dev qml6-module-qtquick qml6-module-qtqml + echo "CCACHE_DIR=${{ runner.temp }}/ccache" >> "$GITHUB_ENV" + + - name: Restore Ccache cache + uses: actions/cache/restore@v4 + id: ccache-cache + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ matrix.os }}-ccache-${{ github.run_id }} + restore-keys: ${{ matrix.os }}-ccache- + + - name: Build + run: | + git submodule update --init + if [[ "${{ matrix.os }}" == macos* ]]; then + export CPLUS_INCLUDE_PATH="$(brew --prefix boost)/include" + export LIBRARY_PATH="$(brew --prefix boost)/lib" + fi + cmake -B build + cmake --build build -j$(nproc) + + - name: Save Ccache cache + uses: actions/cache/save@v4 + if: github.event_name != 'pull_request' && steps.ccache-cache.outputs.cache-hit != 'true' + with: + path: ${{ env.CCACHE_DIR }} + key: ${{ matrix.os }}-ccache-${{ github.run_id }} + + - uses: actions/upload-artifact@v4 + with: + name: unsecure_${{ matrix.os }}_gui + path: build/bin/bitcoin-core-app + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..a452ec9c2f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Build subdirectories. +/*build* +!/build-aux +!/build_msvc + +*.pyc + +/CMakeUserPresets.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..fabdfe5061 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "bitcoin"] + path = bitcoin + url = https://github.com/bitcoin/bitcoin diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..d107250ad4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,128 @@ +cmake_minimum_required(VERSION 3.22) + +project(BitcoinCoreApp + DESCRIPTION "Bitcoin GUI" + HOMEPAGE_URL "https://bitcoincore.org/" + LANGUAGES CXX +) + +# Language setup +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND NOT CMAKE_HOST_APPLE) + # We do not use the install_name_tool when cross-compiling for macOS. + # So disable this tool check in further enable_language() commands. + set(CMAKE_PLATFORM_HAS_INSTALLNAME FALSE) +endif() + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Set top-level target output locations. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) + +# Include Find*.cmake files +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/module) + +# Qt dependencies QML +set(qt_components Core Gui LinguistTools Widgets Qml Quick QuickControls2) +find_package(Qt 6.2 MODULE REQUIRED + COMPONENTS ${qt_components} +) +find_package(Libevent 2.1.8 MODULE REQUIRED) + +# Do not build any executable targets from bitcoin submodule +set(BUILD_BENCH OFF) +set(BUILD_BITCOIN_BIN OFF) +set(BUILD_CLI OFF) +set(BUILD_DAEMON OFF) +set(BUILD_FOR_FUZZING OFF) +set(BUILD_FUZZ_BINARY OFF) +set(BUILD_GUI_TESTS OFF) +set(BUILD_KERNEL_LIB OFF) +set(BUILD_SHARED_LIBS OFF) +set(BUILD_TESTS OFF) +set(BUILD_TX OFF) +set(BUILD_UTIL OFF) +set(BUILD_UTIL_CHAINSTATE OFF) +set(BUILD_WALLET_TOOL OFF) +# We need this libraries, can ignore the executable bitcoin-qt +set(BUILD_GUI ON) +set(ENABLE_WALLET ON) + +# Bitcoin Core codebase +# Builds libraries: univalue, core_interface, bitcoin_node, bitcoin_wallet +add_subdirectory(bitcoin) + +# Qt-specific commands +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOMOC_MOC_OPTIONS "-p${CMAKE_CURRENT_SOURCE_DIR}") +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +# but don't let Qt interfere with bitcoin core libraries +set_target_properties(bitcoin_wallet PROPERTIES AUTOUIC OFF) + +# Compile Qt+QML sources +set(CMAKE_AUTOMOC_MOC_OPTIONS "-I${CMAKE_CURRENT_SOURCE_DIR}/qml") +file(GLOB_RECURSE QML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/qml/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/qml/*.h") +list(FILTER QML_SOURCES EXCLUDE REGEX "androidnotifier\\.(cpp|h)$") +set(QML_QRC "${CMAKE_CURRENT_SOURCE_DIR}/qml/bitcoin_qml.qrc") +qt6_add_resources(QML_QRC_CPP ${QML_QRC}) +list(APPEND QML_SOURCES ${QML_QRC_CPP}) +list(APPEND QML_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/bitcoin/src/init/bitcoin-qt.cpp") + +# Build QML library +add_library(bitcoinqml STATIC ${QML_SOURCES}) +# Prevent Qt macros like "signal" from polluting bitcoin core code +target_compile_definitions(bitcoinqml + PUBLIC + QT_NO_KEYWORDS + QT_USE_QSTRINGBUILDER +) +target_include_directories(bitcoinqml + PRIVATE + # to keep the convention of #include + ${CMAKE_CURRENT_SOURCE_DIR} + + # for interfaces consensus chainparams etc... + ${CMAKE_CURRENT_SOURCE_DIR}/bitcoin/src + + # for qt/guiutil.h qt/peertablemodel.h qt/rpcconsole.h etc... + ${CMAKE_CURRENT_SOURCE_DIR}/bitcoin/src/qt + + # for bitcoin-build-config.h + ${CMAKE_CURRENT_BINARY_DIR}/bitcoin/src +) +target_link_libraries(bitcoinqml + PUBLIC + core_interface + bitcoin_node + univalue + Qt6::Qml + Qt6::Quick + Qt6::QuickControls2 + Qt6::Widgets +) + +# Put it all together +add_executable(bitcoin-core-app main.cpp) +target_include_directories(bitcoin-core-app + PRIVATE + # to keep the convention of #include + ${CMAKE_CURRENT_SOURCE_DIR} + + # for interfaces compat util etc... + ${CMAKE_CURRENT_SOURCE_DIR}/bitcoin/src + + # for bitcoin-build-config.h + ${CMAKE_CURRENT_BINARY_DIR}/bitcoin/src +) +target_link_libraries(bitcoin-core-app + PRIVATE + univalue + core_interface + bitcoin_node + bitcoinqml + bitcoinqt +) \ No newline at end of file diff --git a/qml/README.md b/README.md similarity index 50% rename from qml/README.md rename to README.md index cdf9f187e3..cef9a9e427 100644 --- a/qml/README.md +++ b/README.md @@ -1,16 +1,18 @@ -# Bitcoin Core QML GUI +# Bitcoin Core App + +*(The 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-5/qtquick-index.html) framework. +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. -Unsecure CI artifacts are available for local testing of the master branch, avoiding the need to build. These can be found under the [Actions](https://github.com/bitcoin-core/gui-qml/actions?query=branch%3Amain) tab. It is required to have and be logged into a github account in order to download these. +Unsecure CI artifacts are available for local testing of the master branch, avoiding the need to build. These can be found under the [Actions](https://github.com/bitcoin-core/gui-qml/actions?query=branch%3Aqt6) tab. It is required to have and be logged into a github account in order to download these. Note: For macOS, the CI artifact binary must be made executable and code-signed before it can be ran. To make executable and apply a signature, run the following on the unzipped CI artifact: ``` -chmod +x ./Downloads/bitcoin-qt && codesign -s - ./Downloads/bitcoin-qt +chmod +x ./Downloads/bitcoin-core-app && codesign -s - ./Downloads/bitcoin-core-app ``` ## Goals and Limitations @@ -25,24 +27,22 @@ The primary goals of the project can be summed up as follows: - 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. +Avoid conflicts with the Bitcoin Core repository by importing it unmodified as a [git submodule](https://git-scm.com/book/en/v2/Git-Tools-Submodules). +As such, this project **can not** accept pull requests making any significant changes unrelated to the GUI. +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. +This repo is synced with the [Bitcoin Core repo](https://github.com/bitcoin/bitcoin) on a regular basis. -Contributions are welcome from all, developers and designers. If you are a new contributor, please read [CONTRIBUTING.md](../../CONTRIBUTING.md). +Contributions are welcome from all, developers and designers. If you are a new contributor, please read [CONTRIBUTING.md](https://github.com/bitcoin/bitcoin/blob/master/CONTRIBUTING.md). ### Minimum Required Qt Version -All development must adhere to the current upstream Qt Version to minimize our divergence from upstream and avoid costly changes. Review of open PR's must ensure that changes are compatible with this Qt version. Currently, the required version is [Qt 5.15.2](https://github.com/bitcoin-core/gui-qml/blob/main/depends/packages/qt.mk#L2). +All development must adhere to the current upstream Qt Version to minimize our divergence from upstream and avoid costly changes. Review of open PR's must ensure that changes are compatible with this Qt version. Currently, the required version is [Qt 6.2](https://github.com/bitcoin/bitcoin/blob/master/doc/dependencies.md#build-1). -As the Qt Version changes upstream, refactoring is allowed to use the now available features. +As the Qt Version changes upstream, refactoring is allowed to use the newly available features. ### Policies @@ -52,50 +52,69 @@ This project has custom policies for development, see: ## Compile and Run -The master branch is only guaranteed to work and build on Debian-based systems, Fedora, and macOS. +The master branch is only guaranteed to work and build on Debian-based systems 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: +Bitcoin Core App requires all the same dependencies as Bitcoin Core, see the +appropriate document for your platform: + +- [build-osx.md](https://github.com/bitcoin/bitcoin/blob/master/doc/build-osx.md) + +- [build-unix.md](https://github.com/bitcoin/bitcoin/blob/master/doc/build-unix.md) + +In addition the following dependencies are required for the GUI: #### Debian-based systems: ``` -sudo apt install qtdeclarative5-dev qtquickcontrols2-5-dev +sudo apt install \ + qt6-base-dev \ + qt6-tools-dev \ + qt6-l10n-tools \ + qt6-tools-dev-tools \ + qt6-declarative-dev \ + qml6-module-qtquick \ + qml6-module-qtqml \ + libgl-dev \ + libqrencode-dev ``` -The following runtime dependencies are also required for dynamic builds; -they are not needed for static builds: +Additionally, to support Wayland protocol for modern desktop environments: ``` -sudo apt install qml-module-qtquick2 qml-module-qtquick-controls qml-module-qtquick-controls2 qml-module-qtquick-dialogs qml-module-qtquick-layouts qml-module-qtquick-window2 qml-module-qt-labs-settings +sudo apt install qt6-wayland ``` -##### Important: -If you're unable to install the dependencies through your system's package manager, you can instead perform a [depends build](../../depends/README.md). - -#### Fedora: +#### macOS: ``` -sudo dnf install qt5-qtdeclarative-devel qt5-qtquickcontrols qt5-qtquickcontrols2-devel +brew install qt@6 qrencode ``` ### 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: - +1. Install the required dependencies for your platform and clone the repository +2. Fetch the Bitcoin Core submodule: ``` -./configure --with-qml +git submodule update --init +``` +3. Configure +``` +cmake -B build +``` +4. Build +``` +cmake --build build -j$(nproc) ``` ### Run -To run the qml GUI: +Binaries are exported to the `build/` directory: ``` -./src/qt/bitcoin-qt +build/bin/bitcoin-core-app ``` + + + diff --git a/bitcoin b/bitcoin new file mode 160000 index 0000000000..8ffbd7b778 --- /dev/null +++ b/bitcoin @@ -0,0 +1 @@ +Subproject commit 8ffbd7b778600aa1e824027f1e675929a4240856 diff --git a/cmake/module/FindLibevent.cmake b/cmake/module/FindLibevent.cmake new file mode 100644 index 0000000000..c006b43d60 --- /dev/null +++ b/cmake/module/FindLibevent.cmake @@ -0,0 +1,86 @@ +# Copyright (c) 2024-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +#[=======================================================================[ +FindLibevent +------------ + +Finds the Libevent headers and libraries. + +This is a wrapper around find_package()/pkg_check_modules() commands that: + - facilitates searching in various build environments + - prints a standard log message + +#]=======================================================================] + +# Check whether evhttp_connection_get_peer expects const char**. +# See https://github.com/libevent/libevent/commit/a18301a2bb160ff7c3ffaf5b7653c39ffe27b385 +function(check_evhttp_connection_get_peer target) + include(CMakePushCheckState) + cmake_push_check_state(RESET) + set(CMAKE_REQUIRED_LIBRARIES ${target}) + include(CheckCXXSourceCompiles) + check_cxx_source_compiles(" + #include + #include + + int main() + { + evhttp_connection* conn = (evhttp_connection*)1; + const char* host; + uint16_t port; + evhttp_connection_get_peer(conn, &host, &port); + } + " HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR + ) + cmake_pop_check_state() + target_compile_definitions(${target} INTERFACE + $<$:HAVE_EVHTTP_CONNECTION_GET_PEER_CONST_CHAR> + ) +endfunction() + +set(_libevent_components core extra) +if(NOT WIN32) + list(APPEND _libevent_components pthreads) +endif() + +find_package(Libevent ${Libevent_FIND_VERSION} QUIET + NO_MODULE +) + +include(FindPackageHandleStandardArgs) +if(Libevent_FOUND) + find_package(Libevent ${Libevent_FIND_VERSION} QUIET + REQUIRED COMPONENTS ${_libevent_components} + NO_MODULE + ) + find_package_handle_standard_args(Libevent + REQUIRED_VARS Libevent_DIR + VERSION_VAR Libevent_VERSION + ) + check_evhttp_connection_get_peer(libevent::extra) +else() + find_package(PkgConfig REQUIRED) + foreach(component IN LISTS _libevent_components) + pkg_check_modules(libevent_${component} + REQUIRED QUIET + IMPORTED_TARGET GLOBAL + libevent_${component}>=${Libevent_FIND_VERSION} + ) + if(TARGET PkgConfig::libevent_${component} AND NOT TARGET libevent::${component}) + add_library(libevent::${component} ALIAS PkgConfig::libevent_${component}) + endif() + endforeach() + find_package_handle_standard_args(Libevent + REQUIRED_VARS libevent_core_LIBRARY_DIRS + VERSION_VAR libevent_core_VERSION + ) + check_evhttp_connection_get_peer(PkgConfig::libevent_extra) +endif() + +unset(_libevent_components) + +mark_as_advanced(Libevent_DIR) +mark_as_advanced(_event_h) +mark_as_advanced(_event_lib) diff --git a/cmake/module/FindQt.cmake b/cmake/module/FindQt.cmake new file mode 100644 index 0000000000..d98af5bb56 --- /dev/null +++ b/cmake/module/FindQt.cmake @@ -0,0 +1,45 @@ +# Copyright (c) 2024-present The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/license/mit/. + +#[=======================================================================[ +FindQt +------ + +Finds the Qt headers and libraries. + +This is a wrapper around find_package() command that: + - facilitates searching in various build environments + - prints a standard log message + +#]=======================================================================] + +set(_qt_homebrew_prefix) +if(CMAKE_HOST_APPLE) + find_program(HOMEBREW_EXECUTABLE brew) + if(HOMEBREW_EXECUTABLE) + execute_process( + COMMAND ${HOMEBREW_EXECUTABLE} --prefix qt@${Qt_FIND_VERSION_MAJOR} + OUTPUT_VARIABLE _qt_homebrew_prefix + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + endif() +endif() + +find_package(Qt${Qt_FIND_VERSION_MAJOR} ${Qt_FIND_VERSION} + COMPONENTS ${Qt_FIND_COMPONENTS} + HINTS ${_qt_homebrew_prefix} + PATH_SUFFIXES Qt${Qt_FIND_VERSION_MAJOR} # Required on OpenBSD systems. +) +unset(_qt_homebrew_prefix) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Qt + REQUIRED_VARS Qt${Qt_FIND_VERSION_MAJOR}_DIR + VERSION_VAR Qt${Qt_FIND_VERSION_MAJOR}_VERSION +) + +foreach(component IN LISTS Qt_FIND_COMPONENTS ITEMS "") + mark_as_advanced(Qt${Qt_FIND_VERSION_MAJOR}${component}_DIR) +endforeach() diff --git a/qml/doc/icon-policy.md b/doc/icon-policy.md similarity index 100% rename from qml/doc/icon-policy.md rename to doc/icon-policy.md diff --git a/qml/doc/translator-comments.md b/doc/translator-comments.md similarity index 98% rename from qml/doc/translator-comments.md rename to doc/translator-comments.md index 3558dabb6d..e4feb04cf1 100644 --- a/qml/doc/translator-comments.md +++ b/doc/translator-comments.md @@ -10,7 +10,7 @@ follow-up PR. 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) +[Internationalization and Localization with Qt Quick](https://doc.qt.io/qt-6/i18n-source-translation.html) Below, the values for the `text` and `description` properties are examples of new user-facing strings marked for translation without translator comments: diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000000..723673c18c --- /dev/null +++ b/main.cpp @@ -0,0 +1,46 @@ +// 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 // IWYU pragma: keep + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#if defined(QT_STATICPLUGIN) +#include +#if defined(QT_QPA_PLATFORM_XCB) +Q_IMPORT_PLUGIN(QXcbIntegrationPlugin); +#elif defined(QT_QPA_PLATFORM_WINDOWS) +Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin); +#elif defined(QT_QPA_PLATFORM_COCOA) +Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin); +#elif defined(QT_QPA_PLATFORM_ANDROID) +Q_IMPORT_PLUGIN(QAndroidPlatformIntegrationPlugin) +#endif +#endif + +/** 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 +{ + // Subscribe to global signals from core. + noui_connect(); + + return QmlGuiMain(argc, argv); +} diff --git a/qml/bitcoin.cpp b/qml/bitcoin.cpp index f3f78fac71..513e3c8b07 100644 --- a/qml/bitcoin.cpp +++ b/qml/bitcoin.cpp @@ -4,6 +4,9 @@ #include +#include +#include +#include #include #include #include @@ -12,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -130,7 +134,7 @@ void DebugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons { Q_UNUSED(context); if (type == QtDebugMsg) { - LogPrint(BCLog::QT, "GUI: %s\n", msg.toStdString()); + LogDebug(BCLog::QT, "GUI: %s\n", msg.toStdString()); } else { LogPrintf("GUI: %s\n", msg.toStdString()); } @@ -181,60 +185,48 @@ int QmlGuiMain(int argc, char* argv[]) Q_INIT_RESOURCE(bitcoin_qml); qRegisterMetaType("interfaces::BlockAndHeaderTipInfo"); - QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 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); + auto handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox); SetupEnvironment(); util::ThreadSetInternalName("main"); // must be set before parsing command-line options; otherwise, // if invalid parameters were passed, QSetting initialization would fail - // and the error will be displayed on terminal + // and the error will be displayed on terminal. + // must be set before OptionsModel is initialized or translations are loaded, + // as it is used to locate QSettings app.setOrganizationName(QAPP_ORG_NAME); app.setOrganizationDomain(QAPP_ORG_DOMAIN); app.setApplicationName(QAPP_APP_NAME_DEFAULT); - /// Parse command-line options. We do this after qt in order to show an error if there are problems parsing these. - SetupServerArgs(gArgs); + // Parse command-line options. We do this after qt in order to show an error if there are problems parsing these. + SetupServerArgs(gArgs, init->canListenIpc()); + SetupUIArgs(gArgs); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { - InitError(strprintf(Untranslated("Cannot parse command line arguments: %s\n"), error)); - return EXIT_FAILURE; - } - - /// Determine availability of data directory. - if (!CheckDataDirOption(gArgs)) { - InitError(strprintf(Untranslated("Specified data directory \"%s\" does not exist.\n"), gArgs.GetArg("-datadir", ""))); + InitError(Untranslated(strprintf("Cannot parse command line arguments: %s\n", error))); return EXIT_FAILURE; } - /// Read and parse bitcoin.conf file. - if (!gArgs.ReadConfigFiles(error, true)) { - InitError(strprintf(Untranslated("Cannot parse configuration file: %s\n"), error)); + if (auto error = common::InitConfig( + gArgs, + [](const bilingual_str& msg, const std::vector& details) { + return InitError(msg, details); + })) { 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; - } - - /// Read and parse settings.json file. - std::vector errors; - if (!gArgs.ReadSettingsFile(&errors)) { - error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors)); - InitError(Untranslated(error)); - return EXIT_FAILURE; - } + // legacy GUI: parameterSetup() + // Default printtoconsole to false for the GUI. GUI programs should not + // print to the console unnecessarily. + gArgs.SoftSetBoolArg("-printtoconsole", false); + InitLogging(gArgs); + InitParameterInteraction(gArgs); QVariant need_onboarding(true); if (gArgs.IsArgSet("-datadir") && !gArgs.GetPathArg("-datadir").empty()) { @@ -247,16 +239,11 @@ int QmlGuiMain(int argc, char* argv[]) need_onboarding.setValue(true); } - // Default printtoconsole to false for the GUI. GUI programs should not - // print to the console unnecessarily. - gArgs.SoftSetBoolArg("-printtoconsole", false); - InitLogging(gArgs); - InitParameterInteraction(gArgs); - - GUIUtil::LogQtInfo(); - + // legacy GUI: createNode() std::unique_ptr node = init->makeNode(); std::unique_ptr chain = init->makeChain(); + + // legacy GUI: baseInitialize() if (!node->baseInitialize()) { // A dialog with detailed error will have been shown by InitError(). return EXIT_FAILURE; @@ -287,7 +274,7 @@ int QmlGuiMain(int argc, char* argv[]) #endif ChainModel chain_model{*chain}; - chain_model.setCurrentNetworkName(QString::fromStdString(ChainTypeToString(gArgs.GetChainType()))); + chain_model.setCurrentNetworkName(QString::fromStdString(gArgs.GetChainTypeString())); setupChainQSettings(&app, chain_model.currentNetworkName()); QObject::connect(&node_model, &NodeModel::setTimeRatioList, &chain_model, &ChainModel::setTimeRatioList); diff --git a/qml/bitcoin_qml.qrc b/qml/bitcoin_qml.qrc index edeede29e3..2a1a9c4432 100644 --- a/qml/bitcoin_qml.qrc +++ b/qml/bitcoin_qml.qrc @@ -108,7 +108,7 @@ res/icons/blockclock-size-showcase.png res/icons/blocktime-dark.png res/icons/blocktime-light.png - ../qt/res/icons/bitcoin.png + res/icons/bitcoin.png res/icons/caret-down-medium-filled.png res/icons/caret-left.png res/icons/caret-right.png diff --git a/qml/bitcoinamount.cpp b/qml/bitcoinamount.cpp index 3cc31605d8..590e6b750c 100644 --- a/qml/bitcoinamount.cpp +++ b/qml/bitcoinamount.cpp @@ -4,7 +4,7 @@ #include -#include +#include #include BitcoinAmount::BitcoinAmount(QObject* parent) @@ -17,7 +17,7 @@ QString BitcoinAmount::sanitize(const QString &text) QString result = text; // Remove any invalid characters - result.remove(QRegExp("[^0-9.]")); + result.remove(QRegularExpression("[^0-9.]")); // Ensure only one decimal point QStringList parts = result.split('.'); @@ -147,7 +147,7 @@ void BitcoinAmount::fromDisplay(const QString& text) newSat = btcToSats(sanitized); } else { QString digitsOnly = text; - digitsOnly.remove(QRegExp("[^0-9]")); + digitsOnly.remove(QRegularExpression("[^0-9]")); newSat = digitsOnly.trimmed().isEmpty() ? 0 : digitsOnly.toLongLong(); } setSatoshi(newSat); diff --git a/qml/components/ConnectionSettings.qml b/qml/components/ConnectionSettings.qml index 35e78b7a6e..385cb5ab45 100644 --- a/qml/components/ConnectionSettings.qml +++ b/qml/components/ConnectionSettings.qml @@ -25,19 +25,6 @@ ColumnLayout { } } Separator { Layout.fillWidth: true } - Setting { - Layout.fillWidth: true - header: qsTr("Map port using UPnP") - actionItem: OptionSwitch { - checked: optionsModel.upnp - onToggled: optionsModel.upnp = checked - } - onClicked: { - loadedItem.toggle() - loadedItem.toggled() - } - } - Separator { Layout.fillWidth: true } Setting { Layout.fillWidth: true header: qsTr("Map port using NAT-PMP") diff --git a/qml/components/StorageLocations.qml b/qml/components/StorageLocations.qml index acaa69b40a..52cd39850b 100644 --- a/qml/components/StorageLocations.qml +++ b/qml/components/StorageLocations.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 -import QtQuick.Dialogs 1.3 +import QtQuick.Dialogs import org.bitcoincore.qt 1.0 @@ -41,8 +41,7 @@ ColumnLayout { } FileDialog { id: fileDialog - selectFolder: true - folder: shortcuts.home + currentFolder: shortcuts.home onAccepted: { optionsModel.setCustomDataDirString(fileDialog.fileUrls[0].toString()) var customDataDir = fileDialog.fileUrl.toString(); diff --git a/qml/components/blockclockdial.cpp b/qml/components/blockclockdial.cpp index a6842702ea..f39658b5f5 100644 --- a/qml/components/blockclockdial.cpp +++ b/qml/components/blockclockdial.cpp @@ -20,9 +20,9 @@ BlockClockDial::BlockClockDial(QQuickItem *parent) m_delay_timer.setSingleShot(true); m_delay_timer.setInterval(5000); connect(&m_delay_timer, &QTimer::timeout, - this, [=]() { this->m_animation_timer.start(); }); + this, [this]() { this->m_animation_timer.start(); }); connect(&m_animation_timer, &QTimer::timeout, - this, [=]() { + this, [=, this]() { if (m_is_connected && getTargetAnimationAngle() - m_animating_max_angle < 1) { m_animation_timer.stop(); diff --git a/qml/controls/linegraph.cpp b/qml/controls/linegraph.cpp index 1ba1a0f5a5..6c59d0d919 100644 --- a/qml/controls/linegraph.cpp +++ b/qml/controls/linegraph.cpp @@ -94,7 +94,7 @@ void LineGraph::paintMarkerLines(QPainter * painter) void LineGraph::paintPath(QPainterPath * painter_path) { - int item_count = std::min(m_max_samples, m_value_list.size()); + int item_count = std::min(m_max_samples, static_cast(m_value_list.size())); qreal h = height(); qreal w = width(); diff --git a/qml/models/chainmodel.cpp b/qml/models/chainmodel.cpp index aeffe99599..ce3a6b12ae 100644 --- a/qml/models/chainmodel.cpp +++ b/qml/models/chainmodel.cpp @@ -10,6 +10,8 @@ #include #include +using interfaces::FoundBlock; + ChainModel::ChainModel(interfaces::Chain& chain) : m_chain{chain} { @@ -77,7 +79,10 @@ void ChainModel::setTimeRatioListInitial() } 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); + uint256 block_hash{m_chain.getBlockHash(height)}; + int64_t block_time; + m_chain.findBlock(block_hash, FoundBlock().time(block_time)); + m_time_ratio_list.push_back(double(block_time - time_at_meridian) / SECS_IN_12_HOURS); } Q_EMIT timeRatioListChanged(); diff --git a/qml/models/networktraffictower.cpp b/qml/models/networktraffictower.cpp index 57dd9c438f..9a88bff89d 100644 --- a/qml/models/networktraffictower.cpp +++ b/qml/models/networktraffictower.cpp @@ -73,7 +73,7 @@ void NetworkTrafficTower::updateFilterWindowSize(int new_size) float NetworkTrafficTower::applyMovingAverageFilter(QQueue * rate_list) { - int filter_window_size = std::min(rate_list->size(), m_filter_window_size); + int filter_window_size = std::min(static_cast(rate_list->size()), m_filter_window_size); float sum = 0.0f; for (int i = 0; i < filter_window_size; ++i) { sum += rate_list->at(i); @@ -85,7 +85,7 @@ float NetworkTrafficTower::applyMovingAverageFilter(QQueue * rate_list) float NetworkTrafficTower::calculateMaxRateBps(QQueue * smoothed_rate_list) { float max_rate_bps = 0.0f; - int lookback = std::min(smoothed_rate_list->size() - 1, m_filter_window_size * 10); + int lookback = std::min(static_cast(smoothed_rate_list->size()) - 1, m_filter_window_size * 10); for (int i = lookback; i > 0; --i) { if (smoothed_rate_list->at(i) > max_rate_bps) { max_rate_bps = smoothed_rate_list->at(i); diff --git a/qml/models/nodemodel.cpp b/qml/models/nodemodel.cpp index 8ccc532016..be6fc4adba 100644 --- a/qml/models/nodemodel.cpp +++ b/qml/models/nodemodel.cpp @@ -148,7 +148,7 @@ void NodeModel::ConnectToBlockTipSignal() m_handler_notify_block_tip = m_node.handleNotifyBlockTip( [this](SynchronizationState state, interfaces::BlockTip tip, double verification_progress) { - QMetaObject::invokeMethod(this, [=] { + QMetaObject::invokeMethod(this, [&, this] { setBlockTipHeight(tip.block_height); setVerificationProgress(verification_progress); @@ -162,17 +162,31 @@ void NodeModel::ConnectToNumConnectionsChangedSignal() assert(!m_handler_notify_num_peers_changed); m_handler_notify_num_peers_changed = m_node.handleNotifyNumConnectionsChanged( - [this](PeersNumByType new_num_peers) { - setNumOutboundPeers(new_num_peers.outbound_full_relay + new_num_peers.block_relay); + [this](int new_num_connections) { + setNumOutboundPeers(new_num_connections); }); } bool NodeModel::validateProxyAddress(QString address_port) { - return m_node.validateProxyAddress(address_port.toStdString()); + uint16_t port{0}; + std::string addr_port{address_port.toStdString()}; + std::string hostname; + // First, attempt to split the input address into hostname and port components. + // We call SplitHostPort to validate that a port is provided in addr_port. + // If either splitting fails or port is zero (not specified), return false. + if (!SplitHostPort(addr_port, port, hostname) || !port) return false; + + // Create a service endpoint (CService) from the address and port. + // If port is missing in addr_port, DEFAULT_PROXY_PORT is used as the fallback. + CService serv(LookupNumeric(addr_port, DEFAULT_PROXY_PORT)); + + // Construct the Proxy with the service endpoint and return if it's valid + Proxy addrProxy = Proxy(serv, true); + return addrProxy.IsValid(); } QString NodeModel::defaultProxyAddress() { - return QString::fromStdString(m_node.defaultProxyAddress()); + return QString::fromStdString(std::string(DEFAULT_PROXY_HOST) + ":" + util::ToString(DEFAULT_PROXY_PORT)); } diff --git a/qml/models/nodemodel.h b/qml/models/nodemodel.h index 8603c44d36..993da098a5 100644 --- a/qml/models/nodemodel.h +++ b/qml/models/nodemodel.h @@ -14,6 +14,9 @@ #include #include +const char DEFAULT_PROXY_HOST[] = "127.0.0.1"; +constexpr uint16_t DEFAULT_PROXY_PORT = 9050; + QT_BEGIN_NAMESPACE class QTimerEvent; QT_END_NAMESPACE diff --git a/qml/models/options_model.cpp b/qml/models/options_model.cpp index 9e95152311..fe850eb30b 100644 --- a/qml/models/options_model.cpp +++ b/qml/models/options_model.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -28,7 +30,7 @@ OptionsQmlModel::OptionsQmlModel(interfaces::Node& node, bool is_onboarded) : m_node{node} , m_onboarded{is_onboarded} { - m_dbcache_size_mib = SettingToInt(m_node.getPersistentSetting("dbcache"), nDefaultDbCache); + m_dbcache_size_mib = SettingToInt(m_node.getPersistentSetting("dbcache"), DEFAULT_DB_CACHE >> 20); m_listen = SettingToBool(m_node.getPersistentSetting("listen"), DEFAULT_LISTEN); @@ -42,8 +44,6 @@ OptionsQmlModel::OptionsQmlModel(interfaces::Node& node, bool is_onboarded) m_server = SettingToBool(m_node.getPersistentSetting("server"), false); - m_upnp = SettingToBool(m_node.getPersistentSetting("upnp"), DEFAULT_UPNP); - m_dataDir = getDefaultDataDirString(); } @@ -124,17 +124,6 @@ void OptionsQmlModel::setServer(bool new_server) } } -void OptionsQmlModel::setUpnp(bool new_upnp) -{ - if (new_upnp != m_upnp) { - m_upnp = new_upnp; - if (m_onboarded) { - m_node.updateRwSetting("upnp", new_upnp); - } - Q_EMIT upnpChanged(new_upnp); - } -} - common::SettingsValue OptionsQmlModel::pruneSetting() const { assert(!m_prune || m_prune_size_gb >= 1); @@ -143,7 +132,7 @@ common::SettingsValue OptionsQmlModel::pruneSetting() const QString PathToQString(const fs::path &path) { - return QString::fromStdString(path.u8string()); + return QString::fromStdString(path.utf8string()); } QString OptionsQmlModel::getDefaultDataDirString() @@ -204,7 +193,7 @@ void OptionsQmlModel::setDataDir(QString new_data_dir) void OptionsQmlModel::onboard() { m_node.resetSettings(); - if (m_dbcache_size_mib != nDefaultDbCache) { + if (m_dbcache_size_mib != DEFAULT_DB_CACHE >> 20) { m_node.updateRwSetting("dbcache", m_dbcache_size_mib); } if (m_listen) { @@ -222,8 +211,5 @@ void OptionsQmlModel::onboard() if (m_server) { m_node.updateRwSetting("server", m_server); } - if (m_upnp) { - m_node.updateRwSetting("upnp", m_upnp); - } m_onboarded = true; } diff --git a/qml/models/options_model.h b/qml/models/options_model.h index 459d40b574..8d76f64543 100644 --- a/qml/models/options_model.h +++ b/qml/models/options_model.h @@ -7,6 +7,8 @@ #include #include +#include +#include #include #include @@ -33,7 +35,6 @@ class OptionsQmlModel : public QObject Q_PROPERTY(int pruneSizeGB READ pruneSizeGB WRITE setPruneSizeGB NOTIFY pruneSizeGBChanged) Q_PROPERTY(int scriptThreads READ scriptThreads WRITE setScriptThreads NOTIFY scriptThreadsChanged) Q_PROPERTY(bool server READ server WRITE setServer NOTIFY serverChanged) - Q_PROPERTY(bool upnp READ upnp WRITE setUpnp NOTIFY upnpChanged) Q_PROPERTY(QString dataDir READ dataDir WRITE setDataDir NOTIFY dataDirChanged) Q_PROPERTY(QString getDefaultDataDirString READ getDefaultDataDirString CONSTANT) Q_PROPERTY(QUrl getDefaultDataDirectory READ getDefaultDataDirectory CONSTANT) @@ -59,8 +60,6 @@ class OptionsQmlModel : public QObject void setScriptThreads(int new_script_threads); bool server() const { return m_server; } void setServer(bool new_server); - bool upnp() const { return m_upnp; } - void setUpnp(bool new_upnp); QString dataDir() const { return m_dataDir; } void setDataDir(QString new_data_dir); QString getDefaultDataDirString(); @@ -82,7 +81,6 @@ public Q_SLOTS: void pruneSizeGBChanged(int new_prune_size_gb); void scriptThreadsChanged(int new_script_threads); void serverChanged(bool new_server); - void upnpChanged(bool new_upnp); void customDataDirStringChanged(QString new_custom_datadir_string); void dataDirChanged(QString new_data_dir); @@ -92,8 +90,8 @@ public Q_SLOTS: // Properties that are exposed to QML. int m_dbcache_size_mib; - const int m_min_dbcache_size_mib{nMinDbCache}; - const int m_max_dbcache_size_mib{nMaxDbCache}; + const int m_min_dbcache_size_mib{MIN_DB_CACHE >> 20}; + const int m_max_dbcache_size_mib{MAX_COINS_DB_CACHE >> 20}; bool m_listen; const int m_max_script_threads{MAX_SCRIPTCHECK_THREADS}; const int m_min_script_threads{-GetNumCores()}; @@ -102,7 +100,6 @@ public Q_SLOTS: int m_prune_size_gb; int m_script_threads; bool m_server; - bool m_upnp; QString m_custom_datadir_string; QString m_dataDir; diff --git a/qml/models/peerdetailsmodel.h b/qml/models/peerdetailsmodel.h index 9f6b17ad83..3b0211c119 100644 --- a/qml/models/peerdetailsmodel.h +++ b/qml/models/peerdetailsmodel.h @@ -64,7 +64,7 @@ class PeerDetailsModel : public QObject QString pingTime() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStats.m_last_ping_time); } QString pingMin() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStats.m_min_ping_time); } QString pingWait() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStateStats.m_ping_wait); } - QString timeOffset() const { return GUIUtil::formatTimeOffset(m_combinedStats->nodeStats.nTimeOffset); } + QString timeOffset() const { return GUIUtil::formatTimeOffset(Ticks(m_combinedStats->nodeStateStats.time_offset)); } QString mappedAS() const { return m_combinedStats->nodeStats.m_mapped_as != 0 ? QString::number(m_combinedStats->nodeStats.m_mapped_as) : tr("N/A"); } QString permission() const { if (m_combinedStats->nodeStats.m_permission_flags == NetPermissionFlags::None) { diff --git a/qml/models/transaction.cpp b/qml/models/transaction.cpp index d50baa3a91..f9a6de3523 100644 --- a/qml/models/transaction.cpp +++ b/qml/models/transaction.cpp @@ -12,7 +12,6 @@ using wallet::ISMINE_SPENDABLE; using wallet::ISMINE_NO; -using wallet::ISMINE_WATCH_ONLY; using wallet::isminetype; namespace { @@ -150,18 +149,12 @@ QList> Transaction::fromWalletTx(const interfaces::W } else { for (const isminetype mine : wtx.txin_is_mine) { - if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; if(fAllFromMe > mine) fAllFromMe = mine; if (mine) any_from_me = true; } } if (fAllFromMe || !any_from_me) { - for (const isminetype mine : wtx.txout_is_mine) - { - if(mine & ISMINE_WATCH_ONLY) involvesWatchAddress = true; - } - CAmount nTxFee = nDebit - wtx.tx->GetValueOut(); @@ -219,7 +212,7 @@ QList> Transaction::fromWalletTx(const interfaces::W QSharedPointer sub = QSharedPointer::create(hash, nTime); sub->idx = i; // vout index sub->credit = txout.nValue; - sub->involvesWatchAddress = mine & ISMINE_WATCH_ONLY; + sub->involvesWatchAddress = false; if (wtx.txout_address_is_mine[i]) { // Received by Bitcoin Address diff --git a/qml/models/walletlistmodel.cpp b/qml/models/walletlistmodel.cpp index 9bc0f90eae..def378b7d9 100644 --- a/qml/models/walletlistmodel.cpp +++ b/qml/models/walletlistmodel.cpp @@ -23,8 +23,8 @@ void WalletListModel::listWalletDir() existing_names.insert(name); } - for (const std::string &name : m_node.walletLoader().listWalletDir()) { - QString qname = QString::fromStdString(name); + for (const auto& [path, info] : m_node.walletLoader().listWalletDir()) { + QString qname = QString::fromStdString(path); if (!existing_names.contains(qname)) { addItem({ qname }); } diff --git a/qml/models/walletqmlmodel.cpp b/qml/models/walletqmlmodel.cpp index f05b2b38bb..abba0a4ef6 100644 --- a/qml/models/walletqmlmodel.cpp +++ b/qml/models/walletqmlmodel.cpp @@ -76,7 +76,7 @@ interfaces::WalletTx WalletQmlModel::getWalletTx(const uint256& hash) const if (!m_wallet) { return {}; } - return m_wallet->getWalletTx(hash); + return m_wallet->getWalletTx(Txid::FromUint256(hash)); } bool WalletQmlModel::tryGetTxStatus(const uint256& txid, @@ -87,7 +87,7 @@ bool WalletQmlModel::tryGetTxStatus(const uint256& txid, if (!m_wallet) { return false; } - return m_wallet->tryGetTxStatus(txid, tx_status, num_blocks, block_time); + return m_wallet->tryGetTxStatus(Txid::FromUint256(txid), tx_status, num_blocks, block_time); } std::unique_ptr WalletQmlModel::handleTransactionChanged(TransactionChangedFn fn) @@ -107,8 +107,8 @@ bool WalletQmlModel::prepareTransaction() std::vector vecSend; CAmount total = 0; for (auto* recipient : m_send_recipients->recipients()) { - CScript scriptPubKey = GetScriptForDestination(DecodeDestination(recipient->address().toStdString())); - wallet::CRecipient c_recipient = {scriptPubKey, recipient->cAmount(), recipient->subtractFeeFromAmount()}; + CTxDestination destination = DecodeDestination(recipient->address().toStdString()); + wallet::CRecipient c_recipient = {destination, recipient->cAmount(), recipient->subtractFeeFromAmount()}; m_coin_control.m_feerate = CFeeRate(1000); vecSend.push_back(c_recipient); total += recipient->cAmount(); diff --git a/qml/pages/wallet/CreateName.qml b/qml/pages/wallet/CreateName.qml index a780f23f76..e216801162 100644 --- a/qml/pages/wallet/CreateName.qml +++ b/qml/pages/wallet/CreateName.qml @@ -49,7 +49,7 @@ Page { Layout.leftMargin: 20 Layout.rightMargin: 20 placeholderText: qsTr("Eg. My bitcoin wallet...") - validator: RegExpValidator { regExp: /^[a-zA-Z0-9_]{1,20}$/ } + validator: RegularExpressionValidator { regularExpression: /^[a-zA-Z0-9_]{1,20}$/ } onTextChanged: { continueButton.enabled = walletNameInput.text.length > 0 } diff --git a/qml/pages/wallet/Send.qml b/qml/pages/wallet/Send.qml index 65b3b86cdf..3e8bb39c7f 100644 --- a/qml/pages/wallet/Send.qml +++ b/qml/pages/wallet/Send.qml @@ -240,7 +240,7 @@ PageStack { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter source: "image://images/flip-vertical" - icon.color: unitLabel.enabled ? Theme.color.neutral8 : Theme.color.neutral4 + color: unitLabel.enabled ? Theme.color.neutral8 : Theme.color.neutral4 size: 30 } } diff --git a/qml/pages/wallet/SendResult.qml b/qml/pages/wallet/SendResult.qml index 10f5c0449b..3b119aa7c4 100644 --- a/qml/pages/wallet/SendResult.qml +++ b/qml/pages/wallet/SendResult.qml @@ -5,7 +5,7 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 -import QtQuick.Dialogs 1.2 +import QtQuick.Dialogs import org.bitcoincore.qt 1.0 import "../../controls" diff --git a/qml/res/icons/bitcoin.png b/qml/res/icons/bitcoin.png new file mode 100644 index 0000000000..435621af23 Binary files /dev/null and b/qml/res/icons/bitcoin.png differ diff --git a/qml/util.cpp b/qml/util.cpp index 4624150e8b..c2532f02e7 100644 --- a/qml/util.cpp +++ b/qml/util.cpp @@ -18,15 +18,16 @@ QString GraphicsApi(QQuickWindow* window) switch (window->rendererInterface()->graphicsApi()) { case QSGRendererInterface::Unknown: return "Unknown"; case QSGRendererInterface::Software: return "The Qt Quick 2D Renderer"; - case QSGRendererInterface::OpenGL: return "OpenGL ES 2.0 or higher"; - case QSGRendererInterface::Direct3D12: return "Direct3D 12"; case QSGRendererInterface::OpenVG: return "OpenVG via EGL"; #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - case QSGRendererInterface::OpenGLRhi: return "OpenGL ES 2.0 or higher via a graphics abstraction layer"; - case QSGRendererInterface::Direct3D11Rhi: return "Direct3D 11 via a graphics abstraction layer"; - case QSGRendererInterface::VulkanRhi: return "Vulkan 1.0 via a graphics abstraction layer"; - case QSGRendererInterface::MetalRhi: return "Metal via a graphics abstraction layer"; - case QSGRendererInterface::NullRhi: return "Null (no output) via a graphics abstraction layer"; + 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"; + 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"; +#endif +#if (QT_VERSION >= QT_VERSION_CHECK(6, 6, 0)) + case QSGRendererInterface::Direct3D12: return "Direct3D 12 via a graphics abstraction layer"; #endif } // no default case, so the compiler can warn about missing cases assert(false);