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