Skip to content

Commit 4e37f38

Browse files
authoredJun 12, 2018
Merge pull request #7188 from elpaso/locale-formatting
[bugfix] Fix double precision I/O in form widgets
2 parents e899849 + d9afb89 commit 4e37f38

File tree

7 files changed

+371
-54
lines changed

7 files changed

+371
-54
lines changed
 

‎src/app/vertextool/qgsvertexeditor.cpp

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,15 +211,23 @@ bool QgsVertexEditorModel::setData( const QModelIndex &index, const QVariant &va
211211
return false;
212212
}
213213

214-
double x = ( index.column() == 0 ? value.toDouble() : mSelectedFeature->vertexMap().at( index.row() )->point().x() );
215-
double y = ( index.column() == 1 ? value.toDouble() : mSelectedFeature->vertexMap().at( index.row() )->point().y() );
214+
// Get double value wrt current locale.
215+
bool ok;
216+
double doubleValue = QLocale().toDouble( value.toString(), &ok );
217+
// If not valid and locale's decimal point is not '.' let's try with english locale
218+
if ( ! ok && QLocale().decimalPoint() != '.' )
219+
{
220+
doubleValue = QLocale( QLocale::English ).toDouble( value.toString() );
221+
}
222+
223+
double x = ( index.column() == 0 ? doubleValue : mSelectedFeature->vertexMap().at( index.row() )->point().x() );
224+
double y = ( index.column() == 1 ? doubleValue : mSelectedFeature->vertexMap().at( index.row() )->point().y() );
216225

