Skip to content
Permalink
Browse files

[FEATURE] Allow symbol opacity to be data defined

While it was possible to set the opacity for individual symbol layer
colors via data defined expressions, it's so far been impossible to
set a data defined expression to control the overall symbol opacity.

This commit fixes that omission...
  • Loading branch information
nyalldawson committed Nov 18, 2020
1 parent 7642b3e commit bf8075ced6578bf93ebb555d287a5a80a1b7628e
Showing with 624 additions and 40 deletions.
  1. +47 −0 python/core/auto_generated/symbology/qgssymbol.sip.in
  2. +1 −1 python/core/auto_generated/symbology/qgssymbollayer.sip.in
  3. +51 −5 src/core/symbology/qgssymbol.cpp
  4. +55 −0 src/core/symbology/qgssymbol.h
  5. +1 −1 src/core/symbology/qgssymbollayer.h
  6. +12 −1 src/core/symbology/qgssymbollayerutils.cpp
  7. +55 −0 src/gui/symbology/qgssymbolslistwidget.cpp
  8. +5 −0 src/gui/symbology/qgssymbolslistwidget.h
  9. +32 −28 src/ui/symbollayer/widget_symbolslist.ui
  10. +15 −0 tests/src/core/testqgscentroidfillsymbol.cpp
  11. +20 −0 tests/src/core/testqgsellipsemarker.cpp
  12. +16 −0 tests/src/core/testqgsfilledmarker.cpp
  13. +21 −0 tests/src/core/testqgsfontmarker.cpp
  14. +12 −0 tests/src/core/testqgsgradients.cpp
  15. +10 −0 tests/src/core/testqgsrastermarker.cpp
  16. +19 −0 tests/src/core/testqgssimplemarker.cpp
  17. +21 −0 tests/src/core/testqgssvgmarker.cpp
  18. +37 −1 tests/src/python/test_qgsarrowsymbollayer.py
  19. +41 −0 tests/src/python/test_qgshashlinesymbollayer.py
  20. +43 −0 tests/src/python/test_qgsmarkerlinesymbollayer.py
  21. +43 −1 tests/src/python/test_qgsrandommarkersymbollayer.py
  22. +37 −1 tests/src/python/test_qgssimplefillsymbollayer.py
  23. +30 −1 tests/src/python/test_qgssimplelinesymbollayer.py
  24. BIN tests/testdata/control_images/symbol_arrow/expected_arrow_ddopacity/expected_arrow_ddopacity.png
  25. BIN ...ol_centroidfill/expected_symbol_centroidfill_ddopacity/expected_symbol_centroidfill_ddopacity.png
  26. BIN ...images/symbol_ellipsemarker/expected_ellipsemarker_ddopacity/expected_ellipsemarker_ddopacity.png
  27. BIN ...ol_images/symbol_filledmarker/expected_filledmarker_ddopacity/expected_filledmarker_ddopacity.png
  28. BIN .../control_images/symbol_fontmarker/expected_fontmarker_ddopacity/expected_fontmarker_ddopacity.png
  29. BIN ...stdata/control_images/symbol_gradient/expected_gradient_ddopacity/expected_gradient_ddopacity.png
  30. BIN ...stdata/control_images/symbol_hashline/expected_hashline_ddopacity/expected_hashline_ddopacity.png
  31. BIN .../control_images/symbol_markerline/expected_markerline_ddopacity/expected_markerline_ddopacity.png
  32. BIN ...mages/symbol_randommarkerfill/expected_randommarker_ddopacity/expected_randommarker_ddopacity.png
  33. BIN ...ol_images/symbol_rastermarker/expected_rastermarker_ddopacity/expected_rastermarker_ddopacity.png
  34. BIN .../control_images/symbol_simplefill/expected_simplefill_ddopacity/expected_simplefill_ddopacity.png
  35. BIN .../control_images/symbol_simpleline/expected_simpleline_ddopacity/expected_simpleline_ddopacity.png
  36. BIN ...ol_images/symbol_simplemarker/expected_simplemarker_ddopacity/expected_simplemarker_ddopacity.png
  37. BIN ...ata/control_images/symbol_svgmarker/expected_svgmarker_ddopacity/expected_svgmarker_ddopacity.png
@@ -55,6 +55,18 @@ Abstract base class for all rendered symbols.
typedef QFlags<QgsSymbol::RenderHint> RenderHints;


enum Property
{
PropertyOpacity,
};

static const QgsPropertiesDefinition &propertyDefinitions();
%Docstring
Returns the symbol property definitions.

