Skip to content

Commit be23a6e

Browse files
committedDec 16, 2024··
Merge #417: Introduce WalletModel and loadWallet functionality
50c5f87 qml: Protect m_wallets in WalletQmlController with QMutex (johnny9) ec3e68e qml: Move setSelectedWallet work to worker QThread (johnny9) b41a0e4 qml: Introduce WalletModel and loadWallet functionality (johnny9) Pull request description: When a user selects a wallet from the WalletSelect menu the wallet controller can now load the wallet data in and the name and balance will appear in the WalletBadge The WalletQmlModel is introduced that hodes the interface to the backend wallet and provides the balance to the gui as a formatted string. The formatted string isn't quite as nice as specified in the figma. Instead it just uses the satoshi formatting currently provided by our GUI utils. The advanced formatting will be added as its own PR so that it can be reviewed separately. WalletController has been renamed to WalletQmlController to not conflict with the qt widgets controller. A function to set the selected wallet has been added. This function loads or creates the backend wallet interface and a WalletQmlModel owns the interface and is set to the controller's selectedWallet property. This is how the gui will gain access to the wallet's information. Loading encrypted wallets is not currently handled as we need additional dialogs. This will be done in a separate PR so that it can be reviewed independantly. A handler is also added to the wallet controller to handle background loading of wallet either through the rpc interface or at startup. Initial state and loading states of the wallet selector have not been implemented yet and will be done separately so it currently will just show 0 balance until a wallet is properly loaded. ACKs for top commit: jarolrod: ACK 50c5f87 Tree-SHA512: d03d3ffd37122e80b37c3e4dfb1b27910afd358b174970ba326f66ca24a8e623c758e12cf63edd7b62de6926117011e8cb8a21d69a8a8e32a1776136d892b072
2 parents 574817b + 50c5f87 commit be23a6e

13 files changed

+254
-152
lines changed
 

‎src/Makefile.qt.include

+6-3
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ QT_MOC_CPP = \
4444
qml/models/moc_peerdetailsmodel.cpp \
4545
qml/models/moc_peerlistsortproxy.cpp \
4646
qml/models/moc_walletlistmodel.cpp \
47+
qml/models/moc_walletqmlmodel.cpp \
4748
qml/moc_appmode.cpp \
48-
qml/moc_walletcontroller.cpp \
49+
qml/moc_walletqmlcontroller.cpp \
4950
qt/moc_addressbookpage.cpp \
5051
qt/moc_addresstablemodel.cpp \
5152
qt/moc_askpassphrasedialog.cpp \
@@ -126,12 +127,13 @@ BITCOIN_QT_H = \
126127
qml/models/peerdetailsmodel.h \
127128
qml/models/peerlistsortproxy.h \
128129
qml/models/walletlistmodel.h \
130+
qml/models/walletqmlmodel.h \
129131
qml/appmode.h \
130132
qml/bitcoin.h \
131133
qml/guiconstants.h \
132134
qml/imageprovider.h \
133135
qml/util.h \
134-
qml/walletcontroller.h \
136+
qml/walletqmlcontroller.h \
135137
qt/addressbookpage.h \
136138
qt/addresstablemodel.h \
137139
qt/askpassphrasedialog.h \
@@ -317,9 +319,10 @@ BITCOIN_QML_BASE_CPP = \
317319
qml/models/peerdetailsmodel.cpp \
318320
qml/models/peerlistsortproxy.cpp \
319321
qml/models/walletlistmodel.cpp \
322+
qml/models/walletqmlmodel.cpp \
320323
qml/imageprovider.cpp \
321324
qml/util.cpp \
322-
qml/walletcontroller.cpp
325+
qml/walletqmlcontroller.cpp
323326

324327
QML_RES_FONTS = \
325328
qml/res/fonts/Inter-Regular.otf \

‎src/qml/bitcoin.cpp

+23-6
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@
2828
#include <qml/models/peerdetailsmodel.h>
2929
#include <qml/models/peerlistsortproxy.h>
3030
#include <qml/models/walletlistmodel.h>
31+
#include <qml/models/walletqmlmodel.h>
3132
#include <qml/imageprovider.h>
3233
#include <qml/util.h>
33-
#include <qml/walletcontroller.h>
34+
#include <qml/walletqmlcontroller.h>
3435
#include <qt/guiutil.h>
3536
#include <qt/initexecutor.h>
3637
#include <qt/networkstyle.h>
@@ -259,8 +260,17 @@ int QmlGuiMain(int argc, char* argv[])
259260

