QGIS API Documentation  2.5.0-Master
 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 : 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 "qgscomposerarrow.h"
19 #include "qgscomposition.h"
20 #include "qgscomposerutils.h"
21 #include <QPainter>
22 #include <QSvgRenderer>
23 #include <QVector2D>
24 
25 #include <cmath>
26 
28  : QgsComposerItem( c )
29  , mStartPoint( 0, 0 )
30  , mStopPoint( 0, 0 )
31  , mStartXIdx( 0 )
32  , mStartYIdx( 0 )
33  , mMarkerMode( DefaultMarker )
34  , mArrowColor( QColor( 0, 0, 0 ) )
35  , mBoundsBehaviour( 24 )
36 {
38 }
39 
40 QgsComposerArrow::QgsComposerArrow( const QPointF& startPoint, const QPointF& stopPoint, QgsComposition* c )
41  : QgsComposerItem( c )
42  , mStartPoint( startPoint )
43  , mStopPoint( stopPoint )
44  , mMarkerMode( DefaultMarker )
45  , mArrowColor( QColor( 0, 0, 0 ) )
46  , mBoundsBehaviour( 24 )
47 {
48  mStartXIdx = mStopPoint.x() < mStartPoint.x();
49  mStartYIdx = mStopPoint.y() < mStartPoint.y();
52 }
53 
55 {
56 
57 }
58 
60 {
61  setArrowHeadWidth( 4 );
62  mPen.setColor( QColor( 0, 0, 0 ) );
63  mPen.setWidthF( 1 );
64 
65  //default to no background
66  setBackgroundEnabled( false );
67 }
68 
69 void QgsComposerArrow::paint( QPainter* painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget )
70 {
71  Q_UNUSED( itemStyle );
72  Q_UNUSED( pWidget );
73  if ( !painter )
74  {
75  return;
76  }
77 
78  drawBackground( painter );
79 
80  painter->save();
81  //antialiasing on
82  painter->setRenderHint( QPainter::Antialiasing, true );
83 
84  //draw arrow
85  QPen arrowPen = mPen;
86  if ( mBoundsBehaviour == 22 )
87  {
88  //if arrow was created in versions prior to 2.4, use the old rendering style
89  arrowPen.setCapStyle( Qt::FlatCap );
90  }
91  arrowPen.setColor( mArrowColor );
92  painter->setPen( arrowPen );
93  painter->setBrush( QBrush( mArrowColor ) );
94  painter->drawLine( QPointF( mStartPoint.x() - pos().x(), mStartPoint.y() - pos().y() ), QPointF( mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y() ) );
95 
96  if ( mMarkerMode == DefaultMarker )
97  {
98  drawHardcodedMarker( painter, EndMarker );
99  }
100  else if ( mMarkerMode == SVGMarker )
101  {
104  }
105 
106  painter->restore();
107 
108  drawFrame( painter );
109  if ( isSelected() )
110  {
111  drawSelectionBoxes( painter );
112  }
113 }
114 
115 void QgsComposerArrow::setSceneRect( const QRectF& rectangle )
116 {
117  //update rect for data defined size and position
118  QRectF evaluatedRect = evalItemRect( rectangle );
119 
120  if ( evaluatedRect.width() < 0 )
121  {
122  mStartXIdx = 1 - mStartXIdx;
123  }
124  if ( evaluatedRect.height() < 0 )
125  {
126  mStartYIdx = 1 - mStartYIdx;
127  }
128 
129  double margin = computeMarkerMargin();
130 
131  // Ensure the rectangle is at least as large as needed to include the markers
132  QRectF rect = rectangle.united( QRectF( evaluatedRect.x(), evaluatedRect.y(), 2. * margin, 2. * margin ) );
133 
134  // Compute new start and stop positions
135  double x[2] = {rect.x(), rect.x() + rect.width()};
136  double y[2] = {rect.y(), rect.y() + rect.height()};
137 
138  double xsign = x[mStartXIdx] < x[1 - mStartXIdx] ? 1.0 : -1.0;
139  double ysign = y[mStartYIdx] < y[1 - mStartYIdx] ? 1.0 : -1.0;
140 
141  mStartPoint = QPointF( x[mStartXIdx] + xsign * margin, y[mStartYIdx] + ysign * margin );
142  mStopPoint = QPointF( x[1 - mStartXIdx] - xsign * margin, y[1 - mStartYIdx] - ysign * margin );
143 
145 }
146 
148 {
149  Q_UNUSED( type );
150  QBrush arrowBrush = p->brush();
151  arrowBrush.setColor( mArrowColor );
152  p->setBrush( arrowBrush );
153  if ( mBoundsBehaviour == 22 )
154  {
155  //if arrow was created in versions prior to 2.4, use the old rendering style
157  }
158  else
159  {
160  QVector2D dir = QVector2D( mStopPoint - mStartPoint ).normalized();
161  QPointF stop = mStopPoint + ( dir * 0.5 * mArrowHeadWidth ).toPointF();
162  QgsComposerUtils::drawArrowHead( p, stop.x() - pos().x(), stop.y() - pos().y(), QgsComposerUtils::angle( mStartPoint, stop ), mArrowHeadWidth );
163  }
164 }
165 
166 void QgsComposerArrow::drawSVGMarker( QPainter* p, MarkerType type, const QString &markerPath )
167 {
168  Q_UNUSED( markerPath );
170 
171  double arrowHeadHeight;
172  if ( type == StartMarker )
173  {
174  arrowHeadHeight = mStartArrowHeadHeight;
175  }
176  else
177  {
178  arrowHeadHeight = mStopArrowHeadHeight;
179  }
180 
181  //prepare paint device
182  int dpi = ( p->device()->logicalDpiX() + p->device()->logicalDpiY() ) / 2;
183  double viewScaleFactor = horizontalViewScaleFactor();
184  int imageWidth = mArrowHeadWidth / 25.4 * dpi;
185  int imageHeight = arrowHeadHeight / 25.4 * dpi;
186 
187  //make nicer preview
189  {
190  imageWidth *= qMin( viewScaleFactor, 10.0 );
191  imageHeight *= qMin( viewScaleFactor, 10.0 );
192  }
193  QImage markerImage( imageWidth, imageHeight, QImage::Format_ARGB32 );
194  QColor markerBG( 255, 255, 255, 0 ); //transparent white background
195  markerImage.fill( markerBG.rgba() );
196 
197  QPointF imageFixPoint;
198  imageFixPoint.setX( mArrowHeadWidth / 2.0 );
199  QPointF canvasPoint;
200  if ( type == StartMarker )
201  {
202  canvasPoint = QPointF( mStartPoint.x() - pos().x(), mStartPoint.y() - pos().y() );
203  imageFixPoint.setY( mStartArrowHeadHeight );
204  }
205  else //end marker
206  {
207  canvasPoint = QPointF( mStopPoint.x() - pos().x(), mStopPoint.y() - pos().y() );
208  imageFixPoint.setY( 0 );
209  }
210 
211  //rasterize svg
212  QSvgRenderer r;
213  if ( type == StartMarker )
214  {
215  if ( mStartMarkerFile.isEmpty() || !r.load( mStartMarkerFile ) )
216  {
217  return;
218  }
219  }
220  else //end marker
221  {
222  if ( mEndMarkerFile.isEmpty() || !r.load( mEndMarkerFile ) )
223  {
224  return;
225  }
226  }
227 
228  QPainter imagePainter( &markerImage );
229  r.render( &imagePainter );
230 
231  p->save();
232  if ( mBoundsBehaviour == 22 )
233  {
234  //if arrow was created in versions prior to 2.4, use the old rendering style
235  //rotate image fix point for backtransform
236  QPointF fixPoint;
237  if ( type == StartMarker )
238  {
239  fixPoint.setX( 0 ); fixPoint.setY( arrowHeadHeight / 2.0 );
240  }
241  else
242  {
243  fixPoint.setX( 0 ); fixPoint.setY( -arrowHeadHeight / 2.0 );
244  }
245  QPointF rotatedFixPoint;
246  double angleRad = ang / 180 * M_PI;
247  rotatedFixPoint.setX( fixPoint.x() * cos( angleRad ) + fixPoint.y() * -sin( angleRad ) );
248  rotatedFixPoint.setY( fixPoint.x() * sin( angleRad ) + fixPoint.y() * cos( angleRad ) );
249  p->translate( canvasPoint.x() - rotatedFixPoint.x() , canvasPoint.y() - rotatedFixPoint.y() );
250  }
251  else
252  {
253  p->translate( canvasPoint.x() , canvasPoint.y() );
254  }
255 
256  p->rotate( ang );
257  p->translate( -mArrowHeadWidth / 2.0, -arrowHeadHeight / 2.0 );
258 
259  p->drawImage( QRectF( 0, 0, mArrowHeadWidth, arrowHeadHeight ), markerImage, QRectF( 0, 0, imageWidth, imageHeight ) );
260  p->restore();
261 
262  return;
263 }
264 
265 void QgsComposerArrow::setStartMarker( const QString& svgPath )
266 {
267  QSvgRenderer r;
268  if ( svgPath.isEmpty() || !r.load( svgPath ) )
269  {
270  return;
271  // mStartArrowHeadHeight = 0;
272  }
273  mStartMarkerFile = svgPath;
274 
275  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
276  QRect viewBox = r.viewBox();
277  mStartArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
279 }
280 
281 void QgsComposerArrow::setEndMarker( const QString& svgPath )
282 {
283  QSvgRenderer r;
284  if ( svgPath.isEmpty() || !r.load( svgPath ) )
285  {
286  return;
287  // mStopArrowHeadHeight = 0;
288  }
289  mEndMarkerFile = svgPath;
290 
291  //calculate mArrowHeadHeight from svg file and mArrowHeadWidth
292  QRect viewBox = r.viewBox();
293  mStopArrowHeadHeight = mArrowHeadWidth / viewBox.width() * viewBox.height();
295 }
296 
298 {
299  mPen.setWidthF( width );
301 }
302 
304 {
305  mArrowHeadWidth = width;
309 }
310 
312 {
313  double margin = 0;
314 
315  if ( mBoundsBehaviour == 22 )
316  {
317  //if arrow was created in versions prior to 2.4, use the old rendering style
318  if ( mMarkerMode == DefaultMarker )
319  {
320  margin = mPen.widthF() / 2.0 + mArrowHeadWidth / 2.0;
321  }
322  else if ( mMarkerMode == NoMarker )
323  {
324  margin = mPen.widthF() / 2.0;
325  }
326  else if ( mMarkerMode == SVGMarker )
327  {
328  double maxArrowHeight = qMax( mStartArrowHeadHeight, mStopArrowHeadHeight );
329  margin = mPen.widthF() / 2 + qMax( mArrowHeadWidth / 2.0, maxArrowHeight / 2.0 );
330  }
331  }
332  else
333  {
334  if ( mMarkerMode == DefaultMarker )
335  {
336  margin = mPen.widthF() / std::sqrt( 2.0 ) + mArrowHeadWidth / 2.0;
337  }
338  else if ( mMarkerMode == NoMarker )
339  {
340  margin = mPen.widthF() / std::sqrt( 2.0 );
341  }
342  else if ( mMarkerMode == SVGMarker )
343  {
344  double startMarkerMargin = std::sqrt( 0.25 * ( mStartArrowHeadHeight * mStartArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
345  double stopMarkerMargin = std::sqrt( 0.25 * ( mStopArrowHeadHeight * mStopArrowHeadHeight + mArrowHeadWidth * mArrowHeadWidth ) );
346  double markerMargin = qMax( startMarkerMargin, stopMarkerMargin );
347  margin = qMax( mPen.widthF() / std::sqrt( 2.0 ), markerMargin );
348  }
349  }
350  return margin;
351 }
352 
354 {
355  //rectangle containing start and end point
356  QRectF rect = QRectF( qMin( mStartPoint.x(), mStopPoint.x() ), qMin( mStartPoint.y(), mStopPoint.y() ),
357  qAbs( mStopPoint.x() - mStartPoint.x() ), qAbs( mStopPoint.y() - mStartPoint.y() ) );
358  double enlarge = computeMarkerMargin();
359  rect.adjust( -enlarge, -enlarge, enlarge, enlarge );
361 }
362 
364 {
365  mMarkerMode = mode;
367 }
368 
369 bool QgsComposerArrow::writeXML( QDomElement& elem, QDomDocument & doc ) const
370 {
371  QDomElement composerArrowElem = doc.createElement( "ComposerArrow" );
372  composerArrowElem.setAttribute( "outlineWidth", QString::number( outlineWidth() ) );
373  composerArrowElem.setAttribute( "arrowHeadWidth", QString::number( mArrowHeadWidth ) );
374  composerArrowElem.setAttribute( "markerMode", mMarkerMode );
375  composerArrowElem.setAttribute( "startMarkerFile", mStartMarkerFile );
376  composerArrowElem.setAttribute( "endMarkerFile", mEndMarkerFile );
377  composerArrowElem.setAttribute( "boundsBehaviourVersion", QString::number( mBoundsBehaviour ) );
378 
379  //arrow color
380  QDomElement arrowColorElem = doc.createElement( "ArrowColor" );
381  arrowColorElem.setAttribute( "red", mArrowColor.red() );
382  arrowColorElem.setAttribute( "green", mArrowColor.green() );
383  arrowColorElem.setAttribute( "blue", mArrowColor.blue() );
384  arrowColorElem.setAttribute( "alpha", mArrowColor.alpha() );
385  composerArrowElem.appendChild( arrowColorElem );
386 
387  //start point
388  QDomElement startPointElem = doc.createElement( "StartPoint" );
389  startPointElem.setAttribute( "x", QString::number( mStartPoint.x() ) );
390  startPointElem.setAttribute( "y", QString::number( mStartPoint.y() ) );
391  composerArrowElem.appendChild( startPointElem );
392 
393  //stop point
394  QDomElement stopPointElem = doc.createElement( "StopPoint" );
395  stopPointElem.setAttribute( "x", QString::number( mStopPoint.x() ) );
396  stopPointElem.setAttribute( "y", QString::number( mStopPoint.y() ) );
397  composerArrowElem.appendChild( stopPointElem );
398 
399  elem.appendChild( composerArrowElem );
400  return _writeXML( composerArrowElem, doc );
401 }
402 
403 bool QgsComposerArrow::readXML( const QDomElement& itemElem, const QDomDocument& doc )
404 {
405  mArrowHeadWidth = itemElem.attribute( "arrowHeadWidth", "2.0" ).toDouble();
406  mPen.setWidthF( itemElem.attribute( "outlineWidth", "1.0" ).toDouble() );
407  setStartMarker( itemElem.attribute( "startMarkerFile", "" ) );
408  setEndMarker( itemElem.attribute( "endMarkerFile", "" ) );
409  mMarkerMode = QgsComposerArrow::MarkerMode( itemElem.attribute( "markerMode", "0" ).toInt() );
410  //if bounds behaviour version is not set, default to 2.2 behaviour
411  mBoundsBehaviour = itemElem.attribute( "boundsBehaviourVersion", "22" ).toInt();
412 
413  //arrow color
414  QDomNodeList arrowColorList = itemElem.elementsByTagName( "ArrowColor" );
415  if ( arrowColorList.size() > 0 )
416  {
417  QDomElement arrowColorElem = arrowColorList.at( 0 ).toElement();
418  int red = arrowColorElem.attribute( "red", "0" ).toInt();
419  int green = arrowColorElem.attribute( "green", "0" ).toInt();
420  int blue = arrowColorElem.attribute( "blue", "0" ).toInt();
421  int alpha = arrowColorElem.attribute( "alpha", "255" ).toInt();
422  mArrowColor = QColor( red, green, blue, alpha );
423  }
424 
425  //restore general composer item properties
426  //needs to be before start point / stop point because setSceneRect()
427  QDomNodeList composerItemList = itemElem.elementsByTagName( "ComposerItem" );
428  if ( composerItemList.size() > 0 )
429  {
430  QDomElement composerItemElem = composerItemList.at( 0 ).toElement();
431  _readXML( composerItemElem, doc );
432  }
433 
434  //start point
435  QDomNodeList startPointList = itemElem.elementsByTagName( "StartPoint" );
436  if ( startPointList.size() > 0 )
437  {
438  QDomElement startPointElem = startPointList.at( 0 ).toElement();
439  mStartPoint.setX( startPointElem.attribute( "x", "0.0" ).toDouble() );
440  mStartPoint.setY( startPointElem.attribute( "y", "0.0" ).toDouble() );
441  }
442 
443  //stop point
444  QDomNodeList stopPointList = itemElem.elementsByTagName( "StopPoint" );
445  if ( stopPointList.size() > 0 )
446  {
447  QDomElement stopPointElem = stopPointList.at( 0 ).toElement();
448  mStopPoint.setX( stopPointElem.attribute( "x", "0.0" ).toDouble() );
449  mStopPoint.setY( stopPointElem.attribute( "y", "0.0" ).toDouble() );
450  }
451 
452  mStartXIdx = mStopPoint.x() < mStartPoint.x();
453  mStartYIdx = mStopPoint.y() < mStartPoint.y();
454 
456  emit itemChanged();
457  return true;
458 }
459 
460 
QgsComposerArrow(QgsComposition *c)
int mStartXIdx
Considering the rectangle as spanning [x[0], x[1]] x [y[0], y[1]], these indices specify which index ...
void setOutlineWidth(double width)
void itemChanged()
Emitted when the item changes.
A item that forms part of a map composition.
void setStartMarker(const QString &svgPath)
virtual void drawFrame(QPainter *p)
Draw black frame around item.
QString mStartMarkerFile
Path to the start marker file.
void setArrowHeadWidth(double width)
Sets the width of the arrow head in mm.
void adaptItemSceneRect()
Adapts the item scene rect to contain the start point, the stop point including the arrow marker and ...
double outlineWidth() const
bool _readXML(const QDomElement &itemElem, const QDomDocument &doc)
Reads parameter that are not subclass specific in document.
static void drawArrowHead(QPainter *p, const double x, const double y, const double angle, const double arrowHeadWidth)
Draws an arrow head on to a QPainter.
double horizontalViewScaleFactor() const
Returns the zoom factor of the graphics view.
MarkerMode mMarkerMode
Default marker, no marker or svg marker.
virtual void drawSelectionBoxes(QPainter *p)
Draw selection boxes around item.
double computeMarkerMargin() const
Computes the margin around the line necessary to include the markers.
void drawSVGMarker(QPainter *p, MarkerType type, const QString &markerPath)
Draws a user-defined marker (must be an svg file)
#define M_PI
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget)
Reimplementation of QCanvasItem::paint - draw on canvas.
bool readXML(const QDomElement &itemElem, const QDomDocument &doc)
sets state from Dom document
void drawHardcodedMarker(QPainter *p, MarkerType type)
Draws the default marker at the line end.
void setMarkerMode(MarkerMode mode)
void setBackgroundEnabled(const bool drawBackground)
Set whether this item has a Background drawn around it or not.
int mBoundsBehaviour
Indicates QGIS version to mimic bounding box behaviour for.
QRectF evalItemRect(const QRectF &newRect)
Update an item rect to consider data defined position and size of item.
Graphics scene for map printing.
void setSceneRect(const QRectF &rectangle)
Modifies position of start and endpoint and calls QgsComposerItem::setSceneRect.
QgsComposition * mComposition
double mArrowHeadWidth
Width of the arrow marker in mm.
bool _writeXML(QDomElement &itemElem, QDomDocument &doc) const
Writes parameter that are not subclass specific in document.
QString mEndMarkerFile
Path to the end marker file.
virtual void drawBackground(QPainter *p)
Draw background.
void initGraphicsSettings()
Apply default graphics settings.
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...
static double angle(const QPointF &p1, const QPointF &p2)
Calculates the angle of the line from p1 to p2 (counter clockwise, starting from a line from north to...
QgsComposition::PlotStyle plotStyle() const
double mStartArrowHeadHeight
Height of the arrow marker in mm.
void setEndMarker(const QString &svgPath)
bool writeXML(QDomElement &elem, QDomDocument &doc) const
stores state in Dom element