Skip to content

Commit fac7887

Browse files
committed
[FEATURE] Annotations can be styled using fill symbol styles (fix #10552)
This changes the rendering of annotation frames to use QGIS' symbology engine, which means that all the existing fill styles can now be used to style annotation frames. Also paint effects & data defined symbol parameters. Whee!
1 parent a94ca70 commit fac7887

File tree

18 files changed

+182
-235
lines changed

18 files changed

+182
-235
lines changed

python/core/annotations/qgsannotation.sip

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,8 @@ class QgsAnnotation : QObject
3131
void setContentsMargin( const QgsMargins& margins );
3232
QgsMargins contentsMargin() const;
3333

34-
void setFrameBorderWidth( double width );
35-
double frameBorderWidth() const;
36-
37-
void setFrameColor( const QColor& color );
38-
QColor frameColor() const;
39-
40-
void setFrameBackgroundColor( const QColor& color );
41-
QColor frameBackgroundColor() const;
34+
void setFillSymbol( QgsFillSymbol* symbol /Transfer/ );
35+
QgsFillSymbol* fillSymbol() const;
4236

4337
void render( QgsRenderContext& context ) const;
4438

src/app/qgsannotationwidget.cpp

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,6 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem* item, QWid
4646
{
4747
mMapPositionFixedCheckBox->setCheckState( Qt::Unchecked );
4848
}
49-
mFrameWidthSpinBox->setValue( annotation->frameBorderWidth() );
50-
mFrameColorButton->setColor( annotation->frameColor() );
51-
mFrameColorButton->setColorDialogTitle( tr( "Select frame color" ) );
52-
mFrameColorButton->setAllowAlpha( true );
53-
mFrameColorButton->setContext( QStringLiteral( "symbology" ) );
54-
mFrameColorButton->setNoColorString( tr( "Transparent frame" ) );
55-
mFrameColorButton->setShowNoColor( true );
56-
mBackgroundColorButton->setColor( annotation->frameBackgroundColor() );
57-
mBackgroundColorButton->setColorDialogTitle( tr( "Select background color" ) );
58-
mBackgroundColorButton->setAllowAlpha( true );
59-
mBackgroundColorButton->setContext( QStringLiteral( "symbology" ) );
60-
mBackgroundColorButton->setNoColorString( tr( "Transparent" ) );
61-
mBackgroundColorButton->setShowNoColor( true );
6249

6350
whileBlocking( mSpinTopMargin )->setValue( annotation->contentsMargin().top() );
6451
whileBlocking( mSpinLeftMargin )->setValue( annotation->contentsMargin().left() );
@@ -67,14 +54,18 @@ QgsAnnotationWidget::QgsAnnotationWidget( QgsMapCanvasAnnotationItem* item, QWid
6754

6855
mLayerComboBox->setLayer( annotation->mapLayer() );
6956

70-
connect( mBackgroundColorButton, &QgsColorButton::colorChanged, this, &QgsAnnotationWidget::backgroundColorChanged );
71-
7257
const QgsMarkerSymbol* symbol = annotation->markerSymbol();
7358
if ( symbol )
7459
{
7560
mMarkerSymbol.reset( symbol->clone() );
7661
updateCenterIcon();
7762
}
63+
const QgsFillSymbol* fill = annotation->fillSymbol();
64+
if ( fill )
65+
{
66+
mFillSymbol.reset( fill->clone() );
67+
updateFillIcon();
68+
}
7869

7970
blockAllSignals( false );
8071
}
@@ -92,9 +83,7 @@ void QgsAnnotationWidget::apply()
9283
if ( annotation )
9384
{
9485
annotation->setHasFixedMapPosition( mMapPositionFixedCheckBox->checkState() == Qt::Checked );
95-
annotation->setFrameBorderWidth( mFrameWidthSpinBox->value() );
96-
annotation->setFrameColor( mFrameColorButton->color() );
97-
annotation->setFrameBackgroundColor( mBackgroundColorButton->color() );
86+
annotation->setFillSymbol( mFillSymbol->clone() );
9887
annotation->setMarkerSymbol( mMarkerSymbol->clone() );
9988
annotation->setMapLayer( mLayerComboBox->currentLayer() );
10089
annotation->setContentsMargin( QgsMargins( mSpinLeftMargin->value(),
@@ -110,8 +99,6 @@ void QgsAnnotationWidget::blockAllSignals( bool block )
11099
{
111100
mMapPositionFixedCheckBox->blockSignals( block );
112101
mMapMarkerButton->blockSignals( block );
113-
mFrameWidthSpinBox->blockSignals( block );
114-
mFrameColorButton->blockSignals( block );
115102
mLayerComboBox->blockSignals( block );
116103
}
117104

@@ -134,6 +121,26 @@ void QgsAnnotationWidget::on_mMapMarkerButton_clicked()
134121
}
135122
}
136123

124+
void QgsAnnotationWidget::on_mFrameStyleButton_clicked()
125+
{
126+
if ( !mFillSymbol )
127+
{
128+
return;
129+
}
130+
QgsFillSymbol* fillSymbol = mFillSymbol->clone();
131+
QgsSymbolSelectorDialog dlg( fillSymbol, QgsStyle::defaultStyle(), nullptr, this );
132+
if ( dlg.exec() == QDialog::Rejected )
133+
{
134+
delete fillSymbol;
135+
}
136+
else
137+
{
138+
mFillSymbol.reset( fillSymbol );
139+
updateFillIcon();
140+
backgroundColorChanged( fillSymbol->color() );
141+
}
142+
}
143+
137144
void QgsAnnotationWidget::updateCenterIcon()
138145
{
139146
if ( !mMarkerSymbol )
@@ -144,3 +151,13 @@ void QgsAnnotationWidget::updateCenterIcon()
144151
mMapMarkerButton->setIcon( icon );
145152
}
146153

154+
void QgsAnnotationWidget::updateFillIcon()
155+
{
156+
if ( !mFillSymbol )
157+
{
158+
return;
159+
}
160+
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( mFillSymbol.data(), mFrameStyleButton->iconSize() );
161+
mFrameStyleButton->setIcon( icon );
162+
}
163+

src/app/qgsannotationwidget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
class QgsMapCanvasAnnotationItem;
2525
class QgsMarkerSymbol;
26+
class QgsFillSymbol;
2627

2728
/** A configuration widget to configure the annotation item properties. Usually embedded by QgsAnnotation
2829
subclass configuration dialogs*/
@@ -42,13 +43,16 @@ class APP_EXPORT QgsAnnotationWidget: public QWidget, private Ui::QgsAnnotationW
4243

4344
private slots:
4445
void on_mMapMarkerButton_clicked();
46+
void on_mFrameStyleButton_clicked();
4547

4648
private:
4749
QgsMapCanvasAnnotationItem* mItem = nullptr;
4850
QScopedPointer< QgsMarkerSymbol > mMarkerSymbol;
51+
QScopedPointer< QgsFillSymbol > mFillSymbol;
4952

5053
void blockAllSignals( bool block );
5154
void updateCenterIcon();
55+
void updateFillIcon();
5256
};
5357

5458
#endif // QGSANNOTATIONWIDGET_H

src/app/qgstextannotationdialog.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ QgsTextAnnotationDialog::QgsTextAnnotationDialog( QgsMapCanvasAnnotationItem* it
6060

6161
void QgsTextAnnotationDialog::showEvent( QShowEvent* )
6262
{
63-
backgroundColorChanged( mItem && mItem->annotation() ? mItem->annotation()->frameBackgroundColor() : Qt::white );
63+
backgroundColorChanged( mItem && mItem->annotation() && mItem->annotation()->fillSymbol() ? mItem->annotation()->fillSymbol()->color() : Qt::white );
6464
}
6565

6666
void QgsTextAnnotationDialog::on_mButtonBox_clicked( QAbstractButton *button )

src/core/annotations/qgsannotation.cpp

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,14 @@ QgsAnnotation::QgsAnnotation( QObject* parent )
2626
: QObject( parent )
2727
, mMarkerSymbol( new QgsMarkerSymbol() )
2828
{
29-
29+
QgsStringMap props;
30+
props.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
31+
props.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
32+
props.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
33+
props.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
34+
props.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
35+
props.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
36+
mFillSymbol.reset( QgsFillSymbol::createSimple( props ) );
3037
}
3138

3239
void QgsAnnotation::setVisible( bool visible )
@@ -89,21 +96,9 @@ void QgsAnnotation::setContentsMargin( const QgsMargins& margins )
8996
emit appearanceChanged();
9097
}
9198

92-
void QgsAnnotation::setFrameBorderWidth( double width )
99+
void QgsAnnotation::setFillSymbol( QgsFillSymbol* symbol )
93100
{
94-
mFrameBorderWidth = width;
95-
emit appearanceChanged();
96-
}
97-
98-
void QgsAnnotation::setFrameColor( const QColor& c )
99-
{
100-
mFrameColor = c;
101-
emit appearanceChanged();
102-
}
103-
104-
void QgsAnnotation::setFrameBackgroundColor( const QColor& c )
105-
{
106-
mFrameBackgroundColor = c;
101+
mFillSymbol.reset( symbol );
107102
emit appearanceChanged();
108103
}
109104

@@ -263,17 +258,13 @@ QPointF QgsAnnotation::pointOnLineWithDistance( QPointF startPoint, QPointF dire
263258

264259
void QgsAnnotation::drawFrame( QgsRenderContext& context ) const
265260
{
266-
QPen framePen( mFrameColor );
267-
framePen.setWidthF( context.convertToPainterUnits( mFrameBorderWidth, QgsUnitTypes::RenderPixels ) );
268-
269-
QPainter* p = context.painter();
261+
if ( !mFillSymbol )
262+
return;
270263

271-
p->setPen( framePen );
272-
QBrush frameBrush( mFrameBackgroundColor );
273-
p->setBrush( frameBrush );
274-
p->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
264+
context.painter()->setRenderHint( QPainter::Antialiasing, context.flags() & QgsRenderContext::Antialiasing );
275265

276266
QPolygonF poly;
267+
QList<QPolygonF> rings; //empty list
277268
for ( int i = 0; i < 4; ++i )
278269
{
279270
QLineF currentSegment = segment( i );
@@ -286,7 +277,10 @@ void QgsAnnotation::drawFrame( QgsRenderContext& context ) const
286277
}
287278
poly << currentSegment.p2();
288279
}
289-
p->drawPolygon( poly );
280+
281+
mFillSymbol->startRender( context );
282+
mFillSymbol->renderPolygon( poly, &rings, nullptr, context );
283+
mFillSymbol->stopRender( context );
290284
}
291285

292286
void QgsAnnotation::drawMarkerSymbol( QgsRenderContext& context ) const
@@ -322,12 +316,7 @@ void QgsAnnotation::_writeXml( QDomElement& itemElem, QDomDocument& doc ) const
322316
annotationElem.setAttribute( QStringLiteral( "frameHeight" ), qgsDoubleToString( mFrameSize.height() ) );
323317
annotationElem.setAttribute( QStringLiteral( "canvasPosX" ), qgsDoubleToString( mRelativePosition.x() ) );
324318
annotationElem.setAttribute( QStringLiteral( "canvasPosY" ), qgsDoubleToString( mRelativePosition.y() ) );
325-
annotationElem.setAttribute( QStringLiteral( "frameBorderWidth" ), qgsDoubleToString( mFrameBorderWidth ) );
326319
annotationElem.setAttribute( QStringLiteral( "contentsMargin" ), mContentsMargins.toString() );
327-
annotationElem.setAttribute( QStringLiteral( "frameColor" ), mFrameColor.name() );
328-
annotationElem.setAttribute( QStringLiteral( "frameColorAlpha" ), mFrameColor.alpha() );
329-
annotationElem.setAttribute( QStringLiteral( "frameBackgroundColor" ), mFrameBackgroundColor.name() );
330-
annotationElem.setAttribute( QStringLiteral( "frameBackgroundColorAlpha" ), mFrameBackgroundColor.alpha() );
331320
annotationElem.setAttribute( QStringLiteral( "visible" ), isVisible() );
332321
if ( mMapLayer )
333322
{
@@ -341,6 +330,16 @@ void QgsAnnotation::_writeXml( QDomElement& itemElem, QDomDocument& doc ) const
341330
annotationElem.appendChild( symbolElem );
342331
}
343332
}
333+
if ( mFillSymbol )
334+
{
335+
QDomElement fillElem = doc.createElement( QStringLiteral( "fillSymbol" ) );
336+
QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "fill symbol" ), mFillSymbol.data(), doc );
337+
if ( !symbolElem.isNull() )
338+
{
339+
fillElem.appendChild( symbolElem );
340+
annotationElem.appendChild( fillElem );
341+
}
342+
}
344343
itemElem.appendChild( annotationElem );
345344
}
346345