260261
NodeModel node_model{*node};
261262
InitExecutor init_executor{*node};
263+
#ifdef ENABLE_WALLET
264+
WalletQmlController wallet_controller(*node);
265+
QObject::connect(&init_executor, &InitExecutor::initializeResult, &wallet_controller, &WalletQmlController::initialize);
266+
#endif
262267
QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &InitExecutor::initialize);
263-
QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &InitExecutor::shutdown);
268+
QObject::connect(&node_model, &NodeModel::requestedShutdown, [&] {
269+
#ifdef ENABLE_WALLET
270+
wallet_controller.unloadWallets();
271+
#endif
272+
init_executor.shutdown();
273+
});
264274
QObject::connect(&init_executor, &InitExecutor::initializeResult, &node_model, &NodeModel::initializeResult);
265275
QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection);
266276
// QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);
@@ -277,8 +287,12 @@ int QmlGuiMain(int argc, char* argv[])
277287
QObject::connect(&node_model, &NodeModel::setTimeRatioList, &chain_model, &ChainModel::setTimeRatioList);
278288
QObject::connect(&node_model, &NodeModel::setTimeRatioListInitial, &chain_model, &ChainModel::setTimeRatioListInitial);
279289

290+
280291
qGuiApp->setQuitOnLastWindowClosed(false);
281292
QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] {
293+
#ifdef ENABLE_WALLET
294+
wallet_controller.unloadWallets();
295+
#endif
282296
node->startShutdown();
283297
});
284298

@@ -289,23 +303,22 @@ int QmlGuiMain(int argc, char* argv[])
289303
GUIUtil::LoadFont(":/fonts/inter/regular");
290304
GUIUtil::LoadFont(":/fonts/inter/semibold");
291305

292-
WalletController wallet_controller(*node);
293-
294306
QQmlApplicationEngine engine;
295307

296308
QScopedPointer<const NetworkStyle> network_style{NetworkStyle::instantiate(Params().GetChainType())};
297309
assert(!network_style.isNull());
298310
engine.addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()});
299311

300-
WalletListModel wallet_list_model{*node, nullptr};
301-
302312
engine.rootContext()->setContextProperty("networkTrafficTower", &network_traffic_tower);
303313
engine.rootContext()->setContextProperty("nodeModel", &node_model);
304314
engine.rootContext()->setContextProperty("chainModel", &chain_model);
305315
engine.rootContext()->setContextProperty("peerTableModel", &peer_model);
306316
engine.rootContext()->setContextProperty("peerListModelProxy", &peer_model_sort_proxy);
317+
#ifdef ENABLE_WALLET
318+
WalletListModel wallet_list_model{*node, nullptr};
307319
engine.rootContext()->setContextProperty("walletController", &wallet_controller);
308320
engine.rootContext()->setContextProperty("walletListModel", &wallet_list_model);
321+
#endif
309322

310323
OptionsQmlModel options_model(*node, !need_onboarding.toBool());
311324
engine.rootContext()->setContextProperty("optionsModel", &options_model);
@@ -318,6 +331,10 @@ int QmlGuiMain(int argc, char* argv[])
318331
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
319332
qmlRegisterUncreatableType<PeerDetailsModel>("org.bitcoincore.qt", 1, 0, "PeerDetailsModel", "");
320333

334+
#ifdef ENABLE_WALLET
335+
qmlRegisterUncreatableType<WalletQmlModel>("org.bitcoincore.qt", 1, 0, "WalletQmlModel",
336+
"WalletQmlModel cannot be instantiated from QML");
337+
#endif
321338

322339
engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
323340
if (engine.rootObjects().isEmpty()) {

‎src/qml/models/walletlistmodel.cpp

-14
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ WalletListModel::WalletListModel(interfaces::Node& node, QObject *parent)
1212
: QAbstractListModel(parent)
1313
, m_node(node)
1414
{
15-
setSelectedWallet("Singlesig Wallet");
1615
}
1716

1817
void WalletListModel::listWalletDir()
@@ -32,19 +31,6 @@ void WalletListModel::listWalletDir()
3231
}
3332
}
3433

