QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
qgsannotation.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsannotation.cpp
3 -----------------
4 begin : January 2017
5 copyright : (C) 2017 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgsannotation.h"
19
20#include "qgsfillsymbol.h"
21#include "qgsmaplayer.h"
22#include "qgsmarkersymbol.h"
23#include "qgspainting.h"
24#include "qgsproject.h"
25#include "qgsshapegenerator.h"
27#include "qgssymbol.h"
28#include "qgssymbollayerutils.h"
29
30#include <QPainter>
31#include <QPen>
32#include <QString>
33
34#include "moc_qgsannotation.cpp"
35
36using namespace Qt::StringLiterals;
37
39 : QObject( parent )
40 , mMarkerSymbol( new QgsMarkerSymbol() )
41{
42 QVariantMap props;
43 props.insert( u"color"_s, u"white"_s );
44 props.insert( u"style"_s, u"solid"_s );
45 props.insert( u"style_border"_s, u"solid"_s );
46 props.insert( u"color_border"_s, u"black"_s );
47 props.insert( u"width_border"_s, u"0.3"_s );
48 props.insert( u"joinstyle"_s, u"miter"_s );
49 mFillSymbol = QgsFillSymbol::createSimple( props );
50}
51
53
55{
56 if ( mVisible == visible )
57 return;
58
59 mVisible = visible;
60 emit appearanceChanged();
61}
62
64{
65 if ( mHasFixedMapPosition == fixed )
66 return;
67
68 mHasFixedMapPosition = fixed;
69 emit moved();
70}
71
73{
74 mMapPosition = position;
75 emit moved();
76}
77
79{
80 mMapPositionCrs = crs;
81 emit moved();
82}
83
84void QgsAnnotation::setRelativePosition( QPointF position )
85{
86 mRelativePosition = position;
87 emit moved();
88}
89
91{
92 // convert from offset in pixels at 96 dpi to mm
93 setFrameOffsetFromReferencePointMm( offset / 3.7795275 );
94}
95
97{
98 return mOffsetFromReferencePoint / 3.7795275;
99}
100
102{
103 mOffsetFromReferencePoint = offset;
104
105 emit moved();
106 emit appearanceChanged();
107}
108
110{
111 // convert from size in pixels at 96 dpi to mm
112 setFrameSizeMm( size / 3.7795275 );
113}
114
116{
117 return mFrameSize / 3.7795275;
118}
119
121{
122 const QSizeF frameSize = minimumFrameSize().expandedTo( size ); //don't allow frame sizes below minimum
123 mFrameSize = frameSize;
124 emit moved();
125 emit appearanceChanged();
126}
127
129{
130 mContentsMargins = margins;
131 emit appearanceChanged();
132}
133
135{
136 mFillSymbol.reset( symbol );
137 emit appearanceChanged();
138}
139
141{
142 return mFillSymbol.get();
143}
144
146{
147 QPainter *painter = context.painter();
148 if ( !painter || ( context.feedback() && context.feedback()->isCanceled() ) )
149 {
150 return;
151 }
152
153 const QgsScopedQPainterState painterState( context.painter() );
155
156 drawFrame( context );
157 if ( mHasFixedMapPosition )
158 {
159 drawMarkerSymbol( context );
160 }
161 if ( mHasFixedMapPosition )
162 {
163 painter->translate( context.convertToPainterUnits( mOffsetFromReferencePoint.x(), Qgis::RenderUnit::Millimeters ) + context.convertToPainterUnits( mContentsMargins.left(), Qgis::RenderUnit::Millimeters ),
164 context.convertToPainterUnits( mOffsetFromReferencePoint.y(), Qgis::RenderUnit::Millimeters ) + context.convertToPainterUnits( mContentsMargins.top(), Qgis::RenderUnit::Millimeters ) );
165 }
166 else
167 {
168 painter->translate( context.convertToPainterUnits( mContentsMargins.left(), Qgis::RenderUnit::Millimeters ),
169 context.convertToPainterUnits( mContentsMargins.top(), Qgis::RenderUnit::Millimeters ) );
170 }
171 const QSizeF size( context.convertToPainterUnits( mFrameSize.width(), Qgis::RenderUnit::Millimeters ) - context.convertToPainterUnits( mContentsMargins.left() + mContentsMargins.right(), Qgis::RenderUnit::Millimeters ),
172 context.convertToPainterUnits( mFrameSize.height(), Qgis::RenderUnit::Millimeters ) - context.convertToPainterUnits( mContentsMargins.top() + mContentsMargins.bottom(), Qgis::RenderUnit::Millimeters ) );
173
174 // scale back from painter dpi to 96 dpi --
175// double dotsPerMM = context.painter()->device()->logicalDpiX() / ( 25.4 * 3.78 );
176// context.painter()->scale( dotsPerMM, dotsPerMM );
177
178 renderAnnotation( context, size );
179}
180
182{
183 mMarkerSymbol.reset( symbol );
184 emit appearanceChanged();
185}
186
188{
189 mMapLayer = layer;
190 emit mapLayerChanged();
191}
192
194{
195 mFeature = feature;
196}
197
199{
200 // NOTE: if visitEnter returns false it means "don't visit the annotation", not "abort all further visitations"
201 if ( !visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Annotation, u"annotation"_s, tr( "Annotation" ) ) ) )
202 return true;
203
204 if ( mMarkerSymbol )
205 {
206 QgsStyleSymbolEntity entity( mMarkerSymbol.get() );
207 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, u"marker"_s, QObject::tr( "Marker" ) ) ) )
208 return false;
209 }
210
211 if ( mFillSymbol )
212 {
213 QgsStyleSymbolEntity entity( mFillSymbol.get() );
214 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, u"fill"_s, QObject::tr( "Fill" ) ) ) )
215 return false;
216 }
217
218 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Annotation, u"annotation"_s, tr( "Annotation" ) ) ) )
219 return false;
220
221 return true;
222}
223
225{
226 return QSizeF( 0, 0 );
227}
228
229void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
230{
231 if ( !mFillSymbol )
232 return;
233
234 auto scaleSize = [&context]( double size )->double
235 {
237 };
238
239 const QRectF frameRect( mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.x() ) : 0,
240 mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.y() ) : 0,
241 scaleSize( mFrameSize.width() ),
242 scaleSize( mFrameSize.height() ) );
243 const QgsPointXY origin = mHasFixedMapPosition ? QgsPointXY( 0, 0 ) : QgsPointXY( frameRect.center().x(), frameRect.center().y() );
244
245 const QPolygonF poly = QgsShapeGenerator::createBalloon( origin, frameRect, context.convertToPainterUnits( mSegmentPointWidthMm, Qgis::RenderUnit::Millimeters ) );
246
247 mFillSymbol->startRender( context );
248 const QVector<QPolygonF> rings; //empty list
249 mFillSymbol->renderPolygon( poly, &rings, nullptr, context );
250 mFillSymbol->stopRender( context );
251}
252
253void QgsAnnotation::drawMarkerSymbol( QgsRenderContext &context ) const
254{
255 if ( !context.painter() )
256 {
257 return;
258 }
259
260 if ( mMarkerSymbol )
261 {
262 mMarkerSymbol->startRender( context );
263 mMarkerSymbol->renderPoint( QPointF( 0, 0 ), nullptr, context );
264 mMarkerSymbol->stopRender( context );
265 }
266}
267
268void QgsAnnotation::_writeXml( QDomElement &itemElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
269{
270 if ( itemElem.isNull() )
271 {
272 return;
273 }
274 QDomElement annotationElem = doc.createElement( u"AnnotationItem"_s );
275 annotationElem.setAttribute( u"mapPositionFixed"_s, mHasFixedMapPosition );
276 annotationElem.setAttribute( u"mapPosX"_s, qgsDoubleToString( mMapPosition.x() ) );
277 annotationElem.setAttribute( u"mapPosY"_s, qgsDoubleToString( mMapPosition.y() ) );
278 if ( mMapPositionCrs.isValid() )
279 mMapPositionCrs.writeXml( annotationElem, doc );
280 annotationElem.setAttribute( u"offsetXMM"_s, qgsDoubleToString( mOffsetFromReferencePoint.x() ) );
281 annotationElem.setAttribute( u"offsetYMM"_s, qgsDoubleToString( mOffsetFromReferencePoint.y() ) );
282 annotationElem.setAttribute( u"frameWidthMM"_s, qgsDoubleToString( mFrameSize.width() ) );
283 annotationElem.setAttribute( u"frameHeightMM"_s, qgsDoubleToString( mFrameSize.height() ) );
284 annotationElem.setAttribute( u"canvasPosX"_s, qgsDoubleToString( mRelativePosition.x() ) );
285 annotationElem.setAttribute( u"canvasPosY"_s, qgsDoubleToString( mRelativePosition.y() ) );
286 annotationElem.setAttribute( u"contentsMargin"_s, mContentsMargins.toString() );
287 annotationElem.setAttribute( u"visible"_s, isVisible() );
288 if ( mMapLayer )
289 {
290 annotationElem.setAttribute( u"mapLayer"_s, mMapLayer->id() );
291 }
292 if ( mMarkerSymbol )
293 {
294 const QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( u"marker symbol"_s, mMarkerSymbol.get(), doc, context );
295 if ( !symbolElem.isNull() )
296 {
297 annotationElem.appendChild( symbolElem );
298 }
299 }
300 if ( mFillSymbol )
301 {
302 QDomElement fillElem = doc.createElement( u"fillSymbol"_s );
303 const QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( u"fill symbol"_s, mFillSymbol.get(), doc, context );
304 if ( !symbolElem.isNull() )
305 {
306 fillElem.appendChild( symbolElem );
307 annotationElem.appendChild( fillElem );
308 }
309 }
310 itemElem.appendChild( annotationElem );
311}
312
313void QgsAnnotation::_readXml( const QDomElement &annotationElem, const QgsReadWriteContext &context )
314{
315 if ( annotationElem.isNull() )
316 {
317 return;
318 }
319 QPointF pos;
320 pos.setX( annotationElem.attribute( u"canvasPosX"_s, u"0"_s ).toDouble() );
321 pos.setY( annotationElem.attribute( u"canvasPosY"_s, u"0"_s ).toDouble() );
322 if ( pos.x() >= 1 || pos.x() < 0 || pos.y() < 0 || pos.y() >= 1 )
323 mRelativePosition = QPointF();
324 else
325 mRelativePosition = pos;
326 QgsPointXY mapPos;
327 mapPos.setX( annotationElem.attribute( u"mapPosX"_s, u"0"_s ).toDouble() );
328 mapPos.setY( annotationElem.attribute( u"mapPosY"_s, u"0"_s ).toDouble() );
329 mMapPosition = mapPos;
330
331 if ( !mMapPositionCrs.readXml( annotationElem ) )
332 {
333 mMapPositionCrs = QgsCoordinateReferenceSystem();
334 }
335
336 mContentsMargins = QgsMargins::fromString( annotationElem.attribute( u"contentsMargin"_s ) );
337 const double dpiScale = 25.4 / QgsPainting::qtDefaultDpiX();
338 if ( annotationElem.hasAttribute( u"frameWidthMM"_s ) )
339 mFrameSize.setWidth( annotationElem.attribute( u"frameWidthMM"_s, u"5"_s ).toDouble() );
340 else
341 mFrameSize.setWidth( dpiScale * annotationElem.attribute( u"frameWidth"_s, u"50"_s ).toDouble() );
342 if ( annotationElem.hasAttribute( u"frameHeightMM"_s ) )
343 mFrameSize.setHeight( annotationElem.attribute( u"frameHeightMM"_s, u"3"_s ).toDouble() );
344 else
345 mFrameSize.setHeight( dpiScale * annotationElem.attribute( u"frameHeight"_s, u"50"_s ).toDouble() );
346
347 if ( annotationElem.hasAttribute( u"offsetXMM"_s ) )
348 mOffsetFromReferencePoint.setX( annotationElem.attribute( u"offsetXMM"_s, u"0"_s ).toDouble() );
349 else
350 mOffsetFromReferencePoint.setX( dpiScale * annotationElem.attribute( u"offsetX"_s, u"0"_s ).toDouble() );
351 if ( annotationElem.hasAttribute( u"offsetYMM"_s ) )
352 mOffsetFromReferencePoint.setY( annotationElem.attribute( u"offsetYMM"_s, u"0"_s ).toDouble() );
353 else
354 mOffsetFromReferencePoint.setY( dpiScale * annotationElem.attribute( u"offsetY"_s, u"0"_s ).toDouble() );
355
356 mHasFixedMapPosition = annotationElem.attribute( u"mapPositionFixed"_s, u"1"_s ).toInt();
357 mVisible = annotationElem.attribute( u"visible"_s, u"1"_s ).toInt();
358 if ( annotationElem.hasAttribute( u"mapLayer"_s ) )
359 {
360 mMapLayer = QgsProject::instance()->mapLayer( annotationElem.attribute( u"mapLayer"_s ) ); // skip-keyword-check
361 }
362
363 //marker symbol
364 {
365 const QDomElement symbolElem = annotationElem.firstChildElement( u"symbol"_s );
366 if ( !symbolElem.isNull() )
367 {
368 std::unique_ptr< QgsMarkerSymbol > symbol = QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElem, context );
369 if ( symbol )
370 {
371 mMarkerSymbol = std::move( symbol );
372 }
373 }
374 }
375
376 mFillSymbol.reset( nullptr );
377 const QDomElement fillElem = annotationElem.firstChildElement( u"fillSymbol"_s );
378 if ( !fillElem.isNull() )
379 {
380 const QDomElement symbolElem = fillElem.firstChildElement( u"symbol"_s );
381 if ( !symbolElem.isNull() )
382 {
383 std::unique_ptr< QgsFillSymbol >symbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context );
384 if ( symbol )
385 {
386 mFillSymbol = std::move( symbol );
387 }
388 }
389 }
390 if ( !mFillSymbol )
391 {
392 QColor frameColor;
393 frameColor.setNamedColor( annotationElem.attribute( u"frameColor"_s, u"#000000"_s ) );
394 frameColor.setAlpha( annotationElem.attribute( u"frameColorAlpha"_s, u"255"_s ).toInt() );
395 QColor frameBackgroundColor;
396 frameBackgroundColor.setNamedColor( annotationElem.attribute( u"frameBackgroundColor"_s ) );
397 frameBackgroundColor.setAlpha( annotationElem.attribute( u"frameBackgroundColorAlpha"_s, u"255"_s ).toInt() );
398 double frameBorderWidth = annotationElem.attribute( u"frameBorderWidth"_s, u"0.5"_s ).toDouble();
399 // need to roughly convert border width from pixels to mm - just assume 96 dpi
400 frameBorderWidth = frameBorderWidth * 25.4 / 96.0;
401 QVariantMap props;
402 props.insert( u"color"_s, frameBackgroundColor.name() );
403 props.insert( u"style"_s, u"solid"_s );
404 props.insert( u"style_border"_s, u"solid"_s );
405 props.insert( u"color_border"_s, frameColor.name() );
406 props.insert( u"width_border"_s, QString::number( frameBorderWidth ) );
407 props.insert( u"joinstyle"_s, u"miter"_s );
408 mFillSymbol = QgsFillSymbol::createSimple( props );
409 }
410
411 emit mapLayerChanged();
412}
413
415{
416 target->mVisible = mVisible;
417 target->mHasFixedMapPosition = mHasFixedMapPosition;
418 target->mMapPosition = mMapPosition;
419 target->mMapPositionCrs = mMapPositionCrs;
420 target->mRelativePosition = mRelativePosition;
421 target->mOffsetFromReferencePoint = mOffsetFromReferencePoint;
422 target->mFrameSize = mFrameSize;
423 target->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr );
424 target->mContentsMargins = mContentsMargins;
425 target->mFillSymbol.reset( mFillSymbol ? mFillSymbol->clone() : nullptr );
426 target->mSegmentPointWidthMm = mSegmentPointWidthMm;
427 target->mMapLayer = mMapLayer;
428 target->mFeature = mFeature;
429}
430
@ Millimeters
Millimeters.
Definition qgis.h:5256
void appearanceChanged()
Emitted whenever the annotation's appearance changes.
Q_DECL_DEPRECATED void setFrameSize(QSizeF size)
Sets the size (in pixels) of the annotation's frame (the main area in which the annotation's content ...
void setFillSymbol(QgsFillSymbol *symbol)
Sets the fill symbol used for rendering the annotation frame.
Q_DECL_DEPRECATED void setFrameOffsetFromReferencePoint(QPointF offset)
Sets the annotation's frame's offset (in pixels) from the mapPosition() reference point.
void setRelativePosition(QPointF position)
Sets the relative position of the annotation, if it is not attached to a fixed map position.
virtual void renderAnnotation(QgsRenderContext &context, QSizeF size) const =0
Renders the annotation's contents to a target /a context at the specified /a size.
void setMapPosition(const QgsPointXY &position)
Sets the map position of the annotation, if it is attached to a fixed map position.
void moved()
Emitted when the annotation's position has changed and items need to be moved to reflect this.
Q_DECL_DEPRECATED QPointF frameOffsetFromReferencePoint() const
Returns the annotation's frame's offset (in pixels) from the mapPosition() reference point.
void _writeXml(QDomElement &itemElem, QDomDocument &doc, const QgsReadWriteContext &context) const
Writes common annotation properties to a DOM element.
virtual bool accept(QgsStyleEntityVisitorInterface *visitor) const
Accepts the specified style entity visitor, causing it to visit all style entities associated within ...
void setContentsMargin(const QgsMargins &margins)
Sets the margins (in millimeters) between the outside of the frame and the annotation content.
void setFrameSizeMm(QSizeF size)
Sets the size (in millimeters) of the annotation's frame (the main area in which the annotation's con...
virtual void setAssociatedFeature(const QgsFeature &feature)
Sets the feature associated with the annotation.
void setFrameOffsetFromReferencePointMm(QPointF offset)
Sets the annotation's frame's offset (in millimeters) from the mapPosition() reference point.
void setMapPositionCrs(const QgsCoordinateReferenceSystem &crs)
Sets the CRS of the map position.
~QgsAnnotation() override
void _readXml(const QDomElement &annotationElem, const QgsReadWriteContext &context)
Reads common annotation properties from a DOM element.
void copyCommonProperties(QgsAnnotation *target) const
Copies common annotation properties to the targe annotation.
void render(QgsRenderContext &context) const
Renders the annotation to a target render context.
virtual QSizeF minimumFrameSize() const
Returns the minimum frame size for the annotation.
bool isVisible() const
Returns true if the annotation is visible and should be rendered.
void setHasFixedMapPosition(bool fixed)
Sets whether the annotation is attached to a fixed map position, or uses a position relative to the c...
QgsAnnotation(QObject *parent=nullptr)
Constructor for QgsAnnotation.
void setMarkerSymbol(QgsMarkerSymbol *symbol)
Sets the symbol that is drawn at the annotation's map position.
QgsFillSymbol * fillSymbol() const
Returns the symbol that is used for rendering the annotation frame.
void setVisible(bool visible)
Sets whether the annotation is visible and should be rendered.
void mapLayerChanged()
Emitted when the map layer associated with the annotation changes.
void setMapLayer(QgsMapLayer *layer)
Sets the map layer associated with the annotation.
Represents a coordinate reference system (CRS).
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:60
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition qgsfeedback.h:55
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
static std::unique_ptr< QgsFillSymbol > createSimple(const QVariantMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties.
Base class for all map layer types.
Definition qgsmaplayer.h:83
Defines the four margins of a rectangle.
Definition qgsmargins.h:40
static QgsMargins fromString(const QString &string)
Returns a QgsMargins object decoded from a string, or a null QgsMargins if the string could not be in...
A marker symbol type, for rendering Point and MultiPoint geometries.
static int qtDefaultDpiX()
Returns the default Qt horizontal DPI.
Represents a 2D point.
Definition qgspointxy.h:62
void setY(double y)
Sets the y value of the point.
Definition qgspointxy.h:131
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:121
static QgsProject * instance()
Returns the QgsProject singleton instance.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
A container for the context for various read/write operations on objects.
Contains information about the context of a rendering operation.
double convertToPainterUnits(double size, Qgis::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale(), Qgis::RenderSubcomponentProperty property=Qgis::RenderSubcomponentProperty::Generic) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
void setPainterFlagsUsingContext(QPainter *painter=nullptr) const
Sets relevant flags on a destination painter, using the flags and settings currently defined for the ...
QgsFeedback * feedback() const
Returns the feedback object that can be queried regularly during rendering to check if rendering shou...
Scoped object for saving and restoring a QPainter object's state.
static QPolygonF createBalloon(const QgsPointXY &origin, const QRectF &rect, double wedgeWidth)
Generates a "balloon"/"talking bubble" style shape (as a QPolygonF).
An interface for classes which can visit style entity (e.g.
virtual bool visitExit(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor stops visiting a node.
virtual bool visitEnter(const QgsStyleEntityVisitorInterface::Node &node)
Called when the visitor starts visiting a node.
virtual bool visit(const QgsStyleEntityVisitorInterface::StyleLeaf &entity)
Called when the visitor will visit a style entity.
A symbol entity for QgsStyle databases.
Definition qgsstyle.h:1398
static std::unique_ptr< QgsSymbol > loadSymbol(const QDomElement &element, const QgsReadWriteContext &context)
Attempts to load a symbol from a DOM element.
static QDomElement saveSymbol(const QString &symbolName, const QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:6817
Contains information relating to a node (i.e.
Contains information relating to the style entity currently being visited.