.. versionadded:: 3.18
%End

virtual ~QgsSymbol();

static QgsSymbol *defaultSymbol( QgsWkbTypes::GeometryType geomType ) /Factory/;
@@ -495,6 +507,41 @@ direction.
Returns a list of attributes required to render this feature.
This should include any attributes required by the symbology including
the ones required by expressions.
%End

void setDataDefinedProperty( Property key, const QgsProperty &property );
%Docstring
Sets a data defined property for the symbol. Any existing property with the same key
will be overwritten.

.. seealso:: :py:func:`dataDefinedProperties`

.. seealso:: Property

.. versionadded:: 3.18
%End

QgsPropertyCollection &dataDefinedProperties();
%Docstring
Returns a reference to the symbol's property collection, used for data defined overrides.

.. seealso:: :py:func:`setDataDefinedProperties`

.. seealso:: Property

.. versionadded:: 3.18
%End


void setDataDefinedProperties( const QgsPropertyCollection &collection );
%Docstring
Sets the symbol's property collection, used for data defined overrides.

:param collection: property collection. Existing properties will be replaced.

.. seealso:: :py:func:`dataDefinedProperties`

.. versionadded:: 3.18
%End

bool hasDataDefinedProperties() const;
@@ -503,7 +503,7 @@ Sets the symbol layer's property collection, used for data defined overrides.

:param collection: property collection. Existing properties will be replaced.

.. seealso:: :py:func:`properties`
.. seealso:: :py:func:`dataDefinedProperties`

.. versionadded:: 3.0
%End
@@ -52,6 +52,8 @@
#include "qgsrenderedfeaturehandlerinterface.h"
#include "qgslegendpatchshape.h"

QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;

inline
QgsProperty rotateWholeSymbol( double additionalRotation, const QgsProperty &property )
{
@@ -248,6 +250,12 @@ void QgsSymbol::_getPolygon( QPolygonF &pts, QVector<QPolygonF> &holes, QgsRende
}
}

const QgsPropertiesDefinition &QgsSymbol::propertyDefinitions()
{
QgsSymbol::initPropertyDefinitions();
return sPropertyDefinitions;
}

