QGIS API Documentation  2.99.0-Master (b058df7)
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  , mMaxSymbolBleed( 0 )
35 {
36  setFrameEnabled( true );
37  createDefaultShapeStyleSymbol();
38 
39  if ( mComposition )
40  {
41  //connect to atlas feature changes
42  //to update symbol style (in case of data-defined symbology)
44  }
45 }
46 
47 QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition *composition )
48  : QgsComposerItem( x, y, width, height, composition )
49  , mShape( Ellipse )
50  , mCornerRadius( 0 )
51  , mUseSymbol( false ) //default to not using Symbol for shapes, to preserve 2.0 api
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  QgsReadWriteContext context;
286 
287  QDomElement shapeStyleElem = QgsSymbolLayerUtils::saveSymbol( QString(), mShapeStyleSymbol, doc, context );
288  composerShapeElem.appendChild( shapeStyleElem );
289 
290  elem.appendChild( composerShapeElem );
291  return _writeXml( composerShapeElem, doc );
292 }
293 
294 bool QgsComposerShape::readXml( const QDomElement &itemElem, const QDomDocument &doc )
295 {
296  mShape = QgsComposerShape::Shape( itemElem.attribute( QStringLiteral( "shapeType" ), QStringLiteral( "0" ) ).toInt() );
297  mCornerRadius = itemElem.attribute( QStringLiteral( "cornerRadius" ), QStringLiteral( "0" ) ).toDouble();
298 
299  //restore general composer item properties
300  QDomNodeList composerItemList = itemElem.elementsByTagName( QStringLiteral( "ComposerItem" ) );
301  if ( !composerItemList.isEmpty() )
302  {
303  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
304 
305  //rotation
306  if ( !qgsDoubleNear( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble(), 0.0 ) )
307  {
308  //check for old (pre 2.1) rotation attribute
309  setItemRotation( composerItemElem.attribute( QStringLiteral( "rotation" ), QStringLiteral( "0" ) ).toDouble() );
310  }
311 
312  _readXml( composerItemElem, doc );
313  }
314 
315  QgsReadWriteContext context;
317 
318  QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( QStringLiteral( "symbol" ) );
319  if ( !shapeStyleSymbolElem.isNull() )
320  {
321  delete mShapeStyleSymbol;
322  mShapeStyleSymbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( shapeStyleSymbolElem, context );
323  }
324  else
325  {
326  //upgrade project file from 2.0 to use symbol styling
327  delete mShapeStyleSymbol;
328  QgsStringMap properties;
329  properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( brush().color() ) );
330  if ( hasBackground() )
331  {
332  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
333  }
334  else
335  {
336  properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
337  }
338  if ( hasFrame() )
339  {
340  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) );
341  }
342  else
343  {
344  properties.insert( QStringLiteral( "style_border" ), QStringLiteral( "no" ) );
345  }
346  properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( pen().color() ) );
347  properties.insert( QStringLiteral( "width_border" ), QString::number( pen().widthF() ) );
348 
349  //for pre 2.0 projects, shape color and outline were specified in a different element...
350  QDomNodeList outlineColorList = itemElem.elementsByTagName( QStringLiteral( "OutlineColor" ) );
351  if ( !outlineColorList.isEmpty() )
352  {
353  QDomElement frameColorElem = outlineColorList.at( 0 ).toElement();
354  bool redOk, greenOk, blueOk, alphaOk, widthOk;
355  int penRed, penGreen, penBlue, penAlpha;
356  double penWidth;
357 
358  penWidth = itemElem.attribute( QStringLiteral( "outlineWidth" ) ).toDouble( &widthOk );
359  penRed = frameColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
360  penGreen = frameColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
361  penBlue = frameColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
362  penAlpha = frameColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
363 
364  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
365  {
366  properties.insert( QStringLiteral( "color_border" ), QgsSymbolLayerUtils::encodeColor( QColor( penRed, penGreen, penBlue, penAlpha ) ) );
367  properties.insert( QStringLiteral( "width_border" ), QString::number( penWidth ) );
368  }
369  }
370  QDomNodeList fillColorList = itemElem.elementsByTagName( QStringLiteral( "FillColor" ) );
371  if ( !fillColorList.isEmpty() )
372  {
373  QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
374  bool redOk, greenOk, blueOk, alphaOk;
375  int fillRed, fillGreen, fillBlue, fillAlpha;
376 
377  fillRed = fillColorElem.attribute( QStringLiteral( "red" ) ).toDouble( &redOk );
378  fillGreen = fillColorElem.attribute( QStringLiteral( "green" ) ).toDouble( &greenOk );
379  fillBlue = fillColorElem.attribute( QStringLiteral( "blue" ) ).toDouble( &blueOk );
380  fillAlpha = fillColorElem.attribute( QStringLiteral( "alpha" ) ).toDouble( &alphaOk );
381 
382  if ( redOk && greenOk && blueOk && alphaOk )
383  {
384  properties.insert( QStringLiteral( "color" ), QgsSymbolLayerUtils::encodeColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) ) );
385  properties.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) );
386  }
387  }
388  if ( itemElem.hasAttribute( QStringLiteral( "transparentFill" ) ) )
389  {
390  //old style (pre 2.0) of specifying that shapes had no fill
391  bool hasOldTransparentFill = itemElem.attribute( QStringLiteral( "transparentFill" ), QStringLiteral( "0" ) ).toInt();
392  if ( hasOldTransparentFill )
393  {
394  properties.insert( QStringLiteral( "style" ), QStringLiteral( "no" ) );
395  }
396  }
397 
398  mShapeStyleSymbol = QgsFillSymbol::createSimple( properties );
399  }
400  emit itemChanged();
401  return true;
402 }
403 
405 {
406  if ( s == mShape )
407  {
408  return;
409  }
410 
411  mShape = s;
412 
413  if ( mComposition && id().isEmpty() )
414  {
415  //notify the model that the display name has changed
417  }
418 }
419 
421 {
422  mCornerRadius = radius;
423 }
424 
426 {
427  return mCurrentRectangle;
428 }
429 
430 void QgsComposerShape::updateBoundingRect()
431 {
432  QRectF rectangle = rect();
433  rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
434  if ( rectangle != mCurrentRectangle )
435  {
436  prepareGeometryChange();
437  mCurrentRectangle = rectangle;
438  }
439 }
440 
441 void QgsComposerShape::setSceneRect( const QRectF &rectangle )
442 {
443  // Reimplemented from QgsComposerItem as we need to call updateBoundingRect after the shape's size changes
444 
445  //update rect for data defined size and position
446  QRectF evaluatedRect = evalItemRect( rectangle );
447  QgsComposerItem::setSceneRect( evaluatedRect );
448 
449  updateBoundingRect();
450  update();
451 }
452 
454 {
455  if ( !id().isEmpty() )
456  {
457  return id();
458  }
459 
460  switch ( mShape )
461  {
462  case Ellipse:
463  return tr( "<ellipse>" );
464  case Rectangle:
465  return tr( "<rectangle>" );
466  case Triangle:
467  return tr( "<triangle>" );
468  }
469 
470  return tr( "<shape>" );
471 }
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:1084
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:443
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:227
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:383
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:1742
virtual QgsFillSymbol * clone() const override
Get a deep copy of this symbol.
Definition: qgssymbol.cpp:1841
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:405
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.