Skip to content

Commit

Permalink
[feature] Allow end point marker symbols for balloon callout style
Browse files Browse the repository at this point in the history
Allows rendering a marker symbol below the endpoint of the balloon
callout. Designed to allow balloon callouts to reproduce the same
visual appearance as the older annotation framework items.
  • Loading branch information
nyalldawson committed Aug 5, 2024
1 parent 00412a4 commit e26f182
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 116 deletions.
29 changes: 29 additions & 0 deletions python/PyQt6/core/auto_generated/callouts/qgscallout.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,35 @@ Sets the fill ``symbol`` used to render the callout. Ownership of ``symbol`` is
transferred to the callout.

.. seealso:: :py:func:`fillSymbol`
%End

QgsMarkerSymbol *markerSymbol();
%Docstring
Returns the marker symbol used to render the callout endpoint.

May be ``None``, if no endpoint marker will be used.

The marker will always be rendered below the fill symbol for the callout.

Ownership is not transferred.

.. seealso:: :py:func:`setMarkerSymbol`

.. versionadded:: 3.40
%End

void setMarkerSymbol( QgsMarkerSymbol *symbol /Transfer/ );
%Docstring
Sets the marker ``symbol`` used to render the callout endpoint. Ownership of ``symbol`` is
transferred to the callout.

Set to ``None`` to disable the endpoint marker.

The marker will always be rendered below the fill symbol for the callout.

.. seealso:: :py:func:`markerSymbol`

.. versionadded:: 3.40
%End

double offsetFromAnchor() const;
Expand Down
29 changes: 29 additions & 0 deletions python/core/auto_generated/callouts/qgscallout.sip.in
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,35 @@ Sets the fill ``symbol`` used to render the callout. Ownership of ``symbol`` is
transferred to the callout.

.. seealso:: :py:func:`fillSymbol`
%End

QgsMarkerSymbol *markerSymbol();
%Docstring
Returns the marker symbol used to render the callout endpoint.

May be ``None``, if no endpoint marker will be used.

The marker will always be rendered below the fill symbol for the callout.

Ownership is not transferred.

.. seealso:: :py:func:`setMarkerSymbol`

.. versionadded:: 3.40
%End

void setMarkerSymbol( QgsMarkerSymbol *symbol /Transfer/ );
%Docstring
Sets the marker ``symbol`` used to render the callout endpoint. Ownership of ``symbol`` is
transferred to the callout.

Set to ``None`` to disable the endpoint marker.

The marker will always be rendered below the fill symbol for the callout.

.. seealso:: :py:func:`markerSymbol`

.. versionadded:: 3.40
%End

double offsetFromAnchor() const;
Expand Down
58 changes: 51 additions & 7 deletions src/core/callouts/qgscallout.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#include "qgspainting.h"
#include "qgsfillsymbol.h"
#include "qgslinesymbol.h"
#include "qgsmarkersymbol.h"
#include "qgsunittypes.h"

#include <mutex>
Expand Down Expand Up @@ -1042,6 +1043,7 @@ QgsBalloonCallout::~QgsBalloonCallout() = default;
QgsBalloonCallout::QgsBalloonCallout( const QgsBalloonCallout &other )
: QgsCallout( other )
, mFillSymbol( other.mFillSymbol ? other.mFillSymbol->clone() : nullptr )
, mMarkerSymbol( other.mMarkerSymbol ? other.mMarkerSymbol->clone() : nullptr )
, mOffsetFromAnchorDistance( other.mOffsetFromAnchorDistance )
, mOffsetFromAnchorUnit( other.mOffsetFromAnchorUnit )
, mOffsetFromAnchorScale( other.mOffsetFromAnchorScale )
Expand Down Expand Up @@ -1083,6 +1085,11 @@ QVariantMap QgsBalloonCallout::properties( const QgsReadWriteContext &context )
props[ QStringLiteral( "fillSymbol" ) ] = QgsSymbolLayerUtils::symbolProperties( mFillSymbol.get() );
}

if ( mMarkerSymbol )
{
props[ QStringLiteral( "markerSymbol" ) ] = QgsSymbolLayerUtils::symbolProperties( mMarkerSymbol.get() );
}

