Skip to content
Permalink
Browse files
Merge pull request #43705 from elpaso/bugfix-gh43678-table-layout-ite…
…m-localization

Localization for table layout items
  • Loading branch information
elpaso committed Jun 21, 2021
2 parents adc9d07 + bbd68be commit b4741038129715a8c0fbe36356e1ebe5509884ba
Showing with 141 additions and 8 deletions.
  1. +56 −0 src/core/expression/qgsexpressionutils.h
  2. +10 −7 src/core/layout/qgslayouttable.cpp
  3. +36 −0 tests/src/core/testqgsexpression.cpp
  4. +39 −1 tests/src/core/testqgslayouttable.cpp
@@ -29,6 +29,7 @@
#include "qgsvectorlayer.h"

#include <QThread>
#include <QLocale>

#define ENSURE_NO_EVAL_ERROR { if ( parent->hasEvalError() ) return QVariant(); }
#define SET_EVAL_ERROR(x) { parent->setEvalErrorString( x ); return QVariant(); }
@@ -429,6 +430,61 @@ class QgsExpressionUtils
return QVariantMap();
}
}

/**
* Returns the localized string representation of a QVariant, converting numbers according to locale settings.
* \param value the QVariant to convert.
* \returns the string representation of the value.
* \since QGIS 3.20
*/
static QString toLocalizedString( const QVariant &value )
{
if ( value.type() == QVariant::Int || value.type() == QVariant::UInt || value.type() == QVariant::LongLong || value.type() == QVariant::ULongLong )
{
bool ok;
QString res;

if ( value.type() == QVariant::ULongLong )
{
res = QLocale().toString( value.toULongLong( &ok ) );
}
else
{
res = QLocale().toString( value.toLongLong( &ok ) );
}

if ( ok )
{
return res;
}
else
{
return value.toString();
}
}
// Qt madness with QMetaType::Float :/
else if ( value.type() == QVariant::Double || value.type() == static_cast<QVariant::Type>( QMetaType::Float ) )
{
bool ok;
const QString strVal { value.toString() };
const int dotPosition { strVal.indexOf( '.' ) };
const int precision { dotPosition > 0 ? strVal.length() - dotPosition - 1 : 0 };
const QString res { QLocale().toString( value.toDouble( &ok ), 'f', precision ) };

if ( ok )
{
return res;
}
else
{
return value.toString();
}
}
else
{
return value.toString();
}
}
};

/// @endcond
@@ -16,6 +16,7 @@
***************************************************************************/

#include "qgsexpressioncontextutils.h"
#include "qgsexpressionutils.h"
#include "qgslayouttable.h"
#include "qgslayout.h"
#include "qgslayoututils.h"
@@ -525,17 +526,18 @@ void QgsLayoutTable::render( QgsLayoutItemRenderContext &context, const QRectF &
currentX += mCellMargin;

QVariant cellContents = mTableContents.at( row ).at( col );
QStringList str = cellContents.toString().split( '\n' );
const QString localizedString { QgsExpressionUtils::toLocalizedString( cellContents ) };
QStringList str = localizedString.split( '\n' );

QgsTextFormat cellFormat = textFormatForCell( row, col );
QgsExpressionContextScopePopper popper( context.renderContext().expressionContext(), scopeForCell( row, col ) );
cellFormat.updateDataDefinedProperties( context.renderContext() );

// disable text clipping to target text rectangle, because we manually clip to the full cell bounds below
// and it's ok if text overlaps into the margin (e.g. extenders or italicized text)
if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), cellContents.toString(), column.width(), cellFormat ) )
if ( ( mWrapBehavior != TruncateText || column.width() > 0 ) && textRequiresWrapping( context.renderContext(), localizedString, column.width(), cellFormat ) )
{
str = wrappedText( context.renderContext(), cellContents.toString(), column.width(), cellFormat );
str = wrappedText( context.renderContext(), localizedString, column.width(), cellFormat );
}

p->save();
@@ -1127,7 +1129,7 @@ bool QgsLayoutTable::calculateMaxColumnWidths()
if ( mColumns.at( col ).width() <= 0 )
{
//column width set to automatic, so check content size
const QStringList multiLineSplit = ( *colIt ).toString().split( '\n' );
const QStringList multiLineSplit = QgsExpressionUtils::toLocalizedString( *colIt ).split( '\n' );

QgsTextFormat cellFormat = textFormatForCell( row - 1, col );
QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, col ) );
@@ -1215,15 +1217,16 @@ bool QgsLayoutTable::calculateMaxRowHeights()
QgsExpressionContextScopePopper popper( context.expressionContext(), scopeForCell( row - 1, i ) );
cellFormat.updateDataDefinedProperties( context );
const double contentDescentMm = QgsTextRenderer::fontMetrics( context, cellFormat, QgsTextRenderer::FONT_WORKAROUND_SCALE ).descent() / QgsTextRenderer::FONT_WORKAROUND_SCALE / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters );
const QString localizedString { QgsExpressionUtils::toLocalizedString( *colIt ) };

