Skip to content

Commit be69994

Browse files
committed
qml: support mBTC and bits display units
1 parent 548d0c0 commit be69994

17 files changed

Lines changed: 257 additions & 68 deletions

qml/bitcoinamount.cpp

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <qml/bitcoinamount.h>
66

7+
#include <cassert>
78
#include <limits>
89

910
#include <QRegularExpression>
@@ -27,9 +28,10 @@ QString BitcoinAmount::sanitize(const QString &text)
2728
result = parts[0] + "." + parts[1];
2829
}
2930

30-
// Limit decimal places to 8
31-
if (parts.size() == 2 && parts[1].length() > 8) {
32-
result = parts[0] + "." + parts[1].left(8);
31+
// Limit decimal places to the selected unit precision.
32+
const int decimals = unitDecimals();
33+
if (parts.size() == 2 && parts[1].length() > decimals) {
34+
result = parts[0] + "." + parts[1].left(decimals);
3335
}
3436

3537
return result;
@@ -85,6 +87,8 @@ QString BitcoinAmount::unitLabel() const
8587
{
8688
switch (m_unit) {
8789
case Unit::BTC: return QStringLiteral("");
90+
case Unit::mBTC: return QStringLiteral("mBTC");
91+
case Unit::uBTC: return QStringLiteral("bits");
8892
case Unit::SAT:
8993
return (qAbs(m_satoshi) == 1)
9094
? tr("sat", "unit label, singular")
@@ -95,10 +99,10 @@ QString BitcoinAmount::unitLabel() const
9599

96100
void BitcoinAmount::flipUnit()
97101
{
98-
if (m_unit == Unit::BTC) {
99-
m_unit = Unit::SAT;
100-
} else {
102+
if (m_unit == Unit::SAT) {
101103
m_unit = Unit::BTC;
104+
} else {
105+
m_unit = Unit::SAT;
102106
}
103107
Q_EMIT unitChanged();
104108
Q_EMIT displayChanged();
@@ -126,11 +130,17 @@ QString BitcoinAmount::toDisplay() const
126130
if (!m_isSet) {
127131
return "";
128132
}
129-
if (m_unit == Unit::SAT) {
130-
return QString::number(m_satoshi);
131-
} else {
132-
return satsToBtcString(m_satoshi);
133+
const qint64 factor = unitFactor();
134+
const int decimals = unitDecimals();
135+
const bool negative = m_satoshi < 0;
136+
const qint64 abs_sat = negative ? -m_satoshi : m_satoshi;
137+
const qint64 whole = abs_sat / factor;
138+
QString result = QString::number(whole);
139+
if (decimals > 0) {
140+
result += "." + QString::number(abs_sat % factor).rightJustified(decimals, '0');
133141
}
142+
if (negative) result.prepend('-');
143+
return result;
134144
}
135145

136146
QString BitcoinAmount::displayWithUnit() const
@@ -139,25 +149,48 @@ QString BitcoinAmount::displayWithUnit() const
139149
return display.isEmpty() ? QString{} : display + QStringLiteral(" ") + unitLabel();
140150
}
141151

142-
qint64 BitcoinAmount::btcToSats(const QString& btcSanitized)
152+
qint64 BitcoinAmount::unitFactor() const
143153
{
144-
if (btcSanitized.isEmpty() || btcSanitized == ".") return 0;
154+
switch (m_unit) {
155+
case Unit::BTC: return COIN;
156+
case Unit::mBTC: return 100000;
157+
case Unit::uBTC: return 100;
158+
case Unit::SAT: return 1;
159+
}
160+
assert(false);
161+
}
145162

146-
QString cleaned = btcSanitized;
163+
int BitcoinAmount::unitDecimals() const
164+
{
165+
switch (m_unit) {
166+
case Unit::BTC: return 8;
167+
case Unit::mBTC: return 5;
168+
case Unit::uBTC: return 2;
169+
case Unit::SAT: return 0;
170+
}
171+
assert(false);
172+
}
173+
174+
qint64 BitcoinAmount::displayToSats(const QString& sanitized) const
175+
{
176+
if (sanitized.isEmpty() || sanitized == ".") return 0;
177+
178+
QString cleaned = sanitized;
147179
if (cleaned.startsWith('.')) cleaned.prepend('0');
148180

149181
QStringList parts = cleaned.split('.');
150182
const qint64 whole = parts[0].isEmpty() ? 0 : parts[0].toLongLong();
151183
qint64 frac = 0;
152184
if (parts.size() == 2) {
153-
frac = parts[1].leftJustified(8, '0').toLongLong();
185+
frac = parts[1].leftJustified(unitDecimals(), '0').toLongLong();
154186
}
155187

156-
if (whole > std::numeric_limits<qint64>::max() / COIN) {
188+
const qint64 factor = unitFactor();
189+
if (whole > std::numeric_limits<qint64>::max() / factor) {
157190
return std::numeric_limits<qint64>::max();
158191
}
159192

160-
return whole * COIN + frac;
193+
return whole * factor + frac;
161194
}
162195

163196
void BitcoinAmount::fromDisplay(const QString& text)
@@ -168,13 +201,13 @@ void BitcoinAmount::fromDisplay(const QString& text)
168201
}
169202

170203
qint64 newSat = 0;
171-
if (m_unit == Unit::BTC) {
172-
QString sanitized = sanitize(text);
173-
newSat = btcToSats(sanitized);
174-
} else {
204+
if (m_unit == Unit::SAT) {
175205
QString digitsOnly = text;
176206
digitsOnly.remove(QRegularExpression("[^0-9]"));
177207
newSat = digitsOnly.trimmed().isEmpty() ? 0 : digitsOnly.toLongLong();
208+
} else {
209+
QString sanitized = sanitize(text);
210+
newSat = displayToSats(sanitized);
178211
}
179212
setSatoshi(newSat);
180213
}

qml/bitcoinamount.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class BitcoinAmount : public QObject
2222
public:
2323
enum class Unit {
2424
BTC,
25+
mBTC,
26+
uBTC,
2527
SAT
2628
};
2729
Q_ENUM(Unit)
@@ -56,7 +58,9 @@ public Q_SLOTS:
5658

5759
private:
5860
QString sanitize(const QString& text);
59-
static qint64 btcToSats(const QString& btc);
61+
qint64 displayToSats(const QString& sanitized) const;
62+
qint64 unitFactor() const;
63+
int unitDecimals() const;
6064

6165
qint64 m_satoshi{0};
6266
bool m_isSet{false};

qml/components/BitcoinAmountInputField.qml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,9 @@ ColumnLayout {
6969
}
7070

7171
validator: RegExpValidator {
72-
regExp: /^(0|[1-9]\d*)(\.\d{0,8})?$/
72+
regExp: root.amount && root.amount.unit === BitcoinAmount.SAT
73+
? /^(0|[1-9]\d*)?$/
74+
: /^(0|[1-9]\d*)(\.\d{0,8})?$/
7375
}
7476
maximumLength: 17
7577
}

qml/models/activityfilterproxymodel.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,24 @@ void WriteCsvRow(QTextStream& stream, const QStringList& values)
3434

3535
QmlBitcoinUnits::Unit ExportDisplayUnit(int display_unit)
3636
{
37-
return display_unit == 1 ? QmlBitcoinUnits::Unit::SAT : QmlBitcoinUnits::Unit::BTC;
37+
switch (display_unit) {
38+
case static_cast<int>(QmlBitcoinUnits::Unit::BTC): return QmlBitcoinUnits::Unit::BTC;
39+
case static_cast<int>(QmlBitcoinUnits::Unit::mBTC): return QmlBitcoinUnits::Unit::mBTC;
40+
case static_cast<int>(QmlBitcoinUnits::Unit::uBTC): return QmlBitcoinUnits::Unit::uBTC;
41+
case static_cast<int>(QmlBitcoinUnits::Unit::SAT): return QmlBitcoinUnits::Unit::SAT;
42+
}
43+
return QmlBitcoinUnits::Unit::BTC;
3844
}
3945

4046
QString ExportDisplayUnitLabel(int display_unit)
4147
{
42-
return display_unit == 1 ? QStringLiteral("sat") : QStringLiteral("BTC");
48+
switch (ExportDisplayUnit(display_unit)) {
49+
case QmlBitcoinUnits::Unit::BTC: return QStringLiteral("BTC");
50+
case QmlBitcoinUnits::Unit::mBTC: return QStringLiteral("mBTC");
51+
case QmlBitcoinUnits::Unit::uBTC: return QStringLiteral("bits");
52+
case QmlBitcoinUnits::Unit::SAT: return QStringLiteral("sat");
53+
}
54+
return QStringLiteral("BTC");
4355
}
4456
} // namespace
4557

qml/models/transaction.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,17 @@ using wallet::isminetype;
1717

1818
namespace {
1919
const int RecommendedNumConfirmations = 6;
20+
21+
QmlBitcoinUnits::Unit DisplayUnit(int display_unit)
22+
{
23+
switch (display_unit) {
24+
case static_cast<int>(QmlBitcoinUnits::Unit::BTC): return QmlBitcoinUnits::Unit::BTC;
25+
case static_cast<int>(QmlBitcoinUnits::Unit::mBTC): return QmlBitcoinUnits::Unit::mBTC;
26+
case static_cast<int>(QmlBitcoinUnits::Unit::uBTC): return QmlBitcoinUnits::Unit::uBTC;
27+
case static_cast<int>(QmlBitcoinUnits::Unit::SAT): return QmlBitcoinUnits::Unit::SAT;
28+
}
29+
return QmlBitcoinUnits::Unit::BTC;
30+
}
2031
}
2132

2233
Transaction::Transaction(
@@ -57,10 +68,7 @@ QString Transaction::prettyAmount(int display_unit) const
5768
{
5869
const CAmount net = netAmount();
5970
const bool plus_sign = (net > 0);
60-
QmlBitcoinUnits::Unit unit = (display_unit == 1)
61-
? QmlBitcoinUnits::Unit::SAT
62-
: QmlBitcoinUnits::Unit::BTC;
63-
return QmlBitcoinUnits::format(unit, net, plus_sign);
71+
return QmlBitcoinUnits::format(DisplayUnit(display_unit), net, plus_sign);
6472
}
6573

6674
QString Transaction::dateTimeString() const

qml/models/walletqmlmodel.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ QString FormatFeeEstimate(CAmount amount)
7474
return bitcoin_amount.displayWithUnit();
7575
}
7676

77+
QmlBitcoinUnits::Unit DisplayUnit(int display_unit)
78+
{
79+
switch (display_unit) {
80+
case static_cast<int>(QmlBitcoinUnits::Unit::BTC): return QmlBitcoinUnits::Unit::BTC;
81+
case static_cast<int>(QmlBitcoinUnits::Unit::mBTC): return QmlBitcoinUnits::Unit::mBTC;
82+
case static_cast<int>(QmlBitcoinUnits::Unit::uBTC): return QmlBitcoinUnits::Unit::uBTC;
83+
case static_cast<int>(QmlBitcoinUnits::Unit::SAT): return QmlBitcoinUnits::Unit::SAT;
84+
}
85+
return QmlBitcoinUnits::Unit::BTC;
86+
}
87+
7788
std::optional<CAmount> ParseCustomFeeRatePerKvB(const QString& custom_fee_rate)
7889
{
7990
const QString trimmed = custom_fee_rate.trimmed();
@@ -360,10 +371,7 @@ QString WalletQmlModel::balance() const
360371
if (!m_wallet) {
361372
return "0";
362373
}
363-
QmlBitcoinUnits::Unit unit = (m_display_unit == 1)
364-
? QmlBitcoinUnits::Unit::SAT
365-
: QmlBitcoinUnits::Unit::BTC;
366-
return QmlBitcoinUnits::format(unit, m_wallet->getBalance());
374+
return QmlBitcoinUnits::format(DisplayUnit(m_display_unit), m_wallet->getBalance());
367375
}
368376

369377
qint64 WalletQmlModel::balanceSatoshi() const

qml/models/walletqmlmodeltransaction.cpp

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,31 @@
88
#include <qml/models/sendrecipient.h>
99
#include <qml/models/sendrecipientslistmodel.h>
1010

11+
namespace {
12+
QmlBitcoinUnits::Unit DisplayUnit(int display_unit)
13+
{
14+
switch (display_unit) {
15+
case static_cast<int>(QmlBitcoinUnits::Unit::BTC): return QmlBitcoinUnits::Unit::BTC;
16+
case static_cast<int>(QmlBitcoinUnits::Unit::mBTC): return QmlBitcoinUnits::Unit::mBTC;
17+
case static_cast<int>(QmlBitcoinUnits::Unit::uBTC): return QmlBitcoinUnits::Unit::uBTC;
18+
case static_cast<int>(QmlBitcoinUnits::Unit::SAT): return QmlBitcoinUnits::Unit::SAT;
19+
}
20+
return QmlBitcoinUnits::Unit::BTC;
21+
}
22+
23+
QString DisplayUnitLabel(QmlBitcoinUnits::Unit unit, CAmount value)
24+
{
25+
switch (unit) {
26+
case QmlBitcoinUnits::Unit::BTC: return QStringLiteral("");
27+
case QmlBitcoinUnits::Unit::mBTC: return QStringLiteral("mBTC");
28+
case QmlBitcoinUnits::Unit::uBTC: return QStringLiteral("bits");
29+
case QmlBitcoinUnits::Unit::SAT:
30+
return (qAbs(value) == 1) ? QStringLiteral("sat") : QStringLiteral("sats");
31+
}
32+
return QStringLiteral("");
33+
}
34+
} // namespace
35+
1136
WalletQmlModelTransaction::WalletQmlModelTransaction(const SendRecipientsListModel* recipient, QObject* parent)
1237
: QObject(parent),
1338
m_amount(recipient->totalAmountSatoshi()),
@@ -35,12 +60,9 @@ BitcoinAmount* WalletQmlModelTransaction::amountAmount() const
3560

3661
QString WalletQmlModelTransaction::formatWithUnit(CAmount value, int display_unit)
3762
{
38-
auto unit = (display_unit == 1) ? QmlBitcoinUnits::Unit::SAT : QmlBitcoinUnits::Unit::BTC;
63+
const auto unit = DisplayUnit(display_unit);
3964
QString num = QmlBitcoinUnits::format(unit, value, false, QmlBitcoinUnits::SeparatorStyle::STANDARD);
40-
if (display_unit == 1) {
41-
return num + " " + ((qAbs(value) == 1) ? QStringLiteral("sat") : QStringLiteral("sats"));
42-
}
43-
return num + "";
65+
return num + " " + DisplayUnitLabel(unit, value);
4466
}
4567

4668
QString WalletQmlModelTransaction::amount() const

qml/pages/settings/SettingsDisplayUnit.qml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,31 @@ Page {
4747
onClicked: optionsModel.displayUnit = 0
4848
}
4949

50+
OptionButton {
51+
objectName: "displayUnitMBTC"
52+
Layout.fillWidth: true
53+
text: qsTr("mBTC")
54+
description: qsTr("5 decimal places (1 mBTC = 100,000 sat)")
55+
checked: optionsModel.displayUnit === 1
56+
onClicked: optionsModel.displayUnit = 1
57+
}
58+
59+
OptionButton {
60+
objectName: "displayUnitUBTC"
61+
Layout.fillWidth: true
62+
text: qsTr("bits")
63+
description: qsTr("2 decimal places (1 bit = 100 sat)")
64+
checked: optionsModel.displayUnit === 2
65+
onClicked: optionsModel.displayUnit = 2
66+
}
67+
5068
OptionButton {
5169
objectName: "displayUnitSAT"
5270
Layout.fillWidth: true
5371
text: qsTr("sat")
5472
description: qsTr("Satoshi, the smallest unit (1 sat = 0.00000001 BTC)")
55-
checked: optionsModel.displayUnit === 1
56-
onClicked: optionsModel.displayUnit = 1
73+
checked: optionsModel.displayUnit === 3
74+
onClicked: optionsModel.displayUnit = 3
5775
}
5876
}
5977
}

