Skip to content

Commit b357e39

Browse files
committed
qml: Cleanup BitcoinAmount
This merges BitcoinAmount with SendRecipient to simplify the qml logic. By doing so, the conversions can be managed all in c++ against the satoshi member variable. Each recipient having its own BitcoinAmount allows the amounts to be saved independantly when there are multiple recipients.
1 parent 95fee66 commit b357e39

File tree

6 files changed

+114
-136
lines changed

6 files changed

+114
-136
lines changed

src/qml/bitcoinamount.cpp

Lines changed: 77 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2024 The Bitcoin Core developers
1+
// Copyright (c) 2024-2025 The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

@@ -7,19 +7,9 @@
77
#include <QRegExp>
88
#include <QStringList>
99

10-
11-
BitcoinAmount::BitcoinAmount(QObject *parent) : QObject(parent)
10+
BitcoinAmount::BitcoinAmount(QObject* parent)
11+
: QObject(parent)
1212
{
13-
m_unit = Unit::BTC;
14-
}
15-
16-
int BitcoinAmount::decimals(Unit unit)
17-
{
18-
switch (unit) {
19-
case Unit::BTC: return 8;
20-
case Unit::SAT: return 0;
21-
} // no default case, so the compiler can warn about missing cases
22-
assert(false);
2313
}
2414

2515
QString BitcoinAmount::sanitize(const QString &text)
@@ -43,6 +33,30 @@ QString BitcoinAmount::sanitize(const QString &text)
4333
return result;
4434
}
4535