QgsSymbol::~QgsSymbol()
{
// delete all symbol layers (we own them, so it's okay)
@@ -465,6 +473,8 @@ void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields
std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
mSymbolRenderContext->setExpressionContextScope( scope.release() );

mDataDefinedProperties.prepare( context.expressionContext() );

const auto constMLayers = mLayers;
for ( QgsSymbolLayer *layer : constMLayers )
{
@@ -535,7 +545,10 @@ void QgsSymbol::drawPreviewIcon( QPainter *painter, QSize size, QgsRenderContext

const bool prevForceVector = context->forceVectorOutput();
context->setForceVectorOutput( true );
QgsSymbolRenderContext symbolContext( *context, QgsUnitTypes::RenderUnknownUnit, mOpacity, false, mRenderHints, nullptr );

const double opacity = expressionContext ? dataDefinedProperties().valueAsDouble( QgsSymbol::PropertyOpacity, *expressionContext, mOpacity ) : mOpacity;

QgsSymbolRenderContext symbolContext( *context, QgsUnitTypes::RenderUnknownUnit, opacity, false, mRenderHints, nullptr );
symbolContext.setSelected( selected );
symbolContext.setOriginalGeometryType( mType == Fill ? QgsWkbTypes::PolygonGeometry : QgsWkbTypes::UnknownGeometry );
if ( patchShape )
@@ -756,7 +769,10 @@ void QgsSymbol::renderUsingLayer( QgsSymbolLayer *layer, QgsSymbolRenderContext

QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
{
QSet<QString> attributes;
// calling referencedFields() with ignoreContext=true because in our expression context
// we do not have valid QgsFields yet - because of that the field names from expressions
// wouldn't get reported
QSet<QString> attributes = mDataDefinedProperties.referencedFields( context.expressionContext(), true );
QgsSymbolLayerList::const_iterator sIt = mLayers.constBegin();
for ( ; sIt != mLayers.constEnd(); ++sIt )
{
@@ -768,8 +784,16 @@ QSet<QString> QgsSymbol::usedAttributes( const QgsRenderContext &context ) const
return attributes;
}

void QgsSymbol::setDataDefinedProperty( QgsSymbol::Property key, const QgsProperty &property )
{
mDataDefinedProperties.setProperty( key, property );
}

bool QgsSymbol::hasDataDefinedProperties() const
{
if ( mDataDefinedProperties.hasActiveProperties() )
return true;

const auto constMLayers = mLayers;
for ( QgsSymbolLayer *layer : constMLayers )
{
@@ -1341,6 +1365,19 @@ void QgsSymbol::renderVertexMarker( QPointF pt, QgsRenderContext &context, int c
QgsSymbolLayerUtils::drawVertexMarker( pt.x(), pt.y(), *context.painter(), static_cast< QgsSymbolLayerUtils::VertexMarkerType >( currentVertexMarkerType ), markerSize );
}

void QgsSymbol::initPropertyDefinitions()
{
if ( !sPropertyDefinitions.isEmpty() )
return;

QString origin = QStringLiteral( "symbol" );

sPropertyDefinitions = QgsPropertiesDefinition
{
{ QgsSymbol::PropertyOpacity, QgsPropertyDefinition( "alpha", QObject::tr( "Opacity" ), QgsPropertyDefinition::Opacity, origin )},
};
}

void QgsSymbol::startFeatureRender( const QgsFeature &feature, QgsRenderContext &context, const int layer )
{
if ( layer != -1 )
@@ -1875,7 +1912,9 @@ void QgsMarkerSymbol::renderPointUsingLayer( QgsMarkerSymbolLayer *layer, QPoint

void QgsMarkerSymbol::renderPoint( QPointF point, const QgsFeature *f, QgsRenderContext &context, int layerIdx, bool selected )
{
QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, selected, mRenderHints, f );
const double opacity = dataDefinedProperties().valueAsDouble( QgsSymbol::PropertyOpacity, context.expressionContext(), mOpacity * 100 ) * 0.01;

QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, opacity, selected, mRenderHints, f );
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );

@@ -1943,6 +1982,7 @@ QgsMarkerSymbol *QgsMarkerSymbol::clone() const
Q_NOWARN_DEPRECATED_POP
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
cloneSymbol->setForceRHR( mForceRHR );
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
return cloneSymbol;
}

@@ -2124,9 +2164,11 @@ QgsProperty QgsLineSymbol::dataDefinedWidth() const

void QgsLineSymbol::renderPolyline( const QPolygonF &points, const QgsFeature *f, QgsRenderContext &context, int layerIdx, bool selected )
{
const double opacity = dataDefinedProperties().valueAsDouble( QgsSymbol::PropertyOpacity, context.expressionContext(), mOpacity * 100 ) * 0.01;

//save old painter
QPainter *renderPainter = context.painter();
QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, selected, mRenderHints, f );
QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, opacity, selected, mRenderHints, f );
symbolContext.setOriginalGeometryType( QgsWkbTypes::LineGeometry );
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );
@@ -2199,6 +2241,7 @@ QgsLineSymbol *QgsLineSymbol::clone() const
Q_NOWARN_DEPRECATED_POP
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
cloneSymbol->setForceRHR( mForceRHR );
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
return cloneSymbol;
}

@@ -2214,7 +2257,9 @@ QgsFillSymbol::QgsFillSymbol( const QgsSymbolLayerList &layers )

void QgsFillSymbol::renderPolygon( const QPolygonF &points, const QVector<QPolygonF> *rings, const QgsFeature *f, QgsRenderContext &context, int layerIdx, bool selected )
{
QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, selected, mRenderHints, f );
const double opacity = dataDefinedProperties().valueAsDouble( QgsSymbol::PropertyOpacity, context.expressionContext(), mOpacity * 100 ) * 0.01;

QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, opacity, selected, mRenderHints, f );
symbolContext.setOriginalGeometryType( QgsWkbTypes::PolygonGeometry );
symbolContext.setGeometryPartCount( symbolRenderContext()->geometryPartCount() );
symbolContext.setGeometryPartNum( symbolRenderContext()->geometryPartNum() );
@@ -2323,6 +2368,7 @@ QgsFillSymbol *QgsFillSymbol::clone() const
Q_NOWARN_DEPRECATED_POP
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
cloneSymbol->setForceRHR( mForceRHR );
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
return cloneSymbol;
}

@@ -25,6 +25,7 @@
#include "qgsrendercontext.h"
#include "qgsproperty.h"
#include "qgssymbollayerreference.h"
#include "qgspropertycollection.h"

class QColor;
class QImage;
@@ -107,6 +108,21 @@ class CORE_EXPORT QgsSymbol
};
Q_DECLARE_FLAGS( RenderHints, RenderHint )

/**
* Data definable properties.
* \since QGIS 3.18
*/
enum Property
{
PropertyOpacity, //!< Opacity
};

