[FEATURE] Allow array values as a valid result for data defined offset

or size properties

Previously only string values of the format 'x,y' would be permitted. But
I've seen MANY bug reports and questions about this, so also allow
arrays of doubles as a valid result. E.g. "array(3,5)". In any case, it's
just nicer.

Fixes #31444
This commit is contained in:
Nyall Dawson 2019-08-29 10:01:37 +10:00
parent 60e2b70ee3
commit ddbb1b175f
8 changed files with 286 additions and 36 deletions

View File

@ -92,6 +92,23 @@ Decodes a QSizeF from a string.
.. seealso:: :py:func:`encodePoint`
.. seealso:: :py:func:`decodeSize`
%End
static QPointF toPoint( const QVariant &value, bool *ok /Out/ = 0 );
%Docstring
Converts a ``value`` to a point.
:param value: value to convert
:return: - converted point
- ok: will be set to ``True`` if value was successfully converted
.. seealso:: :py:func:`decodePoint`
.. seealso:: :py:func:`toSize`
.. versionadded:: 3.10
%End
static QString encodeSize( QSizeF size );
@ -114,6 +131,23 @@ Decodes a QSizeF from a string.
.. seealso:: :py:func:`decodePoint`
.. versionadded:: 3.0
%End
static QSizeF toSize( const QVariant &value, bool *ok /Out/ = 0 );
%Docstring
Converts a ``value`` to a size.
:param value: value to convert
:return: - converted size
- ok: will be set to ``True`` if value was successfully converted
.. seealso:: :py:func:`decodeSize`
.. seealso:: :py:func:`toPoint`
.. versionadded:: 3.10
%End
static QString encodeMapUnitScale( const QgsMapUnitScale &mapUnitScale );

View File

