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