qml/pages/wallet/RequestPayment.qml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ Page {
221221
color: Theme.color.neutral9
222222
placeholderTextColor: enabled ? Theme.color.neutral7 : Theme.color.neutral4
223223
background: Item {}
224-
placeholderText: !root.request || root.request.amount.unit === BitcoinAmount.BTC
224+
placeholderText: !root.request || root.request.amount.unit !== BitcoinAmount.SAT
225225
? "0.00000000" : "0"
226226
selectByMouse: true
227227
enabled: root.requestIsEditing()
@@ -243,11 +243,11 @@ Page {
243243
}
244244
}
245245
validator: RegularExpressionValidator {
246-
regularExpression: !root.request || root.request.amount.unit === BitcoinAmount.BTC
246+
regularExpression: !root.request || root.request.amount.unit !== BitcoinAmount.SAT
247247
? /^(0|[1-9]\d{0,7})(\.\d{0,8})?$/
248248
: /^(0|[1-9]\d{0,15})$/
249249
}
250-
maximumLength: !root.request || root.request.amount.unit === BitcoinAmount.BTC ? 17 : 16
250+
maximumLength: !root.request || root.request.amount.unit !== BitcoinAmount.SAT ? 17 : 16
251251
}
252252

253253
Item {

qml/pages/wallet/Send.qml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,11 +309,11 @@ PageStack {
309309
}
310310
}
311311
validator: RegularExpressionValidator {
312-
regularExpression: root.recipient.amount.unit === BitcoinAmount.BTC
312+
regularExpression: root.recipient.amount.unit !== BitcoinAmount.SAT
313313
? /^(0|[1-9]\d{0,7})(\.\d{0,8})?$/
314314
: /^(0|[1-9]\d{0,15})$/
315315
}
316-
maximumLength: root.recipient.amount.unit === BitcoinAmount.BTC ? 17 : 16
316+
maximumLength: root.recipient.amount.unit !== BitcoinAmount.SAT ? 17 : 16
317317
}
318318
Item {
319319
objectName: "sendAmountUnitToggle"

0 commit comments

Comments
 (0)