QGIS API Documentation  2.99.0-Master (69af2f5)
qgscomposershape.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposershape.cpp
3  ----------------------
4  begin : November 2009
5  copyright : (C) 2009 by Marco Hugentobler
6  email : marco@hugis.net
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 "qgscomposershape.h"
19 #include "qgscomposition.h"
20 #include "qgspathresolver.h"
21 #include "qgsreadwritecontext.h"
22 #include "qgssymbol.h"
23 #include "qgssymbollayerutils.h"
24 #include "qgscomposermodel.h"
25 #include "qgsmapsettings.h"
26 #include "qgscomposerutils.h"
27 #include <QPainter>
28 
30  : QgsComposerItem( composition )
31  , mShape( Ellipse )
32  , mCornerRadius( 0 )
33  , mUseSymbol( false ) //default to not using symbol for shapes, to preserve 2.0 api
34  , mShapeStyleSymbol( nullptr )
35  , mMaxSymbolBleed( 0 )
36 {
37  setFrameEnabled( true );
38  createDefaultShapeStyleSymbol();
39 
40  if ( mComposition )
41  {
42  //connect to atlas feature changes
43  //to update symbol style (in case of data-defined symbology)
45  }
46 }
47 
48 QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition *composition )
49  : QgsComposerItem( x, y, width, height, composition )
50  , mShape( Ellipse )
51  , mCornerRadius( 0 )
52  , mUseSymbol( false ) //default to not using Symbol for shapes, to preserve 2.0 api
53  , mShapeStyleSymbol( nullptr )
54  , mMaxSymbolBleed( 0 )
55 {
56  setSceneRect( QRectF( x, y, width, height ) );
57  setFrameEnabled( true );
58  createDefaultShapeStyleSymbol();
59 
60  if ( mComposition )
61  {
62  //connect to atlas feature changes
63  //to update symbol style (in case of data-defined symbology)
65  }
66 }
67 
69 {
70  delete mShapeStyleSymbol;
71 }
72 
73 void QgsComposerShape::setUseSymbol( bool useSymbol )
74 {
75  mUseSymbol = useSymbol;
76  setFrameEnabled( !useSymbol );
77 }
78 
80 {
81  delete mShapeStyleSymbol;
82  mShapeStyleSymbol = static_cast<QgsFillSymbol *>( symbol->clone() );
83  refreshSymbol();
84 }
85 
87 {
89  mMaxSymbolBleed = ( 25.4 / mComposition->printResolution() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol, rc );
90 
91  updateBoundingRect();
92 
93  update();
94  emit frameChanged();
95 }
96 
97 void QgsComposerShape::createDefaultShapeStyleSymbol()
98 {
99  delete mShapeStyleSymbol;
100  QgsStringMap properties;
101  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
102  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
103  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
104  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
105  properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
106  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
107  mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
108 
109  if ( mComposition )
110  {
112  mMaxSymbolBleed = ( 25.4 / mComposition->printResolution() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol, rc );
113  }
114 
115  updateBoundingRect();
116 
117  emit frameChanged();
118 }
119 
120 void QgsComposerShape::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
121 {
122  Q_UNUSED( itemStyle );
123  Q_UNUSED( pWidget );
124  if ( !painter )
125  {
126  return;
127  }
128  if ( !shouldDrawItem() )
129  {
130  return;
131  }
132 
133  drawBackground( painter );
134  drawFrame( painter );
135 
136  if ( isSelected() )
137  {
138  drawSelectionBoxes( painter );
139  }
140 }
141 
142 
143 void QgsComposerShape::drawShape( QPainter *p )
144 {
145  if ( mUseSymbol )
146  {
147  drawShapeUsingSymbol( p );
148  return;
149  }
150 
151  //draw using QPainter brush and pen to keep 2.0 api compatibility
152  p->save();
153  p->setRenderHint( QPainter::Antialiasing );
154 
155  switch ( mShape )
156  {
157  case Ellipse:
158  p->drawEllipse( QRectF( 0, 0, rect().width(), rect().height() ) );
159  break;
160  case Rectangle:
161  //if corner radius set, then draw a rounded rectangle
162  if ( mCornerRadius > 0 )
163  {
164  p->drawRoundedRect( QRectF( 0, 0, rect().width(), rect().height() ), mCornerRadius, mCornerRadius );
165  }
166  else
167  {
168  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
169  }
170  break;
171  case Triangle:
172  QPolygonF triangle;
173  triangle << QPointF( 0, rect().height() );
174  triangle << QPointF( rect().width(), rect().height() );
175  triangle << QPointF( rect().width() / 2.0, 0 );
176  p->drawPolygon( triangle );
177  break;
178  }
179  p->restore();
180 }
181 
182 void QgsComposerShape::drawShapeUsingSymbol( QPainter *p )
183 {
184  p->save();
185  p->setRenderHint( QPainter::Antialiasing );
186 
187  //setup painter scaling to dots so that raster symbology is drawn to scale
188  double dotsPerMM = p->device()->logicalDpiX() / 25.4;
189 
190  //setup render context
192  context.setForceVectorOutput( true );
193  QgsExpressionContext expressionContext = createExpressionContext();
194  context.setExpressionContext( expressionContext );
195 
196  p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
197 
198  //generate polygon to draw
199  QList<QPolygonF> rings; //empty list
200  QPolygonF shapePolygon;
201 
202  //shapes with curves must be enlarged before conversion to QPolygonF, or
203  //the curves are approximated too much and appear jaggy
204  QTransform t = QTransform::fromScale( 100, 100 );
205  //inverse transform used to scale created polygons back to expected size
206  QTransform ti = t.inverted();
207 
208  switch ( mShape )
209  {
210  case Ellipse:
211  {
212  //create an ellipse
213  QPainterPath ellipsePath;
214  ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
215  QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
216  shapePolygon = ti.map( ellipsePoly );
217  break;
218  }
219  case Rectangle:
220  {
221  //if corner radius set, then draw a rounded rectangle
222  if ( mCornerRadius > 0 )
223  {
224  QPainterPath roundedRectPath;
225  roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ), mCornerRadius * dotsPerMM, mCornerRadius * dotsPerMM );
226  QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
227  shapePolygon = ti.map( roundedPoly );
228  }
229  else
230  {
231  shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
232  }
233  break;
234  }
235  case Triangle:
236  {
237  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
238  shapePolygon << QPointF( rect().width() * dotsPerMM, rect().height() * dotsPerMM );
239  shapePolygon << QPointF( rect().width() / 2.0 * dotsPerMM, 0 );
240  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
241  break;
242  }
243  }
244 
245  mShapeStyleSymbol->startRender( context );
246  mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, nullptr, context );
247  mShapeStyleSymbol->stopRender( context );
248 
249  p->restore();
250 }
251 
252 
253 void QgsComposerShape::drawFrame( QPainter *p )
254 {
255  if ( mFrame && p && !mUseSymbol )
256  {
257  p->setPen( pen() );
258  p->setBrush( Qt::NoBrush );
259  p->setRenderHint( QPainter::Antialiasing, true );
260  drawShape( p );
261  }
262 }
263 
265 {
266  if ( p && ( mBackground || mUseSymbol ) )
267  {
268  p->setBrush( brush() );//this causes a problem in atlas generation
269  p->setPen( Qt::NoPen );
270  p->setRenderHint( QPainter::Antialiasing, true );
271  drawShape( p );
272  }
273 }
274 
276 {
277  return mMaxSymbolBleed;
278 }
279 
280 bool QgsComposerShape::writeXml( QDomElement &elem, QDomDocument &doc ) const
281 {
282  QDomElement composerShapeElem = doc.createElement( QStringLiteral( "ComposerShape" ) );
283  composerShapeElem.setAttribute( QStringLiteral( "shapeType" ), mShape );
284  composerShapeElem.setAttribute( QStringLiteral( "cornerRadius" ), mCornerRadius );
285 
286  QgsReadWriteContext context;
288 
289  QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol, doc, context );
290  composerShapeElem.appendChild( shapeStyleElem );
291 
292  elem.appendChild( composerShapeElem );
293  return _writeXml( composerShapeElem, doc );
294 }
295 
296 bool QgsComposerShape::readXml( const QDomElement &itemElem, const QDomDocument &doc )
297 {
298  mShape = QgsComposerShape::Shape( itemElem.attribute( QStringLiteral( "shapeType" ), QStringLiteral( "0" ) ).toInt() );
299  mCornerRadius = itemElem.attribute( QStringLiteral( "cornerRadius" ), QStringLiteral( "0" ) ).toDouble();
300 
301  //restore general composer item properties
302  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
303  if ( !composerItemList.isEmpty() )
304  {
305  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
306 
307  //rotation
308  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
309  {
310  //check for old (pre 2.1) rotation attribute
311  setItemRotation( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble() );
312  }
313 
314  _readXml( composerItemElem, doc );
315  }
316 
317  QgsReadWriteContext context;
319 
320  QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
321  if ( !shapeStyleSymbolElem.isNull() )
322  {
323  delete mShapeStyleSymbol;
324  mShapeStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( shapeStyleSymbolElem, context );
325  }
326  else
327  {
328  //upgrade project file from 2.0 to use symbol styling
329  delete mShapeStyleSymbol;
330  QgsStringMap properties;
331  properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( brush().color() ) );
332  if ( hasBackground() )
333  {
334  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
335  }
336  else
337  {
338  properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
339  }
340  if ( hasFrame() )
341  {
342  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
343  }
344  else
345  {
346  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
347  }
348  properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( pen().color() ) );
349  properties.insert( QStringLiteral( "width_border" ), QString::number( pen().widthF() ) );
350 
351  //for pre 2.0 projects, shape color and outline were specified in a different element...
352  QDomNodeList outlineColorList = itemElem.elementsByTagName( QStringLiteral( "OutlineColor" ) );
353  if ( !outlineColorList.isEmpty() )
354  {
355  QDomElement frameColorElem = outlineColorList.at( 0 ).toElement();
356  bool redOk, greenOk, blueOk, alphaOk, widthOk;
357  int penRed, penGreen, penBlue, penAlpha;
358  double penWidth;
359 
360  penWidth = itemElem.attribute( QStringLiteral( "outlineWidth" ) ).toDouble( &widthOk );
361  penRed = frameColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
362  penGreen = frameColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
363  penBlue = frameColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
364  penAlpha = frameColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
365 
366  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
367  {
368  properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( QColor( penRed, penGreen, penBlue, penAlpha ) ) );
369  properties.insert( QStringLiteral( "width_border" ), QString::number( penWidth ) );
370  }
371  }
372  QDomNodeList fillColorList = itemElem.elementsByTagName( QStringLiteral( "FillColor" ) );
373  if ( !fillColorList.isEmpty() )
374  {
375  QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
376  bool redOk, greenOk, blueOk, alphaOk;
377  int fillRed, fillGreen, fillBlue, fillAlpha;
378 
379  fillRed = fillColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
380  fillGreen = fillColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
381  fillBlue = fillColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
382  fillAlpha = fillColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
383 
384  if ( redOk && greenOk && blueOk && alphaOk )
385  {
386  properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) ) );
387  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
388  }
389  }
390  if ( itemElem.hasAttribute( QStringLiteral( "transparentFill" ) ) )
391  {
392  //old style (pre 2.0) of specifying that shapes had no fill
393  bool hasOldTransparentFill = itemElem.attribute( QStringLiteral( "transparentFill" ), QStringLiteral( "0" ) ).toInt();
394  if ( hasOldTransparentFill )
395  {
396  properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
397  }
398  }
399 
400  mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
401  }
402  emit itemChanged();
403  return true;
404 }
405 
407 {
408  if ( s == mShape )
409  {
410  return;
411  }
412 
413  mShape = s;
414 
415  if ( mComposition && id().isEmpty() )
416  {
417  //notify the model that the display name has changed
419  }
420 }
421 
423 {
424  mCornerRadius = radius;
425 }
426 
428 {
429  return mCurrentRectangle;
430 }
431 
432 void QgsComposerShape::updateBoundingRect()
433 {
434  QRectF rectangle = rect();
435  rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
436  if ( rectangle != mCurrentRectangle )
437  {
438  prepareGeometryChange();
439  mCurrentRectangle = rectangle;
440  }
441 }
442 
443 void QgsComposerShape::setSceneRect( const QRectF &rectangle )
444 {
445  // Reimplemented from QgsComposerItem as we need to call updateBoundingRect after the shape's size changes
446 
447  //update rect for data defined size and position
448  QRectF evaluatedRect = evalItemRect( rectangle );
449  QgsComposerItem::setSceneRect( evaluatedRect );
450 
451  updateBoundingRect();
452  update();
453 }
454 
456 {
457  if ( !id().isEmpty() )
458  {
459  return id();
460  }
461 
462  switch ( mShape )
463  {
464  case Ellipse:
465  return tr( "<ellipse>" );
466  case Rectangle:
467  return tr( "<rectangle>" );
468  case Triangle:
469  return tr( "<triangle>" );
470  }
471 
472  return tr( "<shape>" );
473 }
void setForceVectorOutput(bool force)
void setShapeType(QgsComposerShape::Shape s)
The class is used as a container of context for various read/write operations on other objects...
void setSceneRect(const QRectF &rectangle) override
Sets new scene rectangle bounds and recalculates hight and extent.
virtual QString displayName() const override
Get item display name.
void setPathResolver(const QgsPathResolver &resolver)
Sets up path resolver for conversion between relative and absolute paths.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
bool writeXml(QDomElement &elem, QDomDocument &doc) const override
Stores state in Dom element.
QgsComposerModel * itemsModel()
Returns the items model attached to the composition.
void itemChanged()
Emitted when the item changes.
static QgsFillSymbol * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
Definition: qgssymbol.cpp:1075
int printResolution() const
A item that forms part of a map composition.
QRectF evalItemRect(const QRectF &newRect, const bool resizeOnly=false, const QgsExpressionContext *context=nullptr)
Evaluates an item&#39;s bounding rect to consider data defined position and size of item and reference po...
virtual void setFrameEnabled(const bool drawFrame)
Set whether this item has a frame drawn around it or not.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:340
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:203
virtual void drawBackground(QPainter *p) override
Draw background.
void updateItemDisplayName(QgsComposerItem *item)
Must be called when an item&#39;s display name is modified.
void startRender(QgsRenderContext &context, const QgsFields &fields=QgsFields())
Begins the rendering process for the symbol.
Definition: qgssymbol.cpp:384
static QString encodeColor(const QColor &color)
static QgsRenderContext createRenderContextForMap(QgsComposerMap *map, QPainter *painter, double dpi=-1)
Creates a render context suitable for the specified composer map and painter destination.
void setUseSymbol(bool useSymbol)
Controls whether the shape should be drawn using a QgsFillSymbol.
void frameChanged()
Emitted if the item&#39;s frame style changes.
bool _writeXml(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document. Usually called from writeXml methods of ...
QgsPathResolver pathResolver() const
Return path resolver object with considering whether the project uses absolute or relative paths and ...
virtual void drawSelectionBoxes(QPainter *p)
Draws additional graphics on selected items.
void setCornerRadius(double radius)
Sets radius for rounded rectangle corners. Added in v2.1.
void renderPolygon(const QPolygonF &points, QList< QPolygonF > *rings, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
Definition: qgssymbol.cpp:1733
virtual QgsFillSymbol * clone() const override
Get a deep copy of this symbol.
Definition: qgssymbol.cpp:1832
bool mFrame
True if item fram needs to be painted.
QgsComposerMap * referenceMap() const
Returns the map item which will be used to generate corresponding world files when the composition is...
virtual void drawFrame(QPainter *p) override
Draw black frame around item.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setShapeStyleSymbol(QgsFillSymbol *symbol)
Sets the QgsFillSymbol used to draw the shape.
bool hasBackground() const
Whether this item has a Background or not.
void repaint() override
static double estimateMaxSymbolBleed(QgsSymbol *symbol, const QgsRenderContext &context)
Returns the maximum estimated bleed for the symbol.
bool readXml(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
bool _readXml(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document. Usually called from readXml methods of su...
Graphics scene for map printing.
void featureChanged(QgsFeature *feature)
Is emitted when the current atlas feature changes.
void refreshSymbol()
Should be called after the shape&#39;s symbol is changed.
virtual QgsExpressionContext createExpressionContext() const override
Creates an expression context relating to the item&#39;s current state.
QgsComposition * mComposition
Contains information about the context of a rendering operation.
const QgsComposition * composition() const
Returns the composition the item is attached to.
QgsProject * project() const
The project associated with the composition.
virtual void setSceneRect(const QRectF &rectangle)
Sets this items bound in scene coordinates such that 1 item size units corresponds to 1 scene size un...
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
Reimplementation of QCanvasItem::paint - draw on canvas.
QgsAtlasComposition & atlasComposition()
bool hasFrame() const
Whether this item has a frame or not.
bool mBackground
True if item background needs to be painted.
static QgsRenderContext createRenderContextForComposition(QgsComposition *composition, QPainter *painter)
Creates a render context suitable for the specified composition and painter destination.
void stopRender(QgsRenderContext &context)
Ends the rendering process.
Definition: qgssymbol.cpp:403
QString id() const
Get item&#39;s id (which is not necessarly unique)
virtual void setItemRotation(const double rotation, const bool adjustPosition=false)
Sets the item rotation, in degrees clockwise.
virtual double estimatedFrameBleed() const override
Reimplement estimatedFrameBleed, since frames on shapes are drawn using symbology rather than the ite...
QRectF boundingRect() const override
Depending on the symbol style, the bounding rectangle can be larger than the shape.
QgsComposerShape(QgsComposition *composition)
void setExpressionContext(const QgsExpressionContext &context)
Sets the expression context.