qt: Replace thin spaces with locale-specific number formatting
This commit is contained in:
parent
82d8665dad
commit
8cc0d8165f
|
@ -121,6 +121,9 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans
|
|||
// 1) System default language
|
||||
QString lang_territory = GetLangTerritory();
|
||||
|
||||
// Set default locale for amount and date formatting according to the selected language
|
||||
QLocale::setDefault(QLocale(lang_territory));
|
||||
|
||||
// Convert to "de" only by truncating "_DE"
|
||||
QString lang = lang_territory;
|
||||
lang.truncate(lang_territory.lastIndexOf('_'));
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
explicit AmountSpinBox(QWidget *parent):
|
||||
QAbstractSpinBox(parent),
|
||||
currentUnit(BitcoinUnits::BTC),
|
||||
singleStep(100000000) // koinu
|
||||
singleStep(COIN) // koinu
|
||||
{
|
||||
setAlignment(Qt::AlignRight);
|
||||
|
||||
|
@ -48,7 +48,7 @@ public:
|
|||
CAmount val = parse(input, &valid);
|
||||
if(valid)
|
||||
{
|
||||
input = BitcoinUnits::format(currentUnit, val, false, BitcoinUnits::separatorAlways);
|
||||
input = BitcoinUnits::format(currentUnit, val, false, true);
|
||||
lineEdit()->setText(input);
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public:
|
|||
|
||||
void setValue(const CAmount& value)
|
||||
{
|
||||
lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, BitcoinUnits::separatorAlways));
|
||||
lineEdit()->setText(BitcoinUnits::format(currentUnit, value, false, true));
|
||||
emit valueChanged();
|
||||
}
|
||||
|
||||
|
@ -195,7 +195,6 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
|
|||
amount(0)
|
||||
{
|
||||
amount = new AmountSpinBox(this);
|
||||
amount->setLocale(QLocale::c());
|
||||
amount->installEventFilter(this);
|
||||
amount->setMaximumWidth(170);
|
||||
|
||||
|
@ -220,6 +219,14 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
|
|||
unitChanged(unit->currentIndex());
|
||||
}
|
||||
|
||||
void BitcoinAmountField::setText(const QString &text)
|
||||
{
|
||||
if (text.isEmpty())
|
||||
amount->clear();
|
||||
else
|
||||
amount->setValue(QLocale().toDouble(text));
|
||||
}
|
||||
|
||||
void BitcoinAmountField::clear()
|
||||
{
|
||||
amount->clear();
|
||||
|
|
|
@ -15,6 +15,8 @@ QT_BEGIN_NAMESPACE
|
|||
class QValueComboBox;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class AmountSpinBox;
|
||||
|
||||
/** Widget for entering bitcoin amounts.
|
||||
*/
|
||||
class BitcoinAmountField: public QWidget
|
||||
|
@ -67,6 +69,8 @@ private:
|
|||
AmountSpinBox *amount;
|
||||
QValueComboBox *unit;
|
||||
|
||||
void setText(const QString &text);
|
||||
QString text() const;
|
||||
private slots:
|
||||
void unitChanged(int idx);
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "primitives/transaction.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QLocale>
|
||||
|
||||
BitcoinUnits::BitcoinUnits(QObject *parent):
|
||||
QAbstractListModel(parent),
|
||||
|
@ -69,6 +70,17 @@ qint64 BitcoinUnits::factor(int unit)
|
|||
}
|
||||
}
|
||||
|
||||
qint64 BitcoinUnits::maxAmount(int unit)
|
||||
{
|
||||
switch(unit)
|
||||
{
|
||||
case BTC: return Q_INT64_C(900000000000); //less than the coin supply until the year 2170
|
||||
case mBTC: return Q_INT64_C(900000000000000);
|
||||
case uBTC: return Q_INT64_C(900000000000000000); // Slightly under max value for int64
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int BitcoinUnits::decimals(int unit)
|
||||
{
|
||||
switch(unit)
|
||||
|
@ -80,37 +92,42 @@ int BitcoinUnits::decimals(int unit)
|
|||
}
|
||||
}
|
||||
|
||||
QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, SeparatorStyle separators)
|
||||
QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, bool fTrim, const QLocale &locale_in)
|
||||
{
|
||||
// Note: not using straight sprintf here because we do NOT want
|
||||
// localized number formatting.
|
||||
if(!valid(unit))
|
||||
return QString(); // Refuse to format invalid unit
|
||||
qint64 n = (qint64)nIn;
|
||||
QLocale locale(locale_in);
|
||||
qint64 coin = factor(unit);
|
||||
int num_decimals = decimals(unit);
|
||||
|
||||
qint64 n_abs = (n > 0 ? n : -n);
|
||||
qint64 quotient = n_abs / coin;
|
||||
qint64 remainder = n_abs % coin;
|
||||
QString quotient_str = QString::number(quotient);
|
||||
QString remainder_str = QString::number(remainder).rightJustified(num_decimals, '0');
|
||||
// Quotient has group (decimal) separators if locale has this enabled
|
||||
QString quotient_str = locale.toString(quotient);
|
||||
// Remainder does not have group separators
|
||||
locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
|
||||
QString remainder_str = locale.toString(remainder).rightJustified(num_decimals, '0');
|
||||
|
||||
// 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);
|
||||
int q_size = quotient_str.size();
|
||||
if (separators == separatorAlways || (separators == separatorStandard && q_size > 4))
|
||||
for (int i = 3; i < q_size; i += 3)
|
||||
quotient_str.insert(q_size - i, thin_sp);
|
||||
if(fTrim)
|
||||
{
|
||||
// Right-trim excess zeros after the decimal point
|
||||
int nTrim = 0;
|
||||
for (int i = remainder_str.size()-1; i>=2 && (remainder_str.at(i) == '0'); --i)
|
||||
++nTrim;
|
||||
remainder_str.chop(nTrim);
|
||||
}
|
||||
|
||||
if (n < 0)
|
||||
quotient_str.insert(0, '-');
|
||||
else if (fPlus && n > 0)
|
||||
quotient_str.insert(0, '+');
|
||||
return quotient_str + QString(".") + remainder_str;
|
||||
return quotient_str + locale.decimalPoint() + remainder_str;
|
||||
}
|
||||
|
||||
|
||||
// TODO: Review all remaining calls to BitcoinUnits::formatWithUnit to
|
||||
// TODO: determine whether the output is used in a plain text context
|
||||
// TODO: or an HTML context (and replace with
|
||||
|
@ -126,54 +143,64 @@ QString BitcoinUnits::format(int unit, const CAmount& nIn, bool fPlus, Separator
|
|||
// Please take care to use formatHtmlWithUnit instead, when
|
||||
// appropriate.
|
||||
|
||||
QString BitcoinUnits::formatWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators)
|
||||
QString BitcoinUnits::formatWithUnit(int unit, const CAmount& amount, bool plussign, bool trim, const QLocale &locale)
|
||||
{
|
||||
return format(unit, amount, plussign, separators) + QString(" ") + name(unit);
|
||||
return format(unit, amount, plussign, trim) + QString(" ") + name(unit);
|
||||
}
|
||||
|
||||
QString BitcoinUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, SeparatorStyle separators)
|
||||
QString BitcoinUnits::formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign, bool trim, const QLocale &locale)
|
||||
{
|
||||
QString str(formatWithUnit(unit, amount, plussign, separators));
|
||||
QString str(formatWithUnit(unit, amount, plussign, trim, locale));
|
||||
str.replace(QChar(THIN_SP_CP), QString(THIN_SP_HTML));
|
||||
return QString("<span style='white-space: nowrap;'>%1</span>").arg(str);
|
||||
}
|
||||
|
||||
|
||||
bool BitcoinUnits::parse(int unit, const QString &value, CAmount *val_out)
|
||||
bool BitcoinUnits::parse(int unit, const QString &value, CAmount *val_out, const QLocale &locale_in)
|
||||
{
|
||||
if(!valid(unit) || value.isEmpty())
|
||||
return false; // Refuse to parse invalid unit or empty string
|
||||
|
||||
QLocale locale(locale_in);
|
||||
qint64 coin = factor(unit);
|
||||
int num_decimals = decimals(unit);
|
||||
|
||||
// Ignore spaces and thin spaces when parsing
|
||||
QStringList parts = removeSpaces(value).split(".");
|
||||
QStringList parts = value.split(locale.decimalPoint());
|
||||
bool ok = false;
|
||||
|
||||
if(parts.size() > 2)
|
||||
{
|
||||
return false; // More than one dot
|
||||
}
|
||||
QString whole = parts[0];
|
||||
QString decimals;
|
||||
return false; // More than one decimal point
|
||||
|
||||
// Parse whole part (may include locale-specific group separators)
|
||||
#if QT_VERSION < 0x050000
|
||||
qint64 whole = locale.toLongLong(parts[0], &ok, 10);
|
||||
#else
|
||||
qint64 whole = locale.toLongLong(parts[0], &ok);
|
||||
#endif
|
||||
if(!ok)
|
||||
return false; // Parse error
|
||||
if(whole > maxAmount(unit) || whole < 0)
|
||||
return false; // Overflow or underflow
|
||||
|
||||
// Parse decimals part (if present, may not include group separators)
|
||||
qint64 decimals = 0;
|
||||
if(parts.size() > 1)
|
||||
{
|
||||
decimals = parts[1];
|
||||
if(parts[1].size() > num_decimals)
|
||||
return false; // Exceeds max precision
|
||||
locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
|
||||
#if QT_VERSION < 0x050000
|
||||
decimals = locale.toLongLong(parts[1].leftJustified(num_decimals, '0'), &ok, 10);
|
||||
#else
|
||||
decimals = locale.toLongLong(parts[1].leftJustified(num_decimals, '0'), &ok);
|
||||
#endif
|
||||
if(!ok || decimals < 0)
|
||||
return false; // Parse error
|
||||
}
|
||||
if(decimals.size() > num_decimals)
|
||||
{
|
||||
return false; // Exceeds max precision
|
||||
}
|
||||
bool ok = false;
|
||||
QString str = whole + decimals.leftJustified(num_decimals, '0');
|
||||
|
||||
if(str.size() > 18)
|
||||
{
|
||||
return false; // Longer numbers will exceed 63 bits
|
||||
}
|
||||
CAmount retvalue(str.toLongLong(&ok));
|
||||
if(val_out)
|
||||
{
|
||||
*val_out = retvalue;
|
||||
*val_out = whole * coin + decimals;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
#include <QLocale>
|
||||
|
||||
// U+2009 THIN SPACE = UTF-8 E2 80 89
|
||||
#define REAL_THIN_SP_CP 0x2009
|
||||
|
@ -82,15 +83,17 @@ public:
|
|||
static QString description(int unit);
|
||||
//! Number of Satoshis (1e-8) per unit
|
||||
static qint64 factor(int unit);
|
||||
//! Max amount per unit
|
||||
static qint64 maxAmount(int unit);
|
||||
//! Number of decimals left
|
||||
static int decimals(int unit);
|
||||
//! Format as string
|
||||
static QString format(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
|
||||
static QString format(int unit, const CAmount& amount, bool plussign=false, bool trim=true, const QLocale &locale=QLocale());
|
||||
//! Format as string (with unit)
|
||||
static QString formatWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
|
||||
static QString formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign=false, SeparatorStyle separators=separatorStandard);
|
||||
static QString formatWithUnit(int unit, const CAmount& amount, bool plussign=false, bool trim=true, const QLocale &locale=QLocale());
|
||||
static QString formatHtmlWithUnit(int unit, const CAmount& amount, bool plussign=false, bool trim=true, const QLocale &locale=QLocale());
|
||||
//! Parse string to coin amount
|
||||
static bool parse(int unit, const QString &value, CAmount *val_out);
|
||||
static bool parse(int unit, const QString &value, CAmount *val_out, const QLocale &locale=QLocale());
|
||||
//! Gets title for amount column including current display unit if optionsModel reference available */
|
||||
static QString getAmountColumnTitle(int unit);
|
||||
///@}
|
||||
|
|
|
@ -161,7 +161,10 @@ bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
|
|||
{
|
||||
if(!i->second.isEmpty())
|
||||
{
|
||||
if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount))
|
||||
// Parse amount in C locale with no number separators
|
||||
QLocale locale(QLocale::c());
|
||||
locale.setNumberOptions(QLocale::OmitGroupSeparator | QLocale::RejectGroupSeparator);
|
||||
if(!BitcoinUnits::parse(BitcoinUnits::BTC, i->second, &rv.amount, locale))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue