qt: Use locale-specific number formatting
- Change bitcoinamountfield to use locale-specific number format - Change bitcoinunits to show and parse locale-specific numbers - If a language/territory is selected in options, this is set as default locale (overrides system locale). Fixes #3887 (cherry picked from commit laanwj/bitcoin@db0b8f3a2e, bitcoin/bitcoin#3893)
This commit is contained in:
parent
509cc32778
commit
4111787ef3
|
@ -94,6 +94,9 @@ static void initTranslations(QTranslator &qtTranslatorBase, QTranslator &qtTrans
|
|||
// 3) -lang command line argument
|
||||
lang_territory = QString::fromStdString(GetArg("-lang", lang_territory.toStdString()));
|
||||
|
||||
// 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('_'));
|
||||
|
|
|
@ -14,6 +14,46 @@
|
|||
#include <QKeyEvent>
|
||||
#include <qmath.h> // for qPow()
|
||||
|
||||
/** QDoubleSpinBox that shows number group seperators.
|
||||
* In Qt 5.3+ this could be replaced with QAbstractSpinBox::setGroupSeparatorShown(true)
|
||||
* See https://bugreports.qt-project.org/browse/QTBUG-5142
|
||||
*
|
||||
* TODO: We should not use a QDoubleSpinBox at all but implement our own
|
||||
* spinbox for fixed-point numbers.
|
||||
*/
|
||||
class AmountSpinBox: public QDoubleSpinBox
|
||||
{
|
||||
public:
|
||||
explicit AmountSpinBox(QWidget *parent):
|
||||
QDoubleSpinBox(parent)
|
||||
{
|
||||
}
|
||||
QString textFromValue(double value) const
|
||||
{
|
||||
return QLocale().toString(value, 'f', decimals());
|
||||
}
|
||||
QValidator::State validate (QString &text, int &pos) const
|
||||
{
|
||||
bool ok = false;
|
||||
QValidator::State rv = QDoubleSpinBox::validate(text, pos);
|
||||
if (rv == QValidator::Acceptable)
|
||||
{
|
||||
// Make sure that we only return acceptable if group seperators
|
||||
// are in the right place. If not, a fixup step is needed first so
|
||||
// return Intermediate.
|
||||
QLocale().toDouble(text, &ok);
|
||||
if (!ok)
|
||||
return QValidator::Intermediate;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
double valueFromText(const QString& text) const
|
||||
{
|
||||
return QLocale().toDouble(text);
|
||||
}
|
||||
};
|
||||
|
||||
BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
amount(0),
|
||||
|
@ -21,8 +61,7 @@ BitcoinAmountField::BitcoinAmountField(QWidget *parent) :
|
|||
{
|
||||
nSingleStep = 100000; // satoshis
|
||||
|
||||
amount = new QDoubleSpinBox(this);
|
||||
amount->setLocale(QLocale::c());
|
||||
amount = new AmountSpinBox(this);
|
||||
amount->installEventFilter(this);
|
||||
amount->setMaximumWidth(170);
|
||||
|
||||
|
@ -52,7 +91,7 @@ void BitcoinAmountField::setText(const QString &text)
|
|||
if (text.isEmpty())
|
||||
amount->clear();
|
||||
else
|
||||
amount->setValue(text.toDouble());
|
||||
amount->setValue(QLocale().toDouble(text));
|
||||
}
|
||||
|
||||
void BitcoinAmountField::clear()
|
||||
|
@ -99,17 +138,6 @@ bool BitcoinAmountField::eventFilter(QObject *object, QEvent *event)
|
|||
// Clear invalid flag on focus
|
||||
setValid(true);
|
||||
}
|
||||
else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease)
|
||||
{
|
||||
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
||||
if (keyEvent->key() == Qt::Key_Comma)
|
||||
{
|
||||
// Translate a comma into a period
|
||||
QKeyEvent periodKeyEvent(event->type(), Qt::Key_Period, keyEvent->modifiers(), ".", keyEvent->isAutoRepeat(), keyEvent->count());
|
||||
QApplication::sendEvent(object, &periodKeyEvent);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "bitcoinunits.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <QLocale>
|
||||
|
||||
BitcoinUnits::BitcoinUnits(QObject *parent):
|
||||
QAbstractListModel(parent),
|
||||
|
@ -108,71 +109,89 @@ int BitcoinUnits::decimals(int unit)
|
|||
}
|
||||
}
|
||||
|
||||
QString BitcoinUnits::format(int unit, qint64 n, bool fPlus)
|
||||
QString BitcoinUnits::format(int unit, qint64 n, 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
|
||||
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');
|
||||
|
||||
// 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(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)
|
||||
else if (fPlus && n >= 0)
|
||||
quotient_str.insert(0, '+');
|
||||
return quotient_str + QString(".") + remainder_str;
|
||||
return quotient_str + locale.decimalPoint() + remainder_str;
|
||||
}
|
||||
|
||||
QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign)
|
||||
QString BitcoinUnits::formatWithUnit(int unit, qint64 amount, bool plussign, bool trim, const QLocale &locale)
|
||||
{
|
||||
return format(unit, amount, plussign) + QString(" ") + name(unit);
|
||||
return format(unit, amount, plussign, trim) + QString(" ") + name(unit);
|
||||
}
|
||||
|
||||
bool BitcoinUnits::parse(int unit, const QString &value, qint64 *val_out)
|
||||
bool BitcoinUnits::parse(int unit, const QString &value, qint64 *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);
|
||||
QStringList parts = 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
|
||||
}
|
||||
qint64 retvalue = str.toLongLong(&ok);
|
||||
if(val_out)
|
||||
{
|
||||
*val_out = retvalue;
|
||||
*val_out = whole * coin + decimals;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
#include <QLocale>
|
||||
|
||||
/** Bitcoin unit definitions. Encapsulates parsing and formatting
|
||||
and serves as list model for drop-down selection boxes.
|
||||
|
@ -52,11 +53,11 @@ public:
|
|||
//! Number of decimals left
|
||||
static int decimals(int unit);
|
||||
//! Format as string
|
||||
static QString format(int unit, qint64 amount, bool plussign=false);
|
||||
static QString format(int unit, qint64 amount, bool plussign=false, bool trim=true, const QLocale &locale=QLocale());
|
||||
//! Format as string (with unit)
|
||||
static QString formatWithUnit(int unit, qint64 amount, bool plussign=false);
|
||||
static QString formatWithUnit(int unit, qint64 amount, bool plussign=false, bool trim=true, const QLocale &locale=QLocale());
|
||||
//! Parse string to coin amount
|
||||
static bool parse(int unit, const QString &value, qint64 *val_out);
|
||||
static bool parse(int unit, const QString &value, qint64 *val_out, const QLocale &locale=QLocale());
|
||||
///@}
|
||||
|
||||
//! @name AbstractListModel implementation
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace GUIUtil {
|
|||
|
||||
QString dateTimeStr(const QDateTime &date)
|
||||
{
|
||||
return date.date().toString(Qt::SystemLocaleShortDate) + QString(" ") + date.toString("hh:mm");
|
||||
return date.date().toString(Qt::DefaultLocaleShortDate) + QString(" ") + date.toString("hh:mm");
|
||||
}
|
||||
|
||||
QString dateTimeStr(qint64 nTime)
|
||||
|
@ -133,7 +133,10 @@ bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
|
|||
{
|
||||
if(!i->second.isEmpty())
|
||||
{
|
||||
if(!BitcoinUnits::parse(BitcoinUnits::DOGE, 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::DOGE, i->second, &rv.amount, locale))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue