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:
Wladimir J. van der Laan 2014-03-18 15:49:21 +01:00 committed by Jannis Froese
parent 509cc32778
commit 4111787ef3
5 changed files with 106 additions and 52 deletions

View file

@ -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('_'));

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}