Merge 929 via tbc

This commit is contained in:
Luke Dashjr 2025-03-05 03:27:08 +00:00
commit fe176fa702
7 changed files with 298 additions and 25 deletions

View File

@ -159,6 +159,7 @@ BITCOIN_QT_H = \
qt/sendcoinsrecipient.h \
qt/signverifymessagedialog.h \
qt/splashscreen.h \
qt/tonalutils.h \
qt/trafficgraphwidget.h \
qt/transactiondesc.h \
qt/transactiondescdialog.h \
@ -254,6 +255,7 @@ BITCOIN_QT_BASE_CPP = \
qt/qvaluecombobox.cpp \
qt/rpcconsole.cpp \
qt/splashscreen.cpp \
qt/tonalutils.cpp \
qt/trafficgraphwidget.cpp \
qt/utilitydialog.cpp

View File

@ -94,7 +94,11 @@ public:
{
bool valid = false;
CAmount val = value(&valid);
val = val + steps * singleStep;
CAmount currentSingleStep = singleStep;
if (!currentSingleStep) {
currentSingleStep = BitcoinUnits::singlestep(currentUnit);
}
val = val + steps * currentSingleStep;
val = qBound(m_min_amount, val, m_max_amount);
setValue(val);
}
@ -151,7 +155,7 @@ public:
private:
BitcoinUnit currentUnit{BitcoinUnit::BTC};
CAmount singleStep{CAmount(100000)}; // satoshis
CAmount singleStep{CAmount(0)};
mutable QSize cachedMinimumSizeHint;
bool m_allow_empty{true};
CAmount m_min_amount{CAmount(0)};

View File

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <qt/bitcoinunits.h>
#include <qt/tonalutils.h>
#include <consensus/amount.h>
@ -11,6 +12,7 @@
#include <cassert>
static constexpr auto MAX_DIGITS_BTC = 16;
static constexpr auto MAX_DIGITS_TBC = 13;
BitcoinUnits::BitcoinUnits(QObject *parent):
QAbstractListModel(parent),
@ -25,6 +27,12 @@ QList<BitcoinUnit> BitcoinUnits::availableUnits()
unitlist.append(Unit::mBTC);
unitlist.append(Unit::uBTC);
unitlist.append(Unit::SAT);
if (TonalUtils::Supported())
{
unitlist.append(Unit::bTBC);
unitlist.append(Unit::sTBC);
unitlist.append(Unit::TBC);
}
return unitlist;
}
@ -35,6 +43,9 @@ QString BitcoinUnits::longName(Unit unit)
case Unit::mBTC: return QString("mBTC");
case Unit::uBTC: return QString::fromUtf8("µBTC (bits)");
case Unit::SAT: return QString("Satoshi (sat)");
case Unit::bTBC: return QString::fromUtf8("ᵇTBC");
case Unit::sTBC: return QString::fromUtf8("ˢTBC");
case Unit::TBC: return QString("TBC");
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@ -46,6 +57,9 @@ QString BitcoinUnits::shortName(Unit unit)
case Unit::mBTC: return longName(unit);
case Unit::uBTC: return QString("bits");
case Unit::SAT: return QString("sat");
case Unit::bTBC: return QString::fromUtf8("ᵇTBC");
case Unit::sTBC: return QString::fromUtf8("ˢTBC");
case Unit::TBC: return QString("TBC");
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@ -53,10 +67,13 @@ QString BitcoinUnits::shortName(Unit unit)
QString BitcoinUnits::description(Unit unit)
{
switch (unit) {
case Unit::BTC: return QString("Bitcoins");
case Unit::BTC: return QString("Bitcoins (decimal)");
case Unit::mBTC: return QString("Milli-Bitcoins (1 / 1" THIN_SP_UTF8 "000)");
case Unit::uBTC: return QString("Micro-Bitcoins (bits) (1 / 1" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)");
case Unit::SAT: return QString("Satoshi (sat) (1 / 100" THIN_SP_UTF8 "000" THIN_SP_UTF8 "000)");
case Unit::bTBC: return QString("Bong-Bitcoins (1,0000 tonal)");
case Unit::sTBC: return QString("San-Bitcoins (100 tonal)");
case Unit::TBC: return QString("Bitcoins (tonal)");
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@ -68,6 +85,9 @@ qint64 BitcoinUnits::factor(Unit unit)
case Unit::mBTC: return 100'000;
case Unit::uBTC: return 100;
case Unit::SAT: return 1;
case Unit::bTBC: return 0x100000000LL;
case Unit::sTBC: return 0x1000000;
case Unit::TBC: return 0x10000;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
@ -79,10 +99,57 @@ int BitcoinUnits::decimals(Unit unit)
case Unit::mBTC: return 5;
case Unit::uBTC: return 2;
case Unit::SAT: return 0;
case Unit::bTBC: return 8;
case Unit::sTBC: return 6;
case Unit::TBC: return 4;
} // no default case, so the compiler can warn about missing cases
assert(false);
}
int BitcoinUnits::radix(Unit unit)
{
switch (unit) {
case Unit::bTBC:
case Unit::sTBC:
case Unit::TBC:
return 0x10;
default:
return 10;
}
}
BitcoinUnit BitcoinUnits::numsys(Unit unit)
{
switch (unit) {
case Unit::bTBC:
case Unit::sTBC:
case Unit::TBC:
return Unit::TBC;
default:
return Unit::BTC;
}
}
qint64 BitcoinUnits::max_digits(Unit unit)
{
switch (numsys(unit)) {
case Unit::TBC:
return MAX_DIGITS_TBC;
default:
return MAX_DIGITS_BTC;
}
}
qint64 BitcoinUnits::singlestep(Unit unit)
{
switch (numsys(unit)) {
case Unit::TBC:
return 0x10000;
default:
return 100000;
}
}
QString BitcoinUnits::format(Unit unit, const CAmount& nIn, bool fPlus, SeparatorStyle separators, bool justify)
{
// Note: not using straight sprintf here because we do NOT want
@ -92,11 +159,22 @@ QString BitcoinUnits::format(Unit unit, const CAmount& nIn, bool fPlus, Separato
int num_decimals = decimals(unit);
qint64 n_abs = (n > 0 ? n : -n);
qint64 quotient = n_abs / coin;
QString quotient_str = QString::number(quotient);
int uradix = radix(unit);
QString quotient_str = QString::number(quotient, uradix);
if (justify) {
quotient_str = quotient_str.rightJustified(MAX_DIGITS_BTC - num_decimals, ' ');
quotient_str = quotient_str.rightJustified(max_digits(unit) - num_decimals, ' ');
}
QString remainder_str;
if (num_decimals > 0) {
const qint64 remainder = n_abs % coin;
remainder_str = QString::number(remainder, uradix).rightJustified(num_decimals, '0');
}
switch (numsys(unit)) {
case Unit::BTC:
{
// Use SI-style thin space separators as these are locale independent and can't be
// confused with the decimal marker.
QChar thin_sp(THIN_SP_CP);
@ -105,18 +183,28 @@ QString BitcoinUnits::format(Unit unit, const CAmount& nIn, bool fPlus, Separato
for (int i = 3; i < q_size; i += 3)
quotient_str.insert(q_size - i, thin_sp);
break;
}
case Unit::TBC:
{
// Right-trim excess zeros after the decimal point
static const QRegExp tail_zeros("0+$");
remainder_str.remove(tail_zeros);
TonalUtils::ConvertFromHex(quotient_str);
TonalUtils::ConvertFromHex(remainder_str);
break;
}
default: assert(false);
}
if (n < 0)
quotient_str.insert(0, '-');
else if (fPlus && n > 0)
quotient_str.insert(0, '+');
if (num_decimals > 0) {
qint64 remainder = n_abs % coin;
QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0');
return quotient_str + QString(".") + remainder_str;
} else {
return quotient_str;
}
if (!remainder_str.isEmpty())
quotient_str += QString(".") + remainder_str;
return quotient_str;
}
@ -180,11 +268,18 @@ bool BitcoinUnits::parse(Unit unit, const QString& value, CAmount* val_out)
bool ok = false;
QString str = whole + decimals.leftJustified(num_decimals, '0');
Unit unumsys = numsys(unit);
if (unumsys == Unit::TBC) {
if (str.size() > 15)
return false; // Longer numbers may exceed 63 bits
TonalUtils::ConvertToHex(str);
} else
if(str.size() > 18)
{
return false; // Longer numbers will exceed 63 bits
}
CAmount retvalue(str.toLongLong(&ok));
CAmount retvalue(str.toLongLong(&ok, radix(unit)));
if(val_out)
{
*val_out = retvalue;
@ -228,18 +323,21 @@ CAmount BitcoinUnits::maxMoney()
return MAX_MONEY;
}
namespace {
qint8 ToQint8(BitcoinUnit unit)
std::variant<qint8, QString> BitcoinUnits::ToSetting(BitcoinUnit unit)
{
switch (unit) {
case BitcoinUnit::BTC: return 0;
case BitcoinUnit::mBTC: return 1;
case BitcoinUnit::uBTC: return 2;
case BitcoinUnit::SAT: return 3;
case BitcoinUnit::BTC: return qint8{0};
case BitcoinUnit::mBTC: return qint8{1};
case BitcoinUnit::uBTC: return qint8{2};
case BitcoinUnit::SAT: return qint8{3};
case BitcoinUnit::bTBC: return QString("bTBC");
case BitcoinUnit::sTBC: return QString("sTBC");
case BitcoinUnit::TBC: return QString("TBC");
} // no default case, so the compiler can warn about missing cases
assert(false);
}
namespace {
BitcoinUnit FromQint8(qint8 num)
{
switch (num) {
@ -248,13 +346,32 @@ BitcoinUnit FromQint8(qint8 num)
case 2: return BitcoinUnit::uBTC;
case 3: return BitcoinUnit::SAT;
}
assert(false);
return BitcoinUnit::BTC;
}
} // namespace
BitcoinUnit BitcoinUnits::FromSetting(const QString& s, BitcoinUnit def)
{
if (s == "0") return BitcoinUnit::BTC;
if (s == "1") return BitcoinUnit::mBTC;
if (s == "2") return BitcoinUnit::uBTC;
if (s == "3") return BitcoinUnit::SAT;
if (s == "4") return BitcoinUnit::sTBC;
if (s == "5") return BitcoinUnit::TBC;
if (s == "bTBC") return BitcoinUnit::bTBC;
if (s == "sTBC") return BitcoinUnit::sTBC;
if (s == "TBC") return BitcoinUnit::TBC;
return def;
}
QDataStream& operator<<(QDataStream& out, const BitcoinUnit& unit)
{
return out << ToQint8(unit);
auto setting_val = BitcoinUnits::ToSetting(unit);
if (const QString* setting_str = std::get_if<QString>(&setting_val)) {
return out << qint8{0} << *setting_str;
} else {
return out << std::get<qint8>(setting_val);
}
}
QDataStream& operator>>(QDataStream& in, BitcoinUnit& unit)
@ -262,5 +379,10 @@ QDataStream& operator>>(QDataStream& in, BitcoinUnit& unit)
qint8 input;
in >> input;
unit = FromQint8(input);
if (!in.atEnd()) {
QString setting_str;
in >> setting_str;
unit = BitcoinUnits::FromSetting(setting_str, unit);
}
return in;
}

View File

@ -43,7 +43,10 @@ public:
BTC,
mBTC,
uBTC,
SAT
SAT,
bTBC,
sTBC,
TBC,
};
Q_ENUM(Unit)
@ -60,6 +63,10 @@ public:
//! Get list of units, for drop-down box
static QList<Unit> availableUnits();
//! String for setting(s)
static std::variant<qint8, QString> ToSetting(Unit unit);
//! Convert setting(s) string to unit
static Unit FromSetting(const QString&, Unit def);
//! Long name
static QString longName(Unit unit);
//! Short name
@ -68,8 +75,16 @@ public:
static QString description(Unit unit);
//! Number of Satoshis (1e-8) per unit
static qint64 factor(Unit unit);
//! Number of decimals left
//! Number of fractional places
static int decimals(Unit unit);
//! Radix
static int radix(Unit unit);
//! Number system
static Unit numsys(Unit unit);
//! Number of digits total in maximum value
static qint64 max_digits(Unit unit);
//! "Single step" amount, in satoshis
static qint64 singlestep(Unit unit);
//! Format as string
static QString format(Unit unit, const CAmount& amount, bool plussign = false, SeparatorStyle separators = SeparatorStyle::STANDARD, bool justify = false);
//! Format as string (with unit)

View File

@ -245,9 +245,17 @@ bool OptionsModel::Init(bilingual_str& error)
// Display
if (!settings.contains("DisplayBitcoinUnit")) {
settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(BitcoinUnit::BTC));
auto init_unit = BitcoinUnit::BTC;
if (settings.contains("nDisplayUnit")) {
// Migrate to new setting
init_unit = BitcoinUnits::FromSetting(settings.value("nDisplayUnit").toString(), init_unit);
}
settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(init_unit));
}
QVariant unit = settings.value("DisplayBitcoinUnit");
if (settings.contains("DisplayBitcoinUnitKnots")) {
unit = settings.value("DisplayBitcoinUnitKnots");
}
if (unit.canConvert<BitcoinUnit>()) {
m_display_bitcoin_unit = unit.value<BitcoinUnit>();
} else {
@ -919,7 +927,21 @@ void OptionsModel::setDisplayUnit(const QVariant& new_unit)
if (new_unit.isNull() || new_unit.value<BitcoinUnit>() == m_display_bitcoin_unit) return;
m_display_bitcoin_unit = new_unit.value<BitcoinUnit>();
QSettings settings;
settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(m_display_bitcoin_unit));
if (BitcoinUnits::numsys(m_display_bitcoin_unit) == BitcoinUnit::BTC) {
settings.setValue("DisplayBitcoinUnit", QVariant::fromValue(m_display_bitcoin_unit));
settings.remove("DisplayBitcoinUnitKnots");
} else {
settings.setValue("DisplayBitcoinUnitKnots", QVariant::fromValue(m_display_bitcoin_unit));
}
{
// For older versions:
auto setting_val = BitcoinUnits::ToSetting(m_display_bitcoin_unit);
if (const QString* setting_str = std::get_if<QString>(&setting_val)) {
settings.setValue("nDisplayUnit", *setting_str);
} else {
settings.setValue("nDisplayUnit", std::get<qint8>(setting_val));
}
}
Q_EMIT displayUnitChanged(m_display_bitcoin_unit);
}

