QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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(
164 context.convertToPainterUnits( mOffsetFromReferencePoint.x(), Qgis::RenderUnit::Millimeters ) + context.convertToPainterUnits( mContentsMargins.left(), Qgis::RenderUnit::Millimeters ),
165 context.convertToPainterUnits( mOffsetFromReferencePoint.y(), Qgis::RenderUnit::Millimeters ) + context.convertToPainterUnits( mContentsMargins.top(), Qgis::RenderUnit::Millimeters )
166 );
167 }
168 else
169 {
170 painter->translate( context.convertToPainterUnits( mContentsMargins.left(), Qgis::RenderUnit::Millimeters ), context.convertToPainterUnits( mContentsMargins.top(), Qgis::RenderUnit::Millimeters ) );
171 }
172 const QSizeF size(
173 context.convertToPainterUnits( mFrameSize.width(), Qgis::RenderUnit::Millimeters )
174 - context.convertToPainterUnits( mContentsMargins.left() + mContentsMargins.right(), Qgis::RenderUnit::Millimeters ),
175 context.convertToPainterUnits( mFrameSize.height(), Qgis::RenderUnit::Millimeters ) - context.convertToPainterUnits( mContentsMargins.top() + mContentsMargins.bottom(), Qgis::RenderUnit::Millimeters )
176 );
177
178 // scale back from painter dpi to 96 dpi --
179 // double dotsPerMM = context.painter()->device()->logicalDpiX() / ( 25.4 * 3.78 );
180 // context.painter()->scale( dotsPerMM, dotsPerMM );
181
182 renderAnnotation( context, size );
183}
184
186{
187 mMarkerSymbol.reset( symbol );
188 emit appearanceChanged();
189}
190
192{
193 mMapLayer = layer;
194 emit mapLayerChanged();
195}
196
198{
199 mFeature = feature;
200}
201
203{
204 // NOTE: if visitEnter returns false it means "don't visit the annotation", not "abort all further visitations"
205 if ( !visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Annotation, u"annotation"_s, tr( "Annotation" ) ) ) )
206 return true;
207
208 if ( mMarkerSymbol )
209 {
210 QgsStyleSymbolEntity entity( mMarkerSymbol.get() );
211 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, u"marker"_s, QObject::tr( "Marker" ) ) ) )
212 return false;
213 }
214
215 if ( mFillSymbol )
216 {
217 QgsStyleSymbolEntity entity( mFillSymbol.get() );
218 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, u"fill"_s, QObject::tr( "Fill" ) ) ) )
219 return false;
220 }
221
222 if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Annotation, u"annotation"_s, tr( "Annotation" ) ) ) )
223 return false;
224
225 return true;
226}
227
229{
230 return QSizeF( 0, 0 );
231}
232
233void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
234{
235 if ( !mFillSymbol )
236 return;
237
238 auto scaleSize = [&context]( double size ) -> double { return context.convertToPainterUnits( size, Qgis::RenderUnit::Millimeters ); };
239
240 const QRectF frameRect(
241 mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.x() ) : 0,
242 mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.y() ) : 0,
243 scaleSize( mFrameSize.width() ),
244 scaleSize( mFrameSize.height() )
245 );
246 const QgsPointXY origin = mHasFixedMapPosition ? QgsPointXY( 0, 0 ) : QgsPointXY( frameRect.center().x(), frameRect.center().y() );
247
248 const QPolygonF poly = QgsShapeGenerator::createBalloon( origin, frameRect, context.convertToPainterUnits( mSegmentPointWidthMm, Qgis::RenderUnit::Millimeters ) );
249
250 mFillSymbol->startRender( context );
251 const QVector<QPolygonF> rings; //empty list
252 mFillSymbol->renderPolygon( poly, &rings, nullptr, context );
253 mFillSymbol->stopRender( context );
254}
255
256void QgsAnnotation::drawMarkerSymbol( QgsRenderContext &context ) const
257{
258 if ( !context.painter() )
259 {
260 return;
261 }
262
263 if ( mMarkerSymbol )
264 {
265 mMarkerSymbol->startRender( context );
266 mMarkerSymbol->renderPoint( QPointF( 0, 0 ), nullptr, context );
267 mMarkerSymbol->stopRender( context );
268 }
269}
270
271void QgsAnnotation::_writeXml( QDomElement &itemElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
272{
273 if ( itemElem.isNull() )
274 {
275 return;
276 }
277 QDomElement annotationElem = doc.createElement( u"AnnotationItem"_s );
278 annotationElem.setAttribute( u"mapPositionFixed"_s, mHasFixedMapPosition );
279 annotationElem.setAttribute( u"mapPosX"_s, qgsDoubleToString( mMapPosition.x() ) );
280 annotationElem.setAttribute( u"mapPosY"_s, qgsDoubleToString( mMapPosition.y() ) );
281 if ( mMapPositionCrs.isValid() )
282 mMapPositionCrs.writeXml( annotationElem, doc );
283 annotationElem.setAttribute( u"offsetXMM"_s, qgsDoubleToString( mOffsetFromReferencePoint.x() ) );
284 annotationElem.setAttribute( u"offsetYMM"_s, qgsDoubleToString( mOffsetFromReferencePoint.y() ) );
285 annotationElem.setAttribute( u"frameWidthMM"_s, qgsDoubleToString( mFrameSize.width() ) );
286 annotationElem.setAttribute( u"frameHeightMM"_s, qgsDoubleToString( mFrameSize.height() ) );
287 annotationElem.setAttribute( u"canvasPosX"_s, qgsDoubleToString( mRelativePosition.x() ) );
288 annotationElem.setAttribute( u"canvasPosY"_s, qgsDoubleToString( mRelativePosition.y() ) );
289 annotationElem.setAttribute( u"contentsMargin"_s, mContentsMargins.toString() );
290 annotationElem.setAttribute( u"visible"_s, isVisible() );
291 if ( mMapLayer )
292 {
293 annotationElem.setAttribute( u"mapLayer"_s, mMapLayer->id() );
294 }
295 if ( mMarkerSymbol )
296 {
297 const QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( u"marker symbol"_s, mMarkerSymbol.get(), doc, context );
298 if ( !symbolElem.isNull() )
299 {
300 annotationElem.appendChild( symbolElem );
301 }
302 }
303 if ( mFillSymbol )
304 {
305 QDomElement fillElem = doc.createElement( u"fillSymbol"_s );
306 const QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( u"fill symbol"_s, mFillSymbol.get(), doc, context );
307 if ( !symbolElem.isNull() )
308 {
309 fillElem.appendChild( symbolElem );
310 annotationElem.appendChild( fillElem );
311 }
312 }
313 itemElem.appendChild( annotationElem );
314}
315
316void QgsAnnotation::_readXml( const QDomElement &annotationElem, const QgsReadWriteContext &context )
317{
318 if ( annotationElem.isNull() )
319 {
320 return;
321 }
322 QPointF pos;
323 pos.setX( annotationElem.attribute( u"canvasPosX"_s, u"0"_s ).toDouble() );
324 pos.setY( annotationElem.attribute( u"canvasPosY"_s, u"0"_s ).toDouble() );
325 if ( pos.x() >= 1 || pos.x() < 0 || pos.y() < 0 || pos.y() >= 1 )
326 mRelativePosition = QPointF();
327 else
328 mRelativePosition = pos;
329 QgsPointXY mapPos;
330 mapPos.setX( annotationElem.attribute( u"mapPosX"_s, u"0"_s ).toDouble() );
331 mapPos.setY( annotationElem.attribute( u"mapPosY"_s, u"0"_s ).toDouble() );
332 mMapPosition = mapPos;
333
334 if ( !mMapPositionCrs.readXml( annotationElem ) )
335 {
336 mMapPositionCrs = QgsCoordinateReferenceSystem();
337 }
338
339 mContentsMargins = QgsMargins::fromString( annotationElem.attribute( u"contentsMargin"_s ) );
340 const double dpiScale = 25.4 / QgsPainting::qtDefaultDpiX();
341 if ( annotationElem.hasAttribute( u"frameWidthMM"_s ) )
342 mFrameSize.setWidth( annotationElem.attribute( u"frameWidthMM"_s, u"5"_s ).toDouble() );
343 else
344 mFrameSize.setWidth( dpiScale * annotationElem.attribute( u"frameWidth"_s, u"50"_s ).toDouble() );
345 if ( annotationElem.hasAttribute( u"frameHeightMM"_s ) )
346 mFrameSize.setHeight( annotationElem.attribute( u"frameHeightMM"_s, u"3"_s ).toDouble() );
347 else
348 mFrameSize.setHeight( dpiScale * annotationElem.attribute( u"frameHeight"_s, u"50"_s ).toDouble() );
349
350 if ( annotationElem.hasAttribute( u"offsetXMM"_s ) )
351 mOffsetFromReferencePoint.setX( annotationElem.attribute( u"offsetXMM"_s, u"0"_s ).toDouble() );
352 else
353 mOffsetFromReferencePoint.setX( dpiScale * annotationElem.attribute( u"offsetX"_s, u"0"_s ).toDouble() );
354 if ( annotationElem.hasAttribute( u"offsetYMM"_s ) )
355 mOffsetFromReferencePoint.setY( annotationElem.attribute( u"offsetYMM"_s, u"0"_s ).toDouble() );
356 else
357 mOffsetFromReferencePoint.setY( dpiScale * annotationElem.attribute( u"offsetY"_s, u"0"_s ).toDouble() );
358
359 mHasFixedMapPosition = annotationElem.attribute( u"mapPositionFixed"_s, u"1"_s ).toInt();
360 mVisible = annotationElem.attribute( u"visible"_s, u"1"_s ).toInt();
361 if ( annotationElem.hasAttribute( u"mapLayer"_s ) )
362 {
363 mMapLayer = QgsProject::instance()->mapLayer( annotationElem.attribute( u"mapLayer"_s ) ); // skip-keyword-check
364 }
365
366 //marker symbol
367 {
368 const QDomElement symbolElem = annotationElem.firstChildElement( u"symbol"_s );
369 if ( !symbolElem.isNull() )
370 {
371 std::unique_ptr< QgsMarkerSymbol > symbol = QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElem, context );
372 if ( symbol )
373 {
374 mMarkerSymbol = std::move( symbol );
375 }
376 }
377 }
378
379 mFillSymbol.reset( nullptr );
380 const QDomElement fillElem = annotationElem.firstChildElement( u"fillSymbol"_s );
381 if ( !fillElem.isNull() )
382 {
383 const QDomElement symbolElem = fillElem.firstChildElement( u"symbol"_s );
384 if ( !symbolElem.isNull() )
385 {
386 std::unique_ptr< QgsFillSymbol > symbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context );
387 if ( symbol )
388 {
389 mFillSymbol = std::move( symbol );
390 }
391 }
392 }
393 if ( !mFillSymbol )
394 {
395 QColor frameColor;
396 frameColor.setNamedColor( annotationElem.attribute( u"frameColor"_s, u"#000000"_s ) );
397 frameColor.setAlpha( annotationElem.attribute( u"frameColorAlpha"_s, u"255"_s ).toInt() );
398 QColor frameBackgroundColor;
399 frameBackgroundColor.setNamedColor( annotationElem.attribute( u"frameBackgroundColor"_s ) );
400 frameBackgroundColor.setAlpha( annotationElem.attribute( u"frameBackgroundColorAlpha"_s, u"255"_s ).toInt() );
401 double frameBorderWidth = annotationElem.attribute( u"frameBorderWidth"_s, u"0.5"_s ).toDouble();
402 // need to roughly convert border width from pixels to mm - just assume 96 dpi
403 frameBorderWidth = frameBorderWidth * 25.4 / 96.0;
404 QVariantMap props;
405 props.insert( u"color"_s, frameBackgroundColor.name() );
406 props.insert( u"style"_s, u"solid"_s );
407 props.insert( u"style_border"_s, u"solid"_s );
408 props.insert( u"color_border"_s, frameColor.name() );
409 props.insert( u"width_border"_s, QString::number( frameBorderWidth ) );
410 props.insert( u"joinstyle"_s, u"miter"_s );
411 mFillSymbol = QgsFillSymbol::createSimple( props );
412 }
413
414 emit mapLayerChanged();
415}
416
418{
419 target->mVisible = mVisible;
420 target->mHasFixedMapPosition = mHasFixedMapPosition;
421 target->mMapPosition = mMapPosition;
422 target->mMapPositionCrs = mMapPositionCrs;
423 target->mRelativePosition = mRelativePosition;
424 target->mOffsetFromReferencePoint = mOffsetFromReferencePoint;
425 target->mFrameSize = mFrameSize;
426 target->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr );
427 target->mContentsMargins = mContentsMargins;
428 target->mFillSymbol.reset( mFillSymbol ? mFillSymbol->clone() : nullptr );
429 target->mSegmentPointWidthMm = mSegmentPointWidthMm;
430 target->mMapLayer = mMapLayer;
431 target->mFeature = mFeature;
432}
@ Millimeters
Millimeters.
Definition qgis.h:5341
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:56
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:132
void setX(double x)
Sets the x value of the point.
Definition qgspointxy.h:122
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:1393
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:6893
Contains information relating to a node (i.e.
Contains information relating to the style entity currently being visited.