diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index f371a5b5aa..64872a11e8 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -43,8 +43,9 @@ QT_MOC_CPP = \ qml/models/moc_options_model.cpp \ qml/models/moc_peerlistsortproxy.cpp \ qml/models/moc_walletlistmodel.cpp \ + qml/models/moc_walletqmlmodel.cpp \ qml/moc_appmode.cpp \ - qml/moc_walletcontroller.cpp \ + qml/moc_walletqmlcontroller.cpp \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_askpassphrasedialog.cpp \ @@ -124,12 +125,13 @@ BITCOIN_QT_H = \ qml/models/options_model.h \ qml/models/peerlistsortproxy.h \ qml/models/walletlistmodel.h \ + qml/models/walletqmlmodel.h \ qml/appmode.h \ qml/bitcoin.h \ qml/guiconstants.h \ qml/imageprovider.h \ qml/util.h \ - qml/walletcontroller.h \ + qml/walletqmlcontroller.h \ qt/addressbookpage.h \ qt/addresstablemodel.h \ qt/askpassphrasedialog.h \ @@ -314,9 +316,10 @@ BITCOIN_QML_BASE_CPP = \ qml/models/options_model.cpp \ qml/models/peerlistsortproxy.cpp \ qml/models/walletlistmodel.cpp \ + qml/models/walletqmlmodel.cpp \ qml/imageprovider.cpp \ qml/util.cpp \ - qml/walletcontroller.cpp + qml/walletqmlcontroller.cpp QML_RES_FONTS = \ qml/res/fonts/Inter-Regular.otf \ diff --git a/src/qml/bitcoin.cpp b/src/qml/bitcoin.cpp index 367512cbe1..6706b76186 100644 --- a/src/qml/bitcoin.cpp +++ b/src/qml/bitcoin.cpp @@ -27,9 +27,10 @@ #include #include #include +#include #include #include -#include +#include #include #include #include @@ -276,8 +277,17 @@ int QmlGuiMain(int argc, char* argv[]) QObject::connect(&node_model, &NodeModel::setTimeRatioList, &chain_model, &ChainModel::setTimeRatioList); QObject::connect(&node_model, &NodeModel::setTimeRatioListInitial, &chain_model, &ChainModel::setTimeRatioListInitial); + +#ifdef ENABLE_WALLET + WalletQmlController wallet_controller(*node); + QObject::connect(&init_executor, &InitExecutor::initializeResult, &wallet_controller, &WalletQmlController::initialize); +#endif + qGuiApp->setQuitOnLastWindowClosed(false); QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] { +#ifdef ENABLE_WALLET + wallet_controller.unloadWallets(); +#endif node->startShutdown(); }); @@ -288,8 +298,6 @@ int QmlGuiMain(int argc, char* argv[]) GUIUtil::LoadFont(":/fonts/inter/regular"); GUIUtil::LoadFont(":/fonts/inter/semibold"); - WalletController wallet_controller(*node); - QQmlApplicationEngine engine; QScopedPointer network_style{NetworkStyle::instantiate(Params().GetChainType())}; @@ -316,6 +324,9 @@ int QmlGuiMain(int argc, char* argv[]) qmlRegisterType("org.bitcoincore.qt", 1, 0, "BlockClockDial"); qmlRegisterType("org.bitcoincore.qt", 1, 0, "LineGraph"); + qmlRegisterUncreatableType("org.bitcoincore.qt", 1, 0, "WalletQmlModel", + "WalletQmlModel cannot be instantiated from QML"); + engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml"))); if (engine.rootObjects().isEmpty()) { return EXIT_FAILURE; diff --git a/src/qml/models/walletlistmodel.cpp b/src/qml/models/walletlistmodel.cpp index ecf97b025a..9bc0f90eae 100644 --- a/src/qml/models/walletlistmodel.cpp +++ b/src/qml/models/walletlistmodel.cpp @@ -12,7 +12,6 @@ WalletListModel::WalletListModel(interfaces::Node& node, QObject *parent) : QAbstractListModel(parent) , m_node(node) { - setSelectedWallet("Singlesig Wallet"); } void WalletListModel::listWalletDir() @@ -32,19 +31,6 @@ void WalletListModel::listWalletDir() } } -void WalletListModel::setSelectedWallet(QString wallet_name) -{ - if (m_selected_wallet != wallet_name) { - m_selected_wallet = wallet_name; - Q_EMIT selectedWalletChanged(); - } -} - -QString WalletListModel::selectedWallet() const -{ - return m_selected_wallet; -} - int WalletListModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); diff --git a/src/qml/models/walletlistmodel.h b/src/qml/models/walletlistmodel.h index ae1451b21a..c36cb2b4fc 100644 --- a/src/qml/models/walletlistmodel.h +++ b/src/qml/models/walletlistmodel.h @@ -16,7 +16,6 @@ class Node; class WalletListModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString selectedWallet READ selectedWallet WRITE setSelectedWallet NOTIFY selectedWalletChanged) public: WalletListModel(interfaces::Node& node, QObject *parent = nullptr); @@ -30,15 +29,9 @@ class WalletListModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QHash roleNames() const override; - void setSelectedWallet(QString wallet_name); - QString selectedWallet() const; - public Q_SLOTS: void listWalletDir(); -Q_SIGNALS: - void selectedWalletChanged(); - private: struct Item { QString name; @@ -48,8 +41,6 @@ public Q_SLOTS: QList m_items; interfaces::Node& m_node; - QString m_selected_wallet; - }; #endif // BITCOIN_QML_MODELS_WALLETLISTMODEL_H diff --git a/src/qml/models/walletqmlmodel.cpp b/src/qml/models/walletqmlmodel.cpp new file mode 100644 index 0000000000..3e51b5f29b --- /dev/null +++ b/src/qml/models/walletqmlmodel.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2024 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 + +WalletQmlModel::WalletQmlModel(std::unique_ptr wallet, QObject *parent) + : QObject(parent) +{ + m_wallet = std::move(wallet); +} + +WalletQmlModel::WalletQmlModel(QObject *parent) + : QObject(parent) +{ +} + +QString WalletQmlModel::balance() const +{ + if (!m_wallet) { + return "0"; + } + return BitcoinUnits::format(BitcoinUnits::Unit::BTC, m_wallet->getBalance()); +} + +QString WalletQmlModel::name() const +{ + if (!m_wallet) { + return QString(); + } + return QString::fromStdString(m_wallet->getWalletName()); +} diff --git a/src/qml/models/walletqmlmodel.h b/src/qml/models/walletqmlmodel.h new file mode 100644 index 0000000000..1c66709947 --- /dev/null +++ b/src/qml/models/walletqmlmodel.h @@ -0,0 +1,34 @@ +// Copyright (c) 2024 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_MODELS_WALLETQMLMODEL_H +#define BITCOIN_QML_MODELS_WALLETQMLMODEL_H + +#include + +#include + +class WalletQmlModel : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + Q_PROPERTY(QString balance READ balance NOTIFY balanceChanged) + +public: + WalletQmlModel(std::unique_ptr wallet, QObject *parent = nullptr); + WalletQmlModel(QObject *parent = nullptr); + ~WalletQmlModel() = default; + + QString name() const; + QString balance() const; + +Q_SIGNALS: + void nameChanged(); + void balanceChanged(); + +private: + std::unique_ptr m_wallet; +}; + +#endif // BITCOIN_QML_MODELS_WALLETQMLMODEL_H diff --git a/src/qml/pages/wallet/DesktopWallets.qml b/src/qml/pages/wallet/DesktopWallets.qml index 59a7ac15e4..1e45d8d248 100644 --- a/src/qml/pages/wallet/DesktopWallets.qml +++ b/src/qml/pages/wallet/DesktopWallets.qml @@ -24,7 +24,8 @@ Page { leftItem: WalletBadge { implicitWidth: 154 implicitHeight: 46 - text: walletListModel.selectedWallet + text: walletController.selectedWallet.name + balance: walletController.selectedWallet.balance MouseArea { anchors.fill: parent diff --git a/src/qml/pages/wallet/WalletBadge.qml b/src/qml/pages/wallet/WalletBadge.qml index fe28d58f47..befc49eddf 100644 --- a/src/qml/pages/wallet/WalletBadge.qml +++ b/src/qml/pages/wallet/WalletBadge.qml @@ -13,70 +13,6 @@ import "../../controls" Button { id: root - function formatSatoshis(satoshis) { - var highlightColor = Theme.color.neutral9 - var zeroColor = Theme.color.neutral7 - - if (root.checked || root.hovered) { - highlightColor = zeroColor = Theme.color.orange - } - - // Convert satoshis to bitcoins - var bitcoins = satoshis / 100000000; - - // Format bitcoins to a fixed 8 decimal places string - var bitcoinStr = bitcoins.toFixed(8); - - // Split the bitcoin string into integer and fractional parts - var parts = bitcoinStr.split('.'); - - // Add spaces for every 3 digits in the integer part - parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' '); - - // Highlight the first significant digit and all following digits in the integer part - var significantFound = false; - parts[0] = parts[0].replace(/(\d)/g, function(match) { - if (!significantFound && match !== '0') { - significantFound = true; - } - if (significantFound) { - return '' + match + ''; - } - return match; - }); - - // Add spaces for every 3 digits in the decimal part - parts[1] = parts[1].replace(/\B(?=(\d{3})+(?!\d))/g, ' '); - if (significantFound) { - parts[1] = '' + parts[1] + ''; - } else { - // Highlight the first significant digit and all following digits in the fractional part - significantFound = false; - parts[1] = parts[1].replace(/(\d)/g, function(match) { - if (!significantFound && match !== '0') { - significantFound = true; - } - if (significantFound) { - return '' + match + ''; - } - return match; - }); - } - - // Concatenate the parts back together - var formattedBitcoins = parts.join('.'); - - // Format the text with the Bitcoin symbol - var formattedText = ` ${formattedBitcoins}`; - - // Highlight zero in a different color if satoshis are zero - if (satoshis === 0) { - formattedText = `₿ 0.00`; - } - - return formattedText; - } - property color bgActiveColor: Theme.color.neutral2 property color textColor: Theme.color.neutral7 property color textHoverColor: Theme.color.orange @@ -85,17 +21,17 @@ Button { property string iconSource: "" property bool showBalance: true property bool showIcon: true + property string balance: "0.0 000 000" checkable: true hoverEnabled: AppMode.isDesktop implicitHeight: 60 - implicitWidth: 220 + implicitWidth: contentItem.width bottomPadding: 0 topPadding: 0 clip: true contentItem: RowLayout { - anchors.fill: parent anchors.leftMargin: 5 anchors.rightMargin: 5 clip: true @@ -126,7 +62,7 @@ Button { CoreText { id: balanceText visible: root.showBalance - text: formatSatoshis(12300) + text: "₿ " + root.balance color: Theme.color.neutral7 } } diff --git a/src/qml/pages/wallet/WalletSelect.qml b/src/qml/pages/wallet/WalletSelect.qml index 9f226ad0b0..d4bd919b0e 100644 --- a/src/qml/pages/wallet/WalletSelect.qml +++ b/src/qml/pages/wallet/WalletSelect.qml @@ -77,11 +77,12 @@ Popup { width: 220 height: 32 text: name + checked: walletController.selectedWallet.name == name ButtonGroup.group: buttonGroup showBalance: false showIcon: false onClicked: { - walletListModel.selectedWallet = name + walletController.setSelectedWallet(name) root.close() } } diff --git a/src/qml/walletcontroller.cpp b/src/qml/walletcontroller.cpp deleted file mode 100644 index 7726bf5187..0000000000 --- a/src/qml/walletcontroller.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2024 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 - - -WalletController::WalletController(interfaces::Node& node) - : m_node(node) -{ -} - -void WalletController::createSingleSigWallet(const QString& name, const QString& passphrase) -{ - uint64_t flags = 0; - std::vector warning_message; - SecureString secure_passphrase; - flags |= wallet::WALLET_FLAG_DESCRIPTORS; - secure_passphrase.assign(passphrase.toStdString()); - m_node.walletLoader().createWallet(name.toStdString(), secure_passphrase, flags, warning_message); -} diff --git a/src/qml/walletcontroller.h b/src/qml/walletcontroller.h deleted file mode 100644 index 9258895556..0000000000 --- a/src/qml/walletcontroller.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2024 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_WALLETCONTROLLER_H -#define BITCOIN_QML_WALLETCONTROLLER_H - -#include - -#include -#include - -class WalletController : public QObject -{ - Q_OBJECT - -public: - explicit WalletController(interfaces::Node& node); - Q_INVOKABLE void createSingleSigWallet(const QString& name, const QString& passphrase); - -private: - interfaces::Node& m_node; -}; - - -#endif // BITCOIN_QML_WALLETCONTROLLER_H diff --git a/src/qml/walletqmlcontroller.cpp b/src/qml/walletqmlcontroller.cpp new file mode 100644 index 0000000000..fc25476580 --- /dev/null +++ b/src/qml/walletqmlcontroller.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2024 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 + +WalletQmlController::WalletQmlController(interfaces::Node& node, QObject *parent) + : QObject(parent) + , m_node(node) +{ + m_selected_wallet = new WalletQmlModel(parent); +} + +WalletQmlController::~WalletQmlController() +{ + if (m_handler_load_wallet) { + m_handler_load_wallet->disconnect(); + } +} + +void WalletQmlController::setSelectedWallet(QString path) +{ + std::vector warning_message; + auto wallet{m_node.walletLoader().loadWallet(path.toStdString(), warning_message)}; + if (wallet.has_value()) { + m_selected_wallet = new WalletQmlModel(std::move(wallet.value())); + m_wallets.push_back(m_selected_wallet); + Q_EMIT selectedWalletChanged(); + } +} + +WalletQmlModel* WalletQmlController::selectedWallet() const +{ + return m_selected_wallet; +} + +void WalletQmlController::unloadWallets() +{ + m_handler_load_wallet->disconnect(); + for (WalletQmlModel* wallet : m_wallets) { + delete wallet; + } + m_wallets.clear(); +} + +void WalletQmlController::handleLoadWallet(std::unique_ptr wallet) +{ + if (!m_wallets.empty()) { + QString name = QString::fromStdString(wallet->getWalletName()); + for (WalletQmlModel* wallet_model : m_wallets) { + if (wallet_model->name() == name) { + return; + } + } + } + + m_selected_wallet = new WalletQmlModel(std::move(wallet)); + m_wallets.push_back(m_selected_wallet); + Q_EMIT selectedWalletChanged(); +} + +void WalletQmlController::initialize() +{ + m_handler_load_wallet = m_node.walletLoader().handleLoadWallet([this](std::unique_ptr wallet) { + handleLoadWallet(std::move(wallet)); + }); + + auto wallets = m_node.walletLoader().getWallets(); + for (auto& wallet : wallets) { + handleLoadWallet(std::move(wallet)); + } +} diff --git a/src/qml/walletqmlcontroller.h b/src/qml/walletqmlcontroller.h new file mode 100644 index 0000000000..5dffb36f49 --- /dev/null +++ b/src/qml/walletqmlcontroller.h @@ -0,0 +1,45 @@ +// Copyright (c) 2024 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_WALLETQMLCONTROLLER_H +#define BITCOIN_QML_WALLETQMLCONTROLLER_H + +#include +#include +#include +#include + +#include +#include + +class WalletQmlController : public QObject +{ + Q_OBJECT + Q_PROPERTY(WalletQmlModel* selectedWallet READ selectedWallet NOTIFY selectedWalletChanged) + +public: + explicit WalletQmlController(interfaces::Node& node, QObject *parent = nullptr); + ~WalletQmlController(); + + Q_INVOKABLE void setSelectedWallet(QString path); + + WalletQmlModel* selectedWallet() const; + void unloadWallets(); + +Q_SIGNALS: + void selectedWalletChanged(); + +public Q_SLOTS: + void initialize(); + +private: + void handleLoadWallet(std::unique_ptr wallet); + + interfaces::Node& m_node; + WalletQmlModel* m_selected_wallet; + std::vector m_wallets; + std::unique_ptr m_handler_load_wallet; +}; + +#endif // BITCOIN_QML_WALLETQMLCONTROLLER_H