props[ QStringLiteral( "offsetFromAnchor" ) ] = mOffsetFromAnchorDistance;
props[ QStringLiteral( "offsetFromAnchorUnit" ) ] = QgsUnitTypes::encodeUnit( mOffsetFromAnchorUnit );
props[ QStringLiteral( "offsetFromAnchorMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mOffsetFromAnchorScale );
Expand All @@ -1105,13 +1112,25 @@ void QgsBalloonCallout::readProperties( const QVariantMap &props, const QgsReadW
{
QgsCallout::readProperties( props, context );

const QString fillSymbolDef = props.value( QStringLiteral( "fillSymbol" ) ).toString();
QDomDocument doc( QStringLiteral( "symbol" ) );
doc.setContent( fillSymbolDef );
const QDomElement symbolElem = doc.firstChildElement( QStringLiteral( "symbol" ) );
std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( symbolElem, context ) );
if ( fillSymbol )
mFillSymbol = std::move( fillSymbol );
{
const QString fillSymbolDef = props.value( QStringLiteral( "fillSymbol" ) ).toString();
QDomDocument doc( QStringLiteral( "symbol" ) );
doc.setContent( fillSymbolDef );
const QDomElement symbolElem = doc.firstChildElement( QStringLiteral( "symbol" ) );
std::unique_ptr< QgsFillSymbol > fillSymbol( QgsSymbolLayerUtils::loadSymbol< QgsFillSymbol >( symbolElem, context ) );
if ( fillSymbol )
mFillSymbol = std::move( fillSymbol );
}

{
const QString markerSymbolDef = props.value( QStringLiteral( "markerSymbol" ) ).toString();
QDomDocument doc( QStringLiteral( "symbol" ) );
doc.setContent( markerSymbolDef );
const QDomElement symbolElem = doc.firstChildElement( QStringLiteral( "symbol" ) );
std::unique_ptr< QgsMarkerSymbol > markerSymbol( QgsSymbolLayerUtils::loadSymbol< QgsMarkerSymbol >( symbolElem, context ) );
if ( markerSymbol )
mMarkerSymbol = std::move( markerSymbol );
}

mOffsetFromAnchorDistance = props.value( QStringLiteral( "offsetFromAnchor" ), 0 ).toDouble();
mOffsetFromAnchorUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "offsetFromAnchorUnit" ) ).toString() );
Expand All @@ -1134,20 +1153,26 @@ void QgsBalloonCallout::startRender( QgsRenderContext &context )
QgsCallout::startRender( context );
if ( mFillSymbol )
mFillSymbol->startRender( context );
if ( mMarkerSymbol )
mMarkerSymbol->startRender( context );
}

void QgsBalloonCallout::stopRender( QgsRenderContext &context )
{
QgsCallout::stopRender( context );
if ( mFillSymbol )
mFillSymbol->stopRender( context );
if ( mMarkerSymbol )
mMarkerSymbol->stopRender( context );
}

QSet<QString> QgsBalloonCallout::referencedFields( const QgsRenderContext &context ) const
{
QSet<QString> fields = QgsCallout::referencedFields( context );
if ( mFillSymbol )
fields.unite( mFillSymbol->usedAttributes( context ) );
if ( mMarkerSymbol )
fields.unite( mMarkerSymbol->usedAttributes( context ) );
return fields;
}

Expand All @@ -1161,11 +1186,30 @@ void QgsBalloonCallout::setFillSymbol( QgsFillSymbol *symbol )
mFillSymbol.reset( symbol );
}

QgsMarkerSymbol *QgsBalloonCallout::markerSymbol()
{
return mMarkerSymbol.get();
}

void QgsBalloonCallout::setMarkerSymbol( QgsMarkerSymbol *symbol )
{
mMarkerSymbol.reset( symbol );
}