83
src/qt/tonalutils.cpp Normal file
View File

@ -0,0 +1,83 @@
// Copyright (c) 2016 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 <qt/tonalutils.h>
#include <QFont>
#include <QFontMetrics>
#include <QRegExp>
#include <QRegExpValidator>
#include <QString>
static const QList<QChar> tonal_digits{0xe8ef, 0xe8ee, 0xe8ed, 0xe8ec, 0xe8eb, 0xe8ea, 0xe8e9, '8', '7', '6', '5', '4', '3', '2', '1', '0'};
namespace {
bool font_supports_tonal(const QFont& font)
{
const QFontMetrics fm(font);
QString s = "000";
const QSize sz = fm.size(0, s);
for (const auto& c : tonal_digits) {
if (!fm.inFont(c)) return false;
s[0] = s[1] = s[2] = c;
if (sz != fm.size(0, s)) return false;
}
return true;
}
} // anon namespace
bool TonalUtils::Supported()
{
QFont default_font;
if (font_supports_tonal(default_font)) return true;
QFont last_resort_font(default_font.lastResortFamily());
if (font_supports_tonal(last_resort_font)) return true;
return false;
}
#define RE_TONAL_DIGIT "[\\d\\xe8e0-\\xe8ef\\xe9d0-\\xe9df]"
static QRegExpValidator tv(QRegExp("-?(?:" RE_TONAL_DIGIT "+\\.?|" RE_TONAL_DIGIT "*\\." RE_TONAL_DIGIT "+)"), nullptr);
QValidator::State TonalUtils::validate(QString&input, int&pos)
{
return tv.validate(input, pos);
}
void TonalUtils::ConvertFromHex(QString&str)
{
for (int i = 0; i < str.size(); ++i)
{
ushort c = str[i].unicode();
if (c == '9')
str[i] = 0xe8e9;
else
if (c >= 'A' && c <= 'F')
str[i] = c + (0xe8ea - 'A');
else
if (c >= 'a' && c <= 'f')
str[i] = c + (0xe8ea - 'a');
}
}
void TonalUtils::ConvertToHex(QString&str)
{
for (int i = 0; i < str.size(); ++i)
{
ushort c = str[i].unicode();
if (c == '9')
str[i] = 'a';
else
if (c >= 0xe8e0 && c <= 0xe8e9) { // UCSUR 0-9
str[i] = c - (0xe8e0 - '0');
} else if (c >= 0xe8ea && c <= 0xe8ef) { // UCSUR a-f
str[i] = c - (0xe8ea - 'a');
} else if (c >= 0xe9d0 && c <= 0xe9d9) {
str[i] = c - (0xe9d0 - '0');
} else
if (c >= 0xe9da && c <= 0xe9df)
str[i] = c - 0xe999;
}
}

25
src/qt/tonalutils.h Normal file
View File

@ -0,0 +1,25 @@
// Copyright (c) 2016 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_QT_TONALUTILS_H
#define BITCOIN_QT_TONALUTILS_H
#include <QValidator>
QT_BEGIN_NAMESPACE
class QString;
QT_END_NAMESPACE
class TonalUtils
{
public:
static bool Supported();
static QValidator::State validate(QString&input, int&pos);
static void ConvertFromHex(QString&);
static void ConvertToHex(QString&);
};
#endif // BITCOIN_QT_TONALUTILS_H