Skip to content

Commit

Permalink
Merge #417: Introduce WalletModel and loadWallet functionality
Browse files Browse the repository at this point in the history
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
  • Loading branch information
hebasto committed Dec 16, 2024
2 parents 574817b + 50c5f87 commit be23a6e
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 152 deletions.
9 changes: 6 additions & 3 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ QT_MOC_CPP = \
qml/models/moc_peerdetailsmodel.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 \
Expand Down Expand Up @@ -126,12 +127,13 @@ BITCOIN_QT_H = \
qml/models/peerdetailsmodel.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 \
Expand Down Expand Up @@ -317,9 +319,10 @@ BITCOIN_QML_BASE_CPP = \
qml/models/peerdetailsmodel.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 \
Expand Down
29 changes: 23 additions & 6 deletions src/qml/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@
#include <qml/models/peerdetailsmodel.h>
#include <qml/models/peerlistsortproxy.h>
#include <qml/models/walletlistmodel.h>
#include <qml/models/walletqmlmodel.h>
#include <qml/imageprovider.h>
#include <qml/util.h>
#include <qml/walletcontroller.h>
#include <qml/walletqmlcontroller.h>
#include <qt/guiutil.h>
#include <qt/initexecutor.h>
#include <qt/networkstyle.h>
Expand Down Expand Up @@ -259,8 +260,17 @@ int QmlGuiMain(int argc, char* argv[])

NodeModel node_model{*node};
InitExecutor init_executor{*node};
#ifdef ENABLE_WALLET
WalletQmlController wallet_controller(*node);
QObject::connect(&init_executor, &InitExecutor::initializeResult, &wallet_controller, &WalletQmlController::initialize);
#endif
QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &InitExecutor::initialize);
QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &InitExecutor::shutdown);
QObject::connect(&node_model, &NodeModel::requestedShutdown, [&] {
#ifdef ENABLE_WALLET
wallet_controller.unloadWallets();
#endif
init_executor.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);
Expand All @@ -277,8 +287,12 @@ 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);


qGuiApp->setQuitOnLastWindowClosed(false);
QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] {
#ifdef ENABLE_WALLET
wallet_controller.unloadWallets();
#endif
node->startShutdown();
});

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

WalletController wallet_controller(*node);

QQmlApplicationEngine engine;

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

WalletListModel wallet_list_model{*node, nullptr};

engine.rootContext()->setContextProperty("networkTrafficTower", &network_traffic_tower);
engine.rootContext()->setContextProperty("nodeModel", &node_model);
engine.rootContext()->setContextProperty("chainModel", &chain_model);
engine.rootContext()->setContextProperty("peerTableModel", &peer_model);
engine.rootContext()->setContextProperty("peerListModelProxy", &peer_model_sort_proxy);
#ifdef ENABLE_WALLET
WalletListModel wallet_list_model{*node, nullptr};
engine.rootContext()->setContextProperty("walletController", &wallet_controller);
engine.rootContext()->setContextProperty("walletListModel", &wallet_list_model);
#endif

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

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

engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
if (engine.rootObjects().isEmpty()) {
Expand Down
14 changes: 0 additions & 14 deletions src/qml/models/walletlistmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ WalletListModel::WalletListModel(interfaces::Node& node, QObject *parent)
: QAbstractListModel(parent)
, m_node(node)
{
setSelectedWallet("Singlesig Wallet");
}

void WalletListModel::listWalletDir()
Expand All @@ -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);
Expand Down
9 changes: 0 additions & 9 deletions src/qml/models/walletlistmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -30,15 +29,9 @@ class WalletListModel : public QAbstractListModel
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> 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;
Expand All @@ -48,8 +41,6 @@ public Q_SLOTS:

QList<Item> m_items;
interfaces::Node& m_node;
QString m_selected_wallet;

};

#endif // BITCOIN_QML_MODELS_WALLETLISTMODEL_H
36 changes: 36 additions & 0 deletions src/qml/models/walletqmlmodel.cpp
Original file line number Diff line number Diff line change
@@ -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 <qml/models/walletqmlmodel.h>

#include <qt/bitcoinunits.h>

#include <QTimer>

WalletQmlModel::WalletQmlModel(std::unique_ptr<interfaces::Wallet> 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());
}
34 changes: 34 additions & 0 deletions src/qml/models/walletqmlmodel.h
Original file line number Diff line number Diff line change
@@ -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 <interfaces/wallet.h>

#include <QObject>

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<interfaces::Wallet> 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<interfaces::Wallet> m_wallet;
};

#endif // BITCOIN_QML_MODELS_WALLETQMLMODEL_H
3 changes: 2 additions & 1 deletion src/qml/pages/wallet/DesktopWallets.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
70 changes: 3 additions & 67 deletions src/qml/pages/wallet/WalletBadge.qml
Original file line number Diff line number Diff line change
Expand Up @@ -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 '<font color="' + highlightColor + '">' + match + '</font>';
}
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] = '<font color="' + highlightColor + '">' + parts[1] + '</font>';
} 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 '<font color="' + highlightColor + '">' + match + '</font>';
}
return match;
});
}

// Concatenate the parts back together
var formattedBitcoins = parts.join('.');

// Format the text with the Bitcoin symbol
var formattedText = `<font color="${highlightColor}">₿</font> ${formattedBitcoins}`;

// Highlight zero in a different color if satoshis are zero
if (satoshis === 0) {
formattedText = `<font color="${zeroColor}">₿ 0.00</font>`;
}

return formattedText;
}

property color bgActiveColor: Theme.color.neutral2
property color textColor: Theme.color.neutral7
property color textHoverColor: Theme.color.orange
Expand All @@ -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
Expand Down Expand Up @@ -126,7 +62,7 @@ Button {
CoreText {
id: balanceText
visible: root.showBalance
text: formatSatoshis(12300)
text: "" + root.balance
color: Theme.color.neutral7
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/qml/pages/wallet/WalletSelect.qml
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,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()
}
}
Expand Down
Loading

0 comments on commit be23a6e

Please sign in to comment.