217226
if ( index.column() == mRCol ) // radius modified
218227
{
219228
if ( index.row() == 0 || index.row() >= mSelectedFeature->vertexMap().count() - 1 )
220229
return false;
221230

222-
double r = value.toDouble();
223231
double x1 = mSelectedFeature->vertexMap().at( index.row() - 1 )->point().x();
224232
double y1 = mSelectedFeature->vertexMap().at( index.row() - 1 )->point().y();
225233
double x2 = x;
@@ -228,7 +236,7 @@ bool QgsVertexEditorModel::setData( const QModelIndex &index, const QVariant &va
228236
double y3 = mSelectedFeature->vertexMap().at( index.row() + 1 )->point().y();
229237

230238
QgsPoint result;
231-
if ( QgsGeometryUtils::segmentMidPoint( QgsPoint( x1, y1 ), QgsPoint( x3, y3 ), result, r, QgsPoint( x2, y2 ) ) )
239+
if ( QgsGeometryUtils::segmentMidPoint( QgsPoint( x1, y1 ), QgsPoint( x3, y3 ), result, doubleValue, QgsPoint( x2, y2 ) ) )
232240
{
233241
x = result.x();
234242
y = result.y();

‎src/core/qgsfield.cpp

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
#include <QDataStream>
2424
#include <QIcon>
25+
#include <QLocale>
2526

2627
/***************************************************************************
2728
* This class is considered CRITICAL and any change MUST be accompanied with
@@ -208,9 +209,51 @@ QString QgsField::displayString( const QVariant &v ) const
208209
return QgsApplication::nullRepresentation();
209210
}
210211

211-
if ( d->type == QVariant::Double && d->precision > 0 )
212-
return QString::number( v.toDouble(), 'f', d->precision );
213-
212+
// Special treatment for numeric types if group separator is set or decimalPoint is not a dot
213+
if ( d->type == QVariant::Double )
214+
{
215+
// Locales with decimal point != '.' or that require group separator: use QLocale
216+
if ( QLocale().decimalPoint() != '.' ||
217+
!( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
218+
{
219+
if ( d->precision > 0 )
220+
{
221+
return QLocale().toString( v.toDouble(), 'f', d->precision );
222+
}
223+
else
224+
{
225+
// Precision is not set, let's guess it from the
226+
// standard conversion to string
227+
QString s( v.toString() );
228+
int dotPosition( s.indexOf( '.' ) );
229+
int precision;
230+
if ( dotPosition < 0 )
231+
{
232+
precision = 0;
233+
}
234+
else
235+
{
236+
precision = s.length() - dotPosition - 1;
237+
}
238+
return QLocale().toString( v.toDouble(), 'f', precision );
239+
}
240+
}
241+
// Default for doubles with precision
242+
else if ( d->type == QVariant::Double && d->precision > 0 )
243+
{
244+
return QString::number( v.toDouble(), 'f', d->precision );
245+
}
246+
}
247+
// Other numeric types than doubles
248+
else if ( isNumeric() &&
249+
! QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator )
250+
{
251+
bool ok;
252+
qlonglong converted( v.toLongLong( &ok ) );
253+
if ( ok )
254+
return QLocale().toString( converted );
255+
}
256+
// Fallback if special rules do not apply
214257
return v.toString();
215258
}
216259

@@ -234,6 +277,68 @@ bool QgsField::convertCompatible( QVariant &v ) const
234277
return false;
235278
}
236279

280+
// Give it a chance to convert to double since for not '.' locales
281+
// we accept both comma and dot as decimal point
282+
if ( d->type == QVariant::Double && v.type() == QVariant::String )
283+
{
284+
QVariant tmp( v );
285+
if ( !tmp.convert( d->type ) )
286+
{
287+
// This might be a string with thousand separator: use locale to convert
288+
bool ok;
289+
double d = QLocale().toDouble( v.toString(), &ok );
290+
if ( ok )
291+
{
292+
v = QVariant( d );
293+
return true;
294+
}
295+
// For not 'dot' locales, we also want to accept '.'
296+
if ( QLocale().decimalPoint() != '.' )
297+
{
298+
d = QLocale( QLocale::English ).toDouble( v.toString(), &ok );
299+
if ( ok )
300+
{
301+
v = QVariant( d );
302+
return true;
303+
}
304+
}
305+
}
306+
}
307+
308+
// For string representation of an int we also might have thousand separator
309+
if ( d->type == QVariant::Int && v.type() == QVariant::String )
310+
{
311+
QVariant tmp( v );
312+
if ( !tmp.convert( d->type ) )
313+
{
314+
// This might be a string with thousand separator: use locale to convert
315+
bool ok;
316+
int i = QLocale().toInt( v.toString(), &ok );
317+
if ( ok )
318+
{
319+
v = QVariant( i );
320+
return true;
321+
}
322+
}
323+
}
324+
325+
// For string representation of a long we also might have thousand separator
326+
if ( d->type == QVariant::LongLong && v.type() == QVariant::String )
327+
{
328+
QVariant tmp( v );
329+
if ( !tmp.convert( d->type ) )
330+
{
331+
// This might be a string with thousand separator: use locale to convert
332+
bool ok;
333+
qlonglong l = QLocale().toLongLong( v.toString(), &ok );
334+
if ( ok )
335+
{
336+
v = QVariant( l );
337+
return true;
338+
}
339+
}
340+
}
341+
237342
//String representations of doubles in QVariant will return false to convert( QVariant::Int )
238343
//work around this by first converting to double, and then checking whether the double is convertible to int
239344
if ( d->type == QVariant::Int && v.canConvert( QVariant::Double ) )
@@ -258,6 +363,7 @@ bool QgsField::convertCompatible( QVariant &v ) const
258363
return true;
259364
}
260365

366+
261367
if ( !v.convert( d->type ) )
262368
{
263369
v = QVariant( d->type );

‎src/gui/editorwidgets/qgstexteditwrapper.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,9 @@ void QgsTextEditWrapper::setWidgetValue( const QVariant &val )
217217
v = QgsApplication::nullRepresentation();
218218
}
219219
else
220-
v = val.toString();
220+
{
221+
v = field().displayString( val );
222+
}
221223

222224
if ( mTextEdit )
223225
{

‎src/gui/qgsfieldvalidator.cpp

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,16 @@ QgsFieldValidator::QgsFieldValidator( QObject *parent, const QgsField &field, co
5656
{
5757
if ( mField.length() > 0 && mField.precision() > 0 )
5858
{
59-
QString re = QStringLiteral( "-?\\d{0,%1}(\\.\\d{0,%2})?" ).arg( mField.length() - mField.precision() ).arg( mField.precision() );
59+
QString re;
60+
// Also accept locale's decimalPoint if it's not a dot
61+
if ( QLocale().decimalPoint() != '.' )
62+
{
63+
re = QStringLiteral( "-?\\d{0,%1}([\\.%2]\\d{0,%3})?" ).arg( mField.length() - mField.precision() ).arg( QLocale().decimalPoint() ).arg( mField.precision() );
64+
}
65+
else
66+
{
67+
re = QStringLiteral( "-?\\d{0,%1}([\\.,]\\d{0,%2})?" ).arg( mField.length() - mField.precision() ).arg( mField.precision() );
68+
}
6069
mValidator = new QRegExpValidator( QRegExp( re ), parent );
6170
}
6271
else if ( mField.length() > 0 && mField.precision() == 0 )
@@ -66,7 +75,16 @@ QgsFieldValidator::QgsFieldValidator( QObject *parent, const QgsField &field, co
6675
}
6776
else if ( mField.precision() > 0 )
6877
{
69-
QString re = QStringLiteral( "-?\\d*(\\.\\d{0,%1})?" ).arg( mField.precision() );
78+
QString re;
79+
// Also accept locale's decimalPoint if it's not a dot
80+
if ( QLocale().decimalPoint() != '.' )
81+
{
82+
re = QStringLiteral( "-?\\d*([\\.%1]\\d{0,%2})?" ).arg( QLocale().decimalPoint(), mField.precision() );
83+
}
84+
else
85+
{
86+
re = QStringLiteral( "-?\\d*([\\.]\\d{0,%1})?" ).arg( mField.precision() );
87+
}
7088
mValidator = new QRegExpValidator( QRegExp( re ), parent );
7189
}
7290
else
@@ -112,12 +130,6 @@ QValidator::State QgsFieldValidator::validate( QString &s, int &i ) const
112130
// delegate to the child validator if any
113131
if ( mValidator )
114132
{
115-
// force to use the '.' as a decimal point or in case we are using QDoubleValidator
116-
// we can get a valid number with a comma depending on current locale
117-
// ... but this will fail subsequently when converted from string to double and
118-
// becomes a NULL!
119-
if ( mField.type() == QVariant::Double )
120-
s = s.replace( ',', '.' );
121133
QValidator::State result = mValidator->validate( s, i );
122134
return result;
123135
}

‎tests/src/core/testqgsfield.cpp

Lines changed: 145 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <QObject>
1818
#include <QString>
1919
#include <QStringList>
20+
#include <QLocale>
2021

2122
#include <memory>
2223

@@ -66,12 +67,12 @@ void TestQgsField::cleanupTestCase()
6667

6768
void TestQgsField::init()
6869
{
69-
70+
QLocale::setDefault( QLocale::English );
7071
}
7172

7273
void TestQgsField::cleanup()
7374
{
74-
75+
QLocale::setDefault( QLocale::English );
7576
}
7677

7778
void TestQgsField::create()
@@ -315,12 +316,12 @@ void TestQgsField::displayString()
315316
//test int value in int type
316317
QgsField intField2( QStringLiteral( "int" ), QVariant::Int, QStringLiteral( "int" ) );
317318
QCOMPARE( intField2.displayString( 5 ), QString( "5" ) );
318-
QCOMPARE( intField2.displayString( 599999898999LL ), QString( "599999898999" ) );
319+
QCOMPARE( intField2.displayString( 599999898999LL ), QString( "599,999,898,999" ) );
319320

320321
//test long type
321322
QgsField longField( QStringLiteral( "long" ), QVariant::LongLong, QStringLiteral( "longlong" ) );
322323
QCOMPARE( longField.displayString( 5 ), QString( "5" ) );
323-
QCOMPARE( longField.displayString( 599999898999LL ), QString( "599999898999" ) );
324+
QCOMPARE( longField.displayString( 599999898999LL ), QString( "599,999,898,999" ) );
324325

325326
//test NULL int
326327
QVariant nullInt = QVariant( QVariant::Int );
@@ -332,12 +333,57 @@ void TestQgsField::displayString()
332333
QgsField doubleFieldNoPrec( QStringLiteral( "double" ), QVariant::Double, QStringLiteral( "double" ), 10 );
333334
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005 ), QString( "5.005005" ) );
334335
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005005 ), QString( "5.005005005" ) );
335-
QCOMPARE( doubleFieldNoPrec.displayString( 599999898999.0 ), QString( "599999898999" ) );
336+
QCOMPARE( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator, QLocale::NumberOption::DefaultNumberOptions );
337+
QCOMPARE( doubleFieldNoPrec.displayString( 599999898999.0 ), QString( "599,999,898,999" ) );
336338

337339
//test NULL double
338340
QVariant nullDouble = QVariant( QVariant::Double );
339341
QCOMPARE( doubleField.displayString( nullDouble ), QString( "TEST NULL" ) );
340342

343+
//test double value with German locale
344+
QLocale::setDefault( QLocale::German );
345+
QCOMPARE( doubleField.displayString( 5.005005 ), QString( "5,005" ) );
346+
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005 ), QString( "5,005005" ) );
347+
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005005 ), QString( "5,005005005" ) );
348+
QCOMPARE( doubleFieldNoPrec.displayString( 599999898999.0 ), QString( "599.999.898.999" ) );
349+
QCOMPARE( doubleFieldNoPrec.displayString( 5999.123456 ), QString( "5.999,123456" ) );
350+
351+
//test value with custom German locale (OmitGroupSeparator)
352+
QLocale customGerman( QLocale::German );
353+
customGerman.setNumberOptions( QLocale::NumberOption::OmitGroupSeparator );
354+
QLocale::setDefault( customGerman );
355+
QCOMPARE( doubleField.displayString( 5.005005 ), QString( "5,005" ) );
356+
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005 ), QString( "5,005005" ) );
357+
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005005 ), QString( "5,005005005" ) );
358+
QCOMPARE( doubleFieldNoPrec.displayString( 599999898999.0 ), QString( "599999898999" ) );
359+
QCOMPARE( doubleFieldNoPrec.displayString( 5999.123456 ), QString( "5999,123456" ) );
360+
361+
//test int value in int type with custom German locale (OmitGroupSeparator)
362+
QCOMPARE( intField2.displayString( 5 ), QString( "5" ) );
363+
QCOMPARE( intField2.displayString( 599999898999LL ), QString( "599999898999" ) );
364+
365+
//test long type with custom German locale (OmitGroupSeparator)
366+
QCOMPARE( longField.displayString( 5 ), QString( "5" ) );
367+
QCOMPARE( longField.displayString( 599999898999LL ), QString( "599999898999" ) );
368+
369+
//test value with custom english locale (OmitGroupSeparator)
370+
QLocale customEnglish( QLocale::English );
371+
customEnglish.setNumberOptions( QLocale::NumberOption::OmitGroupSeparator );
372+
QLocale::setDefault( customEnglish );
373+
QCOMPARE( doubleField.displayString( 5.005005 ), QString( "5.005" ) );
374+
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005 ), QString( "5.005005" ) );
375+
QCOMPARE( doubleFieldNoPrec.displayString( 5.005005005 ), QString( "5.005005005" ) );
376+
QCOMPARE( doubleFieldNoPrec.displayString( 599999898999.0 ), QString( "599999898999" ) );
377+
QCOMPARE( doubleFieldNoPrec.displayString( 5999.123456 ), QString( "5999.123456" ) );
378+
379+
//test int value in int type with custom english locale (OmitGroupSeparator)
380+
QCOMPARE( intField2.displayString( 5 ), QString( "5" ) );
381+
QCOMPARE( intField2.displayString( 599999898999LL ), QString( "599999898999" ) );
382+
383+
//test long type with custom english locale (OmitGroupSeparator)
384+
QCOMPARE( longField.displayString( 5 ), QString( "5" ) );
385+
QCOMPARE( longField.displayString( 599999898999LL ), QString( "599999898999" ) );
386+
341387
}
342388

343389
void TestQgsField::convertCompatible()
@@ -452,6 +498,44 @@ void TestQgsField::convertCompatible()
452498
QCOMPARE( longlong.type(), QVariant::LongLong );
453499
QCOMPARE( longlong, QVariant( 99999999999999999LL ) );
454500

501+
//string representation of an int
502+
QVariant stringInt( "123456" );
503+
QVERIFY( intField.convertCompatible( stringInt ) );
504+
QCOMPARE( stringInt.type(), QVariant::Int );
505+
QCOMPARE( stringInt, QVariant( 123456 ) );
506+
// now with group separator for english locale
507+
stringInt = QVariant( "123,456" );
508+
QVERIFY( intField.convertCompatible( stringInt ) );
509+
QCOMPARE( stringInt.type(), QVariant::Int );
510+
QCOMPARE( stringInt, QVariant( "123456" ) );
511+
512+
//string representation of a longlong
513+
QVariant stringLong( "99999999999999999" );
514+
QVERIFY( longlongField.convertCompatible( stringLong ) );
515+
QCOMPARE( stringLong.type(), QVariant::LongLong );
516+
QCOMPARE( stringLong, QVariant( 99999999999999999LL ) );
517+
// now with group separator for english locale
518+
stringLong = QVariant( "99,999,999,999,999,999" );
519+
QVERIFY( longlongField.convertCompatible( stringLong ) );
520+
QCOMPARE( stringLong.type(), QVariant::LongLong );
521+
QCOMPARE( stringLong, QVariant( 99999999999999999LL ) );
522+
523+
524+
//string representation of a double
525+
QVariant stringDouble( "123456.012345" );
526+
QVERIFY( doubleField.convertCompatible( stringDouble ) );
527+
QCOMPARE( stringDouble.type(), QVariant::Double );
528+
QCOMPARE( stringDouble, QVariant( 123456.012345 ) );
529+
// now with group separator for english locale
530+
stringDouble = QVariant( "1,223,456.012345" );
531+
QVERIFY( doubleField.convertCompatible( stringDouble ) );
532+
QCOMPARE( stringDouble.type(), QVariant::Double );
533+
QCOMPARE( stringDouble, QVariant( 1223456.012345 ) );
534+
// This should not convert
535+
stringDouble = QVariant( "1.223.456,012345" );
536+
QVERIFY( ! doubleField.convertCompatible( stringDouble ) );
537+
538+
455539
//double with precision
456540
QgsField doubleWithPrecField( QStringLiteral( "double" ), QVariant::Double, QStringLiteral( "double" ), 10, 3 );
457541
doubleVar = QVariant( 10.12345678 );
@@ -466,6 +550,62 @@ void TestQgsField::convertCompatible()
466550
QVERIFY( !stringWithLen.convertCompatible( stringVar ) );
467551
QCOMPARE( stringVar.type(), QVariant::String );
468552
QCOMPARE( stringVar.toString(), QString( "lon" ) );
553+
554+
555+
/////////////////////////////////////////////////////////
556+
// German locale tests
557+
558+
//double with ',' as decimal separator for German locale
559+
QLocale::setDefault( QLocale::German );
560+
QVariant doubleCommaVar( "1,2345" );
561+
QVERIFY( doubleField.convertCompatible( doubleCommaVar ) );
562+
QCOMPARE( doubleCommaVar.type(), QVariant::Double );
563+
QCOMPARE( doubleCommaVar.toString(), QString( "1.2345" ) );
564+
565+
//string representation of an int
566+
stringInt = QVariant( "123456" );
567+
QVERIFY( intField.convertCompatible( stringInt ) );
568+
QCOMPARE( stringInt.type(), QVariant::Int );
569+
QCOMPARE( stringInt, QVariant( 123456 ) );
570+
// now with group separator for german locale
571+
stringInt = QVariant( "123.456" );
572+
QVERIFY( intField.convertCompatible( stringInt ) );
573+
QCOMPARE( stringInt.type(), QVariant::Int );
574+
QCOMPARE( stringInt, QVariant( "123456" ) );
575+
576+
//string representation of a longlong
577+
stringLong = QVariant( "99999999999999999" );
578+
QVERIFY( longlongField.convertCompatible( stringLong ) );
579+
QCOMPARE( stringLong.type(), QVariant::LongLong );
580+
QCOMPARE( stringLong, QVariant( 99999999999999999LL ) );
581+
// now with group separator for german locale
582+
stringLong = QVariant( "99.999.999.999.999.999" );
583+
QVERIFY( longlongField.convertCompatible( stringLong ) );
584+
QCOMPARE( stringLong.type(), QVariant::LongLong );
585+
QCOMPARE( stringLong, QVariant( 99999999999999999LL ) );
586+
587+
//string representation of a double
588+
stringDouble = QVariant( "123456,012345" );
589+
QVERIFY( doubleField.convertCompatible( stringDouble ) );
590+
QCOMPARE( stringDouble.type(), QVariant::Double );
591+
QCOMPARE( stringDouble, QVariant( 123456.012345 ) );
592+
// For doubles we also want to accept dot as a decimal point
593+
stringDouble = QVariant( "123456.012345" );
594+
QVERIFY( doubleField.convertCompatible( stringDouble ) );
595+
QCOMPARE( stringDouble.type(), QVariant::Double );
596+
QCOMPARE( stringDouble, QVariant( 123456.012345 ) );
597+
// now with group separator for german locale
598+
stringDouble = QVariant( "1.223.456,012345" );
599+
QVERIFY( doubleField.convertCompatible( stringDouble ) );
600+
QCOMPARE( stringDouble.type(), QVariant::Double );
601+
QCOMPARE( stringDouble, QVariant( 1223456.012345 ) );
602+
// Be are good citizens and we also accept english locale
603+
stringDouble = QVariant( "1,223,456.012345" );
604+
QVERIFY( doubleField.convertCompatible( stringDouble ) );
605+
QCOMPARE( stringDouble.type(), QVariant::Double );
606+
QCOMPARE( stringDouble, QVariant( 1223456.012345 ) );
607+
608+
469609
}
470610

471611
void TestQgsField::dataStream()

‎tests/src/python/test_qgsfieldformatters.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,14 @@ def setUpClass(cls):
266266
QCoreApplication.setOrganizationDomain("QGIS_TestPyQgsColorScheme.com")
267267
QCoreApplication.setApplicationName("QGIS_TestPyQgsColorScheme")
268268
QgsSettings().clear()
269+
QLocale.setDefault(QLocale(QLocale.English))
269270
start_app()
270271

272+
@classmethod
273+
def tearDownClass(cls):
274+
"""Reset locale"""
275+
QLocale.setDefault(QLocale(QLocale.English))
276+
271277
def test_representValue(self):
272278

273279
layer = QgsVectorLayer("point?field=int:integer&field=double:double&field=long:long",
@@ -277,23 +283,21 @@ def test_representValue(self):
277283

278284
fieldFormatter = QgsRangeFieldFormatter()
279285

280-
QLocale.setDefault(QLocale.c())
281-
282286
# Precision is ignored for integers and longlongs
283287
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '123'), '123')
284-
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '123000'), '123000')
285-
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '9999999'), '9999999') # no scientific notation for integers!
288+
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '123000'), '123,000')
289+
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '9999999'), '9,999,999') # no scientific notation for integers!
286290
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, None), 'NULL')
287291
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '123'), '123')
288-
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '123000'), '123000')
289-
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '9999999'), '9999999') # no scientific notation for long longs!
292+
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '123000'), '123,000')
293+
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '9999999'), '9,999,999') # no scientific notation for long longs!
290294
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, None), 'NULL')
291295

292296
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 1}, None, None), 'NULL')
293297
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 1}, None, '123'), '123.0')
294298

295299
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, None), 'NULL')
296-
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, '123000'), '123000.00')
300+
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, '123000'), '123,000.00')
297301
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, '0'), '0.00')
298302
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, '123'), '123.00')
299303
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, '0.123'), '0.12')
@@ -308,6 +312,7 @@ def test_representValue(self):
308312
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 3}, None, '-0.127'), '-0.127')
309313
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 3}, None, '-1.27e-1'), '-0.127')
310314

315+
# Check with Italian locale
311316
QLocale.setDefault(QLocale('it'))
312317

313318
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '9999999'),
@@ -333,6 +338,19 @@ def test_representValue(self):
333338
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 3}, None, '-0.127'), '-0,127')
334339
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 3}, None, '-1.27e-1'), '-0,127')
335340

341+
# Check with custom locale without thousand separator
342+
343+
custom = QLocale('en')
344+
custom.setNumberOptions(QLocale.OmitGroupSeparator)
345+
QLocale.setDefault(custom)
346+
347+
self.assertEqual(fieldFormatter.representValue(layer, 0, {'Precision': 1}, None, '9999999'),
348+
'9999999') # scientific notation for integers!
349+
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '123'), '123')
350+
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '123000'), '123000')
351+
self.assertEqual(fieldFormatter.representValue(layer, 2, {'Precision': 1}, None, '9999999'), '9999999') # scientific notation for long longs!
352+
self.assertEqual(fieldFormatter.representValue(layer, 1, {'Precision': 2}, None, '123000'), '123000.00')
353+
336354
QgsProject.instance().removeAllMapLayers()
337355

338356

‎tests/src/python/test_qgsfieldvalidator.py

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
import qgis # NOQA
1616

17-
from qgis.PyQt.QtCore import QVariant
17+
from qgis.PyQt.QtCore import QVariant, QLocale
1818
from qgis.PyQt.QtGui import QValidator
1919
from qgis.core import QgsVectorLayer
2020
from qgis.gui import QgsFieldValidator
@@ -39,45 +39,46 @@ def tearDown(self):
3939
"""Run after each test."""
4040
pass
4141

42-
def test_validator(self):
43-
# Test the double
44-
"""
42+
def _fld_checker(self, field):
43+
"""
4544
Expected results from validate
4645
QValidator::Invalid 0 The string is clearly invalid.
4746
QValidator::Intermediate 1 The string is a plausible intermediate value.
4847
QValidator::Acceptable 2 The string is acceptable as a final result; i.e. it is valid.
4948
"""
49+
DECIMAL_SEPARATOR = QLocale().decimalPoint()
50+
OTHER_SEPARATOR = ',' if DECIMAL_SEPARATOR == '.' else '.'
5051

51-
double_field = self.vl.fields()[self.vl.fields().indexFromName('double_field')]
52-
self.assertEqual(double_field.precision(), 0) # this is what the provider reports :(
53-
self.assertEqual(double_field.length(), 0) # not set
54-
self.assertEqual(double_field.type(), QVariant.Double)
55-
56-
validator = QgsFieldValidator(None, double_field, '0.0', '')
52+
validator = QgsFieldValidator(None, field, '0.0', '')
5753

5854
def _test(value, expected):
5955
ret = validator.validate(value, 0)
60-
self.assertEqual(ret[0], expected, value)
56+
self.assertEqual(ret[0], expected, "%s != %s" % (ret[0], expected))
6157
if value:
6258
self.assertEqual(validator.validate('-' + value, 0)[0], expected, '-' + value)
63-
# Check the decimal comma separator has been properly transformed
64-
if expected != QValidator.Invalid:
65-
self.assertEqual(ret[1], value.replace(',', '.'))
6659

6760
# Valid
6861
_test('0.1234', QValidator.Acceptable)
69-
_test('0,1234', QValidator.Acceptable)
70-
_test('12345.1234e+123', QValidator.Acceptable)
71-
_test('12345.1234e-123', QValidator.Acceptable)
72-
_test('12345,1234e+123', QValidator.Acceptable)
73-
_test('12345,1234e-123', QValidator.Acceptable)
74-
_test('', QValidator.Acceptable)
7562

76-
# Out of range
77-
_test('12345.1234e+823', QValidator.Intermediate)
78-
_test('12345.1234e-823', QValidator.Intermediate)
79-
_test('12345,1234e+823', QValidator.Intermediate)
80-
_test('12345,1234e-823', QValidator.Intermediate)
63+
# Apparently we accept comma only when locale say so
64+
if DECIMAL_SEPARATOR != '.':
65+
_test('0,1234', QValidator.Acceptable)
66+
67+
# If precision is > 0, regexp validator is used (and it does not support sci notation)
68+
if field.precision() == 0:
69+
_test('12345.1234e+123', QValidator.Acceptable)
70+
_test('12345.1234e-123', QValidator.Acceptable)
71+
if DECIMAL_SEPARATOR != '.':
72+
_test('12345,1234e+123', QValidator.Acceptable)
73+
_test('12345,1234e-123', QValidator.Acceptable)
74+
_test('', QValidator.Acceptable)
75+
76+
# Out of range
77+
_test('12345.1234e+823', QValidator.Intermediate)
78+
_test('12345.1234e-823', QValidator.Intermediate)
79+
if DECIMAL_SEPARATOR != '.':
80+
_test('12345,1234e+823', QValidator.Intermediate)
81+
_test('12345,1234e-823', QValidator.Intermediate)
8182

8283
# Invalid
8384
_test('12345-1234', QValidator.Invalid)
@@ -97,9 +98,39 @@ def _test(value, expected):
9798

9899
# Invalid
99100
_test('12345-1234', QValidator.Invalid)
100-
_test('12345.1234', QValidator.Invalid)
101+
_test('12345%s1234' % DECIMAL_SEPARATOR, QValidator.Invalid)
101102
_test('onetwothree', QValidator.Invalid)
102103

104+
def test_doubleValidator(self):
105+
"""Test the double with default (system) locale"""
106+
field = self.vl.fields()[self.vl.fields().indexFromName('double_field')]
107+
self.assertEqual(field.precision(), 0) # this is what the provider reports :(
108+
self.assertEqual(field.length(), 0) # not set
109+
self.assertEqual(field.type(), QVariant.Double)
110+
self._fld_checker(field)
111+
112+
def test_doubleValidatorCommaLocale(self):
113+
"""Test the double with german locale"""
114+
QLocale.setDefault(QLocale(QLocale.German, QLocale.Germany))
115+
assert QLocale().decimalPoint() == ','
116+
field = self.vl.fields()[self.vl.fields().indexFromName('double_field')]
117+
self._fld_checker(field)
118+
119+
def test_doubleValidatorDotLocale(self):
120+
"""Test the double with english locale"""
121+
QLocale.setDefault(QLocale(QLocale.English))
122+
assert QLocale().decimalPoint() == '.'
123+
field = self.vl.fields()[self.vl.fields().indexFromName('double_field')]
124+
self._fld_checker(field)
125+
126+
def test_precision(self):
127+
"""Test different precision"""
128+
QLocale.setDefault(QLocale(QLocale.English))
129+
assert QLocale().decimalPoint() == '.'
130+
field = self.vl.fields()[self.vl.fields().indexFromName('double_field')]
131+
field.setPrecision(4)
132+
self._fld_checker(field)
133+
103134

104135
if __name__ == '__main__':
105136
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.