if ( textRequiresWrapping( context, ( *colIt ).toString(), mColumns.at( i ).width(), cellFormat ) )
if ( textRequiresWrapping( context, localizedString, mColumns.at( i ).width(), cellFormat ) )
{
//contents too wide for cell, need to wrap
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, ( *colIt ).toString(), mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, wrappedText( context, localizedString, mColumns.at( i ).width(), cellFormat ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
}
else
{
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << ( *colIt ).toString().split( '\n' ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
heights[ row * cols + i ] = QgsTextRenderer::textHeight( context, cellFormat, QStringList() << localizedString.split( '\n' ), QgsTextRenderer::Rect ) / context.convertToPainterUnits( 1, QgsUnitTypes::RenderMillimeters ) - contentDescentMm;
}

i++;
@@ -35,6 +35,7 @@
#include "qgsexpressionnodeimpl.h"
#include "qgsvectorlayerutils.h"
#include "qgsexpressioncontextutils.h"
#include "qgsexpressionutils.h"
#include <geos_c.h>

static void _parseAndEvalExpr( int arg )
@@ -4477,6 +4478,41 @@ class TestQgsExpression: public QObject
QCOMPARE( qgis::down_cast< const QgsExpressionNodeColumnRef * >( exp.rootNode()->effectiveNode() )->name(), QStringLiteral( "second_field" ) );
QCOMPARE( exp.evaluate( &context ).toInt(), 20 );
}

void testExpressionUtilsToLocalizedString()
{
const QVariant t_int( 12346 );
QVariant t_uint( QVariant::UInt );
t_uint = 12346;
QVariant t_long( QVariant::LongLong );
t_long = 12346;
QVariant t_ulong( QVariant::ULongLong );
t_ulong = 12346;
const QVariant t_double( 123456.801 );

QLocale().setDefault( QLocale::English );

qDebug() << QVariant( 123456.801 ).toString();

QCOMPARE( QgsExpressionUtils::toLocalizedString( t_int ), QStringLiteral( "12,346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_uint ), QStringLiteral( "12,346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_long ), QStringLiteral( "12,346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_ulong ), QStringLiteral( "12,346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_double ), QStringLiteral( "123,456.801" ) );

QLocale().setDefault( QLocale::Italian );

QCOMPARE( QgsExpressionUtils::toLocalizedString( t_int ), QStringLiteral( "12.346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_uint ), QStringLiteral( "12.346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_long ), QStringLiteral( "12.346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_ulong ), QStringLiteral( "12.346" ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( t_double ), QStringLiteral( "123.456,801" ) );

QLocale().setDefault( QLocale::English );

QCOMPARE( QgsExpressionUtils::toLocalizedString( QString( "hello world" ) ), QStringLiteral( "hello world" ) );
}

};

QGSTEST_MAIN( TestQgsExpression )
@@ -31,6 +31,7 @@
#include "qgsrelationmanager.h"
#include "qgsreadwritecontext.h"
#include "qgsexpressioncontextutils.h"
#include "qgsexpressionutils.h"
#include "qgslayoutmanager.h"
#include "qgsprintlayout.h"
#include "qgslayoutatlas.h"
@@ -55,6 +56,7 @@ class TestQgsLayoutTable : public QObject

void attributeTableHeadings(); //test retrieving attribute table headers
void attributeTableRows(); //test retrieving attribute table rows
void attributeTableRowsLocalized(); //test retrieving attribute table rows with locale
void attributeTableFilterFeatures(); //test filtering attribute table rows
void attributeTableSetAttributes(); //test subset of attributes in table
void attributeTableVisibleOnly(); //test displaying only visible attributes
@@ -193,7 +195,7 @@ void TestQgsLayoutTable::compareTable( QgsLayoutItemAttributeTable *table, const
QgsLayoutTableRow::const_iterator cellIt = ( *resultIt ).constBegin();
for ( ; cellIt != ( *resultIt ).constEnd(); ++cellIt )
{
QCOMPARE( ( *cellIt ).toString(), expectedRows.at( rowNumber ).at( colNumber ) );
QCOMPARE( QgsExpressionUtils::toLocalizedString( *cellIt ), expectedRows.at( rowNumber ).at( colNumber ) );
colNumber++;
}
//also check that number of columns matches expected
@@ -227,6 +229,42 @@ void TestQgsLayoutTable::attributeTableRows()
table->setMaximumNumberOfFeatures( 3 );
compareTable( table, expectedRows );
}
void TestQgsLayoutTable::attributeTableRowsLocalized()
{
//test retrieving attribute table rows

QgsVectorLayer vl { QStringLiteral( "Point?field=int:int&field=double:double&" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) };
QgsFeature f { vl.fields( ) };
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "point(9 45)" ) ) );
f.setAttribute( QStringLiteral( "int" ), 12346 );
f.setAttribute( QStringLiteral( "double" ), 123456.801 );
vl.dataProvider()->addFeatures( QgsFeatureList() << f );

QVector<QStringList> expectedRows;
QStringList row;
row << QStringLiteral( "12,346" ) << QStringLiteral( "123,456.801" );
expectedRows.append( row );

QgsLayout l( QgsProject::instance() );
l.initializeDefaults();
QgsLayoutItemAttributeTable *table = new QgsLayoutItemAttributeTable( &l );
table->setVectorLayer( &vl );

//retrieve rows and check
QLocale().setDefault( QLocale::English );
compareTable( table, expectedRows );

expectedRows.clear();
row.clear();
row << QStringLiteral( "12.346" ) << QStringLiteral( "123.456,801" );
expectedRows.append( row );
QLocale().setDefault( QLocale::Italian );
compareTable( table, expectedRows );

QLocale().setDefault( QLocale::English );

}


void TestQgsLayoutTable::attributeTableFilterFeatures()
{

0 comments on commit b474103

Please sign in to comment.