Branch data Line data Source code
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 : : #include "qgssymbollayerutils.h"
20 : : #include "qgsmaplayer.h"
21 : : #include "qgsproject.h"
22 : : #include "qgsgeometryutils.h"
23 : : #include "qgsstyleentityvisitor.h"
24 : : #include "qgsshapegenerator.h"
25 : :
26 : : #include <QPen>
27 : : #include <QPainter>
28 : :
29 : : Q_GUI_EXPORT extern int qt_defaultDpiX();
30 : :
31 : 0 : QgsAnnotation::QgsAnnotation( QObject *parent )
32 : 0 : : QObject( parent )
33 : 0 : , mMarkerSymbol( new QgsMarkerSymbol() )
34 : 0 : {
35 : 0 : QVariantMap props;
36 : 0 : props.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
37 : 0 : props.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
38 : 0 : props.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
39 : 0 : props.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
40 : 0 : props.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
41 : 0 : props.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
42 : 0 : mFillSymbol.reset( QgsFillSymbol::createSimple( props ) );
43 : 0 : }
44 : :
45 : 0 : void QgsAnnotation::setVisible( bool visible )
46 : : {
47 : 0 : if ( mVisible == visible )
48 : 0 : return;
49 : :
50 : 0 : mVisible = visible;
51 : 0 : emit appearanceChanged();
52 : 0 : }
53 : :
54 : 0 : void QgsAnnotation::setHasFixedMapPosition( bool fixed )
55 : : {
56 : 0 : if ( mHasFixedMapPosition == fixed )
57 : 0 : return;
58 : :
59 : 0 : mHasFixedMapPosition = fixed;
60 : 0 : emit moved();
61 : 0 : }
62 : :
63 : 0 : void QgsAnnotation::setMapPosition( const QgsPointXY &position )
64 : : {
65 : 0 : mMapPosition = position;
66 : 0 : emit moved();
67 : 0 : }
68 : :
69 : 0 : void QgsAnnotation::setMapPositionCrs( const QgsCoordinateReferenceSystem &crs )
70 : : {
71 : 0 : mMapPositionCrs = crs;
72 : 0 : emit moved();
73 : 0 : }
74 : :
75 : 0 : void QgsAnnotation::setRelativePosition( QPointF position )
76 : : {
77 : 0 : mRelativePosition = position;
78 : 0 : emit moved();
79 : 0 : }
80 : :
81 : 0 : void QgsAnnotation::setFrameOffsetFromReferencePoint( QPointF offset )
82 : : {
83 : : // convert from offset in pixels at 96 dpi to mm
84 : 0 : setFrameOffsetFromReferencePointMm( offset / 3.7795275 );
85 : 0 : }
86 : :
87 : 0 : QPointF QgsAnnotation::frameOffsetFromReferencePoint() const
88 : : {
89 : 0 : return mOffsetFromReferencePoint / 3.7795275;
90 : : }
91 : :
92 : 0 : void QgsAnnotation::setFrameOffsetFromReferencePointMm( QPointF offset )
93 : : {
94 : 0 : mOffsetFromReferencePoint = offset;
95 : :
96 : 0 : emit moved();
97 : 0 : emit appearanceChanged();
98 : 0 : }
99 : :
100 : 0 : void QgsAnnotation::setFrameSize( QSizeF size )
101 : : {
102 : : // convert from size in pixels at 96 dpi to mm
103 : 0 : setFrameSizeMm( size / 3.7795275 );
104 : 0 : }
105 : :
106 : 0 : QSizeF QgsAnnotation::frameSize() const
107 : : {
108 : 0 : return mFrameSize / 3.7795275;
109 : : }
110 : :
111 : 0 : void QgsAnnotation::setFrameSizeMm( QSizeF size )
112 : : {
113 : 0 : QSizeF frameSize = minimumFrameSize().expandedTo( size ); //don't allow frame sizes below minimum
114 : 0 : mFrameSize = frameSize;
115 : 0 : emit moved();
116 : 0 : emit appearanceChanged();
117 : 0 : }
118 : :
119 : 0 : void QgsAnnotation::setContentsMargin( const QgsMargins &margins )
120 : : {
121 : 0 : mContentsMargins = margins;
122 : 0 : emit appearanceChanged();
123 : 0 : }
124 : :
125 : 0 : void QgsAnnotation::setFillSymbol( QgsFillSymbol *symbol )
126 : : {
127 : 0 : mFillSymbol.reset( symbol );
128 : 0 : emit appearanceChanged();
129 : 0 : }
130 : :
131 : 0 : void QgsAnnotation::render( QgsRenderContext &context ) const
132 : : {
133 : 0 : QPainter *painter = context.painter();
134 : 0 : if ( !painter )
135 : : {
136 : 0 : return;
137 : : }
138 : :
139 : 0 : QgsScopedQPainterState painterState( context.painter() );
140 : 0 : context.setPainterFlagsUsingContext();
141 : :
142 : 0 : drawFrame( context );
143 : 0 : if ( mHasFixedMapPosition )
144 : : {
145 : 0 : drawMarkerSymbol( context );
146 : 0 : }
147 : 0 : if ( mHasFixedMapPosition )
148 : : {
149 : 0 : painter->translate( context.convertToPainterUnits( mOffsetFromReferencePoint.x(), QgsUnitTypes::RenderMillimeters ) + context.convertToPainterUnits( mContentsMargins.left(), QgsUnitTypes::RenderMillimeters ),
150 : 0 : context.convertToPainterUnits( mOffsetFromReferencePoint.y(), QgsUnitTypes::RenderMillimeters ) + context.convertToPainterUnits( mContentsMargins.top(), QgsUnitTypes::RenderMillimeters ) );
151 : 0 : }
152 : : else
153 : : {
154 : 0 : painter->translate( context.convertToPainterUnits( mContentsMargins.left(), QgsUnitTypes::RenderMillimeters ),
155 : 0 : context.convertToPainterUnits( mContentsMargins.top(), QgsUnitTypes::RenderMillimeters ) );
156 : : }
157 : 0 : QSizeF size( context.convertToPainterUnits( mFrameSize.width(), QgsUnitTypes::RenderMillimeters ) - context.convertToPainterUnits( mContentsMargins.left() + mContentsMargins.right(), QgsUnitTypes::RenderMillimeters ),
158 : 0 : context.convertToPainterUnits( mFrameSize.height(), QgsUnitTypes::RenderMillimeters ) - context.convertToPainterUnits( mContentsMargins.top() + mContentsMargins.bottom(), QgsUnitTypes::RenderMillimeters ) );
159 : :
160 : : // scale back from painter dpi to 96 dpi --
161 : : // double dotsPerMM = context.painter()->device()->logicalDpiX() / ( 25.4 * 3.78 );
162 : : // context.painter()->scale( dotsPerMM, dotsPerMM );
163 : :
164 : 0 : renderAnnotation( context, size );
165 : 0 : }
166 : :
167 : 0 : void QgsAnnotation::setMarkerSymbol( QgsMarkerSymbol *symbol )
168 : : {
169 : 0 : mMarkerSymbol.reset( symbol );
170 : 0 : emit appearanceChanged();
171 : 0 : }
172 : :
173 : 0 : void QgsAnnotation::setMapLayer( QgsMapLayer *layer )
174 : : {
175 : 0 : mMapLayer = layer;
176 : 0 : emit mapLayerChanged();
177 : 0 : }
178 : :
179 : 0 : void QgsAnnotation::setAssociatedFeature( const QgsFeature &feature )
180 : : {
181 : 0 : mFeature = feature;
182 : 0 : }
183 : :
184 : 0 : bool QgsAnnotation::accept( QgsStyleEntityVisitorInterface *visitor ) const
185 : : {
186 : : // NOTE: if visitEnter returns false it means "don't visit the annotation", not "abort all further visitations"
187 : 0 : if ( !visitor->visitEnter( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Annotation, QStringLiteral( "annotation" ), tr( "Annotation" ) ) ) )
188 : 0 : return true;
189 : :
190 : 0 : if ( mMarkerSymbol )
191 : : {
192 : 0 : QgsStyleSymbolEntity entity( mMarkerSymbol.get() );
193 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "marker" ), QObject::tr( "Marker" ) ) ) )
194 : 0 : return false;
195 : 0 : }
196 : :
197 : 0 : if ( mFillSymbol )
198 : : {
199 : 0 : QgsStyleSymbolEntity entity( mFillSymbol.get() );
200 : 0 : if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, QStringLiteral( "fill" ), QObject::tr( "Fill" ) ) ) )
201 : 0 : return false;
202 : 0 : }
203 : :
204 : 0 : if ( !visitor->visitExit( QgsStyleEntityVisitorInterface::Node( QgsStyleEntityVisitorInterface::NodeType::Annotation, QStringLiteral( "annotation" ), tr( "Annotation" ) ) ) )
205 : 0 : return false;
206 : :
207 : 0 : return true;
208 : 0 : }
209 : :
210 : 0 : QSizeF QgsAnnotation::minimumFrameSize() const
211 : : {
212 : 0 : return QSizeF( 0, 0 );
213 : : }
214 : :
215 : 0 : void QgsAnnotation::drawFrame( QgsRenderContext &context ) const
216 : : {
217 : 0 : if ( !mFillSymbol )
218 : 0 : return;
219 : :
220 : 0 : auto scaleSize = [&context]( double size )->double
221 : : {
222 : 0 : return context.convertToPainterUnits( size, QgsUnitTypes::RenderMillimeters );
223 : : };
224 : :
225 : 0 : const QRectF frameRect( mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.x() ) : 0,
226 : 0 : mHasFixedMapPosition ? scaleSize( mOffsetFromReferencePoint.y() ) : 0,
227 : 0 : scaleSize( mFrameSize.width() ),
228 : 0 : scaleSize( mFrameSize.height() ) );
229 : 0 : const QgsPointXY origin = mHasFixedMapPosition ? QgsPointXY( 0, 0 ) : QgsPointXY( frameRect.center().x(), frameRect.center().y() );
230 : :
231 : 0 : const QPolygonF poly = QgsShapeGenerator::createBalloon( origin, frameRect, context.convertToPainterUnits( mSegmentPointWidthMm, QgsUnitTypes::RenderMillimeters ) );
232 : :
233 : 0 : mFillSymbol->startRender( context );
234 : 0 : QVector<QPolygonF> rings; //empty list
235 : 0 : mFillSymbol->renderPolygon( poly, &rings, nullptr, context );
236 : 0 : mFillSymbol->stopRender( context );
237 : 0 : }
238 : :
239 : 0 : void QgsAnnotation::drawMarkerSymbol( QgsRenderContext &context ) const
240 : : {
241 : 0 : if ( !context.painter() )
242 : : {
243 : 0 : return;
244 : : }
245 : :
246 : 0 : if ( mMarkerSymbol )
247 : : {
248 : 0 : mMarkerSymbol->startRender( context );
249 : 0 : mMarkerSymbol->renderPoint( QPointF( 0, 0 ), nullptr, context );
250 : 0 : mMarkerSymbol->stopRender( context );
251 : 0 : }
252 : 0 : }
253 : :
254 : 0 : void QgsAnnotation::_writeXml( QDomElement &itemElem, QDomDocument &doc, const QgsReadWriteContext &context ) const
255 : : {
256 : 0 : if ( itemElem.isNull() )
257 : : {
258 : 0 : return;
259 : : }
260 : 0 : QDomElement annotationElem = doc.createElement( QStringLiteral( "AnnotationItem" ) );
261 : 0 : annotationElem.setAttribute( QStringLiteral( "mapPositionFixed" ), mHasFixedMapPosition );
262 : 0 : annotationElem.setAttribute( QStringLiteral( "mapPosX" ), qgsDoubleToString( mMapPosition.x() ) );
263 : 0 : annotationElem.setAttribute( QStringLiteral( "mapPosY" ), qgsDoubleToString( mMapPosition.y() ) );
264 : 0 : if ( mMapPositionCrs.isValid() )
265 : 0 : mMapPositionCrs.writeXml( annotationElem, doc );
266 : 0 : annotationElem.setAttribute( QStringLiteral( "offsetXMM" ), qgsDoubleToString( mOffsetFromReferencePoint.x() ) );
267 : 0 : annotationElem.setAttribute( QStringLiteral( "offsetYMM" ), qgsDoubleToString( mOffsetFromReferencePoint.y() ) );
268 : 0 : annotationElem.setAttribute( QStringLiteral( "frameWidthMM" ), qgsDoubleToString( mFrameSize.width() ) );
269 : 0 : annotationElem.setAttribute( QStringLiteral( "frameHeightMM" ), qgsDoubleToString( mFrameSize.height() ) );
270 : 0 : annotationElem.setAttribute( QStringLiteral( "canvasPosX" ), qgsDoubleToString( mRelativePosition.x() ) );
271 : 0 : annotationElem.setAttribute( QStringLiteral( "canvasPosY" ), qgsDoubleToString( mRelativePosition.y() ) );
272 : 0 : annotationElem.setAttribute( QStringLiteral( "contentsMargin" ), mContentsMargins.toString() );
273 : 0 : annotationElem.setAttribute( QStringLiteral( "visible" ), isVisible() );
274 : 0 : if ( mMapLayer )
275 : : {
276 : 0 : annotationElem.setAttribute( QStringLiteral( "mapLayer" ), mMapLayer->id() );
277 : 0 : }
278 : 0 : if ( mMarkerSymbol )
279 : : {
280 : 0 : QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "marker symbol" ), mMarkerSymbol.get(), doc, context );
281 : 0 : if ( !symbolElem.isNull() )
282 : : {
283 : 0 : annotationElem.appendChild( symbolElem );
284 : 0 : }
285 : 0 : }
286 : 0 : if ( mFillSymbol )
287 : : {
288 : 0 : QDomElement fillElem = doc.createElement( QStringLiteral( "fillSymbol" ) );
289 : 0 : QDomElement symbolElem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "fill symbol" ), mFillSymbol.get(), doc, context );
290 : 0 : if ( !symbolElem.isNull() )
291 : : {
292 : 0 : fillElem.appendChild( symbolElem );
293 : 0 : annotationElem.appendChild( fillElem );
294 : 0 : }
295 : 0 : }
296 : 0 : itemElem.appendChild( annotationElem );
297 : 0 : }
298 : :
299 : 0 : void QgsAnnotation::_readXml( const QDomElement &annotationElem, const QgsReadWriteContext &context )
300 : : {
301 : 0 : if ( annotationElem.isNull() )
302 : : {
303 : 0 : return;
304 : : }
305 : 0 : QPointF pos;
306 : 0 : pos.setX( annotationElem.attribute( QStringLiteral( "canvasPosX" ), QStringLiteral( "0" ) ).toDouble() );
307 : 0 : pos.setY( annotationElem.attribute( QStringLiteral( "canvasPosY" ), QStringLiteral( "0" ) ).toDouble() );
308 : 0 : if ( pos.x() >= 1 || pos.x() < 0 || pos.y() < 0 || pos.y() >= 1 )
309 : 0 : mRelativePosition = QPointF();
310 : : else
311 : 0 : mRelativePosition = pos;
312 : 0 : QgsPointXY mapPos;
313 : 0 : mapPos.setX( annotationElem.attribute( QStringLiteral( "mapPosX" ), QStringLiteral( "0" ) ).toDouble() );
314 : 0 : mapPos.setY( annotationElem.attribute( QStringLiteral( "mapPosY" ), QStringLiteral( "0" ) ).toDouble() );
315 : 0 : mMapPosition = mapPos;
316 : :
317 : 0 : if ( !mMapPositionCrs.readXml( annotationElem ) )
318 : : {
319 : 0 : mMapPositionCrs = QgsCoordinateReferenceSystem();
320 : 0 : }
321 : :
322 : 0 : mContentsMargins = QgsMargins::fromString( annotationElem.attribute( QStringLiteral( "contentsMargin" ) ) );
323 : 0 : const double dpiScale = 25.4 / qt_defaultDpiX();
324 : 0 : if ( annotationElem.hasAttribute( QStringLiteral( "frameWidthMM" ) ) )
325 : 0 : mFrameSize.setWidth( annotationElem.attribute( QStringLiteral( "frameWidthMM" ), QStringLiteral( "5" ) ).toDouble() );
326 : : else
327 : 0 : mFrameSize.setWidth( dpiScale * annotationElem.attribute( QStringLiteral( "frameWidth" ), QStringLiteral( "50" ) ).toDouble() );
328 : 0 : if ( annotationElem.hasAttribute( QStringLiteral( "frameHeightMM" ) ) )
329 : 0 : mFrameSize.setHeight( annotationElem.attribute( QStringLiteral( "frameHeightMM" ), QStringLiteral( "3" ) ).toDouble() );
330 : : else
331 : 0 : mFrameSize.setHeight( dpiScale * annotationElem.attribute( QStringLiteral( "frameHeight" ), QStringLiteral( "50" ) ).toDouble() );
332 : :
333 : 0 : if ( annotationElem.hasAttribute( QStringLiteral( "offsetXMM" ) ) )
334 : 0 : mOffsetFromReferencePoint.setX( annotationElem.attribute( QStringLiteral( "offsetXMM" ), QStringLiteral( "0" ) ).toDouble() );
335 : : else
336 : 0 : mOffsetFromReferencePoint.setX( dpiScale * annotationElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble() );
337 : 0 : if ( annotationElem.hasAttribute( QStringLiteral( "offsetYMM" ) ) )
338 : 0 : mOffsetFromReferencePoint.setY( annotationElem.attribute( QStringLiteral( "offsetYMM" ), QStringLiteral( "0" ) ).toDouble() );
339 : : else
340 : 0 : mOffsetFromReferencePoint.setY( dpiScale * annotationElem.attribute( QStringLiteral( "offsetY" ), QStringLiteral( "0" ) ).toDouble() );
341 : :
342 : 0 : mHasFixedMapPosition = annotationElem.attribute( QStringLiteral( "mapPositionFixed" ), QStringLiteral( "1" ) ).toInt();
343 : 0 : mVisible = annotationElem.attribute( QStringLiteral( "visible" ), QStringLiteral( "1" ) ).toInt();
344 : 0 : if ( annotationElem.hasAttribute( QStringLiteral( "mapLayer" ) ) )
345 : : {
346 : 0 : mMapLayer = QgsProject::instance()->mapLayer( annotationElem.attribute( QStringLiteral( "mapLayer" ) ) );
347 : 0 : }
348 : :
349 : : //marker symbol
350 : : {
351 : 0 : QDomElement symbolElem = annotationElem.firstChildElement( QStringLiteral( "symbol" ) );
352 : 0 : if ( !symbolElem.isNull() )
353 : : {
354 : 0 : QgsMarkerSymbol *symbol = QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElem, context );
355 : 0 : if ( symbol )
356 : : {
357 : 0 : mMarkerSymbol.reset( symbol );
358 : 0 : }
359 : 0 : }
360 : 0 : }
361 : :
362 : 0 : mFillSymbol.reset( nullptr );
363 : 0 : QDomElement fillElem = annotationElem.firstChildElement( QStringLiteral( "fillSymbol" ) );
364 : 0 : if ( !fillElem.isNull() )
365 : : {
366 : 0 : QDomElement symbolElem = fillElem.firstChildElement( QStringLiteral( "symbol" ) );
367 : 0 : if ( !symbolElem.isNull() )
368 : : {
369 : 0 : QgsFillSymbol *symbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context );
370 : 0 : if ( symbol )
371 : : {
372 : 0 : mFillSymbol.reset( symbol );
373 : 0 : }
374 : 0 : }
375 : 0 : }
376 : 0 : if ( !mFillSymbol )
377 : : {
378 : 0 : QColor frameColor;
379 : 0 : frameColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameColor" ), QStringLiteral( "#000000" ) ) );
380 : 0 : frameColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameColorAlpha" ), QStringLiteral( "255" ) ).toInt() );
381 : 0 : QColor frameBackgroundColor;
382 : 0 : frameBackgroundColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameBackgroundColor" ) ) );
383 : 0 : frameBackgroundColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameBackgroundColorAlpha" ), QStringLiteral( "255" ) ).toInt() );
384 : 0 : double frameBorderWidth = annotationElem.attribute( QStringLiteral( "frameBorderWidth" ), QStringLiteral( "0.5" ) ).toDouble();
385 : : // need to roughly convert border width from pixels to mm - just assume 96 dpi
386 : 0 : frameBorderWidth = frameBorderWidth * 25.4 / 96.0;
387 : 0 : QVariantMap props;
388 : 0 : props.insert( QStringLiteral( "color" ), frameBackgroundColor.name() );
389 : 0 : props.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
390 : 0 : props.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
391 : 0 : props.insert( QStringLiteral( "color_border" ), frameColor.name() );
392 : 0 : props.insert( QStringLiteral( "width_border" ), QString::number( frameBorderWidth ) );
393 : 0 : props.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
394 : 0 : mFillSymbol.reset( QgsFillSymbol::createSimple( props ) );
395 : 0 : }
396 : :
397 : 0 : emit mapLayerChanged();
398 : 0 : }
399 : 0 :
400 : 0 : void QgsAnnotation::copyCommonProperties( QgsAnnotation *target ) const
401 : : {
402 : 0 : target->mVisible = mVisible;
403 : 0 : target->mHasFixedMapPosition = mHasFixedMapPosition;
404 : 0 : target->mMapPosition = mMapPosition;
405 : 0 : target->mMapPositionCrs = mMapPositionCrs;
406 : 0 : target->mRelativePosition = mRelativePosition;
407 : 0 : target->mOffsetFromReferencePoint = mOffsetFromReferencePoint;
408 : 0 : target->mFrameSize = mFrameSize;
409 : 0 : target->mMarkerSymbol.reset( mMarkerSymbol ? mMarkerSymbol->clone() : nullptr );
410 : 0 : target->mContentsMargins = mContentsMargins;
411 : 0 : target->mFillSymbol.reset( mFillSymbol ? mFillSymbol->clone() : nullptr );
412 : 0 : target->mSegmentPointWidthMm = mSegmentPointWidthMm;
413 : 0 : target->mMapLayer = mMapLayer;
414 : 0 : target->mFeature = mFeature;
415 : 0 : }
416 : :
|