QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposerarrow.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposerarrow.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 "qgscomposerarrow.h"
19 #include "qgscomposition.h"
20 #include "qgscomposerutils.h"
21 #include "qgssymbollayerv2utils.h"
22 #include <QPainter>
23 #include <QSvgRenderer>
24 #include <QVector2D>
25 
26 #include <cmath>
27 
29  : QgsComposerItem( c )
30  , mStartPoint( 0, 0 )
31  , mStopPoint( 0, 0 )
32  , mStartXIdx( 0 )
33  , mStartYIdx( 0 )
34  , mMarkerMode( DefaultMarker )
35  , mArrowHeadOutlineWidth( 1.0 )
36  , mArrowHeadOutlineColor( Qt::black )
37  , mArrowHeadFillColor( Qt::black )
38  , mBoundsBehaviour( 24 )
39  , mLineSymbol( 0 )
40 {
41  init();
42 }
43 
44 QgsComposerArrow::QgsComposerArrow( const QPointF& startPoint, const QPointF& stopPoint, QgsComposition* c )
45  : QgsComposerItem( c )
46  , mStartPoint( startPoint )
47  , mStopPoint( stopPoint )
48  , mMarkerMode( DefaultMarker )
49  , mArrowHeadOutlineWidth( 1.0 )
50  , mArrowHeadOutlineColor( Qt::black )
51  , mArrowHeadFillColor( Qt::black )
52  , mBoundsBehaviour( 24 )
53  , mLineSymbol( 0 )
54 {
55  mStartXIdx = mStopPoint.x() < mStartPoint.x();
56  mStartYIdx = mStopPoint.y() < mStartPoint.y();
57  init();
58  adaptItemSceneRect();
59 }
60 
62 {
63  delete mLineSymbol;
64 }
65 
66 void QgsComposerArrow::init()
67 {
68  setArrowHeadWidth( 4 );
69  mPen.setColor( mArrowHeadOutlineColor );
70  mPen.setWidthF( 1 );
71  mBrush.setColor( mArrowHeadFillColor );
72  createDefaultLineSymbol();
73 
74  //default to no background
75  setBackgroundEnabled( false );
76 }
77 
78 
79 void QgsComposerArrow::createDefaultLineSymbol()
80 {
81  delete mLineSymbol;
82  QgsStringMap properties;
83  properties.insert( "color", "0,0,0,255" );
84  properties.insert( "width", "1" );
85  properties.insert( "capstyle", "square" );
86  mLineSymbol = QgsLineSymbolV2::createSimple( properties );
87 }
88 
89 void QgsComposerArrow::paint( QPainter* painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
90 {
91  Q_UNUSED( itemStyle );
92  Q_UNUSED( pWidget );
93  if ( !painter || !painter->device() )
94  {
95  return;
96  }
97  if ( !shouldDrawItem() )
98  {
99  return;
100  }
101 
102  drawBackground( painter );
103 
104  painter->save();
105  //antialiasing on
106  painter->setRenderHint( QPainter::Antialiasing, true );
107 
108  //draw line section
109  drawLine( painter );
110 
111  //draw arrowhead if required
112  if ( mMarkerMode != NoMarker )
113  {
114  painter->setBrush( mBrush );
115  painter->setPen( mPen );
116 
117  if ( mMarkerMode == DefaultMarker )
118  {
119  drawHardcodedMarker( painter, EndMarker );
120  }
121  else if ( mMarkerMode == SVGMarker )
122  {
123  drawSVGMarker( painter, StartMarker, mStartMarkerFile );
124  drawSVGMarker( painter, EndMarker, mEndMarkerFile );
125  }
126  }
127 
128  painter->restore();
129 
130  drawFrame( painter );
131  if ( isSelected() )
132  {
133  drawSelectionBoxes( painter );
134  }
135 }
136 
137 void QgsComposerArrow::setSceneRect( const QRectF& rectangle )
138 {
139  //update rect for data defined size and position
140  QRectF evaluatedRect = evalItemRect( rectangle );
141 
142  if ( evaluatedRect.width() < 0 )
143  {
144  mStartXIdx = 1 - mStartXIdx;
145  }
146  if ( evaluatedRect.height() < 0 )
147  {
148  mStartYIdx = 1 - mStartYIdx;
149  }
150 
151  double margin = computeMarkerMargin();
152 
153  // Ensure the rectangle is at least as large as needed to include the markers
154  QRectF rect = rectangle.united( QRectF( evaluatedRect.x(), evaluatedRect.y(), 2. * margin, 2. * margin ) );
155 
156  // Compute new start and stop positions
157  double x[2] = {rect.x(), rect.x() + rect.width()};
158  double y[2] = {rect.y(), rect.y() + rect.height()};
159 
160  double xsign = x[mStartXIdx] < x[1 - mStartXIdx] ? 1.0 : -1.0;
161  double ysign = y[mStartYIdx] < y[1 - mStartYIdx] ? 1.0 : -1.0;
162 
163  mStartPoint = QPointF( x[mStartXIdx] + xsign * margin, y[mStartYIdx] + ysign * margin );
164  mStopPoint = QPointF( x[1 - mStartXIdx] - xsign * margin, y[1 - mStartYIdx] - ysign * margin );
165 
167 }
168 
169 void QgsComposerArrow::drawLine( QPainter *painter )
170 {
171  if ( ! mLineSymbol || ! mComposition )
172  {
173  return;
174  }
175 
176  QPaintDevice* thePaintDevice = painter->device();
177  painter->save();
178  //setup painter scaling to dots so that raster symbology is drawn to scale
179  double dotsPerMM = thePaintDevice->logicalDpiX() / 25.4;
180  painter->scale( 1 / dotsPerMM, 1 / dotsPerMM ); //scale painter from mm to dots
181 
182  //setup render context
184  //context units should be in dots
185  ms.setOutputDpi( painter->device()->logicalDpiX() );
187  context.setForceVectorOutput( true );
188  context.setPainter( painter );
189 
190  //line scaled to dots
191  QPolygonF line;
192  line << QPointF( mStartPoint.x() - pos().x(), mStartPoint.y() - pos().y() ) * dotsPerMM
193  << QPointF( mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y() ) * dotsPerMM;
194 
195  mLineSymbol->startRender( context );
196  mLineSymbol->renderPolyline( line, 0, context );
197  mLineSymbol->stopRender( context );
198  painter->restore();
199 
200 }
201 
202 void QgsComposerArrow::drawHardcodedMarker( QPainter *p, MarkerType type )
203 {
204  Q_UNUSED( type );
205  if ( mBoundsBehaviour == 22 )
206  {
207  //if arrow was created in versions prior to 2.4, use the old rendering style
208  QgsComposerUtils::drawArrowHead( p, mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y(), QgsComposerUtils::angle( mStartPoint, mStopPoint ), mArrowHeadWidth );
209  }
210  else
211  {
212  QVector2D dir = QVector2D( mStopPoint - mStartPoint ).normalized();
213  QPointF stop = mStopPoint + ( dir * 0.5 * mArrowHeadWidth ).toPointF();
214  QgsComposerUtils::drawArrowHead( p, stop.x() - pos().x(), stop.y() - pos().y(), QgsComposerUtils::angle( mStartPoint, stop ), mArrowHeadWidth );
215  }
216 }
217 
218 void QgsComposerArrow::drawSVGMarker( QPainter* p, MarkerType type, const QString &markerPath )
219 {
220  Q_UNUSED( markerPath );
221  double ang = QgsComposerUtils::angle( mStartPoint, mStopPoint );
222 
223  double arrowHeadHeight;
224  if ( type == StartMarker )
225  {
226  arrowHeadHeight = mStartArrowHeadHeight;
227  }
228  else
229  {
230  arrowHeadHeight = mStopArrowHeadHeight;
231  }
232  if ( mArrowHeadWidth <= 0 || arrowHeadHeight <= 0 )
233  {
234  //bad image size
235  return;
236  }
237 
238  QPointF imageFixPoint;
239  imageFixPoint.setX( mArrowHeadWidth / 2.0 );
240  QPointF canvasPoint;
241  if ( type == StartMarker )
242  {
243  canvasPoint = QPointF( mStartPoint.x() - pos().x(), mStartPoint.y() - pos().y() );
244  imageFixPoint.setY( mStartArrowHeadHeight );
245  }
246  else //end marker
247  {
248  canvasPoint = QPointF( mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y() );
249  imageFixPoint.setY( 0 );
250  }
251 
252  //rasterize svg
253  QSvgRenderer r;
254  if ( type == StartMarker )
255  {
256  if ( mStartMarkerFile.isEmpty() || !r.load( mStartMarkerFile ) )
257  {
258  return;
259  }
260  }
261  else //end marker
262  {
263  if ( mEndMarkerFile.isEmpty() || !r.load( mEndMarkerFile ) )
264  {
265  return;
266  }
267  }
268 
269  p->save();
270  p->setRenderHint( QPainter::Antialiasing );
271  if ( mBoundsBehaviour == 22 )
272  {
273  //if arrow was created in versions prior to 2.4, use the old rendering style
274  //rotate image fix point for backtransform
275  QPointF fixPoint;
276  if ( type == StartMarker )
277  {
278  fixPoint.setX( 0 ); fixPoint.setY( arrowHeadHeight / 2.0 );
279  }
280  else
281  {
282  fixPoint.setX( 0 ); fixPoint.setY( -arrowHeadHeight / 2.0 );
283  }
284  QPointF rotatedFixPoint;
285  double angleRad = ang / 180 * M_PI;
286  rotatedFixPoint.setX( fixPoint.x() * cos( angleRad ) + fixPoint.y() * -sin( angleRad ) );
287  rotatedFixPoint.setY( fixPoint.x() * sin( angleRad ) + fixPoint.y() * cos( angleRad ) );
288  p->translate( canvasPoint.x() - rotatedFixPoint.x(), canvasPoint.y() - rotatedFixPoint.y() );
289  }
290  else
291  {
292  p->translate( canvasPoint.x(), canvasPoint.y() );
293  }
294 
295  p->rotate( ang );
296  p->translate( -mArrowHeadWidth / 2.0, -arrowHeadHeight / 2.0 );
297  r.render( p, QRectF( 0, 0, mArrowHeadWidth, arrowHeadHeight ) );
298  p->restore();
299 
300  return;
301 }
302 
303 void QgsComposerArrow::setStartMarker( const QString& svgPath )
304 {
305  QSvgRenderer r;
306  mStartMarkerFile = svgPath;
307  if ( svgPath.isEmpty() || !r.load( svgPath ) )
308  {
309  mStartArrowHeadHeight = 0;
310  }
311  else
312  {
313  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
314  QRect viewBox = r.viewBox();
315  mStartArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
316  }
317  adaptItemSceneRect();
318 }
319 
320 void QgsComposerArrow::setEndMarker( const QString& svgPath )
321 {
322  QSvgRenderer r;
323  mEndMarkerFile = svgPath;
324  if ( svgPath.isEmpty() || !r.load( svgPath ) )
325  {
326  mStopArrowHeadHeight = 0;
327  }
328  else
329  {
330  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
331  QRect viewBox = r.viewBox();
332  mStopArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
333  }
334  adaptItemSceneRect();
335 }
336 
338 {
339  if ( mLineSymbol )
340  {
341  return mLineSymbol->color();
342  }
343 
344  return Qt::black;
345 }
346 
347 void QgsComposerArrow::setArrowColor( const QColor &c )
348 {
349  if ( mLineSymbol )
350  {
351  mLineSymbol->setColor( c );
352  }
353  mArrowHeadOutlineColor = c;
354  mArrowHeadFillColor = c;
355  mPen.setColor( c );
356  mBrush.setColor( c );
357 }
358 
360 {
361  mArrowHeadOutlineColor = color;
362  mPen.setColor( color );
363 }
364 
365 void QgsComposerArrow::setArrowHeadFillColor( const QColor &color )
366 {
367  mArrowHeadFillColor = color;
368  mBrush.setColor( color );
369 }
370 
372 {
373  if ( mLineSymbol )
374  {
375  mLineSymbol->setWidth( width );
376  }
377  mArrowHeadOutlineWidth = width;
378  mPen.setWidthF( mArrowHeadOutlineWidth );
379 
380  adaptItemSceneRect();
381 }
382 
384 {
385  if ( mLineSymbol )
386  {
387  return mLineSymbol->width();
388  }
389 
390  return 0;
391 }
392 
394 {
395  mArrowHeadOutlineWidth = width;
396  mPen.setWidthF( mArrowHeadOutlineWidth );
397 
398  adaptItemSceneRect();
399 }
400 
402 {
403  delete mLineSymbol;
404  mLineSymbol = symbol;
405 }
406 
408 {
409  mArrowHeadWidth = width;
410  setStartMarker( mStartMarkerFile );
411  setEndMarker( mEndMarkerFile );
412  adaptItemSceneRect();
413 }
414 
415 double QgsComposerArrow::computeMarkerMargin() const
416 {
417  double margin = 0;
418 
419  if ( mBoundsBehaviour == 22 )
420  {
421  //if arrow was created in versions prior to 2.4, use the old rendering style
422  if ( mMarkerMode == DefaultMarker )
423  {
424  margin = mPen.widthF() / 2.0 + mArrowHeadWidth / 2.0;
425  }
426  else if ( mMarkerMode == NoMarker )
427  {
428  margin = mPen.widthF() / 2.0;
429  }
430  else if ( mMarkerMode == SVGMarker )
431  {
432  double maxArrowHeight = qMax( mStartArrowHeadHeight, mStopArrowHeadHeight );
433  margin = mPen.widthF() / 2 + qMax( mArrowHeadWidth / 2.0, maxArrowHeight / 2.0 );
434  }
435  }
436  else
437  {
438  if ( mMarkerMode == DefaultMarker )
439  {
440  margin = mPen.widthF() / std::sqrt( 2.0 ) + mArrowHeadWidth / 2.0;
441  }
442  else if ( mMarkerMode == NoMarker )
443  {
444  margin = mPen.widthF() / std::sqrt( 2.0 );
445  }
446  else if ( mMarkerMode == SVGMarker )
447  {
448  double startMarkerMargin = std::sqrt( 0.25 * ( mStartArrowHeadHeight * mStartArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
449  double stopMarkerMargin = std::sqrt( 0.25 * ( mStopArrowHeadHeight * mStopArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
450  double markerMargin = qMax( startMarkerMargin, stopMarkerMargin );
451  margin = qMax( mPen.widthF() / std::sqrt( 2.0 ), markerMargin );
452  }
453  }
454  return margin;
455 }
456 
457 void QgsComposerArrow::adaptItemSceneRect()
458 {
459  //rectangle containing start and end point
460  QRectF rect = QRectF( qMin( mStartPoint.x(), mStopPoint.x() ), qMin( mStartPoint.y(), mStopPoint.y() ),
461  qAbs( mStopPoint.x() - mStartPoint.x() ), qAbs( mStopPoint.y() - mStartPoint.y() ) );
462  double enlarge = computeMarkerMargin();
463  rect.adjust( -enlarge, -enlarge, enlarge, enlarge );
465 }
466 
468 {
469  mMarkerMode = mode;
470  adaptItemSceneRect();
471 }
472 
473 bool QgsComposerArrow::writeXML( QDomElement& elem, QDomDocument & doc ) const
474 {
475  QDomElement composerArrowElem = doc.createElement( "ComposerArrow" );
476  composerArrowElem.setAttribute( "arrowHeadWidth", QString::number( mArrowHeadWidth ) );
477  composerArrowElem.setAttribute( "arrowHeadFillColor", QgsSymbolLayerV2Utils::encodeColor( mArrowHeadFillColor ) );
478  composerArrowElem.setAttribute( "arrowHeadOutlineColor", QgsSymbolLayerV2Utils::encodeColor( mArrowHeadOutlineColor ) );
479  composerArrowElem.setAttribute( "outlineWidth", QString::number( mArrowHeadOutlineWidth ) );
480  composerArrowElem.setAttribute( "markerMode", mMarkerMode );
481  composerArrowElem.setAttribute( "startMarkerFile", mStartMarkerFile );
482  composerArrowElem.setAttribute( "endMarkerFile", mEndMarkerFile );
483  composerArrowElem.setAttribute( "boundsBehaviourVersion", QString::number( mBoundsBehaviour ) );
484 
485  QDomElement styleElem = doc.createElement( "lineStyle" );
486  QDomElement lineStyleElem = QgsSymbolLayerV2Utils::saveSymbol( QString(), mLineSymbol, doc );
487  styleElem.appendChild( lineStyleElem );
488  composerArrowElem.appendChild( styleElem );
489 
490  //start point
491  QDomElement startPointElem = doc.createElement( "StartPoint" );
492  startPointElem.setAttribute( "x", QString::number( mStartPoint.x() ) );
493  startPointElem.setAttribute( "y", QString::number( mStartPoint.y() ) );
494  composerArrowElem.appendChild( startPointElem );
495 
496  //stop point
497  QDomElement stopPointElem = doc.createElement( "StopPoint" );
498  stopPointElem.setAttribute( "x", QString::number( mStopPoint.x() ) );
499  stopPointElem.setAttribute( "y", QString::number( mStopPoint.y() ) );
500  composerArrowElem.appendChild( stopPointElem );
501 
502  elem.appendChild( composerArrowElem );
503  return _writeXML( composerArrowElem, doc );
504 }
505 
506 bool QgsComposerArrow::readXML( const QDomElement& itemElem, const QDomDocument& doc )
507 {
508  mArrowHeadWidth = itemElem.attribute( "arrowHeadWidth", "2.0" ).toDouble();
509  mArrowHeadFillColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "arrowHeadFillColor", "0,0,0,255" ) );
510  mArrowHeadOutlineColor = QgsSymbolLayerV2Utils::decodeColor( itemElem.attribute( "arrowHeadOutlineColor", "0,0,0,255" ) );
511  mArrowHeadOutlineWidth = itemElem.attribute( "outlineWidth", "1.0" ).toDouble();
512  setStartMarker( itemElem.attribute( "startMarkerFile", "" ) );
513  setEndMarker( itemElem.attribute( "endMarkerFile", "" ) );
514  mMarkerMode = QgsComposerArrow::MarkerMode( itemElem.attribute( "markerMode", "0" ).toInt() );
515  //if bounds behaviour version is not set, default to 2.2 behaviour
516  mBoundsBehaviour = itemElem.attribute( "boundsBehaviourVersion", "22" ).toInt();
517 
518  //arrow style
519  QDomElement styleElem = itemElem.firstChildElement( "lineStyle" );
520  if ( !styleElem.isNull() )
521  {
522  QDomElement lineStyleElem = styleElem.firstChildElement( "symbol" );
523  if ( !lineStyleElem.isNull() )
524  {
525  delete mLineSymbol;
526  mLineSymbol = QgsSymbolLayerV2Utils::loadSymbol<QgsLineSymbolV2>( lineStyleElem );
527  }
528  }
529  else
530  {
531  //old project file, read arrow width and color
532  delete mLineSymbol;
533 
534  QgsStringMap properties;
535  properties.insert( "width", itemElem.attribute( "outlineWidth", "1.0" ) );
536 
537  if ( mBoundsBehaviour == 22 )
538  {
539  //if arrow was created in versions prior to 2.4, use the old rendering style
540  properties.insert( "capstyle", "flat" );
541  }
542  else
543  {
544  properties.insert( "capstyle", "square" );
545  }
546  int red = 0;
547  int blue = 0;
548  int green = 0;
549  int alpha = 255;
550 
551  QDomNodeList arrowColorList = itemElem.elementsByTagName( "ArrowColor" );
552  if ( arrowColorList.size() > 0 )
553  {
554  QDomElement arrowColorElem = arrowColorList.at( 0 ).toElement();
555  red = arrowColorElem.attribute( "red", "0" ).toInt();
556  green = arrowColorElem.attribute( "green", "0" ).toInt();
557  blue = arrowColorElem.attribute( "blue", "0" ).toInt();
558  alpha = arrowColorElem.attribute( "alpha", "255" ).toInt();
559  mArrowHeadFillColor = QColor( red, green, blue, alpha );
560  mArrowHeadOutlineColor = QColor( red, green, blue, alpha );
561  }
562  properties.insert( "color", QString( "%1,%2,%3,%4" ).arg( red ).arg( green ).arg( blue ).arg( alpha ) );
563  mLineSymbol = QgsLineSymbolV2::createSimple( properties );
564  }
565 
566  mPen.setColor( mArrowHeadOutlineColor );
567  mPen.setWidthF( mArrowHeadOutlineWidth );
568  mBrush.setColor( mArrowHeadFillColor );
569 
570  //restore general composer item properties
571  //needs to be before start point / stop point because setSceneRect()
572  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
573  if ( composerItemList.size() > 0 )
574  {
575  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
576  _readXML( composerItemElem, doc );
577  }
578 
579  //start point
580  QDomNodeList startPointList = itemElem.elementsByTagName( "StartPoint" );
581  if ( startPointList.size() > 0 )
582  {
583  QDomElement startPointElem = startPointList.at( 0 ).toElement();
584  mStartPoint.setX( startPointElem.attribute( "x", "0.0" ).toDouble() );
585  mStartPoint.setY( startPointElem.attribute( "y", "0.0" ).toDouble() );
586  }
587 
588  //stop point
589  QDomNodeList stopPointList = itemElem.elementsByTagName( "StopPoint" );
590  if ( stopPointList.size() > 0 )
591  {
592  QDomElement stopPointElem = stopPointList.at( 0 ).toElement();
593  mStopPoint.setX( stopPointElem.attribute( "x", "0.0" ).toDouble() );
594  mStopPoint.setY( stopPointElem.attribute( "y", "0.0" ).toDouble() );
595  }
596 
597  mStartXIdx = mStopPoint.x() < mStartPoint.x();
598  mStartYIdx = mStopPoint.y() < mStartPoint.y();
599 
600  adaptItemSceneRect();
601  emit itemChanged();
602  return true;
603 }
604 
605