@@ -365,12 +364,7 @@ void QgsAnnotation::_readXml( const QDomElement& annotationElem, const QDomDocum
365364
mMapPositionCrs = QgsCoordinateReferenceSystem();
366365
}
367366

368-
mFrameBorderWidth = annotationElem.attribute( QStringLiteral( "frameBorderWidth" ), QStringLiteral( "0.5" ) ).toDouble();
369367
mContentsMargins = QgsMargins::fromString( annotationElem.attribute( QStringLiteral( "contentsMargin" ) ) );
370-
mFrameColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameColor" ), QStringLiteral( "#000000" ) ) );
371-
mFrameColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameColorAlpha" ), QStringLiteral( "255" ) ).toInt() );
372-
mFrameBackgroundColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameBackgroundColor" ) ) );
373-
mFrameBackgroundColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameBackgroundColorAlpha" ), QStringLiteral( "255" ) ).toInt() );
374368
mFrameSize.setWidth( annotationElem.attribute( QStringLiteral( "frameWidth" ), QStringLiteral( "50" ) ).toDouble() );
375369
mFrameSize.setHeight( annotationElem.attribute( QStringLiteral( "frameHeight" ), QStringLiteral( "50" ) ).toDouble() );
376370
mOffsetFromReferencePoint.setX( annotationElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble() );
@@ -393,6 +387,41 @@ void QgsAnnotation::_readXml( const QDomElement& annotationElem, const QDomDocum
393387
}
394388
}
395389

