QGIS API Documentation  2.99.0-Master (40f86b2)
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 : [email protected]
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 "qgssymbol.h"
21 #include "qgssymbollayerutils.h"
22 #include "qgscomposermodel.h"
23 #include "qgsmapsettings.h"
24 #include "qgscomposerutils.h"
25 #include <QPainter>
26 
28  : QgsComposerItem( composition )
29  , mShape( Ellipse )
30  , mCornerRadius( 0 )
31  , mUseSymbol( false ) //default to not using symbol for shapes, to preserve 2.0 api
32  , mShapeStyleSymbol( nullptr )
33  , mMaxSymbolBleed( 0 )
34 {
35  setFrameEnabled( true );
36  createDefaultShapeStyleSymbol();
37 
38  if ( mComposition )
39  {
40  //connect to atlas feature changes
41  //to update symbol style (in case of data-defined symbology)
43  }
44 }
45 
46 QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition *composition )
47  : QgsComposerItem( x, y, width, height, composition )
48  , mShape( Ellipse )
49  , mCornerRadius( 0 )
50  , mUseSymbol( false ) //default to not using Symbol for shapes, to preserve 2.0 api
51  , mShapeStyleSymbol( nullptr )
52  , mMaxSymbolBleed( 0 )
53 {
54  setSceneRect( QRectF( x, y, width, height ) );
55  setFrameEnabled( true );
56  createDefaultShapeStyleSymbol();
57 
58  if ( mComposition )
59  {
60  //connect to atlas feature changes
61  //to update symbol style (in case of data-defined symbology)
63  }
64 }
65 
67 {
68  delete mShapeStyleSymbol;
69 }
70 
71 void QgsComposerShape::setUseSymbol( bool useSymbol )
72 {
73  mUseSymbol = useSymbol;
74  setFrameEnabled( !useSymbol );
75 }
76 
78 {
79  delete mShapeStyleSymbol;
80  mShapeStyleSymbol = static_cast<QgsFillSymbol *>( symbol->clone() );
81  refreshSymbol();
82 }
83 
85 {
87  mMaxSymbolBleed = ( 25.4 / mComposition->printResolution() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol, rc );
88 
89  updateBoundingRect();
90 
91  update();
92  emit frameChanged();
93 }
94 
95 void QgsComposerShape::createDefaultShapeStyleSymbol()
96 {
97  delete mShapeStyleSymbol;
98  QgsStringMap properties;
99  properties.insert( QStringLiteral( "color" ), QStringLiteral( "white" ) );
100  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
101  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
102  properties.insert( QStringLiteral( "color_border" ), QStringLiteral( "black" ) );
103  properties.insert( QStringLiteral( "width_border" ), QStringLiteral( "0.3" ) );
104  properties.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) );
105  mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
106 
107  if ( mComposition )
108  {
110  mMaxSymbolBleed = ( 25.4 / mComposition->printResolution() ) * QgsSymbolLayerUtils::estimateMaxSymbolBleed( mShapeStyleSymbol, rc );
111  }
112 
113  updateBoundingRect();
114 
115  emit frameChanged();
116 }
117 
118 void QgsComposerShape::paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
119 {
120  Q_UNUSED( itemStyle );
121  Q_UNUSED( pWidget );
122  if ( !painter )
123  {
124  return;
125  }
126  if ( !shouldDrawItem() )
127  {
128  return;
129  }
130 
131  drawBackground( painter );
132  drawFrame( painter );
133 
134  if ( isSelected() )
135  {
136  drawSelectionBoxes( painter );
137  }
138 }
139 
140 
141 void QgsComposerShape::drawShape( QPainter *p )
142 {
143  if ( mUseSymbol )
144  {
145  drawShapeUsingSymbol( p );
146  return;
147  }
148 
149  //draw using QPainter brush and pen to keep 2.0 api compatibility
150  p->save();
151  p->setRenderHint( QPainter::Antialiasing );
152 
153  switch ( mShape )
154  {
155  case Ellipse:
156  p->drawEllipse( QRectF( 0, 0, rect().width(), rect().height() ) );
157  break;
158  case Rectangle:
159  //if corner radius set, then draw a rounded rectangle
160  if ( mCornerRadius > 0 )
161  {
162  p->drawRoundedRect( QRectF( 0, 0, rect().width(), rect().height() ), mCornerRadius, mCornerRadius );
163  }
164  else
165  {
166  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
167  }
168  break;
169  case Triangle:
170  QPolygonF triangle;
171  triangle << QPointF( 0, rect().height() );
172  triangle << QPointF( rect().width(), rect().height() );
173  triangle << QPointF( rect().width() / 2.0, 0 );
174  p->drawPolygon( triangle );
175  break;
176  }
177  p->restore();
178 }
179 
180 void QgsComposerShape::drawShapeUsingSymbol( QPainter *p )
181 {
182  p->save();
183  p->setRenderHint( QPainter::Antialiasing );
184 
185  //setup painter scaling to dots so that raster symbology is drawn to scale
186  double dotsPerMM = p->device()->logicalDpiX() / 25.4;
187 
188  //setup render context
190  context.setForceVectorOutput( true );
191  QgsExpressionContext expressionContext = createExpressionContext();
192  context.setExpressionContext( expressionContext );
193 
194  p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
195 
196  //generate polygon to draw
197  QList<QPolygonF> rings; //empty list
198  QPolygonF shapePolygon;
199 
200  //shapes with curves must be enlarged before conversion to QPolygonF, or
201  //the curves are approximated too much and appear jaggy
202  QTransform t = QTransform::fromScale( 100, 100 );
203  //inverse transform used to scale created polygons back to expected size
204  QTransform ti = t.inverted();
205 
206  switch ( mShape )
207  {
208  case Ellipse:
209  {
210  //create an ellipse
211  QPainterPath ellipsePath;
212  ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
213  QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
214  shapePolygon = ti.map( ellipsePoly );
215  break;
216  }
217  case Rectangle:
218  {
219  //if corner radius set, then draw a rounded rectangle
220  if ( mCornerRadius > 0 )
221  {
222  QPainterPath roundedRectPath;
223  roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ), mCornerRadius * dotsPerMM, mCornerRadius * dotsPerMM );
224  QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
225  shapePolygon = ti.map( roundedPoly );
226  }
227  else
228  {
229  shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
230  }
231  break;
232  }
233  case Triangle:
234  {
235  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
236  shapePolygon << QPointF( rect().width() * dotsPerMM, rect().height() * dotsPerMM );
237  shapePolygon << QPointF( rect().width() / 2.0 * dotsPerMM, 0 );
238  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
239  break;
240  }
241  }
242 
243  mShapeStyleSymbol->startRender( context );
244  mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, nullptr, context );
245  mShapeStyleSymbol->stopRender( context );
246 
247  p->restore();
248 }
249 
250 
251 void QgsComposerShape::drawFrame( QPainter *p )
252 {
253  if ( mFrame && p && !mUseSymbol )
254  {
255  p->setPen( pen() );
256  p->setBrush( Qt::NoBrush );
257  p->setRenderHint( QPainter::Antialiasing, true );
258  drawShape( p );
259  }
260 }
261 
263 {
264  if ( p && ( mBackground || mUseSymbol ) )
265  {
266  p->setBrush( brush() );//this causes a problem in atlas generation
267  p->setPen( Qt::NoPen );
268  p->setRenderHint( QPainter::Antialiasing, true );
269  drawShape( p );
270  }
271 }
272 
274 {
275  return mMaxSymbolBleed;
276 }
277 
278 bool QgsComposerShape::writeXml( QDomElement &elem, QDomDocument &doc ) const
279 {
280  QDomElement composerShapeElem = doc.createElement( QStringLiteral( "ComposerShape" ) );
281  composerShapeElem.setAttribute( QStringLiteral( "shapeType" ), mShape );
282  composerShapeElem.setAttribute( QStringLiteral( "cornerRadius" ), mCornerRadius );
283 
284  QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol, doc );
285  composerShapeElem.appendChild( shapeStyleElem );
286 
287  elem.appendChild( composerShapeElem );
288  return _writeXml( composerShapeElem, doc );
289 }
290 
291 bool QgsComposerShape::readXml( const QDomElement &itemElem, const QDomDocument &doc )
292 {
293  mShape = QgsComposerShape::Shape( itemElem.attribute( QStringLiteral( "shapeType" ), QStringLiteral( "0" ) ).toInt() );
294  mCornerRadius = itemElem.attribute( QStringLiteral( "cornerRadius" ), QStringLiteral( "0" ) ).toDouble();
295 
296  //restore general composer item properties
297  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
298  if ( !composerItemList.isEmpty() )
299  {
300  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
301 
302  //rotation
303  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
304  {
305  //check for old (pre 2.1) rotation attribute
306  setItemRotation( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble() );
307  }
308 
309  _readXml( composerItemElem, doc );
310  }
311 
312  QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
313  if ( !shapeStyleSymbolElem.isNull() )
314  {
315  delete mShapeStyleSymbol;
316  mShapeStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( shapeStyleSymbolElem );
317  }
318  else
319  {
320  //upgrade project file from 2.0 to use symbol styling
321  delete mShapeStyleSymbol;
322  QgsStringMap properties;
323  properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( brush().color() ) );
324  if ( hasBackground() )
325  {
326  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
327  }
328  else
329  {
330  properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
331  }
332  if ( hasFrame() )
333  {
334  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
335  }
336  else
337  {
338  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
339  }
340  properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( pen().color() ) );
341  properties.insert( QStringLiteral( "width_border" ), QString::number( pen().widthF() ) );
342 
343  //for pre 2.0 projects, shape color and outline were specified in a different element...
344  QDomNodeList outlineColorList = itemElem.elementsByTagName( QStringLiteral( "OutlineColor" ) );
345  if ( !outlineColorList.isEmpty() )
346  {
347  QDomElement frameColorElem = outlineColorList.at( 0 ).toElement();
348  bool redOk, greenOk, blueOk, alphaOk, widthOk;
349  int penRed, penGreen, penBlue, penAlpha;
350  double penWidth;
351 
352  penWidth = itemElem.attribute( QStringLiteral( "outlineWidth" ) ).toDouble( &widthOk );
353  penRed = frameColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
354  penGreen = frameColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
355  penBlue = frameColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
356  penAlpha = frameColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
357 
358  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
359  {
360  properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( QColor( penRed, penGreen, penBlue, penAlpha ) ) );
361  properties.insert( QStringLiteral( "width_border" ), QString::number( penWidth ) );
362  }
363  }
364  QDomNodeList fillColorList = itemElem.elementsByTagName( QStringLiteral( "FillColor" ) );
365  if ( !fillColorList.isEmpty() )
366  {
367  QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
368  bool redOk, greenOk, blueOk, alphaOk;
369  int fillRed, fillGreen, fillBlue, fillAlpha;
370 
371  fillRed = fillColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
372  fillGreen = fillColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
373  fillBlue = fillColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
374  fillAlpha = fillColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
375 
376  if ( redOk && greenOk && blueOk && alphaOk )
377  {
378  properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) ) );
379  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
380  }
381  }
382  if ( itemElem.hasAttribute( QStringLiteral( "transparentFill" ) ) )
383  {
384  //old style (pre 2.0) of specifying that shapes had no fill
385  bool hasOldTransparentFill = itemElem.attribute( QStringLiteral( "transparentFill" ), QStringLiteral( "0" ) ).toInt();
386  if ( hasOldTransparentFill )
387  {
388  properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
389  }
390  }
391 
392  mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
393  }
394  emit itemChanged();
395  return true;
396 }
397 
399 {
400  if ( s == mShape )
401  {
402  return;
403  }
404 
405  mShape = s;
406 
407  if ( mComposition && id().isEmpty() )
408  {
409  //notify the model that the display name has changed
411  }
412 }
413 
415 {
416  mCornerRadius = radius;
417 }
418 
420 {
421  return mCurrentRectangle;
422 }
423 
424 void QgsComposerShape::updateBoundingRect()
425 {
426  QRectF rectangle = rect();
427  rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
428  if ( rectangle != mCurrentRectangle )
429  {
430  prepareGeometryChange();
431  mCurrentRectangle = rectangle;
432  }
433 }
434 
435 void QgsComposerShape::setSceneRect( const QRectF &rectangle )
436 {
437  // Reimplemented from QgsComposerItem as we need to call updateBoundingRect after the shape's size changes
438 
439  //update rect for data defined size and position
440  QRectF evaluatedRect = evalItemRect( rectangle );
441  QgsComposerItem::setSceneRect( evaluatedRect );
442 
443  updateBoundingRect();
444  update();
445 }
446 
448 {
449  if ( !id().isEmpty() )
450  {
451  return id();
452  }
453 
454  switch ( mShape )
455  {
456  case Ellipse:
457  return tr( "<ellipse>" );
458  case Rectangle:
459  return tr( "<rectangle>" );
460  case Triangle:
461  return tr( "<triangle>" );
462  }
463 
464  return tr( "<shape>" );
465 }
void setForceVectorOutput(bool force)
void setShapeType(QgsComposerShape::Shape s)
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.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc)
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:1052
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:341
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:198
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:378
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 ...
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:1710
virtual QgsFillSymbol * clone() const override
Get a deep copy of this symbol.
Definition: qgssymbol.cpp:1809
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.
virtual void setItemRotation(const double r, const bool adjustPosition=false)
Sets the item rotation.
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...
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:399
QString id() const
Get item&#39;s id (which is not necessarly unique)
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.