36+
qint64 BitcoinAmount::satoshi() const
37+
{
38+
return m_satoshi;
39+
}
40+
41+
void BitcoinAmount::setSatoshi(qint64 new_amount)
42+
{
43+
m_isSet = true;
44+
if (m_satoshi != new_amount) {
45+
m_satoshi = new_amount;
46+
Q_EMIT amountChanged();
47+
}
48+
}
49+
50+
void BitcoinAmount::clear()
51+
{
52+
if (!m_isSet && m_satoshi == 0) {
53+
return;
54+
}
55+
m_satoshi = 0;
56+
m_isSet = false;
57+
Q_EMIT amountChanged();
58+
}
59+
4660
BitcoinAmount::Unit BitcoinAmount::unit() const
4761
{
4862
return m_unit;
@@ -58,97 +72,82 @@ QString BitcoinAmount::unitLabel() const
5872
{
5973
switch (m_unit) {
6074
case Unit::BTC: return "";
61-
case Unit::SAT: return "Sat";
75+
case Unit::SAT: return "sat";
6276
}
6377
assert(false);
6478
}
6579

66-
QString BitcoinAmount::amount() const
80+
void BitcoinAmount::flipUnit()
6781
{
68-
return m_amount;
82+
if (m_unit == Unit::BTC) {
83+
m_unit = Unit::SAT;
84+
} else {
85+
m_unit = Unit::BTC;
86+
}
87+
Q_EMIT unitChanged();
88+
Q_EMIT amountChanged();
6989
}
7090

71-
QString BitcoinAmount::satoshiAmount() const
91+
QString BitcoinAmount::satsToBtc(qint64 sat)
7292
{
73-
return toSatoshis(m_amount);
74-
}
93+
const bool negative = sat < 0;
94+
qint64 absSat = negative ? -sat : sat;
7595

76-
void BitcoinAmount::setAmount(const QString& new_amount)
77-
{
78-
m_amount = sanitize(new_amount);
79-
Q_EMIT amountChanged();
96+
const qint64 wholePart = absSat / COIN;
97+
const qint64 fracInt = absSat % COIN;
98+
QString fracPart = QString("%1").arg(fracInt, 8, 10, QLatin1Char('0'));
99+
100+
QString result = QString::number(wholePart) + '.' + fracPart;
101+
if (negative) {
102+
result.prepend('-');
103+
}
104+
return result;
80105
}
81106

82-
QString BitcoinAmount::toSatoshis(const QString& text) const
107+
QString BitcoinAmount::toDisplay() const
83108
{
109+
if (!m_isSet) {
110+
return "";
111+
}
84112
if (m_unit == Unit::SAT) {
85-
return text;
113+
return QString::number(m_satoshi);
86114
} else {
87-
return convert(text, m_unit);
115+
return satsToBtc(m_satoshi);
88116
}
89117
}
90118

91-
long long BitcoinAmount::toSatoshis(QString& amount, const Unit unit)
119+
qint64 BitcoinAmount::btcToSats(const QString& btcSanitized)
92120
{
93-
int num_decimals = decimals(unit);
94-
95-
QStringList parts = amount.remove(' ').split(".");
121+
if (btcSanitized.isEmpty() || btcSanitized == ".") return 0;
96122

97-
QString whole = parts[0];
98-
QString decimals;
123+
QString cleaned = btcSanitized;
124+
if (cleaned.startsWith('.')) cleaned.prepend('0');
99125

100-
if(parts.size() > 1)
101-
{
102-
decimals = parts[1];
126+
QStringList parts = cleaned.split('.');
127+
const qint64 whole = parts[0].isEmpty() ? 0 : parts[0].toLongLong();
128+
qint64 frac = 0;
129+
if (parts.size() == 2) {
130+
frac = parts[1].leftJustified(8, '0').toLongLong();
103131
}
104-
QString str = whole + decimals.leftJustified(num_decimals, '0', true);
105132

106-
return str.toLongLong();
133+
return whole * COIN + frac;
107134
}
108135

109-
QString BitcoinAmount::convert(const QString& amount, Unit unit) const
136+
void BitcoinAmount::fromDisplay(const QString& text)
110137
{
111-
if (amount == "") {
112-
return amount;
113-
}
114-
115-
QString result = amount;
116-
int decimalPosition = result.indexOf(".");
117-
118-
if (decimalPosition == -1) {
119-
decimalPosition = result.length();
120-
result.append(".");
138+
if (text.trimmed().isEmpty()) {
139+
clear();
140+
return;
121141
}
122142

123-
if (unit == Unit::BTC) {
124-
int numDigitsAfterDecimal = result.length() - decimalPosition - 1;
125-
if (numDigitsAfterDecimal < 8) {
126-
result.append(QString(8 - numDigitsAfterDecimal, '0'));
127-
}
128-
result.remove(decimalPosition, 1);
129-
130-
while (result.startsWith('0') && result.length() > 1) {
131-
result.remove(0, 1);
132-
}
133-
} else if (unit == Unit::SAT) {
134-
result.remove(decimalPosition, 1);
135-
int newDecimalPosition = decimalPosition - 8;
136-
if (newDecimalPosition < 1) {
137-
result = QString("0").repeated(-newDecimalPosition) + result;
138-
newDecimalPosition = 0;
139-
}
140-
result.insert(newDecimalPosition, ".");
141-
142-
while (result.endsWith('0') && result.contains('.')) {
143-
result.chop(1);
144-
}
145-
if (result.endsWith('.')) {
146-
result.chop(1);
147-
}
148-
if (result.startsWith('.')) {
149-
result.insert(0, "0");
150-
}
143+
qint64 newSat = 0;
144+
if (m_unit == Unit::BTC) {
145+
QString sanitized = sanitize(text);
146+
newSat = btcToSats(sanitized);
147+
} else {
148+
QString digitsOnly = text;
149+
digitsOnly.remove(QRegExp("[^0-9]"));
150+
newSat = digitsOnly.trimmed().isEmpty() ? 0 : digitsOnly.toLongLong();
151151
}
152-
153-
return result;
152+
setSatoshi(newSat);
154153
}

src/qml/bitcoinamount.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2024 The Bitcoin Core developers
1+
// Copyright (c) 2024-2025 The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

@@ -15,8 +15,8 @@ class BitcoinAmount : public QObject
1515
Q_OBJECT
1616
Q_PROPERTY(Unit unit READ unit WRITE setUnit NOTIFY unitChanged)
1717
Q_PROPERTY(QString unitLabel READ unitLabel NOTIFY unitChanged)
18-
Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
19-
Q_PROPERTY(QString satoshiAmount READ satoshiAmount NOTIFY amountChanged)
18+
Q_PROPERTY(QString display READ toDisplay WRITE fromDisplay NOTIFY amountChanged)
19+
Q_PROPERTY(qint64 satoshi READ satoshi WRITE setSatoshi NOTIFY amountChanged)
2020

2121
public:
2222
enum class Unit {
@@ -30,27 +30,28 @@ class BitcoinAmount : public QObject
3030
Unit unit() const;
3131
void setUnit(Unit unit);
3232
QString unitLabel() const;
33-
QString amount() const;
34-
void setAmount(const QString& new_amount);
35-
QString satoshiAmount() const;
33+
34+
QString toDisplay() const;
35+
void fromDisplay(const QString& new_amount);
36+
qint64 satoshi() const;
37+
void setSatoshi(qint64 new_amount);
3638

3739
public Q_SLOTS:
38-
QString sanitize(const QString& text);
39-
QString convert(const QString& text, Unit unit) const;
40-
QString toSatoshis(const QString& text) const;
40+
void flipUnit();
41+
void clear();
4142

4243
Q_SIGNALS:
4344
void unitChanged();
44-
void unitLabelChanged();
4545
void amountChanged();
4646

4747
private:
48-
long long toSatoshis(QString &amount, const Unit unit);
49-
int decimals(Unit unit);
48+
QString sanitize(const QString& text);
49+
static QString satsToBtc(qint64 sat);
50+
static qint64 btcToSats(const QString& btc);
5051

51-
Unit m_unit;
52-
QString m_unitLabel;
53-
QString m_amount;
52+
qint64 m_satoshi{0};
53+
bool m_isSet{false};
54+
Unit m_unit{Unit::BTC};
5455
};
5556

5657
#endif // BITCOIN_QML_BITCOINAMOUNT_H

src/qml/models/sendrecipient.cpp

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <qml/models/sendrecipient.h>
6-
#include <qobjectdefs.h>
6+
7+
#include <qml/bitcoinamount.h>
78

89
SendRecipient::SendRecipient(QObject* parent)
9-
: QObject(parent), m_address(""), m_label(""), m_amount(""), m_message("")
10+
: QObject(parent), m_amount(new BitcoinAmount(this))
1011
{
1112
}
1213

@@ -36,19 +37,11 @@ void SendRecipient::setLabel(const QString& label)
3637
}
3738
}
3839

39-
QString SendRecipient::amount() const
40+
BitcoinAmount* SendRecipient::amount() const
4041
{
4142
return m_amount;
4243
}
4344

44-
void SendRecipient::setAmount(const QString& amount)
45-
{
46-
if (m_amount != amount) {
47-
m_amount = amount;
48-
Q_EMIT amountChanged();
49-
}
50-
}
51-
5245
QString SendRecipient::message() const
5346
{
5447
return m_message;
@@ -69,22 +62,17 @@ bool SendRecipient::subtractFeeFromAmount() const
6962

7063
CAmount SendRecipient::cAmount() const
7164
{
72-
// TODO: Figure out who owns the parsing of SendRecipient::amount to CAmount
73-
if (m_amount == "") {
74-
return 0;
75-
}
76-
return m_amount.toLongLong();
65+
return m_amount->satoshi();
7766
}
7867

7968
void SendRecipient::clear()
8069
{
8170
m_address = "";
8271
m_label = "";
83-
m_amount = "";
72+
m_amount->setSatoshi(0);
8473
m_message = "";
8574
m_subtractFeeFromAmount = false;
8675
Q_EMIT addressChanged();
8776
Q_EMIT labelChanged();
88-
Q_EMIT amountChanged();
8977
Q_EMIT messageChanged();
9078
}

src/qml/models/sendrecipient.h

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,18 @@
55
#ifndef BITCOIN_QML_MODELS_SENDRECIPIENT_H
66
#define BITCOIN_QML_MODELS_SENDRECIPIENT_H
77

8+
#include <qml/bitcoinamount.h>
9+
810
#include <QObject>
911
#include <QString>
10-
#include <qml/bitcoinamount.h>
1112

1213
class SendRecipient : public QObject
1314
{
1415
Q_OBJECT
1516
Q_PROPERTY(QString address READ address WRITE setAddress NOTIFY addressChanged)
1617
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged)
17-
Q_PROPERTY(QString amount READ amount WRITE setAmount NOTIFY amountChanged)
1818
Q_PROPERTY(QString message READ message WRITE setMessage NOTIFY messageChanged)
19+
Q_PROPERTY(BitcoinAmount* amount READ amount CONSTANT)
1920

2021
public:
2122
explicit SendRecipient(QObject* parent = nullptr);
@@ -26,7 +27,7 @@ class SendRecipient : public QObject
2627
QString label() const;
2728
void setLabel(const QString& label);
2829

29-
QString amount() const;
30+
BitcoinAmount* amount() const;
3031
void setAmount(const QString& amount);
3132

3233
QString message() const;
@@ -41,14 +42,13 @@ class SendRecipient : public QObject
4142
Q_SIGNALS:
4243
void addressChanged();
4344
void labelChanged();
44-
void amountChanged();
4545
void messageChanged();
4646

4747
private:
48-
QString m_address;
49-
QString m_label;
50-
QString m_amount;
51-
QString m_message;
48+
QString m_address{""};
49+
QString m_label{""};
50+
QString m_message{""};
51+
BitcoinAmount* m_amount;
5252
bool m_subtractFeeFromAmount{false};
5353
};
5454

src/qml/models/sendrecipientslistmodel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ QVariant SendRecipientsListModel::data(const QModelIndex& index, int role) const
2727
switch (role) {
2828
case AddressRole: return r->address();
2929
case LabelRole: return r->label();
30-
case AmountRole: return r->amount();
30+
case AmountRole: return r->amount()->toDisplay();
3131
case MessageRole: return r->message();
3232
default: return {};
3333
}

0 commit comments

Comments
 (0)