QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 "qgssymbolv2.h"
21 #include "qgssymbollayerv2utils.h"
22 #include "qgscomposermodel.h"
23 #include <QPainter>
24 
26  : QgsComposerItem( composition )
27  , mShape( Ellipse )
28  , mCornerRadius( 0 )
29  , mUseSymbolV2( false ) //default to not using SymbolV2 for shapes, to preserve 2.0 api
30  , mShapeStyleSymbol( nullptr )
31  , mMaxSymbolBleed( 0 )
32 {
33  setFrameEnabled( true );
34  createDefaultShapeStyleSymbol();
35 
36  if ( mComposition )
37  {
38  //connect to atlas feature changes
39  //to update symbol style (in case of data-defined symbology)
40  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( repaint() ) );
41  }
42 }
43 
44 QgsComposerShape::QgsComposerShape( qreal x, qreal y, qreal width, qreal height, QgsComposition* composition )
45  : QgsComposerItem( x, y, width, height, composition )
46  , mShape( Ellipse )
47  , mCornerRadius( 0 )
48  , mUseSymbolV2( false ) //default to not using SymbolV2 for shapes, to preserve 2.0 api
49  , mShapeStyleSymbol( nullptr )
50  , mMaxSymbolBleed( 0 )
51 {
52  setSceneRect( QRectF( x, y, width, height ) );
53  setFrameEnabled( true );
54  createDefaultShapeStyleSymbol();
55 
56  if ( mComposition )
57  {
58  //connect to atlas feature changes
59  //to update symbol style (in case of data-defined symbology)
60  connect( &mComposition->atlasComposition(), SIGNAL( featureChanged( QgsFeature* ) ), this, SLOT( repaint() ) );
61  }
62 }
63 
65 {
66  delete mShapeStyleSymbol;
67 }
68 
69 void QgsComposerShape::setUseSymbolV2( bool useSymbolV2 )
70 {
71  mUseSymbolV2 = useSymbolV2;
72  setFrameEnabled( !useSymbolV2 );
73 }
74 
76 {
77  delete mShapeStyleSymbol;
78  mShapeStyleSymbol = static_cast<QgsFillSymbolV2*>( symbol->clone() );
79  refreshSymbol();
80 }
81 
83 {
84  mMaxSymbolBleed = QgsSymbolLayerV2Utils::estimateMaxSymbolBleed( mShapeStyleSymbol );
85  updateBoundingRect();
86 
87  update();
88  emit frameChanged();
89 }
90 
91 void QgsComposerShape::createDefaultShapeStyleSymbol()
92 {
93  delete mShapeStyleSymbol;
94  QgsStringMap properties;
95  properties.insert( "color", "white" );
96  properties.insert( "style", "solid" );
97  properties.insert( "style_border", "solid" );
98  properties.insert( "color_border", "black" );
99  properties.insert( "width_border", "0.3" );
100  properties.insert( "joinstyle", "miter" );
101  mShapeStyleSymbol = QgsFillSymbolV2::createSimple( properties );
102 
103  mMaxSymbolBleed = QgsSymbolLayerV2Utils::estimateMaxSymbolBleed( mShapeStyleSymbol );
104  updateBoundingRect();
105 
106  emit frameChanged();
107 }
108 
109 void QgsComposerShape::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
110 {
111  Q_UNUSED( itemStyle );
112  Q_UNUSED( pWidget );
113  if ( !painter )
114  {
115  return;
116  }
117  if ( !shouldDrawItem() )
118  {
119  return;
120  }
121 
122  drawBackground( painter );
123  drawFrame( painter );
124 
125  if ( isSelected() )
126  {
127  drawSelectionBoxes( painter );
128  }
129 }
130 
131 
132 void QgsComposerShape::drawShape( QPainter* p )
133 {
134  if ( mUseSymbolV2 )
135  {
136  drawShapeUsingSymbol( p );
137  return;
138  }
139 
140  //draw using QPainter brush and pen to keep 2.0 api compatibility
141  p->save();
142  p->setRenderHint( QPainter::Antialiasing );
143 
144  switch ( mShape )
145  {
146  case Ellipse:
147  p->drawEllipse( QRectF( 0, 0, rect().width(), rect().height() ) );
148  break;
149  case Rectangle:
150  //if corner radius set, then draw a rounded rectangle
151  if ( mCornerRadius > 0 )
152  {
153  p->drawRoundedRect( QRectF( 0, 0, rect().width(), rect().height() ), mCornerRadius, mCornerRadius );
154  }
155  else
156  {
157  p->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
158  }
159  break;
160  case Triangle:
161  QPolygonF triangle;
162  triangle << QPointF( 0, rect().height() );
163  triangle << QPointF( rect().width(), rect().height() );
164  triangle << QPointF( rect().width() / 2.0, 0 );
165  p->drawPolygon( triangle );
166  break;
167  }
168  p->restore();
169 }
170 
171 void QgsComposerShape::drawShapeUsingSymbol( QPainter* p )
172 {
173  p->save();
174  p->setRenderHint( QPainter::Antialiasing );
175 
176  //setup painter scaling to dots so that raster symbology is drawn to scale
177  double dotsPerMM = p->device()->logicalDpiX() / 25.4;
178 
179  //setup render context
181  //context units should be in dots
182  ms.setOutputDpi( p->device()->logicalDpiX() );
184  context.setPainter( p );
185  context.setForceVectorOutput( true );
186  QgsExpressionContext* expressionContext = createExpressionContext();
187  context.setExpressionContext( *expressionContext );
188  delete expressionContext;
189 
190  p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
191 
192  //generate polygon to draw
193  QList<QPolygonF> rings; //empty list
194  QPolygonF shapePolygon;
195 
196  //shapes with curves must be enlarged before conversion to QPolygonF, or
197  //the curves are approximated too much and appear jaggy
198  QTransform t = QTransform::fromScale( 100, 100 );
199  //inverse transform used to scale created polygons back to expected size
200  QTransform ti = t.inverted();
201 
202  switch ( mShape )
203  {
204  case Ellipse:
205  {
206  //create an ellipse
207  QPainterPath ellipsePath;
208  ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
209  QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
210  shapePolygon = ti.map( ellipsePoly );
211  break;
212  }
213  case Rectangle:
214  {
215  //if corner radius set, then draw a rounded rectangle
216  if ( mCornerRadius > 0 )
217  {
218  QPainterPath roundedRectPath;
219  roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ), mCornerRadius * dotsPerMM, mCornerRadius * dotsPerMM );
220  QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
221  shapePolygon = ti.map( roundedPoly );
222  }
223  else
224  {
225  shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
226  }
227  break;
228  }
229  case Triangle:
230  {
231  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
232  shapePolygon << QPointF( rect().width() * dotsPerMM, rect().height() * dotsPerMM );
233  shapePolygon << QPointF( rect().width() / 2.0 * dotsPerMM, 0 );
234  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
235  break;
236  }
237  }
238 
239  mShapeStyleSymbol->startRender( context );
240  mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, nullptr, context );
241  mShapeStyleSymbol->stopRender( context );
242 
243  p->restore();
244 }
245 
246 
248 {
249  if ( mFrame && p && !mUseSymbolV2 )
250  {
251  p->setPen( pen() );
252  p->setBrush( Qt::NoBrush );
253  p->setRenderHint( QPainter::Antialiasing, true );
254  drawShape( p );
255  }
256 }
257 
259 {
260  if ( p && ( mBackground || mUseSymbolV2 ) )
261  {
262  p->setBrush( brush() );//this causes a problem in atlas generation
263  p->setPen( Qt::NoPen );
264  p->setRenderHint( QPainter::Antialiasing, true );
265  drawShape( p );
266  }
267 }
268 
270 {
271  return mMaxSymbolBleed;
272 }
273 
275 {
276  QDomElement composerShapeElem = doc.createElement( "ComposerShape" );
277  composerShapeElem.setAttribute( "shapeType", mShape );
278  composerShapeElem.setAttribute( "cornerRadius", mCornerRadius );
279 
280  QDomElement shapeStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mShapeStyleSymbol, doc );
281  composerShapeElem.appendChild( shapeStyleElem );
282 
283  elem.appendChild( composerShapeElem );
284  return _writeXML( composerShapeElem, doc );
285 }
286 
287 bool QgsComposerShape::readXML( const QDomElement& itemElem, const QDomDocument& doc )
288 {
289  mShape = QgsComposerShape::Shape( itemElem.attribute( "shapeType", "0" ).toInt() );
290  mCornerRadius = itemElem.attribute( "cornerRadius", "0" ).toDouble();
291 
292  //restore general composer item properties
293  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
294  if ( !composerItemList.isEmpty() )
295  {
296  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
297 
298  //rotation
299  if ( !qgsDoubleNear( composerItemElem.attribute( "rotation", "0" ).toDouble(), 0.0 ) )
300  {
301  //check for old (pre 2.1) rotation attribute
302  setItemRotation( composerItemElem.attribute( "rotation", "0" ).toDouble() );
303  }
304 
305  _readXML( composerItemElem, doc );
306  }
307 
308  QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( "symbol" );
309  if ( !shapeStyleSymbolElem.isNull() )
310  {
311  delete mShapeStyleSymbol;
312  mShapeStyleSymbol = QgsSymbolLayerV2Utils::loadSymbol<QgsFillSymbolV2>( shapeStyleSymbolElem );
313  }
314  else
315  {
316  //upgrade project file from 2.0 to use symbolV2 styling
317  delete mShapeStyleSymbol;
318  QgsStringMap properties;
319  properties.insert( "color", QgsSymbolLayerV2Utils::encodeColor( brush().color() ) );
320  if ( hasBackground() )
321  {
322  properties.insert( "style", "solid" );
323  }
324  else
325  {
326  properties.insert( "style", "no" );
327  }
328  if ( hasFrame() )
329  {
330  properties.insert( "style_border", "solid" );
331  }
332  else
333  {
334  properties.insert( "style_border", "no" );
335  }
336  properties.insert( "color_border", QgsSymbolLayerV2Utils::encodeColor( pen().color() ) );
337  properties.insert( "width_border", QString::number( pen().widthF() ) );
338 
339  //for pre 2.0 projects, shape color and outline were specified in a different element...
340  QDomNodeList outlineColorList = itemElem.elementsByTagName( "OutlineColor" );
341  if ( !outlineColorList.isEmpty() )
342  {
343  QDomElement frameColorElem = outlineColorList.at( 0 ).toElement();
344  bool redOk, greenOk, blueOk, alphaOk, widthOk;
345  int penRed, penGreen, penBlue, penAlpha;
346  double penWidth;
347 
348  penWidth = itemElem.attribute( "outlineWidth" ).toDouble( &widthOk );
349  penRed = frameColorElem.attribute( "red" ).toDouble( &redOk );
350  penGreen = frameColorElem.attribute( "green" ).toDouble( &greenOk );
351  penBlue = frameColorElem.attribute( "blue" ).toDouble( &blueOk );
352  penAlpha = frameColorElem.attribute( "alpha" ).toDouble( &alphaOk );
353 
354  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
355  {
356  properties.insert( "color_border", QgsSymbolLayerV2Utils::encodeColor( QColor( penRed, penGreen, penBlue, penAlpha ) ) );
357  properties.insert( "width_border", QString::number( penWidth ) );
358  }
359  }
360  QDomNodeList fillColorList = itemElem.elementsByTagName( "FillColor" );
361  if ( !fillColorList.isEmpty() )
362  {
363  QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
364  bool redOk, greenOk, blueOk, alphaOk;
365  int fillRed, fillGreen, fillBlue, fillAlpha;
366 
367  fillRed = fillColorElem.attribute( "red" ).toDouble( &redOk );
368  fillGreen = fillColorElem.attribute( "green" ).toDouble( &greenOk );
369  fillBlue = fillColorElem.attribute( "blue" ).toDouble( &blueOk );
370  fillAlpha = fillColorElem.attribute( "alpha" ).toDouble( &alphaOk );
371 
372  if ( redOk && greenOk && blueOk && alphaOk )
373  {
374  properties.insert( "color", QgsSymbolLayerV2Utils::encodeColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) ) );
375  properties.insert( "style", "solid" );
376  }
377  }
378  if ( itemElem.hasAttribute( "transparentFill" ) )
379  {
380  //old style (pre 2.0) of specifying that shapes had no fill
381  bool hasOldTransparentFill = itemElem.attribute( "transparentFill", "0" ).toInt();
382  if ( hasOldTransparentFill )
383  {
384  properties.insert( "style", "no" );
385  }
386  }
387 
388  mShapeStyleSymbol = QgsFillSymbolV2::createSimple( properties );
389  }
390  emit itemChanged();
391  return true;
392 }
393 
395 {
396  if ( s == mShape )
397  {
398  return;
399  }
400 
401  mShape = s;
402 
403  if ( mComposition && id().isEmpty() )
404  {
405  //notify the model that the display name has changed
407  }
408 }
409 
411 {
412  mCornerRadius = radius;
413 }
414 
416 {
417  return mCurrentRectangle;
418 }
419 
420 void QgsComposerShape::updateBoundingRect()
421 {
422  QRectF rectangle = rect();
423  rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
424  if ( rectangle != mCurrentRectangle )
425  {
427  mCurrentRectangle = rectangle;
428  }
429 }
430 
431 void QgsComposerShape::setSceneRect( const QRectF& rectangle )
432 {
433  // Reimplemented from QgsComposerItem as we need to call updateBoundingRect after the shape's size changes
434 
435  //update rect for data defined size and position
436  QRectF evaluatedRect = evalItemRect( rectangle );
437  QgsComposerItem::setSceneRect( evaluatedRect );
438 
439  updateBoundingRect();
440  update();
441 }
442 
444 {
445  if ( !id().isEmpty() )
446  {
447  return id();
448  }
449 
450  switch ( mShape )
451  {
452  case Ellipse:
453  return tr( "<ellipse>" );
454  case Rectangle:
455  return tr( "<rectangle>" );
456  case Triangle:
457  return tr( "<triangle>" );
458  }
459 
460  return tr( "<shape>" );
461 }
void addEllipse(const QRectF &boundingRectangle)
void setShapeStyleSymbol(QgsFillSymbolV2 *symbol)
Sets the QgsFillSymbolV2 used to draw the shape.
void setForceVectorOutput(bool force)
void setShapeType(QgsComposerShape::Shape s)
QDomNodeList elementsByTagName(const QString &tagname) const
void setSceneRect(const QRectF &rectangle) override
Sets new scene rectangle bounds and recalculates hight and extent.
QTransform fromScale(qreal sx, qreal sy)
qreal x() const
qreal y() const
void renderPolygon(const QPolygonF &points, QList< QPolygonF > *rings, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
virtual QString displayName() const override
Get item display name.
bool shouldDrawItem() const
Returns whether the item should be drawn in the current context.
void setRenderHint(RenderHint hint, bool on)
QDomNode appendChild(const QDomNode &newChild)
QString attribute(const QString &name, const QString &defValue) const
QgsComposerModel * itemsModel()
Returns the items model attached to the composition.
void itemChanged()
Emitted when the item changes.
QPoint map(const QPoint &point) const
static QString encodeColor(const QColor &color)
void scale(qreal sx, qreal sy)
static QgsFillSymbolV2 * createSimple(const QgsStringMap &properties)
Create a fill symbol with one symbol layer: SimpleFill with specified properties. ...
A item that forms part of a map composition.
void addRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
void setOutputDpi(double dpi)
Set DPI used for conversion between real world units (e.g. mm) and pixels.
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...
static QDomElement saveSymbol(const QString &symbolName, QgsSymbolV2 *symbol, QDomDocument &doc)
void save()
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule)
virtual void setFrameEnabled(const bool drawFrame)
Set whether this item has a frame drawn around it or not.
QPolygonF toFillPolygon(const QMatrix &matrix) const
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
bool _writeXML(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document.
double toDouble(bool *ok) const
virtual QgsExpressionContext * createExpressionContext() const override
Creates an expression context relating to the item&#39;s current state.
QString tr(const char *sourceText, const char *disambiguation, int n)
void adjust(qreal dx1, qreal dy1, qreal dx2, qreal dy2)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
virtual void drawBackground(QPainter *p) override
Draw background.
void update(const QRectF &rect)
void updateItemDisplayName(QgsComposerItem *item)
Must be called when an item&#39;s display name is modified.
QTransform inverted(bool *invertible) const
The QgsMapSettings class contains configuration for rendering of the map.
bool _readXML(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document.
QDomElement toElement() const
bool isEmpty() const
void drawRect(const QRectF &rectangle)
void frameChanged()
Emitted if the item&#39;s frame style changes.
QString number(int n, int base)
void startRender(QgsRenderContext &context, const QgsFields *fields=nullptr)
bool hasAttribute(const QString &name) const
virtual void drawSelectionBoxes(QPainter *p)
Draws additional graphics on selected items.
void setCornerRadius(double radius)
Sets radius for rounded rectangle corners.
void setUseSymbolV2(bool useSymbolV2)
Controls whether the shape should be drawn using a QgsFillSymbolV2.
void setPen(const QColor &color)
bool mFrame
True if item fram needs to be painted.
void drawEllipse(const QRectF &rectangle)
void setAttribute(const QString &name, const QString &value)
bool isSelected() const
void drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
int toInt(bool *ok, int base) const
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...
QPaintDevice * device() const
void setBrush(const QBrush &brush)
void setPainter(QPainter *p)
bool hasBackground() const
Whether this item has a Background or not.
void repaint() override
static double estimateMaxSymbolBleed(QgsSymbolV2 *symbol)
Returns the maximum estimated bleed for the symbol.
void prepareGeometryChange()
Graphics scene for map printing.
const QgsMapSettings & mapSettings() const
Return setting of QGIS map canvas.
int logicalDpiX() const
bool readXML(const QDomElement &itemElem, const QDomDocument &doc) override
Sets state from Dom document.
void refreshSymbol()
Should be called after the shape&#39;s symbol is changed.
virtual QgsFillSymbolV2 * clone() const override
bool isNull() const
void restore()
bool writeXML(QDomElement &elem, QDomDocument &doc) const override
Stores state in Dom element.
QgsComposition * mComposition
Contains information about the context of a rendering operation.
void stopRender(QgsRenderContext &context)
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.
static QgsRenderContext fromMapSettings(const QgsMapSettings &mapSettings)
create initialized QgsRenderContext instance from given QgsMapSettings
QDomElement firstChildElement(const QString &tagName) const
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()
iterator insert(const Key &key, const T &value)
bool hasFrame() const
Whether this item has a frame or not.
QDomElement createElement(const QString &tagName)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
bool mBackground
True if item background needs to be painted.
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.
QDomNode at(int index) const
QRectF rect() const