390+
mFillSymbol.reset( nullptr );
391+
QDomElement fillElem = annotationElem.firstChildElement( QStringLiteral( "fillSymbol" ) );
392+
if ( !fillElem.isNull() )
393+
{
394+
QDomElement symbolElem = fillElem.firstChildElement( QStringLiteral( "symbol" ) );
395+
if ( !symbolElem.isNull() )
396+
{
397+
QgsFillSymbol* symbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem );
398+
if ( symbol )
399+
{
400+
mFillSymbol.reset( symbol );
401+
}
402+
}
403+
}
404+
if ( !mFillSymbol )
405+
{
406+
QColor frameColor;
407+
frameColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameColor" ), QStringLiteral( "#000000" ) ) );
408+
frameColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameColorAlpha" ), QStringLiteral( "255" ) ).toInt() );
409+
QColor frameBackgroundColor;
410+
frameBackgroundColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameBackgroundColor" ) ) );
411+
frameBackgroundColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameBackgroundColorAlpha" ), QStringLiteral( "255" ) ).toInt() );
412+
double frameBorderWidth = annotationElem.attribute( QStringLiteral( "frameBorderWidth" ), QStringLiteral( "0.5" ) ).toDouble();
413+
// need to roughly convert border width from pixels to mm - just assume 96 dpi
414+
frameBorderWidth = frameBorderWidth * 25.4 / 96.0;
415+
QgsStringMap props;
416+
props.insert( QStringLiteral( "color" ), frameBackgroundColor.name() );
417+
props.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
418+
props.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
419+
props.insert( QStringLiteral( "color_border" ), frameColor.name() );
420+
props.insert( QStringLiteral( "width_border" ), QString::number( frameBorderWidth ) );
421+
props.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
422+
mFillSymbol.reset( QgsFillSymbol::createSimple( props ) );
423+
}
424+
396425
updateBalloon();
397426
emit mapLayerChanged();
398427
}

