QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups 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 : [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( 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 = QgsSymbolLayerV2Utils::loadSymbol<QgsFillSymbolV2>( 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 }