@ -1912,17 +1912,12 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::CurvedCharAngleInOut ) )
{
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::CurvedCharAngleInOut, context.expressionContext() );
if ( exprVal.isValid() )
bool ok = false;
const QPointF maxcharanglePt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
if ( ok )
{
QString ptstr = exprVal.toString().trimmed();
QgsDebugMsgLevel( QStringLiteral( "exprVal CurvedCharAngleInOut:%1" ).arg( ptstr ), 4 );
if ( !ptstr.isEmpty() )
{
QPointF maxcharanglePt = QgsSymbolLayerUtils::decodePoint( ptstr );
maxcharanglein = qBound( 20.0, static_cast< double >( maxcharanglePt.x() ), 60.0 );
maxcharangleout = qBound( 20.0, static_cast< double >( maxcharanglePt.y() ), 95.0 );
}
maxcharanglein = qBound( 20.0, static_cast< double >( maxcharanglePt.x() ), 60.0 );
maxcharangleout = qBound( 20.0, static_cast< double >( maxcharanglePt.y() ), 95.0 );
}
}
// make sure maxcharangleout is always negative
@ -2134,15 +2129,12 @@ void QgsPalLayerSettings::registerFeature( const QgsFeature &f, QgsRenderContext
{
context.expressionContext().setOriginalValueVariable( QgsSymbolLayerUtils::encodePoint( QPointF( xOffset, yOffset ) ) );
exprVal = mDataDefinedProperties.value( QgsPalLayerSettings::OffsetXY, context.expressionContext() );
if ( exprVal.isValid() )
bool ok = false;
const QPointF ddOffPt = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
if ( ok )
{
QString ptstr = exprVal.toString().trimmed();
if ( !ptstr.isEmpty() )
{
QPointF ddOffPt = QgsSymbolLayerUtils::decodePoint( ptstr );
xOff = ddOffPt.x();
yOff = ddOffPt.y();
}
xOff = ddOffPt.x();
yOff = ddOffPt.y();
}
}
@ -2774,22 +2766,22 @@ bool QgsPalLayerSettings::dataDefinedValEval( DataDefinedValueType valType,
}
case DDPointF:
{
QString ptstr = exprVal.toString().trimmed();
if ( !ptstr.isEmpty() )
bool ok = false;
const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
if ( ok )
{
dataDefinedValues.insert( p, QVariant( QgsSymbolLayerUtils::decodePoint( ptstr ) ) );
dataDefinedValues.insert( p, res );
return true;
}
return false;
}
case DDSizeF:
{
QString ptstr = exprVal.toString().trimmed();
if ( !ptstr.isEmpty() )
bool ok = false;
const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
if ( ok )
{
dataDefinedValues.insert( p, QVariant( QgsSymbolLayerUtils::decodeSize( ptstr ) ) );
dataDefinedValues.insert( p, res );
return true;
}
return false;

View File

@ -1053,10 +1053,11 @@ void QgsTextBackgroundSettings::updateDataDefinedProperties( QgsRenderContext &c
exprVal = properties.value( QgsPalLayerSettings::ShapeOffset, context.expressionContext() );
if ( exprVal.isValid() )
{
QString offset = exprVal.toString();
if ( !offset.isEmpty() )
bool ok = false;
const QPointF res = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
if ( ok )
{
d->offset = QgsSymbolLayerUtils::decodePoint( offset );
d->offset = res;
}
}
exprVal = properties.value( QgsPalLayerSettings::ShapeOffsetUnits, context.expressionContext() );
@ -1075,10 +1076,11 @@ void QgsTextBackgroundSettings::updateDataDefinedProperties( QgsRenderContext &c
exprVal = properties.value( QgsPalLayerSettings::ShapeRadii, context.expressionContext() );
if ( exprVal.isValid() )
{
QString ptstr = exprVal.toString();
if ( !ptstr.isEmpty() )
bool ok = false;
const QSizeF res = QgsSymbolLayerUtils::toSize( exprVal, &ok );
if ( ok )
{
d->radii = QgsSymbolLayerUtils::decodeSize( ptstr );
d->radii = res;
}
}

View File

@ -2391,9 +2391,10 @@ bool QgsSvgMarkerSymbolLayer::writeDxf( QgsDxfExport &e, double mmMapUnitScaleFa
if ( mDataDefinedProperties.isActive( QgsSymbolLayer::PropertyOffset ) )
{
context.setOriginalValueVariable( QgsSymbolLayerUtils::encodePoint( mOffset ) );
QString offsetString = mDataDefinedProperties.valueAsString( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString(), &ok );
const QVariant val = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext(), QString() );
const QPointF res = QgsSymbolLayerUtils::toPoint( val, &ok );
if ( ok )
offset = QgsSymbolLayerUtils::decodePoint( offsetString );
offset = res;
}
double offsetX = offset.x();
double offsetY = offset.y();

View File

@ -467,9 +467,10 @@ void QgsMarkerSymbolLayer::markerOffset( QgsSymbolRenderContext &context, double
{
context.setOriginalValueVariable( QgsSymbolLayerUtils::encodePoint( mOffset ) );
QVariant exprVal = mDataDefinedProperties.value( QgsSymbolLayer::PropertyOffset, context.renderContext().expressionContext() );
if ( exprVal.isValid() )
bool ok = false;
const QPointF offset = QgsSymbolLayerUtils::toPoint( exprVal, &ok );
if ( ok )
{
QPointF offset = QgsSymbolLayerUtils::decodePoint( exprVal.toString() );
offsetX = offset.x();
offsetY = offset.y();
}

View File

@ -440,6 +440,57 @@ QPointF QgsSymbolLayerUtils::decodePoint( const QString &str )
return QPointF( lst[0].toDouble(), lst[1].toDouble() );
}
QPointF QgsSymbolLayerUtils::toPoint( const QVariant &value, bool *ok )
{
if ( ok )
*ok = false;
if ( value.isNull() )
return QPoint();
if ( value.type() == QVariant::List )
{
const QVariantList list = value.toList();
if ( list.size() != 2 )
{
return QPointF();
}
bool convertOk = false;
double x = list.at( 0 ).toDouble( &convertOk );
if ( convertOk )
{
double y = list.at( 1 ).toDouble( &convertOk );
if ( convertOk )
{
if ( ok )
*ok = true;
return QPointF( x, y );
}
}
return QPointF();
}
else
{
// can't use decodePoint here -- has no OK handling
const QStringList list = value.toString().trimmed().split( ',' );
if ( list.count() != 2 )
return QPointF();
bool convertOk = false;
double x = list.at( 0 ).toDouble( &convertOk );
if ( convertOk )
{
double y = list.at( 1 ).toDouble( &convertOk );
if ( convertOk )
{
if ( ok )
*ok = true;
return QPointF( x, y );
}
}
return QPointF();
}
}
QString QgsSymbolLayerUtils::encodeSize( QSizeF size )
{
return QStringLiteral( "%1,%2" ).arg( qgsDoubleToString( size.width() ), qgsDoubleToString( size.height() ) );
@ -453,6 +504,57 @@ QSizeF QgsSymbolLayerUtils::decodeSize( const QString &string )
return QSizeF( lst[0].toDouble(), lst[1].toDouble() );
}
QSizeF QgsSymbolLayerUtils::toSize( const QVariant &value, bool *ok )
{
if ( ok )
*ok = false;
if ( value.isNull() )
return QSizeF();
if ( value.type() == QVariant::List )
{
const QVariantList list = value.toList();
if ( list.size() != 2 )
{
return QSizeF();
}
bool convertOk = false;
double x = list.at( 0 ).toDouble( &convertOk );
if ( convertOk )
{
double y = list.at( 1 ).toDouble( &convertOk );
if ( convertOk )
{
if ( ok )
*ok = true;
return QSizeF( x, y );
}
}
return QSizeF();
}
else
{
// can't use decodePoint here -- has no OK handling
const QStringList list = value.toString().trimmed().split( ',' );
if ( list.count() != 2 )
return QSizeF();
bool convertOk = false;
double x = list.at( 0 ).toDouble( &convertOk );
if ( convertOk )
{
double y = list.at( 1 ).toDouble( &convertOk );
if ( convertOk )
{
if ( ok )
*ok = true;
return QSizeF( x, y );
}
}
return QSizeF();
}
}
QString QgsSymbolLayerUtils::encodeMapUnitScale( const QgsMapUnitScale &mapUnitScale )
{
return QStringLiteral( "3x:%1,%2,%3,%4,%5,%6" ).arg( qgsDoubleToString( mapUnitScale.minScale ),

View File

@ -122,6 +122,20 @@ class CORE_EXPORT QgsSymbolLayerUtils
*/
static QPointF decodePoint( const QString &string );
/**
* Converts a \a value to a point.
*
* \param value value to convert
* \param ok if specified, will be set to TRUE if value was successfully converted
*
* \returns converted point
*
* \see decodePoint()
* \see toSize()
* \since QGIS 3.10
*/
static QPointF toPoint( const QVariant &value, bool *ok SIP_OUT = nullptr );
/**
* Encodes a QSizeF to a string.
* \see decodeSize()
@ -138,6 +152,20 @@ class CORE_EXPORT QgsSymbolLayerUtils
*/
static QSizeF decodeSize( const QString &string );
/**
* Converts a \a value to a size.
*
* \param value value to convert
* \param ok if specified, will be set to TRUE if value was successfully converted
*
* \returns converted size
*
* \see decodeSize()
* \see toPoint()
* \since QGIS 3.10
*/
static QSizeF toSize( const QVariant &value, bool *ok SIP_OUT = nullptr );
static QString encodeMapUnitScale( const QgsMapUnitScale &mapUnitScale );
static QgsMapUnitScale decodeMapUnitScale( const QString &str );

View File

@ -38,6 +38,51 @@ class PyQgsSymbolLayerUtils(unittest.TestCase):
s2 = QgsSymbolLayerUtils.decodeSize('')
self.assertEqual(s2, QSizeF(0, 0))
def testToSize(self):
s2, ok = QgsSymbolLayerUtils.toSize(None)
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toSize(4)
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toSize('4')
self.assertFalse(ok)
# arrays
s2, ok = QgsSymbolLayerUtils.toSize([4])
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toSize([])
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toSize([4, 5, 6])
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toSize([4,5])
self.assertTrue(ok)
self.assertEqual(s2, QSizeF(4,5))
s2, ok = QgsSymbolLayerUtils.toSize(['4','5'])
self.assertTrue(ok)
self.assertEqual(s2, QSizeF(4,5))
# string values
s = QSizeF()
string = QgsSymbolLayerUtils.encodeSize(s)
s2, ok = QgsSymbolLayerUtils.toSize(string)
self.assertTrue(ok)
self.assertEqual(s2, s)
s = QSizeF(1.5, 2.5)
string = QgsSymbolLayerUtils.encodeSize(s)
s2, ok = QgsSymbolLayerUtils.toSize(string)
self.assertTrue(ok)
self.assertEqual(s2, s)
# bad string
s2, ok = QgsSymbolLayerUtils.toSize('')
self.assertFalse(ok)
self.assertEqual(s2, QSizeF())
def testEncodeDecodePoint(self):
s = QPointF()
string = QgsSymbolLayerUtils.encodePoint(s)
@ -52,6 +97,51 @@ class PyQgsSymbolLayerUtils(unittest.TestCase):
s2 = QgsSymbolLayerUtils.decodePoint('')
self.assertEqual(s2, QPointF())
def testToPoint(self):
s2, ok = QgsSymbolLayerUtils.toPoint(None)
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toPoint(4)
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toPoint('4')
self.assertFalse(ok)
# arrays
s2, ok = QgsSymbolLayerUtils.toPoint([4])
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toPoint([])
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toPoint([4, 5, 6])
self.assertFalse(ok)
s2, ok = QgsSymbolLayerUtils.toPoint([4,5])
self.assertTrue(ok)
self.assertEqual(s2, QPointF(4,5))
s2, ok = QgsSymbolLayerUtils.toPoint(['4','5'])
self.assertTrue(ok)
self.assertEqual(s2, QPointF(4,5))
# string values
s = QPointF()
string = QgsSymbolLayerUtils.encodePoint(s)
s2, ok = QgsSymbolLayerUtils.toPoint(string)
self.assertTrue(ok)
self.assertEqual(s2, s)
s = QPointF(1.5, 2.5)
string = QgsSymbolLayerUtils.encodePoint(s)
s2, ok = QgsSymbolLayerUtils.toPoint(string)
self.assertTrue(ok)
self.assertEqual(s2, s)
# bad string
s2, ok = QgsSymbolLayerUtils.toPoint('')
self.assertFalse(ok)
self.assertEqual(s2, QPointF())
def testDecodeArrowHeadType(self):
type, ok = QgsSymbolLayerUtils.decodeArrowHeadType(0)
self.assertTrue(ok)