QGIS API Documentation  2.7.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 "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( 0 ),
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( 0 ),
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 = symbol;
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  p->scale( 1 / dotsPerMM, 1 / dotsPerMM ); // scale painter from mm to dots
186 
187  //generate polygon to draw
188  QList<QPolygonF> rings; //empty list
189  QPolygonF shapePolygon;
190 
191  //shapes with curves must be enlarged before conversion to QPolygonF, or
192  //the curves are approximated too much and appear jaggy
193  QTransform t = QTransform::fromScale( 100, 100 );
194  //inverse transform used to scale created polygons back to expected size
195  QTransform ti = t.inverted();
196 
197  switch ( mShape )
198  {
199  case Ellipse:
200  {
201  //create an ellipse
202  QPainterPath ellipsePath;
203  ellipsePath.addEllipse( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
204  QPolygonF ellipsePoly = ellipsePath.toFillPolygon( t );
205  shapePolygon = ti.map( ellipsePoly );
206  break;
207  }
208  case Rectangle:
209  {
210  //if corner radius set, then draw a rounded rectangle
211  if ( mCornerRadius > 0 )
212  {
213  QPainterPath roundedRectPath;
214  roundedRectPath.addRoundedRect( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ), mCornerRadius * dotsPerMM, mCornerRadius * dotsPerMM );
215  QPolygonF roundedPoly = roundedRectPath.toFillPolygon( t );
216  shapePolygon = ti.map( roundedPoly );
217  }
218  else
219  {
220  shapePolygon = QPolygonF( QRectF( 0, 0, rect().width() * dotsPerMM, rect().height() * dotsPerMM ) );
221  }
222  break;
223  }
224  case Triangle:
225  {
226  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
227  shapePolygon << QPointF( rect().width() * dotsPerMM, rect().height() * dotsPerMM );
228  shapePolygon << QPointF( rect().width() / 2.0 * dotsPerMM, 0 );
229  shapePolygon << QPointF( 0, rect().height() * dotsPerMM );
230  break;
231  }
232  }
233 
234  mShapeStyleSymbol->startRender( context );
235 
236  //need to render using atlas feature properties?
238  {
239  //using an atlas, so render using current atlas feature
240  //since there may be data defined symbols using atlas feature properties
241  mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, mComposition->atlasComposition().currentFeature(), context );
242  }
243  else
244  {
245  mShapeStyleSymbol->renderPolygon( shapePolygon, &rings, 0, context );
246  }
247 
248  mShapeStyleSymbol->stopRender( context );
249  p->restore();
250 }
251 
252 
253 void QgsComposerShape::drawFrame( QPainter* p )
254 {
255  if ( mFrame && p && !mUseSymbolV2 )
256  {
257  p->setPen( pen() );
258  p->setBrush( Qt::NoBrush );
259  p->setRenderHint( QPainter::Antialiasing, true );
260  drawShape( p );
261  }
262 }
263 
265 {
266  if ( p && ( mBackground || mUseSymbolV2 ) )
267  {
268  p->setBrush( brush() );//this causes a problem in atlas generation
269  p->setPen( Qt::NoPen );
270  p->setRenderHint( QPainter::Antialiasing, true );
271  drawShape( p );
272  }
273 }
274 
276 {
277  return mMaxSymbolBleed;
278 }
279 
280 bool QgsComposerShape::writeXML( QDomElement& elem, QDomDocument & doc ) const
281 {
282  QDomElement composerShapeElem = doc.createElement( "ComposerShape" );
283  composerShapeElem.setAttribute( "shapeType", mShape );
284  composerShapeElem.setAttribute( "cornerRadius", mCornerRadius );
285 
286  QDomElement shapeStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mShapeStyleSymbol, doc );
287  composerShapeElem.appendChild( shapeStyleElem );
288 
289  elem.appendChild( composerShapeElem );
290  return _writeXML( composerShapeElem, doc );
291 }
292 
293 bool QgsComposerShape::readXML( const QDomElement& itemElem, const QDomDocument& doc )
294 {
295  mShape = QgsComposerShape::Shape( itemElem.attribute( "shapeType", "0" ).toInt() );
296  mCornerRadius = itemElem.attribute( "cornerRadius", "0" ).toDouble();
297 
298  //restore general composer item properties
299  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
300  if ( composerItemList.size() > 0 )
301  {
302  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
303 
304  //rotation
305  if ( composerItemElem.attribute( "rotation", "0" ).toDouble() != 0 )
306  {
307  //check for old (pre 2.1) rotation attribute
308  setItemRotation( composerItemElem.attribute( "rotation", "0" ).toDouble() );
309  }
310 
311  _readXML( composerItemElem, doc );
312  }
313 
314  QDomElement shapeStyleSymbolElem = itemElem.firstChildElement( "symbol" );
315  if ( !shapeStyleSymbolElem.isNull() )
316  {
317  delete mShapeStyleSymbol;
318  mShapeStyleSymbol = dynamic_cast<QgsFillSymbolV2*>( QgsSymbolLayerV2Utils::loadSymbol( shapeStyleSymbolElem ) );
319  }
320  else
321  {
322  //upgrade project file from 2.0 to use symbolV2 styling
323  delete mShapeStyleSymbol;
324  QgsStringMap properties;
325  properties.insert( "color", QgsSymbolLayerV2Utils::encodeColor( brush().color() ) );
326  if ( hasBackground() )
327  {
328  properties.insert( "style", "solid" );
329  }
330  else
331  {
332  properties.insert( "style", "no" );
333  }
334  if ( hasFrame() )
335  {
336  properties.insert( "style_border", "solid" );
337  }
338  else
339  {
340  properties.insert( "style_border", "no" );
341  }
342  properties.insert( "color_border", QgsSymbolLayerV2Utils::encodeColor( pen().color() ) );
343  properties.insert( "width_border", QString::number( pen().widthF() ) );
344 
345  //for pre 2.0 projects, shape color and outline were specified in a different element...
346  QDomNodeList outlineColorList = itemElem.elementsByTagName( "OutlineColor" );
347  if ( outlineColorList.size() > 0 )
348  {
349  QDomElement frameColorElem = outlineColorList.at( 0 ).toElement();
350  bool redOk, greenOk, blueOk, alphaOk, widthOk;
351  int penRed, penGreen, penBlue, penAlpha;
352  double penWidth;
353 
354  penWidth = itemElem.attribute( "outlineWidth" ).toDouble( &widthOk );
355  penRed = frameColorElem.attribute( "red" ).toDouble( &redOk );
356  penGreen = frameColorElem.attribute( "green" ).toDouble( &greenOk );
357  penBlue = frameColorElem.attribute( "blue" ).toDouble( &blueOk );
358  penAlpha = frameColorElem.attribute( "alpha" ).toDouble( &alphaOk );
359 
360  if ( redOk && greenOk && blueOk && alphaOk && widthOk )
361  {
362  properties.insert( "color_border", QgsSymbolLayerV2Utils::encodeColor( QColor( penRed, penGreen, penBlue, penAlpha ) ) );
363  properties.insert( "width_border", QString::number( penWidth ) );
364  }
365  }
366  QDomNodeList fillColorList = itemElem.elementsByTagName( "FillColor" );
367  if ( fillColorList.size() > 0 )
368  {
369  QDomElement fillColorElem = fillColorList.at( 0 ).toElement();
370  bool redOk, greenOk, blueOk, alphaOk;
371  int fillRed, fillGreen, fillBlue, fillAlpha;
372 
373  fillRed = fillColorElem.attribute( "red" ).toDouble( &redOk );
374  fillGreen = fillColorElem.attribute( "green" ).toDouble( &greenOk );
375  fillBlue = fillColorElem.attribute( "blue" ).toDouble( &blueOk );
376  fillAlpha = fillColorElem.attribute( "alpha" ).toDouble( &alphaOk );
377 
378  if ( redOk && greenOk && blueOk && alphaOk )
379  {
380  properties.insert( "color", QgsSymbolLayerV2Utils::encodeColor( QColor( fillRed, fillGreen, fillBlue, fillAlpha ) ) );
381  properties.insert( "style", "solid" );
382  }
383  }
384  if ( itemElem.hasAttribute( "transparentFill" ) )
385  {
386  //old style (pre 2.0) of specifying that shapes had no fill
387  bool hasOldTransparentFill = itemElem.attribute( "transparentFill", "0" ).toInt();
388  if ( hasOldTransparentFill )
389  {
390  properties.insert( "style", "no" );
391  }
392  }
393 
394  mShapeStyleSymbol = QgsFillSymbolV2::createSimple( properties );
395  }
396  emit itemChanged();
397  return true;
398 }
399 
401 {
402  if ( s == mShape )
403  {
404  return;
405  }
406 
407  mShape = s;
408 
409  if ( mComposition && id().isEmpty() )
410  {
411  //notify the model that the display name has changed
413  }
414 }
415 
417 {
418  mCornerRadius = radius;
419 }
420 
422 {
423  return mCurrentRectangle;
424 }
425 
426 void QgsComposerShape::updateBoundingRect()
427 {
428  QRectF rectangle = rect();
429  rectangle.adjust( -mMaxSymbolBleed, -mMaxSymbolBleed, mMaxSymbolBleed, mMaxSymbolBleed );
430  if ( rectangle != mCurrentRectangle )
431  {
432  prepareGeometryChange();
433  mCurrentRectangle = rectangle;
434  }
435 }
436 
437 void QgsComposerShape::setSceneRect( const QRectF& rectangle )
438 {
439  // Reimplemented from QgsComposerItem as we need to call updateBoundingRect after the shape's size changes
440 
441  //update rect for data defined size and position
442  QRectF evaluatedRect = evalItemRect( rectangle );
443  QgsComposerItem::setSceneRect( evaluatedRect );
444 
445  updateBoundingRect();
446  update();
447 }
448 
450 {
451  if ( !id().isEmpty() )
452  {
453  return id();
454  }
455 
456  switch ( mShape )
457  {
458  case Ellipse:
459  return tr( "<ellipse>" );
460  break;
461  case Rectangle:
462  return tr( "<rectangle>" );
463  break;
464  case Triangle:
465  return tr( "<triangle>" );
466  break;
467  }
468 
469  return tr( "<shape>" );
470 }
void setShapeStyleSymbol(QgsFillSymbolV2 *symbol)
Sets the QgsFillSymbolV2 used to draw the shape.
void setForceVectorOutput(bool force)
void setShapeType(QgsComposerShape::Shape s)
QgsComposition::AtlasMode atlasMode() const
Returns the current atlas mode of the composition.
void renderPolygon(const QPolygonF &points, QList< QPolygonF > *rings, const QgsFeature *f, QgsRenderContext &context, int layer=-1, bool selected=false)
virtual void drawFrame(QPainter *p)
Draw black frame around item.
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.
const QgsMapSettings & mapSettings() const
Return setting of QGIS map canvas.
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.
bool enabled() const
Returns whether the atlas generation is enabled.
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:113
bool writeXML(QDomElement &elem, QDomDocument &doc) const
stores state in Dom element
QMap< QString, QString > QgsStringMap
Definition: qgis.h:412
void updateItemDisplayName(QgsComposerItem *item)
Must be called when an item's display name is modified.
static QString encodeColor(QColor color)
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.
static QDomElement saveSymbol(QString symbolName, QgsSymbolV2 *symbol, QDomDocument &doc)
void frameChanged()
Emitted if the item's frame style changes.
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget)
Reimplementation of QCanvasItem::paint - draw on canvas.
void startRender(QgsRenderContext &context, const QgsFields *fields=0)
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.
bool mFrame
True if item fram needs to be painted.
void setPainter(QPainter *p)
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.
virtual double estimatedFrameBleed() const
reimplement estimatedFrameBleed, since frames on shapes are drawn using symbology rather than the ite...
virtual QString displayName() const
Get item display name.
Graphics scene for map printing.
QgsFeature * currentFeature()
Returns the current atlas feature.
void refreshSymbol()
Should be called after the shape's symbol is changed.
QgsComposition * mComposition
QRectF evalItemRect(const QRectF &newRect, const bool resizeOnly=false)
Evaluates an item's bounding rect to consider data defined position and size of item and reference po...
Contains information about the context of a rendering operation.
void stopRender(QgsRenderContext &context)
QRectF boundingRect() const
Depending on the symbol style, the bounding rectangle can be larger than the shape.
bool _writeXML(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document.
virtual void drawBackground(QPainter *p)
Draw background.
bool readXML(const QDomElement &itemElem, const QDomDocument &doc)
sets state from Dom 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.
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.
QgsAtlasComposition & atlasComposition()
static QgsSymbolV2 * loadSymbol(QDomElement &element)
bool mBackground
True if item background needs to be painted.
QgsComposerShape(QgsComposition *composition)
void setSceneRect(const QRectF &rectangle)
Sets new scene rectangle bounds and recalculates hight and extent.
#define tr(sourceText)
QString id() const
Get item's id (which is not necessarly unique)