void QgsBalloonCallout::draw( QgsRenderContext &context, const QRectF &rect, const double, const QgsGeometry &anchor, QgsCalloutContext &calloutContext )
{
bool destinationIsPinned = false;
QgsGeometry line = calloutLineToPart( QgsGeometry::fromRect( rect ), anchor.constGet(), context, calloutContext, destinationIsPinned );

if ( mMarkerSymbol )
{
if ( const QgsLineString *ls = qgsgeometry_cast< const QgsLineString * >( line.constGet() ) )
{
QgsPoint anchorPoint = ls->endPoint();
mMarkerSymbol->renderPoint( anchorPoint.toQPointF(), nullptr, context );
}
}

double offsetFromAnchor = mOffsetFromAnchorDistance;
if ( dataDefinedProperties().isActive( QgsCallout::Property::OffsetFromAnchor ) )
{
Expand Down
29 changes: 29 additions & 0 deletions src/core/callouts/qgscallout.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <memory>

class QgsLineSymbol;
class QgsMarkerSymbol;
class QgsFillSymbol;
class QgsGeometry;
class QgsRenderContext;
Expand Down Expand Up @@ -919,6 +920,33 @@ class CORE_EXPORT QgsBalloonCallout : public QgsCallout
*/
void setFillSymbol( QgsFillSymbol *symbol SIP_TRANSFER );

/**
* Returns the marker symbol used to render the callout endpoint.
*
* May be NULLPTR, if no endpoint marker will be used.
*
* The marker will always be rendered below the fill symbol for the callout.
*
* Ownership is not transferred.
*
* \see setMarkerSymbol()
* \since QGIS 3.40
*/
QgsMarkerSymbol *markerSymbol();

/**
* Sets the marker \a symbol used to render the callout endpoint. Ownership of \a symbol is
* transferred to the callout.
*
* Set to NULLPTR to disable the endpoint marker.
*
* The marker will always be rendered below the fill symbol for the callout.
*
* \see markerSymbol()
* \since QGIS 3.40
*/
void setMarkerSymbol( QgsMarkerSymbol *symbol SIP_TRANSFER );

/**
* Returns the offset distance from the anchor point at which to start the line. Units are specified through offsetFromAnchorUnit().
* \see setOffsetFromAnchor()
Expand Down Expand Up @@ -1125,6 +1153,7 @@ class CORE_EXPORT QgsBalloonCallout : public QgsCallout
#endif

std::unique_ptr< QgsFillSymbol > mFillSymbol;
std::unique_ptr< QgsMarkerSymbol > mMarkerSymbol;

double mOffsetFromAnchorDistance = 0;
Qgis::RenderUnit mOffsetFromAnchorUnit = Qgis::RenderUnit::Millimeters;
Expand Down
19 changes: 19 additions & 0 deletions src/gui/callouts/qgscalloutwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "qgsauxiliarystorage.h"
#include "qgslinesymbol.h"
#include "qgsfillsymbol.h"
#include "qgsmarkersymbol.h"

QgsExpressionContext QgsCalloutWidget::createExpressionContext() const
{
Expand Down Expand Up @@ -565,7 +566,14 @@ QgsBalloonCalloutWidget::QgsBalloonCalloutWidget( QgsMapLayer *vl, QWidget *pare
mCalloutFillStyleButton->setDialogTitle( tr( "Balloon Symbol" ) );
mCalloutFillStyleButton->registerExpressionContextGenerator( this );

mMarkerSymbolButton->setSymbolType( Qgis::SymbolType::Marker );
mMarkerSymbolButton->setDialogTitle( tr( "Marker Symbol" ) );
mMarkerSymbolButton->registerExpressionContextGenerator( this );
mMarkerSymbolButton->setShowNull( true );
mMarkerSymbolButton->setToNull();

mCalloutFillStyleButton->setLayer( qobject_cast< QgsVectorLayer * >( vl ) );
mMarkerSymbolButton->setLayer( qobject_cast< QgsVectorLayer * >( vl ) );
mOffsetFromAnchorUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Millimeters << Qgis::RenderUnit::MetersInMapUnits << Qgis::RenderUnit::MapUnits << Qgis::RenderUnit::Pixels
<< Qgis::RenderUnit::Points << Qgis::RenderUnit::Inches );
mMarginUnitWidget->setUnits( QgsUnitTypes::RenderUnitList() << Qgis::RenderUnit::Millimeters << Qgis::RenderUnit::MetersInMapUnits << Qgis::RenderUnit::MapUnits << Qgis::RenderUnit::Pixels
Expand Down Expand Up @@ -593,6 +601,7 @@ QgsBalloonCalloutWidget::QgsBalloonCalloutWidget( QgsMapLayer *vl, QWidget *pare
connect( mAnchorPointComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, &QgsBalloonCalloutWidget::mAnchorPointComboBox_currentIndexChanged );

connect( mCalloutFillStyleButton, &QgsSymbolButton::changed, this, &QgsBalloonCalloutWidget::fillSymbolChanged );
connect( mMarkerSymbolButton, &QgsSymbolButton::changed, this, &QgsBalloonCalloutWidget::markerSymbolChanged );

connect( mSpinBottomMargin, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, [ = ]( double value )
{
Expand Down Expand Up @@ -691,6 +700,10 @@ void QgsBalloonCalloutWidget::setCallout( const QgsCallout *callout )
whileBlocking( mCornerRadiusSpin )->setValue( mCallout->cornerRadius() );

whileBlocking( mCalloutFillStyleButton )->setSymbol( mCallout->fillSymbol()->clone() );
if ( QgsMarkerSymbol *marker = mCallout->markerSymbol() )
whileBlocking( mMarkerSymbolButton )->setSymbol( marker->clone() );
else
whileBlocking( mMarkerSymbolButton )->setToNull();

whileBlocking( mAnchorPointComboBox )->setCurrentIndex( mAnchorPointComboBox->findData( static_cast< int >( callout->anchorPoint() ) ) );

Expand Down Expand Up @@ -742,6 +755,12 @@ void QgsBalloonCalloutWidget::fillSymbolChanged()
emit changed();
}

void QgsBalloonCalloutWidget::markerSymbolChanged()
{
mCallout->setMarkerSymbol( mMarkerSymbolButton->isNull() ? nullptr : mMarkerSymbolButton->clonedSymbol< QgsMarkerSymbol >() );
emit changed();
}

void QgsBalloonCalloutWidget::mAnchorPointComboBox_currentIndexChanged( int index )
{
mCallout->setAnchorPoint( static_cast<QgsCallout::AnchorPoint>( mAnchorPointComboBox->itemData( index ).toInt() ) );
Expand Down
1 change: 1 addition & 0 deletions src/gui/callouts/qgscalloutwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ class GUI_EXPORT QgsBalloonCalloutWidget : public QgsCalloutWidget, private Ui::
void offsetFromAnchorUnitWidgetChanged();
void offsetFromAnchorChanged();
void fillSymbolChanged();
void markerSymbolChanged();
void mAnchorPointComboBox_currentIndexChanged( int index );
void mCalloutBlendComboBox_currentIndexChanged( int index );

Expand Down
Loading

0 comments on commit e26f182

Please sign in to comment.