35-
void WalletListModel::setSelectedWallet(QString wallet_name)
36-
{
37-
if (m_selected_wallet != wallet_name) {
38-
m_selected_wallet = wallet_name;
39-
Q_EMIT selectedWalletChanged();
40-
}
41-
}
42-
43-
QString WalletListModel::selectedWallet() const
44-
{
45-
return m_selected_wallet;
46-
}
47-
4834
int WalletListModel::rowCount(const QModelIndex &parent) const
4935
{
5036
Q_UNUSED(parent);

‎src/qml/models/walletlistmodel.h

-9
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class Node;
1616
class WalletListModel : public QAbstractListModel
1717
{
1818
Q_OBJECT
19-
Q_PROPERTY(QString selectedWallet READ selectedWallet WRITE setSelectedWallet NOTIFY selectedWalletChanged)
2019

2120
public:
2221
WalletListModel(interfaces::Node& node, QObject *parent = nullptr);
@@ -30,15 +29,9 @@ class WalletListModel : public QAbstractListModel
3029
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
3130
QHash<int, QByteArray> roleNames() const override;
3231

33-
void setSelectedWallet(QString wallet_name);
34-
QString selectedWallet() const;
35-
3632
public Q_SLOTS:
3733
void listWalletDir();
3834

39-
Q_SIGNALS:
40-
void selectedWalletChanged();
41-
4235
private:
4336
struct Item {
4437
QString name;
@@ -48,8 +41,6 @@ public Q_SLOTS:
4841

4942
QList<Item> m_items;
5043
interfaces::Node& m_node;
51-
QString m_selected_wallet;
52-
5344
};
5445

5546
#endif // BITCOIN_QML_MODELS_WALLETLISTMODEL_H

‎src/qml/models/walletqmlmodel.cpp

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <qml/models/walletqmlmodel.h>
6+
7+
#include <qt/bitcoinunits.h>
8+
9+
#include <QTimer>
10+
11+
WalletQmlModel::WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject *parent)
12+
: QObject(parent)
13+
{
14+
m_wallet = std::move(wallet);
15+
}
16+
17+
WalletQmlModel::WalletQmlModel(QObject *parent)
18+
: QObject(parent)
19+
{
20+
}
21+
22+
QString WalletQmlModel::balance() const
23+
{
24+
if (!m_wallet) {
25+
return "0";
26+
}
27+
return BitcoinUnits::format(BitcoinUnits::Unit::BTC, m_wallet->getBalance());
28+
}
29+
30+
QString WalletQmlModel::name() const
31+
{
32+
if (!m_wallet) {
33+
return QString();
34+
}
35+
return QString::fromStdString(m_wallet->getWalletName());
36+
}

‎src/qml/models/walletqmlmodel.h

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_QML_MODELS_WALLETQMLMODEL_H
6+
#define BITCOIN_QML_MODELS_WALLETQMLMODEL_H
7+
8+
#include <interfaces/wallet.h>
9+
10+
#include <QObject>
11+
12+
class WalletQmlModel : public QObject
13+
{
14+
Q_OBJECT
15+
Q_PROPERTY(QString name READ name NOTIFY nameChanged)
16+
Q_PROPERTY(QString balance READ balance NOTIFY balanceChanged)
17+
18+
public:
19+
WalletQmlModel(std::unique_ptr<interfaces::Wallet> wallet, QObject *parent = nullptr);
20+
WalletQmlModel(QObject *parent = nullptr);
21+
~WalletQmlModel() = default;
22+
23+
QString name() const;
24+
QString balance() const;
25+
26+
Q_SIGNALS:
27+
void nameChanged();
28+
void balanceChanged();
29+
30+
private:
31+
std::unique_ptr<interfaces::Wallet> m_wallet;
32+
};
33+
34+
#endif // BITCOIN_QML_MODELS_WALLETQMLMODEL_H

‎src/qml/pages/wallet/DesktopWallets.qml

+2-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ Page {
2424
leftItem: WalletBadge {
2525
implicitWidth: 154
2626
implicitHeight: 46
27-
text: walletListModel.selectedWallet
27+
text: walletController.selectedWallet.name
28+
balance: walletController.selectedWallet.balance
2829

2930
MouseArea {
3031
anchors.fill: parent

‎src/qml/pages/wallet/WalletBadge.qml

+3-67
Original file line numberDiff line numberDiff line change
@@ -13,70 +13,6 @@ import "../../controls"
1313
Button {
1414
id: root
1515

16-
function formatSatoshis(satoshis) {
17-
var highlightColor = Theme.color.neutral9
18-
var zeroColor = Theme.color.neutral7
19-
20-
if (root.checked || root.hovered) {
21-
highlightColor = zeroColor = Theme.color.orange
22-
}
23-
24-
// Convert satoshis to bitcoins
25-
var bitcoins = satoshis / 100000000;
26-
27-
// Format bitcoins to a fixed 8 decimal places string
28-
var bitcoinStr = bitcoins.toFixed(8);
29-
30-
// Split the bitcoin string into integer and fractional parts
31-
var parts = bitcoinStr.split('.');
32-
33-
// Add spaces for every 3 digits in the integer part
34-
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
35-
36-
// Highlight the first significant digit and all following digits in the integer part
37-
var significantFound = false;
38-
parts[0] = parts[0].replace(/(\d)/g, function(match) {
39-
if (!significantFound && match !== '0') {
40-
significantFound = true;
41-
}
42-
if (significantFound) {
43-
return '<font color="' + highlightColor + '">' + match + '</font>';
44-
}
45-
return match;
46-
});
47-
48-
// Add spaces for every 3 digits in the decimal part
49-
parts[1] = parts[1].replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
50-
if (significantFound) {
51-
parts[1] = '<font color="' + highlightColor + '">' + parts[1] + '</font>';
52-
} else {
53-
// Highlight the first significant digit and all following digits in the fractional part
54-
significantFound = false;
55-
parts[1] = parts[1].replace(/(\d)/g, function(match) {
56-
if (!significantFound && match !== '0') {
57-
significantFound = true;
58-
}
59-
if (significantFound) {
60-
return '<font color="' + highlightColor + '">' + match + '</font>';
61-
}
62-
return match;
63-
});
64-
}
65-
66-
// Concatenate the parts back together
67-
var formattedBitcoins = parts.join('.');
68-
69-
// Format the text with the Bitcoin symbol
70-
var formattedText = `<font color="${highlightColor}">₿</font> ${formattedBitcoins}`;
71-
72-
// Highlight zero in a different color if satoshis are zero
73-
if (satoshis === 0) {
74-
formattedText = `<font color="${zeroColor}">₿ 0.00</font>`;
75-
}
76-
77-
return formattedText;
78-
}
79-
8016
property color bgActiveColor: Theme.color.neutral2
8117
property color textColor: Theme.color.neutral7
8218
property color textHoverColor: Theme.color.orange
@@ -85,17 +21,17 @@ Button {
8521
property string iconSource: ""
8622
property bool showBalance: true
8723
property bool showIcon: true
24+
property string balance: "0.0 000 000"
8825

8926
checkable: true
9027
hoverEnabled: AppMode.isDesktop
9128
implicitHeight: 60
92-
implicitWidth: 220
29+
implicitWidth: contentItem.width
9330
bottomPadding: 0
9431
topPadding: 0
9532
clip: true
9633

9734
contentItem: RowLayout {
98-
anchors.fill: parent
9935
anchors.leftMargin: 5
10036
anchors.rightMargin: 5
10137
clip: true
@@ -126,7 +62,7 @@ Button {
12662
CoreText {
12763
id: balanceText
12864
visible: root.showBalance
129-
text: formatSatoshis(12300)
65+
text: "" + root.balance
13066
color: Theme.color.neutral7
13167
}
13268
}

‎src/qml/pages/wallet/WalletSelect.qml

+2-1
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,12 @@ Popup {
7878
width: 220
7979
height: 32
8080
text: name
81+
checked: walletController.selectedWallet.name == name
8182
ButtonGroup.group: buttonGroup
8283
showBalance: false
8384
showIcon: false
8485
onClicked: {
85-
walletListModel.selectedWallet = name
86+
walletController.setSelectedWallet(name)
8687
root.close()
8788
}
8889
}

0 commit comments

Comments
 (0)