Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[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 bf8075c
Show file tree
Hide file tree
Showing 37 changed files with 624 additions and 40 deletions.
47 changes: 47 additions & 0 deletions python/core/auto_generated/symbology/qgssymbol.sip.in
Expand Up @@ -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/;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion python/core/auto_generated/symbology/qgssymbollayer.sip.in
Expand Up @@ -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
Expand Down
56 changes: 51 additions & 5 deletions src/core/symbology/qgssymbol.cpp
Expand Up @@ -52,6 +52,8 @@
#include "qgsrenderedfeaturehandlerinterface.h"
#include "qgslegendpatchshape.h"

QgsPropertiesDefinition QgsSymbol::sPropertyDefinitions;

inline
QgsProperty rotateWholeSymbol( double additionalRotation, const QgsProperty &property )
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 )
{
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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 )
{
Expand All @@ -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 )
{
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -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() );

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

Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -2199,6 +2241,7 @@ QgsLineSymbol *QgsLineSymbol::clone() const
Q_NOWARN_DEPRECATED_POP
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
cloneSymbol->setForceRHR( mForceRHR );
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
return cloneSymbol;
}

Expand All @@ -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() );
Expand Down Expand Up @@ -2323,6 +2368,7 @@ QgsFillSymbol *QgsFillSymbol::clone() const
Q_NOWARN_DEPRECATED_POP
cloneSymbol->setClipFeaturesToExtent( mClipFeaturesToExtent );
cloneSymbol->setForceRHR( mForceRHR );
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
return cloneSymbol;
}

Expand Down
55 changes: 55 additions & 0 deletions src/core/symbology/qgssymbol.h
Expand Up @@ -25,6 +25,7 @@
#include "qgsrendercontext.h"
#include "qgsproperty.h"
#include "qgssymbollayerreference.h"
#include "qgspropertycollection.h"

class QColor;
class QImage;
Expand Down Expand Up @@ -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();

/**
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand All @@ -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.
*
Expand Down
2 changes: 1 addition & 1 deletion src/core/symbology/qgssymbollayer.h
Expand Up @@ -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; }
Expand Down
13 changes: 12 additions & 1 deletion src/core/symbology/qgssymbollayerutils.cpp
Expand Up @@ -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" ) )
{
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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 );
Expand Down

0 comments on commit bf8075c

Please sign in to comment.