src/core/annotations/qgsannotation.h

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -172,40 +172,17 @@ class CORE_EXPORT QgsAnnotation : public QObject
172172
QgsMargins contentsMargin() const { return mContentsMargins; }
173173

174174
/**
175-
* Sets the annotation's frame's border width (in pixels).
176-
* @see frameBorderWidth()
177-
*/
178-
void setFrameBorderWidth( double width );
179-
180-
/**
181-
* Returns the annotation's frame's border width (in pixels).
182-
* @see setFrameBorderWidth
183-
*/
184-
double frameBorderWidth() const { return mFrameBorderWidth; }
185-
186-
/**
187-
* Sets the annotation's frame's border color.
188-
* @see frameColor()
189-
*/
190-
void setFrameColor( const QColor& color );
191-
192-
/**
193-
* Returns the annotation's frame's border color.
194-
* @see setFrameColor()
195-
*/
196-
QColor frameColor() const { return mFrameColor; }
197-
198-
/**
199-
* Sets the annotation's frame's background color.
200-
* @see frameBackgroundColor()
175+
* Sets the fill symbol used for rendering the annotation frame. Ownership
176+
* of the symbol is transferred to the annotation.
177+
* @see fillSymbol()
201178
*/
202-
void setFrameBackgroundColor( const QColor& color );
179+
void setFillSymbol( QgsFillSymbol* symbol );
203180

204181
/**
205-
* Returns the annotation's frame's background color.
206-
* @see setFrameBackgroundColor()
182+
* Returns the symbol that is used for rendering the annotation frame.
183+
* @see setFillSymbol()
207184
*/
208-
QColor frameBackgroundColor() const { return mFrameBackgroundColor; }
185+
QgsFillSymbol* fillSymbol() const { return mFillSymbol.data(); }
209186

210187
/**
211188
* Renders the annotation to a target render context.
@@ -354,14 +331,8 @@ class CORE_EXPORT QgsAnnotation : public QObject
354331

355332
QgsMargins mContentsMargins;
356333

357-
//! Width of the frame
358-
double mFrameBorderWidth = 1.0;
359-
360-
//! Frame / balloon color
361-
QColor mFrameColor = QColor( 0, 0, 0 );
362-
363-
//! Frame background color
364-
QColor mFrameBackgroundColor = QColor( 255, 255, 255 );
334+
//! Fill symbol used for drawing annotation
335+
QScopedPointer<QgsFillSymbol> mFillSymbol;
365336

366337
//! Segment number where the connection to the map point is attached. -1 if no balloon needed (e.g. if point is contained in frame)
367338
int mBalloonSegment = -1;

0 commit comments

Comments
 (0)