/**
* Returns the symbol property definitions.
* \since QGIS 3.18
*/
static const QgsPropertiesDefinition &propertyDefinitions();

virtual ~QgsSymbol();

/**
@@ -527,6 +543,38 @@ class CORE_EXPORT QgsSymbol
*/
QSet<QString> usedAttributes( const QgsRenderContext &context ) const;

/**
* Sets a data defined property for the symbol. Any existing property with the same key
* will be overwritten.
* \see dataDefinedProperties()
* \see Property
* \since QGIS 3.18
*/
void setDataDefinedProperty( Property key, const QgsProperty &property );

/**
* Returns a reference to the symbol's property collection, used for data defined overrides.
* \see setDataDefinedProperties()
* \see Property
* \since QGIS 3.18
*/
QgsPropertyCollection &dataDefinedProperties() { return mDataDefinedProperties; }

/**
* Returns a reference to the symbol's property collection, used for data defined overrides.
* \see setDataDefinedProperties()
* \since QGIS 3.18
*/
const QgsPropertyCollection &dataDefinedProperties() const { return mDataDefinedProperties; } SIP_SKIP

/**
* Sets the symbol's property collection, used for data defined overrides.
* \param collection property collection. Existing properties will be replaced.
* \see dataDefinedProperties()
* \since QGIS 3.18
*/
void setDataDefinedProperties( const QgsPropertyCollection &collection ) { mDataDefinedProperties = collection; }

/**
* Returns whether the symbol utilizes any data defined properties.
* \since QGIS 2.12
@@ -644,6 +692,11 @@ class CORE_EXPORT QgsSymbol
QgsSymbol( const QgsSymbol & );
#endif

static void initPropertyDefinitions();

//! Property definitions
static QgsPropertiesDefinition sPropertyDefinitions;

/**
* TRUE if render has already been started - guards against multiple calls to
* startRender() (usually a result of not cloning a shared symbol instance before rendering).
@@ -653,6 +706,8 @@ class CORE_EXPORT QgsSymbol
//! Initialized in startRender, destroyed in stopRender
std::unique_ptr< QgsSymbolRenderContext > mSymbolRenderContext;

QgsPropertyCollection mDataDefinedProperties;

/**
* Called before symbol layers will be rendered for a particular \a feature.
*
@@ -495,7 +495,7 @@ class CORE_EXPORT QgsSymbolLayer
/**
* Sets the symbol layer's property collection, used for data defined overrides.
* \param collection property collection. Existing properties will be replaced.
* \see properties()
* \see dataDefinedProperties()
* \since QGIS 3.0
*/
void setDataDefinedProperties( const QgsPropertyCollection &collection ) { mDataDefinedProperties = collection; }
@@ -1048,7 +1048,7 @@ QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const Qg
while ( !layerNode.isNull() )
{
QDomElement e = layerNode.toElement();
if ( !e.isNull() )
if ( !e.isNull() && e.tagName() != QLatin1String( "data_defined_properties" ) )
{
if ( e.tagName() != QLatin1String( "layer" ) )
{
@@ -1115,6 +1115,13 @@ QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const Qg
symbol->setOpacity( element.attribute( QStringLiteral( "alpha" ), QStringLiteral( "1.0" ) ).toDouble() );
symbol->setClipFeaturesToExtent( element.attribute( QStringLiteral( "clip_to_extent" ), QStringLiteral( "1" ) ).toInt() );
symbol->setForceRHR( element.attribute( QStringLiteral( "force_rhr" ), QStringLiteral( "0" ) ).toInt() );

QDomElement ddProps = element.firstChildElement( QStringLiteral( "data_defined_properties" ) );
if ( !ddProps.isNull() )
{
symbol->dataDefinedProperties().readXml( ddProps, QgsSymbol::propertyDefinitions() );
}

return symbol;
}

@@ -1190,6 +1197,10 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbo
symEl.setAttribute( QStringLiteral( "force_rhr" ), symbol->forceRHR() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
//QgsDebugMsg( "num layers " + QString::number( symbol->symbolLayerCount() ) );

QDomElement ddProps = doc.createElement( QStringLiteral( "data_defined_properties" ) );
symbol->dataDefinedProperties().writeXml( ddProps, QgsSymbol::propertyDefinitions() );
symEl.appendChild( ddProps );

for ( int i = 0; i < symbol->symbolLayerCount(); i++ )
{
const QgsSymbolLayer *layer = symbol->symbolLayer( i );

0 comments on commit bf8075c

Please sign in to comment.
You can’t perform that action at this time.